Skip to content

Commit b8dbe62

Browse files
authored
feat: structured audit logging system (#537)
* feat: structured audit log schema & storage layer (#527) * feat: add structured audit log schema and storage layer Add AuditLog schema with support for all 6 database providers (SQL/GORM, MongoDB, ArangoDB, Cassandra, DynamoDB, Couchbase). Includes AddAuditLog, ListAuditLogs (with filtering by actor_id, action, resource_type, etc), and DeleteAuditLogsBefore for log retention. Ref: RFC #505. * chore: add more auditlog events * chore: update storage provider test * feat: add _audit_logs GraphQL query for admin audit log access (#532) Add AuditLog type, AuditLogs response, and ListAuditLogRequest input to the GraphQL schema. Supports filtering by action, actor_id, resource_type, resource_id, organization_id, and timestamp range. - Add AsAPIAuditLog() conversion method to schemas - Create audit_logs.go handler with admin-only access control - Add AuditLogs method to graphql Provider interface - Wire resolver in schema.resolvers.go * feat: add audit log helper and instrument user auth flows (#533) Create logAuditEvent() helper on graphqlProvider that extracts request info (IP, UserAgent) before spawning a fire-and-forget goroutine for safe async audit logging. Instrument all user-facing auth operations: - login (success + failed), signup, logout - verify_email, verify_otp (email vs phone conditional) - magic_link_login, forgot_password, reset_password - resend_otp, resend_verify_email - deactivate_account, update_profile * feat: instrument admin operations with audit logging (#534) * feat: add audit log helper and instrument user auth flows Create logAuditEvent() helper on graphqlProvider that extracts request info (IP, UserAgent) before spawning a fire-and-forget goroutine for safe async audit logging. Instrument all user-facing auth operations: - login (success + failed), signup, logout - verify_email, verify_otp (email vs phone conditional) - magic_link_login, forgot_password, reset_password - resend_otp, resend_verify_email - deactivate_account, update_profile * feat: instrument admin operations with audit logging Add audit events to all admin GraphQL mutations: - admin_login (success + failed), admin_logout - delete_user, update_user, enable_access, revoke_access - invite_members - add/update/delete webhook - add/update/delete email_template * fix: address review findings in admin audit instrumentation - Remove incorrect ActorEmail from delete_user (was logging deleted user's email as actor's email, but admin has no email) - Capture ResourceID from AddWebhook return value for audit log - Capture ResourceID from AddEmailTemplate return value for audit log * feat: instrument OAuth/HTTP handlers with audit logging (#535) * feat: add audit log helper and instrument user auth flows Create logAuditEvent() helper on graphqlProvider that extracts request info (IP, UserAgent) before spawning a fire-and-forget goroutine for safe async audit logging. Instrument all user-facing auth operations: - login (success + failed), signup, logout - verify_email, verify_otp (email vs phone conditional) - magic_link_login, forgot_password, reset_password - resend_otp, resend_verify_email - deactivate_account, update_profile * feat: instrument admin operations with audit logging Add audit events to all admin GraphQL mutations: - admin_login (success + failed), admin_logout - delete_user, update_user, enable_access, revoke_access - invite_members - add/update/delete webhook - add/update/delete email_template * fix: address review findings in admin audit instrumentation - Remove incorrect ActorEmail from delete_user (was logging deleted user's email as actor's email, but admin has no email) - Capture ResourceID from AddWebhook return value for audit log - Capture ResourceID from AddEmailTemplate return value for audit log * feat: instrument OAuth/HTTP handlers with audit logging Create logAuditEvent() helper for httpProvider with safe goroutine pattern (extracts IP/UserAgent before spawn, uses context.Background). Instrument HTTP handlers: - oauth_callback: AuditOAuthCallbackSuccessEvent with provider metadata - token: AuditTokenIssuedEvent / AuditTokenRefreshedEvent by grant type - revoke_refresh_token: AuditTokenRevokedEvent after session deletion - logout: AuditSessionTerminatedEvent after session cleanup * refactor: audit log schema cleanup and string literal constants (#536) Schema cleanup: - Remove OrganizationID field (no org ID exists in config) - Remove Timestamp field (duplicated CreatedAt) - Remove UpdatedAt field (audit logs are immutable) - Update all 6 DB providers, GraphQL schema, helpers, and conversion - Fix Cassandra CREATE TABLE to match new schema - Fix CreatedAt handling: all providers now conditionally set it (preserves caller-supplied values for retention testing) String literal constants: - Add AuditActorTypeUser, AuditActorTypeAdmin constants - Add AuditResourceTypeUser, AuditResourceTypeSession, AuditResourceTypeAdminSession, AuditResourceTypeWebhook, AuditResourceTypeEmailTemplate, AuditResourceTypeToken constants - Replace all inline string literals across 30 files Test improvements: - Migrate audit_logs_test.go to runForEachDB pattern - Add resource_type filter test - Add timestamp range filter test - Add round-trip field preservation test - Use constants throughout all test files
1 parent c3a132a commit b8dbe62

58 files changed

Lines changed: 2767 additions & 20 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.local.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@
2929
"Bash(grep -c \"getTestConfig\" internal/integration_tests/*.go)",
3030
"Bash(TEST_DBS=\"sqlite\" go test -p 1 -v -run TestSignup ./internal/integration_tests/)",
3131
"Bash(TEST_DBS=\"sqlite\" go test -p 1 -v -run TestDBSelection ./internal/integration_tests/)",
32-
"Bash(TEST_DBS=\"sqlite\" go test -p 1 -v -run TestStorageProvider ./internal/storage/)"
32+
"Bash(TEST_DBS=\"sqlite\" go test -p 1 -v -run TestStorageProvider ./internal/storage/)",
33+
"Bash(find /Users/lakhansamani/personal/authorizer/authorizer -type f -name *audit*)",
34+
"Bash(find /Users/lakhansamani/personal/authorizer/authorizer/internal/graphql -type f -name *.go)",
35+
"Bash(find /Users/lakhansamani/personal/authorizer/authorizer/internal/http_handlers -type f -name *.go)",
36+
"Bash(for db:*)",
37+
"Bash(do echo:*)",
38+
"Read(//Users/lakhansamani/personal/authorizer/authorizer/internal/storage/db/**)",
39+
"Bash(done)",
40+
"Bash(grep:*)"
3341
]
3442
}
3543
}

