diff --git a/src/lib/create-config.test.ts b/src/lib/create-config.test.ts index 8995972fe2..7fe34ef082 100644 --- a/src/lib/create-config.test.ts +++ b/src/lib/create-config.test.ts @@ -24,7 +24,7 @@ test('should add initApiToken for admin token from options', async () => { project: '*', secret: '*:*.some-random-string', type: ApiTokenType.ADMIN, - username: 'admin', + tokenName: 'admin', }; const config = createConfig({ db: { @@ -58,7 +58,7 @@ test('should add initApiToken for client token from options', async () => { project: 'default', secret: 'default:development.some-random-string', type: ApiTokenType.CLIENT, - username: 'admin', + tokenName: 'admin', }; const config = createConfig({ db: { @@ -143,7 +143,7 @@ test('should merge initApiToken from options and env vars', async () => { project: '*', secret: '*:*.some-random-string', type: ApiTokenType.ADMIN, - username: 'admin', + tokenName: 'admin', }; const config = createConfig({ db: { @@ -204,7 +204,7 @@ test('should handle cases where no env var specified for tokens', async () => { project: '*', secret: '*:*.some-random-string', type: ApiTokenType.ADMIN, - username: 'admin', + tokenName: 'admin', }; const config = createConfig({ db: { diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 2085b13c6f..01d688cdd5 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -264,7 +264,7 @@ const loadTokensFromString = ( environment, secret, type: tokenType, - username: 'admin', + tokenName: 'admin', }; validateApiToken(mapLegacyToken(token)); return token; diff --git a/src/lib/db/api-token-store.ts b/src/lib/db/api-token-store.ts index b33aee78a9..69cfd457d7 100644 --- a/src/lib/db/api-token-store.ts +++ b/src/lib/db/api-token-store.ts @@ -27,6 +27,7 @@ interface ITokenInsert { created_at: Date; seen_at?: Date; environment: string; + tokenName?: string; } interface ITokenRow extends ITokenInsert { @@ -38,7 +39,7 @@ const tokenRowReducer = (acc, tokenRow) => { if (!acc[tokenRow.secret]) { acc[tokenRow.secret] = { secret: token.secret, - username: token.username, + tokenName: token.token_name, type: token.type, project: ALL, projects: [ALL], @@ -47,6 +48,7 @@ const tokenRowReducer = (acc, tokenRow) => { createdAt: token.created_at, alias: token.alias, seenAt: token.seen_at, + username: token.token_name, }; } const currentToken = acc[tokenRow.secret]; @@ -61,7 +63,7 @@ const tokenRowReducer = (acc, tokenRow) => { }; const toRow = (newToken: IApiTokenCreate) => ({ - username: newToken.username, + token_name: newToken.tokenName ?? newToken.username, secret: newToken.secret, type: newToken.type, environment: @@ -123,7 +125,7 @@ export class ApiTokenStore implements IApiTokenStore { ) .select( 'tokens.secret', - 'username', + 'token_name', 'type', 'expires_at', 'created_at', @@ -154,6 +156,7 @@ 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, diff --git a/src/lib/middleware/api-token-middleware.test.ts b/src/lib/middleware/api-token-middleware.test.ts index 4aecd1d287..5339bc3586 100644 --- a/src/lib/middleware/api-token-middleware.test.ts +++ b/src/lib/middleware/api-token-middleware.test.ts @@ -82,7 +82,7 @@ test('should not make database query when provided PAT format', async () => { test('should add user if known token', async () => { const apiUser = new ApiUser({ - username: 'default', + tokenName: 'default', permissions: [CLIENT], project: ALL, environment: ALL, @@ -114,7 +114,7 @@ test('should not add user if not /api/client', async () => { expect.assertions(5); const apiUser = new ApiUser({ - username: 'default', + tokenName: 'default', permissions: [CLIENT], project: ALL, environment: ALL, @@ -153,7 +153,7 @@ test('should not add user if not /api/client', async () => { test('should not add user if disabled', async () => { const apiUser = new ApiUser({ - username: 'default', + tokenName: 'default', permissions: [CLIENT], project: ALL, environment: ALL, diff --git a/src/lib/middleware/demo-authentication.ts b/src/lib/middleware/demo-authentication.ts index c988705764..2a45b294d6 100644 --- a/src/lib/middleware/demo-authentication.ts +++ b/src/lib/middleware/demo-authentication.ts @@ -42,7 +42,7 @@ function demoAuthentication( if (!authentication.enableApiToken && !req.user) { // @ts-expect-error req.user = new ApiUser({ - username: 'unauthed-default-client', + tokenName: 'unauthed-default-client', permissions: [], environment: 'default', type: ApiTokenType.CLIENT, diff --git a/src/lib/middleware/rbac-middleware.test.ts b/src/lib/middleware/rbac-middleware.test.ts index 10794d2993..6e1e0b1493 100644 --- a/src/lib/middleware/rbac-middleware.test.ts +++ b/src/lib/middleware/rbac-middleware.test.ts @@ -45,7 +45,7 @@ test('should give api-user ADMIN permission', async () => { const cb = jest.fn(); const req: any = { user: new ApiUser({ - username: 'api', + tokenName: 'api', permissions: [perms.ADMIN], project: '*', environment: '*', @@ -71,7 +71,7 @@ test('should not give api-user ADMIN permission', async () => { const cb = jest.fn(); const req: any = { user: new ApiUser({ - username: 'api', + tokenName: 'api', permissions: [perms.CLIENT], project: '*', environment: '*', diff --git a/src/lib/openapi/spec/__snapshots__/api-token-schema.test.ts.snap b/src/lib/openapi/spec/__snapshots__/api-token-schema.test.ts.snap index 6ebf0dd545..485792fc4d 100644 --- a/src/lib/openapi/spec/__snapshots__/api-token-schema.test.ts.snap +++ b/src/lib/openapi/spec/__snapshots__/api-token-schema.test.ts.snap @@ -10,7 +10,23 @@ exports[`apiTokenSchema empty 1`] = ` "params": { "missingProperty": "username", }, - "schemaPath": "#/required", + "schemaPath": "#/anyOf/0/required", + }, + { + "instancePath": "", + "keyword": "required", + "message": "must have required property 'tokenName'", + "params": { + "missingProperty": "tokenName", + }, + "schemaPath": "#/anyOf/1/required", + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf", }, { "instancePath": "", diff --git a/src/lib/openapi/spec/api-token-schema.ts b/src/lib/openapi/spec/api-token-schema.ts index 8be4a7bcfb..6cf7d9374b 100644 --- a/src/lib/openapi/spec/api-token-schema.ts +++ b/src/lib/openapi/spec/api-token-schema.ts @@ -5,13 +5,21 @@ export const apiTokenSchema = { $id: '#/components/schemas/apiTokenSchema', type: 'object', additionalProperties: false, - required: ['username', 'type'], + required: ['type'], properties: { secret: { type: 'string', }, username: { type: 'string', + deprecated: true, + description: + 'This property was deprecated in Unleash v5. Prefer the `tokenName` property instead.', + }, + tokenName: { + type: 'string', + description: 'A unique name for this particular token', + example: 'some-user', }, type: { type: 'string', @@ -49,6 +57,24 @@ export const apiTokenSchema = { nullable: true, }, }, + anyOf: [ + { + properties: { + username: { + type: 'string', + }, + }, + required: ['username'], + }, + { + properties: { + tokenName: { + type: 'string', + }, + }, + required: ['tokenName'], + }, + ], components: {}, } as const; diff --git a/src/lib/openapi/spec/create-api-token-schema.ts b/src/lib/openapi/spec/create-api-token-schema.ts index 504719cc90..4a6c65f114 100644 --- a/src/lib/openapi/spec/create-api-token-schema.ts +++ b/src/lib/openapi/spec/create-api-token-schema.ts @@ -20,14 +20,11 @@ import { ApiTokenType } from '../../types/models/api-token'; export const createApiTokenSchema = { $id: '#/components/schemas/createApiTokenSchema', type: 'object', - required: ['username', 'type'], + required: ['type'], properties: { secret: { type: 'string', }, - username: { - type: 'string', - }, type: { type: 'string', description: `One of ${Object.values(ApiTokenType).join(', ')}`, @@ -50,6 +47,24 @@ export const createApiTokenSchema = { nullable: true, }, }, + anyOf: [ + { + properties: { + username: { + type: 'string', + }, + }, + required: ['username'], + }, + { + properties: { + tokenName: { + type: 'string', + }, + }, + required: ['tokenName'], + }, + ], components: {}, } as const; diff --git a/src/lib/schema/api-token-schema.ts b/src/lib/schema/api-token-schema.ts index 7b9c69bb64..3a8198d847 100644 --- a/src/lib/schema/api-token-schema.ts +++ b/src/lib/schema/api-token-schema.ts @@ -5,7 +5,8 @@ import { DEFAULT_ENV } from '../util/constants'; export const createApiToken = joi .object() .keys({ - username: joi.string().required(), + username: joi.string().optional(), + tokenName: joi.string().optional(), type: joi .string() .lowercase() @@ -27,5 +28,6 @@ export const createApiToken = joi otherwise: joi.string().optional().default(ALL), }), }) + .nand('username', 'tokenName') .nand('project', 'projects') .options({ stripUnknown: true, allowUnknown: false, abortEarly: false }); diff --git a/src/lib/services/api-token-service.test.ts b/src/lib/services/api-token-service.test.ts index 94fcd2836a..0c0791d058 100644 --- a/src/lib/services/api-token-service.test.ts +++ b/src/lib/services/api-token-service.test.ts @@ -18,7 +18,7 @@ test('Should init api token', async () => { project: '*', secret: '*:*:some-random-string', type: ApiTokenType.ADMIN, - username: 'admin', + tokenName: 'admin', }; const config: IUnleashConfig = createTestConfig({ @@ -51,7 +51,7 @@ test("Shouldn't return frontend token when secret is undefined", async () => { projects: ['*'], secret: '*:*:some-random-string', type: ApiTokenType.FRONTEND, - username: 'front', + tokenName: 'front', expiresAt: null, }; @@ -86,7 +86,7 @@ test('Api token operations should all have events attached', async () => { projects: ['*'], secret: '*:*:some-random-string', type: ApiTokenType.FRONTEND, - username: 'front', + tokenName: 'front', expiresAt: null, }; diff --git a/src/lib/services/api-token-service.ts b/src/lib/services/api-token-service.ts index c595e433b1..d78e8681c1 100644 --- a/src/lib/services/api-token-service.ts +++ b/src/lib/services/api-token-service.ts @@ -144,7 +144,7 @@ export class ApiTokenService { this.lastSeenSecrets.add(token.secret); return new ApiUser({ - username: token.username, + tokenName: token.tokenName, permissions: resolveTokenPermissions(token.type), projects: token.projects, environment: token.environment, @@ -202,7 +202,6 @@ export class ApiTokenService { createdBy: string = 'unleash-system', ): Promise { validateApiToken(newToken); - const environments = await this.environmentStore.getAll(); validateApiTokenEnvironment(newToken, environments); diff --git a/src/lib/types/api-user.ts b/src/lib/types/api-user.ts index 86a663fd0a..3e608a8065 100644 --- a/src/lib/types/api-user.ts +++ b/src/lib/types/api-user.ts @@ -4,20 +4,18 @@ import { ValidationError } from 'joi'; import { CLIENT } from './permissions'; interface IApiUserData { - username: string; permissions?: string[]; projects?: string[]; project?: string; environment: string; type: ApiTokenType; secret: string; + tokenName: string; } export default class ApiUser { readonly isAPI: boolean = true; - readonly username: string; - readonly permissions: string[]; readonly projects: string[]; @@ -29,18 +27,17 @@ export default class ApiUser { readonly secret: string; constructor({ - username, permissions = [CLIENT], projects, project, environment, type, secret, + tokenName, }: IApiUserData) { - if (!username) { - throw new ValidationError('username is required', [], undefined); + if (!tokenName) { + throw new ValidationError('tokenName is required', [], undefined); } - this.username = username; this.permissions = permissions; this.environment = environment; this.type = type; diff --git a/src/lib/types/models/api-token.ts b/src/lib/types/models/api-token.ts index df5904cfd9..b1d55577ed 100644 --- a/src/lib/types/models/api-token.ts +++ b/src/lib/types/models/api-token.ts @@ -11,22 +11,30 @@ export enum ApiTokenType { export interface ILegacyApiTokenCreate { secret: string; - username: string; + /** + * @deprecated Use tokenName instead + */ + username?: string; type: ApiTokenType; environment: string; project?: string; projects?: string[]; expiresAt?: Date; + tokenName?: string; } export interface IApiTokenCreate { secret: string; - username: string; + tokenName: string; alias?: string; type: ApiTokenType; environment: string; projects: string[]; expiresAt?: Date; + /** + * @deprecated Use tokenName instead + */ + username?: string; } export interface IApiToken extends Omit { @@ -66,7 +74,7 @@ export const mapLegacyToken = ( ): Omit => { const cleanedProjects = mapLegacyProjects(token.project, token.projects); return { - username: token.username, + tokenName: token.username ?? token.tokenName!, type: token.type, environment: token.environment, projects: cleanedProjects, diff --git a/src/migrations/20230426110026-rename-api-token-username-field.js b/src/migrations/20230426110026-rename-api-token-username-field.js new file mode 100644 index 0000000000..51e15848a0 --- /dev/null +++ b/src/migrations/20230426110026-rename-api-token-username-field.js @@ -0,0 +1,20 @@ +/* eslint camelcase: "off" */ +'use strict'; + +exports.up = function (db, cb) { + db.runSql( + ` + ALTER TABLE api_tokens RENAME COLUMN username TO token_name; + `, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` + ALTER TABLE api_tokens RENAME COLUMN token_name TO username; + `, + cb, + ); +}; diff --git a/src/server-dev.ts b/src/server-dev.ts index 1a675ed291..325d793efa 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -49,7 +49,7 @@ process.nextTick(async () => { project: '*', secret: '*:*.964a287e1b728cb5f4f3e0120df92cb5', type: ApiTokenType.ADMIN, - username: 'some-user', + tokenName: 'some-user', }, ], }, 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 259b560238..4adfa8d887 100644 --- a/src/test/e2e/api/admin/api-token.e2e.test.ts +++ b/src/test/e2e/api/admin/api-token.e2e.test.ts @@ -44,6 +44,7 @@ test('creates new client token', async () => { .expect(201) .expect((res) => { expect(res.body.username).toBe('default-client'); + expect(res.body.tokenName).toBe(res.body.username); expect(res.body.type).toBe('client'); expect(res.body.createdAt).toBeTruthy(); expect(res.body.secret.length > 16).toBe(true); @@ -61,6 +62,7 @@ test('creates new admin token', async () => { .expect(201) .expect((res) => { expect(res.body.username).toBe('default-admin'); + expect(res.body.tokenName).toBe(res.body.username); expect(res.body.type).toBe('admin'); expect(res.body.environment).toBe(ALL); expect(res.body.createdAt).toBeTruthy(); @@ -80,6 +82,7 @@ test('creates new ADMIN token should fix casing', async () => { .expect(201) .expect((res) => { expect(res.body.username).toBe('default-admin'); + expect(res.body.tokenName).toBe(res.body.username); expect(res.body.type).toBe('admin'); expect(res.body.createdAt).toBeTruthy(); expect(res.body.expiresAt).toBeFalsy(); @@ -307,6 +310,48 @@ test('admin token only supports ALL projects', async () => { .expect(400); }); +test('needs one of the username and tokenName properties set', async () => { + return app.request + .post('/api/admin/api-tokens') + .send({ + type: 'admin', + environment: '*', + }) + .set('Content-Type', 'application/json') + .expect(400); +}); + +test('can create with tokenName only', async () => { + return app.request + .post('/api/admin/api-tokens') + .send({ + tokenName: 'default-admin', + type: 'admin', + environment: '*', + }) + .set('Content-Type', 'application/json') + .expect(201) + .expect((res) => { + expect(res.body.type).toBe('admin'); + expect(res.body.secret.length > 16).toBe(true); + expect(res.body.username).toBe('default-admin'); + expect(res.body.tokenName).toBe('default-admin'); + }); +}); + +test('only one of tokenName and username can be set', async () => { + return app.request + .post('/api/admin/api-tokens') + .send({ + username: 'default-client-name', + tokenName: 'default-token-name', + type: 'admin', + environment: '*', + }) + .set('Content-Type', 'application/json') + .expect(400); +}); + test('admin token only supports ALL environments', async () => { return app.request .post('/api/admin/api-tokens') diff --git a/src/test/e2e/api/admin/playground.e2e.test.ts b/src/test/e2e/api/admin/playground.e2e.test.ts index 4807c54aa3..d2c9fa2943 100644 --- a/src/test/e2e/api/admin/playground.e2e.test.ts +++ b/src/test/e2e/api/admin/playground.e2e.test.ts @@ -25,7 +25,7 @@ beforeAll(async () => { const { apiTokenService } = app.services; token = await apiTokenService.createApiTokenWithProjects({ type: ApiTokenType.ADMIN, - username: 'tester', + tokenName: 'tester', environment: ALL, projects: [ALL], }); diff --git a/src/test/e2e/api/admin/project/features.e2e.test.ts b/src/test/e2e/api/admin/project/features.e2e.test.ts index 2bfadfc40f..255f501664 100644 --- a/src/test/e2e/api/admin/project/features.e2e.test.ts +++ b/src/test/e2e/api/admin/project/features.e2e.test.ts @@ -2000,7 +2000,7 @@ test('Should not allow changing project to target project without the same enabl .send({}) .expect(200); const user = new ApiUser({ - username: 'project-changer', + tokenName: 'project-changer', permissions: ['ADMIN'], project: '*', type: ApiTokenType.ADMIN, @@ -2080,7 +2080,7 @@ test('Should allow changing project to target project with the same enabled envi .send({}) .expect(200); const user = new ApiUser({ - username: 'project-changer', + tokenName: 'project-changer', permissions: ['ADMIN'], project: '*', type: ApiTokenType.ADMIN, diff --git a/src/test/e2e/api/admin/state.e2e.test.ts b/src/test/e2e/api/admin/state.e2e.test.ts index 483eabe472..aa2bbdec26 100644 --- a/src/test/e2e/api/admin/state.e2e.test.ts +++ b/src/test/e2e/api/admin/state.e2e.test.ts @@ -398,7 +398,7 @@ test(`should not delete api_tokens on import when drop-flag is set`, async () => userName, ); await app.services.apiTokenService.createApiTokenWithProjects({ - username: apiTokenName, + tokenName: apiTokenName, type: ApiTokenType.CLIENT, environment: environment, projects: [projectId], diff --git a/src/test/e2e/api/client/feature.token.access.e2e.test.ts b/src/test/e2e/api/client/feature.token.access.e2e.test.ts index c879847d22..4992fd8476 100644 --- a/src/test/e2e/api/client/feature.token.access.e2e.test.ts +++ b/src/test/e2e/api/client/feature.token.access.e2e.test.ts @@ -13,7 +13,7 @@ let apiTokenService: ApiTokenService; const environment = 'testing'; const project = 'default'; const project2 = 'some'; -const username = 'test'; +const tokenName = 'test'; const feature1 = 'f1.token.access'; const feature2 = 'f2.token.access'; const feature3 = 'f3.p2.token.access'; @@ -47,7 +47,7 @@ beforeAll(async () => { name: feature1, description: 'the #1 feature', }, - username, + tokenName, ); await featureToggleServiceV2.createStrategy( @@ -57,7 +57,7 @@ beforeAll(async () => { parameters: {}, }, { projectId: project, featureName: feature1, environment: DEFAULT_ENV }, - username, + tokenName, ); await featureToggleServiceV2.createStrategy( { @@ -66,7 +66,7 @@ beforeAll(async () => { parameters: {}, }, { projectId: project, featureName: feature1, environment }, - username, + tokenName, ); // create feature 2 @@ -75,7 +75,7 @@ beforeAll(async () => { { name: feature2, }, - username, + tokenName, ); await featureToggleServiceV2.createStrategy( { @@ -84,7 +84,7 @@ beforeAll(async () => { parameters: {}, }, { projectId: project, featureName: feature2, environment }, - username, + tokenName, ); // create feature 3 @@ -93,7 +93,7 @@ beforeAll(async () => { { name: feature3, }, - username, + tokenName, ); await featureToggleServiceV2.createStrategy( { @@ -102,7 +102,7 @@ beforeAll(async () => { parameters: {}, }, { projectId: project2, featureName: feature3, environment }, - username, + tokenName, ); }); @@ -114,7 +114,7 @@ afterAll(async () => { test('returns feature toggle with "default" config', async () => { const token = await apiTokenService.createApiToken({ type: ApiTokenType.CLIENT, - username, + tokenName, environment: DEFAULT_ENV, project, }); @@ -136,7 +136,7 @@ test('returns feature toggle with "default" config', async () => { test('returns feature toggle with testing environment config', async () => { const token = await apiTokenService.createApiToken({ type: ApiTokenType.CLIENT, - username, + tokenName: tokenName, environment, project, }); @@ -162,7 +162,7 @@ test('returns feature toggle with testing environment config', async () => { test('returns feature toggle for project2', async () => { const token = await apiTokenService.createApiToken({ type: ApiTokenType.CLIENT, - username, + tokenName: tokenName, environment, project: project2, }); @@ -182,7 +182,7 @@ test('returns feature toggle for project2', async () => { test('returns feature toggle for all projects', async () => { const token = await apiTokenService.createApiToken({ type: ApiTokenType.CLIENT, - username, + tokenName: tokenName, environment, project: '*', }); diff --git a/src/test/e2e/api/client/metrics.e2e.access.e2e.test.ts b/src/test/e2e/api/client/metrics.e2e.access.e2e.test.ts index e3f613b1c4..8d616855be 100644 --- a/src/test/e2e/api/client/metrics.e2e.access.e2e.test.ts +++ b/src/test/e2e/api/client/metrics.e2e.access.e2e.test.ts @@ -28,7 +28,7 @@ test('should enrich metrics with environment from api-token', async () => { const token = await apiTokenService.createApiToken({ type: ApiTokenType.CLIENT, - username: 'test', + tokenName: 'test', environment: 'some', project: '*', }); diff --git a/src/test/e2e/api/client/metricsV2.e2e.test.ts b/src/test/e2e/api/client/metricsV2.e2e.test.ts index 6fabf4ca69..f7ad1d00b9 100644 --- a/src/test/e2e/api/client/metricsV2.e2e.test.ts +++ b/src/test/e2e/api/client/metricsV2.e2e.test.ts @@ -16,7 +16,7 @@ beforeAll(async () => { type: ApiTokenType.CLIENT, project: 'default', environment: 'default', - username: 'tester', + tokenName: 'tester', }); }); @@ -70,7 +70,7 @@ test('should pick up environment from token', async () => { type: ApiTokenType.CLIENT, project: 'default', environment, - username: 'tester', + tokenName: 'tester', }); await app.request @@ -114,7 +114,7 @@ test('should set lastSeen for toggles with metrics', async () => { type: ApiTokenType.CLIENT, project: 'default', environment: 'default', - username: 'tester', + tokenName: 'tester', }); await app.request diff --git a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap index 052f870ca3..941bb7d666 100644 --- a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap +++ b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap @@ -632,6 +632,28 @@ The provider you choose for your addon dictates what properties the \`parameters }, "apiTokenSchema": { "additionalProperties": false, + "anyOf": [ + { + "properties": { + "username": { + "type": "string", + }, + }, + "required": [ + "username", + ], + }, + { + "properties": { + "tokenName": { + "type": "string", + }, + }, + "required": [ + "tokenName", + ], + }, + ], "properties": { "alias": { "nullable": true, @@ -667,6 +689,11 @@ The provider you choose for your addon dictates what properties the \`parameters "nullable": true, "type": "string", }, + "tokenName": { + "description": "A unique name for this particular token", + "example": "some-user", + "type": "string", + }, "type": { "enum": [ "client", @@ -676,11 +703,12 @@ The provider you choose for your addon dictates what properties the \`parameters "type": "string", }, "username": { + "deprecated": true, + "description": "This property was deprecated in Unleash v5. Prefer the \`tokenName\` property instead.", "type": "string", }, }, "required": [ - "username", "type", ], "type": "object", @@ -1341,6 +1369,28 @@ The provider you choose for your addon dictates what properties the \`parameters "type": "array", }, "createApiTokenSchema": { + "anyOf": [ + { + "properties": { + "username": { + "type": "string", + }, + }, + "required": [ + "username", + ], + }, + { + "properties": { + "tokenName": { + "type": "string", + }, + }, + "required": [ + "tokenName", + ], + }, + ], "properties": { "environment": { "type": "string", @@ -1366,12 +1416,8 @@ The provider you choose for your addon dictates what properties the \`parameters "description": "One of client, admin, frontend", "type": "string", }, - "username": { - "type": "string", - }, }, "required": [ - "username", "type", ], "type": "object", diff --git a/src/test/e2e/api/proxy/proxy.concurrency.e2e.test.ts b/src/test/e2e/api/proxy/proxy.concurrency.e2e.test.ts index bc89dd722a..fdf53556b3 100644 --- a/src/test/e2e/api/proxy/proxy.concurrency.e2e.test.ts +++ b/src/test/e2e/api/proxy/proxy.concurrency.e2e.test.ts @@ -48,7 +48,7 @@ test('multiple parallel calls to api/frontend should not create multiple instanc type: ApiTokenType.FRONTEND, projects: ['default'], environment: 'default', - username: `test-token-${randomId()}`, + tokenName: `test-token-${randomId()}`, }); await Promise.all( diff --git a/src/test/e2e/api/proxy/proxy.e2e.test.ts b/src/test/e2e/api/proxy/proxy.e2e.test.ts index 729d181d73..7baa9ba5c4 100644 --- a/src/test/e2e/api/proxy/proxy.e2e.test.ts +++ b/src/test/e2e/api/proxy/proxy.e2e.test.ts @@ -51,7 +51,7 @@ export const createApiToken = ( type, projects: ['*'], environment: 'default', - username: `${type}-token-${randomId()}`, + tokenName: `${type}-token-${randomId()}`, ...overrides, }); }; diff --git a/src/test/e2e/services/api-token-service.e2e.test.ts b/src/test/e2e/services/api-token-service.e2e.test.ts index 98db5d7551..f6ce28d92b 100644 --- a/src/test/e2e/services/api-token-service.e2e.test.ts +++ b/src/test/e2e/services/api-token-service.e2e.test.ts @@ -86,7 +86,7 @@ test('should have empty list of tokens', async () => { test('should create client token', async () => { const token = await apiTokenService.createApiToken({ - username: 'default-client', + tokenName: 'default-client', type: ApiTokenType.CLIENT, project: '*', environment: DEFAULT_ENV, @@ -102,7 +102,7 @@ test('should create client token', async () => { test('should create admin token', async () => { const token = await apiTokenService.createApiToken({ - username: 'admin', + tokenName: 'admin', type: ApiTokenType.ADMIN, project: '*', environment: '*', @@ -115,7 +115,7 @@ test('should create admin token', async () => { test('should set expiry of token', async () => { const time = new Date('2022-01-01'); await apiTokenService.createApiToken({ - username: 'default-client', + tokenName: 'default-client', type: ApiTokenType.CLIENT, expiresAt: time, project: '*', @@ -133,7 +133,7 @@ test('should update expiry of token', async () => { const token = await apiTokenService.createApiToken( { - username: 'default-client', + tokenName: 'default-client', type: ApiTokenType.CLIENT, expiresAt: time, project: '*', @@ -155,7 +155,7 @@ test('should only return valid tokens', async () => { const tomorrow = addDays(now, 1); await apiTokenService.createApiToken({ - username: 'default-expired', + tokenName: 'default-expired', type: ApiTokenType.CLIENT, expiresAt: yesterday, project: '*', @@ -163,7 +163,7 @@ test('should only return valid tokens', async () => { }); const activeToken = await apiTokenService.createApiToken({ - username: 'default-valid', + tokenName: 'default-valid', type: ApiTokenType.CLIENT, expiresAt: tomorrow, project: '*', @@ -178,7 +178,7 @@ test('should only return valid tokens', async () => { test('should create client token with project list', async () => { const token = await apiTokenService.createApiToken({ - username: 'default-client', + tokenName: 'default-client', type: ApiTokenType.CLIENT, projects: ['default', 'test-project'], environment: DEFAULT_ENV, @@ -190,7 +190,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({ - username: 'default-client', + tokenName: 'default-client', type: ApiTokenType.CLIENT, projects: ['*', 'default'], environment: DEFAULT_ENV, @@ -204,7 +204,7 @@ test('should return user with multiple projects', async () => { const tomorrow = addDays(now, 1); await apiTokenService.createApiToken({ - username: 'default-valid', + tokenName: 'default-valid', type: ApiTokenType.CLIENT, expiresAt: tomorrow, projects: ['test-project', 'default'], @@ -212,7 +212,7 @@ test('should return user with multiple projects', async () => { }); await apiTokenService.createApiToken({ - username: 'default-also-valid', + tokenName: 'default-also-valid', type: ApiTokenType.CLIENT, expiresAt: tomorrow, projects: ['test-project'], @@ -237,7 +237,7 @@ test('should return user with multiple projects', async () => { test('should not partially create token if projects are invalid', async () => { try { await apiTokenService.createApiTokenWithProjects({ - username: 'default-client', + tokenName: 'default-client', type: ApiTokenType.CLIENT, projects: ['non-existent-project'], environment: DEFAULT_ENV,