@lalternative/auth 0.18.0 → 0.19.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/README.md CHANGED
@@ -45,17 +45,20 @@ creation and again on every sign-in, so a role removed at the provider is
45
45
  removed here the next time the person signs in.
46
46
 
47
47
  ```ts
48
+ import { createPlatformAuth, ssoFromEnv } from "@lalternative/auth/server"
49
+
48
50
  export const auth = createPlatformAuth({
49
51
  // …
50
- sso: {
51
- issuer: "https://id.urbangate.dev",
52
- clientId: "tornade-admin",
53
- clientSecret: process.env.URBANGATE_CLIENT_SECRET!,
54
- adminRole: "tornade:admin",
55
- },
52
+ sso: ssoFromEnv("tornad", process.env),
56
53
  })
57
54
  ```
58
55
 
56
+ `ssoFromEnv` reads the suite's variables (see [Environment](#environment))
57
+ and is `undefined` while `URBANGATE_CLIENT_SECRET` is unset, so an app boots
58
+ without SSO until the secret reaches it. The client is `<product>-admin` and
59
+ the admin role `<product>:admin`, which is what urbangate declares for every
60
+ product. Pass a `PlatformSsoConfig` by hand only when an app departs from that.
61
+
59
62
  The callback is `/api/auth/callback/urbangate`; register it on the Hydra
60
63
  client. The provider's endpoints are derived from the issuer, so the app boots
61
64
  even when the issuer is unreachable; discovery only adds ID-token verification.
@@ -63,6 +66,24 @@ On the client, `startSso(authClient, { callbackURL: "/admin" })`
63
66
  starts the redirect (Better Auth 1.7 serves generic providers through
64
67
  `signIn.social`, so no client plugin is needed).
65
68
 
69
+ ### Environment
70
+
71
+ Both helpers read one set of variables, typed as `UrbangateEnv`. A secret is
72
+ the switch of what it enables: unset, that part stays off and the app behaves
73
+ as before.
74
+
75
+ | Variable | Enables | Default |
76
+ |---|---|---|
77
+ | `URBANGATE_ISSUER_URL` | — | `https://id.urbangate.dev` |
78
+ | `URBANGATE_PUBLIC_URL` | — | the issuer |
79
+ | `URBANGATE_CLIENT_ID` | — | `<product>-admin` |
80
+ | `URBANGATE_CLIENT_SECRET` | single sign-on | none |
81
+ | `URBANGATE_PROVISIONER_CLIENT_ID` | — | `<product>-provisioner` |
82
+ | `URBANGATE_PROVISIONER_CLIENT_SECRET` | enrolment at the provider, and the key relay | none |
83
+
84
+ `URBANGATE_PUBLIC_URL` is for a dev stack where the front reaches Kratos by
85
+ another address than Hydra's issuer; in production the two are the same host.
86
+
66
87
  ### Magic link
67
88
 
68
89
  Passwordless sign-in by emailed link. Off unless `magicLink` is passed — the
@@ -197,20 +218,23 @@ until `kratosPasswords` is passed. An app upgrading to 0.14.x never changes
197
218
  behaviour by accident.
198
219
 
199
220
  ```ts
221
+ import { createPlatformAuth, kratosPasswordsFromEnv } from "@lalternative/auth/server"
222
+
200
223
  createPlatformAuth({
201
224
  // …
202
- kratosPasswords: {
203
- publicUrl: process.env.URBANGATE_PUBLIC_URL!,
204
- issuer: process.env.URBANGATE_ISSUER_URL!,
205
- clientId: process.env.URBANGATE_PROVISIONER_CLIENT_ID!,
206
- clientSecret: process.env.URBANGATE_PROVISIONER_CLIENT_SECRET!,
207
- role: "spore:user",
208
- product: "spore",
225
+ kratosPasswords: kratosPasswordsFromEnv("spore", process.env, {
209
226
  onProvisioningDeferred: ({ userId, email }) => queueIdentityRepair(userId, email),
210
- },
227
+ }),
211
228
  })
212
229
  ```
213
230
 
