@dwk/remotestorage 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 +131 -0
  3. package/dist/auth.d.ts +42 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +108 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/config.d.ts +132 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +67 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/cors.d.ts +23 -0
  12. package/dist/cors.d.ts.map +1 -0
  13. package/dist/cors.js +56 -0
  14. package/dist/cors.js.map +1 -0
  15. package/dist/discovery.d.ts +31 -0
  16. package/dist/discovery.d.ts.map +1 -0
  17. package/dist/discovery.js +35 -0
  18. package/dist/discovery.js.map +1 -0
  19. package/dist/encoding.d.ts +11 -0
  20. package/dist/encoding.d.ts.map +1 -0
  21. package/dist/encoding.js +21 -0
  22. package/dist/encoding.js.map +1 -0
  23. package/dist/folder.d.ts +78 -0
  24. package/dist/folder.d.ts.map +1 -0
  25. package/dist/folder.js +0 -0
  26. package/dist/folder.js.map +1 -0
  27. package/dist/gc.d.ts +23 -0
  28. package/dist/gc.d.ts.map +1 -0
  29. package/dist/gc.js +34 -0
  30. package/dist/gc.js.map +1 -0
  31. package/dist/handler.d.ts +21 -0
  32. package/dist/handler.d.ts.map +1 -0
  33. package/dist/handler.js +166 -0
  34. package/dist/handler.js.map +1 -0
  35. package/dist/index.d.ts +33 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +32 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/jwt.d.ts +35 -0
  40. package/dist/jwt.d.ts.map +1 -0
  41. package/dist/jwt.js +119 -0
  42. package/dist/jwt.js.map +1 -0
  43. package/dist/log.d.ts +29 -0
  44. package/dist/log.d.ts.map +1 -0
  45. package/dist/log.js +27 -0
  46. package/dist/log.js.map +1 -0
  47. package/dist/scope.d.ts +54 -0
  48. package/dist/scope.d.ts.map +1 -0
  49. package/dist/scope.js +83 -0
  50. package/dist/scope.js.map +1 -0
  51. package/dist/storage.d.ts +22 -0
  52. package/dist/storage.d.ts.map +1 -0
  53. package/dist/storage.js +313 -0
  54. package/dist/storage.js.map +1 -0
  55. package/package.json +50 -0
  56. package/src/auth.ts +146 -0
  57. package/src/config.ts +197 -0
  58. package/src/cors.ts +68 -0
  59. package/src/discovery.ts +48 -0
  60. package/src/encoding.ts +22 -0
  61. package/src/folder.ts +0 -0
  62. package/src/gc.ts +50 -0
  63. package/src/handler.ts +225 -0
  64. package/src/index.ts +84 -0
  65. package/src/jwt.ts +155 -0
  66. package/src/log.ts +31 -0
  67. package/src/scope.ts +99 -0
  68. package/src/storage.ts +398 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * remoteStorage discovery (draft §10): the WebFinger link advertising a user's
