Documentation
¶
Overview ¶
Package audit provides best-effort audit event logging to the datastore.
Audit writes MUST NOT block the user flow or propagate errors — an A datastore outage must not break login. All failures are caught and logged via zap, never returned to callers.
Usage:
l := audit.NewLogger(writer, "default-project", zapLogger)
l.Log(ctx, audit.EventLoginSuccess,
audit.WithActor("user-42"),
audit.WithIP("10.0.0.1"),
audit.WithUserAgent("Mozilla/5.0"),
audit.WithDetails(map[string]any{"method": "password"}),
)
In a multi-project deployment, install a ProjectScoper via WithProjectScoper so each write lands under the project the request resolved to (ADR-0002) rather than the boot default.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type EventType ¶
type EventType string
EventType enumerates all auditable events. Values MUST stay in sync with schema.yaml AuditEvent enum_values.
const ( EventLoginSuccess EventType = "login_success" EventLoginFailure EventType = "login_failure" EventLoginLocked EventType = "login_locked" // login attempt while account is in lockout window EventAccountLocked EventType = "account_locked" // threshold tripped, lockout window opened EventLogout EventType = "logout" EventPasswordChanged EventType = "password_changed" EventPasswordReset EventType = "password_reset" EventTotpEnabled EventType = "totp_enabled" EventTotpDisabled EventType = "totp_disabled" EventTotpVerified EventType = "totp_verified" EventPasskeyAdded EventType = "passkey_added" EventPasskeyRemoved EventType = "passkey_removed" EventPasskeyUsed EventType = "passkey_used" EventSessionRevoked EventType = "session_revoked" EventUserInvited EventType = "user_invited" EventUserDeactivated EventType = "user_deactivated" EventUserReactivated EventType = "user_reactivated" EventUserDeleted EventType = "user_deleted" EventAdminResetPassword EventType = "admin_reset_password" EventOAuthLogin EventType = "oauth_login" EventQrLoginApproved EventType = "qr_login_approved" EventQrLoginRejected EventType = "qr_login_rejected" EventAdminHelpRequested EventType = "admin_help_requested" EventAdminHelpResolved EventType = "admin_help_resolved" EventPhoneVerificationRequested EventType = "phone_verification_requested" EventPhoneVerified EventType = "phone_verified" // EventIdentityLinked records a provider identity being attached to a // user — both implicit login-time auto-linking and the self-service // LinkIdentity RPC. EventIdentityUnlinked records a self-service // disconnect via UnlinkIdentity. EventIdentityLinked EventType = "identity_linked" EventIdentityUnlinked EventType = "identity_unlinked" // EventPlatformAdminBootstrapBlocked records a first-admin bootstrap // attempt that arrived AFTER the platform_admins table was no longer // empty (the bootstrap is permanently closed → FailedPrecondition). It // makes a closed-bootstrap probe against the ungated endpoint visible. EventPlatformAdminBootstrapBlocked EventType = "platform_admin_bootstrap_blocked" // EventLoginPolicyUpserted / EventLoginPolicyDeleted record an operator // authoring or clearing a claimed tenant's LoginPolicy (the policy the // login path enforces). EventProjectConfigUpdated records an operator // replacing a project's config_json blob. They make control-plane policy // changes — which alter how every member of a tenant/project authenticates // — visible in the audit trail. EventLoginPolicyUpserted EventType = "login_policy_upserted" EventLoginPolicyDeleted EventType = "login_policy_deleted" EventProjectConfigUpdated EventType = "project_config_updated" )
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger writes audit events to the datastore. All methods are best-effort.
By default, Log writes synchronously on the caller's goroutine. Call StartAsync to move writes to a background goroutine with a bounded queue, so the auth hot path is not gated on datastore latency. Drops when the queue is full are counted and visible via DroppedCount.
func NewLogger ¶
func NewLogger(writer NodeWriter, defaultProjectID string, logger *zap.Logger) *Logger
NewLogger creates an audit Logger.
A nil writer is tolerated — Log calls will be silently dropped with a warning, matching the best-effort contract. defaultProjectID is the boot-default storage partition (ADR-0002); per-request scoping is layered on via WithProjectScoper.
func (*Logger) DroppedCount ¶ added in v0.6.0
DroppedCount returns the cumulative number of audit events dropped because the async queue was full. Useful for tests and Prometheus exporters.
func (*Logger) Log ¶
Log writes an audit event to the datastore. It never returns an error and never panics — failures are logged via zap and silently dropped.
func (*Logger) StartAsync ¶ added in v0.6.0
StartAsync switches Log into async mode: writes are enqueued on a bounded channel and drained by a background goroutine. queueSize must be > 0. Calling StartAsync more than once is a no-op. The returned Close func stops the flusher and drains pending writes; safe to call multiple times.
func (*Logger) WithProjectScoper ¶ added in v1.1.0
func (l *Logger) WithProjectScoper(scoper ProjectScoper) *Logger
WithProjectScoper installs the per-request project resolver and returns the receiver for chaining. internal/app calls it once at boot. Passing nil is a no-op, leaving the logger on its boot-default binding.
type NodeWriter ¶
type NodeWriter interface {
ExecuteAtomic(
ctx context.Context,
tenantID, actor string,
ops []graph.Operation,
) (*graph.CommitResult, error)
}
NodeWriter is the subset of datastore operations needed by the audit logger. Accepting an interface rather than *graph.DbClient makes the logger testable without a live gRPC connection.
type Option ¶
type Option func(*eventConfig)
Option configures a single Log call.
func WithDetails ¶
WithDetails attaches arbitrary key-value metadata to the event.
func WithSuccess ¶
WithSuccess sets whether the audited action succeeded.
func WithTarget ¶
WithTarget sets the target user for the audit event. When omitted, defaults to the actor (the event is about the actor themselves).
func WithUserAgent ¶
WithUserAgent sets the client User-Agent header value.
type ProjectScoper ¶ added in v1.1.0
type ProjectScoper func(ctx context.Context) (writer NodeWriter, projectID string)
ProjectScoper resolves the project an audit write must land under, from the request context. It returns the project-bound writer and the project id (ADR-0002: the Project is the data-plane shard). The two outputs cover both backend shapes the identity service ships:
- the graph DB keys on the per-call tenant argument, so the returned project id becomes that argument and selects the partition;
- postgres ignores that argument and filters on the project its writer was bound to, so the returned writer is the project-bound sibling.
internal/app wires this to service.ScopedDB so an audit write lands under the SAME project the request resolved to. When no scoper is injected the logger falls back to its boot-default writer and project, preserving the zero-config single-project behaviour.