Skip to content

Commit 9e3072f

Browse files
delvedorszabosteve
andauthored
Added Elasticsearch proxy example (#1398)
Co-authored-by: István Zoltán Szabó <[email protected]>
1 parent 2494f08 commit 9e3072f

12 files changed

+614
-2
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ npm install @elastic/elasticsearch@<major>
7171
#### Browser
7272

7373
WARNING: There is no official support for the browser environment. It exposes your Elasticsearch instance to everyone, which could lead to security issues.
74-
We recommend that you write a lightweight proxy that uses this client instead.
74+
We recommend that you write a lightweight proxy that uses this client instead, you can see a proxy example [here](./docs/examples/proxy).
7575

7676
## Documentation
7777

docs/examples/proxy/.gitignore

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
6+
# Runtime data
7+
pids
8+
*.pid
9+
*.seed
10+
11+
# Directory for instrumented libs generated by jscoverage/JSCover
12+
lib-cov
13+
14+
# Coverage directory used by tools like istanbul
15+
coverage
16+
17+
# coverage output
18+
coverage.lcov
19+
20+
# nyc test coverage
21+
.nyc_output
22+
23+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24+
.grunt
25+
26+
# node-waf configuration
27+
.lock-wscript
28+
29+
# Compiled binary addons (http://nodejs.org/api/addons.html)
30+
build/Release
31+
32+
# Dependency directories
33+
node_modules
34+
jspm_packages
35+
36+
# Optional npm cache directory
37+
.npm
38+
39+
# Optional REPL history
40+
.node_repl_history
41+
42+
# mac files
43+
.DS_Store
44+
45+
# vim swap files
46+
*.swp
47+
48+
#Jetbrains editor folder
49+
.idea
50+
51+
.vercel

docs/examples/proxy/README.md

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Elasticsearch proxy example
2+
3+
This folder contains an example of how to build a lightweight proxy
4+
between your frontend code and Elasticsearch if you don't
5+
have a more sophisticated backend in place yet.
6+
7+
> **IMPORTANT:** This is not a production ready code and it is only for demonstration purposes,
8+
> we make no guarantees on it's security and stability.
9+
10+
This project is designed to be deployed on [Vercel](https://vercel.com/), a cloud platform
11+
for static sites and Serverless Functions. You can use other functions providers,
12+
such as [Google Cloud functions](https://cloud.google.com/functions).
13+
14+
## Project structure
15+
16+
The project comes with four endpoints:
17+
18+
- `/api/search`: runs a search, requires `'read'` permission
19+
- `/api/autocomplete`: runs an autocomplete suggestion, requires `'read'` permission
20+
- `/api/index`: indexes or updates a document, requires `'write'` permission
21+
- `/api/delete`: deletes a document, requires `'write'` permission
22+
23+
Inside `utils/authorize.js` you can find the authorization logic for the endpoints.
24+
In each endpoint you should configure the `INDEX` variable.
25+
26+
## How to use
27+
28+
Create an account on Vercel, then create a deployment on Elastic Cloud. If you
29+
don't have an account on Elastic Cloud, you can create one with a free 14-day trial
30+
of the [Elasticsearch Service](https://www.elastic.co/elasticsearch/service).
31+
32+
### Configure Elasticsearch
33+
34+
Once you have created a deployment on Elastic Cloud copy the generated Cloud Id and the credentials.
35+
Then open `utils/prepare-elasticsearch.js` and fill your credentials. The script generates
36+
an [Api Key](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html)
37+
that you can use for authenticating your request. Based on the configuration of the Api Key, you will be able
38+
to perform different operation on the specified indices or index pattern.
39+
40+
### Configure Vercel
41+
42+
Install the [Vercel CLI](https://vercel.com/docs/cli) to bootstrap the project,
43+
or read the [quickstart](https://vercel.com/docs) documentation.
44+
45+
If you are using the CLI, bootstrap the project by running `vercel`. Test the project locally
46+
with `vercel dev`, and deploy it with `vercel deploy`.
47+
Configure the `ELASTIC_CLOUD_ID` [environment varible](https://vercel.com/docs/environment-variables) as well.
48+
The Api Key is passed from the frontend app via a `Authorization` header as `Bearer` token and is
49+
used to authorize the API calls to the endpoints as well.
50+
Additional configuration, such as CORS, can be added to [`vercel.json`](https://vercel.com/docs/configuration).
51+
52+
## Authentication
53+
54+
If you are using Elasticsearch only for search purposes, such as a search box, you can create
55+
an Api Key with `read` permissions and store it in your frontend app. Then you can send it
56+
via `Authorization` header to the proxy and run your searches.
57+
58+
If you need to ingest data as well, it's more secure to have a strong authentication in your application.
59+
For such cases, use an external authentication service, such as [Auth0](https://auth0.com/)
60+
or [Magic Link](https://magic.link/). Then create a different Api Key with `read` and `write`
61+
permissions for authenticated users, that will not be stored in the frontend app.
62+
63+
## License
64+
65+
This software is licensed under the [Apache 2 license](../../LICENSE).
+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
// IMPORTANT: this is not a production ready code & purely for demonstration purposes,
21+
// we make no guarantees on it's security and stability
22+
23+
// NOTE: to make this endpoint work, you should create an ApiKey with 'read' permissions
24+
25+
'use strict'
26+
27+
const { Client } = require('@elastic/elasticsearch')
28+
const authorize = require('../utils/authorize')
29+
30+
const INDEX = '<index-name>'
31+
const client = new Client({
32+
cloud: {
33+
id: process.env.ELASTIC_CLOUD_ID
34+
}
35+
})
36+
37+
module.exports = async (req, res) => {
38+
const [err, token] = authorize(req)
39+
if (err) {
40+
res.status(401)
41+
res.json(err)
42+
return
43+
}
44+
45+
if (typeof req.query.q !== 'string') {
46+
res.status(400)
47+
res.json({
48+
error: 'Bad Request',
49+
message: 'Missing parameter "query.q"',
50+
statusCode: 400
51+
})
52+
return
53+
}
54+
55+
if (req.query.q.length < 3) {
56+
res.status(400)
57+
res.json({
58+
error: 'Bad Request',
59+
message: 'The length of "query.q" should be at least 3',
60+
statusCode: 400
61+
})
62+
return
63+
}
64+
65+
try {
66+
const response = await client.search({
67+
index: INDEX,
68+
// You could directly send from the browser
69+
// the Elasticsearch's query DSL, but it will
70+
// expose you to the risk that a malicious user
71+
// could overload your cluster by crafting
72+
// expensive queries.
73+
body: {
74+
_source: ['id', 'url', 'name'], // the fields you want to show in the autocompletion
75+
size: 0,
76+
// https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
77+
suggest: {
78+
suggestions: {
79+
prefix: req.query.q,
80+
completion: {
81+
field: 'suggest',
82+
size: 5
83+
}
84+
}
85+
}
86+
}
87+
}, {
88+
headers: {
89+
Authorization: `ApiKey ${token}`
90+
}
91+
})
92+
93+
// It might be useful to configure http control caching headers
94+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
95+
// res.setHeader('stale-while-revalidate', '30')
96+
res.json(response.body)
97+
} catch (err) {
98+
res.status(err.statusCode || 500)
99+
res.json({
100+
error: err.name,
101+
message: err.message,
102+
statusCode: err.statusCode || 500
103+
})
104+
}
105+
}

docs/examples/proxy/api/delete.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
// IMPORTANT: this is not a production ready code & purely for demonstration purposes,
21+
// we make no guarantees on it's security and stability
22+
23+
// NOTE: to make this endpoint work, you should create an ApiKey with 'write' permissions
24+
25+
'use strict'
26+
27+
const { Client } = require('@elastic/elasticsearch')
28+
const authorize = require('../utils/authorize')
29+
30+
const INDEX = '<index-name>'
31+
const client = new Client({
32+
cloud: {
33+
id: process.env.ELASTIC_CLOUD_ID
34+
}
35+
})
36+
37+
module.exports = async (req, res) => {
38+
const [err, token] = authorize(req)
39+
if (err) {
40+
res.status(401)
41+
res.json(err)
42+
return
43+
}
44+
45+
if (typeof req.query.id !== 'string' && req.query.id.length === 0) {
46+
res.status(400)
47+
res.json({
48+
error: 'Bad Request',
49+
message: 'Missing document id',
50+
statusCode: 400
51+
})
52+
return
53+
}
54+
55+
try {
56+
const response = await client.delete({
57+
index: INDEX,
58+
id: req.query.id
59+
}, {
60+
headers: {
61+
Authorization: `ApiKey ${token}`
62+
}
63+
})
64+
65+
res.json(response.body)
66+
} catch (err) {
67+
res.status(err.statusCode || 500)
68+
res.json({
69+
error: err.name,
70+
message: err.message,
71+
statusCode: err.statusCode || 500
72+
})
73+
}
74+
}

docs/examples/proxy/api/index.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
// IMPORTANT: this is not a production ready code & purely for demonstration purposes,
21+
// we make no guarantees on it's security and stability
22+
23+
// NOTE: to make this endpoint work, you should create an ApiKey with 'write' permissions
24+
25+
'use strict'
26+
27+
const { Client } = require('@elastic/elasticsearch')
28+
const authorize = require('../utils/authorize')
29+
30+
const INDEX = '<index-name>'
31+
const client = new Client({
32+
cloud: {
33+
id: process.env.ELASTIC_CLOUD_ID
34+
}
35+
})
36+
37+
module.exports = async (req, res) => {
38+
const [err, token] = authorize(req)
39+
if (err) {
40+
res.status(401)
41+
res.json(err)
42+
return
43+
}
44+
45+
if (typeof req.body !== 'object') {
46+
res.status(400)
47+
res.json({
48+
error: 'Bad Request',
49+
message: 'The document should be an object',
50+
statusCode: 400
51+
})
52+
return
53+
}
54+
55+
try {
56+
const response = await client.index({
57+
index: INDEX,
58+
id: req.query.id,
59+
body: req.body
60+
}, {
61+
headers: {
62+
Authorization: `ApiKey ${token}`
63+
}
64+
})
65+
66+
res.status(response.statusCode)
67+
res.json(response.body)
68+
} catch (err) {
69+
res.status(err.statusCode || 500)
70+
res.json({
71+
error: err.name,
72+
message: err.message,
73+
statusCode: err.statusCode || 500
74+
})
75+
}
76+
}

0 commit comments

Comments
 (0)