From 9688955d4b4763b08bee8dddd4abe0b73bd0f42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Fri, 3 Nov 2023 12:00:24 +0100 Subject: [PATCH] chore: expose types so we can use them properly (#5251) Expose types to be used in enterprise and cloud addons --- .../middleware/api-token-middleware.test.ts | 24 ++++++++++--------- src/lib/middleware/api-token-middleware.ts | 13 +++++----- src/lib/proxy/proxy-repository.ts | 6 ++--- src/lib/routes/unleash-types.ts | 16 +++++++++++-- src/lib/services/api-token-service.ts | 6 ++--- src/lib/types/api-user.ts | 18 +++++++++++--- src/lib/types/user.ts | 16 ++++++------- .../leading-slashes-are-stripped.e2e.test.ts | 2 +- 8 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/lib/middleware/api-token-middleware.test.ts b/src/lib/middleware/api-token-middleware.test.ts index 5339bc3586..1222d06585 100644 --- a/src/lib/middleware/api-token-middleware.test.ts +++ b/src/lib/middleware/api-token-middleware.test.ts @@ -6,22 +6,24 @@ import { ALL, ApiTokenType } from '../types/models/api-token'; import apiTokenMiddleware, { TOKEN_TYPE_ERROR_MESSAGE, } from './api-token-middleware'; +import { ApiTokenService } from 'lib/services'; +import { IUnleashConfig } from 'lib/types'; -let config: any; +let config: IUnleashConfig; beforeEach(() => { - config = { + config = createTestConfig({ getLogger, authentication: { enableApiToken: true, }, - }; + }); }); test('should not do anything if request does not contain a authorization', async () => { const apiTokenService = { getUserForToken: jest.fn(), - }; + } as unknown as ApiTokenService; const func = apiTokenMiddleware(config, { apiTokenService }); @@ -40,7 +42,7 @@ test('should not do anything if request does not contain a authorization', async test('should not add user if unknown token', async () => { const apiTokenService = { getUserForToken: jest.fn(), - }; + } as unknown as ApiTokenService; const func = apiTokenMiddleware(config, { apiTokenService }); @@ -61,7 +63,7 @@ test('should not add user if unknown token', async () => { test('should not make database query when provided PAT format', async () => { const apiTokenService = { getUserForToken: jest.fn(), - }; + } as unknown as ApiTokenService; const func = apiTokenMiddleware(config, { apiTokenService }); @@ -91,7 +93,7 @@ test('should add user if known token', async () => { }); const apiTokenService = { getUserForToken: jest.fn().mockReturnValue(apiUser), - }; + } as unknown as ApiTokenService; const func = apiTokenMiddleware(config, { apiTokenService }); @@ -124,7 +126,7 @@ test('should not add user if not /api/client', async () => { const apiTokenService = { getUserForToken: jest.fn().mockReturnValue(apiUser), - }; + } as unknown as ApiTokenService; const func = apiTokenMiddleware(config, { apiTokenService }); const cb = jest.fn(); @@ -162,7 +164,7 @@ test('should not add user if disabled', async () => { }); const apiTokenService = { getUserForToken: jest.fn().mockReturnValue(apiUser), - }; + } as unknown as ApiTokenService; const disabledConfig = createTestConfig({ getLogger, @@ -203,7 +205,7 @@ test('should call next if apiTokenService throws', async () => { getUserForToken: () => { throw new Error('hi there, i am stupid'); }, - }; + } as unknown as ApiTokenService; const func = apiTokenMiddleware(config, { apiTokenService }); @@ -226,7 +228,7 @@ test('should call next if apiTokenService throws x2', async () => { getUserForToken: () => { throw new Error('hi there, i am stupid'); }, - }; + } as unknown as ApiTokenService; const func = apiTokenMiddleware(config, { apiTokenService }); diff --git a/src/lib/middleware/api-token-middleware.ts b/src/lib/middleware/api-token-middleware.ts index 62ec2a9ac9..7dbd1e64e8 100644 --- a/src/lib/middleware/api-token-middleware.ts +++ b/src/lib/middleware/api-token-middleware.ts @@ -1,7 +1,7 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { ApiTokenType } from '../types/models/api-token'; import { IUnleashConfig } from '../types/option'; -import { IAuthRequest } from '../routes/unleash-types'; +import { IApiRequest, IAuthRequest } from '../routes/unleash-types'; +import { IUnleashServices } from 'lib/server-impl'; const isClientApi = ({ path }) => { return path && path.indexOf('/api/client') > -1; @@ -33,7 +33,7 @@ const apiAccessMiddleware = ( authentication, flagResolver, }: Pick, - { apiTokenService }: any, + { apiTokenService }: Pick, ): any => { const logger = getLogger('/middleware/api-token.ts'); logger.debug('Enabling api-token middleware'); @@ -42,7 +42,7 @@ const apiAccessMiddleware = ( return (req, res, next) => next(); } - return (req: IAuthRequest, res, next) => { + return (req: IAuthRequest | IApiRequest, res, next) => { if (req.user) { return next(); } @@ -50,7 +50,9 @@ const apiAccessMiddleware = ( try { const apiToken = req.header('authorization'); if (!apiToken?.startsWith('user:')) { - const apiUser = apiTokenService.getUserForToken(apiToken); + const apiUser = apiToken + ? apiTokenService.getUserForToken(apiToken) + : undefined; const { CLIENT, FRONTEND } = ApiTokenType; if (apiUser) { @@ -79,7 +81,6 @@ const apiAccessMiddleware = ( } catch (error) { logger.warn(error); } - next(); }; }; diff --git a/src/lib/proxy/proxy-repository.ts b/src/lib/proxy/proxy-repository.ts index 4f832a349c..3f1e398d84 100644 --- a/src/lib/proxy/proxy-repository.ts +++ b/src/lib/proxy/proxy-repository.ts @@ -2,7 +2,7 @@ import EventEmitter from 'events'; import { RepositoryInterface } from 'unleash-client/lib/repository'; import { Segment } from 'unleash-client/lib/strategy/strategy'; import { FeatureInterface } from 'unleash-client/lib/feature'; -import ApiUser from '../types/api-user'; +import { IApiUser } from '../types/api-user'; import { IUnleashConfig, IUnleashServices, IUnleashStores } from '../types'; import { mapFeaturesForClient, @@ -35,7 +35,7 @@ export class ProxyRepository extends EventEmitter implements RepositoryInterface private readonly configurationRevisionService: ConfigurationRevisionService; - private readonly token: ApiUser; + private readonly token: IApiUser; private features: FeatureInterface[]; @@ -51,7 +51,7 @@ export class ProxyRepository extends EventEmitter implements RepositoryInterface config: Config, stores: Stores, services: Services, - token: ApiUser, + token: IApiUser, ) { super(); this.config = config; diff --git a/src/lib/routes/unleash-types.ts b/src/lib/routes/unleash-types.ts index 4c684edf02..c6fb331170 100644 --- a/src/lib/routes/unleash-types.ts +++ b/src/lib/routes/unleash-types.ts @@ -1,5 +1,6 @@ import { Request } from 'express'; -import User from '../types/user'; +import IUser from '../types/user'; +import { IApiUser } from '../types'; export interface IAuthRequest< PARAM = any, @@ -7,7 +8,18 @@ export interface IAuthRequest< ReqBody = any, ReqQuery = any, > extends Request { - user: User; + user: IUser; + logout: (() => void) | ((callback: (err?: any) => void) => void); + session: any; +} + +export interface IApiRequest< + PARAM = any, + ResBody = any, + ReqBody = any, + ReqQuery = any, +> extends Request { + user: IApiUser; logout: (() => void) | ((callback: (err?: any) => void) => void); session: any; } diff --git a/src/lib/services/api-token-service.ts b/src/lib/services/api-token-service.ts index 064f08680e..5ede0b06e5 100644 --- a/src/lib/services/api-token-service.ts +++ b/src/lib/services/api-token-service.ts @@ -3,7 +3,7 @@ import { Logger } from '../logger'; import { ADMIN, CLIENT, FRONTEND } from '../types/permissions'; import { IUnleashStores } from '../types/stores'; import { IUnleashConfig } from '../types/option'; -import ApiUser from '../types/api-user'; +import ApiUser, { IApiUser } from '../types/api-user'; import { ApiTokenType, IApiToken, @@ -121,7 +121,7 @@ export class ApiTokenService { } } - public getUserForToken(secret: string): ApiUser | undefined { + public getUserForToken(secret: string): IApiUser | undefined { if (!secret) { return undefined; } @@ -138,7 +138,7 @@ export class ApiTokenService { token = this.activeTokens.find( (activeToken) => Boolean(activeToken.alias) && - constantTimeCompare(activeToken.alias, secret), + constantTimeCompare(activeToken.alias!, secret), ); } diff --git a/src/lib/types/api-user.ts b/src/lib/types/api-user.ts index 3e608a8065..a62c6b0a80 100644 --- a/src/lib/types/api-user.ts +++ b/src/lib/types/api-user.ts @@ -3,7 +3,7 @@ import { ValidationError } from 'joi'; import { CLIENT } from './permissions'; -interface IApiUserData { +export interface IApiUserData { permissions?: string[]; projects?: string[]; project?: string; @@ -13,7 +13,16 @@ interface IApiUserData { tokenName: string; } -export default class ApiUser { +export interface IApiUser { + username: string; + permissions: string[]; + projects: string[]; + environment: string; + type: ApiTokenType; + secret: string; +} + +export default class ApiUser implements IApiUser { readonly isAPI: boolean = true; readonly permissions: string[]; @@ -26,6 +35,8 @@ export default class ApiUser { readonly secret: string; + readonly username: string; + constructor({ permissions = [CLIENT], projects, @@ -38,6 +49,7 @@ export default class ApiUser { if (!tokenName) { throw new ValidationError('tokenName is required', [], undefined); } + this.username = tokenName; this.permissions = permissions; this.environment = environment; this.type = type; @@ -45,7 +57,7 @@ export default class ApiUser { if (projects && projects.length > 0) { this.projects = projects; } else { - this.projects = [project]; + this.projects = project ? [project] : []; } } } diff --git a/src/lib/types/user.ts b/src/lib/types/user.ts index a733503656..42934f93ae 100644 --- a/src/lib/types/user.ts +++ b/src/lib/types/user.ts @@ -23,9 +23,9 @@ export interface IUser { email?: string; inviteLink?: string; seenAt?: Date; - createdAt: Date; + createdAt?: Date; permissions: string[]; - loginAttempts: number; + loginAttempts?: number; isAPI: boolean; imageUrl: string; accountType?: AccountType; @@ -50,11 +50,11 @@ export default class User implements IUser { imageUrl: string; - seenAt: Date; + seenAt?: Date; - loginAttempts: number; + loginAttempts?: number; - createdAt: Date; + createdAt?: Date; accountType?: AccountType = 'User'; @@ -77,9 +77,9 @@ export default class User implements IUser { Joi.assert(name, Joi.string(), 'Name'); this.id = id; - this.name = name; - this.username = username; - this.email = email; + this.name = name!; + this.username = username!; + this.email = email!; this.imageUrl = imageUrl || this.generateImageUrl(); this.seenAt = seenAt; this.loginAttempts = loginAttempts; diff --git a/src/test/e2e/api/auth/leading-slashes-are-stripped.e2e.test.ts b/src/test/e2e/api/auth/leading-slashes-are-stripped.e2e.test.ts index a173e60611..a4fb58b61b 100644 --- a/src/test/e2e/api/auth/leading-slashes-are-stripped.e2e.test.ts +++ b/src/test/e2e/api/auth/leading-slashes-are-stripped.e2e.test.ts @@ -29,7 +29,7 @@ afterAll(async () => { await db.destroy(); }); -test('Access to//api/admin/tags are refused no matter how many leading slashes', async () => { +test('Access to //api/admin/tags are refused no matter how many leading slashes', async () => { await app.request.get('//api/admin/tags').expect(401); await app.request.get('////api/admin/tags').expect(401); });