Commit Graph

657 Commits

Author SHA1 Message Date
Jeff Emmett 92fde65d60 fix(security): rate limit, sender verification, icon sanitization, SSRF prevention
- H-3: Rate limit wallet-link nonce to 5 per user per 5 minutes (429)
- H-4: Verify sender address matches JWT walletAddress in add-owner-proposal;
  also include walletAddress in JWT eid claims
- M-1: Sanitize EIP-6963 provider icons — only allow https: and safe
  data:image/(png|jpeg|gif|webp), block SVG and javascript: URIs
- M-2: Validate threshold is a positive integer ≤ newOwnerCount, fetch
  actual Safe owner list for bounds checking
- M-3: Add VALID_ETH_ADDR regex validation to all 9 routes that accept
  address params (Safe proxy, EOA proxy, propose, confirm, execute,
  add-owner-proposal) to prevent SSRF via path traversal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:51:18 -07:00
Jeff Emmett 45f5cea095 fix(security): AES-256-GCM encryption at rest, XSS escape, salted hashes
- C-1: Replace Base64 fake encryption with real AES-256-GCM server-side
  encryption for linked wallet data (HKDF-derived key from JWT_SECRET)
- H-1: Escape token name/symbol in balance table to prevent XSS
- H-2: Salt address hash with user ID to prevent cross-user correlation
- M-4: Remove cleartext sessionStorage cache for linked wallets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:42:09 -07:00
Jeff Emmett 8723aae3f6 fix(encryptid): show success page instead of auto-OIDC redirect
The auto-redirect through OIDC authorize fails because the client app
(Postiz) didn't initiate the OAuth flow and has no matching state.
Instead, show a branded success page with a link to the app. The user
signs in with their passkey when they visit the app normally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:31:32 -07:00
Jeff Emmett d861c0ad99 fix(encryptid): harden wallet link flow + add device_registration type
- Atomic nonce consumption prevents TOCTOU races
- SIWE domain validation against allowlist
- Unique constraint on linked_wallets(user_id, address_hash)
- Add device_registration to challenge type enum

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:17:50 -07:00
Jeff Emmett c789481d91 feat(rwallet): link external wallets via EIP-6963 + SIWE
Users can now connect browser wallets (MetaMask, Rainbow, etc.) to their
EncryptID identity via SIWE ownership proof, and view linked wallet
balances in the unified rWallet viewer.

New files:
- eip6963.ts: EIP-6963 multi-provider discovery
- external-signer.ts: EIP-1193 provider wrapper for tx signing
- linked-wallets.ts: encrypted client-side store (same AES-256-GCM pattern)

Server: wallet-link nonce/verify/list/delete routes, linked_wallets table,
Safe add-owner-proposal endpoint, new session permissions.

UI: "My Wallets" section with provider picker, SIWE linking flow,
wallet type badges, and click-to-view for linked wallets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:10:12 -07:00
Jeff Emmett ac156cbbf2 fix(encryptid): fix Docker build context for encryptid compose
Build context was set to parent dir (..) which broke all COPY paths.
Use context: . with additional_contexts for the encryptid-sdk sibling,
matching the pattern in the main docker-compose.yml.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:40:32 -07:00
Jeff Emmett b6bc1a756a fix: prioritize ridentity.online in WebAuthn Related Origins
Browsers enforce a 5 eTLD+1 limit on Related Origins. The previous
config dumped all 29 r*.online domains, causing ridentity.online to
be ignored (position 15). Now only lists the 5 domains that actually
need passkey auth: ridentity, rsocials, crypto-commons, p2pfoundation,
rwallet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:37:47 -07:00
Jeff Emmett 9c74bff465 feat: add OIDC admin page for managing clients and email allowlists
Admin UI at /admin/oidc with passkey login, gated by ADMIN_DIDS.
Supports viewing/creating/deleting clients, adding/removing allowed
emails per client, revealing/rotating secrets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:32:19 -07:00
Jeff Emmett 4665d14633 fix: seed OIDC clients after DB init completes
Move OIDC client seeding into the database init IIFE to prevent
race condition where seeding runs before tables are created.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:03:46 -07:00
Jeff Emmett f19a5b9904 feat: add OIDC provider + identity invite flow to EncryptID
Add full OIDC Authorization Code flow (discovery, authorize, token,
userinfo) so external apps like Postiz can authenticate via EncryptID
passkeys through auth.ridentity.online.

