@voyant-travel/auth 0.140.2 → 0.141.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/server.d.ts CHANGED
@@ -232,7 +232,25 @@ export declare function createCustomerBetterAuth<const UserOptions extends Bette
232
232
  disableOrganizationDeletion: true;
233
233
  requireEmailVerificationOnInvitation: true;
234
234
  invitationLimit: number;
235
- }>] | [import("better-auth/plugins").DefaultOrganizationPlugin<{
235
+ }>, {
236
+ id: "bearer";
237
+ version: string;
238
+ hooks: {
239
+ before: {
240
+ matcher(context: import("better-auth").HookEndpointContext): boolean;
241
+ handler: (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
242
+ context: {
243
+ headers: Headers;
244
+ };
245
+ } | undefined>;
246
+ }[];
247
+ after: {
248
+ matcher(context: import("better-auth").HookEndpointContext): true;
249
+ handler: (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>;
250
+ }[];
251
+ };
252
+ options: import("better-auth/plugins").BearerOptions | undefined;
253
+ }] | [import("better-auth/plugins").DefaultOrganizationPlugin<{
236
254
  schema: {
237
255
  organization: {
238
256
  additionalFields: {
@@ -276,4 +294,22 @@ export declare function createCustomerBetterAuth<const UserOptions extends Bette
276
294
  disableOrganizationDeletion: true;
277
295
  requireEmailVerificationOnInvitation: true;
278
296
  invitationLimit: number;
279
- }>, ...BetterAuthPlugin[]]>>;
297
+ }>, {
298
+ id: "bearer";
299
+ version: string;
300
+ hooks: {
301
+ before: {
302
+ matcher(context: import("better-auth").HookEndpointContext): boolean;
303
+ handler: (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
304
+ context: {
305
+ headers: Headers;
306
+ };
307
+ } | undefined>;
308
+ }[];
309
+ after: {
310
+ matcher(context: import("better-auth").HookEndpointContext): true;
311
+ handler: (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>;
312
+ }[];
313
+ };
314
+ options: import("better-auth/plugins").BearerOptions | undefined;
315
+ }, ...BetterAuthPlugin[]]>>;
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@ import { getDb } from "@voyant-travel/db";
3
3
  import { apikeyTable, authAccount, authSession, authUser, authVerification, customerAuthAccount, customerAuthInvitation, customerAuthMember, customerAuthOrganization, customerAuthSession, customerAuthUser, customerAuthVerification, } from "@voyant-travel/db/schema/iam";
4
4
  import { betterAuth } from "better-auth";
5
5
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
6
- import { emailOTP, organization } from "better-auth/plugins";
6
+ import { bearer, emailOTP, organization } from "better-auth/plugins";
7
7
  import { sql } from "drizzle-orm";
8
8
  import { normalizeCustomerBuyerAccountPolicy, } from "./customer-buyer-accounts.js";
9
9
  import { createLocalMemberAccessPlugin, isCustomerSessionActive, isLocalMemberDeactivated, isLocalMemberEmailDeactivated, isLocalMemberSessionActive, } from "./local-member-access.js";
@@ -336,7 +336,13 @@ export function createCustomerBetterAuth(options = {}) {
336
336
  },
337
337
  },
338
338
  extraSchema: options.extraSchema,
339
- plugins: [customerOrganizationPlugin, ...(options.plugins ?? [])],
339
+ // `bearer` lets a direct (cross-origin / native) client authenticate with an
340
+ // `Authorization: Bearer <token>` header instead of a cookie: on sign-in
341
+ // Better Auth returns the session token via `set-auth-token`, and the bearer
342
+ // hook converts the presented token back into a session on later calls. The
343
+ // BFF/same-origin path keeps using host-only cookies untouched. Direct
344
+ // clients dodge third-party-cookie deprecation this way.
345
+ plugins: [customerOrganizationPlugin, bearer(), ...(options.plugins ?? [])],
340
346
  });
341
347
  }
