From 6898186df5b71e695efb114f87562a420c3b6f2e Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Tue, 13 Oct 2026 13:54:14 -0400 Subject: [PATCH] nessa: retire the API copy now served by nessa-api.freno.me MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Nessa API (nessaDb.*, nessa.community.*, Clerk auth and the Clerk user webhook) lives in the Nessa repo's server/ on pan. This copy never served the app: RemoteAPIClient wraps every input in {"input": …}, which the stock tRPC adapter here reads as the input itself (queries 404, writes no-op). Clerk's webhook already targets nessa-api.freno.me. Drops the routers, nessa-auth, the webhook, NessaConnectionFactory, the NESSA_* env and the now-unused @clerk/backend and svix. Nessa's marketing, privacy and deletion pages stay. Co-Authored-By: Claude Opus 5.5 --- .env.example | 7 - bun.lockb | Bin 432942 -> 429401 bytes package.json | 2 - src/env/server.ts | 16 - src/routes/api/clerk-webhook.ts | 52 - src/routes/nessa/privacy.tsx | 19 +- src/server/api/root.ts | 8 +- src/server/api/routers/misc.test.ts | 2 +- .../api/routers/nessa-community-authz.test.ts | 321 --- .../api/routers/nessa-community-authz.ts | 90 - .../routers/nessa-community-sanitize.test.ts | 400 --- src/server/api/routers/nessa-community.ts | 1604 ------------ .../api/routers/nessa-ownership.test.ts | 371 --- src/server/api/routers/nessa.ts | 2276 ----------------- src/server/api/utils.ts | 60 +- src/server/clerk-user-webhook.test.ts | 336 --- src/server/clerk-user-webhook.ts | 232 -- src/server/database.ts | 2 - src/server/db-connections.ts | 12 - src/server/nessa-auth.test.ts | 133 - src/server/nessa-auth.ts | 47 - 21 files changed, 12 insertions(+), 5978 deletions(-) delete mode 100644 src/routes/api/clerk-webhook.ts delete mode 100644 src/server/api/routers/nessa-community-authz.test.ts delete mode 100644 src/server/api/routers/nessa-community-authz.ts delete mode 100644 src/server/api/routers/nessa-community-sanitize.test.ts delete mode 100644 src/server/api/routers/nessa-community.ts delete mode 100644 src/server/api/routers/nessa-ownership.test.ts delete mode 100644 src/server/api/routers/nessa.ts delete mode 100644 src/server/clerk-user-webhook.test.ts delete mode 100644 src/server/clerk-user-webhook.ts delete mode 100644 src/server/nessa-auth.test.ts delete mode 100644 src/server/nessa-auth.ts diff --git a/.env.example b/.env.example index 4f7c9c4..039685a 100644 --- a/.env.example +++ b/.env.example @@ -55,15 +55,8 @@ TURSO_DB_TOKEN="" # eyJ... TURSO_DB_API_TOKEN="" # org-level API token TURSO_LINEAGE_URL="libsql://.turso.io" TURSO_LINEAGE_TOKEN="" -NESSA_DB_URL="libsql://.turso.io" -NESSA_DB_TOKEN="" -# Clerk authentication — rotate via Clerk Dashboard or `clerk api .../rotate_secret_keys` -NESSA_CLERK_SECRET="sk_test/live_" # secret key (sk_test_... for dev, sk_live_... for prod) -NESSA_CLERK_JWT_ISSUER="https://.clerk.accounts.dev" # JWT issuer from Clerk Dashboard -NESSA_CLERK_WEBHOOK_SECRET="whsec_" # Svix signing secret from Clerk Dashboard → Webhooks → Signing Secret APPLE_CLIENT_ID_LINEAGE=com... -APPLE_CLIENT_ID_NESSA=com... # ── Infra / integration tokens ── INFILL_BEARER_TOKEN="" diff --git a/bun.lockb b/bun.lockb index 1aafab591ed25d7490c3f7175824b4c3951ee65e..551f395889db48512cb70a5f446d332c50f9be96 100755 GIT binary patch delta 72534 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 ArT_o{ delta 74695 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 IzRdN10hsQG_5c6? diff --git a/package.json b/package.json index b955d35..8ed8203 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "dependencies": { "@aws-sdk/client-s3": "^3.953.0", "@aws-sdk/s3-request-presigner": "^3.953.0", - "@clerk/backend": "^3.12.0", "@libsql/client": "^0.15.15", "@sentry/solidstart": "^10.67.0", "@solidjs/meta": "^0.29.4", @@ -55,7 +54,6 @@ "motion": "^12.23.26", "solid-js": "^1.9.5", "solid-tiptap": "^0.8.0", - "svix": "^1.98.0", "ua-parser-js": "^2.0.7", "uuid": "^13.0.0", "vinxi": "^0.5.7", diff --git a/src/env/server.ts b/src/env/server.ts index 123a40e..f0c9a85 100644 --- a/src/env/server.ts +++ b/src/env/server.ts @@ -53,18 +53,7 @@ const serverEnvSchema = z.object({ VITE_WEBSOCKET: z.string().min(1), VITE_INFILL_ENDPOINT: z.string().min(1), INFILL_BEARER_TOKEN: z.string().min(1), - NESSA_DB_URL: z.string().min(1), - NESSA_DB_TOKEN: z.string().min(1), - // Clerk authentication — Nessa auth is now Clerk-backed. The - // legacy self-issued JWT signing env var was removed. - NESSA_CLERK_SECRET: z.string().min(1), - NESSA_CLERK_JWT_ISSUER: z.string().min(1), - // Clerk webhook signing secret (Svix). Used to verify `user.created` / - // `user.updated` webhook payloads. Find it under Clerk Dashboard → - // Webhooks → your endpoint → Signing Secret (starts with `whsec_`). - NESSA_CLERK_WEBHOOK_SECRET: z.string().min(1), LINEAGE_JWT_SECRET: z.string().min(32), - APPLE_CLIENT_ID_NESSA: z.string().min(1).optional(), APPLE_CLIENT_ID_LINEAGE: z.string().min(1).optional(), VITE_TURNSTILE_SITE_KEY: z.string().min(1), TURNSTILE_SECRET_KEY: z.string().min(1), @@ -183,11 +172,6 @@ export const getMissingEnvVars = (): string[] => { "VITE_GOOGLE_CLIENT_ID_MAGIC_DELVE", "VITE_GITHUB_CLIENT_ID", "VITE_WEBSOCKET", - "NESSA_DB_URL", - "NESSA_DB_TOKEN", - "NESSA_CLERK_SECRET", - "NESSA_CLERK_JWT_ISSUER", - "NESSA_CLERK_WEBHOOK_SECRET", "LINEAGE_JWT_SECRET" ]; diff --git a/src/routes/api/clerk-webhook.ts b/src/routes/api/clerk-webhook.ts deleted file mode 100644 index 8dae5a8..0000000 --- a/src/routes/api/clerk-webhook.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { APIEvent } from "@solidjs/start/server"; -import { env } from "~/env/server"; -import { NessaConnectionFactory } from "~/server/database"; -import { handleClerkUserWebhook } from "~/server/clerk-user-webhook"; - -function json(status: number, body: unknown): Response { - return new Response(JSON.stringify(body), { - status, - headers: { "Content-Type": "application/json" } - }); -} - -/** - * Clerk webhook endpoint — receives `user.created` / `user.updated` events. - * - * Configure the endpoint URL in the Clerk Dashboard → Webhooks - * (e.g. https://freno.me/api/clerk-webhook in prod, or an ngrok/dev url for - * local dev). The signing secret (`whsec_...`) is stored in - * `NESSA_CLERK_WEBHOOK_SECRET` and used to verify each request via Svix. - * - * The raw body is read verbatim from the inflight request so the Svix - * signature is computed over the exact bytes Clerk sent. - */ -export async function POST(event: APIEvent) { - const svixId = event.request.headers.get("svix-id"); - const svixTimestamp = event.request.headers.get("svix-timestamp"); - const svixSignature = event.request.headers.get("svix-signature"); - - if (!svixId || !svixTimestamp || !svixSignature) { - return json(400, { error: "Missing Svix signature headers" }); - } - - let rawBody: string; - try { - rawBody = await event.request.text(); - } catch { - return json(400, { error: "Missing request body" }); - } - - const result = await handleClerkUserWebhook({ - rawBody, - headers: { - "svix-id": svixId, - "svix-timestamp": svixTimestamp, - "svix-signature": svixSignature - }, - webhookSecret: env.NESSA_CLERK_WEBHOOK_SECRET, - conn: NessaConnectionFactory() - }); - - return json(result.status, result.body); -} diff --git a/src/routes/nessa/privacy.tsx b/src/routes/nessa/privacy.tsx index d491a34..392e2db 100644 --- a/src/routes/nessa/privacy.tsx +++ b/src/routes/nessa/privacy.tsx @@ -4,17 +4,16 @@ * Net-new privacy policy for the Nessa subdomain. Modeled on the Life and * Lineage policy (the template for products with user accounts) but scoped to Nessa's real data practices: * - * - Authentication: user accounts are managed by Clerk - * (`src/server/nessa-auth.ts` verifies Clerk session JWTs via the Clerk - * Backend JWKS endpoint). Nessa itself does not store passwords — Clerk is - * the identity provider. + * - Authentication: user accounts are managed by Clerk (the Nessa API server + * verifies Clerk session JWTs). Nessa itself does not store passwords — + * Clerk is the identity provider. * - Community content: clubs, club challenges, club posts, post likes, and - * post comments (see `src/server/api/routers/nessa-community.ts`, - * `clubMemberships`, `clubChallenges`, `clubChallengeParticipations`, - * `clubPosts`, `clubPostLikes`, `clubPostComments`). - * - Storage: the Nessa data lives in a dedicated Turso (libSQL) database - * (`NessaConnectionFactory` in `src/server/db-connections.ts`), separate - * from the freno.me main DB and the Lineage DB. + * post comments (`clubMemberships`, `clubChallenges`, + * `clubChallengeParticipations`, `clubPosts`, `clubPostLikes`, + * `clubPostComments`). + * - Storage: the Nessa data lives in a dedicated Turso (libSQL) database, + * served by the Nessa API (`nessa-api.freno.me`, the Nessa repo's + * `server/`), separate from the freno.me main DB and the Lineage DB. * * PageHead is site-aware: only the base title is supplied; the * ` | Nessa` suffix, `https://nessa.freno.me/privacy` canonical, and OG image diff --git a/src/server/api/root.ts b/src/server/api/root.ts index 601e852..1c5cf8d 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -5,7 +5,7 @@ import { databaseRouter } from "./routers/database"; import { lineageRouter } from "./routers/lineage"; // Re-exported so the REST shim (src/routes/api/lineage/[...path].ts) can mount // ONLY the lineage sub-router as the trpc-openapi REST surface — not the -// whole appRouter. Keeps nessa/community/auth/audit routers unreachable via +// whole appRouter. Keeps auth/audit routers unreachable via // `/api/lineage/*`. export { lineageRouter }; import { miscRouter } from "./routers/misc"; @@ -16,8 +16,6 @@ import { postHistoryRouter } from "./routers/post-history"; import { infillRouter } from "./routers/infill"; import { accountRouter } from "./routers/account"; import { downloadsRouter } from "./routers/downloads"; -import { nessaDbRouter } from "./routers/nessa"; -import { nessaCommunityRouter } from "./routers/nessa-community"; import { appleNotificationsRouter } from "./routers/apple-notifications"; import { createTRPCRouter, createTRPCContext, t } from "./utils"; import type { H3Event } from "h3"; @@ -37,10 +35,6 @@ export const appRouter = createTRPCRouter({ infill: infillRouter, account: accountRouter, downloads: downloadsRouter, - nessaDb: nessaDbRouter, - // Community features (clubs, challenges, social feed) — ported from the - // standalone nessa-api Express prototype. Exposes nessa.community.*. - nessa: createTRPCRouter({ community: nessaCommunityRouter }), appleNotifications: appleNotificationsRouter }); diff --git a/src/server/api/routers/misc.test.ts b/src/server/api/routers/misc.test.ts index 3434744..03d696b 100644 --- a/src/server/api/routers/misc.test.ts +++ b/src/server/api/routers/misc.test.ts @@ -137,7 +137,7 @@ describe("p8-001 / p8-008 static source audit", () => { it(`${proc} is not declared as publicProcedure`, () => { // Match the procedure declaration line and ensure it is not publicProcedure. const re = new RegExp( - `\\b${proc}\\s*:\\s*(publicProcedure|csrfProtectedProcedure|protectedProcedure|adminProcedure|nessaProcedure)` + `\\b${proc}\\s*:\\s*(publicProcedure|csrfProtectedProcedure|protectedProcedure|adminProcedure)` ); const m = SOURCE.match(re); expect(m, `${proc} declaration not found`).not.toBeNull(); diff --git a/src/server/api/routers/nessa-community-authz.test.ts b/src/server/api/routers/nessa-community-authz.test.ts deleted file mode 100644 index 2682777..0000000 --- a/src/server/api/routers/nessa-community-authz.test.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { describe, it, expect, beforeAll, beforeEach } from "vitest"; -import { Database } from "bun:sqlite"; -import { - requireClubMembership, - resolveClubIdFromPost, - resolveClubIdFromChallenge, - type NessaConn -} from "./nessa-community-authz"; - -/** - * Regression tests for p8-003: private club content (posts, comments, likes, - * challenge participation) must NOT be readable/actionable by non-members. - * - * These tests exercise the shared membership-gating helpers directly against - * an in-memory SQLite DB (`bun:sqlite`) wrapped to match the libsql - * `execute({ sql, args }) -> { rows }` contract the router uses. The - * `nessa-community.ts` router calls these same helpers in the same order, so a - * pass here guarantees the authorization decision each endpoint makes before - * touching data. - * - * Two users are seeded: A is a member (owner) of club C (and owns the post + - * challenge under test); B is NOT a member of C. - */ - -// --------------------------------------------------------------------------- -// In-memory SQLite connection (libsql-shaped) -// --------------------------------------------------------------------------- - -let db: Database; -let conn: NessaConn; - -function makeConn(): NessaConn { - return { - execute: async ({ - sql, - args - }: { - sql: string; - args?: (string | number | null)[]; - }) => { - const stmt = db.prepare(sql); - const upper = sql.trim().toUpperCase(); - const isRead = upper.startsWith("SELECT") || upper.startsWith("WITH"); - if (isRead) { - const rows = stmt.all(...(args ?? [])); - return { rows: rows as unknown[] }; - } - stmt.run(...(args ?? [])); - return { rows: [] as unknown[] }; - } - }; -} - -// --------------------------------------------------------------------------- -// Schema + seed -// --------------------------------------------------------------------------- - -const USER_A = "user-a"; -const USER_B = "user-b"; -const CLUB_C = "club-c"; -const POST_P = "post-p"; // created by A in club C -const CHALLENGE_CH = "challenge-ch"; // in club C, created by A - -function initSchema() { - db = new Database(":memory:"); - db.run("PRAGMA foreign_keys = ON"); - - db.run( - "CREATE TABLE clubMemberships (id TEXT PRIMARY KEY, clubId TEXT, userId TEXT, role TEXT, joinedAt TEXT)" - ); - db.run( - "CREATE TABLE clubPosts (id TEXT PRIMARY KEY, clubId TEXT, userId TEXT, content TEXT, postType TEXT, challengeId TEXT, createdAt TEXT, updatedAt TEXT)" - ); - db.run( - "CREATE TABLE clubChallenges (id TEXT PRIMARY KEY, clubId TEXT, title TEXT, description TEXT, goalType TEXT, goalValue REAL, startDate TEXT, endDate TEXT, createdBy TEXT, status TEXT, createdAt TEXT, updatedAt TEXT)" - ); -} - -function seed() { - // Club C: A is a member (owner). B is NOT. - db.run( - "INSERT INTO clubMemberships (id, clubId, userId, role, joinedAt) VALUES (?, ?, ?, ?, datetime('now'))", - ["mem-a", CLUB_C, USER_A, "owner"] - ); - - // Post P by A in club C. - db.run( - "INSERT INTO clubPosts (id, clubId, userId, content, postType, challengeId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, NULL, datetime('now'), datetime('now'))", - [POST_P, CLUB_C, USER_A, "Hello from A", "text"] - ); - - // Challenge CH in club C, created by A. - db.run( - "INSERT INTO clubChallenges (id, clubId, title, description, goalType, goalValue, startDate, endDate, createdBy, status, createdAt, updatedAt) VALUES (?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))", - [ - CHALLENGE_CH, - CLUB_C, - "Run 5k", - "distance", - 5000, - "2025-01-01", - "2025-12-31", - USER_A, - "active" - ] - ); -} - -// --------------------------------------------------------------------------- - -beforeAll(() => { - initSchema(); - seed(); - conn = makeConn(); -}); - -beforeEach(() => { - // Keep membership state stable across tests (join/leave integration mutates it). - db.run("DELETE FROM clubMemberships"); - db.run( - "INSERT INTO clubMemberships (id, clubId, userId, role, joinedAt) VALUES (?, ?, ?, ?, datetime('now'))", - ["mem-a", CLUB_C, USER_A, "owner"] - ); -}); - -async function errCode(p: Promise): Promise { - try { - await p; - return undefined; - } catch (e) { - return (e as { code?: string }).code; - } -} - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -describe("p8-003: resolveClubIdFromPost", () => { - it("resolves the owning club for an existing post", async () => { - expect(await resolveClubIdFromPost(conn, POST_P)).toBe(CLUB_C); - }); - - it("throws NOT_FOUND for a missing post", async () => { - expect(await errCode(resolveClubIdFromPost(conn, "no-such-post"))).toBe( - "NOT_FOUND" - ); - }); -}); - -describe("p8-003: resolveClubIdFromChallenge", () => { - it("resolves the owning club for an existing challenge", async () => { - expect(await resolveClubIdFromChallenge(conn, CHALLENGE_CH)).toBe(CLUB_C); - }); - - it("throws NOT_FOUND for a missing challenge", async () => { - expect( - await errCode(resolveClubIdFromChallenge(conn, "no-such-challenge")) - ).toBe("NOT_FOUND"); - }); -}); - -describe("p8-003: requireClubMembership", () => { - it("passes silently for a member", async () => { - await expect( - requireClubMembership(conn, CLUB_C, USER_A) - ).resolves.toBeUndefined(); - }); - - it("throws FORBIDDEN for a non-member", async () => { - expect(await errCode(requireClubMembership(conn, CLUB_C, USER_B))).toBe( - "FORBIDDEN" - ); - }); -}); - -/** - * End-to-end authorization sequence for each of the 7 fixed endpoints. The - * router does exactly: resolve the resource's clubId, then - * requireClubMembership on it. Replaying that here proves the decision a - * non-member is rejected / a member is allowed. - */ -describe("p8-003: endpoint authorization sequences (resolve → require)", () => { - // social.getPost / addComment / comments / like / unlike - it("getPost/addComment/comments/like/unlike: non-member B rejected with FORBIDDEN", async () => { - const clubId = await resolveClubIdFromPost(conn, POST_P); - expect(await errCode(requireClubMembership(conn, clubId, USER_B))).toBe( - "FORBIDDEN" - ); - }); - - it("getPost/addComment/comments/like/unlike: member A allowed", async () => { - const clubId = await resolveClubIdFromPost(conn, POST_P); - await expect( - requireClubMembership(conn, clubId, USER_A) - ).resolves.toBeUndefined(); - }); - - // challenges.leave / challenges.submitProgress - it("challenges.leave / submitProgress: non-member B rejected with FORBIDDEN", async () => { - const clubId = await resolveClubIdFromChallenge(conn, CHALLENGE_CH); - expect(await errCode(requireClubMembership(conn, clubId, USER_B))).toBe( - "FORBIDDEN" - ); - }); - - it("challenges.leave / submitProgress: member A allowed", async () => { - const clubId = await resolveClubIdFromChallenge(conn, CHALLENGE_CH); - await expect( - requireClubMembership(conn, clubId, USER_A) - ).resolves.toBeUndefined(); - }); -}); - -describe("p8-003: join then allowed / leave then blocked (integration)", () => { - it("B is blocked, allowed after joining C, blocked again after leaving", async () => { - // Initially blocked. - const clubId = await resolveClubIdFromPost(conn, POST_P); - expect(await errCode(requireClubMembership(conn, clubId, USER_B))).toBe( - "FORBIDDEN" - ); - - // B joins. - db.run( - "INSERT INTO clubMemberships (id, clubId, userId, role, joinedAt) VALUES (?, ?, ?, ?, datetime('now'))", - ["mem-b", CLUB_C, USER_B, "member"] - ); - await expect( - requireClubMembership(conn, clubId, USER_B) - ).resolves.toBeUndefined(); - - // B leaves. - db.run("DELETE FROM clubMemberships WHERE clubId = ? AND userId = ?", [ - CLUB_C, - USER_B - ]); - expect(await errCode(requireClubMembership(conn, clubId, USER_B))).toBe( - "FORBIDDEN" - ); - }); -}); - -// --------------------------------------------------------------------------- -// Clerk session → local users.id resolution (migrate-to-clerk-auth-03) -// -// `createTRPCContext` verifies a Clerk session JWT (`verifyNessaToken`) -// and resolves `ctx.nessaUserId` by looking up `users.id` via the indexed -// `clerkUserId` column. These tests exercise that lookup path against an -// in-memory SQLite DB so the contract is guaranteed: -// - seeded row with matching clerkUserId → local id resolved -// - missing local row → UNAUTHORIZED -// - the resolved id is the LOCAL users.id, never the Clerk sub -// --------------------------------------------------------------------------- - -const CLERK_USER_ID = "user_test_abc123"; -const LOCAL_USER_A = "local-user-a"; -const LOCAL_USER_B = "local-user-b"; - -function initUsersTable() { - db.run(`CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - email TEXT, - clerkUserId TEXT - )`); - db.run( - `CREATE INDEX IF NOT EXISTS idx_users_clerkUserId ON users(clerkUserId)` - ); -} - -async function resolveLocalUserId(clerkUserId: string): Promise { - const result = await conn.execute({ - sql: "SELECT id FROM users WHERE clerkUserId = ?", - args: [clerkUserId] - }); - if (result.rows.length === 0) return null; - return (result.rows[0] as { id: string }).id; -} - -describe("clerkUserId lookup (migrate-to-clerk-auth-03)", () => { - beforeAll(() => { - initUsersTable(); - }); - - beforeEach(() => { - db.run("DELETE FROM users"); - }); - - it("resolves local users.id for a seeded clerkUserId", async () => { - db.run("INSERT INTO users (id, email, clerkUserId) VALUES (?, ?, ?)", [ - LOCAL_USER_A, - "a@nessa.app", - CLERK_USER_ID - ]); - expect(await resolveLocalUserId(CLERK_USER_ID)).toBe(LOCAL_USER_A); - }); - - it("returns null when no local row matches the clerkUserId", async () => { - // No users seeded — the webhook has not run yet. - expect(await resolveLocalUserId(CLERK_USER_ID)).toBeNull(); - }); - - it("returns null for a Clerk id that exists but maps to a different local user", async () => { - db.run("INSERT INTO users (id, email, clerkUserId) VALUES (?, ?, ?)", [ - LOCAL_USER_B, - "b@nessa.app", - "user_test_other" - ]); - expect(await resolveLocalUserId(CLERK_USER_ID)).toBeNull(); - }); - - it("ctx.nessaUserId is the LOCAL id, never the Clerk sub", async () => { - db.run("INSERT INTO users (id, email, clerkUserId) VALUES (?, ?, ?)", [ - LOCAL_USER_A, - "a@nessa.app", - CLERK_USER_ID - ]); - const resolved = await resolveLocalUserId(CLERK_USER_ID); - expect(resolved).toBe(LOCAL_USER_A); - expect(resolved).not.toBe(CLERK_USER_ID); - }); -}); diff --git a/src/server/api/routers/nessa-community-authz.ts b/src/server/api/routers/nessa-community-authz.ts deleted file mode 100644 index 5d3e755..0000000 --- a/src/server/api/routers/nessa-community-authz.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { TRPCError } from "@trpc/server"; - -/** - * Community authorization helpers (p8-003). - * - * Membership gating for `nessaCommunityRouter`. Extracted into a dependency- - * free module (no `~/env/server` import) so it can be unit-tested directly - * against an in-memory SQLite connection without booting the SSR-guarded env - * chain, and so every membership-gated endpoint shares ONE implementation of - * each check (no ad-hoc duplicated SQL). - * - * Contract mirrors the libsql client the router uses: a connection exposes - * `execute({ sql, args }) -> { rows }`. - */ - -/** Minimal libsql-shaped connection surface used by community authz. */ -export interface NessaConn { - execute: (q: { - sql: string; - args?: (string | number | null)[]; - }) => Promise<{ rows: unknown[] }>; -} - -/** - * Require that the calling user is a member of the club (or is the owner). - * - * Throws `TRPCError({ code: "FORBIDDEN", message: "Not a member of this club" })` - * on miss and returns void on success. This is the single source of truth for - * "is this user allowed to touch this club's content" — every membership-gated - * endpoint in `nessa-community.ts` MUST route through this helper. - */ -export async function requireClubMembership( - conn: NessaConn, - clubId: string, - userId: string -): Promise { - const result = await conn.execute({ - sql: "SELECT id FROM clubMemberships WHERE clubId = ? AND userId = ?", - args: [clubId, userId] - }); - if (!result.rows.length) { - throw new TRPCError({ - code: "FORBIDDEN", - message: "Not a member of this club" - }); - } -} - -/** - * Resolve the clubId that owns a post. Returns the owning club's id, or - * throws `NOT_FOUND` if the post does not exist. Used by read/interaction - * endpoints (`getPost`, `addComment`, `comments`, `like`, `unlike`) to derive - * the club a target post belongs to before gating on membership. - */ -export async function resolveClubIdFromPost( - conn: NessaConn, - postId: string -): Promise { - const result = await conn.execute({ - sql: "SELECT clubId FROM clubPosts WHERE id = ?", - args: [postId] - }); - if (!result.rows.length) { - throw new TRPCError({ code: "NOT_FOUND", message: "Post not found" }); - } - return (result.rows[0] as unknown as { clubId: string }).clubId; -} - -/** - * Resolve the clubId that owns a challenge. Returns the owning club's id, or - * throws `NOT_FOUND` if the challenge does not exist. Used by challenge - * interaction endpoints (`challenges.leave`, `challenges.submitProgress`) to - * derive the club a challenge belongs to before gating on membership. - */ -export async function resolveClubIdFromChallenge( - conn: NessaConn, - challengeId: string -): Promise { - const result = await conn.execute({ - sql: "SELECT clubId FROM clubChallenges WHERE id = ?", - args: [challengeId] - }); - if (!result.rows.length) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Challenge not found" - }); - } - return (result.rows[0] as unknown as { clubId: string }).clubId; -} diff --git a/src/server/api/routers/nessa-community-sanitize.test.ts b/src/server/api/routers/nessa-community-sanitize.test.ts deleted file mode 100644 index a30b4b1..0000000 --- a/src/server/api/routers/nessa-community-sanitize.test.ts +++ /dev/null @@ -1,400 +0,0 @@ -import { describe, it, expect, beforeAll, beforeEach } from "vitest"; -import { Database } from "bun:sqlite"; -import { sanitizeCommunityContent } from "~/server/lib/sanitize"; -import { - requireClubMembership, - resolveClubIdFromPost, - type NessaConn -} from "./nessa-community-authz"; - -/** - * Tests for p8-012: sanitize community post/comment content on write. - * - * Content model: plain text — no HTML is stored. The iOS client renders - * with SwiftUI `Text()` (not a WebView), so there is no render-time XSS - * surface. Sanitization on write is defense-in-depth against a future HTML - * render path. - * - * The router's `social.createPost` and `social.addComment` run - * `sanitizeCommunityContent(input.content)` before the INSERT. These tests - * verify the sanitizer directly and then exercise the full write path - * against an in-memory SQLite DB wrapped to match the libsql contract. - */ - -// --------------------------------------------------------------------------- -// Sanitizer unit tests -// --------------------------------------------------------------------------- - -describe("sanitizeCommunityContent", () => { - it("strips hello") - ).toBe("alert(1)hello"); - }); - - it("strips safe" - ) - ).toBe("alert('xss') safe"); - }); - - it("strips event handlers", () => { - expect( - sanitizeCommunityContent( - 'check this out' - ) - ).toBe("check this out"); - }); - - it("strips ", () => { - expect( - sanitizeCommunityContent('text') - ).toBe("text"); - }); - - it("strips ", () => { - expect( - sanitizeCommunityContent('body') - ).toBe("body"); - }); - - it("strips ", () => { - expect( - sanitizeCommunityContent( - 'text' - ) - ).toBe("text"); - }); - - it("strips ", () => { - expect( - sanitizeCommunityContent( - 'click here now' - ) - ).toBe("click here now"); - }); - - it("strips text' - ) - ).toBe("body{background:red}text"); - }); - - it("strips tags", () => { - expect( - sanitizeCommunityContent( - 'text' - ) - ).toBe("text"); - }); - - it("strips self-closing tags", () => { - expect( - sanitizeCommunityContent('text
more
end') - ).toBe("textmoreend"); - }); - - it("handles HTML entities — no round-trip through future HTML renderer", () => { - // `<script>` decoded to `bold

end' - ) - ).toBe("alert(1)boldend"); - }); - - it("handles unclosed tags", () => { - expect(sanitizeCommunityContent("
text")).toBe("text"); - expect(sanitizeCommunityContent("text
")).toBe("text"); - }); - - it("strips , , ", () => { - expect( - sanitizeCommunityContent( - 'text' - ) - ).toBe("text"); - expect( - sanitizeCommunityContent('text') - ).toBe("text"); - expect( - sanitizeCommunityContent('text') - ).toBe("text"); - }); - - it("strips data: URI tags — remaining text is harmless as plain text", () => { - // The tag and its attribute are stripped; the inner text remains. - // As plain text, the leftover characters are not executable. - const result = sanitizeCommunityContent( - 'link' - ); - expect(result).not.toContain("' - ) - ).toBe("alert(1)"); - }); - - it("strips
/ /