Add "Claim your rSpace" identity invite system — authenticated users
can invite friends by email, who register a passkey and optionally
auto-join a space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 15:53:37 -07:00
Jeff Emmett 93b6b2eb2c fix(rwallet): use CSS variable theming + soften global light mode
Replace ~30 hardcoded dark hex colors in folk-wallet-viewer with --rs-*
CSS variables so rWallet adapts to both light and dark themes. Warm up
the global light palette from pure white to off-white tones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 15:27:07 -07:00
Jeff Emmett f2ce77321f fix(rnotes): remove premature seed from onInit (runs before loadAllDocs)
seedDemoIfEmpty in onInit ran before docs were loaded from disk, causing
duplicate notebooks. Seeding is already handled post-loadAllDocs via
the seedTemplate hook.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 15:16:19 -07:00
Jeff Emmett a689445af5 feat(rcal): add swipe/pan navigation and pinch-to-zoom gestures
Add pointer-based gesture handling to the calendar for lateral navigation
(drag/swipe left/right) and pinch-to-zoom between temporal layers. Also
fix dark mode across 30+ canvas shape components by replacing hardcoded
white backgrounds and input styles with --rs-* CSS custom property
references (with light-mode fallbacks).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 15:12:47 -07:00
Jeff Emmett fe031094a4 fix(rnotes): resolve global scope for notebook docIds and seeding
The rnotes module has defaultScope: 'global' but several code paths
used the raw space slug instead of the resolved scope:

- subscribeNotebook() built docIds with space slug (e.g. jeff🎶...)
  instead of resolved scope (global🎶...), breaking Automerge sync
- seedDemoIfEmpty() seeded notebooks under space-scoped paths, orphaning
  them when the API queries global-scoped prefix
- onSpaceCreate() created default notebook under space scope

Also migrated 3 orphaned jeff-scoped notebook files to global/ on server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 14:36:38 -07:00
Jeff Emmett f5388ecc2c fix(rwallet): migrate to new Safe Global API (api.safe.global)
Safe Global deprecated per-chain subdomains (safe-transaction-*.safe.global)
in favour of api.safe.global/tx-service/{shortcode}. The old URLs now 308
redirect, and the /balances/usd/ endpoint no longer exists.

- Update CHAIN_MAP prefixes to new shortcodes (eth, oeth, gno, etc.)
- Switch all Safe API calls to new base URL
- Use /balances/ instead of /balances/usd/ (fiat data no longer available)
- Normalize balance response with native token info and placeholder fiat fields
- Update client to show tokens with non-zero balance even without fiat data
- Update encryptid/server.ts Safe verify endpoint to new API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:57:09 -07:00
Jeff Emmett f9ccd18f15 fix(rnotes): add auth headers to all REST API calls
folk-notes-app was missing Authorization headers on all fetch calls,
causing 403 errors on non-demo spaces. Now uses getAccessToken() from
rstack-identity consistently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:51:04 -07:00
Jeff Emmett 494568599f fix(rnotes): cache-bust JS/CSS for notebook creation fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:39:55 -07:00
Jeff Emmett c73635dcd3 fix(rsocials): remove redundant /demo space prefix from internal URLs
Subdomain routing (demo.rspace.online) already prepends the space,
so links should use /rsocials/... not /demo/rsocials/...

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:38:57 -07:00
Jeff Emmett 0b5e8198d6 fix(rwallet): always show testnet chains when toggle is on
Testnet chains (Sepolia, Base Sepolia) are now always included in
detection results when testnets are toggled on, even if no native
balance is found. This allows viewing ERC-20 tokens on testnets
where native ETH balance may be zero.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:38:39 -07:00
Jeff Emmett b553acd756 merge: resolve rsocials conflict, keep dev campaigns page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:32:45 -07:00
Jeff Emmett 79f0ebb538 fix(rnotes): create notebook instantly without browser prompt
Skip the browser prompt() dialog — clicking "+ New Notebook" now
immediately creates an "Untitled Notebook" and opens it for editing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:32:03 -07:00
Jeff Emmett 6173e17bb9 feat(rsocials): hub navigation page, route renames, and campaigns listing
Restructure rSocials landing to a hub with navigation cards for Campaigns,
Threads, and Thread Editor. Rename /thread routes to /thread-editor for
clarity. Render campaigns listing inline instead of redirecting. Also
improve rNotes notebook creation to auto-open first note for editing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:31:23 -07:00
Jeff Emmett 01a4c95fa2 chore(rwallet): cache-bust wallet viewer JS (v=2)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:56:36 -07:00
Jeff Emmett b8f32a863e chore(rwallet): cache-bust wallet viewer JS (v=2)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:56:18 -07:00
Jeff Emmett 20c26cd3d7 feat: scope system, cross-space navigation, and spaces-as-layers
Phase 1 — Fix scope system: new scope-resolver.ts resolves global vs
space-scoped docId prefixes. Server middleware sets effectiveSpace on
Hono context. All 18 modules updated to use dataSpace for Automerge
doc access while keeping space for display. Client runtime gets
setModuleScopes() and resolveDocSpace() for local-first scope
resolution.

