Skip to content

feat: Support Linked Web Storage, alone or together with Solid - #124

Draft
jeswr wants to merge 2 commits into
lws-basefrom
feat/lws
Draft

jeswr wants to merge 2 commits into
lws-basefrom
feat/lws

Conversation

@jeswr

@jeswr jeswr commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Requested by Jesse · project thread

This review PR lives in the fork. Its base lws-base is a copy of upstream main at 3cd2aca, so the diff contains only this change. The upstream PR can be opened from this compare link, and the body below can be reused there.

📁 Related issues

None yet. This adds support for the Linked Web Storage (LWS) draft. An issue can be opened to track it if maintainers prefer.

✍️ Description

Before: CSS can only serve the Solid Protocol.

After: CSS can also be configured to serve LWS, either on its own or together with Solid, with WAC or ACP for authorization. There are four new presets: lws.json, lws-acp.json, solid-lws.json and solid-lws-acp.json. They have the same setup as file.json, and every pod is an LWS storage. This includes pagination, access requests and grants, webhook notifications, recursive deletes and problem details. Existing configurations behave as before, except for the 412 fix listed under "Core changes".

The work follows the LWS editor's draft at w3c/lws-protocol 9b03b32 (28 Sep 2026). User documentation is in documentation/markdown/usage/linked-web-storage.md.

How it works. Everything is new components plus new config options (lws / solid-lws) for the handler, metadata parser/writer, modes, authentication, storage middleware and representation conversion.

Resources and discovery

  • LwsStorageDescriptionHandler serves the storage description (application/lws+cid) on the storage root, using conneg. LwsStorageDescribers add services and verification methods to it.
  • LwsLinkMetadataWriter adds the lws#storage, up, type, linkset and pagination links.
  • LwsContainerTypeParser accepts Link: <lws#Container>; rel="type" when creating a container.
  • NoContentOperationHandler maps 205 to 204.

Containers, linksets and PATCH

  • ContainerToLwsJsonConverter produces the application/lws+json container representation. Listings over pageSize (default 1000) are paginated via ?page=N (parsed by LwsPagePreferenceParser) with first/last/prev/next links.
  • LinksetMapper, QuadToLinksetConverter and LinksetMergePatcher expose the existing description resource (.meta) as an RFC 9264 linkset, modifiable with JSON Merge Patch.
  • JsonMergePatcher and JsonMergePatchModesExtractor add RFC 7386 JSON Merge Patch for JSON resources.
  • DepthParser, RecursiveDeleteModesExtractor and RecursiveDeleteOperationHandler support DELETE with Depth: infinity, requiring delete permission on every contained resource.
  • ErrorToProblemJsonConverter and FallbackErrorHandler return application/problem+json whenever the error can't be converted to a type the client prefers.

Authorization server and tokens

  • LwsTokenHttpHandler, LwsAuthorizationServerMetadataHttpHandler, LwsJwksHttpHandler and LwsAccessTokenIssuer form the LWS authorization server (/.well-known/lws-configuration, RFC 8693 token exchange).
  • Subject tokens are checked by DidKeySubjectTokenVerifier, CidSubjectTokenVerifier and OidcSubjectTokenVerifier (which also accepts solid:oidcIssuer).
  • LwsAccessTokenExtractor validates at+jwt Bearer tokens; LwsWwwAuthMetadataWriter sends WWW-Authenticate: Bearer as_uri, realm[, error="invalid_token"].
  • The agent in the token becomes the WebID used by WAC and ACP.

Access requests and grants

  • Every storage has .lws/requests/ and .lws/grants/ containers, created lazily by AccessContainerInitializingHandler and advertised by AccessServiceDescriber with the lws#AccessProfile.
  • AccessDocumentOperationHandler validates posted documents against the Access Profile, makes them immutable (GET/HEAD/DELETE only), forbids targets inside .lws/, and notifies the grant's inbox.
  • AccessGrantReader (added to the PathBasedReader) grants permissions from grants cached by AccessGrantIndex: exact, non-recursive targets with a type filter; foaf:Agent for public access; create on a container covers its direct members; client, format, type and dateTime constraints are enforced, and purpose is accepted. Authenticated agents can create access requests. Grants only add to WAC/ACP; deleting one revokes it.

Webhook notifications

  • LwsSubscriptionHttpHandler serves /.notifications/lws/: create (topics must be readable), list the subscriber's subscriptions as an LWS container, GET and DELETE a subscription.
  • LwsNotificationListener turns resource changes into Create/Update/Delete activities (no actor), matches recursive container topics, and re-checks read permission at delivery time. Subscriptions are removed after 5 consecutive failures or on 410 Gone.
  • WebhookLwsNotificationSender POSTs application/lws+json envelopes signed by HttpMessageSigner (RFC 9421, ES256, content-digest), retrying 5xx and network errors. LwsNotificationServiceDescriber advertises the service and the key as verificationMethod + authentication.

