UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

133 lines (119 loc) 4.38 kB
import { $inject, Alepha, AlephaError } from "alepha"; import type { ParameterPrimitive } from "alepha/api/parameters"; import { $repository, type Repository } from "alepha/orm"; import { type RealmAuthSettings, realmAuthSettingsAtom, } from "../atoms/realmAuthSettingsAtom.ts"; import { identities } from "../entities/identities.ts"; import { sessions } from "../entities/sessions.ts"; import { DEFAULT_USER_REALM_NAME, users } from "../entities/users.ts"; import type { RealmFeatures, RealmOptions } from "../primitives/$realm.ts"; export interface RealmRepositories { identities: Repository<typeof identities.schema>; sessions: Repository<typeof sessions.schema>; users: Repository<typeof users.schema>; } export interface Realm { name: string; repositories: RealmRepositories; settings: RealmAuthSettings; features: RealmFeatures; settingsParameter?: ParameterPrimitive<typeof realmAuthSettingsAtom.schema>; getSettings(): Promise<RealmAuthSettings>; } export class RealmProvider { protected readonly alepha = $inject(Alepha); // Default repositories using $repository() for eager initialization protected readonly defaultIdentities = $repository(identities); protected readonly defaultSessions = $repository(sessions); protected readonly defaultUsers = $repository(users); protected realms = new Map<string, Realm>(); public register(realmName: string, realmOptions: RealmOptions = {}) { if (realmName.includes(".")) { throw new AlephaError( `Realm name "${realmName}" must not contain dots — dots are reserved for parameter tree paths`, ); } // Merge features with defaults const features: RealmFeatures = { jobs: false, notifications: false, apiKeys: false, parameters: false, avatars: false, audits: false, ...realmOptions.features, }; const realm: Realm = { name: realmName, repositories: { identities: realmOptions.entities?.identities ?? this.defaultIdentities, sessions: realmOptions.entities?.sessions ?? this.defaultSessions, users: realmOptions.entities?.users ?? this.defaultUsers, }, // TODO: Remove deep merge when alepha supports it natively settings: { ...realmAuthSettingsAtom.options.default, ...realmOptions.settings, passwordPolicy: { ...realmAuthSettingsAtom.options.default.passwordPolicy, ...realmOptions.settings?.passwordPolicy, }, loginRateLimit: { ...realmAuthSettingsAtom.options.default.loginRateLimit, ...realmOptions.settings?.loginRateLimit, }, refreshToken: { ...realmAuthSettingsAtom.options.default.refreshToken, ...realmOptions.settings?.refreshToken, }, }, features, getSettings: async function () { if (this.settingsParameter) { return await this.settingsParameter.get(); } return this.settings; }, }; this.realms.set(realmName, realm); return this.getRealm(realmName); } /** * Gets a registered realm by name, auto-creating default if needed. */ public getRealm(realmName = DEFAULT_USER_REALM_NAME): Realm { let realm = this.realms.get(realmName); if (!realm) { // Auto-register default realm for backward compatibility const realms = Array.from(this.realms.values()); const firstRealm = realms[0]; if (realmName === DEFAULT_USER_REALM_NAME && firstRealm) { realm = firstRealm; } else if (this.alepha.isTest()) { realm = this.register(realmName); // Auto-create default realm in tests } else { throw new AlephaError( `Missing realm '${realmName}', please declare $realm in your application.`, ); } } return realm; } public identityRepository( realmName = DEFAULT_USER_REALM_NAME, ): Repository<typeof identities.schema> { return this.getRealm(realmName).repositories.identities; } public sessionRepository( realmName = DEFAULT_USER_REALM_NAME, ): Repository<typeof sessions.schema> { return this.getRealm(realmName).repositories.sessions; } public userRepository( realmName = DEFAULT_USER_REALM_NAME, ): Repository<typeof users.schema> { return this.getRealm(realmName).repositories.users; } }