Yaltopia-Ticket-Admin/dev-docs/LOGIN_API_DOCUMENTATION.md

358 lines
8.0 KiB
Markdown

# Login API Documentation
## Endpoint
```
POST /api/v1/auth/login
```
## Description
Login user with email or phone number. This endpoint authenticates users using either email address or phone number along with password.
## Current Implementation
### Frontend Code
**API Client** (`src/lib/api-client.ts`):
```typescript
export const adminApiHelpers = {
// Auth - uses publicApi (no token required)
login: (data: { email: string; password: string }) =>
publicApi.post('/auth/login', data),
// ...
}
```
**Login Page** (`src/pages/login/index.tsx`):
```typescript
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
try {
const response = await adminApiHelpers.login({ email, password })
const { access_token, user } = response.data
// Check if user is admin
if (user.role !== 'ADMIN') {
toast.error("Access denied. Admin privileges required.")
return
}
// Store credentials
if (access_token) {
localStorage.setItem('access_token', access_token)
}
localStorage.setItem('user', JSON.stringify(user))
toast.success("Login successful!")
navigate(from, { replace: true })
} catch (error: any) {
const message = error.response?.data?.message || "Invalid email or password"
toast.error(message)
} finally {
setIsLoading(false)
}
}
```
## Request
### Headers
```
Content-Type: application/json
```
### Body (JSON)
**Option 1: Email + Password**
```json
{
"email": "admin@example.com",
"password": "your-password"
}
```
**Option 2: Phone + Password** (if backend supports)
```json
{
"phone": "+1234567890",
"password": "your-password"
}
```
### Example Request
```bash
curl -X POST https://api.yourdomain.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "admin@example.com",
"password": "password123"
}'
```
## Response
### Success Response (200 OK)
**Option 1: With Access Token in Body** (localStorage fallback)
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user-id-123",
"email": "admin@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "ADMIN",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z"
}
}
```
**Option 2: With httpOnly Cookies** (recommended)
```http
HTTP/1.1 200 OK
Set-Cookie: access_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
Set-Cookie: refresh_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800
Content-Type: application/json
```
### Error Responses
**401 Unauthorized** - Invalid credentials
```json
{
"message": "Invalid email or password",
"statusCode": 401
}
```
**403 Forbidden** - Account inactive or not admin
```json
{
"message": "Account is inactive",
"statusCode": 403
}
```
**400 Bad Request** - Validation error
```json
{
"message": "Validation failed",
"errors": [
{
"field": "email",
"message": "Invalid email format"
}
],
"statusCode": 400
}
```
**429 Too Many Requests** - Rate limit exceeded
```json
{
"message": "Too many login attempts. Please try again later.",
"statusCode": 429,
"retryAfter": 900
}
```
**500 Internal Server Error** - Server error
```json
{
"message": "Internal server error",
"statusCode": 500
}
```
## Frontend Behavior
### 1. Form Validation
- Email: Required, valid email format
- Password: Required, minimum 8 characters
- Show/hide password toggle
### 2. Loading State
- Disable form during submission
- Show "Logging in..." button text
- Prevent multiple submissions
### 3. Success Flow
1. Validate response contains user data
2. Check if user.role === 'ADMIN'
3. Store access_token (if provided)
4. Store user data in localStorage
5. Show success toast
6. Redirect to dashboard or original destination
### 4. Error Handling
- Display user-friendly error messages
- Show toast notification
- Keep form enabled for retry
- Don't expose sensitive error details
### 5. Security Features
- HTTPS only in production
- httpOnly cookies support
- CSRF protection (SameSite cookies)
- Automatic token refresh
- Role-based access control
## Backend Requirements
### Must Implement
1. **Password Hashing**: bcrypt with salt rounds >= 12
2. **Rate Limiting**: 5 attempts per 15 minutes per IP
3. **Account Lockout**: Lock after 5 failed attempts
4. **Role Verification**: Ensure user.role === 'ADMIN'
5. **Active Status Check**: Verify user.isActive === true
6. **Token Generation**: JWT with proper expiration
7. **Audit Logging**: Log all login attempts
### Recommended
1. **httpOnly Cookies**: Store tokens in cookies, not response body
2. **Refresh Tokens**: Long-lived tokens for session renewal
3. **2FA Support**: Two-factor authentication
4. **IP Whitelisting**: Restrict admin access by IP
5. **Session Management**: Track active sessions
6. **Email Notifications**: Alert on new login
## Testing
### Manual Testing
```bash
# Test with valid credentials
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"password123"}'
# Test with invalid credentials
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"wrong"}'
# Test with non-admin user
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'
```
### Automated Testing
See `src/pages/login/__tests__/index.test.tsx` for component tests.
## Environment Variables
### Development (`.env`)
```env
VITE_BACKEND_API_URL=http://localhost:3000/api/v1
```
### Production (`.env.production`)
```env
VITE_BACKEND_API_URL=https://api.yourdomain.com/api/v1
```
## API Client Configuration
The login endpoint uses the `publicApi` instance which:
- Does NOT require authentication
- Does NOT send Authorization header
- DOES send cookies (`withCredentials: true`)
- DOES handle CORS properly
## Flow Diagram
```
User enters credentials
Form validation
POST /api/v1/auth/login
Backend validates credentials
Backend checks role === 'ADMIN'
Backend generates tokens
Backend returns user + tokens
Frontend checks role === 'ADMIN'
Frontend stores tokens
Frontend redirects to dashboard
```
## Security Checklist
Backend:
- [ ] Passwords hashed with bcrypt
- [ ] Rate limiting enabled
- [ ] Account lockout implemented
- [ ] HTTPS enforced
- [ ] CORS configured properly
- [ ] httpOnly cookies used
- [ ] Audit logging enabled
- [ ] Input validation
- [ ] SQL injection prevention
Frontend:
- [x] HTTPS only in production
- [x] Cookie support enabled
- [x] Role verification
- [x] Error handling
- [x] Loading states
- [x] Form validation
- [x] Token storage
- [x] Automatic token refresh
## Troubleshooting
### Issue: "Network Error"
**Solution:** Check API URL in environment variables
### Issue: "CORS Error"
**Solution:** Backend must allow credentials and specific origin
### Issue: "Invalid credentials" for valid user
**Solution:** Check backend password hashing and comparison
### Issue: "Access denied" for admin user
**Solution:** Verify user.role === 'ADMIN' in database
### Issue: Token not persisting
**Solution:** Check if backend is setting httpOnly cookies or returning access_token
## Related Documentation
- [Authentication Setup](./AUTHENTICATION.md)
- [API Standards](./API_STANDARDS.md)
- [Security Checklist](./SECURITY_CHECKLIST.md)
- [Backend Requirements](./SECURITY_CHECKLIST.md#backend-security)
## Support
For issues with login:
1. Check browser console for errors
2. Check network tab for API response
3. Verify environment variables
4. Check backend logs
5. Test with curl/Postman