import assert from "node:assert/strict" import test from "node:test" import { randomUUID } from "node:crypto" import Fastify from "fastify" import jwt from "jsonwebtoken" import cookie from "@fastify/cookie" import authPlugin from "../src/plugins/auth" import { secrets } from "../src/utils/secrets" import { collaborationRoutes, collaborationGuestRoutes } from "../src/routes/collaboration" import { CollaborationService } from "../src/modules/collaboration/service" import { collaborationDatabase, testDatabaseUrl } from "./helpers/collaboration-db" test("HTTP guest sessions never authorize normal APIs or other subrooms", { skip: !testDatabaseUrl }, async () => { const fixture = await collaborationDatabase(["0075_project_collaboration.sql"]) const app = Fastify(), ownerId = randomUUID(), mail: string[] = [] const previousSecret = secrets.JWT_SECRET secrets.JWT_SECRET = "collaboration-test-only-secret-" + randomUUID() const service = new CollaborationService(fixture.db, { publicUrl: "https://owner.example", guestPepper: () => "p".repeat(64), sendMail: async (to, subject, html) => { mail.push(html); return { success: true } } }) const options = { service, apiUrl: "https://owner.example/backend" } try { await fixture.pool.query("INSERT INTO tenants VALUES (1,'Owner',null)") await fixture.pool.query("INSERT INTO auth_users(id,email,is_admin) VALUES ($1,'owner@example.test',true)", [ownerId]) await fixture.pool.query("INSERT INTO auth_tenant_users(tenant_id,user_id) VALUES (1,$1)", [ownerId]) await fixture.pool.query("INSERT INTO projects(id,tenant,name,profiles,archived) VALUES (1,1,'Internal','{}',false)") app.decorate("db", fixture.db) await app.register(cookie) await app.register(collaborationGuestRoutes, options) await app.register(async protectedApp => { await protectedApp.register(authPlugin) await protectedApp.register(collaborationRoutes, options) }, { prefix: "/api" }) const ownerToken = jwt.sign({ user_id: ownerId, tenant_id: 1, is_admin: true }, secrets.JWT_SECRET) const headers = { authorization: `Bearer ${ownerToken}` } const created = await app.inject({ method: "POST", url: "/api/collaboration/rooms", headers, payload: { projectId: 1, name: "Guest room" } }) assert.equal(created.statusCode, 200, created.body) const roomId = created.json().id const invited = await app.inject({ method: "POST", url: `/api/collaboration/rooms/${roomId}/invitations`, headers, payload: { kind: "guest", email: "guest@example.test", name: "Guest", sendEmail: false } }) assert.equal(invited.statusCode, 200, invited.body) const token = new URL(invited.json().url).hash.slice("#invite=".length) const requested = await app.inject({ method: "POST", url: "/guest/api/auth/request", payload: { invitationToken: token } }) assert.equal(requested.statusCode, 200, requested.body) const verified = await app.inject({ method: "POST", url: "/guest/api/auth/verify", payload: { challengeId: requested.json().challengeId, code: mail[0].match(/(\d+)<\/strong>/)![1], } }) assert.equal(verified.statusCode, 200, verified.body) const guestHeaders = { authorization: `Bearer ${verified.json().token}` } const accepted = await app.inject({ method: "POST", url: `/guest/api/invitations/${invited.json().id}/accept`, headers: guestHeaders, payload: { token } }) assert.equal(accepted.statusCode, 200, accepted.body) assert.equal((await app.inject({ url: "/api/collaboration", headers: guestHeaders })).statusCode, 401) assert.equal((await app.inject({ url: "/guest/api/rooms", headers })).statusCode, 401) assert.equal((await app.inject({ url: `/guest/api/rooms/${roomId}`, headers: guestHeaders })).statusCode, 200) assert.equal((await app.inject({ url: `/guest/api/rooms/${randomUUID()}`, headers: guestHeaders })).statusCode, 404) const sent = await app.inject({ method: "POST", url: `/guest/api/rooms/${roomId}/messages`, headers: guestHeaders, payload: { text: "Guest message", clientId: randomUUID(), tenantId: 999, principalId: ownerId } }) assert.equal(sent.statusCode, 200, sent.body) const stored = await fixture.pool.query("SELECT p.kind FROM collaboration_messages m JOIN collaboration_principals p ON p.id=m.principal_id WHERE m.id=$1", [sent.json().id]) assert.equal(stored.rows[0].kind, "guest") assert.equal((await app.inject({ method: "POST", url: "/guest/api/logout", headers: guestHeaders })).statusCode, 200) assert.equal((await app.inject({ url: "/guest/api/rooms", headers: guestHeaders })).statusCode, 401) } finally { secrets.JWT_SECRET = previousSecret; await app.close(); await fixture.close() } })