77
88CREATE TABLE IF NOT EXISTS api_keys (
99 id uuid DEFAULT gen_random_uuid() PRIMARY KEY ,
10+ -- NULL user_id = system/service key (e.g. dogfood-mcp).
11+ -- RLS policies using auth.uid() = user_id will NOT match these rows;
12+ -- only the service_role (backend) can access system keys.
1013 user_id uuid REFERENCES auth .users (id) ON DELETE CASCADE ,
1114 name text NOT NULL ,
1215 key_hash text NOT NULL UNIQUE,
13- tier text DEFAULT ' free' ,
16+ tier text DEFAULT ' free' CHECK (tier IN ( ' free ' , ' pro ' , ' enterprise ' )) ,
1417 active boolean DEFAULT true,
1518 created_at timestamptz DEFAULT now(),
1619 last_used_at timestamptz
@@ -20,10 +23,36 @@ CREATE TABLE IF NOT EXISTS api_keys (
2023CREATE INDEX IF NOT EXISTS idx_api_keys_hash_active
2124 ON api_keys (key_hash) WHERE active = true;
2225
26+ -- Prevent users from modifying sensitive columns via UPDATE.
27+ -- Only active and last_used_at can change; key_hash, tier, name are immutable.
28+ -- Service role (backend/admin) bypasses this check.
29+ CREATE OR REPLACE FUNCTION protect_api_key_immutable_cols ()
30+ RETURNS TRIGGER AS $fn$
31+ BEGIN
32+ IF current_setting(' role' , true) = ' service_role' THEN
33+ RETURN NEW;
34+ END IF;
35+ IF NEW .key_hash IS DISTINCT FROM OLD .key_hash THEN
36+ RAISE EXCEPTION ' Cannot modify key_hash' ;
37+ END IF;
38+ IF NEW .tier IS DISTINCT FROM OLD .tier THEN
39+ RAISE EXCEPTION ' Cannot modify tier' ;
40+ END IF;
41+ IF NEW .name IS DISTINCT FROM OLD .name THEN
42+ RAISE EXCEPTION ' Cannot modify name' ;
43+ END IF;
44+ RETURN NEW;
45+ END;
46+ $fn$ LANGUAGE plpgsql;
47+
48+ CREATE TRIGGER api_keys_immutable_guard
49+ BEFORE UPDATE ON api_keys
50+ FOR EACH ROW
51+ EXECUTE FUNCTION protect_api_key_immutable_cols();
52+
2353-- Enable RLS
2454ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY;
2555
26- -- Users can only see their own keys
2756CREATE POLICY " Users can view own keys"
2857 ON api_keys FOR SELECT
2958 USING (auth .uid () = user_id);
0 commit comments