342
348
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { apiKey } from \"@better-auth/api-key\"\nimport { getDb } from \"@voyant-travel/db\"\nimport {\n apikeyTable,\n authAccount,\n authSession,\n authUser,\n authVerification,\n customerAuthAccount,\n customerAuthInvitation,\n customerAuthMember,\n customerAuthOrganization,\n customerAuthSession,\n customerAuthUser,\n customerAuthVerification,\n} from \"@voyant-travel/db/schema/iam\"\nimport { type BetterAuthOptions, betterAuth } from \"better-auth\"\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\"\nimport { emailOTP, organization } from \"better-auth/plugins\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\nimport { sql } from \"drizzle-orm\"\nimport type { AnyPgTable } from \"drizzle-orm/pg-core\"\nimport {\n type CustomerBuyerAccountPolicy,\n normalizeCustomerBuyerAccountPolicy,\n} from \"./customer-buyer-accounts.js\"\nimport {\n createLocalMemberAccessPlugin,\n isCustomerSessionActive,\n isLocalMemberDeactivated,\n isLocalMemberEmailDeactivated,\n isLocalMemberSessionActive,\n} from \"./local-member-access.js\"\nimport { expandTrustedOrigins, getTrustedOrigins } from \"./trusted-origins.js\"\nimport { isFirstAuthUser, provisionCurrentUserProfile } from \"./workspace.js\"\n\nexport function getAuthSecret(realm: \"admin\" | \"customer\" = \"admin\"): string {\n const envName = realm === \"customer\" ? \"BETTER_AUTH_CUSTOMER_SECRET\" : \"BETTER_AUTH_ADMIN_SECRET\"\n const secret = process.env[envName] || \"\"\n\n if (!secret || secret.length < 32) {\n throw new Error(`Missing ${envName} with at least 32 characters`)\n }\n\n return secret\n}\n\nexport type {\n AccountProfileUpdateHandler,\n ApiTokenRotationStore,\n BetterAuthApiTokenManagement,\n HandleAccountProfileRequestOptions,\n HandleApiTokenManagementRequestOptions,\n HandleOrganizationMembersRequestOptions,\n OrganizationMemberRecord,\n OrganizationMembersListHandler,\n OrganizationMembersListInput,\n} from \"./auth-facades.js\"\nexport {\n handleAccountProfileRequest,\n handleApiTokenManagementRequest,\n handleOrganizationMembersRequest,\n} from \"./auth-facades.js\"\n\ntype DefinedBetterAuthUserOptions<UserOptions extends BetterAuthOptions[\"user\"]> =\n UserOptions extends undefined ? Record<PropertyKey, never> : NonNullable<UserOptions>\n\ntype ResolvedBetterAuthChangeEmail<UserOptions extends BetterAuthOptions[\"user\"]> =\n DefinedBetterAuthUserOptions<UserOptions> extends { changeEmail?: infer ChangeEmail }\n ? ChangeEmail extends { enabled: infer Enabled }\n ? Omit<ChangeEmail, \"enabled\"> & { enabled: Enabled }\n : NonNullable<ChangeEmail> & { enabled: true }\n : { enabled: true }\n\ntype ResolvedBetterAuthUserOptions<UserOptions extends BetterAuthOptions[\"user\"]> = Omit<\n DefinedBetterAuthUserOptions<UserOptions>,\n \"changeEmail\"\n> & {\n changeEmail: ResolvedBetterAuthChangeEmail<UserOptions>\n}\n\ntype VoyantBetterAuthPlugins = [\n ReturnType<typeof apiKey>,\n ReturnType<typeof emailOTP>,\n ReturnType<typeof createLocalMemberAccessPlugin>,\n]\n\ntype ResolvedBetterAuthPlugins<_Plugins extends BetterAuthPlugin[] | undefined> =\n | VoyantBetterAuthPlugins\n | BetterAuthPlugin[]\n\nconst DEFAULT_SIGNUP_BLOCK_SURFACES = [\"admin\"] as const\nconst CUSTOMER_SIGNUP_ENDPOINT_SUFFIXES = [\n \"/sign-up/email\",\n \"/phone-number/verify\",\n \"/sign-in/email-otp\",\n] as const\n\nexport interface DisableSignupWhenUsersExistOptions {\n /**\n * Set to false when the consuming app owns all signup admission checks.\n *\n * Defaults to true.\n */\n enabled?: boolean\n /**\n * User surfaces that should keep the single-tenant signup guard. New users\n * with only other explicit surfaces can still be created by customer-facing\n * auth plugins such as phone OTP.\n *\n * Defaults to [\"admin\"]. Users without an explicit surfaces array are\n * treated as admin users for backward compatibility.\n */\n surfaces?: readonly string[]\n}\n\ntype BetterAuthCreateBeforeResult =\n | boolean\n | undefined\n | {\n data: Record<string, unknown>\n }\n\ntype SignupBlockUserPayload = {\n surfaces?: unknown\n}\n\ntype BetterAuthHookContext = {\n path?: string\n}\n\nfunction normalizeSignupBlockSurfaces(\n options: DisableSignupWhenUsersExistOptions | undefined,\n): readonly string[] {\n return options?.surfaces ?? DEFAULT_SIGNUP_BLOCK_SURFACES\n}\n\nfunction isSignupBlockEnabled(options: DisableSignupWhenUsersExistOptions | undefined): boolean {\n return options?.enabled !== false\n}\n\nfunction normalizeSurfaceList(surfaces: readonly string[] | undefined): readonly string[] {\n if (!surfaces) return []\n\n const normalized = new Set<string>()\n for (const surface of surfaces) {\n const trimmed = surface.trim()\n if (trimmed.length > 0) {\n normalized.add(trimmed)\n }\n }\n return Array.from(normalized)\n}\n\nfunction signupBlockAppliesToUser(\n user: SignupBlockUserPayload | undefined,\n blockedSurfaces: readonly string[],\n): boolean {\n if (blockedSurfaces.length === 0) return false\n\n const surfaces = user?.surfaces\n if (Array.isArray(surfaces)) {\n const explicitSurfaces = surfaces.filter(\n (surface): surface is string => typeof surface === \"string\" && surface.trim().length > 0,\n )\n if (explicitSurfaces.length > 0) {\n return explicitSurfaces.some((surface) => blockedSurfaces.includes(surface.trim()))\n }\n }\n\n if (typeof surfaces === \"string\" && surfaces.trim().length > 0) {\n return blockedSurfaces.includes(surfaces.trim())\n }\n\n return blockedSurfaces.includes(\"admin\")\n}\n\nfunction isCustomerSignupCreate(context: BetterAuthHookContext | null | undefined): boolean {\n const path = context?.path\n if (!path) return false\n\n return CUSTOMER_SIGNUP_ENDPOINT_SUFFIXES.some((suffix) => path.endsWith(suffix))\n}\n\nfunction stampCustomerSignupSurfaces(\n user: SignupBlockUserPayload | undefined,\n context: BetterAuthHookContext | null | undefined,\n surfaces: readonly string[],\n): SignupBlockUserPayload | undefined {\n if (surfaces.length === 0 || !isCustomerSignupCreate(context)) {\n return user\n }\n\n return {\n ...(user ?? {}),\n surfaces,\n }\n}\n\ntype ResolvedCreateBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined,\n> = Omit<BetterAuthOptions, \"plugins\" | \"user\"> & {\n plugins: ResolvedBetterAuthPlugins<Plugins>\n user: ResolvedBetterAuthUserOptions<UserOptions>\n}\n\nexport type BetterAuthDrizzleSchema = Record<string, AnyPgTable>\n\nexport type VoyantAuthRealm = \"admin\" | \"customer\"\n\nexport interface BetterAuthRealmTables {\n user: AnyPgTable\n session: AnyPgTable\n account: AnyPgTable\n verification: AnyPgTable\n apikey?: AnyPgTable\n}\n\nconst ADMIN_AUTH_TABLES: BetterAuthRealmTables = {\n user: authUser,\n session: authSession,\n account: authAccount,\n verification: authVerification,\n apikey: apikeyTable,\n}\n\nconst CUSTOMER_AUTH_TABLES = {\n user: customerAuthUser,\n session: customerAuthSession,\n account: customerAuthAccount,\n verification: customerAuthVerification,\n organization: customerAuthOrganization,\n member: customerAuthMember,\n invitation: customerAuthInvitation,\n} satisfies BetterAuthRealmTables & BetterAuthDrizzleSchema\n\nexport interface CreateBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> {\n db?: ReturnType<typeof getDb>\n secret?: string\n baseURL?: string\n basePath?: string\n trustedOrigins?: string[]\n /** Security realm. Existing callers default to the isolated admin realm. */\n realm?: VoyantAuthRealm\n /** Override only when an adapter owns a compatible Better Auth schema. */\n realmTables?: BetterAuthRealmTables\n /**\n * Additional Drizzle tables for Better Auth plugins. The consuming app owns\n * matching migrations for every table passed here.\n */\n extraSchema?: BetterAuthDrizzleSchema\n plugins?: Plugins\n user?: UserOptions\n /** Resolved credentials; the auth package never persists provider secrets. */\n socialProviders?: BetterAuthOptions[\"socialProviders\"]\n emailCodeEnabled?: boolean\n emailPasswordEnabled?: boolean\n /**\n * Surfaces stamped on Better Auth customer self-signups before the bundled\n * single-tenant signup guard evaluates the new user. Applies to Better\n * Auth OTP signup endpoints that create a user as part of verification.\n *\n * Leave undefined to preserve Better Auth's raw user payload.\n */\n customerSignupSurfaces?: readonly string[]\n disableSignupWhenUsersExist?: DisableSignupWhenUsersExistOptions\n /** Called when a user requests a password reset. If not provided, logs to console. */\n sendResetPassword?: (data: {\n user: { email: string; name: string }\n url: string\n token: string\n }) => Promise<void>\n /** Called to send a verification OTP. If not provided, logs to console. */\n sendVerificationOTP?: (data: { email: string; otp: string; type: string }) => Promise<void>\n /**\n * Better Auth session cookie cache: session data rides in a short-lived\n * signed cookie. Enabled by default with a 5-minute TTL. Voyant still\n * validates local membership and session existence against Postgres so a\n * deleted session cannot be revived from the cookie cache. Pass `false` to\n * disable payload caching, or tune `maxAge` (seconds).\n */\n sessionCookieCache?: false | { maxAge?: number }\n /** Better Auth secondary storage, typically Redis/KV, used for distributed rate limits. */\n secondaryStorage?: BetterAuthOptions[\"secondaryStorage\"]\n /** Better Auth rate-limit config. Defaults to secondary storage when present. */\n rateLimit?: BetterAuthOptions[\"rateLimit\"]\n /**\n * Additional Better Auth advanced options. Voyant supplies secure cookies\n * by default except in explicit local development. Set\n * `advanced.useSecureCookies` to override that default.\n */\n advanced?: BetterAuthOptions[\"advanced\"]\n}\n\n/**\n * Framework-agnostic Better Auth factory.\n *\n * Accepts optional overrides for db, secret, baseURL, trustedOrigins.\n * Does NOT depend on Next.js — safe to use in Hono workers, TanStack Start, etc.\n */\nexport function createBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateBetterAuthOptions<UserOptions, Plugins> = {}) {\n const realm = options.realm ?? \"admin\"\n const db = options.db ?? getDb(\"edge\")\n const secret = options.secret ?? getAuthSecret(realm)\n const baseURL =\n options.baseURL ??\n process.env.BETTER_AUTH_URL ??\n process.env.APP_URL ??\n process.env.NEXT_PUBLIC_APP_URL ??\n \"http://localhost:3000\"\n const trustedOrigins = expandTrustedOrigins(\n options.trustedOrigins ?? getTrustedOrigins(),\n baseURL,\n )\n const extraPlugins = options.plugins ?? []\n const realmTables =\n options.realmTables ?? (realm === \"customer\" ? CUSTOMER_AUTH_TABLES : ADMIN_AUTH_TABLES)\n const signupBlockSurfaces = normalizeSignupBlockSurfaces(options.disableSignupWhenUsersExist)\n const signupBlockEnabled = isSignupBlockEnabled(options.disableSignupWhenUsersExist)\n const customerSignupSurfaces = normalizeSurfaceList(options.customerSignupSurfaces)\n const reservedSchemaCollision = Object.keys(options.extraSchema ?? {}).find(\n (modelName) => (realmTables as unknown as BetterAuthDrizzleSchema)[modelName] !== undefined,\n )\n if (reservedSchemaCollision) {\n throw new Error(`extraSchema cannot override reserved auth model: ${reservedSchemaCollision}`)\n }\n const schema = {\n ...realmTables,\n ...(options.extraSchema ?? {}),\n } satisfies BetterAuthDrizzleSchema\n const rateLimit =\n options.rateLimit ??\n (options.secondaryStorage\n ? ({\n enabled: true,\n window: 60,\n max: 100,\n storage: \"secondary-storage\",\n } as BetterAuthOptions[\"rateLimit\"])\n : undefined)\n\n const authOptions = {\n appName: \"Voyant\",\n baseURL,\n ...(options.basePath ? { basePath: options.basePath } : {}),\n secret,\n database: drizzleAdapter(db, {\n provider: \"pg\",\n schema,\n }),\n ...(options.secondaryStorage ? { secondaryStorage: options.secondaryStorage } : {}),\n ...(rateLimit ? { rateLimit } : {}),\n ...(options.sessionCookieCache === false\n ? {}\n : {\n session: {\n cookieCache: {\n enabled: true,\n maxAge: options.sessionCookieCache?.maxAge ?? 300,\n },\n },\n }),\n emailAndPassword: {\n enabled: options.emailPasswordEnabled !== false,\n minPasswordLength: 8,\n maxPasswordLength: 128,\n requireEmailVerification: true,\n revokeSessionsOnPasswordReset: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.\n sendResetPassword:\n options.sendResetPassword ??\n (async ({ user, url }) => {\n console.warn(\n `[Auth] No email provider configured — password reset for ${user.email}: ${url}`,\n )\n }),\n },\n emailVerification: {\n sendOnSignUp: false, // OTP plugin handles this\n autoSignInAfterVerification: true,\n },\n user: {\n ...options.user,\n changeEmail: {\n enabled: true,\n ...options.user?.changeEmail,\n },\n } as ResolvedBetterAuthUserOptions<UserOptions>,\n socialProviders: options.socialProviders ?? {},\n trustedOrigins,\n plugins: [\n ...(realm === \"admin\"\n ? [\n apiKey({\n defaultPrefix: \"voy_\",\n apiKeyHeaders: [\"authorization\"],\n requireName: true,\n keyExpiration: {\n defaultExpiresIn: null,\n },\n }),\n ]\n : []),\n ...(options.emailCodeEnabled === false\n ? []\n : [\n emailOTP({\n // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.\n sendVerificationOTP:\n options.sendVerificationOTP ??\n (async ({ email, otp, type }) => {\n console.warn(`[Auth] OTP for ${email}: ${otp} (${type})`)\n }),\n otpLength: 6,\n expiresIn: 600,\n sendVerificationOnSignUp: true,\n changeEmail: {\n enabled: true,\n },\n }),\n ]),\n createLocalMemberAccessPlugin(\n realm === \"admin\"\n ? {\n isEmailDeactivated: (email) => isLocalMemberEmailDeactivated(db, email),\n isSessionActive: (sessionId, userId) =>\n isLocalMemberSessionActive(db, sessionId, userId),\n isUserDeactivated: (userId) => isLocalMemberDeactivated(db, userId),\n }\n : {\n isEmailDeactivated: async () => false,\n isSessionActive: (sessionId, userId) =>\n isCustomerSessionActive(db, sessionId, userId),\n isUserDeactivated: async () => false,\n },\n ),\n ...extraPlugins,\n ] as ResolvedBetterAuthPlugins<Plugins>,\n ...(realm === \"admin\"\n ? {\n databaseHooks: {\n user: {\n create: {\n // Single-tenant: once a user exists, reject any new-user creation.\n // Covers email sign-up AND social-provider sign-up (Google would\n // otherwise auto-create a user on first OAuth callback). Existing\n // social sign-ins still work because this hook only fires on CREATE.\n // Seed scripts do raw drizzle inserts, so they bypass this hook —\n // which is intentional.\n before: async (user, context): Promise<BetterAuthCreateBeforeResult> => {\n const normalizedUser = stampCustomerSignupSurfaces(\n user as SignupBlockUserPayload,\n context as BetterAuthHookContext | null,\n customerSignupSurfaces,\n )\n\n if (\n !signupBlockEnabled ||\n !signupBlockAppliesToUser(normalizedUser, signupBlockSurfaces)\n ) {\n if (normalizedUser === user) {\n return\n }\n return { data: normalizedUser as Record<string, unknown> }\n }\n\n const [row] = await db\n .select({ count: sql<number>`count(*)::int` })\n .from(realmTables.user)\n if ((row?.count ?? 0) > 0) {\n throw new Error(\"Sign-up is disabled. Ask an admin to invite you.\")\n }\n\n if (normalizedUser !== user) {\n return { data: normalizedUser as Record<string, unknown> }\n }\n },\n after: async (user, context) => {\n if (\n customerSignupSurfaces.length > 0 &&\n isCustomerSignupCreate(context as BetterAuthHookContext | null)\n ) {\n return\n }\n\n // Single-tenant bootstrap: the very first user to register becomes\n // the super-admin. Runs atomically after the `user` row is\n // inserted, so a simple COUNT(*) = 1 check identifies them.\n await provisionCurrentUserProfile(db, {\n userId: user.id,\n name: user.name,\n image: user.image,\n isSuperAdmin: await isFirstAuthUser(db),\n })\n },\n },\n },\n },\n }\n : {}),\n advanced: {\n ...options.advanced,\n cookiePrefix:\n options.advanced?.cookiePrefix ??\n (realm === \"customer\" ? \"voyant-customer\" : \"voyant-admin\"),\n useSecureCookies:\n options.advanced?.useSecureCookies ?? process.env.NODE_ENV !== \"development\",\n },\n } as ResolvedCreateBetterAuthOptions<UserOptions, Plugins>\n\n return betterAuth<ResolvedCreateBetterAuthOptions<UserOptions, Plugins>>(authOptions)\n}\n\nexport type CreateAdminBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> = Omit<CreateBetterAuthOptions<UserOptions, Plugins>, \"realm\" | \"realmTables\">\n\nexport function createAdminBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateAdminBetterAuthOptions<UserOptions, Plugins> = {}) {\n return createBetterAuth({\n ...options,\n realm: \"admin\",\n realmTables: ADMIN_AUTH_TABLES,\n basePath: options.basePath ?? \"/auth/admin\",\n })\n}\n\nexport interface CustomerAuthMethods {\n emailCode?: boolean\n emailPassword?: boolean\n /** Better Auth provider configs such as google, facebook, and apple. */\n socialProviders?: BetterAuthOptions[\"socialProviders\"]\n}\n\nexport type CreateCustomerBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> = Omit<\n CreateBetterAuthOptions<UserOptions, Plugins>,\n | \"realm\"\n | \"realmTables\"\n | \"customerSignupSurfaces\"\n | \"disableSignupWhenUsersExist\"\n | \"socialProviders\"\n | \"emailCodeEnabled\"\n | \"emailPasswordEnabled\"\n> & {\n methods?: CustomerAuthMethods\n accountPolicy?: CustomerBuyerAccountPolicy | null\n sendOrganizationInvitation?: (\n data: {\n id: string\n role: string\n email: string\n organization: { id: string; name: string; slug: string }\n invitation: {\n id: string\n email: string\n role: string\n organizationId: string\n inviterId: string\n status: string\n expiresAt: Date\n }\n inviter: {\n id: string\n role: string\n user: { id: string; name: string; email: string }\n }\n },\n request?: Request,\n ) => Promise<void>\n}\n\n/** Storefront realm factory with isolated tables, cookies, secret, and routes. */\nexport function createCustomerBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateCustomerBetterAuthOptions<UserOptions, Plugins> = {}) {\n const accountPolicy = normalizeCustomerBuyerAccountPolicy(options.accountPolicy)\n const customerOrganizationPlugin = organization({\n // A Better Auth organization is only the membership container. Public\n // creation cannot also create/link the canonical Relationships Organization,\n // so onboarding must go through the provider-neutral orchestration seam.\n allowUserToCreateOrganization: false,\n disableOrganizationDeletion: true,\n requireEmailVerificationOnInvitation: true,\n invitationLimit: options.sendOrganizationInvitation ? 100 : 0,\n ...(options.sendOrganizationInvitation\n ? { sendInvitationEmail: options.sendOrganizationInvitation }\n : {}),\n schema: {\n organization: {\n additionalFields: {\n relationshipOrganizationId: {\n type: \"string\",\n required: false,\n input: false,\n },\n },\n },\n },\n })\n\n return createBetterAuth({\n ...options,\n realm: \"customer\",\n realmTables: CUSTOMER_AUTH_TABLES,\n basePath: options.basePath ?? \"/auth/customer\",\n disableSignupWhenUsersExist: { enabled: false },\n emailCodeEnabled: options.methods?.emailCode ?? true,\n emailPasswordEnabled: options.methods?.emailPassword ?? true,\n socialProviders: options.methods?.socialProviders,\n user: {\n ...options.user,\n additionalFields: {\n ...options.user?.additionalFields,\n personalBuyerEntitlementEligible: {\n type: \"boolean\",\n required: true,\n input: false,\n defaultValue:\n accountPolicy.allowedKinds.includes(\"personal\") &&\n accountPolicy.personalSignup === \"open\",\n },\n relationshipPersonId: {\n type: \"string\",\n required: false,\n input: false,\n },\n },\n },\n extraSchema: options.extraSchema,\n plugins: [customerOrganizationPlugin, ...(options.plugins ?? [])],\n })\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { apiKey } from \"@better-auth/api-key\"\nimport { getDb } from \"@voyant-travel/db\"\nimport {\n apikeyTable,\n authAccount,\n authSession,\n authUser,\n authVerification,\n customerAuthAccount,\n customerAuthInvitation,\n customerAuthMember,\n customerAuthOrganization,\n customerAuthSession,\n customerAuthUser,\n customerAuthVerification,\n} from \"@voyant-travel/db/schema/iam\"\nimport { type BetterAuthOptions, betterAuth } from \"better-auth\"\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\"\nimport { bearer, emailOTP, organization } from \"better-auth/plugins\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\nimport { sql } from \"drizzle-orm\"\nimport type { AnyPgTable } from \"drizzle-orm/pg-core\"\nimport {\n type CustomerBuyerAccountPolicy,\n normalizeCustomerBuyerAccountPolicy,\n} from \"./customer-buyer-accounts.js\"\nimport {\n createLocalMemberAccessPlugin,\n isCustomerSessionActive,\n isLocalMemberDeactivated,\n isLocalMemberEmailDeactivated,\n isLocalMemberSessionActive,\n} from \"./local-member-access.js\"\nimport { expandTrustedOrigins, getTrustedOrigins } from \"./trusted-origins.js\"\nimport { isFirstAuthUser, provisionCurrentUserProfile } from \"./workspace.js\"\n\nexport function getAuthSecret(realm: \"admin\" | \"customer\" = \"admin\"): string {\n const envName = realm === \"customer\" ? \"BETTER_AUTH_CUSTOMER_SECRET\" : \"BETTER_AUTH_ADMIN_SECRET\"\n const secret = process.env[envName] || \"\"\n\n if (!secret || secret.length < 32) {\n throw new Error(`Missing ${envName} with at least 32 characters`)\n }\n\n return secret\n}\n\nexport type {\n AccountProfileUpdateHandler,\n ApiTokenRotationStore,\n BetterAuthApiTokenManagement,\n HandleAccountProfileRequestOptions,\n HandleApiTokenManagementRequestOptions,\n HandleOrganizationMembersRequestOptions,\n OrganizationMemberRecord,\n OrganizationMembersListHandler,\n OrganizationMembersListInput,\n} from \"./auth-facades.js\"\nexport {\n handleAccountProfileRequest,\n handleApiTokenManagementRequest,\n handleOrganizationMembersRequest,\n} from \"./auth-facades.js\"\n\ntype DefinedBetterAuthUserOptions<UserOptions extends BetterAuthOptions[\"user\"]> =\n UserOptions extends undefined ? Record<PropertyKey, never> : NonNullable<UserOptions>\n\ntype ResolvedBetterAuthChangeEmail<UserOptions extends BetterAuthOptions[\"user\"]> =\n DefinedBetterAuthUserOptions<UserOptions> extends { changeEmail?: infer ChangeEmail }\n ? ChangeEmail extends { enabled: infer Enabled }\n ? Omit<ChangeEmail, \"enabled\"> & { enabled: Enabled }\n : NonNullable<ChangeEmail> & { enabled: true }\n : { enabled: true }\n\ntype ResolvedBetterAuthUserOptions<UserOptions extends BetterAuthOptions[\"user\"]> = Omit<\n DefinedBetterAuthUserOptions<UserOptions>,\n \"changeEmail\"\n> & {\n changeEmail: ResolvedBetterAuthChangeEmail<UserOptions>\n}\n\ntype VoyantBetterAuthPlugins = [\n ReturnType<typeof apiKey>,\n ReturnType<typeof emailOTP>,\n ReturnType<typeof createLocalMemberAccessPlugin>,\n]\n\ntype ResolvedBetterAuthPlugins<_Plugins extends BetterAuthPlugin[] | undefined> =\n | VoyantBetterAuthPlugins\n | BetterAuthPlugin[]\n\nconst DEFAULT_SIGNUP_BLOCK_SURFACES = [\"admin\"] as const\nconst CUSTOMER_SIGNUP_ENDPOINT_SUFFIXES = [\n \"/sign-up/email\",\n \"/phone-number/verify\",\n \"/sign-in/email-otp\",\n] as const\n\nexport interface DisableSignupWhenUsersExistOptions {\n /**\n * Set to false when the consuming app owns all signup admission checks.\n *\n * Defaults to true.\n */\n enabled?: boolean\n /**\n * User surfaces that should keep the single-tenant signup guard. New users\n * with only other explicit surfaces can still be created by customer-facing\n * auth plugins such as phone OTP.\n *\n * Defaults to [\"admin\"]. Users without an explicit surfaces array are\n * treated as admin users for backward compatibility.\n */\n surfaces?: readonly string[]\n}\n\ntype BetterAuthCreateBeforeResult =\n | boolean\n | undefined\n | {\n data: Record<string, unknown>\n }\n\ntype SignupBlockUserPayload = {\n surfaces?: unknown\n}\n\ntype BetterAuthHookContext = {\n path?: string\n}\n\nfunction normalizeSignupBlockSurfaces(\n options: DisableSignupWhenUsersExistOptions | undefined,\n): readonly string[] {\n return options?.surfaces ?? DEFAULT_SIGNUP_BLOCK_SURFACES\n}\n\nfunction isSignupBlockEnabled(options: DisableSignupWhenUsersExistOptions | undefined): boolean {\n return options?.enabled !== false\n}\n\nfunction normalizeSurfaceList(surfaces: readonly string[] | undefined): readonly string[] {\n if (!surfaces) return []\n\n const normalized = new Set<string>()\n for (const surface of surfaces) {\n const trimmed = surface.trim()\n if (trimmed.length > 0) {\n normalized.add(trimmed)\n }\n }\n return Array.from(normalized)\n}\n\nfunction signupBlockAppliesToUser(\n user: SignupBlockUserPayload | undefined,\n blockedSurfaces: readonly string[],\n): boolean {\n if (blockedSurfaces.length === 0) return false\n\n const surfaces = user?.surfaces\n if (Array.isArray(surfaces)) {\n const explicitSurfaces = surfaces.filter(\n (surface): surface is string => typeof surface === \"string\" && surface.trim().length > 0,\n )\n if (explicitSurfaces.length > 0) {\n return explicitSurfaces.some((surface) => blockedSurfaces.includes(surface.trim()))\n }\n }\n\n if (typeof surfaces === \"string\" && surfaces.trim().length > 0) {\n return blockedSurfaces.includes(surfaces.trim())\n }\n\n return blockedSurfaces.includes(\"admin\")\n}\n\nfunction isCustomerSignupCreate(context: BetterAuthHookContext | null | undefined): boolean {\n const path = context?.path\n if (!path) return false\n\n return CUSTOMER_SIGNUP_ENDPOINT_SUFFIXES.some((suffix) => path.endsWith(suffix))\n}\n\nfunction stampCustomerSignupSurfaces(\n user: SignupBlockUserPayload | undefined,\n context: BetterAuthHookContext | null | undefined,\n surfaces: readonly string[],\n): SignupBlockUserPayload | undefined {\n if (surfaces.length === 0 || !isCustomerSignupCreate(context)) {\n return user\n }\n\n return {\n ...(user ?? {}),\n surfaces,\n }\n}\n\ntype ResolvedCreateBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined,\n> = Omit<BetterAuthOptions, \"plugins\" | \"user\"> & {\n plugins: ResolvedBetterAuthPlugins<Plugins>\n user: ResolvedBetterAuthUserOptions<UserOptions>\n}\n\nexport type BetterAuthDrizzleSchema = Record<string, AnyPgTable>\n\nexport type VoyantAuthRealm = \"admin\" | \"customer\"\n\nexport interface BetterAuthRealmTables {\n user: AnyPgTable\n session: AnyPgTable\n account: AnyPgTable\n verification: AnyPgTable\n apikey?: AnyPgTable\n}\n\nconst ADMIN_AUTH_TABLES: BetterAuthRealmTables = {\n user: authUser,\n session: authSession,\n account: authAccount,\n verification: authVerification,\n apikey: apikeyTable,\n}\n\nconst CUSTOMER_AUTH_TABLES = {\n user: customerAuthUser,\n session: customerAuthSession,\n account: customerAuthAccount,\n verification: customerAuthVerification,\n organization: customerAuthOrganization,\n member: customerAuthMember,\n invitation: customerAuthInvitation,\n} satisfies BetterAuthRealmTables & BetterAuthDrizzleSchema\n\nexport interface CreateBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> {\n db?: ReturnType<typeof getDb>\n secret?: string\n baseURL?: string\n basePath?: string\n trustedOrigins?: string[]\n /** Security realm. Existing callers default to the isolated admin realm. */\n realm?: VoyantAuthRealm\n /** Override only when an adapter owns a compatible Better Auth schema. */\n realmTables?: BetterAuthRealmTables\n /**\n * Additional Drizzle tables for Better Auth plugins. The consuming app owns\n * matching migrations for every table passed here.\n */\n extraSchema?: BetterAuthDrizzleSchema\n plugins?: Plugins\n user?: UserOptions\n /** Resolved credentials; the auth package never persists provider secrets. */\n socialProviders?: BetterAuthOptions[\"socialProviders\"]\n emailCodeEnabled?: boolean\n emailPasswordEnabled?: boolean\n /**\n * Surfaces stamped on Better Auth customer self-signups before the bundled\n * single-tenant signup guard evaluates the new user. Applies to Better\n * Auth OTP signup endpoints that create a user as part of verification.\n *\n * Leave undefined to preserve Better Auth's raw user payload.\n */\n customerSignupSurfaces?: readonly string[]\n disableSignupWhenUsersExist?: DisableSignupWhenUsersExistOptions\n /** Called when a user requests a password reset. If not provided, logs to console. */\n sendResetPassword?: (data: {\n user: { email: string; name: string }\n url: string\n token: string\n }) => Promise<void>\n /** Called to send a verification OTP. If not provided, logs to console. */\n sendVerificationOTP?: (data: { email: string; otp: string; type: string }) => Promise<void>\n /**\n * Better Auth session cookie cache: session data rides in a short-lived\n * signed cookie. Enabled by default with a 5-minute TTL. Voyant still\n * validates local membership and session existence against Postgres so a\n * deleted session cannot be revived from the cookie cache. Pass `false` to\n * disable payload caching, or tune `maxAge` (seconds).\n */\n sessionCookieCache?: false | { maxAge?: number }\n /** Better Auth secondary storage, typically Redis/KV, used for distributed rate limits. */\n secondaryStorage?: BetterAuthOptions[\"secondaryStorage\"]\n /** Better Auth rate-limit config. Defaults to secondary storage when present. */\n rateLimit?: BetterAuthOptions[\"rateLimit\"]\n /**\n * Additional Better Auth advanced options. Voyant supplies secure cookies\n * by default except in explicit local development. Set\n * `advanced.useSecureCookies` to override that default.\n */\n advanced?: BetterAuthOptions[\"advanced\"]\n}\n\n/**\n * Framework-agnostic Better Auth factory.\n *\n * Accepts optional overrides for db, secret, baseURL, trustedOrigins.\n * Does NOT depend on Next.js — safe to use in Hono workers, TanStack Start, etc.\n */\nexport function createBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateBetterAuthOptions<UserOptions, Plugins> = {}) {\n const realm = options.realm ?? \"admin\"\n const db = options.db ?? getDb(\"edge\")\n const secret = options.secret ?? getAuthSecret(realm)\n const baseURL =\n options.baseURL ??\n process.env.BETTER_AUTH_URL ??\n process.env.APP_URL ??\n process.env.NEXT_PUBLIC_APP_URL ??\n \"http://localhost:3000\"\n const trustedOrigins = expandTrustedOrigins(\n options.trustedOrigins ?? getTrustedOrigins(),\n baseURL,\n )\n const extraPlugins = options.plugins ?? []\n const realmTables =\n options.realmTables ?? (realm === \"customer\" ? CUSTOMER_AUTH_TABLES : ADMIN_AUTH_TABLES)\n const signupBlockSurfaces = normalizeSignupBlockSurfaces(options.disableSignupWhenUsersExist)\n const signupBlockEnabled = isSignupBlockEnabled(options.disableSignupWhenUsersExist)\n const customerSignupSurfaces = normalizeSurfaceList(options.customerSignupSurfaces)\n const reservedSchemaCollision = Object.keys(options.extraSchema ?? {}).find(\n (modelName) => (realmTables as unknown as BetterAuthDrizzleSchema)[modelName] !== undefined,\n )\n if (reservedSchemaCollision) {\n throw new Error(`extraSchema cannot override reserved auth model: ${reservedSchemaCollision}`)\n }\n const schema = {\n ...realmTables,\n ...(options.extraSchema ?? {}),\n } satisfies BetterAuthDrizzleSchema\n const rateLimit =\n options.rateLimit ??\n (options.secondaryStorage\n ? ({\n enabled: true,\n window: 60,\n max: 100,\n storage: \"secondary-storage\",\n } as BetterAuthOptions[\"rateLimit\"])\n : undefined)\n\n const authOptions = {\n appName: \"Voyant\",\n baseURL,\n ...(options.basePath ? { basePath: options.basePath } : {}),\n secret,\n database: drizzleAdapter(db, {\n provider: \"pg\",\n schema,\n }),\n ...(options.secondaryStorage ? { secondaryStorage: options.secondaryStorage } : {}),\n ...(rateLimit ? { rateLimit } : {}),\n ...(options.sessionCookieCache === false\n ? {}\n : {\n session: {\n cookieCache: {\n enabled: true,\n maxAge: options.sessionCookieCache?.maxAge ?? 300,\n },\n },\n }),\n emailAndPassword: {\n enabled: options.emailPasswordEnabled !== false,\n minPasswordLength: 8,\n maxPasswordLength: 128,\n requireEmailVerification: true,\n revokeSessionsOnPasswordReset: true,\n // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.\n sendResetPassword:\n options.sendResetPassword ??\n (async ({ user, url }) => {\n console.warn(\n `[Auth] No email provider configured — password reset for ${user.email}: ${url}`,\n )\n }),\n },\n emailVerification: {\n sendOnSignUp: false, // OTP plugin handles this\n autoSignInAfterVerification: true,\n },\n user: {\n ...options.user,\n changeEmail: {\n enabled: true,\n ...options.user?.changeEmail,\n },\n } as ResolvedBetterAuthUserOptions<UserOptions>,\n socialProviders: options.socialProviders ?? {},\n trustedOrigins,\n plugins: [\n ...(realm === \"admin\"\n ? [\n apiKey({\n defaultPrefix: \"voy_\",\n apiKeyHeaders: [\"authorization\"],\n requireName: true,\n keyExpiration: {\n defaultExpiresIn: null,\n },\n }),\n ]\n : []),\n ...(options.emailCodeEnabled === false\n ? []\n : [\n emailOTP({\n // eslint-disable-next-line @typescript-eslint/require-await -- owner: auth; existing suppression is intentional pending typed cleanup.\n sendVerificationOTP:\n options.sendVerificationOTP ??\n (async ({ email, otp, type }) => {\n console.warn(`[Auth] OTP for ${email}: ${otp} (${type})`)\n }),\n otpLength: 6,\n expiresIn: 600,\n sendVerificationOnSignUp: true,\n changeEmail: {\n enabled: true,\n },\n }),\n ]),\n createLocalMemberAccessPlugin(\n realm === \"admin\"\n ? {\n isEmailDeactivated: (email) => isLocalMemberEmailDeactivated(db, email),\n isSessionActive: (sessionId, userId) =>\n isLocalMemberSessionActive(db, sessionId, userId),\n isUserDeactivated: (userId) => isLocalMemberDeactivated(db, userId),\n }\n : {\n isEmailDeactivated: async () => false,\n isSessionActive: (sessionId, userId) =>\n isCustomerSessionActive(db, sessionId, userId),\n isUserDeactivated: async () => false,\n },\n ),\n ...extraPlugins,\n ] as ResolvedBetterAuthPlugins<Plugins>,\n ...(realm === \"admin\"\n ? {\n databaseHooks: {\n user: {\n create: {\n // Single-tenant: once a user exists, reject any new-user creation.\n // Covers email sign-up AND social-provider sign-up (Google would\n // otherwise auto-create a user on first OAuth callback). Existing\n // social sign-ins still work because this hook only fires on CREATE.\n // Seed scripts do raw drizzle inserts, so they bypass this hook —\n // which is intentional.\n before: async (user, context): Promise<BetterAuthCreateBeforeResult> => {\n const normalizedUser = stampCustomerSignupSurfaces(\n user as SignupBlockUserPayload,\n context as BetterAuthHookContext | null,\n customerSignupSurfaces,\n )\n\n if (\n !signupBlockEnabled ||\n !signupBlockAppliesToUser(normalizedUser, signupBlockSurfaces)\n ) {\n if (normalizedUser === user) {\n return\n }\n return { data: normalizedUser as Record<string, unknown> }\n }\n\n const [row] = await db\n .select({ count: sql<number>`count(*)::int` })\n .from(realmTables.user)\n if ((row?.count ?? 0) > 0) {\n throw new Error(\"Sign-up is disabled. Ask an admin to invite you.\")\n }\n\n if (normalizedUser !== user) {\n return { data: normalizedUser as Record<string, unknown> }\n }\n },\n after: async (user, context) => {\n if (\n customerSignupSurfaces.length > 0 &&\n isCustomerSignupCreate(context as BetterAuthHookContext | null)\n ) {\n return\n }\n\n // Single-tenant bootstrap: the very first user to register becomes\n // the super-admin. Runs atomically after the `user` row is\n // inserted, so a simple COUNT(*) = 1 check identifies them.\n await provisionCurrentUserProfile(db, {\n userId: user.id,\n name: user.name,\n image: user.image,\n isSuperAdmin: await isFirstAuthUser(db),\n })\n },\n },\n },\n },\n }\n : {}),\n advanced: {\n ...options.advanced,\n cookiePrefix:\n options.advanced?.cookiePrefix ??\n (realm === \"customer\" ? \"voyant-customer\" : \"voyant-admin\"),\n useSecureCookies:\n options.advanced?.useSecureCookies ?? process.env.NODE_ENV !== \"development\",\n },\n } as ResolvedCreateBetterAuthOptions<UserOptions, Plugins>\n\n return betterAuth<ResolvedCreateBetterAuthOptions<UserOptions, Plugins>>(authOptions)\n}\n\nexport type CreateAdminBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> = Omit<CreateBetterAuthOptions<UserOptions, Plugins>, \"realm\" | \"realmTables\">\n\nexport function createAdminBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateAdminBetterAuthOptions<UserOptions, Plugins> = {}) {\n return createBetterAuth({\n ...options,\n realm: \"admin\",\n realmTables: ADMIN_AUTH_TABLES,\n basePath: options.basePath ?? \"/auth/admin\",\n })\n}\n\nexport interface CustomerAuthMethods {\n emailCode?: boolean\n emailPassword?: boolean\n /** Better Auth provider configs such as google, facebook, and apple. */\n socialProviders?: BetterAuthOptions[\"socialProviders\"]\n}\n\nexport type CreateCustomerBetterAuthOptions<\n UserOptions extends BetterAuthOptions[\"user\"] = BetterAuthOptions[\"user\"],\n Plugins extends BetterAuthPlugin[] | undefined = BetterAuthPlugin[] | undefined,\n> = Omit<\n CreateBetterAuthOptions<UserOptions, Plugins>,\n | \"realm\"\n | \"realmTables\"\n | \"customerSignupSurfaces\"\n | \"disableSignupWhenUsersExist\"\n | \"socialProviders\"\n | \"emailCodeEnabled\"\n | \"emailPasswordEnabled\"\n> & {\n methods?: CustomerAuthMethods\n accountPolicy?: CustomerBuyerAccountPolicy | null\n sendOrganizationInvitation?: (\n data: {\n id: string\n role: string\n email: string\n organization: { id: string; name: string; slug: string }\n invitation: {\n id: string\n email: string\n role: string\n organizationId: string\n inviterId: string\n status: string\n expiresAt: Date\n }\n inviter: {\n id: string\n role: string\n user: { id: string; name: string; email: string }\n }\n },\n request?: Request,\n ) => Promise<void>\n}\n\n/** Storefront realm factory with isolated tables, cookies, secret, and routes. */\nexport function createCustomerBetterAuth<\n const UserOptions extends BetterAuthOptions[\"user\"] = undefined,\n const Plugins extends BetterAuthPlugin[] | undefined = undefined,\n>(options: CreateCustomerBetterAuthOptions<UserOptions, Plugins> = {}) {\n const accountPolicy = normalizeCustomerBuyerAccountPolicy(options.accountPolicy)\n const customerOrganizationPlugin = organization({\n // A Better Auth organization is only the membership container. Public\n // creation cannot also create/link the canonical Relationships Organization,\n // so onboarding must go through the provider-neutral orchestration seam.\n allowUserToCreateOrganization: false,\n disableOrganizationDeletion: true,\n requireEmailVerificationOnInvitation: true,\n invitationLimit: options.sendOrganizationInvitation ? 100 : 0,\n ...(options.sendOrganizationInvitation\n ? { sendInvitationEmail: options.sendOrganizationInvitation }\n : {}),\n schema: {\n organization: {\n additionalFields: {\n relationshipOrganizationId: {\n type: \"string\",\n required: false,\n input: false,\n },\n },\n },\n },\n })\n\n return createBetterAuth({\n ...options,\n realm: \"customer\",\n realmTables: CUSTOMER_AUTH_TABLES,\n basePath: options.basePath ?? \"/auth/customer\",\n disableSignupWhenUsersExist: { enabled: false },\n emailCodeEnabled: options.methods?.emailCode ?? true,\n emailPasswordEnabled: options.methods?.emailPassword ?? true,\n socialProviders: options.methods?.socialProviders,\n user: {\n ...options.user,\n additionalFields: {\n ...options.user?.additionalFields,\n personalBuyerEntitlementEligible: {\n type: \"boolean\",\n required: true,\n input: false,\n defaultValue:\n accountPolicy.allowedKinds.includes(\"personal\") &&\n accountPolicy.personalSignup === \"open\",\n },\n relationshipPersonId: {\n type: \"string\",\n required: false,\n input: false,\n },\n },\n },\n extraSchema: options.extraSchema,\n // `bearer` lets a direct (cross-origin / native) client authenticate with an\n // `Authorization: Bearer <token>` header instead of a cookie: on sign-in\n // Better Auth returns the session token via `set-auth-token`, and the bearer\n // hook converts the presented token back into a session on later calls. The\n // BFF/same-origin path keeps using host-only cookies untouched. Direct\n // clients dodge third-party-cookie deprecation this way.\n plugins: [customerOrganizationPlugin, bearer(), ...(options.plugins ?? [])],\n })\n}\n"]}
@@ -2,8 +2,18 @@ import type { CustomerAuthRuntimeContext } from "./node-runtime.js";
2
2
  import type { StorefrontResolveContext, StorefrontRuntimeProvider } from "./storefront-runtime-port.js";
