Files
secrets-manager/schema.sql
2026-01-30 14:02:52 +01:00

95 lines
3.1 KiB
PL/PgSQL

-- 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";