@voyant-travel/hono 0.130.1 → 0.131.1
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/anonymous-paths.d.ts +6 -1
- package/dist/anonymous-paths.js +27 -1
- package/dist/app.js +5 -2
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/require-customer-buyer.d.ts +27 -0
- package/dist/auth/require-customer-buyer.js +55 -0
- package/dist/auth/require-customer-identity.d.ts +9 -0
- package/dist/auth/require-customer-identity.js +16 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/middleware/auth.d.ts +1 -0
- package/dist/middleware/auth.js +39 -5
- package/dist/middleware/require-actor.js +3 -0
- package/dist/module.d.ts +7 -3
- package/package.json +6 -6
|
@@ -9,6 +9,11 @@ import type { ApiExtension, ApiModule } from "./module.js";
|
|
|
9
9
|
* `publicPath`/name under `/v1/public`. `anonymous: true` opens the whole mount;
|
|
10
10
|
* a string array opens specific sub-paths relative to it. Pure and sorted for a
|
|
11
11
|
* deterministic, snapshot-auditable result; the global list is what `requireAuth`
|
|
12
|
-
* matches to skip auth
|
|
12
|
+
* matches to skip auth and mark the request as explicitly anonymous.
|
|
13
13
|
*/
|
|
14
14
|
export declare function assembleAnonymousPaths(modules: readonly ApiModule[], extensions: readonly ApiExtension[], explicit?: readonly string[]): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Assemble anonymous paths that use mixed auth: valid customer sessions are
|
|
17
|
+
* resolved, while requests without a valid session continue as explicit guests.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assembleOptionalCustomerAuthPaths(modules: readonly ApiModule[], extensions: readonly ApiExtension[]): string[];
|
package/dist/anonymous-paths.js
CHANGED
|
@@ -9,7 +9,7 @@ import { resolveSurfaceMountPath } from "./mount-paths.js";
|
|
|
9
9
|
* `publicPath`/name under `/v1/public`. `anonymous: true` opens the whole mount;
|
|
10
10
|
* a string array opens specific sub-paths relative to it. Pure and sorted for a
|
|
11
11
|
* deterministic, snapshot-auditable result; the global list is what `requireAuth`
|
|
12
|
-
* matches to skip auth
|
|
12
|
+
* matches to skip auth and mark the request as explicitly anonymous.
|
|
13
13
|
*/
|
|
14
14
|
export function assembleAnonymousPaths(modules, extensions, explicit = []) {
|
|
15
15
|
const paths = new Set(explicit);
|
|
@@ -53,3 +53,29 @@ export function assembleAnonymousPaths(modules, extensions, explicit = []) {
|
|
|
53
53
|
}
|
|
54
54
|
return [...paths].sort();
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Assemble anonymous paths that use mixed auth: valid customer sessions are
|
|
58
|
+
* resolved, while requests without a valid session continue as explicit guests.
|
|
59
|
+
*/
|
|
60
|
+
export function assembleOptionalCustomerAuthPaths(modules, extensions) {
|
|
61
|
+
const paths = new Set();
|
|
62
|
+
const add = (mount, declaration) => {
|
|
63
|
+
if (!declaration)
|
|
64
|
+
return;
|
|
65
|
+
if (declaration === true) {
|
|
66
|
+
paths.add(mount);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const sub of declaration) {
|
|
70
|
+
const trimmed = sub.trim().replace(/^\/+|\/+$/g, "");
|
|
71
|
+
paths.add(trimmed ? `${mount}/${trimmed}` : mount);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
for (const module of modules) {
|
|
75
|
+
add(resolveSurfaceMountPath("/v1/public", module.publicPath, module.module.name), module.optionalCustomerAuth);
|
|
76
|
+
}
|
|
77
|
+
for (const extension of extensions) {
|
|
78
|
+
add(resolveSurfaceMountPath("/v1/public", extension.publicPath, extension.extension.module), extension.optionalCustomerAuth);
|
|
79
|
+
}
|
|
80
|
+
return [...paths].sort();
|
|
81
|
+
}
|
package/dist/app.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
4
4
|
import { createContainer, createEventBus, createQueryRunner, } from "@voyant-travel/core";
|
|
5
5
|
import { createLinkServiceFactory } from "@voyant-travel/db/links";
|
|
6
|
-
import { assembleAnonymousPaths } from "./anonymous-paths.js";
|
|
6
|
+
import { assembleAnonymousPaths, assembleOptionalCustomerAuthPaths } from "./anonymous-paths.js";
|
|
7
7
|
import { containerToServiceResolver, makeFrameworkLogger, wireWorkflowRuntime, } from "./app-workflows.js";
|
|
8
8
|
import { composeAuthAugmentations } from "./auth-augmentation.js";
|
|
9
9
|
import { expandApiBundles, isLazyApiBundle, } from "./bundle.js";
|
|
@@ -99,13 +99,14 @@ export function mountApp(config) {
|
|
|
99
99
|
// Anonymous-access allow-list (ADR-0008): assembled from module/extension
|
|
100
100
|
// `anonymous` declarations + bundle-declared absolute anonymous paths (e.g. a
|
|
101
101
|
// payment-processor webhook) + any explicit `publicPaths` escape-hatch entries.
|
|
102
|
-
// Used by both the auth middleware (skip auth /
|
|
102
|
+
// Used by both the auth middleware (skip auth / mark an explicit guest) and the
|
|
103
103
|
// public-write rate-limit matcher below, so the two never diverge.
|
|
104
104
|
const anonymousPaths = assembleAnonymousPaths(allModules, allExtensions, [
|
|
105
105
|
...(config.publicPaths ?? []),
|
|
106
106
|
...(expanded?.anonymousPaths ?? []),
|
|
107
107
|
...lazyPlugins.flatMap((plugin) => plugin.anonymous ?? []),
|
|
108
108
|
]);
|
|
109
|
+
const optionalCustomerAuthPaths = assembleOptionalCustomerAuthPaths(allModules, allExtensions);
|
|
109
110
|
const clientAuthenticatedRoutes = assembleClientAuthenticatedRoutes(allModules);
|
|
110
111
|
const composedAuth = composeAuthAugmentations(config.auth, allModules);
|
|
111
112
|
// When the framework owns the bus, route subscriber-dispatch failures
|
|
@@ -263,6 +264,7 @@ export function mountApp(config) {
|
|
|
263
264
|
anonymousPaths.push(...assembleAnonymousPaths(lazyExpanded.modules, lazyExpanded.extensions, [
|
|
264
265
|
...lazyExpanded.anonymousPaths,
|
|
265
266
|
]));
|
|
267
|
+
optionalCustomerAuthPaths.push(...assembleOptionalCustomerAuthPaths(lazyExpanded.modules, lazyExpanded.extensions));
|
|
266
268
|
for (const mod of lazyExpanded.modules) {
|
|
267
269
|
if (mod.module.service !== undefined) {
|
|
268
270
|
container.register(mod.module.name, mod.module.service);
|
|
@@ -483,6 +485,7 @@ export function mountApp(config) {
|
|
|
483
485
|
: config.db;
|
|
484
486
|
const authOptions = {
|
|
485
487
|
publicPaths: anonymousPaths,
|
|
488
|
+
optionalCustomerAuthPaths,
|
|
486
489
|
clientAuthenticatedRoutes,
|
|
487
490
|
basePath: config.basePath,
|
|
488
491
|
auth: composedAuth,
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { constantTimeEqual, generateNumericCode, randomBytesHex, sha256Base64Url, sha256Hex, unsignCookie, } from "./crypto.js";
|
|
2
|
+
export { type BusinessCustomerBuyerContext, type CustomerBuyerContext, type PersonalCustomerBuyerContext, requireBusinessCustomerBuyerContext, requireCustomerBuyerContext, requirePersonalCustomerBuyerContext, } from "./require-customer-buyer.js";
|
|
3
|
+
export { type CustomerIdentityContext, requireCustomerIdentityContext, } from "./require-customer-identity.js";
|
|
2
4
|
export { requireUserId } from "./require-user.js";
|
|
3
5
|
export type { SessionAuthContext } from "./session-jwt.js";
|
|
4
6
|
export { extractBearerToken, verifySession } from "./session-jwt.js";
|
package/dist/auth/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { constantTimeEqual, generateNumericCode, randomBytesHex, sha256Base64Url, sha256Hex, unsignCookie, } from "./crypto.js";
|
|
2
|
+
export { requireBusinessCustomerBuyerContext, requireCustomerBuyerContext, requirePersonalCustomerBuyerContext, } from "./require-customer-buyer.js";
|
|
3
|
+
export { requireCustomerIdentityContext, } from "./require-customer-identity.js";
|
|
2
4
|
export { requireUserId } from "./require-user.js";
|
|
3
5
|
export { extractBearerToken, verifySession } from "./session-jwt.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
interface CustomerBuyerContextBase {
|
|
3
|
+
userId: string;
|
|
4
|
+
buyerAccountId: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PersonalCustomerBuyerContext extends CustomerBuyerContextBase {
|
|
7
|
+
kind: "personal";
|
|
8
|
+
authOrganizationId: null;
|
|
9
|
+
relationshipOrganizationId: null;
|
|
10
|
+
relationshipPersonId: string | null;
|
|
11
|
+
membershipId: null;
|
|
12
|
+
membershipRole: null;
|
|
13
|
+
}
|
|
14
|
+
export interface BusinessCustomerBuyerContext extends CustomerBuyerContextBase {
|
|
15
|
+
kind: "business";
|
|
16
|
+
authOrganizationId: string;
|
|
17
|
+
relationshipOrganizationId: string;
|
|
18
|
+
relationshipPersonId: null;
|
|
19
|
+
membershipId: string;
|
|
20
|
+
membershipRole: string;
|
|
21
|
+
}
|
|
22
|
+
export type CustomerBuyerContext = PersonalCustomerBuyerContext | BusinessCustomerBuyerContext;
|
|
23
|
+
/** Requires the provider-neutral, request-revalidated storefront buyer context. */
|
|
24
|
+
export declare function requireCustomerBuyerContext(c: Context): CustomerBuyerContext;
|
|
25
|
+
export declare function requirePersonalCustomerBuyerContext(c: Context): PersonalCustomerBuyerContext;
|
|
26
|
+
export declare function requireBusinessCustomerBuyerContext(c: Context): BusinessCustomerBuyerContext;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ForbiddenApiError } from "../validation.js";
|
|
2
|
+
import { requireCustomerIdentityContext } from "./require-customer-identity.js";
|
|
3
|
+
/** Requires the provider-neutral, request-revalidated storefront buyer context. */
|
|
4
|
+
export function requireCustomerBuyerContext(c) {
|
|
5
|
+
const identity = requireCustomerIdentityContext(c);
|
|
6
|
+
const userId = identity.userId;
|
|
7
|
+
const buyerAccountId = c.get("buyerAccountId");
|
|
8
|
+
const kind = c.get("buyerAccountKind");
|
|
9
|
+
if (!buyerAccountId || (kind !== "personal" && kind !== "business")) {
|
|
10
|
+
throw new ForbiddenApiError("A customer buyer account must be selected");
|
|
11
|
+
}
|
|
12
|
+
if (kind === "personal") {
|
|
13
|
+
return {
|
|
14
|
+
userId,
|
|
15
|
+
buyerAccountId,
|
|
16
|
+
kind,
|
|
17
|
+
authOrganizationId: null,
|
|
18
|
+
relationshipOrganizationId: null,
|
|
19
|
+
relationshipPersonId: identity.relationshipPersonId,
|
|
20
|
+
membershipId: null,
|
|
21
|
+
membershipRole: null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const authOrganizationId = c.get("authOrganizationId");
|
|
25
|
+
const relationshipOrganizationId = c.get("relationshipOrganizationId");
|
|
26
|
+
const membershipId = c.get("buyerMembershipId");
|
|
27
|
+
const membershipRole = c.get("buyerMembershipRole");
|
|
28
|
+
if (!authOrganizationId || !relationshipOrganizationId || !membershipId || !membershipRole) {
|
|
29
|
+
throw new ForbiddenApiError("The business buyer membership is no longer available");
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
userId,
|
|
33
|
+
buyerAccountId,
|
|
34
|
+
kind,
|
|
35
|
+
authOrganizationId,
|
|
36
|
+
relationshipOrganizationId,
|
|
37
|
+
relationshipPersonId: null,
|
|
38
|
+
membershipId,
|
|
39
|
+
membershipRole,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function requirePersonalCustomerBuyerContext(c) {
|
|
43
|
+
const buyer = requireCustomerBuyerContext(c);
|
|
44
|
+
if (buyer.kind !== "personal") {
|
|
45
|
+
throw new ForbiddenApiError("A personal buyer account is required");
|
|
46
|
+
}
|
|
47
|
+
return buyer;
|
|
48
|
+
}
|
|
49
|
+
export function requireBusinessCustomerBuyerContext(c) {
|
|
50
|
+
const buyer = requireCustomerBuyerContext(c);
|
|
51
|
+
if (buyer.kind !== "business") {
|
|
52
|
+
throw new ForbiddenApiError("A business buyer account is required");
|
|
53
|
+
}
|
|
54
|
+
return buyer;
|
|
55
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
export interface CustomerIdentityContext {
|
|
3
|
+
userId: string;
|
|
4
|
+
sessionId: string | null;
|
|
5
|
+
email: string | null;
|
|
6
|
+
relationshipPersonId: string | null;
|
|
7
|
+
}
|
|
8
|
+
/** Requires customer-realm identity without requiring a selected buyer account. */
|
|
9
|
+
export declare function requireCustomerIdentityContext(c: Context): CustomerIdentityContext;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UnauthorizedApiError } from "../validation.js";
|
|
2
|
+
/** Requires customer-realm identity without requiring a selected buyer account. */
|
|
3
|
+
export function requireCustomerIdentityContext(c) {
|
|
4
|
+
if (c.get("realm") !== "customer" || c.get("actor") !== "customer") {
|
|
5
|
+
throw new UnauthorizedApiError();
|
|
6
|
+
}
|
|
7
|
+
const userId = c.get("userId");
|
|
8
|
+
if (!userId)
|
|
9
|
+
throw new UnauthorizedApiError();
|
|
10
|
+
return {
|
|
11
|
+
userId,
|
|
12
|
+
sessionId: c.get("sessionId") ?? null,
|
|
13
|
+
email: c.get("email") ?? null,
|
|
14
|
+
relationshipPersonId: c.get("relationshipPersonId") ?? null,
|
|
15
|
+
};
|
|
16
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type { VoyantPermission } from "@voyant-travel/core";
|
|
2
|
-
export { assembleAnonymousPaths } from "./anonymous-paths.js";
|
|
2
|
+
export { assembleAnonymousPaths, assembleOptionalCustomerAuthPaths } from "./anonymous-paths.js";
|
|
3
3
|
export { mountApp } from "./app.js";
|
|
4
|
-
export type { SessionAuthContext } from "./auth/index.js";
|
|
5
|
-
export { constantTimeEqual, extractBearerToken, generateNumericCode, randomBytesHex, requireUserId, sha256Base64Url, sha256Hex, unsignCookie, verifySession, } from "./auth/index.js";
|
|
4
|
+
export type { BusinessCustomerBuyerContext, CustomerBuyerContext, CustomerIdentityContext, PersonalCustomerBuyerContext, SessionAuthContext, } from "./auth/index.js";
|
|
5
|
+
export { constantTimeEqual, extractBearerToken, generateNumericCode, randomBytesHex, requireBusinessCustomerBuyerContext, requireCustomerBuyerContext, requireCustomerIdentityContext, requirePersonalCustomerBuyerContext, requireUserId, sha256Base64Url, sha256Hex, unsignCookie, verifySession, } from "./auth/index.js";
|
|
6
6
|
export type { ApiBundle, ApiBundleInput, ExpandedApiBundles, LazyApiBundle, } from "./bundle.js";
|
|
7
7
|
export { defineApiBundle, defineLazyApiBundle, expandApiBundles, isLazyApiBundle, } from "./bundle.js";
|
|
8
8
|
export { type CreateAppConfig, createApp } from "./create-app.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { assembleAnonymousPaths } from "./anonymous-paths.js";
|
|
1
|
+
export { assembleAnonymousPaths, assembleOptionalCustomerAuthPaths } from "./anonymous-paths.js";
|
|
2
2
|
export { mountApp } from "./app.js";
|
|
3
|
-
export { constantTimeEqual, extractBearerToken, generateNumericCode, randomBytesHex, requireUserId, sha256Base64Url, sha256Hex, unsignCookie, verifySession, } from "./auth/index.js";
|
|
3
|
+
export { constantTimeEqual, extractBearerToken, generateNumericCode, randomBytesHex, requireBusinessCustomerBuyerContext, requireCustomerBuyerContext, requireCustomerIdentityContext, requirePersonalCustomerBuyerContext, requireUserId, sha256Base64Url, sha256Hex, unsignCookie, verifySession, } from "./auth/index.js";
|
|
4
4
|
export { defineApiBundle, defineLazyApiBundle, expandApiBundles, isLazyApiBundle, } from "./bundle.js";
|
|
5
5
|
export { createApp } from "./create-app.js";
|
|
6
6
|
export { createAuthenticatedDocumentDownloadResolver, encodeStorageKeyPath, resolveStoredDocumentDownload, } from "./document-download.js";
|
|
@@ -3,6 +3,7 @@ import type { AbsoluteClientAuthenticatedRoute } from "../client-authenticated-r
|
|
|
3
3
|
import { type DbSource, type VoyantAuthIntegration, type VoyantBindings, type VoyantVariables } from "../types.js";
|
|
4
4
|
export declare function requireAuth<TBindings extends VoyantBindings>(dbSource: DbSource<TBindings>, opts?: {
|
|
5
5
|
publicPaths?: string[];
|
|
6
|
+
optionalCustomerAuthPaths?: string[];
|
|
6
7
|
clientAuthenticatedRoutes?: readonly AbsoluteClientAuthenticatedRoute[];
|
|
7
8
|
basePath?: string;
|
|
8
9
|
auth?: VoyantAuthIntegration<TBindings>;
|
package/dist/middleware/auth.js
CHANGED
|
@@ -117,8 +117,32 @@ function applyAuthContext(c, auth) {
|
|
|
117
117
|
c.set("userId", auth.userId);
|
|
118
118
|
if (auth.sessionId)
|
|
119
119
|
c.set("sessionId", auth.sessionId);
|
|
120
|
+
if (auth.realm)
|
|
121
|
+
c.set("realm", auth.realm);
|
|
122
|
+
if (auth.isAnonymousRequest !== undefined) {
|
|
123
|
+
c.set("isAnonymousRequest", auth.isAnonymousRequest);
|
|
124
|
+
}
|
|
120
125
|
if (auth.organizationId !== undefined)
|
|
121
126
|
c.set("organizationId", auth.organizationId ?? undefined);
|
|
127
|
+
if (auth.buyerAccountId !== undefined)
|
|
128
|
+
c.set("buyerAccountId", auth.buyerAccountId ?? undefined);
|
|
129
|
+
if (auth.buyerAccountKind !== undefined)
|
|
130
|
+
c.set("buyerAccountKind", auth.buyerAccountKind);
|
|
131
|
+
if (auth.authOrganizationId !== undefined) {
|
|
132
|
+
c.set("authOrganizationId", auth.authOrganizationId ?? undefined);
|
|
133
|
+
}
|
|
134
|
+
if (auth.relationshipOrganizationId !== undefined) {
|
|
135
|
+
c.set("relationshipOrganizationId", auth.relationshipOrganizationId ?? undefined);
|
|
136
|
+
}
|
|
137
|
+
if (auth.relationshipPersonId !== undefined) {
|
|
138
|
+
c.set("relationshipPersonId", auth.relationshipPersonId ?? undefined);
|
|
139
|
+
}
|
|
140
|
+
if (auth.buyerMembershipId !== undefined) {
|
|
141
|
+
c.set("buyerMembershipId", auth.buyerMembershipId ?? undefined);
|
|
142
|
+
}
|
|
143
|
+
if (auth.buyerMembershipRole !== undefined) {
|
|
144
|
+
c.set("buyerMembershipRole", auth.buyerMembershipRole ?? undefined);
|
|
145
|
+
}
|
|
122
146
|
if (auth.callerType)
|
|
123
147
|
c.set("callerType", auth.callerType);
|
|
124
148
|
if (auth.actor)
|
|
@@ -173,6 +197,7 @@ function matchesRequestRealm(path, auth) {
|
|
|
173
197
|
}
|
|
174
198
|
export function requireAuth(dbSource, opts) {
|
|
175
199
|
const publicPaths = opts?.publicPaths ?? [];
|
|
200
|
+
const optionalCustomerAuthPaths = opts?.optionalCustomerAuthPaths ?? [];
|
|
176
201
|
return async (c, next) => {
|
|
177
202
|
if (c.req.method === "OPTIONS")
|
|
178
203
|
return next();
|
|
@@ -188,10 +213,10 @@ export function requireAuth(dbSource, opts) {
|
|
|
188
213
|
if (matchesClientAuthenticatedRoute(c.req.method, p, opts?.clientAuthenticatedRoutes ?? [])) {
|
|
189
214
|
return next();
|
|
190
215
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
216
|
+
const isAnonymousPath = matchesPublicPath(p, publicPaths);
|
|
217
|
+
const isOptionalCustomerAuthPath = matchesPublicPath(p, optionalCustomerAuthPaths);
|
|
218
|
+
if (isAnonymousPath && !isOptionalCustomerAuthPath) {
|
|
219
|
+
c.set("isAnonymousRequest", true);
|
|
195
220
|
return next();
|
|
196
221
|
}
|
|
197
222
|
const authHeader = c.req.header("authorization") || c.req.header("Authorization");
|
|
@@ -369,11 +394,16 @@ export function requireAuth(dbSource, opts) {
|
|
|
369
394
|
: sessionRealm === "customer"
|
|
370
395
|
? customerSessionSecret
|
|
371
396
|
: undefined;
|
|
372
|
-
if (token &&
|
|
397
|
+
if (token &&
|
|
398
|
+
sessionRealm &&
|
|
399
|
+
sessionSecret &&
|
|
400
|
+
sessionSecret.length >= 32 &&
|
|
401
|
+
token.includes(".")) {
|
|
373
402
|
try {
|
|
374
403
|
const sessionAuth = await verifySession(token, sessionSecret);
|
|
375
404
|
applyAuthContext(c, {
|
|
376
405
|
...sessionAuth,
|
|
406
|
+
realm: sessionRealm,
|
|
377
407
|
callerType: "session",
|
|
378
408
|
actor: sessionRealm === "admin" ? "staff" : "customer",
|
|
379
409
|
audience: sessionRealm === "admin" ? "staff" : "customer",
|
|
@@ -384,6 +414,10 @@ export function requireAuth(dbSource, opts) {
|
|
|
384
414
|
// fall through
|
|
385
415
|
}
|
|
386
416
|
}
|
|
417
|
+
if (isAnonymousPath && isOptionalCustomerAuthPath) {
|
|
418
|
+
c.set("isAnonymousRequest", true);
|
|
419
|
+
return next();
|
|
420
|
+
}
|
|
387
421
|
if (opts?.auth?.onUnauthorized) {
|
|
388
422
|
const response = await opts.auth.onUnauthorized({
|
|
389
423
|
request: c.req.raw,
|
|
@@ -99,6 +99,9 @@ export function requireActor(...args) {
|
|
|
99
99
|
return async (c, next) => {
|
|
100
100
|
if (c.req.method === "OPTIONS")
|
|
101
101
|
return next();
|
|
102
|
+
if (c.get("isAnonymousRequest") && allowSet.has("customer") && !allowSet.has("staff")) {
|
|
103
|
+
return next();
|
|
104
|
+
}
|
|
102
105
|
const pathname = normalizePathname(new URL(c.req.url).pathname, {
|
|
103
106
|
basePath: options.basePath,
|
|
104
107
|
});
|
package/dist/module.d.ts
CHANGED
|
@@ -52,11 +52,13 @@ export interface ApiModule {
|
|
|
52
52
|
* opens `/v1/public/customer-portal/contact-exists`). The framework assembles
|
|
53
53
|
* the global anonymous allow-list from these declarations, so the
|
|
54
54
|
* "reachable-without-auth" decision lives next to the route rather than in a
|
|
55
|
-
* hand-maintained `publicPaths` list. Anonymous public requests are
|
|
56
|
-
*
|
|
57
|
-
* require a `staff` actor.
|
|
55
|
+
* hand-maintained `publicPaths` list. Anonymous public requests are marked as
|
|
56
|
+
* guests and do not receive a synthetic actor. Only affects the public
|
|
57
|
+
* surface — admin routes always require a `staff` actor.
|
|
58
58
|
*/
|
|
59
59
|
anonymous?: boolean | readonly string[];
|
|
60
|
+
/** Anonymous paths that also attempt live customer-session resolution. */
|
|
61
|
+
optionalCustomerAuth?: boolean | readonly string[];
|
|
60
62
|
/**
|
|
61
63
|
* Concrete admin endpoints whose credential is validated by the route itself
|
|
62
64
|
* instead of by the staff-session middleware. Each declaration is matched by
|
|
@@ -125,6 +127,8 @@ export interface ApiExtension {
|
|
|
125
127
|
* to the extension's public mount.
|
|
126
128
|
*/
|
|
127
129
|
anonymous?: boolean | readonly string[];
|
|
130
|
+
/** Anonymous paths that also attempt live customer-session resolution. */
|
|
131
|
+
optionalCustomerAuth?: boolean | readonly string[];
|
|
128
132
|
/**
|
|
129
133
|
* Absolute transactional path prefixes — same semantics as
|
|
130
134
|
* {@link ApiModule.transactionalPaths}.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.131.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -130,17 +130,17 @@
|
|
|
130
130
|
"drizzle-orm": "^0.45.2",
|
|
131
131
|
"hono": "^4.12.27",
|
|
132
132
|
"zod": "^4.4.3",
|
|
133
|
-
"@voyant-travel/core": "^0.
|
|
134
|
-
"@voyant-travel/db": "^0.
|
|
135
|
-
"@voyant-travel/types": "^0.109.
|
|
133
|
+
"@voyant-travel/core": "^0.130.0",
|
|
134
|
+
"@voyant-travel/db": "^0.116.0",
|
|
135
|
+
"@voyant-travel/types": "^0.109.7",
|
|
136
136
|
"@voyant-travel/utils": "^0.108.0",
|
|
137
|
-
"@voyant-travel/workflows": "^0.122.
|
|
137
|
+
"@voyant-travel/workflows": "^0.122.10"
|
|
138
138
|
},
|
|
139
139
|
"devDependencies": {
|
|
140
140
|
"typescript": "^6.0.3",
|
|
141
141
|
"vitest": "^4.1.9",
|
|
142
142
|
"@voyant-travel/voyant-typescript-config": "^0.1.0",
|
|
143
|
-
"@voyant-travel/workflows-orchestrator": "^0.122.
|
|
143
|
+
"@voyant-travel/workflows-orchestrator": "^0.122.10"
|
|
144
144
|
},
|
|
145
145
|
"files": [
|
|
146
146
|
"dist"
|