3
3
  /** Default header the storefront BFF uses to declare its browser origin. */
4
4
  export declare const STOREFRONT_ORIGIN_HEADER = "x-voyant-storefront-origin";
5
+ /** Standard browser header a direct (non-BFF) cross-origin client sends. */
6
+ export declare const STANDARD_ORIGIN_HEADER = "origin";
5
7
  /** Default header carrying the storefront's publishable/secret access key. */
6
8
  export declare const STOREFRONT_KEY_HEADER = "x-api-key";
9
+ /**
10
+ * Resolve the storefront browser origin for a request. The BFF forwards its
11
+ * origin explicitly via {@link STOREFRONT_ORIGIN_HEADER} and that always wins;
12
+ * a direct (non-BFF) cross-origin client carries no BFF header, so fall back to
13
+ * the standard `Origin` header the browser attaches. Same-origin server
14
+ * requests keep sending the BFF header exactly as before.
15
+ */
16
+ export declare function resolveStorefrontRequestOrigin(request: Request, originHeader?: string): string | null;
7
17
  export interface LocalStorefrontCustomerAuthResolverConfig<Env> {
8
18
  provider: StorefrontRuntimeProvider;
9
19
  /**
@@ -18,8 +28,26 @@ export interface LocalStorefrontCustomerAuthResolverConfig<Env> {
18
28
  originHeader?: string;
19
29
  keyHeader?: string;
20
30
  }
31
+ /**
32
+ * Reason a storefront customer-auth resolution failed. Every variant is a
33
+ * client-side auth failure (bad/absent credentials or a disallowed origin), not
34
+ * a server fault, so the auth handler maps these to 401/403 — never 500.
35
+ */
36
+ export type StorefrontCustomerAuthFailureReason = "missing_origin" | "missing_key" | "unknown_key" | "origin_not_allowed";
37
+ /**
38
+ * Thrown by the local storefront customer-auth resolver when the presented
39
+ * credentials/origin cannot be resolved to a storefront. Carries an HTTP status
40
+ * and a stable machine code so the auth handler can translate it into a clean
41
+ * 401 (or 403 for a known key from a disallowed origin) instead of a 500. The
42
+ * message is intentionally non-leaky — it never echoes the presented key.
43
+ */
21
44
  export declare class StorefrontCustomerAuthResolutionError extends Error {
22
- constructor(message: string);
45
+ readonly reason: StorefrontCustomerAuthFailureReason;
46
+ /** HTTP status the auth handler should surface (401 unauthorized / 403 forbidden). */
47
+ readonly status: 401 | 403;
48
+ /** Stable client-facing error code. */
49
+ readonly code: "unauthorized" | "forbidden";
50
+ constructor(reason: StorefrontCustomerAuthFailureReason, message: string);
23
51
  }
24
52
  /**
25
53
  * Build the `resolveCustomerAuthContext` function a self-host runtime passes to
@@ -28,3 +56,20 @@ export declare class StorefrontCustomerAuthResolutionError extends Error {
28
56
  * managed cloud fetch.
29
57
  */
30
58
  export declare function createLocalStorefrontCustomerAuthResolver<Env>(config: LocalStorefrontCustomerAuthResolverConfig<Env>): (env: Env, request: Request) => Promise<CustomerAuthRuntimeContext>;
59
+ /**
60
+ * Build the request-time dynamic-CORS origin authorizer a self-host runtime
61
+ * passes to {@link createOperatorAuthNodeRuntime} as `resolveCustomerCorsOrigin`.
62
+ *
63
+ * Returns the exact request origin to echo in `Access-Control-Allow-Origin` when
64
+ * a storefront authorizes it, or `null` when it does not (the caller then omits
65
+ * CORS headers so the browser blocks the cross-origin response).
66
+ *
67
+ * Two request shapes are handled:
68
+ * - Real/credentialed requests carry the publishable/secret key: the storefront
69
+ * is resolved by key and the origin checked against its declared origins.
70
+ * - CORS preflight (`OPTIONS`) carries no key or cookies, so the origin is
71
+ * matched against any storefront that declares it via
72
+ * {@link StorefrontRuntimeProvider.resolveStorefrontByOrigin}. This never
73
+ * echoes an origin that no storefront allows.
74
+ */
75
+ export declare function createLocalStorefrontCorsOriginResolver<Env>(config: LocalStorefrontCustomerAuthResolverConfig<Env>): (env: Env, request: Request) => Promise<string | null>;
@@ -1,12 +1,44 @@
1
1
  import { enabledStorefrontSocialProviders, isStorefrontOriginAllowed, } from "./storefront-origins.js";
2
2
  /** Default header the storefront BFF uses to declare its browser origin. */
3
3
  export const STOREFRONT_ORIGIN_HEADER = "x-voyant-storefront-origin";
4
+ /** Standard browser header a direct (non-BFF) cross-origin client sends. */
5
+ export const STANDARD_ORIGIN_HEADER = "origin";
4
6
  /** Default header carrying the storefront's publishable/secret access key. */
5
7
  export const STOREFRONT_KEY_HEADER = "x-api-key";
8
+ const WILDCARD_ORIGIN_PREFIX = "https://*.";
9
+ /**
10
+ * Resolve the storefront browser origin for a request. The BFF forwards its
11
+ * origin explicitly via {@link STOREFRONT_ORIGIN_HEADER} and that always wins;
12
+ * a direct (non-BFF) cross-origin client carries no BFF header, so fall back to
13
+ * the standard `Origin` header the browser attaches. Same-origin server
14
+ * requests keep sending the BFF header exactly as before.
15
+ */
16
+ export function resolveStorefrontRequestOrigin(request, originHeader = STOREFRONT_ORIGIN_HEADER) {
17
+ const bffOrigin = request.headers.get(originHeader)?.trim();
18
+ if (bffOrigin)
19
+ return bffOrigin;
20
+ const browserOrigin = request.headers.get(STANDARD_ORIGIN_HEADER)?.trim();
21
+ return browserOrigin || null;
22
+ }
23
+ /**
24
+ * Thrown by the local storefront customer-auth resolver when the presented
25
+ * credentials/origin cannot be resolved to a storefront. Carries an HTTP status
26
+ * and a stable machine code so the auth handler can translate it into a clean
27
+ * 401 (or 403 for a known key from a disallowed origin) instead of a 500. The
28
+ * message is intentionally non-leaky — it never echoes the presented key.
29
+ */
6
30
  export class StorefrontCustomerAuthResolutionError extends Error {
7
- constructor(message) {
31
+ reason;
32
+ /** HTTP status the auth handler should surface (401 unauthorized / 403 forbidden). */
33
+ status;
34
+ /** Stable client-facing error code. */
35
+ code;
36
+ constructor(reason, message) {
8
37
  super(message);
9
38
  this.name = "StorefrontCustomerAuthResolutionError";
39
+ this.reason = reason;
40
+ this.status = reason === "origin_not_allowed" ? 403 : 401;
41
+ this.code = reason === "origin_not_allowed" ? "forbidden" : "unauthorized";
10
42
  }
11
43
  }
12
44
  function toSocialProviders(secrets) {
@@ -30,23 +62,23 @@ export function createLocalStorefrontCustomerAuthResolver(config) {
30
62
  const originHeader = config.originHeader ?? STOREFRONT_ORIGIN_HEADER;
31
63
  const keyHeader = config.keyHeader ?? STOREFRONT_KEY_HEADER;
32
64
  return async (env, request) => {
33
- const origin = request.headers.get(originHeader)?.trim();
65
+ const origin = resolveStorefrontRequestOrigin(request, originHeader);
34
66
  if (!origin) {
35
- throw new StorefrontCustomerAuthResolutionError(`Local storefront customer auth requires ${originHeader} from the storefront BFF.`);
67
+ throw new StorefrontCustomerAuthResolutionError("missing_origin", `Local storefront customer auth requires ${originHeader} (BFF) or a standard Origin header.`);
36
68
  }
37
69
  const token = request.headers.get(keyHeader)?.trim();
38
70
  if (!token) {
39
- throw new StorefrontCustomerAuthResolutionError(`Local storefront customer auth requires a storefront key (${keyHeader}).`);
71
+ throw new StorefrontCustomerAuthResolutionError("missing_key", `Local storefront customer auth requires a storefront key (${keyHeader}).`);
40
72
  }
41
73
  const { context, dispose } = await config.openResolveContext(env, request);
42
74
  try {
43
75
  const resolved = await config.provider.resolveStorefrontByApiKey(context, token);
44
76
  if (!resolved) {
45
- throw new StorefrontCustomerAuthResolutionError("The presented storefront key is unknown or revoked.");
77
+ throw new StorefrontCustomerAuthResolutionError("unknown_key", "The presented storefront key is unknown or revoked.");
46
78
  }
47
79
  const { storefront } = resolved;
48
80
  if (!isStorefrontOriginAllowed(origin, storefront.allowedOrigins)) {
49
- throw new StorefrontCustomerAuthResolutionError("The request origin is not a declared allowed origin for this storefront.");
81
+ throw new StorefrontCustomerAuthResolutionError("origin_not_allowed", "The request origin is not a declared allowed origin for this storefront.");
50
82
  }
51
83
  const enabledProviders = enabledStorefrontSocialProviders(storefront.methods);
52
84
  const secrets = await config.provider.resolveProviderCredentials(context, storefront.id, enabledProviders);
@@ -55,11 +87,19 @@ export function createLocalStorefrontCustomerAuthResolver(config) {
55
87
  emailPassword: storefront.methods.emailPassword,
56
88
  socialProviders: toSocialProviders(secrets),
57
89
  };
90
+ // Trust every declared exact origin plus the concrete request origin (which
91
+ // covers a `https://*.host` wildcard match, since the browser sends the
92
+ // resolved sub-domain). Wildcard entries themselves are surfaced via
93
+ // `allowedOrigins` for dynamic CORS but kept out of `trustedOrigins`, which
94
+ // the runtime validates as canonical origins.
95
+ const exactAllowedOrigins = storefront.allowedOrigins.filter((candidate) => !candidate.startsWith(WILDCARD_ORIGIN_PREFIX));
96
+ const trustedOrigins = [...new Set([origin, ...exactAllowedOrigins])];
58
97
  return {
59
98
  baseURL: origin,
60
99
  publicApiBaseURL: `${origin}/api`,
61
100
  invitationAcceptBaseURL: origin,
62
- trustedOrigins: [origin],
101
+ trustedOrigins,
102
+ allowedOrigins: [...storefront.allowedOrigins],
63
103
  methods,
64
104
  accountPolicy: storefront.accountPolicy,
65
105
  };
@@ -69,4 +109,45 @@ export function createLocalStorefrontCustomerAuthResolver(config) {
69
109
  }
70
110
  };
71
111
  }
112
+ /**
113
+ * Build the request-time dynamic-CORS origin authorizer a self-host runtime
114
+ * passes to {@link createOperatorAuthNodeRuntime} as `resolveCustomerCorsOrigin`.
115
+ *
116
+ * Returns the exact request origin to echo in `Access-Control-Allow-Origin` when
117
+ * a storefront authorizes it, or `null` when it does not (the caller then omits
118
+ * CORS headers so the browser blocks the cross-origin response).
119
+ *
120
+ * Two request shapes are handled:
121
+ * - Real/credentialed requests carry the publishable/secret key: the storefront
122
+ * is resolved by key and the origin checked against its declared origins.
123
+ * - CORS preflight (`OPTIONS`) carries no key or cookies, so the origin is
124
+ * matched against any storefront that declares it via
125
+ * {@link StorefrontRuntimeProvider.resolveStorefrontByOrigin}. This never
126
+ * echoes an origin that no storefront allows.
127
+ */
128
+ export function createLocalStorefrontCorsOriginResolver(config) {
129
+ const originHeader = config.originHeader ?? STOREFRONT_ORIGIN_HEADER;
130
+ const keyHeader = config.keyHeader ?? STOREFRONT_KEY_HEADER;
131
+ return async (env, request) => {
132
+ const origin = resolveStorefrontRequestOrigin(request, originHeader);
133
+ if (!origin)
134
+ return null;
135
+ const { context, dispose } = await config.openResolveContext(env, request);
136
+ try {
137
+ const token = request.headers.get(keyHeader)?.trim();
138
+ if (token) {
139
+ const resolved = await config.provider.resolveStorefrontByApiKey(context, token);
140
+ if (!resolved)
141
+ return null;
142
+ return isStorefrontOriginAllowed(origin, resolved.storefront.allowedOrigins) ? origin : null;
143
+ }
144
+ // Keyless preflight: authorize purely by declared origin.
145
+ const storefront = await config.provider.resolveStorefrontByOrigin(context, origin);
146
+ return storefront ? origin : null;
147
+ }
148
+ finally {
149
+ await dispose?.();
150
+ }
151
+ };
152
+ }
72
153
  //# sourceMappingURL=storefront-customer-auth-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"storefront-customer-auth-resolver.js","sourceRoot":"","sources":["../src/storefront-customer-auth-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local storefront customer-auth resolver.\n *\n * Implements the `resolveCustomerAuthContext` seam of\n * {@link createOperatorAuthNodeRuntime} for a self-host runtime: instead of\n * fetching a merchant policy from Voyant Cloud, it resolves the storefront from\n * the presented access key via {@link storefrontRuntimePort}, enforces the\n * operator-declared origin allowlist, and projects the storefront's methods,\n * trusted origins, buyer-account policy, and decrypted provider secrets into a\n * `CustomerAuthRuntimeContext`.\n *\n * The BFF must forward the storefront origin explicitly (never derived from\n * Host/X-Forwarded-Host) plus the storefront key, exactly like the managed\n * broker.\n */\nimport type { CustomerBuyerAccountPolicy } from \"./customer-buyer-accounts.js\"\nimport type { CustomerAuthRuntimeContext } from \"./node-runtime.js\"\nimport type { CustomerAuthMethods } from \"./server.js\"\nimport {\n enabledStorefrontSocialProviders,\n isStorefrontOriginAllowed,\n} from \"./storefront-origins.js\"\nimport type {\n StorefrontResolveContext,\n StorefrontRuntimeProvider,\n} from \"./storefront-runtime-port.js\"\n\n/** Default header the storefront BFF uses to declare its browser origin. */\nexport const STOREFRONT_ORIGIN_HEADER = \"x-voyant-storefront-origin\"\n/** Default header carrying the storefront's publishable/secret access key. */\nexport const STOREFRONT_KEY_HEADER = \"x-api-key\"\n\nexport interface LocalStorefrontCustomerAuthResolverConfig<Env> {\n provider: StorefrontRuntimeProvider\n /**\n * Open the request-time resolve context (db + bindings) for this runtime.\n * The self-host host wires this to its database lifecycle; `dispose` is\n * always awaited, success or failure.\n */\n openResolveContext: (\n env: Env,\n request: Request,\n ) => Promise<{ context: StorefrontResolveContext; dispose?: () => Promise<void> }>\n originHeader?: string\n keyHeader?: string\n}\n\nexport class StorefrontCustomerAuthResolutionError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"StorefrontCustomerAuthResolutionError\"\n }\n}\n\nfunction toSocialProviders(\n secrets: Partial<Record<\"google\" | \"facebook\" | \"apple\", Record<string, string>>>,\n): CustomerAuthMethods[\"socialProviders\"] {\n const providers: Record<string, { clientId: string; clientSecret: string }> = {}\n for (const [provider, credential] of Object.entries(secrets)) {\n const clientId = credential?.clientId\n const clientSecret = credential?.clientSecret\n if (!clientId || !clientSecret) continue\n providers[provider] = { clientId, clientSecret }\n }\n return providers as CustomerAuthMethods[\"socialProviders\"]\n}\n\n/**\n * Build the `resolveCustomerAuthContext` function a self-host runtime passes to\n * {@link createOperatorAuthNodeRuntime}. Keeps the existing seam intact — this\n * is simply the local (port-backed) option alongside the env default and the\n * managed cloud fetch.\n */\nexport function createLocalStorefrontCustomerAuthResolver<Env>(\n config: LocalStorefrontCustomerAuthResolverConfig<Env>,\n): (env: Env, request: Request) => Promise<CustomerAuthRuntimeContext> {\n const originHeader = config.originHeader ?? STOREFRONT_ORIGIN_HEADER\n const keyHeader = config.keyHeader ?? STOREFRONT_KEY_HEADER\n\n return async (env, request) => {\n const origin = request.headers.get(originHeader)?.trim()\n if (!origin) {\n throw new StorefrontCustomerAuthResolutionError(\n `Local storefront customer auth requires ${originHeader} from the storefront BFF.`,\n )\n }\n const token = request.headers.get(keyHeader)?.trim()\n if (!token) {\n throw new StorefrontCustomerAuthResolutionError(\n `Local storefront customer auth requires a storefront key (${keyHeader}).`,\n )\n }\n\n const { context, dispose } = await config.openResolveContext(env, request)\n try {\n const resolved = await config.provider.resolveStorefrontByApiKey(context, token)\n if (!resolved) {\n throw new StorefrontCustomerAuthResolutionError(\n \"The presented storefront key is unknown or revoked.\",\n )\n }\n const { storefront } = resolved\n if (!isStorefrontOriginAllowed(origin, storefront.allowedOrigins)) {\n throw new StorefrontCustomerAuthResolutionError(\n \"The request origin is not a declared allowed origin for this storefront.\",\n )\n }\n\n const enabledProviders = enabledStorefrontSocialProviders(storefront.methods)\n const secrets = await config.provider.resolveProviderCredentials(\n context,\n storefront.id,\n enabledProviders,\n )\n\n const methods: CustomerAuthMethods = {\n emailCode: storefront.methods.emailCode,\n emailPassword: storefront.methods.emailPassword,\n socialProviders: toSocialProviders(secrets),\n }\n\n return {\n baseURL: origin,\n publicApiBaseURL: `${origin}/api`,\n invitationAcceptBaseURL: origin,\n trustedOrigins: [origin],\n methods,\n accountPolicy: storefront.accountPolicy as CustomerBuyerAccountPolicy,\n }\n } finally {\n await dispose?.()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"storefront-customer-auth-resolver.js","sourceRoot":"","sources":["../src/storefront-customer-auth-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local storefront customer-auth resolver.\n *\n * Implements the `resolveCustomerAuthContext` seam of\n * {@link createOperatorAuthNodeRuntime} for a self-host runtime: instead of\n * fetching a merchant policy from Voyant Cloud, it resolves the storefront from\n * the presented access key via {@link storefrontRuntimePort}, enforces the\n * operator-declared origin allowlist, and projects the storefront's methods,\n * trusted origins, buyer-account policy, and decrypted provider secrets into a\n * `CustomerAuthRuntimeContext`.\n *\n * The BFF must forward the storefront origin explicitly (never derived from\n * Host/X-Forwarded-Host) plus the storefront key, exactly like the managed\n * broker.\n */\nimport type { CustomerBuyerAccountPolicy } from \"./customer-buyer-accounts.js\"\nimport type { CustomerAuthRuntimeContext } from \"./node-runtime.js\"\nimport type { CustomerAuthMethods } from \"./server.js\"\nimport {\n enabledStorefrontSocialProviders,\n isStorefrontOriginAllowed,\n} from \"./storefront-origins.js\"\nimport type {\n StorefrontResolveContext,\n StorefrontRuntimeProvider,\n} from \"./storefront-runtime-port.js\"\n\n/** Default header the storefront BFF uses to declare its browser origin. */\nexport const STOREFRONT_ORIGIN_HEADER = \"x-voyant-storefront-origin\"\n/** Standard browser header a direct (non-BFF) cross-origin client sends. */\nexport const STANDARD_ORIGIN_HEADER = \"origin\"\n/** Default header carrying the storefront's publishable/secret access key. */\nexport const STOREFRONT_KEY_HEADER = \"x-api-key\"\n\nconst WILDCARD_ORIGIN_PREFIX = \"https://*.\"\n\n/**\n * Resolve the storefront browser origin for a request. The BFF forwards its\n * origin explicitly via {@link STOREFRONT_ORIGIN_HEADER} and that always wins;\n * a direct (non-BFF) cross-origin client carries no BFF header, so fall back to\n * the standard `Origin` header the browser attaches. Same-origin server\n * requests keep sending the BFF header exactly as before.\n */\nexport function resolveStorefrontRequestOrigin(\n request: Request,\n originHeader: string = STOREFRONT_ORIGIN_HEADER,\n): string | null {\n const bffOrigin = request.headers.get(originHeader)?.trim()\n if (bffOrigin) return bffOrigin\n const browserOrigin = request.headers.get(STANDARD_ORIGIN_HEADER)?.trim()\n return browserOrigin || null\n}\n\nexport interface LocalStorefrontCustomerAuthResolverConfig<Env> {\n provider: StorefrontRuntimeProvider\n /**\n * Open the request-time resolve context (db + bindings) for this runtime.\n * The self-host host wires this to its database lifecycle; `dispose` is\n * always awaited, success or failure.\n */\n openResolveContext: (\n env: Env,\n request: Request,\n ) => Promise<{ context: StorefrontResolveContext; dispose?: () => Promise<void> }>\n originHeader?: string\n keyHeader?: string\n}\n\n/**\n * Reason a storefront customer-auth resolution failed. Every variant is a\n * client-side auth failure (bad/absent credentials or a disallowed origin), not\n * a server fault, so the auth handler maps these to 401/403 — never 500.\n */\nexport type StorefrontCustomerAuthFailureReason =\n | \"missing_origin\"\n | \"missing_key\"\n | \"unknown_key\"\n | \"origin_not_allowed\"\n\n/**\n * Thrown by the local storefront customer-auth resolver when the presented\n * credentials/origin cannot be resolved to a storefront. Carries an HTTP status\n * and a stable machine code so the auth handler can translate it into a clean\n * 401 (or 403 for a known key from a disallowed origin) instead of a 500. The\n * message is intentionally non-leaky — it never echoes the presented key.\n */\nexport class StorefrontCustomerAuthResolutionError extends Error {\n readonly reason: StorefrontCustomerAuthFailureReason\n /** HTTP status the auth handler should surface (401 unauthorized / 403 forbidden). */\n readonly status: 401 | 403\n /** Stable client-facing error code. */\n readonly code: \"unauthorized\" | \"forbidden\"\n\n constructor(reason: StorefrontCustomerAuthFailureReason, message: string) {\n super(message)\n this.name = \"StorefrontCustomerAuthResolutionError\"\n this.reason = reason\n this.status = reason === \"origin_not_allowed\" ? 403 : 401\n this.code = reason === \"origin_not_allowed\" ? \"forbidden\" : \"unauthorized\"\n }\n}\n\nfunction toSocialProviders(\n secrets: Partial<Record<\"google\" | \"facebook\" | \"apple\", Record<string, string>>>,\n): CustomerAuthMethods[\"socialProviders\"] {\n const providers: Record<string, { clientId: string; clientSecret: string }> = {}\n for (const [provider, credential] of Object.entries(secrets)) {\n const clientId = credential?.clientId\n const clientSecret = credential?.clientSecret\n if (!clientId || !clientSecret) continue\n providers[provider] = { clientId, clientSecret }\n }\n return providers as CustomerAuthMethods[\"socialProviders\"]\n}\n\n/**\n * Build the `resolveCustomerAuthContext` function a self-host runtime passes to\n * {@link createOperatorAuthNodeRuntime}. Keeps the existing seam intact — this\n * is simply the local (port-backed) option alongside the env default and the\n * managed cloud fetch.\n */\nexport function createLocalStorefrontCustomerAuthResolver<Env>(\n config: LocalStorefrontCustomerAuthResolverConfig<Env>,\n): (env: Env, request: Request) => Promise<CustomerAuthRuntimeContext> {\n const originHeader = config.originHeader ?? STOREFRONT_ORIGIN_HEADER\n const keyHeader = config.keyHeader ?? STOREFRONT_KEY_HEADER\n\n return async (env, request) => {\n const origin = resolveStorefrontRequestOrigin(request, originHeader)\n if (!origin) {\n throw new StorefrontCustomerAuthResolutionError(\n \"missing_origin\",\n `Local storefront customer auth requires ${originHeader} (BFF) or a standard Origin header.`,\n )\n }\n const token = request.headers.get(keyHeader)?.trim()\n if (!token) {\n throw new StorefrontCustomerAuthResolutionError(\n \"missing_key\",\n `Local storefront customer auth requires a storefront key (${keyHeader}).`,\n )\n }\n\n const { context, dispose } = await config.openResolveContext(env, request)\n try {\n const resolved = await config.provider.resolveStorefrontByApiKey(context, token)\n if (!resolved) {\n throw new StorefrontCustomerAuthResolutionError(\n \"unknown_key\",\n \"The presented storefront key is unknown or revoked.\",\n )\n }\n const { storefront } = resolved\n if (!isStorefrontOriginAllowed(origin, storefront.allowedOrigins)) {\n throw new StorefrontCustomerAuthResolutionError(\n \"origin_not_allowed\",\n \"The request origin is not a declared allowed origin for this storefront.\",\n )\n }\n\n const enabledProviders = enabledStorefrontSocialProviders(storefront.methods)\n const secrets = await config.provider.resolveProviderCredentials(\n context,\n storefront.id,\n enabledProviders,\n )\n\n const methods: CustomerAuthMethods = {\n emailCode: storefront.methods.emailCode,\n emailPassword: storefront.methods.emailPassword,\n socialProviders: toSocialProviders(secrets),\n }\n\n // Trust every declared exact origin plus the concrete request origin (which\n // covers a `https://*.host` wildcard match, since the browser sends the\n // resolved sub-domain). Wildcard entries themselves are surfaced via\n // `allowedOrigins` for dynamic CORS but kept out of `trustedOrigins`, which\n // the runtime validates as canonical origins.\n const exactAllowedOrigins = storefront.allowedOrigins.filter(\n (candidate) => !candidate.startsWith(WILDCARD_ORIGIN_PREFIX),\n )\n const trustedOrigins = [...new Set([origin, ...exactAllowedOrigins])]\n\n return {\n baseURL: origin,\n publicApiBaseURL: `${origin}/api`,\n invitationAcceptBaseURL: origin,\n trustedOrigins,\n allowedOrigins: [...storefront.allowedOrigins],\n methods,\n accountPolicy: storefront.accountPolicy as CustomerBuyerAccountPolicy,\n }\n } finally {\n await dispose?.()\n }\n }\n}\n\n/**\n * Build the request-time dynamic-CORS origin authorizer a self-host runtime\n * passes to {@link createOperatorAuthNodeRuntime} as `resolveCustomerCorsOrigin`.\n *\n * Returns the exact request origin to echo in `Access-Control-Allow-Origin` when\n * a storefront authorizes it, or `null` when it does not (the caller then omits\n * CORS headers so the browser blocks the cross-origin response).\n *\n * Two request shapes are handled:\n * - Real/credentialed requests carry the publishable/secret key: the storefront\n * is resolved by key and the origin checked against its declared origins.\n * - CORS preflight (`OPTIONS`) carries no key or cookies, so the origin is\n * matched against any storefront that declares it via\n * {@link StorefrontRuntimeProvider.resolveStorefrontByOrigin}. This never\n * echoes an origin that no storefront allows.\n */\nexport function createLocalStorefrontCorsOriginResolver<Env>(\n config: LocalStorefrontCustomerAuthResolverConfig<Env>,\n): (env: Env, request: Request) => Promise<string | null> {\n const originHeader = config.originHeader ?? STOREFRONT_ORIGIN_HEADER\n const keyHeader = config.keyHeader ?? STOREFRONT_KEY_HEADER\n\n return async (env, request) => {\n const origin = resolveStorefrontRequestOrigin(request, originHeader)\n if (!origin) return null\n\n const { context, dispose } = await config.openResolveContext(env, request)\n try {\n const token = request.headers.get(keyHeader)?.trim()\n if (token) {\n const resolved = await config.provider.resolveStorefrontByApiKey(context, token)\n if (!resolved) return null\n return isStorefrontOriginAllowed(origin, resolved.storefront.allowedOrigins) ? origin : null\n }\n // Keyless preflight: authorize purely by declared origin.\n const storefront = await config.provider.resolveStorefrontByOrigin(context, origin)\n return storefront ? origin : null\n } finally {\n await dispose?.()\n }\n }\n}\n"]}
@@ -5,10 +5,10 @@
5
5
  * and request-time resolution (`resolveStorefrontByApiKey`) is org-agnostic.
6
6
  */
7
7
  import { storefrontApiKeys, storefrontCustomerAuthCredentials, storefronts, } from "@voyant-travel/db/schema/iam";
8
- import { and, desc, eq } from "drizzle-orm";
8
+ import { and, desc, eq, sql } from "drizzle-orm";
9
9
  import { validateStorefrontCredentialBundle, } from "./storefront-credentials.js";
10
10
  import { classifyStorefrontApiKey, generateStorefrontApiKey, hashStorefrontApiKey, } from "./storefront-keys.js";
11
- import { enabledStorefrontSocialProviders, normalizeStorefrontAllowedOrigins, normalizeStorefrontCustomerAccountPolicy, normalizeStorefrontCustomerAuthMethods, StorefrontInputError, } from "./storefront-origins.js";
11
+ import { enabledStorefrontSocialProviders, isStorefrontOriginAllowed, normalizeStorefrontAllowedOrigins, normalizeStorefrontCustomerAccountPolicy, normalizeStorefrontCustomerAuthMethods, StorefrontInputError, } from "./storefront-origins.js";
12
12
  export { isStorefrontOriginAllowed } from "./storefront-origins.js";
13
13
  function toStorefrontDto(row) {
14
14
  return {
@@ -234,6 +234,31 @@ export function createLocalStorefrontAdapter(options) {
234
234
  }
235
235
  return { storefront: toStorefrontDto(storefront), key: toApiKeyDto(key) };
236
236
  },
237
+ async resolveStorefrontByOrigin(context, origin) {
238
+ // Preflight authorization is keyless, so it cannot select a single
239
+ // storefront up front. Exact-origin entries are filtered in SQL via array
240
+ // containment; wildcard (`https://*.host`) declarations are matched in
241
+ // memory over the small self-host storefront set. First match wins — a
242
+ // browser origin resolves to at most one storefront in practice.
243
+ const [exactMatch] = await context.db
244
+ .select()
245
+ .from(storefronts)
246
+ // agent-quality: raw-sql reviewed -- Postgres text[] containment authorizes the exact declared origin without loading every row.
247
+ .where(sql `${storefronts.allowedOrigins} @> ARRAY[${origin}]::text[]`)
248
+ .limit(1);
249
+ if (exactMatch)
250
+ return toStorefrontDto(exactMatch);
251
+ const wildcardCandidates = await context.db
252
+ .select()
253
+ .from(storefronts)
254
+ // agent-quality: raw-sql reviewed -- Only rows carrying a wildcard origin declaration are considered for the in-memory match.
255
+ .where(sql `EXISTS (SELECT 1 FROM unnest(${storefronts.allowedOrigins}) AS o WHERE o LIKE 'https://*.%')`);
256
+ for (const row of wildcardCandidates) {
257
+ if (isStorefrontOriginAllowed(origin, row.allowedOrigins))
258
+ return toStorefrontDto(row);
259
+ }
260
+ return null;
261
+ },
237
262
  async updateAccountPolicy(context, storefrontId, policy) {
238
263
  await requireOwnedStorefront(context, storefrontId);
239
264
  return persistStorefrontPatch(context, storefrontId, {