alepha 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/jobs/index.d.ts +20 -20
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/keys/index.d.ts +6 -6
- package/dist/api/users/index.d.ts +43 -9
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +24 -3
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +13 -13
- package/dist/cli/core/index.d.ts +46 -40
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +51 -101
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/i18n/index.d.ts +12 -5
- package/dist/cli/i18n/index.d.ts.map +1 -1
- package/dist/cli/i18n/index.js +45 -11
- package/dist/cli/i18n/index.js.map +1 -1
- package/dist/cli/platform-lib/index.d.ts +32 -6
- package/dist/cli/platform-lib/index.d.ts.map +1 -1
- package/dist/cli/platform-lib/index.js +82 -19
- package/dist/cli/platform-lib/index.js.map +1 -1
- package/dist/command/index.d.ts +1 -1
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +23 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/react/form/index.d.ts +0 -1
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +16 -15
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +43 -0
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +114 -10
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/router/index.browser.js +128 -5
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +108 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +184 -6
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/sitemap/index.browser.js +35 -0
- package/dist/react/sitemap/index.browser.js.map +1 -0
- package/dist/react/sitemap/index.d.ts +92 -0
- package/dist/react/sitemap/index.d.ts.map +1 -0
- package/dist/react/sitemap/index.js +131 -0
- package/dist/react/sitemap/index.js.map +1 -0
- package/dist/server/auth/index.d.ts +105 -1
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1604 -7
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts +15 -0
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +22 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +18 -0
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +25 -0
- package/dist/server/core/index.js.map +1 -1
- package/package.json +16 -3
- package/src/api/users/controllers/RealmController.ts +1 -0
- package/src/api/users/primitives/$realm.ts +26 -0
- package/src/api/users/providers/RealmProvider.ts +15 -0
- package/src/api/users/schemas/realmConfigSchema.ts +14 -0
- package/src/cli/core/atoms/buildOptions.ts +0 -12
- package/src/cli/core/commands/build.ts +0 -10
- package/src/cli/core/index.ts +0 -3
- package/src/cli/core/tasks/BuildCloudflareTask.ts +37 -17
- package/src/cli/core/tasks/BuildPrerenderTask.ts +44 -7
- package/src/cli/i18n/__tests__/I18nCheckService.spec.ts +48 -0
- package/src/cli/i18n/services/I18nCheckService.ts +65 -11
- package/src/cli/platform-lib/adapters/CloudflareAdapter.ts +128 -36
- package/src/mcp/__tests__/McpServerProvider.spec.ts +71 -0
- package/src/mcp/providers/McpServerProvider.ts +55 -0
- package/src/react/form/__tests__/FormModel-submit-loading.spec.ts +71 -0
- package/src/react/form/__tests__/form-submitting-reactive.browser.spec.tsx +96 -0
- package/src/react/form/services/FormModel.ts +57 -39
- package/src/react/i18n/__tests__/I18nProvider.spec.ts +89 -0
- package/src/react/i18n/__tests__/locale-routing.spec.ts +107 -0
- package/src/react/i18n/providers/I18nProvider.ts +171 -12
- package/src/react/router/__tests__/RouterLocaleProvider.spec.ts +127 -0
- package/src/react/router/index.browser.ts +4 -0
- package/src/react/router/index.shared.ts +1 -0
- package/src/react/router/index.ts +9 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +15 -1
- package/src/react/router/providers/ReactPageProvider.ts +12 -1
- package/src/react/router/providers/ReactServerProvider.ts +92 -1
- package/src/react/router/providers/RootComponentsProvider.ts +13 -0
- package/src/react/router/providers/RouterLocaleProvider.ts +125 -0
- package/src/react/router/providers/__tests__/RootComponentsProvider.spec.ts +15 -0
- package/src/react/router/providers/__tests__/rootComponents.ssr.browser.spec.tsx +67 -0
- package/src/react/sitemap/__tests__/$sitemap.spec.ts +131 -0
- package/src/react/sitemap/index.browser.ts +21 -0
- package/src/react/sitemap/index.ts +25 -0
- package/src/react/sitemap/primitives/$sitemap.browser.ts +26 -0
- package/src/react/sitemap/primitives/$sitemap.ts +196 -0
- package/src/server/auth/__tests__/appleClientSecret.spec.ts +34 -0
- package/src/server/auth/__tests__/authFederationClient.spec.ts +40 -0
- package/src/server/auth/__tests__/federationAssertion.spec.ts +146 -0
- package/src/server/auth/__tests__/federationRedirectReplay.spec.ts +44 -0
- package/src/server/auth/helpers/appleClientSecret.ts +24 -0
- package/src/server/auth/helpers/federationAssertion.ts +74 -0
- package/src/server/auth/helpers/jtiReplayGuard.ts +41 -0
- package/src/server/auth/helpers/safeRedirectPath.ts +19 -0
- package/src/server/auth/index.ts +4 -0
- package/src/server/auth/primitives/$authFederationBroker.ts +273 -0
- package/src/server/auth/primitives/$authFederationClient.ts +89 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +18 -4
- package/src/server/cookies/__tests__/ServerCookiesProvider.spec.ts +70 -0
- package/src/server/cookies/providers/ServerCookiesProvider.ts +23 -3
- package/src/server/core/interfaces/ServerRequest.ts +8 -0
- package/src/server/core/primitives/$route.ts +27 -0
- package/src/cli/core/tasks/BuildSitemapTask.ts +0 -130
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["INTENT_TTL_MINUTES"],"sources":["../../../src/api/users/audits/SessionAudits.ts","../../../src/api/users/audits/UserAudits.ts","../../../src/api/users/buckets/UserBuckets.ts","../../../src/api/users/schemas/identityQuerySchema.ts","../../../src/api/users/entities/users.ts","../../../src/api/users/entities/identities.ts","../../../src/api/users/schemas/identityResourceSchema.ts","../../../src/api/users/atoms/realmAuthSettingsAtom.ts","../../../src/api/users/entities/sessions.ts","../../../src/api/users/providers/RealmProvider.ts","../../../src/api/users/services/IdentityService.ts","../../../src/api/users/controllers/AdminIdentityController.ts","../../../src/api/users/schemas/sessionQuerySchema.ts","../../../src/api/users/schemas/sessionResourceSchema.ts","../../../src/api/users/services/SessionCrudService.ts","../../../src/api/users/controllers/AdminSessionController.ts","../../../src/api/users/schemas/createUserSchema.ts","../../../src/api/users/schemas/updateUserSchema.ts","../../../src/api/users/schemas/userQuerySchema.ts","../../../src/api/users/schemas/userResourceSchema.ts","../../../src/api/users/notifications/UserNotifications.ts","../../../src/api/users/services/UserService.ts","../../../src/api/users/controllers/AdminUserController.ts","../../../src/api/users/schemas/realmConfigSchema.ts","../../../src/api/users/controllers/RealmController.ts","../../../src/api/users/schemas/completePasswordResetRequestSchema.ts","../../../src/api/users/schemas/completeRegistrationRequestSchema.ts","../../../src/api/users/schemas/passwordResetIntentResponseSchema.ts","../../../src/api/users/schemas/registerQuerySchema.ts","../../../src/api/users/schemas/registerRequestSchema.ts","../../../src/api/users/schemas/registrationIntentResponseSchema.ts","../../../src/api/users/services/CredentialService.ts","../../../src/api/users/services/UsernameSlugger.ts","../../../src/api/users/services/RegistrationService.ts","../../../src/api/users/controllers/UserController.ts","../../../src/api/users/jobs/UserJobs.ts","../../../src/api/users/services/SessionService.ts","../../../src/api/users/primitives/$realm.ts","../../../src/api/users/schemas/loginSchema.ts","../../../src/api/users/schemas/registerSchema.ts","../../../src/api/users/schemas/resetPasswordSchema.ts","../../../src/api/users/index.ts"],"sourcesContent":["import { $audit } from \"alepha/api/audits\";\n\n/**\n * Authentication & session-security audit events.\n *\n * Holds two audit types:\n * - `auth` — login / logout / token refresh / MFA.\n * - `security` — rate limiting, session invalidation, and related guards.\n *\n * Failed events are logged with `success: false`; severity (`warning`) is\n * derived centrally in `AuditService.create`. Register as a module variant\n * and log via the exposed primitives:\n * `sessionAudits(realm)?.auth.log(\"login\", { success: false, … })`.\n */\nexport class SessionAudits {\n public readonly auth = $audit({\n type: \"auth\",\n description: \"Authentication events (login, logout, token refresh, MFA).\",\n actions: [\"login\", \"logout\", \"token_refresh\", \"mfa_setup\", \"mfa_verify\"],\n });\n\n public readonly security = $audit({\n type: \"security\",\n description:\n \"Security events (rate limiting, session invalidation, blocked access).\",\n actions: [\n \"rate_limited\",\n \"sessions_invalidated\",\n \"permission_denied\",\n \"blocked\",\n ],\n });\n}\n","import { $audit } from \"alepha/api/audits\";\n\n/**\n * User-management audit events.\n *\n * Holds the `user` audit type. Mirrors the `$notification`/`$job` holder\n * pattern (see {@link UserNotifications}) — register as a module variant and\n * log via the exposed primitive: `userAudits(realm)?.user.log(\"create\", …)`.\n */\nexport class UserAudits {\n public readonly user = $audit({\n type: \"user\",\n description:\n \"User management events (create, update, delete, role/password changes).\",\n actions: [\n \"create\",\n \"update\",\n \"delete\",\n \"role_change\",\n \"password_change\",\n \"enable\",\n \"disable\",\n ],\n });\n}\n","import { $bucket } from \"alepha/bucket\";\n\n/**\n * User-specific file storage wrapper service.\n *\n * This service provides file storage for user-related files such as:\n * - User avatars/profile pictures\n *\n * Declared as a module variant — not auto-injected. It is instantiated\n * lazily the first time something calls `alepha.inject(UserBuckets)`.\n */\nexport class UserBuckets {\n /**\n * Bucket for user avatar storage.\n */\n public readonly avatars = $bucket({\n maxSize: 5 * 1024 * 1024, // 5 MB\n mimeTypes: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"],\n });\n}\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const identityQuerySchema = t.extend(pageQuerySchema, {\n userId: t.optional(t.uuid()),\n provider: t.optional(t.string()),\n});\n\nexport type IdentityQuery = Static<typeof identityQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db, sql } from \"alepha/orm\";\n\nexport const DEFAULT_USER_REALM_NAME = \"default\";\n\nexport const users = $entity({\n name: \"users\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n realm: db.default(t.text(), DEFAULT_USER_REALM_NAME),\n\n username: t.optional(\n t.shortText({\n minLength: 3,\n maxLength: 30,\n // pattern is handled at the realm settings level\n }),\n ),\n\n email: t.optional(t.string({ format: \"email\" })),\n\n phoneNumber: t.optional(t.e164()),\n\n roles: db.default(t.array(t.string()), []),\n firstName: t.optional(t.string()),\n lastName: t.optional(t.string()),\n picture: t.optional(t.string()),\n enabled: db.default(t.boolean(), true),\n\n emailVerified: db.default(t.boolean(), false),\n\n lastLoginAt: t.optional(t.datetime()),\n\n organizationId: db.organization(),\n }),\n indexes: [\n {\n expressions: (self) => [self.realm, sql`LOWER(${self.username})`],\n unique: true,\n name: \"users_realm_username_lower_idx\",\n },\n { columns: [\"realm\", \"email\"], unique: true },\n { columns: [\"realm\", \"phoneNumber\"], unique: true },\n ],\n});\n\nexport type UserEntity = Static<typeof users.schema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\nimport { users } from \"./users.ts\";\n\nexport const identities = $entity({\n name: \"identities\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n userId: db.ref(t.uuid(), () => users.cols.id),\n password: t.optional(t.text()),\n provider: t.text(),\n providerUserId: t.optional(t.text()),\n providerData: t.optional(t.json()),\n }),\n indexes: [\n \"userId\",\n \"provider\",\n { columns: [\"userId\", \"provider\"] },\n { columns: [\"provider\", \"providerUserId\"], unique: true },\n ],\n});\n\nexport type IdentityEntity = Static<typeof identities.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { identities } from \"../entities/identities.ts\";\n\nexport const identityResourceSchema = t.omit(identities.schema, [\"password\"]);\n\nexport type IdentityResource = Static<typeof identityResourceSchema>;\n","import { $atom, type Static, t } from \"alepha\";\n\n/**\n * Tri-state field requirement for realm auth settings.\n *\n * - `\"none\"`: Field is disabled and not shown.\n * - `\"optional\"`: Field is shown but not required.\n * - `\"required\"`: Field is shown and required.\n */\nexport type FieldRequirement = \"none\" | \"optional\" | \"required\";\n\nconst fieldRequirement = (description: string) =>\n t.union([t.const(\"none\"), t.const(\"optional\"), t.const(\"required\")], {\n description,\n });\n\n/**\n * Username-specific field requirement, extending {@link FieldRequirement}\n * with an additional auto-derivation mode.\n *\n * - `\"none\"` / `\"optional\"` / `\"required\"`: same as {@link FieldRequirement}.\n * - `\"email\"`: Field is hidden in the registration UI and the value is\n * auto-derived from the user's email at signup. Same handling on\n * credentials and OAuth flows. See `UsernameSlugger` for the rule and\n * collision behavior.\n */\nexport type UsernameFieldRequirement = FieldRequirement | \"email\";\n\nconst usernameFieldRequirement = (description: string) =>\n t.union(\n [\n t.const(\"none\"),\n t.const(\"optional\"),\n t.const(\"required\"),\n t.const(\"email\"),\n ],\n {\n description,\n },\n );\n\nexport const realmAuthSettingsAtom = $atom({\n name: \"alepha.api.users.realmAuthSettings\",\n schema: t.object({\n // Branding and display settings\n displayName: t.optional(\n t.string({\n description:\n \"Display name shown on auth pages (e.g., 'Customer Portal')\",\n }),\n ),\n description: t.optional(\n t.string({\n description: \"Description shown on auth pages\",\n }),\n ),\n logoUrl: t.optional(\n t.string({\n description: \"Logo URL for auth pages\",\n }),\n ),\n\n // Auth settings\n registrationAllowed: t.boolean({\n description: \"Enable user self-registration\",\n }),\n email: fieldRequirement(\n \"Email address field requirement for user accounts\",\n ),\n username: usernameFieldRequirement(\n \"Username field requirement for user accounts\",\n ),\n usernameRegExp: t.string({\n description:\n \"Regular expression that usernames must match (if username is enabled)\",\n }),\n usernameBlocklist: t.array(t.text(), {\n description:\n \"Usernames that the slugger / manual registration must reject. \" +\n \"Default empty so apps can register `admin`/`root`/`me`/etc. if \" +\n \"they want; populate it explicitly for handles you want to keep \" +\n \"off-limits.\",\n }),\n phoneNumber: fieldRequirement(\n \"Phone number field requirement for user accounts\",\n ),\n verifyEmailRequired: t.boolean({\n description: \"Require email verification for user accounts\",\n }),\n verifyPhoneRequired: t.boolean({\n description: \"Require phone verification for user accounts\",\n }),\n firstNameLastName: fieldRequirement(\n \"First and last name field requirement for user accounts\",\n ),\n resetPasswordAllowed: t.boolean({\n description: \"Enable forgot password functionality\",\n }),\n captchaRequired: t.boolean({\n description:\n \"Require captcha verification on registration (needs a CaptchaProvider registered, e.g. TurnstileCaptchaProvider)\",\n }),\n adminEmails: t.array(t.email(), {\n description:\n \"List of email addresses that are automatically promoted to admin role on login\",\n }),\n adminUsernames: t.array(t.text(), {\n description:\n \"List of usernames that are automatically promoted to admin role on login\",\n }),\n defaultRoles: t.array(t.string(), {\n description: \"Default roles assigned to newly registered users\",\n }),\n verifyEmailUrl: t.optional(\n t.string({\n description:\n \"Base URL for email verification links (used when verification method is 'link'). Token and email are appended as query params.\",\n }),\n ),\n passwordPolicy: t.object({\n minLength: t.integer({\n description: \"Minimum password length\",\n default: 8,\n minimum: 1,\n }),\n requireUppercase: t.boolean({\n description: \"Require at least one uppercase letter\",\n }),\n requireLowercase: t.boolean({\n description: \"Require at least one lowercase letter\",\n }),\n requireNumbers: t.boolean({\n description: \"Require at least one number\",\n }),\n requireSpecialCharacters: t.boolean({\n description: \"Require at least one special character\",\n }),\n }),\n loginRateLimit: t.object({\n ipMaxAttempts: t.integer({\n description:\n \"Max failed login attempts per IP before temporary lockout\",\n default: 15,\n minimum: 1,\n }),\n accountMaxAttempts: t.integer({\n description:\n \"Max failed login attempts per account before temporary lockout\",\n default: 5,\n minimum: 1,\n }),\n windowMs: t.integer({\n description: \"Rate limit window duration in milliseconds\",\n default: 15 * 60 * 1000,\n minimum: 1000,\n }),\n }),\n registrationIpMaxAttempts: t.integer({\n description:\n \"Max registration attempts per IP before temporary lockout. Default 10 protects against signup abuse; raise it in dev/e2e environments where a single localhost IP spawns many test users.\",\n default: 10,\n minimum: 1,\n }),\n refreshToken: t.object({\n expirationIdle: t.optional(\n t.integer({\n description:\n \"Maximum time in milliseconds a refresh token may stay unused before being invalidated. \" +\n \"When set, sessions whose last refresh is older than this window are rejected and deleted, \" +\n \"even if the absolute `expiresAt` has not been reached. Recommended for SaaS auth posture \" +\n \"(SOC2/ISO27001). Leave undefined to disable idle invalidation (default).\",\n minimum: 1000,\n }),\n ),\n }),\n }),\n default: {\n // for a fresh hello world setup, we accept registration and email login\n registrationAllowed: true,\n email: \"required\" as FieldRequirement,\n username: \"none\" as UsernameFieldRequirement,\n // Allow hyphens by default so the UsernameSlugger output (`ni-foures-testkv`)\n // matches without app overrides. Existing `[a-zA-Z0-9_]` usernames remain\n // valid because the new pattern is a strict superset.\n usernameRegExp: \"^[a-zA-Z0-9_-]{3,30}$\",\n usernameBlocklist: [] as string[],\n phoneNumber: \"none\" as FieldRequirement,\n verifyEmailRequired: false,\n verifyPhoneRequired: false,\n resetPasswordAllowed: false,\n captchaRequired: false,\n firstNameLastName: \"none\" as FieldRequirement,\n adminEmails: [],\n adminUsernames: [],\n defaultRoles: [\"user\"],\n passwordPolicy: {\n minLength: 8,\n requireUppercase: true,\n requireLowercase: true,\n requireNumbers: true,\n requireSpecialCharacters: false,\n },\n loginRateLimit: {\n ipMaxAttempts: 15,\n accountMaxAttempts: 5,\n windowMs: 15 * 60 * 1000,\n },\n registrationIpMaxAttempts: 10,\n refreshToken: {\n // expirationIdle: undefined — opt-in\n },\n },\n});\n\nexport type RealmAuthSettings = Static<typeof realmAuthSettingsAtom.schema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\nimport { users } from \"./users.ts\";\n\nexport const sessions = $entity({\n name: \"sessions\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n refreshToken: t.uuid(),\n userId: db.ref(t.uuid(), () => users.cols.id),\n /**\n * OAuth client this session was minted for, when it was created via the\n * OAuth 2.1 authorization flow — the `client_id` of an `oauth_clients`\n * row. Null for first-party logins. Deliberately NOT a DB-level foreign\n * key: `sessions` is a core entity and must not depend on the optional\n * OAuth module's table; the join to `oauth_clients` is done at query time.\n */\n clientId: t.optional(t.text({ maxLength: 64 })),\n expiresAt: t.datetime(),\n /**\n * Last time the session was used to refresh an access token.\n * Used by realm `refreshToken.expirationIdle` to invalidate idle sessions.\n * `null` on existing rows pre-migration — falls back to `createdAt`.\n */\n lastUsedAt: t.optional(t.datetime()),\n ip: t.optional(t.text()),\n /**\n * ISO 3166-1 alpha-2 country code derived from the request geo headers\n * (`cf-ipcountry` on Cloudflare, CDN equivalents elsewhere) at login time.\n * `null` on pre-migration rows and where geo isn't available.\n */\n country: t.optional(t.text({ maxLength: 2 })),\n userAgent: t.optional(\n t.object({\n os: t.text(),\n browser: t.text(),\n device: t.enum([\"MOBILE\", \"DESKTOP\", \"TABLET\"]),\n }),\n ),\n }),\n indexes: [\"userId\", \"expiresAt\", { column: \"refreshToken\", unique: true }],\n});\n\nexport type SessionEntity = Static<typeof sessions.schema>;\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport type { ParameterPrimitive } from \"alepha/api/parameters\";\nimport { $repository, type Repository } from \"alepha/orm\";\nimport {\n type RealmAuthSettings,\n realmAuthSettingsAtom,\n} from \"../atoms/realmAuthSettingsAtom.ts\";\nimport { identities } from \"../entities/identities.ts\";\nimport { sessions } from \"../entities/sessions.ts\";\nimport { DEFAULT_USER_REALM_NAME, users } from \"../entities/users.ts\";\nimport type { RealmFeatures, RealmOptions } from \"../primitives/$realm.ts\";\n\nexport interface RealmRepositories {\n identities: Repository<typeof identities.schema>;\n sessions: Repository<typeof sessions.schema>;\n users: Repository<typeof users.schema>;\n}\n\nexport interface Realm {\n name: string;\n repositories: RealmRepositories;\n settings: RealmAuthSettings;\n features: RealmFeatures;\n settingsParameter?: ParameterPrimitive<typeof realmAuthSettingsAtom.schema>;\n getSettings(): Promise<RealmAuthSettings>;\n}\n\nexport class RealmProvider {\n protected readonly alepha = $inject(Alepha);\n // Default repositories using $repository() for eager initialization\n protected readonly defaultIdentities = $repository(identities);\n protected readonly defaultSessions = $repository(sessions);\n protected readonly defaultUsers = $repository(users);\n\n protected realms = new Map<string, Realm>();\n\n public register(realmName: string, realmOptions: RealmOptions = {}) {\n if (realmName.includes(\".\")) {\n throw new AlephaError(\n `Realm name \"${realmName}\" must not contain dots — dots are reserved for parameter tree paths`,\n );\n }\n\n // Merge features with defaults\n const features: RealmFeatures = {\n jobs: false,\n notifications: false,\n apiKeys: false,\n parameters: false,\n avatars: false,\n audits: false,\n ...realmOptions.features,\n };\n\n const realm: Realm = {\n name: realmName,\n repositories: {\n identities: realmOptions.entities?.identities ?? this.defaultIdentities,\n sessions: realmOptions.entities?.sessions ?? this.defaultSessions,\n users: realmOptions.entities?.users ?? this.defaultUsers,\n },\n // TODO: Remove deep merge when alepha supports it natively\n settings: {\n ...realmAuthSettingsAtom.options.default,\n ...realmOptions.settings,\n passwordPolicy: {\n ...realmAuthSettingsAtom.options.default.passwordPolicy,\n ...realmOptions.settings?.passwordPolicy,\n },\n loginRateLimit: {\n ...realmAuthSettingsAtom.options.default.loginRateLimit,\n ...realmOptions.settings?.loginRateLimit,\n },\n refreshToken: {\n ...realmAuthSettingsAtom.options.default.refreshToken,\n ...realmOptions.settings?.refreshToken,\n },\n },\n features,\n getSettings: async function () {\n if (this.settingsParameter) {\n return await this.settingsParameter.get();\n }\n return this.settings;\n },\n };\n this.realms.set(realmName, realm);\n return this.getRealm(realmName);\n }\n\n /**\n * Gets a registered realm by name, auto-creating default if needed.\n */\n public getRealm(realmName = DEFAULT_USER_REALM_NAME): Realm {\n let realm = this.realms.get(realmName);\n\n if (!realm) {\n // Auto-register default realm for backward compatibility\n const realms = Array.from(this.realms.values());\n const firstRealm = realms[0];\n if (realmName === DEFAULT_USER_REALM_NAME && firstRealm) {\n realm = firstRealm;\n } else if (this.alepha.isTest()) {\n realm = this.register(realmName); // Auto-create default realm in tests\n } else {\n throw new AlephaError(\n `Missing realm '${realmName}', please declare $realm in your application.`,\n );\n }\n }\n\n return realm;\n }\n\n public identityRepository(\n realmName = DEFAULT_USER_REALM_NAME,\n ): Repository<typeof identities.schema> {\n return this.getRealm(realmName).repositories.identities;\n }\n\n public sessionRepository(\n realmName = DEFAULT_USER_REALM_NAME,\n ): Repository<typeof sessions.schema> {\n return this.getRealm(realmName).repositories.sessions;\n }\n\n public userRepository(\n realmName = DEFAULT_USER_REALM_NAME,\n ): Repository<typeof users.schema> {\n return this.getRealm(realmName).repositories.users;\n }\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { Page } from \"alepha/orm\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport type { IdentityEntity } from \"../entities/identities.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { IdentityQuery } from \"../schemas/identityQuerySchema.ts\";\n\nexport class IdentityService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly realmProvider = $inject(RealmProvider);\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n public identities(userRealmName?: string) {\n return this.realmProvider.identityRepository(userRealmName);\n }\n\n /**\n * Find identities with pagination and filtering.\n */\n public async findIdentities(\n q: IdentityQuery = {},\n userRealmName?: string,\n ): Promise<Page<IdentityEntity>> {\n this.log.trace(\"Finding identities\", { query: q, userRealmName });\n q.sort ??= \"-createdAt\";\n\n const where = this.identities(userRealmName).createQueryWhere();\n\n if (q.userId) {\n where.userId = { eq: q.userId };\n }\n\n if (q.provider) {\n where.provider = { like: q.provider };\n }\n\n const result = await this.identities(userRealmName).paginate(\n q,\n { where },\n { count: true },\n );\n\n this.log.debug(\"Identities found\", {\n count: result.content.length,\n total: result.page.totalElements,\n });\n\n return result;\n }\n\n /**\n * Get an identity by ID.\n */\n public async getIdentityById(\n id: string,\n userRealmName?: string,\n ): Promise<IdentityEntity> {\n this.log.trace(\"Getting identity by ID\", { id, userRealmName });\n const identity = await this.identities(userRealmName).getById(id);\n this.log.debug(\"Identity retrieved\", {\n id,\n provider: identity.provider,\n userId: identity.userId,\n });\n return identity;\n }\n\n /**\n * Delete an identity by ID.\n */\n public async deleteIdentity(\n id: string,\n userRealmName?: string,\n ): Promise<void> {\n this.log.trace(\"Deleting identity\", { id, userRealmName });\n\n // Verify identity exists\n const identity = await this.getIdentityById(id, userRealmName);\n\n await this.identities(userRealmName).deleteById(id);\n this.log.info(\"Identity deleted\", {\n id,\n provider: identity.provider,\n userId: identity.userId,\n });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n\n await this.userAudits(userRealmName)?.user.log(\"update\", {\n resourceType: \"user\",\n userRealm: realm.name,\n resourceId: identity.userId,\n description: `Identity provider disconnected: ${identity.provider}`,\n metadata: {\n identityId: id,\n provider: identity.provider,\n userId: identity.userId,\n },\n });\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { identityQuerySchema } from \"../schemas/identityQuerySchema.ts\";\nimport { identityResourceSchema } from \"../schemas/identityResourceSchema.ts\";\nimport { IdentityService } from \"../services/IdentityService.ts\";\n\nexport class AdminIdentityController {\n protected readonly url = \"/identities\";\n protected readonly group = \"admin:identities\";\n protected readonly identityService = $inject(IdentityService);\n\n /**\n * Find identities with pagination and filtering.\n */\n public readonly findIdentities = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:identity:read\"] })],\n description: \"Find identities with pagination and filtering\",\n schema: {\n query: t.extend(identityQuerySchema, {\n userRealmName: t.optional(t.string()),\n }),\n response: t.page(identityResourceSchema),\n },\n handler: ({ query }) => {\n const { userRealmName, ...q } = query;\n return this.identityService.findIdentities(q, userRealmName);\n },\n });\n\n /**\n * Get an identity by ID.\n */\n public readonly getIdentity = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:identity:read\"] })],\n description: \"Get an identity by ID\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: identityResourceSchema,\n },\n handler: ({ params, query }) =>\n this.identityService.getIdentityById(params.id, query.userRealmName),\n });\n\n /**\n * Delete an identity.\n */\n public readonly deleteIdentity = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:identity:delete\"] })],\n description: \"Delete an identity\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: okSchema,\n },\n handler: async ({ params, query }) => {\n await this.identityService.deleteIdentity(params.id, query.userRealmName);\n return { ok: true, id: params.id };\n },\n });\n}\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const sessionQuerySchema = t.extend(pageQuerySchema, {\n userId: t.optional(t.uuid()),\n});\n\nexport type SessionQuery = Static<typeof sessionQuerySchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Slim view of the session's owner — embedded by the admin listing so the\n * UI can render a human-readable identifier instead of just a UUID. Comes\n * back via a left join, so it's optional (a session whose user was deleted\n * still returns; `user` is undefined).\n */\nexport const sessionUserSummarySchema = t.object({\n id: t.uuid(),\n email: t.optional(t.string({ format: \"email\" })),\n username: t.optional(t.shortText({ minLength: 3, maxLength: 30 })),\n firstName: t.optional(t.string()),\n lastName: t.optional(t.string()),\n});\n\nexport const sessionResourceSchema = t.object({\n id: t.uuid(),\n version: t.number(),\n createdAt: t.datetime(),\n updatedAt: t.datetime(),\n refreshToken: t.uuid(),\n userId: t.uuid(),\n expiresAt: t.datetime(),\n ip: t.optional(t.string()),\n country: t.optional(t.string()),\n userAgent: t.optional(\n t.object({\n os: t.string(),\n browser: t.string(),\n device: t.enum([\"MOBILE\", \"DESKTOP\", \"TABLET\"]),\n }),\n ),\n user: t.optional(sessionUserSummarySchema),\n});\n\nexport type SessionResource = Static<typeof sessionResourceSchema>;\n","import { $inject } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { Page } from \"alepha/orm\";\nimport type { SessionEntity } from \"../entities/sessions.ts\";\nimport { users } from \"../entities/users.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { SessionQuery } from \"../schemas/sessionQuerySchema.ts\";\n\n/**\n * Relation map embedding a slim user summary on every session row, so the\n * admin UI can render `user.email`/`user.username` instead of a bare UUID.\n * Left-join (default) so sessions whose owner was deleted still come back\n * with `user: undefined`.\n */\nconst withUser = {\n user: {\n join: users,\n on: [\"userId\", users.cols.id] as [\"userId\", { name: string }],\n },\n};\n\nexport class SessionCrudService {\n protected readonly log = $logger();\n protected readonly realmProvider = $inject(RealmProvider);\n\n public sessions(userRealmName?: string) {\n return this.realmProvider.sessionRepository(userRealmName);\n }\n\n /**\n * Find sessions with pagination and filtering.\n */\n public async findSessions(\n q: SessionQuery = {},\n userRealmName?: string,\n ): Promise<Page<SessionEntity>> {\n this.log.trace(\"Finding sessions\", { query: q, userRealmName });\n q.sort ??= \"-createdAt\";\n\n const where = this.sessions(userRealmName).createQueryWhere();\n\n if (q.userId) {\n where.userId = { eq: q.userId };\n }\n\n const result = await this.sessions(userRealmName).paginate(\n q,\n { where, with: withUser },\n { count: true },\n );\n\n this.log.debug(\"Sessions found\", {\n count: result.content.length,\n total: result.page.totalElements,\n });\n\n return result;\n }\n\n /**\n * Get a session by ID.\n */\n public async getSessionById(\n id: string,\n userRealmName?: string,\n ): Promise<SessionEntity> {\n this.log.trace(\"Getting session by ID\", { id, userRealmName });\n const session = await this.sessions(userRealmName).getOne({\n where: { id: { eq: id } },\n with: withUser,\n });\n this.log.debug(\"Session retrieved\", { id, userId: session.userId });\n return session;\n }\n\n /**\n * Delete a session by ID.\n */\n public async deleteSession(\n id: string,\n userRealmName?: string,\n ): Promise<void> {\n this.log.trace(\"Deleting session\", { id, userRealmName });\n\n // Verify session exists\n await this.getSessionById(id, userRealmName);\n\n await this.sessions(userRealmName).deleteById(id);\n this.log.info(\"Session deleted\", { id });\n }\n\n /**\n * Delete many sessions by ID in one repository call.\n */\n public async deleteSessions(\n ids: string[],\n userRealmName?: string,\n ): Promise<string[]> {\n if (ids.length === 0) return [];\n this.log.trace(\"Deleting sessions\", { count: ids.length, userRealmName });\n const deleted = await this.sessions(userRealmName).deleteMany({\n id: { inArray: ids },\n });\n this.log.info(\"Sessions deleted\", { count: deleted.length });\n return deleted.map(String);\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { sessionQuerySchema } from \"../schemas/sessionQuerySchema.ts\";\nimport { sessionResourceSchema } from \"../schemas/sessionResourceSchema.ts\";\nimport { SessionCrudService } from \"../services/SessionCrudService.ts\";\n\nexport class AdminSessionController {\n protected readonly url = \"/sessions\";\n protected readonly group = \"admin:sessions\";\n protected readonly sessionService = $inject(SessionCrudService);\n\n /**\n * Find sessions with pagination and filtering.\n */\n public readonly findSessions = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:session:read\"] })],\n description: \"Find sessions with pagination and filtering\",\n schema: {\n query: t.extend(sessionQuerySchema, {\n userRealmName: t.optional(t.string()),\n }),\n response: t.page(sessionResourceSchema),\n },\n handler: ({ query }) => {\n const { userRealmName, ...q } = query;\n return this.sessionService.findSessions(q, userRealmName);\n },\n });\n\n /**\n * Get a session by ID.\n */\n public readonly getSession = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:session:read\"] })],\n description: \"Get a session by ID\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: sessionResourceSchema,\n },\n handler: ({ params, query }) =>\n this.sessionService.getSessionById(params.id, query.userRealmName),\n });\n\n /**\n * Delete a session.\n */\n public readonly deleteSession = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:session:delete\"] })],\n description: \"Delete a session\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: okSchema,\n },\n handler: async ({ params, query }) => {\n await this.sessionService.deleteSession(params.id, query.userRealmName);\n return { ok: true, id: params.id };\n },\n });\n\n /**\n * Delete many sessions in one repository call.\n */\n public readonly deleteSessions = $action({\n method: \"POST\",\n path: `${this.url}/delete`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:session:delete\"] })],\n description: \"Delete many sessions\",\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n ids: t.array(t.uuid(), { minItems: 1, maxItems: 1000 }),\n }),\n response: t.object({\n deleted: t.array(t.string()),\n }),\n },\n handler: async ({ body, query }) => {\n const deleted = await this.sessionService.deleteSessions(\n body.ids,\n query.userRealmName,\n );\n return { deleted };\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { users } from \"../entities/users.ts\";\n\nexport const createUserSchema = t.omit(users.insertSchema, [\"realm\"]);\n\nexport type CreateUser = Static<typeof createUserSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { users } from \"../entities/users.ts\";\n\nexport const updateUserSchema = t.partial(\n t.omit(users.insertSchema, [\"id\", \"version\", \"createdAt\", \"updatedAt\"]),\n);\n\nexport type UpdateUser = Static<typeof updateUserSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const userQuerySchema = t.extend(pageQuerySchema, {\n /**\n * Free-text search applied (case-insensitive) across `email`,\n * `username`, `firstName`, and `lastName`. Matches with a leading and\n * trailing wildcard, so `?search=foo` finds any user whose email,\n * username, or name contains `foo`.\n */\n search: t.optional(t.string()),\n email: t.optional(t.string()),\n enabled: t.optional(t.boolean()),\n emailVerified: t.optional(t.boolean()),\n roles: t.optional(t.array(t.string())),\n});\n\nexport type UserQuery = Static<typeof userQuerySchema>;\n","import type { Static } from \"alepha\";\nimport { users } from \"../entities/users.ts\";\n\nexport const userResourceSchema = users.schema;\n\nexport type UserResource = Static<typeof userResourceSchema>;\n","import { t } from \"alepha\";\nimport { $notification } from \"alepha/api/notifications\";\n\nexport class UserNotifications {\n // Code-based notifications (preferred)\n public readonly passwordReset = $notification({\n category: \"security\",\n description:\n \"Email sent to users with a verification code to reset their password.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Reset your password\",\n body: (it) => `\n\t\t\t<h1>Reset Your Password</h1>\n\t\t\t<p>Hi ${it.email},</p>\n\t\t\t<p>We received a request to reset your password. Use the code below to verify your identity:</p>\n\t\t\t<p style=\"margin: 30px 0; text-align: center;\">\n\t\t\t\t<span style=\"font-size: 32px; font-weight: bold; letter-spacing: 8px; font-family: monospace; background-color: #f5f5f5; padding: 16px 24px; border-radius: 8px; display: inline-block;\">\n\t\t\t\t\t${it.code}\n\t\t\t\t</span>\n\t\t\t</p>\n\t\t\t<p>This code will expire in ${it.expiresInMinutes} minutes.</p>\n\t\t\t<p>If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>\n\t\t\t<p>Best regards,<br>The Team</p>\n\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n code: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n\n public readonly emailVerification = $notification({\n category: \"security\",\n description:\n \"Email sent to users with a verification code to verify their email address.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Verify your email address\",\n body: (it) => `\n\t\t\t<h1>Verify Your Email Address</h1>\n\t\t\t<p>Hi ${it.email},</p>\n\t\t\t<p>Thanks for signing up! Use the code below to verify your email address:</p>\n\t\t\t<p style=\"margin: 30px 0; text-align: center;\">\n\t\t\t\t<span style=\"font-size: 32px; font-weight: bold; letter-spacing: 8px; font-family: monospace; background-color: #f5f5f5; padding: 16px 24px; border-radius: 8px; display: inline-block;\">\n\t\t\t\t\t${it.code}\n\t\t\t\t</span>\n\t\t\t</p>\n\t\t\t<p>This code will expire in ${it.expiresInMinutes} minutes.</p>\n\t\t\t<p>If you did not create an account, please ignore this email.</p>\n\t\t\t<p>Best regards,<br>The Team</p>\n\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n code: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n\n public readonly phoneVerification = $notification({\n category: \"security\",\n description:\n \"SMS sent to users with a verification code to verify their phone number.\",\n critical: true,\n sensitive: true,\n sms: {\n message: (it) =>\n `Your verification code is: ${it.code}. This code expires in ${it.expiresInMinutes} minutes.`,\n },\n schema: t.object({\n phoneNumber: t.string(),\n code: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n\n // Link-based notifications (alternative)\n public readonly passwordResetLink = $notification({\n category: \"security\",\n description: \"Email sent to users with a link to reset their password.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Reset your password\",\n body: (it) => `\n\t\t\t<h1>Reset Your Password</h1>\n\t\t\t<p>Hi ${it.email},</p>\n\t\t\t<p>We received a request to reset your password. Click the link below to create a new password:</p>\n\t\t\t<p style=\"margin: 30px 0;\">\n\t\t\t\t<a href=\"${it.resetUrl}\" style=\"background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;\">\n\t\t\t\t\tReset Password\n\t\t\t\t</a>\n\t\t\t</p>\n\t\t\t<p>Or copy and paste this link into your browser:</p>\n\t\t\t<p style=\"word-break: break-all; color: #666;\">${it.resetUrl}</p>\n\t\t\t<p>This link will expire in ${it.expiresInMinutes} minutes.</p>\n\t\t\t<p>If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>\n\t\t\t<p>Best regards,<br>The Team</p>\n\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n resetUrl: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n\n public readonly accountLockout = $notification({\n category: \"security\",\n description:\n \"Email sent to users when their account is temporarily locked due to too many failed login attempts.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Account temporarily locked\",\n body: (it) => `\n\t\t\t\t<h1>Account Temporarily Locked</h1>\n\t\t\t\t<p>Hi ${it.email},</p>\n\t\t\t\t<p>Your account has been temporarily locked due to too many failed login attempts.</p>\n\t\t\t\t<p>If this was you, please wait ${it.lockoutMinutes} minutes before trying again. If you've forgotten your password, you can reset it using the password reset feature.</p>\n\t\t\t\t<p>If this wasn't you, someone may be trying to access your account. We recommend changing your password as soon as possible.</p>\n\t\t\t\t<p>Best regards,<br>The Team</p>\n\t\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n lockoutMinutes: t.number(),\n }),\n });\n\n public readonly emailVerificationLink = $notification({\n category: \"security\",\n description:\n \"Email sent to users with a link to verify their email address.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Verify your email address\",\n body: (it) => `\n\t\t\t<h1>Verify Your Email Address</h1>\n\t\t\t<p>Hi ${it.email},</p>\n\t\t\t<p>Thanks for signing up! Click the button below to verify your email address:</p>\n\t\t\t<p style=\"margin: 30px 0;\">\n\t\t\t\t<a href=\"${it.verifyUrl}\" style=\"background-color: #28a745; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;\">\n\t\t\t\t\tVerify Email\n\t\t\t\t</a>\n\t\t\t</p>\n\t\t\t<p>Or copy and paste this link into your browser:</p>\n\t\t\t<p style=\"word-break: break-all; color: #666;\">${it.verifyUrl}</p>\n\t\t\t<p>This link will expire in ${it.expiresInMinutes} minutes.</p>\n\t\t\t<p>If you did not create an account, please ignore this email.</p>\n\t\t\t<p>Best regards,<br>The Team</p>\n\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n verifyUrl: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n}\n","import { $inject, Alepha } from \"alepha\";\nimport type { VerificationController } from \"alepha/api/verifications\";\nimport { $logger } from \"alepha/logger\";\nimport type { Page } from \"alepha/orm\";\nimport { CryptoProvider } from \"alepha/security\";\nimport { BadRequestError, ConflictError } from \"alepha/server\";\nimport { $client } from \"alepha/server/links\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport type { UserEntity } from \"../entities/users.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { CreateUser } from \"../schemas/createUserSchema.ts\";\nimport type { UpdateUser } from \"../schemas/updateUserSchema.ts\";\nimport type { UserQuery } from \"../schemas/userQuerySchema.ts\";\n\nexport class UserService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly verificationController = $client<VerificationController>();\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly cryptoProvider = $inject(CryptoProvider);\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n protected userNotifications(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.notifications) {\n return this.alepha.inject(UserNotifications);\n }\n return undefined;\n }\n\n public users(userRealmName?: string) {\n return this.realmProvider.userRepository(userRealmName);\n }\n\n /**\n * Request email verification for a user.\n * @param email - The email address to verify.\n * @param userRealmName - Optional realm name.\n * @param method - The verification method: \"code\" (default) or \"link\".\n * @param verifyUrl - Base URL for verification link (required when method is \"link\").\n */\n public async requestEmailVerification(\n email: string,\n userRealmName?: string,\n method: \"code\" | \"link\" = \"code\",\n ): Promise<boolean> {\n this.log.trace(\"Requesting email verification\", {\n email,\n userRealmName,\n method,\n });\n\n const user = await this.users(userRealmName).findOne({\n where: { email: { eq: email } },\n });\n\n if (!user) {\n this.log.debug(\"Email verification requested for non-existent user\", {\n email,\n });\n return true;\n }\n\n if (user.emailVerified) {\n this.log.debug(\"Email verification requested for already verified user\", {\n email,\n userId: user.id,\n });\n return true;\n }\n\n try {\n const verification =\n await this.verificationController.requestVerificationCode({\n params: { type: method },\n body: { target: email },\n });\n\n if (method === \"link\") {\n // Build verification URL from realm settings (server-controlled, not user input)\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n const baseUrl = realmSettings.verifyEmailUrl ?? \"/verify-email\";\n const url = new URL(baseUrl, \"http://localhost\");\n url.searchParams.set(\"email\", email);\n url.searchParams.set(\"token\", verification.token);\n const fullVerifyUrl = realmSettings.verifyEmailUrl\n ? `${baseUrl}${url.search}`\n : url.pathname + url.search;\n\n await this.userNotifications(userRealmName)?.emailVerificationLink.push(\n {\n contact: email,\n variables: {\n email,\n verifyUrl: fullVerifyUrl,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n },\n );\n\n this.log.debug(\"Email verification link sent\", {\n email,\n userId: user.id,\n });\n } else {\n await this.userNotifications(userRealmName)?.emailVerification.push({\n contact: email,\n variables: {\n email,\n code: verification.token,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n });\n\n this.log.debug(\"Email verification code sent\", {\n email,\n userId: user.id,\n });\n }\n } catch (error) {\n // Silent fail for security\n this.log.warn(\"Failed to send email verification\", { email, error });\n }\n\n return true;\n }\n\n /**\n * Verify a user's email using a valid verification token.\n * Supports both code (6-digit) and link (UUID) verification tokens.\n */\n public async verifyEmail(\n email: string,\n token: string,\n userRealmName?: string,\n ): Promise<void> {\n this.log.trace(\"Verifying email\", { email, userRealmName });\n\n // Detect verification type based on token format\n // Codes are 6-digit numbers, links are UUIDs\n const isCode = /^\\d{6}$/.test(token);\n const type = isCode ? \"code\" : \"link\";\n\n const result = await this.verificationController\n .validateVerificationCode({\n params: { type },\n body: { target: email, token },\n })\n .catch(() => {\n this.log.warn(\"Invalid email verification token\", { email, type });\n throw new BadRequestError(\"Invalid or expired verification token\");\n });\n\n if (result.alreadyVerified) {\n this.log.warn(\"Email verification token already used\", { email });\n throw new BadRequestError(\"Invalid or expired verification token\");\n }\n\n const user = await this.users(userRealmName).getOne({\n where: { email: { eq: email } },\n });\n\n await this.users(userRealmName).updateById(user.id, {\n emailVerified: true,\n });\n\n this.log.info(\"Email verified\", { email, userId: user.id, type });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n\n await this.userAudits(userRealmName)?.user.log(\"update\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: email,\n userRealm: realm.name,\n resourceId: user.id,\n description: \"Email verified\",\n metadata: { email, verificationType: type },\n });\n }\n\n /**\n * Check if an email is verified.\n */\n public async isEmailVerified(\n email: string,\n userRealmName?: string,\n ): Promise<boolean> {\n this.log.trace(\"Checking if email is verified\", { email, userRealmName });\n\n const user = await this.users(userRealmName).findOne({\n where: { email: { eq: email } },\n });\n\n return user?.emailVerified ?? false;\n }\n\n /**\n * Find users with pagination and filtering.\n */\n public async findUsers(\n q: UserQuery = {},\n userRealmName?: string,\n ): Promise<Page<UserEntity>> {\n this.log.trace(\"Finding users\", { query: q, userRealmName });\n q.sort ??= \"-createdAt\";\n\n const where = this.users(userRealmName).createQueryWhere();\n\n if (q.search) {\n const pattern = `%${q.search}%`;\n where.or = [\n { email: { ilike: pattern } },\n { username: { ilike: pattern } },\n { firstName: { ilike: pattern } },\n { lastName: { ilike: pattern } },\n ];\n }\n\n if (q.email) {\n where.email = { like: q.email };\n }\n\n if (q.enabled !== undefined) {\n where.enabled = { eq: q.enabled };\n }\n\n if (q.emailVerified !== undefined) {\n where.emailVerified = { eq: q.emailVerified };\n }\n\n if (q.roles) {\n where.roles = { arrayContains: q.roles };\n }\n\n const result = await this.users(userRealmName).paginate(\n q,\n { where },\n { count: true },\n );\n\n this.log.debug(\"Users found\", {\n count: result.content.length,\n total: result.page.totalElements,\n });\n\n return result;\n }\n\n /**\n * Get a user by ID.\n */\n public async getUserById(\n id: string,\n userRealmName?: string,\n ): Promise<UserEntity> {\n this.log.trace(\"Getting user by ID\", { id, userRealmName });\n return await this.users(userRealmName).getById(id);\n }\n\n /**\n * Create a new user.\n */\n public async createUser(\n data: CreateUser,\n userRealmName?: string,\n ): Promise<UserEntity> {\n this.log.trace(\"Creating user\", {\n username: data.username,\n email: data.email,\n userRealmName,\n });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n\n // Check for existing user based on provided unique fields (scoped to realm)\n if (data.username) {\n const existingUser = await this.users(userRealmName).findOne({\n where: { realm: realm.name, username: { ilike: data.username } },\n });\n\n if (existingUser) {\n this.log.debug(\"Username already taken\", { username: data.username });\n throw new BadRequestError(\"User with this username already exists\");\n }\n }\n\n if (data.email) {\n const existingUser = await this.users(userRealmName).findOne({\n where: { realm: realm.name, email: { eq: data.email } },\n });\n\n if (existingUser) {\n this.log.debug(\"Email already taken\", { email: data.email });\n throw new BadRequestError(\"User with this email already exists\");\n }\n }\n\n if (data.phoneNumber) {\n const existingUser = await this.users(userRealmName).findOne({\n where: { realm: realm.name, phoneNumber: { eq: data.phoneNumber } },\n });\n\n if (existingUser) {\n this.log.debug(\"Phone number already taken\", {\n phoneNumber: data.phoneNumber,\n });\n throw new BadRequestError(\"User with this phone number already exists\");\n }\n }\n\n const user = await this.users(userRealmName).create({\n ...data,\n roles: data.roles ?? realmSettings.defaultRoles,\n realm: realm.name,\n });\n\n this.log.info(\"User created\", {\n userId: user.id,\n username: user.username,\n email: user.email,\n });\n\n await this.userAudits(userRealmName)?.user.log(\"create\", {\n resourceType: \"user\",\n userRealm: realm.name,\n resourceId: user.id,\n description: \"User created\",\n metadata: {\n username: user.username,\n email: user.email,\n roles: user.roles,\n },\n });\n\n return user;\n }\n\n /**\n * Update an existing user.\n */\n public async updateUser(\n id: string,\n data: UpdateUser,\n userRealmName?: string,\n ): Promise<UserEntity> {\n this.log.trace(\"Updating user\", { id, userRealmName });\n const before = await this.getUserById(id, userRealmName);\n const realm = this.realmProvider.getRealm(userRealmName);\n const users = this.users(userRealmName);\n\n // Conflict checks — surface a friendly 409 instead of letting the\n // unique constraint blow up the SQL driver with a generic error.\n if (\n data.username !== undefined &&\n data.username !== null &&\n data.username !== before.username\n ) {\n const existing = await users.findOne({\n where: { realm: realm.name, username: { ilike: data.username } },\n });\n if (existing && existing.id !== id) {\n throw new ConflictError(\"User with this username already exists\");\n }\n }\n if (\n data.email !== undefined &&\n data.email !== null &&\n data.email !== before.email\n ) {\n const existing = await users.findOne({\n where: { realm: realm.name, email: { eq: data.email } },\n });\n if (existing && existing.id !== id) {\n throw new ConflictError(\"User with this email already exists\");\n }\n // Changing the email invalidates the verified flag — never trust\n // the client-passed value when email changes.\n data.emailVerified = false;\n }\n\n const user = await users.updateById(id, data);\n this.log.debug(\"User updated\", { userId: id });\n\n // Build changes object showing what was updated\n const changes: Record<string, { from: unknown; to: unknown }> = {};\n for (const key of Object.keys(data) as (keyof UpdateUser)[]) {\n if (data[key] !== undefined && before[key] !== data[key]) {\n changes[key] = { from: before[key], to: data[key] };\n }\n }\n\n // Detect role changes for special handling\n const isRoleChange =\n data.roles !== undefined &&\n JSON.stringify(before.roles) !== JSON.stringify(data.roles);\n\n await this.userAudits(userRealmName)?.user.log(\n isRoleChange ? \"role_change\" : \"update\",\n {\n resourceType: \"user\",\n userRealm: realm.name,\n resourceId: user.id,\n description: isRoleChange\n ? \"User roles changed\"\n : `User updated: ${Object.keys(changes).join(\", \")}`,\n metadata: { changes },\n },\n );\n\n return user;\n }\n\n /**\n * Set (or reset) a user's password. Upserts a \"credentials\" identity\n * with the new hash. Used by admin password-set flows; does NOT\n * verify any old password or token — the caller is responsible for\n * authorization.\n */\n public async setPassword(\n id: string,\n newPassword: string,\n userRealmName?: string,\n ): Promise<void> {\n this.log.trace(\"Setting password\", { id, userRealmName });\n const user = await this.getUserById(id, userRealmName);\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const settings = await realm.getSettings();\n if (settings.passwordPolicy) {\n const policy = settings.passwordPolicy;\n if (policy.minLength && newPassword.length < policy.minLength) {\n throw new BadRequestError(\n `Password must be at least ${policy.minLength} characters`,\n );\n }\n }\n\n const hash = await this.cryptoProvider.hashPassword(newPassword);\n const identities = this.realmProvider.identityRepository(userRealmName);\n const existing = await identities.findOne({\n where: { userId: { eq: id }, provider: { eq: \"credentials\" } },\n });\n\n if (existing) {\n await identities.updateById(existing.id, { password: hash });\n } else {\n await identities.create({\n userId: id,\n provider: \"credentials\",\n password: hash,\n });\n }\n\n await this.userAudits(userRealmName)?.user.log(\"password_change\", {\n resourceType: \"user\",\n userId: id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: id,\n severity: \"warning\",\n description: \"Password set by admin\",\n });\n }\n\n /**\n * Delete a user by ID.\n */\n public async deleteUser(id: string, userRealmName?: string): Promise<void> {\n this.log.trace(\"Deleting user\", { id, userRealmName });\n const user = await this.getUserById(id, userRealmName);\n\n // Clean up related sessions and identities before deleting the user\n await this.realmProvider\n .sessionRepository(userRealmName)\n .deleteMany({ userId: { eq: id } });\n await this.realmProvider\n .identityRepository(userRealmName)\n .deleteMany({ userId: { eq: id } });\n\n await this.users(userRealmName).deleteById(id);\n this.log.info(\"User deleted\", { userId: id });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n\n await this.userAudits(userRealmName)?.user.log(\"delete\", {\n resourceType: \"user\",\n userRealm: realm.name,\n resourceId: id,\n severity: \"warning\",\n description: \"User deleted\",\n metadata: {\n username: user.username,\n email: user.email,\n },\n });\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure, SecurityProvider } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { createUserSchema } from \"../schemas/createUserSchema.ts\";\nimport { updateUserSchema } from \"../schemas/updateUserSchema.ts\";\nimport { userQuerySchema } from \"../schemas/userQuerySchema.ts\";\nimport { userResourceSchema } from \"../schemas/userResourceSchema.ts\";\nimport { UserService } from \"../services/UserService.ts\";\n\nexport class AdminUserController {\n protected readonly url = \"/users\";\n protected readonly group = \"admin:users\";\n protected readonly userService = $inject(UserService);\n protected readonly securityProvider = $inject(SecurityProvider);\n\n /**\n * List roles available in a realm. Used by the admin UI to render the\n * role picker and grey out defaults (which cannot be removed).\n */\n public readonly findRoles = $action({\n path: \"/metadata/roles\",\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:read\"] })],\n description: \"List roles available in a realm\",\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: t.array(\n t.object({\n name: t.string(),\n default: t.optional(t.boolean()),\n description: t.optional(t.string()),\n }),\n ),\n },\n handler: ({ query }) => {\n const roles = this.securityProvider.getRoles(query.userRealmName);\n return roles.map((r) => ({\n name: r.name,\n default: r.default,\n description: r.description,\n }));\n },\n });\n\n /**\n * Find users with pagination and filtering.\n */\n public readonly findUsers = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:read\"] })],\n description: \"Find users with pagination and filtering\",\n schema: {\n query: t.extend(userQuerySchema, {\n userRealmName: t.optional(t.string()),\n }),\n response: t.page(userResourceSchema),\n },\n handler: ({ query }) => {\n const { userRealmName, ...q } = query;\n return this.userService.findUsers(q, userRealmName);\n },\n });\n\n /**\n * Get a user by ID.\n */\n public readonly getUser = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:read\"] })],\n description: \"Get a user by ID\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: userResourceSchema,\n },\n handler: ({ params, query }) =>\n this.userService.getUserById(params.id, query.userRealmName),\n });\n\n /**\n * Create a new user.\n */\n public readonly createUser = $action({\n method: \"POST\",\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:create\"] })],\n description: \"Create a new user\",\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: createUserSchema,\n response: userResourceSchema,\n },\n handler: ({ body, query }) =>\n this.userService.createUser(body, query.userRealmName),\n });\n\n /**\n * Update a user.\n */\n public readonly updateUser = $action({\n method: \"PATCH\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:update\"] })],\n description: \"Update a user\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: updateUserSchema,\n response: userResourceSchema,\n },\n handler: ({ params, body, query }) =>\n this.userService.updateUser(params.id, body, query.userRealmName),\n });\n\n /**\n * Set (or reset) a user's password. Admin-only flow — does NOT\n * require knowing the previous password. Hash is stored as a\n * \"credentials\" identity for the user (upsert).\n */\n public readonly setUserPassword = $action({\n method: \"POST\",\n path: `${this.url}/:id/password`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:update\"] })],\n description: \"Set a user's password\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n password: t.string({ minLength: 1 }),\n }),\n response: okSchema,\n },\n handler: async ({ params, body, query }) => {\n await this.userService.setPassword(\n params.id,\n body.password,\n query.userRealmName,\n );\n return { ok: true, id: params.id };\n },\n });\n\n /**\n * Delete a user.\n */\n public readonly deleteUser = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:delete\"] })],\n description: \"Delete a user\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: okSchema,\n },\n handler: async ({ params, query }) => {\n await this.userService.deleteUser(params.id, query.userRealmName);\n return { ok: true, id: params.id };\n },\n });\n\n /**\n * Delete many users in one request. Each id is processed sequentially so\n * cascades and side-effects run as if called one-by-one. Errors on a single\n * id surface with that id in the response.\n */\n public readonly deleteUsers = $action({\n method: \"POST\",\n path: `${this.url}/delete`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:delete\"] })],\n description: \"Delete many users\",\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n ids: t.array(t.uuid(), { minItems: 1, maxItems: 1000 }),\n }),\n response: t.object({\n deleted: t.array(t.uuid()),\n }),\n },\n handler: async ({ body, query }) => {\n const deleted: string[] = [];\n for (const id of body.ids) {\n await this.userService.deleteUser(id, query.userRealmName);\n deleted.push(id);\n }\n return { deleted };\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { authenticationProviderSchema } from \"alepha/server/auth\";\nimport { realmAuthSettingsAtom } from \"../atoms/realmAuthSettingsAtom.ts\";\n\nexport const realmConfigSchema = t.object({\n settings: realmAuthSettingsAtom.schema,\n realmName: t.string(),\n authenticationMethods: t.array(authenticationProviderSchema),\n captchaSiteKey: t.optional(\n t.string({\n description:\n \"Public site key for the captcha widget (when settings.captchaRequired is true)\",\n }),\n ),\n});\n\nexport type RealmConfig = Static<typeof realmConfigSchema>;\n","import { $inject, t } from \"alepha\";\nimport { CaptchaProvider } from \"alepha/captcha\";\nimport { $action } from \"alepha/server\";\nimport { ServerAuthProvider } from \"alepha/server/auth\";\nimport { $etag } from \"alepha/server/etag\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport { realmConfigSchema } from \"../schemas/realmConfigSchema.ts\";\n\n/**\n * Controller for exposing realm configuration.\n * Uses $route instead of $action to keep endpoints hidden from API documentation.\n */\nexport class RealmController {\n protected readonly url = \"/realms\";\n protected readonly group = \"realms\";\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly serverAuthProvider = $inject(ServerAuthProvider);\n protected readonly captchaProvider = $inject(CaptchaProvider);\n\n /**\n * Get realm configuration settings.\n * This endpoint is not exposed in the API documentation.\n */\n public readonly getRealmConfig = $action({\n group: this.group,\n method: \"GET\",\n path: `${this.url}/config`,\n use: [$etag()],\n schema: {\n query: t.object({\n realmName: t.optional(t.string()),\n }),\n response: realmConfigSchema,\n },\n handler: async ({ query }) => {\n const realm = this.realmProvider.getRealm(query.realmName);\n const settings = await realm.getSettings();\n const realmName = realm.name;\n\n const authenticationMethods =\n this.serverAuthProvider.getAuthenticationProviders({\n realmName,\n });\n\n return {\n settings,\n realmName,\n authenticationMethods,\n captchaSiteKey: settings.captchaRequired\n ? this.captchaProvider.getSiteKey()\n : undefined,\n };\n },\n });\n\n public readonly checkUsernameAvailability = $action({\n group: this.group,\n path: `${this.url}/check-username`,\n schema: {\n query: t.object({\n realmName: t.optional(t.text()),\n }),\n body: t.object({\n username: t.text(),\n }),\n response: t.object({\n available: t.boolean(),\n }),\n },\n handler: async ({ query, body }) => {\n const realmName = query.realmName;\n const userRepository = this.realmProvider.userRepository(realmName);\n\n const existingUser = await userRepository.findOne({\n where: { username: { eq: body.username } },\n });\n\n return {\n available: !existingUser,\n };\n },\n });\n}\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Request schema for completing a password reset.\n *\n * Requires the intent ID from Phase 1, the verification code,\n * and the new password.\n */\nexport const completePasswordResetRequestSchema = t.object({\n intentId: t.uuid({\n description: \"The intent ID from createPasswordResetIntent\",\n }),\n code: t.string({\n description: \"6-digit verification code sent via email\",\n }),\n newPassword: t.string({\n minLength: 8,\n description: \"New password (minimum 8 characters)\",\n }),\n});\n\nexport type CompletePasswordResetRequest = Static<\n typeof completePasswordResetRequestSchema\n>;\n","import { type Static, t } from \"alepha\";\n\nexport const completeRegistrationRequestSchema = t.object({\n intentId: t.uuid({\n description: \"The registration intent ID from the first phase\",\n }),\n emailCode: t.optional(\n t.string({\n description: \"Email verification code (if email verification required)\",\n }),\n ),\n phoneCode: t.optional(\n t.string({\n description: \"Phone verification code (if phone verification required)\",\n }),\n ),\n captchaToken: t.optional(\n t.string({\n description: \"Captcha token (if captcha required)\",\n }),\n ),\n});\n\nexport type CompleteRegistrationRequest = Static<\n typeof completeRegistrationRequestSchema\n>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Response schema for password reset intent creation.\n *\n * Contains the intent ID needed for Phase 2 completion,\n * along with expiration time.\n */\nexport const passwordResetIntentResponseSchema = t.object({\n intentId: t.uuid({\n description: \"Unique identifier for this password reset intent\",\n }),\n expiresAt: t.datetime({\n description: \"ISO timestamp when this intent expires\",\n }),\n});\n\nexport type PasswordResetIntentResponse = Static<\n typeof passwordResetIntentResponseSchema\n>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Schema for user registration query parameters.\n * Allows specifying a custom user realm.\n */\nexport const registerQuerySchema = t.object({\n userRealmName: t.optional(\n t.text({\n description:\n \"The user realm to register the user in (defaults to 'default')\",\n }),\n ),\n});\n\nexport type RegisterQuery = Static<typeof registerQuerySchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Schema for user registration request body.\n * Password is always required, other fields depend on realm settings.\n */\nexport const registerRequestSchema = t.object({\n // Password is always required\n password: t.string({\n minLength: 8,\n description: \"Password for the account\",\n }),\n\n // Identity fields (requirements depend on realm settings)\n username: t.optional(\n t.string({\n minLength: 3,\n description: \"Unique username for the account\",\n }),\n ),\n\n // Optional contact fields\n email: t.optional(\n t.string({\n format: \"email\",\n description: \"User's email address\",\n }),\n ),\n phoneNumber: t.optional(\n t.string({\n description: \"User's phone number\",\n }),\n ),\n\n // Optional user profile fields\n firstName: t.optional(\n t.string({\n description: \"User's first name\",\n }),\n ),\n lastName: t.optional(\n t.string({\n description: \"User's last name\",\n }),\n ),\n picture: t.optional(\n t.string({\n description: \"User's profile picture URL\",\n }),\n ),\n\n // Captcha token — required when the realm has `captchaRequired: true`.\n // Validated at intent creation (before any verification email is sent).\n captchaToken: t.optional(\n t.string({\n description: \"Captcha response token (if captcha is required)\",\n }),\n ),\n});\n\nexport type RegisterRequest = Static<typeof registerRequestSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const registrationIntentResponseSchema = t.object({\n intentId: t.uuid({\n description: \"Unique identifier for the registration intent\",\n }),\n expectCaptcha: t.boolean({\n description: \"Whether captcha verification is required\",\n }),\n captchaSiteKey: t.optional(\n t.string({\n description:\n \"Public site key the client should render (when expectCaptcha is true)\",\n }),\n ),\n expectEmailVerification: t.boolean({\n description: \"Whether email verification is required\",\n }),\n expectPhoneVerification: t.boolean({\n description: \"Whether phone verification is required\",\n }),\n expiresAt: t.datetime({\n description: \"When the registration intent expires\",\n }),\n});\n\nexport type RegistrationIntentResponse = Static<\n typeof registrationIntentResponseSchema\n>;\n","import { $inject, Alepha } from \"alepha\";\nimport { VerificationService } from \"alepha/api/verifications\";\nimport { $cache } from \"alepha/cache\";\nimport { DatabaseCacheProvider } from \"alepha/cache/database\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { CryptoProvider } from \"alepha/security\";\nimport { BadRequestError, HttpError } from \"alepha/server\";\nimport type { RealmAuthSettings } from \"../atoms/realmAuthSettingsAtom.ts\";\nimport { SessionAudits } from \"../audits/SessionAudits.ts\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { CompletePasswordResetRequest } from \"../schemas/completePasswordResetRequestSchema.ts\";\nimport type { PasswordResetIntentResponse } from \"../schemas/passwordResetIntentResponseSchema.ts\";\n\n/**\n * Intent stored in cache during the password reset flow.\n */\ninterface PasswordResetIntent {\n email: string;\n userId: string;\n identityId: string;\n realmName?: string;\n expiresAt: string;\n}\n\nconst INTENT_TTL_MINUTES = 10;\n\n/**\n * Verification purpose bucket for password-reset codes. Keeping this distinct\n * from the default bucket means a reset request is not rate-limited by an\n * unrelated email-verification code on the same address (e.g. a user who just\n * registered and immediately asks to reset their password).\n */\nconst PASSWORD_RESET_PURPOSE = \"password-reset\";\n\nexport class CredentialService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly cryptoProvider = $inject(CryptoProvider);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly verificationService = $inject(VerificationService);\n protected readonly realmProvider = $inject(RealmProvider);\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n protected sessionAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(SessionAudits);\n }\n return undefined;\n }\n\n protected userNotifications(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.notifications) {\n return this.alepha.inject(UserNotifications);\n }\n return undefined;\n }\n\n protected readonly intentCache = $cache<PasswordResetIntent>({\n // Use the SQL-backed cache so phase-2 reads what phase-1 wrote with\n // strong consistency, and so bare deployments don't need a distributed\n // KV resource just to support password reset.\n provider: DatabaseCacheProvider,\n name: \"api:users:password-reset-intents\",\n ttl: [INTENT_TTL_MINUTES, \"minutes\"],\n });\n\n public users(userRealmName?: string) {\n return this.realmProvider.userRepository(userRealmName);\n }\n\n public sessions(userRealmName?: string) {\n return this.realmProvider.sessionRepository(userRealmName);\n }\n\n public identities(userRealmName?: string) {\n return this.realmProvider.identityRepository(userRealmName);\n }\n\n /**\n * Validate a password against the realm's password policy.\n */\n public validatePasswordPolicy(\n password: string,\n policy: RealmAuthSettings[\"passwordPolicy\"],\n ): void {\n if (password.length < policy.minLength) {\n throw new BadRequestError(\n `Password must be at least ${policy.minLength} characters`,\n );\n }\n if (policy.requireUppercase && !/[A-Z]/.test(password)) {\n throw new BadRequestError(\n \"Password must contain at least one uppercase letter\",\n );\n }\n if (policy.requireLowercase && !/[a-z]/.test(password)) {\n throw new BadRequestError(\n \"Password must contain at least one lowercase letter\",\n );\n }\n if (policy.requireNumbers && !/\\d/.test(password)) {\n throw new BadRequestError(\"Password must contain at least one number\");\n }\n if (policy.requireSpecialCharacters && !/[^a-zA-Z0-9]/.test(password)) {\n throw new BadRequestError(\n \"Password must contain at least one special character\",\n );\n }\n }\n\n /**\n * Phase 1: Create a password reset intent.\n *\n * Validates the email, checks for existing user with credentials,\n * sends verification code, and stores the intent in cache.\n *\n * @param email - User's email address\n * @param userRealmName - Optional realm name\n * @returns Intent response with intentId and expiration (always returns for security)\n */\n public async createPasswordResetIntent(\n email: string,\n userRealmName?: string,\n ): Promise<PasswordResetIntentResponse> {\n this.log.trace(\"Creating password reset intent\", { email, userRealmName });\n\n // Generate intent ID and expiration upfront for consistent response\n const intentId = this.cryptoProvider.randomUUID();\n const expiresAt = this.dateTimeProvider\n .now()\n .add(INTENT_TTL_MINUTES, \"minutes\")\n .toISOString();\n\n // Check if password reset is allowed for this realm\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n if (realmSettings.resetPasswordAllowed === false) {\n this.log.debug(\"Password reset not allowed for realm\", { userRealmName });\n return { intentId, expiresAt };\n }\n\n // Find user by email (silent fail for security)\n const user = await this.users(userRealmName).findOne({\n where: { email: { eq: email } },\n });\n\n if (!user) {\n // Silent fail - don't reveal that email doesn't exist\n this.log.debug(\"Password reset requested for non-existent email\", {\n email,\n });\n return { intentId, expiresAt };\n }\n\n // Find the credentials identity for this user\n const identity = await this.identities(userRealmName).findOne({\n where: {\n userId: { eq: user.id },\n provider: { eq: \"credentials\" },\n },\n });\n\n if (!identity) {\n // User doesn't have credentials identity (maybe OAuth only)\n this.log.debug(\"Password reset requested for user without credentials\", {\n userId: user.id,\n });\n return { intentId, expiresAt };\n }\n\n // Create verification using verification controller\n // This handles: token generation, expiration, rate limiting, cooldown\n try {\n const verification = await this.verificationService.createVerification({\n type: \"code\",\n target: email,\n purpose: PASSWORD_RESET_PURPOSE,\n });\n\n // Send password reset notification with the code\n await this.userNotifications(userRealmName)?.passwordReset.push({\n contact: email,\n variables: {\n email,\n code: verification.token,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n });\n\n // Store intent in cache\n const intent: PasswordResetIntent = {\n email,\n userId: user.id,\n identityId: identity.id,\n realmName: userRealmName,\n expiresAt,\n };\n\n await this.intentCache.set(intentId, intent);\n\n this.log.info(\"Password reset intent created\", {\n intentId,\n userId: user.id,\n email,\n });\n } catch (error) {\n // If rate limit or cooldown hit, still return success for security\n this.log.warn(\"Failed to create password reset verification\", error);\n }\n\n return { intentId, expiresAt };\n }\n\n /**\n * Phase 2: Complete password reset using an intent.\n *\n * Validates the verification code, updates the password,\n * and invalidates all existing sessions.\n *\n * @param body - Request body with intentId, code, and newPassword\n */\n public async completePasswordReset(\n body: CompletePasswordResetRequest,\n ): Promise<void> {\n this.log.trace(\"Completing password reset\", { intentId: body.intentId });\n\n // Fetch intent from cache\n const intent = await this.intentCache.get(body.intentId);\n if (!intent) {\n this.log.warn(\"Invalid or expired password reset intent\", {\n intentId: body.intentId,\n });\n throw new HttpError({\n status: 410,\n message: \"Invalid or expired password reset intent\",\n });\n }\n\n // Validate password against realm policy before consuming the verification code\n const realm = this.realmProvider.getRealm(intent.realmName);\n const realmSettings = await realm.getSettings();\n this.validatePasswordPolicy(body.newPassword, realmSettings.passwordPolicy);\n\n // Verify code using verification service\n const result = await this.verificationService\n .verifyCode(\n {\n type: \"code\",\n target: intent.email,\n purpose: PASSWORD_RESET_PURPOSE,\n },\n body.code,\n )\n .catch(() => {\n this.log.warn(\"Invalid verification code for password reset\", {\n intentId: body.intentId,\n email: intent.email,\n });\n throw new BadRequestError(\"Invalid or expired verification code\");\n });\n\n // If already verified, this is a code reuse attempt\n if (result.alreadyVerified) {\n this.log.warn(\"Verification code reuse attempt\", {\n intentId: body.intentId,\n email: intent.email,\n });\n throw new BadRequestError(\"Verification code has already been used\");\n }\n\n // Hash the new password\n const hashedPassword = await this.cryptoProvider.hashPassword(\n body.newPassword,\n );\n\n // Update the identity with new password\n await this.identities(intent.realmName).updateById(intent.identityId, {\n password: hashedPassword,\n });\n\n // Invalidate all existing sessions for this user\n await this.sessions(intent.realmName).deleteMany({\n userId: { eq: intent.userId },\n });\n\n // Invalidate intent after all operations succeed,\n // so the user can retry if password update or session cleanup fails.\n // The verification code was already consumed (verifiedAt set), so replay is\n // prevented even if the intent is still in cache.\n await this.intentCache.invalidate(body.intentId);\n\n this.log.info(\"Password reset completed\", {\n userId: intent.userId,\n email: intent.email,\n });\n\n // Audit: password reset\n await this.userAudits(intent.realmName)?.user.log(\"update\", {\n resourceType: \"user\",\n userId: intent.userId,\n userEmail: intent.email,\n userRealm: realm.name,\n resourceId: intent.userId,\n description: \"Password reset completed\",\n metadata: { email: intent.email },\n });\n\n // Audit: sessions invalidated (security event)\n await this.sessionAudits(intent.realmName)?.security.log(\n \"sessions_invalidated\",\n {\n userId: intent.userId,\n userEmail: intent.email,\n userRealm: realm.name,\n resourceId: intent.userId,\n severity: \"warning\",\n description: \"All sessions invalidated after password reset\",\n },\n );\n }\n\n // Legacy methods kept for backward compatibility\n\n /**\n * @deprecated Use createPasswordResetIntent instead\n */\n public async requestPasswordReset(\n email: string,\n userRealmName?: string,\n ): Promise<boolean> {\n await this.createPasswordResetIntent(email, userRealmName);\n return true;\n }\n\n /**\n * @deprecated Use completePasswordReset instead\n */\n public async validateResetToken(\n email: string,\n token: string,\n _userRealmName?: string,\n ): Promise<string> {\n // Verify using verification service\n const isValid = await this.verificationService\n .verifyCode(\n { type: \"code\", target: email, purpose: PASSWORD_RESET_PURPOSE },\n token,\n )\n .catch(() => undefined);\n\n if (!isValid?.ok) {\n throw new BadRequestError(\"Invalid or expired reset token\");\n }\n\n return email;\n }\n\n /**\n * @deprecated Use completePasswordReset instead\n */\n public async resetPassword(\n email: string,\n token: string,\n newPassword: string,\n userRealmName?: string,\n ): Promise<void> {\n // Verify token using verification service\n const result = await this.verificationService\n .verifyCode(\n { type: \"code\", target: email, purpose: PASSWORD_RESET_PURPOSE },\n token,\n )\n .catch(() => {\n throw new BadRequestError(\"Invalid or expired reset token\");\n });\n\n // If already verified, this is a token reuse attempt\n if (result.alreadyVerified) {\n throw new BadRequestError(\"Invalid or expired reset token\");\n }\n\n // Validate password against realm policy\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n this.validatePasswordPolicy(newPassword, realmSettings.passwordPolicy);\n\n // Find user and identity\n const user = await this.users(userRealmName).getOne({\n where: { email: { eq: email } },\n });\n\n const identity = await this.identities(userRealmName).getOne({\n where: {\n userId: { eq: user.id },\n provider: { eq: \"credentials\" },\n },\n });\n\n // Hash the new password\n const hashedPassword = await this.cryptoProvider.hashPassword(newPassword);\n\n // Update the identity with new password\n await this.identities(userRealmName).updateById(identity.id, {\n password: hashedPassword,\n });\n\n // Invalidate all existing sessions for this user\n await this.sessions(userRealmName).deleteMany({\n userId: { eq: user.id },\n });\n\n // Audit: password reset (legacy method)\n await this.userAudits(userRealmName)?.user.log(\"update\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: email,\n userRealm: realm.name,\n resourceId: user.id,\n description: \"Password reset completed (legacy)\",\n metadata: { email },\n });\n\n // Audit: sessions invalidated\n await this.sessionAudits(userRealmName)?.security.log(\n \"sessions_invalidated\",\n {\n userId: user.id,\n userEmail: email,\n userRealm: realm.name,\n resourceId: user.id,\n severity: \"warning\",\n description: \"All sessions invalidated after password reset\",\n },\n );\n }\n}\n","import { $inject, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\n\n/**\n * Derive stable, URL-safe usernames from email addresses.\n *\n * Used by the registration flow when `realm.settings.username === \"email\"`,\n * and reusable from any custom user-creation site (e.g. an OAuth-only flow\n * that wants the same handle convention).\n *\n * **Slug rule** — the local-part of the email is kept as-is (gmail\n * `+suffix` retained for predictability). Everything outside `[a-z0-9]` is\n * replaced with `-`, runs of `-` are collapsed, leading/trailing `-` are\n * trimmed, lowercased. If the result is shorter than {@link MIN_LENGTH} it\n * is padded with random alphanumerics. Result is clamped to\n * {@link MAX_LENGTH}.\n *\n * **Collision retry** — `pickAvailable()` checks the realm's `users` table\n * and the realm's `usernameBlocklist`. On hit, it appends `-<4 random>` and\n * retries up to {@link MAX_RETRIES} times. Best-effort against concurrent\n * registrations: the unique index on `(realm, lower(username))` is the\n * authoritative race guard, callers that hit a unique-violation should\n * call `pickAvailable` again with a fresh suffix.\n *\n * @see RegistrationService.createRegistrationIntent\n */\nexport class UsernameSlugger {\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly log = $logger();\n\n /**\n * Floor for derived usernames. Shorter slugs are padded with random\n * alphanumerics. Matches the lower bound of the default `usernameRegExp`.\n */\n static readonly MIN_LENGTH = 3;\n\n /**\n * Ceiling for derived usernames. Matches the upper bound of the default\n * `usernameRegExp` and gives enough headroom for the random suffix added\n * on collisions.\n */\n static readonly MAX_LENGTH = 30;\n\n /**\n * Length of the random suffix appended on collision (e.g. `-3dp6`).\n * 36⁴ ≈ 1.6M variants per base — plenty for a tiny number of retries.\n */\n static readonly SUFFIX_LENGTH = 4;\n\n /**\n * How many times `pickAvailable` retries before giving up.\n */\n static readonly MAX_RETRIES = 5;\n\n /**\n * Alphabet used for the random suffix and for padding short slugs.\n */\n static readonly ALPHABET = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n\n /**\n * Default replacement when the email's local-part contains no\n * `[a-z0-9]` characters at all (rare but possible: `é@example.com`).\n */\n static readonly EMPTY_LOCAL_FALLBACK = \"user\";\n\n /**\n * Sanitize an email into a base username slug.\n *\n * Pure function — no DB access. Always returns a string that satisfies\n * `^[a-z0-9-]{MIN_LENGTH,MAX_LENGTH}$`.\n *\n * @example\n * slug(\"ni.foures+testkv@gmail.com\") // \"ni-foures-testkv\"\n * slug(\"john.doe@example.com\") // \"john-doe\"\n * slug(\"é@example.com\") // \"user-XXX\" (padded)\n */\n public slug(email: string | null | undefined): string {\n const raw = (email ?? \"\").trim();\n const at = raw.indexOf(\"@\");\n const local = at > 0 ? raw.slice(0, at) : raw;\n\n const cleaned = local\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n\n let result = cleaned || UsernameSlugger.EMPTY_LOCAL_FALLBACK;\n\n if (result.length < UsernameSlugger.MIN_LENGTH) {\n const needed = UsernameSlugger.MIN_LENGTH - result.length;\n result += this.randomSuffix(needed);\n }\n\n return this.clamp(result);\n }\n\n /**\n * Find an available username for the realm, starting from `base`.\n *\n * Returns `base` when nothing collides. On a hit (existing row OR\n * blocklisted name) appends `-<4 random>` and tries again, up to\n * {@link MAX_RETRIES} times.\n *\n * The check is best-effort: a concurrent registration may still claim\n * the same value before the caller's INSERT runs, in which case the DB\n * unique index throws and the caller should retry.\n */\n public async pickAvailable(\n realmName: string | undefined,\n base: string,\n ): Promise<string> {\n const blocklist = await this.getBlocklist(realmName);\n const repo = this.realmProvider.userRepository(realmName);\n const realm = this.realmProvider.getRealm(realmName);\n\n const isAvailable = async (candidate: string): Promise<boolean> => {\n if (this.isBlockedAgainst(candidate, blocklist)) {\n return false;\n }\n const existing = await repo.findOne({\n where: {\n realm: { eq: realm.name },\n username: { ilike: candidate },\n },\n });\n return !existing;\n };\n\n if (await isAvailable(base)) {\n return base;\n }\n\n // Reserve room for \"-\" + suffix at the end of the candidate.\n const reserve = 1 + UsernameSlugger.SUFFIX_LENGTH;\n const trimmedBase =\n base.length > UsernameSlugger.MAX_LENGTH - reserve\n ? base.slice(0, UsernameSlugger.MAX_LENGTH - reserve)\n : base;\n\n for (let i = 0; i < UsernameSlugger.MAX_RETRIES; i++) {\n const candidate = `${trimmedBase}-${this.randomSuffix(\n UsernameSlugger.SUFFIX_LENGTH,\n )}`;\n if (await isAvailable(candidate)) {\n return candidate;\n }\n }\n\n throw new AlephaError(\n `Could not find an available username starting from \"${base}\" after ${UsernameSlugger.MAX_RETRIES} attempts.`,\n );\n }\n\n /**\n * Check a name against the realm's `usernameBlocklist`. Case-insensitive.\n */\n public async isBlocked(\n realmName: string | undefined,\n name: string,\n ): Promise<boolean> {\n const blocklist = await this.getBlocklist(realmName);\n return this.isBlockedAgainst(name, blocklist);\n }\n\n // -------------------------------------------------------------------------\n\n protected async getBlocklist(\n realmName: string | undefined,\n ): Promise<Set<string>> {\n const realm = this.realmProvider.getRealm(realmName);\n const settings = await realm.getSettings();\n const list = settings?.usernameBlocklist ?? [];\n return new Set(list.map((b) => b.toLowerCase()));\n }\n\n protected isBlockedAgainst(name: string, blocklist: Set<string>): boolean {\n return blocklist.has(name.toLowerCase());\n }\n\n protected clamp(s: string): string {\n return s.length > UsernameSlugger.MAX_LENGTH\n ? s.slice(0, UsernameSlugger.MAX_LENGTH)\n : s;\n }\n\n protected randomSuffix(length: number): string {\n const chars = UsernameSlugger.ALPHABET;\n let out = \"\";\n for (let i = 0; i < length; i++) {\n out += chars[Math.floor(Math.random() * chars.length)];\n }\n return out;\n }\n}\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport type { VerificationController } from \"alepha/api/verifications\";\nimport { $cache } from \"alepha/cache\";\nimport { DatabaseCacheProvider } from \"alepha/cache/database\";\nimport { CaptchaProvider } from \"alepha/captcha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { CryptoProvider } from \"alepha/security\";\nimport { BadRequestError, ConflictError, HttpError } from \"alepha/server\";\nimport { $client } from \"alepha/server/links\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport type { UserEntity } from \"../entities/users.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { CompleteRegistrationRequest } from \"../schemas/completeRegistrationRequestSchema.ts\";\nimport type { RegisterRequest } from \"../schemas/registerRequestSchema.ts\";\nimport type { RegistrationIntentResponse } from \"../schemas/registrationIntentResponseSchema.ts\";\nimport { CredentialService } from \"./CredentialService.ts\";\nimport { UsernameSlugger } from \"./UsernameSlugger.ts\";\n\n/**\n * Intent stored in cache during the registration flow.\n */\ninterface RegistrationIntent {\n data: {\n username?: string;\n email?: string;\n phoneNumber?: string;\n firstName?: string;\n lastName?: string;\n picture?: string;\n passwordHash: string;\n };\n requirements: {\n email: boolean;\n phone: boolean;\n captcha: boolean;\n };\n realmName?: string;\n expiresAt: string;\n}\n\nconst INTENT_TTL_MINUTES = 10;\n\nexport class RegistrationService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly cryptoProvider = $inject(CryptoProvider);\n protected readonly verificationController = $client<VerificationController>();\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly credentialService = $inject(CredentialService);\n protected readonly captchaProvider = $inject(CaptchaProvider);\n protected readonly usernameSlugger = $inject(UsernameSlugger);\n\n protected readonly intentCache = $cache<RegistrationIntent>({\n // Pinned to the SQL-backed cache so:\n // - phase 2 reliably reads the partial-registration payload phase 1 wrote;\n // - the password hash held in the intent never lives in a distributed\n // K/V outside the user's own DB unless they explicitly opt in.\n provider: DatabaseCacheProvider,\n name: \"api:users:registrations\",\n ttl: [INTENT_TTL_MINUTES, \"minutes\"],\n });\n\n protected readonly rateLimitCache = $cache<number>({\n // Use the SQL-backed cache so `incr()` is atomic (`INSERT ... ON CONFLICT\n // DO UPDATE SET count = count + 1`). Cloudflare KV silently coalesces\n // concurrent writes to the same key, which makes the limiter useless\n // against bursts.\n provider: DatabaseCacheProvider,\n name: \"api:users:registration-rate-limit\",\n ttl: [15, \"minutes\"],\n });\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n protected userNotifications(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n\n if (realm.features.notifications) {\n return this.alepha.inject(UserNotifications);\n }\n\n throw new AlephaError(\n `Notifications feature is not enabled for realm \"${realmName}\". Please enable it in the realm settings to use registration notifications.`,\n );\n }\n\n /**\n * Phase 1: Create a registration intent.\n *\n * Validates the registration data, checks for existing users,\n * creates verification sessions, and stores the intent in cache.\n */\n public async createRegistrationIntent(\n body: RegisterRequest,\n userRealmName?: string,\n ): Promise<RegistrationIntentResponse> {\n this.log.trace(\"Creating registration intent\", {\n email: body.email,\n username: body.username,\n userRealmName,\n });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n\n // IP rate limiting — threshold driven by realm settings so dev/e2e can\n // bump it (a single localhost IP otherwise burns through the default 10\n // before the suite finishes).\n const request = this.alepha.store.get(\"alepha.http.request\");\n const ipKey = request?.ip ? `register:ip:${request.ip}` : undefined;\n if (ipKey) {\n const count = (await this.rateLimitCache.get(ipKey)) ?? 0;\n if (count >= realmSettings.registrationIpMaxAttempts) {\n this.log.warn(\"Registration rate limit exceeded\", { ip: request?.ip });\n throw new BadRequestError(\n \"Too many registration attempts, please try again later\",\n );\n }\n await this.rateLimitCache.set(ipKey, count + 1);\n }\n\n // Check if registration is allowed\n if (realmSettings?.registrationAllowed === false) {\n this.log.warn(\"Registration not allowed for realm\", { userRealmName });\n throw new BadRequestError(\"Registration is not allowed\");\n }\n\n // Validate required fields based on settings\n if (realmSettings?.username === \"required\" && !body.username) {\n this.log.debug(\"Registration rejected: username required\", {\n userRealmName,\n });\n throw new BadRequestError(\"Username is required\");\n }\n\n // In \"email\" mode the server is authoritative — any username sent in\n // the request is dropped on the floor and replaced by the slugger\n // output below.\n if (realmSettings?.username === \"email\") {\n body.username = undefined;\n }\n\n if (body.username) {\n // Security note: usernameRegExp is admin-controlled (from realmAuthSettingsAtom),\n // not user input. Default is ^[a-zA-Z0-9_-]{3,30}$ which is ReDoS-safe.\n // No need for regex timeout or safe-regex validation here.\n const usernameRegExp = realmSettings?.usernameRegExp;\n if (usernameRegExp) {\n const regex = new RegExp(usernameRegExp);\n if (!regex.test(body.username)) {\n this.log.debug(\"Registration rejected: username regex mismatch\", {\n userRealmName,\n username: body.username,\n });\n throw new BadRequestError(\n \"Username does not meet the required format\",\n );\n }\n }\n\n // Manual usernames must also clear the realm blocklist — same set\n // that the slugger uses, so apps can't be sidestepped by clients\n // POSTing the reserved name directly.\n if (await this.usernameSlugger.isBlocked(userRealmName, body.username)) {\n this.log.debug(\"Registration rejected: username is blocked\", {\n userRealmName,\n username: body.username,\n });\n throw new BadRequestError(\"This username is not available\");\n }\n }\n\n if (realmSettings?.email === \"required\" && !body.email) {\n this.log.debug(\"Registration rejected: email required\", {\n userRealmName,\n });\n throw new BadRequestError(\"Email is required\");\n }\n\n if (realmSettings?.phoneNumber === \"required\" && !body.phoneNumber) {\n this.log.debug(\"Registration rejected: phone required\", {\n userRealmName,\n });\n throw new BadRequestError(\"Phone number is required\");\n }\n\n // In \"email\" mode, derive the username from the email *now* so that\n // checkUserAvailability picks it up too, the DB unique index sees a\n // concrete value, and the persisted intent already carries the final\n // username.\n if (realmSettings?.username === \"email\") {\n if (!body.email) {\n throw new BadRequestError(\n \"Email is required to derive a username from email\",\n );\n }\n const base = this.usernameSlugger.slug(body.email);\n body.username = await this.usernameSlugger.pickAvailable(\n userRealmName,\n base,\n );\n }\n\n // Check for existing users (username, email, phone)\n await this.checkUserAvailability(body, userRealmName);\n\n // Validate password against realm policy\n this.credentialService.validatePasswordPolicy(\n body.password,\n realmSettings.passwordPolicy,\n );\n\n // Validate captcha BEFORE any side effects (email sends, rate-limit counters).\n // This blocks bots from triggering verification emails on other people's addresses.\n if (realmSettings?.captchaRequired === true) {\n if (!body.captchaToken) {\n throw new BadRequestError(\"Captcha verification is required\");\n }\n const valid = await this.captchaProvider.verify(body.captchaToken);\n if (!valid) {\n throw new BadRequestError(\"Captcha verification failed\");\n }\n }\n\n // Hash the password\n const passwordHash = await this.cryptoProvider.hashPassword(body.password);\n\n // Determine requirements based on realm settings\n const requirements = {\n email: realmSettings?.verifyEmailRequired === true && !!body.email,\n phone: realmSettings?.verifyPhoneRequired === true && !!body.phoneNumber,\n captcha: false, // validated above, single-use — no gate at complete time\n };\n\n // Create verification sessions and send codes\n if (requirements.email && body.email) {\n await this.sendEmailVerification(body.email, userRealmName);\n }\n\n if (requirements.phone && body.phoneNumber) {\n await this.sendPhoneVerification(body.phoneNumber, userRealmName);\n }\n\n // Generate intent ID and expiration\n const intentId = this.cryptoProvider.randomUUID();\n const expiresAt = this.dateTimeProvider\n .now()\n .add(INTENT_TTL_MINUTES, \"minutes\")\n .toISOString();\n\n // Store intent in cache\n const intent: RegistrationIntent = {\n data: {\n username: body.username,\n email: body.email,\n phoneNumber: body.phoneNumber,\n firstName: body.firstName,\n lastName: body.lastName,\n picture: body.picture,\n passwordHash,\n },\n requirements,\n realmName: userRealmName,\n expiresAt,\n };\n\n await this.intentCache.set(intentId, intent);\n\n this.log.info(\"Registration intent created\", {\n intentId,\n email: body.email,\n username: body.username,\n requiresEmailVerification: requirements.email,\n requiresPhoneVerification: requirements.phone,\n });\n\n return {\n intentId,\n expectCaptcha: requirements.captcha,\n captchaSiteKey: requirements.captcha\n ? this.captchaProvider.getSiteKey()\n : undefined,\n expectEmailVerification: requirements.email,\n expectPhoneVerification: requirements.phone,\n expiresAt,\n };\n }\n\n /**\n * Phase 2: Complete registration using an intent.\n *\n * Validates all requirements (verification codes, captcha),\n * creates the user and credentials, and returns the user.\n */\n public async completeRegistration(\n body: CompleteRegistrationRequest,\n ): Promise<UserEntity> {\n this.log.trace(\"Completing registration\", { intentId: body.intentId });\n\n // Fetch intent from cache\n const intent = await this.intentCache.get(body.intentId);\n if (!intent) {\n this.log.warn(\"Invalid or expired registration intent\", {\n intentId: body.intentId,\n });\n throw new HttpError({\n status: 410,\n message: \"Invalid or expired registration intent\",\n });\n }\n\n const userRealmName = intent.realmName;\n const userRepository = this.realmProvider.userRepository(userRealmName);\n const identityRepository =\n this.realmProvider.identityRepository(userRealmName);\n\n // Validate email verification if required\n if (intent.requirements.email) {\n if (!body.emailCode) {\n this.log.debug(\"Registration completion missing email code\", {\n intentId: body.intentId,\n });\n throw new BadRequestError(\"Email verification code is required\");\n }\n\n if (!intent.data.email) {\n throw new BadRequestError(\"Email is missing from registration intent\");\n }\n\n await this.verifyEmailCode(intent.data.email, body.emailCode);\n }\n\n // Validate phone verification if required\n if (intent.requirements.phone) {\n if (!body.phoneCode) {\n this.log.debug(\"Registration completion missing phone code\", {\n intentId: body.intentId,\n });\n throw new BadRequestError(\"Phone verification code is required\");\n }\n\n if (!intent.data.phoneNumber) {\n throw new BadRequestError(\n \"Phone number is missing from registration intent\",\n );\n }\n\n await this.verifyPhoneCode(intent.data.phoneNumber, body.phoneCode);\n }\n\n // Captcha is validated at intent creation (see createIntent) so bots can't\n // trigger verification emails; nothing to re-check here.\n\n // Final availability check (race condition guard)\n await this.checkUserAvailability(\n {\n username: intent.data.username,\n email: intent.data.email,\n phoneNumber: intent.data.phoneNumber,\n },\n userRealmName,\n );\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n\n // Create the user\n const user = await userRepository.create({\n realm: realm.name,\n username: intent.data.username,\n email: intent.data.email,\n phoneNumber: intent.data.phoneNumber,\n firstName: intent.data.firstName,\n lastName: intent.data.lastName,\n picture: intent.data.picture,\n roles: realmSettings.defaultRoles,\n enabled: true,\n emailVerified: intent.requirements.email, // Marked as verified if we verified during registration\n });\n\n // Create credentials identity\n await identityRepository.create({\n userId: user.id,\n provider: \"credentials\",\n password: intent.data.passwordHash,\n });\n\n // Invalidate intent after both user and identity creation succeed,\n // so the user can retry if either operation fails.\n await this.intentCache.invalidate(body.intentId);\n\n this.log.info(\"User registered successfully\", {\n userId: user.id,\n email: user.email,\n username: user.username,\n });\n\n await this.userAudits(userRealmName)?.user.log(\"create\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: user.id,\n description: \"User registered\",\n metadata: {\n username: user.username,\n email: user.email,\n emailVerified: user.emailVerified,\n registrationMethod: \"credentials\",\n },\n });\n\n return user;\n }\n\n /**\n * Check if username, email, and phone are available.\n */\n protected async checkUserAvailability(\n body: Pick<RegisterRequest, \"username\" | \"email\" | \"phoneNumber\">,\n userRealmName?: string,\n ): Promise<void> {\n const realm = this.realmProvider.getRealm(userRealmName);\n const userRepository = this.realmProvider.userRepository(userRealmName);\n\n if (body.username) {\n const existingUser = await userRepository.findOne({\n where: { realm: realm.name, username: { ilike: body.username } },\n });\n if (existingUser) {\n this.log.debug(\"Username already taken\", { username: body.username });\n throw new ConflictError(\"User with this username already exists\");\n }\n }\n\n if (body.email) {\n const existingUser = await userRepository.findOne({\n where: { realm: realm.name, email: { eq: body.email } },\n });\n if (existingUser) {\n this.log.debug(\"Email already taken\", { email: body.email });\n throw new ConflictError(\"User with this email already exists\");\n }\n }\n\n if (body.phoneNumber) {\n const existingUser = await userRepository.findOne({\n where: { realm: realm.name, phoneNumber: { eq: body.phoneNumber } },\n });\n if (existingUser) {\n this.log.debug(\"Phone number already taken\", {\n phoneNumber: body.phoneNumber,\n });\n throw new ConflictError(\"User with this phone number already exists\");\n }\n }\n }\n\n /**\n * Send email verification code.\n */\n protected async sendEmailVerification(\n email: string,\n realmName?: string,\n ): Promise<void> {\n this.log.debug(\"Sending email verification code\", { email });\n\n const verification =\n await this.verificationController.requestVerificationCode({\n params: { type: \"code\" },\n body: { target: email },\n });\n\n await this.userNotifications(realmName).emailVerification.push({\n contact: email,\n variables: {\n email,\n code: verification.token,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n });\n\n this.log.debug(\"Email verification code sent\", { email });\n }\n\n /**\n * Send phone verification code.\n */\n protected async sendPhoneVerification(\n phoneNumber: string,\n realmName?: string,\n ): Promise<void> {\n this.log.debug(\"Sending phone verification code\", { phoneNumber });\n try {\n const verification =\n await this.verificationController.requestVerificationCode({\n params: { type: \"code\" },\n body: { target: phoneNumber },\n });\n\n await this.userNotifications(realmName).phoneVerification.push({\n contact: phoneNumber,\n variables: {\n phoneNumber,\n code: verification.token,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n });\n this.log.debug(\"Phone verification code sent\", { phoneNumber });\n } catch (error) {\n // Silent fail - verification service may have rate limiting\n this.log.warn(\"Failed to send phone verification code\", {\n phoneNumber,\n error,\n });\n }\n }\n\n /**\n * Verify email code using verification service.\n */\n protected async verifyEmailCode(email: string, code: string): Promise<void> {\n const result = await this.verificationController\n .validateVerificationCode({\n params: { type: \"code\" },\n body: { target: email, token: code },\n })\n .catch(() => {\n this.log.warn(\"Invalid email verification code\", { email });\n throw new BadRequestError(\"Invalid or expired email verification code\");\n });\n\n if (result.alreadyVerified) {\n this.log.warn(\"Email verification code already used\", { email });\n throw new BadRequestError(\n \"Email verification code has already been used\",\n );\n }\n }\n\n /**\n * Verify phone code using verification service.\n */\n protected async verifyPhoneCode(\n phoneNumber: string,\n code: string,\n ): Promise<void> {\n const result = await this.verificationController\n .validateVerificationCode({\n params: { type: \"code\" },\n body: { target: phoneNumber, token: code },\n })\n .catch(() => {\n this.log.warn(\"Invalid phone verification code\", { phoneNumber });\n throw new BadRequestError(\"Invalid or expired phone verification code\");\n });\n\n if (result.alreadyVerified) {\n this.log.warn(\"Phone verification code already used\", { phoneNumber });\n throw new BadRequestError(\n \"Phone verification code has already been used\",\n );\n }\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { completePasswordResetRequestSchema } from \"../schemas/completePasswordResetRequestSchema.ts\";\nimport { completeRegistrationRequestSchema } from \"../schemas/completeRegistrationRequestSchema.ts\";\nimport { passwordResetIntentResponseSchema } from \"../schemas/passwordResetIntentResponseSchema.ts\";\nimport { registerQuerySchema } from \"../schemas/registerQuerySchema.ts\";\nimport { registerRequestSchema } from \"../schemas/registerRequestSchema.ts\";\nimport { registrationIntentResponseSchema } from \"../schemas/registrationIntentResponseSchema.ts\";\nimport { userResourceSchema } from \"../schemas/userResourceSchema.ts\";\nimport { CredentialService } from \"../services/CredentialService.ts\";\nimport { RegistrationService } from \"../services/RegistrationService.ts\";\nimport { UserService } from \"../services/UserService.ts\";\n\nexport class UserController {\n protected readonly url = \"/users\";\n protected readonly group = \"users\";\n protected readonly credentialService = $inject(CredentialService);\n protected readonly userService = $inject(UserService);\n protected readonly registrationService = $inject(RegistrationService);\n\n /**\n * Phase 1: Create a registration intent.\n * Validates data, creates verification sessions, and stores intent in cache.\n */\n public readonly createRegistrationIntent = $action({\n group: this.group,\n method: \"POST\",\n path: `${this.url}/register`,\n schema: {\n body: registerRequestSchema,\n query: registerQuerySchema,\n response: registrationIntentResponseSchema,\n },\n handler: ({ body, query }) =>\n this.registrationService.createRegistrationIntent(\n body,\n query.userRealmName,\n ),\n });\n\n /**\n * Phase 2: Complete registration using an intent.\n * Validates verification codes and creates the user.\n */\n public readonly createUserFromIntent = $action({\n group: this.group,\n method: \"POST\",\n path: `${this.url}/register/complete`,\n schema: {\n body: completeRegistrationRequestSchema,\n response: userResourceSchema,\n },\n handler: ({ body }) => this.registrationService.completeRegistration(body),\n });\n\n /**\n * Phase 1: Create a password reset intent.\n * Validates email, sends verification code, and stores intent in cache.\n */\n public readonly createPasswordResetIntent = $action({\n group: this.group,\n method: \"POST\",\n path: `${this.url}/password-reset`,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n email: t.email(),\n }),\n response: passwordResetIntentResponseSchema,\n },\n handler: ({ body, query }) =>\n this.credentialService.createPasswordResetIntent(\n body.email,\n query.userRealmName,\n ),\n });\n\n /**\n * Phase 2: Complete password reset using an intent.\n * Validates verification code, updates password, and invalidates sessions.\n */\n public readonly completePasswordReset = $action({\n group: this.group,\n method: \"POST\",\n path: `${this.url}/password-reset/complete`,\n schema: {\n body: completePasswordResetRequestSchema,\n response: okSchema,\n },\n handler: async ({ body }) => {\n await this.credentialService.completePasswordReset(body);\n return { ok: true };\n },\n });\n\n // Legacy endpoints for backward compatibility\n\n /**\n * @deprecated Use createPasswordResetIntent instead\n */\n public requestPasswordReset = $action({\n path: \"/users/password-reset/request\",\n group: this.group,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n email: t.email(),\n }),\n response: t.object({\n success: t.boolean(),\n message: t.string(),\n }),\n },\n handler: async ({ body, query }) => {\n await this.credentialService.requestPasswordReset(\n body.email,\n query.userRealmName,\n );\n\n return {\n success: true,\n message:\n \"If an account exists with this email, a password reset code has been sent.\",\n };\n },\n });\n\n /**\n * @deprecated Use completePasswordReset instead\n */\n public validateResetToken = $action({\n path: \"/users/password-reset/validate\",\n group: this.group,\n schema: {\n query: t.object({\n email: t.email(),\n token: t.string(),\n userRealmName: t.optional(t.string()),\n }),\n response: t.object({\n valid: t.boolean(),\n email: t.optional(t.email()),\n }),\n },\n handler: async ({ query }) => {\n try {\n const email = await this.credentialService.validateResetToken(\n query.email,\n query.token,\n query.userRealmName,\n );\n return {\n valid: true,\n email,\n };\n } catch {\n return {\n valid: false,\n };\n }\n },\n });\n\n /**\n * @deprecated Use completePasswordReset instead\n */\n public resetPassword = $action({\n path: \"/users/password-reset/reset\",\n group: this.group,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n email: t.email(),\n token: t.string(),\n newPassword: t.string({ minLength: 8 }),\n }),\n response: t.object({\n success: t.boolean(),\n message: t.string(),\n }),\n },\n handler: async ({ body, query }) => {\n await this.credentialService.resetPassword(\n body.email,\n body.token,\n body.newPassword,\n query.userRealmName,\n );\n\n return {\n success: true,\n message: \"Password has been reset successfully. Please log in.\",\n };\n },\n });\n\n /**\n * Request email verification.\n * Generates a verification token using verification service and sends an email to the user.\n * @param method - The verification method: \"code\" (default) sends a 6-digit code, \"link\" sends a clickable verification link.\n * @param verifyUrl - Required when method is \"link\". The base URL for the verification link. Token and email will be appended as query params.\n */\n public requestEmailVerification = $action({\n path: \"/users/email-verification/request\",\n group: this.group,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n method: t.optional(\n t.enum([\"code\", \"link\"], {\n default: \"code\",\n description:\n 'Verification method: \"code\" sends a 6-digit code, \"link\" sends a clickable verification link. When using \"link\", configure verifyEmailUrl in realm settings.',\n }),\n ),\n }),\n body: t.object({\n email: t.email(),\n }),\n response: t.object({\n success: t.boolean(),\n message: t.string(),\n }),\n },\n handler: async ({ body, query }) => {\n const method = query.method ?? \"code\";\n await this.userService.requestEmailVerification(\n body.email,\n query.userRealmName,\n method,\n );\n\n return {\n success: true,\n message:\n method === \"link\"\n ? \"If an account exists with this email, a verification link has been sent.\"\n : \"If an account exists with this email, a verification code has been sent.\",\n };\n },\n });\n\n /**\n * Verify email with a valid token.\n * Updates the user's emailVerified status.\n */\n public verifyEmail = $action({\n path: \"/users/email-verification/verify\",\n group: this.group,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n email: t.email(),\n token: t.string(),\n }),\n response: t.object({\n success: t.boolean(),\n message: t.string(),\n }),\n },\n handler: async ({ body, query }) => {\n await this.userService.verifyEmail(\n body.email,\n body.token,\n query.userRealmName,\n );\n\n return {\n success: true,\n message: \"Email has been verified successfully.\",\n };\n },\n });\n\n /**\n * Check if an email is verified.\n */\n public checkEmailVerification = $action({\n path: \"/users/email-verification/check\",\n group: this.group,\n use: [$secure()],\n schema: {\n query: t.object({\n email: t.email(),\n userRealmName: t.optional(t.string()),\n }),\n response: t.object({\n verified: t.boolean(),\n }),\n },\n handler: async ({ query }) => {\n const verified = await this.userService.isEmailVerified(\n query.email,\n query.userRealmName,\n );\n\n return {\n verified,\n };\n },\n });\n}\n","import { $inject } from \"alepha\";\nimport { $job } from \"alepha/api/jobs\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { sessions } from \"../entities/sessions.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\n\n/**\n * User-specific jobs wrapper service.\n *\n * This service handles user-related scheduled jobs such as:\n * - Session purge (cleaning up expired sessions)\n * - Verification code cleanup\n * - Inactive user notifications\n *\n * Declared as a module variant — not auto-injected. It is instantiated\n * lazily the first time something calls `alepha.inject(UserJobs)`.\n */\nexport class UserJobs {\n protected readonly log = $logger();\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly sessionRepository = $repository(sessions);\n protected readonly realmProvider = $inject(RealmProvider);\n\n /**\n * Purge expired sessions from the database.\n *\n * Runs hourly (at :00) and deletes:\n * - sessions whose absolute `expiresAt` has passed\n * - sessions whose `lastUsedAt` exceeds the realm's `refreshToken.expirationIdle`\n * (when configured). Falls back to `createdAt` for sessions without a\n * recorded `lastUsedAt`.\n *\n * The idle sweep is best-effort cleanup — runtime enforcement happens in\n * `SessionService.refreshSession()`.\n */\n public readonly purgeExpiredSessions = $job({\n name: \"api:users:purgeExpiredSessions\",\n cron: \"0 * * * *\", // Hourly at minute 0\n handler: async () => {\n const now = this.dateTimeProvider.nowISOString();\n\n this.log.info(\"Starting expired sessions purge\", { cutoffTime: now });\n\n const absoluteDeletedIds = await this.sessionRepository.deleteMany({\n expiresAt: { lt: now },\n });\n\n if (absoluteDeletedIds.length > 0) {\n this.log.info(\"Expired sessions purged (absolute)\", {\n deletedCount: absoluteDeletedIds.length,\n });\n }\n\n // Idle sweep — only if the default realm has expirationIdle configured.\n // Multi-realm setups with per-realm session tables should add their own\n // job; this default job sweeps the default sessions table.\n const realm = this.realmProvider.getRealm();\n const settings = await realm.getSettings();\n const idleMs = settings.refreshToken?.expirationIdle;\n if (idleMs && idleMs > 0) {\n const cutoff = this.dateTimeProvider\n .now()\n .subtract(idleMs, \"milliseconds\")\n .toISOString();\n\n // Two passes: rows with an explicit lastUsedAt, and pre-migration rows\n // where lastUsedAt is null — those fall back to createdAt.\n const lastUsedDeletedIds = await this.sessionRepository.deleteMany({\n lastUsedAt: { lt: cutoff },\n });\n const fallbackDeletedIds = await this.sessionRepository.deleteMany({\n lastUsedAt: { isNull: true },\n createdAt: { lt: cutoff },\n });\n\n const idleTotal = lastUsedDeletedIds.length + fallbackDeletedIds.length;\n if (idleTotal > 0) {\n this.log.info(\"Expired sessions purged (idle)\", {\n deletedCount: idleTotal,\n thresholdMs: idleMs,\n });\n }\n }\n },\n });\n}\n","import { randomInt } from \"node:crypto\";\nimport { $inject, Alepha } from \"alepha\";\nimport type { FileController } from \"alepha/api/files\";\nimport { CacheProvider } from \"alepha/cache\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport {\n CryptoProvider,\n InvalidCredentialsError,\n type UserAccount,\n} from \"alepha/security\";\nimport { BadRequestError, UnauthorizedError } from \"alepha/server\";\nimport type { OAuth2Profile } from \"alepha/server/auth\";\nimport { $client } from \"alepha/server/links\";\nimport { FileSystemProvider } from \"alepha/system\";\nimport { SessionAudits } from \"../audits/SessionAudits.ts\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport type { UserEntity } from \"../entities/users.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport { UsernameSlugger } from \"./UsernameSlugger.ts\";\n\nexport class SessionService {\n protected readonly alepha = $inject(Alepha);\n protected readonly fsp = $inject(FileSystemProvider);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly cryptoProvider = $inject(CryptoProvider);\n protected readonly log = $logger();\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly fileController = $client<FileController>();\n protected readonly cacheProvider = $inject(CacheProvider);\n protected readonly usernameSlugger = $inject(UsernameSlugger);\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n protected sessionAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(SessionAudits);\n }\n return undefined;\n }\n\n protected userNotifications(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.notifications) {\n return this.alepha.inject(UserNotifications);\n }\n return undefined;\n }\n\n public users(userRealmName?: string) {\n return this.realmProvider.userRepository(userRealmName);\n }\n\n public sessions(userRealmName?: string) {\n return this.realmProvider.sessionRepository(userRealmName);\n }\n\n public identities(userRealmName?: string) {\n return this.realmProvider.identityRepository(userRealmName);\n }\n\n /**\n * Check if user should be auto-promoted to admin based on adminEmails/adminUsernames settings.\n * If user matches and doesn't have admin role, promote them.\n */\n protected async ensureAdminRole(\n user: {\n id: string;\n email?: string | null;\n username?: string | null;\n roles: string[];\n },\n userRealmName?: string,\n ): Promise<boolean> {\n if (user.roles.includes(\"admin\")) return false;\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const settings = await realm.getSettings();\n const { name } = realm;\n const adminEmails = settings.adminEmails ?? [];\n const adminUsernames = settings.adminUsernames ?? [];\n\n const isAdminByEmail = user.email && adminEmails.includes(user.email);\n const isAdminByUsername =\n user.username && adminUsernames.includes(user.username);\n\n if (!isAdminByEmail && !isAdminByUsername) return false;\n\n // Promote to admin\n user.roles = [...user.roles.filter((r) => r !== \"admin\"), \"admin\"];\n await this.users(userRealmName).updateById(user.id, { roles: user.roles });\n\n const reason = isAdminByEmail ? \"adminEmails\" : \"adminUsernames\";\n this.log.info(`User auto-promoted to admin via ${reason} setting`, {\n userId: user.id,\n email: user.email,\n username: user.username,\n realm: name,\n });\n\n await this.userAudits(userRealmName)?.user.log(\"role_change\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: name,\n resourceId: user.id,\n description: `User auto-promoted to admin via ${reason} setting`,\n metadata: { addedRole: \"admin\", reason },\n });\n\n return true;\n }\n\n /**\n * Generate a unique username from an OAuth profile.\n *\n * Routes through {@link UsernameSlugger}, which is the same code path as\n * `username: \"email\"` registration. The OAuth profile's email is the\n * primary signal; if absent (rare — most IDPs return one), we fall back\n * to `profile.name`, then to a random handle. The slugger applies the\n * realm's `usernameBlocklist` and retries on collision.\n */\n protected async generateUniqueUsername(\n profile: OAuth2Profile,\n _realmSettings: any,\n _users: any,\n realmName?: string,\n ): Promise<string> {\n const seed =\n profile.email ??\n profile.name ??\n `user-${Math.random().toString(36).slice(2, 8)}`;\n const base = this.usernameSlugger.slug(seed);\n return this.usernameSlugger.pickAvailable(realmName, base);\n }\n\n /**\n * Random delay to prevent timing attacks (50-200ms)\n * Uses cryptographically secure random number generation\n */\n protected randomDelay(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, randomInt(50, 201)));\n }\n\n protected static readonly LOGIN_CACHE_NAME = \"login-rate-limit\";\n\n /**\n * Check if a login key is currently locked out.\n * Read-only — does not increment the counter.\n */\n protected async isLoginLocked(key: string, max: number): Promise<boolean> {\n try {\n const count = await this.cacheProvider.getTyped<number>(\n SessionService.LOGIN_CACHE_NAME,\n key,\n );\n return count != null && count >= max;\n } catch (error) {\n this.log.warn(\n \"Failed to check login rate limit, allowing attempt\",\n error,\n );\n return false;\n }\n }\n\n /**\n * Record a failed login attempt. Uses getTyped + setTyped (not incr) so that\n * each write refreshes the TTL — implementing sliding-window behavior.\n *\n * Returns `true` if this failure just crossed the lockout threshold.\n */\n protected async recordFailedLogin(\n key: string,\n max: number,\n windowMs: number,\n ): Promise<boolean> {\n try {\n const count =\n (await this.cacheProvider.getTyped<number>(\n SessionService.LOGIN_CACHE_NAME,\n key,\n )) ?? 0;\n const newCount = count + 1;\n await this.cacheProvider.setTyped(\n SessionService.LOGIN_CACHE_NAME,\n key,\n newCount,\n { ttl: windowMs },\n );\n return newCount === max;\n } catch (error) {\n this.log.warn(\"Failed to record failed login attempt\", error);\n return false;\n }\n }\n\n /**\n * Validate user credentials and return the user if valid.\n */\n public async login(\n provider: string,\n username: string,\n password: string,\n userRealmName?: string,\n ): Promise<UserEntity> {\n const realm = this.realmProvider.getRealm(userRealmName);\n const settings = await realm.getSettings();\n const { name } = realm;\n const { loginRateLimit } = settings;\n const isEmail = username.includes(\"@\");\n const isPhone = /^[+\\d][\\d\\s()-]+$/.test(username);\n const isUsername = !isEmail && !isPhone;\n const identities = this.identities(userRealmName);\n const users = this.users(userRealmName);\n\n // IP rate limit check (global, cross-realm) — before any DB work\n const request = this.alepha.store.get(\"alepha.http.request\");\n const ipKey = request?.ip ? `login:ip:${request.ip}` : undefined;\n\n if (ipKey) {\n const ipLocked = await this.isLoginLocked(\n ipKey,\n loginRateLimit.ipMaxAttempts,\n );\n if (ipLocked) {\n this.log.warn(\"Login blocked — IP rate limit exceeded\", {\n ip: request?.ip,\n });\n throw new InvalidCredentialsError();\n }\n }\n\n await this.randomDelay();\n\n try {\n const where = users.createQueryWhere();\n\n where.realm = name;\n\n if (settings.username !== \"none\" && isUsername) {\n // validate username format if regex is provided\n if (settings.usernameRegExp) {\n const regex = new RegExp(settings.usernameRegExp);\n if (!regex.test(username)) {\n this.log.warn(\"Username does not match required format\", {\n provider,\n username,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n description: \"Username does not match required format\",\n metadata: { provider, username },\n });\n\n throw new InvalidCredentialsError();\n }\n }\n where.username = { ilike: username };\n } else if (settings.email !== \"none\" && isEmail) {\n where.email = username;\n } else if (settings.phoneNumber !== \"none\" && isPhone) {\n where.phoneNumber = username;\n } else {\n this.log.warn(\"Invalid login identifier format\", {\n provider,\n username,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n description: \"Invalid login identifier format\",\n metadata: { provider, username },\n });\n\n throw new InvalidCredentialsError();\n }\n\n const user = await users.findOne({ where });\n if (!user) {\n this.log.warn(\"User not found during login attempt\", {\n provider,\n username,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n description: \"User not found\",\n metadata: { provider, username },\n });\n\n // Only increment IP counter (no user ID to track)\n if (ipKey) {\n const justLocked = await this.recordFailedLogin(\n ipKey,\n loginRateLimit.ipMaxAttempts,\n loginRateLimit.windowMs,\n );\n if (justLocked) {\n await this.sessionAudits(userRealmName)?.security.log(\n \"rate_limited\",\n {\n userRealm: name,\n success: false,\n description:\n \"IP temporarily locked due to too many failed login attempts\",\n metadata: { ip: request?.ip },\n },\n );\n }\n }\n\n throw new InvalidCredentialsError();\n }\n\n // Check if user account is enabled\n if (!user.enabled) {\n this.log.warn(\"Login attempt for disabled account\", {\n userId: user.id,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n resourceId: user.id,\n description: \"Login attempt for disabled account\",\n metadata: { provider, username },\n });\n\n throw new InvalidCredentialsError();\n }\n\n // Account rate limit check (per-realm)\n const accountKey = `login:account:${name}:${user.id}`;\n const accountLocked = await this.isLoginLocked(\n accountKey,\n loginRateLimit.accountMaxAttempts,\n );\n if (accountLocked) {\n this.log.warn(\"Login blocked — account rate limit exceeded\", {\n userId: user.id,\n realm: name,\n });\n throw new InvalidCredentialsError();\n }\n\n const identity = await identities.getOne({\n where: {\n provider: { eq: provider },\n userId: { eq: user.id },\n },\n });\n\n const storedPassword = identity.password;\n if (!storedPassword) {\n this.log.error(\"Identity has no password configured\", {\n provider,\n username,\n identityId: identity.id,\n realm: name,\n });\n throw new InvalidCredentialsError();\n }\n\n const valid = await this.cryptoProvider.verifyPassword(\n password,\n storedPassword,\n );\n\n if (!valid) {\n this.log.warn(\"Invalid password during login attempt\", {\n provider,\n username,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n resourceId: user.id,\n description: \"Invalid password\",\n metadata: { provider, username },\n });\n\n // Record failed attempt on both IP and account counters\n if (ipKey) {\n const ipJustLocked = await this.recordFailedLogin(\n ipKey,\n loginRateLimit.ipMaxAttempts,\n loginRateLimit.windowMs,\n );\n if (ipJustLocked) {\n await this.sessionAudits(userRealmName)?.security.log(\n \"rate_limited\",\n {\n userRealm: name,\n success: false,\n description:\n \"IP temporarily locked due to too many failed login attempts\",\n metadata: { ip: request?.ip },\n },\n );\n }\n }\n\n const accountJustLocked = await this.recordFailedLogin(\n accountKey,\n loginRateLimit.accountMaxAttempts,\n loginRateLimit.windowMs,\n );\n if (accountJustLocked) {\n await this.sessionAudits(userRealmName)?.security.log(\n \"rate_limited\",\n {\n userRealm: name,\n resourceId: user.id,\n success: false,\n description:\n \"Account temporarily locked due to too many failed login attempts\",\n metadata: { userId: user.id },\n },\n );\n\n // Notify user about account lockout\n if (user.email) {\n const lockoutMinutes = Math.round(loginRateLimit.windowMs / 60_000);\n await this.userNotifications(userRealmName)?.accountLockout.push({\n contact: user.email,\n variables: { email: user.email, lockoutMinutes },\n });\n }\n }\n\n throw new InvalidCredentialsError();\n }\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: name,\n resourceId: user.id,\n description: `User logged in via ${provider}`,\n metadata: { provider, username },\n });\n\n // Auto-promote to admin if configured\n await this.ensureAdminRole(user, userRealmName);\n\n return user;\n } catch (error) {\n if (error instanceof InvalidCredentialsError) {\n throw error;\n }\n\n this.log.warn(\"Error during login attempt\", error);\n\n throw new InvalidCredentialsError();\n }\n }\n\n public async createSession(\n user: UserAccount,\n expiresIn: number,\n userRealmName?: string,\n clientId?: string,\n ) {\n this.log.trace(\"Creating session\", { userId: user.id, expiresIn });\n\n const request = this.alepha.store.get(\"alepha.http.request\");\n const refreshToken = this.cryptoProvider.randomUUID();\n\n const expiresAt = this.dateTimeProvider\n .now()\n .add(expiresIn, \"seconds\")\n .toISOString();\n\n const nowIso = this.dateTimeProvider.nowISOString();\n\n const session = await this.sessions(userRealmName).create({\n userId: user.id,\n expiresAt,\n lastUsedAt: nowIso,\n ip: request?.ip,\n country: request?.geo?.country,\n userAgent: request?.userAgent,\n refreshToken,\n clientId,\n });\n\n await this.users(userRealmName).updateById(user.id, {\n lastLoginAt: nowIso,\n });\n\n this.log.info(\"Session created\", {\n sessionId: session.id,\n userId: user.id,\n ip: request?.ip,\n });\n\n return {\n refreshToken,\n sessionId: session.id,\n };\n }\n\n public async refreshSession(refreshToken: string, userRealmName?: string) {\n this.log.trace(\"Refreshing session\");\n\n // getOne() throws DbEntityNotFoundError if not found — never returns null.\n // No null check needed here.\n const session = await this.sessions(userRealmName).getOne({\n where: {\n refreshToken: { eq: refreshToken },\n },\n });\n\n const now = this.dateTimeProvider.now();\n const expiresAt = this.dateTimeProvider.of(session.expiresAt);\n\n if (this.dateTimeProvider.of(session.expiresAt) < now) {\n this.log.debug(\"Session expired during refresh\", {\n sessionId: session.id,\n userId: session.userId,\n });\n await this.sessions(userRealmName).deleteById(session.id);\n throw new UnauthorizedError(\"Session expired\");\n }\n\n // Idle timeout check — opt-in via realm settings.\n // Falls back to createdAt when lastUsedAt is null (pre-migration rows or\n // sessions that never refreshed since the column was introduced).\n const realm = this.realmProvider.getRealm(userRealmName);\n const settings = await realm.getSettings();\n const idleMs = settings.refreshToken?.expirationIdle;\n if (idleMs && idleMs > 0) {\n const lastUsedRef = session.lastUsedAt ?? session.createdAt;\n const idleSince = now.diff(this.dateTimeProvider.of(lastUsedRef));\n if (idleSince > idleMs) {\n this.log.info(\"Session expired (idle timeout)\", {\n sessionId: session.id,\n userId: session.userId,\n idleMs: idleSince,\n thresholdMs: idleMs,\n });\n await this.sessions(userRealmName).deleteById(session.id);\n throw new UnauthorizedError(\"Session expired\");\n }\n }\n\n const user = await this.users(userRealmName).getOne({\n where: {\n id: { eq: session.userId },\n },\n });\n\n // Check if user account is still enabled\n if (!user.enabled) {\n this.log.warn(\"Session refresh for disabled account\", {\n userId: user.id,\n sessionId: session.id,\n });\n await this.sessions(userRealmName).deleteById(session.id);\n throw new UnauthorizedError(\"Account disabled\");\n }\n\n // Auto-promote to admin if configured (handles \"I promote you admin\" case)\n await this.ensureAdminRole(user, userRealmName);\n\n // Update lastUsedAt — sliding-window for idle timeout enforcement.\n await this.sessions(userRealmName).updateById(session.id, {\n lastUsedAt: now.toISOString(),\n });\n\n this.log.debug(\"Session refreshed\", {\n sessionId: session.id,\n userId: session.userId,\n });\n\n return {\n user,\n expiresIn: expiresAt.unix() - now.unix(),\n sessionId: session.id,\n };\n }\n\n public async deleteSession(refreshToken: string, userRealmName?: string) {\n this.log.trace(\"Deleting session\");\n\n // Get session info before deletion for audit\n const session = await this.sessions(userRealmName).findOne({\n where: { refreshToken: { eq: refreshToken } },\n });\n\n await this.sessions(userRealmName).deleteOne({\n refreshToken,\n });\n this.log.debug(\"Session deleted\");\n\n if (session) {\n const { name } = this.realmProvider.getRealm(userRealmName);\n\n await this.sessionAudits(userRealmName)?.auth.log(\"logout\", {\n userId: session.userId,\n userRealm: name,\n sessionId: session.id,\n description: \"User logged out\",\n });\n }\n }\n\n public async link(\n provider: string,\n profile: OAuth2Profile,\n userRealmName?: string,\n ) {\n this.log.trace(\"Linking OAuth2 profile\", {\n provider,\n profileSub: profile.sub,\n email: profile.email,\n });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const identities = this.identities(userRealmName);\n const users = this.users(userRealmName);\n\n const identity = await identities.findOne({\n where: {\n provider,\n providerUserId: profile.sub,\n },\n });\n\n // existing identity found, return associated user\n if (identity) {\n this.log.debug(\"Existing identity found\", {\n provider,\n identityId: identity.id,\n userId: identity.userId,\n });\n\n const user = await users.getById(identity.userId);\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: user.id,\n description: `User logged in via OAuth2 (${provider})`,\n metadata: { provider, providerUserId: profile.sub },\n });\n\n // Auto-promote to admin if configured\n await this.ensureAdminRole(user, userRealmName);\n\n return user;\n }\n\n if (!profile.email) {\n this.log.debug(\"OAuth2 profile has no email, returning profile as-is\", {\n provider,\n profileSub: profile.sub,\n });\n return {\n id: profile.sub,\n ...profile,\n };\n }\n\n const existing = await users.findOne({\n where: {\n realm: realm.name,\n email: profile.email,\n },\n });\n\n if (existing) {\n // Refuse auto-link if the OAuth provider explicitly says email is not verified\n if (profile.email_verified === false) {\n this.log.warn(\n \"OAuth2 profile email not verified by provider, refusing auto-link\",\n { provider, email: profile.email, userId: existing.id },\n );\n throw new BadRequestError(\n \"Cannot link account: email not verified by provider\",\n );\n }\n\n this.log.debug(\"Linking OAuth2 profile to existing user by email\", {\n provider,\n profileSub: profile.sub,\n userId: existing.id,\n email: profile.email,\n });\n await identities.create({\n provider,\n providerUserId: profile.sub,\n userId: existing.id,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userId: existing.id,\n userEmail: existing.email ?? undefined,\n userRealm: realm.name,\n resourceId: existing.id,\n description: `OAuth2 identity linked to existing user (${provider})`,\n metadata: { provider, providerUserId: profile.sub, linked: true },\n });\n\n // Auto-promote to admin if configured\n await this.ensureAdminRole(existing, userRealmName);\n\n return existing;\n }\n\n const realmSettings = await realm.getSettings();\n const adminEmails = realmSettings?.adminEmails ?? [];\n const isAdmin = profile.email && adminEmails.includes(profile.email);\n\n if (realmSettings?.registrationAllowed === false && !isAdmin) {\n this.log.warn(\"Registration not allowed for realm via OAuth2\", {\n provider,\n userRealmName,\n });\n throw new BadRequestError(\"Account doesn't exist\");\n }\n\n const username = await this.generateUniqueUsername(\n profile,\n realmSettings,\n users,\n userRealmName,\n );\n\n const user = await users.create({\n realm: realm.name,\n username,\n email: profile.email,\n firstName: profile.given_name,\n lastName: profile.family_name,\n // we trust the OAuth2 provider\n emailVerified: true,\n roles: realmSettings.defaultRoles,\n });\n\n if (profile.picture) {\n this.log.debug(\"Fetching user profile picture from OAuth2 provider\", {\n provider,\n url: profile.picture,\n });\n try {\n const response = await fetch(profile.picture);\n const file = this.fsp.createFile({\n response,\n });\n if (response.ok && response.body) {\n const fileEntity = await this.fileController.uploadFile(\n {\n body: { file },\n },\n {\n user,\n },\n );\n await users.updateById(user.id, { picture: fileEntity.id });\n }\n } catch (error) {\n this.log.warn(\"Failed to fetch user profile picture\", error);\n }\n }\n\n await this.identities(userRealmName).create({\n provider,\n providerUserId: profile.sub,\n userId: user.id,\n });\n\n this.log.info(\"New user created via OAuth2 link\", {\n provider,\n userId: user.id,\n email: user.email,\n username: user.username,\n });\n\n // Audit: user created via OAuth\n await this.userAudits(userRealmName)?.user.log(\"create\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: user.id,\n description: `User created via OAuth2 (${provider})`,\n metadata: {\n provider,\n providerUserId: profile.sub,\n username: user.username,\n email: user.email,\n },\n });\n\n // Audit: login event\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: user.id,\n description: `First login via OAuth2 (${provider})`,\n metadata: { provider, providerUserId: profile.sub, firstLogin: true },\n });\n\n // Auto-promote to admin if configured\n await this.ensureAdminRole(user, userRealmName);\n\n return user;\n }\n}\n","import { $context, AlephaError } from \"alepha\";\nimport { AlephaApiKeys, ApiKeyService } from \"alepha/api/keys\";\nimport {\n AlephaOAuth,\n OAuthClientService,\n oauthOptions,\n} from \"alepha/api/oauth\";\nimport { $parameter, AlephaApiParameters } from \"alepha/api/parameters\";\nimport { AlephaApiVerification } from \"alepha/api/verifications\";\nimport { mcpStreamableHttpOptions } from \"alepha/mcp\";\nimport type { Repository } from \"alepha/orm\";\nimport {\n $issuer,\n $permission,\n type IssuerPrimitive,\n type IssuerPrimitiveOptions,\n type IssuerResolver,\n SecurityProvider,\n} from \"alepha/security\";\nimport {\n $authApple,\n $authCredentials,\n $authFacebook,\n $authFranceConnect,\n $authGithub,\n $authGoogle,\n $authMicrosoft,\n type AuthPrimitive,\n type Credentials,\n type LinkAccountOptions,\n type WithLinkFn,\n type WithLoginFn,\n} from \"alepha/server/auth\";\nimport {\n type RealmAuthSettings,\n realmAuthSettingsAtom,\n} from \"../atoms/realmAuthSettingsAtom.ts\";\nimport { SessionAudits } from \"../audits/SessionAudits.ts\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport { UserBuckets } from \"../buckets/UserBuckets.ts\";\nimport type { identities } from \"../entities/identities.ts\";\nimport type { sessions } from \"../entities/sessions.ts\";\nimport { DEFAULT_USER_REALM_NAME, type users } from \"../entities/users.ts\";\nimport { UserJobs } from \"../jobs/UserJobs.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport { SessionService } from \"../services/SessionService.ts\";\n\nexport type RealmPrimitive = IssuerPrimitive & WithLinkFn & WithLoginFn;\n\n/**\n * Already configured realm for user management.\n *\n * Realm contains two roles: `admin` and `user`.\n *\n * - `admin`: Has full access to all resources and permissions.\n * - `user`: Has access to their own resources and permissions, but cannot access admin-level resources.\n *\n * Realm uses session management for handling user sessions.\n *\n * Environment Variables:\n * - `APP_SECRET`: Secret key for signing tokens (if not provided in options).\n */\n\nexport const $realm = (options: RealmOptions = {}): RealmPrimitive => {\n const { alepha } = $context();\n const sessionService = alepha.inject(SessionService);\n const securityProvider = alepha.inject(SecurityProvider);\n const realmProvider = alepha.inject(RealmProvider);\n\n const name = options.issuer?.name ?? DEFAULT_USER_REALM_NAME;\n\n options.settings ??= {};\n\n // Merge features with defaults\n const features: RealmFeatures = {\n jobs: false,\n notifications: false,\n apiKeys: false,\n oauth: false,\n parameters: false,\n avatars: false,\n audits: false,\n ...options.features,\n };\n\n // When notifications are disabled, force verification-dependent settings to false\n // These features require sending codes via email/SMS which won't work without notifications\n if (!features.notifications) {\n options.settings.verifyEmailRequired = false;\n options.settings.verifyPhoneRequired = false;\n options.settings.resetPasswordAllowed = false;\n }\n\n const realmRegistration = realmProvider.register(name, options);\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Enable features based on configuration\n // Each feature registers its wrapper service which internally uses the module primitives\n\n if (features.avatars) {\n alepha.with(UserBuckets);\n }\n\n if (features.audits) {\n alepha.with(UserAudits);\n alepha.with(SessionAudits);\n }\n\n if (features.jobs) {\n alepha.with(UserJobs);\n }\n\n if (features.notifications) {\n alepha.with(UserNotifications);\n alepha.with(AlephaApiVerification);\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Collect custom resolvers that will be registered during $issuer.onInit()\n // This ensures they are registered AFTER the realm is created (not on the default test realm)\n const customResolvers: IssuerResolver[] = [\n ...(options.issuer?.resolvers ?? []),\n ];\n\n // Enable API key authentication - must be added to customResolvers before $issuer() call\n if (features.apiKeys) {\n alepha.with(AlephaApiKeys);\n const apiKeyService = alepha.inject(ApiKeyService);\n customResolvers.push(apiKeyService.createResolver());\n }\n\n const realm: RealmPrimitive = $issuer({\n ...options.issuer,\n name,\n secret: options.secret ?? securityProvider.secretKey,\n resolvers: customResolvers,\n roles: options.issuer?.roles ?? [\n {\n name: \"admin\",\n permissions: [\n {\n name: \"*\",\n },\n ],\n },\n {\n name: \"user\",\n default: true,\n permissions: [\n {\n name: \"*\",\n ownership: true,\n exclude: [\"admin:*\"],\n },\n ],\n },\n ],\n settings: {\n accessToken: {\n expiration: [15, \"minutes\"],\n },\n refreshToken: {\n expiration: [30, \"days\"],\n },\n onCreateSession: async (user, config) => {\n return sessionService.createSession(\n user,\n config.expiresIn,\n undefined,\n config.clientId,\n );\n },\n onRefreshSession: async (refreshToken) => {\n return sessionService.refreshSession(refreshToken);\n },\n onDeleteSession: async (refreshToken) => {\n await sessionService.deleteSession(refreshToken);\n },\n ...options.issuer?.settings,\n },\n });\n\n $permission({\n name: \"admin:access\",\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Enable the OAuth 2.1 authorization server.\n // The OAuth module is realm-agnostic: it mints access tokens through an\n // issuer handed to it via `registerIssuer`. Here we register the realm's own\n // issuer plus a loader that maps a `users` row to a `UserAccount`.\n if (features.oauth) {\n alepha.with(AlephaOAuth);\n const oauthService = alepha.inject(OAuthClientService);\n\n // Point the OAuth controller at this realm so its endpoints mint tokens\n // through the issuer we register below. Merge with the current value so a\n // caller-configured `resource` path is preserved.\n const currentOAuthOptions =\n alepha.get(oauthOptions) ?? oauthOptions.options.default;\n alepha.set(oauthOptions, { ...currentOAuthOptions, realm: name });\n\n const loadUser = async (userId: string) => {\n const user = await sessionService.users(name).findOne({\n where: { id: { eq: userId }, realm: name },\n });\n if (!user) {\n throw new AlephaError(`User '${userId}' not found in realm '${name}'`);\n }\n const composedName =\n [user.firstName, user.lastName]\n .filter((s): s is string => !!s?.trim())\n .join(\" \")\n .trim() || undefined;\n return {\n id: user.id,\n roles: user.roles,\n name: composedName,\n email: user.email,\n username: user.username,\n picture: user.picture,\n organization: user.organizationId ?? undefined,\n realm: name,\n };\n };\n\n oauthService.registerIssuer(name, realm, loadUser);\n\n // Tell the MCP Streamable HTTP transport to challenge unauthenticated\n // requests with an RFC 9728 401 (`WWW-Authenticate`), so MCP clients\n // can discover this OAuth server. Decoupled: the transport never\n // imports the OAuth module — it just reads its own options atom.\n // Harmless when no MCP transport is mounted; the atom is simply set.\n const currentMcpOptions =\n alepha.get(mcpStreamableHttpOptions) ??\n mcpStreamableHttpOptions.options.default;\n alepha.set(mcpStreamableHttpOptions, {\n ...currentMcpOptions,\n requireAuth: true,\n });\n }\n\n realm.link = (name: string) => {\n return (ctx: LinkAccountOptions) =>\n sessionService.link(name, ctx.user, realm.name);\n };\n\n realm.login = (name: string) => {\n return async (credentials: Credentials) => {\n const user = await sessionService.login(\n name,\n credentials.username,\n credentials.password,\n realm.name,\n );\n // Compose display name from first+last for OIDC `name` claim.\n // Without this, credentials-registered users appear as \"Anonymous User\".\n const composedName =\n [user.firstName, user.lastName]\n .filter((s): s is string => !!s?.trim())\n .join(\" \")\n .trim() || undefined;\n return { ...user, name: composedName };\n };\n };\n\n const identities = options.identities ?? {\n credentials: true,\n };\n\n if (identities) {\n const auth: Record<string, AuthPrimitive> = {};\n if (identities.credentials) {\n auth.credentials = $authCredentials(realm);\n } else {\n // if credentials auth is disabled, disable registration as well\n realmRegistration.settings.registrationAllowed = false;\n }\n\n if (identities.google) {\n auth.google = $authGoogle(realm);\n }\n\n if (identities.github) {\n auth.github = $authGithub(realm);\n }\n\n if (identities.apple) {\n auth.apple = $authApple(realm);\n }\n\n if (identities.facebook) {\n auth.facebook = $authFacebook(realm);\n }\n\n if (identities.microsoft) {\n auth.microsoft = $authMicrosoft(realm);\n }\n\n if (identities.franceconnect) {\n auth.franceconnect = $authFranceConnect(realm);\n }\n\n alepha.with(() => auth);\n }\n\n if (features.parameters) {\n alepha.with(AlephaApiParameters);\n const settingsParam = $parameter({\n name: `api.realms.settings.${name}`,\n description: `Authentication and registration settings for realm \"${name}\"`,\n schema: realmAuthSettingsAtom.schema,\n default: realmRegistration.settings,\n });\n realmRegistration.settingsParameter = settingsParam;\n alepha.with(() => ({ [`realmSettings_${name}`]: settingsParam }));\n }\n\n return realm;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RealmFeatures {\n /**\n * Will enable Job module.\n *\n * - Enable session purge functionality for cleaning up expired sessions.\n *\n * @default false\n */\n jobs?: boolean;\n\n /**\n * Enable notification system for password reset, verification emails, etc.\n *\n * @default false\n */\n notifications?: boolean;\n\n /**\n * Enable API key authentication for programmatic access.\n *\n * When enabled, users can create API keys to access protected endpoints\n * without using JWT tokens. API keys are useful for:\n * - Programmatic access (CLI tools, scripts)\n * - Long-lived authentication tokens\n * - Third-party integrations (MCP servers)\n *\n * API keys can be passed via:\n * - Query parameter: `?api_key=ak_xxx`\n * - Bearer header: `Authorization: Bearer ak_xxx`\n *\n * @default false\n */\n apiKeys?: boolean;\n\n /**\n * Enable the OAuth 2.1 authorization server.\n *\n * Exposes RFC 9728 / RFC 8414 metadata, RFC 7591 dynamic client\n * registration, and PKCE authorize/token endpoints so MCP clients\n * (e.g. Claude) can connect to a protected `/mcp` endpoint without an\n * API key in the query string.\n *\n * @default false\n */\n oauth?: boolean;\n\n /**\n * Enable runtime configuration management.\n *\n * Allows configuring realm settings at runtime with versioning and scheduled activation.\n *\n * @default false\n */\n parameters?: boolean;\n\n /**\n * Enable avatar uploads for user profiles.\n *\n * @default false\n */\n avatars?: boolean;\n\n /**\n * Enable audit trail for compliance and event logging.\n *\n * @default false\n */\n audits?: boolean;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RealmOptions {\n /**\n * Secret key for signing tokens.\n *\n * If not provided, the secret from the SecurityProvider will be used (usually from the APP_SECRET environment variable).\n */\n secret?: string;\n\n /**\n * Issuer configuration options.\n *\n * It's already pre-configured for user management with admin and user roles.\n */\n issuer?: Partial<IssuerPrimitiveOptions>;\n\n /**\n * Override entities.\n */\n entities?: {\n users?: Repository<typeof users.schema>;\n identities?: Repository<typeof identities.schema>;\n sessions?: Repository<typeof sessions.schema>;\n };\n\n settings?: Partial<RealmAuthSettings>;\n\n identities?: {\n credentials?: true;\n google?: true;\n github?: true;\n apple?: true;\n facebook?: true;\n microsoft?: true;\n franceconnect?: true;\n };\n\n /**\n * Enable or disable realm features.\n *\n * Features control which modules are loaded with the realm.\n */\n features?: Partial<RealmFeatures>;\n}\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const loginSchema = t.object({\n username: t.text({\n minLength: 3,\n maxLength: 100,\n description: \"Username or email address for login\",\n }),\n password: t.text({\n minLength: 8,\n description: \"User password\",\n }),\n});\n\nexport type LoginInput = Static<typeof loginSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const registerSchema = t.object({\n username: t.string({\n minLength: 3,\n maxLength: 20,\n pattern: /^[a-zA-Z0-9_]+$/,\n description: \"Username for the new account\",\n }),\n email: t.email({\n description: \"Email address for the new account\",\n }),\n password: t.string({\n minLength: 8,\n description: \"Password for the new account\",\n }),\n confirmPassword: t.string({\n minLength: 8,\n description: \"Confirmation of the password\",\n }),\n firstName: t.optional(\n t.string({\n maxLength: 100,\n description: \"User's first name\",\n }),\n ),\n lastName: t.optional(\n t.string({\n maxLength: 100,\n description: \"User's last name\",\n }),\n ),\n});\n\nexport type RegisterInput = Static<typeof registerSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const resetPasswordRequestSchema = t.object({\n email: t.email({\n description: \"Email address to send password reset link\",\n }),\n});\n\nexport const resetPasswordSchema = t.object({\n token: t.string({\n description: \"Password reset token from email\",\n }),\n password: t.string({\n minLength: 8,\n description: \"New password\",\n }),\n confirmPassword: t.string({\n minLength: 8,\n description: \"Confirmation of the new password\",\n }),\n});\n\nexport type ResetPasswordRequest = Static<typeof resetPasswordRequestSchema>;\nexport type ResetPasswordInput = Static<typeof resetPasswordSchema>;\n","import { $module } from \"alepha\";\nimport { SessionAudits } from \"./audits/SessionAudits.ts\";\nimport { UserAudits } from \"./audits/UserAudits.ts\";\nimport { UserBuckets } from \"./buckets/UserBuckets.ts\";\nimport { AdminIdentityController } from \"./controllers/AdminIdentityController.ts\";\nimport { AdminSessionController } from \"./controllers/AdminSessionController.ts\";\nimport { AdminUserController } from \"./controllers/AdminUserController.ts\";\nimport { RealmController } from \"./controllers/RealmController.ts\";\nimport { UserController } from \"./controllers/UserController.ts\";\nimport { UserJobs } from \"./jobs/UserJobs.ts\";\nimport { UserNotifications } from \"./notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"./providers/RealmProvider.ts\";\nimport { CredentialService } from \"./services/CredentialService.ts\";\nimport { IdentityService } from \"./services/IdentityService.ts\";\nimport { RegistrationService } from \"./services/RegistrationService.ts\";\nimport { SessionCrudService } from \"./services/SessionCrudService.ts\";\nimport { SessionService } from \"./services/SessionService.ts\";\nimport { UsernameSlugger } from \"./services/UsernameSlugger.ts\";\nimport { UserService } from \"./services/UserService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./atoms/realmAuthSettingsAtom.ts\";\nexport * from \"./audits/SessionAudits.ts\";\nexport * from \"./audits/UserAudits.ts\";\nexport * from \"./buckets/UserBuckets.ts\";\nexport * from \"./controllers/AdminIdentityController.ts\";\nexport * from \"./controllers/AdminSessionController.ts\";\nexport * from \"./controllers/AdminUserController.ts\";\nexport * from \"./controllers/RealmController.ts\";\nexport * from \"./controllers/UserController.ts\";\nexport * from \"./entities/identities.ts\";\nexport * from \"./entities/sessions.ts\";\nexport * from \"./entities/users.ts\";\nexport * from \"./jobs/UserJobs.ts\";\nexport * from \"./notifications/UserNotifications.ts\";\nexport * from \"./primitives/$realm.ts\";\nexport * from \"./providers/RealmProvider.ts\";\nexport * from \"./schemas/completePasswordResetRequestSchema.ts\";\nexport * from \"./schemas/completeRegistrationRequestSchema.ts\";\nexport * from \"./schemas/createUserSchema.ts\";\nexport * from \"./schemas/identityQuerySchema.ts\";\nexport * from \"./schemas/identityResourceSchema.ts\";\nexport * from \"./schemas/loginSchema.ts\";\nexport * from \"./schemas/passwordResetIntentResponseSchema.ts\";\nexport * from \"./schemas/realmConfigSchema.ts\";\nexport * from \"./schemas/registerSchema.ts\";\nexport * from \"./schemas/registrationIntentResponseSchema.ts\";\nexport * from \"./schemas/resetPasswordSchema.ts\";\nexport * from \"./schemas/sessionQuerySchema.ts\";\nexport * from \"./schemas/sessionResourceSchema.ts\";\nexport * from \"./schemas/updateUserSchema.ts\";\nexport * from \"./schemas/userQuerySchema.ts\";\nexport * from \"./schemas/userResourceSchema.ts\";\nexport * from \"./services/CredentialService.ts\";\nexport * from \"./services/IdentityService.ts\";\nexport * from \"./services/RegistrationService.ts\";\nexport * from \"./services/SessionCrudService.ts\";\nexport * from \"./services/SessionService.ts\";\nexport * from \"./services/UsernameSlugger.ts\";\nexport * from \"./services/UserService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Complete user management with multi-realm support for multi-tenant applications.\n *\n * **Features:**\n * - User registration, login, and profile management\n * - Password reset workflows\n * - Email verification\n * - Session management with multiple devices\n * - Identity management (social logins, SSO)\n * - Multi-realm support for tenant isolation\n * - Credential management\n * - Entities: `users`, `identities`, `sessions`\n *\n * @module alepha.api.users\n */\nexport const AlephaApiUsers = $module({\n name: \"alepha.api.users\",\n services: [\n RealmProvider,\n SessionService,\n SessionCrudService,\n CredentialService,\n RegistrationService,\n UserService,\n UsernameSlugger,\n IdentityService,\n UserController,\n AdminUserController,\n AdminSessionController,\n AdminIdentityController,\n RealmController,\n ],\n variants: [\n UserJobs,\n UserNotifications,\n UserAudits,\n SessionAudits,\n UserBuckets,\n ],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["INTENT_TTL_MINUTES"],"sources":["../../../src/api/users/audits/SessionAudits.ts","../../../src/api/users/audits/UserAudits.ts","../../../src/api/users/buckets/UserBuckets.ts","../../../src/api/users/schemas/identityQuerySchema.ts","../../../src/api/users/entities/users.ts","../../../src/api/users/entities/identities.ts","../../../src/api/users/schemas/identityResourceSchema.ts","../../../src/api/users/atoms/realmAuthSettingsAtom.ts","../../../src/api/users/entities/sessions.ts","../../../src/api/users/providers/RealmProvider.ts","../../../src/api/users/services/IdentityService.ts","../../../src/api/users/controllers/AdminIdentityController.ts","../../../src/api/users/schemas/sessionQuerySchema.ts","../../../src/api/users/schemas/sessionResourceSchema.ts","../../../src/api/users/services/SessionCrudService.ts","../../../src/api/users/controllers/AdminSessionController.ts","../../../src/api/users/schemas/createUserSchema.ts","../../../src/api/users/schemas/updateUserSchema.ts","../../../src/api/users/schemas/userQuerySchema.ts","../../../src/api/users/schemas/userResourceSchema.ts","../../../src/api/users/notifications/UserNotifications.ts","../../../src/api/users/services/UserService.ts","../../../src/api/users/controllers/AdminUserController.ts","../../../src/api/users/schemas/realmConfigSchema.ts","../../../src/api/users/controllers/RealmController.ts","../../../src/api/users/schemas/completePasswordResetRequestSchema.ts","../../../src/api/users/schemas/completeRegistrationRequestSchema.ts","../../../src/api/users/schemas/passwordResetIntentResponseSchema.ts","../../../src/api/users/schemas/registerQuerySchema.ts","../../../src/api/users/schemas/registerRequestSchema.ts","../../../src/api/users/schemas/registrationIntentResponseSchema.ts","../../../src/api/users/services/CredentialService.ts","../../../src/api/users/services/UsernameSlugger.ts","../../../src/api/users/services/RegistrationService.ts","../../../src/api/users/controllers/UserController.ts","../../../src/api/users/jobs/UserJobs.ts","../../../src/api/users/services/SessionService.ts","../../../src/api/users/primitives/$realm.ts","../../../src/api/users/schemas/loginSchema.ts","../../../src/api/users/schemas/registerSchema.ts","../../../src/api/users/schemas/resetPasswordSchema.ts","../../../src/api/users/index.ts"],"sourcesContent":["import { $audit } from \"alepha/api/audits\";\n\n/**\n * Authentication & session-security audit events.\n *\n * Holds two audit types:\n * - `auth` — login / logout / token refresh / MFA.\n * - `security` — rate limiting, session invalidation, and related guards.\n *\n * Failed events are logged with `success: false`; severity (`warning`) is\n * derived centrally in `AuditService.create`. Register as a module variant\n * and log via the exposed primitives:\n * `sessionAudits(realm)?.auth.log(\"login\", { success: false, … })`.\n */\nexport class SessionAudits {\n public readonly auth = $audit({\n type: \"auth\",\n description: \"Authentication events (login, logout, token refresh, MFA).\",\n actions: [\"login\", \"logout\", \"token_refresh\", \"mfa_setup\", \"mfa_verify\"],\n });\n\n public readonly security = $audit({\n type: \"security\",\n description:\n \"Security events (rate limiting, session invalidation, blocked access).\",\n actions: [\n \"rate_limited\",\n \"sessions_invalidated\",\n \"permission_denied\",\n \"blocked\",\n ],\n });\n}\n","import { $audit } from \"alepha/api/audits\";\n\n/**\n * User-management audit events.\n *\n * Holds the `user` audit type. Mirrors the `$notification`/`$job` holder\n * pattern (see {@link UserNotifications}) — register as a module variant and\n * log via the exposed primitive: `userAudits(realm)?.user.log(\"create\", …)`.\n */\nexport class UserAudits {\n public readonly user = $audit({\n type: \"user\",\n description:\n \"User management events (create, update, delete, role/password changes).\",\n actions: [\n \"create\",\n \"update\",\n \"delete\",\n \"role_change\",\n \"password_change\",\n \"enable\",\n \"disable\",\n ],\n });\n}\n","import { $bucket } from \"alepha/bucket\";\n\n/**\n * User-specific file storage wrapper service.\n *\n * This service provides file storage for user-related files such as:\n * - User avatars/profile pictures\n *\n * Declared as a module variant — not auto-injected. It is instantiated\n * lazily the first time something calls `alepha.inject(UserBuckets)`.\n */\nexport class UserBuckets {\n /**\n * Bucket for user avatar storage.\n */\n public readonly avatars = $bucket({\n maxSize: 5 * 1024 * 1024, // 5 MB\n mimeTypes: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"],\n });\n}\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const identityQuerySchema = t.extend(pageQuerySchema, {\n userId: t.optional(t.uuid()),\n provider: t.optional(t.string()),\n});\n\nexport type IdentityQuery = Static<typeof identityQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db, sql } from \"alepha/orm\";\n\nexport const DEFAULT_USER_REALM_NAME = \"default\";\n\nexport const users = $entity({\n name: \"users\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n realm: db.default(t.text(), DEFAULT_USER_REALM_NAME),\n\n username: t.optional(\n t.shortText({\n minLength: 3,\n maxLength: 30,\n // pattern is handled at the realm settings level\n }),\n ),\n\n email: t.optional(t.string({ format: \"email\" })),\n\n phoneNumber: t.optional(t.e164()),\n\n roles: db.default(t.array(t.string()), []),\n firstName: t.optional(t.string()),\n lastName: t.optional(t.string()),\n picture: t.optional(t.string()),\n enabled: db.default(t.boolean(), true),\n\n emailVerified: db.default(t.boolean(), false),\n\n lastLoginAt: t.optional(t.datetime()),\n\n organizationId: db.organization(),\n }),\n indexes: [\n {\n expressions: (self) => [self.realm, sql`LOWER(${self.username})`],\n unique: true,\n name: \"users_realm_username_lower_idx\",\n },\n { columns: [\"realm\", \"email\"], unique: true },\n { columns: [\"realm\", \"phoneNumber\"], unique: true },\n ],\n});\n\nexport type UserEntity = Static<typeof users.schema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\nimport { users } from \"./users.ts\";\n\nexport const identities = $entity({\n name: \"identities\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n userId: db.ref(t.uuid(), () => users.cols.id),\n password: t.optional(t.text()),\n provider: t.text(),\n providerUserId: t.optional(t.text()),\n providerData: t.optional(t.json()),\n }),\n indexes: [\n \"userId\",\n \"provider\",\n { columns: [\"userId\", \"provider\"] },\n { columns: [\"provider\", \"providerUserId\"], unique: true },\n ],\n});\n\nexport type IdentityEntity = Static<typeof identities.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { identities } from \"../entities/identities.ts\";\n\nexport const identityResourceSchema = t.omit(identities.schema, [\"password\"]);\n\nexport type IdentityResource = Static<typeof identityResourceSchema>;\n","import { $atom, type Static, t } from \"alepha\";\n\n/**\n * Tri-state field requirement for realm auth settings.\n *\n * - `\"none\"`: Field is disabled and not shown.\n * - `\"optional\"`: Field is shown but not required.\n * - `\"required\"`: Field is shown and required.\n */\nexport type FieldRequirement = \"none\" | \"optional\" | \"required\";\n\nconst fieldRequirement = (description: string) =>\n t.union([t.const(\"none\"), t.const(\"optional\"), t.const(\"required\")], {\n description,\n });\n\n/**\n * Username-specific field requirement, extending {@link FieldRequirement}\n * with an additional auto-derivation mode.\n *\n * - `\"none\"` / `\"optional\"` / `\"required\"`: same as {@link FieldRequirement}.\n * - `\"email\"`: Field is hidden in the registration UI and the value is\n * auto-derived from the user's email at signup. Same handling on\n * credentials and OAuth flows. See `UsernameSlugger` for the rule and\n * collision behavior.\n */\nexport type UsernameFieldRequirement = FieldRequirement | \"email\";\n\nconst usernameFieldRequirement = (description: string) =>\n t.union(\n [\n t.const(\"none\"),\n t.const(\"optional\"),\n t.const(\"required\"),\n t.const(\"email\"),\n ],\n {\n description,\n },\n );\n\nexport const realmAuthSettingsAtom = $atom({\n name: \"alepha.api.users.realmAuthSettings\",\n schema: t.object({\n // Branding and display settings\n displayName: t.optional(\n t.string({\n description:\n \"Display name shown on auth pages (e.g., 'Customer Portal')\",\n }),\n ),\n description: t.optional(\n t.string({\n description: \"Description shown on auth pages\",\n }),\n ),\n logoUrl: t.optional(\n t.string({\n description: \"Logo URL for auth pages\",\n }),\n ),\n\n // Auth settings\n registrationAllowed: t.boolean({\n description: \"Enable user self-registration\",\n }),\n email: fieldRequirement(\n \"Email address field requirement for user accounts\",\n ),\n username: usernameFieldRequirement(\n \"Username field requirement for user accounts\",\n ),\n usernameRegExp: t.string({\n description:\n \"Regular expression that usernames must match (if username is enabled)\",\n }),\n usernameBlocklist: t.array(t.text(), {\n description:\n \"Usernames that the slugger / manual registration must reject. \" +\n \"Default empty so apps can register `admin`/`root`/`me`/etc. if \" +\n \"they want; populate it explicitly for handles you want to keep \" +\n \"off-limits.\",\n }),\n phoneNumber: fieldRequirement(\n \"Phone number field requirement for user accounts\",\n ),\n verifyEmailRequired: t.boolean({\n description: \"Require email verification for user accounts\",\n }),\n verifyPhoneRequired: t.boolean({\n description: \"Require phone verification for user accounts\",\n }),\n firstNameLastName: fieldRequirement(\n \"First and last name field requirement for user accounts\",\n ),\n resetPasswordAllowed: t.boolean({\n description: \"Enable forgot password functionality\",\n }),\n captchaRequired: t.boolean({\n description:\n \"Require captcha verification on registration (needs a CaptchaProvider registered, e.g. TurnstileCaptchaProvider)\",\n }),\n adminEmails: t.array(t.email(), {\n description:\n \"List of email addresses that are automatically promoted to admin role on login\",\n }),\n adminUsernames: t.array(t.text(), {\n description:\n \"List of usernames that are automatically promoted to admin role on login\",\n }),\n defaultRoles: t.array(t.string(), {\n description: \"Default roles assigned to newly registered users\",\n }),\n verifyEmailUrl: t.optional(\n t.string({\n description:\n \"Base URL for email verification links (used when verification method is 'link'). Token and email are appended as query params.\",\n }),\n ),\n passwordPolicy: t.object({\n minLength: t.integer({\n description: \"Minimum password length\",\n default: 8,\n minimum: 1,\n }),\n requireUppercase: t.boolean({\n description: \"Require at least one uppercase letter\",\n }),\n requireLowercase: t.boolean({\n description: \"Require at least one lowercase letter\",\n }),\n requireNumbers: t.boolean({\n description: \"Require at least one number\",\n }),\n requireSpecialCharacters: t.boolean({\n description: \"Require at least one special character\",\n }),\n }),\n loginRateLimit: t.object({\n ipMaxAttempts: t.integer({\n description:\n \"Max failed login attempts per IP before temporary lockout\",\n default: 15,\n minimum: 1,\n }),\n accountMaxAttempts: t.integer({\n description:\n \"Max failed login attempts per account before temporary lockout\",\n default: 5,\n minimum: 1,\n }),\n windowMs: t.integer({\n description: \"Rate limit window duration in milliseconds\",\n default: 15 * 60 * 1000,\n minimum: 1000,\n }),\n }),\n registrationIpMaxAttempts: t.integer({\n description:\n \"Max registration attempts per IP before temporary lockout. Default 10 protects against signup abuse; raise it in dev/e2e environments where a single localhost IP spawns many test users.\",\n default: 10,\n minimum: 1,\n }),\n refreshToken: t.object({\n expirationIdle: t.optional(\n t.integer({\n description:\n \"Maximum time in milliseconds a refresh token may stay unused before being invalidated. \" +\n \"When set, sessions whose last refresh is older than this window are rejected and deleted, \" +\n \"even if the absolute `expiresAt` has not been reached. Recommended for SaaS auth posture \" +\n \"(SOC2/ISO27001). Leave undefined to disable idle invalidation (default).\",\n minimum: 1000,\n }),\n ),\n }),\n }),\n default: {\n // for a fresh hello world setup, we accept registration and email login\n registrationAllowed: true,\n email: \"required\" as FieldRequirement,\n username: \"none\" as UsernameFieldRequirement,\n // Allow hyphens by default so the UsernameSlugger output (`ni-foures-testkv`)\n // matches without app overrides. Existing `[a-zA-Z0-9_]` usernames remain\n // valid because the new pattern is a strict superset.\n usernameRegExp: \"^[a-zA-Z0-9_-]{3,30}$\",\n usernameBlocklist: [] as string[],\n phoneNumber: \"none\" as FieldRequirement,\n verifyEmailRequired: false,\n verifyPhoneRequired: false,\n resetPasswordAllowed: false,\n captchaRequired: false,\n firstNameLastName: \"none\" as FieldRequirement,\n adminEmails: [],\n adminUsernames: [],\n defaultRoles: [\"user\"],\n passwordPolicy: {\n minLength: 8,\n requireUppercase: true,\n requireLowercase: true,\n requireNumbers: true,\n requireSpecialCharacters: false,\n },\n loginRateLimit: {\n ipMaxAttempts: 15,\n accountMaxAttempts: 5,\n windowMs: 15 * 60 * 1000,\n },\n registrationIpMaxAttempts: 10,\n refreshToken: {\n // expirationIdle: undefined — opt-in\n },\n },\n});\n\nexport type RealmAuthSettings = Static<typeof realmAuthSettingsAtom.schema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\nimport { users } from \"./users.ts\";\n\nexport const sessions = $entity({\n name: \"sessions\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n refreshToken: t.uuid(),\n userId: db.ref(t.uuid(), () => users.cols.id),\n /**\n * OAuth client this session was minted for, when it was created via the\n * OAuth 2.1 authorization flow — the `client_id` of an `oauth_clients`\n * row. Null for first-party logins. Deliberately NOT a DB-level foreign\n * key: `sessions` is a core entity and must not depend on the optional\n * OAuth module's table; the join to `oauth_clients` is done at query time.\n */\n clientId: t.optional(t.text({ maxLength: 64 })),\n expiresAt: t.datetime(),\n /**\n * Last time the session was used to refresh an access token.\n * Used by realm `refreshToken.expirationIdle` to invalidate idle sessions.\n * `null` on existing rows pre-migration — falls back to `createdAt`.\n */\n lastUsedAt: t.optional(t.datetime()),\n ip: t.optional(t.text()),\n /**\n * ISO 3166-1 alpha-2 country code derived from the request geo headers\n * (`cf-ipcountry` on Cloudflare, CDN equivalents elsewhere) at login time.\n * `null` on pre-migration rows and where geo isn't available.\n */\n country: t.optional(t.text({ maxLength: 2 })),\n userAgent: t.optional(\n t.object({\n os: t.text(),\n browser: t.text(),\n device: t.enum([\"MOBILE\", \"DESKTOP\", \"TABLET\"]),\n }),\n ),\n }),\n indexes: [\"userId\", \"expiresAt\", { column: \"refreshToken\", unique: true }],\n});\n\nexport type SessionEntity = Static<typeof sessions.schema>;\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport type { ParameterPrimitive } from \"alepha/api/parameters\";\nimport { $repository, type Repository } from \"alepha/orm\";\nimport {\n type RealmAuthSettings,\n realmAuthSettingsAtom,\n} from \"../atoms/realmAuthSettingsAtom.ts\";\nimport { identities } from \"../entities/identities.ts\";\nimport { sessions } from \"../entities/sessions.ts\";\nimport { DEFAULT_USER_REALM_NAME, users } from \"../entities/users.ts\";\nimport type { RealmFeatures, RealmOptions } from \"../primitives/$realm.ts\";\n\nexport interface RealmRepositories {\n identities: Repository<typeof identities.schema>;\n sessions: Repository<typeof sessions.schema>;\n users: Repository<typeof users.schema>;\n}\n\nexport interface Realm {\n name: string;\n repositories: RealmRepositories;\n settings: RealmAuthSettings;\n features: RealmFeatures;\n /**\n * Federated (broker) login config surfaced to the login UI, when the realm\n * declares `identities.federated`. The broker public key is intentionally\n * omitted — only the broker URL + provider list are client-facing.\n */\n federated?: {\n brokerUrl: string;\n providers: Array<\"google\" | \"apple\">;\n };\n settingsParameter?: ParameterPrimitive<typeof realmAuthSettingsAtom.schema>;\n getSettings(): Promise<RealmAuthSettings>;\n}\n\nexport class RealmProvider {\n protected readonly alepha = $inject(Alepha);\n // Default repositories using $repository() for eager initialization\n protected readonly defaultIdentities = $repository(identities);\n protected readonly defaultSessions = $repository(sessions);\n protected readonly defaultUsers = $repository(users);\n\n protected realms = new Map<string, Realm>();\n\n public register(realmName: string, realmOptions: RealmOptions = {}) {\n if (realmName.includes(\".\")) {\n throw new AlephaError(\n `Realm name \"${realmName}\" must not contain dots — dots are reserved for parameter tree paths`,\n );\n }\n\n // Merge features with defaults\n const features: RealmFeatures = {\n jobs: false,\n notifications: false,\n apiKeys: false,\n parameters: false,\n avatars: false,\n audits: false,\n ...realmOptions.features,\n };\n\n const realm: Realm = {\n name: realmName,\n repositories: {\n identities: realmOptions.entities?.identities ?? this.defaultIdentities,\n sessions: realmOptions.entities?.sessions ?? this.defaultSessions,\n users: realmOptions.entities?.users ?? this.defaultUsers,\n },\n // TODO: Remove deep merge when alepha supports it natively\n settings: {\n ...realmAuthSettingsAtom.options.default,\n ...realmOptions.settings,\n passwordPolicy: {\n ...realmAuthSettingsAtom.options.default.passwordPolicy,\n ...realmOptions.settings?.passwordPolicy,\n },\n loginRateLimit: {\n ...realmAuthSettingsAtom.options.default.loginRateLimit,\n ...realmOptions.settings?.loginRateLimit,\n },\n refreshToken: {\n ...realmAuthSettingsAtom.options.default.refreshToken,\n ...realmOptions.settings?.refreshToken,\n },\n },\n features,\n federated: realmOptions.identities?.federated\n ? {\n brokerUrl: realmOptions.identities.federated.brokerUrl,\n providers: realmOptions.identities.federated.providers,\n }\n : undefined,\n getSettings: async function () {\n if (this.settingsParameter) {\n return await this.settingsParameter.get();\n }\n return this.settings;\n },\n };\n this.realms.set(realmName, realm);\n return this.getRealm(realmName);\n }\n\n /**\n * Gets a registered realm by name, auto-creating default if needed.\n */\n public getRealm(realmName = DEFAULT_USER_REALM_NAME): Realm {\n let realm = this.realms.get(realmName);\n\n if (!realm) {\n // Auto-register default realm for backward compatibility\n const realms = Array.from(this.realms.values());\n const firstRealm = realms[0];\n if (realmName === DEFAULT_USER_REALM_NAME && firstRealm) {\n realm = firstRealm;\n } else if (this.alepha.isTest()) {\n realm = this.register(realmName); // Auto-create default realm in tests\n } else {\n throw new AlephaError(\n `Missing realm '${realmName}', please declare $realm in your application.`,\n );\n }\n }\n\n return realm;\n }\n\n public identityRepository(\n realmName = DEFAULT_USER_REALM_NAME,\n ): Repository<typeof identities.schema> {\n return this.getRealm(realmName).repositories.identities;\n }\n\n public sessionRepository(\n realmName = DEFAULT_USER_REALM_NAME,\n ): Repository<typeof sessions.schema> {\n return this.getRealm(realmName).repositories.sessions;\n }\n\n public userRepository(\n realmName = DEFAULT_USER_REALM_NAME,\n ): Repository<typeof users.schema> {\n return this.getRealm(realmName).repositories.users;\n }\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { Page } from \"alepha/orm\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport type { IdentityEntity } from \"../entities/identities.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { IdentityQuery } from \"../schemas/identityQuerySchema.ts\";\n\nexport class IdentityService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly realmProvider = $inject(RealmProvider);\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n public identities(userRealmName?: string) {\n return this.realmProvider.identityRepository(userRealmName);\n }\n\n /**\n * Find identities with pagination and filtering.\n */\n public async findIdentities(\n q: IdentityQuery = {},\n userRealmName?: string,\n ): Promise<Page<IdentityEntity>> {\n this.log.trace(\"Finding identities\", { query: q, userRealmName });\n q.sort ??= \"-createdAt\";\n\n const where = this.identities(userRealmName).createQueryWhere();\n\n if (q.userId) {\n where.userId = { eq: q.userId };\n }\n\n if (q.provider) {\n where.provider = { like: q.provider };\n }\n\n const result = await this.identities(userRealmName).paginate(\n q,\n { where },\n { count: true },\n );\n\n this.log.debug(\"Identities found\", {\n count: result.content.length,\n total: result.page.totalElements,\n });\n\n return result;\n }\n\n /**\n * Get an identity by ID.\n */\n public async getIdentityById(\n id: string,\n userRealmName?: string,\n ): Promise<IdentityEntity> {\n this.log.trace(\"Getting identity by ID\", { id, userRealmName });\n const identity = await this.identities(userRealmName).getById(id);\n this.log.debug(\"Identity retrieved\", {\n id,\n provider: identity.provider,\n userId: identity.userId,\n });\n return identity;\n }\n\n /**\n * Delete an identity by ID.\n */\n public async deleteIdentity(\n id: string,\n userRealmName?: string,\n ): Promise<void> {\n this.log.trace(\"Deleting identity\", { id, userRealmName });\n\n // Verify identity exists\n const identity = await this.getIdentityById(id, userRealmName);\n\n await this.identities(userRealmName).deleteById(id);\n this.log.info(\"Identity deleted\", {\n id,\n provider: identity.provider,\n userId: identity.userId,\n });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n\n await this.userAudits(userRealmName)?.user.log(\"update\", {\n resourceType: \"user\",\n userRealm: realm.name,\n resourceId: identity.userId,\n description: `Identity provider disconnected: ${identity.provider}`,\n metadata: {\n identityId: id,\n provider: identity.provider,\n userId: identity.userId,\n },\n });\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { identityQuerySchema } from \"../schemas/identityQuerySchema.ts\";\nimport { identityResourceSchema } from \"../schemas/identityResourceSchema.ts\";\nimport { IdentityService } from \"../services/IdentityService.ts\";\n\nexport class AdminIdentityController {\n protected readonly url = \"/identities\";\n protected readonly group = \"admin:identities\";\n protected readonly identityService = $inject(IdentityService);\n\n /**\n * Find identities with pagination and filtering.\n */\n public readonly findIdentities = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:identity:read\"] })],\n description: \"Find identities with pagination and filtering\",\n schema: {\n query: t.extend(identityQuerySchema, {\n userRealmName: t.optional(t.string()),\n }),\n response: t.page(identityResourceSchema),\n },\n handler: ({ query }) => {\n const { userRealmName, ...q } = query;\n return this.identityService.findIdentities(q, userRealmName);\n },\n });\n\n /**\n * Get an identity by ID.\n */\n public readonly getIdentity = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:identity:read\"] })],\n description: \"Get an identity by ID\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: identityResourceSchema,\n },\n handler: ({ params, query }) =>\n this.identityService.getIdentityById(params.id, query.userRealmName),\n });\n\n /**\n * Delete an identity.\n */\n public readonly deleteIdentity = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:identity:delete\"] })],\n description: \"Delete an identity\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: okSchema,\n },\n handler: async ({ params, query }) => {\n await this.identityService.deleteIdentity(params.id, query.userRealmName);\n return { ok: true, id: params.id };\n },\n });\n}\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const sessionQuerySchema = t.extend(pageQuerySchema, {\n userId: t.optional(t.uuid()),\n});\n\nexport type SessionQuery = Static<typeof sessionQuerySchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Slim view of the session's owner — embedded by the admin listing so the\n * UI can render a human-readable identifier instead of just a UUID. Comes\n * back via a left join, so it's optional (a session whose user was deleted\n * still returns; `user` is undefined).\n */\nexport const sessionUserSummarySchema = t.object({\n id: t.uuid(),\n email: t.optional(t.string({ format: \"email\" })),\n username: t.optional(t.shortText({ minLength: 3, maxLength: 30 })),\n firstName: t.optional(t.string()),\n lastName: t.optional(t.string()),\n});\n\nexport const sessionResourceSchema = t.object({\n id: t.uuid(),\n version: t.number(),\n createdAt: t.datetime(),\n updatedAt: t.datetime(),\n refreshToken: t.uuid(),\n userId: t.uuid(),\n expiresAt: t.datetime(),\n ip: t.optional(t.string()),\n country: t.optional(t.string()),\n userAgent: t.optional(\n t.object({\n os: t.string(),\n browser: t.string(),\n device: t.enum([\"MOBILE\", \"DESKTOP\", \"TABLET\"]),\n }),\n ),\n user: t.optional(sessionUserSummarySchema),\n});\n\nexport type SessionResource = Static<typeof sessionResourceSchema>;\n","import { $inject } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { Page } from \"alepha/orm\";\nimport type { SessionEntity } from \"../entities/sessions.ts\";\nimport { users } from \"../entities/users.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { SessionQuery } from \"../schemas/sessionQuerySchema.ts\";\n\n/**\n * Relation map embedding a slim user summary on every session row, so the\n * admin UI can render `user.email`/`user.username` instead of a bare UUID.\n * Left-join (default) so sessions whose owner was deleted still come back\n * with `user: undefined`.\n */\nconst withUser = {\n user: {\n join: users,\n on: [\"userId\", users.cols.id] as [\"userId\", { name: string }],\n },\n};\n\nexport class SessionCrudService {\n protected readonly log = $logger();\n protected readonly realmProvider = $inject(RealmProvider);\n\n public sessions(userRealmName?: string) {\n return this.realmProvider.sessionRepository(userRealmName);\n }\n\n /**\n * Find sessions with pagination and filtering.\n */\n public async findSessions(\n q: SessionQuery = {},\n userRealmName?: string,\n ): Promise<Page<SessionEntity>> {\n this.log.trace(\"Finding sessions\", { query: q, userRealmName });\n q.sort ??= \"-createdAt\";\n\n const where = this.sessions(userRealmName).createQueryWhere();\n\n if (q.userId) {\n where.userId = { eq: q.userId };\n }\n\n const result = await this.sessions(userRealmName).paginate(\n q,\n { where, with: withUser },\n { count: true },\n );\n\n this.log.debug(\"Sessions found\", {\n count: result.content.length,\n total: result.page.totalElements,\n });\n\n return result;\n }\n\n /**\n * Get a session by ID.\n */\n public async getSessionById(\n id: string,\n userRealmName?: string,\n ): Promise<SessionEntity> {\n this.log.trace(\"Getting session by ID\", { id, userRealmName });\n const session = await this.sessions(userRealmName).getOne({\n where: { id: { eq: id } },\n with: withUser,\n });\n this.log.debug(\"Session retrieved\", { id, userId: session.userId });\n return session;\n }\n\n /**\n * Delete a session by ID.\n */\n public async deleteSession(\n id: string,\n userRealmName?: string,\n ): Promise<void> {\n this.log.trace(\"Deleting session\", { id, userRealmName });\n\n // Verify session exists\n await this.getSessionById(id, userRealmName);\n\n await this.sessions(userRealmName).deleteById(id);\n this.log.info(\"Session deleted\", { id });\n }\n\n /**\n * Delete many sessions by ID in one repository call.\n */\n public async deleteSessions(\n ids: string[],\n userRealmName?: string,\n ): Promise<string[]> {\n if (ids.length === 0) return [];\n this.log.trace(\"Deleting sessions\", { count: ids.length, userRealmName });\n const deleted = await this.sessions(userRealmName).deleteMany({\n id: { inArray: ids },\n });\n this.log.info(\"Sessions deleted\", { count: deleted.length });\n return deleted.map(String);\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { sessionQuerySchema } from \"../schemas/sessionQuerySchema.ts\";\nimport { sessionResourceSchema } from \"../schemas/sessionResourceSchema.ts\";\nimport { SessionCrudService } from \"../services/SessionCrudService.ts\";\n\nexport class AdminSessionController {\n protected readonly url = \"/sessions\";\n protected readonly group = \"admin:sessions\";\n protected readonly sessionService = $inject(SessionCrudService);\n\n /**\n * Find sessions with pagination and filtering.\n */\n public readonly findSessions = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:session:read\"] })],\n description: \"Find sessions with pagination and filtering\",\n schema: {\n query: t.extend(sessionQuerySchema, {\n userRealmName: t.optional(t.string()),\n }),\n response: t.page(sessionResourceSchema),\n },\n handler: ({ query }) => {\n const { userRealmName, ...q } = query;\n return this.sessionService.findSessions(q, userRealmName);\n },\n });\n\n /**\n * Get a session by ID.\n */\n public readonly getSession = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:session:read\"] })],\n description: \"Get a session by ID\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: sessionResourceSchema,\n },\n handler: ({ params, query }) =>\n this.sessionService.getSessionById(params.id, query.userRealmName),\n });\n\n /**\n * Delete a session.\n */\n public readonly deleteSession = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:session:delete\"] })],\n description: \"Delete a session\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: okSchema,\n },\n handler: async ({ params, query }) => {\n await this.sessionService.deleteSession(params.id, query.userRealmName);\n return { ok: true, id: params.id };\n },\n });\n\n /**\n * Delete many sessions in one repository call.\n */\n public readonly deleteSessions = $action({\n method: \"POST\",\n path: `${this.url}/delete`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:session:delete\"] })],\n description: \"Delete many sessions\",\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n ids: t.array(t.uuid(), { minItems: 1, maxItems: 1000 }),\n }),\n response: t.object({\n deleted: t.array(t.string()),\n }),\n },\n handler: async ({ body, query }) => {\n const deleted = await this.sessionService.deleteSessions(\n body.ids,\n query.userRealmName,\n );\n return { deleted };\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { users } from \"../entities/users.ts\";\n\nexport const createUserSchema = t.omit(users.insertSchema, [\"realm\"]);\n\nexport type CreateUser = Static<typeof createUserSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { users } from \"../entities/users.ts\";\n\nexport const updateUserSchema = t.partial(\n t.omit(users.insertSchema, [\"id\", \"version\", \"createdAt\", \"updatedAt\"]),\n);\n\nexport type UpdateUser = Static<typeof updateUserSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const userQuerySchema = t.extend(pageQuerySchema, {\n /**\n * Free-text search applied (case-insensitive) across `email`,\n * `username`, `firstName`, and `lastName`. Matches with a leading and\n * trailing wildcard, so `?search=foo` finds any user whose email,\n * username, or name contains `foo`.\n */\n search: t.optional(t.string()),\n email: t.optional(t.string()),\n enabled: t.optional(t.boolean()),\n emailVerified: t.optional(t.boolean()),\n roles: t.optional(t.array(t.string())),\n});\n\nexport type UserQuery = Static<typeof userQuerySchema>;\n","import type { Static } from \"alepha\";\nimport { users } from \"../entities/users.ts\";\n\nexport const userResourceSchema = users.schema;\n\nexport type UserResource = Static<typeof userResourceSchema>;\n","import { t } from \"alepha\";\nimport { $notification } from \"alepha/api/notifications\";\n\nexport class UserNotifications {\n // Code-based notifications (preferred)\n public readonly passwordReset = $notification({\n category: \"security\",\n description:\n \"Email sent to users with a verification code to reset their password.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Reset your password\",\n body: (it) => `\n\t\t\t<h1>Reset Your Password</h1>\n\t\t\t<p>Hi ${it.email},</p>\n\t\t\t<p>We received a request to reset your password. Use the code below to verify your identity:</p>\n\t\t\t<p style=\"margin: 30px 0; text-align: center;\">\n\t\t\t\t<span style=\"font-size: 32px; font-weight: bold; letter-spacing: 8px; font-family: monospace; background-color: #f5f5f5; padding: 16px 24px; border-radius: 8px; display: inline-block;\">\n\t\t\t\t\t${it.code}\n\t\t\t\t</span>\n\t\t\t</p>\n\t\t\t<p>This code will expire in ${it.expiresInMinutes} minutes.</p>\n\t\t\t<p>If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>\n\t\t\t<p>Best regards,<br>The Team</p>\n\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n code: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n\n public readonly emailVerification = $notification({\n category: \"security\",\n description:\n \"Email sent to users with a verification code to verify their email address.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Verify your email address\",\n body: (it) => `\n\t\t\t<h1>Verify Your Email Address</h1>\n\t\t\t<p>Hi ${it.email},</p>\n\t\t\t<p>Thanks for signing up! Use the code below to verify your email address:</p>\n\t\t\t<p style=\"margin: 30px 0; text-align: center;\">\n\t\t\t\t<span style=\"font-size: 32px; font-weight: bold; letter-spacing: 8px; font-family: monospace; background-color: #f5f5f5; padding: 16px 24px; border-radius: 8px; display: inline-block;\">\n\t\t\t\t\t${it.code}\n\t\t\t\t</span>\n\t\t\t</p>\n\t\t\t<p>This code will expire in ${it.expiresInMinutes} minutes.</p>\n\t\t\t<p>If you did not create an account, please ignore this email.</p>\n\t\t\t<p>Best regards,<br>The Team</p>\n\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n code: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n\n public readonly phoneVerification = $notification({\n category: \"security\",\n description:\n \"SMS sent to users with a verification code to verify their phone number.\",\n critical: true,\n sensitive: true,\n sms: {\n message: (it) =>\n `Your verification code is: ${it.code}. This code expires in ${it.expiresInMinutes} minutes.`,\n },\n schema: t.object({\n phoneNumber: t.string(),\n code: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n\n // Link-based notifications (alternative)\n public readonly passwordResetLink = $notification({\n category: \"security\",\n description: \"Email sent to users with a link to reset their password.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Reset your password\",\n body: (it) => `\n\t\t\t<h1>Reset Your Password</h1>\n\t\t\t<p>Hi ${it.email},</p>\n\t\t\t<p>We received a request to reset your password. Click the link below to create a new password:</p>\n\t\t\t<p style=\"margin: 30px 0;\">\n\t\t\t\t<a href=\"${it.resetUrl}\" style=\"background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;\">\n\t\t\t\t\tReset Password\n\t\t\t\t</a>\n\t\t\t</p>\n\t\t\t<p>Or copy and paste this link into your browser:</p>\n\t\t\t<p style=\"word-break: break-all; color: #666;\">${it.resetUrl}</p>\n\t\t\t<p>This link will expire in ${it.expiresInMinutes} minutes.</p>\n\t\t\t<p>If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>\n\t\t\t<p>Best regards,<br>The Team</p>\n\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n resetUrl: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n\n public readonly accountLockout = $notification({\n category: \"security\",\n description:\n \"Email sent to users when their account is temporarily locked due to too many failed login attempts.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Account temporarily locked\",\n body: (it) => `\n\t\t\t\t<h1>Account Temporarily Locked</h1>\n\t\t\t\t<p>Hi ${it.email},</p>\n\t\t\t\t<p>Your account has been temporarily locked due to too many failed login attempts.</p>\n\t\t\t\t<p>If this was you, please wait ${it.lockoutMinutes} minutes before trying again. If you've forgotten your password, you can reset it using the password reset feature.</p>\n\t\t\t\t<p>If this wasn't you, someone may be trying to access your account. We recommend changing your password as soon as possible.</p>\n\t\t\t\t<p>Best regards,<br>The Team</p>\n\t\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n lockoutMinutes: t.number(),\n }),\n });\n\n public readonly emailVerificationLink = $notification({\n category: \"security\",\n description:\n \"Email sent to users with a link to verify their email address.\",\n critical: true,\n sensitive: true,\n email: {\n subject: \"Verify your email address\",\n body: (it) => `\n\t\t\t<h1>Verify Your Email Address</h1>\n\t\t\t<p>Hi ${it.email},</p>\n\t\t\t<p>Thanks for signing up! Click the button below to verify your email address:</p>\n\t\t\t<p style=\"margin: 30px 0;\">\n\t\t\t\t<a href=\"${it.verifyUrl}\" style=\"background-color: #28a745; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;\">\n\t\t\t\t\tVerify Email\n\t\t\t\t</a>\n\t\t\t</p>\n\t\t\t<p>Or copy and paste this link into your browser:</p>\n\t\t\t<p style=\"word-break: break-all; color: #666;\">${it.verifyUrl}</p>\n\t\t\t<p>This link will expire in ${it.expiresInMinutes} minutes.</p>\n\t\t\t<p>If you did not create an account, please ignore this email.</p>\n\t\t\t<p>Best regards,<br>The Team</p>\n\t\t`,\n },\n schema: t.object({\n email: t.string({ format: \"email\" }),\n verifyUrl: t.string(),\n expiresInMinutes: t.number(),\n }),\n });\n}\n","import { $inject, Alepha } from \"alepha\";\nimport type { VerificationController } from \"alepha/api/verifications\";\nimport { $logger } from \"alepha/logger\";\nimport type { Page } from \"alepha/orm\";\nimport { CryptoProvider } from \"alepha/security\";\nimport { BadRequestError, ConflictError } from \"alepha/server\";\nimport { $client } from \"alepha/server/links\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport type { UserEntity } from \"../entities/users.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { CreateUser } from \"../schemas/createUserSchema.ts\";\nimport type { UpdateUser } from \"../schemas/updateUserSchema.ts\";\nimport type { UserQuery } from \"../schemas/userQuerySchema.ts\";\n\nexport class UserService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly verificationController = $client<VerificationController>();\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly cryptoProvider = $inject(CryptoProvider);\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n protected userNotifications(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.notifications) {\n return this.alepha.inject(UserNotifications);\n }\n return undefined;\n }\n\n public users(userRealmName?: string) {\n return this.realmProvider.userRepository(userRealmName);\n }\n\n /**\n * Request email verification for a user.\n * @param email - The email address to verify.\n * @param userRealmName - Optional realm name.\n * @param method - The verification method: \"code\" (default) or \"link\".\n * @param verifyUrl - Base URL for verification link (required when method is \"link\").\n */\n public async requestEmailVerification(\n email: string,\n userRealmName?: string,\n method: \"code\" | \"link\" = \"code\",\n ): Promise<boolean> {\n this.log.trace(\"Requesting email verification\", {\n email,\n userRealmName,\n method,\n });\n\n const user = await this.users(userRealmName).findOne({\n where: { email: { eq: email } },\n });\n\n if (!user) {\n this.log.debug(\"Email verification requested for non-existent user\", {\n email,\n });\n return true;\n }\n\n if (user.emailVerified) {\n this.log.debug(\"Email verification requested for already verified user\", {\n email,\n userId: user.id,\n });\n return true;\n }\n\n try {\n const verification =\n await this.verificationController.requestVerificationCode({\n params: { type: method },\n body: { target: email },\n });\n\n if (method === \"link\") {\n // Build verification URL from realm settings (server-controlled, not user input)\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n const baseUrl = realmSettings.verifyEmailUrl ?? \"/verify-email\";\n const url = new URL(baseUrl, \"http://localhost\");\n url.searchParams.set(\"email\", email);\n url.searchParams.set(\"token\", verification.token);\n const fullVerifyUrl = realmSettings.verifyEmailUrl\n ? `${baseUrl}${url.search}`\n : url.pathname + url.search;\n\n await this.userNotifications(userRealmName)?.emailVerificationLink.push(\n {\n contact: email,\n variables: {\n email,\n verifyUrl: fullVerifyUrl,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n },\n );\n\n this.log.debug(\"Email verification link sent\", {\n email,\n userId: user.id,\n });\n } else {\n await this.userNotifications(userRealmName)?.emailVerification.push({\n contact: email,\n variables: {\n email,\n code: verification.token,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n });\n\n this.log.debug(\"Email verification code sent\", {\n email,\n userId: user.id,\n });\n }\n } catch (error) {\n // Silent fail for security\n this.log.warn(\"Failed to send email verification\", { email, error });\n }\n\n return true;\n }\n\n /**\n * Verify a user's email using a valid verification token.\n * Supports both code (6-digit) and link (UUID) verification tokens.\n */\n public async verifyEmail(\n email: string,\n token: string,\n userRealmName?: string,\n ): Promise<void> {\n this.log.trace(\"Verifying email\", { email, userRealmName });\n\n // Detect verification type based on token format\n // Codes are 6-digit numbers, links are UUIDs\n const isCode = /^\\d{6}$/.test(token);\n const type = isCode ? \"code\" : \"link\";\n\n const result = await this.verificationController\n .validateVerificationCode({\n params: { type },\n body: { target: email, token },\n })\n .catch(() => {\n this.log.warn(\"Invalid email verification token\", { email, type });\n throw new BadRequestError(\"Invalid or expired verification token\");\n });\n\n if (result.alreadyVerified) {\n this.log.warn(\"Email verification token already used\", { email });\n throw new BadRequestError(\"Invalid or expired verification token\");\n }\n\n const user = await this.users(userRealmName).getOne({\n where: { email: { eq: email } },\n });\n\n await this.users(userRealmName).updateById(user.id, {\n emailVerified: true,\n });\n\n this.log.info(\"Email verified\", { email, userId: user.id, type });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n\n await this.userAudits(userRealmName)?.user.log(\"update\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: email,\n userRealm: realm.name,\n resourceId: user.id,\n description: \"Email verified\",\n metadata: { email, verificationType: type },\n });\n }\n\n /**\n * Check if an email is verified.\n */\n public async isEmailVerified(\n email: string,\n userRealmName?: string,\n ): Promise<boolean> {\n this.log.trace(\"Checking if email is verified\", { email, userRealmName });\n\n const user = await this.users(userRealmName).findOne({\n where: { email: { eq: email } },\n });\n\n return user?.emailVerified ?? false;\n }\n\n /**\n * Find users with pagination and filtering.\n */\n public async findUsers(\n q: UserQuery = {},\n userRealmName?: string,\n ): Promise<Page<UserEntity>> {\n this.log.trace(\"Finding users\", { query: q, userRealmName });\n q.sort ??= \"-createdAt\";\n\n const where = this.users(userRealmName).createQueryWhere();\n\n if (q.search) {\n const pattern = `%${q.search}%`;\n where.or = [\n { email: { ilike: pattern } },\n { username: { ilike: pattern } },\n { firstName: { ilike: pattern } },\n { lastName: { ilike: pattern } },\n ];\n }\n\n if (q.email) {\n where.email = { like: q.email };\n }\n\n if (q.enabled !== undefined) {\n where.enabled = { eq: q.enabled };\n }\n\n if (q.emailVerified !== undefined) {\n where.emailVerified = { eq: q.emailVerified };\n }\n\n if (q.roles) {\n where.roles = { arrayContains: q.roles };\n }\n\n const result = await this.users(userRealmName).paginate(\n q,\n { where },\n { count: true },\n );\n\n this.log.debug(\"Users found\", {\n count: result.content.length,\n total: result.page.totalElements,\n });\n\n return result;\n }\n\n /**\n * Get a user by ID.\n */\n public async getUserById(\n id: string,\n userRealmName?: string,\n ): Promise<UserEntity> {\n this.log.trace(\"Getting user by ID\", { id, userRealmName });\n return await this.users(userRealmName).getById(id);\n }\n\n /**\n * Create a new user.\n */\n public async createUser(\n data: CreateUser,\n userRealmName?: string,\n ): Promise<UserEntity> {\n this.log.trace(\"Creating user\", {\n username: data.username,\n email: data.email,\n userRealmName,\n });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n\n // Check for existing user based on provided unique fields (scoped to realm)\n if (data.username) {\n const existingUser = await this.users(userRealmName).findOne({\n where: { realm: realm.name, username: { ilike: data.username } },\n });\n\n if (existingUser) {\n this.log.debug(\"Username already taken\", { username: data.username });\n throw new BadRequestError(\"User with this username already exists\");\n }\n }\n\n if (data.email) {\n const existingUser = await this.users(userRealmName).findOne({\n where: { realm: realm.name, email: { eq: data.email } },\n });\n\n if (existingUser) {\n this.log.debug(\"Email already taken\", { email: data.email });\n throw new BadRequestError(\"User with this email already exists\");\n }\n }\n\n if (data.phoneNumber) {\n const existingUser = await this.users(userRealmName).findOne({\n where: { realm: realm.name, phoneNumber: { eq: data.phoneNumber } },\n });\n\n if (existingUser) {\n this.log.debug(\"Phone number already taken\", {\n phoneNumber: data.phoneNumber,\n });\n throw new BadRequestError(\"User with this phone number already exists\");\n }\n }\n\n const user = await this.users(userRealmName).create({\n ...data,\n roles: data.roles ?? realmSettings.defaultRoles,\n realm: realm.name,\n });\n\n this.log.info(\"User created\", {\n userId: user.id,\n username: user.username,\n email: user.email,\n });\n\n await this.userAudits(userRealmName)?.user.log(\"create\", {\n resourceType: \"user\",\n userRealm: realm.name,\n resourceId: user.id,\n description: \"User created\",\n metadata: {\n username: user.username,\n email: user.email,\n roles: user.roles,\n },\n });\n\n return user;\n }\n\n /**\n * Update an existing user.\n */\n public async updateUser(\n id: string,\n data: UpdateUser,\n userRealmName?: string,\n ): Promise<UserEntity> {\n this.log.trace(\"Updating user\", { id, userRealmName });\n const before = await this.getUserById(id, userRealmName);\n const realm = this.realmProvider.getRealm(userRealmName);\n const users = this.users(userRealmName);\n\n // Conflict checks — surface a friendly 409 instead of letting the\n // unique constraint blow up the SQL driver with a generic error.\n if (\n data.username !== undefined &&\n data.username !== null &&\n data.username !== before.username\n ) {\n const existing = await users.findOne({\n where: { realm: realm.name, username: { ilike: data.username } },\n });\n if (existing && existing.id !== id) {\n throw new ConflictError(\"User with this username already exists\");\n }\n }\n if (\n data.email !== undefined &&\n data.email !== null &&\n data.email !== before.email\n ) {\n const existing = await users.findOne({\n where: { realm: realm.name, email: { eq: data.email } },\n });\n if (existing && existing.id !== id) {\n throw new ConflictError(\"User with this email already exists\");\n }\n // Changing the email invalidates the verified flag — never trust\n // the client-passed value when email changes.\n data.emailVerified = false;\n }\n\n const user = await users.updateById(id, data);\n this.log.debug(\"User updated\", { userId: id });\n\n // Build changes object showing what was updated\n const changes: Record<string, { from: unknown; to: unknown }> = {};\n for (const key of Object.keys(data) as (keyof UpdateUser)[]) {\n if (data[key] !== undefined && before[key] !== data[key]) {\n changes[key] = { from: before[key], to: data[key] };\n }\n }\n\n // Detect role changes for special handling\n const isRoleChange =\n data.roles !== undefined &&\n JSON.stringify(before.roles) !== JSON.stringify(data.roles);\n\n await this.userAudits(userRealmName)?.user.log(\n isRoleChange ? \"role_change\" : \"update\",\n {\n resourceType: \"user\",\n userRealm: realm.name,\n resourceId: user.id,\n description: isRoleChange\n ? \"User roles changed\"\n : `User updated: ${Object.keys(changes).join(\", \")}`,\n metadata: { changes },\n },\n );\n\n return user;\n }\n\n /**\n * Set (or reset) a user's password. Upserts a \"credentials\" identity\n * with the new hash. Used by admin password-set flows; does NOT\n * verify any old password or token — the caller is responsible for\n * authorization.\n */\n public async setPassword(\n id: string,\n newPassword: string,\n userRealmName?: string,\n ): Promise<void> {\n this.log.trace(\"Setting password\", { id, userRealmName });\n const user = await this.getUserById(id, userRealmName);\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const settings = await realm.getSettings();\n if (settings.passwordPolicy) {\n const policy = settings.passwordPolicy;\n if (policy.minLength && newPassword.length < policy.minLength) {\n throw new BadRequestError(\n `Password must be at least ${policy.minLength} characters`,\n );\n }\n }\n\n const hash = await this.cryptoProvider.hashPassword(newPassword);\n const identities = this.realmProvider.identityRepository(userRealmName);\n const existing = await identities.findOne({\n where: { userId: { eq: id }, provider: { eq: \"credentials\" } },\n });\n\n if (existing) {\n await identities.updateById(existing.id, { password: hash });\n } else {\n await identities.create({\n userId: id,\n provider: \"credentials\",\n password: hash,\n });\n }\n\n await this.userAudits(userRealmName)?.user.log(\"password_change\", {\n resourceType: \"user\",\n userId: id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: id,\n severity: \"warning\",\n description: \"Password set by admin\",\n });\n }\n\n /**\n * Delete a user by ID.\n */\n public async deleteUser(id: string, userRealmName?: string): Promise<void> {\n this.log.trace(\"Deleting user\", { id, userRealmName });\n const user = await this.getUserById(id, userRealmName);\n\n // Clean up related sessions and identities before deleting the user\n await this.realmProvider\n .sessionRepository(userRealmName)\n .deleteMany({ userId: { eq: id } });\n await this.realmProvider\n .identityRepository(userRealmName)\n .deleteMany({ userId: { eq: id } });\n\n await this.users(userRealmName).deleteById(id);\n this.log.info(\"User deleted\", { userId: id });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n\n await this.userAudits(userRealmName)?.user.log(\"delete\", {\n resourceType: \"user\",\n userRealm: realm.name,\n resourceId: id,\n severity: \"warning\",\n description: \"User deleted\",\n metadata: {\n username: user.username,\n email: user.email,\n },\n });\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure, SecurityProvider } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { createUserSchema } from \"../schemas/createUserSchema.ts\";\nimport { updateUserSchema } from \"../schemas/updateUserSchema.ts\";\nimport { userQuerySchema } from \"../schemas/userQuerySchema.ts\";\nimport { userResourceSchema } from \"../schemas/userResourceSchema.ts\";\nimport { UserService } from \"../services/UserService.ts\";\n\nexport class AdminUserController {\n protected readonly url = \"/users\";\n protected readonly group = \"admin:users\";\n protected readonly userService = $inject(UserService);\n protected readonly securityProvider = $inject(SecurityProvider);\n\n /**\n * List roles available in a realm. Used by the admin UI to render the\n * role picker and grey out defaults (which cannot be removed).\n */\n public readonly findRoles = $action({\n path: \"/metadata/roles\",\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:read\"] })],\n description: \"List roles available in a realm\",\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: t.array(\n t.object({\n name: t.string(),\n default: t.optional(t.boolean()),\n description: t.optional(t.string()),\n }),\n ),\n },\n handler: ({ query }) => {\n const roles = this.securityProvider.getRoles(query.userRealmName);\n return roles.map((r) => ({\n name: r.name,\n default: r.default,\n description: r.description,\n }));\n },\n });\n\n /**\n * Find users with pagination and filtering.\n */\n public readonly findUsers = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:read\"] })],\n description: \"Find users with pagination and filtering\",\n schema: {\n query: t.extend(userQuerySchema, {\n userRealmName: t.optional(t.string()),\n }),\n response: t.page(userResourceSchema),\n },\n handler: ({ query }) => {\n const { userRealmName, ...q } = query;\n return this.userService.findUsers(q, userRealmName);\n },\n });\n\n /**\n * Get a user by ID.\n */\n public readonly getUser = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:read\"] })],\n description: \"Get a user by ID\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: userResourceSchema,\n },\n handler: ({ params, query }) =>\n this.userService.getUserById(params.id, query.userRealmName),\n });\n\n /**\n * Create a new user.\n */\n public readonly createUser = $action({\n method: \"POST\",\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:create\"] })],\n description: \"Create a new user\",\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: createUserSchema,\n response: userResourceSchema,\n },\n handler: ({ body, query }) =>\n this.userService.createUser(body, query.userRealmName),\n });\n\n /**\n * Update a user.\n */\n public readonly updateUser = $action({\n method: \"PATCH\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:update\"] })],\n description: \"Update a user\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: updateUserSchema,\n response: userResourceSchema,\n },\n handler: ({ params, body, query }) =>\n this.userService.updateUser(params.id, body, query.userRealmName),\n });\n\n /**\n * Set (or reset) a user's password. Admin-only flow — does NOT\n * require knowing the previous password. Hash is stored as a\n * \"credentials\" identity for the user (upsert).\n */\n public readonly setUserPassword = $action({\n method: \"POST\",\n path: `${this.url}/:id/password`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:update\"] })],\n description: \"Set a user's password\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n password: t.string({ minLength: 1 }),\n }),\n response: okSchema,\n },\n handler: async ({ params, body, query }) => {\n await this.userService.setPassword(\n params.id,\n body.password,\n query.userRealmName,\n );\n return { ok: true, id: params.id };\n },\n });\n\n /**\n * Delete a user.\n */\n public readonly deleteUser = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:delete\"] })],\n description: \"Delete a user\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n response: okSchema,\n },\n handler: async ({ params, query }) => {\n await this.userService.deleteUser(params.id, query.userRealmName);\n return { ok: true, id: params.id };\n },\n });\n\n /**\n * Delete many users in one request. Each id is processed sequentially so\n * cascades and side-effects run as if called one-by-one. Errors on a single\n * id surface with that id in the response.\n */\n public readonly deleteUsers = $action({\n method: \"POST\",\n path: `${this.url}/delete`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:user:delete\"] })],\n description: \"Delete many users\",\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n ids: t.array(t.uuid(), { minItems: 1, maxItems: 1000 }),\n }),\n response: t.object({\n deleted: t.array(t.uuid()),\n }),\n },\n handler: async ({ body, query }) => {\n const deleted: string[] = [];\n for (const id of body.ids) {\n await this.userService.deleteUser(id, query.userRealmName);\n deleted.push(id);\n }\n return { deleted };\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { authenticationProviderSchema } from \"alepha/server/auth\";\nimport { realmAuthSettingsAtom } from \"../atoms/realmAuthSettingsAtom.ts\";\n\nexport const realmConfigSchema = t.object({\n settings: realmAuthSettingsAtom.schema,\n realmName: t.string(),\n authenticationMethods: t.array(authenticationProviderSchema),\n captchaSiteKey: t.optional(\n t.string({\n description:\n \"Public site key for the captcha widget (when settings.captchaRequired is true)\",\n }),\n ),\n /**\n * Federated (broker) social login. When present, the login UI renders one\n * button per provider linking to `{brokerUrl}/auth/federated/start`.\n */\n federated: t.optional(\n t.object({\n brokerUrl: t.string({\n description: \"Broker origin that performs the OIDC dance.\",\n }),\n providers: t.array(t.union([t.const(\"google\"), t.const(\"apple\")]), {\n description: \"Federated providers to surface as login buttons.\",\n }),\n }),\n ),\n});\n\nexport type RealmConfig = Static<typeof realmConfigSchema>;\n","import { $inject, t } from \"alepha\";\nimport { CaptchaProvider } from \"alepha/captcha\";\nimport { $action } from \"alepha/server\";\nimport { ServerAuthProvider } from \"alepha/server/auth\";\nimport { $etag } from \"alepha/server/etag\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport { realmConfigSchema } from \"../schemas/realmConfigSchema.ts\";\n\n/**\n * Controller for exposing realm configuration.\n * Uses $route instead of $action to keep endpoints hidden from API documentation.\n */\nexport class RealmController {\n protected readonly url = \"/realms\";\n protected readonly group = \"realms\";\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly serverAuthProvider = $inject(ServerAuthProvider);\n protected readonly captchaProvider = $inject(CaptchaProvider);\n\n /**\n * Get realm configuration settings.\n * This endpoint is not exposed in the API documentation.\n */\n public readonly getRealmConfig = $action({\n group: this.group,\n method: \"GET\",\n path: `${this.url}/config`,\n use: [$etag()],\n schema: {\n query: t.object({\n realmName: t.optional(t.string()),\n }),\n response: realmConfigSchema,\n },\n handler: async ({ query }) => {\n const realm = this.realmProvider.getRealm(query.realmName);\n const settings = await realm.getSettings();\n const realmName = realm.name;\n\n const authenticationMethods =\n this.serverAuthProvider.getAuthenticationProviders({\n realmName,\n });\n\n return {\n settings,\n realmName,\n authenticationMethods,\n captchaSiteKey: settings.captchaRequired\n ? this.captchaProvider.getSiteKey()\n : undefined,\n federated: realm.federated,\n };\n },\n });\n\n public readonly checkUsernameAvailability = $action({\n group: this.group,\n path: `${this.url}/check-username`,\n schema: {\n query: t.object({\n realmName: t.optional(t.text()),\n }),\n body: t.object({\n username: t.text(),\n }),\n response: t.object({\n available: t.boolean(),\n }),\n },\n handler: async ({ query, body }) => {\n const realmName = query.realmName;\n const userRepository = this.realmProvider.userRepository(realmName);\n\n const existingUser = await userRepository.findOne({\n where: { username: { eq: body.username } },\n });\n\n return {\n available: !existingUser,\n };\n },\n });\n}\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Request schema for completing a password reset.\n *\n * Requires the intent ID from Phase 1, the verification code,\n * and the new password.\n */\nexport const completePasswordResetRequestSchema = t.object({\n intentId: t.uuid({\n description: \"The intent ID from createPasswordResetIntent\",\n }),\n code: t.string({\n description: \"6-digit verification code sent via email\",\n }),\n newPassword: t.string({\n minLength: 8,\n description: \"New password (minimum 8 characters)\",\n }),\n});\n\nexport type CompletePasswordResetRequest = Static<\n typeof completePasswordResetRequestSchema\n>;\n","import { type Static, t } from \"alepha\";\n\nexport const completeRegistrationRequestSchema = t.object({\n intentId: t.uuid({\n description: \"The registration intent ID from the first phase\",\n }),\n emailCode: t.optional(\n t.string({\n description: \"Email verification code (if email verification required)\",\n }),\n ),\n phoneCode: t.optional(\n t.string({\n description: \"Phone verification code (if phone verification required)\",\n }),\n ),\n captchaToken: t.optional(\n t.string({\n description: \"Captcha token (if captcha required)\",\n }),\n ),\n});\n\nexport type CompleteRegistrationRequest = Static<\n typeof completeRegistrationRequestSchema\n>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Response schema for password reset intent creation.\n *\n * Contains the intent ID needed for Phase 2 completion,\n * along with expiration time.\n */\nexport const passwordResetIntentResponseSchema = t.object({\n intentId: t.uuid({\n description: \"Unique identifier for this password reset intent\",\n }),\n expiresAt: t.datetime({\n description: \"ISO timestamp when this intent expires\",\n }),\n});\n\nexport type PasswordResetIntentResponse = Static<\n typeof passwordResetIntentResponseSchema\n>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Schema for user registration query parameters.\n * Allows specifying a custom user realm.\n */\nexport const registerQuerySchema = t.object({\n userRealmName: t.optional(\n t.text({\n description:\n \"The user realm to register the user in (defaults to 'default')\",\n }),\n ),\n});\n\nexport type RegisterQuery = Static<typeof registerQuerySchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\n/**\n * Schema for user registration request body.\n * Password is always required, other fields depend on realm settings.\n */\nexport const registerRequestSchema = t.object({\n // Password is always required\n password: t.string({\n minLength: 8,\n description: \"Password for the account\",\n }),\n\n // Identity fields (requirements depend on realm settings)\n username: t.optional(\n t.string({\n minLength: 3,\n description: \"Unique username for the account\",\n }),\n ),\n\n // Optional contact fields\n email: t.optional(\n t.string({\n format: \"email\",\n description: \"User's email address\",\n }),\n ),\n phoneNumber: t.optional(\n t.string({\n description: \"User's phone number\",\n }),\n ),\n\n // Optional user profile fields\n firstName: t.optional(\n t.string({\n description: \"User's first name\",\n }),\n ),\n lastName: t.optional(\n t.string({\n description: \"User's last name\",\n }),\n ),\n picture: t.optional(\n t.string({\n description: \"User's profile picture URL\",\n }),\n ),\n\n // Captcha token — required when the realm has `captchaRequired: true`.\n // Validated at intent creation (before any verification email is sent).\n captchaToken: t.optional(\n t.string({\n description: \"Captcha response token (if captcha is required)\",\n }),\n ),\n});\n\nexport type RegisterRequest = Static<typeof registerRequestSchema>;\n","import { type Static, t } from \"alepha\";\n\nexport const registrationIntentResponseSchema = t.object({\n intentId: t.uuid({\n description: \"Unique identifier for the registration intent\",\n }),\n expectCaptcha: t.boolean({\n description: \"Whether captcha verification is required\",\n }),\n captchaSiteKey: t.optional(\n t.string({\n description:\n \"Public site key the client should render (when expectCaptcha is true)\",\n }),\n ),\n expectEmailVerification: t.boolean({\n description: \"Whether email verification is required\",\n }),\n expectPhoneVerification: t.boolean({\n description: \"Whether phone verification is required\",\n }),\n expiresAt: t.datetime({\n description: \"When the registration intent expires\",\n }),\n});\n\nexport type RegistrationIntentResponse = Static<\n typeof registrationIntentResponseSchema\n>;\n","import { $inject, Alepha } from \"alepha\";\nimport { VerificationService } from \"alepha/api/verifications\";\nimport { $cache } from \"alepha/cache\";\nimport { DatabaseCacheProvider } from \"alepha/cache/database\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { CryptoProvider } from \"alepha/security\";\nimport { BadRequestError, HttpError } from \"alepha/server\";\nimport type { RealmAuthSettings } from \"../atoms/realmAuthSettingsAtom.ts\";\nimport { SessionAudits } from \"../audits/SessionAudits.ts\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { CompletePasswordResetRequest } from \"../schemas/completePasswordResetRequestSchema.ts\";\nimport type { PasswordResetIntentResponse } from \"../schemas/passwordResetIntentResponseSchema.ts\";\n\n/**\n * Intent stored in cache during the password reset flow.\n */\ninterface PasswordResetIntent {\n email: string;\n userId: string;\n identityId: string;\n realmName?: string;\n expiresAt: string;\n}\n\nconst INTENT_TTL_MINUTES = 10;\n\n/**\n * Verification purpose bucket for password-reset codes. Keeping this distinct\n * from the default bucket means a reset request is not rate-limited by an\n * unrelated email-verification code on the same address (e.g. a user who just\n * registered and immediately asks to reset their password).\n */\nconst PASSWORD_RESET_PURPOSE = \"password-reset\";\n\nexport class CredentialService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly cryptoProvider = $inject(CryptoProvider);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly verificationService = $inject(VerificationService);\n protected readonly realmProvider = $inject(RealmProvider);\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n protected sessionAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(SessionAudits);\n }\n return undefined;\n }\n\n protected userNotifications(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.notifications) {\n return this.alepha.inject(UserNotifications);\n }\n return undefined;\n }\n\n protected readonly intentCache = $cache<PasswordResetIntent>({\n // Use the SQL-backed cache so phase-2 reads what phase-1 wrote with\n // strong consistency, and so bare deployments don't need a distributed\n // KV resource just to support password reset.\n provider: DatabaseCacheProvider,\n name: \"api:users:password-reset-intents\",\n ttl: [INTENT_TTL_MINUTES, \"minutes\"],\n });\n\n public users(userRealmName?: string) {\n return this.realmProvider.userRepository(userRealmName);\n }\n\n public sessions(userRealmName?: string) {\n return this.realmProvider.sessionRepository(userRealmName);\n }\n\n public identities(userRealmName?: string) {\n return this.realmProvider.identityRepository(userRealmName);\n }\n\n /**\n * Validate a password against the realm's password policy.\n */\n public validatePasswordPolicy(\n password: string,\n policy: RealmAuthSettings[\"passwordPolicy\"],\n ): void {\n if (password.length < policy.minLength) {\n throw new BadRequestError(\n `Password must be at least ${policy.minLength} characters`,\n );\n }\n if (policy.requireUppercase && !/[A-Z]/.test(password)) {\n throw new BadRequestError(\n \"Password must contain at least one uppercase letter\",\n );\n }\n if (policy.requireLowercase && !/[a-z]/.test(password)) {\n throw new BadRequestError(\n \"Password must contain at least one lowercase letter\",\n );\n }\n if (policy.requireNumbers && !/\\d/.test(password)) {\n throw new BadRequestError(\"Password must contain at least one number\");\n }\n if (policy.requireSpecialCharacters && !/[^a-zA-Z0-9]/.test(password)) {\n throw new BadRequestError(\n \"Password must contain at least one special character\",\n );\n }\n }\n\n /**\n * Phase 1: Create a password reset intent.\n *\n * Validates the email, checks for existing user with credentials,\n * sends verification code, and stores the intent in cache.\n *\n * @param email - User's email address\n * @param userRealmName - Optional realm name\n * @returns Intent response with intentId and expiration (always returns for security)\n */\n public async createPasswordResetIntent(\n email: string,\n userRealmName?: string,\n ): Promise<PasswordResetIntentResponse> {\n this.log.trace(\"Creating password reset intent\", { email, userRealmName });\n\n // Generate intent ID and expiration upfront for consistent response\n const intentId = this.cryptoProvider.randomUUID();\n const expiresAt = this.dateTimeProvider\n .now()\n .add(INTENT_TTL_MINUTES, \"minutes\")\n .toISOString();\n\n // Check if password reset is allowed for this realm\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n if (realmSettings.resetPasswordAllowed === false) {\n this.log.debug(\"Password reset not allowed for realm\", { userRealmName });\n return { intentId, expiresAt };\n }\n\n // Find user by email (silent fail for security)\n const user = await this.users(userRealmName).findOne({\n where: { email: { eq: email } },\n });\n\n if (!user) {\n // Silent fail - don't reveal that email doesn't exist\n this.log.debug(\"Password reset requested for non-existent email\", {\n email,\n });\n return { intentId, expiresAt };\n }\n\n // Find the credentials identity for this user\n const identity = await this.identities(userRealmName).findOne({\n where: {\n userId: { eq: user.id },\n provider: { eq: \"credentials\" },\n },\n });\n\n if (!identity) {\n // User doesn't have credentials identity (maybe OAuth only)\n this.log.debug(\"Password reset requested for user without credentials\", {\n userId: user.id,\n });\n return { intentId, expiresAt };\n }\n\n // Create verification using verification controller\n // This handles: token generation, expiration, rate limiting, cooldown\n try {\n const verification = await this.verificationService.createVerification({\n type: \"code\",\n target: email,\n purpose: PASSWORD_RESET_PURPOSE,\n });\n\n // Send password reset notification with the code\n await this.userNotifications(userRealmName)?.passwordReset.push({\n contact: email,\n variables: {\n email,\n code: verification.token,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n });\n\n // Store intent in cache\n const intent: PasswordResetIntent = {\n email,\n userId: user.id,\n identityId: identity.id,\n realmName: userRealmName,\n expiresAt,\n };\n\n await this.intentCache.set(intentId, intent);\n\n this.log.info(\"Password reset intent created\", {\n intentId,\n userId: user.id,\n email,\n });\n } catch (error) {\n // If rate limit or cooldown hit, still return success for security\n this.log.warn(\"Failed to create password reset verification\", error);\n }\n\n return { intentId, expiresAt };\n }\n\n /**\n * Phase 2: Complete password reset using an intent.\n *\n * Validates the verification code, updates the password,\n * and invalidates all existing sessions.\n *\n * @param body - Request body with intentId, code, and newPassword\n */\n public async completePasswordReset(\n body: CompletePasswordResetRequest,\n ): Promise<void> {\n this.log.trace(\"Completing password reset\", { intentId: body.intentId });\n\n // Fetch intent from cache\n const intent = await this.intentCache.get(body.intentId);\n if (!intent) {\n this.log.warn(\"Invalid or expired password reset intent\", {\n intentId: body.intentId,\n });\n throw new HttpError({\n status: 410,\n message: \"Invalid or expired password reset intent\",\n });\n }\n\n // Validate password against realm policy before consuming the verification code\n const realm = this.realmProvider.getRealm(intent.realmName);\n const realmSettings = await realm.getSettings();\n this.validatePasswordPolicy(body.newPassword, realmSettings.passwordPolicy);\n\n // Verify code using verification service\n const result = await this.verificationService\n .verifyCode(\n {\n type: \"code\",\n target: intent.email,\n purpose: PASSWORD_RESET_PURPOSE,\n },\n body.code,\n )\n .catch(() => {\n this.log.warn(\"Invalid verification code for password reset\", {\n intentId: body.intentId,\n email: intent.email,\n });\n throw new BadRequestError(\"Invalid or expired verification code\");\n });\n\n // If already verified, this is a code reuse attempt\n if (result.alreadyVerified) {\n this.log.warn(\"Verification code reuse attempt\", {\n intentId: body.intentId,\n email: intent.email,\n });\n throw new BadRequestError(\"Verification code has already been used\");\n }\n\n // Hash the new password\n const hashedPassword = await this.cryptoProvider.hashPassword(\n body.newPassword,\n );\n\n // Update the identity with new password\n await this.identities(intent.realmName).updateById(intent.identityId, {\n password: hashedPassword,\n });\n\n // Invalidate all existing sessions for this user\n await this.sessions(intent.realmName).deleteMany({\n userId: { eq: intent.userId },\n });\n\n // Invalidate intent after all operations succeed,\n // so the user can retry if password update or session cleanup fails.\n // The verification code was already consumed (verifiedAt set), so replay is\n // prevented even if the intent is still in cache.\n await this.intentCache.invalidate(body.intentId);\n\n this.log.info(\"Password reset completed\", {\n userId: intent.userId,\n email: intent.email,\n });\n\n // Audit: password reset\n await this.userAudits(intent.realmName)?.user.log(\"update\", {\n resourceType: \"user\",\n userId: intent.userId,\n userEmail: intent.email,\n userRealm: realm.name,\n resourceId: intent.userId,\n description: \"Password reset completed\",\n metadata: { email: intent.email },\n });\n\n // Audit: sessions invalidated (security event)\n await this.sessionAudits(intent.realmName)?.security.log(\n \"sessions_invalidated\",\n {\n userId: intent.userId,\n userEmail: intent.email,\n userRealm: realm.name,\n resourceId: intent.userId,\n severity: \"warning\",\n description: \"All sessions invalidated after password reset\",\n },\n );\n }\n\n // Legacy methods kept for backward compatibility\n\n /**\n * @deprecated Use createPasswordResetIntent instead\n */\n public async requestPasswordReset(\n email: string,\n userRealmName?: string,\n ): Promise<boolean> {\n await this.createPasswordResetIntent(email, userRealmName);\n return true;\n }\n\n /**\n * @deprecated Use completePasswordReset instead\n */\n public async validateResetToken(\n email: string,\n token: string,\n _userRealmName?: string,\n ): Promise<string> {\n // Verify using verification service\n const isValid = await this.verificationService\n .verifyCode(\n { type: \"code\", target: email, purpose: PASSWORD_RESET_PURPOSE },\n token,\n )\n .catch(() => undefined);\n\n if (!isValid?.ok) {\n throw new BadRequestError(\"Invalid or expired reset token\");\n }\n\n return email;\n }\n\n /**\n * @deprecated Use completePasswordReset instead\n */\n public async resetPassword(\n email: string,\n token: string,\n newPassword: string,\n userRealmName?: string,\n ): Promise<void> {\n // Verify token using verification service\n const result = await this.verificationService\n .verifyCode(\n { type: \"code\", target: email, purpose: PASSWORD_RESET_PURPOSE },\n token,\n )\n .catch(() => {\n throw new BadRequestError(\"Invalid or expired reset token\");\n });\n\n // If already verified, this is a token reuse attempt\n if (result.alreadyVerified) {\n throw new BadRequestError(\"Invalid or expired reset token\");\n }\n\n // Validate password against realm policy\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n this.validatePasswordPolicy(newPassword, realmSettings.passwordPolicy);\n\n // Find user and identity\n const user = await this.users(userRealmName).getOne({\n where: { email: { eq: email } },\n });\n\n const identity = await this.identities(userRealmName).getOne({\n where: {\n userId: { eq: user.id },\n provider: { eq: \"credentials\" },\n },\n });\n\n // Hash the new password\n const hashedPassword = await this.cryptoProvider.hashPassword(newPassword);\n\n // Update the identity with new password\n await this.identities(userRealmName).updateById(identity.id, {\n password: hashedPassword,\n });\n\n // Invalidate all existing sessions for this user\n await this.sessions(userRealmName).deleteMany({\n userId: { eq: user.id },\n });\n\n // Audit: password reset (legacy method)\n await this.userAudits(userRealmName)?.user.log(\"update\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: email,\n userRealm: realm.name,\n resourceId: user.id,\n description: \"Password reset completed (legacy)\",\n metadata: { email },\n });\n\n // Audit: sessions invalidated\n await this.sessionAudits(userRealmName)?.security.log(\n \"sessions_invalidated\",\n {\n userId: user.id,\n userEmail: email,\n userRealm: realm.name,\n resourceId: user.id,\n severity: \"warning\",\n description: \"All sessions invalidated after password reset\",\n },\n );\n }\n}\n","import { $inject, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\n\n/**\n * Derive stable, URL-safe usernames from email addresses.\n *\n * Used by the registration flow when `realm.settings.username === \"email\"`,\n * and reusable from any custom user-creation site (e.g. an OAuth-only flow\n * that wants the same handle convention).\n *\n * **Slug rule** — the local-part of the email is kept as-is (gmail\n * `+suffix` retained for predictability). Everything outside `[a-z0-9]` is\n * replaced with `-`, runs of `-` are collapsed, leading/trailing `-` are\n * trimmed, lowercased. If the result is shorter than {@link MIN_LENGTH} it\n * is padded with random alphanumerics. Result is clamped to\n * {@link MAX_LENGTH}.\n *\n * **Collision retry** — `pickAvailable()` checks the realm's `users` table\n * and the realm's `usernameBlocklist`. On hit, it appends `-<4 random>` and\n * retries up to {@link MAX_RETRIES} times. Best-effort against concurrent\n * registrations: the unique index on `(realm, lower(username))` is the\n * authoritative race guard, callers that hit a unique-violation should\n * call `pickAvailable` again with a fresh suffix.\n *\n * @see RegistrationService.createRegistrationIntent\n */\nexport class UsernameSlugger {\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly log = $logger();\n\n /**\n * Floor for derived usernames. Shorter slugs are padded with random\n * alphanumerics. Matches the lower bound of the default `usernameRegExp`.\n */\n static readonly MIN_LENGTH = 3;\n\n /**\n * Ceiling for derived usernames. Matches the upper bound of the default\n * `usernameRegExp` and gives enough headroom for the random suffix added\n * on collisions.\n */\n static readonly MAX_LENGTH = 30;\n\n /**\n * Length of the random suffix appended on collision (e.g. `-3dp6`).\n * 36⁴ ≈ 1.6M variants per base — plenty for a tiny number of retries.\n */\n static readonly SUFFIX_LENGTH = 4;\n\n /**\n * How many times `pickAvailable` retries before giving up.\n */\n static readonly MAX_RETRIES = 5;\n\n /**\n * Alphabet used for the random suffix and for padding short slugs.\n */\n static readonly ALPHABET = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n\n /**\n * Default replacement when the email's local-part contains no\n * `[a-z0-9]` characters at all (rare but possible: `é@example.com`).\n */\n static readonly EMPTY_LOCAL_FALLBACK = \"user\";\n\n /**\n * Sanitize an email into a base username slug.\n *\n * Pure function — no DB access. Always returns a string that satisfies\n * `^[a-z0-9-]{MIN_LENGTH,MAX_LENGTH}$`.\n *\n * @example\n * slug(\"ni.foures+testkv@gmail.com\") // \"ni-foures-testkv\"\n * slug(\"john.doe@example.com\") // \"john-doe\"\n * slug(\"é@example.com\") // \"user-XXX\" (padded)\n */\n public slug(email: string | null | undefined): string {\n const raw = (email ?? \"\").trim();\n const at = raw.indexOf(\"@\");\n const local = at > 0 ? raw.slice(0, at) : raw;\n\n const cleaned = local\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n\n let result = cleaned || UsernameSlugger.EMPTY_LOCAL_FALLBACK;\n\n if (result.length < UsernameSlugger.MIN_LENGTH) {\n const needed = UsernameSlugger.MIN_LENGTH - result.length;\n result += this.randomSuffix(needed);\n }\n\n return this.clamp(result);\n }\n\n /**\n * Find an available username for the realm, starting from `base`.\n *\n * Returns `base` when nothing collides. On a hit (existing row OR\n * blocklisted name) appends `-<4 random>` and tries again, up to\n * {@link MAX_RETRIES} times.\n *\n * The check is best-effort: a concurrent registration may still claim\n * the same value before the caller's INSERT runs, in which case the DB\n * unique index throws and the caller should retry.\n */\n public async pickAvailable(\n realmName: string | undefined,\n base: string,\n ): Promise<string> {\n const blocklist = await this.getBlocklist(realmName);\n const repo = this.realmProvider.userRepository(realmName);\n const realm = this.realmProvider.getRealm(realmName);\n\n const isAvailable = async (candidate: string): Promise<boolean> => {\n if (this.isBlockedAgainst(candidate, blocklist)) {\n return false;\n }\n const existing = await repo.findOne({\n where: {\n realm: { eq: realm.name },\n username: { ilike: candidate },\n },\n });\n return !existing;\n };\n\n if (await isAvailable(base)) {\n return base;\n }\n\n // Reserve room for \"-\" + suffix at the end of the candidate.\n const reserve = 1 + UsernameSlugger.SUFFIX_LENGTH;\n const trimmedBase =\n base.length > UsernameSlugger.MAX_LENGTH - reserve\n ? base.slice(0, UsernameSlugger.MAX_LENGTH - reserve)\n : base;\n\n for (let i = 0; i < UsernameSlugger.MAX_RETRIES; i++) {\n const candidate = `${trimmedBase}-${this.randomSuffix(\n UsernameSlugger.SUFFIX_LENGTH,\n )}`;\n if (await isAvailable(candidate)) {\n return candidate;\n }\n }\n\n throw new AlephaError(\n `Could not find an available username starting from \"${base}\" after ${UsernameSlugger.MAX_RETRIES} attempts.`,\n );\n }\n\n /**\n * Check a name against the realm's `usernameBlocklist`. Case-insensitive.\n */\n public async isBlocked(\n realmName: string | undefined,\n name: string,\n ): Promise<boolean> {\n const blocklist = await this.getBlocklist(realmName);\n return this.isBlockedAgainst(name, blocklist);\n }\n\n // -------------------------------------------------------------------------\n\n protected async getBlocklist(\n realmName: string | undefined,\n ): Promise<Set<string>> {\n const realm = this.realmProvider.getRealm(realmName);\n const settings = await realm.getSettings();\n const list = settings?.usernameBlocklist ?? [];\n return new Set(list.map((b) => b.toLowerCase()));\n }\n\n protected isBlockedAgainst(name: string, blocklist: Set<string>): boolean {\n return blocklist.has(name.toLowerCase());\n }\n\n protected clamp(s: string): string {\n return s.length > UsernameSlugger.MAX_LENGTH\n ? s.slice(0, UsernameSlugger.MAX_LENGTH)\n : s;\n }\n\n protected randomSuffix(length: number): string {\n const chars = UsernameSlugger.ALPHABET;\n let out = \"\";\n for (let i = 0; i < length; i++) {\n out += chars[Math.floor(Math.random() * chars.length)];\n }\n return out;\n }\n}\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport type { VerificationController } from \"alepha/api/verifications\";\nimport { $cache } from \"alepha/cache\";\nimport { DatabaseCacheProvider } from \"alepha/cache/database\";\nimport { CaptchaProvider } from \"alepha/captcha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { CryptoProvider } from \"alepha/security\";\nimport { BadRequestError, ConflictError, HttpError } from \"alepha/server\";\nimport { $client } from \"alepha/server/links\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport type { UserEntity } from \"../entities/users.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport type { CompleteRegistrationRequest } from \"../schemas/completeRegistrationRequestSchema.ts\";\nimport type { RegisterRequest } from \"../schemas/registerRequestSchema.ts\";\nimport type { RegistrationIntentResponse } from \"../schemas/registrationIntentResponseSchema.ts\";\nimport { CredentialService } from \"./CredentialService.ts\";\nimport { UsernameSlugger } from \"./UsernameSlugger.ts\";\n\n/**\n * Intent stored in cache during the registration flow.\n */\ninterface RegistrationIntent {\n data: {\n username?: string;\n email?: string;\n phoneNumber?: string;\n firstName?: string;\n lastName?: string;\n picture?: string;\n passwordHash: string;\n };\n requirements: {\n email: boolean;\n phone: boolean;\n captcha: boolean;\n };\n realmName?: string;\n expiresAt: string;\n}\n\nconst INTENT_TTL_MINUTES = 10;\n\nexport class RegistrationService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly cryptoProvider = $inject(CryptoProvider);\n protected readonly verificationController = $client<VerificationController>();\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly credentialService = $inject(CredentialService);\n protected readonly captchaProvider = $inject(CaptchaProvider);\n protected readonly usernameSlugger = $inject(UsernameSlugger);\n\n protected readonly intentCache = $cache<RegistrationIntent>({\n // Pinned to the SQL-backed cache so:\n // - phase 2 reliably reads the partial-registration payload phase 1 wrote;\n // - the password hash held in the intent never lives in a distributed\n // K/V outside the user's own DB unless they explicitly opt in.\n provider: DatabaseCacheProvider,\n name: \"api:users:registrations\",\n ttl: [INTENT_TTL_MINUTES, \"minutes\"],\n });\n\n protected readonly rateLimitCache = $cache<number>({\n // Use the SQL-backed cache so `incr()` is atomic (`INSERT ... ON CONFLICT\n // DO UPDATE SET count = count + 1`). Cloudflare KV silently coalesces\n // concurrent writes to the same key, which makes the limiter useless\n // against bursts.\n provider: DatabaseCacheProvider,\n name: \"api:users:registration-rate-limit\",\n ttl: [15, \"minutes\"],\n });\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n protected userNotifications(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n\n if (realm.features.notifications) {\n return this.alepha.inject(UserNotifications);\n }\n\n throw new AlephaError(\n `Notifications feature is not enabled for realm \"${realmName}\". Please enable it in the realm settings to use registration notifications.`,\n );\n }\n\n /**\n * Phase 1: Create a registration intent.\n *\n * Validates the registration data, checks for existing users,\n * creates verification sessions, and stores the intent in cache.\n */\n public async createRegistrationIntent(\n body: RegisterRequest,\n userRealmName?: string,\n ): Promise<RegistrationIntentResponse> {\n this.log.trace(\"Creating registration intent\", {\n email: body.email,\n username: body.username,\n userRealmName,\n });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n\n // IP rate limiting — threshold driven by realm settings so dev/e2e can\n // bump it (a single localhost IP otherwise burns through the default 10\n // before the suite finishes).\n const request = this.alepha.store.get(\"alepha.http.request\");\n const ipKey = request?.ip ? `register:ip:${request.ip}` : undefined;\n if (ipKey) {\n const count = (await this.rateLimitCache.get(ipKey)) ?? 0;\n if (count >= realmSettings.registrationIpMaxAttempts) {\n this.log.warn(\"Registration rate limit exceeded\", { ip: request?.ip });\n throw new BadRequestError(\n \"Too many registration attempts, please try again later\",\n );\n }\n await this.rateLimitCache.set(ipKey, count + 1);\n }\n\n // Check if registration is allowed\n if (realmSettings?.registrationAllowed === false) {\n this.log.warn(\"Registration not allowed for realm\", { userRealmName });\n throw new BadRequestError(\"Registration is not allowed\");\n }\n\n // Validate required fields based on settings\n if (realmSettings?.username === \"required\" && !body.username) {\n this.log.debug(\"Registration rejected: username required\", {\n userRealmName,\n });\n throw new BadRequestError(\"Username is required\");\n }\n\n // In \"email\" mode the server is authoritative — any username sent in\n // the request is dropped on the floor and replaced by the slugger\n // output below.\n if (realmSettings?.username === \"email\") {\n body.username = undefined;\n }\n\n if (body.username) {\n // Security note: usernameRegExp is admin-controlled (from realmAuthSettingsAtom),\n // not user input. Default is ^[a-zA-Z0-9_-]{3,30}$ which is ReDoS-safe.\n // No need for regex timeout or safe-regex validation here.\n const usernameRegExp = realmSettings?.usernameRegExp;\n if (usernameRegExp) {\n const regex = new RegExp(usernameRegExp);\n if (!regex.test(body.username)) {\n this.log.debug(\"Registration rejected: username regex mismatch\", {\n userRealmName,\n username: body.username,\n });\n throw new BadRequestError(\n \"Username does not meet the required format\",\n );\n }\n }\n\n // Manual usernames must also clear the realm blocklist — same set\n // that the slugger uses, so apps can't be sidestepped by clients\n // POSTing the reserved name directly.\n if (await this.usernameSlugger.isBlocked(userRealmName, body.username)) {\n this.log.debug(\"Registration rejected: username is blocked\", {\n userRealmName,\n username: body.username,\n });\n throw new BadRequestError(\"This username is not available\");\n }\n }\n\n if (realmSettings?.email === \"required\" && !body.email) {\n this.log.debug(\"Registration rejected: email required\", {\n userRealmName,\n });\n throw new BadRequestError(\"Email is required\");\n }\n\n if (realmSettings?.phoneNumber === \"required\" && !body.phoneNumber) {\n this.log.debug(\"Registration rejected: phone required\", {\n userRealmName,\n });\n throw new BadRequestError(\"Phone number is required\");\n }\n\n // In \"email\" mode, derive the username from the email *now* so that\n // checkUserAvailability picks it up too, the DB unique index sees a\n // concrete value, and the persisted intent already carries the final\n // username.\n if (realmSettings?.username === \"email\") {\n if (!body.email) {\n throw new BadRequestError(\n \"Email is required to derive a username from email\",\n );\n }\n const base = this.usernameSlugger.slug(body.email);\n body.username = await this.usernameSlugger.pickAvailable(\n userRealmName,\n base,\n );\n }\n\n // Check for existing users (username, email, phone)\n await this.checkUserAvailability(body, userRealmName);\n\n // Validate password against realm policy\n this.credentialService.validatePasswordPolicy(\n body.password,\n realmSettings.passwordPolicy,\n );\n\n // Validate captcha BEFORE any side effects (email sends, rate-limit counters).\n // This blocks bots from triggering verification emails on other people's addresses.\n if (realmSettings?.captchaRequired === true) {\n if (!body.captchaToken) {\n throw new BadRequestError(\"Captcha verification is required\");\n }\n const valid = await this.captchaProvider.verify(body.captchaToken);\n if (!valid) {\n throw new BadRequestError(\"Captcha verification failed\");\n }\n }\n\n // Hash the password\n const passwordHash = await this.cryptoProvider.hashPassword(body.password);\n\n // Determine requirements based on realm settings\n const requirements = {\n email: realmSettings?.verifyEmailRequired === true && !!body.email,\n phone: realmSettings?.verifyPhoneRequired === true && !!body.phoneNumber,\n captcha: false, // validated above, single-use — no gate at complete time\n };\n\n // Create verification sessions and send codes\n if (requirements.email && body.email) {\n await this.sendEmailVerification(body.email, userRealmName);\n }\n\n if (requirements.phone && body.phoneNumber) {\n await this.sendPhoneVerification(body.phoneNumber, userRealmName);\n }\n\n // Generate intent ID and expiration\n const intentId = this.cryptoProvider.randomUUID();\n const expiresAt = this.dateTimeProvider\n .now()\n .add(INTENT_TTL_MINUTES, \"minutes\")\n .toISOString();\n\n // Store intent in cache\n const intent: RegistrationIntent = {\n data: {\n username: body.username,\n email: body.email,\n phoneNumber: body.phoneNumber,\n firstName: body.firstName,\n lastName: body.lastName,\n picture: body.picture,\n passwordHash,\n },\n requirements,\n realmName: userRealmName,\n expiresAt,\n };\n\n await this.intentCache.set(intentId, intent);\n\n this.log.info(\"Registration intent created\", {\n intentId,\n email: body.email,\n username: body.username,\n requiresEmailVerification: requirements.email,\n requiresPhoneVerification: requirements.phone,\n });\n\n return {\n intentId,\n expectCaptcha: requirements.captcha,\n captchaSiteKey: requirements.captcha\n ? this.captchaProvider.getSiteKey()\n : undefined,\n expectEmailVerification: requirements.email,\n expectPhoneVerification: requirements.phone,\n expiresAt,\n };\n }\n\n /**\n * Phase 2: Complete registration using an intent.\n *\n * Validates all requirements (verification codes, captcha),\n * creates the user and credentials, and returns the user.\n */\n public async completeRegistration(\n body: CompleteRegistrationRequest,\n ): Promise<UserEntity> {\n this.log.trace(\"Completing registration\", { intentId: body.intentId });\n\n // Fetch intent from cache\n const intent = await this.intentCache.get(body.intentId);\n if (!intent) {\n this.log.warn(\"Invalid or expired registration intent\", {\n intentId: body.intentId,\n });\n throw new HttpError({\n status: 410,\n message: \"Invalid or expired registration intent\",\n });\n }\n\n const userRealmName = intent.realmName;\n const userRepository = this.realmProvider.userRepository(userRealmName);\n const identityRepository =\n this.realmProvider.identityRepository(userRealmName);\n\n // Validate email verification if required\n if (intent.requirements.email) {\n if (!body.emailCode) {\n this.log.debug(\"Registration completion missing email code\", {\n intentId: body.intentId,\n });\n throw new BadRequestError(\"Email verification code is required\");\n }\n\n if (!intent.data.email) {\n throw new BadRequestError(\"Email is missing from registration intent\");\n }\n\n await this.verifyEmailCode(intent.data.email, body.emailCode);\n }\n\n // Validate phone verification if required\n if (intent.requirements.phone) {\n if (!body.phoneCode) {\n this.log.debug(\"Registration completion missing phone code\", {\n intentId: body.intentId,\n });\n throw new BadRequestError(\"Phone verification code is required\");\n }\n\n if (!intent.data.phoneNumber) {\n throw new BadRequestError(\n \"Phone number is missing from registration intent\",\n );\n }\n\n await this.verifyPhoneCode(intent.data.phoneNumber, body.phoneCode);\n }\n\n // Captcha is validated at intent creation (see createIntent) so bots can't\n // trigger verification emails; nothing to re-check here.\n\n // Final availability check (race condition guard)\n await this.checkUserAvailability(\n {\n username: intent.data.username,\n email: intent.data.email,\n phoneNumber: intent.data.phoneNumber,\n },\n userRealmName,\n );\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const realmSettings = await realm.getSettings();\n\n // Create the user\n const user = await userRepository.create({\n realm: realm.name,\n username: intent.data.username,\n email: intent.data.email,\n phoneNumber: intent.data.phoneNumber,\n firstName: intent.data.firstName,\n lastName: intent.data.lastName,\n picture: intent.data.picture,\n roles: realmSettings.defaultRoles,\n enabled: true,\n emailVerified: intent.requirements.email, // Marked as verified if we verified during registration\n });\n\n // Create credentials identity\n await identityRepository.create({\n userId: user.id,\n provider: \"credentials\",\n password: intent.data.passwordHash,\n });\n\n // Invalidate intent after both user and identity creation succeed,\n // so the user can retry if either operation fails.\n await this.intentCache.invalidate(body.intentId);\n\n this.log.info(\"User registered successfully\", {\n userId: user.id,\n email: user.email,\n username: user.username,\n });\n\n await this.userAudits(userRealmName)?.user.log(\"create\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: user.id,\n description: \"User registered\",\n metadata: {\n username: user.username,\n email: user.email,\n emailVerified: user.emailVerified,\n registrationMethod: \"credentials\",\n },\n });\n\n return user;\n }\n\n /**\n * Check if username, email, and phone are available.\n */\n protected async checkUserAvailability(\n body: Pick<RegisterRequest, \"username\" | \"email\" | \"phoneNumber\">,\n userRealmName?: string,\n ): Promise<void> {\n const realm = this.realmProvider.getRealm(userRealmName);\n const userRepository = this.realmProvider.userRepository(userRealmName);\n\n if (body.username) {\n const existingUser = await userRepository.findOne({\n where: { realm: realm.name, username: { ilike: body.username } },\n });\n if (existingUser) {\n this.log.debug(\"Username already taken\", { username: body.username });\n throw new ConflictError(\"User with this username already exists\");\n }\n }\n\n if (body.email) {\n const existingUser = await userRepository.findOne({\n where: { realm: realm.name, email: { eq: body.email } },\n });\n if (existingUser) {\n this.log.debug(\"Email already taken\", { email: body.email });\n throw new ConflictError(\"User with this email already exists\");\n }\n }\n\n if (body.phoneNumber) {\n const existingUser = await userRepository.findOne({\n where: { realm: realm.name, phoneNumber: { eq: body.phoneNumber } },\n });\n if (existingUser) {\n this.log.debug(\"Phone number already taken\", {\n phoneNumber: body.phoneNumber,\n });\n throw new ConflictError(\"User with this phone number already exists\");\n }\n }\n }\n\n /**\n * Send email verification code.\n */\n protected async sendEmailVerification(\n email: string,\n realmName?: string,\n ): Promise<void> {\n this.log.debug(\"Sending email verification code\", { email });\n\n const verification =\n await this.verificationController.requestVerificationCode({\n params: { type: \"code\" },\n body: { target: email },\n });\n\n await this.userNotifications(realmName).emailVerification.push({\n contact: email,\n variables: {\n email,\n code: verification.token,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n });\n\n this.log.debug(\"Email verification code sent\", { email });\n }\n\n /**\n * Send phone verification code.\n */\n protected async sendPhoneVerification(\n phoneNumber: string,\n realmName?: string,\n ): Promise<void> {\n this.log.debug(\"Sending phone verification code\", { phoneNumber });\n try {\n const verification =\n await this.verificationController.requestVerificationCode({\n params: { type: \"code\" },\n body: { target: phoneNumber },\n });\n\n await this.userNotifications(realmName).phoneVerification.push({\n contact: phoneNumber,\n variables: {\n phoneNumber,\n code: verification.token,\n expiresInMinutes: Math.floor(verification.codeExpiration / 60),\n },\n });\n this.log.debug(\"Phone verification code sent\", { phoneNumber });\n } catch (error) {\n // Silent fail - verification service may have rate limiting\n this.log.warn(\"Failed to send phone verification code\", {\n phoneNumber,\n error,\n });\n }\n }\n\n /**\n * Verify email code using verification service.\n */\n protected async verifyEmailCode(email: string, code: string): Promise<void> {\n const result = await this.verificationController\n .validateVerificationCode({\n params: { type: \"code\" },\n body: { target: email, token: code },\n })\n .catch(() => {\n this.log.warn(\"Invalid email verification code\", { email });\n throw new BadRequestError(\"Invalid or expired email verification code\");\n });\n\n if (result.alreadyVerified) {\n this.log.warn(\"Email verification code already used\", { email });\n throw new BadRequestError(\n \"Email verification code has already been used\",\n );\n }\n }\n\n /**\n * Verify phone code using verification service.\n */\n protected async verifyPhoneCode(\n phoneNumber: string,\n code: string,\n ): Promise<void> {\n const result = await this.verificationController\n .validateVerificationCode({\n params: { type: \"code\" },\n body: { target: phoneNumber, token: code },\n })\n .catch(() => {\n this.log.warn(\"Invalid phone verification code\", { phoneNumber });\n throw new BadRequestError(\"Invalid or expired phone verification code\");\n });\n\n if (result.alreadyVerified) {\n this.log.warn(\"Phone verification code already used\", { phoneNumber });\n throw new BadRequestError(\n \"Phone verification code has already been used\",\n );\n }\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { completePasswordResetRequestSchema } from \"../schemas/completePasswordResetRequestSchema.ts\";\nimport { completeRegistrationRequestSchema } from \"../schemas/completeRegistrationRequestSchema.ts\";\nimport { passwordResetIntentResponseSchema } from \"../schemas/passwordResetIntentResponseSchema.ts\";\nimport { registerQuerySchema } from \"../schemas/registerQuerySchema.ts\";\nimport { registerRequestSchema } from \"../schemas/registerRequestSchema.ts\";\nimport { registrationIntentResponseSchema } from \"../schemas/registrationIntentResponseSchema.ts\";\nimport { userResourceSchema } from \"../schemas/userResourceSchema.ts\";\nimport { CredentialService } from \"../services/CredentialService.ts\";\nimport { RegistrationService } from \"../services/RegistrationService.ts\";\nimport { UserService } from \"../services/UserService.ts\";\n\nexport class UserController {\n protected readonly url = \"/users\";\n protected readonly group = \"users\";\n protected readonly credentialService = $inject(CredentialService);\n protected readonly userService = $inject(UserService);\n protected readonly registrationService = $inject(RegistrationService);\n\n /**\n * Phase 1: Create a registration intent.\n * Validates data, creates verification sessions, and stores intent in cache.\n */\n public readonly createRegistrationIntent = $action({\n group: this.group,\n method: \"POST\",\n path: `${this.url}/register`,\n schema: {\n body: registerRequestSchema,\n query: registerQuerySchema,\n response: registrationIntentResponseSchema,\n },\n handler: ({ body, query }) =>\n this.registrationService.createRegistrationIntent(\n body,\n query.userRealmName,\n ),\n });\n\n /**\n * Phase 2: Complete registration using an intent.\n * Validates verification codes and creates the user.\n */\n public readonly createUserFromIntent = $action({\n group: this.group,\n method: \"POST\",\n path: `${this.url}/register/complete`,\n schema: {\n body: completeRegistrationRequestSchema,\n response: userResourceSchema,\n },\n handler: ({ body }) => this.registrationService.completeRegistration(body),\n });\n\n /**\n * Phase 1: Create a password reset intent.\n * Validates email, sends verification code, and stores intent in cache.\n */\n public readonly createPasswordResetIntent = $action({\n group: this.group,\n method: \"POST\",\n path: `${this.url}/password-reset`,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n email: t.email(),\n }),\n response: passwordResetIntentResponseSchema,\n },\n handler: ({ body, query }) =>\n this.credentialService.createPasswordResetIntent(\n body.email,\n query.userRealmName,\n ),\n });\n\n /**\n * Phase 2: Complete password reset using an intent.\n * Validates verification code, updates password, and invalidates sessions.\n */\n public readonly completePasswordReset = $action({\n group: this.group,\n method: \"POST\",\n path: `${this.url}/password-reset/complete`,\n schema: {\n body: completePasswordResetRequestSchema,\n response: okSchema,\n },\n handler: async ({ body }) => {\n await this.credentialService.completePasswordReset(body);\n return { ok: true };\n },\n });\n\n // Legacy endpoints for backward compatibility\n\n /**\n * @deprecated Use createPasswordResetIntent instead\n */\n public requestPasswordReset = $action({\n path: \"/users/password-reset/request\",\n group: this.group,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n email: t.email(),\n }),\n response: t.object({\n success: t.boolean(),\n message: t.string(),\n }),\n },\n handler: async ({ body, query }) => {\n await this.credentialService.requestPasswordReset(\n body.email,\n query.userRealmName,\n );\n\n return {\n success: true,\n message:\n \"If an account exists with this email, a password reset code has been sent.\",\n };\n },\n });\n\n /**\n * @deprecated Use completePasswordReset instead\n */\n public validateResetToken = $action({\n path: \"/users/password-reset/validate\",\n group: this.group,\n schema: {\n query: t.object({\n email: t.email(),\n token: t.string(),\n userRealmName: t.optional(t.string()),\n }),\n response: t.object({\n valid: t.boolean(),\n email: t.optional(t.email()),\n }),\n },\n handler: async ({ query }) => {\n try {\n const email = await this.credentialService.validateResetToken(\n query.email,\n query.token,\n query.userRealmName,\n );\n return {\n valid: true,\n email,\n };\n } catch {\n return {\n valid: false,\n };\n }\n },\n });\n\n /**\n * @deprecated Use completePasswordReset instead\n */\n public resetPassword = $action({\n path: \"/users/password-reset/reset\",\n group: this.group,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n email: t.email(),\n token: t.string(),\n newPassword: t.string({ minLength: 8 }),\n }),\n response: t.object({\n success: t.boolean(),\n message: t.string(),\n }),\n },\n handler: async ({ body, query }) => {\n await this.credentialService.resetPassword(\n body.email,\n body.token,\n body.newPassword,\n query.userRealmName,\n );\n\n return {\n success: true,\n message: \"Password has been reset successfully. Please log in.\",\n };\n },\n });\n\n /**\n * Request email verification.\n * Generates a verification token using verification service and sends an email to the user.\n * @param method - The verification method: \"code\" (default) sends a 6-digit code, \"link\" sends a clickable verification link.\n * @param verifyUrl - Required when method is \"link\". The base URL for the verification link. Token and email will be appended as query params.\n */\n public requestEmailVerification = $action({\n path: \"/users/email-verification/request\",\n group: this.group,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n method: t.optional(\n t.enum([\"code\", \"link\"], {\n default: \"code\",\n description:\n 'Verification method: \"code\" sends a 6-digit code, \"link\" sends a clickable verification link. When using \"link\", configure verifyEmailUrl in realm settings.',\n }),\n ),\n }),\n body: t.object({\n email: t.email(),\n }),\n response: t.object({\n success: t.boolean(),\n message: t.string(),\n }),\n },\n handler: async ({ body, query }) => {\n const method = query.method ?? \"code\";\n await this.userService.requestEmailVerification(\n body.email,\n query.userRealmName,\n method,\n );\n\n return {\n success: true,\n message:\n method === \"link\"\n ? \"If an account exists with this email, a verification link has been sent.\"\n : \"If an account exists with this email, a verification code has been sent.\",\n };\n },\n });\n\n /**\n * Verify email with a valid token.\n * Updates the user's emailVerified status.\n */\n public verifyEmail = $action({\n path: \"/users/email-verification/verify\",\n group: this.group,\n schema: {\n query: t.object({\n userRealmName: t.optional(t.string()),\n }),\n body: t.object({\n email: t.email(),\n token: t.string(),\n }),\n response: t.object({\n success: t.boolean(),\n message: t.string(),\n }),\n },\n handler: async ({ body, query }) => {\n await this.userService.verifyEmail(\n body.email,\n body.token,\n query.userRealmName,\n );\n\n return {\n success: true,\n message: \"Email has been verified successfully.\",\n };\n },\n });\n\n /**\n * Check if an email is verified.\n */\n public checkEmailVerification = $action({\n path: \"/users/email-verification/check\",\n group: this.group,\n use: [$secure()],\n schema: {\n query: t.object({\n email: t.email(),\n userRealmName: t.optional(t.string()),\n }),\n response: t.object({\n verified: t.boolean(),\n }),\n },\n handler: async ({ query }) => {\n const verified = await this.userService.isEmailVerified(\n query.email,\n query.userRealmName,\n );\n\n return {\n verified,\n };\n },\n });\n}\n","import { $inject } from \"alepha\";\nimport { $job } from \"alepha/api/jobs\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { sessions } from \"../entities/sessions.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\n\n/**\n * User-specific jobs wrapper service.\n *\n * This service handles user-related scheduled jobs such as:\n * - Session purge (cleaning up expired sessions)\n * - Verification code cleanup\n * - Inactive user notifications\n *\n * Declared as a module variant — not auto-injected. It is instantiated\n * lazily the first time something calls `alepha.inject(UserJobs)`.\n */\nexport class UserJobs {\n protected readonly log = $logger();\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly sessionRepository = $repository(sessions);\n protected readonly realmProvider = $inject(RealmProvider);\n\n /**\n * Purge expired sessions from the database.\n *\n * Runs hourly (at :00) and deletes:\n * - sessions whose absolute `expiresAt` has passed\n * - sessions whose `lastUsedAt` exceeds the realm's `refreshToken.expirationIdle`\n * (when configured). Falls back to `createdAt` for sessions without a\n * recorded `lastUsedAt`.\n *\n * The idle sweep is best-effort cleanup — runtime enforcement happens in\n * `SessionService.refreshSession()`.\n */\n public readonly purgeExpiredSessions = $job({\n name: \"api:users:purgeExpiredSessions\",\n cron: \"0 * * * *\", // Hourly at minute 0\n handler: async () => {\n const now = this.dateTimeProvider.nowISOString();\n\n this.log.info(\"Starting expired sessions purge\", { cutoffTime: now });\n\n const absoluteDeletedIds = await this.sessionRepository.deleteMany({\n expiresAt: { lt: now },\n });\n\n if (absoluteDeletedIds.length > 0) {\n this.log.info(\"Expired sessions purged (absolute)\", {\n deletedCount: absoluteDeletedIds.length,\n });\n }\n\n // Idle sweep — only if the default realm has expirationIdle configured.\n // Multi-realm setups with per-realm session tables should add their own\n // job; this default job sweeps the default sessions table.\n const realm = this.realmProvider.getRealm();\n const settings = await realm.getSettings();\n const idleMs = settings.refreshToken?.expirationIdle;\n if (idleMs && idleMs > 0) {\n const cutoff = this.dateTimeProvider\n .now()\n .subtract(idleMs, \"milliseconds\")\n .toISOString();\n\n // Two passes: rows with an explicit lastUsedAt, and pre-migration rows\n // where lastUsedAt is null — those fall back to createdAt.\n const lastUsedDeletedIds = await this.sessionRepository.deleteMany({\n lastUsedAt: { lt: cutoff },\n });\n const fallbackDeletedIds = await this.sessionRepository.deleteMany({\n lastUsedAt: { isNull: true },\n createdAt: { lt: cutoff },\n });\n\n const idleTotal = lastUsedDeletedIds.length + fallbackDeletedIds.length;\n if (idleTotal > 0) {\n this.log.info(\"Expired sessions purged (idle)\", {\n deletedCount: idleTotal,\n thresholdMs: idleMs,\n });\n }\n }\n },\n });\n}\n","import { randomInt } from \"node:crypto\";\nimport { $inject, Alepha } from \"alepha\";\nimport type { FileController } from \"alepha/api/files\";\nimport { CacheProvider } from \"alepha/cache\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport {\n CryptoProvider,\n InvalidCredentialsError,\n type UserAccount,\n} from \"alepha/security\";\nimport { BadRequestError, UnauthorizedError } from \"alepha/server\";\nimport type { OAuth2Profile } from \"alepha/server/auth\";\nimport { $client } from \"alepha/server/links\";\nimport { FileSystemProvider } from \"alepha/system\";\nimport { SessionAudits } from \"../audits/SessionAudits.ts\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport type { UserEntity } from \"../entities/users.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport { UsernameSlugger } from \"./UsernameSlugger.ts\";\n\nexport class SessionService {\n protected readonly alepha = $inject(Alepha);\n protected readonly fsp = $inject(FileSystemProvider);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly cryptoProvider = $inject(CryptoProvider);\n protected readonly log = $logger();\n protected readonly realmProvider = $inject(RealmProvider);\n protected readonly fileController = $client<FileController>();\n protected readonly cacheProvider = $inject(CacheProvider);\n protected readonly usernameSlugger = $inject(UsernameSlugger);\n\n protected userAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(UserAudits);\n }\n return undefined;\n }\n\n protected sessionAudits(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.audits) {\n return this.alepha.inject(SessionAudits);\n }\n return undefined;\n }\n\n protected userNotifications(realmName?: string) {\n const realm = this.realmProvider.getRealm(realmName);\n if (realm.features.notifications) {\n return this.alepha.inject(UserNotifications);\n }\n return undefined;\n }\n\n public users(userRealmName?: string) {\n return this.realmProvider.userRepository(userRealmName);\n }\n\n public sessions(userRealmName?: string) {\n return this.realmProvider.sessionRepository(userRealmName);\n }\n\n public identities(userRealmName?: string) {\n return this.realmProvider.identityRepository(userRealmName);\n }\n\n /**\n * Check if user should be auto-promoted to admin based on adminEmails/adminUsernames settings.\n * If user matches and doesn't have admin role, promote them.\n */\n protected async ensureAdminRole(\n user: {\n id: string;\n email?: string | null;\n username?: string | null;\n roles: string[];\n },\n userRealmName?: string,\n ): Promise<boolean> {\n if (user.roles.includes(\"admin\")) return false;\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const settings = await realm.getSettings();\n const { name } = realm;\n const adminEmails = settings.adminEmails ?? [];\n const adminUsernames = settings.adminUsernames ?? [];\n\n const isAdminByEmail = user.email && adminEmails.includes(user.email);\n const isAdminByUsername =\n user.username && adminUsernames.includes(user.username);\n\n if (!isAdminByEmail && !isAdminByUsername) return false;\n\n // Promote to admin\n user.roles = [...user.roles.filter((r) => r !== \"admin\"), \"admin\"];\n await this.users(userRealmName).updateById(user.id, { roles: user.roles });\n\n const reason = isAdminByEmail ? \"adminEmails\" : \"adminUsernames\";\n this.log.info(`User auto-promoted to admin via ${reason} setting`, {\n userId: user.id,\n email: user.email,\n username: user.username,\n realm: name,\n });\n\n await this.userAudits(userRealmName)?.user.log(\"role_change\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: name,\n resourceId: user.id,\n description: `User auto-promoted to admin via ${reason} setting`,\n metadata: { addedRole: \"admin\", reason },\n });\n\n return true;\n }\n\n /**\n * Generate a unique username from an OAuth profile.\n *\n * Routes through {@link UsernameSlugger}, which is the same code path as\n * `username: \"email\"` registration. The OAuth profile's email is the\n * primary signal; if absent (rare — most IDPs return one), we fall back\n * to `profile.name`, then to a random handle. The slugger applies the\n * realm's `usernameBlocklist` and retries on collision.\n */\n protected async generateUniqueUsername(\n profile: OAuth2Profile,\n _realmSettings: any,\n _users: any,\n realmName?: string,\n ): Promise<string> {\n const seed =\n profile.email ??\n profile.name ??\n `user-${Math.random().toString(36).slice(2, 8)}`;\n const base = this.usernameSlugger.slug(seed);\n return this.usernameSlugger.pickAvailable(realmName, base);\n }\n\n /**\n * Random delay to prevent timing attacks (50-200ms)\n * Uses cryptographically secure random number generation\n */\n protected randomDelay(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, randomInt(50, 201)));\n }\n\n protected static readonly LOGIN_CACHE_NAME = \"login-rate-limit\";\n\n /**\n * Check if a login key is currently locked out.\n * Read-only — does not increment the counter.\n */\n protected async isLoginLocked(key: string, max: number): Promise<boolean> {\n try {\n const count = await this.cacheProvider.getTyped<number>(\n SessionService.LOGIN_CACHE_NAME,\n key,\n );\n return count != null && count >= max;\n } catch (error) {\n this.log.warn(\n \"Failed to check login rate limit, allowing attempt\",\n error,\n );\n return false;\n }\n }\n\n /**\n * Record a failed login attempt. Uses getTyped + setTyped (not incr) so that\n * each write refreshes the TTL — implementing sliding-window behavior.\n *\n * Returns `true` if this failure just crossed the lockout threshold.\n */\n protected async recordFailedLogin(\n key: string,\n max: number,\n windowMs: number,\n ): Promise<boolean> {\n try {\n const count =\n (await this.cacheProvider.getTyped<number>(\n SessionService.LOGIN_CACHE_NAME,\n key,\n )) ?? 0;\n const newCount = count + 1;\n await this.cacheProvider.setTyped(\n SessionService.LOGIN_CACHE_NAME,\n key,\n newCount,\n { ttl: windowMs },\n );\n return newCount === max;\n } catch (error) {\n this.log.warn(\"Failed to record failed login attempt\", error);\n return false;\n }\n }\n\n /**\n * Validate user credentials and return the user if valid.\n */\n public async login(\n provider: string,\n username: string,\n password: string,\n userRealmName?: string,\n ): Promise<UserEntity> {\n const realm = this.realmProvider.getRealm(userRealmName);\n const settings = await realm.getSettings();\n const { name } = realm;\n const { loginRateLimit } = settings;\n const isEmail = username.includes(\"@\");\n const isPhone = /^[+\\d][\\d\\s()-]+$/.test(username);\n const isUsername = !isEmail && !isPhone;\n const identities = this.identities(userRealmName);\n const users = this.users(userRealmName);\n\n // IP rate limit check (global, cross-realm) — before any DB work\n const request = this.alepha.store.get(\"alepha.http.request\");\n const ipKey = request?.ip ? `login:ip:${request.ip}` : undefined;\n\n if (ipKey) {\n const ipLocked = await this.isLoginLocked(\n ipKey,\n loginRateLimit.ipMaxAttempts,\n );\n if (ipLocked) {\n this.log.warn(\"Login blocked — IP rate limit exceeded\", {\n ip: request?.ip,\n });\n throw new InvalidCredentialsError();\n }\n }\n\n await this.randomDelay();\n\n try {\n const where = users.createQueryWhere();\n\n where.realm = name;\n\n if (settings.username !== \"none\" && isUsername) {\n // validate username format if regex is provided\n if (settings.usernameRegExp) {\n const regex = new RegExp(settings.usernameRegExp);\n if (!regex.test(username)) {\n this.log.warn(\"Username does not match required format\", {\n provider,\n username,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n description: \"Username does not match required format\",\n metadata: { provider, username },\n });\n\n throw new InvalidCredentialsError();\n }\n }\n where.username = { ilike: username };\n } else if (settings.email !== \"none\" && isEmail) {\n where.email = username;\n } else if (settings.phoneNumber !== \"none\" && isPhone) {\n where.phoneNumber = username;\n } else {\n this.log.warn(\"Invalid login identifier format\", {\n provider,\n username,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n description: \"Invalid login identifier format\",\n metadata: { provider, username },\n });\n\n throw new InvalidCredentialsError();\n }\n\n const user = await users.findOne({ where });\n if (!user) {\n this.log.warn(\"User not found during login attempt\", {\n provider,\n username,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n description: \"User not found\",\n metadata: { provider, username },\n });\n\n // Only increment IP counter (no user ID to track)\n if (ipKey) {\n const justLocked = await this.recordFailedLogin(\n ipKey,\n loginRateLimit.ipMaxAttempts,\n loginRateLimit.windowMs,\n );\n if (justLocked) {\n await this.sessionAudits(userRealmName)?.security.log(\n \"rate_limited\",\n {\n userRealm: name,\n success: false,\n description:\n \"IP temporarily locked due to too many failed login attempts\",\n metadata: { ip: request?.ip },\n },\n );\n }\n }\n\n throw new InvalidCredentialsError();\n }\n\n // Check if user account is enabled\n if (!user.enabled) {\n this.log.warn(\"Login attempt for disabled account\", {\n userId: user.id,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n resourceId: user.id,\n description: \"Login attempt for disabled account\",\n metadata: { provider, username },\n });\n\n throw new InvalidCredentialsError();\n }\n\n // Account rate limit check (per-realm)\n const accountKey = `login:account:${name}:${user.id}`;\n const accountLocked = await this.isLoginLocked(\n accountKey,\n loginRateLimit.accountMaxAttempts,\n );\n if (accountLocked) {\n this.log.warn(\"Login blocked — account rate limit exceeded\", {\n userId: user.id,\n realm: name,\n });\n throw new InvalidCredentialsError();\n }\n\n const identity = await identities.getOne({\n where: {\n provider: { eq: provider },\n userId: { eq: user.id },\n },\n });\n\n const storedPassword = identity.password;\n if (!storedPassword) {\n this.log.error(\"Identity has no password configured\", {\n provider,\n username,\n identityId: identity.id,\n realm: name,\n });\n throw new InvalidCredentialsError();\n }\n\n const valid = await this.cryptoProvider.verifyPassword(\n password,\n storedPassword,\n );\n\n if (!valid) {\n this.log.warn(\"Invalid password during login attempt\", {\n provider,\n username,\n realm: name,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userRealm: name,\n success: false,\n resourceId: user.id,\n description: \"Invalid password\",\n metadata: { provider, username },\n });\n\n // Record failed attempt on both IP and account counters\n if (ipKey) {\n const ipJustLocked = await this.recordFailedLogin(\n ipKey,\n loginRateLimit.ipMaxAttempts,\n loginRateLimit.windowMs,\n );\n if (ipJustLocked) {\n await this.sessionAudits(userRealmName)?.security.log(\n \"rate_limited\",\n {\n userRealm: name,\n success: false,\n description:\n \"IP temporarily locked due to too many failed login attempts\",\n metadata: { ip: request?.ip },\n },\n );\n }\n }\n\n const accountJustLocked = await this.recordFailedLogin(\n accountKey,\n loginRateLimit.accountMaxAttempts,\n loginRateLimit.windowMs,\n );\n if (accountJustLocked) {\n await this.sessionAudits(userRealmName)?.security.log(\n \"rate_limited\",\n {\n userRealm: name,\n resourceId: user.id,\n success: false,\n description:\n \"Account temporarily locked due to too many failed login attempts\",\n metadata: { userId: user.id },\n },\n );\n\n // Notify user about account lockout\n if (user.email) {\n const lockoutMinutes = Math.round(loginRateLimit.windowMs / 60_000);\n await this.userNotifications(userRealmName)?.accountLockout.push({\n contact: user.email,\n variables: { email: user.email, lockoutMinutes },\n });\n }\n }\n\n throw new InvalidCredentialsError();\n }\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: name,\n resourceId: user.id,\n description: `User logged in via ${provider}`,\n metadata: { provider, username },\n });\n\n // Auto-promote to admin if configured\n await this.ensureAdminRole(user, userRealmName);\n\n return user;\n } catch (error) {\n if (error instanceof InvalidCredentialsError) {\n throw error;\n }\n\n this.log.warn(\"Error during login attempt\", error);\n\n throw new InvalidCredentialsError();\n }\n }\n\n public async createSession(\n user: UserAccount,\n expiresIn: number,\n userRealmName?: string,\n clientId?: string,\n ) {\n this.log.trace(\"Creating session\", { userId: user.id, expiresIn });\n\n const request = this.alepha.store.get(\"alepha.http.request\");\n const refreshToken = this.cryptoProvider.randomUUID();\n\n const expiresAt = this.dateTimeProvider\n .now()\n .add(expiresIn, \"seconds\")\n .toISOString();\n\n const nowIso = this.dateTimeProvider.nowISOString();\n\n const session = await this.sessions(userRealmName).create({\n userId: user.id,\n expiresAt,\n lastUsedAt: nowIso,\n ip: request?.ip,\n country: request?.geo?.country,\n userAgent: request?.userAgent,\n refreshToken,\n clientId,\n });\n\n await this.users(userRealmName).updateById(user.id, {\n lastLoginAt: nowIso,\n });\n\n this.log.info(\"Session created\", {\n sessionId: session.id,\n userId: user.id,\n ip: request?.ip,\n });\n\n return {\n refreshToken,\n sessionId: session.id,\n };\n }\n\n public async refreshSession(refreshToken: string, userRealmName?: string) {\n this.log.trace(\"Refreshing session\");\n\n // getOne() throws DbEntityNotFoundError if not found — never returns null.\n // No null check needed here.\n const session = await this.sessions(userRealmName).getOne({\n where: {\n refreshToken: { eq: refreshToken },\n },\n });\n\n const now = this.dateTimeProvider.now();\n const expiresAt = this.dateTimeProvider.of(session.expiresAt);\n\n if (this.dateTimeProvider.of(session.expiresAt) < now) {\n this.log.debug(\"Session expired during refresh\", {\n sessionId: session.id,\n userId: session.userId,\n });\n await this.sessions(userRealmName).deleteById(session.id);\n throw new UnauthorizedError(\"Session expired\");\n }\n\n // Idle timeout check — opt-in via realm settings.\n // Falls back to createdAt when lastUsedAt is null (pre-migration rows or\n // sessions that never refreshed since the column was introduced).\n const realm = this.realmProvider.getRealm(userRealmName);\n const settings = await realm.getSettings();\n const idleMs = settings.refreshToken?.expirationIdle;\n if (idleMs && idleMs > 0) {\n const lastUsedRef = session.lastUsedAt ?? session.createdAt;\n const idleSince = now.diff(this.dateTimeProvider.of(lastUsedRef));\n if (idleSince > idleMs) {\n this.log.info(\"Session expired (idle timeout)\", {\n sessionId: session.id,\n userId: session.userId,\n idleMs: idleSince,\n thresholdMs: idleMs,\n });\n await this.sessions(userRealmName).deleteById(session.id);\n throw new UnauthorizedError(\"Session expired\");\n }\n }\n\n const user = await this.users(userRealmName).getOne({\n where: {\n id: { eq: session.userId },\n },\n });\n\n // Check if user account is still enabled\n if (!user.enabled) {\n this.log.warn(\"Session refresh for disabled account\", {\n userId: user.id,\n sessionId: session.id,\n });\n await this.sessions(userRealmName).deleteById(session.id);\n throw new UnauthorizedError(\"Account disabled\");\n }\n\n // Auto-promote to admin if configured (handles \"I promote you admin\" case)\n await this.ensureAdminRole(user, userRealmName);\n\n // Update lastUsedAt — sliding-window for idle timeout enforcement.\n await this.sessions(userRealmName).updateById(session.id, {\n lastUsedAt: now.toISOString(),\n });\n\n this.log.debug(\"Session refreshed\", {\n sessionId: session.id,\n userId: session.userId,\n });\n\n return {\n user,\n expiresIn: expiresAt.unix() - now.unix(),\n sessionId: session.id,\n };\n }\n\n public async deleteSession(refreshToken: string, userRealmName?: string) {\n this.log.trace(\"Deleting session\");\n\n // Get session info before deletion for audit\n const session = await this.sessions(userRealmName).findOne({\n where: { refreshToken: { eq: refreshToken } },\n });\n\n await this.sessions(userRealmName).deleteOne({\n refreshToken,\n });\n this.log.debug(\"Session deleted\");\n\n if (session) {\n const { name } = this.realmProvider.getRealm(userRealmName);\n\n await this.sessionAudits(userRealmName)?.auth.log(\"logout\", {\n userId: session.userId,\n userRealm: name,\n sessionId: session.id,\n description: \"User logged out\",\n });\n }\n }\n\n public async link(\n provider: string,\n profile: OAuth2Profile,\n userRealmName?: string,\n ) {\n this.log.trace(\"Linking OAuth2 profile\", {\n provider,\n profileSub: profile.sub,\n email: profile.email,\n });\n\n const realm = this.realmProvider.getRealm(userRealmName);\n const identities = this.identities(userRealmName);\n const users = this.users(userRealmName);\n\n const identity = await identities.findOne({\n where: {\n provider,\n providerUserId: profile.sub,\n },\n });\n\n // existing identity found, return associated user\n if (identity) {\n this.log.debug(\"Existing identity found\", {\n provider,\n identityId: identity.id,\n userId: identity.userId,\n });\n\n const user = await users.getById(identity.userId);\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: user.id,\n description: `User logged in via OAuth2 (${provider})`,\n metadata: { provider, providerUserId: profile.sub },\n });\n\n // Auto-promote to admin if configured\n await this.ensureAdminRole(user, userRealmName);\n\n return user;\n }\n\n if (!profile.email) {\n this.log.debug(\"OAuth2 profile has no email, returning profile as-is\", {\n provider,\n profileSub: profile.sub,\n });\n return {\n id: profile.sub,\n ...profile,\n };\n }\n\n const existing = await users.findOne({\n where: {\n realm: realm.name,\n email: profile.email,\n },\n });\n\n if (existing) {\n // Refuse auto-link if the OAuth provider explicitly says email is not verified\n if (profile.email_verified === false) {\n this.log.warn(\n \"OAuth2 profile email not verified by provider, refusing auto-link\",\n { provider, email: profile.email, userId: existing.id },\n );\n throw new BadRequestError(\n \"Cannot link account: email not verified by provider\",\n );\n }\n\n this.log.debug(\"Linking OAuth2 profile to existing user by email\", {\n provider,\n profileSub: profile.sub,\n userId: existing.id,\n email: profile.email,\n });\n await identities.create({\n provider,\n providerUserId: profile.sub,\n userId: existing.id,\n });\n\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userId: existing.id,\n userEmail: existing.email ?? undefined,\n userRealm: realm.name,\n resourceId: existing.id,\n description: `OAuth2 identity linked to existing user (${provider})`,\n metadata: { provider, providerUserId: profile.sub, linked: true },\n });\n\n // Auto-promote to admin if configured\n await this.ensureAdminRole(existing, userRealmName);\n\n return existing;\n }\n\n const realmSettings = await realm.getSettings();\n const adminEmails = realmSettings?.adminEmails ?? [];\n const isAdmin = profile.email && adminEmails.includes(profile.email);\n\n if (realmSettings?.registrationAllowed === false && !isAdmin) {\n this.log.warn(\"Registration not allowed for realm via OAuth2\", {\n provider,\n userRealmName,\n });\n throw new BadRequestError(\"Account doesn't exist\");\n }\n\n const username = await this.generateUniqueUsername(\n profile,\n realmSettings,\n users,\n userRealmName,\n );\n\n const user = await users.create({\n realm: realm.name,\n username,\n email: profile.email,\n firstName: profile.given_name,\n lastName: profile.family_name,\n // we trust the OAuth2 provider\n emailVerified: true,\n roles: realmSettings.defaultRoles,\n });\n\n if (profile.picture) {\n this.log.debug(\"Fetching user profile picture from OAuth2 provider\", {\n provider,\n url: profile.picture,\n });\n try {\n const response = await fetch(profile.picture);\n const file = this.fsp.createFile({\n response,\n });\n if (response.ok && response.body) {\n const fileEntity = await this.fileController.uploadFile(\n {\n body: { file },\n },\n {\n user,\n },\n );\n await users.updateById(user.id, { picture: fileEntity.id });\n }\n } catch (error) {\n this.log.warn(\"Failed to fetch user profile picture\", error);\n }\n }\n\n await this.identities(userRealmName).create({\n provider,\n providerUserId: profile.sub,\n userId: user.id,\n });\n\n this.log.info(\"New user created via OAuth2 link\", {\n provider,\n userId: user.id,\n email: user.email,\n username: user.username,\n });\n\n // Audit: user created via OAuth\n await this.userAudits(userRealmName)?.user.log(\"create\", {\n resourceType: \"user\",\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: user.id,\n description: `User created via OAuth2 (${provider})`,\n metadata: {\n provider,\n providerUserId: profile.sub,\n username: user.username,\n email: user.email,\n },\n });\n\n // Audit: login event\n await this.sessionAudits(userRealmName)?.auth.log(\"login\", {\n userId: user.id,\n userEmail: user.email ?? undefined,\n userRealm: realm.name,\n resourceId: user.id,\n description: `First login via OAuth2 (${provider})`,\n metadata: { provider, providerUserId: profile.sub, firstLogin: true },\n });\n\n // Auto-promote to admin if configured\n await this.ensureAdminRole(user, userRealmName);\n\n return user;\n }\n}\n","import { $context, AlephaError } from \"alepha\";\nimport { AlephaApiKeys, ApiKeyService } from \"alepha/api/keys\";\nimport {\n AlephaOAuth,\n OAuthClientService,\n oauthOptions,\n} from \"alepha/api/oauth\";\nimport { $parameter, AlephaApiParameters } from \"alepha/api/parameters\";\nimport { AlephaApiVerification } from \"alepha/api/verifications\";\nimport { mcpStreamableHttpOptions } from \"alepha/mcp\";\nimport type { Repository } from \"alepha/orm\";\nimport {\n $issuer,\n $permission,\n type IssuerPrimitive,\n type IssuerPrimitiveOptions,\n type IssuerResolver,\n SecurityProvider,\n} from \"alepha/security\";\nimport {\n $authApple,\n $authCredentials,\n $authFacebook,\n $authFederationClient,\n $authFranceConnect,\n $authGithub,\n $authGoogle,\n $authMicrosoft,\n type AuthPrimitive,\n type Credentials,\n type LinkAccountOptions,\n type WithLinkFn,\n type WithLoginFn,\n} from \"alepha/server/auth\";\nimport {\n type RealmAuthSettings,\n realmAuthSettingsAtom,\n} from \"../atoms/realmAuthSettingsAtom.ts\";\nimport { SessionAudits } from \"../audits/SessionAudits.ts\";\nimport { UserAudits } from \"../audits/UserAudits.ts\";\nimport { UserBuckets } from \"../buckets/UserBuckets.ts\";\nimport type { identities } from \"../entities/identities.ts\";\nimport type { sessions } from \"../entities/sessions.ts\";\nimport { DEFAULT_USER_REALM_NAME, type users } from \"../entities/users.ts\";\nimport { UserJobs } from \"../jobs/UserJobs.ts\";\nimport { UserNotifications } from \"../notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"../providers/RealmProvider.ts\";\nimport { SessionService } from \"../services/SessionService.ts\";\n\nexport type RealmPrimitive = IssuerPrimitive & WithLinkFn & WithLoginFn;\n\n/**\n * Already configured realm for user management.\n *\n * Realm contains two roles: `admin` and `user`.\n *\n * - `admin`: Has full access to all resources and permissions.\n * - `user`: Has access to their own resources and permissions, but cannot access admin-level resources.\n *\n * Realm uses session management for handling user sessions.\n *\n * Environment Variables:\n * - `APP_SECRET`: Secret key for signing tokens (if not provided in options).\n */\n\nexport const $realm = (options: RealmOptions = {}): RealmPrimitive => {\n const { alepha } = $context();\n const sessionService = alepha.inject(SessionService);\n const securityProvider = alepha.inject(SecurityProvider);\n const realmProvider = alepha.inject(RealmProvider);\n\n const name = options.issuer?.name ?? DEFAULT_USER_REALM_NAME;\n\n options.settings ??= {};\n\n // Merge features with defaults\n const features: RealmFeatures = {\n jobs: false,\n notifications: false,\n apiKeys: false,\n oauth: false,\n parameters: false,\n avatars: false,\n audits: false,\n ...options.features,\n };\n\n // When notifications are disabled, force verification-dependent settings to false\n // These features require sending codes via email/SMS which won't work without notifications\n if (!features.notifications) {\n options.settings.verifyEmailRequired = false;\n options.settings.verifyPhoneRequired = false;\n options.settings.resetPasswordAllowed = false;\n }\n\n const realmRegistration = realmProvider.register(name, options);\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Enable features based on configuration\n // Each feature registers its wrapper service which internally uses the module primitives\n\n if (features.avatars) {\n alepha.with(UserBuckets);\n }\n\n if (features.audits) {\n alepha.with(UserAudits);\n alepha.with(SessionAudits);\n }\n\n if (features.jobs) {\n alepha.with(UserJobs);\n }\n\n if (features.notifications) {\n alepha.with(UserNotifications);\n alepha.with(AlephaApiVerification);\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Collect custom resolvers that will be registered during $issuer.onInit()\n // This ensures they are registered AFTER the realm is created (not on the default test realm)\n const customResolvers: IssuerResolver[] = [\n ...(options.issuer?.resolvers ?? []),\n ];\n\n // Enable API key authentication - must be added to customResolvers before $issuer() call\n if (features.apiKeys) {\n alepha.with(AlephaApiKeys);\n const apiKeyService = alepha.inject(ApiKeyService);\n customResolvers.push(apiKeyService.createResolver());\n }\n\n const realm: RealmPrimitive = $issuer({\n ...options.issuer,\n name,\n secret: options.secret ?? securityProvider.secretKey,\n resolvers: customResolvers,\n roles: options.issuer?.roles ?? [\n {\n name: \"admin\",\n permissions: [\n {\n name: \"*\",\n },\n ],\n },\n {\n name: \"user\",\n default: true,\n permissions: [\n {\n name: \"*\",\n ownership: true,\n exclude: [\"admin:*\"],\n },\n ],\n },\n ],\n settings: {\n accessToken: {\n expiration: [15, \"minutes\"],\n },\n refreshToken: {\n expiration: [30, \"days\"],\n },\n onCreateSession: async (user, config) => {\n return sessionService.createSession(\n user,\n config.expiresIn,\n undefined,\n config.clientId,\n );\n },\n onRefreshSession: async (refreshToken) => {\n return sessionService.refreshSession(refreshToken);\n },\n onDeleteSession: async (refreshToken) => {\n await sessionService.deleteSession(refreshToken);\n },\n ...options.issuer?.settings,\n },\n });\n\n $permission({\n name: \"admin:access\",\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Enable the OAuth 2.1 authorization server.\n // The OAuth module is realm-agnostic: it mints access tokens through an\n // issuer handed to it via `registerIssuer`. Here we register the realm's own\n // issuer plus a loader that maps a `users` row to a `UserAccount`.\n if (features.oauth) {\n alepha.with(AlephaOAuth);\n const oauthService = alepha.inject(OAuthClientService);\n\n // Point the OAuth controller at this realm so its endpoints mint tokens\n // through the issuer we register below. Merge with the current value so a\n // caller-configured `resource` path is preserved.\n const currentOAuthOptions =\n alepha.get(oauthOptions) ?? oauthOptions.options.default;\n alepha.set(oauthOptions, { ...currentOAuthOptions, realm: name });\n\n const loadUser = async (userId: string) => {\n const user = await sessionService.users(name).findOne({\n where: { id: { eq: userId }, realm: name },\n });\n if (!user) {\n throw new AlephaError(`User '${userId}' not found in realm '${name}'`);\n }\n const composedName =\n [user.firstName, user.lastName]\n .filter((s): s is string => !!s?.trim())\n .join(\" \")\n .trim() || undefined;\n return {\n id: user.id,\n roles: user.roles,\n name: composedName,\n email: user.email,\n username: user.username,\n picture: user.picture,\n organization: user.organizationId ?? undefined,\n realm: name,\n };\n };\n\n oauthService.registerIssuer(name, realm, loadUser);\n\n // Tell the MCP Streamable HTTP transport to challenge unauthenticated\n // requests with an RFC 9728 401 (`WWW-Authenticate`), so MCP clients\n // can discover this OAuth server. Decoupled: the transport never\n // imports the OAuth module — it just reads its own options atom.\n // Harmless when no MCP transport is mounted; the atom is simply set.\n const currentMcpOptions =\n alepha.get(mcpStreamableHttpOptions) ??\n mcpStreamableHttpOptions.options.default;\n alepha.set(mcpStreamableHttpOptions, {\n ...currentMcpOptions,\n requireAuth: true,\n });\n }\n\n realm.link = (name: string) => {\n return (ctx: LinkAccountOptions) =>\n sessionService.link(name, ctx.user, realm.name);\n };\n\n realm.login = (name: string) => {\n return async (credentials: Credentials) => {\n const user = await sessionService.login(\n name,\n credentials.username,\n credentials.password,\n realm.name,\n );\n // Compose display name from first+last for OIDC `name` claim.\n // Without this, credentials-registered users appear as \"Anonymous User\".\n const composedName =\n [user.firstName, user.lastName]\n .filter((s): s is string => !!s?.trim())\n .join(\" \")\n .trim() || undefined;\n return { ...user, name: composedName };\n };\n };\n\n const identities = options.identities ?? {\n credentials: true,\n };\n\n if (identities) {\n const auth: Record<string, AuthPrimitive> = {};\n if (identities.credentials) {\n auth.credentials = $authCredentials(realm);\n } else {\n // if credentials auth is disabled, disable registration as well\n realmRegistration.settings.registrationAllowed = false;\n }\n\n if (identities.google) {\n auth.google = $authGoogle(realm);\n }\n\n if (identities.github) {\n auth.github = $authGithub(realm);\n }\n\n if (identities.apple) {\n auth.apple = $authApple(realm);\n }\n\n if (identities.facebook) {\n auth.facebook = $authFacebook(realm);\n }\n\n if (identities.microsoft) {\n auth.microsoft = $authMicrosoft(realm);\n }\n\n if (identities.franceconnect) {\n auth.franceconnect = $authFranceConnect(realm);\n }\n\n alepha.with(() => auth);\n\n if (identities.federated) {\n const fed = $authFederationClient({\n realm,\n brokerUrl: identities.federated.brokerUrl,\n publicKeyPem: identities.federated.publicKey,\n });\n alepha.with(() => ({ federationCallback: fed.callback }));\n }\n }\n\n if (features.parameters) {\n alepha.with(AlephaApiParameters);\n const settingsParam = $parameter({\n name: `api.realms.settings.${name}`,\n description: `Authentication and registration settings for realm \"${name}\"`,\n schema: realmAuthSettingsAtom.schema,\n default: realmRegistration.settings,\n });\n realmRegistration.settingsParameter = settingsParam;\n alepha.with(() => ({ [`realmSettings_${name}`]: settingsParam }));\n }\n\n return realm;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RealmFeatures {\n /**\n * Will enable Job module.\n *\n * - Enable session purge functionality for cleaning up expired sessions.\n *\n * @default false\n */\n jobs?: boolean;\n\n /**\n * Enable notification system for password reset, verification emails, etc.\n *\n * @default false\n */\n notifications?: boolean;\n\n /**\n * Enable API key authentication for programmatic access.\n *\n * When enabled, users can create API keys to access protected endpoints\n * without using JWT tokens. API keys are useful for:\n * - Programmatic access (CLI tools, scripts)\n * - Long-lived authentication tokens\n * - Third-party integrations (MCP servers)\n *\n * API keys can be passed via:\n * - Query parameter: `?api_key=ak_xxx`\n * - Bearer header: `Authorization: Bearer ak_xxx`\n *\n * @default false\n */\n apiKeys?: boolean;\n\n /**\n * Enable the OAuth 2.1 authorization server.\n *\n * Exposes RFC 9728 / RFC 8414 metadata, RFC 7591 dynamic client\n * registration, and PKCE authorize/token endpoints so MCP clients\n * (e.g. Claude) can connect to a protected `/mcp` endpoint without an\n * API key in the query string.\n *\n * @default false\n */\n oauth?: boolean;\n\n /**\n * Enable runtime configuration management.\n *\n * Allows configuring realm settings at runtime with versioning and scheduled activation.\n *\n * @default false\n */\n parameters?: boolean;\n\n /**\n * Enable avatar uploads for user profiles.\n *\n * @default false\n */\n avatars?: boolean;\n\n /**\n * Enable audit trail for compliance and event logging.\n *\n * @default false\n */\n audits?: boolean;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RealmOptions {\n /**\n * Secret key for signing tokens.\n *\n * If not provided, the secret from the SecurityProvider will be used (usually from the APP_SECRET environment variable).\n */\n secret?: string;\n\n /**\n * Issuer configuration options.\n *\n * It's already pre-configured for user management with admin and user roles.\n */\n issuer?: Partial<IssuerPrimitiveOptions>;\n\n /**\n * Override entities.\n */\n entities?: {\n users?: Repository<typeof users.schema>;\n identities?: Repository<typeof identities.schema>;\n sessions?: Repository<typeof sessions.schema>;\n };\n\n settings?: Partial<RealmAuthSettings>;\n\n identities?: {\n credentials?: true;\n google?: true;\n github?: true;\n apple?: true;\n facebook?: true;\n microsoft?: true;\n franceconnect?: true;\n /**\n * Federated social login via a central broker (the platform).\n *\n * The broker performs the real OIDC dance with Google/Apple on a single\n * shared OAuth client and hands this realm a short-lived, asymmetric-signed\n * assertion. This realm verifies it (broker `publicKey`), links a local\n * user, and establishes a session — no per-tenant OAuth client required.\n */\n federated?: {\n /** Broker origin (assertion `iss`), e.g. https://alepha.club. */\n brokerUrl: string;\n /** Broker EdDSA public key (SPKI PEM) used to verify assertions. */\n publicKey: string;\n /** Providers to surface as broker login buttons. */\n providers: Array<\"google\" | \"apple\">;\n };\n };\n\n /**\n * Enable or disable realm features.\n *\n * Features control which modules are loaded with the realm.\n */\n features?: Partial<RealmFeatures>;\n}\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const loginSchema = t.object({\n username: t.text({\n minLength: 3,\n maxLength: 100,\n description: \"Username or email address for login\",\n }),\n password: t.text({\n minLength: 8,\n description: \"User password\",\n }),\n});\n\nexport type LoginInput = Static<typeof loginSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const registerSchema = t.object({\n username: t.string({\n minLength: 3,\n maxLength: 20,\n pattern: /^[a-zA-Z0-9_]+$/,\n description: \"Username for the new account\",\n }),\n email: t.email({\n description: \"Email address for the new account\",\n }),\n password: t.string({\n minLength: 8,\n description: \"Password for the new account\",\n }),\n confirmPassword: t.string({\n minLength: 8,\n description: \"Confirmation of the password\",\n }),\n firstName: t.optional(\n t.string({\n maxLength: 100,\n description: \"User's first name\",\n }),\n ),\n lastName: t.optional(\n t.string({\n maxLength: 100,\n description: \"User's last name\",\n }),\n ),\n});\n\nexport type RegisterInput = Static<typeof registerSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const resetPasswordRequestSchema = t.object({\n email: t.email({\n description: \"Email address to send password reset link\",\n }),\n});\n\nexport const resetPasswordSchema = t.object({\n token: t.string({\n description: \"Password reset token from email\",\n }),\n password: t.string({\n minLength: 8,\n description: \"New password\",\n }),\n confirmPassword: t.string({\n minLength: 8,\n description: \"Confirmation of the new password\",\n }),\n});\n\nexport type ResetPasswordRequest = Static<typeof resetPasswordRequestSchema>;\nexport type ResetPasswordInput = Static<typeof resetPasswordSchema>;\n","import { $module } from \"alepha\";\nimport { SessionAudits } from \"./audits/SessionAudits.ts\";\nimport { UserAudits } from \"./audits/UserAudits.ts\";\nimport { UserBuckets } from \"./buckets/UserBuckets.ts\";\nimport { AdminIdentityController } from \"./controllers/AdminIdentityController.ts\";\nimport { AdminSessionController } from \"./controllers/AdminSessionController.ts\";\nimport { AdminUserController } from \"./controllers/AdminUserController.ts\";\nimport { RealmController } from \"./controllers/RealmController.ts\";\nimport { UserController } from \"./controllers/UserController.ts\";\nimport { UserJobs } from \"./jobs/UserJobs.ts\";\nimport { UserNotifications } from \"./notifications/UserNotifications.ts\";\nimport { RealmProvider } from \"./providers/RealmProvider.ts\";\nimport { CredentialService } from \"./services/CredentialService.ts\";\nimport { IdentityService } from \"./services/IdentityService.ts\";\nimport { RegistrationService } from \"./services/RegistrationService.ts\";\nimport { SessionCrudService } from \"./services/SessionCrudService.ts\";\nimport { SessionService } from \"./services/SessionService.ts\";\nimport { UsernameSlugger } from \"./services/UsernameSlugger.ts\";\nimport { UserService } from \"./services/UserService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./atoms/realmAuthSettingsAtom.ts\";\nexport * from \"./audits/SessionAudits.ts\";\nexport * from \"./audits/UserAudits.ts\";\nexport * from \"./buckets/UserBuckets.ts\";\nexport * from \"./controllers/AdminIdentityController.ts\";\nexport * from \"./controllers/AdminSessionController.ts\";\nexport * from \"./controllers/AdminUserController.ts\";\nexport * from \"./controllers/RealmController.ts\";\nexport * from \"./controllers/UserController.ts\";\nexport * from \"./entities/identities.ts\";\nexport * from \"./entities/sessions.ts\";\nexport * from \"./entities/users.ts\";\nexport * from \"./jobs/UserJobs.ts\";\nexport * from \"./notifications/UserNotifications.ts\";\nexport * from \"./primitives/$realm.ts\";\nexport * from \"./providers/RealmProvider.ts\";\nexport * from \"./schemas/completePasswordResetRequestSchema.ts\";\nexport * from \"./schemas/completeRegistrationRequestSchema.ts\";\nexport * from \"./schemas/createUserSchema.ts\";\nexport * from \"./schemas/identityQuerySchema.ts\";\nexport * from \"./schemas/identityResourceSchema.ts\";\nexport * from \"./schemas/loginSchema.ts\";\nexport * from \"./schemas/passwordResetIntentResponseSchema.ts\";\nexport * from \"./schemas/realmConfigSchema.ts\";\nexport * from \"./schemas/registerSchema.ts\";\nexport * from \"./schemas/registrationIntentResponseSchema.ts\";\nexport * from \"./schemas/resetPasswordSchema.ts\";\nexport * from \"./schemas/sessionQuerySchema.ts\";\nexport * from \"./schemas/sessionResourceSchema.ts\";\nexport * from \"./schemas/updateUserSchema.ts\";\nexport * from \"./schemas/userQuerySchema.ts\";\nexport * from \"./schemas/userResourceSchema.ts\";\nexport * from \"./services/CredentialService.ts\";\nexport * from \"./services/IdentityService.ts\";\nexport * from \"./services/RegistrationService.ts\";\nexport * from \"./services/SessionCrudService.ts\";\nexport * from \"./services/SessionService.ts\";\nexport * from \"./services/UsernameSlugger.ts\";\nexport * from \"./services/UserService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Complete user management with multi-realm support for multi-tenant applications.\n *\n * **Features:**\n * - User registration, login, and profile management\n * - Password reset workflows\n * - Email verification\n * - Session management with multiple devices\n * - Identity management (social logins, SSO)\n * - Multi-realm support for tenant isolation\n * - Credential management\n * - Entities: `users`, `identities`, `sessions`\n *\n * @module alepha.api.users\n */\nexport const AlephaApiUsers = $module({\n name: \"alepha.api.users\",\n services: [\n RealmProvider,\n SessionService,\n SessionCrudService,\n CredentialService,\n RegistrationService,\n UserService,\n UsernameSlugger,\n IdentityService,\n UserController,\n AdminUserController,\n AdminSessionController,\n AdminIdentityController,\n RealmController,\n ],\n variants: [\n UserJobs,\n UserNotifications,\n UserAudits,\n SessionAudits,\n UserBuckets,\n ],\n});\n"],"mappings":"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"}
|