feat: enterprise MFA with TOTP, email codes, backup codes, and trusted devices
Three verification methods (TOTP authenticator, email code, backup codes), trusted device management with 30-day expiry, role-based enforcement for super_admin and org_admin, admin reset capability, and full test coverage (46 tests). Modifies login flow to support MFA challenge/response with temporary session tokens stored in cache. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,8 @@
|
||||
## Table of Contents
|
||||
|
||||
1. [3.5.1 Foundation](#351-foundation)
|
||||
2. [3.5.2 Locations](#352-locations)
|
||||
2. [3.5.1a Multi-Factor Authentication](#351a-multi-factor-authentication)
|
||||
3. [3.5.2 Locations](#352-locations)
|
||||
3. [3.5.3 Festival Sections, Time Slots & Shifts](#353-festival-sections-time-slots--shifts)
|
||||
4. [3.5.4 Volunteer Profile & History](#354-volunteer-profile--history)
|
||||
5. [3.5.5 Crowd Types, Persons & Crowd Lists](#355-crowd-types-persons--crowd-lists)
|
||||
@@ -68,6 +69,11 @@
|
||||
| `first_name` | string | |
|
||||
| `last_name` | string | |
|
||||
| `email` | string | unique |
|
||||
| `mfa_enabled` | boolean | default: false |
|
||||
| `mfa_method` | string(20) nullable | `totp` or `email` |
|
||||
| `mfa_secret` | text nullable | encrypted TOTP secret |
|
||||
| `mfa_confirmed_at` | timestamp nullable | null = setup not yet verified |
|
||||
| `mfa_enforced` | boolean | default: false — forced by policy or admin |
|
||||
| `password` | string | hashed |
|
||||
| `timezone` | string | default: Europe/Amsterdam |
|
||||
| `locale` | string | default: nl |
|
||||
@@ -75,7 +81,7 @@
|
||||
| `email_verified_at` | timestamp nullable | |
|
||||
| `deleted_at` | timestamp nullable | Soft delete |
|
||||
|
||||
**Relations:** `belongsToMany` organisations (via `organisation_user`), `belongsToMany` events (via `event_user_roles`)
|
||||
**Relations:** `belongsToMany` organisations (via `organisation_user`), `belongsToMany` events (via `event_user_roles`), `hasMany` mfa_backup_codes, `hasMany` mfa_email_codes, `hasMany` trusted_devices
|
||||
**Soft delete:** yes
|
||||
|
||||
---
|
||||
@@ -263,6 +269,67 @@ scopeFestivals() // WHERE event_type IN ('festival', 'series')
|
||||
|
||||
---
|
||||
|
||||
## 3.5.1a Multi-Factor Authentication
|
||||
|
||||
> MFA tables supporting TOTP, email codes, backup codes, and trusted devices.
|
||||
> See `/dev-docs/AUTH_ARCHITECTURE.md` section 9 for full architecture.
|
||||
|
||||
### `mfa_backup_codes`
|
||||
|
||||
| Column | Type | Notes |
|
||||
| ----------- | ------------------ | ------------------------ |
|
||||
| `id` | bigint | PK, auto-increment |
|
||||
| `user_id` | ULID | FK → users |
|
||||
| `code_hash` | string(64) | bcrypt hash of code |
|
||||
| `used` | boolean | default: false |
|
||||
| `used_at` | timestamp nullable | |
|
||||
| `created_at`| timestamp | |
|
||||
| `updated_at`| timestamp | |
|
||||
|
||||
**Relations:** `belongsTo` User
|
||||
**Indexes:** `(user_id, used)`
|
||||
**Soft delete:** no (audit record)
|
||||
|
||||
---
|
||||
|
||||
### `mfa_email_codes`
|
||||
|
||||
| Column | Type | Notes |
|
||||
| ----------- | ------------------ | ------------------------ |
|
||||
| `id` | bigint | PK, auto-increment |
|
||||
| `user_id` | ULID | FK → users |
|
||||
| `code` | string(6) | 6-digit numeric code |
|
||||
| `expires_at`| timestamp | 10 min from creation |
|
||||
| `used` | boolean | default: false |
|
||||
| `created_at`| timestamp | |
|
||||
| `updated_at`| timestamp | |
|
||||
|
||||
**Relations:** `belongsTo` User
|
||||
**Indexes:** `(user_id, code, used, expires_at)`
|
||||
**Soft delete:** no (audit record)
|
||||
|
||||
---
|
||||
|
||||
### `trusted_devices`
|
||||
|
||||
| Column | Type | Notes |
|
||||
| -------------- | ------------------ | ------------------------------ |
|
||||
| `id` | ULID | PK |
|
||||
| `user_id` | ULID | FK → users |
|
||||
| `device_hash` | string(64) | SHA-256 of fingerprint+user_id |
|
||||
| `device_name` | string nullable | e.g. "Chrome on macOS" |
|
||||
| `ip_address` | string(45) | IPv4 or IPv6 |
|
||||
| `trusted_until`| timestamp | 30 days from creation |
|
||||
| `last_used_at` | timestamp nullable | |
|
||||
| `created_at` | timestamp | |
|
||||
| `updated_at` | timestamp | |
|
||||
|
||||
**Relations:** `belongsTo` User
|
||||
**Indexes:** `(user_id, device_hash, trusted_until)`
|
||||
**Soft delete:** no
|
||||
|
||||
---
|
||||
|
||||
## 3.5.2 Locations
|
||||
|
||||
> Locations are event-scoped and reusable across sections within an event.
|
||||
|
||||
Reference in New Issue
Block a user