Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions ddl/migrations/0190_oauth_pkce.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
BEGIN;

-- oauth_authorization_codes: short-lived (10 min), one-time-use authorization codes for PKCE flow
CREATE TABLE IF NOT EXISTS oauth_authorization_codes (
code VARCHAR(255) NOT NULL PRIMARY KEY,
client_id VARCHAR(255) NOT NULL,
user_id INTEGER NOT NULL,
redirect_uri TEXT NOT NULL,
code_challenge VARCHAR(255) NOT NULL,
code_challenge_method VARCHAR(10) NOT NULL DEFAULT 'S256',
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code_challenge_method is free-form VARCHAR; PKCE only allows specific values (typically S256 or plain). Add a CHECK constraint (or enum type) to restrict this column to supported methods to prevent invalid rows that the auth flow can’t verify.

Suggested change
code_challenge_method VARCHAR(10) NOT NULL DEFAULT 'S256',
code_challenge_method VARCHAR(10) NOT NULL DEFAULT 'S256' CHECK (code_challenge_method IN ('S256', 'plain')),

Copilot uses AI. Check for mistakes.
scope VARCHAR(50) NOT NULL,
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '10 minutes'),
used BOOLEAN NOT NULL DEFAULT false
);

CREATE INDEX IF NOT EXISTS idx_oauth_authorization_codes_client_id ON oauth_authorization_codes(client_id);
CREATE INDEX IF NOT EXISTS idx_oauth_authorization_codes_expires_used ON oauth_authorization_codes(expires_at, used);

-- oauth_tokens: opaque access and refresh tokens for PKCE flow
CREATE TABLE IF NOT EXISTS oauth_tokens (
token VARCHAR(255) NOT NULL PRIMARY KEY,
token_type VARCHAR(10) NOT NULL,
client_id VARCHAR(255) NOT NULL,
user_id INTEGER NOT NULL,
scope VARCHAR(50) NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
Comment on lines +20 to +26
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

token_type is free-form VARCHAR. If the code expects a small set of values (e.g., access/refresh), add a CHECK constraint (or enum) so invalid token types can’t be inserted and accidentally bypass/skip token validation logic.

Copilot uses AI. Check for mistakes.
is_revoked BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
refresh_token_id VARCHAR(255),
family_id VARCHAR(255) NOT NULL
Comment on lines +3 to +30
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tokens and authorization codes are stored as plaintext values. If the database is ever exposed, these can be replayed until expiry/revocation. Consider storing only a cryptographic hash of token/code (e.g., SHA-256) and indexing the hash instead, comparing hashes on lookup.

Copilot uses AI. Check for mistakes.
);

CREATE INDEX IF NOT EXISTS idx_oauth_tokens_client_id ON oauth_tokens(client_id);
CREATE INDEX IF NOT EXISTS idx_oauth_tokens_user_id ON oauth_tokens(user_id);
CREATE INDEX IF NOT EXISTS idx_oauth_tokens_family_id ON oauth_tokens(family_id);
CREATE INDEX IF NOT EXISTS idx_oauth_tokens_refresh_token_id ON oauth_tokens(refresh_token_id);
CREATE INDEX IF NOT EXISTS idx_oauth_tokens_lookup ON oauth_tokens(token, token_type, is_revoked, expires_at);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idx_oauth_tokens_lookup is likely redundant: oauth_tokens.token is already the PRIMARY KEY (and thus indexed), so adding another index that begins with token increases write overhead without improving point lookups. Consider dropping this index or replacing it with an index that matches actual non-PK query patterns (e.g., by token_type/is_revoked/expires_at).

Suggested change
CREATE INDEX IF NOT EXISTS idx_oauth_tokens_lookup ON oauth_tokens(token, token_type, is_revoked, expires_at);

Copilot uses AI. Check for mistakes.

