compassmock/src/app/actions/projects.ts
Nicholai ad2f0c0b9c
feat(security): add multi-tenancy isolation and demo mode (#90)
Add org-scoped data isolation across all server actions to
prevent cross-org data leakage. Add read-only demo mode with
mutation guards on all write endpoints.

Multi-tenancy:
- org filter on executeDashboardQueries (all query types)
- org boundary checks on getChannel, joinChannel
- searchMentionableUsers derives org from session
- getConversationUsage scoped to user, not org-wide for admins
- organizations table, members, org switcher component

Demo mode:
- /demo route sets strict sameSite cookie
- isDemoUser guards on all mutation server actions
- demo banner, CTA dialog, and gate components
- seed script for demo org data

Also: exclude scripts/ from tsconfig (fixes build), add
multi-tenancy architecture documentation.

Co-authored-by: Nicholai <nicholaivogelfilms@gmail.com>
2026-02-15 22:05:12 -07:00

30 lines
787 B
TypeScript
Executable File

"use server"
import { getCloudflareContext } from "@opennextjs/cloudflare"
import { getDb } from "@/db"
import { projects } from "@/db/schema"
import { asc, eq } from "drizzle-orm"
import { requireAuth } from "@/lib/auth"
import { requireOrg } from "@/lib/org-scope"
export async function getProjects(): Promise<{ id: string; name: string }[]> {
try {
const user = await requireAuth()
const orgId = requireOrg(user)
const { env } = await getCloudflareContext()
if (!env?.DB) return []
const db = getDb(env.DB)
const allProjects = await db
.select({ id: projects.id, name: projects.name })
.from(projects)
.where(eq(projects.organizationId, orgId))
.orderBy(asc(projects.name))
return allProjects
} catch {
return []
}
}