mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: PATs (#6101)
https://linear.app/unleash/issue/SR-379/refactor-pats This PR refactors PATs. - Adds a new `createPatSchema`, which better aligns with https://docs.getunleash.io/contributing/ADRs/overarching/separation-request-response-schemas - Drops the model type and class in favor of using the schema types directly, which is more consistent with the rest of the codebase and easier to maintain - Misc scouting, improvement and fixes This breaks Enterprise temporarily, but it's faster to move forward this way.
This commit is contained in:
		
							parent
							
								
									28fc36a1de
								
							
						
					
					
						commit
						db0a0d7097
					
				| @ -1,8 +1,8 @@ | ||||
| import { Logger, LogProvider } from '../logger'; | ||||
| import { IPatStore } from '../types/stores/pat-store'; | ||||
| import Pat, { IPat } from '../types/models/pat'; | ||||
| import NotFoundError from '../error/notfound-error'; | ||||
| import { Db } from './db'; | ||||
| import { CreatePatSchema, PatSchema } from '../openapi'; | ||||
| 
 | ||||
| const TABLE = 'personal_access_tokens'; | ||||
| 
 | ||||
| @ -15,26 +15,25 @@ const PAT_PUBLIC_COLUMNS = [ | ||||
|     'seen_at', | ||||
| ]; | ||||
| 
 | ||||
| const fromRow = (row) => { | ||||
|     if (!row) { | ||||
|         throw new NotFoundError('No PAT found'); | ||||
|     } | ||||
|     return new Pat({ | ||||
|         id: row.id, | ||||
|         secret: row.secret, | ||||
|         userId: row.user_id, | ||||
|         description: row.description, | ||||
|         createdAt: row.created_at, | ||||
|         seenAt: row.seen_at, | ||||
|         expiresAt: row.expires_at, | ||||
|     }); | ||||
| }; | ||||
| const rowToPat = ({ | ||||
|     id, | ||||
|     description, | ||||
|     expires_at, | ||||
|     user_id, | ||||
|     created_at, | ||||
|     seen_at, | ||||
| }): PatSchema => ({ | ||||
|     id, | ||||
|     description, | ||||
|     expiresAt: expires_at, | ||||
|     userId: user_id, | ||||
|     createdAt: created_at, | ||||
|     seenAt: seen_at, | ||||
| }); | ||||
| 
 | ||||
| const toRow = (pat: IPat) => ({ | ||||
|     secret: pat.secret, | ||||
|     description: pat.description, | ||||
|     user_id: pat.userId, | ||||
|     expires_at: pat.expiresAt, | ||||
| const patToRow = ({ description, expiresAt }: CreatePatSchema) => ({ | ||||
|     description, | ||||
|     expires_at: expiresAt, | ||||
| }); | ||||
| 
 | ||||
| export default class PatStore implements IPatStore { | ||||
| @ -47,9 +46,15 @@ export default class PatStore implements IPatStore { | ||||
|         this.logger = getLogger('pat-store.ts'); | ||||
|     } | ||||
| 
 | ||||
|     async create(token: IPat): Promise<IPat> { | ||||
|         const row = await this.db(TABLE).insert(toRow(token)).returning('*'); | ||||
|         return fromRow(row[0]); | ||||
|     async create( | ||||
|         pat: CreatePatSchema, | ||||
|         secret: string, | ||||
|         userId: number, | ||||
|     ): Promise<PatSchema> { | ||||
|         const rows = await this.db(TABLE) | ||||
|             .insert({ ...patToRow(pat), secret, user_id: userId }) | ||||
|             .returning('*'); | ||||
|         return rowToPat(rows[0]); | ||||
|     } | ||||
| 
 | ||||
|     async delete(id: number): Promise<void> { | ||||
| @ -96,21 +101,24 @@ export default class PatStore implements IPatStore { | ||||
|         return count; | ||||
|     } | ||||
| 
 | ||||
|     async get(id: number): Promise<Pat> { | ||||
|     async get(id: number): Promise<PatSchema> { | ||||
|         const row = await this.db(TABLE).where({ id }).first(); | ||||
|         return fromRow(row); | ||||
|         if (!row) { | ||||
|             throw new NotFoundError('No PAT found.'); | ||||
|         } | ||||
|         return rowToPat(row); | ||||
|     } | ||||
| 
 | ||||
|     async getAll(): Promise<Pat[]> { | ||||
|         const groups = await this.db.select(PAT_PUBLIC_COLUMNS).from(TABLE); | ||||
|         return groups.map(fromRow); | ||||
|     async getAll(): Promise<PatSchema[]> { | ||||
|         const pats = await this.db.select(PAT_PUBLIC_COLUMNS).from(TABLE); | ||||
|         return pats.map(rowToPat); | ||||
|     } | ||||
| 
 | ||||
|     async getAllByUser(userId: number): Promise<Pat[]> { | ||||
|         const groups = await this.db | ||||
|     async getAllByUser(userId: number): Promise<PatSchema[]> { | ||||
|         const pats = await this.db | ||||
|             .select(PAT_PUBLIC_COLUMNS) | ||||
|             .from(TABLE) | ||||
|             .where('user_id', userId); | ||||
|         return groups.map(fromRow); | ||||
|         return pats.map(rowToPat); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -79,6 +79,7 @@ import { | ||||
|     passwordSchema, | ||||
|     patchesSchema, | ||||
|     patchSchema, | ||||
|     createPatSchema, | ||||
|     patSchema, | ||||
|     patsSchema, | ||||
|     permissionSchema, | ||||
| @ -306,6 +307,7 @@ export const schemas: UnleashSchemas = { | ||||
|     passwordSchema, | ||||
|     patchesSchema, | ||||
|     patchSchema, | ||||
|     createPatSchema, | ||||
|     patSchema, | ||||
|     patsSchema, | ||||
|     permissionSchema, | ||||
|  | ||||
							
								
								
									
										25
									
								
								src/lib/openapi/spec/create-pat-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/lib/openapi/spec/create-pat-schema.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| import { FromSchema } from 'json-schema-to-ts'; | ||||
| 
 | ||||
| export const createPatSchema = { | ||||
|     $id: '#/components/schemas/createPatSchema', | ||||
|     description: | ||||
|         'Describes the properties required to create a [personal access token](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens), or PAT. PATs are automatically scoped to the authenticated user.', | ||||
|     type: 'object', | ||||
|     required: ['description', 'expiresAt'], | ||||
|     properties: { | ||||
|         description: { | ||||
|             type: 'string', | ||||
|             description: `The PAT's description.`, | ||||
|             example: 'user:xyzrandomstring', | ||||
|         }, | ||||
|         expiresAt: { | ||||
|             type: 'string', | ||||
|             format: 'date-time', | ||||
|             description: `The PAT's expiration date.`, | ||||
|             example: '2023-04-19T08:15:14.000Z', | ||||
|         }, | ||||
|     }, | ||||
|     components: {}, | ||||
| } as const; | ||||
| 
 | ||||
| export type CreatePatSchema = FromSchema<typeof createPatSchema>; | ||||
| @ -1,5 +1,6 @@ | ||||
| export * from './id-schema'; | ||||
| export * from './me-schema'; | ||||
| export * from './create-pat-schema'; | ||||
| export * from './pat-schema'; | ||||
| export * from './tag-schema'; | ||||
| export * from './date-schema'; | ||||
|  | ||||
| @ -1,36 +1,30 @@ | ||||
| import { FromSchema } from 'json-schema-to-ts'; | ||||
| import { createPatSchema } from './create-pat-schema'; | ||||
| 
 | ||||
| export const patSchema = { | ||||
|     $id: '#/components/schemas/patSchema', | ||||
|     type: 'object', | ||||
|     description: | ||||
|         'An overview of a [Personal Access Token](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens).', | ||||
|         'Describes a [personal access token](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens), or PAT. PATs are automatically scoped to the authenticated user.', | ||||
|     required: ['id', 'createdAt', ...createPatSchema.required], | ||||
|     properties: { | ||||
|         id: { | ||||
|             type: 'integer', | ||||
|             description: | ||||
|                 'The unique identification number for this Personal Access Token. (This property is set by Unleash when the token is created and cannot be set manually: if you provide a value when creating a PAT, Unleash will ignore it.)', | ||||
|             description: `The PAT's ID. PAT IDs are incrementing integers. In other words, a more recently created PAT will always have a higher ID than an older one.`, | ||||
|             example: 1, | ||||
|             minimum: 1, | ||||
|         }, | ||||
|         secret: { | ||||
|             type: 'string', | ||||
|             description: | ||||
|                 'The token used for authentication. (This property is set by Unleash when the token is created and cannot be set manually: if you provide a value when creating a PAT, Unleash will ignore it.)', | ||||
|                 'The token used for authentication. It is automatically generated by Unleash when the PAT is created and that is the only time this property is returned.', | ||||
|             example: 'user:xyzrandomstring', | ||||
|         }, | ||||
|         expiresAt: { | ||||
|             type: 'string', | ||||
|             format: 'date-time', | ||||
|             description: `The token's expiration date.`, | ||||
|             example: '2023-04-19T08:15:14.000Z', | ||||
|         }, | ||||
|         createdAt: { | ||||
|             type: 'string', | ||||
|             format: 'date-time', | ||||
|             example: '2023-04-19T08:15:14.000Z', | ||||
|             description: | ||||
|                 'When the token was created. (This property is set by Unleash when the token is created and cannot be set manually: if you provide a value when creating a PAT, Unleash will ignore it.)', | ||||
|             description: 'The date and time of when the PAT was created.', | ||||
|         }, | ||||
|         seenAt: { | ||||
|             type: 'string', | ||||
| @ -38,8 +32,14 @@ export const patSchema = { | ||||
|             nullable: true, | ||||
|             example: '2023-04-19T08:15:14.000Z', | ||||
|             description: | ||||
|                 'When the token was last seen/used to authenticate with. `null` if it has not been used yet. (This property is set by Unleash when the token is created and cannot be set manually: if you provide a value when creating a PAT, Unleash will ignore it.)', | ||||
|                 'When the PAT was last seen/used to authenticate with. `null` if it has not been used yet.', | ||||
|         }, | ||||
|         userId: { | ||||
|             type: 'integer', | ||||
|             description: 'The ID of the user this PAT belongs to.', | ||||
|             example: 1337, | ||||
|         }, | ||||
|         ...createPatSchema.properties, | ||||
|     }, | ||||
|     components: { | ||||
|         schemas: {}, | ||||
|  | ||||
| @ -5,14 +5,14 @@ export const patsSchema = { | ||||
|     $id: '#/components/schemas/patsSchema', | ||||
|     type: 'object', | ||||
|     description: | ||||
|         'Contains a collection of [Personal Access Tokens](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens).', | ||||
|         'Contains a collection of [personal access tokens](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens), or PATs. PATs are automatically scoped to the authenticated user.', | ||||
|     properties: { | ||||
|         pats: { | ||||
|             type: 'array', | ||||
|             description: 'A collection of PATs.', | ||||
|             items: { | ||||
|                 $ref: '#/components/schemas/patSchema', | ||||
|                 $ref: patSchema.$id, | ||||
|             }, | ||||
|             description: 'A collection of Personal Access Tokens', | ||||
|         }, | ||||
|     }, | ||||
|     components: { | ||||
|  | ||||
| @ -19,8 +19,13 @@ import PatService from '../../../services/pat-service'; | ||||
| import { NONE } from '../../../types/permissions'; | ||||
| import { IAuthRequest } from '../../unleash-types'; | ||||
| import { serializeDates } from '../../../types/serialize-dates'; | ||||
| import { patSchema } from '../../../openapi/spec/pat-schema'; | ||||
| import { PatSchema, patSchema } from '../../../openapi/spec/pat-schema'; | ||||
| import { PatsSchema, patsSchema } from '../../../openapi/spec/pats-schema'; | ||||
| import { | ||||
|     CreatePatSchema, | ||||
|     createPatSchema, | ||||
| } from '../../../openapi/spec/create-pat-schema'; | ||||
| import { ForbiddenError, NotFoundError } from '../../../error'; | ||||
| 
 | ||||
| export default class PatController extends Controller { | ||||
|     private patService: PatService; | ||||
| @ -53,11 +58,11 @@ export default class PatController extends Controller { | ||||
|                     tags: ['Personal access tokens'], | ||||
|                     operationId: 'getPats', | ||||
|                     summary: | ||||
|                         'Get all Personal Access Tokens for the current user.', | ||||
|                         'Get all personal access tokens (PATs) for the current user.', | ||||
|                     description: | ||||
|                         'Returns all of the [Personal Access Tokens](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens) belonging to the current user.', | ||||
|                         'Returns all of the [personal access tokens](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens) (PATs) belonging to the current user.', | ||||
|                     responses: { | ||||
|                         200: createResponseSchema('patsSchema'), | ||||
|                         200: createResponseSchema(patsSchema.$id), | ||||
|                         ...getStandardResponses(401, 403, 404), | ||||
|                     }, | ||||
|                 }), | ||||
| @ -72,12 +77,13 @@ export default class PatController extends Controller { | ||||
|                 openApiService.validPath({ | ||||
|                     tags: ['Personal access tokens'], | ||||
|                     operationId: 'createPat', | ||||
|                     summary: 'Create a new Personal Access Token.', | ||||
|                     summary: | ||||
|                         'Create a new personal access token (PAT) for the current user.', | ||||
|                     description: | ||||
|                         'Creates a new [Personal Access Token](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens) for the current user.', | ||||
|                     requestBody: createRequestSchema('patSchema'), | ||||
|                         'Creates a new [personal access token](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens) (PAT) belonging to the current user.', | ||||
|                     requestBody: createRequestSchema(createPatSchema.$id), | ||||
|                     responses: { | ||||
|                         201: resourceCreatedResponseSchema('patSchema'), | ||||
|                         201: resourceCreatedResponseSchema(patSchema.$id), | ||||
|                         ...getStandardResponses(401, 403, 404), | ||||
|                     }, | ||||
|                 }), | ||||
| @ -94,9 +100,10 @@ export default class PatController extends Controller { | ||||
|                 openApiService.validPath({ | ||||
|                     tags: ['Personal access tokens'], | ||||
|                     operationId: 'deletePat', | ||||
|                     summary: 'Delete a Personal Access Token.', | ||||
|                     summary: | ||||
|                         'Delete a personal access token (PAT) for the current user.', | ||||
|                     description: | ||||
|                         'This endpoint allows for deleting a [Personal Access Token](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens) belonging to the current user.', | ||||
|                         'Deletes a [personal access token](https://docs.getunleash.io/how-to/how-to-create-personal-access-tokens) (PAT) belonging to the current user.', | ||||
|                     responses: { | ||||
|                         200: emptyResponse, | ||||
|                         ...getStandardResponses(401, 403, 404), | ||||
| @ -106,10 +113,16 @@ export default class PatController extends Controller { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     async createPat(req: IAuthRequest, res: Response): Promise<void> { | ||||
|     async createPat( | ||||
|         req: IAuthRequest<unknown, unknown, CreatePatSchema>, | ||||
|         res: Response<PatSchema>, | ||||
|     ): Promise<void> { | ||||
|         if (this.flagResolver.isEnabled('personalAccessTokensKillSwitch')) { | ||||
|             res.status(404).send({ message: 'PAT is disabled' }); | ||||
|             return; | ||||
|             throw new NotFoundError('PATs are disabled.'); | ||||
|         } | ||||
| 
 | ||||
|         if (!req.user.id) { | ||||
|             throw new ForbiddenError('PATs require an authenticated user.'); | ||||
|         } | ||||
| 
 | ||||
|         const pat = req.body; | ||||
| @ -128,9 +141,13 @@ export default class PatController extends Controller { | ||||
| 
 | ||||
|     async getPats(req: IAuthRequest, res: Response<PatsSchema>): Promise<void> { | ||||
|         if (this.flagResolver.isEnabled('personalAccessTokensKillSwitch')) { | ||||
|             res.status(404).send({ message: 'PAT is disabled' }); | ||||
|             return; | ||||
|             throw new NotFoundError('PATs are disabled.'); | ||||
|         } | ||||
| 
 | ||||
|         if (!req.user.id) { | ||||
|             throw new ForbiddenError('PATs require an authenticated user.'); | ||||
|         } | ||||
| 
 | ||||
|         const pats = await this.patService.getAll(req.user.id); | ||||
|         this.openApiService.respondWithValidation(200, res, patsSchema.$id, { | ||||
|             pats: serializeDates(pats), | ||||
|  | ||||
| @ -2,7 +2,6 @@ import { IUnleashConfig, IUnleashStores } from '../types'; | ||||
| import { Logger } from '../logger'; | ||||
| import { IPatStore } from '../types/stores/pat-store'; | ||||
| import { PAT_CREATED, PAT_DELETED } from '../types/events'; | ||||
| import { IPat } from '../types/models/pat'; | ||||
| import crypto from 'crypto'; | ||||
| import { IUser } from '../types/user'; | ||||
| import BadDataError from '../error/bad-data-error'; | ||||
| @ -10,6 +9,7 @@ import NameExistsError from '../error/name-exists-error'; | ||||
| import { OperationDeniedError } from '../error/operation-denied-error'; | ||||
| import { PAT_LIMIT } from '../util/constants'; | ||||
| import EventService from '../features/events/event-service'; | ||||
| import { CreatePatSchema, PatSchema } from '../openapi'; | ||||
| 
 | ||||
| export default class PatService { | ||||
|     private config: IUnleashConfig; | ||||
| @ -32,54 +32,50 @@ export default class PatService { | ||||
|     } | ||||
| 
 | ||||
|     async createPat( | ||||
|         pat: IPat, | ||||
|         pat: CreatePatSchema, | ||||
|         forUserId: number, | ||||
|         editor: IUser, | ||||
|     ): Promise<IPat> { | ||||
|         byUser: IUser, | ||||
|     ): Promise<PatSchema> { | ||||
|         await this.validatePat(pat, forUserId); | ||||
|         pat.secret = this.generateSecretKey(); | ||||
|         pat.userId = forUserId; | ||||
|         const newPat = await this.patStore.create(pat); | ||||
| 
 | ||||
|         pat.secret = '***'; | ||||
|         await this.eventService.storeEvent({ | ||||
|         const secret = this.generateSecretKey(); | ||||
|         const newPat = await this.patStore.create(pat, secret, forUserId); | ||||
| 
 | ||||
|         await this.eventService.storeUserEvent({ | ||||
|             type: PAT_CREATED, | ||||
|             createdBy: editor.email || editor.username, | ||||
|             createdByUserId: editor.id, | ||||
|             data: pat, | ||||
|             byUser, | ||||
|             data: { ...pat, secret: '***' }, | ||||
|         }); | ||||
| 
 | ||||
|         return newPat; | ||||
|         return { ...newPat, secret }; | ||||
|     } | ||||
| 
 | ||||
|     async getAll(userId: number): Promise<IPat[]> { | ||||
|     async getAll(userId: number): Promise<PatSchema[]> { | ||||
|         return this.patStore.getAllByUser(userId); | ||||
|     } | ||||
| 
 | ||||
|     async deletePat( | ||||
|         id: number, | ||||
|         forUserId: number, | ||||
|         editor: IUser, | ||||
|         byUser: IUser, | ||||
|     ): Promise<void> { | ||||
|         const pat = await this.patStore.get(id); | ||||
| 
 | ||||
|         pat.secret = '***'; | ||||
|         await this.eventService.storeEvent({ | ||||
|         await this.eventService.storeUserEvent({ | ||||
|             type: PAT_DELETED, | ||||
|             createdBy: editor.email || editor.username, | ||||
|             createdByUserId: editor.id, | ||||
|             data: pat, | ||||
|             byUser, | ||||
|             data: { ...pat, secret: '***' }, | ||||
|         }); | ||||
| 
 | ||||
|         return this.patStore.deleteForUser(id, forUserId); | ||||
|     } | ||||
| 
 | ||||
|     async validatePat( | ||||
|         { description, expiresAt }: IPat, | ||||
|         { description, expiresAt }: CreatePatSchema, | ||||
|         userId: number, | ||||
|     ): Promise<void> { | ||||
|         if (!description) { | ||||
|             throw new BadDataError('PAT description cannot be empty'); | ||||
|             throw new BadDataError('PAT description cannot be empty.'); | ||||
|         } | ||||
| 
 | ||||
|         if (new Date(expiresAt) < new Date()) { | ||||
| @ -95,7 +91,7 @@ export default class PatService { | ||||
|         if ( | ||||
|             await this.patStore.existsWithDescriptionByUser(description, userId) | ||||
|         ) { | ||||
|             throw new NameExistsError('PAT description already exists'); | ||||
|             throw new NameExistsError('PAT description already exists.'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,43 +0,0 @@ | ||||
| export interface IPat { | ||||
|     id: number; | ||||
|     secret: string; | ||||
|     description: string; | ||||
|     userId: number; | ||||
|     expiresAt?: Date; | ||||
|     createdAt?: Date; | ||||
|     seenAt?: Date; | ||||
| } | ||||
| 
 | ||||
| export default class Pat implements IPat { | ||||
|     id: number; | ||||
| 
 | ||||
|     secret: string; | ||||
| 
 | ||||
|     description: string; | ||||
| 
 | ||||
|     userId: number; | ||||
| 
 | ||||
|     expiresAt: Date; | ||||
| 
 | ||||
|     seenAt: Date; | ||||
| 
 | ||||
|     createdAt: Date; | ||||
| 
 | ||||
|     constructor({ | ||||
|         id, | ||||
|         userId, | ||||
|         expiresAt, | ||||
|         seenAt, | ||||
|         createdAt, | ||||
|         secret, | ||||
|         description, | ||||
|     }: IPat) { | ||||
|         this.id = id; | ||||
|         this.secret = secret; | ||||
|         this.userId = userId; | ||||
|         this.expiresAt = expiresAt; | ||||
|         this.seenAt = seenAt; | ||||
|         this.createdAt = createdAt; | ||||
|         this.description = description; | ||||
|     } | ||||
| } | ||||
| @ -1,9 +1,13 @@ | ||||
| import { Store } from './store'; | ||||
| import { IPat } from '../models/pat'; | ||||
| import { CreatePatSchema, PatSchema } from '../../openapi'; | ||||
| 
 | ||||
| export interface IPatStore extends Store<IPat, number> { | ||||
|     create(group: IPat): Promise<IPat>; | ||||
|     getAllByUser(userId: number): Promise<IPat[]>; | ||||
| export interface IPatStore extends Store<PatSchema, number> { | ||||
|     create( | ||||
|         pat: CreatePatSchema, | ||||
|         secret: string, | ||||
|         userId: number, | ||||
|     ): Promise<PatSchema>; | ||||
|     getAllByUser(userId: number): Promise<PatSchema[]>; | ||||
|     deleteForUser(id: number, userId: number): Promise<void>; | ||||
|     existsWithDescriptionByUser( | ||||
|         description: string, | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| import { IUnleashTest, setupAppWithAuth } from '../../../helpers/test-helper'; | ||||
| import dbInit, { ITestDb } from '../../../helpers/database-init'; | ||||
| import getLogger from '../../../../fixtures/no-logger'; | ||||
| import { IPat } from '../../../../../lib/types/models/pat'; | ||||
| import { IPatStore } from '../../../../../lib/types/stores/pat-store'; | ||||
| import { PAT_LIMIT } from '../../../../../lib/util/constants'; | ||||
| 
 | ||||
| @ -43,7 +42,7 @@ test('should create a PAT', async () => { | ||||
|         .send({ | ||||
|             expiresAt: tomorrow, | ||||
|             description: description, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(201); | ||||
| 
 | ||||
| @ -69,7 +68,7 @@ test('should delete the PAT', async () => { | ||||
|         .send({ | ||||
|             description, | ||||
|             expiresAt: tomorrow, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(201); | ||||
| 
 | ||||
| @ -134,7 +133,7 @@ test('should get only current user PATs', async () => { | ||||
|         .send({ | ||||
|             description: 'my pat', | ||||
|             expiresAt: tomorrow, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(201); | ||||
| 
 | ||||
| @ -156,7 +155,7 @@ test('should fail creation of PAT with passed expiry', async () => { | ||||
|         .send({ | ||||
|             description: 'my expired pat', | ||||
|             expiresAt: yesterday, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(400); | ||||
| }); | ||||
| @ -166,7 +165,7 @@ test('should fail creation of PAT without a description', async () => { | ||||
|         .post('/api/admin/user/tokens') | ||||
|         .send({ | ||||
|             expiresAt: tomorrow, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(400); | ||||
| }); | ||||
| @ -179,7 +178,7 @@ test('should fail creation of PAT with a description that already exists for the | ||||
|         .send({ | ||||
|             description, | ||||
|             expiresAt: tomorrow, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(201); | ||||
| 
 | ||||
| @ -188,7 +187,7 @@ test('should fail creation of PAT with a description that already exists for the | ||||
|         .send({ | ||||
|             description, | ||||
|             expiresAt: tomorrow, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(409); | ||||
| }); | ||||
| @ -201,7 +200,7 @@ test('should not fail creation of PAT when a description already exists for anot | ||||
|         .send({ | ||||
|             description, | ||||
|             expiresAt: tomorrow, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(201); | ||||
| 
 | ||||
| @ -217,7 +216,7 @@ test('should not fail creation of PAT when a description already exists for anot | ||||
|         .send({ | ||||
|             description, | ||||
|             expiresAt: tomorrow, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(201); | ||||
| }); | ||||
| @ -260,17 +259,21 @@ test('should not get user with invalid token', async () => { | ||||
| }); | ||||
| 
 | ||||
| test('should not get user with expired token', async () => { | ||||
|     const token = await patStore.create({ | ||||
|         id: 1, | ||||
|         secret: 'user:expired-token', | ||||
|         description: 'expired-token', | ||||
|         userId: 1, | ||||
|         expiresAt: new Date('2020-01-01'), | ||||
|     }); | ||||
|     const secret = 'user:expired-token'; | ||||
| 
 | ||||
|     await patStore.create( | ||||
|         { | ||||
|             id: 1, | ||||
|             description: 'expired-token', | ||||
|             expiresAt: '2020-01-01', | ||||
|         }, | ||||
|         secret, | ||||
|         1, | ||||
|     ); | ||||
| 
 | ||||
|     await app.request | ||||
|         .get('/api/admin/user') | ||||
|         .set('Authorization', token.secret) | ||||
|         .set('Authorization', secret) | ||||
|         .expect(401); | ||||
| }); | ||||
| 
 | ||||
| @ -290,7 +293,7 @@ test('should fail creation of PAT when PAT limit has been reached', async () => | ||||
|                 .send({ | ||||
|                     description: `my pat ${i}`, | ||||
|                     expiresAt: tomorrow, | ||||
|                 } as IPat) | ||||
|                 }) | ||||
|                 .set('Content-Type', 'application/json') | ||||
|                 .expect(201), | ||||
|         ); | ||||
| @ -302,7 +305,7 @@ test('should fail creation of PAT when PAT limit has been reached', async () => | ||||
|         .send({ | ||||
|             description: `my pat ${PAT_LIMIT}`, | ||||
|             expiresAt: tomorrow, | ||||
|         } as IPat) | ||||
|         }) | ||||
|         .set('Content-Type', 'application/json') | ||||
|         .expect(403); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										14
									
								
								src/test/fixtures/fake-pat-store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								src/test/fixtures/fake-pat-store.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,8 +1,12 @@ | ||||
| import { IPatStore } from '../../lib/types/stores/pat-store'; | ||||
| import { IPat } from '../../lib/types/models/pat'; | ||||
| import { CreatePatSchema, PatSchema } from '../../lib/openapi'; | ||||
| /* eslint-disable @typescript-eslint/no-unused-vars */ | ||||
| export default class FakePatStore implements IPatStore { | ||||
|     create(group: IPat): Promise<IPat> { | ||||
|     create( | ||||
|         pat: CreatePatSchema, | ||||
|         secret: string, | ||||
|         userId: number, | ||||
|     ): Promise<PatSchema> { | ||||
|         throw new Error('Method not implemented.'); | ||||
|     } | ||||
| 
 | ||||
| @ -31,15 +35,15 @@ export default class FakePatStore implements IPatStore { | ||||
|         throw new Error('Method not implemented.'); | ||||
|     } | ||||
| 
 | ||||
|     get(key: number): Promise<IPat> { | ||||
|     get(key: number): Promise<PatSchema> { | ||||
|         throw new Error('Method not implemented.'); | ||||
|     } | ||||
| 
 | ||||
|     getAll(query?: Object): Promise<IPat[]> { | ||||
|     getAll(query?: Object): Promise<PatSchema[]> { | ||||
|         throw new Error('Method not implemented.'); | ||||
|     } | ||||
| 
 | ||||
|     getAllByUser(userId: number): Promise<IPat[]> { | ||||
|     getAllByUser(userId: number): Promise<PatSchema[]> { | ||||
|         throw new Error('Method not implemented.'); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user