import type { AlpRouteRef, Context } from "alp-node"; import type MongoUsersManager from "./MongoUsersManager"; import type { AccessResponseHooks, AuthenticationService, } from "./services/authentification/AuthenticationService"; import type { AllowedMapParamsStrategy, AllowedStrategyKeys, } from "./services/authentification/types"; import type { User, UserSanitized } from "./types"; export interface CreateAuthControllerParams< StrategyKeys extends AllowedStrategyKeys, U extends User = User, USanitized extends UserSanitized = UserSanitized, > { authenticationService: AuthenticationService; homeRouterKey?: string; usersManager: MongoUsersManager; defaultStrategy?: StrategyKeys; authHooks?: AuthHooks; } export interface AuthController { login: AlpRouteRef; addScope: AlpRouteRef; response: AlpRouteRef; logout: AlpRouteRef; } type OptionalRecord = Partial>; export interface AuthHooks extends AccessResponseHooks { paramsForLogin?: ( strategy: StrategyKey, ctx: Context, ) => | OptionalRecord | Promise> | Promise // eslint-disable-next-line @typescript-eslint/no-invalid-void-type | void; } export function createAuthController< StrategyKeys extends AllowedStrategyKeys, U extends User = User, USanitized extends UserSanitized = UserSanitized, >({ usersManager, authenticationService, homeRouterKey = "/", defaultStrategy, authHooks = {}, }: CreateAuthControllerParams): AuthController { return { async login(ctx: Context): Promise { const strategy: StrategyKeys = (ctx.namedRouteParam("strategy") || defaultStrategy) as StrategyKeys; if (!strategy) throw new Error("Strategy missing"); const params = (authHooks.paramsForLogin && (await authHooks.paramsForLogin(strategy, ctx))) || {}; await authenticationService.redirectAuthUrl(ctx, strategy, {}, params); }, /** * Add scope in existing * The user must already be connected */ async addScope(ctx: Context): Promise { if (!ctx.state.loggedInUser) { ctx.redirectTo(homeRouterKey); return; } const strategy: StrategyKeys = (ctx.namedRouteParam("strategy") || defaultStrategy) as StrategyKeys; if (!strategy) throw new Error("Strategy missing"); const scopeKey = ctx.namedRouteParam("scopeKey"); if (!scopeKey) throw new Error("Scope missing"); await authenticationService.redirectAuthUrl(ctx, strategy, { scopeKey }); }, async response(ctx: Context): Promise { const strategy: StrategyKeys = ctx.namedRouteParam( "strategy", ) as StrategyKeys; ctx.assert(strategy); const loggedInUser = await authenticationService.accessResponse( ctx, strategy, !!ctx.state.loggedInUser, { afterLoginSuccess: authHooks.afterLoginSuccess, afterScopeUpdate: authHooks.afterScopeUpdate, }, ); const keyPath = usersManager.store.keyPath; await ctx.setLoggedIn(loggedInUser[keyPath], loggedInUser); ctx.redirectTo(homeRouterKey); }, // eslint-disable-next-line @typescript-eslint/require-await -- keep async in case i later need await in this method async logout(ctx: Context): Promise { ctx.logout(); ctx.redirectTo(homeRouterKey); }, }; }