diff --git a/.ci/doc/docker-compose.yml b/.ci/doc/docker-compose.yml
index eadf29609..4c77f6188 100644
--- a/.ci/doc/docker-compose.yml
+++ b/.ci/doc/docker-compose.yml
@@ -22,7 +22,7 @@ services:
image: redis:5
elasticsearch:
- image: kuzzleio/elasticsearch:7.4.0
+ image: kuzzleio/elasticsearch:7
ulimits:
nofile: 65536
diff --git a/.ci/docker-compose.yml b/.ci/docker-compose.yml
index 088f611d5..745fc6f4e 100644
--- a/.ci/docker-compose.yml
+++ b/.ci/docker-compose.yml
@@ -21,6 +21,6 @@ services:
image: redis:5
elasticsearch:
- image: kuzzleio/elasticsearch:7.4.0
+ image: kuzzleio/elasticsearch:7
ulimits:
nofile: 65536
\ No newline at end of file
diff --git a/doc/7/controllers/auth/create-api-key/index.md b/doc/7/controllers/auth/create-api-key/index.md
new file mode 100644
index 000000000..3f21770ef
--- /dev/null
+++ b/doc/7/controllers/auth/create-api-key/index.md
@@ -0,0 +1,70 @@
+---
+code: true
+type: page
+title: createApiKey
+description: Creates a new API key for the currently loggued user.
+---
+
+# createApiKey
+
+
+
+```js
+createApiKey(description, [options]);
+```
+
+
+
+| Property | Type | Description |
+| --- | --- | --- |
+| `description` |
string| API key description | +| `options` |
object| Additional options | + +### options + +Additional query options + +| Property | Type
string/number
string
boolean
string| ID of the newly created API key | +| `_source` |
object| API key content | + +The API key content has the following properties: + +| Name | Type | Description | +| --------- | ----------------- | ---------------- | +| `userId` |
string| User kuid | +| `expiresAt` |
number| Aexpiration date in UNIX micro-timestamp format (`-1` if the token never expires) | +| `ttl` |
number| Original TTL | +| `description` |
string| API key description | +| `token` |
string| Authentication token associated with this API key | + +::: warning +The authentication token `token` will never be returned by Kuzzle again. If you lose it, you'll have to delete the API key and recreate a new one. +::: + +## Usage + +<<< ./snippets/create-api-key.js diff --git a/doc/7/controllers/auth/create-api-key/snippets/create-api-key.js b/doc/7/controllers/auth/create-api-key/snippets/create-api-key.js new file mode 100644 index 000000000..288a689ca --- /dev/null +++ b/doc/7/controllers/auth/create-api-key/snippets/create-api-key.js @@ -0,0 +1,23 @@ +try { + await kuzzle.auth.login('local', { username: 'john.doe', password: 'password' }); + + const apiKey = await kuzzle.auth.createApiKey('Sigfox API key'); + + console.log(apiKey); + /* + { + _id: '-fQRa28BsidO6V_wmOcL', + _source: { + description: 'Sigfox API key', + userId: 'john.doe', + expiresAt: -1, + ttl: -1, + token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiJqb2huLmRvZSIsImlhdCI6MTU3ODA0OTMxMn0.XO_keW6EtsCGmPzxVJCChU9VREakEECDGg-N5lhCfF8' + } + } + */ + + console.log('API key successfully created'); +} catch (e) { + console.error(e); +} diff --git a/doc/7/controllers/auth/create-api-key/snippets/create-api-key.test.yml b/doc/7/controllers/auth/create-api-key/snippets/create-api-key.test.yml new file mode 100644 index 000000000..40db9afa6 --- /dev/null +++ b/doc/7/controllers/auth/create-api-key/snippets/create-api-key.test.yml @@ -0,0 +1,22 @@ +name: auth#createApiKey +description: Creates a new API key for the currently loggued user. +hooks: + before: > + curl -H "Content-type: application/json" -d '{ + "content": { + "profileIds": ["default"] + }, + "credentials": { + "local": { + "username": "john.doe", + "password": "password" + } + } + }' "kuzzle:7512/users/john.doe/_create?refresh=wait_for" + after: + curl -XDELETE kuzzle:7512/users/john.doe +template: default +expected: + - "description: 'Sigfox API key'" + - "userId: 'john.doe'" + - API key successfully created diff --git a/doc/7/controllers/auth/delete-api-key/index.md b/doc/7/controllers/auth/delete-api-key/index.md new file mode 100644 index 000000000..efc7db6c2 --- /dev/null +++ b/doc/7/controllers/auth/delete-api-key/index.md @@ -0,0 +1,43 @@ +--- +code: true +type: page +title: deleteApiKey +description: Deletes an API key for the currently loggued user. +--- + +# deleteApiKey + +
string| API key unique ID | +| `options` |
object| Additional options | + +### options + +Additional query options + +| Property | Type
boolean
object| Search query | +| `options` |
object| Additional options | + +### query + +The search query to apply to API keys content, using [ElasticSearch Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/7.3/query-dsl.html) syntax. + +If left empty, the result will return all available API keys of the currently loggued user. + +### options + +Additional query options + +| Property | Type
number
number
object[]| Array of objects representing found API keys | +| `total` |
number| Total number of API keys found. Depending on pagination options, this can be greater than the actual number of API keys in a single result page | + +Each object of the `hits` array has the following properties: + +| Name | Type | Description | +| --------- | ----------------- | ---------------- | +| `_id_` |
string| API key unique ID | +| `_source` |
object| API key definition without the `token` field | + + +## Usage + +<<< ./snippets/search-api-keys.js diff --git a/doc/7/controllers/auth/search-api-keys/snippets/search-api-keys.js b/doc/7/controllers/auth/search-api-keys/snippets/search-api-keys.js new file mode 100644 index 000000000..a8b0981c3 --- /dev/null +++ b/doc/7/controllers/auth/search-api-keys/snippets/search-api-keys.js @@ -0,0 +1,57 @@ +try { + const promises = []; + + // Create some API keys for user "john.doe" + promises.push( + kuzzle.security.createApiKey('john.doe', 'Sigfox API key')); + promises.push( + kuzzle.security.createApiKey('john.doe', 'LoRa 6 month API key', { + expiresIn: '6m' + })); + promises.push( + kuzzle.security.createApiKey('john.doe', 'LoRa permanent API key', { + refresh: 'wait_for' + })); + + await Promise.all(promises); + + // Log as "john.doe" + await kuzzle.auth.login('local', { username: 'john.doe', password: 'password' }); + + const results = await kuzzle.auth.searchApiKeys({ + match: { + description: 'LoRa' + } + }); + + console.log(results); + /* + { + "total": 2, + "hits": [ + { + "_id": "znEwbG8BJASM_0-bWU-q", + "_source": { + "description": "LoRa permanent API key", + "userId": "john.doe", + "expiresAt": -1, + "ttl": -1 + } + }, + { + "_id": "zXEwbG8BJASM_0-bWU-q", + "_source": { + "description": "LoRa 1 year API key", + "userId": "john.doe", + "expiresAt": 31557600000, + "ttl": 360000 + } + } + ] + } + */ + + console.log(`Found ${results.total} API keys matching "LoRa"`); +} catch (e) { + console.error(e); +} diff --git a/doc/7/controllers/auth/search-api-keys/snippets/search-api-keys.test.yml b/doc/7/controllers/auth/search-api-keys/snippets/search-api-keys.test.yml new file mode 100644 index 000000000..f6eca283f --- /dev/null +++ b/doc/7/controllers/auth/search-api-keys/snippets/search-api-keys.test.yml @@ -0,0 +1,20 @@ +name: auth#searchApiKeys +description: Searches API keys for the currently loggued user. +hooks: + before: > + curl -H "Content-type: application/json" -d '{ + "content": { + "profileIds": ["default"] + }, + "credentials": { + "local": { + "username": "john.doe", + "password": "password" + } + } + }' "kuzzle:7512/users/john.doe/_create?refresh=wait_for" + after: + curl -XDELETE kuzzle:7512/users/john.doe +template: default +expected: + - Found 2 API keys matching "LoRa" diff --git a/doc/7/controllers/security/create-api-key/index.md b/doc/7/controllers/security/create-api-key/index.md new file mode 100644 index 000000000..35202ecb1 --- /dev/null +++ b/doc/7/controllers/security/create-api-key/index.md @@ -0,0 +1,71 @@ +--- +code: true +type: page +title: createApiKey +description: Creates a new API key for a user +--- + +# createApiKey + +
string| User [kuid](/core/2/guides/essentials/user-authentication#kuzzle-user-identifier-kuid) | +| `description` |
string| API key description | +| `options` |
object| Additional options | + +### options + +Additional query options + +| Property | Type
string/number
string
boolean
string| ID of the newly created API key | +| `_source` |
object| API key content | + +The API key content has the following properties: + +| Name | Type | Description | +| --------- | ----------------- | ---------------- | +| `userId` |
string| User kuid | +| `expiresAt` |
number| Aexpiration date in UNIX micro-timestamp format (`-1` if the token never expires) | +| `ttl` |
number| Original TTL | +| `description` |
string| API key description | +| `token` |
string| Authentication token associated with this API key | + +::: warning +The authentication token `token` will never be returned by Kuzzle again. If you lose it, you'll have to delete the API key and recreate a new one. +::: + +## Usage + +<<< ./snippets/create-api-key.js diff --git a/doc/7/controllers/security/create-api-key/snippets/create-api-key.js b/doc/7/controllers/security/create-api-key/snippets/create-api-key.js new file mode 100644 index 000000000..337b15fd1 --- /dev/null +++ b/doc/7/controllers/security/create-api-key/snippets/create-api-key.js @@ -0,0 +1,21 @@ +try { + const apiKey = await kuzzle.security.createApiKey('john.doe', 'Sigfox API key'); + + console.log(apiKey); + /* + { + _id: '-fQRa28BsidO6V_wmOcL', + _source: { + description: 'Sigfox API key', + userId: 'john.doe', + expiresAt: -1, + ttl: -1, + token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiJqb2huLmRvZSIsImlhdCI6MTU3ODA0OTMxMn0.XO_keW6EtsCGmPzxVJCChU9VREakEECDGg-N5lhCfF8' + } + } + */ + + console.log('API key successfully created'); +} catch (e) { + console.error(e); +} diff --git a/doc/7/controllers/security/create-api-key/snippets/create-api-key.test.yml b/doc/7/controllers/security/create-api-key/snippets/create-api-key.test.yml new file mode 100644 index 000000000..66485aad9 --- /dev/null +++ b/doc/7/controllers/security/create-api-key/snippets/create-api-key.test.yml @@ -0,0 +1,16 @@ +name: security#createApiKey +description: Creates a new API key for an user +hooks: + before: > + curl -H "Content-type: application/json" -d '{ + "content": { + "profileIds": ["default"] + } + }' "kuzzle:7512/users/john.doe/_create?refresh=wait_for" + after: + curl -XDELETE kuzzle:7512/users/john.doe +template: default +expected: + - "description: 'Sigfox API key'" + - "userId: 'john.doe'" + - API key successfully created diff --git a/doc/7/controllers/security/delete-api-key/index.md b/doc/7/controllers/security/delete-api-key/index.md new file mode 100644 index 000000000..003482cf6 --- /dev/null +++ b/doc/7/controllers/security/delete-api-key/index.md @@ -0,0 +1,44 @@ +--- +code: true +type: page +title: deleteApiKey +description: Deletes a user's API key. +--- + +# deleteApiKey + +
string| User [kuid](/core/2/guides/essentials/user-authentication#kuzzle-user-identifier-kuid) | +| `id` |
string| API key unique ID | +| `options` |
object| Additional options | + +### options + +Additional query options + +| Property | Type
boolean
string| User kuid | +| `query` |
object| Search query | +| `options` |
object| Additional options | + +### query + +The search query to apply to API keys content, using [ElasticSearch Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/7.3/query-dsl.html) syntax. + +If left empty, the result will return all available API keys for the user. + +### options + +Additional query options + +| Property | Type
number
number
object[]| Array of objects representing found API keys | +| `total` |
number| Total number of API keys found. Depending on pagination options, this can be greater than the actual number of API keys in a single result page | + +Each object of the `hits` array has the following properties: + +| Name | Type | Description | +| --------- | ----------------- | ---------------- | +| `_id_` |
string| API key unique ID | +| `_source` |
object| API key definition without the `token` field | + + +## Usage + +<<< ./snippets/search-api-keys.js diff --git a/doc/7/controllers/security/search-api-keys/snippets/search-api-keys.js b/doc/7/controllers/security/search-api-keys/snippets/search-api-keys.js new file mode 100644 index 000000000..a4b6039db --- /dev/null +++ b/doc/7/controllers/security/search-api-keys/snippets/search-api-keys.js @@ -0,0 +1,54 @@ +try { + const promises = []; + + // Create some API keys + promises.push( + kuzzle.security.createApiKey('john.doe', 'Sigfox API key')); + promises.push( + kuzzle.security.createApiKey('john.doe', 'LoRa 6 month API key', { + expiresIn: '6m' + })); + promises.push( + kuzzle.security.createApiKey('john.doe', 'LoRa permanent API key', { + refresh: 'wait_for' + })); + + await Promise.all(promises); + + const results = await kuzzle.security.searchApiKeys('john.doe', { + match: { + description: 'LoRa' + } + }); + + console.log(results); + /* + { + "total": 2, + "hits": [ + { + "_id": "znEwbG8BJASM_0-bWU-q", + "_source": { + "description": "LoRa permanent API key", + "userId": "john.doe", + "expiresAt": -1, + "ttl": -1 + } + }, + { + "_id": "zXEwbG8BJASM_0-bWU-q", + "_source": { + "description": "LoRa 1 year API key", + "userId": "john.doe", + "expiresAt": 31557600000, + "ttl": 360000 + } + } + ] + } + */ + + console.log(`Found ${results.total} API keys matching "LoRa"`); +} catch (e) { + console.error(e); +} diff --git a/doc/7/controllers/security/search-api-keys/snippets/search-api-keys.test.yml b/doc/7/controllers/security/search-api-keys/snippets/search-api-keys.test.yml new file mode 100644 index 000000000..91f234349 --- /dev/null +++ b/doc/7/controllers/security/search-api-keys/snippets/search-api-keys.test.yml @@ -0,0 +1,14 @@ +name: security#searchApiKeys +description: Searches for an user API keys +hooks: + before: > + curl -H "Content-type: application/json" -d '{ + "content": { + "profileIds": ["default"] + } + }' "kuzzle:7512/users/john.doe/_create?refresh=wait_for" + after: + curl -XDELETE kuzzle:7512/users/john.doe +template: default +expected: + - Found 2 API keys matching "LoRa" diff --git a/features/steps/document.js b/features/steps/document.js index f7d396099..f7a3c8ea3 100644 --- a/features/steps/document.js +++ b/features/steps/document.js @@ -53,7 +53,7 @@ When('I create a document in {string}', function (collection) { collection, {a: 'document'}, 'some-id', - {refresh: true}) + {refresh: 'wait_for'}) .then(content => { this.content = content; }); @@ -68,7 +68,7 @@ When('I create a document with id {string}', function (id) { this.collection, {a: 'document'}, id, - {refresh: true}) + {refresh: 'wait_for'}) .then(content => { this.content = content; }) @@ -108,7 +108,7 @@ When('I createOrReplace a document with id {string}', function (id) { this.collection, id, {a: 'replaced document'}, - {refresh: true}) + {refresh: 'wait_for'}) .then(content => { this.content = content; }) @@ -159,7 +159,7 @@ When('I delete the documents [{string}, {string}]', function (id1, id2) { this.index, this.collection, [id1, id2], - {refresh: true}) + {refresh: 'wait_for'}) .then(content => { this.content = content; }) @@ -177,7 +177,7 @@ When('I replace a document with id {string}', function (id) { this.collection, id, {a: 'replaced document'}, - {refresh: true}) + {refresh: 'wait_for'}) .then(content => { this.content = content; }) @@ -197,7 +197,7 @@ When('I replace the documents [{string}, {string}]', function (id1, id2) { {_id: id1, body: {a: 'replaced document'}}, {_id: id2, body: {a: 'replaced document'}} ], - {refresh: true}) + {refresh: 'wait_for'}) .then(content => { this.content = content; }) @@ -293,7 +293,7 @@ When('I update the documents [{string}, {string}]', function (id1, id2) { {_id: id1, body: {a: 'replaced document', some: 'update'}}, {_id: id2, body: {a: 'replaced document', some: 'update'}} ], - { refresh: true }) + { refresh: 'wait_for' }) .then(content => { this.content = content; }) diff --git a/src/Kuzzle.js b/src/Kuzzle.js index ba039be47..a8df7ad03 100644 --- a/src/Kuzzle.js +++ b/src/Kuzzle.js @@ -406,10 +406,8 @@ class Kuzzle extends KuzzleEventEmitter { request.requestId = uuidv4(); } - // we follow the api but allow some more logical "mistakes" - // (the only allowed value for refresh arg is "wait_for") - if (request.refresh || options.refresh) { - request.refresh = 'wait_for'; + if (typeof request.refresh === 'undefined' && typeof options.refresh !== 'undefined') { + request.refresh = options.refresh; } if (! request.volatile) { diff --git a/src/controllers/auth.js b/src/controllers/auth.js index a92ac72ad..028a4d729 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -52,6 +52,68 @@ class AuthController extends BaseController { request.jwt = this.authenticationToken.encodedJwt; } + /** + * Creates a new API key for the currently loggued user. + * + * @param {String} description - API key description + * @param {Object} [options] - { _id, expiresIn, refresh } + * + * @returns {Promise.