CLAUDE.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,11 @@ Detailed agent files in `.claude/agents/`. Summary:
5757

5858
| Agent | Focus |
5959
|-------|-------|
60-
| `golang-engineer` | Go idioms, provider pattern, DI |
61-
| `security-engineer` | OWASP, OAuth2/OIDC security, vulnerability audit |
62-
| `code-reviewer` | Structured review (critical/suggestions/nits/good) |
60+
| `software-engineer` | Full SDLC: Plan → Execute → Test → Review, git practices, code review, issue management |
61+
| `golang-engineer` | Go idioms, provider pattern, code style, GraphQL + REST API conventions |
62+
| `security-engineer` | Security + auth protocols: OWASP, OAuth2/OIDC, JWT, MFA, vulnerability audit |
6363
| `database-engineer` | Multi-DB consistency across 13+ providers |
64-
| `auth-engineer` | OAuth2/OIDC/JWT/MFA protocol compliance |
65-
| `qa-engineer` | Integration testing, cross-DB coverage |
66-
| `git-practices` | Commit conventions, branch strategy, PR standards |
67-
| `api-conventions` | GraphQL schema + REST/OAuth2 endpoint design |
68-
| `issue-manager` | GitHub issue triage and templates |
6964
| `doc-writer` | API docs, guides, migration docs |
70-
| `code-style` | Go naming, imports, error handling patterns |
7165

