validate([ 'correo' => ['required', 'string'], 'contra' => ['required', 'string'], 'website' => ['nullable', 'string', 'max:255'], ]); if (!$this->honeypotValido($request)) { return back() ->withInput($request->except(['contra', 'website'])) ->withErrors(['login_error' => 'No se pudo procesar el inicio de sesión.']); } $correo = trim((string) $request->input('correo')); $contra = (string) $request->input('contra'); $credencial = CredencialCliente::with('cliente.persona.telefonos')->where('correo', $correo)->first(); if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) { return back() ->withInput($request->except('contra')) ->with('login_error', 'Usuario o contraseña incorrectos.'); } if ((int) ($credencial->cliente?->baja_id ?? 0) !== 1) { return back() ->withInput($request->except('contra')) ->with('login_error', 'No es posible iniciar sesion. Comuníquese con un profesional de Abogadas del Litoral'); } $token = Str::random(64); $credencial->token = $token; $credencial->fecha_hora = now(); $credencial->save(); $personaCliente = $credencial->cliente?->persona; $nombreCliente = trim((string) ($personaCliente?->nombre ?? '')); $apellidoCliente = trim((string) ($personaCliente?->apellido ?? '')); $celularCliente = trim((string) ($personaCliente?->telefonos?->first()?->telefono ?? '')); $request->session()->regenerate(); $request->session()->regenerateToken(); $request->session()->put([ 'cliente_auth' => true, 'cliente_id' => (int) ($credencial->cliente?->id ?? 0), 'cliente_token' => $token, 'cliente_correo' => (string) $credencial->correo, 'cliente_nombre' => $nombreCliente !== '' ? $nombreCliente : (string) $credencial->correo, 'cliente_apellido' => $apellidoCliente, 'cliente_celular' => $celularCliente, ]); $this->registrarAccionSeguridad( (int) ($credencial->cliente?->persona_id ?? 0), 'CLIENTE', (string) $request->ip(), 'Inició sesión', 'El cliente ID ' . (int) ($credencial->cliente?->id ?? 0) . ' inició sesión.' ); $this->limpiarThrottle($request, 'login-cliente-web'); return redirect('/cliente/dashboard')->with('login_success', 'Login exitoso.'); } public function loginPersonalWeb(Request $request): RedirectResponse { $request->validate([ 'usuario' => ['required', 'string'], 'contra' => ['required', 'string'], 'website' => ['nullable', 'string', 'max:255'], ]); if (!$this->honeypotValido($request)) { return back() ->withInput($request->except(['contra', 'website'])) ->withErrors(['login_error' => 'No se pudo procesar el inicio de sesión.']); } $usuario = trim((string) $request->input('usuario')); $contra = (string) $request->input('contra'); $credencial = CredencialProfesional::with(['administrador.persona', 'profesional.persona']) ->where('usuario', $usuario) ->first(); if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) { return back() ->withInput($request->except('contra')) ->with('login_error', 'Usuario o contraseña incorrectos.'); } if (strtoupper((string) $credencial->rol) !== 'ADMIN' && (int) ($credencial->profesional?->baja_id ?? 0) !== 1) { return back() ->withInput($request->except('contra')) ->with('login_error', 'Usted está dado de baja. Comuníquese con el administrador'); } $token = Str::random(64); $credencial->token = $token; $credencial->fecha_hora = now(); $credencial->save(); $rol = strtoupper((string) $credencial->rol); $personaProfesional = $credencial->profesional?->persona; $nombreProfesional = trim((string) ($personaProfesional?->nombre ?? '') . ' ' . (string) ($personaProfesional?->apellido ?? '')); $personaAdmin = $credencial->administrador?->persona; $nombreAdmin = trim((string) ($personaAdmin?->nombre ?? '') . ' ' . (string) ($personaAdmin?->apellido ?? '')); $nombreSesion = $rol === 'ADMIN' ? $nombreAdmin : $nombreProfesional; $request->session()->regenerate(); $request->session()->regenerateToken(); $request->session()->put([ 'personal_auth' => true, 'personal_token' => $token, 'personal_usuario' => (string) $credencial->usuario, 'personal_nombre' => $nombreSesion !== '' ? $nombreSesion : (string) $credencial->usuario, 'personal_apellido' => $rol === 'ADMIN' ? ($personaAdmin?->apellido ?? '') : ($personaProfesional?->apellido ?? ''), 'personal_rol' => $rol, 'personal_credencial_id' => (int) $credencial->id, ]); $this->registrarAccionSeguridad( $rol === 'ADMIN' ? (int) ($credencial->administrador?->persona_id ?? 0) : (int) ($credencial->profesional?->persona_id ?? 0), $rol === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL', (string) $request->ip(), 'Inició sesión', ($rol === 'ADMIN' ? 'La administradora ID ' . (int) ($credencial->administrador?->id ?? 0) : 'El profesional ID ' . (int) ($credencial->profesional?->id ?? 0)) . ' inició sesión.' ); $this->limpiarThrottle($request, 'login-personal-web'); $redirectPath = $rol === 'ADMIN' ? '/administrador/dashboard' : '/profesional/dashboard'; return redirect($redirectPath)->with('login_success', 'Login exitoso.'); } public function loginCliente(Request $request): JsonResponse { $request->validate([ 'correo' => ['required', 'string'], 'contra' => ['required', 'string'], ]); $correo = trim((string) $request->input('correo')); $contra = (string) $request->input('contra'); $credencial = CredencialCliente::with('cliente')->where('correo', $correo)->first(); if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) { return response()->json([ 'success' => false, 'message' => 'Credenciales invalidas', ], 401); } if ((int) ($credencial->cliente?->baja_id ?? 0) !== 1) { return response()->json([ 'success' => false, 'message' => 'No es posible iniciar sesion. Comuníquese con un profesional de Abogadas del Litoral', ], 403); } $token = Str::random(64); $credencial->token = $token; $credencial->fecha_hora = now(); $credencial->save(); $this->registrarAccionSeguridad( (int) ($credencial->cliente?->persona_id ?? 0), 'CLIENTE', (string) $request->ip(), 'Inició sesión', 'El cliente ID ' . (int) ($credencial->cliente?->id ?? 0) . ' inició sesión.' ); $this->limpiarThrottle($request, 'login-cliente-api'); return response()->json([ 'success' => true, 'data' => [ 'tipo' => 'cliente', 'id_credencial' => $credencial->id, 'token' => $token, ], 'message' => 'Login de cliente exitoso', ], 200); } public function loginPersonal(Request $request): JsonResponse { $request->validate([ 'usuario' => ['required', 'string'], 'contra' => ['required', 'string'], ]); $usuario = trim((string) $request->input('usuario')); $contra = (string) $request->input('contra'); $credencial = CredencialProfesional::with(['administrador', 'profesional'])->where('usuario', $usuario)->first(); if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) { return response()->json([ 'success' => false, 'message' => 'Credenciales invalidas', ], 401); } if (strtoupper((string) $credencial->rol) !== 'ADMIN' && (int) ($credencial->profesional?->baja_id ?? 0) !== 1) { return response()->json([ 'success' => false, 'message' => 'Usted está dado de baja. Comuníquese con el administrador', ], 403); } $token = Str::random(64); $credencial->token = $token; $credencial->fecha_hora = now(); $credencial->save(); $rol = strtoupper((string) $credencial->rol); $this->registrarAccionSeguridad( $rol === 'ADMIN' ? (int) ($credencial->administrador?->persona_id ?? 0) : (int) ($credencial->profesional?->persona_id ?? 0), $rol === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL', (string) $request->ip(), 'Inició sesión', ($rol === 'ADMIN' ? 'La administradora ID ' . (int) ($credencial->administrador?->id ?? 0) : 'El profesional ID ' . (int) ($credencial->profesional?->id ?? 0)) . ' inició sesión.' ); $this->limpiarThrottle($request, 'login-personal-api'); return response()->json([ 'success' => true, 'data' => [ 'tipo' => 'personal', 'rol' => $credencial->rol, 'id_credencial' => $credencial->id, 'token' => $token, ], 'message' => 'Login de personal exitoso', ], 200); } public function login(Request $request): JsonResponse { $request->validate([ 'identificador' => ['required', 'string'], 'contra' => ['required', 'string'], 'tipo' => ['nullable', 'in:cliente,profesional'], ]); $identificador = trim((string) $request->input('identificador')); $contra = (string) $request->input('contra'); $tipo = $request->input('tipo'); $credencial = null; $tipoDetectado = null; if ($tipo === 'cliente') { $credencial = CredencialCliente::with('cliente')->where('correo', $identificador)->first(); $tipoDetectado = 'cliente'; } elseif ($tipo === 'profesional') { $credencial = CredencialProfesional::where('usuario', $identificador)->first(); $tipoDetectado = 'personal'; } else { $credencial = CredencialCliente::with('cliente')->where('correo', $identificador)->first(); $tipoDetectado = $credencial ? 'cliente' : null; if (!$credencial) { $credencial = CredencialProfesional::where('usuario', $identificador)->first(); $tipoDetectado = $credencial ? 'personal' : null; } } if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) { return response()->json([ 'success' => false, 'message' => 'Credenciales invalidas', ], 401); } if ($credencial instanceof CredencialCliente && (int) ($credencial->cliente?->baja_id ?? 0) !== 1) { return response()->json([ 'success' => false, 'message' => 'No es posible iniciar sesion. Comuníquese con un profesional de Abogadas del Litoral', ], 403); } if ($credencial instanceof CredencialProfesional && strtoupper((string) $credencial->rol) !== 'ADMIN' && (int) ($credencial->profesional?->baja_id ?? 0) !== 1) { return response()->json([ 'success' => false, 'message' => 'Usted está dado de baja. Comuníquese con el administrador', ], 403); } $token = Str::random(64); $credencial->token = $token; $credencial->fecha_hora = now(); $credencial->save(); if ($credencial instanceof CredencialCliente) { $this->registrarAccionSeguridad( (int) ($credencial->cliente?->persona_id ?? 0), 'CLIENTE', (string) $request->ip(), 'Inició sesión', 'El cliente ID ' . (int) ($credencial->cliente?->id ?? 0) . ' inició sesión.' ); } elseif ($credencial instanceof CredencialProfesional) { $rolDescripcion = strtoupper((string) $credencial->rol) === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL'; $this->registrarAccionSeguridad( $rolDescripcion === 'ADMIN' ? (int) ($credencial->administrador?->persona_id ?? 0) : (int) ($credencial->profesional?->persona_id ?? 0), $rolDescripcion, (string) $request->ip(), 'Inició sesión', ($rolDescripcion === 'ADMIN' ? 'La administradora ID ' . (int) ($credencial->administrador?->id ?? 0) : 'El profesional ID ' . (int) ($credencial->profesional?->id ?? 0)) . ' inició sesión.' ); } $this->limpiarThrottle($request, 'login-api-general'); return response()->json([ 'success' => true, 'data' => [ 'tipo' => $tipoDetectado, 'rol' => $credencial instanceof CredencialProfesional ? $credencial->rol : null, 'id_credencial' => $credencial->id, 'token' => $token, ], 'message' => 'Login exitoso', ], 200); } private function honeypotValido(Request $request): bool { return trim((string) $request->input('website', '')) === ''; } public function logout(Request $request): JsonResponse { $token = (string) $request->input('token', ''); if ($token === '') { return response()->json([ 'success' => false, 'message' => 'Token requerido', ], 422); } $credencialCliente = CredencialCliente::with('cliente')->where('token', $token)->first(); if ($credencialCliente) { $this->registrarAccionSeguridad( (int) ($credencialCliente->cliente?->persona_id ?? 0), 'CLIENTE', (string) $request->ip(), 'Cerró sesión', 'El cliente ID ' . (int) ($credencialCliente->cliente?->id ?? 0) . ' cerró sesión.' ); $credencialCliente->token = null; $credencialCliente->fecha_hora = now(); $credencialCliente->save(); return response()->json([ 'success' => true, 'message' => 'Logout exitoso', ], 200); } $credencialProfesional = CredencialProfesional::with(['administrador', 'profesional'])->where('token', $token)->first(); if ($credencialProfesional) { $rol = strtoupper((string) $credencialProfesional->rol); $this->registrarAccionSeguridad( $rol === 'ADMIN' ? (int) ($credencialProfesional->administrador?->persona_id ?? 0) : (int) ($credencialProfesional->profesional?->persona_id ?? 0), $rol === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL', (string) $request->ip(), 'Cerró sesión', ($rol === 'ADMIN' ? 'La administradora ID ' . (int) ($credencialProfesional->administrador?->id ?? 0) : 'El profesional ID ' . (int) ($credencialProfesional->profesional?->id ?? 0)) . ' cerró sesión.' ); $credencialProfesional->token = null; $credencialProfesional->fecha_hora = now(); $credencialProfesional->save(); return response()->json([ 'success' => true, 'message' => 'Logout exitoso', ], 200); } return response()->json([ 'success' => false, 'message' => 'Token invalido', ], 401); } private function credencialValida(string $contraIngresada, string $contraGuardada): bool { if ($contraIngresada === $contraGuardada) { return true; } return Hash::check($contraIngresada, $contraGuardada); } private function limpiarThrottle(Request $request, string $prefijo): void { RateLimiter::clear(md5($prefijo . $request->ip())); } private function registrarAccionSeguridad(?int $personaId, string $rol, string $ipOrigen, string $accionDescripcion, string $descripcion): void { if (!$personaId) { return; } $accion = AccionLog::query()->firstWhere('descripcion', $accionDescripcion); if (!$accion) { return; } LogSeguridad::create([ 'descripcion' => $descripcion, 'fechahora' => now(), 'IPorigen' => $ipOrigen, 'rol' => $rol, 'persona_id' => (int) $personaId, 'accion_id' => (int) $accion->id, ]); } }