-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Summary
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)
What is remoteStorage?
remoteStorage is an open protocol for per-user storage on the web, predating Solid. It enables "unhosted" web apps where:
- Users own their data
- Apps request scoped access via OAuth
- Data syncs across devices automatically
The protocol is defined in IETF draft-dejong-remotestorage.
Protocol Comparison
| 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 |
Why Integrate?
- Larger ecosystem - Access to remoteStorage apps (100+ apps listed)
- Simpler onboarding - RS OAuth is simpler than Solid-OIDC for some apps
- Migration path - RS users can migrate to Solid gradually
- Unified storage - One server, two protocols, same data
- Unique positioning - First server supporting both protocols
Architecture
Proposed Plugin Structure
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
Request Flow
┌─────────────────────────────────────────────────────────────┐
│ 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) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Implementation Plan
Phase 1: WebFinger Extension (10/100)
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"
}
}
]
}Phase 2: OAuth 2.0 Implicit Flow (25/100)
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:
photos:rw- Read/write access to /storage/{user}/photos/documents:r- Read-only access to /storage/{user}/documents/*:rw- Full access (root scope)
Phase 3: Storage API (20/100)
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"Phase 4: Access Control (15/100)
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:
/storage/{user}/public/*- Readable without auth- Maps to ACL with
acl:agentClass foaf:Agentin Solid terms
Phase 5: Testing & Compliance (15/100)
Run the official RS API Test Suite.
# Run compliance tests
npx rs-api-test-suite https://localhost:3000/storage/testuserTest categories:
- WebFinger discovery
- OAuth authorization flow
- Document CRUD operations
- Folder listings
- Conditional requests (ETags)
- CORS headers
- Public folder access
Configuration
// 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=trueStorage Mapping
Option A: Shared Storage (Recommended)
RS 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:
- Solid:
GET /alice/photos/vacation.jpg(with Solid-OIDC) - RS:
GET /storage/alice/photos/vacation.jpg(with Bearer token)
Option B: Isolated Storage
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.
Scope ↔ WAC Mapping
| 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.
Compatibility Notes
remoteStorage Apps
Popular RS apps that would work:
- Litewrite - Distraction-free writing
- Groovebasin - Music player
- Sharesome - File sharing
- Webmarks - Bookmark manager
remoteStorage.js Library
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');Difficulty Breakdown
| 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 |
Open Questions
- Default state: Should RS be enabled by default or opt-in?
- Storage mapping: Shared storage (Option A) or isolated (Option B)?
- Token storage: Reuse IdP accounts DB or separate RS tokens?
- Scope persistence: Store granted scopes in accounts DB?
- Revocation UI: Add RS token management to future admin panel?
References
- remoteStorage Protocol
- IETF draft-dejong-remotestorage-26
- remoteStorage Servers
- RS API Test Suite
- remotestorage.js Library
- RS App List
Related Issues
- Roadmap: Production Readiness for v0.1 Release - Code Quality & Cleanup #100 - Production Readiness (foundation for RS integration)
- Feature: Admin Onboarding Wizard & Configuration Improvements #95 - Web Onboarding (could add RS setup option)
- Feature: Admin Control Panel for Server Management #96 - Admin Panel (RS token management)
- Feature: ETag Strategy Alignment with Web Best Practices #101 - ETag Strategy (shared with RS)