4.3 KiB
| id | title | status | assignee | created_date | updated_date | labels | dependencies | priority | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| TASK-15 | EncryptID personal subdomains: <user>.r*.online with local-first data | Done | 2026-02-25 03:01 | 2026-02-25 04:48 |
|
high |
Description
When a user logs in via EncryptID, they should operate out of <encryptID>.r*.online (e.g., alice.rnotes.online, alice.rspace.online), with all their data saved securely to their local space.
Scope
This is a cross-cutting feature affecting all rStack apps. Key areas:
1. DNS & Routing
- Wildcard DNS for each
r*.onlinedomain (*.rnotes.online,*.rspace.online, etc.) - Cloudflare wildcard CNAME records pointing to the tunnel
- Traefik wildcard Host rules to route
*.r*.onlineto the correct app container
2. Auth / EncryptID Integration
- On login, redirect to
<encryptID>.r*.online - Middleware to extract subdomain, validate it matches the authenticated EncryptID
- Reject requests where subdomain doesn't match session identity
3. Data Isolation & Local-First Storage
- Each user's data is scoped to their EncryptID
- Explore local-first / CRDTs (e.g., Yjs, Automerge) for offline-capable storage
- Sync strategy: local device ↔ user's personal encrypted space on server
- Encryption at rest using keys derived from EncryptID
4. Multi-App Consistency
- Shared auth session across
*.r*.onlinesubdomains (cross-subdomain cookies or token-based) - AppSwitcher links should resolve to
<user>.rspace.online,<user>.rnotes.online, etc. when logged in - Consistent UX: user always sees their subdomain in the URL bar
5. Migration
- Existing data needs a migration path to the new per-user scoped storage
- Backward compat for users accessing the root domain (redirect to subdomain after login)
Open Questions
- What is the EncryptID identifier format? (alphanumeric, length constraints, case sensitivity)
- Should unauthenticated users see public content at the root domain?
- How does this interact with rSpace's existing space/room model?
- Storage backend: SQLite per user? Postgres row-level security? Encrypted blob store?
Acceptance Criteria
- #1 Wildcard DNS configured for all r*.online domains
- #2 Middleware extracts and validates EncryptID from subdomain
- #3 User data is scoped and encrypted per EncryptID
- #4 AppSwitcher links resolve to user's personal subdomain when logged in
- #5 Cross-subdomain auth session works across rStack apps
- #6 Migration path defined for existing data
- #7 Local-first storage strategy documented and prototyped
Implementation Notes
Research complete - EncryptID uses DID format (did🔑z, 50+ chars). DIDs too long for DNS labels (63 char limit). Username-based subdomains recommended but usernames are currently optional.
Final Summary
Implemented username-based personal subdomains (<username>.rnotes.online) across 8 phases:
Phase 1 - Infrastructure: DNS wildcard and Traefik routing already configured. Cloudflared needs manual *.rnotes.online entry.
Phase 2 - EncryptID: Server already enforces username UNIQUE NOT NULL + includes in JWT. SDK updated: made username required in EncryptIDClaims type, updated createSession().
Phase 3 - Schema: Added workspaceSlug field to Notebook model with index + migration SQL.
Phase 4 - Middleware: Sets x-workspace-slug header from subdomain extraction. New workspace.ts helper.
Phase 5 - API Filtering: All GET endpoints filter by workspace on subdomains (notebooks, notes, search, notebook detail, notebook notes).
Phase 6 - AppSwitcher: Fetches /api/me for username, generates <username>.r*.online links when logged in.
Phase 7 - Sessions: SubdomainSession component syncs localStorage ↔ .rnotes.online domain-wide cookie. authFetch falls back to domain cookie.
Phase 8 - Migration: Auto-assigns unscoped notebooks to user's workspace on auth.
Manual steps remaining: Remove stale container on netcup, run migration, add cloudflared wildcard entry.