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:
@@ -44,6 +44,9 @@ use App\Http\Controllers\Api\V1\Admin\AdminUserController;
|
||||
use App\Http\Controllers\Api\V1\Admin\AdminStatsController;
|
||||
use App\Http\Controllers\Api\V1\Admin\AdminActivityLogController;
|
||||
use App\Http\Controllers\Api\V1\Admin\AdminImpersonationController;
|
||||
use App\Http\Controllers\Api\V1\Auth\MfaSetupController;
|
||||
use App\Http\Controllers\Api\V1\Auth\MfaVerifyController;
|
||||
use App\Http\Controllers\Api\V1\Auth\TrustedDeviceController;
|
||||
use App\Models\FestivalSection;
|
||||
use App\Models\Organisation;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
@@ -68,6 +71,10 @@ Route::get('/', fn () => response()->json([
|
||||
// Public auth routes
|
||||
Route::post('auth/login', LoginController::class)->middleware('throttle:5,1');
|
||||
|
||||
// MFA verification during login (NO auth middleware — uses session token)
|
||||
Route::post('auth/mfa/verify', [MfaVerifyController::class, 'verify'])->middleware('throttle:10,1');
|
||||
Route::post('auth/mfa/email/send', [MfaVerifyController::class, 'sendEmailCode'])->middleware('throttle:5,1');
|
||||
|
||||
// Public invitation routes (no auth required)
|
||||
Route::get('invitations/{token}', [InvitationController::class, 'show'])->middleware('throttle:10,1');
|
||||
Route::post('invitations/{token}/accept', [InvitationController::class, 'accept'])->middleware('throttle:10,1');
|
||||
@@ -100,6 +107,7 @@ Route::prefix('admin')
|
||||
// Users
|
||||
Route::apiResource('users', AdminUserController::class)
|
||||
->except(['store']);
|
||||
Route::post('users/{user}/reset-mfa', [AdminUserController::class, 'resetMfa']);
|
||||
|
||||
// Platform statistics
|
||||
Route::get('stats', [AdminStatsController::class, 'index']);
|
||||
@@ -121,6 +129,20 @@ Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::post('auth/logout', LogoutController::class);
|
||||
Route::post('auth/refresh', AuthRefreshController::class);
|
||||
|
||||
// MFA setup and management (authenticated)
|
||||
Route::post('auth/mfa/setup/totp', [MfaSetupController::class, 'setupTotp']);
|
||||
Route::post('auth/mfa/setup/totp/confirm', [MfaSetupController::class, 'confirmTotp']);
|
||||
Route::post('auth/mfa/setup/email', [MfaSetupController::class, 'setupEmail']);
|
||||
Route::post('auth/mfa/setup/email/confirm', [MfaSetupController::class, 'confirmEmail']);
|
||||
Route::post('auth/mfa/disable', [MfaSetupController::class, 'disable']);
|
||||
Route::post('auth/mfa/backup-codes', [MfaSetupController::class, 'regenerateBackupCodes']);
|
||||
Route::get('auth/mfa/status', [MfaSetupController::class, 'status']);
|
||||
|
||||
// Trusted devices
|
||||
Route::get('auth/trusted-devices', [TrustedDeviceController::class, 'index']);
|
||||
Route::delete('auth/trusted-devices/{device}', [TrustedDeviceController::class, 'destroy']);
|
||||
Route::delete('auth/trusted-devices', [TrustedDeviceController::class, 'destroyAll']);
|
||||
|
||||
// Account management (self-service)
|
||||
Route::post('me/change-password', [AccountController::class, 'changePassword']);
|
||||
Route::post('me/change-email', [EmailChangeController::class, 'request']);
|
||||
|
||||
Reference in New Issue
Block a user