From 0e9d00d2acf3bf04168744f554c92c6056e75f33 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 9 Mar 2026 18:15:05 -0700 Subject: [PATCH] feat(rsocials): add Listmonk newsletter page + legacy domain redirect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /newsletter-list route embedding Listmonk via iframe - Add LISTMONK_URL env var to docker-compose - Add Traefik redirect: social.jeffemmett.com → demo.rspace.online/rsocials - Add backlog task for linked wallets security hardening (TASK-HIGH.5) Co-Authored-By: Claude Opus 4.6 --- ...Wallets-to-EncryptID-Security-Hardening.md | 42 +++++++++++++++++++ docker-compose.yml | 10 +++++ modules/rsocials/mod.ts | 36 ++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 backlog/tasks/task-high.5 - Link-External-Wallets-to-EncryptID-Security-Hardening.md diff --git a/backlog/tasks/task-high.5 - Link-External-Wallets-to-EncryptID-Security-Hardening.md b/backlog/tasks/task-high.5 - Link-External-Wallets-to-EncryptID-Security-Hardening.md new file mode 100644 index 0000000..ea81df5 --- /dev/null +++ b/backlog/tasks/task-high.5 - Link-External-Wallets-to-EncryptID-Security-Hardening.md @@ -0,0 +1,42 @@ +--- +id: TASK-HIGH.5 +title: Link External Wallets to EncryptID + Security Hardening +status: Done +assignee: [] +created_date: '2026-03-10 01:07' +updated_date: '2026-03-10 01:08' +labels: [] +dependencies: [] +parent_task_id: TASK-HIGH +--- + +## Description + + +Implemented EIP-6963 wallet discovery, SIWE ownership verification, server-side AES-256-GCM encrypted storage, and Safe owner addition flow. Full security audit addressed 16 findings across Critical, High, Medium, Low, and Informational categories. + + +## Acceptance Criteria + +- [x] #1 EIP-6963 provider discovery for browser wallets +- [x] #2 SIWE (Sign-In with Ethereum) ownership verification +- [x] #3 Server-side AES-256-GCM encryption at rest for linked wallet data +- [x] #4 Safe add-owner-proposal with threshold validation +- [x] #5 Security: real encryption replaces Base64 (C-1) +- [x] #6 Security: XSS-safe token name escaping (H-1) +- [x] #7 Security: salted address hashes (H-2) +- [x] #8 Security: rate limiting on nonce endpoint (H-3) +- [x] #9 Security: sender verified against JWT (H-4) +- [x] #10 Security: icon URI sanitization (M-1) +- [x] #11 Security: threshold bounds checking (M-2) +- [x] #12 Security: SSRF prevention via address validation (M-3) +- [x] #13 Security: no cleartext sessionStorage cache (M-4) +- [x] #14 Security: low-severity hardening (L-1 through L-7) +- [x] #15 Security: headers and EIP-712 fixes (I-1, I-9) + + +## Implementation Notes + + +Implemented across 5 commits (c789481, d861c0a, 45f5cea, 92fde65, bc810d3). New files: eip6963.ts, external-signer.ts, linked-wallets.ts. Modified: server.ts, db.ts, session.ts, schema.sql, mod.ts, folk-wallet-viewer.ts. Full security audit: 16 findings (1C, 4H, 4M, 7L, 9I) — all actionable items resolved. + diff --git a/docker-compose.yml b/docker-compose.yml index e3d6a08..eec359b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,7 @@ services: - INFISICAL_AI_CLIENT_SECRET=${INFISICAL_AI_CLIENT_SECRET} - INFISICAL_AI_PROJECT_SLUG=claude-ops - INFISICAL_AI_SECRET_PATH=/ai + - LISTMONK_URL=https://newsletter.cosmolocal.world depends_on: rspace-db: condition: service_healthy @@ -151,6 +152,15 @@ services: - "traefik.http.routers.rspace-rsocials.entrypoints=web" - "traefik.http.routers.rspace-rsocials.priority=120" - "traefik.http.routers.rspace-rsocials.service=rspace-online" + # ── Legacy redirect: social.jeffemmett.com → demo.rspace.online/rsocials ── + - "traefik.http.routers.rspace-social-redirect.rule=Host(`social.jeffemmett.com`)" + - "traefik.http.routers.rspace-social-redirect.entrypoints=web" + - "traefik.http.routers.rspace-social-redirect.priority=130" + - "traefik.http.middlewares.social-redirect.redirectregex.regex=^https?://social\\.jeffemmett\\.com(.*)" + - "traefik.http.middlewares.social-redirect.redirectregex.replacement=https://demo.rspace.online/rsocials$${1}" + - "traefik.http.middlewares.social-redirect.redirectregex.permanent=true" + - "traefik.http.routers.rspace-social-redirect.middlewares=social-redirect" + - "traefik.http.routers.rspace-social-redirect.service=rspace-online" # Service configuration - "traefik.http.services.rspace-online.loadbalancer.server.port=3000" - "traefik.docker.network=traefik-public" diff --git a/modules/rsocials/mod.ts b/modules/rsocials/mod.ts index 8bb13ee..80d9c0f 100644 --- a/modules/rsocials/mod.ts +++ b/modules/rsocials/mod.ts @@ -576,6 +576,10 @@ function renderDemoFeedHTML(): string { // The /scheduler route renders a full-page iframe shell. const POSTIZ_URL = process.env.POSTIZ_URL || "https://demo.rsocials.online"; +// ── Listmonk newsletter — embedded via iframe ── +// Listmonk admin at newsletter.cosmolocal.world (not behind CF Access). +const LISTMONK_URL = process.env.LISTMONK_URL || "https://newsletter.cosmolocal.world"; + routes.get("/scheduler", (c) => { const space = c.req.param("space") || "demo"; const dataSpace = (c.get("effectiveSpace" as any) as string) || space; @@ -590,6 +594,19 @@ routes.get("/scheduler", (c) => { })); }); +routes.get("/newsletter-list", (c) => { + const space = c.req.param("space") || "demo"; + return c.html(renderExternalAppShell({ + title: `Newsletter List — rSocials | rSpace`, + moduleId: "rsocials", + spaceSlug: space, + modules: getModuleInfoList(), + theme: "dark", + appName: "Listmonk", + appUrl: LISTMONK_URL, + })); +}); + routes.get("/feed", (c) => { const space = c.req.param("space") || "demo"; const dataSpace = (c.get("effectiveSpace" as any) as string) || space; @@ -670,6 +687,13 @@ routes.get("/", (c) => {

Compose and preview tweet threads with live card preview

+ + 📧 + + `, })); @@ -740,5 +764,17 @@ export const socialsModule: RSpaceModule = { { icon: "🖼️", title: "Share Images", text: "Auto-generate a branded share image of your thread for cross-posting." }, ], }, + { + path: "newsletter-list", + title: "Newsletter List", + icon: "📧", + tagline: "rSocials Tool", + description: "Manage newsletter subscribers and send email campaigns via the embedded Listmonk interface.", + features: [ + { icon: "👥", title: "Subscriber Management", text: "View, import, and manage newsletter subscribers across multiple mailing lists." }, + { icon: "📨", title: "Email Campaigns", text: "Compose and send email campaigns with templates and scheduling." }, + { icon: "📊", title: "Analytics", text: "Track open rates, click-throughs, and subscriber engagement." }, + ], + }, ], };