@voyant-travel/hono 0.109.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/LICENSE +201 -0
- package/README.md +58 -0
- package/dist/app-workflows.d.ts +31 -0
- package/dist/app-workflows.d.ts.map +1 -0
- package/dist/app-workflows.js +110 -0
- package/dist/app.d.ts +45 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +403 -0
- package/dist/auth/crypto.d.ts +16 -0
- package/dist/auth/crypto.d.ts.map +1 -0
- package/dist/auth/crypto.js +66 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth/require-user.d.ts +3 -0
- package/dist/auth/require-user.d.ts.map +1 -0
- package/dist/auth/require-user.js +8 -0
- package/dist/auth/session-jwt.d.ts +7 -0
- package/dist/auth/session-jwt.d.ts.map +1 -0
- package/dist/auth/session-jwt.js +23 -0
- package/dist/composition.d.ts +67 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +46 -0
- package/dist/document-download.d.ts +30 -0
- package/dist/document-download.d.ts.map +1 -0
- package/dist/document-download.js +102 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/lib/db-selector.d.ts +24 -0
- package/dist/lib/db-selector.d.ts.map +1 -0
- package/dist/lib/db-selector.js +28 -0
- package/dist/lib/execution-ctx.d.ts +16 -0
- package/dist/lib/execution-ctx.d.ts.map +1 -0
- package/dist/lib/execution-ctx.js +16 -0
- package/dist/lib/public-paths.d.ts +19 -0
- package/dist/lib/public-paths.d.ts.map +1 -0
- package/dist/lib/public-paths.js +27 -0
- package/dist/lib/request-event-bus.d.ts +21 -0
- package/dist/lib/request-event-bus.d.ts.map +1 -0
- package/dist/lib/request-event-bus.js +43 -0
- package/dist/middleware/auth.d.ts +10 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +280 -0
- package/dist/middleware/body-size.d.ts +7 -0
- package/dist/middleware/body-size.d.ts.map +1 -0
- package/dist/middleware/body-size.js +20 -0
- package/dist/middleware/cors.d.ts +6 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +94 -0
- package/dist/middleware/db.d.ts +43 -0
- package/dist/middleware/db.d.ts.map +1 -0
- package/dist/middleware/db.js +78 -0
- package/dist/middleware/error-boundary.d.ts +5 -0
- package/dist/middleware/error-boundary.d.ts.map +1 -0
- package/dist/middleware/error-boundary.js +76 -0
- package/dist/middleware/idempotency-key.d.ts +97 -0
- package/dist/middleware/idempotency-key.d.ts.map +1 -0
- package/dist/middleware/idempotency-key.js +235 -0
- package/dist/middleware/index.d.ts +14 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +13 -0
- package/dist/middleware/logger.d.ts +5 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +27 -0
- package/dist/middleware/metrics.d.ts +55 -0
- package/dist/middleware/metrics.d.ts.map +1 -0
- package/dist/middleware/metrics.js +94 -0
- package/dist/middleware/public-cache.d.ts +44 -0
- package/dist/middleware/public-cache.d.ts.map +1 -0
- package/dist/middleware/public-cache.js +205 -0
- package/dist/middleware/rate-limit.d.ts +214 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +240 -0
- package/dist/middleware/request-db.d.ts +42 -0
- package/dist/middleware/request-db.d.ts.map +1 -0
- package/dist/middleware/request-db.js +62 -0
- package/dist/middleware/require-actor.d.ts +28 -0
- package/dist/middleware/require-actor.d.ts.map +1 -0
- package/dist/middleware/require-actor.js +89 -0
- package/dist/middleware/require-permission.d.ts +9 -0
- package/dist/middleware/require-permission.d.ts.map +1 -0
- package/dist/middleware/require-permission.js +62 -0
- package/dist/middleware/security-headers.d.ts +10 -0
- package/dist/middleware/security-headers.d.ts.map +1 -0
- package/dist/middleware/security-headers.js +19 -0
- package/dist/module.d.ts +41 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +1 -0
- package/dist/plugin.d.ts +66 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +37 -0
- package/dist/public-capability.d.ts +46 -0
- package/dist/public-capability.d.ts.map +1 -0
- package/dist/public-capability.js +140 -0
- package/dist/public-document-delivery.d.ts +111 -0
- package/dist/public-document-delivery.d.ts.map +1 -0
- package/dist/public-document-delivery.js +234 -0
- package/dist/types.d.ts +318 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/validation.d.ts +36 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +106 -0
- package/package.json +156 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type DbFactory, type VoyantBindings, type VoyantDb } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural slice of a Hono `Context` that the request-db
|
|
4
|
+
* helpers need. Typed structurally so unit tests can pass a stub and so
|
|
5
|
+
* this module doesn't constrain the app's `Variables` generic.
|
|
6
|
+
*/
|
|
7
|
+
export interface RequestDbContextLike<TBindings extends VoyantBindings = VoyantBindings> {
|
|
8
|
+
req: {
|
|
9
|
+
raw: Request;
|
|
10
|
+
};
|
|
11
|
+
env: TBindings;
|
|
12
|
+
/**
|
|
13
|
+
* Hono throws on `executionCtx` access in runtimes without one (Node
|
|
14
|
+
* tests, some adapters) — callers of {@link acquireRequestDb} never
|
|
15
|
+
* touch it directly; we read it defensively inside `release`.
|
|
16
|
+
*/
|
|
17
|
+
executionCtx?: unknown;
|
|
18
|
+
}
|
|
19
|
+
export interface RequestDbLease {
|
|
20
|
+
db: VoyantDb;
|
|
21
|
+
/**
|
|
22
|
+
* Whether this lease created the underlying client. Only the creator's
|
|
23
|
+
* `release()` disposes; reuse leases are no-ops, so the client stays
|
|
24
|
+
* alive until the outermost (creating) middleware's `finally` runs —
|
|
25
|
+
* which is after the entire downstream pipeline has completed.
|
|
26
|
+
*/
|
|
27
|
+
isCreator: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Settle the lease. For the creating lease this schedules `dispose()`
|
|
30
|
+
* via `executionCtx.waitUntil` (or awaits inline outside Workers).
|
|
31
|
+
* Idempotent; reuse leases resolve immediately.
|
|
32
|
+
*/
|
|
33
|
+
release: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the shared per-request db client for `factory`, creating it on
|
|
37
|
+
* first acquisition. The creating caller MUST call `lease.release()` in
|
|
38
|
+
* a `finally` after its `next()` completes; later acquirers within the
|
|
39
|
+
* same request reuse the client and their `release()` is a no-op.
|
|
40
|
+
*/
|
|
41
|
+
export declare function acquireRequestDb<TBindings extends VoyantBindings>(c: RequestDbContextLike<TBindings>, factory: DbFactory<TBindings>): RequestDbLease;
|
|
42
|
+
//# sourceMappingURL=request-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-db.d.ts","sourceRoot":"","sources":["../../src/middleware/request-db.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,cAAc,EACnB,KAAK,QAAQ,EACd,MAAM,aAAa,CAAA;AAOpB;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CAAC,SAAS,SAAS,cAAc,GAAG,cAAc;IACrF,GAAG,EAAE;QAAE,GAAG,EAAE,OAAO,CAAA;KAAE,CAAA;IACrB,GAAG,EAAE,SAAS,CAAA;IACd;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,QAAQ,CAAA;IACZ;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AA4BD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,cAAc,EAC/D,CAAC,EAAE,oBAAoB,CAAC,SAAS,CAAC,EAClC,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,GAC5B,cAAc,CAkChB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { resolveDbFactoryResult, } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Per-request db holders, keyed by the raw `Request` then by factory
|
|
4
|
+
* identity. One `createApp` instance passes the same `config.db` factory
|
|
5
|
+
* to the auth, permission, and db middlewares — so all of them resolve
|
|
6
|
+
* the same holder and the request opens a single client instead of one
|
|
7
|
+
* per middleware (previously 2–3 Neon WebSocket pools per authenticated
|
|
8
|
+
* request). The WeakMap keeps holders from outliving their request.
|
|
9
|
+
*/
|
|
10
|
+
const requestDbHolders = new WeakMap();
|
|
11
|
+
function readExecutionCtx(c) {
|
|
12
|
+
try {
|
|
13
|
+
const ctx = c.executionCtx;
|
|
14
|
+
if (ctx && typeof ctx.waitUntil === "function")
|
|
15
|
+
return ctx;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Hono throws when the adapter provides no ExecutionContext.
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the shared per-request db client for `factory`, creating it on
|
|
24
|
+
* first acquisition. The creating caller MUST call `lease.release()` in
|
|
25
|
+
* a `finally` after its `next()` completes; later acquirers within the
|
|
26
|
+
* same request reuse the client and their `release()` is a no-op.
|
|
27
|
+
*/
|
|
28
|
+
export function acquireRequestDb(c, factory) {
|
|
29
|
+
let byFactory = requestDbHolders.get(c.req.raw);
|
|
30
|
+
if (!byFactory) {
|
|
31
|
+
byFactory = new Map();
|
|
32
|
+
requestDbHolders.set(c.req.raw, byFactory);
|
|
33
|
+
}
|
|
34
|
+
const existing = byFactory.get(factory);
|
|
35
|
+
if (existing && !existing.disposed) {
|
|
36
|
+
return { db: existing.db, isCreator: false, release: async () => { } };
|
|
37
|
+
}
|
|
38
|
+
const { db, dispose } = resolveDbFactoryResult(factory(c.env));
|
|
39
|
+
const holder = { db, dispose, disposed: false };
|
|
40
|
+
byFactory.set(factory, holder);
|
|
41
|
+
return {
|
|
42
|
+
db,
|
|
43
|
+
isCreator: true,
|
|
44
|
+
release: async () => {
|
|
45
|
+
if (holder.disposed)
|
|
46
|
+
return;
|
|
47
|
+
holder.disposed = true;
|
|
48
|
+
if (!holder.dispose)
|
|
49
|
+
return;
|
|
50
|
+
// `waitUntil` keeps the Workers isolate alive for the close
|
|
51
|
+
// handshake without delaying the response; outside Workers we
|
|
52
|
+
// await inline so tests and Node deployments clean up too.
|
|
53
|
+
const ctx = readExecutionCtx(c);
|
|
54
|
+
if (ctx) {
|
|
55
|
+
ctx.waitUntil(holder.dispose());
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
await holder.dispose();
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Actor } from "@voyant-travel/core";
|
|
2
|
+
import type { MiddlewareHandler } from "hono";
|
|
3
|
+
import type { VoyantBindings, VoyantVariables } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Guards a route surface by actor type.
|
|
6
|
+
*
|
|
7
|
+
* Voyant exposes two API surfaces:
|
|
8
|
+
* - `/v1/admin/*` — operator staff (`"staff"`)
|
|
9
|
+
* - `/v1/public/*` — customers, partners, suppliers
|
|
10
|
+
*
|
|
11
|
+
* Requests carry an `actor` on `c.var`, typically set by `requireAuth` or a
|
|
12
|
+
* custom `auth.resolve` integration.
|
|
13
|
+
*
|
|
14
|
+
* When the caller has no resolved actor, this middleware returns `401
|
|
15
|
+
* Unauthorized`. Earlier versions defaulted unset callers to `"staff"` for
|
|
16
|
+
* backwards compatibility, but that meant a misordered or missing auth
|
|
17
|
+
* middleware silently granted operator privileges to anonymous traffic.
|
|
18
|
+
* The default is now fail-closed.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* app.use("/v1/admin/*", requireActor("staff"))
|
|
22
|
+
* app.use("/v1/public/*", requireActor("customer", "partner", "supplier"))
|
|
23
|
+
*/
|
|
24
|
+
export declare function requireActor<TBindings extends VoyantBindings = VoyantBindings>(...allowed: Actor[]): MiddlewareHandler<{
|
|
25
|
+
Bindings: TBindings;
|
|
26
|
+
Variables: VoyantVariables;
|
|
27
|
+
}>;
|
|
28
|
+
//# sourceMappingURL=require-actor.d.ts.map
|
|
@@ -0,0 +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;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAuClE;;;;;;;;;;;;;;;;;;;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,CA6CD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { hasApiKeyPermission, permissionStringsToPermissions } from "@voyant-travel/types/api-keys";
|
|
2
|
+
function apiKeyResourceFromPath(pathname) {
|
|
3
|
+
const surfaceMatch = pathname.match(/^\/v1\/(?:admin|public)\/([^/]+)/);
|
|
4
|
+
if (surfaceMatch?.[1])
|
|
5
|
+
return surfaceMatch[1];
|
|
6
|
+
const legacyMatch = pathname.match(/^\/v1\/([^/]+)/);
|
|
7
|
+
if (legacyMatch?.[1] && legacyMatch[1] !== "admin" && legacyMatch[1] !== "public") {
|
|
8
|
+
return legacyMatch[1];
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function apiKeyPermissionActionsForMethod(method) {
|
|
13
|
+
switch (method.toUpperCase()) {
|
|
14
|
+
case "GET":
|
|
15
|
+
case "HEAD":
|
|
16
|
+
return ["read", "search"];
|
|
17
|
+
case "POST":
|
|
18
|
+
return ["write", "trigger", "relay"];
|
|
19
|
+
case "PUT":
|
|
20
|
+
case "PATCH":
|
|
21
|
+
return ["write"];
|
|
22
|
+
case "DELETE":
|
|
23
|
+
return ["delete"];
|
|
24
|
+
default:
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function hasAnyApiKeyPermission(scopes, resource, actions) {
|
|
29
|
+
if (!scopes || scopes.length === 0)
|
|
30
|
+
return false;
|
|
31
|
+
const permissions = permissionStringsToPermissions(scopes);
|
|
32
|
+
return actions.some((action) => hasApiKeyPermission(permissions, resource, action));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Guards a route surface by actor type.
|
|
36
|
+
*
|
|
37
|
+
* Voyant exposes two API surfaces:
|
|
38
|
+
* - `/v1/admin/*` — operator staff (`"staff"`)
|
|
39
|
+
* - `/v1/public/*` — customers, partners, suppliers
|
|
40
|
+
*
|
|
41
|
+
* Requests carry an `actor` on `c.var`, typically set by `requireAuth` or a
|
|
42
|
+
* custom `auth.resolve` integration.
|
|
43
|
+
*
|
|
44
|
+
* When the caller has no resolved actor, this middleware returns `401
|
|
45
|
+
* Unauthorized`. Earlier versions defaulted unset callers to `"staff"` for
|
|
46
|
+
* backwards compatibility, but that meant a misordered or missing auth
|
|
47
|
+
* middleware silently granted operator privileges to anonymous traffic.
|
|
48
|
+
* The default is now fail-closed.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* app.use("/v1/admin/*", requireActor("staff"))
|
|
52
|
+
* app.use("/v1/public/*", requireActor("customer", "partner", "supplier"))
|
|
53
|
+
*/
|
|
54
|
+
export function requireActor(...allowed) {
|
|
55
|
+
if (allowed.length === 0) {
|
|
56
|
+
throw new Error("requireActor: must specify at least one allowed actor");
|
|
57
|
+
}
|
|
58
|
+
const allowSet = new Set(allowed);
|
|
59
|
+
return async (c, next) => {
|
|
60
|
+
if (c.req.method === "OPTIONS")
|
|
61
|
+
return next();
|
|
62
|
+
if (c.get("callerType") === "api_key" || c.get("callerType") === "internal") {
|
|
63
|
+
const resource = apiKeyResourceFromPath(new URL(c.req.url).pathname);
|
|
64
|
+
// Meta/discovery endpoints (e.g. `/v1/admin/_meta/capabilities`) report
|
|
65
|
+
// what the caller can do — including the key's own granted scopes — so any
|
|
66
|
+
// authenticated API key may read them, regardless of module scope. `_meta`
|
|
67
|
+
// is a reserved namespace (no module is named `_meta`).
|
|
68
|
+
if (resource === "_meta")
|
|
69
|
+
return next();
|
|
70
|
+
const actions = apiKeyPermissionActionsForMethod(c.req.method);
|
|
71
|
+
if (resource && hasAnyApiKeyPermission(c.get("scopes"), resource, actions)) {
|
|
72
|
+
return next();
|
|
73
|
+
}
|
|
74
|
+
return c.json({ error: "Forbidden: API key missing required permission" }, 403);
|
|
75
|
+
}
|
|
76
|
+
const actor = c.get("actor");
|
|
77
|
+
if (!actor) {
|
|
78
|
+
return c.json({
|
|
79
|
+
error: "Unauthorized: actor not resolved. The auth pipeline did not assign an `actor` to this request. " +
|
|
80
|
+
"If you set `auth.resolve` on `createApp({...})`, the returned object must include `actor` " +
|
|
81
|
+
'(usually `"staff"` for admin sessions). Public routes should be listed in `publicPaths`.',
|
|
82
|
+
}, 401);
|
|
83
|
+
}
|
|
84
|
+
if (!allowSet.has(actor)) {
|
|
85
|
+
return c.json({ error: "Forbidden: actor not permitted on this surface" }, 403);
|
|
86
|
+
}
|
|
87
|
+
return next();
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
import { type DbSource, type VoyantAuthIntegration, type VoyantBindings, type VoyantVariables } from "../types.js";
|
|
3
|
+
export declare function requirePermission<TBindings extends VoyantBindings>(dbSource: DbSource<TBindings>, resource: string, action: string, opts?: {
|
|
4
|
+
auth?: VoyantAuthIntegration<TBindings>;
|
|
5
|
+
}): MiddlewareHandler<{
|
|
6
|
+
Bindings: TBindings;
|
|
7
|
+
Variables: VoyantVariables;
|
|
8
|
+
}>;
|
|
9
|
+
//# sourceMappingURL=require-permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-permission.d.ts","sourceRoot":"","sources":["../../src/middleware/require-permission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAI7C,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAA;AAIpB,wBAAgB,iBAAiB,CAAC,SAAS,SAAS,cAAc,EAChE,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,EAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAA;CACxC,GACA,iBAAiB,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,eAAe,CAAA;CAC3B,CAAC,CAmED"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { hasApiKeyPermission, permissionStringsToPermissions } from "@voyant-travel/types/api-keys";
|
|
2
|
+
import { requireUserId } from "../auth/require-user.js";
|
|
3
|
+
import { tryGetExecutionCtx } from "../lib/execution-ctx.js";
|
|
4
|
+
import { selectDbFactory, } from "../types.js";
|
|
5
|
+
import { ForbiddenApiError, UnauthorizedApiError } from "../validation.js";
|
|
6
|
+
import { acquireRequestDb } from "./request-db.js";
|
|
7
|
+
export function requirePermission(dbSource, resource, action, opts) {
|
|
8
|
+
return async (c, next) => {
|
|
9
|
+
const permission = { resource, action };
|
|
10
|
+
const scopes = c.get("scopes");
|
|
11
|
+
if (scopes &&
|
|
12
|
+
hasApiKeyPermission(permissionStringsToPermissions(scopes), permission.resource, permission.action)) {
|
|
13
|
+
return next();
|
|
14
|
+
}
|
|
15
|
+
const userId = requireUserId(c);
|
|
16
|
+
const actor = c.get("actor");
|
|
17
|
+
if (!actor) {
|
|
18
|
+
// Should be unreachable in well-wired apps: `requireActor` runs before
|
|
19
|
+
// `requirePermission`. Throw rather than fabricate a default so callers
|
|
20
|
+
// see the upstream wiring bug instead of a silent privilege grant.
|
|
21
|
+
throw new UnauthorizedApiError();
|
|
22
|
+
}
|
|
23
|
+
if (!opts?.auth?.hasPermission) {
|
|
24
|
+
return c.json({ error: "No auth permission checker configured" }, 500);
|
|
25
|
+
}
|
|
26
|
+
// Reuses the per-request client created by the auth/db middleware
|
|
27
|
+
// upstream (same factory) instead of opening another Pool.
|
|
28
|
+
const lease = acquireRequestDb(c, selectDbFactory(dbSource, c.req.path));
|
|
29
|
+
try {
|
|
30
|
+
const allowed = await opts.auth.hasPermission({
|
|
31
|
+
request: c.req.raw,
|
|
32
|
+
env: c.env,
|
|
33
|
+
db: lease.db,
|
|
34
|
+
// Guarded: Hono throws on `executionCtx` access outside Workers.
|
|
35
|
+
ctx: tryGetExecutionCtx(c),
|
|
36
|
+
auth: {
|
|
37
|
+
userId,
|
|
38
|
+
actor,
|
|
39
|
+
sessionId: c.get("sessionId"),
|
|
40
|
+
organizationId: c.get("organizationId"),
|
|
41
|
+
callerType: c.get("callerType"),
|
|
42
|
+
scopes,
|
|
43
|
+
isInternalRequest: c.get("isInternalRequest"),
|
|
44
|
+
apiTokenId: c.get("apiTokenId"),
|
|
45
|
+
apiKeyId: c.get("apiKeyId"),
|
|
46
|
+
},
|
|
47
|
+
permission,
|
|
48
|
+
});
|
|
49
|
+
if (!allowed) {
|
|
50
|
+
throw new ForbiddenApiError();
|
|
51
|
+
}
|
|
52
|
+
// `await` is load-bearing: a bare `return next()` would run the
|
|
53
|
+
// `finally` (and release the shared client) as soon as the
|
|
54
|
+
// downstream promise is created, while the route is still
|
|
55
|
+
// querying it.
|
|
56
|
+
return await next();
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await lease.release();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
import type { VoyantBindings } from "../types.js";
|
|
3
|
+
export interface SecurityHeadersOptions {
|
|
4
|
+
contentSecurityPolicy?: string | false;
|
|
5
|
+
hsts?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function securityHeaders(options?: SecurityHeadersOptions): MiddlewareHandler<{
|
|
8
|
+
Bindings: VoyantBindings;
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=security-headers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/middleware/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAOjD,MAAM,WAAW,sBAAsB;IACrC,qBAAqB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACtC,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,wBAAgB,eAAe,CAC7B,OAAO,GAAE,sBAA2B,GACnC,iBAAiB,CAAC;IAAE,QAAQ,EAAE,cAAc,CAAA;CAAE,CAAC,CAiBjD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const DEFAULT_CSP = "default-src 'self'; base-uri 'self'; frame-ancestors 'none'; object-src 'none'; " +
|
|
2
|
+
"img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self'; " +
|
|
3
|
+
"connect-src 'self'";
|
|
4
|
+
export function securityHeaders(options = {}) {
|
|
5
|
+
const csp = options.contentSecurityPolicy === undefined ? DEFAULT_CSP : options.contentSecurityPolicy;
|
|
6
|
+
const hsts = options.hsts ?? true;
|
|
7
|
+
return async (c, next) => {
|
|
8
|
+
await next();
|
|
9
|
+
c.header("X-Content-Type-Options", "nosniff");
|
|
10
|
+
c.header("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
11
|
+
c.header("X-Frame-Options", "DENY");
|
|
12
|
+
c.header("Cross-Origin-Opener-Policy", "same-origin");
|
|
13
|
+
if (csp)
|
|
14
|
+
c.header("Content-Security-Policy", csp);
|
|
15
|
+
if (hsts && new URL(c.req.url).protocol === "https:") {
|
|
16
|
+
c.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Extension, Module } from "@voyant-travel/core";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
export interface HonoModule {
|
|
4
|
+
module: Module;
|
|
5
|
+
/**
|
|
6
|
+
* Legacy routes — mounted at `/v1/{module.name}`. Gated by the caller's
|
|
7
|
+
* `requireAuth` configuration. Use `adminRoutes` / `publicRoutes` for new
|
|
8
|
+
* modules that participate in the admin/public API split.
|
|
9
|
+
*
|
|
10
|
+
* @deprecated Prefer `adminRoutes` or `publicRoutes`.
|
|
11
|
+
*/
|
|
12
|
+
routes?: Hono<any>;
|
|
13
|
+
/** Staff-facing routes — mounted at `/v1/admin/{module.name}`. */
|
|
14
|
+
adminRoutes?: Hono<any>;
|
|
15
|
+
/** Customer/partner/supplier-facing routes — mounted at `/v1/public/{module.name}`. */
|
|
16
|
+
publicRoutes?: Hono<any>;
|
|
17
|
+
/**
|
|
18
|
+
* Optional override for the public mount path relative to `/v1/public`.
|
|
19
|
+
*
|
|
20
|
+
* Defaults to `{module.name}`. Use `"/"` to mount a module directly at the
|
|
21
|
+
* public root and omit the extra module segment.
|
|
22
|
+
*/
|
|
23
|
+
publicPath?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface HonoExtension {
|
|
26
|
+
extension: Extension;
|
|
27
|
+
/** @deprecated Prefer `adminRoutes` or `publicRoutes`. */
|
|
28
|
+
routes?: Hono<any>;
|
|
29
|
+
/** Staff-facing routes — mounted at `/v1/admin/{extension.module}`. */
|
|
30
|
+
adminRoutes?: Hono<any>;
|
|
31
|
+
/** Customer/partner/supplier-facing routes — mounted at `/v1/public/{extension.module}`. */
|
|
32
|
+
publicRoutes?: Hono<any>;
|
|
33
|
+
/**
|
|
34
|
+
* Optional override for the public mount path relative to `/v1/public`.
|
|
35
|
+
*
|
|
36
|
+
* Defaults to `{extension.module}`. Use `"/"` to mount an extension directly
|
|
37
|
+
* at the public root and omit the extra module segment.
|
|
38
|
+
*/
|
|
39
|
+
publicPath?: string;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IAEH,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IAClB,kEAAkE;IAElE,WAAW,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACvB,uFAAuF;IAEvF,YAAY,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACxB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,SAAS,CAAA;IACpB,0DAA0D;IAE1D,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IAClB,uEAAuE;IAEvE,WAAW,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACvB,4FAA4F;IAE5F,YAAY,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACxB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB"}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { BootstrapHandler, EventFilterDescriptor, LinkDefinition, Subscriber, WorkflowDescriptor } from "@voyant-travel/core";
|
|
2
|
+
import type { HonoExtension, HonoModule } from "./module.js";
|
|
3
|
+
/**
|
|
4
|
+
* Hono-flavoured bundle contribution surface.
|
|
5
|
+
*
|
|
6
|
+
* `@voyant-travel/hono` is the default HTTP transport adapter for Voyant. The
|
|
7
|
+
* preferred `HonoBundle` term describes reusable packages that contribute
|
|
8
|
+
* {@link HonoModule} / {@link HonoExtension} wrappers that can carry HTTP
|
|
9
|
+
* routes. `HonoPlugin` remains as a compatibility alias for the same shape.
|
|
10
|
+
*
|
|
11
|
+
* Registered via `createApp({ plugins: [...] })` — the app factory expands
|
|
12
|
+
* each bundle into the underlying modules, extensions, subscribers, and link
|
|
13
|
+
* definitions before mounting them.
|
|
14
|
+
*/
|
|
15
|
+
export interface HonoBundle {
|
|
16
|
+
/** Unique bundle identifier (e.g. "payload-cms", "bokun"). */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Optional version tag for diagnostics. */
|
|
19
|
+
version?: string;
|
|
20
|
+
/** Optional lazy runtime bootstrap executed once per app/isolate. */
|
|
21
|
+
bootstrap?: BootstrapHandler;
|
|
22
|
+
/** Hono modules (module + routes) contributed by the plugin. */
|
|
23
|
+
modules?: HonoModule[];
|
|
24
|
+
/** Hono extensions (extension + routes) contributed by the plugin. */
|
|
25
|
+
extensions?: HonoExtension[];
|
|
26
|
+
/** Event subscribers wired to the caller's event bus, when provided. */
|
|
27
|
+
subscribers?: Subscriber[];
|
|
28
|
+
/** Link definitions contributed by the plugin. */
|
|
29
|
+
links?: LinkDefinition[];
|
|
30
|
+
/**
|
|
31
|
+
* Workflows contributed by the plugin. Mirrors the `Plugin.workflows`
|
|
32
|
+
* field in `@voyant-travel/core` — collected at `createApp()` boot and
|
|
33
|
+
* registered with the configured workflow driver.
|
|
34
|
+
*/
|
|
35
|
+
workflows?: readonly WorkflowDescriptor[];
|
|
36
|
+
/**
|
|
37
|
+
* Event filters contributed by the plugin. Mirrors
|
|
38
|
+
* `Plugin.eventFilters` in `@voyant-travel/core`.
|
|
39
|
+
*/
|
|
40
|
+
eventFilters?: readonly EventFilterDescriptor[];
|
|
41
|
+
}
|
|
42
|
+
/** @deprecated Prefer {@link HonoBundle}. */
|
|
43
|
+
export type HonoPlugin = HonoBundle;
|
|
44
|
+
/**
|
|
45
|
+
* Identity helper — returns the bundle unchanged, purely for IDE inference.
|
|
46
|
+
*/
|
|
47
|
+
export declare function defineHonoBundle<P extends HonoBundle>(bundle: P): P;
|
|
48
|
+
/** @deprecated Prefer {@link defineHonoBundle}. */
|
|
49
|
+
export declare const defineHonoPlugin: typeof defineHonoBundle;
|
|
50
|
+
export interface ExpandedHonoBundles {
|
|
51
|
+
modules: HonoModule[];
|
|
52
|
+
extensions: HonoExtension[];
|
|
53
|
+
subscribers: Subscriber[];
|
|
54
|
+
links: LinkDefinition[];
|
|
55
|
+
}
|
|
56
|
+
/** @deprecated Prefer {@link ExpandedHonoBundles}. */
|
|
57
|
+
export type ExpandedHonoPlugins = ExpandedHonoBundles;
|
|
58
|
+
/**
|
|
59
|
+
* Flatten a list of {@link HonoBundle} values into their constituent pieces.
|
|
60
|
+
*
|
|
61
|
+
* Throws if two bundles declare the same `name`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function expandHonoBundles(bundles: ReadonlyArray<HonoBundle>): ExpandedHonoBundles;
|
|
64
|
+
/** @deprecated Prefer {@link expandHonoBundles}. */
|
|
65
|
+
export declare const expandHonoPlugins: typeof expandHonoBundles;
|
|
66
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,UAAU,EACV,kBAAkB,EACnB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE5D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qEAAqE;IACrE,SAAS,CAAC,EAAE,gBAAgB,CAAA;IAC5B,gEAAgE;IAChE,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB,sEAAsE;IACtE,UAAU,CAAC,EAAE,aAAa,EAAE,CAAA;IAC5B,wEAAwE;IACxE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;IAC1B,kDAAkD;IAClD,KAAK,CAAC,EAAE,cAAc,EAAE,CAAA;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACzC;;;OAGG;IACH,YAAY,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAA;CAChD;AAED,6CAA6C;AAC7C,MAAM,MAAM,UAAU,GAAG,UAAU,CAAA;AAEnC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEnE;AAED,mDAAmD;AACnD,eAAO,MAAM,gBAAgB,yBAAmB,CAAA;AAEhD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,UAAU,EAAE,aAAa,EAAE,CAAA;IAC3B,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,KAAK,EAAE,cAAc,EAAE,CAAA;CACxB;AAED,sDAAsD;AACtD,MAAM,MAAM,mBAAmB,GAAG,mBAAmB,CAAA;AAErD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,mBAAmB,CAoBzF;AAED,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,0BAAoB,CAAA"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity helper — returns the bundle unchanged, purely for IDE inference.
|
|
3
|
+
*/
|
|
4
|
+
export function defineHonoBundle(bundle) {
|
|
5
|
+
return bundle;
|
|
6
|
+
}
|
|
7
|
+
/** @deprecated Prefer {@link defineHonoBundle}. */
|
|
8
|
+
export const defineHonoPlugin = defineHonoBundle;
|
|
9
|
+
/**
|
|
10
|
+
* Flatten a list of {@link HonoBundle} values into their constituent pieces.
|
|
11
|
+
*
|
|
12
|
+
* Throws if two bundles declare the same `name`.
|
|
13
|
+
*/
|
|
14
|
+
export function expandHonoBundles(bundles) {
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const modules = [];
|
|
17
|
+
const extensions = [];
|
|
18
|
+
const subscribers = [];
|
|
19
|
+
const links = [];
|
|
20
|
+
for (const bundle of bundles) {
|
|
21
|
+
if (seen.has(bundle.name)) {
|
|
22
|
+
throw new Error(`Duplicate bundle name: "${bundle.name}"`);
|
|
23
|
+
}
|
|
24
|
+
seen.add(bundle.name);
|
|
25
|
+
if (bundle.modules)
|
|
26
|
+
modules.push(...bundle.modules);
|
|
27
|
+
if (bundle.extensions)
|
|
28
|
+
extensions.push(...bundle.extensions);
|
|
29
|
+
if (bundle.subscribers)
|
|
30
|
+
subscribers.push(...bundle.subscribers);
|
|
31
|
+
if (bundle.links)
|
|
32
|
+
links.push(...bundle.links);
|
|
33
|
+
}
|
|
34
|
+
return { modules, extensions, subscribers, links };
|
|
35
|
+
}
|
|
36
|
+
/** @deprecated Prefer {@link expandHonoBundles}. */
|
|
37
|
+
export const expandHonoPlugins = expandHonoBundles;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
export interface PublicCapabilityPayload {
|
|
3
|
+
v: 1;
|
|
4
|
+
scope: string;
|
|
5
|
+
subjectId: string;
|
|
6
|
+
actions: string[];
|
|
7
|
+
iat: number;
|
|
8
|
+
exp: number;
|
|
9
|
+
jti?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CreatePublicCapabilityOptions {
|
|
12
|
+
secret: string;
|
|
13
|
+
scope: string;
|
|
14
|
+
subjectId: string;
|
|
15
|
+
actions: string[];
|
|
16
|
+
ttlSeconds: number;
|
|
17
|
+
now?: Date;
|
|
18
|
+
jti?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface VerifyPublicCapabilityOptions {
|
|
21
|
+
secret: string;
|
|
22
|
+
scope: string;
|
|
23
|
+
subjectId: string;
|
|
24
|
+
action: string;
|
|
25
|
+
now?: Date;
|
|
26
|
+
}
|
|
27
|
+
export interface PublicCapabilityCookieOptions {
|
|
28
|
+
name: string;
|
|
29
|
+
token: string;
|
|
30
|
+
expiresAt: Date;
|
|
31
|
+
secure?: boolean;
|
|
32
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
33
|
+
path?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function createPublicCapabilityToken(options: CreatePublicCapabilityOptions): Promise<{
|
|
36
|
+
token: string;
|
|
37
|
+
payload: PublicCapabilityPayload;
|
|
38
|
+
expiresAt: Date;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function verifyPublicCapabilityToken(token: string, options: VerifyPublicCapabilityOptions): Promise<PublicCapabilityPayload>;
|
|
41
|
+
export declare function extractPublicCapabilityToken(c: Context, options?: {
|
|
42
|
+
headerName?: string;
|
|
43
|
+
cookieName?: string;
|
|
44
|
+
}): string | null;
|
|
45
|
+
export declare function serializePublicCapabilityCookie(options: PublicCapabilityCookieOptions): string;
|
|
46
|
+
//# sourceMappingURL=public-capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-capability.d.ts","sourceRoot":"","sources":["../src/public-capability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAInC,MAAM,WAAW,uBAAuB;IACtC,CAAC,EAAE,CAAC,CAAA;IACJ,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,IAAI,CAAA;IACV,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,IAAI,CAAA;CACX;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,IAAI,CAAA;IACf,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;IACpC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA2DD,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,uBAAuB,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,CAAC,CAyB/E;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CAyClC;AAED,wBAAgB,4BAA4B,CAC1C,CAAC,EAAE,OAAO,EACV,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,iBA0B3D;AAED,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,6BAA6B,UAiBrF"}
|