Account deletion lifecycle: schedule, cancel, execute.
Implements configurable account deletion with three strategies:
:soft_delete- Mark as deleted, preserve data (default):hard_delete- Cascade delete all Sigra tables, remove user row:anonymize- Replace PII with anonymous values, preserve row
Security Properties
- Grace period (D-14): configurable delay before finalization (default 14 days)
- Immediate deactivation (D-13): all sessions/tokens revoked on schedule
- Pending email change auto-cancelled (D-24)
- MFA data cleared at finalization only (D-13)
- Cooldown rate limiting (D-22): 24h after cancellation
- Telemetry events for audit trail (D-26)
Summary
Functions
Cancel scheduled deletion and reactivate account.
Execute deletion based on configured strategy.
Schedule account deletion with grace period.
Check if deletion is scheduled.
Get deletion status.
Check if a cancellation is within the cooldown period.
Functions
Cancel scheduled deletion and reactivate account.
Clears deletion timestamps and original_email. The user must re-authenticate to log in (all sessions were revoked on scheduling).
Options
:changeset_fn-(user, attrs -> Ecto.Changeset.t())for user updates
Returns
{:ok, user}on success{:error, :not_scheduled}if no deletion is scheduled
Execute deletion based on configured strategy.
Called by the Oban worker when the grace period expires, or immediately for zero-grace-period configurations.
Strategies
:soft_delete- No additional action (deleted_at already set):hard_delete- Cascade delete Sigra tables, delete user row:anonymize- Replace email withdeleted_{id}@deleted.invalid, clear hashed_password, null optional PII fields
All strategies clear MFA data (TOTP secrets, backup codes) per D-13.
Options
:changeset_fn-(user, attrs -> Ecto.Changeset.t())for user updates:token_query_fn-(user, contexts -> Ecto.Queryable.t())for token cleanup:config- Config with:deletionsection
Returns
{:ok, strategy}on success{:error, :not_scheduled}if user is not scheduled for deletion
@spec schedule(module(), map(), keyword()) :: {:ok, map(), DateTime.t()} | {:error, :already_scheduled}
Schedule account deletion with grace period.
Immediately deactivates the account (revokes sessions/tokens), sets deletion timestamps, and preserves the original email for potential restoration.
Options
:changeset_fn-(user, attrs -> Ecto.Changeset.t())for user updates:session_store- SessionStore for session revocation:token_query_fn-(user, contexts -> Ecto.Queryable.t())for token cleanup:config- Config with:deletionsection (strategy, grace_period_days, etc.)
Returns
{:ok, user, scheduled_deletion_at}on success{:error, :already_scheduled}if deletion already scheduled
Check if deletion is scheduled.
Returns true when both deleted_at and scheduled_deletion_at
are set (account is in grace period).
@spec status(map()) :: {:scheduled, non_neg_integer()} | :not_scheduled | :deleted
Get deletion status.
Returns:
{:scheduled, days_remaining}when in grace period:not_scheduledwhen no deletion is pending:deletedwhen finalized (deleted_at set but no scheduled_deletion_at)
@spec within_cooldown?(DateTime.t(), non_neg_integer()) :: boolean()
Check if a cancellation is within the cooldown period.
Used to enforce the 24h cooldown after cancelling deletion (D-22). Prevents abuse of the request/cancel cycle.
Parameters
cancelled_at- The DateTime when deletion was cancelledcooldown_hours- Number of hours in the cooldown window