Phase 2 — Seamless cross-space navigation: TabCache now tracks panes
per space:module key. OfflineRuntime maintains lazy WebSocket
connections per space. Space-switcher dispatches space-switch event
handled client-side with history.pushState instead of full reload.

Phase 3 — Spaces as layers: Layer type extended with spaceSlug and
spaceRole. Tab bar gains "Add Space Layer" picker. Canvas renders
cross-space shapes with visual indicators. Space layers persisted as
SpaceRefs via nesting API. Runtime provides getAllActiveSpaces() and
subscribeModuleAcrossSpaces() for module-level data aggregation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 06:33:32 +00:00
Jeff Emmett bfb3a588ed feat(rwallet): rich landing UI, testnet toggle, and wallet type badges
Replaces the bare address-only empty state with a full dashboard landing:
hero branding, supported chain chips, feature cards, clickable example
wallets (TEC, Gitcoin, Vitalik), and animated loading spinner.

Adds testnet toggle (off by default) that filters Sepolia/Base Sepolia
from detection endpoints via ?testnets=true query param. Shows Safe
Multisig / EOA Wallet badge when a wallet is loaded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:42:23 -08:00
Jeff Emmett be8982f160 chore: switch payment defaults from staging to production
- Transak: default env PRODUCTION, fail if API key missing instead of
  falling back to STAGING_KEY
- Coinbase: default environment production instead of sandbox
- Openfort: default chain ID 8453 (Base mainnet) instead of 84532 (Sepolia)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:28:22 -08:00
Jeff Emmett a62a33b4dc feat: add wallet and spaces sections to EncryptID account page
- My Wallet: shows linked wallet address with rWallet link, plus any
  pending fund claims with "Claim Now" button
- My Spaces: lists communities the user belongs to with role and
  treasury wallet links
- New APIs: GET /api/user/spaces, GET /api/user/claims
- New DB function: listSpacesForUser()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:57:55 -08:00
Jeff Emmett f662881170 fix: cumulative fund claims — deposits accumulate until claimed
Instead of expiring old claims, new deposits add to the existing
pending claim's fiat_amount and extend the expiry. The claim email
shows the updated total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:42:44 -08:00
Jeff Emmett 6c807afeb0 fix: one wallet per EncryptID user, deduplicate fund claims
- OpenfortProvider.findOrCreateWallet() searches by player name before
  creating, ensuring the same email always maps to the same wallet
- Fund claims endpoint expires old pending claims before creating new ones
- Added expireFundClaim() to db layer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:33:45 -08:00
Jeff Emmett 5f3ffcc8d2 feat: add EOA wallet support to rWallet for any address
Previously rWallet only supported Safe multisig wallets. Now it falls
back to EOA detection via public RPC endpoints when no Safe is found,
allowing any Ethereum address to view native balances across chains.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:17:46 -08:00
Jeff Emmett c2f9a07389 feat(encryptid): link claimed funds to rWallet with wallet address
After claiming funds, the success button now links to
{username}.rspace.online/rwallet?address={walletAddress} instead of
rflows.online, so users can visually analyze their wallet flows.

