audit

package
v1.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

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

func (l *Logger) DroppedCount() uint64

DroppedCount returns the cumulative number of audit events dropped because the async queue was full. Useful for tests and Prometheus exporters.

func (*Logger) Log

func (l *Logger) Log(ctx context.Context, event EventType, opts ...Option)

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

func (l *Logger) StartAsync(queueSize int) func()

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 WithActor

func WithActor(userID string) Option

WithActor sets the actor (initiating user) for the audit event.

func WithDetails

func WithDetails(details map[string]any) Option

WithDetails attaches arbitrary key-value metadata to the event.

func WithIP

func WithIP(ip string) Option

WithIP sets the client IP address.

func WithSuccess

func WithSuccess(success bool) Option

WithSuccess sets whether the audited action succeeded.

func WithTarget

func WithTarget(userID string) Option

WithTarget sets the target user for the audit event. When omitted, defaults to the actor (the event is about the actor themselves).

func WithUserAgent

func WithUserAgent(ua string) Option

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL