@opendatalabs/personal-server-ts-server 1.33.8 → 1.34.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.
Files changed (167) hide show
  1. package/dist/__fixtures__/bound-record-store.d.ts +19 -0
  2. package/dist/__fixtures__/bound-record-store.d.ts.map +1 -0
  3. package/dist/__fixtures__/bound-record-store.js +60 -0
  4. package/dist/__fixtures__/bound-record-store.js.map +1 -0
  5. package/dist/__fixtures__/cg-pdpp-client/pdpp-authorization-strategy.d.ts +41 -0
  6. package/dist/__fixtures__/cg-pdpp-client/pdpp-authorization-strategy.d.ts.map +1 -0
  7. package/dist/__fixtures__/cg-pdpp-client/pdpp-authorization-strategy.js +34 -0
  8. package/dist/__fixtures__/cg-pdpp-client/pdpp-authorization-strategy.js.map +1 -0
  9. package/dist/__fixtures__/cg-pdpp-client/pdpp-context-client.d.ts +112 -0
  10. package/dist/__fixtures__/cg-pdpp-client/pdpp-context-client.d.ts.map +1 -0
  11. package/dist/__fixtures__/cg-pdpp-client/pdpp-context-client.js +226 -0
  12. package/dist/__fixtures__/cg-pdpp-client/pdpp-context-client.js.map +1 -0
  13. package/dist/__fixtures__/cg-pdpp-client/pdpp-types.d.ts +135 -0
  14. package/dist/__fixtures__/cg-pdpp-client/pdpp-types.d.ts.map +1 -0
  15. package/dist/__fixtures__/cg-pdpp-client/pdpp-types.js +21 -0
  16. package/dist/__fixtures__/cg-pdpp-client/pdpp-types.js.map +1 -0
  17. package/dist/api-auth.d.ts +18 -2
  18. package/dist/api-auth.d.ts.map +1 -1
  19. package/dist/api-auth.js +114 -22
  20. package/dist/api-auth.js.map +1 -1
  21. package/dist/app.d.ts +60 -1
  22. package/dist/app.d.ts.map +1 -1
  23. package/dist/app.js +139 -1
  24. package/dist/app.js.map +1 -1
  25. package/dist/bootstrap.d.ts +10 -0
  26. package/dist/bootstrap.d.ts.map +1 -1
  27. package/dist/bootstrap.js +358 -7
  28. package/dist/bootstrap.js.map +1 -1
  29. package/dist/enclave-main.d.ts +4 -0
  30. package/dist/enclave-main.d.ts.map +1 -1
  31. package/dist/enclave-main.js +152 -1
  32. package/dist/enclave-main.js.map +1 -1
  33. package/dist/middleware/body-limit.d.ts +1 -1
  34. package/dist/middleware/body-limit.d.ts.map +1 -1
  35. package/dist/middleware/body-limit.js +8 -3
  36. package/dist/middleware/body-limit.js.map +1 -1
  37. package/dist/middleware/cors.d.ts.map +1 -1
  38. package/dist/middleware/cors.js +1 -0
  39. package/dist/middleware/cors.js.map +1 -1
  40. package/dist/package.json +9 -1
  41. package/dist/pdpp/bootstrap.d.ts +72 -0
  42. package/dist/pdpp/bootstrap.d.ts.map +1 -0
  43. package/dist/pdpp/bootstrap.js +270 -0
  44. package/dist/pdpp/bootstrap.js.map +1 -0
  45. package/dist/pdpp/chain-enforcement.d.ts +95 -0
  46. package/dist/pdpp/chain-enforcement.d.ts.map +1 -0
  47. package/dist/pdpp/chain-enforcement.js +145 -0
  48. package/dist/pdpp/chain-enforcement.js.map +1 -0
  49. package/dist/pdpp/client-document-fetch.d.ts +63 -0
  50. package/dist/pdpp/client-document-fetch.d.ts.map +1 -0
  51. package/dist/pdpp/client-document-fetch.js +254 -0
  52. package/dist/pdpp/client-document-fetch.js.map +1 -0
  53. package/dist/pdpp/declaration-registry.d.ts +83 -0
  54. package/dist/pdpp/declaration-registry.d.ts.map +1 -0
  55. package/dist/pdpp/declaration-registry.js +251 -0
  56. package/dist/pdpp/declaration-registry.js.map +1 -0
  57. package/dist/pdpp/deployment.d.ts +70 -0
  58. package/dist/pdpp/deployment.d.ts.map +1 -0
  59. package/dist/pdpp/deployment.js +104 -0
  60. package/dist/pdpp/deployment.js.map +1 -0
  61. package/dist/pdpp/record-schema.d.ts +2 -0
  62. package/dist/pdpp/record-schema.d.ts.map +1 -0
  63. package/dist/pdpp/record-schema.js +2 -0
  64. package/dist/pdpp/record-schema.js.map +1 -0
  65. package/dist/pdpp/records-bootstrap.d.ts +111 -0
  66. package/dist/pdpp/records-bootstrap.d.ts.map +1 -0
  67. package/dist/pdpp/records-bootstrap.js +353 -0
  68. package/dist/pdpp/records-bootstrap.js.map +1 -0
  69. package/dist/pdpp/source-id-compat.d.ts +5 -0
  70. package/dist/pdpp/source-id-compat.d.ts.map +1 -0
  71. package/dist/pdpp/source-id-compat.js +16 -0
  72. package/dist/pdpp/source-id-compat.js.map +1 -0
  73. package/dist/pdpp/test-connections.d.ts +6 -0
  74. package/dist/pdpp/test-connections.d.ts.map +1 -0
  75. package/dist/pdpp/test-connections.js +37 -0
  76. package/dist/pdpp/test-connections.js.map +1 -0
  77. package/dist/routes/auth-device.d.ts.map +1 -1
  78. package/dist/routes/auth-device.js +3 -0
  79. package/dist/routes/auth-device.js.map +1 -1
  80. package/dist/routes/chunked-scope-import.d.ts +41 -0
  81. package/dist/routes/chunked-scope-import.d.ts.map +1 -0
  82. package/dist/routes/chunked-scope-import.js +901 -0
  83. package/dist/routes/chunked-scope-import.js.map +1 -0
  84. package/dist/routes/data.d.ts +23 -1
  85. package/dist/routes/data.d.ts.map +1 -1
  86. package/dist/routes/data.js +112 -1
  87. package/dist/routes/data.js.map +1 -1
  88. package/dist/routes/grants.d.ts.map +1 -1
  89. package/dist/routes/grants.js +2 -0
  90. package/dist/routes/grants.js.map +1 -1
  91. package/dist/routes/mcp.d.ts.map +1 -1
  92. package/dist/routes/mcp.js +3 -0
  93. package/dist/routes/mcp.js.map +1 -1
  94. package/dist/routes/pdpp-auth.d.ts +157 -0
  95. package/dist/routes/pdpp-auth.d.ts.map +1 -0
  96. package/dist/routes/pdpp-auth.js +953 -0
  97. package/dist/routes/pdpp-auth.js.map +1 -0
  98. package/dist/routes/pdpp-blobs.d.ts +26 -0
  99. package/dist/routes/pdpp-blobs.d.ts.map +1 -0
  100. package/dist/routes/pdpp-blobs.js +179 -0
  101. package/dist/routes/pdpp-blobs.js.map +1 -0
  102. package/dist/routes/pdpp-connections.d.ts +28 -0
  103. package/dist/routes/pdpp-connections.d.ts.map +1 -0
  104. package/dist/routes/pdpp-connections.js +144 -0
  105. package/dist/routes/pdpp-connections.js.map +1 -0
  106. package/dist/routes/pdpp-declarations.d.ts +58 -0
  107. package/dist/routes/pdpp-declarations.d.ts.map +1 -0
  108. package/dist/routes/pdpp-declarations.js +94 -0
  109. package/dist/routes/pdpp-declarations.js.map +1 -0
  110. package/dist/routes/pdpp-instance-bindings.d.ts +26 -0
  111. package/dist/routes/pdpp-instance-bindings.d.ts.map +1 -0
  112. package/dist/routes/pdpp-instance-bindings.js +124 -0
  113. package/dist/routes/pdpp-instance-bindings.js.map +1 -0
  114. package/dist/routes/pdpp-records.d.ts +158 -0
  115. package/dist/routes/pdpp-records.d.ts.map +1 -0
  116. package/dist/routes/pdpp-records.js +1537 -0
  117. package/dist/routes/pdpp-records.js.map +1 -0
  118. package/dist/routes/pdpp-well-known.d.ts +13 -0
  119. package/dist/routes/pdpp-well-known.d.ts.map +1 -0
  120. package/dist/routes/pdpp-well-known.js +20 -0
  121. package/dist/routes/pdpp-well-known.js.map +1 -0
  122. package/dist/storage/declaration-witness.d.ts +56 -0
  123. package/dist/storage/declaration-witness.d.ts.map +1 -0
  124. package/dist/storage/declaration-witness.js +273 -0
  125. package/dist/storage/declaration-witness.js.map +1 -0
  126. package/dist/storage/hierarchy.d.ts +25 -1
  127. package/dist/storage/hierarchy.d.ts.map +1 -1
  128. package/dist/storage/hierarchy.js +210 -16
  129. package/dist/storage/hierarchy.js.map +1 -1
  130. package/dist/storage/import-operations.d.ts +11 -0
  131. package/dist/storage/import-operations.d.ts.map +1 -0
  132. package/dist/storage/import-operations.js +261 -0
  133. package/dist/storage/import-operations.js.map +1 -0
  134. package/dist/storage/index-manager.d.ts +24 -2
  135. package/dist/storage/index-manager.d.ts.map +1 -1
  136. package/dist/storage/index-manager.js +279 -30
  137. package/dist/storage/index-manager.js.map +1 -1
  138. package/dist/storage/index-schema.d.ts +1 -1
  139. package/dist/storage/index-schema.d.ts.map +1 -1
  140. package/dist/storage/index-schema.js +189 -8
  141. package/dist/storage/index-schema.js.map +1 -1
  142. package/dist/storage/legacy-scope-authority.d.ts +14 -0
  143. package/dist/storage/legacy-scope-authority.d.ts.map +1 -0
  144. package/dist/storage/legacy-scope-authority.js +298 -0
  145. package/dist/storage/legacy-scope-authority.js.map +1 -0
  146. package/dist/storage/node-data-storage.d.ts +6 -0
  147. package/dist/storage/node-data-storage.d.ts.map +1 -1
  148. package/dist/storage/node-data-storage.js +161 -2
  149. package/dist/storage/node-data-storage.js.map +1 -1
  150. package/dist/storage/pdpp-grant-binding-sqlite-store.d.ts +17 -0
  151. package/dist/storage/pdpp-grant-binding-sqlite-store.d.ts.map +1 -0
  152. package/dist/storage/pdpp-grant-binding-sqlite-store.js +159 -0
  153. package/dist/storage/pdpp-grant-binding-sqlite-store.js.map +1 -0
  154. package/dist/storage/pdpp-records-sqlite-store.d.ts +112 -0
  155. package/dist/storage/pdpp-records-sqlite-store.d.ts.map +1 -0
  156. package/dist/storage/pdpp-records-sqlite-store.js +1440 -0
  157. package/dist/storage/pdpp-records-sqlite-store.js.map +1 -0
  158. package/dist/storage/pdpp-stream-completion.d.ts +5 -0
  159. package/dist/storage/pdpp-stream-completion.d.ts.map +1 -0
  160. package/dist/storage/pdpp-stream-completion.js +10 -0
  161. package/dist/storage/pdpp-stream-completion.js.map +1 -0
  162. package/dist/storage/scope-deletion-history.d.ts +8 -0
  163. package/dist/storage/scope-deletion-history.d.ts.map +1 -0
  164. package/dist/storage/scope-deletion-history.js +40 -0
  165. package/dist/storage/scope-deletion-history.js.map +1 -0
  166. package/dist/ui/ps-lite-debug.js +3644 -869
  167. package/package.json +12 -4
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Boot the PDPP Authorization Server for a real Personal Server.
3
+ *
4
+ * This is the seam between "the AS exists as a library" and "this running
5
+ * server is a PDPP authorization server". It opens the persistent store,
6
+ * derives the declaration trust policy from what the deployment actually
7
+ * serves, and hands the route module the owner-proof wiring so consent
8
+ * decisions authenticate against the same wallet signature every other owner
9
+ * route uses.
10
+ *
11
+ * Returns undefined when PDPP is disabled and no connection registry is
12
+ * configured. It also returns undefined when no declarations or connection
13
+ * methods remain, so `/pdpp/v1` is mounted only when it can issue tokens.
14
+ */
15
+ import { createHash } from "node:crypto";
16
+ import { readFile } from "node:fs/promises";
17
+ import { join, resolve } from "node:path";
18
+ import { AuthorizationSessionStore, declaredSourceId, openPdppAuthStore, PdppTokenService, resolveUrlHostedClientIdentity, UnsupportedAuthStateError, } from "@opendatalabs/personal-server-ts-core/pdpp";
19
+ import { boundedClientDocumentFetcher } from "./client-document-fetch.js";
20
+ import { resolveRetainedSourceId } from "./source-id-compat.js";
21
+ import { deriveSupportedConnectors, } from "./deployment.js";
22
+ import { openDeclarationRegistry, } from "./declaration-registry.js";
23
+ export async function createPdppAuthDeps(options) {
24
+ const { config, logger } = options;
25
+ const hasConnectionRegistry = (config.pdpp.connectionMethods?.length ?? 0) > 0;
26
+ if (!config.pdpp.enabled && !hasConnectionRegistry)
27
+ return undefined;
28
+ // The AS binds grants to a subject and authenticates decisions against the
29
+ // server owner. Without a known owner there is no subject to bind to and no
30
+ // signature to check, so refuse to mount rather than invent either.
31
+ if (!options.serverOwner) {
32
+ logger.warn("PDPP enabled but no server owner is configured — authorization server not mounted");
33
+ return undefined;
34
+ }
35
+ // The connector inventory is what this PS holds. It gates runtime
36
+ // submissions only. A configured declaration is admitted without it: the
37
+ // operator (Desktop, after verifying the signed artifact) chose it, and a
38
+ // fresh server that holds no legacy data yet must still mount it.
39
+ const { scopes } = options.indexManager.listDistinctScopes();
40
+ const supportedConnectors = deriveSupportedConnectors(scopes.map((s) => s.scope));
41
+ const declarations = await readDeclarations(config.pdpp.enabled ? config.pdpp.declarationPaths : [], logger);
42
+ const declarationSourceByPath = new Map(declarations
43
+ .filter((declaration) => declaration.path)
44
+ .map((declaration) => [resolve(declaration.path), declaration.sourceId]));
45
+ const configuredMethods = (config.pdpp.enabled ? config.pdpp.methods : []).flatMap((method) => {
46
+ const sourceId = declarationSourceByPath.get(resolve(method.declaration_path));
47
+ if (!sourceId) {
48
+ logger.warn({
49
+ declarationPath: method.declaration_path,
50
+ methodId: method.method_id,
51
+ }, "PDPP method does not reference a retained declaration — method is inactive");
52
+ return [];
53
+ }
54
+ return [{ sourceId, methodId: method.method_id }];
55
+ });
56
+ const connectionMethods = new Map();
57
+ for (const method of config.pdpp.connectionMethods ?? []) {
58
+ const methods = connectionMethods.get(method.source_id) ?? [];
59
+ methods.push(method.method_id);
60
+ connectionMethods.set(method.source_id, methods);
61
+ }
62
+ const connectionSourceIds = new Set(connectionMethods.keys());
63
+ // The durable registry the submission route writes to, seeded from the
64
+ // configured paths. Seeds and submissions share one validator; only
65
+ // submissions are held to the connector gate. A submitted declaration
66
+ // becomes resolvable in this lifetime and survives the next restart.
67
+ const declarationRegistry = openDeclarationRegistry({
68
+ path: join(options.storageRoot, "pdpp-declarations.db"),
69
+ supportedConnectors,
70
+ logger,
71
+ seed: declarations.map((d) => ({
72
+ sourceId: d.sourceId,
73
+ document: d.document,
74
+ })),
75
+ });
76
+ const registry = {
77
+ resolve: declarationRegistry.resolve,
78
+ retained: config.pdpp.enabled ? declarationRegistry.list() : [],
79
+ retainedDocuments: new Map(declarationRegistry
80
+ .list()
81
+ .map((snapshot) => [
82
+ snapshot.source_id,
83
+ declarationRegistry.documentFor(snapshot.source_id) ?? "",
84
+ ])),
85
+ };
86
+ const retainedSourceIds = new Set(registry.retained.map((declaration) => declaration.source_id));
87
+ const activeConfiguredMethods = configuredMethods.filter((method) => retainedSourceIds.has(method.sourceId));
88
+ for (const method of activeConfiguredMethods) {
89
+ const methods = connectionMethods.get(method.sourceId) ?? [];
90
+ if (!methods.includes(method.methodId))
91
+ methods.push(method.methodId);
92
+ connectionMethods.set(method.sourceId, methods);
93
+ }
94
+ for (const sourceId of connectionMethods.keys()) {
95
+ connectionSourceIds.add(sourceId);
96
+ }
97
+ if (registry.retained.length === 0 && connectionSourceIds.size === 0) {
98
+ logger.warn({ supportedConnectors, configured: config.pdpp.declarationPaths.length }, "PDPP enabled but no declarations were retained — authorization server not mounted");
99
+ return undefined;
100
+ }
101
+ let store;
102
+ try {
103
+ store = openPdppAuthStore(join(options.storageRoot, "pdpp-auth.db"));
104
+ }
105
+ catch (err) {
106
+ if (err instanceof UnsupportedAuthStateError) {
107
+ // §9 AS item 21: persisted authorization state whose contract this build
108
+ // cannot validate must not be reinterpreted. Refusing to mount is the
109
+ // conservative half of "migrate or require fresh consent" — the server
110
+ // keeps running, but it issues nothing against state it cannot read.
111
+ logger.error({ err: err.message }, "PDPP authorization state is unsupported — authorization server not mounted");
112
+ return undefined;
113
+ }
114
+ throw err;
115
+ }
116
+ const tokens = new PdppTokenService(store);
117
+ const sessions = new AuthorizationSessionStore();
118
+ const subjectId = options.serverOwner.toLowerCase();
119
+ logger.info({
120
+ declarations: registry.retained.map((d) => d.source_id),
121
+ supportedConnectors,
122
+ requirePkce: config.pdpp.requirePkce,
123
+ }, "PDPP Authorization Server mounted at /pdpp/v1");
124
+ return {
125
+ logger,
126
+ store,
127
+ tokens,
128
+ sessions,
129
+ retainedDeclarations: registry.retained,
130
+ retainedDocuments: registry.retainedDocuments,
131
+ configuredMethods: activeConfiguredMethods,
132
+ connectionMethods,
133
+ resolveConnectionSourceId: (requested) => resolveRetainedSourceId(requested, connectionSourceIds),
134
+ resolveDeclaration: config.pdpp.enabled ? registry.resolve : () => null,
135
+ declarationRegistry,
136
+ supportedConnectors,
137
+ // The AS's own issuer identity, for its metadata document. Carried as the
138
+ // same thunk-or-string `serverOrigin` already is, so a deployment that
139
+ // resolves its origin late still advertises the live one.
140
+ issuer: options.serverOrigin,
141
+ // The records bootstrap replaces this with a live registry lookup once
142
+ // the shared SQLite store is available. Until then, no connection is
143
+ // eligible for consent.
144
+ inventoryFor: () => ({ eligibleFor: () => [] }),
145
+ /**
146
+ * Bind the session's subject to an *authenticated* owner.
147
+ *
148
+ * This PS is single-owner, so there is only ever one subject — but "there
149
+ * is one subject" and "any caller may open a session bound to it" are
150
+ * different claims, and the second is the one that was wrong. Previously
151
+ * this ignored the request entirely and returned the configured owner, so
152
+ * an unauthenticated caller could create sessions against the owner's
153
+ * subject: consent-screen spam and unbounded session allocation, even
154
+ * though approval still required a real owner token.
155
+ *
156
+ * Now the caller must present an active PDPP owner token, and the subject
157
+ * it resolves to must be the configured owner. Single-owner stays a
158
+ * deployment property; it stops being an authentication bypass.
159
+ */
160
+ currentSubjectId: (c) => {
161
+ const header = c.req.header("authorization");
162
+ if (!header?.toLowerCase().startsWith("bearer "))
163
+ return null;
164
+ const presented = header.slice(7).trim();
165
+ if (presented.length === 0)
166
+ return null;
167
+ const context = tokens.resolveToken(presented);
168
+ if (!context.active || context.tokenKind !== "owner")
169
+ return null;
170
+ // A token for some other subject is not authority here, even though a
171
+ // single-owner deployment should never mint one.
172
+ if (context.subjectId !== subjectId)
173
+ return null;
174
+ return subjectId;
175
+ },
176
+ // Redirect targets are validated by exact match against this. A client
177
+ // absent from the config gets null, which fails the request closed — an
178
+ // unregistered client cannot receive an authorization code.
179
+ registeredClient: (clientId) => {
180
+ const registered = config.pdpp.clients.find((candidate) => candidate.clientId === clientId);
181
+ return registered
182
+ ? {
183
+ client_id: registered.clientId,
184
+ redirect_uris: registered.redirectUris,
185
+ }
186
+ : null;
187
+ },
188
+ // Operator grant-lifetime policy, per registered client. Only a client
189
+ // with a `grantLifetimeSeconds` in config gets a computed expiry; every
190
+ // other client (including one resolved via `resolveClientIdentity`) gets
191
+ // `undefined` here, which preserves the existing no-expiry behavior
192
+ // exactly. This is deployment policy read from static config, never from
193
+ // the request — there is no client-supplied expiry field to honor.
194
+ grantExpiryFor: (_request, clientId) => {
195
+ const registered = config.pdpp.clients.find((candidate) => candidate.clientId === clientId);
196
+ const lifetimeSeconds = registered?.grantLifetimeSeconds;
197
+ if (!lifetimeSeconds)
198
+ return undefined;
199
+ return new Date(Date.now() + lifetimeSeconds * 1000).toISOString();
200
+ },
201
+ // §6 URL-hosted identity for a client NOT in `clients` above. Left
202
+ // unset (undefined, not a function that always fails) when the
203
+ // allowlist is empty, so this deployment performs no outbound fetch and
204
+ // the route's registration-only fallback runs exactly as it did before
205
+ // this path existed.
206
+ resolveClientIdentity: config.pdpp.urlHostedClientHosts.length > 0
207
+ ? (clientId) => resolveUrlHostedClientIdentity({
208
+ clientId,
209
+ fetcher: boundedClientDocumentFetcher,
210
+ policy: { trustedHosts: config.pdpp.urlHostedClientHosts },
211
+ })
212
+ : undefined,
213
+ requirePkce: config.pdpp.requirePkce,
214
+ ownerAuth: {
215
+ serverOrigin: options.serverOrigin,
216
+ serverOwner: options.serverOwner,
217
+ devToken: options.devToken,
218
+ accessToken: options.accessToken,
219
+ tokenStore: options.tokenStore,
220
+ },
221
+ // The verified signer is the owner; normalize to the same subject the
222
+ // grants use so a token minted here resolves sessions created above.
223
+ ownerSubjectId: () => subjectId,
224
+ };
225
+ }
226
+ /**
227
+ * Read each configured declaration document.
228
+ *
229
+ * A path that cannot be read is logged and skipped rather than fatal: one
230
+ * bad declaration should not stop a server booting, and the registry will
231
+ * refuse to mount if nothing survives.
232
+ */
233
+ async function readDeclarations(entries, logger) {
234
+ const out = [];
235
+ for (const entry of entries) {
236
+ const path = typeof entry === "string" ? entry : entry.path;
237
+ try {
238
+ const document = await readFile(path, "utf-8");
239
+ if (typeof entry !== "string") {
240
+ const actual = createHash("sha256")
241
+ .update(document, "utf8")
242
+ .digest("hex");
243
+ if (actual !== entry.sha256) {
244
+ logger.warn({ path, expected: entry.sha256, actual }, "PDPP declaration does not match its pinned digest — skipped");
245
+ continue;
246
+ }
247
+ }
248
+ // The source id is the declaration's own claim; `parseDeclaration`
249
+ // cross-checks it, so read it here only to key the retention.
250
+ //
251
+ // Asking Core for the id rather than picking a field keeps the boot path
252
+ // shape-agnostic. Reading `source_id` directly meant a normative §5
253
+ // document -- which nests it under `source.id` -- was skipped here before
254
+ // the parser (which handles both) ever saw it, so the AS never mounted
255
+ // and the only evidence was a warning about a missing field the document
256
+ // was never supposed to have.
257
+ const sourceId = declaredSourceId(document);
258
+ if (sourceId === null) {
259
+ logger.warn({ path }, "PDPP declaration declares no source id — skipped");
260
+ continue;
261
+ }
262
+ out.push({ sourceId, document, path });
263
+ }
264
+ catch (err) {
265
+ logger.warn({ path, err: err.message }, "PDPP declaration could not be read — skipped");
266
+ }
267
+ }
268
+ return out;
269
+ }
270
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/pdpp/bootstrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Enforcing the Vana chain permission on PDPP client reads.
3
+ *
4
+ * ## The gap this closes
5
+ *
6
+ * A PDPP access token proves the owner consented. It does not prove the Vana
7
+ * permission that authorizes the read is still live. Those are two authorities
8
+ * over two different objects, and the bearer read path checked only the first:
9
+ * it resolved a locally-issued token in-process and served records. So a
10
+ * permission revoked on chain kept reading fine through PDPP, and the ledger's
11
+ * authority was silently not enforced for any PDPP client.
12
+ *
13
+ * This adapter is the join. It looks up the retained binding for the presented
14
+ * PDPP grant, reads the chain grant live, and hands both to
15
+ * `verifyPdppGrantBinding`, which checks every axis at once: the record, the
16
+ * deployment, the owner, the grantee app, and revocation.
17
+ *
18
+ * ## Why the verification is not re-implemented here
19
+ *
20
+ * `packages/core/src/grants/pdpp-binding.ts` already does it, and correctly:
21
+ * bindings are keyed by the chain's own identity tuple rather than a derived
22
+ * hash, the record is append-only, and revocation is read live instead of
23
+ * mirrored into storage. Re-deriving any of that here would create a second
24
+ * opinion about what a valid binding is, and the two would drift. This file
25
+ * only supplies inputs and translates the outcome.
26
+ *
27
+ * `verifyPdppGrantBinding` checks revocation but NOT expiry — that axis is
28
+ * added here, immediately after it passes, using the same
29
+ * `parseGrantExpiresAtSeconds` the legacy chain-grant read path
30
+ * (`verifyDataReadPolicy`) uses. Without it, this bearer path would silently
31
+ * keep serving an expired-but-not-revoked grant that the legacy path already
32
+ * denies — a gap, not a design choice.
33
+ *
34
+ * ## Fail closed, and the retryable distinction
35
+ *
36
+ * Every denial path answers "no". The only judgment this file makes is whether
37
+ * a "no" is *definite* or merely *unknown*:
38
+ *
39
+ * - No binding, wrong owner, wrong grantee, revoked, expired → definite.
40
+ * Asking again changes nothing.
41
+ * - Gateway unreachable, RPC timeout, malformed response → unknown.
42
+ *
43
+ * Unknown must still deny. Not knowing is not permission, and an outage that
44
+ * opened reads would be an authorization bypass that nobody notices, because
45
+ * the reads all look successful. What unknown gets is a *retryable* answer, so
46
+ * the route can say "try again" rather than "your grant is dead" to a client
47
+ * whose grant is probably fine.
48
+ *
49
+ * A missing binding is deliberately in the definite-denial bucket, not the
50
+ * unknown one. "Not bound yet" and "never bound" are indistinguishable from
51
+ * here, so treating absence as a temporary condition would let an unbound
52
+ * grant read for as long as a caller kept retrying.
53
+ */
54
+ import { type PdppGrantBindingStore } from "@opendatalabs/personal-server-ts-core/grants";
55
+ import type { GatewayGrantResponse } from "@opendatalabs/vana-sdk/browser";
56
+ import type { Logger } from "pino";
57
+ import type { PdppChainEnforcementPort } from "../routes/pdpp-records.js";
58
+ export interface ChainEnforcementOptions {
59
+ /** Retained bindings, written at grant creation. */
60
+ bindings: PdppGrantBindingStore;
61
+ /**
62
+ * Reads the chain grant as it stands NOW. Returning null means "this
63
+ * permission does not exist", which `verifyPdppGrantBinding` treats as
64
+ * revoked; THROWING means "I could not find out", which is retryable.
65
+ *
66
+ * The two are different answers and must not be collapsed: a gateway that
67
+ * returns null on a network error would turn every outage into a permanent
68
+ * revocation for every client.
69
+ */
70
+ readChainGrant(permissionId: string): Promise<GatewayGrantResponse | null>;
71
+ /** The owner this server is configured for, at read time. */
72
+ serverOwner: `0x${string}`;
73
+ /** The deployment the PS is configured against. */
74
+ deployment: {
75
+ chainId: number;
76
+ contractAddress: `0x${string}`;
77
+ };
78
+ /**
79
+ * The app's grantee wallet address for a given PDPP `client_id`.
80
+ *
81
+ * Resolved per request rather than taken from the binding, because this is
82
+ * the half that proves the CALLER is the bound app rather than merely
83
+ * knowing its grant id. Returning null denies.
84
+ */
85
+ granteeAddressFor(clientId: string): `0x${string}` | null;
86
+ logger: Logger;
87
+ /**
88
+ * Deterministic clock for expiry comparisons. Defaults to the real clock;
89
+ * tests inject a fixed value so expiry assertions aren't a race against
90
+ * wall time.
91
+ */
92
+ now?: () => Date;
93
+ }
94
+ export declare function chainPermissionEnforcement(options: ChainEnforcementOptions): PdppChainEnforcementPort;
95
+ //# sourceMappingURL=chain-enforcement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain-enforcement.d.ts","sourceRoot":"","sources":["../../src/pdpp/chain-enforcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,8CAA8C,CAAC;AAEtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAEV,wBAAwB,EACzB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,uBAAuB;IACtC,oDAAoD;IACpD,QAAQ,EAAE,qBAAqB,CAAC;IAChC;;;;;;;;OAQG;IACH,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC3E,6DAA6D;IAC7D,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3B,mDAAmD;IACnD,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,KAAK,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE;;;;;;OAMG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,IAAI,CAAC;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,uBAAuB,GAC/B,wBAAwB,CAmG1B"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Enforcing the Vana chain permission on PDPP client reads.
3
+ *
4
+ * ## The gap this closes
5
+ *
6
+ * A PDPP access token proves the owner consented. It does not prove the Vana
7
+ * permission that authorizes the read is still live. Those are two authorities
8
+ * over two different objects, and the bearer read path checked only the first:
9
+ * it resolved a locally-issued token in-process and served records. So a
10
+ * permission revoked on chain kept reading fine through PDPP, and the ledger's
11
+ * authority was silently not enforced for any PDPP client.
12
+ *
13
+ * This adapter is the join. It looks up the retained binding for the presented
14
+ * PDPP grant, reads the chain grant live, and hands both to
15
+ * `verifyPdppGrantBinding`, which checks every axis at once: the record, the
16
+ * deployment, the owner, the grantee app, and revocation.
17
+ *
18
+ * ## Why the verification is not re-implemented here
19
+ *
20
+ * `packages/core/src/grants/pdpp-binding.ts` already does it, and correctly:
21
+ * bindings are keyed by the chain's own identity tuple rather than a derived
22
+ * hash, the record is append-only, and revocation is read live instead of
23
+ * mirrored into storage. Re-deriving any of that here would create a second
24
+ * opinion about what a valid binding is, and the two would drift. This file
25
+ * only supplies inputs and translates the outcome.
26
+ *
27
+ * `verifyPdppGrantBinding` checks revocation but NOT expiry — that axis is
28
+ * added here, immediately after it passes, using the same
29
+ * `parseGrantExpiresAtSeconds` the legacy chain-grant read path
30
+ * (`verifyDataReadPolicy`) uses. Without it, this bearer path would silently
31
+ * keep serving an expired-but-not-revoked grant that the legacy path already
32
+ * denies — a gap, not a design choice.
33
+ *
34
+ * ## Fail closed, and the retryable distinction
35
+ *
36
+ * Every denial path answers "no". The only judgment this file makes is whether
37
+ * a "no" is *definite* or merely *unknown*:
38
+ *
39
+ * - No binding, wrong owner, wrong grantee, revoked, expired → definite.
40
+ * Asking again changes nothing.
41
+ * - Gateway unreachable, RPC timeout, malformed response → unknown.
42
+ *
43
+ * Unknown must still deny. Not knowing is not permission, and an outage that
44
+ * opened reads would be an authorization bypass that nobody notices, because
45
+ * the reads all look successful. What unknown gets is a *retryable* answer, so
46
+ * the route can say "try again" rather than "your grant is dead" to a client
47
+ * whose grant is probably fine.
48
+ *
49
+ * A missing binding is deliberately in the definite-denial bucket, not the
50
+ * unknown one. "Not bound yet" and "never bound" are indistinguishable from
51
+ * here, so treating absence as a temporary condition would let an unbound
52
+ * grant read for as long as a caller kept retrying.
53
+ */
54
+ import { verifyPdppGrantBinding, } from "@opendatalabs/personal-server-ts-core/grants";
55
+ import { parseGrantExpiresAtSeconds } from "@opendatalabs/personal-server-ts-core/policy";
56
+ export function chainPermissionEnforcement(options) {
57
+ const { bindings, deployment, logger, serverOwner } = options;
58
+ return {
59
+ async authorize({ clientId, pdppGrantId }) {
60
+ const binding = bindings.getByPdppGrantId(pdppGrantId);
61
+ if (!binding) {
62
+ // Definite: absence of a binding is not a pending state.
63
+ return {
64
+ ok: false,
65
+ reason: "No chain binding for this grant",
66
+ retryable: false,
67
+ };
68
+ }
69
+ const granteeAddress = options.granteeAddressFor(clientId);
70
+ if (!granteeAddress) {
71
+ return {
72
+ ok: false,
73
+ reason: "No grantee address for this client",
74
+ retryable: false,
75
+ };
76
+ }
77
+ let chainGrant;
78
+ try {
79
+ chainGrant = await options.readChainGrant(binding.permission.permissionId);
80
+ }
81
+ catch (err) {
82
+ // Unknown, not denied. Logged at warn because a persistent unknown is
83
+ // an outage worth paging on, and it is invisible in the response.
84
+ logger.warn({
85
+ err: err.message,
86
+ permissionId: binding.permission.permissionId,
87
+ pdppGrantId,
88
+ }, "PDPP chain enforcement: could not read chain grant");
89
+ return {
90
+ ok: false,
91
+ reason: "Chain grant could not be read",
92
+ retryable: true,
93
+ };
94
+ }
95
+ try {
96
+ verifyPdppGrantBinding({
97
+ binding,
98
+ chainGrant,
99
+ deployment,
100
+ pdppGrantId,
101
+ // The caller proves it is the app by being the bound grantee. On the
102
+ // bearer path the token IS the proof of which client is calling, so
103
+ // the resolved address for that client_id stands in for a signature.
104
+ requestSigner: granteeAddress,
105
+ serverOwner,
106
+ });
107
+ }
108
+ catch (err) {
109
+ // Every failure here is definite: owner mismatch, grantee mismatch,
110
+ // wrong deployment, or revocation. None improve on retry.
111
+ return {
112
+ ok: false,
113
+ reason: err.message,
114
+ retryable: false,
115
+ };
116
+ }
117
+ // `verifyPdppGrantBinding` does not check expiry — it only checks
118
+ // revocation. The legacy `/v1/data/:scope` chain-grant read path
119
+ // (`verifyDataReadPolicy`) enforces `grant.expiresAt`; this bearer path
120
+ // must not silently skip a check that path already makes. `chainGrant`
121
+ // is non-null here (a null grant already returned above via
122
+ // `verifyPdppGrantBinding`'s "not found = revoked" check).
123
+ const expiresAtSec = parseGrantExpiresAtSeconds(chainGrant.expiresAt);
124
+ if (expiresAtSec === null) {
125
+ return {
126
+ ok: false,
127
+ reason: "Grant expiry is invalid",
128
+ retryable: false,
129
+ };
130
+ }
131
+ if (expiresAtSec > 0) {
132
+ const nowSec = Math.floor((options.now?.() ?? new Date()).getTime() / 1000);
133
+ if (expiresAtSec < nowSec) {
134
+ return {
135
+ ok: false,
136
+ reason: "Grant has expired",
137
+ retryable: false,
138
+ };
139
+ }
140
+ }
141
+ return { ok: true };
142
+ },
143
+ };
144
+ }
145
+ //# sourceMappingURL=chain-enforcement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain-enforcement.js","sourceRoot":"","sources":["../../src/pdpp/chain-enforcement.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Bounded production fetcher for a URL-hosted client identity document
3
+ * (Core §6). Implements `ClientDocumentFetcher` from
4
+ * `@opendatalabs/personal-server-ts-core/pdpp`.
5
+ *
6
+ * Invariants:
7
+ * - HTTPS only.
8
+ * - Every address actually dialed is checked: a hostname's DNS answers via
9
+ * a custom `lookup` on the `undici` dispatcher (defends DNS rebinding —
10
+ * `net`/`tls`/`undici` dial exactly the address `lookup` returns, so
11
+ * there is no separate resolution to race), and a literal-IP URL
12
+ * hostname separately, since that never reaches `dns.lookup` at all.
13
+ * - No redirect is followed; a redirect or an oversized/rejected body is
14
+ * drained (`discardBody`), never left dangling on the socket.
15
+ * - One `AbortSignal`/timeout spans connect + headers + body + cleanup.
16
+ * - The body is read incrementally and dropped past the byte cap.
17
+ * - No credentials are forwarded.
18
+ */
19
+ import { Agent } from "undici";
20
+ import type { ClientDocumentFetcher } from "@opendatalabs/personal-server-ts-core/pdpp";
21
+ export declare const FETCH_TIMEOUT_MS = 5000;
22
+ export declare const MAX_RESPONSE_BYTES: number;
23
+ /** Thrown by the connect-time guard; surfaces as a `fetch_failed` result. */
24
+ export declare class BlockedAddressError extends Error {
25
+ }
26
+ export declare function isBlockedAddress(address: string): boolean;
27
+ /**
28
+ * Build a dispatcher that only ever connects to an address that passes the
29
+ * guard, bundled with the exact predicate it was built with so a caller
30
+ * can't pass a mismatched one to the pre-dial literal-IP check in
31
+ * {@link createBoundedFetcher}. `tlsOpts` is exposed solely so tests can
32
+ * trust a local self-signed certificate; `isBlocked` lets tests run against
33
+ * loopback without disabling the guard mechanism itself. Production code
34
+ * must call {@link boundedClientDocumentFetcher}, which passes neither.
35
+ */
36
+ declare function buildGuardedAgent(tlsOpts?: {
37
+ ca?: string | Buffer;
38
+ isBlocked?: (address: string) => boolean;
39
+ }): {
40
+ dispatcher: Agent;
41
+ isBlocked: (address: string) => boolean;
42
+ };
43
+ /**
44
+ * Build a bounded `ClientDocumentFetcher`. HTTPS-only, no redirects, one
45
+ * connect+headers+body timeout, a streamed byte cap, and address validation
46
+ * on every DNS answer *and* on a literal-IP hostname (which never reaches
47
+ * `dns.lookup`, so the dispatcher's guarded `lookup` alone would miss it).
48
+ *
49
+ * Takes {@link buildGuardedAgent}'s return value as a whole (not just its
50
+ * `dispatcher`) so the pre-dial literal-IP check always uses the exact same
51
+ * predicate the dispatcher's `lookup` guard was built with — there is no
52
+ * call shape that lets the two diverge.
53
+ */
54
+ declare function createBoundedFetcher({ dispatcher, isBlocked, }: ReturnType<typeof buildGuardedAgent>): ClientDocumentFetcher;
55
+ /** The production fetcher: no test-only overrides reach this path. */
56
+ export declare const boundedClientDocumentFetcher: ClientDocumentFetcher;
57
+ /** Test-only seam: same bounds, with a dispatcher that trusts a test CA. */
58
+ export declare const __testing: {
59
+ createBoundedFetcher: typeof createBoundedFetcher;
60
+ buildGuardedAgent: typeof buildGuardedAgent;
61
+ };
62
+ export {};
63
+ //# sourceMappingURL=client-document-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-document-fetch.d.ts","sourceRoot":"","sources":["../../src/pdpp/client-document-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAAE,KAAK,EAAwB,MAAM,QAAQ,CAAC;AAGrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAExF,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AACtC,eAAO,MAAM,kBAAkB,QAAY,CAAC;AAE5C,6EAA6E;AAC7E,qBAAa,mBAAoB,SAAQ,KAAK;CAAG;AAyCjD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAIzD;AAwED;;;;;;;;GAQG;AACH,iBAAS,iBAAiB,CAAC,OAAO,CAAC,EAAE;IACnC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;CAC1C,GAAG;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,CAQjE;AAwCD;;;;;;;;;;GAUG;AACH,iBAAS,oBAAoB,CAAC,EAC5B,UAAU,EACV,SAAS,GACV,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,GAAG,qBAAqB,CAwD9D;AAmBD,sEAAsE;AACtE,eAAO,MAAM,4BAA4B,EAAE,qBACZ,CAAC;AAEhC,4EAA4E;AAC5E,eAAO,MAAM,SAAS;;;CAA8C,CAAC"}