@opendatalabs/personal-server-ts-core 1.10.2 → 1.11.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.
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Delegated-write authorization, shared by every build.
3
+ *
4
+ * `authorizeWrite` / `authorizeWriteSession` are the two write-session halves
5
+ * of `PersonalServerApiAuthPort`. They were originally written inside the Node
6
+ * server's auth adapter (packages/server/src/api-auth.ts); the checks they run
7
+ * are pure protocol — a token hash, a live grant re-check, an EIP-191 payload
8
+ * proof and a replay reservation — with nothing Node-specific in them, so they
9
+ * live here and BOTH the Node server adapter and the browser (PS-Lite)
10
+ * adapters call the same code. There is exactly one implementation of the
11
+ * write policy; a browser build cannot drift into a weaker one.
12
+ *
13
+ * The two entry points differ in what they are allowed to decide:
14
+ *
15
+ * - `authorizeSessionWrite` authorizes a write to a NAMED scope. It
16
+ * re-runs `verifyDataWritePolicy` against the LIVE grant on every call,
17
+ * so revocation, expiry, grantee binding, owner binding and scope
18
+ * coverage stay authoritative per write rather than being frozen at
19
+ * handshake, and it verifies the builder's per-write payload proof.
20
+ * - `recognizeWriteSession` is identity only: the bearer must resolve to a
21
+ * live session and the request must carry that builder's valid proof, but
22
+ * NO grant policy runs (there is no scope to run one against). Callers use
23
+ * it only to choose between error shapes, never to release data.
24
+ *
25
+ * Both fail closed: an unknown bearer, an unknown session, a missing server
26
+ * owner, or any failing proof check returns/raises rather than falling back to
27
+ * a weaker credential. A request that is not a write-session request at all
28
+ * returns `undefined`, and the caller applies its own owner gate.
29
+ */
30
+ import { ProtocolError } from "../errors/catalog.js";
31
+ import { type DataWritePolicyPorts } from "../policy/data-write.js";
32
+ import type { PersonalServerWriteAuthInput, PersonalServerWriteAuthResult, PersonalServerWriteSessionResult } from "../api/index.js";
33
+ import { type WriteProofReplayStore, type WriteSessionStore } from "./session.js";
34
+ export interface WriteSessionAuthorizationDeps {
35
+ /**
36
+ * This server's origin. Every payload proof must name it as its audience,
37
+ * so a proof signed for another Personal Server cannot be relayed here.
38
+ */
39
+ serverOrigin: string | (() => string);
40
+ /**
41
+ * The owner every grant must have been issued by. Absent = fail closed:
42
+ * a delegated write cannot be bound to an owner we cannot name.
43
+ */
44
+ serverOwner?: `0x${string}`;
45
+ /**
46
+ * Live write sessions (minted by POST /v1/write/session). Absent = the host
47
+ * does not support delegated writes and every request falls through to the
48
+ * caller's owner gate.
49
+ */
50
+ sessionStore?: WriteSessionStore;
51
+ /** Replay guard for the per-write `X-Vana-Write-Signature` proofs. */
52
+ replayStore: WriteProofReplayStore;
53
+ /** Live builder/grant verification, re-run on every delegated write. */
54
+ policyPorts: DataWritePolicyPorts;
55
+ now?: () => Date;
56
+ }
57
+ export interface WriteSessionAuthorization {
58
+ /**
59
+ * Authorize a delegated write to `input.scope`, or `undefined` when the
60
+ * request carries no live write-session credential (the caller then applies
61
+ * its owner gate, exactly as before write sessions existed).
62
+ */
63
+ authorizeSessionWrite(input: PersonalServerWriteAuthInput): Promise<PersonalServerWriteAuthResult | undefined>;
64
+ /**
65
+ * Identity-only recognition of a write-session caller. Never releases data.
66
+ */
67
+ recognizeWriteSession(request: Request): Promise<PersonalServerWriteSessionResult | undefined>;
68
+ }
69
+ /** The bearer credential a write session is presented under. */
70
+ export declare function writeSessionBearerToken(request: Request): string | null;
71
+ /**
72
+ * Same shape and message as the auth layer's own missing-owner failure, so a
73
+ * host that never configured an owner reports one thing, not two.
74
+ */
75
+ export declare function writeServerNotConfigured(): ProtocolError;
76
+ export declare function createWriteSessionAuthorization(deps: WriteSessionAuthorizationDeps): WriteSessionAuthorization;
77
+ //# sourceMappingURL=authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/write/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,gCAAgC,EACjC,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,YAAY,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,sEAAsE;IACtE,WAAW,EAAE,qBAAqB,CAAC;IACnC,wEAAwE;IACxE,WAAW,EAAE,oBAAoB,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,qBAAqB,CACnB,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,GAAG,SAAS,CAAC,CAAC;IACtD;;OAEG;IACH,qBAAqB,CACnB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,gCAAgC,GAAG,SAAS,CAAC,CAAC;CAC1D;AAED,gEAAgE;AAChE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIvE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,aAAa,CAMxD;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,6BAA6B,GAClC,yBAAyB,CA6D3B"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Delegated-write authorization, shared by every build.
3
+ *
4
+ * `authorizeWrite` / `authorizeWriteSession` are the two write-session halves
5
+ * of `PersonalServerApiAuthPort`. They were originally written inside the Node
6
+ * server's auth adapter (packages/server/src/api-auth.ts); the checks they run
7
+ * are pure protocol — a token hash, a live grant re-check, an EIP-191 payload
8
+ * proof and a replay reservation — with nothing Node-specific in them, so they
9
+ * live here and BOTH the Node server adapter and the browser (PS-Lite)
10
+ * adapters call the same code. There is exactly one implementation of the
11
+ * write policy; a browser build cannot drift into a weaker one.
12
+ *
13
+ * The two entry points differ in what they are allowed to decide:
14
+ *
15
+ * - `authorizeSessionWrite` authorizes a write to a NAMED scope. It
16
+ * re-runs `verifyDataWritePolicy` against the LIVE grant on every call,
17
+ * so revocation, expiry, grantee binding, owner binding and scope
18
+ * coverage stay authoritative per write rather than being frozen at
19
+ * handshake, and it verifies the builder's per-write payload proof.
20
+ * - `recognizeWriteSession` is identity only: the bearer must resolve to a
21
+ * live session and the request must carry that builder's valid proof, but
22
+ * NO grant policy runs (there is no scope to run one against). Callers use
23
+ * it only to choose between error shapes, never to release data.
24
+ *
25
+ * Both fail closed: an unknown bearer, an unknown session, a missing server
26
+ * owner, or any failing proof check returns/raises rather than falling back to
27
+ * a weaker credential. A request that is not a write-session request at all
28
+ * returns `undefined`, and the caller applies its own owner gate.
29
+ */
30
+ import { ProtocolError } from "../errors/catalog.js";
31
+ import { verifyDataWritePolicy, } from "../policy/data-write.js";
32
+ import { verifyWriterAttribution } from "./attribution.js";
33
+ import { hashWriteSessionToken, } from "./session.js";
34
+ /** The bearer credential a write session is presented under. */
35
+ export function writeSessionBearerToken(request) {
36
+ const header = request.headers.get("authorization");
37
+ if (!header?.startsWith("Bearer "))
38
+ return null;
39
+ return header.slice(7);
40
+ }
41
+ /**
42
+ * Same shape and message as the auth layer's own missing-owner failure, so a
43
+ * host that never configured an owner reports one thing, not two.
44
+ */
45
+ export function writeServerNotConfigured() {
46
+ return new ProtocolError(500, "SERVER_NOT_CONFIGURED", "Server owner address not configured. Set VANA_MASTER_KEY_SIGNATURE environment variable.");
47
+ }
48
+ export function createWriteSessionAuthorization(deps) {
49
+ async function liveSession(request) {
50
+ const token = writeSessionBearerToken(request);
51
+ if (!token || !deps.sessionStore)
52
+ return null;
53
+ return deps.sessionStore.getByTokenHash(await hashWriteSessionToken(token));
54
+ }
55
+ return {
56
+ async authorizeSessionWrite(input) {
57
+ const session = await liveSession(input.request);
58
+ if (!session)
59
+ return undefined;
60
+ if (!deps.serverOwner)
61
+ throw writeServerNotConfigured();
62
+ // The grant is re-checked live on EVERY write: a session token minted
63
+ // an hour ago against a grant that has since been revoked, narrowed or
64
+ // expired authorizes nothing.
65
+ const grant = await verifyDataWritePolicy({
66
+ signer: session.builderAddress,
67
+ grantId: session.grantId,
68
+ requestedScope: input.scope,
69
+ serverOwner: deps.serverOwner,
70
+ }, deps.policyPorts);
71
+ // releaseProof is a rollback hook for the handler, never part of the
72
+ // stored attribution record.
73
+ const { releaseProof, ...attribution } = await verifyWriterAttribution({
74
+ request: input.request,
75
+ builderAddress: session.builderAddress,
76
+ grantId: grant.id,
77
+ serverOrigin: deps.serverOrigin,
78
+ replayStore: deps.replayStore,
79
+ now: deps.now,
80
+ });
81
+ return {
82
+ builder: session.builderAddress,
83
+ grantId: grant.id,
84
+ grantScopes: grant.scopes ?? [],
85
+ attribution,
86
+ releaseProof,
87
+ };
88
+ },
89
+ async recognizeWriteSession(request) {
90
+ const session = await liveSession(request);
91
+ if (!session)
92
+ return undefined;
93
+ const { releaseProof } = await verifyWriterAttribution({
94
+ request,
95
+ builderAddress: session.builderAddress,
96
+ grantId: session.grantId,
97
+ serverOrigin: deps.serverOrigin,
98
+ replayStore: deps.replayStore,
99
+ now: deps.now,
100
+ });
101
+ return {
102
+ builder: session.builderAddress,
103
+ grantId: session.grantId,
104
+ releaseProof,
105
+ };
106
+ },
107
+ };
108
+ }
109
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/write/authorize.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
1
  export { createInMemoryWriteProofReplayStore, createInMemoryWriteSessionStore, createWriteSession, hashWriteSessionToken, type CreateWriteSessionInput, type CreateWriteSessionOptions, type CreateWriteSessionResult, type WriteProofReplayStore, type WriteSessionRecord, type WriteSessionStore, } from "./session.js";
