Skip to content

Commit 5076161

Browse files
fix: show audit logs for forgot password flow (coder#15181)
Fixes coder#15150 Audit logs for requesting a password reset, and a user updating their password, now show up in the audit log.
1 parent 297089e commit 5076161

File tree

17 files changed

+130
-40
lines changed

17 files changed

+130
-40
lines changed

coderd/apidoc/docs.go

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/audit.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,15 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
274274

275275
func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
276276
b := strings.Builder{}
277+
277278
// NOTE: WriteString always returns a nil error, so we never check it
278-
_, _ = b.WriteString("{user} ")
279+
280+
// Requesting a password reset can be performed by anyone that knows the email
281+
// of a user so saying the user performed this action might be slightly misleading.
282+
if alog.AuditLog.Action != database.AuditActionRequestPasswordReset {
283+
_, _ = b.WriteString("{user} ")
284+
}
285+
279286
if alog.AuditLog.StatusCode >= 400 {
280287
_, _ = b.WriteString("unsuccessfully attempted to ")
281288
_, _ = b.WriteString(string(alog.AuditLog.Action))
@@ -298,8 +305,12 @@ func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
298305
return b.String()
299306
}
300307

301-
_, _ = b.WriteString(" ")
302-
_, _ = b.WriteString(codersdk.ResourceType(alog.AuditLog.ResourceType).FriendlyString())
308+
if alog.AuditLog.Action == database.AuditActionRequestPasswordReset {
309+
_, _ = b.WriteString(" for")
310+
} else {
311+
_, _ = b.WriteString(" ")
312+
_, _ = b.WriteString(codersdk.ResourceType(alog.AuditLog.ResourceType).FriendlyString())
313+
}
303314

304315
if alog.AuditLog.ResourceType == database.ResourceTypeConvertLogin {
305316
_, _ = b.WriteString(" to")

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- It's not possible to drop enum values from enum types, so the UP has "IF NOT
2+
-- EXISTS".
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TYPE audit_action
2+
ADD VALUE IF NOT EXISTS 'request_password_reset';

coderd/database/models.go

Lines changed: 12 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/userauth.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func (api *API) postRequestOneTimePasscode(rw http.ResponseWriter, r *http.Reque
220220
Audit: *auditor,
221221
Log: api.Logger,
222222
Request: r,
223-
Action: database.AuditActionWrite,
223+
Action: database.AuditActionRequestPasswordReset,
224224
})
225225
)
226226
defer commitAudit()
@@ -253,6 +253,7 @@ func (api *API) postRequestOneTimePasscode(rw http.ResponseWriter, r *http.Reque
253253
}
254254
// We continue if err == sql.ErrNoRows to help prevent a timing-based attack.
255255
aReq.Old = user
256+
aReq.UserID = user.ID
256257

257258
passcode := uuid.New()
258259
passcodeExpiresAt := dbtime.Now().Add(api.OneTimePasscodeValidityPeriod)
@@ -365,6 +366,7 @@ func (api *API) postChangePasswordWithOneTimePasscode(rw http.ResponseWriter, r
365366
}
366367
// We continue if err == sql.ErrNoRows to help prevent a timing-based attack.
367368
aReq.Old = user
369+
aReq.UserID = user.ID
368370

369371
equal, err := userpassword.Compare(string(user.HashedOneTimePasscode), req.OneTimePasscode)
370372
if err != nil {

codersdk/audit.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,15 @@ func (r ResourceType) FriendlyString() string {
8686
type AuditAction string
8787

8888
const (
89-
AuditActionCreate AuditAction = "create"
90-
AuditActionWrite AuditAction = "write"
91-
AuditActionDelete AuditAction = "delete"
92-
AuditActionStart AuditAction = "start"
93-
AuditActionStop AuditAction = "stop"
94-
AuditActionLogin AuditAction = "login"
95-
AuditActionLogout AuditAction = "logout"
96-
AuditActionRegister AuditAction = "register"
89+
AuditActionCreate AuditAction = "create"
90+
AuditActionWrite AuditAction = "write"
91+
AuditActionDelete AuditAction = "delete"
92+
AuditActionStart AuditAction = "start"
93+
AuditActionStop AuditAction = "stop"
94+
AuditActionLogin AuditAction = "login"
95+
AuditActionLogout AuditAction = "logout"
96+
AuditActionRegister AuditAction = "register"
97+
AuditActionRequestPasswordReset AuditAction = "request_password_reset"
9798
)
9899

99100
func (a AuditAction) Friendly() string {
@@ -114,6 +115,8 @@ func (a AuditAction) Friendly() string {
114115
return "logged out"
115116
case AuditActionRegister:
116117
return "registered"
118+
case AuditActionRequestPasswordReset:
119+
return "password reset requested"
117120
default:
118121
return "unknown"
119122
}

docs/admin/security/audit-logs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ We track the following resources:
2525
| Organization<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>is_default</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>true</td></tr></tbody></table> |
2626
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>activity_bump</td><td>true</td></tr><tr><td>allow_user_autostart</td><td>true</td></tr><tr><td>allow_user_autostop</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>autostart_block_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_weeks</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deprecated</td><td>true</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>failure_ttl</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>max_port_sharing_level</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_display_name</td><td>false</td></tr><tr><td>organization_icon</td><td>false</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>organization_name</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>require_active_version</td><td>true</td></tr><tr><td>time_til_dormant</td><td>true</td></tr><tr><td>time_til_dormant_autodelete</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
2727
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>archived</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>external_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
28-
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>github_com_user_id</td><td>false</td></tr><tr><td>hashed_one_time_passcode</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>must_reset_password</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>one_time_passcode_expires_at</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>theme_preference</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
28+
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>github_com_user_id</td><td>false</td></tr><tr><td>hashed_one_time_passcode</td><td>false</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>must_reset_password</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>one_time_passcode_expires_at</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>theme_preference</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
2929
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>automatic_updates</td><td>true</td></tr><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>dormant_at</td><td>true</td></tr><tr><td>favorite</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
3030
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_by_avatar_url</td><td>false</td></tr><tr><td>initiator_by_username</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
3131
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>version</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy