@voyant-travel/auth 0.140.3 → 0.141.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node-runtime.d.ts +24 -0
- package/dist/node-runtime.js +64 -4
- package/dist/node-runtime.js.map +1 -1
- package/dist/server.d.ts +38 -2
- package/dist/server.js +8 -2
- package/dist/server.js.map +1 -1
- package/dist/storefront-customer-auth-resolver.d.ts +27 -0
- package/dist/storefront-customer-auth-resolver.js +69 -3
- package/dist/storefront-customer-auth-resolver.js.map +1 -1
- package/dist/storefront-local-adapter.js +27 -2
- package/dist/storefront-local-adapter.js.map +1 -1
- package/dist/storefront-runtime-port.d.ts +6 -0
- package/dist/storefront-runtime-port.js +1 -0
- package/dist/storefront-runtime-port.js.map +1 -1
- package/package.json +1 -1
package/dist/node-runtime.d.ts
CHANGED
|
@@ -17,6 +17,14 @@ import type { CustomerBusinessAccountOnboardingRuntimeProvider } from "./custome
|
|
|
17
17
|
import { type CustomerBuyerAccountPolicy } from "./customer-buyer-accounts.js";
|
|
18
18
|
import { type CreateBetterAuthOptions, type CustomerAuthMethods } from "./server.js";
|
|
19
19
|
type OperatorAuthMode = "local" | "voyant-cloud";
|
|
20
|
+
/**
|
|
21
|
+
* Whether a request targets the customer realm surface eligible for per-storefront
|
|
22
|
+
* dynamic CORS: the public storefront API (`/v1/public` + `/v1/public/*`) and the
|
|
23
|
+
* customer auth routes (`/auth/customer` + `/auth/customer/*`) a direct client
|
|
24
|
+
* hits to sign in. Tolerates an optional `/api` host prefix. Admin/dash surfaces
|
|
25
|
+
* are intentionally excluded — they keep the static `CORS_ALLOWLIST` behavior.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isCustomerCorsSurface(requestUrl: string): boolean;
|
|
20
28
|
export interface OperatorAuthNodeEnv extends NodeDatabaseEnv {
|
|
21
29
|
API_BASE_URL?: string;
|
|
22
30
|
APP_URL?: string;
|
|
@@ -114,6 +122,15 @@ export interface CreateOperatorAuthNodeRuntimeOptions<Env extends OperatorAuthNo
|
|
|
114
122
|
* merchant credentials. Never derive these values from Host/X-Forwarded-Host.
|
|
115
123
|
*/
|
|
116
124
|
resolveCustomerAuthContext?: (env: Env, request: Request) => CustomerAuthRuntimeContext | Promise<CustomerAuthRuntimeContext>;
|
|
125
|
+
/**
|
|
126
|
+
* Request-time dynamic-CORS origin authorizer for the customer realm
|
|
127
|
+
* (`/v1/public/*` + `/auth/customer/*`). Returns the exact request origin to
|
|
128
|
+
* echo in `Access-Control-Allow-Origin` when a storefront authorizes it, or
|
|
129
|
+
* `null` to omit CORS headers. Authorizes keyed requests by the presented
|
|
130
|
+
* storefront key and keyless preflight by the declared allowed origins. When
|
|
131
|
+
* unset, the customer realm keeps the static `CORS_ALLOWLIST` behavior.
|
|
132
|
+
*/
|
|
133
|
+
resolveCustomerCorsOrigin?: (env: Env, request: Request) => Promise<string | null>;
|
|
117
134
|
}
|
|
118
135
|
export interface CustomerAuthRuntimeContext {
|
|
119
136
|
/** Browser-visible proxy base used by storefront/BFF auth callbacks. */
|
|
@@ -123,6 +140,12 @@ export interface CustomerAuthRuntimeContext {
|
|
|
123
140
|
/** Explicit trusted storefront origin used for customer invitation links and request audit. */
|
|
124
141
|
invitationAcceptBaseURL?: string;
|
|
125
142
|
trustedOrigins: string[];
|
|
143
|
+
/**
|
|
144
|
+
* Full declared browser origins for the resolved storefront (exact +
|
|
145
|
+
* `https://*.host` wildcard). Carried for dynamic CORS; `trustedOrigins` is
|
|
146
|
+
* the canonical-origin subset Better Auth validates.
|
|
147
|
+
*/
|
|
148
|
+
allowedOrigins?: string[];
|
|
126
149
|
methods: CustomerAuthMethods;
|
|
127
150
|
/** Buyer capabilities are independent from identity sign-up methods. */
|
|
128
151
|
accountPolicy?: CustomerBuyerAccountPolicy | null;
|
|
@@ -164,6 +187,7 @@ export declare function createOperatorAuthNodeRuntime<Env extends OperatorAuthNo
|
|
|
164
187
|
getCurrentUserForRequest: (request: Request, env: Env) => Promise<OperatorCurrentUser | null>;
|
|
165
188
|
hasAuthPermission: (request: Request, env: Env) => Promise<boolean>;
|
|
166
189
|
resolveAuthRequest: (request: Request, env: Env) => Promise<VoyantResolvedSessionAuthContext | null>;
|
|
190
|
+
resolveCustomerCorsOrigin: (request: Request, env: Env) => Promise<string | null>;
|
|
167
191
|
validateApiTokenAccess: (env: Env, db: VoyantDb, apiKey: SelectApikey) => Promise<boolean>;
|
|
168
192
|
};
|
|
169
193
|
export {};
|
package/dist/node-runtime.js
CHANGED
|
@@ -34,6 +34,21 @@ function classifyOperatorApiAuthSurface(requestUrl) {
|
|
|
34
34
|
return "customer";
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether a request targets the customer realm surface eligible for per-storefront
|
|
39
|
+
* dynamic CORS: the public storefront API (`/v1/public` + `/v1/public/*`) and the
|
|
40
|
+
* customer auth routes (`/auth/customer` + `/auth/customer/*`) a direct client
|
|
41
|
+
* hits to sign in. Tolerates an optional `/api` host prefix. Admin/dash surfaces
|
|
42
|
+
* are intentionally excluded — they keep the static `CORS_ALLOWLIST` behavior.
|
|
43
|
+
*/
|
|
44
|
+
export function isCustomerCorsSurface(requestUrl) {
|
|
45
|
+
const pathname = new URL(requestUrl).pathname.replace(/\/+$/, "") || "/";
|
|
46
|
+
const normalized = pathname.startsWith("/api/") ? pathname.slice(4) : pathname;
|
|
47
|
+
return (normalized === "/v1/public" ||
|
|
48
|
+
normalized.startsWith("/v1/public/") ||
|
|
49
|
+
normalized === "/auth/customer" ||
|
|
50
|
+
normalized.startsWith("/auth/customer/"));
|
|
51
|
+
}
|
|
37
52
|
/** Resolve Better Auth's standard cross-subdomain cookie policy for Node hosts. */
|
|
38
53
|
export function buildBetterAuthCookieAdvancedOptions(env) {
|
|
39
54
|
const domain = env.AUTH_COOKIE_DOMAIN?.trim();
|
|
@@ -419,7 +434,14 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
419
434
|
: defaultCustomerAuthContext(env, parseCustomerAuthPolicy(env));
|
|
420
435
|
const baseURL = requireCanonicalOrigin(context.baseURL, "customer auth baseURL");
|
|
421
436
|
const publicApiBaseURL = requirePublicApiBaseUrl(context.publicApiBaseURL ?? `${baseURL}/api`, baseURL);
|
|
422
|
-
|
|
437
|
+
// Fold the resolved storefront's trusted origins together with the static
|
|
438
|
+
// env allowlist so a cross-origin customer-auth call from any allowed
|
|
439
|
+
// storefront origin is trusted by Better Auth (WORK: direct-client support).
|
|
440
|
+
// Wildcard (`https://*.host`) entries pass through untouched — Better Auth
|
|
441
|
+
// matches them natively; every other entry must be a canonical origin.
|
|
442
|
+
const trustedOrigins = [...new Set([...context.trustedOrigins, ...getTrustedOrigins(env)])].map((origin) => isCustomerWildcardOrigin(origin)
|
|
443
|
+
? origin
|
|
444
|
+
: requireCanonicalOrigin(origin, "customer auth trusted origin"));
|
|
423
445
|
const invitationAcceptBaseURL = context.invitationAcceptBaseURL
|
|
424
446
|
? requireCanonicalOrigin(context.invitationAcceptBaseURL, "customer invitation accept baseURL")
|
|
425
447
|
: undefined;
|
|
@@ -431,6 +453,13 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
431
453
|
...(invitationAcceptBaseURL ? { invitationAcceptBaseURL } : {}),
|
|
432
454
|
};
|
|
433
455
|
}
|
|
456
|
+
/** A single-label `https://*.host` wildcard trusted-origin declaration. */
|
|
457
|
+
function isCustomerWildcardOrigin(value) {
|
|
458
|
+
if (!value.startsWith("https://*."))
|
|
459
|
+
return false;
|
|
460
|
+
const host = value.slice("https://*.".length);
|
|
461
|
+
return host.length > 0 && !host.includes("*") && !host.includes("/") && !host.includes(":");
|
|
462
|
+
}
|
|
434
463
|
function requireCanonicalOrigin(value, label) {
|
|
435
464
|
let parsed;
|
|
436
465
|
try {
|
|
@@ -647,9 +676,22 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
647
676
|
}
|
|
648
677
|
const { db, dispose } = openDatabase(env);
|
|
649
678
|
try {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
679
|
+
// A storefront customer-auth resolution failure on `/v1/public/*` (missing/
|
|
680
|
+
// unknown/revoked key or a disallowed origin) is a client-side auth failure,
|
|
681
|
+
// not a server fault: resolve it to "unauthorized" (null) so the framework
|
|
682
|
+
// returns 401 instead of letting it surface as a 500. Genuine faults still
|
|
683
|
+
// throw. Mirrors the auth handler's onError mapping for `/auth/customer/*`.
|
|
684
|
+
let customerContext = null;
|
|
685
|
+
if (customerSurface) {
|
|
686
|
+
try {
|
|
687
|
+
customerContext = await resolveCustomerAuthContext(env, request);
|
|
688
|
+
}
|
|
689
|
+
catch (error) {
|
|
690
|
+
if (error instanceof StorefrontCustomerAuthResolutionError)
|
|
691
|
+
return null;
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
653
695
|
const auth = customerSurface
|
|
654
696
|
? await buildCustomerBetterAuth(env, db, request, customerContext ?? undefined)
|
|
655
697
|
: buildAdminBetterAuth(env, db);
|
|
@@ -753,6 +795,23 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
753
795
|
const auth = await resolveAuthRequest(request, env);
|
|
754
796
|
return auth !== null;
|
|
755
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Authorize a customer-realm cross-origin request for dynamic CORS. Returns
|
|
800
|
+
* the exact request origin to echo in `Access-Control-Allow-Origin`, or `null`
|
|
801
|
+
* to omit CORS headers. Only the customer surface (`/v1/public/*` and
|
|
802
|
+
* `/auth/customer/*`) is dynamically authorized; every other surface keeps the
|
|
803
|
+
* static `CORS_ALLOWLIST` behavior. The customer realm being disabled, or no
|
|
804
|
+
* authorizer being wired, yields `null` (static behavior).
|
|
805
|
+
*/
|
|
806
|
+
async function resolveCustomerCorsOrigin(request, env) {
|
|
807
|
+
if (!runtimeOptions.resolveCustomerCorsOrigin)
|
|
808
|
+
return null;
|
|
809
|
+
if (env.VOYANT_CUSTOMER_AUTH_MODE?.trim() === "disabled")
|
|
810
|
+
return null;
|
|
811
|
+
if (!isCustomerCorsSurface(request.url))
|
|
812
|
+
return null;
|
|
813
|
+
return runtimeOptions.resolveCustomerCorsOrigin(env, request);
|
|
814
|
+
}
|
|
756
815
|
class CurrentUserNotFoundError extends Error {
|
|
757
816
|
constructor() {
|
|
758
817
|
super("User not found");
|
|
@@ -1429,6 +1488,7 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
1429
1488
|
getCurrentUserForRequest,
|
|
1430
1489
|
hasAuthPermission,
|
|
1431
1490
|
resolveAuthRequest,
|
|
1491
|
+
resolveCustomerCorsOrigin,
|
|
1432
1492
|
validateApiTokenAccess,
|
|
1433
1493
|
};
|
|
1434
1494
|
}
|