refactor(auth): replace @encryptid/sdk imports with local auth module

Consolidates token verification into server/auth.ts, removing the
external SDK dependency. All modules now import from the local module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-22 16:41:59 -07:00
parent 2c0fbb76ac
commit 7618433498
26 changed files with 344 additions and 201 deletions

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View File

@ -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";

View File

@ -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<MortgagePosition>;
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<string, number> };
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;

View File

@ -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")];

View File

@ -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;

View File

@ -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);

View File

@ -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 */ }

View File

@ -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);
}

View File

@ -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");

View File

@ -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;

View File

@ -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");

View File

@ -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;

View File

@ -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;

View File

@ -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;

24
server/auth.ts Normal file
View File

@ -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<EncryptIDClaims> {
return verifyEncryptIDToken(token, verifyOpts);
}

View File

@ -1,5 +1,16 @@
declare module '@encryptid/sdk/server' {
export function verifyEncryptIDToken(token: string): Promise<EncryptIDClaims>;
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<EncryptIDClaims>;
export function evaluateSpaceAccess(
slug: string,
token: string | null,

View File

@ -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<BackupEnv>, next: Next) => {
}
let claims: EncryptIDClaims;
try {
claims = await verifyEncryptIDToken(token);
claims = await verifyToken(token);
} catch {
return c.json({ error: "Invalid or expired token" }, 401);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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<string>();
@ -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);

View File

@ -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<string, string | boolean> = {};
@ -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<string, { username: string; displayName: string }>;
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
? `<span class="role-badge role-owner">Owner</span>`
@ -404,8 +410,20 @@ export class RStackSpaceSettings extends HTMLElement {
${this._addMode === "username" ? `
<div class="add-form">
<input type="text" class="input" id="add-username" placeholder="Username…" />
${this._lookupResult ? `<div class="lookup-result">Found: <strong>${this._esc(this._lookupResult.displayName)}</strong> (@${this._esc(this._lookupResult.username)})</div>` : ""}
<div class="search-wrapper">
<input type="text" class="input" id="add-username" placeholder="Search username…" autocomplete="off" />
${this._searchResults.length > 0 ? `
<div class="search-dropdown">
${this._searchResults.map((u, i) => `
<div class="search-item" data-idx="${i}">
<span class="search-item-name">${this._esc(u.displayName)}</span>
<span class="search-item-username">@${this._esc(u.username)}</span>
</div>
`).join("")}
</div>
` : ""}
</div>
${this._lookupResult ? `<div class="lookup-result">Selected: <strong>${this._esc(this._lookupResult.displayName)}</strong> (@${this._esc(this._lookupResult.username)})</div>` : ""}
${this._lookupError ? `<div class="error-msg">${this._esc(this._lookupError)}</div>` : ""}
<div class="add-row">
<select class="input role-input" id="add-role">
@ -414,7 +432,7 @@ export class RStackSpaceSettings extends HTMLElement {
<option value="moderator">moderator</option>
<option value="admin">admin</option>
</select>
<button class="add-btn" id="add-by-username">Add</button>
<button class="add-btn" id="add-by-username" ${!this._lookupResult ? "disabled" : ""}>Add</button>
</div>
</div>
` : `
@ -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;

View File

@ -3819,6 +3819,35 @@ app.get('/api/users/lookup', async (c) => {
});
});
// GET /api/users/search?q=<partial>&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();