@voyant-travel/auth 0.133.5 → 0.135.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 +3 -3
- package/dist/cloud-admin-session-public.d.ts +2 -0
- package/dist/cloud-admin-session-public.js.map +1 -1
- package/dist/cloud-admin-session.d.ts +2 -0
- package/dist/cloud-admin-session.js +1 -0
- package/dist/cloud-admin-session.js.map +1 -1
- package/dist/cloud-broker/state.d.ts +3 -1
- package/dist/cloud-broker/state.js +11 -13
- package/dist/cloud-broker/state.js.map +1 -1
- package/dist/edge.js +1 -1
- package/dist/edge.js.map +1 -1
- package/dist/node-runtime.d.ts +18 -5
- package/dist/node-runtime.js +109 -50
- package/dist/node-runtime.js.map +1 -1
- package/dist/runtime-contributor.js +1 -2
- package/dist/runtime-contributor.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +6 -5
- package/dist/server.js.map +1 -1
- package/package.json +5 -5
package/dist/node-runtime.js
CHANGED
|
@@ -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/*
|
|
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();
|
|
@@ -71,6 +80,24 @@ export function withCustomerSocialRedirectUris(methods, publicApiBaseUrl) {
|
|
|
71
80
|
},
|
|
72
81
|
};
|
|
73
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Rewrite Better Auth's internally generated customer password-reset URL to
|
|
85
|
+
* the browser-visible storefront/BFF API base.
|
|
86
|
+
*/
|
|
87
|
+
export function withCustomerPublicResetPasswordUrl(generatedUrl, publicApiBaseURL) {
|
|
88
|
+
try {
|
|
89
|
+
const generated = new URL(generatedUrl);
|
|
90
|
+
const publicApi = new URL(publicApiBaseURL);
|
|
91
|
+
const apiPath = publicApi.pathname.replace(/\/+$/, "");
|
|
92
|
+
publicApi.pathname = `${apiPath}${generated.pathname}`;
|
|
93
|
+
publicApi.search = generated.search;
|
|
94
|
+
publicApi.hash = generated.hash;
|
|
95
|
+
return publicApi.toString();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return generatedUrl;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
74
101
|
export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
75
102
|
const openDatabase = runtimeOptions.openDatabase ??
|
|
76
103
|
((env) => {
|
|
@@ -186,7 +213,7 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
186
213
|
cloudAuthStartUrl,
|
|
187
214
|
deploymentId,
|
|
188
215
|
adminCallbackUrl: `${getPublicApiBaseUrl(env)}/auth/admin/cloud/callback`,
|
|
189
|
-
cookieSecret: env.
|
|
216
|
+
cookieSecret: env.SESSION_CLAIMS_ADMIN_SECRET,
|
|
190
217
|
appId: env.VOYANT_CLOUD_APP_ID?.trim() || undefined,
|
|
191
218
|
environment: env.VOYANT_CLOUD_ENVIRONMENT?.trim() || undefined,
|
|
192
219
|
};
|
|
@@ -313,6 +340,7 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
313
340
|
env.CUSTOMER_AUTH_APPLE_CLIENT_SECRET?.trim() &&
|
|
314
341
|
(!hasCanonicalPolicy || policy.methods.apple.enabled));
|
|
315
342
|
return {
|
|
343
|
+
publicApiBaseURL: getPublicApiBaseUrl(env),
|
|
316
344
|
baseURL: getAuthBaseUrl(env),
|
|
317
345
|
trustedOrigins: getTrustedOrigins(env),
|
|
318
346
|
methods: {
|
|
@@ -347,13 +375,55 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
347
375
|
},
|
|
348
376
|
};
|
|
349
377
|
}
|
|
350
|
-
function resolveCustomerAuthContext(env, request) {
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
defaultCustomerAuthContext(env,
|
|
378
|
+
async function resolveCustomerAuthContext(env, request) {
|
|
379
|
+
const context = runtimeOptions.resolveCustomerAuthContext
|
|
380
|
+
? await runtimeOptions.resolveCustomerAuthContext(env, request)
|
|
381
|
+
: defaultCustomerAuthContext(env, parseCustomerAuthPolicy(env));
|
|
382
|
+
const baseURL = requireCanonicalOrigin(context.baseURL, "customer auth baseURL");
|
|
383
|
+
const publicApiBaseURL = requirePublicApiBaseUrl(context.publicApiBaseURL ?? `${baseURL}/api`, baseURL);
|
|
384
|
+
const trustedOrigins = context.trustedOrigins.map((origin) => requireCanonicalOrigin(origin, "customer auth trusted origin"));
|
|
385
|
+
return { ...context, baseURL, publicApiBaseURL, trustedOrigins };
|
|
386
|
+
}
|
|
387
|
+
function requireCanonicalOrigin(value, label) {
|
|
388
|
+
let parsed;
|
|
389
|
+
try {
|
|
390
|
+
parsed = new URL(value);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
throw new Error(`${label} must be an absolute HTTP(S) origin`);
|
|
394
|
+
}
|
|
395
|
+
if (!["http:", "https:"].includes(parsed.protocol) ||
|
|
396
|
+
parsed.username ||
|
|
397
|
+
parsed.password ||
|
|
398
|
+
(parsed.pathname !== "/" && parsed.pathname !== "") ||
|
|
399
|
+
parsed.search ||
|
|
400
|
+
parsed.hash) {
|
|
401
|
+
throw new Error(`${label} must be an absolute HTTP(S) origin`);
|
|
402
|
+
}
|
|
403
|
+
return parsed.origin;
|
|
354
404
|
}
|
|
355
|
-
function
|
|
356
|
-
|
|
405
|
+
function requirePublicApiBaseUrl(value, baseURL) {
|
|
406
|
+
let parsed;
|
|
407
|
+
try {
|
|
408
|
+
parsed = new URL(value);
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
throw new Error("customer auth publicApiBaseURL must be an absolute HTTP(S) URL");
|
|
412
|
+
}
|
|
413
|
+
if (!["http:", "https:"].includes(parsed.protocol) ||
|
|
414
|
+
parsed.username ||
|
|
415
|
+
parsed.password ||
|
|
416
|
+
parsed.search ||
|
|
417
|
+
parsed.hash ||
|
|
418
|
+
parsed.origin !== baseURL ||
|
|
419
|
+
parsed.pathname === "/") {
|
|
420
|
+
throw new Error("customer auth publicApiBaseURL must be an absolute HTTP(S) URL with a path");
|
|
421
|
+
}
|
|
422
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
423
|
+
return parsed.toString().replace(/\/$/, "");
|
|
424
|
+
}
|
|
425
|
+
async function publicCustomerAuthConfiguration(env, request) {
|
|
426
|
+
const methods = (await resolveCustomerAuthContext(env, request)).methods;
|
|
357
427
|
const providers = methods.socialProviders ?? {};
|
|
358
428
|
return {
|
|
359
429
|
methods: {
|
|
@@ -387,7 +457,9 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
387
457
|
? [
|
|
388
458
|
createVoyantCloudAdminAuthPlugin({
|
|
389
459
|
db: cloudAuthDb,
|
|
390
|
-
cookieSecret: env.
|
|
460
|
+
cookieSecret: env.SESSION_CLAIMS_ADMIN_SECRET,
|
|
461
|
+
secureStateCookie: new URL(`${getPublicApiBaseUrl(env)}/auth/admin/cloud/callback`).protocol ===
|
|
462
|
+
"https:",
|
|
391
463
|
exchange: cloudAuthExchange,
|
|
392
464
|
}),
|
|
393
465
|
]
|
|
@@ -420,9 +492,10 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
420
492
|
},
|
|
421
493
|
});
|
|
422
494
|
}
|
|
423
|
-
function buildCustomerBetterAuth(env, db, request) {
|
|
495
|
+
async function buildCustomerBetterAuth(env, db, request) {
|
|
424
496
|
const emailSender = runtimeOptions.resolveEmailSender?.(env);
|
|
425
|
-
const context = resolveCustomerAuthContext(env, request);
|
|
497
|
+
const context = await resolveCustomerAuthContext(env, request);
|
|
498
|
+
const publicApiBaseURL = context.publicApiBaseURL ?? getPublicApiBaseUrl(env);
|
|
426
499
|
const authDb = db;
|
|
427
500
|
return createCustomerBetterAuth({
|
|
428
501
|
db: authDb,
|
|
@@ -430,16 +503,17 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
430
503
|
baseURL: context.baseURL,
|
|
431
504
|
basePath: "/auth/customer",
|
|
432
505
|
trustedOrigins: context.trustedOrigins,
|
|
433
|
-
methods: withCustomerSocialRedirectUris(context.methods,
|
|
506
|
+
methods: withCustomerSocialRedirectUris(context.methods, publicApiBaseURL),
|
|
434
507
|
sendResetPassword: async ({ user, url }) => {
|
|
508
|
+
const publicUrl = withCustomerPublicResetPasswordUrl(url, publicApiBaseURL);
|
|
435
509
|
if (!emailSender) {
|
|
436
510
|
if (allowAuthSecretLogging(env)) {
|
|
437
|
-
console.info(`[auth/customer] reset-password (debug fallback) -> ${user.email}: ${
|
|
511
|
+
console.info(`[auth/customer] reset-password (debug fallback) -> ${user.email}: ${publicUrl}`);
|
|
438
512
|
return;
|
|
439
513
|
}
|
|
440
514
|
throw new Error("Password reset email provider is not configured");
|
|
441
515
|
}
|
|
442
|
-
await emailSender.sendResetPassword({ user, url });
|
|
516
|
+
await emailSender.sendResetPassword({ user, url: publicUrl });
|
|
443
517
|
},
|
|
444
518
|
sendVerificationOTP: async ({ email, otp, type }) => {
|
|
445
519
|
if (!emailSender) {
|
|
@@ -453,11 +527,6 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
453
527
|
},
|
|
454
528
|
});
|
|
455
529
|
}
|
|
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
530
|
const FULL_ACCESS_SCOPES = ["*"];
|
|
462
531
|
function scopesForOperatorRole(role) {
|
|
463
532
|
const base = accessCatalogScopesForRole(role, runtimeOptions.accessCatalog);
|
|
@@ -503,14 +572,17 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
503
572
|
return profile?.permissions ?? FULL_ACCESS_SCOPES;
|
|
504
573
|
}
|
|
505
574
|
async function resolveAuthRequest(request, env) {
|
|
506
|
-
const
|
|
575
|
+
const surface = classifyOperatorApiAuthSurface(request.url);
|
|
576
|
+
if (!surface)
|
|
577
|
+
return null;
|
|
578
|
+
const customerSurface = surface === "customer";
|
|
507
579
|
if (customerSurface && env.VOYANT_CUSTOMER_AUTH_MODE?.trim() === "disabled") {
|
|
508
580
|
return null;
|
|
509
581
|
}
|
|
510
582
|
const { db, dispose } = openDatabase(env);
|
|
511
583
|
try {
|
|
512
584
|
const auth = customerSurface
|
|
513
|
-
? buildCustomerBetterAuth(env, db, request)
|
|
585
|
+
? await buildCustomerBetterAuth(env, db, request)
|
|
514
586
|
: buildAdminBetterAuth(env, db);
|
|
515
587
|
const session = await auth.api.getSession({ headers: request.headers });
|
|
516
588
|
if (!session) {
|
|
@@ -637,16 +709,19 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
637
709
|
}
|
|
638
710
|
}
|
|
639
711
|
async function getBootstrapStatusForRequest(request, env) {
|
|
712
|
+
const modules = runtimeOptions.activeModules
|
|
713
|
+
? { modules: [...runtimeOptions.activeModules] }
|
|
714
|
+
: {};
|
|
640
715
|
if (shouldUseBrowserEvidenceAuthFallback(env, request)) {
|
|
641
|
-
return { hasUsers: true };
|
|
716
|
+
return { hasUsers: true, ...modules };
|
|
642
717
|
}
|
|
643
718
|
if (isVoyantCloudAuthMode(env)) {
|
|
644
|
-
return { hasUsers: true, authMode: "voyant-cloud" };
|
|
719
|
+
return { hasUsers: true, authMode: "voyant-cloud", ...modules };
|
|
645
720
|
}
|
|
646
721
|
const { db, dispose } = openDatabase(env);
|
|
647
722
|
try {
|
|
648
723
|
const [row] = await db.select({ count: sql `count(*)::int` }).from(authUser);
|
|
649
|
-
return { hasUsers: (row?.count ?? 0) > 0, authMode: "local" };
|
|
724
|
+
return { hasUsers: (row?.count ?? 0) > 0, authMode: "local", ...modules };
|
|
650
725
|
}
|
|
651
726
|
finally {
|
|
652
727
|
await dispose();
|
|
@@ -837,17 +912,15 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
837
912
|
const exchangeConfig = getCloudAuthExchangeConfig(c.env);
|
|
838
913
|
if (!exchangeConfig) {
|
|
839
914
|
const url = new URL(c.req.url);
|
|
915
|
+
const callbackUrl = getCloudAuthStartConfig(c.env)?.adminCallbackUrl;
|
|
840
916
|
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"),
|
|
917
|
+
"Set-Cookie": buildClearCloudAdminAuthStateCookie(callbackUrl ? new URL(callbackUrl).protocol === "https:" : url.protocol === "https:", url.pathname.replace(/\/callback$/, "") || "/auth/admin/cloud"),
|
|
842
918
|
});
|
|
843
919
|
}
|
|
844
920
|
const { db, dispose } = openDatabase(c.env);
|
|
845
921
|
try {
|
|
846
922
|
const betterAuth = buildAdminBetterAuth(c.env, db);
|
|
847
|
-
|
|
848
|
-
? c.req.raw
|
|
849
|
-
: rewriteLegacyAdminAuthRequest(c.req.raw);
|
|
850
|
-
return await betterAuth.handler(request);
|
|
923
|
+
return await betterAuth.handler(c.req.raw);
|
|
851
924
|
}
|
|
852
925
|
catch (error) {
|
|
853
926
|
console.error("[auth/cloud/callback] Error:", error);
|
|
@@ -859,17 +932,17 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
859
932
|
}
|
|
860
933
|
auth.get("/auth/admin/cloud/start", handleCloudAuthStart);
|
|
861
934
|
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
935
|
auth.all("/auth/api-tokens", handleApiTokensFacade);
|
|
866
936
|
auth.all("/auth/api-tokens/:keyId", handleApiTokensFacade);
|
|
867
937
|
auth.all("/auth/api-tokens/:keyId/rotate", handleApiTokensFacade);
|
|
868
938
|
auth.get("/auth/organization/list-members", handleOrganizationMembersFacade);
|
|
869
939
|
auth.get("/auth/customer/status", async (c) => {
|
|
940
|
+
if (c.env.VOYANT_CUSTOMER_AUTH_MODE?.trim() === "disabled") {
|
|
941
|
+
return c.json({ authenticated: false, disabled: true });
|
|
942
|
+
}
|
|
870
943
|
const { db, dispose } = openDatabase(c.env);
|
|
871
944
|
try {
|
|
872
|
-
const betterAuth = buildCustomerBetterAuth(c.env, db, c.req.raw);
|
|
945
|
+
const betterAuth = await buildCustomerBetterAuth(c.env, db, c.req.raw);
|
|
873
946
|
const session = await betterAuth.api.getSession({ headers: c.req.raw.headers });
|
|
874
947
|
return c.json({ authenticated: Boolean(session) });
|
|
875
948
|
}
|
|
@@ -877,7 +950,7 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
877
950
|
c.executionCtx.waitUntil(dispose());
|
|
878
951
|
}
|
|
879
952
|
});
|
|
880
|
-
auth.get("/auth/customer/config", (c) => {
|
|
953
|
+
auth.get("/auth/customer/config", async (c) => {
|
|
881
954
|
if (c.env.VOYANT_CUSTOMER_AUTH_MODE?.trim() === "disabled") {
|
|
882
955
|
return c.json({
|
|
883
956
|
methods: {
|
|
@@ -889,7 +962,7 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
889
962
|
},
|
|
890
963
|
});
|
|
891
964
|
}
|
|
892
|
-
return c.json(publicCustomerAuthConfiguration(c.env, c.req.raw));
|
|
965
|
+
return c.json(await publicCustomerAuthConfiguration(c.env, c.req.raw));
|
|
893
966
|
});
|
|
894
967
|
auth.all("/auth/customer/*", async (c) => {
|
|
895
968
|
if (c.env.VOYANT_CUSTOMER_AUTH_MODE?.trim() === "disabled") {
|
|
@@ -897,7 +970,7 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
897
970
|
}
|
|
898
971
|
const { db, dispose } = openDatabase(c.env);
|
|
899
972
|
try {
|
|
900
|
-
const betterAuth = buildCustomerBetterAuth(c.env, db, c.req.raw);
|
|
973
|
+
const betterAuth = await buildCustomerBetterAuth(c.env, db, c.req.raw);
|
|
901
974
|
return await betterAuth.handler(c.req.raw);
|
|
902
975
|
}
|
|
903
976
|
finally {
|
|
@@ -924,20 +997,6 @@ export function createOperatorAuthNodeRuntime(runtimeOptions) {
|
|
|
924
997
|
c.executionCtx.waitUntil(dispose());
|
|
925
998
|
}
|
|
926
999
|
});
|
|
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
1000
|
return {
|
|
942
1001
|
handler: auth,
|
|
943
1002
|
getBootstrapStatusForRequest,
|