From d4da7a3c01b6fd4a11c856993349cc2c8676ed94 Mon Sep 17 00:00:00 2001 From: Aschen Date: Fri, 3 Jan 2020 17:36:58 +0100 Subject: [PATCH 1/7] Add security:*ApiKey methods --- .ci/doc/docker-compose.yml | 2 +- .ci/docker-compose.yml | 2 +- .../security/create-api-key/index.md | 67 +++++++++++++++++ .../create-api-key/snippets/create-api-key.js | 21 ++++++ .../snippets/create-api-key.test.yml | 16 ++++ .../security/delete-api-key/index.md | 40 ++++++++++ .../delete-api-key/snippets/delete-api-key.js | 7 ++ .../snippets/delete-api-key.test.yml | 18 +++++ .../security/search-api-keys/index.md | 60 +++++++++++++++ .../snippets/search-api-keys.js | 54 ++++++++++++++ .../snippets/search-api-keys.test.yml | 14 ++++ src/controllers/security/index.js | 68 +++++++++++++++++ test/controllers/security.test.js | 74 +++++++++++++++++++ 13 files changed, 441 insertions(+), 2 deletions(-) create mode 100644 doc/7/controllers/security/create-api-key/index.md create mode 100644 doc/7/controllers/security/create-api-key/snippets/create-api-key.js create mode 100644 doc/7/controllers/security/create-api-key/snippets/create-api-key.test.yml create mode 100644 doc/7/controllers/security/delete-api-key/index.md create mode 100644 doc/7/controllers/security/delete-api-key/snippets/delete-api-key.js create mode 100644 doc/7/controllers/security/delete-api-key/snippets/delete-api-key.test.yml create mode 100644 doc/7/controllers/security/search-api-keys/index.md create mode 100644 doc/7/controllers/security/search-api-keys/snippets/search-api-keys.js create mode 100644 doc/7/controllers/security/search-api-keys/snippets/search-api-keys.test.yml 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/security/create-api-key/index.md b/doc/7/controllers/security/create-api-key/index.md new file mode 100644 index 000000000..9077828db --- /dev/null +++ b/doc/7/controllers/security/create-api-key/index.md @@ -0,0 +1,67 @@ +--- +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..43abc3f78 --- /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 a 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..ceb0c1ae5 --- /dev/null +++ b/doc/7/controllers/security/delete-api-key/index.md @@ -0,0 +1,40 @@ +--- +code: true +type: page +title: deleteApiKey +description: Creates a new API key for a user +--- + +# deleteApiKey + +Deletes an user 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..a904fbaf5 --- /dev/null +++ b/doc/7/controllers/security/search-api-keys/index.md @@ -0,0 +1,60 @@ +--- +code: true +type: page +title: searchApiKeys +description: Creates a new API key for a user +--- + +# 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/src/controllers/security/index.js b/src/controllers/security/index.js index ec3de7759..4f3a58b2c 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} + */ + 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/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 = { From 021ff5b57b6cfb3e334365dc161a4e1a6cc49d1b Mon Sep 17 00:00:00 2001 From: Aschen Date: Fri, 3 Jan 2020 18:04:07 +0100 Subject: [PATCH 2/7] Add auth:*ApiKey methods --- .../controllers/auth/create-api-key/index.md | 70 +++++++++++++++++++ .../create-api-key/snippets/create-api-key.js | 23 ++++++ .../snippets/create-api-key.test.yml | 22 ++++++ .../controllers/auth/delete-api-key/index.md | 43 ++++++++++++ .../delete-api-key/snippets/delete-api-key.js | 9 +++ .../snippets/delete-api-key.test.yml | 24 +++++++ .../controllers/auth/search-api-keys/index.md | 63 +++++++++++++++++ .../snippets/search-api-keys.js | 57 +++++++++++++++ .../snippets/search-api-keys.test.yml | 20 ++++++ .../security/create-api-key/index.md | 4 ++ .../security/delete-api-key/index.md | 6 +- .../security/search-api-keys/index.md | 4 ++ src/controllers/auth.js | 62 ++++++++++++++++ test/controllers/auth.test.js | 70 +++++++++++++++++++ 14 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 doc/7/controllers/auth/create-api-key/index.md create mode 100644 doc/7/controllers/auth/create-api-key/snippets/create-api-key.js create mode 100644 doc/7/controllers/auth/create-api-key/snippets/create-api-key.test.yml create mode 100644 doc/7/controllers/auth/delete-api-key/index.md create mode 100644 doc/7/controllers/auth/delete-api-key/snippets/delete-api-key.js create mode 100644 doc/7/controllers/auth/delete-api-key/snippets/delete-api-key.test.yml create mode 100644 doc/7/controllers/auth/search-api-keys/index.md create mode 100644 doc/7/controllers/auth/search-api-keys/snippets/search-api-keys.js create mode 100644 doc/7/controllers/auth/search-api-keys/snippets/search-api-keys.test.yml 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 index 9077828db..35202ecb1 100644 --- a/doc/7/controllers/security/create-api-key/index.md +++ b/doc/7/controllers/security/create-api-key/index.md @@ -7,6 +7,10 @@ description: Creates a new API key for a user # createApiKey + + + + Creates a new API key for a user.
diff --git a/doc/7/controllers/security/delete-api-key/index.md b/doc/7/controllers/security/delete-api-key/index.md index ceb0c1ae5..117fd16f5 100644 --- a/doc/7/controllers/security/delete-api-key/index.md +++ b/doc/7/controllers/security/delete-api-key/index.md @@ -2,11 +2,15 @@ code: true type: page title: deleteApiKey -description: Creates a new API key for a user +description: Deletes an user API key. --- # deleteApiKey + + + + Deletes an user API key.
diff --git a/doc/7/controllers/security/search-api-keys/index.md b/doc/7/controllers/security/search-api-keys/index.md index a904fbaf5..fb450f30c 100644 --- a/doc/7/controllers/security/search-api-keys/index.md +++ b/doc/7/controllers/security/search-api-keys/index.md @@ -7,6 +7,10 @@ description: Creates a new API key for a user # searchApiKeys + + + + Searches for an user API keys.
diff --git a/src/controllers/auth.js b/src/controllers/auth.js index a92ac72ad..5ba88938a 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} + */ + 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/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({ From f76e168abd103861763e14958ba00240b4de7ca9 Mon Sep 17 00:00:00 2001 From: Aschen Date: Fri, 3 Jan 2020 18:08:05 +0100 Subject: [PATCH 3/7] nit --- doc/7/controllers/security/create-api-key/index.md | 4 ++-- .../security/create-api-key/snippets/create-api-key.test.yml | 2 +- doc/7/controllers/security/search-api-keys/index.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/7/controllers/security/create-api-key/index.md b/doc/7/controllers/security/create-api-key/index.md index 35202ecb1..acb2b56b2 100644 --- a/doc/7/controllers/security/create-api-key/index.md +++ b/doc/7/controllers/security/create-api-key/index.md @@ -2,7 +2,7 @@ code: true type: page title: createApiKey -description: Creates a new API key for a user +description: Creates a new API key for an user --- # createApiKey @@ -11,7 +11,7 @@ description: Creates a new API key for a user -Creates a new API key for a user. +Creates a new API key for an user.
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 index 43abc3f78..66485aad9 100644 --- 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 @@ -1,5 +1,5 @@ name: security#createApiKey -description: Creates a new API key for a user +description: Creates a new API key for an user hooks: before: > curl -H "Content-type: application/json" -d '{ diff --git a/doc/7/controllers/security/search-api-keys/index.md b/doc/7/controllers/security/search-api-keys/index.md index fb450f30c..b812fd0f4 100644 --- a/doc/7/controllers/security/search-api-keys/index.md +++ b/doc/7/controllers/security/search-api-keys/index.md @@ -2,7 +2,7 @@ code: true type: page title: searchApiKeys -description: Creates a new API key for a user +description: Searches for an user API keys. --- # searchApiKeys From f1f48b86e836acbe606978e75cf507b91c422c33 Mon Sep 17 00:00:00 2001 From: Aschen Date: Fri, 3 Jan 2020 18:09:33 +0100 Subject: [PATCH 4/7] nit --- src/controllers/auth.js | 2 +- src/controllers/security/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/auth.js b/src/controllers/auth.js index 5ba88938a..028a4d729 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -100,7 +100,7 @@ class AuthController extends BaseController { * @param {Object} [query] - Search query * @param {Object} [options] - { from, size } * - * @returns {Promise} + * @returns {Promise.} - { hits, total } */ searchApiKeys(query = {}, options = {}) { const request = { diff --git a/src/controllers/security/index.js b/src/controllers/security/index.js index 4f3a58b2c..7fd5c91ce 100644 --- a/src/controllers/security/index.js +++ b/src/controllers/security/index.js @@ -68,7 +68,7 @@ class SecurityController extends BaseController { * @param {Object} [query] - Search query * @param {Object} [options] - { from, size } * - * @returns {Promise} + * @returns {Promise.} - { hits, total } */ searchApiKeys(userId, query = {}, options = {}) { const request = { From 8d7a49acf067cf608d81836338d67d58c8c0c7d3 Mon Sep 17 00:00:00 2001 From: Aschen Date: Mon, 6 Jan 2020 18:06:48 +0100 Subject: [PATCH 5/7] Fix refresh param --- src/Kuzzle.js | 6 ++---- test/kuzzle/query.test.js | 6 ++++-- 2 files changed, 6 insertions(+), 6 deletions(-) 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/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', () => { From cad0c171319edea4738bef3d0a9e4f7fb7b3c14b Mon Sep 17 00:00:00 2001 From: Aschen Date: Mon, 6 Jan 2020 18:41:40 +0100 Subject: [PATCH 6/7] Fix refresh true --- features/steps/document.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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; }) From b7429060544e813baf5b3058424c30c86723d45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cottinet?= Date: Wed, 8 Jan 2020 09:02:23 +0100 Subject: [PATCH 7/7] Apply suggestions from code review Typo fixes. --- doc/7/controllers/security/create-api-key/index.md | 4 ++-- doc/7/controllers/security/delete-api-key/index.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/7/controllers/security/create-api-key/index.md b/doc/7/controllers/security/create-api-key/index.md index acb2b56b2..35202ecb1 100644 --- a/doc/7/controllers/security/create-api-key/index.md +++ b/doc/7/controllers/security/create-api-key/index.md @@ -2,7 +2,7 @@ code: true type: page title: createApiKey -description: Creates a new API key for an user +description: Creates a new API key for a user --- # createApiKey @@ -11,7 +11,7 @@ description: Creates a new API key for an user -Creates a new API key for an user. +Creates a new API key for a user.
diff --git a/doc/7/controllers/security/delete-api-key/index.md b/doc/7/controllers/security/delete-api-key/index.md index 117fd16f5..003482cf6 100644 --- a/doc/7/controllers/security/delete-api-key/index.md +++ b/doc/7/controllers/security/delete-api-key/index.md @@ -2,7 +2,7 @@ code: true type: page title: deleteApiKey -description: Deletes an user API key. +description: Deletes a user's API key. --- # deleteApiKey @@ -11,7 +11,7 @@ description: Deletes an user API key. -Deletes an user API key. +Deletes a user's API key.