231
+ `kratosPasswordsFromEnv` reads the suite's variables (see
232
+ [Environment](#environment)) and is `undefined` while
233
+ `URBANGATE_PROVISIONER_CLIENT_SECRET` is unset. The client is
234
+ `<product>-provisioner` and the customer role `<product>:user`; urbangate
235
+ checks that role against the client's own name, so neither can be anything
236
+ else.
237
+
214
238
  The login form, its copy and its routes do not change, and nobody is
215
239
  redirected: the password is posted to this app as before and checked against
216
240
  Kratos instead of a local hash.
package/dist/server.d.ts CHANGED
@@ -1,8 +1,30 @@
1
1
  import { APIError, Auth, BetterAuthOptions } from 'better-auth';
2
- import { o as PlatformAuthConfig } from './types-COX3VaBw.js';
2
+ import { s as PlatformKratosPasswordConfig, x as PlatformSsoConfig, o as PlatformAuthConfig } from './types-COX3VaBw.js';
3
3
  export { t as PlatformRateLimitConfig, u as PlatformRateLimitRule, v as PlatformSession, w as PlatformSessionData, y as PlatformTwoFactorConfig, z as PlatformUser } from './types-COX3VaBw.js';
4
4
  export { b as ClaimInvitationOptions, C as ClaimOutcome, S as SsoMappedUser, a as SsoProfile, c as claimInvitation, d as completesSignup, h as holdInviteTokenCookie, e as identityIdFromIdToken, f as invitationOutcomeCookie, g as inviteTokenFrom, i as isInvitationFailure, m as mapSsoProfile, p as pinInviteToken, r as releaseInviteTokenCookie } from './sso-profile-CJLK-hNf.js';
5
5
 
6
+ declare const URBANGATE_ISSUER = "https://id.urbangate.dev";
7
+ /**
8
+ * The variables an app deployed in the suite receives to reach urbangate.
9
+ * Each secret is the switch of what it enables: unset, that part stays off.
10
+ */
11
+ interface UrbangateEnv {
12
+ /** Hydra's issuer. Defaults to the suite's. */
13
+ URBANGATE_ISSUER_URL?: string;
14
+ /** Kratos' public URL, when the front reaches it by another address than the issuer. Defaults to the issuer. */
15
+ URBANGATE_PUBLIC_URL?: string;
16
+ /** The back-office's OIDC client. Defaults to `<product>-admin`. */
17
+ URBANGATE_CLIENT_ID?: string;
18
+ /** Set, mounts single sign-on. */
19
+ URBANGATE_CLIENT_SECRET?: string;
20
+ /** The client that enrols customers and issues their keys. Defaults to `<product>-provisioner`. */
21
+ URBANGATE_PROVISIONER_CLIENT_ID?: string;
22
+ /** Set, enrols every verified address at the provider. */
23
+ URBANGATE_PROVISIONER_CLIENT_SECRET?: string;
24
+ }
25
+ declare function ssoFromEnv(product: string, env: UrbangateEnv): PlatformSsoConfig | undefined;
26
+ declare function kratosPasswordsFromEnv(product: string, env: UrbangateEnv, options?: Pick<PlatformKratosPasswordConfig, "onProvisioningDeferred">): PlatformKratosPasswordConfig | undefined;
27
+
6
28
  /**
7
29
  * The value written in place of a password hash once Kratos holds the
8
30
  * password. Better Auth's sign-in route refuses the request before reaching
@@ -247,4 +269,4 @@ declare class KratosSignInError extends APIError {
247
269
  }
248
270
  type PlatformAuth = ReturnType<typeof createPlatformAuth>;
249
271
 
250
- export { type BootstrapAdminClient, type BootstrapAdminPool, type BootstrapFirstAdminInput, type BootstrapFirstAdminResult, type DeleteAccountConfig, type DeleteAccountRequest, type DeleteAccountResult, type DeleteAccountStep, type DeletionOutcome, type IdentityProvisioningConfig, KRATOS_SENTINEL_HASH, type KratosOutcome, KratosSignInError, type PlatformAuth, type ProvisionOutcome, bootstrapFirstAdmin, createPlatformAuth, deleteAccount, isKratosSentinel, provisionIdentity, rememberSignInIdentifier, requestAccountDeletion, updateIdentityPassword };
272
+ export { type BootstrapAdminClient, type BootstrapAdminPool, type BootstrapFirstAdminInput, type BootstrapFirstAdminResult, type DeleteAccountConfig, type DeleteAccountRequest, type DeleteAccountResult, type DeleteAccountStep, type DeletionOutcome, type IdentityProvisioningConfig, KRATOS_SENTINEL_HASH, type KratosOutcome, KratosSignInError, type PlatformAuth, type ProvisionOutcome, URBANGATE_ISSUER, type UrbangateEnv, bootstrapFirstAdmin, createPlatformAuth, deleteAccount, isKratosSentinel, kratosPasswordsFromEnv, provisionIdentity, rememberSignInIdentifier, requestAccountDeletion, ssoFromEnv, updateIdentityPassword };
package/dist/server.js CHANGED
@@ -151,6 +151,31 @@ function resolveRateLimit(config) {
151
151
  };
152
152
  }
153
153
 
154
+ // src/urbangate-env.ts
155
+ var URBANGATE_ISSUER = "https://id.urbangate.dev";
156
+ function ssoFromEnv(product, env) {
157
+ if (!env.URBANGATE_CLIENT_SECRET) return void 0;
158
+ return {
159
+ issuer: env.URBANGATE_ISSUER_URL || URBANGATE_ISSUER,
160
+ clientId: env.URBANGATE_CLIENT_ID || `${product}-admin`,
161
+ clientSecret: env.URBANGATE_CLIENT_SECRET,
162
+ adminRole: `${product}:admin`
163
+ };
164
+ }
165
+ function kratosPasswordsFromEnv(product, env, options = {}) {
166
+ if (!env.URBANGATE_PROVISIONER_CLIENT_SECRET) return void 0;
167
+ const issuer = env.URBANGATE_ISSUER_URL || URBANGATE_ISSUER;
168
+ return {
169
+ issuer,
170
+ publicUrl: env.URBANGATE_PUBLIC_URL || issuer,
171
+ clientId: env.URBANGATE_PROVISIONER_CLIENT_ID || `${product}-provisioner`,
172
+ clientSecret: env.URBANGATE_PROVISIONER_CLIENT_SECRET,
173
+ role: `${product}:user`,
174
+ product,
175
+ ...options
176
+ };
177
+ }
178
+
154
179
  // src/delete-account.ts
155
180
  async function deleteAccount(config, request, deleteLogin) {
156
181
  const warnings = [];
@@ -625,6 +650,7 @@ function sessionEmail(ctx) {
625
650
  export {
626
651
  KRATOS_SENTINEL_HASH,
627
652
  KratosSignInError,
653
+ URBANGATE_ISSUER,
628
654
  bootstrapFirstAdmin,
629
655
  claimInvitation,
630
656
  completesSignup,
@@ -636,12 +662,14 @@ export {
636
662
  inviteTokenFrom,
637
663
  isInvitationFailure,
638
664
  isKratosSentinel,
665
+ kratosPasswordsFromEnv,
639
666
  mapSsoProfile,
640
667
  pinInviteToken,
641
668
  provisionIdentity,
642
669
  releaseInviteTokenCookie,
643
670
  rememberSignInIdentifier,
644
671
  requestAccountDeletion,
672
+ ssoFromEnv,
645
673
  updateIdentityPassword
646
674
  };
647
675
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/kratos-credentials.ts","../src/google-defaults.ts","../src/sso-endpoints.ts","../src/rate-limit.ts","../src/delete-account.ts","../src/bootstrap-admin.ts"],"sourcesContent":["import { betterAuth, APIError, type Auth, type BetterAuthOptions } from \"better-auth\"\nimport { emailOTP, admin, magicLink, twoFactor, genericOAuth } from \"better-auth/plugins\"\nimport type {\n PlatformAuthConfig,\n PlatformAuthMailerType,\n PlatformKratosPasswordConfig,\n PlatformSsoConfig,\n} from \"./types\"\nimport {\n KRATOS_SENTINEL_HASH,\n verifyAgainstKratos,\n type KratosOutcome,\n} from \"./kratos-credentials\"\nimport { provisionIdentity } from \"./identity-provisioning\"\nimport { withGoogleDefaults } from \"./google-defaults\"\nimport {\n identityIdFromIdToken,\n mapSsoProfile,\n roleFromIdToken,\n type SsoProfile,\n} from \"./sso-profile\"\nimport { ssoEndpoints } from \"./sso-endpoints\"\nimport { withSignUpName } from \"./signup-name\"\nimport { resolveRateLimit } from \"./rate-limit\"\n\nconst DEFAULT_EMAIL_SUBJECTS: Record<string, string> = {\n \"email-verification\": \"Verify your account\",\n \"forget-password\": \"Reset your password\",\n \"sign-in\": \"Your sign-in code\",\n}\n\nfunction defaultRenderOtpEmail(otp: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your verification code</h2>\n <p style=\"color:#555;margin-bottom:24px\">Use the code below to continue. It expires in 5 minutes.</p>\n <div style=\"background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700\">\n ${otp}\n </div>\n <p style=\"color:#999;font-size:12px;margin-top:24px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\nfunction defaultRenderMagicLinkEmail(url: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your sign-in link</h2>\n <p style=\"color:#555;margin-bottom:24px\">Click the button below to sign in. The link expires in 5 minutes and works once.</p>\n <a href=\"${url}\" style=\"display:inline-block;background:#111;color:#fff;text-decoration:none;border-radius:8px;padding:14px 28px;font-weight:600\">Sign in</a>\n <p style=\"color:#999;font-size:12px;margin-top:24px;word-break:break-all\">Or paste this address into your browser:<br>${url}</p>\n <p style=\"color:#999;font-size:12px;margin-top:16px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\n/**\n * Creates a Better Auth instance with platform defaults.\n * Each app calls this with its own config (DB, secret, providers, plugins).\n */\nexport function createPlatformAuth(\n config: PlatformAuthConfig,\n): Auth<BetterAuthOptions> {\n const {\n database,\n baseURL,\n secret,\n appName,\n mailer,\n google,\n github,\n plugins = [],\n databaseHooks,\n betaMode = false,\n isInvited,\n emailSubjects,\n renderOtpEmail,\n magicLink: magicLinkConfig,\n rateLimit,\n twoFactor: twoFactorConfig,\n trustedOrigins,\n sso,\n kratosPasswords,\n } = config\n const ssoProviderId = sso?.providerId ?? \"urbangate\"\n\n const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects }\n const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail\n\n // The concrete instance type (with email-otp/admin plugins) is widened to\n // the base Auth type so the published .d.ts stays portable (inferring the\n // full plugin type triggers TS2742 — it can't be named without a zod ref).\n // The admin() plugin's user.role field is re-exposed via module augmentation\n // below, so consumers (e.g. transcript-web me.ts) still see session.user.role.\n return betterAuth({\n database,\n baseURL,\n secret,\n ...(trustedOrigins ? { trustedOrigins } : {}),\n rateLimit: resolveRateLimit(rateLimit),\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n ...(kratosPasswords\n ? {\n password: {\n // Sign-in refuses before reaching the verifier when the account\n // carries no hash, so a sign-up must still write one. It is a\n // constant that validates nothing, never a hash of the password.\n hash: kratosHasher(kratosPasswords),\n verify: kratosVerifier(kratosPasswords),\n },\n }\n : {}),\n },\n // Never auto-merge a social identity into an existing account by matching\n // email. Better Auth links by default (email-verified providers are trusted),\n // so signing in with Google/GitHub on an email already registered would fold\n // that identity into the existing account. We keep each sign-in method its\n // own account: a social login on a taken email is refused, not linked.\n // The suite's own identity provider is the one exception: it verifies\n // emails itself, and the same person must land on the same account\n // whether they signed in here before the SSO existed or not.\n account: {\n accountLinking: sso\n ? { enabled: true, trustedProviders: [ssoProviderId] }\n : { enabled: false },\n },\n // Declared for single sign-on too, not only for Kratos passwords: the\n // suite addresses a person by their provider identity id, and an app key\n // is issued against it, so a row without one cannot ask for a key.\n ...(kratosPasswords || sso\n ? {\n user: {\n additionalFields: {\n identityId: {\n type: \"string\",\n required: false,\n input: false,\n },\n },\n },\n }\n : {}),\n // Naming happens here rather than on /sign-up/email so that every way in\n // is covered: a magic link that signs up bypasses the endpoint entirely\n // and calls createUser straight, with `name: name || \"\"`.\n databaseHooks: {\n ...databaseHooks,\n account: withSsoRoleSync(databaseHooks?.account, sso, ssoProviderId),\n user: {\n ...databaseHooks?.user,\n update: {\n ...databaseHooks?.user?.update,\n // A password sign-up is created unverified and confirmed by its OTP\n // a moment later; a social sign-up may be confirmed by the provider\n // later still. Enrolment follows the address becoming verified,\n // whenever that happens, and is idempotent so it never doubles.\n after: withIdentityProvisioning(\n databaseHooks?.user?.update?.after,\n kratosPasswords,\n ),\n },\n create: {\n ...databaseHooks?.user?.create,\n after: withIdentityProvisioning(\n databaseHooks?.user?.create?.after,\n kratosPasswords,\n ),\n before: async (user: Record<string, unknown>, ctx: unknown) => {\n const named = withSignUpName(user)\n const appHook = databaseHooks?.user?.create?.before\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const applied = await appHook?.(named as any, ctx as any)\n if (applied === false) return false\n if (applied && typeof applied === \"object\" && \"data\" in applied) {\n return { data: withSignUpName(applied.data) }\n }\n return { data: named }\n },\n },\n },\n },\n hooks: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n before: async (ctx: any) => {\n if (kratosPasswords) {\n const body = ctx.body as\n | { email?: string; password?: string; newPassword?: string }\n | undefined\n if (ctx.path === \"/sign-in/email\" && body?.email && body?.password) {\n rememberSignInIdentifier(body.password, body.email)\n }\n // Sign-up and the OTP reset name the address they act on; a change\n // of password only has the session, whose user carries it.\n if (\n (ctx.path === \"/sign-up/email\" ||\n ctx.path === \"/email-otp/reset-password\") &&\n body?.email &&\n body?.password\n ) {\n rememberPasswordOwner(body.password, body.email)\n }\n if (ctx.path === \"/change-password\" && body?.newPassword) {\n const email = sessionEmail(ctx)\n if (email) rememberPasswordOwner(body.newPassword, email)\n }\n }\n if (!betaMode) return\n if (ctx.path !== \"/sign-up/email\") return\n const body = ctx.body as { email?: string; inviteToken?: string } | undefined\n const email = body?.email\n const inviteToken = body?.inviteToken\n if (email && inviteToken && isInvited) {\n const ok = await isInvited(email, inviteToken)\n if (ok) return\n }\n throw new APIError(\"FORBIDDEN\", {\n message: \"Registration is invite-only during the private beta.\",\n })\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n const subject = subjects[type]\n ? `${subjects[type]} - ${appName}`\n : `Your ${appName} code`\n const html = renderEmail(otp, type as PlatformAuthMailerType)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: type as PlatformAuthMailerType,\n otp,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging OTP to stdout for ${email} (${type}): ${otp}`,\n )\n },\n otpLength: 6,\n expiresIn: 300,\n overrideDefaultEmailVerification: true,\n }),\n ...(magicLinkConfig\n ? [\n magicLink({\n expiresIn: magicLinkConfig.expiresIn ?? 300,\n // A magic link that signs up walks past both gates the platform\n // puts on the front door: requireEmailVerification, and the\n // invite-only hook, which only guards /sign-up/email.\n disableSignUp: !magicLinkConfig.allowSignUp,\n async sendMagicLink({ email, url }) {\n const subject =\n magicLinkConfig.subject ?? `Your sign-in link - ${appName}`\n const html = (\n magicLinkConfig.render ?? defaultRenderMagicLinkEmail\n )(url, email)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: \"magic-link\",\n url,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging magic link to stdout for ${email}: ${url}`,\n )\n },\n }),\n ]\n : []),\n admin(),\n ...(sso\n ? [\n genericOAuth({\n config: [\n {\n providerId: ssoProviderId,\n ...ssoEndpoints(sso.issuer),\n clientId: sso.clientId,\n clientSecret: sso.clientSecret,\n scopes: [\"openid\", \"email\", \"profile\", \"offline_access\"],\n pkce: true,\n overrideUserInfo: true,\n disableSignUp: sso.allowSignUp === false,\n mapProfileToUser: (profile) =>\n mapSsoProfile(profile as SsoProfile, sso.adminRole),\n },\n ],\n }),\n ]\n : []),\n ...(twoFactorConfig?.enabled\n ? [\n twoFactor({\n issuer: twoFactorConfig.issuer ?? appName,\n skipVerificationOnEnable:\n twoFactorConfig.skipVerificationOnEnable ?? false,\n }),\n ]\n : []),\n ...plugins, // app-specific plugins (e.g. tanstackStartCookies)\n ],\n socialProviders: {\n // Spread as given rather than rebuilt field by field: anything Better\n // Auth accepts belongs to the app, and a config silently dropped on the\n // way through is how an app ends up writing a plugin to put it back.\n //\n // The two defaults below are the platform's, not Google's: without\n // accessType 'offline' Google never mints a refresh token, and without\n // 'consent' it stops minting one for an account that already consented.\n // A NULL refreshToken means deleting an account can revoke the access\n // token but cannot remove the app from myaccount.google.com/permissions,\n // so the grant outlives the account it belonged to.\n ...(google ? { google: withGoogleDefaults(google) } : {}),\n ...(github ? { github } : {}),\n },\n }) as unknown as Auth<BetterAuthOptions>\n}\n\ntype UserHooks = NonNullable<NonNullable<BetterAuthOptions[\"databaseHooks\"]>[\"user\"]>\ntype UserAfterHook = NonNullable<NonNullable<UserHooks[\"create\"]>[\"after\"]>\n\n/**\n * Gives the new local user an identity at the provider and stores its id.\n *\n * This runs after the insert commits, so it cannot be atomic with the\n * sign-up: a provider that is down leaves `identityId` null and the person\n * registered all the same. The endpoint is idempotent on the address, so the\n * repair re-sends without risking a second identity.\n */\nfunction withIdentityProvisioning(\n own: UserAfterHook | undefined,\n config: PlatformKratosPasswordConfig | undefined,\n): UserAfterHook | undefined {\n if (!config) return own\n return async (user, ctx) => {\n await own?.(user, ctx)\n const record = user as {\n id?: string\n email?: string\n name?: string\n emailVerified?: boolean\n identityId?: unknown\n }\n if (!record.id || !record.email) return\n // Already enrolled: every later update of the row would otherwise call the\n // provider again for nothing.\n if (typeof record.identityId === \"string\" && record.identityId) return\n // An address nobody proved belongs to this person must not reach the\n // provider: enrolment is idempotent on the address, so an unverified one\n // would join them to the identity of whoever actually owns it. A social\n // sign-up whose provider reports the address unverified, and a password\n // sign-up before its OTP, are enrolled once the address is confirmed.\n if (record.emailVerified !== true) return\n\n const outcome = await provisionIdentity(config, {\n email: record.email,\n name: record.name,\n })\n\n if (outcome.status === \"provisioned\" && ctx) {\n await ctx.context.internalAdapter.updateUser(record.id, {\n identityId: outcome.identityId,\n })\n return\n }\n\n await config.onProvisioningDeferred?.({ userId: record.id, email: record.email })\n }\n}\n\ntype AccountHooks = NonNullable<NonNullable<BetterAuthOptions[\"databaseHooks\"]>[\"account\"]>\ntype AccountAfterHook = NonNullable<NonNullable<AccountHooks[\"create\"]>[\"after\"]>\n\n// The admin plugin declares `role` as not settable from input, so the role\n// mapProfileToUser returns is dropped when the OAuth path creates the user.\n// The account row, created then refreshed on every sign-in, carries the ID\n// token: its roles claim is what sets the local role, each time.\nfunction withSsoRoleSync(\n hooks: AccountHooks | undefined,\n sso: PlatformSsoConfig | undefined,\n providerId: string,\n): AccountHooks | undefined {\n if (!sso) return hooks\n const sync: AccountAfterHook = async (account, ctx) => {\n if (account.providerId !== providerId || !ctx) return\n const role = roleFromIdToken(account.idToken, sso.adminRole)\n // Written on every sign-in rather than at creation only: the OAuth path\n // drops what mapProfileToUser returns, and an account that predates this\n // field fills it the next time its holder signs in, with no backfill.\n const identityId = identityIdFromIdToken(account.idToken)\n const update = {\n ...(role ? { role } : {}),\n ...(identityId ? { identityId } : {}),\n }\n if (Object.keys(update).length === 0) return\n await ctx.context.internalAdapter.updateUser(account.userId, update)\n }\n const chain =\n (own: AccountAfterHook | undefined): AccountAfterHook =>\n async (account, ctx) => {\n await own?.(account, ctx)\n await sync(account, ctx)\n }\n return {\n ...hooks,\n create: { ...hooks?.create, after: chain(hooks?.create?.after) },\n update: { ...hooks?.update, after: chain(hooks?.update?.after) },\n }\n}\n\n/**\n * Better Auth's verifier is handed the stored hash and the submitted password,\n * never the address, and the same verifier serves sign-in, password change and\n * account deletion. The address of the sign-in being processed is carried here\n * by the route hook, keyed by the submitted password so two concurrent\n * sign-ins cannot read each other's.\n */\nconst pendingIdentifiers = new Map<string, string>()\n\nexport function rememberSignInIdentifier(password: string, email: string): void {\n pendingIdentifiers.set(password, email.trim().toLowerCase())\n}\n\nfunction takeSignInIdentifier(password: string): string | undefined {\n const email = pendingIdentifiers.get(password)\n pendingIdentifiers.delete(password)\n return email\n}\n\n// The same blind spot on the writing side: `hash` is handed the new password\n// and nothing else, and it is the only place Better Auth exposes it before\n// storing a placeholder in its stead. Sign-up and reset carry the address in\n// their body; a password change carries only a session, so the route hook\n// resolves it there.\nconst pendingPasswordOwners = new Map<string, string>()\n\nfunction rememberPasswordOwner(password: string, email: string): void {\n pendingPasswordOwners.set(password, email.trim().toLowerCase())\n}\n\nfunction takePasswordOwner(password: string): string | undefined {\n const email = pendingPasswordOwners.get(password)\n pendingPasswordOwners.delete(password)\n return email\n}\n\n// Writing a password is the one operation that must reach the provider: a\n// placeholder stored against a password Kratos never received is an account\n// its owner can never open. It is relayed before the local write, so a\n// provider that refuses it fails the request instead of stranding the account.\nfunction kratosHasher(config: PlatformKratosPasswordConfig) {\n return async (password: string): Promise<string> => {\n const email = takePasswordOwner(password)\n if (!email) return KRATOS_SENTINEL_HASH\n\n const outcome = await provisionIdentity(config, { email, password })\n if (outcome.status === \"provisioned\") return KRATOS_SENTINEL_HASH\n\n if (outcome.status === \"unavailable\") {\n throw new KratosSignInError(\n \"SERVICE_UNAVAILABLE\",\n \"IDENTITY_PROVIDER_UNAVAILABLE\",\n \"The identity service is unavailable. Nothing was changed — try again shortly.\",\n )\n }\n throw new KratosSignInError(\n \"UNPROCESSABLE_ENTITY\",\n \"IDENTITY_PASSWORD_REFUSED\",\n \"The identity service refused this password.\",\n )\n }\n}\n\nexport class KratosSignInError extends APIError {\n constructor(\n status:\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"SERVICE_UNAVAILABLE\"\n | \"UNPROCESSABLE_ENTITY\",\n code: string,\n message: string,\n ) {\n super(status, { code, message })\n }\n}\n\nfunction refusalFor(outcome: KratosOutcome): KratosSignInError | undefined {\n switch (outcome.status) {\n case \"no_credential\":\n return new KratosSignInError(\n \"FORBIDDEN\",\n \"IDENTITY_HAS_NO_PASSWORD\",\n \"This account has no password yet at the identity provider. Use the password recovery to set one.\",\n )\n case \"second_factor_required\":\n return new KratosSignInError(\n \"FORBIDDEN\",\n \"SECOND_FACTOR_REQUIRED\",\n \"A second factor is required to sign in.\",\n )\n case \"account_disabled\":\n return new KratosSignInError(\n \"FORBIDDEN\",\n \"ACCOUNT_DISABLED\",\n \"This account is deactivated.\",\n )\n case \"unavailable\":\n return new KratosSignInError(\n \"SERVICE_UNAVAILABLE\",\n \"IDENTITY_PROVIDER_UNAVAILABLE\",\n \"The identity service is unavailable. Your password has not been refused — try again shortly.\",\n )\n default:\n return undefined\n }\n}\n\n/**\n * Fails closed: anything other than an explicit success refuses the sign-in,\n * and only an explicit refusal by Kratos reads as a wrong password. An outage\n * answers 503, so nobody is told their password is wrong and rotates a\n * password that was right.\n */\nfunction kratosVerifier(config: PlatformKratosPasswordConfig) {\n return async ({ password }: { hash: string; password: string }): Promise<boolean> => {\n const email = takeSignInIdentifier(password)\n if (!email) return false\n const outcome = await verifyAgainstKratos(config.publicUrl, { email, password })\n if (outcome.status === \"valid\") return true\n const refusal = refusalFor(outcome)\n if (refusal) throw refusal\n return false\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sessionEmail(ctx: any): string | undefined {\n const email = ctx?.context?.session?.user?.email\n return typeof email === \"string\" ? email : undefined\n}\n\nexport type PlatformAuth = ReturnType<typeof createPlatformAuth>\n\n// Re-export the session contract from /server so consumers that import the\n// auth factory can type api.getSession() without a second import path.\nexport type {\n PlatformUser,\n PlatformSession,\n PlatformSessionData,\n PlatformRateLimitConfig,\n PlatformRateLimitRule,\n PlatformTwoFactorConfig,\n} from \"./types\"\n\n// Invitation claiming runs on the auth callback, where the session is\n// established — the one place every sign-up flow passes through.\nexport {\n claimInvitation,\n completesSignup,\n holdInviteTokenCookie,\n invitationOutcomeCookie,\n inviteTokenFrom,\n isInvitationFailure,\n pinInviteToken,\n releaseInviteTokenCookie,\n} from \"./invitation\"\nexport type { ClaimOutcome, ClaimInvitationOptions } from \"./invitation\"\n\nexport { identityIdFromIdToken, mapSsoProfile } from \"./sso-profile\"\nexport type { SsoProfile, SsoMappedUser } from \"./sso-profile\"\n\nexport { KRATOS_SENTINEL_HASH, isKratosSentinel } from \"./kratos-credentials\"\nexport type { KratosOutcome } from \"./kratos-credentials\"\n\n// The repair path an app schedules for the sign-ups whose provisioning could\n// not reach the provider.\nexport {\n provisionIdentity,\n updateIdentityPassword,\n requestAccountDeletion,\n} from \"./identity-provisioning\"\nexport type {\n IdentityProvisioningConfig,\n ProvisionOutcome,\n DeletionOutcome,\n} from \"./identity-provisioning\"\n\nexport { deleteAccount } from \"./delete-account\"\nexport type {\n DeleteAccountConfig,\n DeleteAccountRequest,\n DeleteAccountResult,\n DeleteAccountStep,\n} from \"./delete-account\"\n\nexport { bootstrapFirstAdmin } from \"./bootstrap-admin\"\nexport type {\n BootstrapAdminPool,\n BootstrapAdminClient,\n BootstrapFirstAdminInput,\n BootstrapFirstAdminResult,\n} from \"./bootstrap-admin\"\n","/**\n * The value written in place of a password hash once Kratos holds the\n * password. Better Auth's sign-in route refuses the request before reaching\n * the verifier when the credential account carries no hash, so a row must\n * exist and be non-empty for the Kratos check to run at all.\n *\n * It is not a hash and cannot become one: argon2/bcrypt/scrypt verification\n * of this string fails on its format, so a build that ever bypassed the\n * custom verifier refuses everyone instead of admitting anyone.\n */\nexport const KRATOS_SENTINEL_HASH = \"kratos:external-credential:no-local-hash\"\n\nexport function isKratosSentinel(hash: string | null | undefined): boolean {\n return hash === KRATOS_SENTINEL_HASH\n}\n\nexport type KratosOutcome =\n | { status: \"valid\"; identityId: string }\n | { status: \"invalid_credentials\" }\n | { status: \"no_credential\" }\n | { status: \"email_not_verified\" }\n | { status: \"second_factor_required\" }\n | { status: \"account_disabled\" }\n | { status: \"unavailable\" }\n\nexport interface KratosCredentialCheck {\n email: string\n password: string\n}\n\ninterface KratosLoginFlow {\n id: string\n ui?: { nodes?: Array<{ attributes?: { name?: string; value?: string } }> }\n}\n\ninterface KratosErrorBody {\n error?: { id?: string; code?: number; reason?: string; message?: string }\n ui?: { messages?: Array<{ id?: number; text?: string; type?: string }> }\n redirect_browser_to?: string\n}\n\ninterface KratosSuccessBody {\n session?: { identity?: { id?: string; state?: string } }\n session_token?: string\n}\n\nconst FLOW_TIMEOUT_MS = 5000\n\nfunction csrfTokenOf(flow: KratosLoginFlow): string | undefined {\n return flow.ui?.nodes?.find((n) => n.attributes?.name === \"csrf_token\")\n ?.attributes?.value\n}\n\n/**\n * Kratos reports a refusal through numbered UI messages rather than a status\n * code of its own; 4000006 is the generic credential refusal, 4000010 an\n * inactive account, 4000002 a missing field. The address-unverified and\n * second-factor cases arrive as a redirect or an aal2 requirement instead.\n */\nfunction outcomeFromMessages(body: KratosErrorBody): KratosOutcome | undefined {\n const ids = (body.ui?.messages ?? []).map((m) => m.id)\n if (ids.includes(4000010)) return { status: \"account_disabled\" }\n if (ids.includes(4000006) || ids.includes(4000002)) {\n return { status: \"invalid_credentials\" }\n }\n const errorId = body.error?.id\n if (errorId === \"session_aal2_required\") {\n return { status: \"second_factor_required\" }\n }\n if (errorId === \"session_verified_address_required\") {\n return { status: \"email_not_verified\" }\n }\n if (errorId === \"browser_location_change_required\") {\n return { status: \"second_factor_required\" }\n }\n return undefined\n}\n\n/**\n * Validates a password against Kratos through the native (API) login flow,\n * which returns the outcome as JSON instead of driving a browser.\n *\n * Every failure that is not an explicit refusal by Kratos answers\n * `unavailable`, never `invalid_credentials`: a person told their password is\n * wrong during an outage changes a password that was right.\n */\nexport async function verifyAgainstKratos(\n publicUrl: string,\n check: KratosCredentialCheck,\n fetchImpl: typeof fetch = fetch,\n): Promise<KratosOutcome> {\n const base = publicUrl.replace(/\\/$/, \"\")\n let flow: KratosLoginFlow\n try {\n const started = await fetchImpl(`${base}/self-service/login/api`, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n signal: AbortSignal.timeout(FLOW_TIMEOUT_MS),\n })\n if (!started.ok) return { status: \"unavailable\" }\n flow = (await started.json()) as KratosLoginFlow\n if (!flow?.id) return { status: \"unavailable\" }\n } catch {\n return { status: \"unavailable\" }\n }\n\n const csrf = csrfTokenOf(flow)\n try {\n const submitted = await fetchImpl(\n `${base}/self-service/login?flow=${encodeURIComponent(flow.id)}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({\n method: \"password\",\n identifier: check.email.trim().toLowerCase(),\n password: check.password,\n ...(csrf ? { csrf_token: csrf } : {}),\n }),\n signal: AbortSignal.timeout(FLOW_TIMEOUT_MS),\n },\n )\n\n if (submitted.ok) {\n const body = (await submitted.json()) as KratosSuccessBody\n const identity = body.session?.identity\n if (!identity?.id) return { status: \"unavailable\" }\n if (identity.state && identity.state !== \"active\") {\n return { status: \"account_disabled\" }\n }\n return { status: \"valid\", identityId: identity.id }\n }\n\n if (submitted.status >= 500) return { status: \"unavailable\" }\n\n const body = (await submitted.json().catch(() => ({}))) as KratosErrorBody\n const mapped = outcomeFromMessages(body)\n if (mapped) return mapped\n if (submitted.status === 400 || submitted.status === 401) {\n return { status: \"invalid_credentials\" }\n }\n if (submitted.status === 403) return { status: \"second_factor_required\" }\n return { status: \"unavailable\" }\n } catch {\n return { status: \"unavailable\" }\n }\n}\n","import type { PlatformAuthConfig } from \"./types\"\n\n/**\n * Applies the platform's Google defaults without touching what the app set.\n *\n * Better Auth also accepts a function returning the options, which cannot be\n * amended without calling it — such a config is passed straight through and\n * owns its own defaults.\n */\nexport function withGoogleDefaults(\n google: NonNullable<PlatformAuthConfig[\"google\"]>,\n): NonNullable<PlatformAuthConfig[\"google\"]> {\n if (typeof google === \"function\") return google\n return {\n ...google,\n accessType: google.accessType ?? \"offline\",\n prompt: google.prompt ?? \"select_account consent\",\n }\n}\n\n","export interface SsoEndpoints {\n discoveryUrl: string\n accountIssuer: string\n authorizationUrl: string\n tokenUrl: string\n userInfoUrl: string\n}\n\n/**\n * Hydra's endpoints, derived from the issuer so that the provider comes up\n * without a discovery fetch: Better Auth refuses to initialise a provider\n * whose discovery failed unless it already knows the account issuer and the\n * endpoints, and that failure would take the whole app down at boot.\n */\nexport function ssoEndpoints(issuer: string): SsoEndpoints {\n const base = issuer.replace(/\\/$/, \"\")\n return {\n discoveryUrl: `${base}/.well-known/openid-configuration`,\n accountIssuer: base,\n authorizationUrl: `${base}/oauth2/auth`,\n tokenUrl: `${base}/oauth2/token`,\n userInfoUrl: `${base}/userinfo`,\n }\n}\n","import type { PlatformRateLimitConfig, PlatformRateLimitRule } from \"./types\"\n\n// Better Auth keys its own rate limiter on NODE_ENV === \"production\", so a\n// deployment that forgets the variable serves sign-in with no brute-force\n// protection and nothing reports it. These are the platform's rules, applied\n// regardless of the environment.\nconst PLATFORM_RULES: Record<string, PlatformRateLimitRule> = {\n \"/sign-in/email\": { window: 60, max: 5 },\n \"/sign-up/email\": { window: 60, max: 5 },\n \"/email-otp/send-verification-otp\": { window: 60, max: 3 },\n \"/email-otp/verify-email\": { window: 60, max: 5 },\n \"/email-otp/reset-password\": { window: 60, max: 5 },\n \"/forget-password\": { window: 60, max: 3 },\n \"/reset-password\": { window: 60, max: 5 },\n \"/sign-in/magic-link\": { window: 60, max: 3 },\n \"/two-factor/verify-totp\": { window: 60, max: 5 },\n \"/two-factor/verify-otp\": { window: 60, max: 5 },\n \"/two-factor/verify-backup-code\": { window: 60, max: 5 },\n \"/two-factor/send-otp\": { window: 60, max: 3 },\n}\n\nexport interface ResolvedRateLimit {\n enabled: boolean\n window: number\n max: number\n storage?: \"memory\" | \"database\" | \"secondary-storage\"\n modelName?: string\n customRules: Record<string, PlatformRateLimitRule>\n}\n\nexport function resolveRateLimit(\n config?: PlatformRateLimitConfig,\n): ResolvedRateLimit {\n return {\n enabled: config?.enabled ?? true,\n window: config?.window ?? 10,\n max: config?.max ?? 100,\n ...(config?.storage ? { storage: config.storage } : {}),\n ...(config?.modelName ? { modelName: config.modelName } : {}),\n customRules: { ...PLATFORM_RULES, ...config?.customRules },\n }\n}\n\nexport { PLATFORM_RULES }\n","import type {\n DeletionOutcome,\n IdentityProvisioningConfig,\n} from \"./identity-provisioning\"\n\nexport interface DeleteAccountConfig {\n /**\n * Ends the person's subscription. Called first and its failure is fatal:\n * an account that is deleted but keeps being charged is far worse than a\n * deletion its owner has to retry. Must be idempotent — no subscription is\n * a success, not an error, because a retried deletion reaches it again.\n *\n * Omit on a product that bills nobody.\n */\n cancelBilling?: (userId: string) => Promise<void>\n /**\n * Purges the product's own data: the domain tables keyed on the user, the\n * object storage prefix, whatever else only this product knows about.\n * Fatal, and called before anything that cannot be undone, so a failure\n * leaves an account its owner can still sign into and delete again.\n *\n * Must be idempotent: `DELETE ... WHERE user_id` on already-purged rows\n * affects none, which is what makes a retry safe.\n */\n purgeDomain?: (userId: string) => Promise<void>\n /**\n * Revokes what third parties still hold: OAuth grants on the person's\n * Google or Bluesky account, API keys, webhook endpoints. Best-effort —\n * the tokens die with the rows anyway, and a right to erasure cannot\n * depend on another company's uptime.\n */\n revokeExternal?: (userId: string) => Promise<void>\n /**\n * Drops this product's role at urbangate. Omit to leave the identity\n * untouched, which is right for a product that does not enrol one.\n */\n identity?: IdentityProvisioningConfig\n /** Reports a step that failed without stopping the deletion. */\n onWarning?: (step: string, error: unknown) => void\n /**\n * The call that asks urbangate to drop the role. Defaults to\n * `requestAccountDeletion`; an app overrides it only in a test.\n */\n requestDeletion?: (\n identity: IdentityProvisioningConfig,\n request: { identityId: string; userId?: string },\n ) => Promise<DeletionOutcome>\n}\n\nexport interface DeleteAccountRequest {\n userId: string\n /** The `identityId` on the local user row; absent on accounts that predate urbangate. */\n identityId?: string | null\n}\n\nexport type DeleteAccountResult =\n | { status: \"deleted\"; warnings: Array<string> }\n | { status: \"failed\"; step: DeleteAccountStep; cause: unknown }\n\nexport type DeleteAccountStep =\n | \"cancel_billing\"\n | \"purge_domain\"\n | \"drop_identity_role\"\n | \"delete_login\"\n\n/**\n * Deletes one product's account, in the order that leaves the least damage\n * when a step fails.\n *\n * No step can be rolled back once the next one has run, and no transaction\n * spans a payment provider, a domain database and an identity provider. What\n * the order buys is that a failure is always recoverable by retrying: billing\n * stops first because a charge that outlives the account is the one outcome\n * nobody notices, the domain data goes next while the account still exists to\n * try again from, and the login row goes last because it is what the person\n * would need to come back.\n *\n * Every step is idempotent, so the retry is safe.\n */\nexport async function deleteAccount(\n config: DeleteAccountConfig,\n request: DeleteAccountRequest,\n deleteLogin: (userId: string) => Promise<void>,\n): Promise<DeleteAccountResult> {\n const warnings: Array<string> = []\n const warn = (step: string, error: unknown) => {\n warnings.push(step)\n config.onWarning?.(step, error)\n }\n\n if (config.cancelBilling) {\n try {\n await config.cancelBilling(request.userId)\n } catch (cause) {\n return { status: \"failed\", step: \"cancel_billing\", cause }\n }\n }\n\n if (config.purgeDomain) {\n try {\n await config.purgeDomain(request.userId)\n } catch (cause) {\n return { status: \"failed\", step: \"purge_domain\", cause }\n }\n }\n\n // Before the login row goes: the tokens live on it, and a row that is gone\n // takes with it the only way to find what to revoke.\n if (config.revokeExternal) {\n try {\n await config.revokeExternal(request.userId)\n } catch (cause) {\n warn(\"revoke_external\", cause)\n }\n }\n\n if (config.identity && request.identityId) {\n // Imported here rather than at the top so this module carries no runtime\n // dependency on the provisioning client: a caller that injects its own\n // never loads it.\n const call =\n config.requestDeletion ??\n (await import(\"./identity-provisioning\")).requestAccountDeletion\n const outcome = await call(config.identity, {\n identityId: request.identityId,\n userId: request.userId,\n })\n if (outcome.status === \"unavailable\") {\n return {\n status: \"failed\",\n step: \"drop_identity_role\",\n cause: new Error(\"urbangate unavailable\"),\n }\n }\n // A rejection is urbangate refusing this request, not a transient fault:\n // retrying sends the same one. The role survives, and the warning is what\n // says so.\n if (outcome.status === \"rejected\") {\n warn(\"drop_identity_role\", new Error(outcome.reason))\n }\n }\n\n try {\n await deleteLogin(request.userId)\n } catch (cause) {\n return { status: \"failed\", step: \"delete_login\", cause }\n }\n\n return { status: \"deleted\", warnings }\n}\n","import type { PlatformAuth } from \"./server\"\n\n/**\n * Minimal Postgres pool contract this module needs — kept structural so the\n * package doesn't pull in `pg` as a dependency; any `pg.Pool` satisfies it.\n */\nexport interface BootstrapAdminPool {\n connect(): Promise<BootstrapAdminClient>\n}\n\nexport interface BootstrapAdminClient {\n query(text: string, values?: unknown[]): Promise<{ rowCount: number | null }>\n release(): void\n}\n\nexport interface BootstrapFirstAdminInput {\n email: string\n password: string\n name: string\n}\n\nexport type BootstrapFirstAdminResult =\n | { ok: true }\n | { ok: false; error: \"already_completed\" }\n\n// The admin() plugin's createUser endpoint isn't in PlatformAuth's published\n// type (see the widening note on createPlatformAuth in ./server), even though\n// it's mounted at runtime by every app that enables admin().\ninterface AuthWithAdminCreateUser {\n api: {\n createUser: (input: {\n body: {\n email: string\n password: string\n name: string\n role: string\n data?: Record<string, unknown>\n }\n }) => Promise<unknown>\n }\n}\n\n/**\n * Creates the very first admin for an app, before any admin exists — the one\n * case the admin() plugin's own `createUser` endpoint can't cover, since it\n * requires an already-authenticated admin session to call.\n *\n * Delegates the actual user/account creation to `auth.api.createUser` (called\n * server-side, with no request/session, so the plugin's own auth check is\n * skipped the same way a trusted server script would be) instead of inserting\n * `user`/`account` rows by hand — that keeps this in sync with whatever\n * Better Auth's internal adapter does (account.issuer, password hashing,\n * future schema changes) rather than re-deriving it and letting the two\n * drift apart.\n *\n * Serializes concurrent callers with a Postgres advisory lock: a plain\n * `WHERE NOT EXISTS` check on an empty `user` table lets two racing requests\n * both pass before either has inserted, minting two admins.\n *\n * Callers are expected to have already applied their own access gate (setup\n * token, allowed-emails list, etc.) — this function only enforces \"at most\n * one admin, ever\".\n */\nexport async function bootstrapFirstAdmin(\n auth: PlatformAuth,\n pool: BootstrapAdminPool,\n input: BootstrapFirstAdminInput,\n): Promise<BootstrapFirstAdminResult> {\n const client = await pool.connect()\n try {\n await client.query(\"SELECT pg_advisory_lock(hashtext($1))\", [\"admin-setup\"])\n\n const existing = await client.query(\n `SELECT 1 FROM \"user\" WHERE role = 'admin'`,\n )\n if (existing.rowCount && existing.rowCount > 0) {\n return { ok: false, error: \"already_completed\" }\n }\n\n const adminApi = (auth as unknown as AuthWithAdminCreateUser).api\n await adminApi.createUser({\n body: {\n email: input.email,\n password: input.password,\n name: input.name,\n role: \"admin\",\n data: { emailVerified: true },\n },\n })\n\n return { ok: true }\n } finally {\n await client.query(\"SELECT pg_advisory_unlock(hashtext($1))\", [\"admin-setup\"])\n client.release()\n }\n}\n"],"mappings":"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","names":["body","body","email"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/kratos-credentials.ts","../src/google-defaults.ts","../src/sso-endpoints.ts","../src/rate-limit.ts","../src/urbangate-env.ts","../src/delete-account.ts","../src/bootstrap-admin.ts"],"sourcesContent":["import { betterAuth, APIError, type Auth, type BetterAuthOptions } from \"better-auth\"\nimport { emailOTP, admin, magicLink, twoFactor, genericOAuth } from \"better-auth/plugins\"\nimport type {\n PlatformAuthConfig,\n PlatformAuthMailerType,\n PlatformKratosPasswordConfig,\n PlatformSsoConfig,\n} from \"./types\"\nimport {\n KRATOS_SENTINEL_HASH,\n verifyAgainstKratos,\n type KratosOutcome,\n} from \"./kratos-credentials\"\nimport { provisionIdentity } from \"./identity-provisioning\"\nimport { withGoogleDefaults } from \"./google-defaults\"\nimport {\n identityIdFromIdToken,\n mapSsoProfile,\n roleFromIdToken,\n type SsoProfile,\n} from \"./sso-profile\"\nimport { ssoEndpoints } from \"./sso-endpoints\"\nimport { withSignUpName } from \"./signup-name\"\nimport { resolveRateLimit } from \"./rate-limit\"\n\nconst DEFAULT_EMAIL_SUBJECTS: Record<string, string> = {\n \"email-verification\": \"Verify your account\",\n \"forget-password\": \"Reset your password\",\n \"sign-in\": \"Your sign-in code\",\n}\n\nfunction defaultRenderOtpEmail(otp: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your verification code</h2>\n <p style=\"color:#555;margin-bottom:24px\">Use the code below to continue. It expires in 5 minutes.</p>\n <div style=\"background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700\">\n ${otp}\n </div>\n <p style=\"color:#999;font-size:12px;margin-top:24px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\nfunction defaultRenderMagicLinkEmail(url: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your sign-in link</h2>\n <p style=\"color:#555;margin-bottom:24px\">Click the button below to sign in. The link expires in 5 minutes and works once.</p>\n <a href=\"${url}\" style=\"display:inline-block;background:#111;color:#fff;text-decoration:none;border-radius:8px;padding:14px 28px;font-weight:600\">Sign in</a>\n <p style=\"color:#999;font-size:12px;margin-top:24px;word-break:break-all\">Or paste this address into your browser:<br>${url}</p>\n <p style=\"color:#999;font-size:12px;margin-top:16px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\n/**\n * Creates a Better Auth instance with platform defaults.\n * Each app calls this with its own config (DB, secret, providers, plugins).\n */\nexport function createPlatformAuth(\n config: PlatformAuthConfig,\n): Auth<BetterAuthOptions> {\n const {\n database,\n baseURL,\n secret,\n appName,\n mailer,\n google,\n github,\n plugins = [],\n databaseHooks,\n betaMode = false,\n isInvited,\n emailSubjects,\n renderOtpEmail,\n magicLink: magicLinkConfig,\n rateLimit,\n twoFactor: twoFactorConfig,\n trustedOrigins,\n sso,\n kratosPasswords,\n } = config\n const ssoProviderId = sso?.providerId ?? \"urbangate\"\n\n const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects }\n const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail\n\n // The concrete instance type (with email-otp/admin plugins) is widened to\n // the base Auth type so the published .d.ts stays portable (inferring the\n // full plugin type triggers TS2742 — it can't be named without a zod ref).\n // The admin() plugin's user.role field is re-exposed via module augmentation\n // below, so consumers (e.g. transcript-web me.ts) still see session.user.role.\n return betterAuth({\n database,\n baseURL,\n secret,\n ...(trustedOrigins ? { trustedOrigins } : {}),\n rateLimit: resolveRateLimit(rateLimit),\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n ...(kratosPasswords\n ? {\n password: {\n // Sign-in refuses before reaching the verifier when the account\n // carries no hash, so a sign-up must still write one. It is a\n // constant that validates nothing, never a hash of the password.\n hash: kratosHasher(kratosPasswords),\n verify: kratosVerifier(kratosPasswords),\n },\n }\n : {}),\n },\n // Never auto-merge a social identity into an existing account by matching\n // email. Better Auth links by default (email-verified providers are trusted),\n // so signing in with Google/GitHub on an email already registered would fold\n // that identity into the existing account. We keep each sign-in method its\n // own account: a social login on a taken email is refused, not linked.\n // The suite's own identity provider is the one exception: it verifies\n // emails itself, and the same person must land on the same account\n // whether they signed in here before the SSO existed or not.\n account: {\n accountLinking: sso\n ? { enabled: true, trustedProviders: [ssoProviderId] }\n : { enabled: false },\n },\n // Declared for single sign-on too, not only for Kratos passwords: the\n // suite addresses a person by their provider identity id, and an app key\n // is issued against it, so a row without one cannot ask for a key.\n ...(kratosPasswords || sso\n ? {\n user: {\n additionalFields: {\n identityId: {\n type: \"string\",\n required: false,\n input: false,\n },\n },\n },\n }\n : {}),\n // Naming happens here rather than on /sign-up/email so that every way in\n // is covered: a magic link that signs up bypasses the endpoint entirely\n // and calls createUser straight, with `name: name || \"\"`.\n databaseHooks: {\n ...databaseHooks,\n account: withSsoRoleSync(databaseHooks?.account, sso, ssoProviderId),\n user: {\n ...databaseHooks?.user,\n update: {\n ...databaseHooks?.user?.update,\n // A password sign-up is created unverified and confirmed by its OTP\n // a moment later; a social sign-up may be confirmed by the provider\n // later still. Enrolment follows the address becoming verified,\n // whenever that happens, and is idempotent so it never doubles.\n after: withIdentityProvisioning(\n databaseHooks?.user?.update?.after,\n kratosPasswords,\n ),\n },\n create: {\n ...databaseHooks?.user?.create,\n after: withIdentityProvisioning(\n databaseHooks?.user?.create?.after,\n kratosPasswords,\n ),\n before: async (user: Record<string, unknown>, ctx: unknown) => {\n const named = withSignUpName(user)\n const appHook = databaseHooks?.user?.create?.before\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const applied = await appHook?.(named as any, ctx as any)\n if (applied === false) return false\n if (applied && typeof applied === \"object\" && \"data\" in applied) {\n return { data: withSignUpName(applied.data) }\n }\n return { data: named }\n },\n },\n },\n },\n hooks: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n before: async (ctx: any) => {\n if (kratosPasswords) {\n const body = ctx.body as\n | { email?: string; password?: string; newPassword?: string }\n | undefined\n if (ctx.path === \"/sign-in/email\" && body?.email && body?.password) {\n rememberSignInIdentifier(body.password, body.email)\n }\n // Sign-up and the OTP reset name the address they act on; a change\n // of password only has the session, whose user carries it.\n if (\n (ctx.path === \"/sign-up/email\" ||\n ctx.path === \"/email-otp/reset-password\") &&\n body?.email &&\n body?.password\n ) {\n rememberPasswordOwner(body.password, body.email)\n }\n if (ctx.path === \"/change-password\" && body?.newPassword) {\n const email = sessionEmail(ctx)\n if (email) rememberPasswordOwner(body.newPassword, email)\n }\n }\n if (!betaMode) return\n if (ctx.path !== \"/sign-up/email\") return\n const body = ctx.body as { email?: string; inviteToken?: string } | undefined\n const email = body?.email\n const inviteToken = body?.inviteToken\n if (email && inviteToken && isInvited) {\n const ok = await isInvited(email, inviteToken)\n if (ok) return\n }\n throw new APIError(\"FORBIDDEN\", {\n message: \"Registration is invite-only during the private beta.\",\n })\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n const subject = subjects[type]\n ? `${subjects[type]} - ${appName}`\n : `Your ${appName} code`\n const html = renderEmail(otp, type as PlatformAuthMailerType)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: type as PlatformAuthMailerType,\n otp,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging OTP to stdout for ${email} (${type}): ${otp}`,\n )\n },\n otpLength: 6,\n expiresIn: 300,\n overrideDefaultEmailVerification: true,\n }),\n ...(magicLinkConfig\n ? [\n magicLink({\n expiresIn: magicLinkConfig.expiresIn ?? 300,\n // A magic link that signs up walks past both gates the platform\n // puts on the front door: requireEmailVerification, and the\n // invite-only hook, which only guards /sign-up/email.\n disableSignUp: !magicLinkConfig.allowSignUp,\n async sendMagicLink({ email, url }) {\n const subject =\n magicLinkConfig.subject ?? `Your sign-in link - ${appName}`\n const html = (\n magicLinkConfig.render ?? defaultRenderMagicLinkEmail\n )(url, email)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: \"magic-link\",\n url,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging magic link to stdout for ${email}: ${url}`,\n )\n },\n }),\n ]\n : []),\n admin(),\n ...(sso\n ? [\n genericOAuth({\n config: [\n {\n providerId: ssoProviderId,\n ...ssoEndpoints(sso.issuer),\n clientId: sso.clientId,\n clientSecret: sso.clientSecret,\n scopes: [\"openid\", \"email\", \"profile\", \"offline_access\"],\n pkce: true,\n overrideUserInfo: true,\n disableSignUp: sso.allowSignUp === false,\n mapProfileToUser: (profile) =>\n mapSsoProfile(profile as SsoProfile, sso.adminRole),\n },\n ],\n }),\n ]\n : []),\n ...(twoFactorConfig?.enabled\n ? [\n twoFactor({\n issuer: twoFactorConfig.issuer ?? appName,\n skipVerificationOnEnable:\n twoFactorConfig.skipVerificationOnEnable ?? false,\n }),\n ]\n : []),\n ...plugins, // app-specific plugins (e.g. tanstackStartCookies)\n ],\n socialProviders: {\n // Spread as given rather than rebuilt field by field: anything Better\n // Auth accepts belongs to the app, and a config silently dropped on the\n // way through is how an app ends up writing a plugin to put it back.\n //\n // The two defaults below are the platform's, not Google's: without\n // accessType 'offline' Google never mints a refresh token, and without\n // 'consent' it stops minting one for an account that already consented.\n // A NULL refreshToken means deleting an account can revoke the access\n // token but cannot remove the app from myaccount.google.com/permissions,\n // so the grant outlives the account it belonged to.\n ...(google ? { google: withGoogleDefaults(google) } : {}),\n ...(github ? { github } : {}),\n },\n }) as unknown as Auth<BetterAuthOptions>\n}\n\ntype UserHooks = NonNullable<NonNullable<BetterAuthOptions[\"databaseHooks\"]>[\"user\"]>\ntype UserAfterHook = NonNullable<NonNullable<UserHooks[\"create\"]>[\"after\"]>\n\n/**\n * Gives the new local user an identity at the provider and stores its id.\n *\n * This runs after the insert commits, so it cannot be atomic with the\n * sign-up: a provider that is down leaves `identityId` null and the person\n * registered all the same. The endpoint is idempotent on the address, so the\n * repair re-sends without risking a second identity.\n */\nfunction withIdentityProvisioning(\n own: UserAfterHook | undefined,\n config: PlatformKratosPasswordConfig | undefined,\n): UserAfterHook | undefined {\n if (!config) return own\n return async (user, ctx) => {\n await own?.(user, ctx)\n const record = user as {\n id?: string\n email?: string\n name?: string\n emailVerified?: boolean\n identityId?: unknown\n }\n if (!record.id || !record.email) return\n // Already enrolled: every later update of the row would otherwise call the\n // provider again for nothing.\n if (typeof record.identityId === \"string\" && record.identityId) return\n // An address nobody proved belongs to this person must not reach the\n // provider: enrolment is idempotent on the address, so an unverified one\n // would join them to the identity of whoever actually owns it. A social\n // sign-up whose provider reports the address unverified, and a password\n // sign-up before its OTP, are enrolled once the address is confirmed.\n if (record.emailVerified !== true) return\n\n const outcome = await provisionIdentity(config, {\n email: record.email,\n name: record.name,\n })\n\n if (outcome.status === \"provisioned\" && ctx) {\n await ctx.context.internalAdapter.updateUser(record.id, {\n identityId: outcome.identityId,\n })\n return\n }\n\n await config.onProvisioningDeferred?.({ userId: record.id, email: record.email })\n }\n}\n\ntype AccountHooks = NonNullable<NonNullable<BetterAuthOptions[\"databaseHooks\"]>[\"account\"]>\ntype AccountAfterHook = NonNullable<NonNullable<AccountHooks[\"create\"]>[\"after\"]>\n\n// The admin plugin declares `role` as not settable from input, so the role\n// mapProfileToUser returns is dropped when the OAuth path creates the user.\n// The account row, created then refreshed on every sign-in, carries the ID\n// token: its roles claim is what sets the local role, each time.\nfunction withSsoRoleSync(\n hooks: AccountHooks | undefined,\n sso: PlatformSsoConfig | undefined,\n providerId: string,\n): AccountHooks | undefined {\n if (!sso) return hooks\n const sync: AccountAfterHook = async (account, ctx) => {\n if (account.providerId !== providerId || !ctx) return\n const role = roleFromIdToken(account.idToken, sso.adminRole)\n // Written on every sign-in rather than at creation only: the OAuth path\n // drops what mapProfileToUser returns, and an account that predates this\n // field fills it the next time its holder signs in, with no backfill.\n const identityId = identityIdFromIdToken(account.idToken)\n const update = {\n ...(role ? { role } : {}),\n ...(identityId ? { identityId } : {}),\n }\n if (Object.keys(update).length === 0) return\n await ctx.context.internalAdapter.updateUser(account.userId, update)\n }\n const chain =\n (own: AccountAfterHook | undefined): AccountAfterHook =>\n async (account, ctx) => {\n await own?.(account, ctx)\n await sync(account, ctx)\n }\n return {\n ...hooks,\n create: { ...hooks?.create, after: chain(hooks?.create?.after) },\n update: { ...hooks?.update, after: chain(hooks?.update?.after) },\n }\n}\n\n/**\n * Better Auth's verifier is handed the stored hash and the submitted password,\n * never the address, and the same verifier serves sign-in, password change and\n * account deletion. The address of the sign-in being processed is carried here\n * by the route hook, keyed by the submitted password so two concurrent\n * sign-ins cannot read each other's.\n */\nconst pendingIdentifiers = new Map<string, string>()\n\nexport function rememberSignInIdentifier(password: string, email: string): void {\n pendingIdentifiers.set(password, email.trim().toLowerCase())\n}\n\nfunction takeSignInIdentifier(password: string): string | undefined {\n const email = pendingIdentifiers.get(password)\n pendingIdentifiers.delete(password)\n return email\n}\n\n// The same blind spot on the writing side: `hash` is handed the new password\n// and nothing else, and it is the only place Better Auth exposes it before\n// storing a placeholder in its stead. Sign-up and reset carry the address in\n// their body; a password change carries only a session, so the route hook\n// resolves it there.\nconst pendingPasswordOwners = new Map<string, string>()\n\nfunction rememberPasswordOwner(password: string, email: string): void {\n pendingPasswordOwners.set(password, email.trim().toLowerCase())\n}\n\nfunction takePasswordOwner(password: string): string | undefined {\n const email = pendingPasswordOwners.get(password)\n pendingPasswordOwners.delete(password)\n return email\n}\n\n// Writing a password is the one operation that must reach the provider: a\n// placeholder stored against a password Kratos never received is an account\n// its owner can never open. It is relayed before the local write, so a\n// provider that refuses it fails the request instead of stranding the account.\nfunction kratosHasher(config: PlatformKratosPasswordConfig) {\n return async (password: string): Promise<string> => {\n const email = takePasswordOwner(password)\n if (!email) return KRATOS_SENTINEL_HASH\n\n const outcome = await provisionIdentity(config, { email, password })\n if (outcome.status === \"provisioned\") return KRATOS_SENTINEL_HASH\n\n if (outcome.status === \"unavailable\") {\n throw new KratosSignInError(\n \"SERVICE_UNAVAILABLE\",\n \"IDENTITY_PROVIDER_UNAVAILABLE\",\n \"The identity service is unavailable. Nothing was changed — try again shortly.\",\n )\n }\n throw new KratosSignInError(\n \"UNPROCESSABLE_ENTITY\",\n \"IDENTITY_PASSWORD_REFUSED\",\n \"The identity service refused this password.\",\n )\n }\n}\n\nexport class KratosSignInError extends APIError {\n constructor(\n status:\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"SERVICE_UNAVAILABLE\"\n | \"UNPROCESSABLE_ENTITY\",\n code: string,\n message: string,\n ) {\n super(status, { code, message })\n }\n}\n\nfunction refusalFor(outcome: KratosOutcome): KratosSignInError | undefined {\n switch (outcome.status) {\n case \"no_credential\":\n return new KratosSignInError(\n \"FORBIDDEN\",\n \"IDENTITY_HAS_NO_PASSWORD\",\n \"This account has no password yet at the identity provider. Use the password recovery to set one.\",\n )\n case \"second_factor_required\":\n return new KratosSignInError(\n \"FORBIDDEN\",\n \"SECOND_FACTOR_REQUIRED\",\n \"A second factor is required to sign in.\",\n )\n case \"account_disabled\":\n return new KratosSignInError(\n \"FORBIDDEN\",\n \"ACCOUNT_DISABLED\",\n \"This account is deactivated.\",\n )\n case \"unavailable\":\n return new KratosSignInError(\n \"SERVICE_UNAVAILABLE\",\n \"IDENTITY_PROVIDER_UNAVAILABLE\",\n \"The identity service is unavailable. Your password has not been refused — try again shortly.\",\n )\n default:\n return undefined\n }\n}\n\n/**\n * Fails closed: anything other than an explicit success refuses the sign-in,\n * and only an explicit refusal by Kratos reads as a wrong password. An outage\n * answers 503, so nobody is told their password is wrong and rotates a\n * password that was right.\n */\nfunction kratosVerifier(config: PlatformKratosPasswordConfig) {\n return async ({ password }: { hash: string; password: string }): Promise<boolean> => {\n const email = takeSignInIdentifier(password)\n if (!email) return false\n const outcome = await verifyAgainstKratos(config.publicUrl, { email, password })\n if (outcome.status === \"valid\") return true\n const refusal = refusalFor(outcome)\n if (refusal) throw refusal\n return false\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sessionEmail(ctx: any): string | undefined {\n const email = ctx?.context?.session?.user?.email\n return typeof email === \"string\" ? email : undefined\n}\n\nexport type PlatformAuth = ReturnType<typeof createPlatformAuth>\n\n// Re-export the session contract from /server so consumers that import the\n// auth factory can type api.getSession() without a second import path.\nexport type {\n PlatformUser,\n PlatformSession,\n PlatformSessionData,\n PlatformRateLimitConfig,\n PlatformRateLimitRule,\n PlatformTwoFactorConfig,\n} from \"./types\"\n\n// Invitation claiming runs on the auth callback, where the session is\n// established — the one place every sign-up flow passes through.\nexport {\n claimInvitation,\n completesSignup,\n holdInviteTokenCookie,\n invitationOutcomeCookie,\n inviteTokenFrom,\n isInvitationFailure,\n pinInviteToken,\n releaseInviteTokenCookie,\n} from \"./invitation\"\nexport type { ClaimOutcome, ClaimInvitationOptions } from \"./invitation\"\n\nexport { identityIdFromIdToken, mapSsoProfile } from \"./sso-profile\"\nexport { URBANGATE_ISSUER, kratosPasswordsFromEnv, ssoFromEnv } from \"./urbangate-env\"\nexport type { UrbangateEnv } from \"./urbangate-env\"\nexport type { SsoProfile, SsoMappedUser } from \"./sso-profile\"\n\nexport { KRATOS_SENTINEL_HASH, isKratosSentinel } from \"./kratos-credentials\"\nexport type { KratosOutcome } from \"./kratos-credentials\"\n\n// The repair path an app schedules for the sign-ups whose provisioning could\n// not reach the provider.\nexport {\n provisionIdentity,\n updateIdentityPassword,\n requestAccountDeletion,\n} from \"./identity-provisioning\"\nexport type {\n IdentityProvisioningConfig,\n ProvisionOutcome,\n DeletionOutcome,\n} from \"./identity-provisioning\"\n\nexport { deleteAccount } from \"./delete-account\"\nexport type {\n DeleteAccountConfig,\n DeleteAccountRequest,\n DeleteAccountResult,\n DeleteAccountStep,\n} from \"./delete-account\"\n\nexport { bootstrapFirstAdmin } from \"./bootstrap-admin\"\nexport type {\n BootstrapAdminPool,\n BootstrapAdminClient,\n BootstrapFirstAdminInput,\n BootstrapFirstAdminResult,\n} from \"./bootstrap-admin\"\n","/**\n * The value written in place of a password hash once Kratos holds the\n * password. Better Auth's sign-in route refuses the request before reaching\n * the verifier when the credential account carries no hash, so a row must\n * exist and be non-empty for the Kratos check to run at all.\n *\n * It is not a hash and cannot become one: argon2/bcrypt/scrypt verification\n * of this string fails on its format, so a build that ever bypassed the\n * custom verifier refuses everyone instead of admitting anyone.\n */\nexport const KRATOS_SENTINEL_HASH = \"kratos:external-credential:no-local-hash\"\n\nexport function isKratosSentinel(hash: string | null | undefined): boolean {\n return hash === KRATOS_SENTINEL_HASH\n}\n\nexport type KratosOutcome =\n | { status: \"valid\"; identityId: string }\n | { status: \"invalid_credentials\" }\n | { status: \"no_credential\" }\n | { status: \"email_not_verified\" }\n | { status: \"second_factor_required\" }\n | { status: \"account_disabled\" }\n | { status: \"unavailable\" }\n\nexport interface KratosCredentialCheck {\n email: string\n password: string\n}\n\ninterface KratosLoginFlow {\n id: string\n ui?: { nodes?: Array<{ attributes?: { name?: string; value?: string } }> }\n}\n\ninterface KratosErrorBody {\n error?: { id?: string; code?: number; reason?: string; message?: string }\n ui?: { messages?: Array<{ id?: number; text?: string; type?: string }> }\n redirect_browser_to?: string\n}\n\ninterface KratosSuccessBody {\n session?: { identity?: { id?: string; state?: string } }\n session_token?: string\n}\n\nconst FLOW_TIMEOUT_MS = 5000\n\nfunction csrfTokenOf(flow: KratosLoginFlow): string | undefined {\n return flow.ui?.nodes?.find((n) => n.attributes?.name === \"csrf_token\")\n ?.attributes?.value\n}\n\n/**\n * Kratos reports a refusal through numbered UI messages rather than a status\n * code of its own; 4000006 is the generic credential refusal, 4000010 an\n * inactive account, 4000002 a missing field. The address-unverified and\n * second-factor cases arrive as a redirect or an aal2 requirement instead.\n */\nfunction outcomeFromMessages(body: KratosErrorBody): KratosOutcome | undefined {\n const ids = (body.ui?.messages ?? []).map((m) => m.id)\n if (ids.includes(4000010)) return { status: \"account_disabled\" }\n if (ids.includes(4000006) || ids.includes(4000002)) {\n return { status: \"invalid_credentials\" }\n }\n const errorId = body.error?.id\n if (errorId === \"session_aal2_required\") {\n return { status: \"second_factor_required\" }\n }\n if (errorId === \"session_verified_address_required\") {\n return { status: \"email_not_verified\" }\n }\n if (errorId === \"browser_location_change_required\") {\n return { status: \"second_factor_required\" }\n }\n return undefined\n}\n\n/**\n * Validates a password against Kratos through the native (API) login flow,\n * which returns the outcome as JSON instead of driving a browser.\n *\n * Every failure that is not an explicit refusal by Kratos answers\n * `unavailable`, never `invalid_credentials`: a person told their password is\n * wrong during an outage changes a password that was right.\n */\nexport async function verifyAgainstKratos(\n publicUrl: string,\n check: KratosCredentialCheck,\n fetchImpl: typeof fetch = fetch,\n): Promise<KratosOutcome> {\n const base = publicUrl.replace(/\\/$/, \"\")\n let flow: KratosLoginFlow\n try {\n const started = await fetchImpl(`${base}/self-service/login/api`, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n signal: AbortSignal.timeout(FLOW_TIMEOUT_MS),\n })\n if (!started.ok) return { status: \"unavailable\" }\n flow = (await started.json()) as KratosLoginFlow\n if (!flow?.id) return { status: \"unavailable\" }\n } catch {\n return { status: \"unavailable\" }\n }\n\n const csrf = csrfTokenOf(flow)\n try {\n const submitted = await fetchImpl(\n `${base}/self-service/login?flow=${encodeURIComponent(flow.id)}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({\n method: \"password\",\n identifier: check.email.trim().toLowerCase(),\n password: check.password,\n ...(csrf ? { csrf_token: csrf } : {}),\n }),\n signal: AbortSignal.timeout(FLOW_TIMEOUT_MS),\n },\n )\n\n if (submitted.ok) {\n const body = (await submitted.json()) as KratosSuccessBody\n const identity = body.session?.identity\n if (!identity?.id) return { status: \"unavailable\" }\n if (identity.state && identity.state !== \"active\") {\n return { status: \"account_disabled\" }\n }\n return { status: \"valid\", identityId: identity.id }\n }\n\n if (submitted.status >= 500) return { status: \"unavailable\" }\n\n const body = (await submitted.json().catch(() => ({}))) as KratosErrorBody\n const mapped = outcomeFromMessages(body)\n if (mapped) return mapped\n if (submitted.status === 400 || submitted.status === 401) {\n return { status: \"invalid_credentials\" }\n }\n if (submitted.status === 403) return { status: \"second_factor_required\" }\n return { status: \"unavailable\" }\n } catch {\n return { status: \"unavailable\" }\n }\n}\n","import type { PlatformAuthConfig } from \"./types\"\n\n/**\n * Applies the platform's Google defaults without touching what the app set.\n *\n * Better Auth also accepts a function returning the options, which cannot be\n * amended without calling it — such a config is passed straight through and\n * owns its own defaults.\n */\nexport function withGoogleDefaults(\n google: NonNullable<PlatformAuthConfig[\"google\"]>,\n): NonNullable<PlatformAuthConfig[\"google\"]> {\n if (typeof google === \"function\") return google\n return {\n ...google,\n accessType: google.accessType ?? \"offline\",\n prompt: google.prompt ?? \"select_account consent\",\n }\n}\n\n","export interface SsoEndpoints {\n discoveryUrl: string\n accountIssuer: string\n authorizationUrl: string\n tokenUrl: string\n userInfoUrl: string\n}\n\n/**\n * Hydra's endpoints, derived from the issuer so that the provider comes up\n * without a discovery fetch: Better Auth refuses to initialise a provider\n * whose discovery failed unless it already knows the account issuer and the\n * endpoints, and that failure would take the whole app down at boot.\n */\nexport function ssoEndpoints(issuer: string): SsoEndpoints {\n const base = issuer.replace(/\\/$/, \"\")\n return {\n discoveryUrl: `${base}/.well-known/openid-configuration`,\n accountIssuer: base,\n authorizationUrl: `${base}/oauth2/auth`,\n tokenUrl: `${base}/oauth2/token`,\n userInfoUrl: `${base}/userinfo`,\n }\n}\n","import type { PlatformRateLimitConfig, PlatformRateLimitRule } from \"./types\"\n\n// Better Auth keys its own rate limiter on NODE_ENV === \"production\", so a\n// deployment that forgets the variable serves sign-in with no brute-force\n// protection and nothing reports it. These are the platform's rules, applied\n// regardless of the environment.\nconst PLATFORM_RULES: Record<string, PlatformRateLimitRule> = {\n \"/sign-in/email\": { window: 60, max: 5 },\n \"/sign-up/email\": { window: 60, max: 5 },\n \"/email-otp/send-verification-otp\": { window: 60, max: 3 },\n \"/email-otp/verify-email\": { window: 60, max: 5 },\n \"/email-otp/reset-password\": { window: 60, max: 5 },\n \"/forget-password\": { window: 60, max: 3 },\n \"/reset-password\": { window: 60, max: 5 },\n \"/sign-in/magic-link\": { window: 60, max: 3 },\n \"/two-factor/verify-totp\": { window: 60, max: 5 },\n \"/two-factor/verify-otp\": { window: 60, max: 5 },\n \"/two-factor/verify-backup-code\": { window: 60, max: 5 },\n \"/two-factor/send-otp\": { window: 60, max: 3 },\n}\n\nexport interface ResolvedRateLimit {\n enabled: boolean\n window: number\n max: number\n storage?: \"memory\" | \"database\" | \"secondary-storage\"\n modelName?: string\n customRules: Record<string, PlatformRateLimitRule>\n}\n\nexport function resolveRateLimit(\n config?: PlatformRateLimitConfig,\n): ResolvedRateLimit {\n return {\n enabled: config?.enabled ?? true,\n window: config?.window ?? 10,\n max: config?.max ?? 100,\n ...(config?.storage ? { storage: config.storage } : {}),\n ...(config?.modelName ? { modelName: config.modelName } : {}),\n customRules: { ...PLATFORM_RULES, ...config?.customRules },\n }\n}\n\nexport { PLATFORM_RULES }\n","import type { PlatformKratosPasswordConfig, PlatformSsoConfig } from \"./types\"\n\nexport const URBANGATE_ISSUER = \"https://id.urbangate.dev\"\n\n/**\n * The variables an app deployed in the suite receives to reach urbangate.\n * Each secret is the switch of what it enables: unset, that part stays off.\n */\nexport interface UrbangateEnv {\n /** Hydra's issuer. Defaults to the suite's. */\n URBANGATE_ISSUER_URL?: string\n /** Kratos' public URL, when the front reaches it by another address than the issuer. Defaults to the issuer. */\n URBANGATE_PUBLIC_URL?: string\n /** The back-office's OIDC client. Defaults to `<product>-admin`. */\n URBANGATE_CLIENT_ID?: string\n /** Set, mounts single sign-on. */\n URBANGATE_CLIENT_SECRET?: string\n /** The client that enrols customers and issues their keys. Defaults to `<product>-provisioner`. */\n URBANGATE_PROVISIONER_CLIENT_ID?: string\n /** Set, enrols every verified address at the provider. */\n URBANGATE_PROVISIONER_CLIENT_SECRET?: string\n}\n\nexport function ssoFromEnv(\n product: string,\n env: UrbangateEnv,\n): PlatformSsoConfig | undefined {\n if (!env.URBANGATE_CLIENT_SECRET) return undefined\n return {\n issuer: env.URBANGATE_ISSUER_URL || URBANGATE_ISSUER,\n clientId: env.URBANGATE_CLIENT_ID || `${product}-admin`,\n clientSecret: env.URBANGATE_CLIENT_SECRET,\n adminRole: `${product}:admin`,\n }\n}\n\nexport function kratosPasswordsFromEnv(\n product: string,\n env: UrbangateEnv,\n options: Pick<PlatformKratosPasswordConfig, \"onProvisioningDeferred\"> = {},\n): PlatformKratosPasswordConfig | undefined {\n if (!env.URBANGATE_PROVISIONER_CLIENT_SECRET) return undefined\n const issuer = env.URBANGATE_ISSUER_URL || URBANGATE_ISSUER\n return {\n issuer,\n publicUrl: env.URBANGATE_PUBLIC_URL || issuer,\n clientId: env.URBANGATE_PROVISIONER_CLIENT_ID || `${product}-provisioner`,\n clientSecret: env.URBANGATE_PROVISIONER_CLIENT_SECRET,\n role: `${product}:user`,\n product,\n ...options,\n }\n}\n","import type {\n DeletionOutcome,\n IdentityProvisioningConfig,\n} from \"./identity-provisioning\"\n\nexport interface DeleteAccountConfig {\n /**\n * Ends the person's subscription. Called first and its failure is fatal:\n * an account that is deleted but keeps being charged is far worse than a\n * deletion its owner has to retry. Must be idempotent — no subscription is\n * a success, not an error, because a retried deletion reaches it again.\n *\n * Omit on a product that bills nobody.\n */\n cancelBilling?: (userId: string) => Promise<void>\n /**\n * Purges the product's own data: the domain tables keyed on the user, the\n * object storage prefix, whatever else only this product knows about.\n * Fatal, and called before anything that cannot be undone, so a failure\n * leaves an account its owner can still sign into and delete again.\n *\n * Must be idempotent: `DELETE ... WHERE user_id` on already-purged rows\n * affects none, which is what makes a retry safe.\n */\n purgeDomain?: (userId: string) => Promise<void>\n /**\n * Revokes what third parties still hold: OAuth grants on the person's\n * Google or Bluesky account, API keys, webhook endpoints. Best-effort —\n * the tokens die with the rows anyway, and a right to erasure cannot\n * depend on another company's uptime.\n */\n revokeExternal?: (userId: string) => Promise<void>\n /**\n * Drops this product's role at urbangate. Omit to leave the identity\n * untouched, which is right for a product that does not enrol one.\n */\n identity?: IdentityProvisioningConfig\n /** Reports a step that failed without stopping the deletion. */\n onWarning?: (step: string, error: unknown) => void\n /**\n * The call that asks urbangate to drop the role. Defaults to\n * `requestAccountDeletion`; an app overrides it only in a test.\n */\n requestDeletion?: (\n identity: IdentityProvisioningConfig,\n request: { identityId: string; userId?: string },\n ) => Promise<DeletionOutcome>\n}\n\nexport interface DeleteAccountRequest {\n userId: string\n /** The `identityId` on the local user row; absent on accounts that predate urbangate. */\n identityId?: string | null\n}\n\nexport type DeleteAccountResult =\n | { status: \"deleted\"; warnings: Array<string> }\n | { status: \"failed\"; step: DeleteAccountStep; cause: unknown }\n\nexport type DeleteAccountStep =\n | \"cancel_billing\"\n | \"purge_domain\"\n | \"drop_identity_role\"\n | \"delete_login\"\n\n/**\n * Deletes one product's account, in the order that leaves the least damage\n * when a step fails.\n *\n * No step can be rolled back once the next one has run, and no transaction\n * spans a payment provider, a domain database and an identity provider. What\n * the order buys is that a failure is always recoverable by retrying: billing\n * stops first because a charge that outlives the account is the one outcome\n * nobody notices, the domain data goes next while the account still exists to\n * try again from, and the login row goes last because it is what the person\n * would need to come back.\n *\n * Every step is idempotent, so the retry is safe.\n */\nexport async function deleteAccount(\n config: DeleteAccountConfig,\n request: DeleteAccountRequest,\n deleteLogin: (userId: string) => Promise<void>,\n): Promise<DeleteAccountResult> {\n const warnings: Array<string> = []\n const warn = (step: string, error: unknown) => {\n warnings.push(step)\n config.onWarning?.(step, error)\n }\n\n if (config.cancelBilling) {\n try {\n await config.cancelBilling(request.userId)\n } catch (cause) {\n return { status: \"failed\", step: \"cancel_billing\", cause }\n }\n }\n\n if (config.purgeDomain) {\n try {\n await config.purgeDomain(request.userId)\n } catch (cause) {\n return { status: \"failed\", step: \"purge_domain\", cause }\n }\n }\n\n // Before the login row goes: the tokens live on it, and a row that is gone\n // takes with it the only way to find what to revoke.\n if (config.revokeExternal) {\n try {\n await config.revokeExternal(request.userId)\n } catch (cause) {\n warn(\"revoke_external\", cause)\n }\n }\n\n if (config.identity && request.identityId) {\n // Imported here rather than at the top so this module carries no runtime\n // dependency on the provisioning client: a caller that injects its own\n // never loads it.\n const call =\n config.requestDeletion ??\n (await import(\"./identity-provisioning\")).requestAccountDeletion\n const outcome = await call(config.identity, {\n identityId: request.identityId,\n userId: request.userId,\n })\n if (outcome.status === \"unavailable\") {\n return {\n status: \"failed\",\n step: \"drop_identity_role\",\n cause: new Error(\"urbangate unavailable\"),\n }\n }\n // A rejection is urbangate refusing this request, not a transient fault:\n // retrying sends the same one. The role survives, and the warning is what\n // says so.\n if (outcome.status === \"rejected\") {\n warn(\"drop_identity_role\", new Error(outcome.reason))\n }\n }\n\n try {\n await deleteLogin(request.userId)\n } catch (cause) {\n return { status: \"failed\", step: \"delete_login\", cause }\n }\n\n return { status: \"deleted\", warnings }\n}\n","import type { PlatformAuth } from \"./server\"\n\n/**\n * Minimal Postgres pool contract this module needs — kept structural so the\n * package doesn't pull in `pg` as a dependency; any `pg.Pool` satisfies it.\n */\nexport interface BootstrapAdminPool {\n connect(): Promise<BootstrapAdminClient>\n}\n\nexport interface BootstrapAdminClient {\n query(text: string, values?: unknown[]): Promise<{ rowCount: number | null }>\n release(): void\n}\n\nexport interface BootstrapFirstAdminInput {\n email: string\n password: string\n name: string\n}\n\nexport type BootstrapFirstAdminResult =\n | { ok: true }\n | { ok: false; error: \"already_completed\" }\n\n// The admin() plugin's createUser endpoint isn't in PlatformAuth's published\n// type (see the widening note on createPlatformAuth in ./server), even though\n// it's mounted at runtime by every app that enables admin().\ninterface AuthWithAdminCreateUser {\n api: {\n createUser: (input: {\n body: {\n email: string\n password: string\n name: string\n role: string\n data?: Record<string, unknown>\n }\n }) => Promise<unknown>\n }\n}\n\n/**\n * Creates the very first admin for an app, before any admin exists — the one\n * case the admin() plugin's own `createUser` endpoint can't cover, since it\n * requires an already-authenticated admin session to call.\n *\n * Delegates the actual user/account creation to `auth.api.createUser` (called\n * server-side, with no request/session, so the plugin's own auth check is\n * skipped the same way a trusted server script would be) instead of inserting\n * `user`/`account` rows by hand — that keeps this in sync with whatever\n * Better Auth's internal adapter does (account.issuer, password hashing,\n * future schema changes) rather than re-deriving it and letting the two\n * drift apart.\n *\n * Serializes concurrent callers with a Postgres advisory lock: a plain\n * `WHERE NOT EXISTS` check on an empty `user` table lets two racing requests\n * both pass before either has inserted, minting two admins.\n *\n * Callers are expected to have already applied their own access gate (setup\n * token, allowed-emails list, etc.) — this function only enforces \"at most\n * one admin, ever\".\n */\nexport async function bootstrapFirstAdmin(\n auth: PlatformAuth,\n pool: BootstrapAdminPool,\n input: BootstrapFirstAdminInput,\n): Promise<BootstrapFirstAdminResult> {\n const client = await pool.connect()\n try {\n await client.query(\"SELECT pg_advisory_lock(hashtext($1))\", [\"admin-setup\"])\n\n const existing = await client.query(\n `SELECT 1 FROM \"user\" WHERE role = 'admin'`,\n )\n if (existing.rowCount && existing.rowCount > 0) {\n return { ok: false, error: \"already_completed\" }\n }\n\n const adminApi = (auth as unknown as AuthWithAdminCreateUser).api\n await adminApi.createUser({\n body: {\n email: input.email,\n password: input.password,\n name: input.name,\n role: \"admin\",\n data: { emailVerified: true },\n },\n })\n\n return { ok: true }\n } finally {\n await client.query(\"SELECT pg_advisory_unlock(hashtext($1))\", [\"admin-setup\"])\n client.release()\n }\n}\n"],"mappings":"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","names":["body","body","email"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalternative/auth",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Shared Better Auth wrapper for L'Alternative apps (server + React client + auth UI)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",