2
+ export { createWriteSessionAuthorization, writeServerNotConfigured, writeSessionBearerToken, type WriteSessionAuthorization, type WriteSessionAuthorizationDeps, } from "./authorize.js";
3
+ export { handleWriteSessionRequest, type HandleWriteSessionRequestDeps, type WriteSessionResponseBody, type WriteSessionRouteLogger, } from "./route.js";
2
4
  export { WRITE_SIGNATURE_HEADER, WRITER_ATTRIBUTION_KEY, binaryWriteSignedBytes, hasReservedWriterKey, stampWriterAttribution, verifyStoredWriterAttribution, verifyWriterAttribution, WriterAttributionVerificationError, type BinaryWriteSignedBytesInput, type StoredWriterAttributionVerification, type VerifiedWriterAttribution, type VerifyStoredWriterAttributionOptions, type VerifyWriterAttributionInput, type WriterAttribution, } from "./attribution.js";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/write/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,EACnC,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,kCAAkC,EAClC,KAAK,2BAA2B,EAChC,KAAK,mCAAmC,EACxC,KAAK,yBAAyB,EAC9B,KAAK,oCAAoC,EACzC,KAAK,4BAA4B,EACjC,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/write/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,EACnC,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,EACxB,uBAAuB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,GACnC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,kCAAkC,EAClC,KAAK,2BAA2B,EAChC,KAAK,mCAAmC,EACxC,KAAK,yBAAyB,EAC9B,KAAK,oCAAoC,EACzC,KAAK,4BAA4B,EACjC,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC"}
