mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
chore: deprecate username on api-tokens (#3616)
<!-- Thanks for creating a PR! To make it easier for reviewers and
everyone else to understand what your changes relate to, please add some
relevant content to the headings below. Feel free to ignore or delete
sections that you don't think are relevant. Thank you! ❤️ -->
## About the changes
<!-- Describe the changes introduced. What are they and why are they
being introduced? Feel free to also add screenshots or steps to view the
changes if they're visual. -->
This deprecates the `username` properties on api-token schemas, and adds
a `tokenName` property.
DB field `username` has been renamed to `token_name`, migration added
for the rename.
Both `username` and `tokenName` can be used when consuming the service,
but only one of them.
## Discussion points
<!-- Anything about the PR you'd like to discuss before it gets merged?
Got any questions or doubts? -->
There's a couple of things I'd like to get opinions on and discuss:
- Frontend still uses the deprecated `username` property
- ApiTokenSchema is used both for input and output of `Create`
controller endpoints and should be split out into separate schemas. I'll
set up a task for this
---------
Co-authored-by: Thomas Heartman <thomas@getunleash.ai>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
392e46f43d
commit
f35d9390c1
@ -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: {
|
||||
|
@ -264,7 +264,7 @@ const loadTokensFromString = (
|
||||
environment,
|
||||
secret,
|
||||
type: tokenType,
|
||||
username: 'admin',
|
||||
tokenName: 'admin',
|
||||
};
|
||||
validateApiToken(mapLegacyToken(token));
|
||||
return token;
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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: '*',
|
||||
|
@ -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": "",
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 });
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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<IApiToken> {
|
||||
validateApiToken(newToken);
|
||||
|
||||
const environments = await this.environmentStore.getAll();
|
||||
validateApiTokenEnvironment(newToken, environments);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<IApiTokenCreate, 'alias'> {
|
||||
@ -66,7 +74,7 @@ export const mapLegacyToken = (
|
||||
): Omit<IApiTokenCreate, 'secret'> => {
|
||||
const cleanedProjects = mapLegacyProjects(token.project, token.projects);
|
||||
return {
|
||||
username: token.username,
|
||||
tokenName: token.username ?? token.tokenName!,
|
||||
type: token.type,
|
||||
environment: token.environment,
|
||||
projects: cleanedProjects,
|
||||
|
@ -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,
|
||||
);
|
||||
};
|
@ -49,7 +49,7 @@ process.nextTick(async () => {
|
||||
project: '*',
|
||||
secret: '*:*.964a287e1b728cb5f4f3e0120df92cb5',
|
||||
type: ApiTokenType.ADMIN,
|
||||
username: 'some-user',
|
||||
tokenName: 'some-user',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -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')
|
||||
|
@ -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],
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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],
|
||||
|
@ -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: '*',
|
||||
});
|
||||
|
@ -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: '*',
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -51,7 +51,7 @@ export const createApiToken = (
|
||||
type,
|
||||
projects: ['*'],
|
||||
environment: 'default',
|
||||
username: `${type}-token-${randomId()}`,
|
||||
tokenName: `${type}-token-${randomId()}`,
|
||||
...overrides,
|
||||
});
|
||||
};
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user