Core changes outside the new components

  • assertReadConditions: a GET or HEAD with a failing If-Match or If-Unmodified-Since now returns 412 instead of 304, as RFC 9110 §13.1 requires.
  • DataAccessorBasedStore: a .meta GET carries the subject's modification date, so linksets get ETags.
  • New vocabulary terms and content types.

Choices made where LWS is ambiguous or conflicts with Solid (reviewers may want to weigh in)

  1. The storage URI is the root container URI. No Accept, or a preference for application/lws+cid, gets the storage description; for */* the LWS-only presets return the description and the dual presets the container.
  2. In dual mode, application/ld+json on containers keeps returning LDP JSON-LD, so two Touchstone conneg MUSTs fail in dual mode. (Being revisited separately.)
  3. In dual mode, a Bearer token with a webid claim is treated as Solid-OIDC; every other Bearer token as LWS.
  4. The storage description is public.
  5. OIDC ID tokens whose aud holds only the client are accepted.
  6. The access containers are protected by the storage's own WAC/ACP rules, so by default only the owner reads requests and creates grants. Policies without a target grant nothing. Purpose constraints can't be verified and are treated as satisfied.
  7. Grants and subscriptions are cached in memory, so these features assume a single worker.

Not implemented yet: notifying the storage controller of new access requests (LWS doesn't define how its inbox is found), requesters reading or deleting their own requests, and SAML.

Testing

  • Unit tests cover all new files at 100%; the full unit suite (2840 tests) and integration suite (916 tests) pass.
  • test/integration/LinkedWebStorage.test.ts runs did:key, grants, access requests, webhook delivery (with a local inbox), Depth delete and problem details against all four combinations.
  • Touchstone, run directly (no Docker), against a secured server:
    • LWS + WAC and LWS + ACP, core: 116 passed, 0 failed (pagination run with a page size of 2; the rest of the inapplicable tests need harness-issued tokens).
    • notifications/webhook: 11 of 11 passed, including signature verification, retry and deactivation.
    • Solid + LWS: same, except the two application/ld+json conneg MUSTs and one SHOULD from choice 2.
    • auth (from the first round): 23 of 23, 3 SAML tests inapplicable.

✅ PR check list

  • Make sure you have personally reviewed and understood this contribution when using generative AI tools.
  • Target the correct branch based on semantic versioning.
  • Update documentation when relevant.
  • Update the release notes when adding new major features.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QzAV2EGR8JGsCnqErKMUWx

Adds components and configurations to serve the Linked Web Storage (LWS)
protocol, based on the LWS editor's draft of 28 September 2026,
either on its own or together with the Solid Protocol, using WAC or ACP.

- Storage description on the storage root (application/lws+cid)
- LWS container representation (application/lws+json)
- LWS Link headers: storage, up, type, and linkset
- Linksets (RFC 9264) backed by description resources, with JSON Merge Patch
- JSON Merge Patch (RFC 7386) for JSON resources
- 204 responses after successful modifications
- LWS authorization server: metadata, JWKS, and token exchange (RFC 8693)
  accepting did:key, controlled identifier, and OpenID Connect credentials
- Validation of LWS access tokens and the LWS WWW-Authenticate challenge
- New lws, lws-acp, solid-lws, and solid-lws-acp configurations
- GET/HEAD requests with a failing If-Match or If-Unmodified-Since now get 412

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzAV2EGR8JGsCnqErKMUWx
@jeswr jeswr self-assigned this Oct 3, 2026
… delete and problem details

Container listings are paginated with first/last/prev/next links. Every storage
gets access request and access grant containers that are validated against the
LWS Access Profile, and grants add permissions next to WAC or ACP. Webhook
subscriptions deliver signed LWS notifications with delivery-time authorization.
DELETE with Depth: infinity removes containers recursively, and errors fall back
to application/problem+json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzAV2EGR8JGsCnqErKMUWx
@elf-pavlik

Copy link
Copy Markdown

@jeswr why not do this based on https://github.com/CommunitySolidServer/CommunitySolidServer/tree/versions/next-major
I'd like use your branch here in sai-js but I have everything build on top of next-major

jeswr commented Oct 7, 2026

Copy link
Copy Markdown
Owner Author

@elf-pavlik here is a version rebased onto versions/next-major: #126 (branch feat/lws-next-major).

It is the same LWS work, ported to the v8 changes (policy engine permissions, the extracted handler and logging libraries, the new storage middleware layout). The full unit and integration suites pass on it, including the LWS integration tests with WAC and ACP. The PR description lists the differences from this PR.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants