@voyant-travel/auth 0.133.5 → 0.134.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
@@ -172,11 +172,11 @@ import { createBetterAuth } from "@voyant-travel/auth/server"
172
172
 
173
173
  const auth = createBetterAuth({
174
174
  db,
175
- basePath: "/auth",
175
+ basePath: "/auth/admin",
176
176
  plugins: [
177
177
  createVoyantCloudAdminAuthPlugin({
178
178
  db,
179
- cookieSecret: env.SESSION_CLAIMS_SECRET,
179
+ cookieSecret: env.SESSION_CLAIMS_ADMIN_SECRET,
180
180
  exchange: {
181
181
  exchangeUrl: env.VOYANT_CLOUD_ADMIN_AUTH_EXCHANGE_URL,
182
182
  deploymentId: env.VOYANT_CLOUD_DEPLOYMENT_ID,
@@ -194,7 +194,7 @@ const auth = createBetterAuth({
194
194
  })
195
195
  ```
196
196
 
197
- The plugin mounts Better Auth's `/auth/cloud/callback` endpoint. That endpoint
197
+ The plugin mounts Better Auth's `/auth/admin/cloud/callback` endpoint. That endpoint
198
198
  validates the signed broker state, exchanges the one-time Cloud code, verifies
199
199
  the signed Cloud assertion, upserts the local mirror user/account/profile,
200
200
  stores Cloud linkage side-table rows, and then creates the local Better Auth
@@ -24,6 +24,8 @@ export type CloudAdminUserProvisioningHandler = (input: CloudAdminUserProvisioni
24
24
  export interface VoyantCloudAdminAuthPluginOptions {
25
25
  db: ReturnType<typeof getDb>;
26
26
  cookieSecret: string;
27
+ /** Whether the externally visible callback is HTTPS, even behind an HTTP proxy hop. */
28
+ secureStateCookie?: boolean;
27
29
  exchange: CloudAdminAuthExchangeConfig;
28
30
  fetch?: typeof fetch;
29
31
  revalidateAfterSeconds?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-admin-session-public.js","sourceRoot":"","sources":["../src/cloud-admin-session-public.ts"],"names":[],"mappings":"","sourcesContent":["import type { getDb } from \"@voyant-travel/db\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\n\nimport type {\n CloudAdminAssertion,\n CloudAdminAuthExchangeConfig,\n CloudAdminAuthRevalidateConfig,\n} from \"./cloud-broker.js\"\n\nexport type CloudAdminProvisionedUser = {\n id: string\n name: string\n email: string\n emailVerified: boolean\n image?: string | null\n createdAt: Date\n updatedAt: Date\n}\n\nexport type CloudAdminUserProvisioningInput = {\n db: ReturnType<typeof getDb>\n assertion: CloudAdminAssertion\n user: CloudAdminProvisionedUser\n isNewUser: boolean\n provider: {\n providerId: \"voyant-cloud\"\n providerAccountId: string\n }\n}\n\nexport type CloudAdminUserProvisioningHandler = (\n input: CloudAdminUserProvisioningInput,\n) => Promise<void> | void\n\nexport interface VoyantCloudAdminAuthPluginOptions {\n db: ReturnType<typeof getDb>\n cookieSecret: string\n exchange: CloudAdminAuthExchangeConfig\n fetch?: typeof fetch\n revalidateAfterSeconds?: number\n onUserProvisioning?: CloudAdminUserProvisioningHandler\n}\n\nexport declare function createVoyantCloudAdminAuthPlugin(\n options: VoyantCloudAdminAuthPluginOptions,\n): BetterAuthPlugin\n\nexport type VoyantCloudAdminSessionRevalidationInput = {\n db: ReturnType<typeof getDb>\n sessionId: string\n config: CloudAdminAuthRevalidateConfig\n fetch?: typeof fetch\n now?: Date\n revalidateAfterSeconds?: number\n}\n\nexport type VoyantCloudAdminSessionRevalidationResult =\n | {\n ok: true\n status: \"active\" | \"cached\"\n }\n | {\n ok: false\n status: \"revoked\"\n reason?: string\n }\n\nexport declare function revalidateVoyantCloudAdminAuthSession(\n input: VoyantCloudAdminSessionRevalidationInput,\n): Promise<VoyantCloudAdminSessionRevalidationResult>\n\nexport type VoyantCloudAdminUserRevalidationInput = {\n db: ReturnType<typeof getDb>\n userId: string\n config: CloudAdminAuthRevalidateConfig\n fetch?: typeof fetch\n now?: Date\n revalidateAfterSeconds?: number\n}\n\nexport type VoyantCloudAdminUserRevalidationResult =\n | {\n ok: true\n status: \"active\" | \"cached\"\n }\n | {\n ok: false\n status: \"revoked\"\n reason?: string\n }\n\nexport declare function revalidateVoyantCloudAdminAuthUser(\n input: VoyantCloudAdminUserRevalidationInput,\n): Promise<VoyantCloudAdminUserRevalidationResult>\n"]}
1
+ {"version":3,"file":"cloud-admin-session-public.js","sourceRoot":"","sources":["../src/cloud-admin-session-public.ts"],"names":[],"mappings":"","sourcesContent":["import type { getDb } from \"@voyant-travel/db\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\n\nimport type {\n CloudAdminAssertion,\n CloudAdminAuthExchangeConfig,\n CloudAdminAuthRevalidateConfig,\n} from \"./cloud-broker.js\"\n\nexport type CloudAdminProvisionedUser = {\n id: string\n name: string\n email: string\n emailVerified: boolean\n image?: string | null\n createdAt: Date\n updatedAt: Date\n}\n\nexport type CloudAdminUserProvisioningInput = {\n db: ReturnType<typeof getDb>\n assertion: CloudAdminAssertion\n user: CloudAdminProvisionedUser\n isNewUser: boolean\n provider: {\n providerId: \"voyant-cloud\"\n providerAccountId: string\n }\n}\n\nexport type CloudAdminUserProvisioningHandler = (\n input: CloudAdminUserProvisioningInput,\n) => Promise<void> | void\n\nexport interface VoyantCloudAdminAuthPluginOptions {\n db: ReturnType<typeof getDb>\n cookieSecret: string\n /** Whether the externally visible callback is HTTPS, even behind an HTTP proxy hop. */\n secureStateCookie?: boolean\n exchange: CloudAdminAuthExchangeConfig\n fetch?: typeof fetch\n revalidateAfterSeconds?: number\n onUserProvisioning?: CloudAdminUserProvisioningHandler\n}\n\nexport declare function createVoyantCloudAdminAuthPlugin(\n options: VoyantCloudAdminAuthPluginOptions,\n): BetterAuthPlugin\n\nexport type VoyantCloudAdminSessionRevalidationInput = {\n db: ReturnType<typeof getDb>\n sessionId: string\n config: CloudAdminAuthRevalidateConfig\n fetch?: typeof fetch\n now?: Date\n revalidateAfterSeconds?: number\n}\n\nexport type VoyantCloudAdminSessionRevalidationResult =\n | {\n ok: true\n status: \"active\" | \"cached\"\n }\n | {\n ok: false\n status: \"revoked\"\n reason?: string\n }\n\nexport declare function revalidateVoyantCloudAdminAuthSession(\n input: VoyantCloudAdminSessionRevalidationInput,\n): Promise<VoyantCloudAdminSessionRevalidationResult>\n\nexport type VoyantCloudAdminUserRevalidationInput = {\n db: ReturnType<typeof getDb>\n userId: string\n config: CloudAdminAuthRevalidateConfig\n fetch?: typeof fetch\n now?: Date\n revalidateAfterSeconds?: number\n}\n\nexport type VoyantCloudAdminUserRevalidationResult =\n | {\n ok: true\n status: \"active\" | \"cached\"\n }\n | {\n ok: false\n status: \"revoked\"\n reason?: string\n }\n\nexport declare function revalidateVoyantCloudAdminAuthUser(\n input: VoyantCloudAdminUserRevalidationInput,\n): Promise<VoyantCloudAdminUserRevalidationResult>\n"]}
@@ -4,6 +4,8 @@ import { type CloudAdminAssertion, type CloudAdminAuthExchangeConfig, type Cloud
4
4
  export interface VoyantCloudAdminAuthPluginOptions {
5
5
  db: ReturnType<typeof getDb>;
6
6
  cookieSecret: string;
7
+ /** Whether the externally visible callback is HTTPS, even behind an HTTP proxy hop. */
8
+ secureStateCookie?: boolean;
7
9
  exchange: CloudAdminAuthExchangeConfig;
8
10
  fetch?: typeof fetch;
9
11
  revalidateAfterSeconds?: number;
@@ -16,6 +16,7 @@ export function createVoyantCloudAdminAuthPlugin(options) {
16
16
  requestUrl: ctx.request.url,
17
17
  cookieHeader: ctx.headers?.get("cookie") ?? ctx.request.headers.get("cookie"),
18
18
  cookieSecret: options.cookieSecret,
19
+ secureCookie: options.secureStateCookie,
19
20
  });
20
21
  ctx.setHeader("Set-Cookie", callback.clearCookie);
21
22
  if (!callback.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-admin-session.js","sourceRoot":"","sources":["../src/cloud-admin-session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { getDb } from \"@voyant-travel/db\"\nimport {\n authAccount,\n authUser,\n cloudAuthSessionLinks,\n cloudAuthUserLinks,\n} from \"@voyant-travel/db/schema/iam\"\nimport { createAuthEndpoint } from \"better-auth/api\"\nimport { setSessionCookie } from \"better-auth/cookies\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\nimport { and, eq } from \"drizzle-orm\"\nimport {\n markCloudAuthSessionRevalidated,\n markCloudAuthUserRevalidated,\n revokeCloudAuthUserAccess,\n} from \"./cloud-admin-session-revalidation-store.js\"\nimport {\n type CloudAdminAssertion,\n type CloudAdminAuthExchangeConfig,\n type CloudAdminAuthRevalidateConfig,\n exchangeCloudAdminAuthCode,\n revalidateCloudAdminAuthAccess,\n verifyCloudAdminAuthCallback,\n} from \"./cloud-broker.js\"\nimport { isFirstAuthUser, provisionCurrentUserProfile } from \"./workspace.js\"\n\nexport interface VoyantCloudAdminAuthPluginOptions {\n db: ReturnType<typeof getDb>\n cookieSecret: string\n exchange: CloudAdminAuthExchangeConfig\n fetch?: typeof fetch\n revalidateAfterSeconds?: number\n onUserProvisioning?: CloudAdminUserProvisioningHandler\n}\n\nexport type VoyantCloudAdminSessionRevalidationInput = {\n db: ReturnType<typeof getDb>\n sessionId: string\n config: CloudAdminAuthRevalidateConfig\n fetch?: typeof fetch\n now?: Date\n revalidateAfterSeconds?: number\n}\n\nexport type VoyantCloudAdminSessionRevalidationResult =\n | {\n ok: true\n status: \"active\" | \"cached\"\n }\n | {\n ok: false\n status: \"revoked\"\n reason?: string\n }\n\nexport type VoyantCloudAdminUserRevalidationInput = {\n db: ReturnType<typeof getDb>\n userId: string\n config: CloudAdminAuthRevalidateConfig\n fetch?: typeof fetch\n now?: Date\n revalidateAfterSeconds?: number\n}\n\nexport type VoyantCloudAdminUserRevalidationResult =\n | {\n ok: true\n status: \"active\" | \"cached\"\n }\n | {\n ok: false\n status: \"revoked\"\n reason?: string\n }\n\nconst VOYANT_CLOUD_PROVIDER_ID = \"voyant-cloud\"\nconst DEFAULT_CLOUD_SESSION_REVALIDATE_AFTER_SECONDS = 15 * 60\n\ntype BetterAuthUserForCookie = {\n id: string\n name: string\n email: string\n emailVerified: boolean\n image?: string | null\n createdAt: Date\n updatedAt: Date\n}\n\ntype BetterAuthSessionForCookie = {\n id: string\n token: string\n userId: string\n expiresAt: Date\n createdAt: Date\n updatedAt: Date\n ipAddress?: string | null\n userAgent?: string | null\n}\n\nexport type CloudAdminProvisionedUser = BetterAuthUserForCookie\n\nexport type CloudAdminUserProvisioningInput = {\n db: ReturnType<typeof getDb>\n assertion: CloudAdminAssertion\n user: CloudAdminProvisionedUser\n isNewUser: boolean\n provider: {\n providerId: typeof VOYANT_CLOUD_PROVIDER_ID\n providerAccountId: string\n }\n}\n\nexport type CloudAdminUserProvisioningHandler = (\n input: CloudAdminUserProvisioningInput,\n) => Promise<void> | void\n\nexport function createVoyantCloudAdminAuthPlugin(\n options: VoyantCloudAdminAuthPluginOptions,\n): BetterAuthPlugin {\n return {\n id: \"voyant-cloud-admin-auth\",\n endpoints: {\n voyantCloudAdminAuthCallback: createAuthEndpoint(\n \"/cloud/callback\",\n { method: \"GET\", requireRequest: true },\n async (ctx) => {\n const callback = await verifyCloudAdminAuthCallback({\n requestUrl: ctx.request.url,\n cookieHeader: ctx.headers?.get(\"cookie\") ?? ctx.request.headers.get(\"cookie\"),\n cookieSecret: options.cookieSecret,\n })\n ctx.setHeader(\"Set-Cookie\", callback.clearCookie)\n\n if (!callback.ok) {\n const status = callback.error === \"cloud_error\" ? 401 : 400\n return ctx.json(\n {\n error: callback.error,\n ...(callback.cloudError ? { cloudError: callback.cloudError } : {}),\n },\n { status },\n )\n }\n\n let assertion: CloudAdminAssertion\n try {\n assertion = await exchangeCloudAdminAuthCode({\n code: callback.code,\n state: callback.state,\n config: options.exchange,\n ...(options.fetch ? { fetch: options.fetch } : {}),\n })\n } catch (error) {\n ctx.context.logger.error(\"[auth/cloud/callback] Exchange error\", error)\n return ctx.json({ error: \"Voyant Cloud auth exchange failed\" }, { status: 401 })\n }\n\n const user = await upsertVoyantCloudMirrorUser(options.db, assertion, {\n onUserProvisioning: options.onUserProvisioning,\n })\n const session = (await ctx.context.internalAdapter.createSession(\n user.id,\n )) as BetterAuthSessionForCookie\n await upsertVoyantCloudSessionLink(options.db, {\n assertion,\n session,\n revalidateAfterSeconds:\n options.revalidateAfterSeconds ?? DEFAULT_CLOUD_SESSION_REVALIDATE_AFTER_SECONDS,\n })\n await setSessionCookie(ctx, { session, user })\n\n throw ctx.redirect(callback.state.next)\n },\n ),\n },\n }\n}\n\nexport async function revalidateVoyantCloudAdminAuthSession({\n db,\n sessionId,\n config,\n fetch,\n now = new Date(),\n revalidateAfterSeconds = DEFAULT_CLOUD_SESSION_REVALIDATE_AFTER_SECONDS,\n}: VoyantCloudAdminSessionRevalidationInput): Promise<VoyantCloudAdminSessionRevalidationResult> {\n const [sessionLink] = await db\n .select()\n .from(cloudAuthSessionLinks)\n .where(eq(cloudAuthSessionLinks.sessionId, sessionId))\n .limit(1)\n\n if (!sessionLink || sessionLink.revokedAt) {\n return { ok: false, status: \"revoked\", reason: \"missing_or_revoked_session\" }\n }\n\n if (sessionLink.revalidateAfter > now) {\n return { ok: true, status: \"cached\" }\n }\n\n const revalidation = await revalidateCloudAdminAuthAccess({\n workosUserId: sessionLink.providerAccountId,\n config,\n ...(fetch ? { fetch } : {}),\n })\n\n if (revalidation.ok) {\n await markCloudAuthSessionRevalidated(db, {\n sessionId: sessionLink.sessionId,\n userId: sessionLink.userId,\n now,\n revalidateAfterSeconds,\n })\n // Refresh the cached RBAC scopes so a permission change applied while the\n // member is signed in takes effect at the next revalidation, not only at\n // next login (member-rbac-rfc, voyant#2085). `undefined` ⇒ older platform\n // didn't send scopes; leave the column as-is.\n if (revalidation.scopes !== undefined) {\n await db\n .update(cloudAuthUserLinks)\n .set({ scopes: revalidation.scopes, updatedAt: now })\n .where(eq(cloudAuthUserLinks.userId, sessionLink.userId))\n }\n return { ok: true, status: \"active\" }\n }\n\n await revokeCloudAuthUserAccess(db, {\n sessionId: sessionLink.sessionId,\n userId: sessionLink.userId,\n now,\n })\n\n return {\n ok: false,\n status: \"revoked\",\n reason: revalidation.reason,\n }\n}\n\nexport async function revalidateVoyantCloudAdminAuthUser({\n db,\n userId,\n config,\n fetch,\n now = new Date(),\n revalidateAfterSeconds = DEFAULT_CLOUD_SESSION_REVALIDATE_AFTER_SECONDS,\n}: VoyantCloudAdminUserRevalidationInput): Promise<VoyantCloudAdminUserRevalidationResult> {\n const [userLink] = await db\n .select()\n .from(cloudAuthUserLinks)\n .where(eq(cloudAuthUserLinks.userId, userId))\n .limit(1)\n\n if (!userLink || userLink.revokedAt) {\n return { ok: false, status: \"revoked\", reason: \"missing_or_revoked_user\" }\n }\n\n const nextRevalidationAt = userLink.lastRevalidatedAt\n ? new Date(userLink.lastRevalidatedAt.getTime() + revalidateAfterSeconds * 1000)\n : null\n if (nextRevalidationAt && nextRevalidationAt > now) {\n return { ok: true, status: \"cached\" }\n }\n\n const revalidation = await revalidateCloudAdminAuthAccess({\n workosUserId: userLink.providerAccountId,\n config,\n ...(fetch ? { fetch } : {}),\n })\n\n if (revalidation.ok) {\n await markCloudAuthUserRevalidated(db, {\n userId: userLink.userId,\n now,\n })\n return { ok: true, status: \"active\" }\n }\n\n await revokeCloudAuthUserAccess(db, {\n userId: userLink.userId,\n now,\n })\n\n return {\n ok: false,\n status: \"revoked\",\n reason: revalidation.reason,\n }\n}\n\nasync function upsertVoyantCloudMirrorUser(\n db: ReturnType<typeof getDb>,\n assertion: CloudAdminAssertion,\n options: {\n onUserProvisioning?: CloudAdminUserProvisioningHandler\n } = {},\n): Promise<BetterAuthUserForCookie> {\n const now = new Date()\n const providerAccountId = assertion.workosUserId\n const displayName = cloudAssertionDisplayName(assertion)\n const existingUser = await findCloudMirrorUser(\n db,\n providerAccountId,\n assertion.email,\n assertion.emailVerified,\n )\n const isNewUser = !existingUser\n const userId = existingUser?.id ?? crypto.randomUUID()\n\n if (existingUser) {\n await db\n .update(authUser)\n .set({\n name: displayName,\n email: assertion.email,\n emailVerified: assertion.emailVerified,\n image: assertion.image ?? null,\n updatedAt: now,\n })\n .where(eq(authUser.id, existingUser.id))\n } else {\n await db.insert(authUser).values({\n id: userId,\n name: displayName,\n email: assertion.email,\n emailVerified: assertion.emailVerified,\n image: assertion.image ?? null,\n createdAt: now,\n updatedAt: now,\n })\n }\n\n await db\n .insert(authAccount)\n .values({\n id: cloudAuthAccountId(providerAccountId),\n userId,\n accountId: providerAccountId,\n providerId: VOYANT_CLOUD_PROVIDER_ID,\n createdAt: now,\n updatedAt: now,\n })\n .onConflictDoUpdate({\n target: [authAccount.providerId, authAccount.accountId],\n set: {\n userId,\n updatedAt: now,\n },\n })\n\n await upsertVoyantCloudUserLink(db, userId, assertion, now)\n\n await provisionCurrentUserProfile(db, {\n userId,\n name: displayName,\n image: assertion.image,\n isSuperAdmin: await isFirstAuthUser(db),\n })\n\n const [user] = await db\n .select({\n id: authUser.id,\n name: authUser.name,\n email: authUser.email,\n emailVerified: authUser.emailVerified,\n image: authUser.image,\n createdAt: authUser.createdAt,\n updatedAt: authUser.updatedAt,\n })\n .from(authUser)\n .where(eq(authUser.id, userId))\n .limit(1)\n\n if (!user?.email) {\n throw new Error(\"Voyant Cloud auth mirror user was not provisioned\")\n }\n\n const provisionedUser = {\n id: user.id,\n name: user.name,\n email: user.email,\n emailVerified: user.emailVerified,\n image: user.image,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt,\n }\n\n await options.onUserProvisioning?.({\n db,\n assertion,\n user: provisionedUser,\n isNewUser,\n provider: {\n providerId: VOYANT_CLOUD_PROVIDER_ID,\n providerAccountId,\n },\n })\n\n return provisionedUser\n}\n\nasync function findCloudMirrorUser(\n db: ReturnType<typeof getDb>,\n providerAccountId: string,\n email?: string,\n emailVerified?: boolean,\n): Promise<{ id: string } | null> {\n const [linked] = await db\n .select({ id: authUser.id })\n .from(cloudAuthUserLinks)\n .innerJoin(authUser, eq(authUser.id, cloudAuthUserLinks.userId))\n .where(\n and(\n eq(cloudAuthUserLinks.providerId, VOYANT_CLOUD_PROVIDER_ID),\n eq(cloudAuthUserLinks.providerAccountId, providerAccountId),\n ),\n )\n .limit(1)\n\n if (linked) return linked\n\n const [account] = await db\n .select({ id: authUser.id })\n .from(authAccount)\n .innerJoin(authUser, eq(authUser.id, authAccount.userId))\n .where(\n and(\n eq(authAccount.providerId, VOYANT_CLOUD_PROVIDER_ID),\n eq(authAccount.accountId, providerAccountId),\n ),\n )\n .limit(1)\n\n if (account) return account\n if (!email || emailVerified !== true) return null\n\n const [emailUser] = await db\n .select({ id: authUser.id })\n .from(authUser)\n .where(eq(authUser.email, email))\n .limit(1)\n\n return emailUser ?? null\n}\n\nfunction cloudAuthUserLinkValues(\n userId: string,\n assertion: CloudAdminAssertion,\n now: Date,\n): typeof cloudAuthUserLinks.$inferInsert {\n return {\n userId,\n providerId: VOYANT_CLOUD_PROVIDER_ID,\n providerAccountId: assertion.workosUserId,\n deploymentId: assertion.deploymentId,\n platformOrganizationId: assertion.platformOrganizationId,\n workosOrganizationId: assertion.workosOrganizationId,\n membershipId: assertion.membershipId ?? null,\n roleSlug: assertion.roleSlug ?? null,\n roleName: assertion.roleName ?? null,\n surfaces: assertion.surfaces ?? [],\n // Member RBAC scope set from the assertion. `null` (not `[]`) when the\n // platform sent none, so resolveAuthRequest can tell \"no scopes claim\"\n // (derive from role / full-access fallback) from \"explicitly empty\".\n scopes: assertion.scopes ?? null,\n lastAssertionAt: new Date(assertion.iat * 1000),\n lastRevalidatedAt: now,\n revokedAt: null,\n updatedAt: now,\n }\n}\n\nasync function upsertVoyantCloudUserLink(\n db: ReturnType<typeof getDb>,\n userId: string,\n assertion: CloudAdminAssertion,\n now: Date,\n): Promise<void> {\n const values = cloudAuthUserLinkValues(userId, assertion, now)\n const [existingForUser] = await db\n .select({ userId: cloudAuthUserLinks.userId })\n .from(cloudAuthUserLinks)\n .where(eq(cloudAuthUserLinks.userId, userId))\n .limit(1)\n\n if (existingForUser) {\n await db\n .update(cloudAuthUserLinks)\n .set({\n ...values,\n userId,\n revokedAt: null,\n })\n .where(eq(cloudAuthUserLinks.userId, userId))\n return\n }\n\n await db\n .insert(cloudAuthUserLinks)\n .values(values)\n .onConflictDoUpdate({\n target: [cloudAuthUserLinks.providerId, cloudAuthUserLinks.providerAccountId],\n set: {\n ...values,\n userId,\n revokedAt: null,\n },\n })\n}\n\nasync function upsertVoyantCloudSessionLink(\n db: ReturnType<typeof getDb>,\n input: {\n assertion: CloudAdminAssertion\n session: BetterAuthSessionForCookie\n revalidateAfterSeconds: number\n },\n): Promise<void> {\n const now = new Date()\n await db.insert(cloudAuthSessionLinks).values({\n sessionId: input.session.id,\n userId: input.session.userId,\n providerId: VOYANT_CLOUD_PROVIDER_ID,\n providerAccountId: input.assertion.workosUserId,\n deploymentId: input.assertion.deploymentId,\n revalidateAfter: new Date(now.getTime() + input.revalidateAfterSeconds * 1000),\n lastRevalidatedAt: now,\n revokedAt: null,\n updatedAt: now,\n })\n}\n\nfunction cloudAssertionDisplayName(assertion: CloudAdminAssertion): string {\n const fromParts = [assertion.firstName, assertion.lastName]\n .map((part) => part?.trim())\n .filter((part): part is string => Boolean(part))\n .join(\" \")\n return assertion.name?.trim() || fromParts || assertion.email\n}\n\nfunction cloudAuthAccountId(providerAccountId: string): string {\n return `acc_${VOYANT_CLOUD_PROVIDER_ID}_${providerAccountId}`\n}\n"]}
1
+ {"version":3,"file":"cloud-admin-session.js","sourceRoot":"","sources":["../src/cloud-admin-session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { getDb } from \"@voyant-travel/db\"\nimport {\n authAccount,\n authUser,\n cloudAuthSessionLinks,\n cloudAuthUserLinks,\n} from \"@voyant-travel/db/schema/iam\"\nimport { createAuthEndpoint } from \"better-auth/api\"\nimport { setSessionCookie } from \"better-auth/cookies\"\nimport type { BetterAuthPlugin } from \"better-auth/types\"\nimport { and, eq } from \"drizzle-orm\"\nimport {\n markCloudAuthSessionRevalidated,\n markCloudAuthUserRevalidated,\n revokeCloudAuthUserAccess,\n} from \"./cloud-admin-session-revalidation-store.js\"\nimport {\n type CloudAdminAssertion,\n type CloudAdminAuthExchangeConfig,\n type CloudAdminAuthRevalidateConfig,\n exchangeCloudAdminAuthCode,\n revalidateCloudAdminAuthAccess,\n verifyCloudAdminAuthCallback,\n} from \"./cloud-broker.js\"\nimport { isFirstAuthUser, provisionCurrentUserProfile } from \"./workspace.js\"\n\nexport interface VoyantCloudAdminAuthPluginOptions {\n db: ReturnType<typeof getDb>\n cookieSecret: string\n /** Whether the externally visible callback is HTTPS, even behind an HTTP proxy hop. */\n secureStateCookie?: boolean\n exchange: CloudAdminAuthExchangeConfig\n fetch?: typeof fetch\n revalidateAfterSeconds?: number\n onUserProvisioning?: CloudAdminUserProvisioningHandler\n}\n\nexport type VoyantCloudAdminSessionRevalidationInput = {\n db: ReturnType<typeof getDb>\n sessionId: string\n config: CloudAdminAuthRevalidateConfig\n fetch?: typeof fetch\n now?: Date\n revalidateAfterSeconds?: number\n}\n\nexport type VoyantCloudAdminSessionRevalidationResult =\n | {\n ok: true\n status: \"active\" | \"cached\"\n }\n | {\n ok: false\n status: \"revoked\"\n reason?: string\n }\n\nexport type VoyantCloudAdminUserRevalidationInput = {\n db: ReturnType<typeof getDb>\n userId: string\n config: CloudAdminAuthRevalidateConfig\n fetch?: typeof fetch\n now?: Date\n revalidateAfterSeconds?: number\n}\n\nexport type VoyantCloudAdminUserRevalidationResult =\n | {\n ok: true\n status: \"active\" | \"cached\"\n }\n | {\n ok: false\n status: \"revoked\"\n reason?: string\n }\n\nconst VOYANT_CLOUD_PROVIDER_ID = \"voyant-cloud\"\nconst DEFAULT_CLOUD_SESSION_REVALIDATE_AFTER_SECONDS = 15 * 60\n\ntype BetterAuthUserForCookie = {\n id: string\n name: string\n email: string\n emailVerified: boolean\n image?: string | null\n createdAt: Date\n updatedAt: Date\n}\n\ntype BetterAuthSessionForCookie = {\n id: string\n token: string\n userId: string\n expiresAt: Date\n createdAt: Date\n updatedAt: Date\n ipAddress?: string | null\n userAgent?: string | null\n}\n\nexport type CloudAdminProvisionedUser = BetterAuthUserForCookie\n\nexport type CloudAdminUserProvisioningInput = {\n db: ReturnType<typeof getDb>\n assertion: CloudAdminAssertion\n user: CloudAdminProvisionedUser\n isNewUser: boolean\n provider: {\n providerId: typeof VOYANT_CLOUD_PROVIDER_ID\n providerAccountId: string\n }\n}\n\nexport type CloudAdminUserProvisioningHandler = (\n input: CloudAdminUserProvisioningInput,\n) => Promise<void> | void\n\nexport function createVoyantCloudAdminAuthPlugin(\n options: VoyantCloudAdminAuthPluginOptions,\n): BetterAuthPlugin {\n return {\n id: \"voyant-cloud-admin-auth\",\n endpoints: {\n voyantCloudAdminAuthCallback: createAuthEndpoint(\n \"/cloud/callback\",\n { method: \"GET\", requireRequest: true },\n async (ctx) => {\n const callback = await verifyCloudAdminAuthCallback({\n requestUrl: ctx.request.url,\n cookieHeader: ctx.headers?.get(\"cookie\") ?? ctx.request.headers.get(\"cookie\"),\n cookieSecret: options.cookieSecret,\n secureCookie: options.secureStateCookie,\n })\n ctx.setHeader(\"Set-Cookie\", callback.clearCookie)\n\n if (!callback.ok) {\n const status = callback.error === \"cloud_error\" ? 401 : 400\n return ctx.json(\n {\n error: callback.error,\n ...(callback.cloudError ? { cloudError: callback.cloudError } : {}),\n },\n { status },\n )\n }\n\n let assertion: CloudAdminAssertion\n try {\n assertion = await exchangeCloudAdminAuthCode({\n code: callback.code,\n state: callback.state,\n config: options.exchange,\n ...(options.fetch ? { fetch: options.fetch } : {}),\n })\n } catch (error) {\n ctx.context.logger.error(\"[auth/cloud/callback] Exchange error\", error)\n return ctx.json({ error: \"Voyant Cloud auth exchange failed\" }, { status: 401 })\n }\n\n const user = await upsertVoyantCloudMirrorUser(options.db, assertion, {\n onUserProvisioning: options.onUserProvisioning,\n })\n const session = (await ctx.context.internalAdapter.createSession(\n user.id,\n )) as BetterAuthSessionForCookie\n await upsertVoyantCloudSessionLink(options.db, {\n assertion,\n session,\n revalidateAfterSeconds:\n options.revalidateAfterSeconds ?? DEFAULT_CLOUD_SESSION_REVALIDATE_AFTER_SECONDS,\n })\n await setSessionCookie(ctx, { session, user })\n\n throw ctx.redirect(callback.state.next)\n },\n ),\n },\n }\n}\n\nexport async function revalidateVoyantCloudAdminAuthSession({\n db,\n sessionId,\n config,\n fetch,\n now = new Date(),\n revalidateAfterSeconds = DEFAULT_CLOUD_SESSION_REVALIDATE_AFTER_SECONDS,\n}: VoyantCloudAdminSessionRevalidationInput): Promise<VoyantCloudAdminSessionRevalidationResult> {\n const [sessionLink] = await db\n .select()\n .from(cloudAuthSessionLinks)\n .where(eq(cloudAuthSessionLinks.sessionId, sessionId))\n .limit(1)\n\n if (!sessionLink || sessionLink.revokedAt) {\n return { ok: false, status: \"revoked\", reason: \"missing_or_revoked_session\" }\n }\n\n if (sessionLink.revalidateAfter > now) {\n return { ok: true, status: \"cached\" }\n }\n\n const revalidation = await revalidateCloudAdminAuthAccess({\n workosUserId: sessionLink.providerAccountId,\n config,\n ...(fetch ? { fetch } : {}),\n })\n\n if (revalidation.ok) {\n await markCloudAuthSessionRevalidated(db, {\n sessionId: sessionLink.sessionId,\n userId: sessionLink.userId,\n now,\n revalidateAfterSeconds,\n })\n // Refresh the cached RBAC scopes so a permission change applied while the\n // member is signed in takes effect at the next revalidation, not only at\n // next login (member-rbac-rfc, voyant#2085). `undefined` ⇒ older platform\n // didn't send scopes; leave the column as-is.\n if (revalidation.scopes !== undefined) {\n await db\n .update(cloudAuthUserLinks)\n .set({ scopes: revalidation.scopes, updatedAt: now })\n .where(eq(cloudAuthUserLinks.userId, sessionLink.userId))\n }\n return { ok: true, status: \"active\" }\n }\n\n await revokeCloudAuthUserAccess(db, {\n sessionId: sessionLink.sessionId,\n userId: sessionLink.userId,\n now,\n })\n\n return {\n ok: false,\n status: \"revoked\",\n reason: revalidation.reason,\n }\n}\n\nexport async function revalidateVoyantCloudAdminAuthUser({\n db,\n userId,\n config,\n fetch,\n now = new Date(),\n revalidateAfterSeconds = DEFAULT_CLOUD_SESSION_REVALIDATE_AFTER_SECONDS,\n}: VoyantCloudAdminUserRevalidationInput): Promise<VoyantCloudAdminUserRevalidationResult> {\n const [userLink] = await db\n .select()\n .from(cloudAuthUserLinks)\n .where(eq(cloudAuthUserLinks.userId, userId))\n .limit(1)\n\n if (!userLink || userLink.revokedAt) {\n return { ok: false, status: \"revoked\", reason: \"missing_or_revoked_user\" }\n }\n\n const nextRevalidationAt = userLink.lastRevalidatedAt\n ? new Date(userLink.lastRevalidatedAt.getTime() + revalidateAfterSeconds * 1000)\n : null\n if (nextRevalidationAt && nextRevalidationAt > now) {\n return { ok: true, status: \"cached\" }\n }\n\n const revalidation = await revalidateCloudAdminAuthAccess({\n workosUserId: userLink.providerAccountId,\n config,\n ...(fetch ? { fetch } : {}),\n })\n\n if (revalidation.ok) {\n await markCloudAuthUserRevalidated(db, {\n userId: userLink.userId,\n now,\n })\n return { ok: true, status: \"active\" }\n }\n\n await revokeCloudAuthUserAccess(db, {\n userId: userLink.userId,\n now,\n })\n\n return {\n ok: false,\n status: \"revoked\",\n reason: revalidation.reason,\n }\n}\n\nasync function upsertVoyantCloudMirrorUser(\n db: ReturnType<typeof getDb>,\n assertion: CloudAdminAssertion,\n options: {\n onUserProvisioning?: CloudAdminUserProvisioningHandler\n } = {},\n): Promise<BetterAuthUserForCookie> {\n const now = new Date()\n const providerAccountId = assertion.workosUserId\n const displayName = cloudAssertionDisplayName(assertion)\n const existingUser = await findCloudMirrorUser(\n db,\n providerAccountId,\n assertion.email,\n assertion.emailVerified,\n )\n const isNewUser = !existingUser\n const userId = existingUser?.id ?? crypto.randomUUID()\n\n if (existingUser) {\n await db\n .update(authUser)\n .set({\n name: displayName,\n email: assertion.email,\n emailVerified: assertion.emailVerified,\n image: assertion.image ?? null,\n updatedAt: now,\n })\n .where(eq(authUser.id, existingUser.id))\n } else {\n await db.insert(authUser).values({\n id: userId,\n name: displayName,\n email: assertion.email,\n emailVerified: assertion.emailVerified,\n image: assertion.image ?? null,\n createdAt: now,\n updatedAt: now,\n })\n }\n\n await db\n .insert(authAccount)\n .values({\n id: cloudAuthAccountId(providerAccountId),\n userId,\n accountId: providerAccountId,\n providerId: VOYANT_CLOUD_PROVIDER_ID,\n createdAt: now,\n updatedAt: now,\n })\n .onConflictDoUpdate({\n target: [authAccount.providerId, authAccount.accountId],\n set: {\n userId,\n updatedAt: now,\n },\n })\n\n await upsertVoyantCloudUserLink(db, userId, assertion, now)\n\n await provisionCurrentUserProfile(db, {\n userId,\n name: displayName,\n image: assertion.image,\n isSuperAdmin: await isFirstAuthUser(db),\n })\n\n const [user] = await db\n .select({\n id: authUser.id,\n name: authUser.name,\n email: authUser.email,\n emailVerified: authUser.emailVerified,\n image: authUser.image,\n createdAt: authUser.createdAt,\n updatedAt: authUser.updatedAt,\n })\n .from(authUser)\n .where(eq(authUser.id, userId))\n .limit(1)\n\n if (!user?.email) {\n throw new Error(\"Voyant Cloud auth mirror user was not provisioned\")\n }\n\n const provisionedUser = {\n id: user.id,\n name: user.name,\n email: user.email,\n emailVerified: user.emailVerified,\n image: user.image,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt,\n }\n\n await options.onUserProvisioning?.({\n db,\n assertion,\n user: provisionedUser,\n isNewUser,\n provider: {\n providerId: VOYANT_CLOUD_PROVIDER_ID,\n providerAccountId,\n },\n })\n\n return provisionedUser\n}\n\nasync function findCloudMirrorUser(\n db: ReturnType<typeof getDb>,\n providerAccountId: string,\n email?: string,\n emailVerified?: boolean,\n): Promise<{ id: string } | null> {\n const [linked] = await db\n .select({ id: authUser.id })\n .from(cloudAuthUserLinks)\n .innerJoin(authUser, eq(authUser.id, cloudAuthUserLinks.userId))\n .where(\n and(\n eq(cloudAuthUserLinks.providerId, VOYANT_CLOUD_PROVIDER_ID),\n eq(cloudAuthUserLinks.providerAccountId, providerAccountId),\n ),\n )\n .limit(1)\n\n if (linked) return linked\n\n const [account] = await db\n .select({ id: authUser.id })\n .from(authAccount)\n .innerJoin(authUser, eq(authUser.id, authAccount.userId))\n .where(\n and(\n eq(authAccount.providerId, VOYANT_CLOUD_PROVIDER_ID),\n eq(authAccount.accountId, providerAccountId),\n ),\n )\n .limit(1)\n\n if (account) return account\n if (!email || emailVerified !== true) return null\n\n const [emailUser] = await db\n .select({ id: authUser.id })\n .from(authUser)\n .where(eq(authUser.email, email))\n .limit(1)\n\n return emailUser ?? null\n}\n\nfunction cloudAuthUserLinkValues(\n userId: string,\n assertion: CloudAdminAssertion,\n now: Date,\n): typeof cloudAuthUserLinks.$inferInsert {\n return {\n userId,\n providerId: VOYANT_CLOUD_PROVIDER_ID,\n providerAccountId: assertion.workosUserId,\n deploymentId: assertion.deploymentId,\n platformOrganizationId: assertion.platformOrganizationId,\n workosOrganizationId: assertion.workosOrganizationId,\n membershipId: assertion.membershipId ?? null,\n roleSlug: assertion.roleSlug ?? null,\n roleName: assertion.roleName ?? null,\n surfaces: assertion.surfaces ?? [],\n // Member RBAC scope set from the assertion. `null` (not `[]`) when the\n // platform sent none, so resolveAuthRequest can tell \"no scopes claim\"\n // (derive from role / full-access fallback) from \"explicitly empty\".\n scopes: assertion.scopes ?? null,\n lastAssertionAt: new Date(assertion.iat * 1000),\n lastRevalidatedAt: now,\n revokedAt: null,\n updatedAt: now,\n }\n}\n\nasync function upsertVoyantCloudUserLink(\n db: ReturnType<typeof getDb>,\n userId: string,\n assertion: CloudAdminAssertion,\n now: Date,\n): Promise<void> {\n const values = cloudAuthUserLinkValues(userId, assertion, now)\n const [existingForUser] = await db\n .select({ userId: cloudAuthUserLinks.userId })\n .from(cloudAuthUserLinks)\n .where(eq(cloudAuthUserLinks.userId, userId))\n .limit(1)\n\n if (existingForUser) {\n await db\n .update(cloudAuthUserLinks)\n .set({\n ...values,\n userId,\n revokedAt: null,\n })\n .where(eq(cloudAuthUserLinks.userId, userId))\n return\n }\n\n await db\n .insert(cloudAuthUserLinks)\n .values(values)\n .onConflictDoUpdate({\n target: [cloudAuthUserLinks.providerId, cloudAuthUserLinks.providerAccountId],\n set: {\n ...values,\n userId,\n revokedAt: null,\n },\n })\n}\n\nasync function upsertVoyantCloudSessionLink(\n db: ReturnType<typeof getDb>,\n input: {\n assertion: CloudAdminAssertion\n session: BetterAuthSessionForCookie\n revalidateAfterSeconds: number\n },\n): Promise<void> {\n const now = new Date()\n await db.insert(cloudAuthSessionLinks).values({\n sessionId: input.session.id,\n userId: input.session.userId,\n providerId: VOYANT_CLOUD_PROVIDER_ID,\n providerAccountId: input.assertion.workosUserId,\n deploymentId: input.assertion.deploymentId,\n revalidateAfter: new Date(now.getTime() + input.revalidateAfterSeconds * 1000),\n lastRevalidatedAt: now,\n revokedAt: null,\n updatedAt: now,\n })\n}\n\nfunction cloudAssertionDisplayName(assertion: CloudAdminAssertion): string {\n const fromParts = [assertion.firstName, assertion.lastName]\n .map((part) => part?.trim())\n .filter((part): part is string => Boolean(part))\n .join(\" \")\n return assertion.name?.trim() || fromParts || assertion.email\n}\n\nfunction cloudAuthAccountId(providerAccountId: string): string {\n return `acc_${VOYANT_CLOUD_PROVIDER_ID}_${providerAccountId}`\n}\n"]}
@@ -34,6 +34,8 @@ export type VerifyCloudAdminAuthCallbackInput = {
34
34
  requestUrl: string;
35
35
  cookieHeader?: string | null;
36
36
  cookieSecret: string;
37
+ /** Trusted external-scheme hint for deployments behind TLS termination. */
38
+ secureCookie?: boolean;
37
39
  now?: Date;
38
40
  };
39
41
  export type VerifyCloudAdminAuthCallbackResult = {
@@ -49,6 +51,6 @@ export type VerifyCloudAdminAuthCallbackResult = {
49
51
  clearCookie: string;
50
52
  };
51
53
  export declare function createCloudAdminAuthStart({ requestUrl, next, config, now, randomState, randomNonce, }: CreateCloudAdminAuthStartInput): Promise<CreateCloudAdminAuthStartResult>;
52
- export declare function verifyCloudAdminAuthCallback({ requestUrl, cookieHeader, cookieSecret, now, }: VerifyCloudAdminAuthCallbackInput): Promise<VerifyCloudAdminAuthCallbackResult>;
54
+ export declare function verifyCloudAdminAuthCallback({ requestUrl, cookieHeader, cookieSecret, secureCookie, now, }: VerifyCloudAdminAuthCallbackInput): Promise<VerifyCloudAdminAuthCallbackResult>;
53
55
  export declare function normalizeCloudAdminAuthNext(next: string | null | undefined, adminOrigin: string): string;
54
56
  export declare function buildClearCloudAdminAuthStateCookie(secure: boolean, path?: string): string;
@@ -1,3 +1,4 @@
1
+ import { CLOUD_STATE_COOKIE_KEY_CONTEXT, deriveContextKey, } from "@voyant-travel/utils/session-claims";
1
2
  import { base64UrlDecode, base64UrlEncode, isRecord, normalizeAbsoluteUrl, timingSafeEqual, } from "./utils.js";
2
3
  export const VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE = "voyant-cloud-admin-auth";
3
4
  const DEFAULT_STATE_TTL_SECONDS = 300;
@@ -33,16 +34,16 @@ export async function createCloudAdminAuthStart({ requestUrl, next, config, now
33
34
  setCookie: await buildCloudAdminAuthStateCookie({
34
35
  state,
35
36
  secret: config.cookieSecret,
36
- secure: parsedRequestUrl.protocol === "https:",
37
+ secure: new URL(state.redirectUri).protocol === "https:",
37
38
  path: cloudAuthCookiePath(new URL(state.redirectUri).pathname),
38
39
  maxAgeSeconds: ttlSeconds,
39
40
  }),
40
41
  };
41
42
  }
42
- export async function verifyCloudAdminAuthCallback({ requestUrl, cookieHeader, cookieSecret, now = new Date(), }) {
43
+ export async function verifyCloudAdminAuthCallback({ requestUrl, cookieHeader, cookieSecret, secureCookie, now = new Date(), }) {
43
44
  assertUsableSecret(cookieSecret);
44
45
  const url = new URL(requestUrl);
45
- const clearCookie = buildClearCloudAdminAuthStateCookie(url.protocol === "https:", cloudAuthCookiePath(url.pathname));
46
+ const clearCookie = buildClearCloudAdminAuthStateCookie(secureCookie ?? url.protocol === "https:", cloudAuthCookiePath(url.pathname));
46
47
  const cloudError = url.searchParams.get("error");
47
48
  if (cloudError) {
48
49
  return {
@@ -88,7 +89,7 @@ export function normalizeCloudAdminAuthNext(next, adminOrigin) {
88
89
  return "/";
89
90
  }
90
91
  }
91
- export function buildClearCloudAdminAuthStateCookie(secure, path = "/auth/cloud") {
92
+ export function buildClearCloudAdminAuthStateCookie(secure, path = "/auth/admin/cloud") {
92
93
  return serializeCookie(VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE, "", {
93
94
  httpOnly: true,
94
95
  maxAge: 0,
@@ -155,15 +156,11 @@ function readCookie(cookieHeader, name) {
155
156
  }
156
157
  function cloudAuthCookiePath(pathname) {
157
158
  const normalized = pathname.replace(/\/+$/, "") || "/";
158
- const marker = "/auth/cloud/";
159
- const markerIndex = normalized.indexOf(marker);
160
- if (markerIndex >= 0) {
161
- return normalized.slice(0, markerIndex + marker.length - 1) || "/auth/cloud";
162
- }
163
- if (normalized.endsWith("/auth/cloud")) {
159
+ if (normalized.endsWith("/callback"))
160
+ return normalized.slice(0, -"/callback".length) || "/";
161
+ if (normalized.endsWith("/cloud"))
164
162
  return normalized;
165
- }
166
- return "/auth/cloud";
163
+ return "/auth/admin/cloud";
167
164
  }
168
165
  function serializeCookie(name, value, options) {
169
166
  const parts = [
@@ -184,7 +181,8 @@ function randomToken() {
184
181
  return base64UrlEncode(bytes);
185
182
  }
186
183
  async function hmac(value, secret) {
187
- const key = await globalThis.crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
184
+ const stateCookieKey = await deriveContextKey(secret, CLOUD_STATE_COOKIE_KEY_CONTEXT);
185
+ const key = await globalThis.crypto.subtle.importKey("raw", new TextEncoder().encode(stateCookieKey), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
188
186
  const signature = await globalThis.crypto.subtle.sign("HMAC", key, new TextEncoder().encode(value));
189
187
  return base64UrlEncode(new Uint8Array(signature));
190
188
  }
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/cloud-broker/state.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n base64UrlDecode,\n base64UrlEncode,\n isRecord,\n normalizeAbsoluteUrl,\n timingSafeEqual,\n} from \"./utils.js\"\n\nexport const VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE = \"voyant-cloud-admin-auth\"\n\nconst DEFAULT_STATE_TTL_SECONDS = 300\nconst RANDOM_BYTES = 32\n\nexport type CloudAdminAuthStartConfig = {\n cloudAuthStartUrl: string\n deploymentId: string\n adminCallbackUrl: string\n cookieSecret: string\n appId?: string | null\n environment?: string | null\n surface?: string | null\n stateTtlSeconds?: number\n}\n\nexport type CloudAdminAuthState = {\n state: string\n nonce: string\n deploymentId: string\n redirectUri: string\n next: string\n expiresAt: number\n}\n\nexport type CreateCloudAdminAuthStartInput = {\n requestUrl: string\n next?: string | null\n config: CloudAdminAuthStartConfig\n now?: Date\n randomState?: string\n randomNonce?: string\n}\n\nexport type CreateCloudAdminAuthStartResult = {\n redirectUrl: string\n state: CloudAdminAuthState\n setCookie: string\n}\n\nexport type VerifyCloudAdminAuthCallbackInput = {\n requestUrl: string\n cookieHeader?: string | null\n cookieSecret: string\n now?: Date\n}\n\nexport type VerifyCloudAdminAuthCallbackResult =\n | {\n ok: true\n code: string\n state: CloudAdminAuthState\n clearCookie: string\n }\n | {\n ok: false\n error: \"cloud_error\" | \"invalid_request\" | \"invalid_state\" | \"expired_state\"\n description?: string\n cloudError?: string\n clearCookie: string\n }\n\nexport async function createCloudAdminAuthStart({\n requestUrl,\n next,\n config,\n now = new Date(),\n randomState,\n randomNonce,\n}: CreateCloudAdminAuthStartInput): Promise<CreateCloudAdminAuthStartResult> {\n validateStartConfig(config)\n\n const parsedRequestUrl = new URL(requestUrl)\n const ttlSeconds = config.stateTtlSeconds ?? DEFAULT_STATE_TTL_SECONDS\n const state: CloudAdminAuthState = {\n state: randomState ?? randomToken(),\n nonce: randomNonce ?? randomToken(),\n deploymentId: config.deploymentId.trim(),\n redirectUri: normalizeAbsoluteUrl(config.adminCallbackUrl, \"adminCallbackUrl\"),\n next: normalizeCloudAdminAuthNext(next, parsedRequestUrl.origin),\n expiresAt: now.getTime() + ttlSeconds * 1000,\n }\n const redirectUrl = new URL(config.cloudAuthStartUrl)\n redirectUrl.searchParams.set(\"deployment_id\", state.deploymentId)\n redirectUrl.searchParams.set(\"redirect_uri\", state.redirectUri)\n redirectUrl.searchParams.set(\"state\", state.state)\n redirectUrl.searchParams.set(\"nonce\", state.nonce)\n redirectUrl.searchParams.set(\"surface\", config.surface?.trim() || \"admin\")\n redirectUrl.searchParams.set(\"next\", state.next)\n\n if (config.appId?.trim()) {\n redirectUrl.searchParams.set(\"app_id\", config.appId.trim())\n }\n if (config.environment?.trim()) {\n redirectUrl.searchParams.set(\"environment\", config.environment.trim())\n }\n\n return {\n redirectUrl: redirectUrl.toString(),\n state,\n setCookie: await buildCloudAdminAuthStateCookie({\n state,\n secret: config.cookieSecret,\n secure: parsedRequestUrl.protocol === \"https:\",\n path: cloudAuthCookiePath(new URL(state.redirectUri).pathname),\n maxAgeSeconds: ttlSeconds,\n }),\n }\n}\n\nexport async function verifyCloudAdminAuthCallback({\n requestUrl,\n cookieHeader,\n cookieSecret,\n now = new Date(),\n}: VerifyCloudAdminAuthCallbackInput): Promise<VerifyCloudAdminAuthCallbackResult> {\n assertUsableSecret(cookieSecret)\n\n const url = new URL(requestUrl)\n const clearCookie = buildClearCloudAdminAuthStateCookie(\n url.protocol === \"https:\",\n cloudAuthCookiePath(url.pathname),\n )\n const cloudError = url.searchParams.get(\"error\")\n if (cloudError) {\n return {\n ok: false,\n error: \"cloud_error\",\n cloudError,\n description: url.searchParams.get(\"error_description\") ?? undefined,\n clearCookie,\n }\n }\n\n const stateParam = url.searchParams.get(\"state\")\n const code = url.searchParams.get(\"code\")\n if (!stateParam || !code) {\n return { ok: false, error: \"invalid_request\", clearCookie }\n }\n\n const stateCookie = readCookie(cookieHeader, VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE)\n if (!stateCookie) {\n return { ok: false, error: \"invalid_state\", clearCookie }\n }\n\n const state = await verifyCloudAdminAuthStateCookie(stateCookie, cookieSecret)\n if (!state || state.state !== stateParam) {\n return { ok: false, error: \"invalid_state\", clearCookie }\n }\n\n if (state.expiresAt <= now.getTime()) {\n return { ok: false, error: \"expired_state\", clearCookie }\n }\n\n return { ok: true, code, state, clearCookie }\n}\n\nexport function normalizeCloudAdminAuthNext(\n next: string | null | undefined,\n adminOrigin: string,\n): string {\n const value = next?.trim()\n if (!value) return \"/\"\n\n if (value.startsWith(\"/\") && !value.startsWith(\"//\")) {\n return value\n }\n\n try {\n const url = new URL(value)\n if (url.origin !== adminOrigin) return \"/\"\n return `${url.pathname}${url.search}${url.hash}` || \"/\"\n } catch {\n return \"/\"\n }\n}\n\nexport function buildClearCloudAdminAuthStateCookie(secure: boolean, path = \"/auth/cloud\"): string {\n return serializeCookie(VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE, \"\", {\n httpOnly: true,\n maxAge: 0,\n path,\n sameSite: \"Lax\",\n secure,\n })\n}\n\nasync function buildCloudAdminAuthStateCookie({\n state,\n secret,\n secure,\n path,\n maxAgeSeconds,\n}: {\n state: CloudAdminAuthState\n secret: string\n secure: boolean\n path: string\n maxAgeSeconds: number\n}): Promise<string> {\n const payload = base64UrlEncode(new TextEncoder().encode(JSON.stringify(state)))\n const signature = await hmac(payload, secret)\n\n return serializeCookie(VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE, `${payload}.${signature}`, {\n httpOnly: true,\n maxAge: maxAgeSeconds,\n path,\n sameSite: \"Lax\",\n secure,\n })\n}\n\nasync function verifyCloudAdminAuthStateCookie(\n cookieValue: string,\n secret: string,\n): Promise<CloudAdminAuthState | null> {\n const [payload, signature, ...rest] = cookieValue.split(\".\")\n if (!payload || !signature || rest.length > 0) return null\n\n const expectedSignature = await hmac(payload, secret)\n if (!timingSafeEqual(signature, expectedSignature)) return null\n\n const parsed = JSON.parse(new TextDecoder().decode(base64UrlDecode(payload))) as unknown\n if (!isCloudAdminAuthState(parsed)) return null\n return parsed\n}\n\nfunction validateStartConfig(config: CloudAdminAuthStartConfig): void {\n assertUsableSecret(config.cookieSecret)\n normalizeAbsoluteUrl(config.cloudAuthStartUrl, \"cloudAuthStartUrl\")\n normalizeAbsoluteUrl(config.adminCallbackUrl, \"adminCallbackUrl\")\n\n if (!config.deploymentId.trim()) {\n throw new Error(\"Voyant Cloud auth requires deploymentId\")\n }\n}\n\nfunction assertUsableSecret(secret: string): void {\n if (!secret || secret.length < 32) {\n throw new Error(\"Voyant Cloud auth requires a cookie secret with at least 32 characters\")\n }\n}\n\nfunction isCloudAdminAuthState(value: unknown): value is CloudAdminAuthState {\n if (!isRecord(value)) return false\n return (\n typeof value.state === \"string\" &&\n typeof value.nonce === \"string\" &&\n typeof value.deploymentId === \"string\" &&\n typeof value.redirectUri === \"string\" &&\n typeof value.next === \"string\" &&\n typeof value.expiresAt === \"number\"\n )\n}\n\nfunction readCookie(cookieHeader: string | null | undefined, name: string): string | null {\n if (!cookieHeader) return null\n\n for (const part of cookieHeader.split(\";\")) {\n const [rawName, ...rawValue] = part.trim().split(\"=\")\n if (rawName === name) return rawValue.join(\"=\")\n }\n\n return null\n}\n\nfunction cloudAuthCookiePath(pathname: string): string {\n const normalized = pathname.replace(/\\/+$/, \"\") || \"/\"\n const marker = \"/auth/cloud/\"\n const markerIndex = normalized.indexOf(marker)\n if (markerIndex >= 0) {\n return normalized.slice(0, markerIndex + marker.length - 1) || \"/auth/cloud\"\n }\n\n if (normalized.endsWith(\"/auth/cloud\")) {\n return normalized\n }\n\n return \"/auth/cloud\"\n}\n\nfunction serializeCookie(\n name: string,\n value: string,\n options: {\n httpOnly: boolean\n maxAge: number\n path: string\n sameSite: \"Lax\" | \"Strict\" | \"None\"\n secure: boolean\n },\n): string {\n const parts = [\n `${name}=${value}`,\n `Max-Age=${options.maxAge}`,\n `Path=${options.path}`,\n `SameSite=${options.sameSite}`,\n ]\n\n if (options.httpOnly) parts.push(\"HttpOnly\")\n if (options.secure) parts.push(\"Secure\")\n\n return parts.join(\"; \")\n}\n\nfunction randomToken(): string {\n const bytes = new Uint8Array(RANDOM_BYTES)\n globalThis.crypto.getRandomValues(bytes)\n return base64UrlEncode(bytes)\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await globalThis.crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n )\n const signature = await globalThis.crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(value),\n )\n return base64UrlEncode(new Uint8Array(signature))\n}\n"]}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/cloud-broker/state.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CLOUD_STATE_COOKIE_KEY_CONTEXT,\n deriveContextKey,\n} from \"@voyant-travel/utils/session-claims\"\nimport {\n base64UrlDecode,\n base64UrlEncode,\n isRecord,\n normalizeAbsoluteUrl,\n timingSafeEqual,\n} from \"./utils.js\"\n\nexport const VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE = \"voyant-cloud-admin-auth\"\n\nconst DEFAULT_STATE_TTL_SECONDS = 300\nconst RANDOM_BYTES = 32\n\nexport type CloudAdminAuthStartConfig = {\n cloudAuthStartUrl: string\n deploymentId: string\n adminCallbackUrl: string\n cookieSecret: string\n appId?: string | null\n environment?: string | null\n surface?: string | null\n stateTtlSeconds?: number\n}\n\nexport type CloudAdminAuthState = {\n state: string\n nonce: string\n deploymentId: string\n redirectUri: string\n next: string\n expiresAt: number\n}\n\nexport type CreateCloudAdminAuthStartInput = {\n requestUrl: string\n next?: string | null\n config: CloudAdminAuthStartConfig\n now?: Date\n randomState?: string\n randomNonce?: string\n}\n\nexport type CreateCloudAdminAuthStartResult = {\n redirectUrl: string\n state: CloudAdminAuthState\n setCookie: string\n}\n\nexport type VerifyCloudAdminAuthCallbackInput = {\n requestUrl: string\n cookieHeader?: string | null\n cookieSecret: string\n /** Trusted external-scheme hint for deployments behind TLS termination. */\n secureCookie?: boolean\n now?: Date\n}\n\nexport type VerifyCloudAdminAuthCallbackResult =\n | {\n ok: true\n code: string\n state: CloudAdminAuthState\n clearCookie: string\n }\n | {\n ok: false\n error: \"cloud_error\" | \"invalid_request\" | \"invalid_state\" | \"expired_state\"\n description?: string\n cloudError?: string\n clearCookie: string\n }\n\nexport async function createCloudAdminAuthStart({\n requestUrl,\n next,\n config,\n now = new Date(),\n randomState,\n randomNonce,\n}: CreateCloudAdminAuthStartInput): Promise<CreateCloudAdminAuthStartResult> {\n validateStartConfig(config)\n\n const parsedRequestUrl = new URL(requestUrl)\n const ttlSeconds = config.stateTtlSeconds ?? DEFAULT_STATE_TTL_SECONDS\n const state: CloudAdminAuthState = {\n state: randomState ?? randomToken(),\n nonce: randomNonce ?? randomToken(),\n deploymentId: config.deploymentId.trim(),\n redirectUri: normalizeAbsoluteUrl(config.adminCallbackUrl, \"adminCallbackUrl\"),\n next: normalizeCloudAdminAuthNext(next, parsedRequestUrl.origin),\n expiresAt: now.getTime() + ttlSeconds * 1000,\n }\n const redirectUrl = new URL(config.cloudAuthStartUrl)\n redirectUrl.searchParams.set(\"deployment_id\", state.deploymentId)\n redirectUrl.searchParams.set(\"redirect_uri\", state.redirectUri)\n redirectUrl.searchParams.set(\"state\", state.state)\n redirectUrl.searchParams.set(\"nonce\", state.nonce)\n redirectUrl.searchParams.set(\"surface\", config.surface?.trim() || \"admin\")\n redirectUrl.searchParams.set(\"next\", state.next)\n\n if (config.appId?.trim()) {\n redirectUrl.searchParams.set(\"app_id\", config.appId.trim())\n }\n if (config.environment?.trim()) {\n redirectUrl.searchParams.set(\"environment\", config.environment.trim())\n }\n\n return {\n redirectUrl: redirectUrl.toString(),\n state,\n setCookie: await buildCloudAdminAuthStateCookie({\n state,\n secret: config.cookieSecret,\n secure: new URL(state.redirectUri).protocol === \"https:\",\n path: cloudAuthCookiePath(new URL(state.redirectUri).pathname),\n maxAgeSeconds: ttlSeconds,\n }),\n }\n}\n\nexport async function verifyCloudAdminAuthCallback({\n requestUrl,\n cookieHeader,\n cookieSecret,\n secureCookie,\n now = new Date(),\n}: VerifyCloudAdminAuthCallbackInput): Promise<VerifyCloudAdminAuthCallbackResult> {\n assertUsableSecret(cookieSecret)\n\n const url = new URL(requestUrl)\n const clearCookie = buildClearCloudAdminAuthStateCookie(\n secureCookie ?? url.protocol === \"https:\",\n cloudAuthCookiePath(url.pathname),\n )\n const cloudError = url.searchParams.get(\"error\")\n if (cloudError) {\n return {\n ok: false,\n error: \"cloud_error\",\n cloudError,\n description: url.searchParams.get(\"error_description\") ?? undefined,\n clearCookie,\n }\n }\n\n const stateParam = url.searchParams.get(\"state\")\n const code = url.searchParams.get(\"code\")\n if (!stateParam || !code) {\n return { ok: false, error: \"invalid_request\", clearCookie }\n }\n\n const stateCookie = readCookie(cookieHeader, VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE)\n if (!stateCookie) {\n return { ok: false, error: \"invalid_state\", clearCookie }\n }\n\n const state = await verifyCloudAdminAuthStateCookie(stateCookie, cookieSecret)\n if (!state || state.state !== stateParam) {\n return { ok: false, error: \"invalid_state\", clearCookie }\n }\n\n if (state.expiresAt <= now.getTime()) {\n return { ok: false, error: \"expired_state\", clearCookie }\n }\n\n return { ok: true, code, state, clearCookie }\n}\n\nexport function normalizeCloudAdminAuthNext(\n next: string | null | undefined,\n adminOrigin: string,\n): string {\n const value = next?.trim()\n if (!value) return \"/\"\n\n if (value.startsWith(\"/\") && !value.startsWith(\"//\")) {\n return value\n }\n\n try {\n const url = new URL(value)\n if (url.origin !== adminOrigin) return \"/\"\n return `${url.pathname}${url.search}${url.hash}` || \"/\"\n } catch {\n return \"/\"\n }\n}\n\nexport function buildClearCloudAdminAuthStateCookie(\n secure: boolean,\n path = \"/auth/admin/cloud\",\n): string {\n return serializeCookie(VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE, \"\", {\n httpOnly: true,\n maxAge: 0,\n path,\n sameSite: \"Lax\",\n secure,\n })\n}\n\nasync function buildCloudAdminAuthStateCookie({\n state,\n secret,\n secure,\n path,\n maxAgeSeconds,\n}: {\n state: CloudAdminAuthState\n secret: string\n secure: boolean\n path: string\n maxAgeSeconds: number\n}): Promise<string> {\n const payload = base64UrlEncode(new TextEncoder().encode(JSON.stringify(state)))\n const signature = await hmac(payload, secret)\n\n return serializeCookie(VOYANT_CLOUD_ADMIN_AUTH_STATE_COOKIE, `${payload}.${signature}`, {\n httpOnly: true,\n maxAge: maxAgeSeconds,\n path,\n sameSite: \"Lax\",\n secure,\n })\n}\n\nasync function verifyCloudAdminAuthStateCookie(\n cookieValue: string,\n secret: string,\n): Promise<CloudAdminAuthState | null> {\n const [payload, signature, ...rest] = cookieValue.split(\".\")\n if (!payload || !signature || rest.length > 0) return null\n\n const expectedSignature = await hmac(payload, secret)\n if (!timingSafeEqual(signature, expectedSignature)) return null\n\n const parsed = JSON.parse(new TextDecoder().decode(base64UrlDecode(payload))) as unknown\n if (!isCloudAdminAuthState(parsed)) return null\n return parsed\n}\n\nfunction validateStartConfig(config: CloudAdminAuthStartConfig): void {\n assertUsableSecret(config.cookieSecret)\n normalizeAbsoluteUrl(config.cloudAuthStartUrl, \"cloudAuthStartUrl\")\n normalizeAbsoluteUrl(config.adminCallbackUrl, \"adminCallbackUrl\")\n\n if (!config.deploymentId.trim()) {\n throw new Error(\"Voyant Cloud auth requires deploymentId\")\n }\n}\n\nfunction assertUsableSecret(secret: string): void {\n if (!secret || secret.length < 32) {\n throw new Error(\"Voyant Cloud auth requires a cookie secret with at least 32 characters\")\n }\n}\n\nfunction isCloudAdminAuthState(value: unknown): value is CloudAdminAuthState {\n if (!isRecord(value)) return false\n return (\n typeof value.state === \"string\" &&\n typeof value.nonce === \"string\" &&\n typeof value.deploymentId === \"string\" &&\n typeof value.redirectUri === \"string\" &&\n typeof value.next === \"string\" &&\n typeof value.expiresAt === \"number\"\n )\n}\n\nfunction readCookie(cookieHeader: string | null | undefined, name: string): string | null {\n if (!cookieHeader) return null\n\n for (const part of cookieHeader.split(\";\")) {\n const [rawName, ...rawValue] = part.trim().split(\"=\")\n if (rawName === name) return rawValue.join(\"=\")\n }\n\n return null\n}\n\nfunction cloudAuthCookiePath(pathname: string): string {\n const normalized = pathname.replace(/\\/+$/, \"\") || \"/\"\n if (normalized.endsWith(\"/callback\")) return normalized.slice(0, -\"/callback\".length) || \"/\"\n if (normalized.endsWith(\"/cloud\")) return normalized\n\n return \"/auth/admin/cloud\"\n}\n\nfunction serializeCookie(\n name: string,\n value: string,\n options: {\n httpOnly: boolean\n maxAge: number\n path: string\n sameSite: \"Lax\" | \"Strict\" | \"None\"\n secure: boolean\n },\n): string {\n const parts = [\n `${name}=${value}`,\n `Max-Age=${options.maxAge}`,\n `Path=${options.path}`,\n `SameSite=${options.sameSite}`,\n ]\n\n if (options.httpOnly) parts.push(\"HttpOnly\")\n if (options.secure) parts.push(\"Secure\")\n\n return parts.join(\"; \")\n}\n\nfunction randomToken(): string {\n const bytes = new Uint8Array(RANDOM_BYTES)\n globalThis.crypto.getRandomValues(bytes)\n return base64UrlEncode(bytes)\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const stateCookieKey = await deriveContextKey(secret, CLOUD_STATE_COOKIE_KEY_CONTEXT)\n const key = await globalThis.crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(stateCookieKey),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n )\n const signature = await globalThis.crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(value),\n )\n return base64UrlEncode(new Uint8Array(signature))\n}\n"]}
package/dist/edge.js CHANGED
@@ -82,7 +82,7 @@ export async function getAuthContextFromHeaders(headers, options = {}) {
82
82
  ? [options.cookieName]
83
83
  : realm === "customer"
84
84
  ? ["voyant-customer.session_token"]
85
- : ["voyant-admin.session_token", "better-auth.session_token"];
85
+ : ["voyant-admin.session_token"];
86
86
  const rawCookie = getRawCookieValue(headers, cookieNames);
87
87
  if (!rawCookie)
88
88
  return EMPTY;
package/dist/edge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"edge.js","sourceRoot":"","sources":["../src/edge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Edge-compatible session check for Next.js middleware.\n *\n * Does NOT import better-auth (which uses eval / new Function via\n * createTelemetry, incompatible with Edge Runtime).\n *\n * Instead it reads the session cookie, unsigns it using the same\n * HMAC-SHA256 scheme that better-call uses, and queries better-auth's\n * `session` + `user` tables directly via the Neon HTTP drizzle adapter.\n *\n * With the identity table collapse, authUser.id IS the userId —\n * no extra identity lookup required.\n */\n\nimport { getDb } from \"@voyant-travel/db\"\nimport {\n authSession,\n authUser,\n customerAuthSession,\n customerAuthUser,\n} from \"@voyant-travel/db/schema/iam\"\nimport { and, eq, gt } from \"drizzle-orm\"\n\nexport interface EdgeAuthRealmOptions {\n realm?: \"admin\" | \"customer\"\n cookieName?: string\n secret?: string\n}\n\nfunction getAuthSecret(realm: \"admin\" | \"customer\"): string {\n if (realm === \"customer\") {\n return process.env.BETTER_AUTH_CUSTOMER_SECRET || \"\"\n }\n return process.env.BETTER_AUTH_ADMIN_SECRET || \"\"\n}\n\n/**\n * Unsign a Better Auth cookie value.\n *\n * Better Auth (via better-call) signs cookies as:\n * encodeURIComponent(value + \".\" + base64(HMAC-SHA256(value, secret)))\n *\n * This function reverses that: URL-decode, split at last \".\",\n * verify the HMAC-SHA256 signature, and return the raw value.\n */\nasync function unsignCookie(rawCookieValue: string, secret: string): Promise<string | null> {\n const decoded = decodeURIComponent(rawCookieValue)\n\n const lastDot = decoded.lastIndexOf(\".\")\n if (lastDot < 1) return null\n\n const value = decoded.substring(0, lastDot)\n const signature = decoded.substring(lastDot + 1)\n\n // Better Auth signatures are 44-char base64 strings ending with \"=\"\n if (signature.length !== 44 || !signature.endsWith(\"=\")) return null\n\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"verify\"],\n )\n\n const sigBinStr = atob(signature)\n const sigBytes = new Uint8Array(sigBinStr.length)\n for (let i = 0; i < sigBinStr.length; i++) sigBytes[i] = sigBinStr.charCodeAt(i)\n\n const valid = await crypto.subtle.verify(\"HMAC\", key, sigBytes, encoder.encode(value))\n return valid ? value : null\n}\n\nfunction getRawCookieValue(headers: Headers, cookieNames: readonly string[]): string | null {\n const cookieHeader = headers.get(\"cookie\")\n if (!cookieHeader) return null\n\n for (const part of cookieHeader.split(\";\")) {\n const trimmed = part.trim()\n const eqIdx = trimmed.indexOf(\"=\")\n if (eqIdx === -1) continue\n const name = trimmed.slice(0, eqIdx).trim()\n if (cookieNames.includes(name)) {\n return trimmed.slice(eqIdx + 1).trim() || null\n }\n }\n return null\n}\n\ntype AuthContext = {\n userId: string | null\n email: string | null\n sessionId: string | null\n}\n\nconst EMPTY: AuthContext = {\n userId: null,\n email: null,\n sessionId: null,\n}\n\n/**\n * Lightweight edge-compatible session check.\n * Safe to call from Next.js middleware.\n *\n * Returns `userId` which is Better Auth's `user.id` — the single\n * canonical user identifier after the identity table collapse.\n */\nexport async function getAuthContextFromHeaders(\n headers: Headers,\n options: EdgeAuthRealmOptions = {},\n): Promise<AuthContext> {\n const realm = options.realm ?? \"admin\"\n const cookieNames = options.cookieName\n ? [options.cookieName]\n : realm === \"customer\"\n ? [\"voyant-customer.session_token\"]\n : [\"voyant-admin.session_token\", \"better-auth.session_token\"]\n const rawCookie = getRawCookieValue(headers, cookieNames)\n if (!rawCookie) return EMPTY\n\n const secret = options.secret ?? getAuthSecret(realm)\n if (!secret) return EMPTY\n\n const token = await unsignCookie(rawCookie, secret)\n if (!token) return EMPTY\n\n const db = getDb(\"edge\")\n\n const [row] =\n realm === \"customer\"\n ? await db\n .select({\n sessionId: customerAuthSession.id,\n userId: customerAuthSession.userId,\n email: customerAuthUser.email,\n })\n .from(customerAuthSession)\n .innerJoin(customerAuthUser, eq(customerAuthUser.id, customerAuthSession.userId))\n .where(\n and(\n eq(customerAuthSession.token, token),\n gt(customerAuthSession.expiresAt, new Date()),\n ),\n )\n .limit(1)\n : await db\n .select({\n sessionId: authSession.id,\n userId: authSession.userId,\n email: authUser.email,\n })\n .from(authSession)\n .innerJoin(authUser, eq(authUser.id, authSession.userId))\n .where(and(eq(authSession.token, token), gt(authSession.expiresAt, new Date())))\n .limit(1)\n\n if (!row) return EMPTY\n\n return {\n userId: row.userId,\n email: row.email,\n sessionId: row.sessionId,\n }\n}\n"]}
1
+ {"version":3,"file":"edge.js","sourceRoot":"","sources":["../src/edge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACzC,OAAO,EACL,WAAW,EACX,QAAQ,EACR,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAQzC,SAAS,aAAa,CAAC,KAA2B;IAChD,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,EAAE,CAAA;IACtD,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,EAAE,CAAA;AACnD,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,YAAY,CAAC,cAAsB,EAAE,MAAc;IAChE,MAAM,OAAO,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAA;IAElD,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACxC,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAE5B,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;IAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,GAAG,CAAC,CAAC,CAAA;IAEhD,oEAAoE;IACpE,IAAI,SAAS,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAEpE,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACvC,KAAK,EACL,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EACtB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAA;IACjC,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IAEhF,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IACtF,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AAC7B,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAgB,EAAE,WAA8B;IACzE,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC1C,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAA;IAE9B,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAC3B,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,SAAQ;QAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QAC3C,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAA;QAChD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAQD,MAAM,KAAK,GAAgB;IACzB,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,SAAS,EAAE,IAAI;CAChB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAgB,EAChB,UAAgC,EAAE;IAElC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAA;IACtC,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU;QACpC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QACtB,CAAC,CAAC,KAAK,KAAK,UAAU;YACpB,CAAC,CAAC,CAAC,+BAA+B,CAAC;YACnC,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAA;IACpC,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IACzD,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAA;IAE5B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC,KAAK,CAAC,CAAA;IACrD,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEzB,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IACnD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IAExB,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;IAExB,MAAM,CAAC,GAAG,CAAC,GACT,KAAK,KAAK,UAAU;QAClB,CAAC,CAAC,MAAM,EAAE;aACL,MAAM,CAAC;YACN,SAAS,EAAE,mBAAmB,CAAC,EAAE;YACjC,MAAM,EAAE,mBAAmB,CAAC,MAAM;YAClC,KAAK,EAAE,gBAAgB,CAAC,KAAK;SAC9B,CAAC;aACD,IAAI,CAAC,mBAAmB,CAAC;aACzB,SAAS,CAAC,gBAAgB,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;aAChF,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,EACpC,EAAE,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,CAC9C,CACF;aACA,KAAK,CAAC,CAAC,CAAC;QACb,CAAC,CAAC,MAAM,EAAE;aACL,MAAM,CAAC;YACN,SAAS,EAAE,WAAW,CAAC,EAAE;YACzB,MAAM,EAAE,WAAW,CAAC,MAAM;YAC1B,KAAK,EAAE,QAAQ,CAAC,KAAK;SACtB,CAAC;aACD,IAAI,CAAC,WAAW,CAAC;aACjB,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;aACxD,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;aAC/E,KAAK,CAAC,CAAC,CAAC,CAAA;IAEjB,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IAEtB,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,SAAS,EAAE,GAAG,CAAC,SAAS;KACzB,CAAA;AACH,CAAC","sourcesContent":["/**\n * Edge-compatible session check for Next.js middleware.\n *\n * Does NOT import better-auth (which uses eval / new Function via\n * createTelemetry, incompatible with Edge Runtime).\n *\n * Instead it reads the session cookie, unsigns it using the same\n * HMAC-SHA256 scheme that better-call uses, and queries better-auth's\n * `session` + `user` tables directly via the Neon HTTP drizzle adapter.\n *\n * With the identity table collapse, authUser.id IS the userId —\n * no extra identity lookup required.\n */\n\nimport { getDb } from \"@voyant-travel/db\"\nimport {\n authSession,\n authUser,\n customerAuthSession,\n customerAuthUser,\n} from \"@voyant-travel/db/schema/iam\"\nimport { and, eq, gt } from \"drizzle-orm\"\n\nexport interface EdgeAuthRealmOptions {\n realm?: \"admin\" | \"customer\"\n cookieName?: string\n secret?: string\n}\n\nfunction getAuthSecret(realm: \"admin\" | \"customer\"): string {\n if (realm === \"customer\") {\n return process.env.BETTER_AUTH_CUSTOMER_SECRET || \"\"\n }\n return process.env.BETTER_AUTH_ADMIN_SECRET || \"\"\n}\n\n/**\n * Unsign a Better Auth cookie value.\n *\n * Better Auth (via better-call) signs cookies as:\n * encodeURIComponent(value + \".\" + base64(HMAC-SHA256(value, secret)))\n *\n * This function reverses that: URL-decode, split at last \".\",\n * verify the HMAC-SHA256 signature, and return the raw value.\n */\nasync function unsignCookie(rawCookieValue: string, secret: string): Promise<string | null> {\n const decoded = decodeURIComponent(rawCookieValue)\n\n const lastDot = decoded.lastIndexOf(\".\")\n if (lastDot < 1) return null\n\n const value = decoded.substring(0, lastDot)\n const signature = decoded.substring(lastDot + 1)\n\n // Better Auth signatures are 44-char base64 strings ending with \"=\"\n if (signature.length !== 44 || !signature.endsWith(\"=\")) return null\n\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"verify\"],\n )\n\n const sigBinStr = atob(signature)\n const sigBytes = new Uint8Array(sigBinStr.length)\n for (let i = 0; i < sigBinStr.length; i++) sigBytes[i] = sigBinStr.charCodeAt(i)\n\n const valid = await crypto.subtle.verify(\"HMAC\", key, sigBytes, encoder.encode(value))\n return valid ? value : null\n}\n\nfunction getRawCookieValue(headers: Headers, cookieNames: readonly string[]): string | null {\n const cookieHeader = headers.get(\"cookie\")\n if (!cookieHeader) return null\n\n for (const part of cookieHeader.split(\";\")) {\n const trimmed = part.trim()\n const eqIdx = trimmed.indexOf(\"=\")\n if (eqIdx === -1) continue\n const name = trimmed.slice(0, eqIdx).trim()\n if (cookieNames.includes(name)) {\n return trimmed.slice(eqIdx + 1).trim() || null\n }\n }\n return null\n}\n\ntype AuthContext = {\n userId: string | null\n email: string | null\n sessionId: string | null\n}\n\nconst EMPTY: AuthContext = {\n userId: null,\n email: null,\n sessionId: null,\n}\n\n/**\n * Lightweight edge-compatible session check.\n * Safe to call from Next.js middleware.\n *\n * Returns `userId` which is Better Auth's `user.id` — the single\n * canonical user identifier after the identity table collapse.\n */\nexport async function getAuthContextFromHeaders(\n headers: Headers,\n options: EdgeAuthRealmOptions = {},\n): Promise<AuthContext> {\n const realm = options.realm ?? \"admin\"\n const cookieNames = options.cookieName\n ? [options.cookieName]\n : realm === \"customer\"\n ? [\"voyant-customer.session_token\"]\n : [\"voyant-admin.session_token\"]\n const rawCookie = getRawCookieValue(headers, cookieNames)\n if (!rawCookie) return EMPTY\n\n const secret = options.secret ?? getAuthSecret(realm)\n if (!secret) return EMPTY\n\n const token = await unsignCookie(rawCookie, secret)\n if (!token) return EMPTY\n\n const db = getDb(\"edge\")\n\n const [row] =\n realm === \"customer\"\n ? await db\n .select({\n sessionId: customerAuthSession.id,\n userId: customerAuthSession.userId,\n email: customerAuthUser.email,\n })\n .from(customerAuthSession)\n .innerJoin(customerAuthUser, eq(customerAuthUser.id, customerAuthSession.userId))\n .where(\n and(\n eq(customerAuthSession.token, token),\n gt(customerAuthSession.expiresAt, new Date()),\n ),\n )\n .limit(1)\n : await db\n .select({\n sessionId: authSession.id,\n userId: authSession.userId,\n email: authUser.email,\n })\n .from(authSession)\n .innerJoin(authUser, eq(authUser.id, authSession.userId))\n .where(and(eq(authSession.token, token), gt(authSession.expiresAt, new Date())))\n .limit(1)\n\n if (!row) return EMPTY\n\n return {\n userId: row.userId,\n email: row.email,\n sessionId: row.sessionId,\n }\n}\n"]}
@@ -3,14 +3,14 @@
3
3
  *
4
4
  * agent-quality: file-size exception -- Auth handler keeps local and Voyant Cloud auth flows co-located until the route surface is split by auth mode.
5
5
  *
6
- * Mounts Better Auth at /auth/* for authentication operations.
6
+ * Mounts Better Auth at /auth/admin/* and /auth/customer/*.
7
7
  * Same-origin — no CORS needed. Session cookies work naturally.
8
8
  *
9
9
  * Also provides /auth/status (user provisioning) and /auth/me (user info).
10
10
  */
11
11
  import { type NodeDatabaseEnv } from "@voyant-travel/db/runtime";
12
12
  import { type SelectApikey } from "@voyant-travel/db/schema/iam";
13
- import type { Reporter, VoyantDb, VoyantRequestAuthContext } from "@voyant-travel/hono";
13
+ import type { Reporter, VoyantDb, VoyantResolvedSessionAuthContext } from "@voyant-travel/hono";
14
14
  import type { AccessCatalog } from "@voyant-travel/types/api-keys";
15
15
  import { Hono } from "hono";
16
16
  import { type CreateBetterAuthOptions, type CustomerAuthMethods } from "./server.js";
@@ -24,7 +24,8 @@ export interface OperatorAuthNodeEnv extends NodeDatabaseEnv {
24
24
  CORS_ALLOWLIST?: string;
25
25
  DASH_BASE_URL?: string;
26
26
  EMAIL_FROM?: string;
27
- SESSION_CLAIMS_SECRET: string;
27
+ SESSION_CLAIMS_ADMIN_SECRET: string;
28
+ SESSION_CLAIMS_CUSTOMER_SECRET?: string;
28
29
  VOYANT_ADMIN_AUTH_MODE?: string;
29
30
  VOYANT_CUSTOMER_AUTH_MODE?: string;
30
31
  VOYANT_CUSTOMER_AUTH_CONFIG_JSON?: string;
@@ -133,7 +134,7 @@ export declare function createOperatorAuthNodeRuntime<Env extends OperatorAuthNo
133
134
  getBootstrapStatusForRequest: (request: Request, env: Env) => Promise<OperatorAuthBootstrapStatus>;
134
135
  getCurrentUserForRequest: (request: Request, env: Env) => Promise<OperatorCurrentUser | null>;
135
136
  hasAuthPermission: (request: Request, env: Env) => Promise<boolean>;
136
- resolveAuthRequest: (request: Request, env: Env) => Promise<VoyantRequestAuthContext | null>;
137
+ resolveAuthRequest: (request: Request, env: Env) => Promise<VoyantResolvedSessionAuthContext | null>;
137
138
  validateApiTokenAccess: (env: Env, db: VoyantDb, apiKey: SelectApikey) => Promise<boolean>;
138
139
  };
139
140
  export {};
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * agent-quality: file-size exception -- Auth handler keeps local and Voyant Cloud auth flows co-located until the route surface is split by auth mode.
5
5
  *
6
- * Mounts Better Auth at /auth/* for authentication operations.
6
+ * Mounts Better Auth at /auth/admin/* and /auth/customer/*.
7
7
  * Same-origin — no CORS needed. Session cookies work naturally.
8
8
  *
9
9
  * Also provides /auth/status (user provisioning) and /auth/me (user info).
@@ -19,6 +19,15 @@ import { createVoyantCloudAdminAuthPlugin, revalidateVoyantCloudAdminAuthSession
19
19
  import { buildClearCloudAdminAuthStateCookie, createCloudAdminAuthStart } from "./cloud-broker.js";
20
20
  import { createAdminBetterAuth, createCustomerBetterAuth, handleApiTokenManagementRequest, handleOrganizationMembersRequest, } from "./server.js";
21
21
  import { ensureCurrentUserProfile } from "./workspace.js";
22
+ function classifyOperatorApiAuthSurface(requestUrl) {
23
+ const pathname = new URL(requestUrl).pathname.replace(/\/+$/, "") || "/";
24
+ const normalized = pathname.startsWith("/api/") ? pathname.slice(4) : pathname;
25
+ if (normalized === "/v1/admin" || normalized.startsWith("/v1/admin/"))
26
+ return "admin";
27
+ if (normalized === "/v1/public" || normalized.startsWith("/v1/public/"))
28
+ return "customer";
29
+ return null;
30
+ }
22
31
  /** Resolve Better Auth's standard cross-subdomain cookie policy for Node hosts. */
23
32
  export function buildBetterAuthCookieAdvancedOptions(env) {
24
33
  const domain = env.AUTH_COOKIE_DOMAIN?.trim();
@@ -186,7 +195,7 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
186
195
  cloudAuthStartUrl,
187
196
  deploymentId,
188
197
  adminCallbackUrl: `${getPublicApiBaseUrl(env)}/auth/admin/cloud/callback`,
189
- cookieSecret: env.SESSION_CLAIMS_SECRET,
198
+ cookieSecret: env.SESSION_CLAIMS_ADMIN_SECRET,
190
199
  appId: env.VOYANT_CLOUD_APP_ID?.trim() || undefined,
191
200
  environment: env.VOYANT_CLOUD_ENVIRONMENT?.trim() || undefined,
192
201
  };
@@ -387,7 +396,9 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
387
396
  ? [
388
397
  createVoyantCloudAdminAuthPlugin({
389
398
  db: cloudAuthDb,
390
- cookieSecret: env.SESSION_CLAIMS_SECRET,
399
+ cookieSecret: env.SESSION_CLAIMS_ADMIN_SECRET,
400
+ secureStateCookie: new URL(`${getPublicApiBaseUrl(env)}/auth/admin/cloud/callback`).protocol ===
401
+ "https:",
391
402
  exchange: cloudAuthExchange,
392
403
  }),
393
404
  ]
@@ -453,11 +464,6 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
453
464
  },
454
465
  });
455
466
  }
456
- function rewriteLegacyAdminAuthRequest(request) {
457
- const url = new URL(request.url);
458
- url.pathname = url.pathname.replace("/auth", "/auth/admin");
459
- return new Request(url, request);
460
- }
461
467
  const FULL_ACCESS_SCOPES = ["*"];
462
468
  function scopesForOperatorRole(role) {
463
469
  const base = accessCatalogScopesForRole(role, runtimeOptions.accessCatalog);
@@ -503,7 +509,10 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
503
509
  return profile?.permissions ?? FULL_ACCESS_SCOPES;
504
510
  }
505
511
  async function resolveAuthRequest(request, env) {
506
- const customerSurface = new URL(request.url).pathname.includes("/v1/public/");
512
+ const surface = classifyOperatorApiAuthSurface(request.url);
513
+ if (!surface)
514
+ return null;
515
+ const customerSurface = surface === "customer";
507
516
  if (customerSurface && env.VOYANT_CUSTOMER_AUTH_MODE?.trim() === "disabled") {
508
517
  return null;
509
518
  }
@@ -837,17 +846,15 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
837
846
  const exchangeConfig = getCloudAuthExchangeConfig(c.env);
838
847
  if (!exchangeConfig) {
839
848
  const url = new URL(c.req.url);
849
+ const callbackUrl = getCloudAuthStartConfig(c.env)?.adminCallbackUrl;
840
850
  return c.json({ error: "Voyant Cloud auth exchange is not configured yet" }, 501, {
841
- "Set-Cookie": buildClearCloudAdminAuthStateCookie(url.protocol === "https:", url.pathname.replace(/\/callback$/, "") || "/auth/cloud"),
851
+ "Set-Cookie": buildClearCloudAdminAuthStateCookie(callbackUrl ? new URL(callbackUrl).protocol === "https:" : url.protocol === "https:", url.pathname.replace(/\/callback$/, "") || "/auth/admin/cloud"),
842
852
  });
843
853
  }
844
854
  const { db, dispose } = openDatabase(c.env);
845
855
  try {
846
856
  const betterAuth = buildAdminBetterAuth(c.env, db);
847
- const request = c.req.path.startsWith("/auth/admin/")
848
- ? c.req.raw
849
- : rewriteLegacyAdminAuthRequest(c.req.raw);
850
- return await betterAuth.handler(request);
857
+ return await betterAuth.handler(c.req.raw);
851
858
  }
852
859
  catch (error) {
853
860
  console.error("[auth/cloud/callback] Error:", error);
@@ -859,14 +866,14 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
859
866
  }
860
867
  auth.get("/auth/admin/cloud/start", handleCloudAuthStart);
861
868
  auth.get("/auth/admin/cloud/callback", handleCloudAuthCallback);
862
- /** @deprecated Use /auth/admin/cloud/*. */
863
- auth.get("/auth/cloud/start", handleCloudAuthStart);
864
- auth.get("/auth/cloud/callback", handleCloudAuthCallback);
865
869
  auth.all("/auth/api-tokens", handleApiTokensFacade);
866
870
  auth.all("/auth/api-tokens/:keyId", handleApiTokensFacade);
867
871
  auth.all("/auth/api-tokens/:keyId/rotate", handleApiTokensFacade);
868
872
  auth.get("/auth/organization/list-members", handleOrganizationMembersFacade);
869
873
  auth.get("/auth/customer/status", async (c) => {
874
+ if (c.env.VOYANT_CUSTOMER_AUTH_MODE?.trim() === "disabled") {
875
+ return c.json({ authenticated: false, disabled: true });
876
+ }
870
877
  const { db, dispose } = openDatabase(c.env);
871
878
  try {
872
879
  const betterAuth = buildCustomerBetterAuth(c.env, db, c.req.raw);
@@ -924,20 +931,6 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
924
931
  c.executionCtx.waitUntil(dispose());
925
932
  }
926
933
  });
927
- /** @deprecated Admin Better Auth routes moved to /auth/admin/*. */
928
- auth.all("/auth/*", async (c) => {
929
- const rewritten = rewriteLegacyAdminAuthRequest(c.req.raw);
930
- if (isVoyantCloudAuthMode(c.env) && !isCloudAllowedBetterAuthRoute(rewritten)) {
931
- return localAuthDisabledResponse(c);
932
- }
933
- const { db, dispose } = openDatabase(c.env);
934
- try {
935
- return await buildAdminBetterAuth(c.env, db).handler(rewritten);
936
- }
937
- finally {
938
- c.executionCtx.waitUntil(dispose());
939
- }
940
- });
941
934
  return {
942
935
  handler: auth,
943
936
  getBootstrapStatusForRequest,