Skip to content

ApiKey should take precedence over basic auth #1115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/authentication.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const client = new Client({
You can provide your credentials by passing the `username` and `password`
parameters via the `auth` option.

NOTE: If you provide both basic authentication credentials and the Api Key configuration, the Api Key will take precedence.

[source,js]
----
const { Client } = require('@elastic/elasticsearch')
Expand Down Expand Up @@ -74,6 +76,8 @@ authentication by passing the `apiKey` parameter via the `auth` option. The
values that you can obtain from the
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/security-api-create-api-key.html[create api key endpoint].

NOTE: If you provide both basic authentication credentials and the Api Key configuration, the Api Key will take precedence.

[source,js]
----
const { Client } = require('@elastic/elasticsearch')
Expand Down
6 changes: 2 additions & 4 deletions lib/Connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,16 +294,14 @@ function resolve (host, path) {

function prepareHeaders (headers = {}, auth) {
if (auth != null && headers.authorization == null) {
if (auth.username && auth.password) {
headers.authorization = 'Basic ' + Buffer.from(`${auth.username}:${auth.password}`).toString('base64')
}

if (auth.apiKey) {
if (typeof auth.apiKey === 'object') {
headers.authorization = 'ApiKey ' + Buffer.from(`${auth.apiKey.id}:${auth.apiKey.api_key}`).toString('base64')
} else {
headers.authorization = `ApiKey ${auth.apiKey}`
}
} else if (auth.username && auth.password) {
headers.authorization = 'Basic ' + Buffer.from(`${auth.username}:${auth.password}`).toString('base64')
}
}
return headers
Expand Down
6 changes: 3 additions & 3 deletions lib/pool/BaseConnectionPool.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ class BaseConnectionPool {
opts = this.urlToHost(opts)
}

if (opts.url.username !== '' && opts.url.password !== '') {
if (this.auth !== null) {
opts.auth = this.auth
} else if (opts.url.username !== '' && opts.url.password !== '') {
opts.auth = {
username: decodeURIComponent(opts.url.username),
password: decodeURIComponent(opts.url.password)
}
} else if (this.auth !== null) {
opts.auth = this.auth
}

if (opts.ssl == null) opts.ssl = this._ssl
Expand Down
94 changes: 94 additions & 0 deletions test/unit/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,62 @@ test('Authentication', t => {
})
})

t.test('ApiKey should take precedence over basic auth (in url)', t => {
t.plan(3)

function handler (req, res) {
t.match(req.headers, {
authorization: 'ApiKey Zm9vOmJhcg=='
})
res.setHeader('Content-Type', 'application/json;utf=8')
res.end(JSON.stringify({ hello: 'world' }))
}

buildServer(handler, ({ port }, server) => {
const client = new Client({
node: `http://user:pwd@localhost:${port}`,
auth: {
apiKey: 'Zm9vOmJhcg=='
}
})

client.info((err, { body }) => {
t.error(err)
t.deepEqual(body, { hello: 'world' })
server.stop()
})
})
})

t.test('ApiKey should take precedence over basic auth (in opts)', t => {
t.plan(3)

function handler (req, res) {
t.match(req.headers, {
authorization: 'ApiKey Zm9vOmJhcg=='
})
res.setHeader('Content-Type', 'application/json;utf=8')
res.end(JSON.stringify({ hello: 'world' }))
}

buildServer(handler, ({ port }, server) => {
const client = new Client({
node: `http://localhost:${port}`,
auth: {
apiKey: 'Zm9vOmJhcg==',
username: 'user',
password: 'pwd'
}
})

client.info((err, { body }) => {
t.error(err)
t.deepEqual(body, { hello: 'world' })
server.stop()
})
})
})

t.end()
})

Expand Down Expand Up @@ -827,6 +883,44 @@ test('Elastic cloud config', t => {
t.deepEqual(pool._ssl, { secureProtocol: 'TLSv1_2_method' })
})

t.test('ApiKey should take precedence over basic auth', t => {
t.plan(5)
const client = new Client({
cloud: {
// 'localhost$abcd$efgh'
id: 'name:bG9jYWxob3N0JGFiY2QkZWZnaA=='
},
auth: {
username: 'elastic',
password: 'changeme',
apiKey: 'Zm9vOmJhcg=='
}
})

const pool = client.connectionPool
t.ok(pool instanceof CloudConnectionPool)
t.match(pool.connections.find(c => c.id === 'https://abcd.localhost/'), {
url: new URL('https://elastic:[email protected]'),
id: 'https://abcd.localhost/',
headers: {
authorization: 'ApiKey Zm9vOmJhcg=='
},
ssl: { secureProtocol: 'TLSv1_2_method' },
deadCount: 0,
resurrectTimeout: 0,
roles: {
master: true,
data: true,
ingest: true,
ml: false
}
})

t.strictEqual(client.transport.compression, 'gzip')
t.strictEqual(client.transport.suggestCompression, true)
t.deepEqual(pool._ssl, { secureProtocol: 'TLSv1_2_method' })
})

t.test('Override default options', t => {
t.plan(4)
const client = new Client({
Expand Down