From f15b16543f0bb7e0b1966d1faf3fc14bfa070aad Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 25 Sep 2024 14:44:56 +0000 Subject: [PATCH 1/5] feat: add one time passcode columns to users table --- coderd/database/dump.sql | 11 ++- .../000258_add_otp_reset_to_user.down.sql | 3 + .../000258_add_otp_reset_to_user.up.sql | 8 ++ coderd/database/modelqueries.go | 3 + coderd/database/models.go | 6 ++ coderd/database/queries.sql.go | 97 +++++++++++++------ docs/admin/audit-logs.md | 2 +- enterprise/audit/table.go | 35 ++++--- 8 files changed, 118 insertions(+), 47 deletions(-) create mode 100644 coderd/database/migrations/000258_add_otp_reset_to_user.down.sql create mode 100644 coderd/database/migrations/000258_add_otp_reset_to_user.up.sql diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 27c384e794239..67a4d35caf9e5 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -663,7 +663,10 @@ CREATE TABLE users ( quiet_hours_schedule text DEFAULT ''::text NOT NULL, theme_preference text DEFAULT ''::text NOT NULL, name text DEFAULT ''::text NOT NULL, - github_com_user_id bigint + github_com_user_id bigint, + hashed_one_time_passcode bytea, + one_time_passcode_expires_at timestamp with time zone, + must_reset_password boolean DEFAULT false NOT NULL ); COMMENT ON COLUMN users.quiet_hours_schedule IS 'Daily (!) cron schedule (with optional CRON_TZ) signifying the start of the user''s quiet hours. If empty, the default quiet hours on the instance is used instead.'; @@ -674,6 +677,12 @@ COMMENT ON COLUMN users.name IS 'Name of the Coder user'; COMMENT ON COLUMN users.github_com_user_id IS 'The GitHub.com numerical user ID. At time of implementation, this is used to check if the user has starred the Coder repository.'; +COMMENT ON COLUMN users.hashed_one_time_passcode IS 'A hash of the one-time-passcode given to the user.'; + +COMMENT ON COLUMN users.one_time_passcode_expires_at IS 'The time when the one-time-passcode expires.'; + +COMMENT ON COLUMN users.must_reset_password IS 'Determines if the user should be forced to change their password.'; + CREATE VIEW group_members_expanded AS WITH all_members AS ( SELECT group_members.user_id, diff --git a/coderd/database/migrations/000258_add_otp_reset_to_user.down.sql b/coderd/database/migrations/000258_add_otp_reset_to_user.down.sql new file mode 100644 index 0000000000000..357c5fda06a96 --- /dev/null +++ b/coderd/database/migrations/000258_add_otp_reset_to_user.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE users DROP COLUMN hashed_one_time_passcode; +ALTER TABLE users DROP COLUMN one_time_passcode_expires_at; +ALTER TABLE users DROP COLUMN must_reset_password; diff --git a/coderd/database/migrations/000258_add_otp_reset_to_user.up.sql b/coderd/database/migrations/000258_add_otp_reset_to_user.up.sql new file mode 100644 index 0000000000000..26402d382066a --- /dev/null +++ b/coderd/database/migrations/000258_add_otp_reset_to_user.up.sql @@ -0,0 +1,8 @@ +ALTER TABLE users ADD COLUMN hashed_one_time_passcode bytea; +COMMENT ON COLUMN users.hashed_one_time_passcode IS 'A hash of the one-time-passcode given to the user.'; + +ALTER TABLE users ADD COLUMN one_time_passcode_expires_at timestamp with time zone; +COMMENT ON COLUMN users.one_time_passcode_expires_at IS 'The time when the one-time-passcode expires.'; + +ALTER TABLE users ADD COLUMN must_reset_password bool NOT NULL DEFAULT false; +COMMENT ON COLUMN users.must_reset_password IS 'Determines if the user should be forced to change their password.'; diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index c2061adf13d6a..1274608a7d276 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -364,6 +364,9 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams, &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, &i.Count, ); err != nil { return nil, err diff --git a/coderd/database/models.go b/coderd/database/models.go index 87ef020e8be5e..05b4c404ea16f 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2869,6 +2869,12 @@ type User struct { Name string `db:"name" json:"name"` // The GitHub.com numerical user ID. At time of implementation, this is used to check if the user has starred the Coder repository. GithubComUserID sql.NullInt64 `db:"github_com_user_id" json:"github_com_user_id"` + // A hash of the one-time-passcode given to the user. + HashedOneTimePasscode []byte `db:"hashed_one_time_passcode" json:"hashed_one_time_passcode"` + // The time when the one-time-passcode expires. + OneTimePasscodeExpiresAt sql.NullTime `db:"one_time_passcode_expires_at" json:"one_time_passcode_expires_at"` + // Determines if the user should be forced to change their password. + MustResetPassword bool `db:"must_reset_password" json:"must_reset_password"` } type UserLink struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index a8ea40a395cf1..8d423d6f2f6ac 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -9993,7 +9993,7 @@ func (q *sqlQuerier) GetAuthorizationUserRoles(ctx context.Context, userID uuid. const getUserByEmailOrUsername = `-- name: GetUserByEmailOrUsername :one SELECT - id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id + id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password FROM users WHERE @@ -10028,13 +10028,16 @@ func (q *sqlQuerier) GetUserByEmailOrUsername(ctx context.Context, arg GetUserBy &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } const getUserByID = `-- name: GetUserByID :one SELECT - id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id + id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password FROM users WHERE @@ -10063,6 +10066,9 @@ func (q *sqlQuerier) GetUserByID(ctx context.Context, id uuid.UUID) (User, error &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } @@ -10085,7 +10091,7 @@ func (q *sqlQuerier) GetUserCount(ctx context.Context) (int64, error) { const getUsers = `-- name: GetUsers :many SELECT - id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, COUNT(*) OVER() AS count + id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password, COUNT(*) OVER() AS count FROM users WHERE @@ -10169,23 +10175,26 @@ type GetUsersParams struct { } type GetUsersRow struct { - ID uuid.UUID `db:"id" json:"id"` - Email string `db:"email" json:"email"` - Username string `db:"username" json:"username"` - HashedPassword []byte `db:"hashed_password" json:"hashed_password"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - Status UserStatus `db:"status" json:"status"` - RBACRoles pq.StringArray `db:"rbac_roles" json:"rbac_roles"` - LoginType LoginType `db:"login_type" json:"login_type"` - AvatarURL string `db:"avatar_url" json:"avatar_url"` - Deleted bool `db:"deleted" json:"deleted"` - LastSeenAt time.Time `db:"last_seen_at" json:"last_seen_at"` - QuietHoursSchedule string `db:"quiet_hours_schedule" json:"quiet_hours_schedule"` - ThemePreference string `db:"theme_preference" json:"theme_preference"` - Name string `db:"name" json:"name"` - GithubComUserID sql.NullInt64 `db:"github_com_user_id" json:"github_com_user_id"` - Count int64 `db:"count" json:"count"` + ID uuid.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + HashedPassword []byte `db:"hashed_password" json:"hashed_password"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + Status UserStatus `db:"status" json:"status"` + RBACRoles pq.StringArray `db:"rbac_roles" json:"rbac_roles"` + LoginType LoginType `db:"login_type" json:"login_type"` + AvatarURL string `db:"avatar_url" json:"avatar_url"` + Deleted bool `db:"deleted" json:"deleted"` + LastSeenAt time.Time `db:"last_seen_at" json:"last_seen_at"` + QuietHoursSchedule string `db:"quiet_hours_schedule" json:"quiet_hours_schedule"` + ThemePreference string `db:"theme_preference" json:"theme_preference"` + Name string `db:"name" json:"name"` + GithubComUserID sql.NullInt64 `db:"github_com_user_id" json:"github_com_user_id"` + HashedOneTimePasscode []byte `db:"hashed_one_time_passcode" json:"hashed_one_time_passcode"` + OneTimePasscodeExpiresAt sql.NullTime `db:"one_time_passcode_expires_at" json:"one_time_passcode_expires_at"` + MustResetPassword bool `db:"must_reset_password" json:"must_reset_password"` + Count int64 `db:"count" json:"count"` } // This will never return deleted users. @@ -10224,6 +10233,9 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, &i.Count, ); err != nil { return nil, err @@ -10240,7 +10252,7 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse } const getUsersByIDs = `-- name: GetUsersByIDs :many -SELECT id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id FROM users WHERE id = ANY($1 :: uuid [ ]) +SELECT id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password FROM users WHERE id = ANY($1 :: uuid [ ]) ` // This shouldn't check for deleted, because it's frequently used @@ -10272,6 +10284,9 @@ func (q *sqlQuerier) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]User &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ); err != nil { return nil, err } @@ -10300,7 +10315,7 @@ INSERT INTO login_type ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password ` type InsertUserParams struct { @@ -10345,6 +10360,9 @@ func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } @@ -10403,7 +10421,7 @@ SET updated_at = $3 WHERE id = $1 -RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id +RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password ` type UpdateUserAppearanceSettingsParams struct { @@ -10432,6 +10450,9 @@ func (q *sqlQuerier) UpdateUserAppearanceSettings(ctx context.Context, arg Updat &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } @@ -10495,7 +10516,7 @@ SET last_seen_at = $2, updated_at = $3 WHERE - id = $1 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id + id = $1 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password ` type UpdateUserLastSeenAtParams struct { @@ -10524,6 +10545,9 @@ func (q *sqlQuerier) UpdateUserLastSeenAt(ctx context.Context, arg UpdateUserLas &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } @@ -10541,7 +10565,7 @@ SET '':: bytea END WHERE - id = $2 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id + id = $2 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password ` type UpdateUserLoginTypeParams struct { @@ -10569,6 +10593,9 @@ func (q *sqlQuerier) UpdateUserLoginType(ctx context.Context, arg UpdateUserLogi &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } @@ -10584,7 +10611,7 @@ SET name = $6 WHERE id = $1 -RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id +RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password ` type UpdateUserProfileParams struct { @@ -10623,6 +10650,9 @@ func (q *sqlQuerier) UpdateUserProfile(ctx context.Context, arg UpdateUserProfil &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } @@ -10634,7 +10664,7 @@ SET quiet_hours_schedule = $2 WHERE id = $1 -RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id +RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password ` type UpdateUserQuietHoursScheduleParams struct { @@ -10662,6 +10692,9 @@ func (q *sqlQuerier) UpdateUserQuietHoursSchedule(ctx context.Context, arg Updat &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } @@ -10674,7 +10707,7 @@ SET rbac_roles = ARRAY(SELECT DISTINCT UNNEST($1 :: text[])) WHERE id = $2 -RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id +RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password ` type UpdateUserRolesParams struct { @@ -10702,6 +10735,9 @@ func (q *sqlQuerier) UpdateUserRoles(ctx context.Context, arg UpdateUserRolesPar &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } @@ -10713,7 +10749,7 @@ SET status = $2, updated_at = $3 WHERE - id = $1 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id + id = $1 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, must_reset_password ` type UpdateUserStatusParams struct { @@ -10742,6 +10778,9 @@ func (q *sqlQuerier) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusP &i.ThemePreference, &i.Name, &i.GithubComUserID, + &i.HashedOneTimePasscode, + &i.OneTimePasscodeExpiresAt, + &i.MustResetPassword, ) return i, err } diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index 5fb017ba73e92..b43674108a41b 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -25,7 +25,7 @@ We track the following resources: | Organization
|
FieldTracked
created_atfalse
descriptiontrue
display_nametrue
icontrue
idfalse
is_defaulttrue
nametrue
updated_attrue
| | Template
write, delete |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| | TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| -| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
nametrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| +| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodetrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
must_reset_passwordfalse
nametrue
one_time_passcode_expires_atfalse
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| | Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| | WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| | WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 7310848f4f959..706996bdb5670 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -129,22 +129,25 @@ var auditableResourcesTypes = map[any]map[string]Action{ "archived": ActionTrack, }, &database.User{}: { - "id": ActionTrack, - "email": ActionTrack, - "username": ActionTrack, - "hashed_password": ActionSecret, // Do not expose a users hashed password. - "created_at": ActionIgnore, // Never changes. - "updated_at": ActionIgnore, // Changes, but is implicit and not helpful in a diff. - "status": ActionTrack, - "rbac_roles": ActionTrack, - "login_type": ActionTrack, - "avatar_url": ActionIgnore, - "last_seen_at": ActionIgnore, - "deleted": ActionTrack, - "quiet_hours_schedule": ActionTrack, - "theme_preference": ActionIgnore, - "name": ActionTrack, - "github_com_user_id": ActionIgnore, + "id": ActionTrack, + "email": ActionTrack, + "username": ActionTrack, + "hashed_password": ActionSecret, // Do not expose a users hashed password. + "created_at": ActionIgnore, // Never changes. + "updated_at": ActionIgnore, // Changes, but is implicit and not helpful in a diff. + "status": ActionTrack, + "rbac_roles": ActionTrack, + "login_type": ActionTrack, + "avatar_url": ActionIgnore, + "last_seen_at": ActionIgnore, + "deleted": ActionTrack, + "quiet_hours_schedule": ActionTrack, + "theme_preference": ActionIgnore, + "name": ActionTrack, + "github_com_user_id": ActionIgnore, + "hashed_one_time_passcode": ActionSecret, // Do not expose a users one time passcode. + "one_time_passcode_expires_at": ActionIgnore, + "must_reset_password": ActionIgnore, }, &database.Workspace{}: { "id": ActionTrack, From e121fc6e92e2796eec7136c085274425d410faa0 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 25 Sep 2024 15:29:11 +0000 Subject: [PATCH 2/5] fix: grammar --- enterprise/audit/table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 706996bdb5670..9f8446b2a63d3 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -145,7 +145,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "theme_preference": ActionIgnore, "name": ActionTrack, "github_com_user_id": ActionIgnore, - "hashed_one_time_passcode": ActionSecret, // Do not expose a users one time passcode. + "hashed_one_time_passcode": ActionSecret, // Do not expose a user's one time passcode. "one_time_passcode_expires_at": ActionIgnore, "must_reset_password": ActionIgnore, }, From 67fb55132eb7c48bad879a469270b4ced816b2f8 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 25 Sep 2024 15:36:38 +0000 Subject: [PATCH 3/5] fix: track changes --- enterprise/audit/table.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 9f8446b2a63d3..15eaaeb11b4f5 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -146,8 +146,8 @@ var auditableResourcesTypes = map[any]map[string]Action{ "name": ActionTrack, "github_com_user_id": ActionIgnore, "hashed_one_time_passcode": ActionSecret, // Do not expose a user's one time passcode. - "one_time_passcode_expires_at": ActionIgnore, - "must_reset_password": ActionIgnore, + "one_time_passcode_expires_at": ActionTrack, + "must_reset_password": ActionTrack, }, &database.Workspace{}: { "id": ActionTrack, From 6dd119f8b069f7be8b823533ab3dcc9d4bf61f24 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 25 Sep 2024 15:45:45 +0000 Subject: [PATCH 4/5] fix: run 'make gen' --- docs/admin/audit-logs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index b43674108a41b..8872d382fe79b 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -25,7 +25,7 @@ We track the following resources: | Organization
|
FieldTracked
created_atfalse
descriptiontrue
display_nametrue
icontrue
idfalse
is_defaulttrue
nametrue
updated_attrue
| | Template
write, delete |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| | TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| -| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodetrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
must_reset_passwordfalse
nametrue
one_time_passcode_expires_atfalse
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| +| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodetrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
must_reset_passwordtrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| | Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| | WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| | WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| From 9f3a036e5c7259070ca374b0ceb53ad52d866e30 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 25 Sep 2024 16:09:58 +0000 Subject: [PATCH 5/5] fix: add constraint --- coderd/database/dump.sql | 3 ++- .../migrations/000258_add_otp_reset_to_user.down.sql | 2 ++ .../database/migrations/000258_add_otp_reset_to_user.up.sql | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 67a4d35caf9e5..626d00cc81b41 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -666,7 +666,8 @@ CREATE TABLE users ( github_com_user_id bigint, hashed_one_time_passcode bytea, one_time_passcode_expires_at timestamp with time zone, - must_reset_password boolean DEFAULT false NOT NULL + must_reset_password boolean DEFAULT false NOT NULL, + CONSTRAINT one_time_passcode_set CHECK ((((hashed_one_time_passcode IS NULL) AND (one_time_passcode_expires_at IS NULL)) OR ((hashed_one_time_passcode IS NOT NULL) AND (one_time_passcode_expires_at IS NOT NULL)))) ); COMMENT ON COLUMN users.quiet_hours_schedule IS 'Daily (!) cron schedule (with optional CRON_TZ) signifying the start of the user''s quiet hours. If empty, the default quiet hours on the instance is used instead.'; diff --git a/coderd/database/migrations/000258_add_otp_reset_to_user.down.sql b/coderd/database/migrations/000258_add_otp_reset_to_user.down.sql index 357c5fda06a96..0b812889247b6 100644 --- a/coderd/database/migrations/000258_add_otp_reset_to_user.down.sql +++ b/coderd/database/migrations/000258_add_otp_reset_to_user.down.sql @@ -1,3 +1,5 @@ +ALTER TABLE users DROP CONSTRAINT one_time_passcode_set; + ALTER TABLE users DROP COLUMN hashed_one_time_passcode; ALTER TABLE users DROP COLUMN one_time_passcode_expires_at; ALTER TABLE users DROP COLUMN must_reset_password; diff --git a/coderd/database/migrations/000258_add_otp_reset_to_user.up.sql b/coderd/database/migrations/000258_add_otp_reset_to_user.up.sql index 26402d382066a..3453a2e786078 100644 --- a/coderd/database/migrations/000258_add_otp_reset_to_user.up.sql +++ b/coderd/database/migrations/000258_add_otp_reset_to_user.up.sql @@ -4,5 +4,10 @@ COMMENT ON COLUMN users.hashed_one_time_passcode IS 'A hash of the one-time-pass ALTER TABLE users ADD COLUMN one_time_passcode_expires_at timestamp with time zone; COMMENT ON COLUMN users.one_time_passcode_expires_at IS 'The time when the one-time-passcode expires.'; +ALTER TABLE users ADD CONSTRAINT one_time_passcode_set CHECK ( + (hashed_one_time_passcode IS NULL AND one_time_passcode_expires_at IS NULL) + OR (hashed_one_time_passcode IS NOT NULL AND one_time_passcode_expires_at IS NOT NULL) +); + ALTER TABLE users ADD COLUMN must_reset_password bool NOT NULL DEFAULT false; COMMENT ON COLUMN users.must_reset_password IS 'Determines if the user should be forced to change their password.';