@voyant-travel/hono 0.120.1 → 0.121.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/app.d.ts.map +1 -1
- package/dist/app.js +24 -18
- package/dist/lazy-routes.d.ts +1 -1
- package/dist/lazy-routes.d.ts.map +1 -1
- package/dist/lazy-routes.js +21 -2
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +31 -5
- package/dist/middleware/require-actor.d.ts.map +1 -1
- package/dist/middleware/require-actor.js +18 -6
- package/package.json +9 -9
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAQhC,OAAO,EAAE,KAAK,gBAAgB,EAA0C,MAAM,kBAAkB,CAAA;AA+BhG,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAY,eAAe,EAAE,MAAM,YAAY,CAAA;AAI5F,4EAA4E;AAC5E,KAAK,QAAQ,CAAC,SAAS,SAAS,cAAc,IAAI;IAChD,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAA;AAuBD;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IAEd,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;CAC/D;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB,CAAC,SAAS,GAAG,OAAO;IACtD;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C;;;;;;;;OAQG;IACH,QAAQ,EAAE,OAAO,qBAAqB,EAAE,QAAQ,CAAA;IAChD;;;;;OAKG;IACH,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB;;;;;;OAMG;IACH,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,SAAS,SAAS,cAAc,EACvD,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,GAAG,mBAAmB,CAAC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAQhC,OAAO,EAAE,KAAK,gBAAgB,EAA0C,MAAM,kBAAkB,CAAA;AA+BhG,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAY,eAAe,EAAE,MAAM,YAAY,CAAA;AAI5F,4EAA4E;AAC5E,KAAK,QAAQ,CAAC,SAAS,SAAS,cAAc,IAAI;IAChD,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAA;AAuBD;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IAEd,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;CAC/D;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB,CAAC,SAAS,GAAG,OAAO;IACtD;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C;;;;;;;;OAQG;IACH,QAAQ,EAAE,OAAO,qBAAqB,EAAE,QAAQ,CAAA;IAChD;;;;;OAKG;IACH,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB;;;;;;OAMG;IACH,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,SAAS,SAAS,cAAc,EACvD,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,GAAG,mBAAmB,CAAC,SAAS,CAAC,CA4gB5D"}
|
package/dist/app.js
CHANGED
|
@@ -417,6 +417,21 @@ export function mountApp(config) {
|
|
|
417
417
|
// uniform across eager + lazy. Webhook routes are intentionally excluded — the
|
|
418
418
|
// per-module docs cover the admin/storefront surfaces only.
|
|
419
419
|
const moduleMounts = [];
|
|
420
|
+
const deferredLazyRouteMounts = [];
|
|
421
|
+
function deferLazyRoutesAt(moduleName, prefix, load) {
|
|
422
|
+
lazyMounts.push({ prefix, load });
|
|
423
|
+
moduleMounts.push({ moduleName, prefix, load });
|
|
424
|
+
deferredLazyRouteMounts.push(() => {
|
|
425
|
+
mountLazyRoutesAt(app, prefix, load);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function deferLazyRoutePaths(moduleName, paths, load) {
|
|
429
|
+
lazyMounts.push({ prefix: "/", load });
|
|
430
|
+
moduleMounts.push({ moduleName, prefix: "/", load });
|
|
431
|
+
deferredLazyRouteMounts.push(() => {
|
|
432
|
+
mountLazyRoutePaths(app, paths, load);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
420
435
|
// Mount module routes
|
|
421
436
|
for (const mod of allModules) {
|
|
422
437
|
const moduleName = mod.module.name;
|
|
@@ -433,19 +448,13 @@ export function mountApp(config) {
|
|
|
433
448
|
moduleMounts.push({ moduleName, prefix: publicPrefix, load: () => publicRoutes });
|
|
434
449
|
}
|
|
435
450
|
if (mod.lazyAdminRoutes) {
|
|
436
|
-
|
|
437
|
-
lazyMounts.push({ prefix: adminPrefix, load: mod.lazyAdminRoutes });
|
|
438
|
-
moduleMounts.push({ moduleName, prefix: adminPrefix, load: mod.lazyAdminRoutes });
|
|
451
|
+
deferLazyRoutesAt(moduleName, adminPrefix, mod.lazyAdminRoutes);
|
|
439
452
|
}
|
|
440
453
|
if (mod.lazyPublicRoutes) {
|
|
441
|
-
|
|
442
|
-
lazyMounts.push({ prefix: publicPrefix, load: mod.lazyPublicRoutes });
|
|
443
|
-
moduleMounts.push({ moduleName, prefix: publicPrefix, load: mod.lazyPublicRoutes });
|
|
454
|
+
deferLazyRoutesAt(moduleName, publicPrefix, mod.lazyPublicRoutes);
|
|
444
455
|
}
|
|
445
456
|
if (mod.lazyRoutes) {
|
|
446
|
-
|
|
447
|
-
lazyMounts.push({ prefix: "/", load: mod.lazyRoutes.load });
|
|
448
|
-
moduleMounts.push({ moduleName, prefix: "/", load: mod.lazyRoutes.load });
|
|
457
|
+
deferLazyRoutePaths(moduleName, mod.lazyRoutes.paths, mod.lazyRoutes.load);
|
|
449
458
|
}
|
|
450
459
|
if (mod.webhookRoutes) {
|
|
451
460
|
app.route(`/v1/${moduleName}`, mod.webhookRoutes);
|
|
@@ -467,24 +476,21 @@ export function mountApp(config) {
|
|
|
467
476
|
moduleMounts.push({ moduleName, prefix: publicPrefix, load: () => publicRoutes });
|
|
468
477
|
}
|
|
469
478
|
if (ext.lazyAdminRoutes) {
|
|
470
|
-
|
|
471
|
-
lazyMounts.push({ prefix: adminPrefix, load: ext.lazyAdminRoutes });
|
|
472
|
-
moduleMounts.push({ moduleName, prefix: adminPrefix, load: ext.lazyAdminRoutes });
|
|
479
|
+
deferLazyRoutesAt(moduleName, adminPrefix, ext.lazyAdminRoutes);
|
|
473
480
|
}
|
|
474
481
|
if (ext.lazyPublicRoutes) {
|
|
475
|
-
|
|
476
|
-
lazyMounts.push({ prefix: publicPrefix, load: ext.lazyPublicRoutes });
|
|
477
|
-
moduleMounts.push({ moduleName, prefix: publicPrefix, load: ext.lazyPublicRoutes });
|
|
482
|
+
deferLazyRoutesAt(moduleName, publicPrefix, ext.lazyPublicRoutes);
|
|
478
483
|
}
|
|
479
484
|
if (ext.lazyRoutes) {
|
|
480
|
-
|
|
481
|
-
lazyMounts.push({ prefix: "/", load: ext.lazyRoutes.load });
|
|
482
|
-
moduleMounts.push({ moduleName, prefix: "/", load: ext.lazyRoutes.load });
|
|
485
|
+
deferLazyRoutePaths(moduleName, ext.lazyRoutes.paths, ext.lazyRoutes.load);
|
|
483
486
|
}
|
|
484
487
|
if (ext.webhookRoutes) {
|
|
485
488
|
app.route(`/v1/${moduleName}`, ext.webhookRoutes);
|
|
486
489
|
}
|
|
487
490
|
}
|
|
491
|
+
for (const mountLazyRoutes of deferredLazyRouteMounts) {
|
|
492
|
+
mountLazyRoutes();
|
|
493
|
+
}
|
|
488
494
|
// Additional routes
|
|
489
495
|
if (config.additionalRoutes) {
|
|
490
496
|
config.additionalRoutes(app);
|
package/dist/lazy-routes.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export interface LazyHonoRoutes {
|
|
|
49
49
|
* relative-route loaders, or `"/"` for loaders that already return absolute
|
|
50
50
|
* routes.
|
|
51
51
|
*/
|
|
52
|
-
export declare function createLazyRouteHandler(mountPrefix: string, load: LazyRoutesLoader): (c: Context) => Promise<Response>;
|
|
52
|
+
export declare function createLazyRouteHandler(mountPrefix: string, load: LazyRoutesLoader): (c: Context, next?: () => Promise<void>) => Promise<Response>;
|
|
53
53
|
/**
|
|
54
54
|
* Register a single lazy surface on `app` at `prefix` (loader returns RELATIVE
|
|
55
55
|
* routes). Matches both the prefix root (`POST /v1/admin/foo`) and any sub-path
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-routes.d.ts","sourceRoot":"","sources":["../src/lazy-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,MAAM,CAAA;AAMrD,KAAK,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;AAE5B,oEAAoE;AACpE,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;AAErD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,IAAI,EAAE,gBAAgB,CAAA;CACvB;
|
|
1
|
+
{"version":3,"file":"lazy-routes.d.ts","sourceRoot":"","sources":["../src/lazy-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,MAAM,CAAA;AAMrD,KAAK,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;AAE5B,oEAAoE;AACpE,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;AAErD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAgBD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,IA6ClE,GAAG,OAAO,EAAE,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,QAAQ,CAAC,CAezE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAI5F;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,IAAI,EAAE,gBAAgB,GACrB,IAAI,CAKN"}
|
package/dist/lazy-routes.js
CHANGED
|
@@ -2,6 +2,16 @@ import { Hono } from "hono";
|
|
|
2
2
|
import { tryGetExecutionCtx } from "./lib/execution-ctx.js";
|
|
3
3
|
/** Private carrier key for snapshotting `c.var` across the forward. */
|
|
4
4
|
const LAZY_CONTEXT_CARRIER = Symbol.for("voyant.hono.lazyContextCarrier");
|
|
5
|
+
const LAZY_ROUTE_MISS_HEADER = "x-voyant-lazy-route-miss";
|
|
6
|
+
function withoutLazyRouteMissHeader(response) {
|
|
7
|
+
const headers = new Headers(response.headers);
|
|
8
|
+
headers.delete(LAZY_ROUTE_MISS_HEADER);
|
|
9
|
+
return new Response(response.body, {
|
|
10
|
+
status: response.status,
|
|
11
|
+
statusText: response.statusText,
|
|
12
|
+
headers,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
5
15
|
/**
|
|
6
16
|
* Build a cached, context-preserving request handler. `mountPrefix` is where the
|
|
7
17
|
* loaded routes are re-mounted in the wrapper sub-app so the forwarded absolute
|
|
@@ -27,6 +37,7 @@ export function createLazyRouteHandler(mountPrefix, load) {
|
|
|
27
37
|
wrapped.onError((err) => {
|
|
28
38
|
throw err;
|
|
29
39
|
});
|
|
40
|
+
wrapped.notFound(() => new Response(null, { status: 404, headers: { [LAZY_ROUTE_MISS_HEADER]: "1" } }));
|
|
30
41
|
wrapped.use("*", async (cc, next) => {
|
|
31
42
|
const carried = cc.env?.[LAZY_CONTEXT_CARRIER];
|
|
32
43
|
if (carried) {
|
|
@@ -47,12 +58,20 @@ export function createLazyRouteHandler(mountPrefix, load) {
|
|
|
47
58
|
}
|
|
48
59
|
return cached;
|
|
49
60
|
}
|
|
50
|
-
return async (c) => {
|
|
61
|
+
return async (c, next) => {
|
|
51
62
|
const app = await getApp();
|
|
52
63
|
const snapshot = { ...c.var };
|
|
53
64
|
const env = { ...c.env, [LAZY_CONTEXT_CARRIER]: snapshot };
|
|
54
65
|
// biome-ignore lint/suspicious/noExplicitAny: forward the host execution context when present -- owner: hono.
|
|
55
|
-
|
|
66
|
+
const response = await app.fetch(c.req.raw, env, tryGetExecutionCtx(c));
|
|
67
|
+
const routeMiss = response.headers.get(LAZY_ROUTE_MISS_HEADER) === "1";
|
|
68
|
+
if (routeMiss && next) {
|
|
69
|
+
// Match eager `app.route(...)` composition: overlapping lazy mounts under
|
|
70
|
+
// the same prefix must let later modules/extensions try the request.
|
|
71
|
+
await next();
|
|
72
|
+
return c.res;
|
|
73
|
+
}
|
|
74
|
+
return routeMiss ? withoutLazyRouteMissHeader(response) : response;
|
|
56
75
|
};
|
|
57
76
|
}
|
|
58
77
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAM7C,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAM7C,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAA;AAgIpB,wBAAgB,WAAW,CAAC,SAAS,SAAS,cAAc,EAC1D,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,EAC7B,IAAI,CAAC,EAAE;IACL,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAA;CACxC,GACA,iBAAiB,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAC,CA4LD"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apikeyTable } from "@voyant-travel/db/schema/iam";
|
|
2
|
-
import { permissionsToStrings } from "@voyant-travel/types/api-keys";
|
|
2
|
+
import { API_KEY_AUDIENCES, permissionsToStrings } from "@voyant-travel/types/api-keys";
|
|
3
3
|
import { and, eq, sql } from "drizzle-orm";
|
|
4
4
|
import { constantTimeEqual, sha256Base64Url, sha256Hex } from "../auth/crypto.js";
|
|
5
5
|
import { extractBearerToken, verifySession } from "../auth/session-jwt.js";
|
|
@@ -91,6 +91,26 @@ async function writeCachedApiKey(kv, keyHash, row) {
|
|
|
91
91
|
// cache writes are best-effort
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
const API_KEY_AUDIENCE_SET = new Set(API_KEY_AUDIENCES);
|
|
95
|
+
/**
|
|
96
|
+
* Extract the grant audience from an API key's `metadata` JSON. Malformed or
|
|
97
|
+
* absent metadata falls back to `"staff"` (legacy server-to-server default).
|
|
98
|
+
*/
|
|
99
|
+
function resolveApiKeyAudience(metadata) {
|
|
100
|
+
if (!metadata)
|
|
101
|
+
return "staff";
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(metadata);
|
|
104
|
+
const value = parsed?.audience;
|
|
105
|
+
if (typeof value === "string" && API_KEY_AUDIENCE_SET.has(value)) {
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Ignore malformed metadata — fall back to the staff default.
|
|
111
|
+
}
|
|
112
|
+
return "staff";
|
|
113
|
+
}
|
|
94
114
|
function applyAuthContext(c, auth) {
|
|
95
115
|
if (auth.userId)
|
|
96
116
|
c.set("userId", auth.userId);
|
|
@@ -102,6 +122,8 @@ function applyAuthContext(c, auth) {
|
|
|
102
122
|
c.set("callerType", auth.callerType);
|
|
103
123
|
if (auth.actor)
|
|
104
124
|
c.set("actor", auth.actor);
|
|
125
|
+
if (auth.audience)
|
|
126
|
+
c.set("audience", auth.audience);
|
|
105
127
|
if (auth.scopes !== undefined)
|
|
106
128
|
c.set("scopes", auth.scopes);
|
|
107
129
|
if (auth.isInternalRequest !== undefined)
|
|
@@ -209,16 +231,20 @@ export function requireAuth(dbSource, opts) {
|
|
|
209
231
|
.catch(() => { });
|
|
210
232
|
tryGetExecutionCtx(c)?.waitUntil(counterUpdate);
|
|
211
233
|
const scopes = permissionsToStrings(row.permissions);
|
|
234
|
+
// Audience is a grant attribute carried on the key's `metadata`, not
|
|
235
|
+
// inferred from scopes (D3). Legacy keys with no audience default to
|
|
236
|
+
// `staff`, preserving prior server-to-server behaviour. The actor
|
|
237
|
+
// follows the audience so a non-staff key resolves to its own
|
|
238
|
+
// visibility pool instead of silently gaining operator privileges.
|
|
239
|
+
const audience = resolveApiKeyAudience(row.metadata);
|
|
212
240
|
applyAuthContext(c, {
|
|
213
241
|
organizationId: row.referenceId,
|
|
214
242
|
scopes,
|
|
215
243
|
callerType: "api_key",
|
|
216
244
|
apiTokenId: row.id,
|
|
217
245
|
apiKeyId: row.id,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// `requireActor` doesn't have to default unset callers to "staff".
|
|
221
|
-
actor: "staff",
|
|
246
|
+
actor: audience,
|
|
247
|
+
audience,
|
|
222
248
|
});
|
|
223
249
|
// `await` is load-bearing: with a bare `return next()` the
|
|
224
250
|
// `finally` would run as soon as the downstream promise is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"require-actor.d.ts","sourceRoot":"","sources":["../../src/middleware/require-actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAG7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"require-actor.d.ts","sourceRoot":"","sources":["../../src/middleware/require-actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAG7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AA0ElE;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAIzD;AAED,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,SAAS,SAAS,cAAc,GAAG,cAAc,EAC5E,GAAG,OAAO,EAAE,KAAK,EAAE,GAClB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAC,CAAA;AACF,wBAAgB,YAAY,CAAC,SAAS,SAAS,cAAc,GAAG,cAAc,EAC5E,OAAO,EAAE,mBAAmB,EAC5B,GAAG,OAAO,EAAE,KAAK,EAAE,GAClB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAC,CAAA"}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { hasApiKeyPermission, permissionStringsToPermissions } from "@voyant-travel/types/api-keys";
|
|
2
2
|
import { normalizePathname } from "../lib/public-paths.js";
|
|
3
|
+
/**
|
|
4
|
+
* Route surfaces exempt from the coarse method+path permission guard because
|
|
5
|
+
* they enforce authorization at a finer grain internally: `_meta` (capability
|
|
6
|
+
* discovery) and `mcp` (the agent tool server, which gates every tool by its own
|
|
7
|
+
* `requiredScopes`). Neither is a real module name.
|
|
8
|
+
*/
|
|
9
|
+
const COARSE_GUARD_EXEMPT_RESOURCES = new Set(["_meta", "mcp"]);
|
|
10
|
+
function isCoarseGuardExempt(resource) {
|
|
11
|
+
return resource !== null && COARSE_GUARD_EXEMPT_RESOURCES.has(resource);
|
|
12
|
+
}
|
|
3
13
|
function apiKeyResourceFromPath(pathname) {
|
|
4
14
|
const surfaceMatch = pathname.match(/^\/v1\/(?:admin|public)\/([^/]+)/);
|
|
5
15
|
if (surfaceMatch?.[1])
|
|
@@ -84,11 +94,13 @@ export function requireActor(...args) {
|
|
|
84
94
|
basePath: options.basePath,
|
|
85
95
|
});
|
|
86
96
|
const resource = apiKeyResourceFromPath(pathname);
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
|
|
97
|
+
// Coarse-guard-exempt surfaces. `_meta` (capability discovery) and `mcp`
|
|
98
|
+
// (the agent tool server) are dispatch surfaces where authorization is
|
|
99
|
+
// enforced at a finer grain than method+path: the MCP server filters and
|
|
100
|
+
// gates each tool by its own `requiredScopes` (voyant#2792, D2). So any
|
|
101
|
+
// authenticated key reaches them; a key with no relevant scopes simply
|
|
102
|
+
// sees an empty tool list. Neither is a real module name.
|
|
103
|
+
if (isCoarseGuardExempt(resource))
|
|
92
104
|
return next();
|
|
93
105
|
const actions = apiKeyPermissionActionsForMethod(c.req.method, pathname);
|
|
94
106
|
if (resource && hasAnyApiKeyPermission(c.get("scopes"), resource, actions)) {
|
|
@@ -119,7 +131,7 @@ export function requireActor(...args) {
|
|
|
119
131
|
basePath: options.basePath,
|
|
120
132
|
});
|
|
121
133
|
const resource = apiKeyResourceFromPath(pathname);
|
|
122
|
-
if (resource && resource
|
|
134
|
+
if (resource && !isCoarseGuardExempt(resource)) {
|
|
123
135
|
const actions = apiKeyPermissionActionsForMethod(c.req.method, pathname);
|
|
124
136
|
if (!hasAnyApiKeyPermission(scopes, resource, actions)) {
|
|
125
137
|
return c.json({ error: "Forbidden: missing required permission" }, 403);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.121.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -133,21 +133,21 @@
|
|
|
133
133
|
"dependencies": {
|
|
134
134
|
"@hono/zod-openapi": "^1.4.0",
|
|
135
135
|
"drizzle-orm": "^0.45.2",
|
|
136
|
-
"hono": "^4.12.
|
|
136
|
+
"hono": "^4.12.27",
|
|
137
137
|
"zod": "^4.4.3",
|
|
138
|
-
"@voyant-travel/core": "^0.
|
|
139
|
-
"@voyant-travel/
|
|
138
|
+
"@voyant-travel/core": "^0.112.0",
|
|
139
|
+
"@voyant-travel/db": "^0.109.5",
|
|
140
140
|
"@voyant-travel/storage": "^0.106.0",
|
|
141
|
-
"@voyant-travel/
|
|
142
|
-
"@voyant-travel/utils": "^0.105.
|
|
143
|
-
"@voyant-travel/workflows": "^0.111.
|
|
141
|
+
"@voyant-travel/types": "^0.107.0",
|
|
142
|
+
"@voyant-travel/utils": "^0.105.6",
|
|
143
|
+
"@voyant-travel/workflows": "^0.111.15"
|
|
144
144
|
},
|
|
145
145
|
"devDependencies": {
|
|
146
|
-
"@cloudflare/workers-types": "^4.
|
|
146
|
+
"@cloudflare/workers-types": "^4.20260702.1",
|
|
147
147
|
"typescript": "^6.0.3",
|
|
148
148
|
"vitest": "^4.1.9",
|
|
149
149
|
"@voyant-travel/voyant-typescript-config": "^0.1.0",
|
|
150
|
-
"@voyant-travel/workflows-orchestrator": "^0.111.
|
|
150
|
+
"@voyant-travel/workflows-orchestrator": "^0.111.15"
|
|
151
151
|
},
|
|
152
152
|
"files": [
|
|
153
153
|
"dist"
|