mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore(apitoken)!: remove ILegacyApiTokenCreate (#10072)
This commit is contained in:
		
							parent
							
								
									37548c3436
								
							
						
					
					
						commit
						1aadbb3641
					
				| @ -27,7 +27,7 @@ test('should create default config', async () => { | ||||
| test('should add initApiToken for admin token from options', async () => { | ||||
|     const token = { | ||||
|         environment: '*', | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|         secret: '*:*.some-random-string', | ||||
|         type: ApiTokenType.ADMIN, | ||||
|         tokenName: 'admin', | ||||
| @ -52,7 +52,9 @@ test('should add initApiToken for admin token from options', async () => { | ||||
|     expect(config.authentication.initApiTokens[0].environment).toBe( | ||||
|         token.environment, | ||||
|     ); | ||||
|     expect(config.authentication.initApiTokens[0].project).toBe(token.project); | ||||
|     expect(config.authentication.initApiTokens[0].projects).toMatchObject( | ||||
|         token.projects, | ||||
|     ); | ||||
|     expect(config.authentication.initApiTokens[0].type).toBe( | ||||
|         ApiTokenType.ADMIN, | ||||
|     ); | ||||
| @ -61,7 +63,7 @@ test('should add initApiToken for admin token from options', async () => { | ||||
| test('should add initApiToken for client token from options', async () => { | ||||
|     const token = { | ||||
|         environment: 'development', | ||||
|         project: 'default', | ||||
|         projects: ['default'], | ||||
|         secret: 'default:development.some-random-string', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName: 'admin', | ||||
| @ -86,7 +88,9 @@ test('should add initApiToken for client token from options', async () => { | ||||
|     expect(config.authentication.initApiTokens[0].environment).toBe( | ||||
|         token.environment, | ||||
|     ); | ||||
|     expect(config.authentication.initApiTokens[0].project).toBe(token.project); | ||||
|     expect(config.authentication.initApiTokens[0].projects).toMatchObject( | ||||
|         token.projects, | ||||
|     ); | ||||
|     expect(config.authentication.initApiTokens[0].type).toBe( | ||||
|         ApiTokenType.CLIENT, | ||||
|     ); | ||||
| @ -110,7 +114,9 @@ test('should add initApiToken for admin token from env var', async () => { | ||||
| 
 | ||||
|     expect(config.authentication.initApiTokens).toHaveLength(2); | ||||
|     expect(config.authentication.initApiTokens[0].environment).toBe('*'); | ||||
|     expect(config.authentication.initApiTokens[0].project).toBe('*'); | ||||
|     expect(config.authentication.initApiTokens[0].projects).toMatchObject([ | ||||
|         '*', | ||||
|     ]); | ||||
|     expect(config.authentication.initApiTokens[0].type).toBe( | ||||
|         ApiTokenType.ADMIN, | ||||
|     ); | ||||
| @ -146,7 +152,7 @@ test('should merge initApiToken from options and env vars', async () => { | ||||
|     process.env.INIT_CLIENT_API_TOKENS = 'default:development.some-token1'; | ||||
|     const token = { | ||||
|         environment: '*', | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|         secret: '*:*.some-random-string', | ||||
|         type: ApiTokenType.ADMIN, | ||||
|         tokenName: 'admin', | ||||
| @ -193,7 +199,9 @@ test('should add initApiToken for client token from env var', async () => { | ||||
|     expect(config.authentication.initApiTokens[0].environment).toBe( | ||||
|         'development', | ||||
|     ); | ||||
|     expect(config.authentication.initApiTokens[0].project).toBe('default'); | ||||
|     expect(config.authentication.initApiTokens[0].projects).toMatchObject([ | ||||
|         'default', | ||||
|     ]); | ||||
|     expect(config.authentication.initApiTokens[0].type).toBe( | ||||
|         ApiTokenType.CLIENT, | ||||
|     ); | ||||
| @ -207,7 +215,7 @@ test('should add initApiToken for client token from env var', async () => { | ||||
| test('should handle cases where no env var specified for tokens', async () => { | ||||
|     const token = { | ||||
|         environment: '*', | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|         secret: '*:*.some-random-string', | ||||
|         type: ApiTokenType.ADMIN, | ||||
|         tokenName: 'admin', | ||||
| @ -506,7 +514,7 @@ test('create config should be idempotent in terms of tokens', async () => { | ||||
|     process.env.INIT_FRONTEND_API_TOKENS = 'frontend:development.some-token1'; | ||||
|     const token = { | ||||
|         environment: '*', | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|         secret: '*:*.some-random-string', | ||||
|         type: ApiTokenType.ADMIN, | ||||
|         tokenName: 'admin', | ||||
|  | ||||
| @ -37,7 +37,7 @@ import { | ||||
|     secondsToMilliseconds, | ||||
| } from 'date-fns'; | ||||
| import EventEmitter from 'events'; | ||||
| import { mapLegacyToken, validateApiToken } from './types/models/api-token.js'; | ||||
| import { validateApiToken } from './types/models/api-token.js'; | ||||
| import { | ||||
|     parseEnvVarBoolean, | ||||
|     parseEnvVarJSON, | ||||
| @ -417,13 +417,13 @@ const loadTokensFromString = ( | ||||
|         const [environment = '*'] = rest.split('.'); | ||||
|         const token = { | ||||
|             createdAt: undefined, | ||||
|             project, | ||||
|             projects: [project], | ||||
|             environment, | ||||
|             secret, | ||||
|             type: tokenType, | ||||
|             tokenName: 'admin', | ||||
|         }; | ||||
|         validateApiToken(mapLegacyToken(token)); | ||||
|         validateApiToken(token); | ||||
|         return token; | ||||
|     }); | ||||
|     return tokens; | ||||
|  | ||||
| @ -50,7 +50,6 @@ const tokenRowReducer = (acc, tokenRow) => { | ||||
|             createdAt: token.created_at, | ||||
|             alias: token.alias, | ||||
|             seenAt: token.seen_at, | ||||
|             username: token.token_name ? token.token_name : token.username, | ||||
|         }; | ||||
|     } | ||||
|     const currentToken = acc[tokenRow.secret]; | ||||
| @ -65,8 +64,8 @@ const tokenRowReducer = (acc, tokenRow) => { | ||||
| }; | ||||
| 
 | ||||
| const toRow = (newToken: IApiTokenCreate) => ({ | ||||
|     username: newToken.tokenName ?? newToken.username, | ||||
|     token_name: newToken.tokenName ?? newToken.username, | ||||
|     username: newToken.tokenName, | ||||
|     token_name: newToken.tokenName, | ||||
|     secret: newToken.secret, | ||||
|     type: newToken.type, | ||||
|     environment: | ||||
| @ -192,7 +191,6 @@ export class ApiTokenStore implements IApiTokenStore { | ||||
|             await Promise.all(updateProjectTasks); | ||||
|             return { | ||||
|                 ...newToken, | ||||
|                 username: newToken.tokenName, | ||||
|                 alias: newToken.alias || null, | ||||
|                 project: newToken.projects?.join(',') || '*', | ||||
|                 createdAt: row.created_at, | ||||
|  | ||||
| @ -2436,11 +2436,11 @@ test('should also delete api tokens that were only bound to deleted project', as | ||||
|         auditUser, | ||||
|     ); | ||||
| 
 | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName, | ||||
|         environment: DEFAULT_ENV, | ||||
|         project: project, | ||||
|         projects: [project], | ||||
|     }); | ||||
| 
 | ||||
|     await projectService.deleteProject(project, user, auditUser); | ||||
| @ -2471,7 +2471,7 @@ test('should not delete project-bound api tokens still bound to project', async | ||||
|         auditUser, | ||||
|     ); | ||||
| 
 | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName, | ||||
|         environment: DEFAULT_ENV, | ||||
| @ -2507,7 +2507,7 @@ test('should delete project-bound api tokens when all projects they belong to ar | ||||
|         auditUser, | ||||
|     ); | ||||
| 
 | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName, | ||||
|         environment: DEFAULT_ENV, | ||||
|  | ||||
| @ -4,7 +4,6 @@ import type { ApiTokenSchema } from './api-token-schema.js'; | ||||
| 
 | ||||
| const defaultData: ApiTokenSchema = { | ||||
|     secret: '', | ||||
|     username: '', | ||||
|     tokenName: '', | ||||
|     type: ApiTokenType.CLIENT, | ||||
|     environment: '', | ||||
|  | ||||
| @ -5,14 +5,7 @@ export const apiTokenSchema = { | ||||
|     $id: '#/components/schemas/apiTokenSchema', | ||||
|     type: 'object', | ||||
|     additionalProperties: false, | ||||
|     required: [ | ||||
|         'secret', | ||||
|         'tokenName', | ||||
|         'type', | ||||
|         'project', | ||||
|         'projects', | ||||
|         'createdAt', | ||||
|     ], | ||||
|     required: ['secret', 'tokenName', 'type', 'projects', 'createdAt'], | ||||
|     description: | ||||
|         'An overview of an [Unleash API token](https://docs.getunleash.io/reference/api-tokens-and-client-keys).', | ||||
|     properties: { | ||||
| @ -21,13 +14,6 @@ export const apiTokenSchema = { | ||||
|             description: 'The token used for authentication.', | ||||
|             example: 'project:environment.xyzrandomstring', | ||||
|         }, | ||||
|         username: { | ||||
|             type: 'string', | ||||
|             deprecated: true, | ||||
|             description: | ||||
|                 'This property was deprecated in Unleash v5. Prefer the `tokenName` property instead.', | ||||
|             example: 'a-name', | ||||
|         }, | ||||
|         tokenName: { | ||||
|             type: 'string', | ||||
|             description: 'A unique name for this particular token', | ||||
|  | ||||
| @ -1,17 +1,5 @@ | ||||
| import type { FromSchema } from 'json-schema-to-ts'; | ||||
| import { mergeAllOfs } from '../util/all-of.js'; | ||||
| const adminSchema = { | ||||
|     required: ['type'], | ||||
|     type: 'object', | ||||
|     properties: { | ||||
|         type: { | ||||
|             type: 'string', | ||||
|             pattern: '^[Aa][Dd][Mm][Ii][Nn]$', | ||||
|             description: `An admin token. Must be the string "admin" (not case sensitive).`, | ||||
|             example: 'admin', | ||||
|         }, | ||||
|     }, | ||||
| } as const; | ||||
| 
 | ||||
| const tokenNameSchema = { | ||||
|     type: 'object', | ||||
| @ -25,20 +13,6 @@ const tokenNameSchema = { | ||||
|     }, | ||||
| } as const; | ||||
| 
 | ||||
| const usernameSchema = { | ||||
|     type: 'object', | ||||
|     required: ['username'], | ||||
|     properties: { | ||||
|         username: { | ||||
|             deprecated: true, | ||||
|             type: 'string', | ||||
|             description: | ||||
|                 'The name of the token. This property was deprecated in v5. Use `tokenName` instead.', | ||||
|             example: 'token-64523', | ||||
|         }, | ||||
|     }, | ||||
| } as const; | ||||
| 
 | ||||
| const clientFrontendSchema = { | ||||
|     required: ['type'], | ||||
|     type: 'object', | ||||
| @ -100,12 +74,7 @@ export const createApiTokenSchema = { | ||||
|     type: 'object', | ||||
|     description: | ||||
|         'The data required to create an [Unleash API token](https://docs.getunleash.io/reference/api-tokens-and-client-keys).', | ||||
|     oneOf: [ | ||||
|         mergeAllOfs([expireSchema, adminSchema, tokenNameSchema]), | ||||
|         mergeAllOfs([expireSchema, adminSchema, usernameSchema]), | ||||
|         mergeAllOfs([expireSchema, clientFrontendSchema, tokenNameSchema]), | ||||
|         mergeAllOfs([expireSchema, clientFrontendSchema, usernameSchema]), | ||||
|     ], | ||||
|     oneOf: [mergeAllOfs([expireSchema, clientFrontendSchema, tokenNameSchema])], | ||||
|     components: {}, | ||||
| } as const; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										41
									
								
								src/lib/openapi/spec/create-project-api-token-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/lib/openapi/spec/create-project-api-token-schema.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| import type { FromSchema } from 'json-schema-to-ts'; | ||||
| 
 | ||||
| export const createProjectApiTokenSchema = { | ||||
|     type: 'object', | ||||
|     required: ['tokenName', 'type'], | ||||
|     $id: '#/components/schemas/createProjectApiTokenSchema', | ||||
|     description: | ||||
|         'The schema for creating a project API token. This schema is used to create a new project API token.', | ||||
|     properties: { | ||||
|         type: { | ||||
|             type: 'string', | ||||
|             pattern: | ||||
|                 '^([Cc][Ll][Ii][Ee][Nn][Tt]|[Ff][Rr][Oo][Nn][Tt][Ee][Nn][Dd])$', | ||||
|             description: `A client or frontend token. Must be one of the strings "client" or "frontend" (not case sensitive).`, | ||||
|             example: 'frontend', | ||||
|         }, | ||||
|         environment: { | ||||
|             type: 'string', | ||||
|             description: | ||||
|                 'The environment that the token should be valid for. Defaults to "default".', | ||||
|             example: 'development', | ||||
|             default: 'default', | ||||
|         }, | ||||
|         expiresAt: { | ||||
|             type: 'string', | ||||
|             description: | ||||
|                 'The date and time when the token should expire. The date should be in ISO 8601 format.', | ||||
|             example: '2023-10-01T00:00:00Z', | ||||
|             format: 'date-time', | ||||
|         }, | ||||
|         tokenName: { | ||||
|             type: 'string', | ||||
|             description: 'A unique name for this particular token', | ||||
|             example: 'some-user', | ||||
|         }, | ||||
|     }, | ||||
|     components: {}, | ||||
| } as const; | ||||
| export type CreateProjectApiTokenSchema = FromSchema< | ||||
|     typeof createProjectApiTokenSchema | ||||
| >; | ||||
| @ -53,6 +53,7 @@ export * from './create-feature-strategy-schema.js'; | ||||
| export * from './create-group-schema.js'; | ||||
| export * from './create-invited-user-schema.js'; | ||||
| export * from './create-pat-schema.js'; | ||||
| export * from './create-project-api-token-schema.js'; | ||||
| export * from './create-strategy-schema.js'; | ||||
| export * from './create-strategy-variant-schema.js'; | ||||
| export * from './create-tag-schema.js'; | ||||
|  | ||||
| @ -1,90 +0,0 @@ | ||||
| import permissions from '../../../test/fixtures/permissions.js'; | ||||
| import { createTestConfig } from '../../../test/config/test-config.js'; | ||||
| import createStores from '../../../test/fixtures/store.js'; | ||||
| import { createServices } from '../../services/index.js'; | ||||
| import getApp from '../../app.js'; | ||||
| import supertest from 'supertest'; | ||||
| import { addDays } from 'date-fns'; | ||||
| 
 | ||||
| async function getSetup() { | ||||
|     const base = `/random${Math.round(Math.random() * 1000)}`; | ||||
|     const perms = permissions(); | ||||
|     const config = createTestConfig({ | ||||
|         preHook: perms.hook, | ||||
|         server: { baseUriPath: base }, | ||||
|         //@ts-ignore - Just testing, so only need the isEnabled call here
 | ||||
|     }); | ||||
|     const stores = createStores(); | ||||
|     const services = createServices(stores, config); | ||||
| 
 | ||||
|     //@ts-expect-error: we're accessing a private field, but we need
 | ||||
|     //to set up an environment to test the functionality. Because we
 | ||||
|     //don't have a db to use, we need to access the service's store
 | ||||
|     //directly.
 | ||||
|     await services.apiTokenService.environmentStore.create({ | ||||
|         name: 'development', | ||||
|         type: 'development', | ||||
|         enabled: true, | ||||
|     }); | ||||
| 
 | ||||
|     const app = await getApp(config, stores, services); | ||||
| 
 | ||||
|     return { | ||||
|         base, | ||||
|         request: supertest(app), | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| describe('Admin token killswitch', () => { | ||||
|     test('If killswitch is on we will get an operation denied if we try to create an admin token', async () => { | ||||
|         const setup = await getSetup(); | ||||
|         return setup.request | ||||
|             .post(`${setup.base}/api/admin/api-tokens`) | ||||
|             .set('Content-Type', 'application/json') | ||||
|             .send({ | ||||
|                 expiresAt: addDays(new Date(), 60), | ||||
|                 type: 'ADMIN', | ||||
|                 tokenName: 'Killswitched', | ||||
|             }) | ||||
|             .expect(403) | ||||
|             .expect((res) => { | ||||
|                 expect(res.body.message).toBe( | ||||
|                     'Admin tokens are disabled in this instance. Use a Service account or a PAT to access admin operations instead', | ||||
|                 ); | ||||
|             }); | ||||
|     }); | ||||
|     test('If killswitch is on we can still create a client token', async () => { | ||||
|         const setup = await getSetup(); | ||||
|         return setup.request | ||||
|             .post(`${setup.base}/api/admin/api-tokens`) | ||||
|             .set('Content-Type', 'application/json') | ||||
|             .send({ | ||||
|                 expiresAt: addDays(new Date(), 60), | ||||
|                 type: 'CLIENT', | ||||
|                 environment: 'development', | ||||
|                 projects: ['*'], | ||||
|                 tokenName: 'Client', | ||||
|             }) | ||||
|             .expect(201) | ||||
|             .expect((res) => { | ||||
|                 expect(res.body.secret).toBeTruthy(); | ||||
|             }); | ||||
|     }); | ||||
|     test('If killswitch is on we can still create a frontend token', async () => { | ||||
|         const setup = await getSetup(); | ||||
|         return setup.request | ||||
|             .post(`${setup.base}/api/admin/api-tokens`) | ||||
|             .set('Content-Type', 'application/json') | ||||
|             .send({ | ||||
|                 expiresAt: addDays(new Date(), 60), | ||||
|                 type: 'FRONTEND', | ||||
|                 environment: 'development', | ||||
|                 projects: ['*'], | ||||
|                 tokenName: 'Frontend', | ||||
|             }) | ||||
|             .expect(201) | ||||
|             .expect((res) => { | ||||
|                 expect(res.body.secret).toBeTruthy(); | ||||
|             }); | ||||
|     }); | ||||
| }); | ||||
| @ -43,6 +43,7 @@ import { | ||||
| } from '../../openapi/util/standard-responses.js'; | ||||
| import type { FrontendApiService } from '../../features/frontend-api/frontend-api-service.js'; | ||||
| import { OperationDeniedError } from '../../error/index.js'; | ||||
| import type { CreateApiTokenSchema } from '../../internals.js'; | ||||
| 
 | ||||
| interface TokenParam { | ||||
|     token: string; | ||||
| @ -299,24 +300,20 @@ export class ApiTokenController extends Controller { | ||||
|     } | ||||
| 
 | ||||
|     async createApiToken( | ||||
|         req: IAuthRequest, | ||||
|         req: IAuthRequest<CreateApiTokenSchema>, | ||||
|         res: Response<ApiTokenSchema>, | ||||
|     ): Promise<any> { | ||||
|         const createToken = await createApiToken.validateAsync(req.body); | ||||
|         const permissionRequired = tokenTypeToCreatePermission( | ||||
|             createToken.type, | ||||
|         ); | ||||
|         if (createToken.type.toUpperCase() === 'ADMIN') { | ||||
|             throw new OperationDeniedError( | ||||
|                 `Admin tokens are disabled in this instance. Use a Service account or a PAT to access admin operations instead`, | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         const hasPermission = await this.accessService.hasPermission( | ||||
|             req.user, | ||||
|             permissionRequired, | ||||
|         ); | ||||
|         if (hasPermission) { | ||||
|             const token = await this.apiTokenService.createApiToken( | ||||
|             const token = await this.apiTokenService.createApiTokenWithProjects( | ||||
|                 createToken, | ||||
|                 req.audit, | ||||
|             ); | ||||
|  | ||||
| @ -32,8 +32,9 @@ import Controller from '../../controller.js'; | ||||
| import type { Logger } from '../../../logger.js'; | ||||
| import type { Response } from 'express'; | ||||
| import { timingSafeEqual } from 'crypto'; | ||||
| import { createApiToken } from '../../../schema/api-token-schema.js'; | ||||
| import { OperationDeniedError } from '../../../error/index.js'; | ||||
| import type { CreateProjectApiTokenSchema } from '../../../openapi/spec/create-project-api-token-schema.js'; | ||||
| import { createProjectApiToken } from '../../../schema/create-project-api-token-schema.js'; | ||||
| 
 | ||||
| interface ProjectTokenParam { | ||||
|     token: string; | ||||
| @ -109,7 +110,9 @@ export class ProjectApiTokenController extends Controller { | ||||
|                 openApiService.validPath({ | ||||
|                     tags: ['Projects'], | ||||
|                     operationId: 'createProjectApiToken', | ||||
|                     requestBody: createRequestSchema('createApiTokenSchema'), | ||||
|                     requestBody: createRequestSchema( | ||||
|                         'createProjectApiTokenSchema', | ||||
|                     ), | ||||
|                     summary: 'Create a project API token.', | ||||
|                     description: | ||||
|                         'Endpoint that allows creation of [project API tokens](https://docs.getunleash.io/reference/api-tokens-and-client-keys#api-token-visibility) for the specified project.', | ||||
| @ -160,10 +163,10 @@ export class ProjectApiTokenController extends Controller { | ||||
|     } | ||||
| 
 | ||||
|     async createProjectApiToken( | ||||
|         req: IAuthRequest, | ||||
|         req: IAuthRequest<{ projectId: string }, CreateProjectApiTokenSchema>, | ||||
|         res: Response<ApiTokenSchema>, | ||||
|     ): Promise<any> { | ||||
|         const createToken = await createApiToken.validateAsync(req.body); | ||||
|         const createToken = await createProjectApiToken.validateAsync(req.body); | ||||
|         const { projectId } = req.params; | ||||
|         await this.projectService.getProject(projectId); // Validates that the project exists
 | ||||
| 
 | ||||
| @ -178,30 +181,17 @@ export class ProjectApiTokenController extends Controller { | ||||
|                 `You don't have the necessary access [${permissionRequired}] to perform this operation]`, | ||||
|             ); | ||||
|         } | ||||
|         if (!createToken.project) { | ||||
|             createToken.project = projectId; | ||||
|         } | ||||
| 
 | ||||
|         if ( | ||||
|             createToken.projects.length === 1 && | ||||
|             createToken.projects[0] === projectId | ||||
|         ) { | ||||
|             const token = await this.apiTokenService.createApiToken( | ||||
|                 createToken, | ||||
|                 req.audit, | ||||
|             ); | ||||
|             this.openApiService.respondWithValidation( | ||||
|                 201, | ||||
|                 res, | ||||
|                 apiTokenSchema.$id, | ||||
|                 serializeDates(token), | ||||
|                 { location: `api-tokens` }, | ||||
|             ); | ||||
|         } else { | ||||
|             res.statusMessage = | ||||
|                 'Project level tokens can only be created for one project'; | ||||
|             res.status(400); | ||||
|         } | ||||
|         const token = await this.apiTokenService.createApiTokenWithProjects( | ||||
|             { ...createToken, projects: [projectId] }, | ||||
|             req.audit, | ||||
|         ); | ||||
|         this.openApiService.respondWithValidation( | ||||
|             201, | ||||
|             res, | ||||
|             apiTokenSchema.$id, | ||||
|             serializeDates(token), | ||||
|             { location: `api-tokens` }, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     async deleteProjectApiToken( | ||||
|  | ||||
| @ -1,51 +1,50 @@ | ||||
| import { ALL } from '../types/models/api-token.js'; | ||||
| import { createApiToken } from './api-token-schema.js'; | ||||
| 
 | ||||
| test('should reject token with projects and project', async () => { | ||||
|     expect.assertions(1); | ||||
| test('should ignore token extra project field', async () => { | ||||
|     expect.assertions(0); | ||||
|     try { | ||||
|         await createApiToken.validateAsync({ | ||||
|             username: 'test', | ||||
|             type: 'admin', | ||||
|             tokenName: 'test', | ||||
|             type: 'client', | ||||
|             project: 'default', | ||||
|             projects: ['default'], | ||||
|         }); | ||||
|     } catch (error) { | ||||
|         expect(error.details[0].message).toEqual( | ||||
|             '"project" must not exist simultaneously with [projects]', | ||||
|         ); | ||||
|         expect(error).toBeUndefined(); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| test('should not have default project set if projects is present', async () => { | ||||
|     const token = await createApiToken.validateAsync({ | ||||
|         username: 'test', | ||||
|         type: 'admin', | ||||
|         tokenName: 'test', | ||||
|         type: 'client', | ||||
|         projects: ['default'], | ||||
|     }); | ||||
|     expect(token.project).not.toBeDefined(); | ||||
| }); | ||||
| 
 | ||||
| test('should have project set to default if projects is missing', async () => { | ||||
| test('should have a projects entry consisting of ALL if projects is missing', async () => { | ||||
|     const token = await createApiToken.validateAsync({ | ||||
|         username: 'test', | ||||
|         type: 'admin', | ||||
|         tokenName: 'test', | ||||
|         type: 'client', | ||||
|     }); | ||||
|     expect(token.project).toBe(ALL); | ||||
|     expect(token.projects).toMatchObject([ALL]); | ||||
| }); | ||||
| 
 | ||||
| test('should not have projects set if project is present', async () => { | ||||
| test('should not have project set after validation if project is present', async () => { | ||||
|     const token = await createApiToken.validateAsync({ | ||||
|         username: 'test', | ||||
|         type: 'admin', | ||||
|         tokenName: 'test', | ||||
|         type: 'client', | ||||
|         project: 'default', | ||||
|     }); | ||||
|     expect(token.projects).not.toBeDefined(); | ||||
|     expect(token.project).not.toBeDefined(); | ||||
|     expect(token.projects).toMatchObject([ALL]); | ||||
| }); | ||||
| 
 | ||||
| test('should allow for embedded proxy (frontend) key', async () => { | ||||
|     const token = await createApiToken.validateAsync({ | ||||
|         username: 'test', | ||||
|         tokenName: 'test', | ||||
|         type: 'frontend', | ||||
|         project: 'default', | ||||
|     }); | ||||
| @ -54,9 +53,8 @@ test('should allow for embedded proxy (frontend) key', async () => { | ||||
| 
 | ||||
| test('should set environment to default for frontend key', async () => { | ||||
|     const token = await createApiToken.validateAsync({ | ||||
|         username: 'test', | ||||
|         tokenName: 'test', | ||||
|         type: 'frontend', | ||||
|         project: 'default', | ||||
|     }); | ||||
|     expect(token.environment).toEqual('default'); | ||||
| }); | ||||
|  | ||||
| @ -6,29 +6,18 @@ import { DEFAULT_ENV } from '../util/constants.js'; | ||||
| export const createApiToken = joi | ||||
|     .object() | ||||
|     .keys({ | ||||
|         username: joi.string().optional(), | ||||
|         tokenName: joi.string().optional(), | ||||
|         tokenName: joi.string().required(), | ||||
|         type: joi | ||||
|             .string() | ||||
|             .lowercase() | ||||
|             .required() | ||||
|             .valid( | ||||
|                 ApiTokenType.ADMIN, | ||||
|                 ApiTokenType.CLIENT, | ||||
|                 ApiTokenType.FRONTEND, | ||||
|             ), | ||||
|             .valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND), | ||||
|         expiresAt: joi.date().optional(), | ||||
|         project: joi.when('projects', { | ||||
|             not: joi.required(), | ||||
|             then: joi.string().optional().default(ALL), | ||||
|         }), | ||||
|         projects: joi.array().min(0).optional(), | ||||
|         projects: joi.array().min(1).optional().default([ALL]), | ||||
|         environment: joi.when('type', { | ||||
|             is: joi.string().valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND), | ||||
|             then: joi.string().optional().default(DEFAULT_ENV), | ||||
|             otherwise: joi.string().optional().default(ALL), | ||||
|         }), | ||||
|     }) | ||||
|     .nand('username', 'tokenName') | ||||
|     .nand('project', 'projects') | ||||
|     .options({ stripUnknown: true, allowUnknown: false, abortEarly: false }); | ||||
|  | ||||
							
								
								
									
										20
									
								
								src/lib/schema/create-project-api-token-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/lib/schema/create-project-api-token-schema.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| import joi from 'joi'; | ||||
| import { ApiTokenType } from '../types/model.js'; | ||||
| import { DEFAULT_ENV } from '../util/constants.js'; | ||||
| 
 | ||||
| export const createProjectApiToken = joi | ||||
|     .object() | ||||
|     .keys({ | ||||
|         tokenName: joi.string().required(), | ||||
|         type: joi | ||||
|             .string() | ||||
|             .lowercase() | ||||
|             .required() | ||||
|             .valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND), | ||||
|         expiresAt: joi.date().optional(), | ||||
|         environment: joi.when('type', { | ||||
|             is: joi.string().valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND), | ||||
|             then: joi.string().optional().default(DEFAULT_ENV), | ||||
|         }), | ||||
|     }) | ||||
|     .options({ stripUnknown: true, allowUnknown: false, abortEarly: false }); | ||||
| @ -19,7 +19,7 @@ import { vi } from 'vitest'; | ||||
| test('Should init api token', async () => { | ||||
|     const token = { | ||||
|         environment: '*', | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|         secret: '*:*:some-random-string', | ||||
|         type: ApiTokenType.ADMIN, | ||||
|         tokenName: 'admin', | ||||
|  | ||||
| @ -5,11 +5,9 @@ import type { IUnleashStores } from '../types/stores.js'; | ||||
| import type { IUnleashConfig } from '../types/option.js'; | ||||
| import ApiUser, { type IApiUser } from '../types/api-user.js'; | ||||
| import { | ||||
|     type ILegacyApiTokenCreate, | ||||
|     resolveValidProjects, | ||||
|     validateApiToken, | ||||
|     validateApiTokenEnvironment, | ||||
|     mapLegacyToken, | ||||
|     mapLegacyTokenWithSecret, | ||||
| } from '../types/models/api-token.js'; | ||||
| import type { IApiTokenStore } from '../types/stores/api-token-store.js'; | ||||
| import { FOREIGN_KEY_VIOLATION } from '../error/db-error.js'; | ||||
| @ -194,7 +192,7 @@ export class ApiTokenService { | ||||
|         return this.store.getAll(); | ||||
|     } | ||||
| 
 | ||||
|     async initApiTokens(tokens: ILegacyApiTokenCreate[]) { | ||||
|     async initApiTokens(tokens: IApiTokenCreate[]) { | ||||
|         const tokenCount = await this.store.count(); | ||||
|         if (tokenCount > 0) { | ||||
|             this.logger.debug( | ||||
| @ -203,9 +201,9 @@ export class ApiTokenService { | ||||
|             return; | ||||
|         } | ||||
|         try { | ||||
|             const createAll = tokens | ||||
|                 .map(mapLegacyTokenWithSecret) | ||||
|                 .map((t) => this.insertNewApiToken(t, SYSTEM_USER_AUDIT)); | ||||
|             const createAll = tokens.map((t) => | ||||
|                 this.insertNewApiToken(t, SYSTEM_USER_AUDIT), | ||||
|             ); | ||||
|             await Promise.all(createAll); | ||||
|             this.logger.info( | ||||
|                 `Created initial API tokens: ${tokens.map((t) => `(name: ${t.tokenName}, type: ${t.type})`).join(', ')}`, | ||||
| @ -273,17 +271,6 @@ export class ApiTokenService { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @deprecated This may be removed in a future release, prefer createApiTokenWithProjects | ||||
|      */ | ||||
|     public async createApiToken( | ||||
|         newToken: Omit<ILegacyApiTokenCreate, 'secret'>, | ||||
|         auditUser: IAuditUser = SYSTEM_USER_AUDIT, | ||||
|     ): Promise<IApiToken> { | ||||
|         const token = mapLegacyToken(newToken); | ||||
|         return this.internalCreateApiTokenWithProjects(token, auditUser); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param newToken | ||||
|      * @param createdBy should be IApiUser or IUser. Still supports optional or string for backward compatibility | ||||
| @ -293,7 +280,13 @@ export class ApiTokenService { | ||||
|         newToken: Omit<IApiTokenCreate, 'secret'>, | ||||
|         auditUser: IAuditUser = SYSTEM_USER_AUDIT, | ||||
|     ): Promise<IApiToken> { | ||||
|         return this.internalCreateApiTokenWithProjects(newToken, auditUser); | ||||
|         return this.internalCreateApiTokenWithProjects( | ||||
|             { | ||||
|                 ...newToken, | ||||
|                 projects: resolveValidProjects(newToken.projects), | ||||
|             }, | ||||
|             auditUser, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private async internalCreateApiTokenWithProjects( | ||||
|  | ||||
| @ -210,10 +210,6 @@ export interface IApiTokenCreate { | ||||
|     environment: string; | ||||
|     projects: string[]; | ||||
|     expiresAt?: Date; | ||||
|     /** | ||||
|      * @deprecated Use tokenName instead | ||||
|      */ | ||||
|     username?: string; | ||||
| } | ||||
| 
 | ||||
| export interface IApiToken extends Omit<IApiTokenCreate, 'alias'> { | ||||
|  | ||||
| @ -4,64 +4,16 @@ import { ApiTokenType } from '../model.js'; | ||||
| 
 | ||||
| export const ALL = '*'; | ||||
| 
 | ||||
| export interface ILegacyApiTokenCreate { | ||||
|     secret: string; | ||||
|     /** | ||||
|      * @deprecated Use tokenName instead | ||||
|      */ | ||||
|     username?: string; | ||||
|     type: ApiTokenType; | ||||
|     environment?: string; | ||||
|     project?: string; | ||||
|     projects?: string[]; | ||||
|     expiresAt?: Date; | ||||
|     tokenName?: string; | ||||
| } | ||||
| 
 | ||||
| export const isAllProjects = (projects: string[]): boolean => { | ||||
|     return projects && projects.length === 1 && projects[0] === ALL; | ||||
| }; | ||||
| 
 | ||||
| export const mapLegacyProjects = ( | ||||
|     project?: string, | ||||
|     projects?: string[], | ||||
| ): string[] => { | ||||
|     let cleanedProjects: string[]; | ||||
|     if (project) { | ||||
|         cleanedProjects = [project]; | ||||
|     } else if (projects) { | ||||
|         cleanedProjects = projects; | ||||
|         if (cleanedProjects.includes('*')) { | ||||
|             cleanedProjects = ['*']; | ||||
|         } | ||||
|     } else { | ||||
|         throw new BadDataError( | ||||
|             'API tokens must either contain a project or projects field', | ||||
|         ); | ||||
| export const resolveValidProjects = (projects: string[]): string[] => { | ||||
|     if (projects.includes('*')) { | ||||
|         return ['*']; | ||||
|     } | ||||
|     return cleanedProjects; | ||||
| }; | ||||
| 
 | ||||
| export const mapLegacyToken = ( | ||||
|     token: Omit<ILegacyApiTokenCreate, 'secret'>, | ||||
| ): Omit<IApiTokenCreate, 'secret'> => { | ||||
|     const cleanedProjects = mapLegacyProjects(token.project, token.projects); | ||||
|     return { | ||||
|         tokenName: token.username ?? token.tokenName!, | ||||
|         type: token.type, | ||||
|         environment: token.environment || 'development', | ||||
|         projects: cleanedProjects, | ||||
|         expiresAt: token.expiresAt, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const mapLegacyTokenWithSecret = ( | ||||
|     token: ILegacyApiTokenCreate, | ||||
| ): IApiTokenCreate => { | ||||
|     return { | ||||
|         ...mapLegacyToken(token), | ||||
|         secret: token.secret, | ||||
|     }; | ||||
|     return projects; | ||||
| }; | ||||
| 
 | ||||
| export const validateApiToken = ({ | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import type { Express } from 'express'; | ||||
| import type EventEmitter from 'events'; | ||||
| import type { LogLevel, LogProvider } from '../logger.js'; | ||||
| import type { ILegacyApiTokenCreate } from './models/api-token.js'; | ||||
| import type { IApiTokenCreate } from './model.js'; | ||||
| import type { | ||||
|     IExperimentalOptions, | ||||
|     IFlagContext, | ||||
| @ -85,7 +85,7 @@ export interface IAuthOption { | ||||
|     customAuthHandler?: CustomAuthHandler; | ||||
|     createAdminUser?: boolean; | ||||
|     initialAdminUser?: UsernameAdminUser; | ||||
|     initApiTokens: ILegacyApiTokenCreate[]; | ||||
|     initApiTokens: IApiTokenCreate[]; | ||||
| } | ||||
| 
 | ||||
| export interface IImportOption { | ||||
|  | ||||
| @ -63,7 +63,7 @@ process.nextTick(async () => { | ||||
|                     initApiTokens: [ | ||||
|                         { | ||||
|                             environment: '*', | ||||
|                             project: '*', | ||||
|                             projects: ['*'], | ||||
|                             secret: '*:*.964a287e1b728cb5f4f3e0120df92cb5', | ||||
|                             type: ApiTokenType.ADMIN, | ||||
|                             tokenName: 'some-user', | ||||
|  | ||||
| @ -1,7 +1,4 @@ | ||||
| import { | ||||
|     setupAppWithAuth, | ||||
|     setupAppWithCustomAuth, | ||||
| } from '../../helpers/test-helper.js'; | ||||
| import { setupAppWithCustomAuth } from '../../helpers/test-helper.js'; | ||||
| import dbInit, { type ITestDb } from '../../helpers/database-init.js'; | ||||
| import getLogger from '../../../fixtures/no-logger.js'; | ||||
| import { ApiTokenType } from '../../../../lib/types/model.js'; | ||||
| @ -16,7 +13,6 @@ import { | ||||
|     SYSTEM_USER, | ||||
|     SYSTEM_USER_AUDIT, | ||||
|     SYSTEM_USER_ID, | ||||
|     TEST_AUDIT_USER, | ||||
|     UPDATE_CLIENT_API_TOKEN, | ||||
| } from '../../../../lib/types/index.js'; | ||||
| import { addDays } from 'date-fns'; | ||||
| @ -79,8 +75,7 @@ test('editor users should only get client or frontend tokens', async () => { | ||||
|     await stores.apiTokenStore.insert({ | ||||
|         environment: '', | ||||
|         projects: [], | ||||
|         tokenName: '', | ||||
|         username: 'test', | ||||
|         tokenName: 'test', | ||||
|         secret: '*:environment.1234', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|     }); | ||||
| @ -88,8 +83,7 @@ test('editor users should only get client or frontend tokens', async () => { | ||||
|     await stores.apiTokenStore.insert({ | ||||
|         environment: '', | ||||
|         projects: [], | ||||
|         tokenName: '', | ||||
|         username: 'frontend', | ||||
|         tokenName: 'frontend', | ||||
|         secret: '*:environment.12345', | ||||
|         type: ApiTokenType.FRONTEND, | ||||
|     }); | ||||
| @ -97,8 +91,7 @@ test('editor users should only get client or frontend tokens', async () => { | ||||
|     await stores.apiTokenStore.insert({ | ||||
|         environment: '', | ||||
|         projects: [], | ||||
|         tokenName: '', | ||||
|         username: 'test', | ||||
|         tokenName: 'test', | ||||
|         secret: '*:*.sdfsdf2d', | ||||
|         type: ApiTokenType.ADMIN, | ||||
|     }); | ||||
| @ -141,8 +134,7 @@ test('viewer users should not be allowed to fetch tokens', async () => { | ||||
|     await stores.apiTokenStore.insert({ | ||||
|         environment: '', | ||||
|         projects: [], | ||||
|         tokenName: '', | ||||
|         username: 'test', | ||||
|         tokenName: 'test', | ||||
|         secret: '*:environment.1234', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|     }); | ||||
| @ -150,8 +142,7 @@ test('viewer users should not be allowed to fetch tokens', async () => { | ||||
|     await stores.apiTokenStore.insert({ | ||||
|         environment: '', | ||||
|         projects: [], | ||||
|         tokenName: '', | ||||
|         username: 'test', | ||||
|         tokenName: 'test', | ||||
|         secret: '*:*.sdfsdf2d', | ||||
|         type: ApiTokenType.ADMIN, | ||||
|     }); | ||||
| @ -164,102 +155,6 @@ test('viewer users should not be allowed to fetch tokens', async () => { | ||||
|     await destroy(); | ||||
| }); | ||||
| 
 | ||||
| test('Only token-admins should be allowed to create token', async () => { | ||||
|     expect.assertions(0); | ||||
| 
 | ||||
|     const preHook = (app, config, { userService, accessService }) => { | ||||
|         app.use('/api/admin/', async (req, res, next) => { | ||||
|             const role = await accessService.getPredefinedRole(RoleName.EDITOR); | ||||
|             req.user = await userService.createUser({ | ||||
|                 email: 'editor2@example.com', | ||||
|                 rootRole: role.id, | ||||
|             }); | ||||
|             next(); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const { request, destroy } = await setupAppWithCustomAuth( | ||||
|         stores, | ||||
|         preHook, | ||||
|         undefined, | ||||
|         db.rawDatabase, | ||||
|     ); | ||||
| 
 | ||||
|     await request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-admin', | ||||
|             type: 'admin', | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(403); | ||||
| 
 | ||||
|     await destroy(); | ||||
| }); | ||||
| 
 | ||||
| test('Token-admin should not be allowed to create token', async () => { | ||||
|     expect.assertions(0); | ||||
| 
 | ||||
|     const preHook = (app, config, { userService, accessService }) => { | ||||
|         app.use('/api/admin/', async (req, res, next) => { | ||||
|             const role = await accessService.getPredefinedRole(RoleName.ADMIN); | ||||
|             req.user = await userService.createUser({ | ||||
|                 email: 'admin@example.com', | ||||
|                 rootRole: role.id, | ||||
|             }); | ||||
|             next(); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const { request, destroy } = await setupAppWithCustomAuth( | ||||
|         stores, | ||||
|         preHook, | ||||
|         undefined, | ||||
|         db.rawDatabase, | ||||
|     ); | ||||
| 
 | ||||
|     await request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-admin', | ||||
|             type: 'admin', | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(403); | ||||
| 
 | ||||
|     await destroy(); | ||||
| }); | ||||
| 
 | ||||
| test('An admin should be forbidden to create an admin token', async () => { | ||||
|     const { request, destroy, services } = await setupAppWithAuth( | ||||
|         stores, | ||||
|         undefined, | ||||
|         db.rawDatabase, | ||||
|     ); | ||||
| 
 | ||||
|     const { secret } = | ||||
|         await services.apiTokenService.createApiTokenWithProjects( | ||||
|             { | ||||
|                 tokenName: 'default-admin', | ||||
|                 type: ApiTokenType.ADMIN, | ||||
|                 projects: ['*'], | ||||
|                 environment: '*', | ||||
|             }, | ||||
|             TEST_AUDIT_USER, | ||||
|         ); | ||||
| 
 | ||||
|     await request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-admin', | ||||
|             type: 'admin', | ||||
|         }) | ||||
|         .set('Authorization', secret) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(403); | ||||
|     await destroy(); | ||||
| }); | ||||
| 
 | ||||
| test('A role with only CREATE_PROJECT_API_TOKEN can create project tokens', async () => { | ||||
|     expect.assertions(0); | ||||
| 
 | ||||
| @ -312,7 +207,7 @@ test('A role with only CREATE_PROJECT_API_TOKEN can create project tokens', asyn | ||||
|     await request | ||||
|         .post('/api/admin/projects/default/api-tokens') | ||||
|         .send({ | ||||
|             username: 'client-token-maker', | ||||
|             tokenName: 'client-token-maker', | ||||
|             type: 'client', | ||||
|             projects: ['default'], | ||||
|         }) | ||||
| @ -374,7 +269,7 @@ describe('Fine grained API token permissions', () => { | ||||
|             await request | ||||
|                 .post('/api/admin/api-tokens') | ||||
|                 .send({ | ||||
|                     username: 'default-client', | ||||
|                     tokenName: 'default-client', | ||||
|                     type: 'client', | ||||
|                 }) | ||||
|                 .set('Content-Type', 'application/json') | ||||
| @ -431,70 +326,13 @@ describe('Fine grained API token permissions', () => { | ||||
|             await request | ||||
|                 .post('/api/admin/api-tokens') | ||||
|                 .send({ | ||||
|                     username: 'default-frontend', | ||||
|                     tokenName: 'default-frontend', | ||||
|                     type: 'frontend', | ||||
|                 }) | ||||
|                 .set('Content-Type', 'application/json') | ||||
|                 .expect(403); | ||||
|             await destroy(); | ||||
|         }); | ||||
|         test('should NOT be allowed to create ADMIN tokens', async () => { | ||||
|             const preHook = ( | ||||
|                 app, | ||||
|                 config, | ||||
|                 { | ||||
|                     userService, | ||||
|                     accessService, | ||||
|                 }: Pick<IUnleashServices, 'userService' | 'accessService'>, | ||||
|             ) => { | ||||
|                 app.use('/api/admin/', async (req, res, next) => { | ||||
|                     const role = await accessService.getPredefinedRole( | ||||
|                         RoleName.VIEWER, | ||||
|                     ); | ||||
|                     const user = await userService.createUser({ | ||||
|                         email: 'mylittlepony_admin@example.com', | ||||
|                         rootRole: role.id, | ||||
|                     }); | ||||
|                     req.user = user; | ||||
|                     const createClientApiTokenRole = | ||||
|                         await accessService.createRole( | ||||
|                             { | ||||
|                                 name: 'client_token_creator_cannot_create_admin', | ||||
|                                 description: 'Can create client tokens', | ||||
|                                 permissions: [], | ||||
|                                 type: 'root-custom', | ||||
|                                 createdByUserId: SYSTEM_USER_ID, | ||||
|                             }, | ||||
|                             SYSTEM_USER_AUDIT, | ||||
|                         ); | ||||
|                     await accessService.addPermissionToRole( | ||||
|                         role.id, | ||||
|                         CREATE_CLIENT_API_TOKEN, | ||||
|                     ); | ||||
|                     await accessService.addUserToRole( | ||||
|                         user.id, | ||||
|                         createClientApiTokenRole.id, | ||||
|                         'default', | ||||
|                     ); | ||||
|                     next(); | ||||
|                 }); | ||||
|             }; | ||||
|             const { request, destroy } = await setupAppWithCustomAuth( | ||||
|                 stores, | ||||
|                 preHook, | ||||
|                 undefined, | ||||
|                 db.rawDatabase, | ||||
|             ); | ||||
|             await request | ||||
|                 .post('/api/admin/api-tokens') | ||||
|                 .send({ | ||||
|                     username: 'default-admin', | ||||
|                     type: 'admin', | ||||
|                 }) | ||||
|                 .set('Content-Type', 'application/json') | ||||
|                 .expect(403); | ||||
|             await destroy(); | ||||
|         }); | ||||
|     }); | ||||
|     describe('Read operations', () => { | ||||
|         test('READ_FRONTEND_API_TOKEN should be able to see FRONTEND tokens', async () => { | ||||
| @ -546,9 +384,8 @@ describe('Fine grained API token permissions', () => { | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
| 
 | ||||
|                 username: 'client', | ||||
|                 tokenName: 'client', | ||||
|                 secret: '*:environment.client_secret', | ||||
|                 type: ApiTokenType.CLIENT, | ||||
|             }); | ||||
| @ -556,16 +393,14 @@ describe('Fine grained API token permissions', () => { | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'admin', | ||||
|                 tokenName: 'admin', | ||||
|                 secret: '*:*.sdfsdf2admin_secret', | ||||
|                 type: ApiTokenType.ADMIN, | ||||
|             }); | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'frontender', | ||||
|                 tokenName: 'frontender', | ||||
|                 secret: '*:environment:sdfsdf2dfrontend_Secret', | ||||
|                 type: ApiTokenType.FRONTEND, | ||||
|             }); | ||||
| @ -631,8 +466,7 @@ describe('Fine grained API token permissions', () => { | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'client', | ||||
|                 tokenName: 'client', | ||||
|                 secret: '*:environment.client_secret_1234', | ||||
|                 type: ApiTokenType.CLIENT, | ||||
|             }); | ||||
| @ -640,16 +474,14 @@ describe('Fine grained API token permissions', () => { | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'admin', | ||||
|                 tokenName: 'admin', | ||||
|                 secret: '*:*.admin_secret_1234', | ||||
|                 type: ApiTokenType.ADMIN, | ||||
|             }); | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'frontender', | ||||
|                 tokenName: 'frontender', | ||||
|                 secret: '*:environment.frontend_secret_1234', | ||||
|                 type: ApiTokenType.FRONTEND, | ||||
|             }); | ||||
| @ -693,8 +525,7 @@ describe('Fine grained API token permissions', () => { | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'client', | ||||
|                 tokenName: 'client', | ||||
|                 secret: '*:environment.client_secret_4321', | ||||
|                 type: ApiTokenType.CLIENT, | ||||
|             }); | ||||
| @ -702,16 +533,14 @@ describe('Fine grained API token permissions', () => { | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'admin', | ||||
|                 tokenName: 'admin', | ||||
|                 secret: '*:*.admin_secret_4321', | ||||
|                 type: ApiTokenType.ADMIN, | ||||
|             }); | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'frontender', | ||||
|                 tokenName: 'frontender', | ||||
|                 secret: '*:environment.frontend_secret_4321', | ||||
|                 type: ApiTokenType.FRONTEND, | ||||
|             }); | ||||
| @ -754,24 +583,21 @@ describe('Fine grained API token permissions', () => { | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'client', | ||||
|                 tokenName: 'client', | ||||
|                 secret: '*:environment.client_secret_4321', | ||||
|                 type: ApiTokenType.CLIENT, | ||||
|             }); | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'admin', | ||||
|                 tokenName: 'admin', | ||||
|                 secret: '*:*.admin_secret_4321', | ||||
|                 type: ApiTokenType.ADMIN, | ||||
|             }); | ||||
|             await stores.apiTokenStore.insert({ | ||||
|                 environment: '', | ||||
|                 projects: [], | ||||
|                 tokenName: '', | ||||
|                 username: 'frontender', | ||||
|                 tokenName: 'frontender', | ||||
|                 secret: '*:environment.frontend_secret_4321', | ||||
|                 type: ApiTokenType.FRONTEND, | ||||
|             }); | ||||
| @ -842,8 +668,7 @@ describe('Fine grained API token permissions', () => { | ||||
|                 const token = await stores.apiTokenStore.insert({ | ||||
|                     environment: '', | ||||
|                     projects: [], | ||||
|                     tokenName: '', | ||||
|                     username: 'cilent', | ||||
|                     tokenName: 'cilent', | ||||
|                     secret: '*:environment.update_client_token', | ||||
|                     type: ApiTokenType.CLIENT, | ||||
|                 }); | ||||
| @ -904,8 +729,7 @@ describe('Fine grained API token permissions', () => { | ||||
|                 const token = await stores.apiTokenStore.insert({ | ||||
|                     environment: '', | ||||
|                     projects: [], | ||||
|                     tokenName: '', | ||||
|                     username: 'frontend', | ||||
|                     tokenName: 'frontend', | ||||
|                     secret: '*:environment.update_frontend_token', | ||||
|                     type: ApiTokenType.FRONTEND, | ||||
|                 }); | ||||
| @ -966,9 +790,8 @@ describe('Fine grained API token permissions', () => { | ||||
|                 const token = await stores.apiTokenStore.insert({ | ||||
|                     environment: '', | ||||
|                     projects: [], | ||||
|                     tokenName: '', | ||||
| 
 | ||||
|                     username: 'admin', | ||||
|                     tokenName: 'admin', | ||||
|                     secret: '*:*.update_admin_token', | ||||
|                     type: ApiTokenType.ADMIN, | ||||
|                 }); | ||||
| @ -1032,8 +855,7 @@ describe('Fine grained API token permissions', () => { | ||||
|                 const token = await stores.apiTokenStore.insert({ | ||||
|                     environment: '', | ||||
|                     projects: [], | ||||
|                     tokenName: '', | ||||
|                     username: 'cilent', | ||||
|                     tokenName: 'cilent', | ||||
|                     secret: '*:environment.delete_client_token', | ||||
|                     type: ApiTokenType.CLIENT, | ||||
|                 }); | ||||
| @ -1094,8 +916,7 @@ describe('Fine grained API token permissions', () => { | ||||
|                 const token = await stores.apiTokenStore.insert({ | ||||
|                     environment: '', | ||||
|                     projects: [], | ||||
|                     tokenName: '', | ||||
|                     username: 'frontend', | ||||
|                     tokenName: 'frontend', | ||||
|                     secret: '*:environment.delete_frontend_token', | ||||
|                     type: ApiTokenType.FRONTEND, | ||||
|                 }); | ||||
| @ -1155,8 +976,7 @@ describe('Fine grained API token permissions', () => { | ||||
|                 const token = await stores.apiTokenStore.insert({ | ||||
|                     environment: '', | ||||
|                     projects: [], | ||||
|                     tokenName: '', | ||||
|                     username: 'admin', | ||||
|                     tokenName: 'admin', | ||||
|                     secret: '*:*:delete_admin_token', | ||||
|                     type: ApiTokenType.ADMIN, | ||||
|                 }); | ||||
|  | ||||
| @ -52,14 +52,13 @@ test('creates new client token', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(201) | ||||
|         .expect((res) => { | ||||
|             expect(res.body.username).toBe('default-client'); | ||||
|             expect(res.body.tokenName).toBe(res.body.username); | ||||
|             expect(res.body.tokenName).toBe('default-client'); | ||||
|             expect(res.body.type).toBe('client'); | ||||
|             expect(res.body.createdAt).toBeTruthy(); | ||||
|             expect(res.body.secret.length > 16).toBe(true); | ||||
| @ -70,7 +69,6 @@ test('update client token with expiry', async () => { | ||||
|     const tokenSecret = '*:environment.random-secret-update'; | ||||
| 
 | ||||
|     await db.stores.apiTokenStore.insert({ | ||||
|         username: 'test', | ||||
|         projects: ['*'], | ||||
|         tokenName: 'test_token', | ||||
|         secret: tokenSecret, | ||||
| @ -104,7 +102,7 @@ test('creates a lot of client tokens', async () => { | ||||
|             app.request | ||||
|                 .post('/api/admin/api-tokens') | ||||
|                 .send({ | ||||
|                     username: 'default-client', | ||||
|                     tokenName: 'default-client', | ||||
|                     type: 'client', | ||||
|                 }) | ||||
|                 .set('Content-Type', 'application/json') | ||||
| @ -138,7 +136,6 @@ test('removes api token', async () => { | ||||
|         environment: 'development', | ||||
|         projects: ['*'], | ||||
|         tokenName: 'testtoken', | ||||
|         username: 'test', | ||||
|         secret: tokenSecret, | ||||
|         type: ApiTokenType.CLIENT, | ||||
|     }); | ||||
| @ -161,7 +158,7 @@ test('creates new client token: project & environment defaults to "*"', async () | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
| @ -178,9 +175,9 @@ test('creates new client token with project & environment set', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|             project: 'default', | ||||
|             projects: ['default'], | ||||
|             environment: DEFAULT_ENV, | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
| @ -197,7 +194,7 @@ test('should prefix default token with "*:*."', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
| @ -211,9 +208,9 @@ test('should prefix token with "project:environment."', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|             project: 'default', | ||||
|             projects: ['default'], | ||||
|             environment: DEFAULT_ENV, | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
| @ -227,9 +224,9 @@ test('should not create token for invalid projectId', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|             project: 'bogus-project-something', | ||||
|             projects: ['bogus-project-something'], | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(400) | ||||
| @ -244,7 +241,7 @@ test('should not create token for invalid environment', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|             environment: 'bogus-environment-something', | ||||
|         }) | ||||
| @ -257,22 +254,21 @@ test('should not create token for invalid environment', async () => { | ||||
|         }); | ||||
| }); | ||||
| 
 | ||||
| test('needs one of the username and tokenName properties set', async () => { | ||||
| test('needs tokenName properties set', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             type: 'admin', | ||||
|             type: 'client', | ||||
|             environment: '*', | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(400); | ||||
| }); | ||||
| 
 | ||||
| test('only one of tokenName and username can be set', async () => { | ||||
| test('can not create token with admin type', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client-name', | ||||
|             tokenName: 'default-token-name', | ||||
|             type: 'admin', | ||||
|             environment: '*', | ||||
| @ -285,7 +281,7 @@ test('client tokens cannot span all environments', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|             environment: ALL, | ||||
|         }) | ||||
| @ -302,9 +298,9 @@ test('should create token for disabled environment', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default', | ||||
|             tokenName: 'default', | ||||
|             type: 'client', | ||||
|             project: 'default', | ||||
|             projects: ['default'], | ||||
|             environment: 'disabledEnvironment', | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|  | ||||
| @ -77,7 +77,7 @@ test('fails to create new client token when given wrong project', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/projects/wrong/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|             projects: ['wrong'], | ||||
|             environment: 'default', | ||||
| @ -90,7 +90,7 @@ test('creates new client token', async () => { | ||||
|     return app.request | ||||
|         .post('/api/admin/projects/default/api-tokens') | ||||
|         .send({ | ||||
|             username: 'default-client', | ||||
|             tokenName: 'default-client', | ||||
|             type: 'client', | ||||
|             projects: ['default'], | ||||
|             environment: 'default', | ||||
| @ -98,7 +98,7 @@ test('creates new client token', async () => { | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(201) | ||||
|         .expect((res) => { | ||||
|             expect(res.body.username).toBe('default-client'); | ||||
|             expect(res.body.tokenName).toBe('default-client'); | ||||
|         }); | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -125,11 +125,11 @@ afterAll(async () => { | ||||
| }); | ||||
| 
 | ||||
| test('returns feature flag with "default" config', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName, | ||||
|         environment: DEFAULT_ENV, | ||||
|         project, | ||||
|         projects: [project], | ||||
|     }); | ||||
|     await app.request | ||||
|         .get('/api/client/features') | ||||
| @ -147,11 +147,11 @@ test('returns feature flag with "default" config', async () => { | ||||
| }); | ||||
| 
 | ||||
| test('returns feature flag with testing environment config', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName: tokenName, | ||||
|         environment, | ||||
|         project, | ||||
|         projects: [project], | ||||
|     }); | ||||
|     await app.request | ||||
|         .get('/api/client/features') | ||||
| @ -173,11 +173,11 @@ test('returns feature flag with testing environment config', async () => { | ||||
| }); | ||||
| 
 | ||||
| test('returns feature flag for project2', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName: tokenName, | ||||
|         environment, | ||||
|         project: project2, | ||||
|         projects: [project2], | ||||
|     }); | ||||
|     await app.request | ||||
|         .get('/api/client/features') | ||||
| @ -193,11 +193,11 @@ test('returns feature flag for project2', async () => { | ||||
| }); | ||||
| 
 | ||||
| test('returns feature flag for all projects', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName: tokenName, | ||||
|         environment, | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|     }); | ||||
|     await app.request | ||||
|         .get('/api/client/features') | ||||
|  | ||||
| @ -133,11 +133,11 @@ afterAll(async () => { | ||||
| }); | ||||
| 
 | ||||
| test('doesnt return feature flags if project deleted', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName: deletionTokenName, | ||||
|         environment, | ||||
|         project: deletionProject, | ||||
|         projects: [deletionProject], | ||||
|     }); | ||||
| 
 | ||||
|     await app.services.projectService.deleteProject( | ||||
|  | ||||
| @ -31,11 +31,11 @@ test('should enrich metrics with environment from api-token', async () => { | ||||
|         type: 'test', | ||||
|     }); | ||||
| 
 | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         tokenName: 'test', | ||||
|         environment: 'some', | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|     }); | ||||
| 
 | ||||
|     const featureName = Object.keys(metricsExample.bucket.toggles)[0]; | ||||
|  | ||||
| @ -74,12 +74,14 @@ test('should accept client metrics', async () => { | ||||
| test('should pick up environment from token', async () => { | ||||
|     const environment = 'test'; | ||||
|     await db.stores.environmentStore.create({ name: 'test', type: 'test' }); | ||||
|     const token = await app.services.apiTokenService.createApiToken({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         project: 'default', | ||||
|         environment, | ||||
|         tokenName: 'tester', | ||||
|     }); | ||||
|     const token = await app.services.apiTokenService.createApiTokenWithProjects( | ||||
|         { | ||||
|             type: ApiTokenType.CLIENT, | ||||
|             projects: ['default'], | ||||
|             environment, | ||||
|             tokenName: 'tester', | ||||
|         }, | ||||
|     ); | ||||
| 
 | ||||
|     // @ts-expect-error - cachedFeatureNames is a private property in ClientMetricsServiceV2
 | ||||
|     app.services.clientMetricsServiceV2.cachedFeatureNames = vi | ||||
| @ -129,12 +131,14 @@ test('should set lastSeen for toggles with metrics both for toggle and toggle en | ||||
|         .fn<() => Promise<string[]>>() | ||||
|         .mockResolvedValue(['t1', 't2']); | ||||
| 
 | ||||
|     const token = await app.services.apiTokenService.createApiToken({ | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         project: 'default', | ||||
|         environment: 'default', | ||||
|         tokenName: 'tester', | ||||
|     }); | ||||
|     const token = await app.services.apiTokenService.createApiTokenWithProjects( | ||||
|         { | ||||
|             type: ApiTokenType.CLIENT, | ||||
|             projects: ['default'], | ||||
|             environment: 'default', | ||||
|             tokenName: 'tester', | ||||
|         }, | ||||
|     ); | ||||
| 
 | ||||
|     await app.request | ||||
|         .post('/api/client/metrics') | ||||
|  | ||||
| @ -68,10 +68,10 @@ test('should have empty list of tokens', async () => { | ||||
| }); | ||||
| 
 | ||||
| test('should create client token', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         tokenName: 'default-client', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|         environment: DEFAULT_ENV, | ||||
|     }); | ||||
|     const allTokens = await apiTokenService.getAllTokens(); | ||||
| @ -79,15 +79,15 @@ test('should create client token', async () => { | ||||
|     expect(allTokens.length).toBe(1); | ||||
|     expect(token.secret.length > 32).toBe(true); | ||||
|     expect(token.type).toBe(ApiTokenType.CLIENT); | ||||
|     expect(token.username).toBe('default-client'); | ||||
|     expect(token.tokenName).toBe('default-client'); | ||||
|     expect(allTokens[0].secret).toBe(token.secret); | ||||
| }); | ||||
| 
 | ||||
| test('should create admin token', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         tokenName: 'admin', | ||||
|         type: ApiTokenType.ADMIN, | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|         environment: '*', | ||||
|     }); | ||||
| 
 | ||||
| @ -97,11 +97,11 @@ test('should create admin token', async () => { | ||||
| 
 | ||||
| test('should set expiry of token', async () => { | ||||
|     const time = new Date('2022-01-01'); | ||||
|     await apiTokenService.createApiToken({ | ||||
|     await apiTokenService.createApiTokenWithProjects({ | ||||
|         tokenName: 'default-client', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         expiresAt: time, | ||||
|         project: '*', | ||||
|         projects: ['*'], | ||||
|         environment: DEFAULT_ENV, | ||||
|     }); | ||||
| 
 | ||||
| @ -114,12 +114,12 @@ test('should update expiry of token', async () => { | ||||
|     const time = new Date('2022-01-01'); | ||||
|     const newTime = new Date('2023-01-01'); | ||||
| 
 | ||||
|     const token = await apiTokenService.createApiToken( | ||||
|     const token = await apiTokenService.createApiTokenWithProjects( | ||||
|         { | ||||
|             tokenName: 'default-client', | ||||
|             type: ApiTokenType.CLIENT, | ||||
|             expiresAt: time, | ||||
|             project: '*', | ||||
|             projects: ['*'], | ||||
|             environment: DEFAULT_ENV, | ||||
|         }, | ||||
|         TEST_AUDIT_USER, | ||||
| @ -133,7 +133,7 @@ test('should update expiry of token', async () => { | ||||
| }); | ||||
| 
 | ||||
| test('should create client token with project list', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         tokenName: 'default-client', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         projects: ['default', 'test-project'], | ||||
| @ -145,7 +145,7 @@ test('should create client token with project list', async () => { | ||||
| }); | ||||
| 
 | ||||
| test('should strip all other projects if ALL_PROJECTS is present', async () => { | ||||
|     const token = await apiTokenService.createApiToken({ | ||||
|     const token = await apiTokenService.createApiTokenWithProjects({ | ||||
|         tokenName: 'default-client', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         projects: ['*', 'default'], | ||||
| @ -159,21 +159,23 @@ test('should return user with multiple projects', async () => { | ||||
|     const now = Date.now(); | ||||
|     const tomorrow = addDays(now, 1); | ||||
| 
 | ||||
|     const { secret: secret1 } = await apiTokenService.createApiToken({ | ||||
|         tokenName: 'default-valid', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         expiresAt: tomorrow, | ||||
|         projects: ['test-project', 'default'], | ||||
|         environment: DEFAULT_ENV, | ||||
|     }); | ||||
|     const { secret: secret1 } = | ||||
|         await apiTokenService.createApiTokenWithProjects({ | ||||
|             tokenName: 'default-valid', | ||||
|             type: ApiTokenType.CLIENT, | ||||
|             expiresAt: tomorrow, | ||||
|             projects: ['test-project', 'default'], | ||||
|             environment: DEFAULT_ENV, | ||||
|         }); | ||||
| 
 | ||||
|     const { secret: secret2 } = await apiTokenService.createApiToken({ | ||||
|         tokenName: 'default-also-valid', | ||||
|         type: ApiTokenType.CLIENT, | ||||
|         expiresAt: tomorrow, | ||||
|         projects: ['test-project'], | ||||
|         environment: DEFAULT_ENV, | ||||
|     }); | ||||
|     const { secret: secret2 } = | ||||
|         await apiTokenService.createApiTokenWithProjects({ | ||||
|             tokenName: 'default-also-valid', | ||||
|             type: ApiTokenType.CLIENT, | ||||
|             expiresAt: tomorrow, | ||||
|             projects: ['test-project'], | ||||
|             environment: DEFAULT_ENV, | ||||
|         }); | ||||
| 
 | ||||
|     const multiProjectUser = await apiTokenService.getUserForToken(secret1); | ||||
|     const singleProjectUser = await apiTokenService.getUserForToken(secret2); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user