@voyant-travel/hono 0.129.1 → 0.130.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 +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/middleware/auth.js +36 -4
- package/dist/types.d.ts +13 -8
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -41,6 +41,11 @@ const app = createApp({
|
|
|
41
41
|
})
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Custom `resolve` adapters must return an explicit realm alongside the actor:
|
|
45
|
+
admin sessions return `{ actor: "staff", realm: "admin" }`, while storefront
|
|
46
|
+
sessions return a non-staff actor with `realm: "customer"`. Realm/actor
|
|
47
|
+
mismatches are rejected before protected routes run.
|
|
48
|
+
|
|
44
49
|
Use `modules`, `extensions`, and provider-backed route helpers as the default
|
|
45
50
|
composition surface. Use `plugins` when you want to register a reusable
|
|
46
51
|
distribution bundle that packages those pieces together.
|
package/dist/index.d.ts
CHANGED
|
@@ -20,5 +20,5 @@ export { stampOpenApiRegistryApiId } from "./openapi-ownership.js";
|
|
|
20
20
|
export { openApiValidationHook } from "./openapi-validation.js";
|
|
21
21
|
export type { CreatePublicCapabilityOptions, PublicCapabilityCookieOptions, PublicCapabilityPayload, VerifyPublicCapabilityOptions, } from "./public-capability.js";
|
|
22
22
|
export { createPublicCapabilityToken, extractPublicCapabilityToken, serializePublicCapabilityCookie, verifyPublicCapabilityToken, } from "./public-capability.js";
|
|
23
|
-
export type { DbFactory, DbFactorySelector, DbSource, DbSurfaceSelection, LogEntry, LoggerProvider, VoyantAppConfig, VoyantAuthAppTokenResolveArgs, VoyantAuthIntegration, VoyantAuthPermissionArgs, VoyantAuthResolveArgs, VoyantBindings, VoyantDb, VoyantExecutionContext, VoyantQueryRuntime, VoyantRequestAuthContext, VoyantRouteHandler, VoyantVariables, } from "./types.js";
|
|
23
|
+
export type { DbFactory, DbFactorySelector, DbSource, DbSurfaceSelection, LogEntry, LoggerProvider, VoyantAppConfig, VoyantAuthAppTokenResolveArgs, VoyantAuthIntegration, VoyantAuthPermissionArgs, VoyantAuthResolveArgs, VoyantBindings, VoyantDb, VoyantExecutionContext, VoyantQueryRuntime, VoyantRequestAuthContext, VoyantResolvedSessionAuthContext, VoyantRouteHandler, VoyantVariables, } from "./types.js";
|
|
24
24
|
export { ApiHttpError, ForbiddenApiError, normalizeValidationError, parseJsonBody, parseOptionalJsonBody, parseQuery, RequestValidationError, UnauthorizedApiError, } from "./validation.js";
|
package/dist/middleware/auth.js
CHANGED
|
@@ -149,6 +149,12 @@ function applyAuthContext(c, auth) {
|
|
|
149
149
|
if (auth.appCredentialGeneration !== undefined) {
|
|
150
150
|
c.set("appCredentialGeneration", auth.appCredentialGeneration);
|
|
151
151
|
}
|
|
152
|
+
if (auth.appWorkloadEnvironmentId) {
|
|
153
|
+
c.set("appWorkloadEnvironmentId", auth.appWorkloadEnvironmentId);
|
|
154
|
+
}
|
|
155
|
+
if (auth.appContractGeneration !== undefined) {
|
|
156
|
+
c.set("appContractGeneration", auth.appContractGeneration);
|
|
157
|
+
}
|
|
152
158
|
if (auth.appTokenMode)
|
|
153
159
|
c.set("appTokenMode", auth.appTokenMode);
|
|
154
160
|
if (auth.appViewerId)
|
|
@@ -156,6 +162,15 @@ function applyAuthContext(c, auth) {
|
|
|
156
162
|
if (auth.appContextConstraint)
|
|
157
163
|
c.set("appContextConstraint", auth.appContextConstraint);
|
|
158
164
|
}
|
|
165
|
+
function matchesRequestRealm(path, auth) {
|
|
166
|
+
if (path === "/v1/admin" || path.startsWith("/v1/admin/")) {
|
|
167
|
+
return auth.actor === "staff" && auth.realm === "admin";
|
|
168
|
+
}
|
|
169
|
+
if (path === "/v1/public" || path.startsWith("/v1/public/")) {
|
|
170
|
+
return auth.actor !== "staff" && auth.realm === "customer";
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
159
174
|
export function requireAuth(dbSource, opts) {
|
|
160
175
|
const publicPaths = opts?.publicPaths ?? [];
|
|
161
176
|
return async (c, next) => {
|
|
@@ -325,7 +340,7 @@ export function requireAuth(dbSource, opts) {
|
|
|
325
340
|
// Guarded: Hono throws on `executionCtx` access outside Workers.
|
|
326
341
|
ctx: tryGetExecutionCtx(c),
|
|
327
342
|
});
|
|
328
|
-
if (resolved?.userId) {
|
|
343
|
+
if (resolved?.userId && matchesRequestRealm(p, resolved)) {
|
|
329
344
|
applyAuthContext(c, resolved);
|
|
330
345
|
// `await` is load-bearing — see strategy 2: a bare
|
|
331
346
|
// `return next()` would let the `finally` release the shared
|
|
@@ -337,14 +352,31 @@ export function requireAuth(dbSource, opts) {
|
|
|
337
352
|
await lease.release();
|
|
338
353
|
}
|
|
339
354
|
}
|
|
340
|
-
// Strategy 5:
|
|
341
|
-
|
|
342
|
-
|
|
355
|
+
// Strategy 5: Realm-bound session-claims bearer token support. Ambiguous
|
|
356
|
+
// surfaces intentionally skip this strategy instead of trying both keys.
|
|
357
|
+
const sessionRealm = p === "/v1/admin" || p.startsWith("/v1/admin/")
|
|
358
|
+
? "admin"
|
|
359
|
+
: p === "/v1/public" || p.startsWith("/v1/public/")
|
|
360
|
+
? "customer"
|
|
361
|
+
: null;
|
|
362
|
+
const adminSessionSecret = c.env.SESSION_CLAIMS_ADMIN_SECRET?.trim();
|
|
363
|
+
const customerSessionSecret = c.env.SESSION_CLAIMS_CUSTOMER_SECRET?.trim();
|
|
364
|
+
const sessionRootsAreDistinct = !adminSessionSecret || !customerSessionSecret || adminSessionSecret !== customerSessionSecret;
|
|
365
|
+
const sessionSecret = !sessionRootsAreDistinct
|
|
366
|
+
? undefined
|
|
367
|
+
: sessionRealm === "admin"
|
|
368
|
+
? adminSessionSecret
|
|
369
|
+
: sessionRealm === "customer"
|
|
370
|
+
? customerSessionSecret
|
|
371
|
+
: undefined;
|
|
372
|
+
if (token && sessionSecret && sessionSecret.length >= 32 && token.includes(".")) {
|
|
343
373
|
try {
|
|
344
374
|
const sessionAuth = await verifySession(token, sessionSecret);
|
|
345
375
|
applyAuthContext(c, {
|
|
346
376
|
...sessionAuth,
|
|
347
377
|
callerType: "session",
|
|
378
|
+
actor: sessionRealm === "admin" ? "staff" : "customer",
|
|
379
|
+
audience: sessionRealm === "admin" ? "staff" : "customer",
|
|
348
380
|
});
|
|
349
381
|
return next();
|
|
350
382
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ export interface VoyantExecutionContext {
|
|
|
17
17
|
export interface VoyantBindings {
|
|
18
18
|
INTERNAL_API_KEY?: string;
|
|
19
19
|
INTERNAL_API_KEY_SCOPES?: string;
|
|
20
|
-
|
|
20
|
+
SESSION_CLAIMS_ADMIN_SECRET?: string;
|
|
21
|
+
SESSION_CLAIMS_CUSTOMER_SECRET?: string;
|
|
21
22
|
BETTER_AUTH_ADMIN_SECRET?: string;
|
|
22
23
|
BETTER_AUTH_CUSTOMER_SECRET?: string;
|
|
23
24
|
DATABASE_URL: string;
|
|
@@ -117,9 +118,13 @@ export declare function resolveDbFactoryResult(value: VoyantDb | DisposableDb):
|
|
|
117
118
|
export type VoyantRequestAuthContext = Omit<VoyantAuthContext, "actor"> & {
|
|
118
119
|
userId: string;
|
|
119
120
|
actor: Actor;
|
|
120
|
-
/** Explicit security realm
|
|
121
|
+
/** Explicit security realm when the context represents a user session. */
|
|
121
122
|
realm?: "admin" | "customer";
|
|
122
123
|
};
|
|
124
|
+
/** User-session identity returned by a custom `auth.resolve` adapter. */
|
|
125
|
+
export type VoyantResolvedSessionAuthContext = VoyantRequestAuthContext & {
|
|
126
|
+
realm: "admin" | "customer";
|
|
127
|
+
};
|
|
123
128
|
export interface LogEntry {
|
|
124
129
|
method: string;
|
|
125
130
|
path: string;
|
|
@@ -159,13 +164,13 @@ export interface VoyantAuthIntegration<TBindings extends VoyantBindings = Voyant
|
|
|
159
164
|
/**
|
|
160
165
|
* Resolve the request to an auth context, or return `null` for anonymous.
|
|
161
166
|
*
|
|
162
|
-
* The returned object MUST include `actor`
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
+
* The returned object MUST include both `actor` and `realm`. Admin sessions
|
|
168
|
+
* use `realm: "admin"` with `actor: "staff"`; customer, partner, or supplier
|
|
169
|
+
* sessions use `realm: "customer"` with the corresponding non-staff actor.
|
|
170
|
+
* Realm/actor mismatches fail closed so credentials cannot cross between
|
|
171
|
+
* `/v1/admin/*` and `/v1/public/*`.
|
|
167
172
|
*/
|
|
168
|
-
resolve?: (args: VoyantAuthResolveArgs<TBindings>) => Promise<
|
|
173
|
+
resolve?: (args: VoyantAuthResolveArgs<TBindings>) => Promise<VoyantResolvedSessionAuthContext | null> | VoyantResolvedSessionAuthContext | null;
|
|
169
174
|
resolveAppToken?: (args: VoyantAuthAppTokenResolveArgs<TBindings>) => Promise<VoyantAuthContext | null> | VoyantAuthContext | null;
|
|
170
175
|
hasPermission?: (args: VoyantAuthPermissionArgs<TBindings>) => Promise<boolean> | boolean;
|
|
171
176
|
validateApiKey?: (args: VoyantAuthApiKeyValidationArgs<TBindings>) => Promise<boolean> | boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.130.0",
|
|
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.114.
|
|
133
|
+
"@voyant-travel/core": "^0.128.0",
|
|
134
|
+
"@voyant-travel/db": "^0.114.14",
|
|
135
135
|
"@voyant-travel/types": "^0.109.4",
|
|
136
|
-
"@voyant-travel/utils": "^0.
|
|
137
|
-
"@voyant-travel/workflows": "^0.122.
|
|
136
|
+
"@voyant-travel/utils": "^0.108.0",
|
|
137
|
+
"@voyant-travel/workflows": "^0.122.7"
|
|
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.7"
|
|
144
144
|
},
|
|
145
145
|
"files": [
|
|
146
146
|
"dist"
|