Skip to main content

Audit logs

Every security-relevant action emits an audit entry into audit_logs.

Shape

CREATE TABLE audit_logs (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL,
actor_id UUID, -- nullable: anonymous events
actor_type VARCHAR(20), -- 'user', 'api_key', 'system'
action VARCHAR(100),
resource_type VARCHAR(50),
resource_id TEXT, -- string form, not necessarily UUID
status VARCHAR(20), -- 'success', 'failure'
user_agent TEXT,
ip_address TEXT,
metadata JSONB,
created_at TIMESTAMPTZ
);

::: tip No foreign keys Audit logs deliberately have NO foreign keys to users or organizations. Compliance reviews require audit trails to survive deletion of their subjects — orphaning a row is intended behavior. :::

Actions emitted by the SDK

ActionWhen
auth.registerA new org + owner user was created.
auth.loginSuccessful password login.
auth.login.failedFailed login (unknown email or wrong password).
auth.login.mfa_requiredLogin returned mfa_required.
auth.logoutAuthenticated logout.
auth.mfa.enabledMFA enrollment confirmed.
auth.mfa.disabledMFA disabled (password-gated).
auth.mfa.step_upStep-up verification succeeded.
auth.mfa.challenge.succeededMFA login challenge passed.
auth.mfa.challenge.failedMFA login challenge failed.
auth.mfa.challenge.lockedBrute-force lockout triggered.
auth.mfa.recovery_codes_regeneratedUser minted new recovery codes.
auth.passkey.addedPasskey enrolled.
auth.passkey.removedPasskey deleted.
auth.passkey.loginPasskey sign-in.
auth.email_otp.loginEmail-OTP sign-in.
auth.email_otp.mfa_requiredEmail-OTP verify returned mfa_required.
auth.oauth.google.linked / auth.oauth.github.linkedOAuth sign-in.
auth.password_reset.consumedPassword reset succeeded.
auth.trusted_browser.added / auth.trusted_browser.revokedTrusted-browser lifecycle.
users.email_verifiedVerification token consumed.
users.password_changedUser-initiated password change.
members.invited / members.invitation_revoked / members.invitation_resent / members.invitation_acceptedInvitation lifecycle.
members.role_changed / members.removedMembership mutations.

Sink

Entries go through AuditService::log, which is a fire-and-forget async write. Failures are logged but never affect the originating request.

For production deployments that need higher durability (every entry must land), wrap or replace AuditService to write through a guaranteed sink (SQS, Kafka, etc.).

Reading entries

The SDK doesn't ship a query API for audit logs. They live in your Postgres — SELECT them directly:

SELECT created_at, action, status, actor_id, resource_id, metadata
FROM IdentSphere.audit_logs
WHERE organization_id = '...'
ORDER BY created_at DESC
LIMIT 100;

The optional identsphere-audit-export premium crate (not in v0.1 OSS) adds a typed query interface, CSV/JSONL export, and S3 archival.

What's NOT audited

The SDK does NOT audit read endpoints (GET /v1/users/me, GET /v1/auth/session, etc.). Audit logs are intended for authentication AND authorization mutations — record everything that changes security state.

If you need read-audit (BAA / HIPAA), add it at the middleware level.

Retention

The SDK does not implement retention policies. Add a scheduled job (pg_cron, cron + psql, etc.) that deletes entries older than your compliance window:

DELETE FROM IdentSphere.audit_logs
WHERE created_at < now() - interval '7 years';