@aotter/mantle-cloudflare 0.0.11-alpha.47 → 0.0.11-alpha.49
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 +14 -0
- package/dist/auth/createAuth.d.ts +33 -0
- package/dist/auth/createAuth.d.ts.map +1 -1
- package/dist/auth/createAuth.js +246 -41
- package/dist/auth/createAuth.js.map +1 -1
- package/dist/mount/bootRuntimeOnce.d.ts +3 -0
- package/dist/mount/bootRuntimeOnce.d.ts.map +1 -1
- package/dist/mount/bootRuntimeOnce.js +2 -0
- package/dist/mount/bootRuntimeOnce.js.map +1 -1
- package/dist/mount/cmsConfig.d.ts +10 -0
- package/dist/mount/cmsConfig.d.ts.map +1 -1
- package/dist/mount/index.d.ts +1 -0
- package/dist/mount/index.d.ts.map +1 -1
- package/dist/mount/index.js +1 -0
- package/dist/mount/index.js.map +1 -1
- package/dist/mount/mountMcp.d.ts +6 -2
- package/dist/mount/mountMcp.d.ts.map +1 -1
- package/dist/mount/mountMcp.js +16 -7
- package/dist/mount/mountMcp.js.map +1 -1
- package/dist/mount/mountServerEndpoints.d.ts.map +1 -1
- package/dist/mount/mountServerEndpoints.js +40 -80
- package/dist/mount/mountServerEndpoints.js.map +1 -1
- package/dist/mount/resolveCaller.d.ts +46 -0
- package/dist/mount/resolveCaller.d.ts.map +1 -0
- package/dist/mount/resolveCaller.js +95 -0
- package/dist/mount/resolveCaller.js.map +1 -0
- package/dist/oauth/mountOAuth.d.ts +2 -1
- package/dist/oauth/mountOAuth.d.ts.map +1 -1
- package/dist/oauth/mountOAuth.js +4 -5
- package/dist/oauth/mountOAuth.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootRuntimeOnce.js","sourceRoot":"","sources":["../../src/mount/bootRuntimeOnce.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,GAEjB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"bootRuntimeOnce.js","sourceRoot":"","sources":["../../src/mount/bootRuntimeOnce.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,GAEjB,MAAM,wBAAwB,CAAC;AAoBhC,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,MAAM,OAAO,GAAG,gBAAgB,CAAC;QAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;QAC7C,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;QACtB,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;QAC9B,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,YAAY;QAC1C,sBAAsB,EAAE,MAAM,CAAC,QAAQ,CAAC,sBAAsB;KAC/D,CAAC,CAAC;IAEH,IAAI,MAAM,GAA+B,IAAI,CAAC;IAC9C,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;QAC7C,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,GAAG;YACD,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAC1B,MAAM,GAAG,OAAO;iBACb,QAAQ,EAAE;iBACV,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC;iBACnB,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACb,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM,GAAG,CAAC;YACZ,CAAC,CAAC,CAAC;YACL,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AnyHandler, CreateCmsRuntimeArgs, PublicPathResolver, TemplateRegistry } from "@aotter/mantle-runtime";
|
|
2
2
|
import type { Manifest, SiteDefaults } from "@aotter/mantle-spec";
|
|
3
3
|
import type { Auth } from "../auth/createAuth.js";
|
|
4
|
+
import type { ConsumerCredentialResolver } from "./resolveCaller.js";
|
|
4
5
|
/**
|
|
5
6
|
* Consumer-supplied config for the Cloudflare adapter mounts. `auth`
|
|
6
7
|
* (Better Auth) gates `/admin/api/*` + MCP bearers. `bindings` carries
|
|
@@ -27,5 +28,14 @@ export interface CmsConfig {
|
|
|
27
28
|
/** Pass-through to runtime: SVG opt-in flag (default false). */
|
|
28
29
|
readonly mediaAllowSvg?: boolean;
|
|
29
30
|
readonly auth: Auth;
|
|
31
|
+
/** Site-owned API key / personal-token verifier. Core supplies only
|
|
32
|
+
* normalization and orchestration; storage and issuance stay in
|
|
33
|
+
* consumer code. */
|
|
34
|
+
readonly credentialResolver?: ConsumerCredentialResolver;
|
|
35
|
+
/** Enable OAuth JWT bearer authentication on manifest REST routes. */
|
|
36
|
+
readonly oauthBearer?: {
|
|
37
|
+
readonly audience: string;
|
|
38
|
+
readonly scopes?: readonly string[];
|
|
39
|
+
};
|
|
30
40
|
}
|
|
31
41
|
//# sourceMappingURL=cmsConfig.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cmsConfig.d.ts","sourceRoot":"","sources":["../../src/mount/cmsConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"cmsConfig.d.ts","sourceRoot":"","sources":["../../src/mount/cmsConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,oBAAoB,EAAE,IAAI,GAAG,IAAI,GAAG,QAAQ,CAAC,GAAG;QACtE;;yCAEiC;QACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC,cAAc,CAAC,CAAC;QAC7D;;;;oBAIY;QACZ,QAAQ,CAAC,sBAAsB,CAAC,EAAE,oBAAoB,CAAC,wBAAwB,CAAC,CAAC;KAClF,CAAC;IACF,gEAAgE;IAChE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB;;yBAEqB;IACrB,QAAQ,CAAC,kBAAkB,CAAC,EAAE,0BAA0B,CAAC;IACzD,sEAAsE;IACtE,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC;CACH"}
|
package/dist/mount/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { mountServerEndpoints } from "./mountServerEndpoints.js";
|
|
2
|
+
export { resolveCaller, type CallerResolution, type ConsumerCredentialResolution, type ConsumerCredentialResolver, type ResolveCallerOptions, } from "./resolveCaller.js";
|
|
2
3
|
export { type CmsRuntimeRef, createCmsRef } from "./bootRuntimeOnce.js";
|
|
3
4
|
export { createMcpApiHandler, type CreateMcpApiHandlerOptions, } from "./mountMcp.js";
|
|
4
5
|
export { mountPublicRoutes, type CollectionRouteConfig, type MountPublicRoutesOptions, type PublicRouteContext, type SlugOverride, } from "./mountPublicRoutes.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mount/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mount/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACL,aAAa,EACb,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/mount/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { mountServerEndpoints } from "./mountServerEndpoints.js";
|
|
2
|
+
export { resolveCaller, } from "./resolveCaller.js";
|
|
2
3
|
export { createCmsRef } from "./bootRuntimeOnce.js";
|
|
3
4
|
export { createMcpApiHandler, } from "./mountMcp.js";
|
|
4
5
|
export { mountPublicRoutes, } from "./mountPublicRoutes.js";
|
package/dist/mount/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mount/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAsB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,mBAAmB,GAEpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,GAKlB,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mount/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACL,aAAa,GAKd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAsB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,mBAAmB,GAEpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,GAKlB,MAAM,wBAAwB,CAAC"}
|
package/dist/mount/mountMcp.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { CmsRuntimeRef } from "./bootRuntimeOnce.js";
|
|
|
2
2
|
export interface CreateMcpApiHandlerOptions {
|
|
3
3
|
readonly ref: CmsRuntimeRef;
|
|
4
4
|
readonly surface: "staff" | "public";
|
|
5
|
+
/** OAuth scopes required to enter this MCP resource. Defaults to
|
|
6
|
+
* the existing compatibility scope `mcp`. Target-specific scopes
|
|
7
|
+
* remain manifest predicates enforced on tools/call. */
|
|
8
|
+
readonly requiredScopes?: readonly string[];
|
|
5
9
|
}
|
|
6
10
|
/**
|
|
7
11
|
* Build a Cloudflare Worker `ExportedHandler` that serves one MCP
|
|
@@ -10,8 +14,8 @@ export interface CreateMcpApiHandlerOptions {
|
|
|
10
14
|
*
|
|
11
15
|
* The OAuthProvider lib verifies the bearer token, decrypts grant
|
|
12
16
|
* props, and sets `ctx.props` BEFORE calling this handler. We read
|
|
13
|
-
*
|
|
14
|
-
*
|
|
17
|
+
* immutable grant props and re-read the caller's mutable staff role
|
|
18
|
+
* from D1 on every invocation.
|
|
15
19
|
*
|
|
16
20
|
* Note: OAuth scope distinction (`mcp:read` vs `mcp:staff`) used to
|
|
17
21
|
* differentiate surfaces here. Removed because claude.ai's MCP client
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountMcp.d.ts","sourceRoot":"","sources":["../../src/mount/mountMcp.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"mountMcp.d.ts","sourceRoot":"","sources":["../../src/mount/mountMcp.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC;;6DAEyD;IACzD,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA2F1C"}
|
package/dist/mount/mountMcp.js
CHANGED
|
@@ -7,8 +7,8 @@ import { STAFF_ROLE_SET } from "../auth/createAuth.js";
|
|
|
7
7
|
*
|
|
8
8
|
* The OAuthProvider lib verifies the bearer token, decrypts grant
|
|
9
9
|
* props, and sets `ctx.props` BEFORE calling this handler. We read
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* immutable grant props and re-read the caller's mutable staff role
|
|
11
|
+
* from D1 on every invocation.
|
|
12
12
|
*
|
|
13
13
|
* Note: OAuth scope distinction (`mcp:read` vs `mcp:staff`) used to
|
|
14
14
|
* differentiate surfaces here. Removed because claude.ai's MCP client
|
|
@@ -18,6 +18,7 @@ import { STAFF_ROLE_SET } from "../auth/createAuth.js";
|
|
|
18
18
|
*/
|
|
19
19
|
export function createMcpApiHandler(options) {
|
|
20
20
|
const { ref, surface } = options;
|
|
21
|
+
const requiredScopes = options.requiredScopes ?? ["mcp"];
|
|
21
22
|
// Key the cached dispatcher to the runtime identity. Without this,
|
|
22
23
|
// if `ref.get()` rejects + resets and the next call returns a new
|
|
23
24
|
// runtime instance, the cached dispatcher would silently keep
|
|
@@ -28,10 +29,14 @@ export function createMcpApiHandler(options) {
|
|
|
28
29
|
async fetch(request, _env, ctx) {
|
|
29
30
|
const props = ctx.props;
|
|
30
31
|
if (!props?.userId)
|
|
31
|
-
return forbidden();
|
|
32
|
-
const
|
|
32
|
+
return forbidden(requiredScopes);
|
|
33
|
+
const grantedScopes = props.scopes?.length ? props.scopes : ["mcp"];
|
|
34
|
+
if (requiredScopes.some((scope) => !grantedScopes.includes(scope))) {
|
|
35
|
+
return forbidden(requiredScopes);
|
|
36
|
+
}
|
|
37
|
+
const role = await ref.auth.getUserRole(props.userId);
|
|
33
38
|
if (surface === "staff" && (!role || !STAFF_ROLE_SET.has(role))) {
|
|
34
|
-
return forbidden();
|
|
39
|
+
return forbidden(requiredScopes);
|
|
35
40
|
}
|
|
36
41
|
const runtime = await ref.get();
|
|
37
42
|
// Media tools require BOTH a storage adapter AND a declared
|
|
@@ -86,15 +91,19 @@ export function createMcpApiHandler(options) {
|
|
|
86
91
|
staff: role && STAFF_ROLE_SET.has(role)
|
|
87
92
|
? { userId: props.userId, role: role }
|
|
88
93
|
: null,
|
|
94
|
+
clientId: props.clientId ?? null,
|
|
95
|
+
credentialId: null,
|
|
96
|
+
scopes: grantedScopes,
|
|
89
97
|
});
|
|
90
98
|
},
|
|
91
99
|
};
|
|
92
100
|
}
|
|
93
|
-
function forbidden() {
|
|
101
|
+
function forbidden(requiredScopes) {
|
|
102
|
+
const scope = requiredScopes.join(" ");
|
|
94
103
|
return new Response("forbidden", {
|
|
95
104
|
status: 403,
|
|
96
105
|
headers: {
|
|
97
|
-
"www-authenticate": `Bearer realm="mcp", error="insufficient_scope"`,
|
|
106
|
+
"www-authenticate": `Bearer realm="mcp", error="insufficient_scope", scope="${scope}"`,
|
|
98
107
|
"access-control-expose-headers": "WWW-Authenticate",
|
|
99
108
|
},
|
|
100
109
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountMcp.js","sourceRoot":"","sources":["../../src/mount/mountMcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAO9D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"mountMcp.js","sourceRoot":"","sources":["../../src/mount/mountMcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountServerEndpoints.d.ts","sourceRoot":"","sources":["../../src/mount/mountServerEndpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AA6B1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"mountServerEndpoints.d.ts","sourceRoot":"","sources":["../../src/mount/mountServerEndpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AA6B1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAO1D;;;;iBAIiB;AACjB,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,IAAI,EACT,GAAG,EAAE,aAAa,GACjB,IAAI,CA6BN;AAkwDD,YAAY,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DiagnosticError, HTTP_STATUS_BY_CODE, MANTLE_REF_KEYWORD, MCP_HINT_KEYWORD, VIEW_PARAMS_RESERVED, isMediaMcpHint, redactForWire, runtimeDiagnostic, } from "@aotter/mantle-spec";
|
|
2
2
|
import { ViewParamCoercionError, coerceViewParams, evaluateAuthAll, matchPath, } from "@aotter/mantle-runtime";
|
|
3
3
|
import { indexHtml } from "@aotter/mantle-admin-ui";
|
|
4
|
+
import { resolveCaller } from "./resolveCaller.js";
|
|
4
5
|
import { STAFF_ROLE_SET } from "../auth/createAuth.js";
|
|
5
6
|
import { AOTTER_FAVICON_SVG } from "../assets/aotterFavicon.js";
|
|
6
7
|
const [PAGE_PARAM, SHOW_PARAM] = VIEW_PARAMS_RESERVED;
|
|
@@ -22,7 +23,7 @@ export function mountServerEndpoints(app, ref) {
|
|
|
22
23
|
app.on(method, honoPath, async (c) => {
|
|
23
24
|
const runtime = await ref.get();
|
|
24
25
|
const waitUntil = readWaitUntil(c);
|
|
25
|
-
return handleHttpTrigger(c.req.raw, runtime, ref
|
|
26
|
+
return handleHttpTrigger(c.req.raw, runtime, ref, triggerName, path, waitUntil);
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
for (const v of ref.manifests) {
|
|
@@ -38,7 +39,7 @@ export function mountServerEndpoints(app, ref) {
|
|
|
38
39
|
app.get(`/api/views/${viewName}`, async (c) => {
|
|
39
40
|
const runtime = await ref.get();
|
|
40
41
|
const waitUntil = readWaitUntil(c);
|
|
41
|
-
return handleViewRequest(c.req.raw, runtime, viewName, ref
|
|
42
|
+
return handleViewRequest(c.req.raw, runtime, viewName, ref, waitUntil, "/api/views");
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
mountAdminBetterAuth(app, ref, ref.auth);
|
|
@@ -261,7 +262,7 @@ function mountAdminBetterAuth(app, ref, auth) {
|
|
|
261
262
|
guarded("get", `/admin/api/views/${viewName}`, async (c) => {
|
|
262
263
|
const runtime = await ref.get();
|
|
263
264
|
const waitUntil = readWaitUntil(c);
|
|
264
|
-
return handleViewRequest(c.req.raw, runtime, viewName, ref
|
|
265
|
+
return handleViewRequest(c.req.raw, runtime, viewName, ref, waitUntil, "/admin/api/views");
|
|
265
266
|
});
|
|
266
267
|
}
|
|
267
268
|
guarded("get", "/admin/api/operations", () => Response.json({
|
|
@@ -1202,6 +1203,12 @@ function adminHandlerContext(c, gate) {
|
|
|
1202
1203
|
return {
|
|
1203
1204
|
user: { id: gate.userId },
|
|
1204
1205
|
staff: { id: gate.userId, role: gate.role },
|
|
1206
|
+
auth: {
|
|
1207
|
+
credential: "session",
|
|
1208
|
+
credentialId: gate.sessionId,
|
|
1209
|
+
clientId: null,
|
|
1210
|
+
scopes: [],
|
|
1211
|
+
},
|
|
1205
1212
|
env: {},
|
|
1206
1213
|
...(waitUntil ? { waitUntil } : {}),
|
|
1207
1214
|
};
|
|
@@ -1210,7 +1217,7 @@ async function readStaffGate(c, auth) {
|
|
|
1210
1217
|
const session = await auth.getSession(c.req.raw);
|
|
1211
1218
|
if (!session)
|
|
1212
1219
|
return { kind: "unauth" };
|
|
1213
|
-
const role = session.user.
|
|
1220
|
+
const role = await auth.getUserRole(session.user.id);
|
|
1214
1221
|
const login = session.user.githubLogin ?? null;
|
|
1215
1222
|
if (!role || !STAFF_ROLE_SET.has(role)) {
|
|
1216
1223
|
return { kind: "forbidden", login };
|
|
@@ -1220,9 +1227,10 @@ async function readStaffGate(c, auth) {
|
|
|
1220
1227
|
userId: session.user.id,
|
|
1221
1228
|
login,
|
|
1222
1229
|
role: role,
|
|
1230
|
+
sessionId: session.session.id,
|
|
1223
1231
|
};
|
|
1224
1232
|
}
|
|
1225
|
-
async function handleHttpTrigger(req, runtime,
|
|
1233
|
+
async function handleHttpTrigger(req, runtime, ref, triggerName, triggerPath, waitUntil) {
|
|
1226
1234
|
const trigger = runtime.triggersByName.get(triggerName);
|
|
1227
1235
|
if (!trigger) {
|
|
1228
1236
|
return jsonError({ status: 500, code: "INTERNAL_ERROR", message: `Trigger '${triggerName}' missing post-boot.` });
|
|
@@ -1232,6 +1240,16 @@ async function handleHttpTrigger(req, runtime, auth, triggerName, triggerPath, w
|
|
|
1232
1240
|
if (!procedure) {
|
|
1233
1241
|
return jsonError({ status: 500, code: "INTERNAL_ERROR", message: `Procedure '${procName}' missing post-boot.` });
|
|
1234
1242
|
}
|
|
1243
|
+
const triggerPathPrefix = `${req.method} ${triggerPath}`;
|
|
1244
|
+
const caller = await resolveCaller(req, {
|
|
1245
|
+
auth: ref.auth,
|
|
1246
|
+
credentialResolver: ref.credentialResolver,
|
|
1247
|
+
oauthBearer: ref.oauthBearer,
|
|
1248
|
+
waitUntil,
|
|
1249
|
+
});
|
|
1250
|
+
if (caller.kind === "invalid") {
|
|
1251
|
+
return Response.json({ ok: false, diagnostic: caller.diagnostic }, { status: caller.status });
|
|
1252
|
+
}
|
|
1235
1253
|
const url = new URL(req.url);
|
|
1236
1254
|
const params = matchPath(triggerPath, url.pathname) ?? {};
|
|
1237
1255
|
const body = await readBody(req);
|
|
@@ -1239,32 +1257,10 @@ async function handleHttpTrigger(req, runtime, auth, triggerName, triggerPath, w
|
|
|
1239
1257
|
// resource identifier (a `DELETE /entries/{id}` body MUST NOT spoof
|
|
1240
1258
|
// `id`). Body fields fill in non-path inputs only.
|
|
1241
1259
|
const input = { ...body, ...params };
|
|
1242
|
-
const triggerPathPrefix = `${req.method} ${triggerPath}`;
|
|
1243
|
-
const ctx = await buildCallerContext(req, auth, waitUntil);
|
|
1244
|
-
// Mirror the View handler's 401-vs-403 discipline (see
|
|
1245
|
-
// `handleViewRequest`). An auth-gated Procedure called with no
|
|
1246
|
-
// session is UNAUTHENTICATED (401); a session whose role fails the
|
|
1247
|
-
// predicate is AUTH_DENIED (403). InvokeProcedureUseCase collapses
|
|
1248
|
-
// both into AUTH_DENIED if we pass `{ user: null, staff: null }`
|
|
1249
|
-
// as ctx, so we pre-check here.
|
|
1250
|
-
if (procedure.spec.requires?.auth && !ctx) {
|
|
1251
|
-
return Response.json({
|
|
1252
|
-
ok: false,
|
|
1253
|
-
diagnostic: runtimeDiagnostic({
|
|
1254
|
-
code: "UNAUTHENTICATED",
|
|
1255
|
-
severity: "error",
|
|
1256
|
-
path: triggerPathPrefix,
|
|
1257
|
-
expected: "authenticated session",
|
|
1258
|
-
message: `Procedure '${procName}' requires authentication.`,
|
|
1259
|
-
}),
|
|
1260
|
-
}, { status: 401 });
|
|
1261
|
-
}
|
|
1262
|
-
const wu = waitUntil ? { waitUntil } : {};
|
|
1263
|
-
const invokeCtx = ctx ?? { user: null, staff: null, env: {}, ...wu };
|
|
1264
1260
|
const result = await runtime.invokeProcedure.execute({
|
|
1265
1261
|
procedure,
|
|
1266
1262
|
input,
|
|
1267
|
-
ctx:
|
|
1263
|
+
ctx: caller.context,
|
|
1268
1264
|
pathPrefix: triggerPathPrefix,
|
|
1269
1265
|
});
|
|
1270
1266
|
if (result.ok) {
|
|
@@ -1273,7 +1269,7 @@ async function handleHttpTrigger(req, runtime, auth, triggerName, triggerPath, w
|
|
|
1273
1269
|
const status = HTTP_STATUS_BY_CODE[result.diagnostic.code] ?? 500;
|
|
1274
1270
|
return Response.json({ ok: false, diagnostic: result.diagnostic }, { status });
|
|
1275
1271
|
}
|
|
1276
|
-
async function handleViewRequest(req, runtime, viewName,
|
|
1272
|
+
async function handleViewRequest(req, runtime, viewName, ref, waitUntil,
|
|
1277
1273
|
// Mount prefix for this View's route. Passed by the caller because
|
|
1278
1274
|
// the same handler serves BOTH the public mount (`/api/views`) and
|
|
1279
1275
|
// the staff mount (`/admin/api/views`); hardcoding the public prefix
|
|
@@ -1284,29 +1280,22 @@ mountPath) {
|
|
|
1284
1280
|
return jsonError({ status: 500, code: "INTERNAL_ERROR", message: `View '${viewName}' missing post-boot.` });
|
|
1285
1281
|
}
|
|
1286
1282
|
const viewPath = `GET ${mountPath}/${viewName}`;
|
|
1287
|
-
// Resolve caller
|
|
1288
|
-
//
|
|
1289
|
-
//
|
|
1290
|
-
//
|
|
1291
|
-
//
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1283
|
+
// Resolve caller and evaluate static auth BEFORE query coercion so a
|
|
1284
|
+
// protected View does not leak its parameter contract. The runtime
|
|
1285
|
+
// evaluates the same predicates again before guard/query execution;
|
|
1286
|
+
// this adapter preflight exists only to preserve the no-leak order at
|
|
1287
|
+
// the HTTP parsing boundary.
|
|
1288
|
+
const caller = await resolveCaller(req, {
|
|
1289
|
+
auth: ref.auth,
|
|
1290
|
+
credentialResolver: ref.credentialResolver,
|
|
1291
|
+
oauthBearer: ref.oauthBearer,
|
|
1292
|
+
waitUntil,
|
|
1293
|
+
});
|
|
1294
|
+
if (caller.kind === "invalid") {
|
|
1295
|
+
return Response.json({ ok: false, diagnostic: caller.diagnostic }, { status: caller.status });
|
|
1296
|
+
}
|
|
1297
|
+
const ctx = caller.context;
|
|
1297
1298
|
if (view.spec.requires?.auth) {
|
|
1298
|
-
if (!ctx) {
|
|
1299
|
-
return Response.json({
|
|
1300
|
-
ok: false,
|
|
1301
|
-
diagnostic: runtimeDiagnostic({
|
|
1302
|
-
code: "UNAUTHENTICATED",
|
|
1303
|
-
severity: "error",
|
|
1304
|
-
path: viewPath,
|
|
1305
|
-
expected: "authenticated session",
|
|
1306
|
-
message: `View '${viewName}' requires authentication.`,
|
|
1307
|
-
}),
|
|
1308
|
-
}, { status: 401 });
|
|
1309
|
-
}
|
|
1310
1299
|
const denial = evaluateAuthAll(view.spec.requires, ctx, viewPath, "runtime");
|
|
1311
1300
|
if (denial) {
|
|
1312
1301
|
return Response.json({
|
|
@@ -1349,35 +1338,6 @@ mountPath) {
|
|
|
1349
1338
|
const status = HTTP_STATUS_BY_CODE[result.diagnostic.code] ?? 500;
|
|
1350
1339
|
return Response.json({ ok: false, diagnostic: result.diagnostic }, { status });
|
|
1351
1340
|
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Resolve caller identity for a View or HTTP-Trigger request from the
|
|
1354
|
-
* Better Auth cookie session. Returns `undefined` when there is no
|
|
1355
|
-
* session so callers can distinguish 401 (no session) from 403
|
|
1356
|
-
* (session but wrong role) — `ExecuteViewUseCase` and the
|
|
1357
|
-
* `handleHttpTrigger` pre-check both rely on this signal.
|
|
1358
|
-
*
|
|
1359
|
-
* Used by both `handleViewRequest` and `handleHttpTrigger` so the
|
|
1360
|
-
* two HTTP surfaces resolve identity identically.
|
|
1361
|
-
*
|
|
1362
|
-
* Note: this resolves cookie sessions only. OAuth bearer tokens
|
|
1363
|
-
* (issued by `createOAuthProvider` for MCP callers) are NOT verified
|
|
1364
|
-
* here — bearer-authenticated identity belongs on the MCP surface,
|
|
1365
|
-
* not the HTTP Trigger surface, so a bearer-only caller hitting an
|
|
1366
|
-
* auth-gated Trigger will land at the 401 branch in
|
|
1367
|
-
* `handleHttpTrigger`. Procedures that need to be agent-callable
|
|
1368
|
-
* should be reached via `/mcp` or `/mcp/staff` (see #281).
|
|
1369
|
-
*/
|
|
1370
|
-
async function buildCallerContext(req, auth, waitUntil) {
|
|
1371
|
-
const session = await auth.getSession(req);
|
|
1372
|
-
if (!session)
|
|
1373
|
-
return undefined;
|
|
1374
|
-
const wu = waitUntil ? { waitUntil } : {};
|
|
1375
|
-
const role = await auth.getUserRole(session.user.id);
|
|
1376
|
-
const staff = role && STAFF_ROLE_SET.has(role)
|
|
1377
|
-
? { id: session.user.id, role: role }
|
|
1378
|
-
: null;
|
|
1379
|
-
return { user: { id: session.user.id }, staff, env: {}, ...wu };
|
|
1380
|
-
}
|
|
1381
1341
|
function parsePositiveInt(raw) {
|
|
1382
1342
|
if (raw == null || raw === "")
|
|
1383
1343
|
return undefined;
|