Skip to main content

Compliance

WIP

GDPR / 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.