From 054a2e31c9c80cb7e2b83223b6b2c1a61b3d12e5 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Thu, 16 Apr 2026 14:42:04 +0200 Subject: [PATCH] feat(audit): add auth source to login audit events Add a Source field to user audit events so OIDC and SAML logins can be distinguished in the audit log. The OIDC callback now passes Source: "oidc" when upserting users. Signed-off-by: Javier Rodriguez --- app/controlplane/internal/service/auth.go | 1 + .../pkg/auditor/events/testdata/users/user_logs_in.json | 3 ++- .../pkg/auditor/events/testdata/users/user_signs_up.json | 5 +++-- app/controlplane/pkg/auditor/events/user.go | 5 +++-- app/controlplane/pkg/auditor/events/user_test.go | 4 +++- app/controlplane/pkg/biz/user.go | 6 +++++- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/controlplane/internal/service/auth.go b/app/controlplane/internal/service/auth.go index e677d153c..cab134287 100644 --- a/app/controlplane/internal/service/auth.go +++ b/app/controlplane/internal/service/auth.go @@ -317,6 +317,7 @@ func callbackHandler(svc *AuthService, w http.ResponseWriter, r *http.Request) * FirstName: &claims.GivenName, LastName: &claims.FamilyName, SSOGroups: claims.Groups, + Source: "oidc", }) if err != nil { return newOauthResp(http.StatusInternalServerError, fmt.Errorf("failed to find or create user: %w", err), false) diff --git a/app/controlplane/pkg/auditor/events/testdata/users/user_logs_in.json b/app/controlplane/pkg/auditor/events/testdata/users/user_logs_in.json index 817657661..c35e4bb54 100644 --- a/app/controlplane/pkg/auditor/events/testdata/users/user_logs_in.json +++ b/app/controlplane/pkg/auditor/events/testdata/users/user_logs_in.json @@ -11,7 +11,8 @@ "Info": { "user_id": "1089bb36-e27b-428b-8009-d015c8737c54", "email": "john@cyberdyne.io", + "source": "oidc", "LoggedIn": "2024-01-01T00:00:00Z" }, - "Digest": "sha256:9a2d11d9423700c50b7a9b1d6e40b3327b3fa3afe4e33f72d1c8b20d733717bd" + "Digest": "sha256:7b4b4d0f771dc72191d794a4db780b84762e48c2912d3b5f69691bda0841f6a8" } \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/testdata/users/user_signs_up.json b/app/controlplane/pkg/auditor/events/testdata/users/user_signs_up.json index 85e9fed61..94817f95c 100644 --- a/app/controlplane/pkg/auditor/events/testdata/users/user_signs_up.json +++ b/app/controlplane/pkg/auditor/events/testdata/users/user_signs_up.json @@ -10,7 +10,8 @@ "Description": "John Connor has signed up", "Info": { "user_id": "1089bb36-e27b-428b-8009-d015c8737c54", - "email": "john@cyberdyne.io" + "email": "john@cyberdyne.io", + "source": "oidc" }, - "Digest": "sha256:d4d9f5e46478c99fef178ac4cc2cc9e01001063ccff9353d038632480ade6786" + "Digest": "sha256:6f55a8b133568683a867ef22ca27d25750cd18e5675fefe08044068ef9c17560" } \ No newline at end of file diff --git a/app/controlplane/pkg/auditor/events/user.go b/app/controlplane/pkg/auditor/events/user.go index fc892e3b2..bb501a4cb 100644 --- a/app/controlplane/pkg/auditor/events/user.go +++ b/app/controlplane/pkg/auditor/events/user.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -37,11 +37,12 @@ const ( UserRoleChangedActionType string = "RoleChanged" ) -// UserBase is the base struct for policy events +// UserBase is the base struct for user audit events type UserBase struct { UserID *uuid.UUID `json:"user_id,omitempty"` Email string `json:"email,omitempty"` SSOGroups []string `json:"sso_groups,omitempty"` + Source string `json:"source,omitempty"` } func (p *UserBase) RequiresActor() bool { diff --git a/app/controlplane/pkg/auditor/events/user_test.go b/app/controlplane/pkg/auditor/events/user_test.go index 28ba7a44e..3c13e78dd 100644 --- a/app/controlplane/pkg/auditor/events/user_test.go +++ b/app/controlplane/pkg/auditor/events/user_test.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -49,6 +49,7 @@ func TestUserEvents(t *testing.T) { UserBase: &events.UserBase{ UserID: uuidPtr(userUUID), Email: testEmail, + Source: "oidc", }, }, expected: "testdata/users/user_signs_up.json", @@ -59,6 +60,7 @@ func TestUserEvents(t *testing.T) { UserBase: &events.UserBase{ UserID: uuidPtr(userUUID), Email: testEmail, + Source: "oidc", }, LoggedIn: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), }, diff --git a/app/controlplane/pkg/biz/user.go b/app/controlplane/pkg/biz/user.go index 8a81e6b11..357f49fdc 100644 --- a/app/controlplane/pkg/biz/user.go +++ b/app/controlplane/pkg/biz/user.go @@ -1,5 +1,5 @@ // -// Copyright 2024-2025 The Chainloop Authors. +// Copyright 2024-2026 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -141,6 +141,8 @@ type UpsertByEmailOpts struct { FirstName *string LastName *string SSOGroups []string + // Source indicates the authentication method used (e.g., "oidc", "saml") + Source string } // UpsertByEmail finds or creates a user by email. By default, it will auto-onboard the user @@ -171,6 +173,7 @@ func (uc *UserUseCase) UpsertByEmail(ctx context.Context, email string, opts *Up UserID: ToPtr(uuid.MustParse(u.ID)), Email: u.Email, SSOGroups: opts.SSOGroups, + Source: opts.Source, }, }, nil) } else { @@ -181,6 +184,7 @@ func (uc *UserUseCase) UpsertByEmail(ctx context.Context, email string, opts *Up UserID: ToPtr(uuid.MustParse(u.ID)), Email: u.Email, SSOGroups: opts.SSOGroups, + Source: opts.Source, }, LoggedIn: time.Now(), }, nil)