diff --git a/coderd/audit/request.go b/coderd/audit/request.go
index 5627e5c3826dd..98359803b473a 100644
--- a/coderd/audit/request.go
+++ b/coderd/audit/request.go
@@ -154,9 +154,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
if ResourceID(req.Old) == uuid.Nil && ResourceID(req.New) == uuid.Nil {
// If the request action is a login or logout, we always want to audit it even if
// there is no diff. This is so we can capture events where an API Key is never created
- // because an unknown user fails to login.
- // TODO: introduce the concept of an anonymous user so we always have a userID even
- // when dealing with a mystery user. https://github.com/coder/coder/issues/6054
+ // because a known user fails to login.
if req.params.Action != database.AuditActionLogin && req.params.Action != database.AuditActionLogout {
return
}
@@ -185,8 +183,13 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
key, ok := httpmw.APIKeyOptional(p.Request)
if ok {
userID = key.UserID
- } else {
+ } else if req.UserID != uuid.Nil {
userID = req.UserID
+ } else {
+ // if we do not have a user associated with the audit action
+ // we do not want to audit
+ // (this pertains to logins; we don't want to capture non-user login attempts)
+ return
}
ip := parseIP(p.Request.RemoteAddr)
diff --git a/coderd/userauth.go b/coderd/userauth.go
index 9418d384833cc..9f92f0615ab94 100644
--- a/coderd/userauth.go
+++ b/coderd/userauth.go
@@ -424,7 +424,6 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
})
return
}
- aReq.UserID = user.ID
cookie, key, err := api.oauthLogin(r, oauthLoginParams{
User: user,
@@ -453,6 +452,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
return
}
aReq.New = key
+ aReq.UserID = key.UserID
http.SetCookie(rw, cookie)
@@ -705,7 +705,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
})
return
}
- aReq.UserID = user.ID
cookie, key, err := api.oauthLogin(r, oauthLoginParams{
User: user,
@@ -736,6 +735,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
return
}
aReq.New = key
+ aReq.UserID = key.UserID
http.SetCookie(rw, cookie)
diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go
index dd13b7f944221..60d10af9ffc72 100644
--- a/coderd/userauth_test.go
+++ b/coderd/userauth_test.go
@@ -14,6 +14,7 @@ import (
"github.com/coreos/go-oidc/v3/oidc"
"github.com/golang-jwt/jwt"
"github.com/google/go-github/v43/github"
+ "github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
@@ -106,9 +107,7 @@ func TestUserOAuth2Github(t *testing.T) {
t.Run("NotInAllowedOrganization", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{
- Auditor: auditor,
GithubOAuth2Config: &coderd.GithubOAuth2Config{
OAuth2Config: &oauth2Config{},
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
@@ -121,19 +120,13 @@ func TestUserOAuth2Github(t *testing.T) {
},
},
})
- numLogs := len(auditor.AuditLogs)
resp := oauth2Callback(t, client)
- numLogs++ // add an audit log for login
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
t.Run("NotInAllowedTeam", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{
- Auditor: auditor,
GithubOAuth2Config: &coderd.GithubOAuth2Config{
AllowOrganizations: []string{"coder"},
AllowTeams: []coderd.GithubOAuth2Team{{"another", "something"}, {"coder", "frontend"}},
@@ -156,20 +149,13 @@ func TestUserOAuth2Github(t *testing.T) {
},
},
})
- numLogs := len(auditor.AuditLogs)
resp := oauth2Callback(t, client)
- numLogs++ // add an audit log for login
-
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
t.Run("UnverifiedEmail", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{
- Auditor: auditor,
GithubOAuth2Config: &coderd.GithubOAuth2Config{
OAuth2Config: &oauth2Config{},
AllowOrganizations: []string{"coder"},
@@ -192,23 +178,16 @@ func TestUserOAuth2Github(t *testing.T) {
},
},
})
- numLogs := len(auditor.AuditLogs)
_ = coderdtest.CreateFirstUser(t, client)
- numLogs++ // add an audit log for user create
resp := oauth2Callback(t, client)
- numLogs++ // add an audit log for login
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
t.Run("BlockSignups", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{
- Auditor: auditor,
GithubOAuth2Config: &coderd.GithubOAuth2Config{
OAuth2Config: &oauth2Config{},
AllowOrganizations: []string{"coder"},
@@ -232,20 +211,14 @@ func TestUserOAuth2Github(t *testing.T) {
},
},
})
- numLogs := len(auditor.AuditLogs)
resp := oauth2Callback(t, client)
- numLogs++ // add an audit log for login
require.Equal(t, http.StatusForbidden, resp.StatusCode)
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
t.Run("MultiLoginNotAllowed", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{
- Auditor: auditor,
GithubOAuth2Config: &coderd.GithubOAuth2Config{
OAuth2Config: &oauth2Config{},
AllowOrganizations: []string{"coder"},
@@ -269,20 +242,15 @@ func TestUserOAuth2Github(t *testing.T) {
},
},
})
- numLogs := len(auditor.AuditLogs)
// Creates the first user with login_type 'password'.
_ = coderdtest.CreateFirstUser(t, client)
- numLogs++ // add an audit log for user create
// Attempting to login should give us a 403 since the user
// already has a login_type of 'password'.
resp := oauth2Callback(t, client)
- numLogs++ // add an audit log for login
require.Equal(t, http.StatusForbidden, resp.StatusCode)
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
t.Run("Signup", func(t *testing.T) {
t.Parallel()
@@ -332,6 +300,7 @@ func TestUserOAuth2Github(t *testing.T) {
require.Equal(t, "/hello-world", user.AvatarURL)
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)
})
t.Run("SignupAllowedTeam", func(t *testing.T) {
@@ -522,9 +491,7 @@ func TestUserOAuth2Github(t *testing.T) {
})
t.Run("SignupFailedInactiveInOrg", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{
- Auditor: auditor,
GithubOAuth2Config: &coderd.GithubOAuth2Config{
AllowSignups: true,
AllowOrganizations: []string{"coder"},
@@ -555,14 +522,10 @@ func TestUserOAuth2Github(t *testing.T) {
},
},
})
- numLogs := len(auditor.AuditLogs)
resp := oauth2Callback(t, client)
- numLogs++ // add an audit log for login
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
}
@@ -753,6 +716,7 @@ func TestUserOIDC(t *testing.T) {
require.Equal(t, tc.Username, user.Username)
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)
}
@@ -826,33 +790,24 @@ func TestUserOIDC(t *testing.T) {
t.Run("NoIDToken", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{
- Auditor: auditor,
OIDCConfig: &coderd.OIDCConfig{
OAuth2Config: &oauth2Config{},
},
})
- numLogs := len(auditor.AuditLogs)
resp := oidcCallback(t, client, "asdf")
- numLogs++ // add an audit log for login
-
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
t.Run("BadVerify", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
verifier := oidc.NewVerifier("", &oidc.StaticKeySet{
PublicKeys: []crypto.PublicKey{},
}, &oidc.Config{})
provider := &oidc.Provider{}
client := coderdtest.New(t, &coderdtest.Options{
- Auditor: auditor,
OIDCConfig: &coderd.OIDCConfig{
OAuth2Config: &oauth2Config{
token: (&oauth2.Token{
@@ -865,14 +820,10 @@ func TestUserOIDC(t *testing.T) {
Verifier: verifier,
},
})
- numLogs := len(auditor.AuditLogs)
resp := oidcCallback(t, client, "asdf")
- numLogs++ // add an audit log for login
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
}
diff --git a/coderd/users_test.go b/coderd/users_test.go
index 22360744e4d79..6980d005745c3 100644
--- a/coderd/users_test.go
+++ b/coderd/users_test.go
@@ -92,10 +92,7 @@ func TestPostLogin(t *testing.T) {
t.Parallel()
t.Run("InvalidUser", func(t *testing.T) {
t.Parallel()
- auditor := audit.NewMock()
- client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
- numLogs := len(auditor.AuditLogs)
-
+ client := coderdtest.New(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@@ -103,13 +100,9 @@ func TestPostLogin(t *testing.T) {
Email: "my@email.org",
Password: "password",
})
- numLogs++ // add an audit log for login
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
-
- require.Len(t, auditor.AuditLogs, numLogs)
- require.Equal(t, database.AuditActionLogin, auditor.AuditLogs[numLogs-1].Action)
})
t.Run("BadPassword", func(t *testing.T) {
diff --git a/site/src/components/AuditLogRow/AuditLogDescription/AuditLogDescription.test.tsx b/site/src/components/AuditLogRow/AuditLogDescription/AuditLogDescription.test.tsx
index cb07125efaeba..4de181cbef02d 100644
--- a/site/src/components/AuditLogRow/AuditLogDescription/AuditLogDescription.test.tsx
+++ b/site/src/components/AuditLogRow/AuditLogDescription/AuditLogDescription.test.tsx
@@ -4,7 +4,6 @@ import {
MockWorkspaceCreateAuditLogForDifferentOwner,
MockAuditLogSuccessfulLogin,
MockAuditLogUnsuccessfulLoginKnownUser,
- MockAuditLogUnsuccessfulLoginUnknownUser,
} from "testHelpers/entities"
import { AuditLogDescription } from "./AuditLogDescription"
import { AuditLogRow } from "../AuditLogRow"
@@ -101,25 +100,6 @@ describe("AuditLogDescription", () => {
),
).toBeInTheDocument()
- const statusPill = screen.getByRole("status")
- expect(statusPill).toHaveTextContent("401")
- })
- it("renders the correct string for unsuccessful login for an unknown user", async () => {
- render()
-
- expect(
- screen.getByText(
- t("auditLog:table.logRow.description.unlinkedAuditDescription", {
- truncatedDescription: "an unknown user logged in",
- target: "",
- onBehalfOf: undefined,
- })
- .replace(/<[^>]*>/g, " ")
- .replace(/\s{2,}/g, " ")
- .trim(),
- ),
- ).toBeInTheDocument()
-
const statusPill = screen.getByRole("status")
expect(statusPill).toHaveTextContent("401")
})
diff --git a/site/src/components/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx b/site/src/components/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx
index 129e48094165e..4e20ee9541be5 100644
--- a/site/src/components/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx
+++ b/site/src/components/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx
@@ -11,7 +11,7 @@ export const AuditLogDescription: FC<{ auditLog: AuditLog }> = ({
const { t } = useTranslation("auditLog")
let target = auditLog.resource_target.trim()
- const user = auditLog.user ? auditLog.user.username.trim() : "an unknown user"
+ const user = auditLog.user?.username.trim()
if (auditLog.resource_type === "workspace_build") {
return
@@ -30,7 +30,7 @@ export const AuditLogDescription: FC<{ auditLog: AuditLog }> = ({
const onBehalfOf =
auditLog.additional_fields.workspace_owner &&
auditLog.additional_fields.workspace_owner !== "unknown" &&
- auditLog.additional_fields.workspace_owner !== auditLog.user?.username
+ auditLog.additional_fields.workspace_owner.trim() !== user
? `on behalf of ${auditLog.additional_fields.workspace_owner}`
: ""
diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts
index 478e841ddd29b..a1fb78c6e6636 100644
--- a/site/src/testHelpers/entities.ts
+++ b/site/src/testHelpers/entities.ts
@@ -1384,12 +1384,6 @@ export const MockAuditLogUnsuccessfulLoginKnownUser: TypesGen.AuditLog = {
status_code: 401,
}
-export const MockAuditLogUnsuccessfulLoginUnknownUser: TypesGen.AuditLog = {
- ...MockAuditLogSuccessfulLogin,
- status_code: 401,
- user: undefined,
-}
-
export const MockWorkspaceQuota: TypesGen.WorkspaceQuota = {
credits_consumed: 0,
budget: 100,