Skip to content

Commit 0d6fe01

Browse files
Merge pull request #1482 from julep-ai/f/api-keys-storage
API keys storage
2 parents 23aeee8 + 44087e4 commit 0d6fe01

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
BEGIN;
2+
3+
-- Drop helper functions
4+
DROP FUNCTION IF EXISTS decrypt_api_key(BYTEA, TEXT);
5+
DROP FUNCTION IF EXISTS encrypt_api_key(TEXT, TEXT);
6+
7+
-- Drop trigger and function
8+
DROP TRIGGER IF EXISTS update_api_keys_timestamp_trigger ON api_keys;
9+
DROP FUNCTION IF EXISTS update_api_keys_timestamp();
10+
11+
-- Drop table
12+
DROP TABLE IF EXISTS api_keys CASCADE;
13+
14+
COMMIT;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
BEGIN;
2+
3+
-- Create api_keys table with encryption for api_key_encrypted (following secrets pattern)
4+
CREATE TABLE IF NOT EXISTS api_keys (
5+
api_key_id UUID NOT NULL,
6+
developer_id UUID NOT NULL,
7+
api_key_encrypted BYTEA NOT NULL,
8+
name TEXT NOT NULL,
9+
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
10+
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
11+
metadata JSONB DEFAULT '{}'::jsonb,
12+
deleted_at TIMESTAMPTZ DEFAULT NULL,
13+
CONSTRAINT pk_api_keys PRIMARY KEY (developer_id, api_key_id),
14+
CONSTRAINT ct_api_keys_metadata_is_object CHECK (jsonb_typeof(metadata) = 'object'),
15+
CONSTRAINT ct_api_keys_name_valid_identifier CHECK (name ~ '^[a-zA-Z][a-zA-Z0-9_]*$'),
16+
CONSTRAINT fk_api_keys_developer FOREIGN KEY (developer_id) REFERENCES developers(developer_id)
17+
);
18+
19+
-- Create partial unique index instead of constraint with WHERE clause
20+
CREATE UNIQUE INDEX IF NOT EXISTS uq_api_keys_developer_id_name
21+
ON api_keys (developer_id, name)
22+
WHERE deleted_at IS NULL;
23+
24+
-- Create trigger function for updated_at
25+
CREATE OR REPLACE FUNCTION update_api_keys_timestamp()
26+
RETURNS TRIGGER AS $$
27+
BEGIN
28+
NEW.updated_at = CURRENT_TIMESTAMP;
29+
RETURN NEW;
30+
END;
31+
$$ LANGUAGE plpgsql;
32+
33+
-- Create trigger
34+
CREATE TRIGGER update_api_keys_timestamp_trigger
35+
BEFORE UPDATE ON api_keys
36+
FOR EACH ROW
37+
EXECUTE FUNCTION update_api_keys_timestamp();
38+
39+
-- Add indexes
40+
CREATE INDEX IF NOT EXISTS idx_api_keys_developer_id ON api_keys(developer_id);
41+
CREATE INDEX IF NOT EXISTS idx_api_keys_name ON api_keys(name);
42+
CREATE INDEX IF NOT EXISTS idx_api_keys_metadata ON api_keys USING gin(metadata);
43+
CREATE INDEX IF NOT EXISTS idx_api_keys_deleted_at ON api_keys(deleted_at) WHERE deleted_at IS NULL;
44+
45+
-- Helper functions for encryption/decryption (following secrets pattern)
46+
CREATE OR REPLACE FUNCTION encrypt_api_key(
47+
p_value TEXT,
48+
p_key TEXT
49+
) RETURNS BYTEA AS $$
50+
BEGIN
51+
RETURN pgp_sym_encrypt(
52+
p_value,
53+
p_key,
54+
'cipher-algo=aes256'
55+
);
56+
END;
57+
$$ LANGUAGE plpgsql SECURITY DEFINER;
58+
59+
CREATE OR REPLACE FUNCTION decrypt_api_key(
60+
p_encrypted_value BYTEA,
61+
p_key TEXT
62+
) RETURNS TEXT AS $$
63+
BEGIN
64+
RETURN pgp_sym_decrypt(
65+
p_encrypted_value,
66+
p_key
67+
);
68+
END;
69+
$$ LANGUAGE plpgsql SECURITY DEFINER;
70+
71+
-- Add comment to table
72+
COMMENT ON TABLE api_keys IS 'Stores API keys with encryption for developers';
73+
74+
COMMIT;

0 commit comments

Comments
 (0)