feat(multi-tenant): add Team model and RLS policies
Implements #9, #10 - Team model with workspace membership - TeamMember model with role-based access (OWNER, ADMIN, MEMBER) - Row-Level Security policies for tenant isolation on 19 tables - Helper functions: current_user_id(), is_workspace_member(), is_workspace_admin() - Developer utilities in src/lib/db-context.ts for easy RLS integration - Comprehensive documentation in docs/design/multi-tenant-rls.md Database migrations: - 20260129220941_add_team_model: Adds Team and TeamMember tables - 20260129221004_add_rls_policies: Enables RLS and creates policies Security features: - Complete database-level tenant isolation - Automatic query filtering based on workspace membership - Defense-in-depth security with application and database layers - Performance-optimized with indexes on workspace_id
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TeamMemberRole" AS ENUM ('OWNER', 'ADMIN', 'MEMBER');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "teams" (
|
||||
"id" UUID NOT NULL,
|
||||
"workspace_id" UUID NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL,
|
||||
|
||||
CONSTRAINT "teams_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "team_members" (
|
||||
"team_id" UUID NOT NULL,
|
||||
"user_id" UUID NOT NULL,
|
||||
"role" "TeamMemberRole" NOT NULL DEFAULT 'MEMBER',
|
||||
"joined_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "team_members_pkey" PRIMARY KEY ("team_id","user_id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "teams_workspace_id_idx" ON "teams"("workspace_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "team_members_user_id_idx" ON "team_members"("user_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "teams" ADD CONSTRAINT "teams_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "team_members" ADD CONSTRAINT "team_members_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "team_members" ADD CONSTRAINT "team_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,319 @@
|
||||
-- Row-Level Security (RLS) for Multi-Tenant Isolation
|
||||
-- This migration enables RLS on all tenant-scoped tables and creates policies
|
||||
-- to ensure users can only access data within their authorized workspaces.
|
||||
|
||||
-- =============================================================================
|
||||
-- ENABLE RLS ON TENANT-SCOPED TABLES
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE workspace_members ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE teams ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE team_members ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE events ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE activity_logs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE memory_embeddings ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE domains ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE ideas ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE relationships ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE agents ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE agent_sessions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE user_layouts ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE knowledge_entries ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE knowledge_tags ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE knowledge_entry_tags ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE knowledge_links ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE knowledge_embeddings ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE knowledge_entry_versions ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- =============================================================================
|
||||
-- HELPER FUNCTION: Check if user is workspace member
|
||||
-- =============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION is_workspace_member(workspace_uuid UUID, user_uuid UUID)
|
||||
RETURNS BOOLEAN AS $$
|
||||
BEGIN
|
||||
RETURN EXISTS (
|
||||
SELECT 1 FROM workspace_members
|
||||
WHERE workspace_id = workspace_uuid
|
||||
AND user_id = user_uuid
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
||||
|
||||
-- =============================================================================
|
||||
-- HELPER FUNCTION: Check if user is workspace owner/admin
|
||||
-- =============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION is_workspace_admin(workspace_uuid UUID, user_uuid UUID)
|
||||
RETURNS BOOLEAN AS $$
|
||||
BEGIN
|
||||
RETURN EXISTS (
|
||||
SELECT 1 FROM workspace_members
|
||||
WHERE workspace_id = workspace_uuid
|
||||
AND user_id = user_uuid
|
||||
AND role IN ('OWNER', 'ADMIN')
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
||||
|
||||
-- =============================================================================
|
||||
-- HELPER FUNCTION: Get current user ID from session variable
|
||||
-- =============================================================================
|
||||
-- Usage in API: SET LOCAL app.current_user_id = 'user-uuid';
|
||||
|
||||
CREATE OR REPLACE FUNCTION current_user_id()
|
||||
RETURNS UUID AS $$
|
||||
BEGIN
|
||||
RETURN NULLIF(current_setting('app.current_user_id', TRUE), '')::UUID;
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE;
|
||||
|
||||
-- =============================================================================
|
||||
-- WORKSPACES: Users can only see workspaces they're members of
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY workspace_member_access ON workspaces
|
||||
FOR ALL
|
||||
USING (
|
||||
id IN (
|
||||
SELECT workspace_id FROM workspace_members
|
||||
WHERE user_id = current_user_id()
|
||||
)
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- WORKSPACE_MEMBERS: Users can see members of their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY workspace_members_access ON workspace_members
|
||||
FOR ALL
|
||||
USING (
|
||||
workspace_id IN (
|
||||
SELECT workspace_id FROM workspace_members
|
||||
WHERE user_id = current_user_id()
|
||||
)
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- TEAMS: Users can see teams in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY teams_workspace_access ON teams
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- TEAM_MEMBERS: Users can see team members in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY team_members_access ON team_members
|
||||
FOR ALL
|
||||
USING (
|
||||
team_id IN (
|
||||
SELECT id FROM teams
|
||||
WHERE is_workspace_member(workspace_id, current_user_id())
|
||||
)
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- TASKS: Users can only see tasks in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY tasks_workspace_access ON tasks
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- EVENTS: Users can only see events in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY events_workspace_access ON events
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- PROJECTS: Users can only see projects in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY projects_workspace_access ON projects
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- ACTIVITY_LOGS: Users can only see activity in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY activity_logs_workspace_access ON activity_logs
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- MEMORY_EMBEDDINGS: Users can only see embeddings in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY memory_embeddings_workspace_access ON memory_embeddings
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- DOMAINS: Users can only see domains in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY domains_workspace_access ON domains
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- IDEAS: Users can only see ideas in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY ideas_workspace_access ON ideas
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- RELATIONSHIPS: Users can only see relationships in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY relationships_workspace_access ON relationships
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- AGENTS: Users can only see agents in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY agents_workspace_access ON agents
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- AGENT_SESSIONS: Users can only see agent sessions in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY agent_sessions_workspace_access ON agent_sessions
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- USER_LAYOUTS: Users can only see their own layouts in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY user_layouts_workspace_access ON user_layouts
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
AND user_id = current_user_id()
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- KNOWLEDGE_ENTRIES: Users can only see entries in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY knowledge_entries_workspace_access ON knowledge_entries
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- KNOWLEDGE_TAGS: Users can only see tags in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY knowledge_tags_workspace_access ON knowledge_tags
|
||||
FOR ALL
|
||||
USING (
|
||||
is_workspace_member(workspace_id, current_user_id())
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- KNOWLEDGE_ENTRY_TAGS: Users can see tags for entries in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY knowledge_entry_tags_access ON knowledge_entry_tags
|
||||
FOR ALL
|
||||
USING (
|
||||
entry_id IN (
|
||||
SELECT id FROM knowledge_entries
|
||||
WHERE is_workspace_member(workspace_id, current_user_id())
|
||||
)
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- KNOWLEDGE_LINKS: Users can see links between entries in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY knowledge_links_access ON knowledge_links
|
||||
FOR ALL
|
||||
USING (
|
||||
source_id IN (
|
||||
SELECT id FROM knowledge_entries
|
||||
WHERE is_workspace_member(workspace_id, current_user_id())
|
||||
)
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- KNOWLEDGE_EMBEDDINGS: Users can see embeddings for entries in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY knowledge_embeddings_access ON knowledge_embeddings
|
||||
FOR ALL
|
||||
USING (
|
||||
entry_id IN (
|
||||
SELECT id FROM knowledge_entries
|
||||
WHERE is_workspace_member(workspace_id, current_user_id())
|
||||
)
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- KNOWLEDGE_ENTRY_VERSIONS: Users can see versions for entries in their workspaces
|
||||
-- =============================================================================
|
||||
|
||||
CREATE POLICY knowledge_entry_versions_access ON knowledge_entry_versions
|
||||
FOR ALL
|
||||
USING (
|
||||
entry_id IN (
|
||||
SELECT id FROM knowledge_entries
|
||||
WHERE is_workspace_member(workspace_id, current_user_id())
|
||||
)
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- GRANT USAGE TO APPLICATION ROLE
|
||||
-- =============================================================================
|
||||
-- The application should connect with a role that has appropriate permissions.
|
||||
-- By default, we assume the owner of the database has full access.
|
||||
-- In production, create a dedicated role with limited permissions.
|
||||
|
||||
-- Example (uncomment and customize for production):
|
||||
-- GRANT USAGE ON SCHEMA public TO mosaic_app;
|
||||
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO mosaic_app;
|
||||
-- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO mosaic_app;
|
||||
Reference in New Issue
Block a user