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 + + + + + +Creates a new API key for the currently loggued user. + +
+ +```js +createApiKey(description, [options]); +``` + +
+ +| Property | Type | Description | +| --- | --- | --- | +| `description` |
string
| API key description | +| `options` |
object
| Additional options | + +### options + +Additional query options + +| Property | Type
(default) | Description | +| --- | --- | --- | +| `expiresIn` |
string/number

(`-1`) | Expiration duration | +| `_id` |
string

(`null`) | API key unique ID | +| `refresh` |
boolean

(`false`) | If set to `wait_for`, Kuzzle will not respond until the API key is indexed | + +**Notes**: +- `expiresIn`: + - if a raw number is provided (not enclosed between quotes), then the expiration delay is in milliseconds. Example: `86400000` + - if this value is a string, then its content is parsed by the [ms](https://www.npmjs.com/package/ms) library. Examples: `"6d"`, `"10h"` + - if `-1` is provided, the token will never expire + +## Resolves + +An object containing the newly created API key: + +| Name | Type | Description | +| --------- | ----------------- | ---------------- | +| `_id` |
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 + + + + + +Deletes an API key for the currently loggued user. + +
+ +```js +deleteApiKey(id, [options]); +``` + +
+ +| Property | Type | Description | +| --- | --- | --- | +| `id` |
string
| API key unique ID | +| `options` |
object
| Additional options | + +### options + +Additional query options + +| Property | Type
(default) | Description | +| --- | --- | --- | +| `refresh` |
boolean

(`false`) | If set to `wait_for`, Kuzzle will not respond until the API key is indexed | + +## Resolves + +Resolves if the API key is successfully deleted. + +## Usage + +<<< ./snippets/delete-api-key.js diff --git a/doc/7/controllers/auth/delete-api-key/snippets/delete-api-key.js b/doc/7/controllers/auth/delete-api-key/snippets/delete-api-key.js new file mode 100644 index 000000000..e6f1feb2b --- /dev/null +++ b/doc/7/controllers/auth/delete-api-key/snippets/delete-api-key.js @@ -0,0 +1,9 @@ +try { + await kuzzle.auth.login('local', { username: 'john.doe', password: 'password' }); + + await kuzzle.auth.deleteApiKey('fQRa28BsidO6V_wmOcL'); + + console.log('API key successfully deleted'); +} catch (e) { + console.error(e); +} diff --git a/doc/7/controllers/auth/delete-api-key/snippets/delete-api-key.test.yml b/doc/7/controllers/auth/delete-api-key/snippets/delete-api-key.test.yml new file mode 100644 index 000000000..7dbd119ef --- /dev/null +++ b/doc/7/controllers/auth/delete-api-key/snippets/delete-api-key.test.yml @@ -0,0 +1,24 @@ +name: auth#deleteApiKey +description: Deletes an 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" + && + curl -XPOST -H "Content-type: application/json" -d '{ + "description": "Sigfox API key" + }' "kuzzle:7512/users/john.doe/api-keys/_create?_id=fQRa28BsidO6V_wmOcL&refresh=wait_for" + after: + curl -XDELETE kuzzle:7512/users/john.doe +template: default +expected: + - API key successfully deleted diff --git a/doc/7/controllers/auth/search-api-keys/index.md b/doc/7/controllers/auth/search-api-keys/index.md new file mode 100644 index 000000000..c6be550d0 --- /dev/null +++ b/doc/7/controllers/auth/search-api-keys/index.md @@ -0,0 +1,63 @@ +--- +code: true +type: page +title: searchApiKeys +description: Searches API keys for the currently loggued user. +--- + +# searchApiKeys + + + + + +Searches API keys for the currently loggued user. + +
+ +```js +searchApiKeys([query], [options]); +``` + +
+ +| Property | Type | Description | +| --- | --- | --- | +| `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 of the currently loggued user. + +### options + +Additional query options + +| Property | Type
(default) | Description | +| ---------- | ------------------ | ------------ | +| `from` |
number

(`0`) | Offset of the first document to fetch | +| `size` |
number

(`10`) | Maximum number of documents to retrieve per page | + +## Resolves + +Resolves an object with the following properties: + +| Name | Type | Description | +| --------- | ----------------- | ---------------- | +| `hits` |
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 + + + + + +Creates a new API key for a user. + +
+ +```js +createApiKey(userId, description, [options]); +``` + +
+ +| Property | Type | Description | +| --- | --- | --- | +| `userId` |
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
(default) | Description | +| --- | --- | --- | +| `expiresIn` |
string/number

(`-1`) | Expiration duration | +| `_id` |
string

(`null`) | API key unique ID | +| `refresh` |
boolean

(`false`) | If set to `wait_for`, Kuzzle will not respond until the API key is indexed | + +**Notes**: +- `expiresIn`: + - if a raw number is provided (not enclosed between quotes), then the expiration delay is in milliseconds. Example: `86400000` + - if this value is a string, then its content is parsed by the [ms](https://www.npmjs.com/package/ms) library. Examples: `"6d"`, `"10h"` + - if `-1` is provided, the token will never expire + +## Resolves + +An object containing the newly created API key: + +| Name | Type | Description | +| --------- | ----------------- | ---------------- | +| `_id` |
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 + + + + + +Deletes a user's API key. + +
+ +```js +deleteApiKey(userId, id, [options]); +``` + +
+ +| Property | Type | Description | +| --- | --- | --- | +| `userId` |
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
(default) | Description | +| --- | --- | --- | +| `refresh` |
boolean

(`false`) | If set to `wait_for`, Kuzzle will not respond until the API key is indexed | + +## Resolves + +Resolves if the API key is successfully deleted. + +## Usage + +<<< ./snippets/delete-api-key.js diff --git a/doc/7/controllers/security/delete-api-key/snippets/delete-api-key.js b/doc/7/controllers/security/delete-api-key/snippets/delete-api-key.js new file mode 100644 index 000000000..67cbf958b --- /dev/null +++ b/doc/7/controllers/security/delete-api-key/snippets/delete-api-key.js @@ -0,0 +1,7 @@ +try { + await kuzzle.security.deleteApiKey('john.doe', 'fQRa28BsidO6V_wmOcL'); + + console.log('API key successfully deleted'); +} catch (e) { + console.error(e); +} diff --git a/doc/7/controllers/security/delete-api-key/snippets/delete-api-key.test.yml b/doc/7/controllers/security/delete-api-key/snippets/delete-api-key.test.yml new file mode 100644 index 000000000..0ee031be4 --- /dev/null +++ b/doc/7/controllers/security/delete-api-key/snippets/delete-api-key.test.yml @@ -0,0 +1,18 @@ +name: security#deleteApiKey +description: Deletes an user API key +hooks: + before: > + curl -H "Content-type: application/json" -d '{ + "content": { + "profileIds": ["default"] + } + }' "kuzzle:7512/users/john.doe/_create?refresh=wait_for" + && + curl -XPOST -H "Content-type: application/json" -d '{ + "description": "Sigfox API key" + }' "kuzzle:7512/users/john.doe/api-keys/_create?_id=fQRa28BsidO6V_wmOcL&refresh=wait_for" + after: + curl -XDELETE kuzzle:7512/users/john.doe +template: default +expected: + - API key successfully deleted diff --git a/doc/7/controllers/security/search-api-keys/index.md b/doc/7/controllers/security/search-api-keys/index.md new file mode 100644 index 000000000..b812fd0f4 --- /dev/null +++ b/doc/7/controllers/security/search-api-keys/index.md @@ -0,0 +1,64 @@ +--- +code: true +type: page +title: searchApiKeys +description: Searches for an user API keys. +--- + +# searchApiKeys + + + + + +Searches for an user API keys. + +
+ +```js +searchApiKeys(userId, [query], [options]); +``` + +
+ +| Property | Type | Description | +| --- | --- | --- | +| `userId` |
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
(default) | Description | +| ---------- | ------------------ | ------------ | +| `from` |
number

(`0`) | Offset of the first document to fetch | +| `size` |
number

(`10`) | Maximum number of documents to retrieve per page | + +## Resolves + +Resolves an object with the following properties: + +| Name | Type | Description | +| --------- | ----------------- | ---------------- | +| `hits` |
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.} ApiKey { _id, _source } + */ + createApiKey(description, options = {}) { + const request = { + action: 'createApiKey', + _id: options._id, + expiresIn: options.expiresIn, + refresh: options.refresh, + body: { + description + } + }; + + return this.query(request) + .then(response => response.result); + } + + /** + * Deletes an API key for the currently loggued user. + * + * @param {String} id - API key ID + * @param {Object} [options] - { refresh } + * + * @returns {Promise} + */ + deleteApiKey(id, options = {}) { + const request = { + action: 'deleteApiKey', + _id: id, + refresh: options.refresh + }; + + return this.query(request) + .then(() => {}); + } + + /** + * Searches API keys for the currently loggued user. + * + * @param {Object} [query] - Search query + * @param {Object} [options] - { from, size } + * + * @returns {Promise.} - { hits, total } + */ + searchApiKeys(query = {}, options = {}) { + const request = { + action: 'searchApiKeys', + from: options.from, + size: options.size, + body: query + }; + + return this.query(request) + .then(response => response.result); + } + /** * Checks whether a given jwt token still represents a valid session in Kuzzle. * diff --git a/src/controllers/security/index.js b/src/controllers/security/index.js index ec3de7759..7fd5c91ce 100644 --- a/src/controllers/security/index.js +++ b/src/controllers/security/index.js @@ -15,6 +15,74 @@ class SecurityController extends BaseController { super(kuzzle, 'security'); } + /** + * Creates a new API key for a user. + * + * @param {String} userId - User kuid + * @param {String} description - API key description + * @param {Object} [options] - { _id, expiresIn, refresh } + * + * @returns {Promise.} ApiKey { _id, _source } + */ + createApiKey(userId, description, options = {}) { + const request = { + userId, + action: 'createApiKey', + _id: options._id, + expiresIn: options.expiresIn, + refresh: options.refresh, + body: { + description + } + }; + + return this.query(request) + .then(response => response.result); + } + + /** + * Deletes an user API key. + * + * @param {String} userId - User kuid + * @param {String} id - API key ID + * @param {Object} [options] - { refresh } + * + * @returns {Promise} + */ + deleteApiKey(userId, id, options = {}) { + const request = { + userId, + action: 'deleteApiKey', + _id: id, + refresh: options.refresh + }; + + return this.query(request) + .then(() => {}); + } + + /** + * Searches for an user API key. + * + * @param {String} userId - User kuid + * @param {Object} [query] - Search query + * @param {Object} [options] - { from, size } + * + * @returns {Promise.} - { hits, total } + */ + searchApiKeys(userId, query = {}, options = {}) { + const request = { + userId, + action: 'searchApiKeys', + from: options.from, + size: options.size, + body: query + }; + + return this.query(request) + .then(response => response.result); + } + createCredentials (strategy, _id, body, options = {}) { return this.query({ _id, diff --git a/test/controllers/auth.test.js b/test/controllers/auth.test.js index 507cacf7c..2f5c2eced 100644 --- a/test/controllers/auth.test.js +++ b/test/controllers/auth.test.js @@ -20,6 +20,76 @@ describe('Auth Controller', () => { kuzzle.auth = new AuthController(kuzzle); }); + describe('createApiKey', () => { + it('should send request to Kuzzle API', async () => { + const apiResult = { + _id: 'api-key-id', + _source: { + userId: 'kuid', + description: 'description', + expiresAt: Date.now() + 10000, + ttl: 10000, + token: 'secret-token' + } + }; + kuzzle.query.resolves({ result: apiResult }); + + const result = await kuzzle.auth.createApiKey('description', { + expiresIn: 10000, + _id: 'api-key-id', + refresh: 'wait_for' + }); + + should(kuzzle.query).be.calledWith({ + controller: 'auth', + action: 'createApiKey', + _id: 'api-key-id', + expiresIn: 10000, + refresh: 'wait_for', + body: { + description: 'description' + } + }); + + should(result).be.eql(apiResult); + }); + }); + + describe('deleteApiKey', () => { + it('should send request to Kuzzle API', async () => { + kuzzle.query.resolves(); + + await kuzzle.auth.deleteApiKey('api-key-id', { refresh: 'wait_for' }); + + should(kuzzle.query).be.calledWith({ + controller: 'auth', + action: 'deleteApiKey', + _id: 'api-key-id', + refresh: 'wait_for' + }); + }); + }); + + describe('searchApiKeys', () => { + it('should send request to Kuzzle API', async () => { + kuzzle.query.resolves({ result: { hits: [1, 2] } }); + + const result = await kuzzle.auth.searchApiKeys( + { match: {} }, + { from: 1, size: 2 }); + + should(kuzzle.query).be.calledWith({ + controller: 'auth', + action: 'searchApiKeys', + body: { match: {} }, + from: 1, + size: 2 + }); + + should(result).be.eql({ hits: [1, 2] }); + }); + }); + describe('#checkToken', () => { it('should call auth/checkToken query with the token and return a Promise which resolves the token validity', () => { kuzzle.query.resolves({ diff --git a/test/controllers/security.test.js b/test/controllers/security.test.js index 2b90744ec..9359a20b4 100644 --- a/test/controllers/security.test.js +++ b/test/controllers/security.test.js @@ -21,6 +21,80 @@ describe('Security Controller', () => { kuzzle.security = new SecurityController(kuzzle); }); + describe('createApiKey', () => { + it('should send request to Kuzzle API', async () => { + const apiResult = { + _id: 'api-key-id', + _source: { + userId: 'kuid', + description: 'description', + expiresAt: Date.now() + 10000, + ttl: 10000, + token: 'secret-token' + } + }; + kuzzle.query.resolves({ result: apiResult }); + + const result = await kuzzle.security.createApiKey('kuid', 'description', { + expiresIn: 10000, + _id: 'api-key-id', + refresh: 'wait_for' + }); + + should(kuzzle.query).be.calledWith({ + controller: 'security', + action: 'createApiKey', + _id: 'api-key-id', + userId: 'kuid', + expiresIn: 10000, + refresh: 'wait_for', + body: { + description: 'description' + } + }); + + should(result).be.eql(apiResult); + }); + }); + + describe('deleteApiKey', () => { + it('should send request to Kuzzle API', async () => { + kuzzle.query.resolves(); + + await kuzzle.security.deleteApiKey('kuid', 'api-key-id', { refresh: 'wait_for' }); + + should(kuzzle.query).be.calledWith({ + controller: 'security', + action: 'deleteApiKey', + _id: 'api-key-id', + userId: 'kuid', + refresh: 'wait_for' + }); + }); + }); + + describe('searchApiKeys', () => { + it('should send request to Kuzzle API', async () => { + kuzzle.query.resolves({ result: { hits: [1, 2] } }); + + const result = await kuzzle.security.searchApiKeys( + 'kuid', + { match: {} }, + { from: 1, size: 2 }); + + should(kuzzle.query).be.calledWith({ + controller: 'security', + action: 'searchApiKeys', + userId: 'kuid', + body: { match: {} }, + from: 1, + size: 2 + }); + + should(result).be.eql({ hits: [1, 2] }); + }); + }); + describe('createCredentials', () => { it('should call security/createCredentials query with the user credentials and return a Promise which resolves a json object', () => { const result = { diff --git a/test/kuzzle/query.test.js b/test/kuzzle/query.test.js index 2f1449f21..75d439fdb 100644 --- a/test/kuzzle/query.test.js +++ b/test/kuzzle/query.test.js @@ -153,9 +153,11 @@ describe('Kuzzle query management', () => { }); it('should handle option "refresh" properly', () => { - kuzzle.query({refresh: 'foo'}); - should(kuzzle.protocol.query).be.calledOnce(); + kuzzle.query({refresh: 'wait_for'}); should(kuzzle.protocol.query).be.calledWithMatch({refresh: 'wait_for'}); + + kuzzle.query({refresh: false}); + should(kuzzle.protocol.query).be.calledWithMatch({refresh: false}); }); it('should not generate a new request ID if one is already defined', () => {