@dwk/solid-pod 0.1.0-beta.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 (68) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +108 -0
  3. package/dist/auth.d.ts +33 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +160 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/config.d.ts +181 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +74 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/encoding.d.ts +13 -0
  12. package/dist/encoding.d.ts.map +1 -0
  13. package/dist/encoding.js +31 -0
  14. package/dist/encoding.js.map +1 -0
  15. package/dist/gc.d.ts +22 -0
  16. package/dist/gc.d.ts.map +1 -0
  17. package/dist/gc.js +33 -0
  18. package/dist/gc.js.map +1 -0
  19. package/dist/handler.d.ts +20 -0
  20. package/dist/handler.d.ts.map +1 -0
  21. package/dist/handler.js +155 -0
  22. package/dist/handler.js.map +1 -0
  23. package/dist/index.d.ts +24 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +23 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/jwt.d.ts +36 -0
  28. package/dist/jwt.d.ts.map +1 -0
  29. package/dist/jwt.js +120 -0
  30. package/dist/jwt.js.map +1 -0
  31. package/dist/ldp.d.ts +37 -0
  32. package/dist/ldp.d.ts.map +1 -0
  33. package/dist/ldp.js +85 -0
  34. package/dist/ldp.js.map +1 -0
  35. package/dist/log.d.ts +55 -0
  36. package/dist/log.d.ts.map +1 -0
  37. package/dist/log.js +51 -0
  38. package/dist/log.js.map +1 -0
  39. package/dist/negotiation.d.ts +23 -0
  40. package/dist/negotiation.d.ts.map +1 -0
  41. package/dist/negotiation.js +80 -0
  42. package/dist/negotiation.js.map +1 -0
  43. package/dist/patch.d.ts +80 -0
  44. package/dist/patch.d.ts.map +1 -0
  45. package/dist/patch.js +425 -0
  46. package/dist/patch.js.map +1 -0
  47. package/dist/pod.d.ts +20 -0
  48. package/dist/pod.d.ts.map +1 -0
  49. package/dist/pod.js +860 -0
  50. package/dist/pod.js.map +1 -0
  51. package/dist/wac.d.ts +33 -0
  52. package/dist/wac.d.ts.map +1 -0
  53. package/dist/wac.js +84 -0
  54. package/dist/wac.js.map +1 -0
  55. package/package.json +55 -0
  56. package/src/auth.ts +203 -0
  57. package/src/config.ts +254 -0
  58. package/src/encoding.ts +32 -0
  59. package/src/gc.ts +47 -0
  60. package/src/handler.ts +199 -0
  61. package/src/index.ts +32 -0
  62. package/src/jwt.ts +166 -0
  63. package/src/ldp.ts +99 -0
  64. package/src/log.ts +59 -0
  65. package/src/negotiation.ts +97 -0
  66. package/src/patch.ts +539 -0
  67. package/src/pod.ts +1195 -0
  68. package/src/wac.ts +119 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 David W. Keith
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # `@dwk/solid-pod`
2
+
3
+ > Edge-native Solid Pod: LDP verbs, content negotiation, N3 Patch, WAC, notifications. Ships the per-pod Durable Object.
4
+
5
+ Part of the [`@dwk` IndieWeb + Solid cohort](../../README.md). See the
6
+ [package specification](../../spec/packages/solid-pod.md) for the full requirements.
7
+
8
+ An edge-native [Solid](https://solidproject.org/TR/protocol) Pod: a stateless
9
+ Worker front door over a per-pod **Durable Object** that is the consistency,
10
+ authorization, and notification authority, with **R2** for blob bodies. This is
11
+ the only `@dwk` package that ships a Durable Object. It composes the reusable
12
+ libraries [`@dwk/dpop`](../dpop) (edge DPoP validation), [`@dwk/rdf`](../rdf)
13
+ (Turtle/JSON-LD), [`@dwk/wac`](../wac) (access control), and
14
+ [`@dwk/store`](../store) (DO-SQLite quads + R2 copy-on-write blobs).
15
+
16
+ ## What it does
17
+
18
+ - **LDP** — `GET / HEAD / OPTIONS / PUT / POST / PATCH / DELETE` with resource
19
+ and basic-container semantics (`ldp:contains`).
20
+ - **Content negotiation** — Turtle and JSON-LD (plus the other Turtle-family
21
+ types) on read, via `@dwk/rdf`.
22
+ - **N3 Patch / `application/sparql-update`** — `solid:where` is matched against
23
+ the current graph with minimal (non-SPARQL) semantics: no exact single
24
+ binding ⇒ **409**; `deletes` then `inserts` apply in one SQLite transaction.
25
+ - **Web Access Control** — walks to the nearest effective `.acl`
26
+ (`acl:accessTo` / `acl:default`), evaluating `Read`/`Write`/`Append`/`Control`,
27
+ agents, groups, `acl:agentClass foaf:Agent`, and `acl:origin` via `@dwk/wac`.
28
+ `Append` authorizes insert-only patches; any delete requires `Write`. The pod
29
+ `owner` always has full access (bootstraps `.acl` management).
30
+ - **Auth (Resource Server)** — DPoP-bound bearer tokens validated at the edge
31
+ (issuer JWKS pinned by `kid`, header `typ: at+jwt`, `aud`/`exp`/`nbf`/`webid`,
32
+ proof `htu`/`htm`/`ath`/`cnf.jkt`). Strict
33
+ single-use `jti` replay is enforced in the DO for **writes**, pruned by expiry;
34
+ reads do not consume a `jti` (a documented tradeoff).
35
+ - **Concurrency** — all writes funnel through the single-threaded DO; the
36
+ `If-Match` / `If-None-Match` (create-only) check and the write are
37
+ TOCTOU-free, evaluated inside the store's write transaction. Deleting a
38
+ non-empty container is likewise rejected inside that transaction.
39
+ - **Oversized / binary bodies** — content-addressed R2 copy-on-write with an
40
+ atomic DO pointer flip; orphaned keys are reclaimed by an out-of-band GC cron
41
+ (`createSolidPodGc`), never by waking a DO.
42
+ - **Notifications** — Solid Notifications over WebSocket channels on the DO's
43
+ hibernatable WebSockets (v1 channels carry the changed resource IRI only).
44
+
45
+ v1 is a **Resource Server only** (no OIDC OP) and runs **one Durable Object per
46
+ pod** (no sharding).
47
+
48
+ ## Usage
49
+
50
+ ```ts
51
+ import { createSolidPod, createSolidPodGc, SolidPodObject } from "@dwk/solid-pod";
52
+
53
+ const pod = createSolidPod({
54
+ baseUrl: "https://pod.example",
55
+ issuer: "https://issuer.example",
56
+ jwksUri: "https://issuer.example/jwks",
57
+ owner: "https://pod.example/profile/card#me",
58
+ });
59
+
60
+ const gc = createSolidPodGc({
61
+ baseUrl: "https://pod.example",
62
+ gcSafetyWindowMs: 300_000,
63
+ });
64
+
65
+ export default {
66
+ fetch: pod,
67
+ scheduled: gc,
68
+ };
69
+
70
+ // Bind the per-pod Durable Object class in your Worker.
71
+ export { SolidPodObject };
72
+ ```
73
+
74
+ ### Required bindings & `wrangler.toml`
75
+
76
+ ```toml
77
+ compatibility_date = "2025-01-01"
78
+ # N3.js (via @dwk/rdf) uses Node's stream/buffer; the pod runs the parser in the DO.
79
+ compatibility_flags = ["nodejs_compat"]
80
+
81
+ [[durable_objects.bindings]]
82
+ name = "POD"
83
+ class_name = "SolidPodObject"
84
+
85
+ [[migrations]]
86
+ tag = "v1"
87
+ new_sqlite_classes = ["SolidPodObject"]
88
+
89
+ [[r2_buckets]]
90
+ binding = "BLOBS"
91
+ bucket_name = "solid-pod-blobs"
92
+
93
+ # Optional: shared D1 table for out-of-band R2 garbage collection.
94
+ [[d1_databases]]
95
+ binding = "GC_DB"
96
+ database_name = "solid-pod-gc"
97
+
98
+ [triggers]
99
+ crons = ["*/5 * * * *"]
100
+ ```
101
+
102
+ The `POD` (Durable Object) and `BLOBS` (R2) bindings are required; the handler
103
+ **fails loudly** at startup if either is missing. `GC_DB` (D1) is optional — when
104
+ bound, the DO forwards orphaned blob keys to it for the GC cron to reclaim.
105
+
106
+ ## License
107
+
108
+ [ISC](../../LICENSE)
package/dist/auth.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Edge authentication for the Resource Server: validate a DPoP-bound bearer
3
+ * token and its proof at the Worker front door, before any request reaches the
4
+ * per-pod Durable Object.
5
+ *
6
+ * A request with no credentials is allowed through unauthenticated — WAC then
7
+ * decides whether the resource is public. A request that *presents* credentials
8
+ * must pass fully or it is rejected `401`: the token signature (issuer JWKS),
9
+ * the `iss` / `aud` / `exp` / `webid` claims, and the RFC 9449 DPoP proof
10
+ * binding (`htu` / `htm` / `ath` / `cnf.jkt`) via `@dwk/dpop`.
11
+ */
12
+ import type { AuthContext, ResolvedConfig } from "./config";
13
+ /** A stable reason an authentication attempt failed (for `WWW-Authenticate`). */
14
+ export type AuthFailureReason = "no_jwks" | "token_malformed" | "token_type_invalid" | "signature_invalid" | "issuer_mismatch" | "audience_mismatch" | "token_expired" | "token_not_yet_valid" | "webid_missing" | "cnf_missing" | "dpop_missing" | "dpop_invalid";
15
+ /** Outcome of {@link authenticate}: a context, an explicit failure, or "no creds". */
16
+ export type AuthResult = {
17
+ readonly kind: "authenticated";
18
+ readonly context: AuthContext;
19
+ } | {
20
+ readonly kind: "anonymous";
21
+ } | {
22
+ readonly kind: "rejected";
23
+ readonly reason: AuthFailureReason;
24
+ };
25
+ /**
26
+ * Authenticate a request at the edge.
27
+ *
28
+ * Returns `anonymous` when no credentials are presented, `authenticated` with
29
+ * the verified {@link AuthContext} on success, and `rejected` with a stable
30
+ * reason on any failure of a *presented* credential.
31
+ */
32
+ export declare function authenticate(request: Request, config: ResolvedConfig): Promise<AuthResult>;
33
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG5D,iFAAiF;AACjF,MAAM,MAAM,iBAAiB,GACzB,SAAS,GACT,iBAAiB,GACjB,oBAAoB,GACpB,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,GACnB,eAAe,GACf,qBAAqB,GACrB,eAAe,GACf,aAAa,GACb,cAAc,GACd,cAAc,CAAC;AAEnB,sFAAsF;AACtF,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GACjE;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAuEtE;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,UAAU,CAAC,CAqFrB"}
package/dist/auth.js ADDED
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Edge authentication for the Resource Server: validate a DPoP-bound bearer
3
+ * token and its proof at the Worker front door, before any request reaches the
4
+ * per-pod Durable Object.
5
+ *
6
+ * A request with no credentials is allowed through unauthenticated — WAC then
7
+ * decides whether the resource is public. A request that *presents* credentials
8
+ * must pass fully or it is rejected `401`: the token signature (issuer JWKS),
9
+ * the `iss` / `aud` / `exp` / `webid` claims, and the RFC 9449 DPoP proof
10
+ * binding (`htu` / `htm` / `ath` / `cnf.jkt`) via `@dwk/dpop`.
11
+ */
12
+ import { verifyDpopProof } from "@dwk/dpop";
13
+ import { decodeJwt, verifyJwtSignature } from "./jwt";
14
+ const JWKS_TTL_MS = 5 * 60 * 1000;
15
+ const jwksCache = new Map();
16
+ /** Resolve the issuer verification keys: static config first, then cached fetch. */
17
+ async function resolveJwks(config) {
18
+ if (config.jwks && config.jwks.length > 0)
19
+ return config.jwks;
20
+ if (!config.jwksUri)
21
+ return null;
22
+ const now = config.now();
23
+ const cached = jwksCache.get(config.jwksUri);
24
+ if (cached && now - cached.fetchedAt < JWKS_TTL_MS)
25
+ return cached.keys;
26
+ try {
27
+ const response = await config.fetch(config.jwksUri);
28
+ if (!response.ok)
29
+ return cached?.keys ?? null;
30
+ const body = (await response.json());
31
+ // Only cache a well-formed JWKS; caching an empty/garbled body would poison
32
+ // verification for the whole TTL and discard the last good keys.
33
+ if (!Array.isArray(body.keys))
34
+ return cached?.keys ?? null;
35
+ jwksCache.set(config.jwksUri, { keys: body.keys, fetchedAt: now });
36
+ return body.keys;
37
+ }
38
+ catch {
39
+ // A transient fetch failure falls back to the last good keys if we have
40
+ // them, rather than failing closed on every request mid-outage.
41
+ return cached?.keys ?? null;
42
+ }
43
+ }
44
+ /** Extract the `DPoP <token>` (or `Bearer <token>`) value, if present. */
45
+ function bearerToken(request) {
46
+ const header = request.headers.get("authorization");
47
+ if (!header)
48
+ return null;
49
+ const match = /^(DPoP|Bearer)\s+(.+)$/i.exec(header.trim());
50
+ return match ? match[2] : null;
51
+ }
52
+ /**
53
+ * Whether the token header's `typ` is the required access-token type. Compared
54
+ * case-insensitively and tolerant of the `application/at+jwt` media-type form,
55
+ * since RFC 9068 permits either the full media type or its `at+jwt` short form.
56
+ */
57
+ function tokenTypeMatches(typ, required) {
58
+ if (typeof typ !== "string")
59
+ return false;
60
+ const normalize = (value) => {
61
+ const lower = value.toLowerCase();
62
+ return lower.startsWith("application/")
63
+ ? lower.slice("application/".length)
64
+ : lower;
65
+ };
66
+ return normalize(typ) === normalize(required);
67
+ }
68
+ /** Whether the token's `aud` claim intersects the accepted audience set. */
69
+ function audienceMatches(aud, accepted) {
70
+ const values = Array.isArray(aud)
71
+ ? aud.filter((a) => typeof a === "string")
72
+ : typeof aud === "string"
73
+ ? [aud]
74
+ : [];
75
+ return values.some((a) => accepted.includes(a));
76
+ }
77
+ /**
78
+ * Authenticate a request at the edge.
79
+ *
80
+ * Returns `anonymous` when no credentials are presented, `authenticated` with
81
+ * the verified {@link AuthContext} on success, and `rejected` with a stable
82
+ * reason on any failure of a *presented* credential.
83
+ */
84
+ export async function authenticate(request, config) {
85
+ // A deployer-supplied hook fully replaces the built-in verifier.
86
+ if (config.authenticate) {
87
+ const context = await config.authenticate(request);
88
+ return context ? { kind: "authenticated", context } : { kind: "anonymous" };
89
+ }
90
+ const token = bearerToken(request);
91
+ if (!token)
92
+ return { kind: "anonymous" };
93
+ const decoded = decodeJwt(token);
94
+ if (!decoded)
95
+ return { kind: "rejected", reason: "token_malformed" };
96
+ const jwks = await resolveJwks(config);
97
+ if (!jwks || jwks.length === 0)
98
+ return { kind: "rejected", reason: "no_jwks" };
99
+ if (!(await verifyJwtSignature(decoded, jwks))) {
100
+ return { kind: "rejected", reason: "signature_invalid" };
101
+ }
102
+ // Enforce the access-token `typ` (`at+jwt`) so an ID token or other
103
+ // issuer-signed JWT sharing this `iss`/`aud`/`webid` cannot be replayed as an
104
+ // access token. Skipped when the deployer opts out via `accessTokenType: null`.
105
+ if (config.accessTokenType !== null &&
106
+ !tokenTypeMatches(decoded.header.typ, config.accessTokenType)) {
107
+ return { kind: "rejected", reason: "token_type_invalid" };
108
+ }
109
+ const { iss, aud, exp, nbf, webid, sub, cnf } = decoded.payload;
110
+ if (config.issuer !== undefined && iss !== config.issuer) {
111
+ return { kind: "rejected", reason: "issuer_mismatch" };
112
+ }
113
+ if (!audienceMatches(aud, config.audience)) {
114
+ return { kind: "rejected", reason: "audience_mismatch" };
115
+ }
116
+ const now = Math.floor(config.now() / 1000);
117
+ if (typeof exp !== "number" || now >= exp) {
118
+ return { kind: "rejected", reason: "token_expired" };
119
+ }
120
+ // Honor `nbf` when present: a token is not valid before its not-before time.
121
+ // Per RFC 7519 §4.1.5 `nbf` MUST be a number; a present-but-malformed value is
122
+ // rejected rather than silently bypassed (mirrors the `exp` handling above).
123
+ if (nbf !== undefined && (typeof nbf !== "number" || now < nbf)) {
124
+ return { kind: "rejected", reason: "token_not_yet_valid" };
125
+ }
126
+ // Solid-OIDC carries the agent identity in `webid`; fall back to `sub`.
127
+ const agent = typeof webid === "string"
128
+ ? webid
129
+ : typeof sub === "string"
130
+ ? sub
131
+ : undefined;
132
+ if (agent === undefined)
133
+ return { kind: "rejected", reason: "webid_missing" };
134
+ const jkt = typeof cnf === "object" &&
135
+ cnf !== null &&
136
+ typeof cnf.jkt === "string"
137
+ ? cnf.jkt
138
+ : undefined;
139
+ if (jkt === undefined)
140
+ return { kind: "rejected", reason: "cnf_missing" };
141
+ const proof = request.headers.get("dpop");
142
+ if (!proof)
143
+ return { kind: "rejected", reason: "dpop_missing" };
144
+ const dpop = await verifyDpopProof({
145
+ proof,
146
+ htm: request.method,
147
+ htu: request.url,
148
+ accessToken: token,
149
+ expectedJkt: jkt,
150
+ now,
151
+ });
152
+ if (!dpop.valid || !dpop.jti || !dpop.jkt) {
153
+ return { kind: "rejected", reason: "dpop_invalid" };
154
+ }
155
+ return {
156
+ kind: "authenticated",
157
+ context: { webid: agent, jti: dpop.jti, jkt: dpop.jkt },
158
+ };
159
+ }
160
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Configuration, the declared Cloudflare `Env` fragment, and config resolution
3
+ * for `@dwk/solid-pod`.
4
+ *
5
+ * Per the composition contract, the package never reads the global environment
6
+ * directly: all tunables (base URL, token issuer/JWKS, accepted audience,
7
+ * offload threshold, replay/GC windows) are passed into {@link createSolidPod}
8
+ * and {@link createSolidPodGc}, so a pod can be instantiated multiple times and
9
+ * unit-tested in isolation. The Cloudflare bindings — the per-pod Durable
10
+ * Object namespace and the R2 blob bucket — are the only runtime coupling, and
11
+ * a missing one fails loudly at startup.
12
+ */
13
+ import { type Logger, type Metrics } from "@dwk/log";
14
+ import type { SolidPodObject } from "./pod";
15
+ /** Cloudflare bindings required by the Solid Pod handler and Durable Object. */
16
+ export interface SolidPodEnv {
17
+ /** Durable Object namespace for the per-pod class ({@link SolidPodObject}). */
18
+ readonly POD: DurableObjectNamespace<SolidPodObject>;
19
+ /** R2 bucket holding blob bodies. */
20
+ readonly BLOBS: R2Bucket;
21
+ /**
22
+ * Shared D1 database tracking orphaned blob keys for the out-of-band GC cron.
23
+ * Optional: when bound, the DO opportunistically forwards its transactional
24
+ * orphan outbox here after writes so {@link createSolidPodGc} can reclaim them
25
+ * without ever waking a Durable Object.
26
+ */
27
+ readonly GC_DB?: D1Database;
28
+ }
29
+ /** Cloudflare bindings required by the out-of-band R2 garbage-collection cron. */
30
+ export interface SolidPodGcEnv {
31
+ /** R2 bucket holding blob bodies. */
32
+ readonly BLOBS: R2Bucket;
33
+ /** Shared D1 database tracking orphaned blob keys (see `@dwk/store`). */
34
+ readonly GC_DB: D1Database;
35
+ }
36
+ /**
37
+ * A verification key set used to validate issuer-signed access tokens. Supply
38
+ * either static {@link SolidPodConfig.jwks} (hermetic, no network) or a
39
+ * {@link SolidPodConfig.jwksUri} the handler fetches and caches.
40
+ */
41
+ export type Jwks = readonly JsonWebKey[];
42
+ /** Configuration passed to {@link createSolidPod}. */
43
+ export interface SolidPodConfig {
44
+ /**
45
+ * The pod's identity root / base URL, e.g. `https://pod.example`. Used as the
46
+ * origin for absolute resource IRIs in WAC and content negotiation, and as
47
+ * the default token audience. No trailing slash.
48
+ */
49
+ readonly baseUrl: string;
50
+ /**
51
+ * The pod owner's WebID(s). An owner is always granted full access
52
+ * (`Read`/`Write`/`Append`/`Control`) regardless of ACLs, which bootstraps
53
+ * ACL management — without it, no one could create the first `.acl`.
54
+ */
55
+ readonly owner?: string | readonly string[];
56
+ /**
57
+ * Accepted access-token issuer (`iss`). When set, a token whose `iss` differs
58
+ * is rejected. Required unless a custom {@link SolidPodConfig.authenticate}
59
+ * hook is supplied.
60
+ */
61
+ readonly issuer?: string;
62
+ /**
63
+ * Accepted token audience(s) (`aud`). A token is accepted when its `aud`
64
+ * intersects this set. Defaults to `["solid", baseUrl]`.
65
+ */
66
+ readonly audience?: string | readonly string[];
67
+ /**
68
+ * Required access-token header `typ`. Solid-OIDC / RFC 9068 access tokens
69
+ * carry `typ: at+jwt`; enforcing it stops an ID token or other issuer-signed
70
+ * JWT sharing the same `iss`/`aud`/`webid` from being replayed as an access
71
+ * token (token-type confusion). Compared case-insensitively, tolerating the
72
+ * `application/at+jwt` media-type form. Set to `null` to skip the check for
73
+ * issuers that omit `typ`. Defaults to `"at+jwt"`.
74
+ */
75
+ readonly accessTokenType?: string | null;
76
+ /** Static JWK verification keys for the issuer (hermetic alternative to {@link jwksUri}). */
77
+ readonly jwks?: Jwks;
78
+ /** Issuer JWKS endpoint; fetched and cached when {@link jwks} is not given. */
79
+ readonly jwksUri?: string;
80
+ /**
81
+ * Bodies larger than this (bytes) are offloaded to R2 as opaque blobs instead
82
+ * of the DO SQLite quad store. Defaults to the ~2 MB DO-cell ceiling.
83
+ */
84
+ readonly maxInlineBytes?: number;
85
+ /**
86
+ * The documented read-replay tradeoff: reads MAY reuse a DPoP proof within
87
+ * this many seconds (edge-cached window) rather than enforcing strict
88
+ * single-use `jti`. Writes are always strict. Defaults to `0` (strict reads).
89
+ */
90
+ readonly readReplayWindowSeconds?: number;
91
+ /**
92
+ * Allow **unauthenticated** writes (`PUT`/`POST`/`PATCH`/`DELETE`) when WAC
93
+ * grants the public agent class (`acl:agentClass foaf:Agent`) the needed
94
+ * mode. Such requests carry no DPoP proof, so they get **no `jti` replay /
95
+ * anti-abuse protection** — the "DPoP everywhere" guarantee does not hold for
96
+ * them. Defaults to `false`: a tokenless write is refused `401` even where a
97
+ * public-write ACL would otherwise permit it. Set `true` to opt into
98
+ * public write as an explicit, documented tradeoff.
99
+ */
100
+ readonly allowAnonymousWrites?: boolean;
101
+ /**
102
+ * GC safety window (ms) advertised to the cron handler; an orphaned R2 object
103
+ * is only reclaimed once it is older than this. MUST be ≥ the maximum write
104
+ * duration. Defaults to five minutes.
105
+ */
106
+ readonly gcSafetyWindowMs?: number;
107
+ /** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now`. */
108
+ readonly now?: () => number;
109
+ /** `fetch` implementation used to resolve {@link jwksUri}; defaults to global `fetch`. */
110
+ readonly fetch?: typeof fetch;
111
+ /**
112
+ * Override the edge authentication step entirely. When provided, the handler
113
+ * calls this instead of the built-in JWKS + DPoP validation — useful for
114
+ * tests and for issuers the default verifier does not cover. Returning
115
+ * `null` means "no/invalid credentials" and the request proceeds
116
+ * unauthenticated (WAC then decides public access).
117
+ */
118
+ readonly authenticate?: (request: Request) => Promise<AuthContext | null> | AuthContext | null;
119
+ /**
120
+ * Logger for auth/authz events; defaults to a no-op. Wired once here at the
121
+ * composition boundary (see `@dwk/log`) to surface edge-authentication
122
+ * rejections and the Durable Object's WAC denials, anonymous-write refusals,
123
+ * and DPoP replay rejections instead of swallowing them.
124
+ */
125
+ readonly logger?: Logger;
126
+ /**
127
+ * Metrics sink for the same events; defaults to a no-op. Wire an adapter (e.g.
128
+ * `analyticsEngineMetrics` from `@dwk/log`) to chart what the logger names —
129
+ * auth rejections by reason, WAC denials/min, replay rejections.
130
+ */
131
+ readonly metrics?: Metrics;
132
+ }
133
+ /** The authenticated facts the front door hands to the Durable Object. */
134
+ export interface AuthContext {
135
+ /** The authenticated agent's WebID. */
136
+ readonly webid: string;
137
+ /** The verified DPoP proof's `jti`, for write replay enforcement in the DO. */
138
+ readonly jti: string;
139
+ /** The DPoP key thumbprint the token is bound to (`cnf.jkt`). */
140
+ readonly jkt: string;
141
+ }
142
+ /** Fully-resolved configuration with defaults applied. */
143
+ export interface ResolvedConfig {
144
+ readonly baseUrl: string;
145
+ readonly origin: string;
146
+ readonly owners: readonly string[];
147
+ readonly issuer?: string;
148
+ readonly audience: readonly string[];
149
+ readonly accessTokenType: string | null;
150
+ readonly jwks?: Jwks;
151
+ readonly jwksUri?: string;
152
+ readonly maxInlineBytes?: number;
153
+ readonly readReplayWindowSeconds: number;
154
+ readonly allowAnonymousWrites: boolean;
155
+ readonly gcSafetyWindowMs: number;
156
+ readonly now: () => number;
157
+ readonly fetch: typeof fetch;
158
+ readonly authenticate?: SolidPodConfig["authenticate"];
159
+ readonly logger: Logger;
160
+ readonly metrics: Metrics;
161
+ }
162
+ /** Internal headers the trusted front door uses to hand auth facts to the DO. */
163
+ export declare const INTERNAL_HEADERS: {
164
+ /** Authenticated agent WebID (absent ⇒ unauthenticated request). */
165
+ readonly webid: "x-solid-webid";
166
+ /** Verified DPoP `jti` (present on authenticated writes for replay control). */
167
+ readonly jti: "x-solid-jti";
168
+ /** Verified DPoP key thumbprint (`cnf.jkt`). */
169
+ readonly jkt: "x-solid-jkt";
170
+ /** JSON-encoded subset of config the DO needs (offload threshold, etc.). */
171
+ readonly config: "x-solid-config";
172
+ /**
173
+ * DO→front-door: a machine-readable authorization outcome (see `PodOutcome`)
174
+ * the composition boundary logs via the injected seams, then strips before the
175
+ * response reaches the client.
176
+ */
177
+ readonly outcome: "x-solid-outcome";
178
+ };
179
+ /** Apply defaults and derived values to raw {@link SolidPodConfig}. */
180
+ export declare function resolveConfig(config: SolidPodConfig): ResolvedConfig;
181
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Configuration, the declared Cloudflare `Env` fragment, and config resolution
3
+ * for `@dwk/solid-pod`.
4
+ *
5
+ * Per the composition contract, the package never reads the global environment
6
+ * directly: all tunables (base URL, token issuer/JWKS, accepted audience,
7
+ * offload threshold, replay/GC windows) are passed into {@link createSolidPod}
8
+ * and {@link createSolidPodGc}, so a pod can be instantiated multiple times and
9
+ * unit-tested in isolation. The Cloudflare bindings — the per-pod Durable
10
+ * Object namespace and the R2 blob bucket — are the only runtime coupling, and
11
+ * a missing one fails loudly at startup.
12
+ */
13
+ import { noopLogger, noopMetrics } from "@dwk/log";
14
+ /** Default GC safety window: five minutes, comfortably above any write. */
15
+ const DEFAULT_GC_SAFETY_WINDOW_MS = 5 * 60 * 1000;
16
+ /** Internal headers the trusted front door uses to hand auth facts to the DO. */
17
+ export const INTERNAL_HEADERS = {
18
+ /** Authenticated agent WebID (absent ⇒ unauthenticated request). */
19
+ webid: "x-solid-webid",
20
+ /** Verified DPoP `jti` (present on authenticated writes for replay control). */
21
+ jti: "x-solid-jti",
22
+ /** Verified DPoP key thumbprint (`cnf.jkt`). */
23
+ jkt: "x-solid-jkt",
24
+ /** JSON-encoded subset of config the DO needs (offload threshold, etc.). */
25
+ config: "x-solid-config",
26
+ /**
27
+ * DO→front-door: a machine-readable authorization outcome (see `PodOutcome`)
28
+ * the composition boundary logs via the injected seams, then strips before the
29
+ * response reaches the client.
30
+ */
31
+ outcome: "x-solid-outcome",
32
+ };
33
+ /** Strip the trailing slash from a base URL so path joins are unambiguous. */
34
+ function normalizeBaseUrl(baseUrl) {
35
+ return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
36
+ }
37
+ /** Apply defaults and derived values to raw {@link SolidPodConfig}. */
38
+ export function resolveConfig(config) {
39
+ if (!config.baseUrl) {
40
+ throw new Error("@dwk/solid-pod: `baseUrl` is required");
41
+ }
42
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
43
+ const origin = new URL(baseUrl).origin;
44
+ const audience = config.audience === undefined
45
+ ? ["solid", baseUrl]
46
+ : typeof config.audience === "string"
47
+ ? [config.audience]
48
+ : [...config.audience];
49
+ const owners = config.owner === undefined
50
+ ? []
51
+ : typeof config.owner === "string"
52
+ ? [config.owner]
53
+ : [...config.owner];
54
+ return {
55
+ baseUrl,
56
+ origin,
57
+ owners,
58
+ issuer: config.issuer,
59
+ audience,
60
+ accessTokenType: config.accessTokenType === undefined ? "at+jwt" : config.accessTokenType,
61
+ jwks: config.jwks,
62
+ jwksUri: config.jwksUri,
63
+ maxInlineBytes: config.maxInlineBytes,
64
+ readReplayWindowSeconds: config.readReplayWindowSeconds ?? 0,
65
+ allowAnonymousWrites: config.allowAnonymousWrites ?? false,
66
+ gcSafetyWindowMs: config.gcSafetyWindowMs ?? DEFAULT_GC_SAFETY_WINDOW_MS,
67
+ now: config.now ?? (() => Date.now()),
68
+ fetch: config.fetch ?? fetch,
69
+ authenticate: config.authenticate,
70
+ logger: config.logger ?? noopLogger,
71
+ metrics: config.metrics ?? noopMetrics,
72
+ };
73
+ }
74
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAA6B,MAAM,UAAU,CAAC;AA6K9E,2EAA2E;AAC3E,MAAM,2BAA2B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAElD,iFAAiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,oEAAoE;IACpE,KAAK,EAAE,eAAe;IACtB,gFAAgF;IAChF,GAAG,EAAE,aAAa;IAClB,gDAAgD;IAChD,GAAG,EAAE,aAAa;IAClB,4EAA4E;IAC5E,MAAM,EAAE,gBAAgB;IACxB;;;;OAIG;IACH,OAAO,EAAE,iBAAiB;CAClB,CAAC;AAEX,8EAA8E;AAC9E,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AAChE,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,MAAsB;IAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACvC,MAAM,QAAQ,GACZ,MAAM,CAAC,QAAQ,KAAK,SAAS;QAC3B,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC;QACpB,CAAC,CAAC,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;YACnC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;YACnB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE7B,MAAM,MAAM,GACV,MAAM,CAAC,KAAK,KAAK,SAAS;QACxB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAChC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAChB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1B,OAAO;QACL,OAAO;QACP,MAAM;QACN,MAAM;QACN,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ;QACR,eAAe,EACb,MAAM,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe;QAC1E,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,uBAAuB,EAAE,MAAM,CAAC,uBAAuB,IAAI,CAAC;QAC5D,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,KAAK;QAC1D,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,IAAI,2BAA2B;QACxE,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACrC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,KAAK;QAC5B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,UAAU;QACnC,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,WAAW;KACvC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * base64url + UTF-8 helpers for the edge auth path (JWT/JWKS, DPoP hashing).
3
+ *
4
+ * Dependency-free and runtime-agnostic (Web Crypto / `atob` / `btoa` only) so
5
+ * the surrounding modules unit-test without a Workers runtime.
6
+ */
7
+ /** Encode bytes as unpadded base64url (RFC 4648 §5). */
8
+ export declare function bytesToBase64url(bytes: Uint8Array): string;
9
+ /** Decode unpadded (or padded) base64url to bytes. */
10
+ export declare function base64urlToBytes(segment: string): Uint8Array;
11
+ /** Decode unpadded base64url to a UTF-8 string. */
12
+ export declare function base64urlToText(segment: string): string;
13
+ //# sourceMappingURL=encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAO1D;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAQ5D;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * base64url + UTF-8 helpers for the edge auth path (JWT/JWKS, DPoP hashing).
3
+ *
4
+ * Dependency-free and runtime-agnostic (Web Crypto / `atob` / `btoa` only) so
5
+ * the surrounding modules unit-test without a Workers runtime.
6
+ */
7
+ /** Encode bytes as unpadded base64url (RFC 4648 §5). */
8
+ export function bytesToBase64url(bytes) {
9
+ let binary = "";
10
+ for (const byte of bytes)
11
+ binary += String.fromCharCode(byte);
12
+ return btoa(binary)
13
+ .replace(/\+/g, "-")
14
+ .replace(/\//g, "_")
15
+ .replace(/=+$/, "");
16
+ }
17
+ /** Decode unpadded (or padded) base64url to bytes. */
18
+ export function base64urlToBytes(segment) {
19
+ const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
20
+ const padded = b64.length % 4 === 0 ? b64 : b64 + "=".repeat(4 - (b64.length % 4));
21
+ const binary = atob(padded);
22
+ const bytes = new Uint8Array(binary.length);
23
+ for (let i = 0; i < binary.length; i++)
24
+ bytes[i] = binary.charCodeAt(i);
25
+ return bytes;
26
+ }
27
+ /** Decode unpadded base64url to a UTF-8 string. */
28
+ export function base64urlToText(segment) {
29
+ return new TextDecoder().decode(base64urlToBytes(segment));
30
+ }
31
+ //# sourceMappingURL=encoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wDAAwD;AACxD,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,MAAM,CAAC;SAChB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,MAAM,GACV,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACxE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7D,CAAC"}