diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 9a8855d0b79be..e0f792039ef58 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6117,7 +6117,8 @@ const docTemplate = `{ "start", "stop", "login", - "logout" + "logout", + "register" ], "x-enum-varnames": [ "AuditActionCreate", @@ -6126,7 +6127,8 @@ const docTemplate = `{ "AuditActionStart", "AuditActionStop", "AuditActionLogin", - "AuditActionLogout" + "AuditActionLogout", + "AuditActionRegister" ] }, "codersdk.AuditDiff": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index f32e9a87713d0..a3b27a60c0eec 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5433,7 +5433,16 @@ }, "codersdk.AuditAction": { "type": "string", - "enum": ["create", "write", "delete", "start", "stop", "login", "logout"], + "enum": [ + "create", + "write", + "delete", + "start", + "stop", + "login", + "logout", + "register" + ], "x-enum-varnames": [ "AuditActionCreate", "AuditActionWrite", @@ -5441,7 +5450,8 @@ "AuditActionStart", "AuditActionStop", "AuditActionLogin", - "AuditActionLogout" + "AuditActionLogout", + "AuditActionRegister" ] }, "codersdk.AuditDiff": { diff --git a/coderd/audit.go b/coderd/audit.go index bde7e6eb0b0a3..4a585aeb0bc99 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -247,9 +247,9 @@ func auditLogDescription(alog database.GetAuditLogsOffsetRow) string { ) // API Key resources (used for authentication) do not have targets and follow the below format: - // "User {logged in | logged out}" + // "User {logged in | logged out | registered}" if alog.ResourceType == database.ResourceTypeApiKey && - (alog.Action == database.AuditActionLogin || alog.Action == database.AuditActionLogout) { + (alog.Action == database.AuditActionLogin || alog.Action == database.AuditActionLogout || alog.Action == database.AuditActionRegister) { return str } diff --git a/coderd/audit/request.go b/coderd/audit/request.go index 4700d1a4de1f8..b060ad1f3949f 100644 --- a/coderd/audit/request.go +++ b/coderd/audit/request.go @@ -36,6 +36,10 @@ type Request[T Auditable] struct { // This optional field can be passed in when the userID cannot be determined from the API Key // such as in the case of login, when the audit log is created prior the API Key's existence. UserID uuid.UUID + + // This optional field can be passed in if the AuditAction must be overridden + // such as in the case of new user authentication when the Audit Action is 'register', not 'login'. + Action database.AuditAction } type BuildAuditParams[T Auditable] struct { @@ -198,6 +202,11 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request return } + action := p.Action + if req.Action != "" { + action = req.Action + } + ip := parseIP(p.Request.RemoteAddr) auditLog := database.AuditLog{ ID: uuid.New(), @@ -208,7 +217,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request ResourceType: either(req.Old, req.New, ResourceType[T], req.params.Action), ResourceID: either(req.Old, req.New, ResourceID[T], req.params.Action), ResourceTarget: either(req.Old, req.New, ResourceTarget[T], req.params.Action), - Action: p.Action, + Action: action, Diff: diffRaw, StatusCode: int32(sw.Status), RequestID: httpmw.RequestID(p.Request), diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f5bbe0cc04a85..b302708cf5159 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -18,7 +18,8 @@ CREATE TYPE audit_action AS ENUM ( 'start', 'stop', 'login', - 'logout' + 'logout', + 'register' ); CREATE TYPE build_reason AS ENUM ( diff --git a/coderd/database/migrations/000117_add_audit_action_register.down.sql b/coderd/database/migrations/000117_add_audit_action_register.down.sql new file mode 100644 index 0000000000000..d1d1637f4fa90 --- /dev/null +++ b/coderd/database/migrations/000117_add_audit_action_register.down.sql @@ -0,0 +1,2 @@ +-- It's not possible to drop enum values from enum types, so the UP has "IF NOT +-- EXISTS". diff --git a/coderd/database/migrations/000117_add_audit_action_register.up.sql b/coderd/database/migrations/000117_add_audit_action_register.up.sql new file mode 100644 index 0000000000000..4c855f5aa8161 --- /dev/null +++ b/coderd/database/migrations/000117_add_audit_action_register.up.sql @@ -0,0 +1,2 @@ +ALTER TYPE audit_action + ADD VALUE IF NOT EXISTS 'register'; diff --git a/coderd/database/models.go b/coderd/database/models.go index 529bb413e48f6..1f8f920a783c4 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -139,13 +139,14 @@ func AllAppSharingLevelValues() []AppSharingLevel { type AuditAction string const ( - AuditActionCreate AuditAction = "create" - AuditActionWrite AuditAction = "write" - AuditActionDelete AuditAction = "delete" - AuditActionStart AuditAction = "start" - AuditActionStop AuditAction = "stop" - AuditActionLogin AuditAction = "login" - AuditActionLogout AuditAction = "logout" + AuditActionCreate AuditAction = "create" + AuditActionWrite AuditAction = "write" + AuditActionDelete AuditAction = "delete" + AuditActionStart AuditAction = "start" + AuditActionStop AuditAction = "stop" + AuditActionLogin AuditAction = "login" + AuditActionLogout AuditAction = "logout" + AuditActionRegister AuditAction = "register" ) func (e *AuditAction) Scan(src interface{}) error { @@ -191,7 +192,8 @@ func (e AuditAction) Valid() bool { AuditActionStart, AuditActionStop, AuditActionLogin, - AuditActionLogout: + AuditActionLogout, + AuditActionRegister: return true } return false @@ -206,6 +208,7 @@ func AllAuditActionValues() []AuditAction { AuditActionStop, AuditActionLogin, AuditActionLogout, + AuditActionRegister, } } diff --git a/coderd/userauth.go b/coderd/userauth.go index 82c020af1b066..5e670b79a0a55 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -395,6 +395,12 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { return } + // If a new user is authenticating for the first time + // the audit action is 'register', not 'login' + if user.ID == uuid.Nil { + aReq.Action = database.AuditActionRegister + } + cookie, key, err := api.oauthLogin(r, oauthLoginParams{ User: user, Link: link, @@ -712,6 +718,12 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { return } + // If a new user is authenticating for the first time + // the audit action is 'register', not 'login' + if user.ID == uuid.Nil { + aReq.Action = database.AuditActionRegister + } + cookie, key, err := api.oauthLogin(r, oauthLoginParams{ User: user, Link: link, diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 60d19ecb26d48..ca6c5b49ce4c2 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -261,7 +261,7 @@ func TestUserOAuth2Github(t *testing.T) { require.Len(t, auditor.AuditLogs(), numLogs) require.NotEqual(t, auditor.AuditLogs()[numLogs-1].UserID, uuid.Nil) - require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action) + require.Equal(t, database.AuditActionRegister, auditor.AuditLogs()[numLogs-1].Action) }) t.Run("SignupAllowedTeam", func(t *testing.T) { t.Parallel() @@ -305,7 +305,7 @@ func TestUserOAuth2Github(t *testing.T) { require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) require.Len(t, auditor.AuditLogs(), numLogs) - require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action) + require.Equal(t, database.AuditActionRegister, auditor.AuditLogs()[numLogs-1].Action) }) t.Run("SignupAllowedTeamInFirstOrganization", func(t *testing.T) { t.Parallel() @@ -357,7 +357,7 @@ func TestUserOAuth2Github(t *testing.T) { require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) require.Len(t, auditor.AuditLogs(), numLogs) - require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action) + require.Equal(t, database.AuditActionRegister, auditor.AuditLogs()[numLogs-1].Action) }) t.Run("SignupAllowedTeamInSecondOrganization", func(t *testing.T) { t.Parallel() @@ -409,7 +409,7 @@ func TestUserOAuth2Github(t *testing.T) { require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) require.Len(t, auditor.AuditLogs(), numLogs) - require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action) + require.Equal(t, database.AuditActionRegister, auditor.AuditLogs()[numLogs-1].Action) }) t.Run("SignupAllowEveryone", func(t *testing.T) { t.Parallel() @@ -447,7 +447,7 @@ func TestUserOAuth2Github(t *testing.T) { require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) require.Len(t, auditor.AuditLogs(), numLogs) - require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action) + require.Equal(t, database.AuditActionRegister, auditor.AuditLogs()[numLogs-1].Action) }) t.Run("SignupFailedInactiveInOrg", func(t *testing.T) { t.Parallel() @@ -721,7 +721,7 @@ func TestUserOIDC(t *testing.T) { require.Len(t, auditor.AuditLogs(), numLogs) require.NotEqual(t, auditor.AuditLogs()[numLogs-1].UserID, uuid.Nil) - require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action) + require.Equal(t, database.AuditActionRegister, auditor.AuditLogs()[numLogs-1].Action) } if tc.AvatarURL != "" { @@ -731,7 +731,7 @@ func TestUserOIDC(t *testing.T) { require.Equal(t, tc.AvatarURL, user.AvatarURL) require.Len(t, auditor.AuditLogs(), numLogs) - require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action) + require.Equal(t, database.AuditActionRegister, auditor.AuditLogs()[numLogs-1].Action) } }) } @@ -782,7 +782,7 @@ func TestUserOIDC(t *testing.T) { require.True(t, strings.HasPrefix(user.Username, "jon-"), "username %q should have prefix %q", user.Username, "jon-") require.Len(t, auditor.AuditLogs(), numLogs) - require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action) + require.Equal(t, database.AuditActionRegister, auditor.AuditLogs()[numLogs-1].Action) }) t.Run("Disabled", func(t *testing.T) { diff --git a/codersdk/audit.go b/codersdk/audit.go index e32bd7e8da708..061f98f118ba6 100644 --- a/codersdk/audit.go +++ b/codersdk/audit.go @@ -55,13 +55,14 @@ func (r ResourceType) FriendlyString() string { type AuditAction string const ( - AuditActionCreate AuditAction = "create" - AuditActionWrite AuditAction = "write" - AuditActionDelete AuditAction = "delete" - AuditActionStart AuditAction = "start" - AuditActionStop AuditAction = "stop" - AuditActionLogin AuditAction = "login" - AuditActionLogout AuditAction = "logout" + AuditActionCreate AuditAction = "create" + AuditActionWrite AuditAction = "write" + AuditActionDelete AuditAction = "delete" + AuditActionStart AuditAction = "start" + AuditActionStop AuditAction = "stop" + AuditActionLogin AuditAction = "login" + AuditActionLogout AuditAction = "logout" + AuditActionRegister AuditAction = "register" ) func (a AuditAction) Friendly() string { @@ -80,6 +81,8 @@ func (a AuditAction) Friendly() string { return "logged in" case AuditActionLogout: return "logged out" + case AuditActionRegister: + return "registered" default: return "unknown" } diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index 43ad3d2ef0a59..f3a43105bd8d4 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -9,18 +9,18 @@ We track the following resources: -| Resource | | -| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| APIKey
login, logout, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| -| Group
create, write, delete |
FieldTracked
avatar_urltrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
| -| GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| -| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| -| Template
write, delete |
FieldTracked
active_version_idtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
created_atfalse
created_bytrue
default_ttltrue
deletedfalse
descriptiontrue
display_nametrue
group_acltrue
icontrue
idtrue
max_ttltrue
nametrue
organization_idfalse
provisionertrue
updated_atfalse
user_acltrue
| -| TemplateVersion
create, write |
FieldTracked
created_atfalse
created_bytrue
git_auth_providersfalse
idtrue
job_idfalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| -| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typefalse
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| -| Workspace
create, write, delete |
FieldTracked
autostart_scheduletrue
created_atfalse
deletedfalse
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_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| -| WorkspaceProxy
|
FieldTracked
created_attrue
deletedtrue
display_nametrue
icontrue
idtrue
nametrue
updated_attrue
urltrue
wildcard_hostnametrue
| +| Resource | | +| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| +| Group
create, write, delete |
FieldTracked
avatar_urltrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
| +| GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| +| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| +| Template
write, delete |
FieldTracked
active_version_idtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
created_atfalse
created_bytrue
default_ttltrue
deletedfalse
descriptiontrue
display_nametrue
group_acltrue
icontrue
idtrue
max_ttltrue
nametrue
organization_idfalse
provisionertrue
updated_atfalse
user_acltrue
| +| TemplateVersion
create, write |
FieldTracked
created_atfalse
created_bytrue
git_auth_providersfalse
idtrue
job_idfalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| +| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typefalse
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| +| Workspace
create, write, delete |
FieldTracked
autostart_scheduletrue
created_atfalse
deletedfalse
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_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| +| WorkspaceProxy
|
FieldTracked
created_attrue
deletedtrue
display_nametrue
icontrue
idtrue
nametrue
updated_attrue
urltrue
wildcard_hostnametrue
| diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 1a6fed6b99e5a..d08ce1b216062 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -805,15 +805,16 @@ #### Enumerated Values -| Value | -| -------- | -| `create` | -| `write` | -| `delete` | -| `start` | -| `stop` | -| `login` | -| `logout` | +| Value | +| ---------- | +| `create` | +| `write` | +| `delete` | +| `start` | +| `stop` | +| `login` | +| `logout` | +| `register` | ## codersdk.AuditDiff diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index a9c8d667da6fd..4b3aa410b9c7c 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -21,7 +21,7 @@ var AuditActionMap = map[string][]codersdk.AuditAction{ "Workspace": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete}, "WorkspaceBuild": {codersdk.AuditActionStart, codersdk.AuditActionStop}, "Group": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete}, - "APIKey": {codersdk.AuditActionLogin, codersdk.AuditActionLogout, codersdk.AuditActionCreate, codersdk.AuditActionDelete}, + "APIKey": {codersdk.AuditActionLogin, codersdk.AuditActionLogout, codersdk.AuditActionRegister, codersdk.AuditActionCreate, codersdk.AuditActionDelete}, "License": {codersdk.AuditActionCreate, codersdk.AuditActionDelete}, } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index e9eb7ce0d0771..baaa256017825 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1275,6 +1275,7 @@ export type AuditAction = | "delete" | "login" | "logout" + | "register" | "start" | "stop" | "write" @@ -1283,6 +1284,7 @@ export const AuditActions: AuditAction[] = [ "delete", "login", "logout", + "register", "start", "stop", "write",