diff --git a/docker-compose.yml b/docker-compose.yml index 9f38ff8..fcb0030 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,7 @@ services: - INFISICAL_PROJECT_SLUG=rspace - INFISICAL_ENV=prod - INFISICAL_URL=http://infisical:8080 + - JWT_SECRET=${JWT_SECRET} - FLOW_SERVICE_URL=http://payment-flow:3010 - FLOW_ID=a79144ec-e6a2-4e30-a42a-6d8237a5953d - FUNNEL_ID=0ff6a9ac-1667-4fc7-9a01-b1620810509f diff --git a/modules/rbnb/mod.ts b/modules/rbnb/mod.ts index d87c734..2b446f2 100644 --- a/modules/rbnb/mod.ts +++ b/modules/rbnb/mod.ts @@ -14,7 +14,7 @@ import * as Automerge from "@automerge/automerge"; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { bnbSchema, bnbDocId } from './schemas'; @@ -573,7 +573,7 @@ routes.get("/api/listings", async (c) => { routes.post("/api/listings", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -637,7 +637,7 @@ routes.get("/api/listings/:id", async (c) => { routes.patch("/api/listings/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -721,7 +721,7 @@ routes.get("/api/listings/:id/availability", async (c) => { routes.post("/api/listings/:id/availability", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -755,7 +755,7 @@ routes.post("/api/listings/:id/availability", async (c) => { routes.patch("/api/availability/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -815,7 +815,7 @@ routes.get("/api/stays", async (c) => { routes.post("/api/stays", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -888,7 +888,7 @@ function stayTransition(statusTarget: StayStatus, timestampField: 'respondedAt' return async (c: any) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -918,7 +918,7 @@ routes.post("/api/stays/:id/complete", stayTransition('completed', 'completedAt' routes.post("/api/stays/:id/messages", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -968,7 +968,7 @@ routes.get("/api/endorsements", async (c) => { routes.post("/api/endorsements", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -1167,7 +1167,7 @@ routes.get("/api/config", async (c) => { routes.patch("/api/config", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; diff --git a/modules/rbooks/mod.ts b/modules/rbooks/mod.ts index cbdd7b9..416d63f 100644 --- a/modules/rbooks/mod.ts +++ b/modules/rbooks/mod.ts @@ -17,10 +17,7 @@ import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module"; import { renderLanding } from "./landing"; -import { - verifyEncryptIDToken, - extractToken, -} from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import type { SyncServer } from '../../server/local-first/sync-server'; import { booksCatalogSchema, @@ -163,7 +160,7 @@ routes.post("/api/books", async (c) => { let claims; try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } diff --git a/modules/rcal/mod.ts b/modules/rcal/mod.ts index 6a6b4f5..d7e1cc0 100644 --- a/modules/rcal/mod.ts +++ b/modules/rcal/mod.ts @@ -13,7 +13,7 @@ import * as Automerge from "@automerge/automerge"; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { calendarSchema, calendarDocId } from './schemas'; @@ -315,7 +315,7 @@ routes.post("/api/events", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -404,7 +404,7 @@ routes.post("/api/events", async (c) => { routes.post("/api/import-ics", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -573,7 +573,7 @@ routes.patch("/api/events/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -672,7 +672,7 @@ routes.get("/api/sources", async (c) => { routes.post("/api/sources", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; diff --git a/modules/rcart/mod.ts b/modules/rcart/mod.ts index 0e056fb..a9b3e98 100644 --- a/modules/rcart/mod.ts +++ b/modules/rcart/mod.ts @@ -14,7 +14,7 @@ import { renderShell, buildSpaceUrl } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import { depositOrderRevenue } from "./flow"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { @@ -484,7 +484,7 @@ routes.post("/api/orders", async (c) => { const token = extractToken(c.req.raw.headers); let buyerDid: string | null = null; if (token) { - try { const claims = await verifyEncryptIDToken(token); buyerDid = claims.sub; } catch {} + try { const claims = await verifyToken(token); buyerDid = claims.sub; } catch {} } const body = await c.req.json(); @@ -567,7 +567,7 @@ routes.get("/api/orders", async (c) => { const token = extractToken(c.req.raw.headers); let authedBuyer: string | null = null; if (token) { - try { const claims = await verifyEncryptIDToken(token); authedBuyer = claims.sub; } catch {} + try { const claims = await verifyToken(token); authedBuyer = claims.sub; } catch {} } const { status, provider_id, buyer_id, limit = "50", offset = "0" } = c.req.query(); @@ -1426,7 +1426,7 @@ routes.post("/api/payments", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { @@ -1512,7 +1512,7 @@ routes.get("/api/payments", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const paymentDocs = getSpacePaymentDocs(space); const payments = paymentDocs @@ -2251,7 +2251,7 @@ routes.post("/api/group-buys", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { catalogEntryId, tiers, description, closesInDays } = body; @@ -2371,7 +2371,7 @@ routes.post("/api/group-buys/:id/pledge", async (c) => { let buyerId: string | null = null; const token = extractToken(c.req.raw.headers); if (token) { - try { const claims = await verifyEncryptIDToken(token); buyerId = claims.sub || null; } catch { /* public pledge */ } + try { const claims = await verifyToken(token); buyerId = claims.sub || null; } catch { /* public pledge */ } } const pledgeId = crypto.randomUUID(); diff --git a/modules/rfiles/mod.ts b/modules/rfiles/mod.ts index 105a381..e7f5222 100644 --- a/modules/rfiles/mod.ts +++ b/modules/rfiles/mod.ts @@ -14,7 +14,7 @@ import * as Automerge from "@automerge/automerge"; import { renderShell, renderExternalAppShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { filesSchema, filesDocId } from './schemas'; @@ -166,7 +166,7 @@ routes.post("/api/files", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const formData = await c.req.formData(); const file = formData.get("file") as File | null; @@ -300,7 +300,7 @@ routes.post("/api/files/:id/share", async (c) => { const authToken = extractToken(c.req.raw.headers); if (!authToken) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } const fileId = c.req.param("id"); const space = c.req.param("space") || c.req.query("space") || "default"; @@ -376,7 +376,7 @@ routes.post("/api/shares/:shareId/revoke", async (c) => { const authToken = extractToken(c.req.raw.headers); if (!authToken) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } const shareId = c.req.param("shareId"); const space = c.req.param("space") || c.req.query("space") || "default"; @@ -506,7 +506,7 @@ routes.post("/api/cards", async (c) => { const authToken = extractToken(c.req.raw.headers); if (!authToken) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json<{ title: string; body?: string; card_type?: string; tags?: string[]; shared_space?: string }>(); const space = c.req.param("space") || body.shared_space || "default"; diff --git a/modules/rflows/mod.ts b/modules/rflows/mod.ts index 9c83b09..727caf9 100644 --- a/modules/rflows/mod.ts +++ b/modules/rflows/mod.ts @@ -9,7 +9,7 @@ import * as Automerge from "@automerge/automerge"; import { renderShell } from "../../server/shell"; import type { RSpaceModule } from "../../shared/module"; import { getModuleInfoList } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import { getTransakEnv, getTransakWebhookSecret } from "../../shared/transak"; import type { SyncServer } from '../../server/local-first/sync-server'; @@ -151,7 +151,7 @@ routes.post("/api/flows", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.text(); const res = await fetch(`${FLOW_SERVICE_URL}/api/flows`, { @@ -386,7 +386,7 @@ const ENTRY_POINT = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'; // v0.6 routes.post("/api/flows/submit-userop", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } if (!_pimlico) return c.json({ error: "Pimlico bundler not configured" }, 503); @@ -404,7 +404,7 @@ routes.post("/api/flows/submit-userop", async (c) => { routes.post("/api/flows/send-userop", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } if (!_pimlico) return c.json({ error: "Pimlico bundler not configured" }, 503); @@ -424,7 +424,7 @@ routes.post("/api/flows/send-userop", async (c) => { routes.get("/api/flows/userop/:hash", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } if (!_pimlico) return c.json({ error: "Pimlico bundler not configured" }, 503); @@ -540,7 +540,7 @@ routes.post("/api/space-flows", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const { space, flowId } = await c.req.json(); if (!space || !flowId) return c.json({ error: "space and flowId required" }, 400); @@ -560,7 +560,7 @@ routes.delete("/api/space-flows/:flowId", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const flowId = c.req.param("flowId"); const space = c.req.query("space") || ""; @@ -636,7 +636,7 @@ routes.post("/api/mortgage/positions", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json() as Partial; if (!body.principal || !body.termMonths || !body.interestRate) { @@ -710,7 +710,7 @@ routes.post("/api/budgets/allocate", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const { space, allocations } = await c.req.json() as { space?: string; allocations?: Record }; if (!allocations) return c.json({ error: "allocations required" }, 400); @@ -745,7 +745,7 @@ routes.post("/api/budgets/segments", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const { space, action, segmentId, name, color } = await c.req.json() as { space?: string; action: 'add' | 'remove'; segmentId?: string; name?: string; color?: string; diff --git a/modules/rforum/mod.ts b/modules/rforum/mod.ts index 5533212..c1a0c84 100644 --- a/modules/rforum/mod.ts +++ b/modules/rforum/mod.ts @@ -9,7 +9,7 @@ import { renderShell, renderExternalAppShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import { provisionInstance, destroyInstance } from "./lib/provisioner"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { @@ -44,7 +44,7 @@ routes.get("/api/instances", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const doc = ensureDoc(); const instances = Object.values(doc.instances).filter( @@ -60,7 +60,7 @@ routes.post("/api/instances", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json<{ name: string; @@ -126,7 +126,7 @@ routes.get("/api/instances/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const doc = ensureDoc(); const instance = doc.instances[c.req.param("id")]; @@ -142,7 +142,7 @@ routes.delete("/api/instances/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const doc = ensureDoc(); const instance = doc.instances[c.req.param("id")]; diff --git a/modules/rinbox/mod.ts b/modules/rinbox/mod.ts index abd9f52..a40f831 100644 --- a/modules/rinbox/mod.ts +++ b/modules/rinbox/mod.ts @@ -13,7 +13,7 @@ import * as Automerge from "@automerge/automerge"; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { @@ -548,7 +548,7 @@ routes.post("/api/mailboxes", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { slug, name, email, description, visibility = "private", imap_user, imap_password } = body; @@ -710,7 +710,7 @@ routes.post("/api/threads/:id/comments", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const threadId = c.req.param("id"); const body = await c.req.json(); @@ -756,7 +756,7 @@ routes.post("/api/threads/:id/reply", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const threadId = c.req.param("id"); const found = findThreadById(threadId); @@ -805,7 +805,7 @@ routes.post("/api/threads/:id/reply-all", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const threadId = c.req.param("id"); const found = findThreadById(threadId); @@ -866,7 +866,7 @@ routes.post("/api/threads/:id/forward", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const threadId = c.req.param("id"); const found = findThreadById(threadId); @@ -963,7 +963,7 @@ routes.post("/api/approvals", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { mailbox_slug, thread_id, subject, body_text, to_addresses, cc_addresses, in_reply_to, references: refs, reply_type } = body; @@ -1009,7 +1009,7 @@ routes.post("/api/approvals/:id/sign", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const id = c.req.param("id"); const body = await c.req.json(); @@ -1083,7 +1083,7 @@ routes.post("/api/personal-inboxes", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { label, email, imap_host, imap_port, imap_user, imap_pass, smtp_host, smtp_port, smtp_user, smtp_pass } = body; @@ -1158,7 +1158,7 @@ routes.get("/api/personal-inboxes", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const inboxes: any[] = []; for (const { doc } of getAllMailboxDocs()) { @@ -1188,7 +1188,7 @@ routes.delete("/api/personal-inboxes/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const inboxId = c.req.param("id"); _personalCredentials.delete(inboxId); @@ -1214,7 +1214,7 @@ routes.post("/api/agent-inboxes", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { name, email, personality, auto_reply = false, auto_classify = false, rules = [] } = body; @@ -1287,7 +1287,7 @@ routes.get("/api/agent-inboxes", async (c) => { routes.delete("/api/agent-inboxes/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const agentId = c.req.param("id"); @@ -1328,7 +1328,7 @@ routes.post("/api/workspaces", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { slug, name, description } = body; diff --git a/modules/rnotes/mod.ts b/modules/rnotes/mod.ts index 895605c..83a2a30 100644 --- a/modules/rnotes/mod.ts +++ b/modules/rnotes/mod.ts @@ -13,7 +13,7 @@ import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module"; import { resolveDataSpace } from "../../shared/scope-resolver"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import { notebookSchema, notebookDocId, connectionsDocId, createNoteItem } from "./schemas"; import type { NotebookDoc, NoteItem, ConnectionsDoc } from "./schemas"; @@ -285,7 +285,7 @@ routes.post("/api/notebooks", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { title, description, cover_color } = body; @@ -339,7 +339,7 @@ routes.put("/api/notebooks/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const id = c.req.param("id"); const body = await c.req.json(); @@ -437,7 +437,7 @@ routes.post("/api/notes", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { notebook_id, title, content, content_format, type, url, language, file_url, mime_type, file_size, duration, tags } = body; @@ -641,7 +641,7 @@ routes.post("/api/import/upload", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const formData = await c.req.formData(); const file = formData.get("file") as File | null; @@ -693,7 +693,7 @@ routes.post("/api/import/files", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const formData = await c.req.formData(); const notebookId = formData.get("notebookId") as string | null; @@ -745,7 +745,7 @@ routes.post("/api/import/notion", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { pageIds, notebookId, recursive } = body; @@ -791,7 +791,7 @@ routes.post("/api/import/google-docs", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { docIds, notebookId } = body; @@ -996,7 +996,7 @@ routes.post("/api/export/notion", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { notebookId, noteIds, parentId } = body; @@ -1036,7 +1036,7 @@ routes.post("/api/export/google-docs", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { notebookId, noteIds, parentId } = body; @@ -1078,7 +1078,7 @@ routes.post("/api/sync/note/:noteId", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const noteId = c.req.param("noteId"); const found = findNote(dataSpace, noteId); @@ -1131,7 +1131,7 @@ routes.post("/api/sync/notebook/:id", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const notebookId = c.req.param("id"); const docId = notebookDocId(dataSpace, notebookId); @@ -1191,7 +1191,7 @@ routes.post("/api/sync/upload", async (c) => { const dataSpace = c.get("effectiveSpace") || space; const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const formData = await c.req.formData(); const file = formData.get("file") as File | null; @@ -1306,7 +1306,7 @@ const UPLOAD_DIR = "/data/files/generated"; routes.post("/api/uploads", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const formData = await c.req.formData(); const file = formData.get("file") as File | null; @@ -1351,7 +1351,7 @@ routes.get("/api/uploads/:filename", async (c) => { routes.post("/api/voice/transcribe", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } try { const formData = await c.req.formData(); @@ -1372,7 +1372,7 @@ routes.post("/api/voice/transcribe", async (c) => { routes.post("/api/voice/diarize", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } try { const formData = await c.req.formData(); @@ -1397,7 +1397,7 @@ const NOTEBOOK_API_URL = process.env.NOTEBOOK_API_URL || "http://open-notebook:5 routes.post("/api/notes/summarize", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const { content, model = "gemini-flash", length = "medium" } = await c.req.json<{ content: string; model?: string; length?: "short" | "medium" | "long"; @@ -1443,7 +1443,7 @@ routes.post("/api/notes/summarize", async (c) => { routes.post("/api/notes/deep-summarize", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const { noteIds, query } = await c.req.json<{ noteIds: string[]; query?: string }>(); if (!noteIds?.length) return c.json({ error: "noteIds required" }, 400); @@ -1498,7 +1498,7 @@ routes.post("/api/notes/deep-summarize", async (c) => { routes.post("/api/notes/send-to-notebook", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const { noteId, title, content } = await c.req.json<{ noteId: string; title: string; content: string; @@ -1535,7 +1535,7 @@ routes.post("/api/notes/send-to-notebook", async (c) => { routes.post("/api/articles/unlock", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Unauthorized" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const { url } = await c.req.json<{ url: string }>(); if (!url) return c.json({ error: "Missing url" }, 400); diff --git a/modules/rsocials/mod.ts b/modules/rsocials/mod.ts index 15f6e61..7cec291 100644 --- a/modules/rsocials/mod.ts +++ b/modules/rsocials/mod.ts @@ -32,8 +32,8 @@ import { import { DEMO_FEED } from "./lib/types"; import { getListmonkConfig, listmonkFetch } from "./lib/listmonk-proxy"; import { getPostizConfig, getIntegrations, createPost, createThread } from "./lib/postiz-client"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; -import type { EncryptIDClaims } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; +import type { EncryptIDClaims } from "../../server/auth"; import { resolveCallerRole, roleAtLeast } from "../../server/spaces"; import type { SpaceRoleString } from "../../server/spaces"; @@ -432,7 +432,7 @@ async function requireNewsletterRole(c: any, minRole: SpaceRoleString): Promise< const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const result = await resolveCallerRole(space, claims); @@ -1951,7 +1951,7 @@ routes.get("/newsletter-list", async (c) => { const token = extractToken(c.req.raw.headers); if (token) { try { - const claims = await verifyEncryptIDToken(token); + const claims = await verifyToken(token); const result = await resolveCallerRole(space, claims); if (result) role = result.role; } catch { /* keep viewer default */ } diff --git a/modules/rsplat/mod.ts b/modules/rsplat/mod.ts index 226456b..8de0eb4 100644 --- a/modules/rsplat/mod.ts +++ b/modules/rsplat/mod.ts @@ -17,10 +17,7 @@ import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module"; import { renderLanding } from "./landing"; -import { - verifyEncryptIDToken, - extractToken, -} from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { setupX402FromEnv } from "../../shared/x402/hono-middleware"; import type { SyncServer } from '../../server/local-first/sync-server'; import { @@ -300,7 +297,7 @@ routes.post("/api/splats", async (c) => { let claims; try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } @@ -413,7 +410,7 @@ routes.post("/api/splats/from-media", async (c) => { let claims; try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } @@ -557,7 +554,7 @@ routes.post("/api/splats/save-generated", async (c) => { let claims; try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } @@ -646,7 +643,7 @@ routes.get("/api/splats/my-history", async (c) => { let claims; try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } @@ -670,7 +667,7 @@ routes.delete("/api/splats/:id", async (c) => { let claims; try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } diff --git a/modules/rtasks/mod.ts b/modules/rtasks/mod.ts index 79ef34c..6521e35 100644 --- a/modules/rtasks/mod.ts +++ b/modules/rtasks/mod.ts @@ -13,7 +13,7 @@ import * as Automerge from '@automerge/automerge'; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { boardSchema, boardDocId, createTaskItem } from './schemas'; @@ -214,7 +214,7 @@ routes.post("/api/spaces", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { name, description, icon } = body; @@ -322,7 +322,7 @@ routes.post("/api/spaces/:slug/tasks", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const slug = c.req.param("slug"); const body = await c.req.json(); @@ -367,7 +367,7 @@ routes.patch("/api/tasks/:id", async (c) => { const token = extractToken(c.req.raw.headers); let updatedBy: string | null = null; if (token) { - try { const claims = await verifyEncryptIDToken(token); updatedBy = claims.sub; } catch {} + try { const claims = await verifyToken(token); updatedBy = claims.sub; } catch {} } const id = c.req.param("id"); diff --git a/modules/rtrips/mod.ts b/modules/rtrips/mod.ts index 6173365..08a7c67 100644 --- a/modules/rtrips/mod.ts +++ b/modules/rtrips/mod.ts @@ -13,7 +13,7 @@ import * as Automerge from "@automerge/automerge"; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { @@ -96,7 +96,7 @@ routes.post("/api/trips", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { title, description, start_date, end_date, budget_total, budget_currency } = body; @@ -199,7 +199,7 @@ routes.put("/api/trips/:id", async (c) => { routes.post("/api/trips/:id/destinations", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -236,7 +236,7 @@ routes.post("/api/trips/:id/destinations", async (c) => { routes.post("/api/trips/:id/itinerary", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -273,7 +273,7 @@ routes.post("/api/trips/:id/itinerary", async (c) => { routes.post("/api/trips/:id/bookings", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -311,7 +311,7 @@ routes.post("/api/trips/:id/bookings", async (c) => { routes.post("/api/trips/:id/expenses", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -362,7 +362,7 @@ routes.get("/api/trips/:id/packing", async (c) => { routes.post("/api/trips/:id/packing", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; diff --git a/modules/rtube/mod.ts b/modules/rtube/mod.ts index 1460eaf..ea76eb8 100644 --- a/modules/rtube/mod.ts +++ b/modules/rtube/mod.ts @@ -9,7 +9,7 @@ import { Hono } from "hono"; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import { S3Client, ListObjectsV2Command, GetObjectCommand, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; @@ -152,7 +152,7 @@ routes.get("/api/v/*", async (c) => { routes.post("/api/videos", async (c) => { const authToken = extractToken(c.req.raw.headers); if (!authToken) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } const client = getS3(); if (!client) return c.json({ error: "R2 not configured" }, 503); @@ -198,7 +198,7 @@ routes.get("/api/health", (c) => c.json({ ok: true })); routes.post("/api/360split", async (c) => { const authToken = extractToken(c.req.raw.headers); if (!authToken) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } if (!SPLIT_360_URL) return c.json({ error: "360split service not configured" }, 503); @@ -255,7 +255,7 @@ routes.get("/api/360split/status/:jobId", async (c) => { routes.post("/api/360split/import/:jobId", async (c) => { const authToken = extractToken(c.req.raw.headers); if (!authToken) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } if (!SPLIT_360_URL) return c.json({ error: "360split service not configured" }, 503); const client = getS3(); @@ -312,7 +312,7 @@ routes.post("/api/360split/import/:jobId", async (c) => { routes.post("/api/live-split", async (c) => { const authToken = extractToken(c.req.raw.headers); if (!authToken) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } if (!SPLIT_360_URL) return c.json({ error: "360split service not configured" }, 503); @@ -363,7 +363,7 @@ routes.get("/api/live-split/status/:sessionId", async (c) => { routes.post("/api/live-split/stop/:sessionId", async (c) => { const authToken = extractToken(c.req.raw.headers); if (!authToken) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); } if (!SPLIT_360_URL) return c.json({ error: "360split service not configured" }, 503); const sessionId = c.req.param("sessionId"); diff --git a/modules/rvnb/mod.ts b/modules/rvnb/mod.ts index 7e6783c..7348ca7 100644 --- a/modules/rvnb/mod.ts +++ b/modules/rvnb/mod.ts @@ -14,7 +14,7 @@ import * as Automerge from "@automerge/automerge"; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { vnbSchema, vnbDocId } from './schemas'; @@ -612,7 +612,7 @@ routes.get("/api/vehicles", async (c) => { routes.post("/api/vehicles", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -691,7 +691,7 @@ routes.get("/api/vehicles/:id", async (c) => { routes.patch("/api/vehicles/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -782,7 +782,7 @@ routes.get("/api/vehicles/:id/availability", async (c) => { routes.post("/api/vehicles/:id/availability", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -822,7 +822,7 @@ routes.post("/api/vehicles/:id/availability", async (c) => { routes.patch("/api/availability/:id", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -888,7 +888,7 @@ routes.get("/api/rentals", async (c) => { routes.post("/api/rentals", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -967,7 +967,7 @@ function rentalTransition(statusTarget: RentalStatus, timestampField: 'responded return async (c: any) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -997,7 +997,7 @@ routes.post("/api/rentals/:id/complete", rentalTransition('completed', 'complete routes.post("/api/rentals/:id/messages", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -1047,7 +1047,7 @@ routes.get("/api/endorsements", async (c) => { routes.post("/api/endorsements", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; @@ -1250,7 +1250,7 @@ routes.get("/api/config", async (c) => { routes.patch("/api/config", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const space = c.req.param("space") || "demo"; const dataSpace = c.get("effectiveSpace") || space; diff --git a/modules/rvote/mod.ts b/modules/rvote/mod.ts index 38ee9d1..a01b37a 100644 --- a/modules/rvote/mod.ts +++ b/modules/rvote/mod.ts @@ -15,7 +15,7 @@ import * as Automerge from '@automerge/automerge'; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; import { renderLanding } from "./landing"; import type { SyncServer } from '../../server/local-first/sync-server'; import { proposalSchema, proposalDocId, computeElo, ELO_DEFAULT } from './schemas'; @@ -286,7 +286,7 @@ routes.post("/api/spaces", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { name, slug, description, visibility = "public" } = body; @@ -359,7 +359,7 @@ routes.post("/api/proposals", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { space_slug, title, description } = body; @@ -407,7 +407,7 @@ routes.post("/api/proposals/:id/vote", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const id = c.req.param("id"); const body = await c.req.json(); @@ -470,7 +470,7 @@ routes.post("/api/proposals/:id/final-vote", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const id = c.req.param("id"); const body = await c.req.json(); @@ -564,7 +564,7 @@ routes.get("/api/proposals/pair", (c) => { routes.post("/api/proposals/compare", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); - try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json(); const { winnerId, loserId } = body; diff --git a/modules/rwallet/mod.ts b/modules/rwallet/mod.ts index dfe1026..353efcb 100644 --- a/modules/rwallet/mod.ts +++ b/modules/rwallet/mod.ts @@ -10,7 +10,7 @@ import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; import { renderLanding } from "./landing"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../../server/auth"; const routes = new Hono(); @@ -271,7 +271,7 @@ async function verifyWalletAuth(c: any): Promise<{ sub: string; did?: string; us const token = extractToken(c.req.raw.headers); if (!token) return null; try { - const claims = await verifyEncryptIDToken(token); + const claims = await verifyToken(token); return claims as any; } catch { return null; diff --git a/server/auth.ts b/server/auth.ts new file mode 100644 index 0000000..5e57e3e --- /dev/null +++ b/server/auth.ts @@ -0,0 +1,24 @@ +/** + * Auth wrapper — local JWT verification for rSpace server. + * + * When JWT_SECRET is available, verifies tokens locally via HMAC-SHA256 (<1ms). + * Otherwise falls back to internal HTTP call to EncryptID service. + * Re-exports extractToken and types for convenience. + */ + +import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; +import type { EncryptIDClaims, VerifyOptions } from "@encryptid/sdk/server"; + +export { extractToken }; +export type { EncryptIDClaims }; + +const JWT_SECRET = process.env.JWT_SECRET; +const ENCRYPTID_INTERNAL = process.env.ENCRYPTID_INTERNAL_URL || "http://encryptid:3000"; + +const verifyOpts: VerifyOptions = JWT_SECRET + ? { secret: JWT_SECRET } + : { serverUrl: ENCRYPTID_INTERNAL }; + +export function verifyToken(token: string): Promise { + return verifyEncryptIDToken(token, verifyOpts); +} diff --git a/server/encryptid-sdk.d.ts b/server/encryptid-sdk.d.ts index b74ef52..e28fa40 100644 --- a/server/encryptid-sdk.d.ts +++ b/server/encryptid-sdk.d.ts @@ -1,5 +1,16 @@ declare module '@encryptid/sdk/server' { - export function verifyEncryptIDToken(token: string): Promise; + export interface VerifyOptions { + /** JWT secret for local HMAC-SHA256 verification */ + secret?: string; + /** EncryptID server URL for remote verification */ + serverUrl?: string; + /** Expected audience */ + audience?: string; + /** Clock tolerance in seconds */ + clockTolerance?: number; + } + + export function verifyEncryptIDToken(token: string, options?: VerifyOptions): Promise; export function evaluateSpaceAccess( slug: string, token: string | null, diff --git a/server/local-first/backup-routes.ts b/server/local-first/backup-routes.ts index 24813d1..39a5e08 100644 --- a/server/local-first/backup-routes.ts +++ b/server/local-first/backup-routes.ts @@ -7,11 +7,8 @@ import { Hono } from "hono"; import type { Context, Next } from "hono"; -import { - verifyEncryptIDToken, - extractToken, -} from "@encryptid/sdk/server"; -import type { EncryptIDClaims } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "../auth"; +import type { EncryptIDClaims } from "../auth"; import { putBackup, getBackup, @@ -40,7 +37,7 @@ backupRouter.use("*", async (c: Context, next: Next) => { } let claims: EncryptIDClaims; try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { return c.json({ error: "Invalid or expired token" }, 401); } diff --git a/server/mi-routes.ts b/server/mi-routes.ts index e46ab53..3202538 100644 --- a/server/mi-routes.ts +++ b/server/mi-routes.ts @@ -13,8 +13,8 @@ import type { MiMessage } from "./mi-provider"; import { getModuleInfoList, getAllModules } from "../shared/module"; import { resolveCallerRole, roleAtLeast } from "./spaces"; import type { SpaceRoleString } from "./spaces"; -import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server"; -import type { EncryptIDClaims } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "./auth"; +import type { EncryptIDClaims } from "./auth"; import { buildModuleCapabilities, MODULE_ROUTES } from "../lib/mi-module-routes"; import type { MiAction } from "../lib/mi-actions"; @@ -40,7 +40,7 @@ mi.post("/ask", async (c) => { try { const token = extractToken(c.req.raw.headers); if (token) { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } } catch { /* unauthenticated → viewer */ } @@ -369,7 +369,7 @@ mi.post("/validate-actions", async (c) => { try { const token = extractToken(c.req.raw.headers); if (token) { - const claims = await verifyEncryptIDToken(token); + const claims = await verifyToken(token); if (space && claims) { const resolved = await resolveCallerRole(space, claims); if (resolved) callerRole = resolved.role; diff --git a/server/notification-routes.ts b/server/notification-routes.ts index d73dd07..33044da 100644 --- a/server/notification-routes.ts +++ b/server/notification-routes.ts @@ -5,10 +5,7 @@ */ import { Hono } from "hono"; -import { - verifyEncryptIDToken, - extractToken, -} from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "./auth"; import { getUserNotifications, getUnreadCount, @@ -28,7 +25,7 @@ async function requireAuth(req: Request) { const token = extractToken(req.headers); if (!token) return null; try { - return await verifyEncryptIDToken(token); + return await verifyToken(token); } catch { return null; } diff --git a/server/spaces.ts b/server/spaces.ts index 7ceb088..0ffb41c 100644 --- a/server/spaces.ts +++ b/server/spaces.ts @@ -59,11 +59,8 @@ import { invertDirection, computeMembranePermeability, } from "../lib/connection-types"; -import { - verifyEncryptIDToken, - extractToken, -} from "@encryptid/sdk/server"; -import type { EncryptIDClaims } from "@encryptid/sdk/server"; +import { verifyToken, extractToken } from "./auth"; +import type { EncryptIDClaims } from "./auth"; import { getAllModules, getModule } from "../shared/module"; import type { SpaceLifecycleContext } from "../shared/module"; import { syncServer } from "./sync-instance"; @@ -222,7 +219,7 @@ spaces.get("/", async (c) => { let claims: EncryptIDClaims | null = null; if (token) { try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { // Invalid token — treat as unauthenticated } @@ -320,7 +317,7 @@ spaces.post("/", async (c) => { let claims: EncryptIDClaims; try { - claims = await verifyEncryptIDToken(token); + claims = await verifyToken(token); } catch { return c.json({ error: "Invalid or expired token" }, 401); } @@ -397,7 +394,7 @@ spaces.patch("/:slug/modules", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const doc = getDocumentData(slug); @@ -484,7 +481,7 @@ spaces.get("/admin", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } if (!isAdmin(claims.sub)) return c.json({ error: "Admin access required" }, 403); const STORAGE_DIR = process.env.STORAGE_DIR || "./data/communities"; @@ -550,7 +547,7 @@ spaces.delete("/admin/:slug", async (c) => { const token = extractToken(c.req.raw.headers); if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } if (!isAdmin(claims.sub)) return c.json({ error: "Admin access required" }, 403); await loadCommunity(slug); @@ -598,7 +595,7 @@ spaces.get("/notifications", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const slugs = await listCommunities(); const ownedSlugs = new Set(); @@ -647,7 +644,7 @@ spaces.delete("/:slug", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } // Protect core spaces if (slug === "demo" || slug === "commonshub") { @@ -696,7 +693,7 @@ spaces.patch("/:slug", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -736,7 +733,7 @@ spaces.get("/:slug/members", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -765,7 +762,7 @@ spaces.patch("/:slug/members/:did", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -815,7 +812,7 @@ spaces.delete("/:slug/members/:did", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -856,7 +853,7 @@ spaces.get("/:slug/access-requests", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -896,7 +893,7 @@ spaces.patch("/:slug/nest-policy", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -935,7 +932,7 @@ spaces.post("/:slug/nest", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json<{ sourceSlug: string; @@ -1072,7 +1069,7 @@ spaces.patch("/:slug/nest/:refId", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1104,7 +1101,7 @@ spaces.delete("/:slug/nest/:refId", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1146,7 +1143,7 @@ spaces.get("/:slug/nested-in", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } // Must be admin/owner of the space to see where it's nested await loadCommunity(slug); @@ -1175,7 +1172,7 @@ spaces.get("/:slug/nest-requests", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1203,7 +1200,7 @@ spaces.patch("/:slug/nest-requests/:reqId", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1290,7 +1287,7 @@ spaces.patch("/:slug/connection-policy", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1316,7 +1313,7 @@ spaces.get("/:slug/connections", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1341,7 +1338,7 @@ spaces.post("/:slug/connections", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json<{ toSlug: string; @@ -1496,7 +1493,7 @@ spaces.get("/:slug/connections/:connId", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1523,7 +1520,7 @@ spaces.patch("/:slug/connections/:connId", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1592,7 +1589,7 @@ spaces.delete("/:slug/connections/:connId", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1641,7 +1638,7 @@ spaces.get("/:slug/connection-requests", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1669,7 +1666,7 @@ spaces.patch("/:slug/connection-requests/:reqId", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1759,7 +1756,7 @@ spaces.get("/:slug/membrane", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1803,7 +1800,7 @@ spaces.patch("/:slug/encryption", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1843,7 +1840,7 @@ spaces.post("/:slug/access-requests", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1904,7 +1901,7 @@ spaces.patch("/:slug/access-requests/:reqId", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -1985,7 +1982,7 @@ spaces.post("/:slug/copy-shapes", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } // Verify target space exists and user has write access await loadCommunity(targetSlug); @@ -2098,7 +2095,7 @@ spaces.post("/:slug/invite", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -2218,7 +2215,7 @@ spaces.post("/:slug/members/add", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); @@ -2325,7 +2322,7 @@ spaces.post("/:slug/invite/accept", async (c) => { if (!token) return c.json({ error: "Authentication required — sign in first" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } const body = await c.req.json<{ inviteToken: string }>(); if (!body.inviteToken) return c.json({ error: "inviteToken is required" }, 400); @@ -2359,7 +2356,7 @@ spaces.get("/:slug/invites", async (c) => { if (!token) return c.json({ error: "Authentication required" }, 401); let claims: EncryptIDClaims; - try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } + try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); } await loadCommunity(slug); const data = getDocumentData(slug); diff --git a/shared/components/rstack-space-settings.ts b/shared/components/rstack-space-settings.ts index eeec2da..2b3b86c 100644 --- a/shared/components/rstack-space-settings.ts +++ b/shared/components/rstack-space-settings.ts @@ -54,6 +54,7 @@ export class RStackSpaceSettings extends HTMLElement { private _addMode: "username" | "email" = "username"; private _lookupResult: { did: string; username: string; displayName: string } | null = null; private _lookupError = ""; + private _searchResults: { id: string; did: string; username: string; displayName: string }[] = []; private _moduleId = ""; private _moduleConfig: ModuleConfig | null = null; private _moduleSettingsValues: Record = {}; @@ -167,20 +168,25 @@ export class RStackSpaceSettings extends HTMLElement { } catch {} } - // Resolve missing displayNames from EncryptID - const unresolvedDids = this._members.filter(m => !m.displayName).map(m => m.did); - if (unresolvedDids.length) { + // Resolve displayNames from EncryptID for all members + owner + const allDids = [...new Set([ + ...this._members.map(m => m.did), + ...(this._ownerDID ? [this._ownerDID] : []), + ])]; + if (allDids.length) { try { const res = await fetch("/api/users/resolve-dids", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ dids: unresolvedDids }), + body: JSON.stringify({ dids: allDids }), }); if (res.ok) { const resolved = await res.json() as Record; for (const m of this._members) { - if (!m.displayName && resolved[m.did]) { - m.displayName = resolved[m.did].displayName || resolved[m.did].username; + // Match by did or by id (members may be stored as userId) + const info = resolved[m.did]; + if (info) { + m.displayName = info.displayName || info.username; } } } @@ -291,7 +297,7 @@ export class RStackSpaceSettings extends HTMLElement { const membersHTML = this._members.map(m => { const isOwnerRow = m.did === this._ownerDID; - const displayName = m.displayName || m.did.slice(0, 16) + "…"; + const displayName = m.displayName || "Unknown user"; const initial = displayName.charAt(0).toUpperCase(); const roleBadge = isOwnerRow ? `Owner` @@ -404,8 +410,20 @@ export class RStackSpaceSettings extends HTMLElement { ${this._addMode === "username" ? `
- - ${this._lookupResult ? `
Found: ${this._esc(this._lookupResult.displayName)} (@${this._esc(this._lookupResult.username)})
` : ""} +
+ + ${this._searchResults.length > 0 ? ` +
+ ${this._searchResults.map((u, i) => ` +
+ ${this._esc(u.displayName)} + @${this._esc(u.username)} +
+ `).join("")} +
+ ` : ""} +
+ ${this._lookupResult ? `
Selected: ${this._esc(this._lookupResult.displayName)} (@${this._esc(this._lookupResult.username)})
` : ""} ${this._lookupError ? `
${this._esc(this._lookupError)}
` : ""}
- +
` : ` @@ -473,6 +491,14 @@ export class RStackSpaceSettings extends HTMLElement { }); } + // Search result selection + sr.querySelectorAll(".search-item").forEach(item => { + item.addEventListener("click", () => { + const idx = parseInt((item as HTMLElement).dataset.idx || "0"); + if (this._searchResults[idx]) this._selectSearchResult(this._searchResults[idx]); + }); + }); + // Add by username sr.getElementById("add-by-username")?.addEventListener("click", () => this._addByUsername()); @@ -522,27 +548,48 @@ export class RStackSpaceSettings extends HTMLElement { } private async _lookupUser(username: string) { - if (!username || username.length < 2) return; + if (!username || username.length < 2) { + this._searchResults = []; + this._lookupResult = null; + this._lookupError = ""; + this._render(); + return; + } const token = getToken(); if (!token) return; try { - const res = await fetch(`https://auth.rspace.online/api/users/lookup?username=${encodeURIComponent(username)}`, { + const res = await fetch(`/api/users/search?q=${encodeURIComponent(username)}&limit=5`, { headers: { "Authorization": `Bearer ${token}` }, }); if (res.ok) { - this._lookupResult = await res.json(); - this._lookupError = ""; + this._searchResults = await res.json(); + this._lookupError = this._searchResults.length === 0 && username.length > 2 ? "No users found" : ""; + // Auto-select if exact match + const exact = this._searchResults.find(u => u.username.toLowerCase() === username.toLowerCase()); + this._lookupResult = exact ? { did: exact.id, username: exact.username, displayName: exact.displayName } : null; } else { + this._searchResults = []; this._lookupResult = null; - this._lookupError = username.length > 2 ? "User not found" : ""; + this._lookupError = username.length > 2 ? "No users found" : ""; } } catch { this._lookupError = "Lookup failed"; + this._searchResults = []; } this._render(); } + private _selectSearchResult(user: { id: string; did: string; username: string; displayName: string }) { + this._lookupResult = { did: user.id, username: user.username, displayName: user.displayName }; + this._searchResults = []; + this._lookupError = ""; + this._render(); + // Set the input value to the selected username + const input = this.shadowRoot?.getElementById("add-username") as HTMLInputElement; + if (input) input.value = user.username; + } + private async _addByUsername() { const sr = this.shadowRoot!; const input = sr.getElementById("add-username") as HTMLInputElement; @@ -914,12 +961,58 @@ const PANEL_CSS = ` } .add-btn:hover { opacity: 0.9; } +.search-wrapper { + position: relative; +} + +.search-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: var(--rs-bg-surface); + border: 1px solid var(--rs-border); + border-radius: 8px; + margin-top: 4px; + overflow: hidden; + z-index: 10; + box-shadow: 0 4px 16px rgba(0,0,0,0.25); +} + +.search-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + cursor: pointer; + transition: background 0.1s; +} +.search-item:hover { + background: var(--rs-bg-hover); +} + +.search-item-name { + font-size: 0.82rem; + font-weight: 500; + color: var(--rs-text-primary); +} + +.search-item-username { + font-size: 0.72rem; + color: var(--rs-text-muted); +} + .lookup-result { font-size: 0.78rem; color: #14b8a6; padding: 4px 0; } +.add-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + .error-msg { font-size: 0.78rem; color: #f87171; diff --git a/src/encryptid/server.ts b/src/encryptid/server.ts index 50381c9..b89ff18 100644 --- a/src/encryptid/server.ts +++ b/src/encryptid/server.ts @@ -3819,6 +3819,35 @@ app.get('/api/users/lookup', async (c) => { }); }); +// GET /api/users/search?q=&limit=5 — fuzzy username/display_name search +app.get('/api/users/search', async (c) => { + const claims = await verifyTokenFromRequest(c.req.header('Authorization')); + if (!claims) return c.json({ error: 'Authentication required' }, 401); + + const q = (c.req.query('q') || '').trim(); + if (q.length < 2) return c.json({ error: 'Query must be at least 2 characters' }, 400); + + const limit = Math.min(Math.max(parseInt(c.req.query('limit') || '5'), 1), 10); + const pattern = `${q}%`; + + const rows = await sql` + SELECT id, did, username, display_name + FROM users + WHERE username ILIKE ${pattern} OR display_name ILIKE ${pattern} + ORDER BY + CASE WHEN username ILIKE ${q} THEN 0 ELSE 1 END, + username + LIMIT ${limit} + `; + + return c.json(rows.map((r: any) => ({ + id: r.id, + did: r.did, + username: r.username, + displayName: r.display_name || r.username, + }))); +}); + // POST /api/users/resolve-dids — batch-resolve DIDs/userIds to usernames (public profile data) app.post('/api/users/resolve-dids', async (c) => { const body = await c.req.json();