diff --git a/src/lib/services/api-token-service.test.ts b/src/lib/services/api-token-service.test.ts index c2227bbed6..162e091f6a 100644 --- a/src/lib/services/api-token-service.test.ts +++ b/src/lib/services/api-token-service.test.ts @@ -3,6 +3,7 @@ import { createTestConfig } from '../../test/config/test-config'; import { IUnleashConfig } from '../server-impl'; import { ApiTokenType } from '../types/models/api-token'; import FakeApiTokenStore from '../../test/fixtures/fake-api-token-store'; +import FakeEnvironmentStore from '../../test/fixtures/fake-environment-store'; test('Should init api token', async () => { const token = { @@ -19,11 +20,12 @@ test('Should init api token', async () => { }, }); const apiTokenStore = new FakeApiTokenStore(); + const environmentStore = new FakeEnvironmentStore(); const insertCalled = new Promise((resolve) => { apiTokenStore.on('insert', resolve); }); - new ApiTokenService({ apiTokenStore }, config); + new ApiTokenService({ apiTokenStore, environmentStore }, config); await insertCalled; diff --git a/src/lib/services/api-token-service.ts b/src/lib/services/api-token-service.ts index 7c1936a58f..ec8f590e99 100644 --- a/src/lib/services/api-token-service.ts +++ b/src/lib/services/api-token-service.ts @@ -9,15 +9,19 @@ import { IApiToken, IApiTokenCreate, validateApiToken, + validateApiTokenEnvironment, } from '../types/models/api-token'; import { IApiTokenStore } from '../types/stores/api-token-store'; import { FOREIGN_KEY_VIOLATION } from '../error/db-error'; import BadDataError from '../error/bad-data-error'; import { minutesToMilliseconds } from 'date-fns'; +import { IEnvironmentStore } from 'lib/types/stores/environment-store'; export class ApiTokenService { private store: IApiTokenStore; + private environmentStore: IEnvironmentStore; + private logger: Logger; private timer: NodeJS.Timeout; @@ -25,10 +29,14 @@ export class ApiTokenService { private activeTokens: IApiToken[] = []; constructor( - { apiTokenStore }: Pick, + { + apiTokenStore, + environmentStore, + }: Pick, config: Pick, ) { this.store = apiTokenStore; + this.environmentStore = environmentStore; this.logger = config.getLogger('/services/api-token-service.ts'); this.fetchActiveTokens(); this.timer = setInterval( @@ -105,6 +113,9 @@ export class ApiTokenService { ): Promise { validateApiToken(newToken); + const environments = await this.environmentStore.getAll(); + validateApiTokenEnvironment(newToken, environments); + const secret = this.generateSecretKey(newToken); const createNewToken = { ...newToken, secret }; return this.insertNewApiToken(createNewToken); diff --git a/src/lib/types/models/api-token.ts b/src/lib/types/models/api-token.ts index e69f2a8195..486ee7de9a 100644 --- a/src/lib/types/models/api-token.ts +++ b/src/lib/types/models/api-token.ts @@ -1,4 +1,5 @@ import BadDataError from '../../error/bad-data-error'; +import { IEnvironment } from '../model'; export const ALL = '*'; @@ -46,3 +47,26 @@ export const validateApiToken = ({ ); } }; + +export const validateApiTokenEnvironment = ( + { environment }: Pick, + environments: IEnvironment[], +): void => { + if (environment === ALL) { + return; + } + + const selectedEnvironment = environments.find( + (env) => env.name === environment, + ); + + if (!selectedEnvironment) { + throw new BadDataError(`Environment=${environment} does not exist`); + } + + if (!selectedEnvironment.enabled) { + throw new BadDataError( + 'Client token cannot be scoped to disabled environments', + ); + } +}; diff --git a/src/test/e2e/api/admin/api-token.e2e.test.ts b/src/test/e2e/api/admin/api-token.e2e.test.ts index 8bf0e28a91..c01a5d1c4b 100644 --- a/src/test/e2e/api/admin/api-token.e2e.test.ts +++ b/src/test/e2e/api/admin/api-token.e2e.test.ts @@ -331,3 +331,21 @@ test('client tokens cannot span all environments', async () => { .set('Content-Type', 'application/json') .expect(400); }); + +test('should not create token for disabled environment', async () => { + await db.stores.environmentStore.create({ + name: 'disabledEnvironment', + type: 'production', + enabled: false, + }); + return app.request + .post('/api/admin/api-tokens') + .send({ + username: 'default', + type: 'client', + project: 'default', + environment: 'disabledEnvironment', + }) + .set('Content-Type', 'application/json') + .expect(400); +});