GIMA’s authentication system handles user login, logout, and session management. Currently implemented as a client-side only flow with simulated authentication.
Overview
The authentication system is a simplified implementation without backend integration. It demonstrates the UI/UX flow and routing patterns that will connect to a real authentication service.
Current Implementation
- Client-side only: No actual credential verification
- No session persistence: State lost on page reload
- Simulated delay: 500ms timeout to mimic API call
- Direct routing: Redirects to dashboard on any form submission
Architecture
User Input → Form Submission → Simulated Delay → Route to Dashboard
Login Flow
Implemented in src/app/auth/login/page.tsx.
Component State
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
Login Function
Navigates to dashboard after simulated authentication delay
Implementation (from login/page.tsx:18-27):
const router = useRouter();
const handleLogin = (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
// Simulate authentication delay
setTimeout(() => {
// Redirect to dashboard on successful login
router.push("/dashboard");
}, 500);
};
Important Notes:
- No credential validation
- No error handling for failed authentication
- Loading state set but not used for UI feedback
- 500ms artificial delay to simulate API call
Email/Username Field
Implementation (from login/page.tsx:138-146):
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Pedro_perez"
className="w-full pl-12 pr-4 py-3 bg-gray-100 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition"
required
/>
Validation: HTML5 required attribute only
Password Field
Toggle for password visibility
Implementation (from login/page.tsx:156-175):
<input
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Pedro123"
className="w-full pl-12 pr-12 py-3 bg-gray-100 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-3.5 text-gray-400 hover:text-gray-600"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
Features:
- Toggle visibility with eye icon
- No password strength validation
- No minimum length requirement
Usage Example:
<form onSubmit={handleLogin} className="space-y-6">
{/* Email field */}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
{/* Password field */}
<input
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
{/* Submit button */}
<button
type="submit"
disabled={isLoading}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-6 rounded-full"
>
Accede al sistema
<ArrowRight className="w-5 h-5" />
</button>
</form>
Logout Flow
Implemented in src/components/layout/Sidebar.tsx.
Logout Function
Navigates user back to login page
Implementation (from Sidebar.tsx:52-55):
const router = useRouter();
const handleLogout = () => {
// Aquí puedes agregar lógica de limpieza de tokens si es necesario
router.push("/auth/login");
};
Important Notes:
- No session cleanup (no tokens to clear)
- No confirmation dialog
- Instant redirect to login page
- Comment indicates future token cleanup location
Implementation (from Sidebar.tsx:131-150):
<button
onClick={handleLogout}
className={cn(
"flex items-center gap-3 w-full px-3 py-3 rounded-xl transition-all duration-200 text-gray-300 hover:text-white hover:bg-red-500/20 whitespace-nowrap",
!isOpen && "justify-center"
)}
title="Cerrar sesión"
>
<div className="min-w-6">
<LogOut size={20} />
</div>
<span
className={cn(
"transition-all duration-300",
isOpen ? "opacity-100" : "opacity-0 w-0 hidden"
)}
>
Cerrar sesión
</span>
</button>
Features:
- Responsive to sidebar state (collapsed/expanded)
- Red hover effect to indicate logout action
- Icon from lucide-react
Session Management
Current State
No session management is currently implemented:
- No persistence: Session lost on page reload
- No tokens: No JWT or session cookies
- No expiration: No automatic logout
- No protected routes: All routes accessible without authentication
Route Protection (Not Implemented)
Currently missing middleware to protect routes:
// Future implementation needed
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token');
if (!token && !request.nextUrl.pathname.startsWith('/auth')) {
return NextResponse.redirect(new URL('/auth/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
Backend Integration Guide
When connecting to a real authentication backend, implement the following:
1. Login with API
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Credenciales inválidas');
}
const { token, user } = await response.json();
// Store token
localStorage.setItem('auth-token', token);
// or use cookies for better security
document.cookie = `auth-token=${token}; path=/; secure; httpOnly`;
// Redirect to dashboard
router.push('/dashboard');
} catch (error) {
console.error('Login failed:', error);
setError('Email o contraseña incorrectos');
} finally {
setIsLoading(false);
}
};
2. Logout with Session Cleanup
const handleLogout = async () => {
try {
// Call logout endpoint
await fetch('/api/auth/logout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('auth-token')}`
}
});
// Clear local storage
localStorage.removeItem('auth-token');
// Clear cookies
document.cookie = 'auth-token=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT';
// Redirect to login
router.push('/auth/login');
} catch (error) {
console.error('Logout failed:', error);
// Force redirect anyway
router.push('/auth/login');
}
};
3. Session Persistence
// app/layout.tsx or AuthProvider
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
function useAuthCheck() {
const router = useRouter();
useEffect(() => {
const token = localStorage.getItem('auth-token');
if (!token && !window.location.pathname.startsWith('/auth')) {
router.push('/auth/login');
}
}, [router]);
}
4. Token Refresh
// utils/auth.ts
export async function refreshToken() {
try {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('auth-token')}`
}
});
const { token } = await response.json();
localStorage.setItem('auth-token', token);
return token;
} catch (error) {
// Refresh failed, redirect to login
window.location.href = '/auth/login';
}
}
Security Considerations
Current Vulnerabilities
- No CSRF protection: Forms lack CSRF tokens
- No rate limiting: Unlimited login attempts
- Credentials in client state: Email/password stored in component state
- No HTTPS enforcement: No secure connection requirement
- No password requirements: Any string accepted
Recommended Security Measures
- Use HTTP-only cookies for token storage (not localStorage)
- Implement CSRF tokens for form submissions
- Add rate limiting to prevent brute force attacks
- Enforce HTTPS in production
- Implement password requirements:
- Minimum 8 characters
- Mix of uppercase, lowercase, numbers, special characters
- Add multi-factor authentication (MFA)
- Implement account lockout after failed attempts
- Log authentication events for security auditing
Password Validation Example
function validatePassword(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Mínimo 8 caracteres');
}
if (!/[A-Z]/.test(password)) {
errors.push('Debe incluir mayúsculas');
}
if (!/[a-z]/.test(password)) {
errors.push('Debe incluir minúsculas');
}
if (!/[0-9]/.test(password)) {
errors.push('Debe incluir números');
}
if (!/[^A-Za-z0-9]/.test(password)) {
errors.push('Debe incluir caracteres especiales');
}
return { valid: errors.length === 0, errors };
}
UI Features
Login Page Design
The login page features a split-screen design:
Left Panel (Desktop only):
- GIMA branding with logo
- System description (“Gestión Inteligente de Activos y Mantenimiento”)
- Feature highlights (Seguridad, Eficiencia, Control)
- Gradient background (#001F3F to #002d5c)
Right Panel:
- Welcome message
- Email/username input with User icon
- Password input with Lock icon and visibility toggle
- “Remember me” checkbox
- “Forgot password” link (non-functional)
- Submit button with loading state
Icons Used
From lucide-react (login/page.tsx:8):
import { Eye, EyeOff, User, Lock, ArrowRight, LogOut } from "lucide-react";
Error States
Current Error Handling
No error handling is currently implemented:
- No invalid credential messages
- No network error handling
- No field validation errors
- No rate limiting messages
Recommended Error States
const [error, setError] = useState<string | null>(null);
// Show error message
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
{error}
</div>
)}
// Clear error on input change
onChange={(e) => {
setEmail(e.target.value);
setError(null);
}}