validate(['locale' => 'required|in:es,en,ca']); session(['locale' => request('locale')]); return response()->noContent(); }); // Endpoints de sesión (guard "web") que consume la SPA para loguearse y // administrar el servidor OAuth2 (clientes, personal access tokens, apps autorizadas). Route::prefix('web-api')->group(function () { Route::get('/setup-status', [AuthController::class, 'setupStatus']); Route::post('/register', [AuthController::class, 'register'])->middleware('throttle:register'); Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:login'); // Segundo paso del login cuando el usuario tiene 2FA activo. Throttle // acotado: el espacio de códigos TOTP de 6 dígitos es chico. Route::post('/login/2fa', [AuthController::class, 'loginTwoFactor'])->middleware('throttle:6,1'); Route::post('/forgot-password', [PasswordResetController::class, 'forgotPassword'])->middleware('throttle:password-reset'); Route::post('/reset-password', [PasswordResetController::class, 'resetPassword'])->middleware('throttle:password-reset'); Route::middleware('auth')->group(function () { Route::post('/logout', [AuthController::class, 'logout']); Route::get('/me', [AuthController::class, 'me']); Route::get('/scopes', fn () => response()->json(Passport::scopes())); // 2FA propio (self-service, como clientes/tokens): setup pendiente, // confirmación, desactivación y regeneración de códigos de recuperación. Route::post('/2fa/setup', [TwoFactorController::class, 'setup']); // Confirmar activación (código TOTP) y confirmar contraseña actual // (disable/recovery-codes) son ambos espacios de adivinanza chicos — // mismo throttle acotado que el segundo paso del login con 2FA. Route::post('/2fa/confirm', [TwoFactorController::class, 'confirm'])->middleware('throttle:6,1'); Route::post('/2fa/disable', [TwoFactorController::class, 'disable'])->middleware('throttle:6,1'); Route::post('/2fa/recovery-codes', [TwoFactorController::class, 'regenerateRecoveryCodes'])->middleware('throttle:6,1'); Route::apiResource('clients', ClientController::class)->except(['show']); Route::post('/clients/{client}/rotate-secret', [ClientController::class, 'rotateSecret']); // Webhooks salientes de un client propio (token.revoked, user.deactivated). Route::get('/clients/{client}/webhooks', [WebhookController::class, 'index']); Route::post('/clients/{client}/webhooks', [WebhookController::class, 'store']); Route::put('/clients/{client}/webhooks/{webhook}', [WebhookController::class, 'update']); Route::delete('/clients/{client}/webhooks/{webhook}', [WebhookController::class, 'destroy']); Route::get('/clients/{client}/webhooks/{webhook}/deliveries', [WebhookController::class, 'deliveries']); Route::post('/clients/{client}/webhooks/{webhook}/deliveries/{delivery}/retry', [WebhookController::class, 'retryDelivery']); Route::get('/personal-access-tokens', [PersonalAccessTokenController::class, 'index']); Route::post('/personal-access-tokens', [PersonalAccessTokenController::class, 'store']); Route::delete('/personal-access-tokens/{tokenId}', [PersonalAccessTokenController::class, 'destroy']); Route::get('/authorized-apps', [AuthorizedAppController::class, 'index']); Route::delete('/authorized-apps/{tokenId}', [AuthorizedAppController::class, 'destroy']); // Analíticas del funnel de autorización OAuth (intento/aprobación/denegación), // registrado desde la pantalla de consentimiento (Authorize.vue). Route::post('/oauth/authorize-events', [AuthorizationEventController::class, 'store']); // Panel de administración de usuarios (solo para roles/permisos con "users.*"). Route::prefix('admin')->group(function () { Route::get('/users', [UserController::class, 'index'])->middleware('can:users.view'); Route::get('/users/trashed', [UserController::class, 'trashed'])->middleware('can:users.view'); Route::get('/users/pending', [UserController::class, 'pending'])->middleware('can:users.view'); Route::post('/users', [UserController::class, 'store'])->middleware('can:users.create'); Route::put('/users/{user}', [UserController::class, 'update'])->middleware('can:users.update'); Route::delete('/users/{user}', [UserController::class, 'destroy'])->middleware('can:users.delete'); Route::post('/users/{id}/restore', [UserController::class, 'restore'])->middleware('can:users.update')->whereNumber('id'); Route::delete('/users/{id}/force', [UserController::class, 'forceDelete'])->middleware('can:users.delete')->whereNumber('id'); Route::post('/users/{user}/approve', [UserController::class, 'approve'])->middleware('can:users.update'); // Roles por app (multi-tenant): a qué app y con qué rol puede entrar cada usuario. Route::get('/apps', [AppController::class, 'index'])->middleware('can:users.view'); Route::get('/users/{user}/app-roles', [UserController::class, 'appRoles'])->middleware('can:users.view'); Route::post('/users/{user}/app-roles', [UserController::class, 'assignAppRole'])->middleware('can:users.update'); Route::delete('/users/{user}/app-roles/{clientId}/{role}', [UserController::class, 'revokeAppRole'])->middleware('can:users.update'); // Sesiones (tokens OAuth activos) de un usuario — para incident response: // ver qué apps/tokens tiene vigentes y cerrarlos remotamente. Route::get('/users/{user}/sessions', [UserSessionController::class, 'index'])->middleware('can:users.view'); Route::delete('/users/{user}/sessions/{tokenId}', [UserSessionController::class, 'destroy'])->middleware('can:users.update'); Route::delete('/users/{user}/sessions', [UserSessionController::class, 'destroyAll'])->middleware('can:users.update'); // Analíticas de uso: sesiones, logins por app, actividad reciente. Route::get('/analytics/summary', [AnalyticsController::class, 'summary'])->middleware('can:users.view'); // Audit log: altas/bajas de usuarios, cambios de rol y de clientes OAuth. Route::get('/audit-log', [AuditLogController::class, 'index'])->middleware('can:audit.view'); }); }); }); // Laravel/Passport redirige a los invitados no autenticados a route('login'), así que // necesita una ruta fija (no el wildcard de abajo, que exige un parámetro). Route::view('/login', 'app')->name('login'); // Todo lo demás (home, register, dashboard, /oauth/authorize cuando hay sesión, etc.) // lo resuelve el shell de la SPA Vue (Vue Router decide qué pantalla mostrar). Route::view('/', 'app'); Route::get('/{any}', fn () => view('app')) ->where('any', '^(?!web-api|api|oauth|storage|login).*$');