mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
fix(coderd): ensure that user API keys are deleted when a user is (#7270)
Fixes an issue where API tokens belonging to a deleted user were not invalidated: - Adds a trigger to delete rows from the api_key stable when the column deleted is set to true in the users table. - Adds a trigger to the api_keys table to ensure that new rows may not be added where user_id corresponds to a deleted user. - Adds a migration to delete all API keys from deleted users. - Adds tests + dbfake implementation for the above.
This commit is contained in:
@ -931,6 +931,13 @@ func (q *fakeQuerier) UpdateUserDeletedByID(_ context.Context, params database.U
|
||||
if u.ID == params.ID {
|
||||
u.Deleted = params.Deleted
|
||||
q.users[i] = u
|
||||
// NOTE: In the real world, this is done by a trigger.
|
||||
for i, k := range q.apiKeys {
|
||||
if k.UserID == u.ID {
|
||||
q.apiKeys[i] = q.apiKeys[len(q.apiKeys)-1]
|
||||
q.apiKeys = q.apiKeys[:len(q.apiKeys)-1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -2768,6 +2775,12 @@ func (q *fakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyP
|
||||
arg.LifetimeSeconds = 86400
|
||||
}
|
||||
|
||||
for _, u := range q.users {
|
||||
if u.ID == arg.UserID && u.Deleted {
|
||||
return database.APIKey{}, xerrors.Errorf("refusing to create APIKey for deleted user")
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gosimple
|
||||
key := database.APIKey{
|
||||
ID: arg.ID,
|
||||
|
32
coderd/database/dump.sql
generated
32
coderd/database/dump.sql
generated
@ -129,6 +129,34 @@ CREATE TYPE workspace_transition AS ENUM (
|
||||
'delete'
|
||||
);
|
||||
|
||||
CREATE FUNCTION delete_deleted_user_api_keys() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
IF (NEW.deleted) THEN
|
||||
DELETE FROM api_keys
|
||||
WHERE user_id = OLD.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION insert_apikey_fail_if_user_deleted() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
||||
DECLARE
|
||||
BEGIN
|
||||
IF (NEW.user_id IS NOT NULL) THEN
|
||||
IF (SELECT deleted FROM users WHERE id = NEW.user_id LIMIT 1) THEN
|
||||
RAISE EXCEPTION 'Cannot create API key for deleted user';
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TABLE api_keys (
|
||||
id text NOT NULL,
|
||||
hashed_secret bytea NOT NULL,
|
||||
@ -895,6 +923,10 @@ CREATE INDEX workspace_resources_job_id_idx ON workspace_resources USING btree (
|
||||
|
||||
CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false);
|
||||
|
||||
CREATE TRIGGER trigger_insert_apikeys BEFORE INSERT ON api_keys FOR EACH ROW EXECUTE FUNCTION insert_apikey_fail_if_user_deleted();
|
||||
|
||||
CREATE TRIGGER trigger_update_users AFTER INSERT OR UPDATE ON users FOR EACH ROW WHEN ((new.deleted = true)) EXECUTE FUNCTION delete_deleted_user_api_keys();
|
||||
|
||||
ALTER TABLE ONLY api_keys
|
||||
ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TRIGGER IF EXISTS trigger_update_users ON users;
|
||||
DROP FUNCTION IF EXISTS delete_deleted_user_api_keys;
|
||||
|
||||
DROP TRIGGER IF EXISTS trigger_insert_apikeys ON api_keys;
|
||||
DROP FUNCTION IF EXISTS insert_apikey_fail_if_user_deleted;
|
||||
|
||||
COMMIT;
|
@ -0,0 +1,55 @@
|
||||
BEGIN;
|
||||
|
||||
-- We need to delete all existing API keys for soft-deleted users.
|
||||
DELETE FROM
|
||||
api_keys
|
||||
WHERE
|
||||
user_id
|
||||
IN (
|
||||
SELECT id FROM users WHERE deleted
|
||||
);
|
||||
|
||||
|
||||
-- When we soft-delete a user, we also want to delete their API key.
|
||||
CREATE FUNCTION delete_deleted_user_api_keys() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
IF (NEW.deleted) THEN
|
||||
DELETE FROM api_keys
|
||||
WHERE user_id = OLD.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
CREATE TRIGGER trigger_update_users
|
||||
AFTER INSERT OR UPDATE ON users
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.deleted = true)
|
||||
EXECUTE PROCEDURE delete_deleted_user_api_keys();
|
||||
|
||||
-- When we insert a new api key, we want to fail if the user is soft-deleted.
|
||||
CREATE FUNCTION insert_apikey_fail_if_user_deleted() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
||||
DECLARE
|
||||
BEGIN
|
||||
IF (NEW.user_id IS NOT NULL) THEN
|
||||
IF (SELECT deleted FROM users WHERE id = NEW.user_id LIMIT 1) THEN
|
||||
RAISE EXCEPTION 'Cannot create API key for deleted user';
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trigger_insert_apikeys
|
||||
BEFORE INSERT ON api_keys
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE insert_apikey_fail_if_user_deleted();
|
||||
|
||||
COMMIT;
|
Reference in New Issue
Block a user