import { Context, Next } from '@tachybase/actions'; import { Registry } from '@tachybase/utils'; import { Auth, AuthExtend } from './auth'; import { JwtOptions, JwtService } from './base/jwt-service'; import { ITokenBlacklistService } from './base/token-blacklist-service'; export interface Authenticator { authType: string; options: Record; [key: string]: any; } export interface Storer { get: (name: string) => Promise; } export type AuthManagerOptions = { authKey: string; default?: string; jwt?: JwtOptions; }; type AuthConfig = { auth: AuthExtend; // The authentication class. title?: string; // The display name of the authentication type. }; export class AuthManager { jwt: JwtService; protected options: AuthManagerOptions; protected authTypes: Registry = new Registry(); // authenticators collection manager. protected storer: Storer; constructor(options: AuthManagerOptions) { this.options = options; this.jwt = new JwtService(options.jwt); } setStorer(storer: Storer) { this.storer = storer; } setTokenBlacklistService(service: ITokenBlacklistService) { this.jwt.blacklist = service; } /** * registerTypes * @description Add a new authenticate type and the corresponding authenticator. * The types will show in the authenticators list of the admin panel. * * @param authType - The type of the authenticator. It is required to be unique. * @param authConfig - Configurations of the kind of authenticator. */ registerTypes(authType: string, authConfig: AuthConfig) { this.authTypes.register(authType, authConfig); } listTypes() { return Array.from(this.authTypes.getEntities()).map(([authType, authConfig]) => ({ name: authType, title: authConfig.title, })); } getAuthConfig(authType: string) { return this.authTypes.get(authType); } /** * get * @description Get authenticator instance by name. * @param name - The name of the authenticator. * @return authenticator instance. */ async get(name: string, ctx: Context) { if (!this.storer) { throw new Error('AuthManager.storer is not set.'); } const authenticator = await this.storer.get(name); if (!authenticator) { throw new Error(`Authenticator [${name}] is not found.`); } const { auth } = this.authTypes.get(authenticator.authType); if (!auth) { throw new Error(`AuthType [${name}] is not found.`); } return new auth({ authenticator, options: authenticator.options, ctx }); } /** * middleware * @description Auth middleware, used to check the authentication status. */ middleware() { return async (ctx: Context & { auth: Auth }, next: Next) => { const token = ctx.getBearerToken(); if (token && (await ctx.app.authManager.jwt.blacklist?.has(token))) { return ctx.throw(403, ctx.t('token is not available')); } const name = ctx.get(this.options.authKey) || this.options.default; let authenticator: Auth; try { authenticator = await ctx.app.authManager.get(name, ctx); ctx.auth = authenticator; } catch (err) { ctx.auth = {} as Auth; ctx.logger.warn(err.message, { method: 'check', authenticator: name }); return next(); } if (authenticator) { const user = await ctx.auth.check(); if (user) { ctx.auth.user = user; } } await next(); }; } }