mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 15:13:21 +00:00
Merge pull request #14230 from strapi/api-token-v2/restrict-lifespans
Api token v2/restrict lifespans
This commit is contained in:
commit
4bb8e1be34
@ -3,6 +3,7 @@
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
const { omit } = require('lodash/fp');
|
||||
const createContext = require('../../../../../../test/helpers/create-context');
|
||||
const constants = require('../../services/constants');
|
||||
const apiTokenController = require('../api-token');
|
||||
|
||||
describe('API Token Controller', () => {
|
||||
@ -65,8 +66,8 @@ describe('API Token Controller', () => {
|
||||
expect(created).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Create API Token with lifespan', async () => {
|
||||
const lifespan = 90 * 24 * 60 * 60 * 1000; // 90 days
|
||||
test('Create API Token with valid lifespan', async () => {
|
||||
const lifespan = constants.API_TOKEN_LIFESPANS.DAYS_7;
|
||||
const createBody = {
|
||||
...body,
|
||||
lifespan,
|
||||
@ -103,6 +104,34 @@ describe('API Token Controller', () => {
|
||||
});
|
||||
|
||||
test('Throws with invalid lifespan', async () => {
|
||||
const lifespan = 1235; // not in constants.API_TOKEN_LIFESPANS
|
||||
const createBody = {
|
||||
...body,
|
||||
lifespan,
|
||||
};
|
||||
|
||||
const create = jest.fn();
|
||||
const created = jest.fn();
|
||||
const ctx = createContext({ body: createBody }, { created });
|
||||
|
||||
global.strapi = {
|
||||
admin: {
|
||||
services: {
|
||||
'api-token': {
|
||||
create,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(async () => {
|
||||
await apiTokenController.create(ctx);
|
||||
}).rejects.toThrow(/lifespan must be one of the following values/);
|
||||
expect(create).not.toHaveBeenCalled();
|
||||
expect(created).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Throws with negative lifespan', async () => {
|
||||
const lifespan = -1;
|
||||
const createBody = {
|
||||
...body,
|
||||
@ -125,13 +154,14 @@ describe('API Token Controller', () => {
|
||||
|
||||
expect(async () => {
|
||||
await apiTokenController.create(ctx);
|
||||
}).rejects.toThrow('lifespan must be greater than or equal to 1');
|
||||
}).rejects.toThrow(/lifespan must be one of the following values/);
|
||||
expect(create).not.toHaveBeenCalled();
|
||||
expect(created).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Ignores a received expiresAt', async () => {
|
||||
const lifespan = 90 * 24 * 60 * 60 * 1000; // 90 days
|
||||
const lifespan = constants.API_TOKEN_LIFESPANS.DAYS_7;
|
||||
|
||||
const createBody = {
|
||||
...body,
|
||||
expiresAt: 1234,
|
||||
|
||||
@ -4,6 +4,7 @@ const { NotFoundError } = require('@strapi/utils/lib/errors');
|
||||
const crypto = require('crypto');
|
||||
const { omit, uniq } = require('lodash/fp');
|
||||
const apiTokenService = require('../api-token');
|
||||
const constants = require('../constants');
|
||||
|
||||
describe('API Token', () => {
|
||||
const mockedApiToken = {
|
||||
@ -70,7 +71,7 @@ describe('API Token', () => {
|
||||
name: 'api-token_tests-name',
|
||||
description: 'api-token_tests-description',
|
||||
type: 'read-only',
|
||||
lifespan: 123456,
|
||||
lifespan: constants.API_TOKEN_LIFESPANS.DAYS_90,
|
||||
};
|
||||
|
||||
const expectedExpires = Date.now() + attributes.lifespan;
|
||||
@ -106,6 +107,31 @@ describe('API Token', () => {
|
||||
expect(res.expiresAt).toBe(expectedExpires);
|
||||
});
|
||||
|
||||
test('It throws when creating a token with invalid lifespan', async () => {
|
||||
const attributes = {
|
||||
name: 'api-token_tests-name',
|
||||
description: 'api-token_tests-description',
|
||||
type: 'read-only',
|
||||
lifespan: 12345,
|
||||
};
|
||||
|
||||
const create = jest.fn(({ data }) => Promise.resolve(data));
|
||||
global.strapi = {
|
||||
query() {
|
||||
return { create };
|
||||
},
|
||||
config: {
|
||||
get: jest.fn(() => ''),
|
||||
},
|
||||
};
|
||||
|
||||
expect(async () => {
|
||||
await apiTokenService.create(attributes);
|
||||
}).rejects.toThrow(/lifespan/);
|
||||
|
||||
expect(create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Creates a custom token', async () => {
|
||||
const attributes = {
|
||||
name: 'api-token_tests-name',
|
||||
|
||||
@ -67,6 +67,24 @@ const assertCustomTokenPermissionsValidity = (attributes) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Assert that a token's permissions attribute is valid for its type
|
||||
*
|
||||
* @param {ApiToken} token
|
||||
*/
|
||||
const assertValidLifespan = ({ lifespan }) => {
|
||||
if (isNil(lifespan)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Object.values(constants.API_TOKEN_LIFESPANS).includes(lifespan)) {
|
||||
throw new ValidationError(
|
||||
`lifespan must be one of the following values:
|
||||
${Object.values(constants.API_TOKEN_LIFESPANS).join(', ')}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Flatten a token's database permissions objects to an array of strings
|
||||
*
|
||||
@ -173,6 +191,7 @@ const create = async (attributes) => {
|
||||
const accessKey = crypto.randomBytes(128).toString('hex');
|
||||
|
||||
assertCustomTokenPermissionsValidity(attributes);
|
||||
assertValidLifespan(attributes);
|
||||
|
||||
// Create the token
|
||||
const apiToken = await strapi.query('admin::api-token').create({
|
||||
@ -348,6 +367,8 @@ const update = async (id, attributes) => {
|
||||
});
|
||||
}
|
||||
|
||||
assertValidLifespan(attributes);
|
||||
|
||||
const updatedToken = await strapi.query('admin::api-token').update({
|
||||
select: SELECT_FIELDS,
|
||||
populate: POPULATE_FIELDS,
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
module.exports = {
|
||||
CONTENT_TYPE_SECTION: 'contentTypes',
|
||||
SUPER_ADMIN_CODE: 'strapi-super-admin',
|
||||
@ -15,4 +17,11 @@ module.exports = {
|
||||
FULL_ACCESS: 'full-access',
|
||||
CUSTOM: 'custom',
|
||||
},
|
||||
// The front-end only displays these values
|
||||
API_TOKEN_LIFESPANS: {
|
||||
UNLIMITED: null,
|
||||
DAYS_7: 7 * DAY_IN_MS,
|
||||
DAYS_30: 30 * DAY_IN_MS,
|
||||
DAYS_90: 90 * DAY_IN_MS,
|
||||
},
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
const { omit } = require('lodash');
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
const constants = require('../services/constants');
|
||||
|
||||
describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
let rq;
|
||||
@ -265,11 +266,11 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
error: {
|
||||
status: 400,
|
||||
name: 'ValidationError',
|
||||
message: 'lifespan must be greater than or equal to 1',
|
||||
message: expect.stringContaining('lifespan must be one of the following values'),
|
||||
details: {
|
||||
errors: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
message: 'lifespan must be greater than or equal to 1',
|
||||
message: expect.stringContaining('lifespan must be one of the following values'),
|
||||
name: 'ValidationError',
|
||||
}),
|
||||
]),
|
||||
@ -459,7 +460,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
);
|
||||
tokens.push(await createValidToken({ type: 'full-access' }));
|
||||
tokens.push(await createValidToken({ type: 'read-only' }));
|
||||
tokens.push(await createValidToken({ lifespan: 12345 }));
|
||||
tokens.push(await createValidToken({ lifespan: constants.API_TOKEN_LIFESPANS.DAYS_7 }));
|
||||
tokens.push(await createValidToken());
|
||||
|
||||
const res = await rq({
|
||||
|
||||
@ -10,7 +10,12 @@ const apiTokenCreationSchema = yup
|
||||
description: yup.string().optional(),
|
||||
type: yup.string().oneOf(Object.values(constants.API_TOKEN_TYPE)).required(),
|
||||
permissions: yup.array().of(yup.string()).nullable(),
|
||||
lifespan: yup.number().integer().min(1).nullable(),
|
||||
lifespan: yup
|
||||
.number()
|
||||
.integer()
|
||||
.min(1)
|
||||
.oneOf(Object.values(constants.API_TOKEN_LIFESPANS))
|
||||
.nullable(),
|
||||
})
|
||||
.noUnknown()
|
||||
.strict();
|
||||
@ -22,7 +27,12 @@ const apiTokenUpdateSchema = yup
|
||||
description: yup.string().nullable(),
|
||||
type: yup.string().oneOf(Object.values(constants.API_TOKEN_TYPE)).notNull(),
|
||||
permissions: yup.array().of(yup.string()).nullable(),
|
||||
lifespan: yup.number().integer().min(1).nullable(),
|
||||
lifespan: yup
|
||||
.number()
|
||||
.integer()
|
||||
.min(1)
|
||||
.oneOf(Object.values(constants.API_TOKEN_LIFESPANS))
|
||||
.nullable(),
|
||||
})
|
||||
.noUnknown()
|
||||
.strict();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user