-- oauth_redirect_uris: pre-registered redirect URIs per app
CREATE TABLE IF NOT EXISTS oauth_redirect_uris (
id SERIAL PRIMARY KEY,
client_id VARCHAR(255) NOT NULL,
redirect_uri TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_oauth_redirect_uris_client_id ON oauth_redirect_uris(client_id);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oauth_redirect_uris does not prevent duplicate redirect URIs for the same client. Since redirect URI matching is security-sensitive, consider adding a UNIQUE constraint (or unique index) on (client_id, redirect_uri) and indexing that pair for lookup.

Suggested change
CREATE INDEX IF NOT EXISTS idx_oauth_redirect_uris_client_id ON oauth_redirect_uris(client_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_oauth_redirect_uris_client_id_redirect_uri ON oauth_redirect_uris(client_id, redirect_uri);

Copilot uses AI. Check for mistakes.

COMMIT;
157 changes: 156 additions & 1 deletion sql/01_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7167,6 +7167,73 @@ CREATE TABLE public.notification_seen (
);


--
-- Name: oauth_authorization_codes; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.oauth_authorization_codes (
code character varying(255) NOT NULL,
client_id character varying(255) NOT NULL,
user_id integer NOT NULL,
redirect_uri text NOT NULL,
code_challenge character varying(255) NOT NULL,
code_challenge_method character varying(10) DEFAULT 'S256'::character varying NOT NULL,
scope character varying(50) NOT NULL,
expires_at timestamp with time zone DEFAULT (now() + '00:10:00'::interval) NOT NULL,
used boolean DEFAULT false NOT NULL
);


--
-- Name: oauth_redirect_uris; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.oauth_redirect_uris (
id integer NOT NULL,
client_id character varying(255) NOT NULL,
redirect_uri text NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oauth_redirect_uris only has a surrogate PK on id and allows duplicates for the same (client_id, redirect_uri). Consider enforcing uniqueness on that pair (UNIQUE constraint or unique index) since redirect URI registration should be unambiguous and is security-sensitive.

Suggested change
created_at timestamp with time zone DEFAULT now() NOT NULL
created_at timestamp with time zone DEFAULT now() NOT NULL,
UNIQUE (client_id, redirect_uri)

Copilot uses AI. Check for mistakes.
);


--
-- Name: oauth_redirect_uris_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.oauth_redirect_uris_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;


--
-- Name: oauth_redirect_uris_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.oauth_redirect_uris_id_seq OWNED BY public.oauth_redirect_uris.id;


--
-- Name: oauth_tokens; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.oauth_tokens (
token character varying(255) NOT NULL,
token_type character varying(10) NOT NULL,
client_id character varying(255) NOT NULL,
user_id integer NOT NULL,
scope character varying(50) NOT NULL,
expires_at timestamp with time zone NOT NULL,
is_revoked boolean DEFAULT false NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
refresh_token_id character varying(255),
family_id character varying(255) NOT NULL
);


--
-- Name: payment_router_txs; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -8188,7 +8255,8 @@ CREATE TABLE public.sol_purchases (
is_valid boolean,
city character varying,
region character varying,
country character varying
country character varying,
block_timestamp timestamp with time zone
);


Expand Down Expand Up @@ -9304,6 +9372,13 @@ ALTER TABLE ONLY public.eth_blocks ALTER COLUMN last_scanned_block SET DEFAULT n
ALTER TABLE ONLY public.notification ALTER COLUMN id SET DEFAULT nextval('public.notification_id_seq'::regclass);


--
-- Name: oauth_redirect_uris id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.oauth_redirect_uris ALTER COLUMN id SET DEFAULT nextval('public.oauth_redirect_uris_id_seq'::regclass);


--
-- Name: plays id; Type: DEFAULT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -9904,6 +9979,30 @@ ALTER TABLE ONLY public.notification_seen
ADD CONSTRAINT notification_seen_pkey PRIMARY KEY (user_id, seen_at);


--
-- Name: oauth_authorization_codes oauth_authorization_codes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.oauth_authorization_codes
ADD CONSTRAINT oauth_authorization_codes_pkey PRIMARY KEY (code);


--
-- Name: oauth_redirect_uris oauth_redirect_uris_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.oauth_redirect_uris
ADD CONSTRAINT oauth_redirect_uris_pkey PRIMARY KEY (id);


--
-- Name: oauth_tokens oauth_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.oauth_tokens
ADD CONSTRAINT oauth_tokens_pkey PRIMARY KEY (token);


--
-- Name: core_indexed_blocks pk_chain_id_height; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -10998,6 +11097,62 @@ CREATE INDEX idx_genre_related_artists ON public.aggregate_user USING btree (dom
CREATE INDEX idx_lower_wallet ON public.users USING btree (lower((wallet)::text));


--
-- Name: idx_oauth_authorization_codes_client_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_oauth_authorization_codes_client_id ON public.oauth_authorization_codes USING btree (client_id);


--
-- Name: idx_oauth_authorization_codes_expires_used; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_oauth_authorization_codes_expires_used ON public.oauth_authorization_codes USING btree (expires_at, used);


--
-- Name: idx_oauth_redirect_uris_client_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_oauth_redirect_uris_client_id ON public.oauth_redirect_uris USING btree (client_id);


--
-- Name: idx_oauth_tokens_client_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_oauth_tokens_client_id ON public.oauth_tokens USING btree (client_id);


--
-- Name: idx_oauth_tokens_family_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_oauth_tokens_family_id ON public.oauth_tokens USING btree (family_id);


--
-- Name: idx_oauth_tokens_lookup; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_oauth_tokens_lookup ON public.oauth_tokens USING btree (token, token_type, is_revoked, expires_at);


--
Comment on lines +11136 to +11142
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idx_oauth_tokens_lookup is redundant with the PRIMARY KEY index on oauth_tokens.token because the composite index starts with token. Consider removing it (or replacing with an index that supports non-PK queries, e.g., by token_type/is_revoked/expires_at).

Suggested change
-- Name: idx_oauth_tokens_lookup; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx_oauth_tokens_lookup ON public.oauth_tokens USING btree (token, token_type, is_revoked, expires_at);
--

Copilot uses AI. Check for mistakes.
-- Name: idx_oauth_tokens_refresh_token_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_oauth_tokens_refresh_token_id ON public.oauth_tokens USING btree (refresh_token_id);


--
-- Name: idx_oauth_tokens_user_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX idx_oauth_tokens_user_id ON public.oauth_tokens USING btree (user_id);


--
-- Name: idx_payment_router_txs_slot; Type: INDEX; Schema: public; Owner: -
--
Expand Down