@@ -1,3 +1,5 @@
1
1
  export { createInMemoryWriteProofReplayStore, createInMemoryWriteSessionStore, createWriteSession, hashWriteSessionToken, } from "./session.js";
2
+ export { createWriteSessionAuthorization, writeServerNotConfigured, writeSessionBearerToken, } from "./authorize.js";
3
+ export { handleWriteSessionRequest, } from "./route.js";
2
4
  export { WRITE_SIGNATURE_HEADER, WRITER_ATTRIBUTION_KEY, binaryWriteSignedBytes, hasReservedWriterKey, stampWriterAttribution, verifyStoredWriterAttribution, verifyWriterAttribution, WriterAttributionVerificationError, } from "./attribution.js";
3
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/write/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,EACnC,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,GAOtB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,kCAAkC,GAOnC,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/write/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,EACnC,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,GAOtB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,EACxB,uBAAuB,GAGxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,GAI1B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,kCAAkC,GAOnC,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The write-session handshake, as a runtime-agnostic request handler.
3
+ *
4
+ * `POST /v1/write/session`: a registered builder proves control of its key and
5
+ * of a write-grant with a Web3Signed proof (the same handshake shape as the
6
+ * self-signing MCP session) and gets a short-lived bearer token. That token
7
+ * then gates delegated writes on the EXISTING ingest endpoint
8
+ * (`POST /v1/data/:scope`) and the derivative question routes. The Personal
9
+ * Server keeps signing AddData as the owner; the builder never holds the owner
10
+ * key.
11
+ *
12
+ * This lived in the Node server as a Hono route (packages/server/src/routes/
13
+ * write-session.ts) and reached for `node:crypto` for two things: a sha-256 of
14
+ * the proof header, and 32 random bytes for the token. Both have Web Crypto
15
+ * equivalents, so the handshake is expressed here over plain `Request` /
16
+ * `Response` and BOTH builds mount the same handler — the Node route is a Hono
17
+ * wrapper around it, and the browser runtime dispatches to it directly. There
18
+ * is one implementation of who may open a write session.
19
+ */
20
+ import type { SessionTokenVerifierPort } from "../auth/index.js";
21
+ import type { AuthSessionVerifierPort, GrantVerifierPort } from "../ports/index.js";
22
+ import { type WriteProofReplayStore, type WriteSessionStore } from "./session.js";
23
+ export interface WriteSessionRouteLogger {
24
+ info?(payload: Record<string, unknown>, message: string): void;
25
+ error?(payload: Record<string, unknown>, message: string): void;
26
+ }
27
+ export interface HandleWriteSessionRequestDeps {
28
+ serverOrigin: string | (() => string);
29
+ serverOwner?: `0x${string}`;
30
+ /**
31
+ * The store the redeeming routes read from. One store is wired into the
32
+ * handshake and into the API auth adapter; see createApp / the PS-Lite
33
+ * runtime.
34
+ */
35
+ sessionStore: WriteSessionStore;
36
+ /** Replay guard for the HANDSHAKE proof (distinct from per-write proofs). */
37
+ replayStore?: WriteProofReplayStore;
38
+ authSessionVerifier: AuthSessionVerifierPort;
39
+ grantVerifier: GrantVerifierPort;
40
+ devToken?: string;
41
+ accessToken?: string;
42
+ tokenStore?: SessionTokenVerifierPort;
43
+ /** Overridable for tests; defaults to `vana_write_` + 32 random bytes. */
44
+ randomToken?: () => string;
45
+ ttlMs?: number;
46
+ now?: () => number;
47
+ logger?: WriteSessionRouteLogger;
48
+ }
49
+ export interface WriteSessionResponseBody {
50
+ access_token: string;
51
+ token_type: "Bearer";
52
+ expires_in: number;
53
+ /** Write patterns, `write:` prefix stripped. */
54
+ scope: string;
55
+ }
56
+ /**
57
+ * Handle one `POST /v1/write/session` request. The caller has already matched
58
+ * the method and path; anything reaching here is treated as the handshake.
59
+ */
60
+ export declare function handleWriteSessionRequest(request: Request, deps: HandleWriteSessionRequestDeps): Promise<Response>;
61
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/write/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAGjE,OAAO,KAAK,EACV,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/D,KAAK,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,WAAW,6BAA6B;IAC5C,YAAY,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,YAAY,EAAE,iBAAiB,CAAC;IAChC,6EAA6E;IAC7E,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,aAAa,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,wBAAwB,CAAC;IACtC,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,uBAAuB,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,QAAQ,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;CACf;AA2CD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,QAAQ,CAAC,CA4GnB"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The write-session handshake, as a runtime-agnostic request handler.
3
+ *
4
+ * `POST /v1/write/session`: a registered builder proves control of its key and
5
+ * of a write-grant with a Web3Signed proof (the same handshake shape as the
6
+ * self-signing MCP session) and gets a short-lived bearer token. That token
7
+ * then gates delegated writes on the EXISTING ingest endpoint
8
+ * (`POST /v1/data/:scope`) and the derivative question routes. The Personal
9
+ * Server keeps signing AddData as the owner; the builder never holds the owner
10
+ * key.
11
+ *
12
+ * This lived in the Node server as a Hono route (packages/server/src/routes/
13
+ * write-session.ts) and reached for `node:crypto` for two things: a sha-256 of
14
+ * the proof header, and 32 random bytes for the token. Both have Web Crypto
15
+ * equivalents, so the handshake is expressed here over plain `Request` /
16
+ * `Response` and BOTH builds mount the same handler — the Node route is a Hono
17
+ * wrapper around it, and the browser runtime dispatches to it directly. There
18
+ * is one implementation of who may open a write session.
19
+ */
20
+ import { authenticateRequest } from "../auth/index.js";
21
+ import { ProtocolError } from "../errors/catalog.js";
22
+ import { hashConnectionToken } from "../mcp/connection-api.js";
23
+ import { createWriteSession, } from "./session.js";
24
+ function jsonError(status, errorCode, message) {
25
+ return { error: { code: status, errorCode, message } };
26
+ }
27
+ function jsonResponse(body, status) {
28
+ return new Response(JSON.stringify(body), {
29
+ status,
30
+ headers: { "Content-Type": "application/json" },
31
+ });
32
+ }
33
+ /**
34
+ * Only ProtocolErrors are client-facing. Anything else (gateway, store,
35
+ * runtime failures) is logged with its cause and answered generically so
36
+ * internal details never reach the caller. Same mapping as the core data
37
+ * handler.
38
+ */
39
+ function internalError(logger, err, stage) {
40
+ logger?.error?.({ err, stage }, "Write session handshake failed with an unexpected error");
41
+ return jsonResponse(jsonError(500, "INTERNAL_ERROR", "Internal server error"), 500);
42
+ }
43
+ function defaultRandomToken() {
44
+ const bytes = new Uint8Array(32);
45
+ globalThis.crypto.getRandomValues(bytes);
46
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
47
+ return `vana_write_${hex}`;
48
+ }
49
+ /**
50
+ * Handle one `POST /v1/write/session` request. The caller has already matched
51
+ * the method and path; anything reaching here is treated as the handshake.
52
+ */
53
+ export async function handleWriteSessionRequest(request, deps) {
54
+ // Fail closed: minting a session requires this server's owner to bind the
55
+ // grant to (createWriteSession rejects wrong-owner grants).
56
+ if (!deps.serverOwner) {
57
+ return jsonResponse(jsonError(500, "SERVER_NOT_CONFIGURED", "Server owner is not configured"), 500);
58
+ }
59
+ let authResult;
60
+ try {
61
+ authResult = await authenticateRequest({
62
+ request,
63
+ serverOrigin: deps.serverOrigin,
64
+ devToken: deps.devToken,
65
+ accessToken: deps.accessToken,
66
+ sessionTokenVerifier: deps.tokenStore,
67
+ serverOwner: deps.serverOwner,
68
+ });
69
+ }
70
+ catch (err) {
71
+ if (!(err instanceof ProtocolError)) {
72
+ return internalError(deps.logger, err, "authenticate");
73
+ }
74
+ return jsonResponse(jsonError(401, "WRITE_SESSION_AUTH_FAILED", err.message), 401);
75
+ }
76
+ // A bearer credential (dev token, control-plane token, CLI session) proves
77
+ // nothing about the builder's key, so it can never open a write session.
78
+ if (authResult.mechanism !== "web3-signed") {
79
+ return jsonResponse(jsonError(401, "WRITE_SESSION_PROOF_REQUIRED", "POST /v1/write/session requires a Web3Signed proof signed by the builder key"), 401);
80
+ }
81
+ const grantId = authResult.auth.payload.grantId;
82
+ if (!grantId) {
83
+ return jsonResponse(jsonError(400, "GRANT_ID_REQUIRED", "The Web3Signed proof must carry a grantId (the write-grant issued to the builder)"), 400);
84
+ }
85
+ // Replay guard: bind to a digest of the exact proof header, remembered
86
+ // until the proof's own expiry (a replay only matters while still valid).
87
+ const proofHeader = request.headers.get("authorization") ?? "";
88
+ const proofId = await hashConnectionToken(proofHeader);
89
+ const expSec = authResult.auth.payload.exp;
90
+ const expiresAtMs = (typeof expSec === "number"
91
+ ? expSec
92
+ : Math.floor(Date.now() / 1000) + 300) * 1000;
93
+ try {
94
+ const session = await createWriteSession({
95
+ builderAddress: authResult.auth.signer,
96
+ grantId,
97
+ proof: { id: proofId, expiresAtMs },
98
+ }, {
99
+ store: deps.sessionStore,
100
+ authSessionVerifier: deps.authSessionVerifier,
101
+ grantVerifier: deps.grantVerifier,
102
+ serverOwner: deps.serverOwner,
103
+ randomToken: deps.randomToken ?? defaultRandomToken,
104
+ ...(deps.ttlMs === undefined ? {} : { ttlMs: deps.ttlMs }),
105
+ ...(deps.now === undefined ? {} : { now: deps.now }),
106
+ ...(deps.replayStore === undefined
107
+ ? {}
108
+ : { replayStore: deps.replayStore }),
109
+ });
110
+ deps.logger?.info?.({
111
+ builder: authResult.auth.signer,
112
+ grantId: session.grantId,
113
+ writeScopes: session.writeScopes,
114
+ }, "Write session minted");
115
+ const body = {
116
+ access_token: session.accessToken,
117
+ token_type: "Bearer",
118
+ expires_in: session.expiresInSeconds,
119
+ // Write patterns, prefix stripped — what POST /v1/data/:scope will
120
+ // accept under this token.
121
+ scope: session.writeScopes.join(" "),
122
+ };
123
+ return jsonResponse(body, 200);
124
+ }
125
+ catch (err) {
126
+ if (err instanceof ProtocolError) {
127
+ return jsonResponse(err.toJSON(), err.code);
128
+ }
129
+ return internalError(deps.logger, err, "create-session");
130
+ }
131
+ }
132
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/write/route.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendatalabs/personal-server-ts-core",
3
- "version": "1.10.2",
3
+ "version": "1.11.0",
4
4
  "description": "Protocol logic for the Vana Personal Server — auth, grants, scopes, storage, keys",
5
5
  "license": "MIT",
6
6
  "repository": {