The accept endpoint now returns the username for URL construction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:10:53 -08:00
Jeff Emmett 8a4fc3051e fix(encryptid): fix JS syntax errors breaking entire landing page
Template literal escape sequences (\' and \\') were consumed by the
template engine, producing broken JavaScript that prevented the entire
<script> block from parsing — no click handlers worked at all.

- removeGuardian onclick: replaced inline onclick with data-guardian-id
  attribute and event delegation (avoids quote escaping entirely)
- device link page: replaced \\' with String.fromCharCode(39)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:55:21 -08:00
Jeff Emmett 57a72e53d4 fix(encryptid): fix claim page login flow
- Claim page now checks both encryptid_token and encryptid_session keys
- Login/register links point to /?redirect=... instead of /auth/login
- Landing page redirects to claim page after successful login
- Support ?tab=register to auto-switch to registration form

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:50:14 -08:00
Jeff Emmett bc73e6e1be fix: claim page syntax error and rflows delete-in-editor bug
- claim page: \'ll in template literal had backslash consumed, breaking
  browser JS parser (Unexpected identifier 'll'). Changed to "will"
- rflows: use composedPath()[0] to pierce Shadow DOM event retargeting
  so Delete/Backspace in editor inputs doesn't delete canvas nodes
- rflows: block Delete/Backspace at document level when editor is open
- rflows: add stopPropagation on editor panel inputs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:44:02 -08:00
Jeff Emmett 9cdf246cc1 fix(rflows): prevent Delete/Backspace from deleting nodes while editing
- Use composedPath()[0] instead of e.target in document keydown handler
  to pierce Shadow DOM event retargeting (inputs inside shadow DOM were
  seen as the host element, bypassing the INPUT tag guard)
- Block Delete/Backspace at document level when editor or inline config
  panel is open
- Add stopPropagation on editor panel inputs for defense-in-depth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:41:00 -08:00
Jeff Emmett cdcb287188 fix(rflows): fallback to demo nodes when IDB document is corrupted
If no nodes were loaded after local-first init (corrupted Automerge
doc from previous IDB transaction errors), fall back to demo nodes
instead of showing empty "No nodes to display" screen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:29:52 -08:00
Jeff Emmett ef2ac6e74e fix: IDB transaction error and sidebar push desync
- storage.ts: read existing meta from same IDB transaction instead of
  calling getMeta() which opens a separate transaction, causing the
  write transaction to auto-commit before meta put executes
- rstack-app-switcher: track open/close state in #isOpen so re-renders
  don't leave body.rstack-sidebar-open orphaned, add click-outside
  handler, close sidebar on module select, clean up on disconnect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:02:18 -08:00
Jeff Emmett c21aab5797 fix(rflows): open Coinbase in popup (CSP blocks iframe), fix delete in modals
Coinbase pay.coinbase.com sets frame-ancestors CSP that blocks iframing
from third-party domains. Now opens Coinbase in a popup window with a
waiting modal, while Transak still uses iframe. Also prevents Delete/
Backspace from deleting canvas nodes when a modal overlay is open.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:51:30 -08:00
Jeff Emmett 10a801e045 feat(encryptid): add fund claim flow for on-ramp transactions
After payment via Coinbase/Transak, users receive a claim email to link
their funded wallet to their EncryptID account — no keys or seed phrases
needed. Adds fund_claims table, CRUD layer, internal service-to-service
API, public claim page, and post-payment UX showing claim instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:53:57 -08:00
Jeff Emmett bc139de0cc feat: add Image Studio for brand-consistent img2img generation
New folk-image-studio canvas shape with multi-image style analysis
(Gemini vision), img2img via fal.ai FLUX and Gemini Nano Banana models,
and image attachment support in folk-prompt chat.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:31:27 -08:00
Jeff Emmett 3e4cdcee0e feat(rflows): dual provider support — Coinbase + Transak selectable
- user-onramp endpoint accepts optional `provider` param
- /api/onramp/config returns `available` array of configured providers
- Fund modal shows provider dropdown when both are available
- Transak widget URL now built server-side (consistent with Coinbase flow)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:29:04 -08:00
Jeff Emmett 4701e74784 feat(rflows): move on-ramp directly into rspace-online
Replace flow-service proxy for user-onramp with direct Coinbase + Openfort
provider calls. Eliminates double-hop dependency and simplifies deployment.