7266
## Token Optimization Notes
7367

internal/constants/audit_event.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package constants
2+
3+
// Audit actor type constants identify who performed an auditable action.
4+
const (
5+
// AuditActorTypeUser identifies a regular user as the audit actor.
6+
AuditActorTypeUser = "user"
7+
// AuditActorTypeAdmin identifies an admin as the audit actor.
8+
AuditActorTypeAdmin = "admin"
9+
)
10+
11+
// Audit resource type constants identify the type of resource affected by an auditable action.
12+
const (
13+
// AuditResourceTypeUser represents a user entity.
14+
AuditResourceTypeUser = "user"
15+
// AuditResourceTypeSession represents a user session.
16+
AuditResourceTypeSession = "session"
17+
// AuditResourceTypeAdminSession represents an admin session.
18+
AuditResourceTypeAdminSession = "admin_session"
19+
// AuditResourceTypeWebhook represents a webhook entity.
20+
AuditResourceTypeWebhook = "webhook"
21+
// AuditResourceTypeEmailTemplate represents an email template entity.
22+
AuditResourceTypeEmailTemplate = "email_template"
23+
// AuditResourceTypeToken represents an auth token.
24+
AuditResourceTypeToken = "token"
25+
)
26+
27+
// Audit event type constants used for structured audit logging.
28+
// Each constant represents a specific auditable action in the system,
29+
// organized by domain: user authentication, admin operations, OAuth,
30+
// token lifecycle, and session management.
31+
const (
32+
// AuditLoginSuccessEvent is logged when a user successfully authenticates.
33+
AuditLoginSuccessEvent = "user.login_success"
34+
// AuditLoginFailedEvent is logged when a user authentication attempt fails.
35+
AuditLoginFailedEvent = "user.login_failed"
36+
// AuditSignupEvent is logged when a new user registers.
37+
AuditSignupEvent = "user.signup"
38+
// AuditLogoutEvent is logged when a user logs out.
39+
AuditLogoutEvent = "user.logout"
40+
// AuditPasswordChangedEvent is logged when a user changes their password.
41+
AuditPasswordChangedEvent = "user.password_changed"
42+
// AuditPasswordResetEvent is logged when a user resets their password via token or OTP.
43+
AuditPasswordResetEvent = "user.password_reset"
44+
// AuditForgotPasswordEvent is logged when a user requests a password reset.
45+
AuditForgotPasswordEvent = "user.forgot_password_requested"
46+
// AuditMagicLinkRequestedEvent is logged when a user requests a magic link login.
47+
AuditMagicLinkRequestedEvent = "user.magic_link_requested"
48+
// AuditEmailVerifiedEvent is logged when a user's email is verified.
49+
AuditEmailVerifiedEvent = "user.email_verified"
50+
// AuditPhoneVerifiedEvent is logged when a user's phone number is verified.
51+
AuditPhoneVerifiedEvent = "user.phone_verified"
52+
// AuditMFAEnabledEvent is logged when a user enables multi-factor authentication.
53+
AuditMFAEnabledEvent = "user.mfa_enabled"
54+
// AuditMFADisabledEvent is logged when a user disables multi-factor authentication.
55+
AuditMFADisabledEvent = "user.mfa_disabled"
56+
// AuditProfileUpdatedEvent is logged when a user updates their profile.
57+
AuditProfileUpdatedEvent = "user.profile_updated"
58+
// AuditUserDeactivatedEvent is logged when a user deactivates their account.
59+
AuditUserDeactivatedEvent = "user.deactivated"
60+
// AuditOTPResentEvent is logged when an OTP is resent to a user.
61+
AuditOTPResentEvent = "user.otp_resent"
62+
// AuditVerifyEmailResentEvent is logged when a verification email is resent.
63+
AuditVerifyEmailResentEvent = "user.verify_email_resent"
64+
65+
// AuditAdminLoginSuccessEvent is logged when an admin successfully authenticates.
66+
AuditAdminLoginSuccessEvent = "admin.login_success"
67+
// AuditAdminLoginFailedEvent is logged when an admin authentication attempt fails.
68+
AuditAdminLoginFailedEvent = "admin.login_failed"
69+
// AuditAdminLogoutEvent is logged when an admin logs out.
70+
AuditAdminLogoutEvent = "admin.logout"
71+
// AuditAdminUserCreatedEvent is logged when an admin creates a user.
72+
AuditAdminUserCreatedEvent = "admin.user_created"
73+
// AuditAdminUserUpdatedEvent is logged when an admin updates a user.
74+
AuditAdminUserUpdatedEvent = "admin.user_updated"
75+
// AuditAdminUserDeletedEvent is logged when an admin deletes a user.
76+
AuditAdminUserDeletedEvent = "admin.user_deleted"
77+
// AuditAdminAccessRevokedEvent is logged when an admin revokes a user's access.
78+
AuditAdminAccessRevokedEvent = "admin.access_revoked"
79+
// AuditAdminAccessEnabledEvent is logged when an admin restores a user's access.
80+
AuditAdminAccessEnabledEvent = "admin.access_enabled"
81+
// AuditAdminInviteSentEvent is logged when an admin sends a user invitation.
82+
AuditAdminInviteSentEvent = "admin.invite_sent"
83+
// AuditAdminConfigChangedEvent is logged when an admin modifies server configuration.
84+
AuditAdminConfigChangedEvent = "admin.config_changed"
85+
// AuditAdminWebhookCreatedEvent is logged when an admin creates a webhook.
86+
AuditAdminWebhookCreatedEvent = "admin.webhook_created"
87+
// AuditAdminWebhookUpdatedEvent is logged when an admin updates a webhook.
88+
AuditAdminWebhookUpdatedEvent = "admin.webhook_updated"
89+
// AuditAdminWebhookDeletedEvent is logged when an admin deletes a webhook.
90+
AuditAdminWebhookDeletedEvent = "admin.webhook_deleted"
91+
// AuditAdminEmailTemplateCreatedEvent is logged when an admin creates an email template.
92+
AuditAdminEmailTemplateCreatedEvent = "admin.email_template_created"
93+
// AuditAdminEmailTemplateUpdatedEvent is logged when an admin updates an email template.
94+
AuditAdminEmailTemplateUpdatedEvent = "admin.email_template_updated"
95+
// AuditAdminEmailTemplateDeletedEvent is logged when an admin deletes an email template.
96+
AuditAdminEmailTemplateDeletedEvent = "admin.email_template_deleted"
97+
98+
// AuditOAuthLoginInitiatedEvent is logged when an OAuth login flow is started.
99+
AuditOAuthLoginInitiatedEvent = "oauth.login_initiated"
100+
// AuditOAuthCallbackSuccessEvent is logged when an OAuth callback completes successfully.
101+
AuditOAuthCallbackSuccessEvent = "oauth.callback_success"
102+
// AuditOAuthCallbackFailedEvent is logged when an OAuth callback fails.
103+
AuditOAuthCallbackFailedEvent = "oauth.callback_failed"
104+
105+
// AuditTokenIssuedEvent is logged when a new token is issued.
106+
AuditTokenIssuedEvent = "token.issued"
107+
// AuditTokenRefreshedEvent is logged when a token is refreshed.
108+
AuditTokenRefreshedEvent = "token.refreshed"
109+
// AuditTokenRevokedEvent is logged when a token is revoked.
110+
AuditTokenRevokedEvent = "token.revoked"
111+
112+
// AuditSessionCreatedEvent is logged when a new session is created.
113+
AuditSessionCreatedEvent = "session.created"
114+
// AuditSessionTerminatedEvent is logged when a session is terminated.
115+
AuditSessionTerminatedEvent = "session.terminated"
116+
)

0 commit comments

Comments
 (0)