@voyant-travel/auth 0.133.1 → 0.133.3
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/auth-realms.d.ts +50 -0
- package/dist/auth-realms.js +13 -0
- package/dist/auth-realms.js.map +1 -0
- package/dist/edge.d.ts +6 -1
- package/dist/edge.js +41 -19
- package/dist/edge.js.map +1 -1
- package/dist/node-runtime.d.ts +44 -2
- package/dist/node-runtime.js +294 -30
- package/dist/node-runtime.js.map +1 -1
- package/dist/runtime-contributor.js +8 -4
- package/dist/runtime-contributor.js.map +1 -1
- package/dist/server.d.ts +30 -1
- package/dist/server.js +127 -81
- package/dist/server.js.map +1 -1
- package/package.json +9 -4
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Provider-neutral identities crossing the auth/runtime boundary. */
|
|
2
|
+
export type AuthRealm = "admin" | "customer";
|
|
3
|
+
interface PrincipalBase {
|
|
4
|
+
realm: AuthRealm;
|
|
5
|
+
userId: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
email: string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface AdminPrincipal extends PrincipalBase {
|
|
10
|
+
realm: "admin";
|
|
11
|
+
actor: "staff";
|
|
12
|
+
scopes: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface CustomerPrincipal extends PrincipalBase {
|
|
15
|
+
realm: "customer";
|
|
16
|
+
actor: "customer";
|
|
17
|
+
}
|
|
18
|
+
export type AuthPrincipal = AdminPrincipal | CustomerPrincipal;
|
|
19
|
+
/** Complete session replacement seam for advanced self-host integrations. */
|
|
20
|
+
export interface AuthRealmSessionPort<Context = unknown> {
|
|
21
|
+
readonly realm: AuthRealm;
|
|
22
|
+
resolve(request: Request, context: Context): Promise<AuthPrincipal | null>;
|
|
23
|
+
signOut(request: Request, context: Context): Promise<Response>;
|
|
24
|
+
revokeUserSessions(userId: string, context: Context): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface ExternalIdentity {
|
|
27
|
+
providerId: string;
|
|
28
|
+
providerAccountId: string;
|
|
29
|
+
email: string | null;
|
|
30
|
+
emailVerified: boolean;
|
|
31
|
+
name: string | null;
|
|
32
|
+
image: string | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Preferred external-IdP seam. Auth0, Clerk, WorkOS, or generic OIDC adapters
|
|
36
|
+
* verify their assertion here; Better Auth remains the local session issuer.
|
|
37
|
+
*/
|
|
38
|
+
export interface ExternalIdentityAdapter<Input, Context = unknown> {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
resolve(input: Input, context: Context): Promise<ExternalIdentity>;
|
|
41
|
+
revalidate?(identity: ExternalIdentity, context: Context): Promise<boolean>;
|
|
42
|
+
}
|
|
43
|
+
export interface BetterAuthIdentityBridge<Context = unknown> {
|
|
44
|
+
createSession(identity: ExternalIdentity, context: Context): Promise<Response>;
|
|
45
|
+
}
|
|
46
|
+
export declare function createExternalIdentitySessionAdapter<Input, Context = unknown>(identityAdapter: ExternalIdentityAdapter<Input, Context>, betterAuthBridge: BetterAuthIdentityBridge<Context>): {
|
|
47
|
+
id: string;
|
|
48
|
+
authenticate(input: Input, context: Context): Promise<Response>;
|
|
49
|
+
};
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function createExternalIdentitySessionAdapter(identityAdapter, betterAuthBridge) {
|
|
2
|
+
return {
|
|
3
|
+
id: identityAdapter.id,
|
|
4
|
+
async authenticate(input, context) {
|
|
5
|
+
const identity = await identityAdapter.resolve(input, context);
|
|
6
|
+
if (identityAdapter.revalidate && !(await identityAdapter.revalidate(identity, context))) {
|
|
7
|
+
return Response.json({ error: "External identity access was revoked" }, { status: 403 });
|
|
8
|
+
}
|
|
9
|
+
return betterAuthBridge.createSession(identity, context);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=auth-realms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-realms.js","sourceRoot":"","sources":["../src/auth-realms.ts"],"names":[],"mappings":"AAsDA,MAAM,UAAU,oCAAoC,CAClD,eAAwD,EACxD,gBAAmD;IAEnD,OAAO;QACL,EAAE,EAAE,eAAe,CAAC,EAAE;QACtB,KAAK,CAAC,YAAY,CAAC,KAAY,EAAE,OAAgB;YAC/C,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;YAC9D,IAAI,eAAe,CAAC,UAAU,IAAI,CAAC,CAAC,MAAM,eAAe,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;gBACzF,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sCAAsC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;YAC1F,CAAC;YACD,OAAO,gBAAgB,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC1D,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["/** Provider-neutral identities crossing the auth/runtime boundary. */\nexport type AuthRealm = \"admin\" | \"customer\"\n\ninterface PrincipalBase {\n realm: AuthRealm\n userId: string\n sessionId: string\n email: string | null\n}\n\nexport interface AdminPrincipal extends PrincipalBase {\n realm: \"admin\"\n actor: \"staff\"\n scopes: readonly string[]\n}\n\nexport interface CustomerPrincipal extends PrincipalBase {\n realm: \"customer\"\n actor: \"customer\"\n}\n\nexport type AuthPrincipal = AdminPrincipal | CustomerPrincipal\n\n/** Complete session replacement seam for advanced self-host integrations. */\nexport interface AuthRealmSessionPort<Context = unknown> {\n readonly realm: AuthRealm\n resolve(request: Request, context: Context): Promise<AuthPrincipal | null>\n signOut(request: Request, context: Context): Promise<Response>\n revokeUserSessions(userId: string, context: Context): Promise<void>\n}\n\nexport interface ExternalIdentity {\n providerId: string\n providerAccountId: string\n email: string | null\n emailVerified: boolean\n name: string | null\n image: string | null\n}\n\n/**\n * Preferred external-IdP seam. Auth0, Clerk, WorkOS, or generic OIDC adapters\n * verify their assertion here; Better Auth remains the local session issuer.\n */\nexport interface ExternalIdentityAdapter<Input, Context = unknown> {\n readonly id: string\n resolve(input: Input, context: Context): Promise<ExternalIdentity>\n revalidate?(identity: ExternalIdentity, context: Context): Promise<boolean>\n}\n\nexport interface BetterAuthIdentityBridge<Context = unknown> {\n createSession(identity: ExternalIdentity, context: Context): Promise<Response>\n}\n\nexport function createExternalIdentitySessionAdapter<Input, Context = unknown>(\n identityAdapter: ExternalIdentityAdapter<Input, Context>,\n betterAuthBridge: BetterAuthIdentityBridge<Context>,\n) {\n return {\n id: identityAdapter.id,\n async authenticate(input: Input, context: Context): Promise<Response> {\n const identity = await identityAdapter.resolve(input, context)\n if (identityAdapter.revalidate && !(await identityAdapter.revalidate(identity, context))) {\n return Response.json({ error: \"External identity access was revoked\" }, { status: 403 })\n }\n return betterAuthBridge.createSession(identity, context)\n },\n }\n}\n"]}
|
package/dist/edge.d.ts
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* With the identity table collapse, authUser.id IS the userId —
|
|
12
12
|
* no extra identity lookup required.
|
|
13
13
|
*/
|
|
14
|
+
export interface EdgeAuthRealmOptions {
|
|
15
|
+
realm?: "admin" | "customer";
|
|
16
|
+
cookieName?: string;
|
|
17
|
+
secret?: string;
|
|
18
|
+
}
|
|
14
19
|
type AuthContext = {
|
|
15
20
|
userId: string | null;
|
|
16
21
|
email: string | null;
|
|
@@ -23,5 +28,5 @@ type AuthContext = {
|
|
|
23
28
|
* Returns `userId` which is Better Auth's `user.id` — the single
|
|
24
29
|
* canonical user identifier after the identity table collapse.
|
|
25
30
|
*/
|
|
26
|
-
export declare function getAuthContextFromHeaders(headers: Headers): Promise<AuthContext>;
|
|
31
|
+
export declare function getAuthContextFromHeaders(headers: Headers, options?: EdgeAuthRealmOptions): Promise<AuthContext>;
|
|
27
32
|
export {};
|
package/dist/edge.js
CHANGED
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
* no extra identity lookup required.
|
|
13
13
|
*/
|
|
14
14
|
import { getDb } from "@voyant-travel/db";
|
|
15
|
-
import { authSession, authUser } from "@voyant-travel/db/schema/iam";
|
|
15
|
+
import { authSession, authUser, customerAuthSession, customerAuthUser, } from "@voyant-travel/db/schema/iam";
|
|
16
16
|
import { and, eq, gt } from "drizzle-orm";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
function getAuthSecret(realm) {
|
|
18
|
+
if (realm === "customer") {
|
|
19
|
+
return process.env.BETTER_AUTH_CUSTOMER_SECRET || "";
|
|
20
|
+
}
|
|
21
|
+
return (process.env.BETTER_AUTH_ADMIN_SECRET ||
|
|
22
|
+
process.env.BETTER_AUTH_SECRET ||
|
|
23
|
+
process.env.SESSION_CLAIMS_SECRET ||
|
|
24
|
+
"");
|
|
20
25
|
}
|
|
21
26
|
/**
|
|
22
27
|
* Unsign a Better Auth cookie value.
|
|
@@ -46,7 +51,7 @@ async function unsignCookie(rawCookieValue, secret) {
|
|
|
46
51
|
const valid = await crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(value));
|
|
47
52
|
return valid ? value : null;
|
|
48
53
|
}
|
|
49
|
-
function getRawCookieValue(headers) {
|
|
54
|
+
function getRawCookieValue(headers, cookieNames) {
|
|
50
55
|
const cookieHeader = headers.get("cookie");
|
|
51
56
|
if (!cookieHeader)
|
|
52
57
|
return null;
|
|
@@ -56,7 +61,7 @@ function getRawCookieValue(headers) {
|
|
|
56
61
|
if (eqIdx === -1)
|
|
57
62
|
continue;
|
|
58
63
|
const name = trimmed.slice(0, eqIdx).trim();
|
|
59
|
-
if (name
|
|
64
|
+
if (cookieNames.includes(name)) {
|
|
60
65
|
return trimmed.slice(eqIdx + 1).trim() || null;
|
|
61
66
|
}
|
|
62
67
|
}
|
|
@@ -74,27 +79,44 @@ const EMPTY = {
|
|
|
74
79
|
* Returns `userId` which is Better Auth's `user.id` — the single
|
|
75
80
|
* canonical user identifier after the identity table collapse.
|
|
76
81
|
*/
|
|
77
|
-
export async function getAuthContextFromHeaders(headers) {
|
|
78
|
-
const
|
|
82
|
+
export async function getAuthContextFromHeaders(headers, options = {}) {
|
|
83
|
+
const realm = options.realm ?? "admin";
|
|
84
|
+
const cookieNames = options.cookieName
|
|
85
|
+
? [options.cookieName]
|
|
86
|
+
: realm === "customer"
|
|
87
|
+
? ["voyant-customer.session_token"]
|
|
88
|
+
: ["voyant-admin.session_token", "better-auth.session_token"];
|
|
89
|
+
const rawCookie = getRawCookieValue(headers, cookieNames);
|
|
79
90
|
if (!rawCookie)
|
|
80
91
|
return EMPTY;
|
|
81
|
-
const secret = getAuthSecret();
|
|
92
|
+
const secret = options.secret ?? getAuthSecret(realm);
|
|
82
93
|
if (!secret)
|
|
83
94
|
return EMPTY;
|
|
84
95
|
const token = await unsignCookie(rawCookie, secret);
|
|
85
96
|
if (!token)
|
|
86
97
|
return EMPTY;
|
|
87
98
|
const db = getDb("edge");
|
|
88
|
-
const [row] =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
const [row] = realm === "customer"
|
|
100
|
+
? await db
|
|
101
|
+
.select({
|
|
102
|
+
sessionId: customerAuthSession.id,
|
|
103
|
+
userId: customerAuthSession.userId,
|
|
104
|
+
email: customerAuthUser.email,
|
|
105
|
+
})
|
|
106
|
+
.from(customerAuthSession)
|
|
107
|
+
.innerJoin(customerAuthUser, eq(customerAuthUser.id, customerAuthSession.userId))
|
|
108
|
+
.where(and(eq(customerAuthSession.token, token), gt(customerAuthSession.expiresAt, new Date())))
|
|
109
|
+
.limit(1)
|
|
110
|
+
: await db
|
|
111
|
+
.select({
|
|
112
|
+
sessionId: authSession.id,
|
|
113
|
+
userId: authSession.userId,
|
|
114
|
+
email: authUser.email,
|
|
115
|
+
})
|
|
116
|
+
.from(authSession)
|
|
117
|
+
.innerJoin(authUser, eq(authUser.id, authSession.userId))
|
|
118
|
+
.where(and(eq(authSession.token, token), gt(authSession.expiresAt, new Date())))
|
|
119
|
+
.limit(1);
|
|
98
120
|
if (!row)
|
|
99
121
|
return EMPTY;
|
|
100
122
|
return {
|
package/dist/edge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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 (\n process.env.BETTER_AUTH_ADMIN_SECRET ||\n process.env.BETTER_AUTH_SECRET ||\n process.env.SESSION_CLAIMS_SECRET ||\n \"\"\n )\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"]}
|
package/dist/node-runtime.d.ts
CHANGED
|
@@ -13,18 +13,31 @@ import { type SelectApikey } from "@voyant-travel/db/schema/iam";
|
|
|
13
13
|
import type { Reporter, VoyantDb, VoyantRequestAuthContext } from "@voyant-travel/hono";
|
|
14
14
|
import type { AccessCatalog } from "@voyant-travel/types/api-keys";
|
|
15
15
|
import { Hono } from "hono";
|
|
16
|
-
import { type CreateBetterAuthOptions } from "./server.js";
|
|
16
|
+
import { type CreateBetterAuthOptions, type CustomerAuthMethods } from "./server.js";
|
|
17
17
|
type OperatorAuthMode = "local" | "voyant-cloud";
|
|
18
18
|
export interface OperatorAuthNodeEnv extends NodeDatabaseEnv {
|
|
19
19
|
API_BASE_URL?: string;
|
|
20
20
|
APP_URL?: string;
|
|
21
21
|
AUTH_COOKIE_DOMAIN?: string;
|
|
22
|
-
|
|
22
|
+
/** @deprecated Use BETTER_AUTH_ADMIN_SECRET. */
|
|
23
|
+
BETTER_AUTH_SECRET?: string;
|
|
24
|
+
BETTER_AUTH_ADMIN_SECRET?: string;
|
|
25
|
+
BETTER_AUTH_CUSTOMER_SECRET?: string;
|
|
23
26
|
CORS_ALLOWLIST?: string;
|
|
24
27
|
DASH_BASE_URL?: string;
|
|
25
28
|
EMAIL_FROM?: string;
|
|
26
29
|
SESSION_CLAIMS_SECRET: string;
|
|
27
30
|
VOYANT_ADMIN_AUTH_MODE?: string;
|
|
31
|
+
VOYANT_CUSTOMER_AUTH_MODE?: string;
|
|
32
|
+
VOYANT_CUSTOMER_AUTH_CONFIG_JSON?: string;
|
|
33
|
+
VOYANT_CUSTOMER_AUTH_EMAIL_CODE?: string;
|
|
34
|
+
VOYANT_CUSTOMER_AUTH_EMAIL_PASSWORD?: string;
|
|
35
|
+
CUSTOMER_AUTH_GOOGLE_CLIENT_ID?: string;
|
|
36
|
+
CUSTOMER_AUTH_GOOGLE_CLIENT_SECRET?: string;
|
|
37
|
+
CUSTOMER_AUTH_FACEBOOK_CLIENT_ID?: string;
|
|
38
|
+
CUSTOMER_AUTH_FACEBOOK_CLIENT_SECRET?: string;
|
|
39
|
+
CUSTOMER_AUTH_APPLE_CLIENT_ID?: string;
|
|
40
|
+
CUSTOMER_AUTH_APPLE_CLIENT_SECRET?: string;
|
|
28
41
|
VOYANT_AUTH_LOG_SECRET_FALLBACKS?: string;
|
|
29
42
|
VOYANT_CLOUD_ADMIN_AUTH_AUDIENCE?: string;
|
|
30
43
|
VOYANT_CLOUD_ADMIN_AUTH_CLIENT_TOKEN?: string;
|
|
@@ -82,7 +95,36 @@ export interface CreateOperatorAuthNodeRuntimeOptions<Env extends OperatorAuthNo
|
|
|
82
95
|
};
|
|
83
96
|
cookieAdvanced?: (env: Env) => Pick<BetterAuthAdvancedOptions, "crossSubDomainCookies" | "defaultCookieAttributes"> | undefined;
|
|
84
97
|
resolveEmailSender?: (env: Env) => OperatorAuthEmailSender | null;
|
|
98
|
+
/**
|
|
99
|
+
* Storefront/BFF seam for a canonical public auth origin and resolved
|
|
100
|
+
* merchant credentials. Never derive these values from Host/X-Forwarded-Host.
|
|
101
|
+
*/
|
|
102
|
+
resolveCustomerAuthContext?: (env: Env, policy: CustomerAuthPolicy, request: Request) => CustomerAuthRuntimeContext;
|
|
85
103
|
}
|
|
104
|
+
export interface CustomerAuthRuntimeContext {
|
|
105
|
+
baseURL: string;
|
|
106
|
+
trustedOrigins: string[];
|
|
107
|
+
methods: CustomerAuthMethods;
|
|
108
|
+
}
|
|
109
|
+
export interface CustomerAuthSocialPolicy {
|
|
110
|
+
enabled: boolean;
|
|
111
|
+
credentialRef: string | null;
|
|
112
|
+
}
|
|
113
|
+
export interface CustomerAuthPolicy {
|
|
114
|
+
methods: {
|
|
115
|
+
emailCode: boolean;
|
|
116
|
+
emailPassword: boolean;
|
|
117
|
+
google: CustomerAuthSocialPolicy;
|
|
118
|
+
facebook: CustomerAuthSocialPolicy;
|
|
119
|
+
apple: CustomerAuthSocialPolicy;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Better Auth is dispatched internally without the hosting `/api` prefix, but
|
|
124
|
+
* OAuth providers must return to the browser-visible route. Keep explicit
|
|
125
|
+
* merchant overrides and supply the canonical public callback otherwise.
|
|
126
|
+
*/
|
|
127
|
+
export declare function withCustomerSocialRedirectUris(methods: CustomerAuthMethods, publicApiBaseUrl: string): CustomerAuthMethods;
|
|
86
128
|
export declare function createOperatorAuthNodeRuntime<Env extends OperatorAuthNodeEnv>(runtimeOptions: CreateOperatorAuthNodeRuntimeOptions<Env>): {
|
|
87
129
|
handler: Hono<{
|
|
88
130
|
Bindings: Env;
|