Repository navigation
Conversation
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
… 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
- Use the external global-logger-factory, asynchronous-handlers, rdf-vocabulary and @solidlab/policy-engine libraries instead of the removed internal modules. - Use the policy engine permissions (Read, Append, Create, Modify, Delete) instead of the removed AccessMode enum. - Combine the access grants with the policy engine reader by overriding the reader of the ReadDeleteReader, as the PathBasedReader no longer combines readers. - Base the LWS storage middleware on the new base and cache configurations. - Update the LWS configurations to the v8 context and handler names. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016NLdD5cDKAKmvebyW2Esqu
|
I've tested Solid Memo against this branch at These are the findings for this branch, most important first. All were seen on local test servers. ID token replay. The token endpoint accepts an ID token whose audience is only the client, and it does no client authentication (
Differences from the current editor's draft:
We run this exact commit as two advisory servers in solid-memo/website#604, and can move the pin when the branch updates. |
I don't think |
Indeed, Solid Memo doesn't need Edit: I took that to include error answers, though it may not be meant to, and a 404 has no resource to name a parent for. Please disregard this item. Edit 2: Of the remaining differences, Solid Memo doesn't send JSON Patch, read the issued token's |
The token endpoint now verifies a DPoP proof when a client sends one. Sender-constrained ID tokens (with a cnf claim) are only exchanged when the request proves possession of their key, and the new requireRestrictedTokens option of the OidcSubjectTokenVerifier rejects ID tokens that are neither restricted to this authorization server nor sender-constrained. It is off by default, as most identity providers can not restrict the audience of their ID tokens. Client identifiers that are not URIs, such as those of dynamically registered clients, are put in the fragment of the issuer URI, so the client_id claim of issued access tokens is always a URI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
The LWS editor's draft now requires the WWW-Authenticate challenge of a 401 response to include the resource_metadata parameter of RFC 9728, pointing to a protected resource metadata document for the requested resource. The LWS presets now serve these documents under /.well-known/oauth-protected-resource, and the challenge links to them. The as_uri and realm parameters are kept for clients of earlier drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
The LWS editor's draft now requires JSON Patch (RFC 6902) for updates, on data resources and on linkset resources. JSON resources and linksets now accept application/json-patch+json, next to JSON Merge Patch, and Accept-Patch advertises it. The patchers and modes extractor are renamed to JsonPatcher, LinksetPatcher and JsonPatchModesExtractor, as they now handle both formats. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
A client can request a container in the LWS profile by adding profile="https://www.w3.org/ns/lws/v1" to an RDF media type. The response then has to be RDF isomorphic to the application/lws+json representation, and its Content-Type has to carry the same profile. Turtle requests got the LDP triples without the profile, and JSON-LD requests lacked the profile in Content-Type. The Accept parser now keeps profile parameters in a new profiles preference, the LWS container converter serializes the LWS RDF in Turtle, N-Triples, TriG and N-Quads, and Content-Type parameters that are not tokens are now quoted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
The nested index type of the new profiles preference could not be handled by the Components.js generator, so it is now a named type that the generator ignores. Also covers a branch of the AccessGrantIndex that was not tested yet, so unit test coverage is back at 100%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
In pod mode the subscription endpoint is not inside any storage, so every access token was rejected there and clients tried to exchange a token for the endpoint itself, which the authorization server refused. The token extractor now accepts tokens for any storage of the server on configured service paths and records the storage in the credentials. Subscriptions are limited to topics in that storage, and a 401 for an unreadable topic now targets the topic so the response points to its storage and authorization server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
|
Thanks @ixuz, and @elf-pavlik for the The other findings are addressed in 90848b3 (details in the PR description):
ef6d664 also fixes webhook subscriptions on pod-based servers: the subscription endpoint now accepts an access token for any of the server's storages, and topics must be in that storage. You can move the pin in solid-memo/website#604 to ef6d664. Generated by Claude Code |
- Check service paths before the storage of the target when validating LWS access tokens: the pod strategy derives a storage from any URL, so the notification endpoint seemed to be in a storage of its own. - CID credentials: only accept keys of the authentication verification relationship (embedded or referenced, relative identifiers resolved), and refuse revoked or expired verification methods (CID 1.0, 2.2, 3.3). - Linksets: refuse patches that change the up link, as moving resources is not supported, instead of ignoring them. - LWS profile RDF: leave out format, size and modified of contained containers, which the LWS context only defines on data resources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
Requested by Jesse · project thread
📁 Related issues
Same work as #124, rebased onto upstream
versions/next-major(requested in #124 (comment)). The base branchlws-next-major-baseis a copy of upstreamversions/next-majorat 9ebe8c4, so the diff only shows the LWS changes. #124 and its branch are unchanged.✍️ Description
Before: Linked Web Storage support only existed on top of upstream
main(v7), so projects built onversions/next-major(v8) could not use it.After: The same LWS features (the
lws,lws-acp,solid-lwsandsolid-lws-acppresets, access tokens, access grants and requests, webhook notifications, pagination,Depth: infinitydelete, problem details) run onversions/next-major.Commits:
DataAccessorBasedStore,ResourceUtil,index.tsand the release notes resolved in favour of next-major's code).mainbut not yet onversions/next-major. LWS container listings need it to report each item'sformatwith the memory backend.global-logger-factory,asynchronous-handlersandrdf-vocabulary.PERMISSIONS(Read,Append,Create,Modify,Delete) instead of the removedAccessModeenum, andPermissionMap/MultiPermissionMapinstead ofPermissionSet/PermissionMap.PathBasedReaderno longer unions readers on next-major, solws-access.jsonnow uses a Components.jsOverrideonReadDeleteReaderto put theAccessGrantReaderin aUnionPermissionReadernext to thePolicyEngineReader. Internal resources are still routed away by thePathBasedReaderbefore this point.storage/middleware/lws.jsonfollows the newbase+cachelayout (newstorage/middleware/base/lws.json), so LWS presets also get the backend cache.StatusWaterfallHandler, and theasynchronous-handlerscontext where needed.🔁 Follow-up to the Solid Memo report
Commits 5 to 9 address the findings in #126 (comment), apart from
rel=upon 401/404, which the discussion below it agreed to drop (a 404 has no parent to point to, and a 401 should not disclose one).DPoPproof (RFC 9449) when the client sends one. An ID token with acnf.jktclaim is only exchanged if the proof is signed with that key. A newrequireRestrictedTokensoption onOidcSubjectTokenVerifieralso rejects ID tokens that are neither DPoP-bound nor have this authorization server inaud. It is off by default: most identity providers, including CSS's own, can't add the AS to an ID token's audience, so turning it on would lock those users out. The issued access token is still aBearertoken, as LWS requires.client_idas a URI. Client identifiers that are not URIs (dynamically registered clients) become<issuer>#<id>, for examplehttps://idp.example/#abc123. Client ID Metadata Documents can't have a fragment, so these never clash with a real client ID URL.resource_metadata(RFC 9728), pointing to a new protected resource metadata document under/.well-known/oauth-protected-resource/<path>withresourceandauthorization_servers, as in w3c/lws-protocol@9420b5d6.as_uriandrealmstay for clients of earlier drafts.application/json-patch+json(RFC 6902), next to JSON Merge Patch, andAccept-Patchlists it first. Paths refer to the linkset asGETreturns it. Server-managed links,upincluded, still can't be changed, so moving resources through the linkset isn't supported (MoveResourceis optional). A patch that changesupis rejected with a 409, as the 19 October draft requires. The patchers and modes extractor are renamedJsonPatcher,LinksetPatcherandJsonPatchModesExtractor.profile="https://www.w3.org/ns/lws/v1"onapplication/ld+json,text/turtle,application/n-triples,application/trigorapplication/n-quadsnow returns the LWS RDF of the container, with the same profile inContent-Type. The mapping follows the spec's isomorphic example tabs (lws:items,lws:totalItems,dcterms:format,schema:size,dcterms:modified). This works in both LWS-only and dual presets. Core changes:AcceptPreferenceParserkeepsprofileparameters in a newprofilespreference, andContentTypenow quotes parameter values that are not tokens.AccessGrantIndexbranch.🧪 Conformance (commits 10 to 12)
Run on 6d7e9df against the latest harnesses of the Dagger workspace (lws-contrib/dagger-workspace):
MoveResourceand similar optional features). The first run found 23 failures, fixed here:authenticationrelationship (embedded or referenced) and refuse revoked or expired verification methods (CID 1.0 §2.2, §3.3). Linkset patches that changeupare refused. LWS-profile RDF leaves outmodifiedof contained containers, which the LWS context only defines on data resources.AuthorizationServerservice in the storage description or a containertypelink on the storage description (both from earlier drafts; the current draft uses RFC 9728 metadata and returns the storage description at the storage URI), and one treats a multi-valuedVaryheader as an error. The workspace's default lws-net input (tests.yamlon theyamlbranch of elf-pavlik/lws-test-suite) no longer loads in current LWS.net, so LWS.net's own suite was used.Notes for reviewers:
ReadDeleteReadermust exist). They will not work withldp/authorization/allow-all.json.npm run build,tsc(src and test), ESLint and markdownlint clean; unit tests 2767 passed at 100% coverage; integration tests 925 passed, 29 skipped (the usual Docker-only suites), includingLinkedWebStorage.test.ts120/120 across LWS and Solid+LWS with WAC and ACP.✅ PR check list
versions/next-majorcontent)🤖 Generated with Claude Code
https://claude.ai/code/session_01KLrUeu4Nbafj3Pc2bedyRD
Generated by Claude Code