-- Extensions CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- For additional crypto if needed -- Schema: api (exposed to PostgREST) CREATE SCHEMA api; -- Users Table CREATE TABLE api.users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email TEXT UNIQUE NOT NULL CHECK (email ~* '^.+@.+\..+$'), verifier TEXT NOT NULL, -- SRP-6a verifier salt TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Secrets Table CREATE TABLE api.secrets ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), owner_id UUID NOT NULL REFERENCES api.users(id), encrypted_data BYTEA NOT NULL, iv TEXT NOT NULL, auth_tag TEXT NOT NULL, type TEXT DEFAULT 'manual', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Access Policies (Many-to-Many for sharing) CREATE TABLE api.access_policies ( secret_id UUID NOT NULL REFERENCES api.secrets(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES api.users(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), PRIMARY KEY (secret_id, user_id) ); -- Roles & Permissions (PostgREST) -- We need a role for the anonymous web user and one for authenticated users. -- Note: 'web_anon' and 'todo_user' are placeholders. We should use: -- 'web_anon' (unauthenticated) -- 'authenticated' (logged in via JWT) CREATE ROLE web_anon NOLOGIN; GRANT USAGE ON SCHEMA api TO web_anon; GRANT SELECT ON api.users TO web_anon; -- Needed for login (salt lookup) CREATE ROLE authenticated NOLOGIN; GRANT USAGE ON SCHEMA api TO authenticated; GRANT ALL ON api.secrets TO authenticated; GRANT SELECT ON api.access_policies TO authenticated; GRANT SELECT ON api.users TO authenticated; -- Row Level Security (RLS) ALTER TABLE api.secrets ENABLE ROW LEVEL SECURITY; -- Helper function to get current user ID from JWT CREATE OR REPLACE FUNCTION api.uid() RETURNS UUID AS $$ SELECT NULLIF(current_setting('request.jwt.claim.sub', true), '')::uuid; $$ LANGUAGE SQL STABLE; -- RLS Policy for Secrets CREATE POLICY secrets_owner_policy ON api.secrets FOR ALL TO authenticated USING ( owner_id = api.uid() ) WITH CHECK ( owner_id = api.uid() ); CREATE POLICY secrets_shared_policy ON api.secrets FOR SELECT TO authenticated USING ( EXISTS ( SELECT 1 FROM api.access_policies WHERE secret_id = api.secrets.id AND user_id = api.uid() ) ); -- Grant privileges to the authenticator role (the one PostgREST connects as) -- In the docker-compose, we used user:pass. We need to grant role usage to 'user'. -- Ideally, create a separate 'authenticator' role. CREATE ROLE authenticator NOINHERIT LOGIN PASSWORD 'mysecretpassword'; GRANT web_anon TO authenticator; GRANT authenticated TO authenticator; -- BUT since we are using 'user' from the postgres docker image as the connector, -- we'll just grant to 'user' or 'postgres'. -- Simpler for dev: Grant to the user specified in connection string. GRANT web_anon TO "user"; GRANT authenticated TO "user";