- Add CoinbaseOnrampProvider (Ed25519 JWT → CDP session → widget URL)
- Add OpenfortProvider (smart wallet creation on Base)
- Initialize providers from env vars in onInit()
- Update folk-flows-app to support Coinbase widget alongside Transak
- Add jose and @openfort/openfort-node dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 18:20:35 -08:00
Jeff Emmett 266bc1cdef fix(rflows): node vanish on edit, API URL prefix, add Quick Fund UX
- Fix redrawNodeOnly() to use innerHTML for atomic DOM replacement
  instead of child-by-child moves that cause blur-event reentrancy
- Fix openTransakWidget/openUserOnRamp to use getApiBase() instead
  of hardcoded /s/ prefix
- Add content-type checking before res.json() to surface clear errors
- Add try/catch in server proxy for flow-service unreachable (502)
- Add "Fund" toolbar button with quickFund() one-step modal
- Replace promptEmail() with promptFundDetails() (amount+email+label)
- Pass actual flowRate instead of hardcoded fiatAmount: 50

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:58:36 -08:00
Jeff Emmett a0ad58df57 feat(rflows): integrate payment-infra user on-ramp for wallet-less funding
- Add POST /api/flows/user-onramp proxy route in mod.ts
- Add promptEmail(), openWidgetModal(), openUserOnRamp() methods
- Fund with Card now branches: wallet exists → direct Transak, no wallet → email → wallet → Transak
- Show truncated wallet address in source modal when provisioned

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:05:10 -08:00
Jeff Emmett eeae7d2aa1 feat(rflows): close source→funnel flow gap, interactive sizing + drag handles
Source nodes now drive funnel inflow rates via computeInflowRates() which
sums source allocations before each simulation tick. Source width scales
with flowRate, funnel height scales linearly with capacity, and valve/
capacity drag handles are always visible on hover (no inline-edit needed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:52:09 -08:00
Jeff Emmett 5b2862afd7 fix: prevent Automerge sync from overwriting local image state
onChange handler was resetting _tweetImages on every sync event,
undoing local deletions before they could round-trip. Now only
updates state in readonly mode; edit mode preserves local changes.

Also add cache-busting timestamps to uploaded/generated image URLs
to prevent browser from showing stale cached images.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:43:16 -08:00
Jeff Emmett b7111f01ee fix: deep-clone Automerge proxies to prevent stale image data
Automerge proxy objects silently ignore property writes outside
change() calls. When this._thread was a proxy, removeTweetImage's
assignment to this._thread.tweetImages was silently discarded,
causing deleted photos to reappear.

Fix by deep-cloning all Automerge reads (subscribeOffline, onChange,
loadDraft) so this._thread is always a plain mutable object.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:14:46 -08:00
Jeff Emmett 0eb6e8fe42 fix: create-space page now uses theme system for light/dark mode
- Replace hardcoded dark colors with --rs-* CSS variables from theme.css
- Add theme.css import and theme initialization script
- Respond to theme changes via localStorage event listener
- Upgrade visibility selector from <select> to radio card UI
- Add subtle gradient background and focus ring styles
- Remove hardcoded data-theme="dark" from body/header

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:12:31 -08:00
Jeff Emmett b591267b81 feat: remove title/header image, add Twitter-style link previews
Remove the title input and header image upload/generate section from
the thread builder editor. Title is now auto-derived from first tweet.

Add link preview cards that render inline in tweet content, similar to
Twitter's URL card unfurling. Server-side /api/link-preview endpoint
fetches OG metadata (title, description, image) with caching.

URLs in tweet text are rendered as clickable links.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:04:58 -08:00