| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Add remoteStorage protocol support to JavaScriptSolidServer, enabling the server to serve both Solid and remoteStorage clients from the same storage backend. This would make JSS the first server to support both major per-user storage protocols.
Difficulty: 45/100
Estimated Effort: 4-6 days
Dependencies: None (can be implemented as optional plugin)
remoteStorage is an open protocol for per-user storage on the web, predating Solid. It enables "unhosted" web apps where:
The protocol is defined in IETF draft-dejong-remotestorage.
| Feature | Solid (current) | remoteStorage | Compatibility |
|---|---|---|---|
| Discovery | WebFinger → WebID | WebFinger → storage URL | Different format, same endpoint |
| Authentication | Solid-OIDC + DPoP | OAuth 2.0 Bearer (simpler) | RS is subset |
| HTTP Methods | GET/PUT/DELETE/PATCH | GET/PUT/DELETE/HEAD | RS is subset |
| Directory Listings | Turtle/JSON-LD (LDP vocab) | JSON-LD (simple format) | Transform needed |
| ETags | ✅ Supported | ✅ Required | Already works |
| Conditional Requests | If-Match, If-None-Match | If-Match, If-None-Match | Already works |
| CORS | ✅ Enabled | ✅ Required | Already works |
| Access Control | WAC (ACL files) | OAuth scopes (category:rw) | Different model |
| Path Structure | /{pod}/path/to/file | /storage/{user}/{module}/ | Different |
| Public Data | ACL-based | /public/ folder | Map to ACL |
src/ ├── remotestorage/ │ ├── index.js # Plugin registration │ ├── webfinger.js # RS WebFinger format │ ├── oauth.js # OAuth 2.0 implicit flow │ ├── storage.js # GET/PUT/DELETE handlers │ ├── listings.js # Directory listing formatter │ └── scopes.js # Scope validation & mapping
┌─────────────────────────────────────────────────────────────┐
│ JSS Server │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ WebFinger Handler │ │
│ │ /.well-known/webfinger │ │
│ │ │ │
│ │ ?resource=acct:alice@example.com │ │
│ │ │ │ │
│ │ ├─► rel=remotestorage → RS client │ │
│ │ └─► rel=solid → Solid client │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ remoteStorage Routes │ │
│ │ │ │
│ │ GET /rs/oauth/authorize → OAuth dialog │ │
│ │ POST /rs/oauth/token → Token exchange │ │
│ │ GET /storage/{user}/* → Read file/folder │ │
│ │ PUT /storage/{user}/* → Write file │ │
│ │ DELETE /storage/{user}/* → Delete file │ │
│ │ HEAD /storage/{user}/* → Get metadata │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Solid Routes (existing) │ │
│ │ │ │
│ │ /{pod}/* → Solid LDP + WAC │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Shared Storage Backend │ │
│ │ (filesystem.js) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Add remoteStorage link relation to existing WebFinger response.
Current response:
{
"subject": "acct:alice@example.com",
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"href": "https://example.com/alice/profile/card"
}
]
}Extended response:
{
"subject": "acct:alice@example.com",
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"href": "https://example.com/alice/profile/card"
},
{
"rel": "http://tools.ietf.org/id/draft-dejong-remotestorage",
"href": "https://example.com/storage/alice",
"properties": {
"http://remotestorage.io/spec/version": "draft-dejong-remotestorage-26",
"http://tools.ietf.org/html/rfc6749#section-4.2": "https://example.com/rs/oauth/authorize",
"http://tools.ietf.org/html/rfc6750#section-2.3": "Bearer"
}
}
]
}Implement simplified OAuth for remoteStorage clients.
Authorization endpoint:
GET /rs/oauth/authorize ?client_id=https://app.example.com &redirect_uri=https://app.example.com/callback &response_type=token &scope=photos:rw documents:r
Authorization dialog:
┌─────────────────────────────────────────────┐ │ App "MyPhotos" is requesting access │ │ │ │ ☑ photos - Read and write │ │ ☑ documents - Read only │ │ │ │ [Deny] [Allow Access] │ └─────────────────────────────────────────────┘
Successful redirect:
https://app.example.com/callback#access_token=abc123&token_type=bearer
Scope format:
Implement remoteStorage HTTP API.
GET document:
GET /storage/alice/photos/vacation.jpg
Authorization: Bearer abc123
200 OK
Content-Type: image/jpeg
ETag: "a1b2c3"
Content-Length: 12345
<binary data>GET folder:
GET /storage/alice/photos/
Authorization: Bearer abc123
200 OK
Content-Type: application/ld+json
ETag: "folder-etag"
{
"@context": "http://remotestorage.io/spec/folder-description",
"items": {
"vacation.jpg": {
"ETag": "a1b2c3",
"Content-Type": "image/jpeg",
"Content-Length": 12345
},
"summer/": {
"ETag": "d4e5f6"
}
}
}PUT document:
PUT /storage/alice/photos/new.jpg
Authorization: Bearer abc123
Content-Type: image/jpeg
If-None-Match: *
<binary data>
201 Created
ETag: "new-etag"DELETE document:
DELETE /storage/alice/photos/old.jpg
Authorization: Bearer abc123
If-Match: "old-etag"
200 OK
ETag: "new-folder-etag"Map OAuth scopes to storage paths.
// src/remotestorage/scopes.js
function validateScope(token, path, method) {
// Parse path: /storage/alice/photos/vacation.jpg
const [, , user, category, ...rest] = path.split('/');
// Check token scopes
const scope = token.scopes.find(s => {
const [cat, perm] = s.split(':');
return (cat === '*' || cat === category) &&
(perm === 'rw' || (perm === 'r' && method === 'GET'));
});
if (!scope) {
return { allowed: false, error: 'Insufficient scope' };
}
return { allowed: true };
}Public folder handling:
Run the official RS API Test Suite.
# Run compliance tests
npx rs-api-test-suite https://localhost:3000/storage/testuserTest categories:
// Proposed config additions
{
"remotestorage": {
"enabled": true,
"basePath": "/storage", // RS storage path prefix
"oauthPath": "/rs/oauth", // OAuth endpoints
"publicFolder": "public", // Public folder name
"tokenExpiry": 86400, // Token lifetime (seconds)
"allowedOrigins": ["*"] // CORS origins for RS
}
}CLI flag:
jss start --remotestorage # Enable RS support
jss start --no-remotestorage # Disable (default?)Environment variable:
JSS_REMOTESTORAGE=trueRS and Solid share the same filesystem:
data/
└── alice/
├── profile/ # Solid WebID profile
├── photos/ # Accessible via both protocols
│ └── vacation.jpg
├── public/ # RS public folder = Solid public
└── .acl # Solid WAC (RS uses OAuth scopes)
Access patterns:
Separate storage areas:
data/
└── alice/
├── solid/ # Solid-only data
└── rs/ # RS-only data
Pros: Clean separation, no ACL conflicts
Cons: Data duplication, users manage two silos
Recommendation: Option A (shared) with clear documentation on access model differences.
| RS Scope | Solid WAC Equivalent |
|---|---|
| photos:r | acl:Read on /photos/ |
| photos:rw | acl:Read, acl:Write on /photos/ |
| *:r | acl:Read on root |
| *:rw | acl:Read, acl:Write, acl:Control on root |
| (public folder) | acl:agentClass foaf:Agent; acl:Read |
Note: RS scopes are simpler than WAC. RS tokens can only access paths within their scopes, regardless of ACL files.
Popular RS apps that would work:
The official remotestorage.js client library should work out of the box once the protocol is implemented.
const rs = new RemoteStorage();
rs.access.claim('photos', 'rw');
rs.connect('alice@example.com');| Component | Difficulty | Notes |
|---|---|---|
| WebFinger extension | 10/100 | Add link relation to existing handler |
| OAuth implicit flow | 25/100 | Simpler than Solid-OIDC |
| Storage GET/PUT/DELETE | 15/100 | Reuse existing storage backend |
| Directory listings | 10/100 | JSON-LD transformation |
| Scope validation | 15/100 | Path-based access check |
| HEAD requests | 5/100 | Trivial |
| Public folder | 10/100 | Skip auth for /public/ |
| Compliance testing | 15/100 | RS test suite |
| Documentation | 10/100 | Usage guide |
| Total | 45/100 |
| Back | FazBrowse Home | New Git URL |
Activity
Add OAuth 2.0 authorize/token flow
Add remoteStorage protocol support
melvincarvalho commented on Apr 2, 2026
Implemented and tested. remoteStorage protocol support has been shipping since v0.0.85.
Testing:
All apps work with any remoteStorage server — JSS acts as a fully compatible remoteStorage backend via the --activitypub flag (for WebFinger discovery + OAuth).
melvincarvalho commented on Apr 2, 2026
All RS apps reported working