Each space gets {space}-agent@rspace.online as a real Mailcow mailbox
(auto-provisioned with generated password). Inbound emails are IMAP-polled
and processed by MI (Gemini Flash) for auto-reply. All outbound emails
(approvals, notifications) set reply-to to the agent address so replies
route back through MI.
- mailcow.ts: createMailbox/deleteMailbox/mailboxExists API
- schema.sql + db.ts: agent_mailboxes table for per-space IMAP creds
- space-alias-service.ts: provisionAgentMailbox/deprovisionAgentMailbox
- server.ts: internal routes for agent mailbox CRUD + member-emails
- rinbox/mod.ts: initAgentMailbox, per-space IMAP sync, processAgentMI
- rinbox/agent-notify.ts: sendSpaceNotification (BCC members)
- rcal/rtasks/rvote: notification hooks on create
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add label column to credentials, PATCH/DELETE endpoints for rename/remove,
device list UI in account modal with rename/remove actions, and clear stale
nudge dismiss timestamp after device registration so multiDevice API check
takes over permanently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix invite accept fetch URL in shell.ts (was missing /api/spaces prefix)
- After accepting invite, redirect to the invited space instead of reloading
- Notification actionUrls now point to the space subdomain (https://slug.rspace.online)
- Direct-add email includes inviter name, role, and space description
- Identity invite email includes space name/role context when inviting to a space
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
listSpaceInvites now queries both space_invites and identity_invites
tables, merging results so email-based invites (via /invite endpoint)
appear in the Pending Invites section. revokeSpaceInvite also falls
through to identity_invites if not found in space_invites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- rstack-identity.ts: throttle server session validation to every 5min,
add reason detail to all auth-change events (signin/signout/revoked/
refresh/persona-switch), remove redundant location.reload on signout
- shell.ts: skip UI side-effects on token refresh, only redirect home
on genuine signout/revocation
- server.ts: add PUT to CORS allowMethods
- folk-inbox-client.ts: pass auth token on mailbox API fetch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- login-button.ts: no-known-accounts state shows passkey-first button
(unscoped WebAuthn) with email magic link fallback, auto-revealed on
NotAllowedError. Fix stale usernameInput ref.
- server.ts (auth.rspace.online): add localStorage known accounts system.
Returning users see their stored usernames as clickable buttons.
handleAuth() accepts optional username for scoped auth. Saves account
after successful login. renderSigninAccounts() called on page init.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Provision {space}@rspace.online forwarding aliases that route to
opted-in members' personal emails. Admins/mods opted in by default;
regular members can opt in via PUT /api/spaces/:slug/email-forwarding/me.
New: space-alias-service.ts, schema tables, 8 DB functions, 6 API routes.
Hooks: rinbox onSpaceCreate/Delete, spaces.ts member lifecycle, startup migration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When no known accounts exist in localStorage, show a username/email
input field instead of immediately triggering the unscoped passkey
picker. User types their username, then gets a scoped passkey prompt
for only that account's credentials.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logout no longer removes the account from the picker — users see
"Sign in as [username]" on next visit. fetchScopedCredentials now
returns full PublicKeyCredentialDescriptor with transports so the
browser can locate the right authenticator without showing a picker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scoped passkey prompts via /api/auth/start so the browser only shows
matching credentials for the selected account. Known accounts stored
in localStorage and surfaced as a picker (1 account = named button,
multiple = list). "Use a different account" falls back to unscoped.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rstack-identity is the actual sign-out component used in production.
clearSession() now calls /api/session/logout, and connectedCallback
validates the session with the server to detect revocation. Also
updated the auth.rspace.online profile page handleLogout().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a user logs out in one browser, all other sessions are now revoked
on their next page load or token refresh. Adds logged_out_at column to
users table, server-side revocation checks on verify/refresh endpoints,
and a new /api/session/logout endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AES-256-GCM encryption for 18 PII fields across 6 tables (users,
guardians, identity_invites, space_invites, notifications, fund_claims).
HMAC-SHA256 hash indexes for email/UP address lookups. Keys derived from
JWT_SECRET via HKDF with dedicated salts. Dual-write to both plaintext
and _enc columns during transition; row mappers decrypt with plaintext
fallback. Includes idempotent backfill migration script.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add /recover/social page for users to finalize account recovery after
guardian approvals, fix status filter so approved requests remain
findable, return requestId from initiation API with tracking link on
login page, and add actionUrl to recovery notifications.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The rspace container cannot resolve encryptid-db hostname, causing
/api/notifications/count to 524 timeout on every 30s poll. Rewrites
notification-routes.ts as an HTTP proxy to encryptid (which has DB
access), adds notification API endpoints to encryptid server, and
wraps BroadcastChannel.postMessage in try/catch to prevent uncaught
errors during navigation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Consolidates token verification into server/auth.ts, removing the
external SDK dependency. All modules now import from the local module.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Email verification wrote to the `email` column but account status read
from `profile_email` — now setUserEmail writes both. Account modal email
section displays the verified address when collapsed. Tour finale step
triggers identity setup on completion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Force authenticatorAttachment: 'platform' across all WebAuthn registration
flows to prevent USB security key prompts. Redirect browser navigations to
space root when accessing disabled modules instead of returning JSON error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The resolve-dids endpoint was returning 401 because unsigned fallback tokens
fail HS256 verification. Since username/displayName is public profile data,
remove auth requirement from the endpoint and client call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add POST /api/users/resolve-dids batch endpoint in EncryptID, proxy
/api/users/* through rspace server, and batch-resolve missing displayNames
in the space settings panel so owners and members show usernames not DIDs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New users get sent to /join for passkey registration + auto-space-join.
Existing users are directly added with in-app + email notification.
Add-by-username now also sends email notification if email is on file.
- Add id to /api/users/lookup response
- Enhance /api/internal/user-email/:userId with recovery + profile email
- Add GET /api/internal/user-by-email for email→DID resolution
- Rewrite POST /:slug/invite to use identity invite flow
- Add email notification to POST /:slug/members/add
- Add success/error feedback to space settings invite UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New spaces start with an empty canvas instead of 25+ template shapes.
Each space gets a {slug}@rspace.online team inbox (multi-sig ready)
via the rinbox onSpaceCreate hook. Fix EncryptID auto-provision passing
raw string instead of SpaceLifecycleContext to module hooks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix DID mismatch: server now stores and reads proper did🔑z6Mk... DIDs
from database instead of deriving truncated did🔑${slice(0,32)}
- Add PRF extension to WebAuthn create/get flows for client-side key derivation
- Derive DID, signing keys, encryption keys, and EOA wallet from passkey PRF
- Auto-upgrade truncated DIDs to proper format on sign-in
- Add POST /api/account/upgrade-did endpoint for DID migration
- Add 5-step educational registration wizard (identity, passkey, DID, wallet, security)
- Add email/username field to sign-in for scoped passkey selection
- Add magic link email login for external devices without passkeys
- Add POST /api/auth/magic-link and GET /magic-login verification page
- Add mintWelcomeBalance() for 5 fUSDC to new users
- Store EOA wallet address during registration when PRF available
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Space creation was broken because the canvas module has id "rspace" but
all navigation URLs used "/canvas". On production subdomain routing this
resulted in 404s after creating a space.
- Switch create-space form from deprecated /api/communities to /api/spaces
- Replace all /canvas navigation URLs with /rspace to match module ID
- Fix DID matching in space listing to check both sub and did:key formats
- Add proper client DID support in EncryptID registration flow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mailcow rejects noreply@rspace.online because the authenticated user
is noreply@rmail.online. Updated all SMTP_FROM and SMTP_USER defaults
to use rmail.online consistently: spaces invites, rSplat notifications,
EncryptID auth emails, and rCart payment receipts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
activeLayerId was being written to the shared Automerge CRDT on every tab
switch, causing all open windows/devices to follow. Now active tab is
local-only. Adds REST API + server-side storage so authenticated users'
tab lists persist across sessions and devices.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- schema.sql: UP columns (up_address, up_key_manager_address, up_chain_id, up_deployed_at)
- db.ts: getUserUPAddress, setUserUPAddress, getUserByUPAddress
- server.ts: GET/POST /api/profile/:id/up endpoints, UP info in JWT claims
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add EncryptID internal endpoint for email lookup by userId
- rcart: send "Payment Sent" to payer and "Payment Received" to recipient
- rcart: resolve emails via EncryptID when not provided in request
- rsplat: add GLB/GLTF 3D viewer using Three.js GLTFLoader
- rsplat: enable publicWrite for photo uploads without space membership
- docker-compose: add SITE_URL and SPLAT_NOTIFY_EMAIL env vars
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Normalize emails to lowercase at all setUserEmail() call sites so
case mismatches no longer break the OIDC allowedEmails check. Split
the authorize error into email_required (shows verification form) vs
access_denied (shows error message) so users with a verified email
are never re-prompted unnecessarily.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wallets stored in local WalletStore are now bidirectionally synced with
the encrypted AccountVault on the server. On login, vault wallets are
restored to the local store; on wallet changes, local state is pushed
back to the vault. The server user profile wallet_address is also set
on login so mobile devices (without PRF) get the address via JWT.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sendVerificationEmail function was hardcoding noreply@ridentity.online
as the sender, but SMTP authenticates as noreply@rspace.online. Mailcow
rejected the mismatch with 553 "Sender address rejected: not owned by user".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch 3d-force-graph CDN from jsdelivr to esm.sh with bundle-deps
to resolve missing "three-forcegraph" bare specifier error
- Fix storeCredential() to pass displayName and DID to createUser()
(prevents NULL did column for credential-first user creation)
- Fix invite acceptance to use claims.did instead of claims.sub for
space_members.user_did (DID format consistency)
- Fix session refresh to look up username from DB when missing from
old token (prevents empty username after token refresh)
- Fix resolveCallerRole() in spaces.ts to check both claims.sub and
claims.did against ownerDID and member keys (auto-provisioned spaces
store ownerDID as did🔑, API-created as raw userId)
- Refactor CRM route to use URL subpath tabs with renderCrm helper
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Key derivation: replace random crypto.subtle.generateKey with deterministic
P-256 via @noble/curves/p256 and real Ed25519 did:key generation via
@noble/curves/ed25519 with multicodec prefix + base58btc encoding
- Guardian recovery: wire RecoveryManager to server API (GET/POST/DELETE
/api/guardians) instead of localStorage-only persistence. Server handles
invite emails, client syncs guardian list on load and merges with local
type metadata. verifyGuardian checks actual server acceptance status.
- Notifications dispatch CustomEvents on document for UI integration
- GuardianSetupElement awaits server sync before first render
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The old constraint rejected new values during UPDATE. Must drop first,
migrate data, then add new constraint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Restructure graph API so trust enrichment runs regardless of whether
Twenty CRM token is configured (demo space has no CRM token)
- Add missing listActiveDelegations import in encryptid server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix graph cache keying: include trust/authority params so cached
non-trust responses don't shadow trust-enriched requests
- Add /api/delegations/space endpoint to EncryptID for space-level
delegation listing (no auth required, for graph/sankey)
- Fetch and include delegates_to edges in graph API response
- Pass auth-url attribute to delegation manager and sankey components
- Rewrite sankey loadData to use space-level delegation endpoint
instead of per-user endpoints (shows all flows, not just current user)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Person-to-person delegation within spaces across 5 authority verticals
(voting, moderation, curation, treasury, membership). Trust engine
recomputes scores every 5 min with time decay, transitive BFS, and
50% per-hop discount. Graph viewer shows trust-weighted node sizing
with authority selector. New Delegations tab in CRM with management
UI and Sankey flow visualization.
Schema: delegations, trust_events, trust_scores tables
API: delegation CRUD, trust scores, events, user directory
Frontend: folk-delegation-manager, folk-trust-sankey, graph trust mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Session manager now calls EncryptID /api/auth/start + /api/auth/complete
to get a properly signed JWT instead of creating unsigned local tokens.
This fixes 401 errors on /api/payments, /api/notifications, and other
authenticated endpoints that verify tokens via EncryptID server.
- Token refresh calls /api/session/refresh instead of extending unsigned tokens
- Server generateSessionToken now includes authTime, jti, recoveryConfigured
- rNetwork: /crm route renders folk-crm-view instead of iframe
- rNetwork: ?view=app redirects 301 to /crm (backward compat)
- rNetwork: graph viewer always uses API (removed hardcoded demo data)
- docker-compose: pass through TWENTY_API_TOKEN from Infisical
- rcart: add catalog product images
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add shared ViewHistory<V> utility class that provides a proper navigation
stack for rApps with hierarchical views. Replaces hardcoded data-back
targets with stack-based back navigation across 10 rApps: rtrips, rmaps,
rtasks, rforum, rphotos, rvote, rnotes, rinbox, rschedule, rcart.
Rename rWork module to rTasks — directory, component (folk-tasks-board),
CSS, exports, domains, and all cross-module references updated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When passkey auth succeeds but user's email doesn't match the OIDC
client's allowedEmails, show an inline email verification form instead
of a dead-end error. Sends a branded verification email with a single-use
30-minute token, then updates users.email on callback and lets the user
retry sign-in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Notification routes: wrap GET / and GET /count in try-catch, return
graceful fallbacks instead of 500s when DB table is missing/unavailable
- getUnreadCount: add null safety (row?.count ?? 0) and catch DB errors
- Service worker: add .catch(() => {}) to all cache.put() calls to
suppress NetworkError on quota-exceeded or corrupted cache entries
- On-ramp error display: coerce err.error to string so alerts show the
actual message instead of [object Object]
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- L-1: Remove internal error details from SIWE verify response
- L-2: Stop forwarding raw Safe API error bodies to clients (log server-side)
- L-3: Evict stale keys from nonce rate limiter to prevent memory leak
- L-4: Add input length/type guards on wallet-link verify body fields
- L-5: Sanitize and cap limit query param on Safe transfers route (max 200)
- L-6: Server recomputes addressHash from SIWE address instead of trusting
client-supplied value for dedup
- L-7: Reset LinkedWalletStore singleton on logout to clear cached keys
- I-1: Add X-Content-Type-Options, X-Frame-Options, Referrer-Policy headers
- I-9: Build EIP712Domain type array dynamically from domain fields in
ExternalSigner.signTypedData (was hardcoded to empty, dropping fields)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>