Compliance
WIPGDPR / SOC 2 / HIPAA primitives - audit events, encrypted-at-rest profile data, retention windows, data-export and data-erase flows.
Audit events
Every state-changing operation emits an event on auth.events. Wire your
SIEM by subscribing once at boot:
const SENSITIVE = new Set([
'signin.success',
'signin.failed',
'session.created',
'session.revoked',
'identity.created',
'identity.merged',
'identity.soft-deleted',
'identity.restored',
'identity.erased',
'recovery.password.requested',
'recovery.password.completed',
'mfa.totp.enrolled',
'mfa.totp.removed',
'impersonate.started',
'impersonate.ended',
])
for (const event of SENSITIVE) {
auth.events.on(event, async (payload) => {
await siem.ingest({ event, payload, ts: Date.now() })
})
}
Encryption at rest
Profile data (AuthIdentity.IIdentity.profile) is the most-likely place to
store PII. Wrap the identities store with AuthAesGcmDataAtRest:
import { AuthAesGcmDataAtRest } from '@gentleduck/auth/core/dataAtRest'
const wrapped = AuthAesGcmDataAtRest.wrapIdentitiesStore(
adapter.identities,
{ keyset: process.env.PII_KEYSET },
)
The wrapper transparently encrypts profile bytes on insert / update and decrypts on read. Tampered ciphertext returns a generic auth-tag-mismatch error (no Node-error leak).
Right to erasure (GDPR Art. 17)
await auth.identities.softDelete(identityId)
// ... user has `softDeleteGracePeriodMs` to undo via auth.identities.restore()
await auth.identities.erase(identityId)
softDelete marks the row and revokes every session; the row remains
restorable until the grace window expires. erase hard-deletes
identity, credentials, and sessions in a single transaction.
Data export (GDPR Art. 20)
const snapshot = await auth.identities.exportForSubject(identityId)
// includes: profile, providers, sessions metadata, credential kinds (no secrets)
The export omits hashed secrets; raw credentials never leave the store.
Retention
await auth.sessions.gc(Date.now())
// removes sessions past their absoluteExpiresAt
Wire to a daily cron. The credential store has its own GC for expired recovery / OTP tokens.
Acting-as / impersonation audit
auth.flows.impersonate(...) requires an authorize callback. Every
impersonated session carries an actingAs envelope; the JWT transport
preserves it across token rotation so SIEM events from the impersonated
window are attributable to the real operator.