-- member_center: API 呼叫方白名單表 -- 位置: public schema BEGIN; CREATE EXTENSION IF NOT EXISTS pgcrypto; DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'client_status') THEN CREATE TYPE client_status AS ENUM ('active', 'inactive'); END IF; END $$; CREATE TABLE IF NOT EXISTS api_clients ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), client_key TEXT NOT NULL UNIQUE, name TEXT NOT NULL, status client_status NOT NULL DEFAULT 'active', -- 只存 hash,不存明文 key api_key_hash TEXT NOT NULL, -- 可先留空,之後再嚴格化 allowed_origins JSONB NOT NULL DEFAULT '[]'::jsonb, allowed_ips JSONB NOT NULL DEFAULT '[]'::jsonb, allowed_paths JSONB NOT NULL DEFAULT '[]'::jsonb, rate_limit_per_min INTEGER, expires_at TIMESTAMPTZ, last_used_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_api_clients_status ON api_clients(status); CREATE INDEX IF NOT EXISTS idx_api_clients_expires_at ON api_clients(expires_at); CREATE OR REPLACE FUNCTION set_updated_at_api_clients() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_trigger WHERE tgname = 'trg_api_clients_set_updated_at' ) THEN CREATE TRIGGER trg_api_clients_set_updated_at BEFORE UPDATE ON api_clients FOR EACH ROW EXECUTE FUNCTION set_updated_at_api_clients(); END IF; END $$; -- 建議初始化 2~3 個 client(api_key_hash 先放占位,後續再更新) INSERT INTO api_clients ( client_key, name, status, api_key_hash, allowed_origins, allowed_ips, allowed_paths, rate_limit_per_min ) VALUES ( 'mkt-backend', 'MKT Backend Service', 'active', 'REPLACE_WITH_BCRYPT_OR_ARGON2_HASH', '[]'::jsonb, '[]'::jsonb, '["/internal/users/upsert-by-sub", "/internal/permissions"]'::jsonb, 600 ), ( 'admin-frontend', 'Admin Frontend', 'active', 'REPLACE_WITH_BCRYPT_OR_ARGON2_HASH', '["https://admin.ose.tw", "https://member.ose.tw"]'::jsonb, '[]'::jsonb, '["/admin"]'::jsonb, 300 ), ( 'ops-local', 'Ops Local Tooling', 'inactive', 'REPLACE_WITH_BCRYPT_OR_ARGON2_HASH', '[]'::jsonb, '["127.0.0.1"]'::jsonb, '["/internal", "/admin"]'::jsonb, 120 ) ON CONFLICT (client_key) DO NOTHING; COMMIT; -- 快速檢查 -- SELECT client_key, status, expires_at, created_at FROM api_clients ORDER BY client_key;