diff --git a/packages/core/admin/server/controllers/api-token.js b/packages/core/admin/server/controllers/api-token.js index 9f82f17e75..71b719f97c 100644 --- a/packages/core/admin/server/controllers/api-token.js +++ b/packages/core/admin/server/controllers/api-token.js @@ -38,6 +38,21 @@ module.exports = { ctx.created({ data: apiToken }); }, + async regenerate(ctx) { + const { id } = ctx.params; + const apiTokenService = getService('api-token'); + + const apiTokenExists = await apiTokenService.getById(id); + if (!apiTokenExists) { + ctx.notFound('API Token not found'); + return; + } + + const accessToken = await apiTokenService.regenerate(id); + + ctx.created({ data: accessToken }); + }, + async list(ctx) { const apiTokenService = getService('api-token'); const apiTokens = await apiTokenService.list(); @@ -60,7 +75,6 @@ module.exports = { if (!apiToken) { ctx.notFound('API Token not found'); - return; } diff --git a/packages/core/admin/server/routes/api-tokens.js b/packages/core/admin/server/routes/api-tokens.js index 30147b0a32..e994382db6 100644 --- a/packages/core/admin/server/routes/api-tokens.js +++ b/packages/core/admin/server/routes/api-tokens.js @@ -56,4 +56,15 @@ module.exports = [ ], }, }, + { + method: 'POST', + path: '/api-tokens/:id/regenerate', + handler: 'api-token.regenerate', + config: { + policies: [ + 'admin::isAuthenticatedAdmin', + { name: 'admin::hasPermissions', config: { actions: ['admin::api-tokens.update'] } }, + ], + }, + }, ]; diff --git a/packages/core/admin/server/services/__tests__/api-token.test.js b/packages/core/admin/server/services/__tests__/api-token.test.js index c32d10dae9..c9554ba92a 100644 --- a/packages/core/admin/server/services/__tests__/api-token.test.js +++ b/packages/core/admin/server/services/__tests__/api-token.test.js @@ -256,6 +256,33 @@ describe('API Token', () => { }); }); + describe('regenerate', () => { + test('It regenerates the accessKey', async () => { + const update = jest.fn(({ data }) => Promise.resolve(data)); + + global.strapi = { + query() { + return { update }; + }, + config: { + get: jest.fn(() => ''), + }, + }; + + const id = 1; + const res = await apiTokenService.regenerate(id); + + expect(update).toHaveBeenCalledWith({ + where: { id }, + select: ['id', 'accessKey'], + data: { + accessKey: apiTokenService.hash(mockedApiToken.hexedString), + }, + }); + expect(res).toEqual({ accessKey: mockedApiToken.hexedString }); + }); + }); + describe('update', () => { test('Updates a non-custom token', async () => { const token = { diff --git a/packages/core/admin/server/services/api-token.js b/packages/core/admin/server/services/api-token.js index 005763c6bf..6b5ce9bbbd 100644 --- a/packages/core/admin/server/services/api-token.js +++ b/packages/core/admin/server/services/api-token.js @@ -161,6 +161,28 @@ const create = async (attributes) => { return result; }; +/** + * @param {string|number} id + * + * @returns {Promise} + */ +const regenerate = async (id) => { + const accessKey = crypto.randomBytes(128).toString('hex'); + + const apiToken = await strapi.query('admin::api-token').update({ + select: ['id', 'accessKey'], + where: { id }, + data: { + accessKey: hash(accessKey), + }, + }); + + return { + ...apiToken, + accessKey, + }; +}; + /** * @returns {void} */ @@ -372,6 +394,7 @@ const mapTokenPermissions = (token) => { module.exports = { create, + regenerate, exists, checkSaltIsDefined, hash, diff --git a/packages/core/admin/server/tests/admin-api-token.test.e2e.js b/packages/core/admin/server/tests/admin-api-token.test.e2e.js index be002a96d9..7f3ac4c699 100644 --- a/packages/core/admin/server/tests/admin-api-token.test.e2e.js +++ b/packages/core/admin/server/tests/admin-api-token.test.e2e.js @@ -609,6 +609,23 @@ describe('Admin API Token v2 CRUD (e2e)', () => { }); }); + test('Regenerates an api token access key', async () => { + const token = await createValidToken(); + + const res = await rq({ + url: `/admin/api-tokens/${token.id}/regenerate`, + method: 'POST', + }); + + expect(res.statusCode).toBe(201); + expect(res.body.data).toMatchObject({ + accessKey: expect.any(String), + }); + }); + + test.todo('Regenerated access key works'); + test.todo('Tokens access content for which they are authorized'); + test.todo('Tokens fail to access content for which they are not authorized'); test.todo('Sets expiration time correctly'); test.todo("Doesn't have expiration if not set"); });