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
320 lines
12 KiB
PL/PgSQL
320 lines
12 KiB
PL/PgSQL
-- 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;
|