feat: add oauth2 token exchange (#12196)

Co-authored-by: Steven Masley <stevenmasley@gmail.com>
This commit is contained in:
Asher
2024-02-20 14:58:43 -09:00
committed by GitHub
co-authored by Steven Masley
parent 07cccf9033
commit 4d39da294e
46 changed files with 4008 additions and 167 deletions
@@ -0,0 +1,18 @@
DROP TRIGGER IF EXISTS trigger_delete_oauth2_provider_app_token ON oauth2_provider_app_tokens;
DROP FUNCTION IF EXISTS delete_deleted_oauth2_provider_app_token_api_key;
DROP TABLE oauth2_provider_app_tokens;
DROP TABLE oauth2_provider_app_codes;
-- It is not possible to drop enum values from enum types, so the UP on
-- login_type has "IF NOT EXISTS".
-- The constraints on the secret prefix (which is used as an id embedded in the
-- secret) are dropped, but avoid completely reverting back to the previous
-- behavior since that will render existing secrets unusable once upgraded
-- again. OAuth2 is blocked outside of development mode in previous versions,
-- so users will not be able to create broken secrets. This is really just to
-- make sure tests keep working (say for a bisect).
ALTER TABLE ONLY oauth2_provider_app_secrets
DROP CONSTRAINT oauth2_provider_app_secrets_secret_prefix_key,
ALTER COLUMN secret_prefix DROP NOT NULL;
@@ -0,0 +1,65 @@
CREATE TABLE oauth2_provider_app_codes (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
secret_prefix bytea NOT NULL,
hashed_secret bytea NOT NULL,
user_id uuid NOT NULL REFERENCES users (id) ON DELETE CASCADE,
app_id uuid NOT NULL REFERENCES oauth2_provider_apps (id) ON DELETE CASCADE,
PRIMARY KEY (id),
UNIQUE(secret_prefix)
);
COMMENT ON TABLE oauth2_provider_app_codes IS 'Codes are meant to be exchanged for access tokens.';
CREATE TABLE oauth2_provider_app_tokens (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
hash_prefix bytea NOT NULL,
refresh_hash bytea NOT NULL,
app_secret_id uuid NOT NULL REFERENCES oauth2_provider_app_secrets (id) ON DELETE CASCADE,
api_key_id text NOT NULL REFERENCES api_keys (id) ON DELETE CASCADE,
PRIMARY KEY (id),
UNIQUE(hash_prefix)
);
COMMENT ON COLUMN oauth2_provider_app_tokens.refresh_hash IS 'Refresh tokens provide a way to refresh an access token (API key). An expired API key can be refreshed if this token is not yet expired, meaning this expiry can outlive an API key.';
-- When we delete a token, delete the API key associated with it.
CREATE FUNCTION delete_deleted_oauth2_provider_app_token_api_key() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
DELETE FROM api_keys
WHERE id = OLD.api_key_id;
RETURN OLD;
END;
$$;
CREATE TRIGGER trigger_delete_oauth2_provider_app_token
AFTER DELETE ON oauth2_provider_app_tokens
FOR EACH ROW
EXECUTE PROCEDURE delete_deleted_oauth2_provider_app_token_api_key();
ALTER TYPE login_type ADD VALUE IF NOT EXISTS 'oauth2_provider_app';
-- Switch to an ID we will prefix to the raw secret that we give to the user
-- (instead of matching on the entire secret as the ID, since they will be
-- salted and we can no longer do that). OAuth2 is blocked outside of
-- development mode so there should be no production secrets unless they
-- previously upgraded, in which case they keep their original prefixes and will
-- be fine. Add a random ID for the development mode case so the upgrade does
-- not fail, at least.
ALTER TABLE ONLY oauth2_provider_app_secrets
ADD COLUMN IF NOT EXISTS secret_prefix bytea NULL;
UPDATE oauth2_provider_app_secrets
SET secret_prefix = substr(md5(random()::text), 0, 10)::bytea
WHERE secret_prefix IS NULL;
ALTER TABLE ONLY oauth2_provider_app_secrets
ALTER COLUMN secret_prefix SET NOT NULL,
ADD CONSTRAINT oauth2_provider_app_secrets_secret_prefix_key UNIQUE (secret_prefix),
DROP CONSTRAINT IF EXISTS oauth2_provider_app_secrets_app_id_hashed_secret_key;
@@ -0,0 +1,23 @@
INSERT INTO oauth2_provider_app_codes
(id, created_at, expires_at, secret_prefix, hashed_secret, user_id, app_id)
VALUES (
'c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
'2023-06-15 10:23:54+00',
'2023-06-15 10:23:54+00',
CAST('abcdefg' AS bytea),
CAST('abcdefg' AS bytea),
'0ed9befc-4911-4ccf-a8e2-559bf72daa94',
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
);
INSERT INTO oauth2_provider_app_tokens
(id, created_at, expires_at, hash_prefix, refresh_hash, app_secret_id, api_key_id)
VALUES (
'd0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
'2023-06-15 10:25:33+00',
'2023-12-15 11:40:20+00',
CAST('gfedcba' AS bytea),
CAST('abcdefg' AS bytea),
'b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
'peuLZhMXt4'
);