feat: backend support for creating and storing service accounts (#22698)

Add is_service_account column to users table with CHECK constraints
enforcing login_type='none' and empty email for service accounts.
Update user creation API to validate service account constraints.

Related to:
https://linear.app/codercom/issue/PLAT-27/feat-backend-support-for-creating-and-storing-service-accounts
This commit is contained in:
George K
2026-03-11 10:19:08 -07:00
committed by GitHub
parent e96cd5cbb2
commit e5c19d0af4
28 changed files with 522 additions and 87 deletions
@@ -4,10 +4,12 @@ ALTER TABLE users ADD COLUMN IF NOT EXISTS
-- Copy "theme_preference" back to "users"
UPDATE users
SET theme_preference = (SELECT value
FROM user_configs
WHERE user_configs.user_id = users.id
AND user_configs.key = 'theme_preference');
-- Use COALESCE(SELECT, <default>) to avoid forcing an insert of user_configs
-- for every users insert in order for this down migration to succeed.
SET theme_preference = COALESCE(
(SELECT value FROM user_configs WHERE user_configs.user_id = users.id AND user_configs.key = 'theme_preference'),
''
);
-- Drop the "user_configs" table.
DROP TABLE user_configs;
@@ -0,0 +1,18 @@
-- Since we can't simply delete a user that potentially has all kinds of tables
-- referencing it, give service accounts with empty emails a unique placeholder
-- so the original unique indexes can be restored. We only run down migrations
-- in dev, so hopefully this is not a big deal.
UPDATE users SET
email = 'ex-service-account-' || id::text || '@localhost',
is_service_account = false
WHERE is_service_account = true AND email = '';
-- Restore original unique indexes.
DROP INDEX IF EXISTS idx_users_email;
DROP INDEX IF EXISTS users_email_lower_idx;
CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false);
CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false);
ALTER TABLE users DROP CONSTRAINT IF EXISTS users_email_not_empty;
ALTER TABLE users DROP CONSTRAINT IF EXISTS users_service_account_login_type;
ALTER TABLE users DROP COLUMN is_service_account;
@@ -0,0 +1,23 @@
ALTER TABLE users ADD COLUMN is_service_account boolean NOT NULL DEFAULT false;
COMMENT ON COLUMN users.is_service_account IS 'Determines if a user is an admin-managed account that cannot login';
-- Service accounts must use login_type 'none'.
ALTER TABLE users ADD CONSTRAINT users_service_account_login_type CHECK (is_service_account = false OR login_type = 'none');
-- Paranoia check: mark any (unlikely) existing user with an empty email as a
-- service account so that adding the constraint below does not fail.
-- NOTE: considered setting email to nobody@localhost instead but for all we
-- know it may already exist, so chose the lesser of two evils.
UPDATE users SET is_service_account = true, login_type = 'none' WHERE email = '';
-- Service accounts must have empty email; other users must not.
ALTER TABLE users ADD CONSTRAINT users_email_not_empty CHECK ((is_service_account = true) = (email = ''));
-- Exclude empty emails from uniqueness so multiple service accounts can omit an
-- email without conflicting. This is the less invasive alternative to making
-- email nullable, which would require a big refactor.
DROP INDEX idx_users_email;
DROP INDEX users_email_lower_idx;
CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false AND email != '');
CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false AND email != '');
@@ -0,0 +1,27 @@
-- Fixture for migration 000433_add_is_service_account_to_users.
-- Inserts a user with an empty email to ensure the migration
-- correctly marks them as a service account before adding the
-- users_email_not_empty constraint.
INSERT INTO users (
id,
email,
username,
hashed_password,
created_at,
updated_at,
status,
rbac_roles,
login_type
)
VALUES (
'8ddb584a-68b8-48ac-998f-86f091ccb380',
'',
'fixture-empty-email-user-to-service-account',
'',
'2024-01-01 00:00:00+00',
'2024-01-01 00:00:00+00',
'active',
'{}',
'password'
);
@@ -0,0 +1,41 @@
-- Fixture for migration 000433_add_is_service_account_to_users.
-- Inserts multiple service accounts with empty emails to help test
-- the down migration, which must assign each a unique placeholder
-- email before restoring the original unique index on email.
INSERT INTO users (
id,
email,
username,
hashed_password,
created_at,
updated_at,
status,
rbac_roles,
login_type,
is_service_account
)
VALUES (
'b2ce097d-2287-4d64-a550-ed821969545d',
'',
'fixture-service-account-1',
'',
'2024-01-01 00:00:00+00',
'2024-01-01 00:00:00+00',
'active',
'{}',
'none',
true
),
(
'3e218a4a-3b4a-4242-b24e-9430277e619d',
'',
'fixture-service-account-2',
'',
'2024-01-01 00:00:00+00',
'2024-01-01 00:00:00+00',
'active',
'{}',
'none',
true
);