3
+ * storage root and the OAuth endpoint a connecting app uses.
4
+ *
5
+ * A no-backend app is given only a user address (`user@host`); it discovers the
6
+ * storage root by querying WebFinger for that account and reading the
7
+ * remoteStorage link. This module builds that link as a plain
8
+ * [`@dwk/webfinger`](../webfinger) `Link` so a deployment can drop it into its
9
+ * existing WebFinger resource record rather than standing up a second endpoint.
10
+ * Pure data — no Workers runtime.
11
+ */
12
+ /** The remoteStorage WebFinger link relation (draft §10). */
13
+ export const REMOTESTORAGE_REL = "http://tools.ietf.org/id/draft-dejong-remotestorage";
14
+ /** The remoteStorage draft version advertised in the link properties. */
15
+ export const REMOTESTORAGE_VERSION = "draft-dejong-remotestorage-22";
16
+ /**
17
+ * Build the WebFinger remoteStorage `Link` for one account. The `properties`
18
+ * carry the spec version, the OAuth authorization URL, and the capability flags
19
+ * remoteStorage clients read (`Bearer` auth, `Content-Range` support); a `null`
20
+ * property value means "advertised as absent", per RFC 7033.
21
+ */
22
+ export function remoteStorageLink(config) {
23
+ return {
24
+ rel: REMOTESTORAGE_REL,
25
+ href: config.storageRoot,
26
+ properties: {
27
+ "http://remotestorage.io/spec/version": REMOTESTORAGE_VERSION,
28
+ "http://tools.ietf.org/html/rfc6749#section-4.2": config.authEndpoint,
29
+ "http://tools.ietf.org/html/rfc6750#section-2.3": "false",
30
+ "http://tools.ietf.org/html/rfc7233": "GET",
31
+ "http://remotestorage.io/spec/web-authoring": null,
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,iBAAiB,GAC5B,qDAAqD,CAAC;AAExD,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,+BAA+B,CAAC;AAUrE;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA+B;IAC/D,OAAO;QACL,GAAG,EAAE,iBAAiB;QACtB,IAAI,EAAE,MAAM,CAAC,WAAW;QACxB,UAAU,EAAE;YACV,sCAAsC,EAAE,qBAAqB;YAC7D,gDAAgD,EAAE,MAAM,CAAC,YAAY;YACrE,gDAAgD,EAAE,OAAO;YACzD,oCAAoC,EAAE,KAAK;YAC3C,4CAA4C,EAAE,IAAI;SACnD;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * base64url + UTF-8 helpers for the OAuth bearer-token decode path.
3
+ *
4
+ * Dependency-free and runtime-agnostic (`atob` / `btoa` / Web `TextDecoder`
5
+ * only) so the surrounding modules unit-test without a Workers runtime.
6
+ */
7
+ /** Decode unpadded (or padded) base64url to bytes. */
8
+ export declare function base64urlToBytes(segment: string): Uint8Array;
9
+ /** Decode unpadded base64url to a UTF-8 string. */
10
+ export declare function base64urlToText(segment: string): string;
11
+ //# 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,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,21 @@
1
+ /**
2
+ * base64url + UTF-8 helpers for the OAuth bearer-token decode path.
3
+ *
4
+ * Dependency-free and runtime-agnostic (`atob` / `btoa` / Web `TextDecoder`
5
+ * only) so the surrounding modules unit-test without a Workers runtime.
6
+ */
7
+ /** Decode unpadded (or padded) base64url to bytes. */
8
+ export function base64urlToBytes(segment) {
9
+ const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
10
+ const padded = b64.length % 4 === 0 ? b64 : b64 + "=".repeat(4 - (b64.length % 4));
11
+ const binary = atob(padded);
12
+ const bytes = new Uint8Array(binary.length);
13
+ for (let i = 0; i < binary.length; i++)
14
+ bytes[i] = binary.charCodeAt(i);
15
+ return bytes;
16
+ }
17
+ /** Decode unpadded base64url to a UTF-8 string. */
18
+ export function base64urlToText(segment) {
19
+ return new TextDecoder().decode(base64urlToBytes(segment));
20
+ }
21
+ //# sourceMappingURL=encoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Folder listings and folder ETags (draft §A), derived purely from the store's
3
+ * generic `list(prefix)` descendant projection.
4
+ *
5
+ * A remoteStorage folder is **virtual** — there is no stored folder object, only
6
+ * the documents beneath it — so a `GET <folder>/` listing and its ETag are
7
+ * computed on demand from the descendants. The contract that makes folder ETags
8
+ * useful is that a folder's ETag MUST change when **any** descendant changes; we
9
+ * guarantee that by deriving the ETag from a canonical signature over every
10
+ * descendant's `(key, etag)` pair, so any document add/remove/overwrite anywhere
11
+ * in the subtree perturbs it. This module is pure (it returns the signatures);
12
+ * the caller hashes them with {@link hashSignature} into opaque ETags.
13
+ */
14
+ import type { ResourceMeta } from "@dwk/store";
15
+ /** An immediate document child of a folder. */
16
+ export interface FolderDocument {
17
+ /** The child's name (the path segment under the folder). */
18
+ readonly name: string;
19
+ /** The document's opaque ETag. */
20
+ readonly etag: string;
21
+ /** The recorded content type. */
22
+ readonly contentType: string;
23
+ }
24
+ /** An immediate subfolder child of a folder. */
25
+ export interface FolderSubfolder {
26
+ /** The subfolder's name (without the trailing slash). */
27
+ readonly name: string;
28
+ /** Canonical signature over the subfolder's own descendants → its ETag. */
29
+ readonly signature: string;
30
+ }
31
+ /** The structured contents of one folder, ready to render and hash. */
32
+ export interface FolderModel {
33
+ /** Canonical signature over every descendant → the folder's own ETag. */
34
+ readonly signature: string;
35
+ /** Immediate document children, sorted by name. */
36
+ readonly documents: readonly FolderDocument[];
37
+ /** Immediate subfolder children, sorted by name. */
38
+ readonly subfolders: readonly FolderSubfolder[];
39
+ }
40
+ /**
41
+ * Group the descendants returned by `store.list(prefix)` into the immediate
42
+ * children of the folder at `prefix` (which MUST end in `/`). A descendant whose
43
+ * remainder past the prefix has no `/` is an immediate document; otherwise it
44
+ * belongs to the subfolder named by its first remaining segment, and its
45
+ * `(key, etag)` pair folds into that subfolder's signature.
46
+ */
47
+ export declare function buildFolderModel(prefix: string, entries: readonly ResourceMeta[]): FolderModel;
48
+ /**
49
+ * Hash a folder signature into an opaque, quoted ETag. SHA-256 over the
50
+ * canonical signature means the ETag is stable for unchanged contents and
51
+ * changes whenever any descendant does. An empty folder hashes the empty string
52
+ * to a fixed ETag, so even an absent/empty folder answers with a usable
53
+ * validator.
54
+ */
55
+ export declare function hashSignature(signature: string): Promise<string>;
56
+ /** The folder-description media type and JSON-LD context (draft §A). */
57
+ export declare const FOLDER_DESCRIPTION_TYPE = "application/ld+json";
58
+ export declare const FOLDER_DESCRIPTION_CONTEXT = "http://remotestorage.io/spec/folder-description";
59
+ /** A single entry in a rendered folder description's `items` map. */
60
+ export type FolderItem = {
61
+ ETag: string;
62
+ } | {
63
+ ETag: string;
64
+ "Content-Type": string;
65
+ };
66
+ /** A rendered folder description body (draft §A). */
67
+ export interface FolderDescription {
68
+ readonly "@context": string;
69
+ readonly items: Record<string, FolderItem>;
70
+ }
71
+ /**
72
+ * Render a {@link FolderModel} into the folder-description body, hashing each
73
+ * subfolder's signature into its aggregate ETag. ETags are emitted unquoted in
74
+ * the JSON body (the wire ETag header keeps the quotes), matching the
75
+ * remoteStorage folder-description convention.
76
+ */
77
+ export declare function renderFolderDescription(model: FolderModel): Promise<FolderDescription>;
78
+ //# sourceMappingURL=folder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder.d.ts","sourceRoot":"","sources":["../src/folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,YAAY,EAAE,GAC/B,WAAW,CAqCb;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAStE;AAED,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAC7D,eAAO,MAAM,0BAA0B,oDACY,CAAC;AAEpD,qEAAqE;AACrE,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CAC5C;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,iBAAiB,CAAC,CAc5B"}
package/dist/folder.js ADDED
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder.js","sourceRoot":"","sources":["../src/folder.ts"],"names":[],"mappings":"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"}
package/dist/gc.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The R2 garbage-collection cron handler.
3
+ *
4
+ * Each account's Durable Object forwards its orphaned blob keys (copy-on-write
5
+ * displacements and deletes) into a shared D1 table as it writes. This handler
6
+ * — wired to a Cloudflare cron trigger — reclaims those R2 objects once they are
7
+ * older than a safety window ≥ the maximum write duration, touching only D1 and
8
+ * R2 and never waking a Durable Object. The reclamation logic lives in
9
+ * `@dwk/store`; this is the thin endpoint wiring (identical in shape to
10
+ * `@dwk/solid-pod`'s GC, and able to share one `BLOBS`/`GC_DB` pair with it).
11
+ */
12
+ import { type RemoteStorageConfig, type RemoteStorageGcEnv } from "./config";
13
+ /** A `scheduled`-compatible cron handler for R2 blob garbage collection. */
14
+ export type RemoteStorageGcHandler = (event: ScheduledController, env: RemoteStorageGcEnv, ctx: ExecutionContext) => Promise<void>;
15
+ /**
16
+ * Create the cron handler that reclaims orphaned R2 blobs. Bind it to a
17
+ * `scheduled` trigger alongside the storage Worker; it shares the same `BLOBS`
18
+ * bucket and the `GC_DB` D1 database the DO forwards orphans into.
19
+ *
20
+ * Fails loudly when the required `BLOBS` / `GC_DB` bindings are missing.
21
+ */
22
+ export declare function createRemoteStorageGc(config: RemoteStorageConfig): RemoteStorageGcHandler;
23
+ //# sourceMappingURL=gc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../src/gc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAElB,4EAA4E;AAC5E,MAAM,MAAM,sBAAsB,GAAG,CACnC,KAAK,EAAE,mBAAmB,EAC1B,GAAG,EAAE,kBAAkB,EACvB,GAAG,EAAE,gBAAgB,KAClB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,mBAAmB,GAC1B,sBAAsB,CAaxB"}
package/dist/gc.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The R2 garbage-collection cron handler.
3
+ *
4
+ * Each account's Durable Object forwards its orphaned blob keys (copy-on-write
5
+ * displacements and deletes) into a shared D1 table as it writes. This handler
6
+ * — wired to a Cloudflare cron trigger — reclaims those R2 objects once they are
7
+ * older than a safety window ≥ the maximum write duration, touching only D1 and
8
+ * R2 and never waking a Durable Object. The reclamation logic lives in
9
+ * `@dwk/store`; this is the thin endpoint wiring (identical in shape to
10
+ * `@dwk/solid-pod`'s GC, and able to share one `BLOBS`/`GC_DB` pair with it).
11
+ */
12
+ import { collectGarbage, ensureGcSchema } from "@dwk/store";
13
+ import { resolveConfig, } from "./config";
14
+ /**
15
+ * Create the cron handler that reclaims orphaned R2 blobs. Bind it to a
16
+ * `scheduled` trigger alongside the storage Worker; it shares the same `BLOBS`
17
+ * bucket and the `GC_DB` D1 database the DO forwards orphans into.
18
+ *
19
+ * Fails loudly when the required `BLOBS` / `GC_DB` bindings are missing.
20
+ */
21
+ export function createRemoteStorageGc(config) {
22
+ const resolved = resolveConfig(config);
23
+ return async (_event, env, _ctx) => {
24
+ if (!env.BLOBS) {
25
+ throw new Error("@dwk/remotestorage: GC requires the `BLOBS` R2 binding");
26
+ }
27
+ if (!env.GC_DB) {
28
+ throw new Error("@dwk/remotestorage: GC requires the `GC_DB` D1 binding");
29
+ }
30
+ await ensureGcSchema(env.GC_DB);
31
+ await collectGarbage(env, { safetyWindowMs: resolved.gcSafetyWindowMs });
32
+ };
33
+ }
34
+ //# sourceMappingURL=gc.js.map
package/dist/gc.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc.js","sourceRoot":"","sources":["../src/gc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,EACL,aAAa,GAGd,MAAM,UAAU,CAAC;AASlB;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAA2B;IAE3B,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAEvC,OAAO,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACjC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,cAAc,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The stateless remoteStorage front door.
3
+ *
4
+ * It answers CORS preflights at the edge, authenticates the OAuth bearer token
5
+ * (`auth.ts`), and enforces the per-module read/write **scope** model
6
+ * (`scope.ts`) — including the public-`/public/`-document exception — before
7
+ * forwarding an already-authorized request to the per-account Durable Object,
8
+ * which owns all storage consistency. One Durable Object per account (keyed by
9
+ * the parsed account id); the handler is mountable under any path prefix because
10
+ * it routes purely on the request URL via {@link RemoteStorageConfig.parsePath}.
11
+ */
12
+ import { type RemoteStorageConfig, type RemoteStorageEnv } from "./config";
13
+ /** A `fetch`-compatible Worker handler. */
14
+ export type RemoteStorageHandler = (request: Request, env: RemoteStorageEnv, ctx: ExecutionContext) => Promise<Response>;
15
+ /**
16
+ * Create the stateless remoteStorage front-door handler. CORS and auth are
17
+ * enforced here; storage flows through the per-account
18
+ * {@link RemoteStorageObject} so it remains the single consistency authority.
19
+ */
20
+ export declare function createRemoteStorage(config: RemoteStorageConfig): RemoteStorageHandler;
21
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EAEtB,MAAM,UAAU,CAAC;AAKlB,2CAA2C;AAC3C,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,gBAAgB,KAClB,OAAO,CAAC,QAAQ,CAAC,CAAC;AA8HvB;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,mBAAmB,GAC1B,oBAAoB,CA4DtB"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The stateless remoteStorage front door.
3
+ *
4
+ * It answers CORS preflights at the edge, authenticates the OAuth bearer token
5
+ * (`auth.ts`), and enforces the per-module read/write **scope** model
6
+ * (`scope.ts`) — including the public-`/public/`-document exception — before
7
+ * forwarding an already-authorized request to the per-account Durable Object,
8
+ * which owns all storage consistency. One Durable Object per account (keyed by
9
+ * the parsed account id); the handler is mountable under any path prefix because
10
+ * it routes purely on the request URL via {@link RemoteStorageConfig.parsePath}.
11
+ */
12
+ import { hostFromUrl } from "@dwk/log";
13
+ import { authenticate } from "./auth";
14
+ import { INTERNAL_HEADERS, resolveConfig, } from "./config";
15
+ import { ALLOWED_METHODS, preflightResponse, withCors } from "./cors";
16
+ import { RemoteStorageLogEvent } from "./log";
17
+ import { authorizeScopes, isPublicDocument } from "./scope";
18
+ /** Client headers safe to forward verbatim to the Durable Object. */
19
+ const FORWARDED_HEADERS = [
20
+ "content-type",
21
+ "content-length",
22
+ "if-match",
23
+ "if-none-match",
24
+ ];
25
+ /** Methods the storage API serves; anything else is `405`. */
26
+ const METHODS = new Set(["GET", "HEAD", "PUT", "DELETE"]);
27
+ /** Fail loudly if a required Cloudflare binding is missing (no silent degradation). */
28
+ function assertBindings(env) {
29
+ if (!env.STORAGE) {
30
+ throw new Error("@dwk/remotestorage: missing required Durable Object binding `STORAGE`");
31
+ }
32
+ if (!env.BLOBS) {
33
+ throw new Error("@dwk/remotestorage: missing required R2 binding `BLOBS`");
34
+ }
35
+ }
36
+ /** Build the internal DO request: clean account-relative URL + config header. */
37
+ function internalRequest(request, storagePath, config) {
38
+ const headers = new Headers();
39
+ for (const name of FORWARDED_HEADERS) {
40
+ const value = request.headers.get(name);
41
+ if (value !== null)
42
+ headers.set(name, value);
43
+ }
44
+ headers.set(INTERNAL_HEADERS.config, JSON.stringify(config.maxInlineBytes !== undefined
45
+ ? { maxInlineBytes: config.maxInlineBytes }
46
+ : {}));
47
+ const url = new URL(request.url);
48
+ url.pathname = storagePath;
49
+ url.search = "";
50
+ const method = request.method.toUpperCase();
51
+ const hasBody = method !== "GET" && method !== "HEAD";
52
+ return new Request(url.toString(), {
53
+ method: request.method,
54
+ headers,
55
+ ...(hasBody ? { body: request.body } : {}),
56
+ });
57
+ }
58
+ /** Emit a structured event on both the logger and the metrics seam. */
59
+ function emit(config, level, event, fields) {
60
+ config.logger[level](event, fields);
61
+ config.metrics.count(event, fields);
62
+ }
63
+ /** A `401` with the Bearer challenge and a machine-readable failure reason. */
64
+ function unauthorized(reason) {
65
+ return new Response("Unauthorized", {
66
+ status: 401,
67
+ headers: {
68
+ "content-type": "text/plain; charset=utf-8",
69
+ "www-authenticate": `Bearer realm="remotestorage", error="invalid_token", error_description="${reason}"`,
70
+ },
71
+ });
72
+ }
73
+ /** A `403` for an authenticated request whose scopes do not cover the target. */
74
+ function forbidden() {
75
+ return new Response("Forbidden", {
76
+ status: 403,
77
+ headers: { "content-type": "text/plain; charset=utf-8" },
78
+ });
79
+ }
80
+ /**
81
+ * Decide whether a request is authorized, given the auth result, the storage
82
+ * path, and whether the method writes. Returns `null` to allow, or the Response
83
+ * to return. The `/public/` document read is allowed even unauthenticated; every
84
+ * other access needs a scope covering the path's module (or `*`).
85
+ */
86
+ function authorize(config, result, path, needWrite, method) {
87
+ const publicRead = !needWrite && isPublicDocument(path);
88
+ if (result.kind === "anonymous") {
89
+ if (publicRead)
90
+ return null;
91
+ emit(config, "warn", RemoteStorageLogEvent.AccessDenied, {
92
+ method,
93
+ status: 401,
94
+ });
95
+ return unauthorized("missing_token");
96
+ }
97
+ // Authenticated: a public-document read always succeeds; otherwise the token
98
+ // must carry a scope for the path's module at the required mode. (A `rejected`
99
+ // result never reaches here — the caller returns before authorizing.)
100
+ if (result.kind === "rejected")
101
+ return unauthorized(result.reason);
102
+ if (publicRead || authorizeScopes(result.auth.scopes, path, needWrite)) {
103
+ return null;
104
+ }
105
+ emit(config, "warn", RemoteStorageLogEvent.AccessDenied, {
106
+ method,
107
+ status: 403,
108
+ });
109
+ return forbidden();
110
+ }
111
+ /**
112
+ * Create the stateless remoteStorage front-door handler. CORS and auth are
113
+ * enforced here; storage flows through the per-account
114
+ * {@link RemoteStorageObject} so it remains the single consistency authority.
115
+ */
116
+ export function createRemoteStorage(config) {
117
+ const resolved = resolveConfig(config);
118
+ return async (request, env, _ctx) => {
119
+ assertBindings(env);
120
+ const requestOrigin = request.headers.get("origin");
121
+ const method = request.method.toUpperCase();
122
+ // Preflight is answered at the edge — no auth, no store hit.
123
+ if (method === "OPTIONS")
124
+ return preflightResponse(requestOrigin);
125
+ const respond = (response) => withCors(response, requestOrigin);
126
+ if (!METHODS.has(method)) {
127
+ return respond(new Response("Method Not Allowed", {
128
+ status: 405,
129
+ headers: {
130
+ "content-type": "text/plain; charset=utf-8",
131
+ allow: ALLOWED_METHODS,
132
+ },
133
+ }));
134
+ }
135
+ const parsed = resolved.parsePath(new URL(request.url).pathname);
136
+ if (!parsed) {
137
+ return respond(new Response("Not Found", {
138
+ status: 404,
139
+ headers: { "content-type": "text/plain; charset=utf-8" },
140
+ }));
141
+ }
142
+ const result = await authenticate(request, resolved);
143
+ if (result.kind === "rejected") {
144
+ emit(resolved, "warn", RemoteStorageLogEvent.AuthRejected, {
145
+ reason: result.reason,
146
+ });
147
+ return respond(unauthorized(result.reason));
148
+ }
149
+ if (result.kind === "authenticated") {
150
+ emit(resolved, "info", RemoteStorageLogEvent.AuthAccepted, {
151
+ ...(result.auth.subject
152
+ ? { subjectHost: hostFromUrl(result.auth.subject) }
153
+ : {}),
154
+ });
155
+ }
156
+ const needWrite = method === "PUT" || method === "DELETE";
157
+ const denied = authorize(resolved, result, parsed.path, needWrite, method);
158
+ if (denied)
159
+ return respond(denied);
160
+ const forwarded = internalRequest(request, parsed.path, resolved);
161
+ const id = env.STORAGE.idFromName(parsed.account);
162
+ const response = await env.STORAGE.get(id).fetch(forwarded);
163
+ return respond(response);
164
+ };
165
+ }
166
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `@dwk/remotestorage` — an Unhosted-style remoteStorage personal data vault.
3
+ *
4
+ * A per-user document vault that "no-backend" web apps read and write over a
5
+ * plain HTTP `GET`/`PUT`/`DELETE` API, scoped by OAuth 2.0 bearer tokens
6
+ * (draft-dejong-remotestorage). It is a *competing* personal-data protocol to
7
+ * Solid — simpler, document-oriented, no RDF — filed for completeness, **not**
8
+ * as a recommendation alongside `@dwk/solid-pod`.
9
+ *
10
+ * Its value here is that it rides on the **same backing store** the Pod uses:
11
+ * `@dwk/store` is a generic `key → { rdf | blob }` pointer map, so remoteStorage
12
+ * documents are just its blob tier, and the only library addition this package
13
+ * required is the generic `list(prefix)` projection (which `@dwk/solid-pod`
14
+ * could use for LDP container enumeration too). This package ships a thin
15
+ * per-account Durable Object that reuses `@dwk/store`; the divergent auth model
16
+ * (plain OAuth bearer + per-module `:r`/`:rw` scopes + a public `/public/` tree,
17
+ * not DPoP/WAC) and folder semantics live entirely here.
18
+ *
19
+ * @see spec/packages/remotestorage.md
20
+ * @packageDocumentation
21
+ */
22
+ export { createRemoteStorage, type RemoteStorageHandler } from "./handler";
23
+ export { createRemoteStorageGc, type RemoteStorageGcHandler } from "./gc";
24
+ export { RemoteStorageObject } from "./storage";
25
+ export { resolveConfig, defaultParsePath, type RemoteStorageConfig, type RemoteStorageEnv, type RemoteStorageGcEnv, type ResolvedConfig, type ParsedPath, } from "./config";
26
+ export { authenticate, bearerToken, type RemoteStorageAuth, type AuthResult, type AuthFailureReason, } from "./auth";
27
+ export { parseScopes, authorizeScopes, moduleForPath, isFolderPath, isPublicDocument, ROOT_MODULE, type RemoteStorageScope, type ScopeMode, } from "./scope";
28
+ export { buildFolderModel, renderFolderDescription, hashSignature, FOLDER_DESCRIPTION_TYPE, FOLDER_DESCRIPTION_CONTEXT, type FolderModel, type FolderDocument, type FolderSubfolder, type FolderDescription, type FolderItem, } from "./folder";
29
+ export { corsHeaders, preflightResponse, withCors, ALLOWED_METHODS, } from "./cors";
30
+ export { remoteStorageLink, REMOTESTORAGE_REL, REMOTESTORAGE_VERSION, type RemoteStorageLinkConfig, } from "./discovery";
31
+ export { RemoteStorageLogEvent } from "./log";
32
+ export type { Logger, Metrics } from "@dwk/log";
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,KAAK,sBAAsB,EAAE,MAAM,MAAM,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,UAAU,GAChB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,iBAAiB,GACvB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,WAAW,EACX,eAAe,EACf,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,SAAS,GACf,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,UAAU,GAChB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,QAAQ,EACR,eAAe,GAChB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AAC9C,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `@dwk/remotestorage` — an Unhosted-style remoteStorage personal data vault.
3
+ *
4
+ * A per-user document vault that "no-backend" web apps read and write over a
5
+ * plain HTTP `GET`/`PUT`/`DELETE` API, scoped by OAuth 2.0 bearer tokens
6
+ * (draft-dejong-remotestorage). It is a *competing* personal-data protocol to
7
+ * Solid — simpler, document-oriented, no RDF — filed for completeness, **not**
8
+ * as a recommendation alongside `@dwk/solid-pod`.
9
+ *
10
+ * Its value here is that it rides on the **same backing store** the Pod uses:
11
+ * `@dwk/store` is a generic `key → { rdf | blob }` pointer map, so remoteStorage
12
+ * documents are just its blob tier, and the only library addition this package
13
+ * required is the generic `list(prefix)` projection (which `@dwk/solid-pod`
14
+ * could use for LDP container enumeration too). This package ships a thin
15
+ * per-account Durable Object that reuses `@dwk/store`; the divergent auth model
16
+ * (plain OAuth bearer + per-module `:r`/`:rw` scopes + a public `/public/` tree,
17
+ * not DPoP/WAC) and folder semantics live entirely here.
18
+ *
19
+ * @see spec/packages/remotestorage.md
20
+ * @packageDocumentation
21
+ */
22
+ export { createRemoteStorage } from "./handler";
23
+ export { createRemoteStorageGc } from "./gc";
24
+ export { RemoteStorageObject } from "./storage";
25
+ export { resolveConfig, defaultParsePath, } from "./config";
26
+ export { authenticate, bearerToken, } from "./auth";
27
+ export { parseScopes, authorizeScopes, moduleForPath, isFolderPath, isPublicDocument, ROOT_MODULE, } from "./scope";
28
+ export { buildFolderModel, renderFolderDescription, hashSignature, FOLDER_DESCRIPTION_TYPE, FOLDER_DESCRIPTION_CONTEXT, } from "./folder";
29
+ export { corsHeaders, preflightResponse, withCors, ALLOWED_METHODS, } from "./cors";
30
+ export { remoteStorageLink, REMOTESTORAGE_REL, REMOTESTORAGE_VERSION, } from "./discovery";
31
+ export { RemoteStorageLogEvent } from "./log";
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,mBAAmB,EAA6B,MAAM,WAAW,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAA+B,MAAM,MAAM,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EACL,aAAa,EACb,gBAAgB,GAMjB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,YAAY,EACZ,WAAW,GAIZ,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,WAAW,EACX,eAAe,EACf,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,WAAW,GAGZ,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,uBAAuB,EACvB,0BAA0B,GAM3B,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,QAAQ,EACR,eAAe,GAChB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,GAEtB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC"}
package/dist/jwt.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Minimal JWT decode and JWKS-based signature verification for the built-in
3
+ * bearer-token verifier.
4
+ *
5
+ * remoteStorage uses **plain OAuth 2.0 bearer tokens** (no DPoP); a deployment
6
+ * that issues self-contained JWT access tokens can have this package verify them
7
+ * directly against the issuer's public JWKS. The module decodes the compact JWS,
8
+ * selects the key by `kid`/`kty`, and verifies with Web Crypto; claim policy
9
+ * (issuer/audience/expiry/`scope`) lives in `auth.ts`.
10
+ *
11
+ * Only asymmetric algorithms are accepted. `HS*` and `none` are refused: an
12
+ * access token must be signed by the issuer's private key, never a symmetric
13
+ * secret a resource server could not safely hold.
14
+ */
15
+ /** Asymmetric JOSE algorithms accepted for issuer-signed access tokens. */
16
+ export type JwtAlgorithm = "ES256" | "ES384" | "RS256" | "PS256";
17
+ /** A decoded JWT: its header, claims, and the raw segments for verification. */
18
+ export interface DecodedJwt {
19
+ readonly header: Record<string, unknown>;
20
+ readonly payload: Record<string, unknown>;
21
+ readonly signingInput: string;
22
+ readonly signature: Uint8Array;
23
+ }
24
+ /**
25
+ * Decode a compact JWS into its header, payload, and signature. Returns `null`
26
+ * for anything that is not three base64url segments with JSON header/payload.
27
+ */
28
+ export declare function decodeJwt(token: string): DecodedJwt | null;
29
+ /**
30
+ * Verify `decoded`'s signature against a JWKS. Selects the verification key by
31
+ * `kid` when the header carries one, otherwise tries every key compatible with
32
+ * the header `alg`. Returns `true` on the first key that verifies.
33
+ */
34
+ export declare function verifyJwtSignature(decoded: DecodedJwt, jwks: readonly JsonWebKey[]): Promise<boolean>;
35
+ //# sourceMappingURL=jwt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAqCjE,gFAAgF;AAChF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAChC;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAsB1D;AASD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,SAAS,UAAU,EAAE,GAC1B,OAAO,CAAC,OAAO,CAAC,CA4ClB"}