487 lines
12 KiB
Markdown
487 lines
12 KiB
Markdown
# Login API Implementation
|
|
|
|
This document describes the complete implementation of the user login feature for the MaplePress frontend, integrated with the MaplePress backend API.
|
|
|
|
## Overview
|
|
|
|
The login feature allows existing users to authenticate with their email and password credentials. Upon successful login, users receive authentication tokens and are automatically logged in to their dashboard.
|
|
|
|
## Backend API Endpoint
|
|
|
|
**Endpoint**: `POST /api/v1/login`
|
|
**Authentication**: None required (public endpoint)
|
|
**Documentation**: `/cloud/maplepress-backend/docs/API.md` (lines 168-228)
|
|
|
|
### Request Structure
|
|
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "SecurePassword123!"
|
|
}
|
|
```
|
|
|
|
### Response Structure
|
|
|
|
```json
|
|
{
|
|
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"user_email": "user@example.com",
|
|
"user_name": "John Doe",
|
|
"user_role": "user",
|
|
"tenant_id": "650e8400-e29b-41d4-a716-446655440000",
|
|
"session_id": "750e8400-e29b-41d4-a716-446655440000",
|
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"access_expiry": "2024-10-24T12:15:00Z",
|
|
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"refresh_expiry": "2024-10-31T00:00:00Z",
|
|
"login_at": "2024-10-24T00:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Key Differences from Registration
|
|
|
|
The login endpoint differs from registration in several ways:
|
|
- **Simpler Request**: Only email and password required
|
|
- **No Tenant Details in Response**: Tenant name/slug not included
|
|
- **Login Timestamp**: Includes `login_at` instead of `created_at`
|
|
- **Existing Session**: Authenticates existing user, doesn't create new account
|
|
|
|
## Frontend Implementation
|
|
|
|
### 1. LoginService (`src/services/API/LoginService.js`)
|
|
|
|
Handles direct communication with the backend login API.
|
|
|
|
**Key Features:**
|
|
- Request validation (required fields)
|
|
- Request body formatting (snake_case for backend)
|
|
- Response transformation (camelCase for frontend)
|
|
- User-friendly error message mapping
|
|
- Rate limit error handling
|
|
- Account lockout detection
|
|
|
|
**Methods:**
|
|
- `login(credentials)` - Main login method
|
|
|
|
**Usage:**
|
|
```javascript
|
|
import LoginService from './services/API/LoginService';
|
|
|
|
const response = await LoginService.login({
|
|
email: "user@example.com",
|
|
password: "SecurePassword123!",
|
|
});
|
|
```
|
|
|
|
### 2. AuthManager Enhancement (`src/services/Manager/AuthManager.js`)
|
|
|
|
Updated to support login functionality while maintaining registration support.
|
|
|
|
**New/Updated Methods:**
|
|
- `login(email, password)` - Login and store auth data
|
|
- `storeAuthData(authResponse)` - Updated to handle optional tenant fields
|
|
|
|
**Key Features:**
|
|
- Handles both registration and login responses
|
|
- Gracefully handles missing tenant name/slug from login
|
|
- Maintains same token storage mechanism
|
|
- Consistent session management
|
|
|
|
**Login Flow:**
|
|
```javascript
|
|
const authManager = useAuth().authManager;
|
|
|
|
// Login
|
|
await authManager.login("user@example.com", "password123");
|
|
|
|
// Check authentication
|
|
if (authManager.isAuthenticated()) {
|
|
const user = authManager.getUser();
|
|
const tenant = authManager.getTenant();
|
|
}
|
|
```
|
|
|
|
### 3. Login Page (`src/pages/Auth/Login.jsx`)
|
|
|
|
Simple and clean login form ready for production use.
|
|
|
|
**Form Fields:**
|
|
- Email Address (required)
|
|
- Password (required)
|
|
|
|
**Features:**
|
|
- Email validation (HTML5 + backend)
|
|
- Loading state during submission
|
|
- Error message display
|
|
- Navigation to registration
|
|
- Navigation back to home
|
|
|
|
## Data Flow
|
|
|
|
```
|
|
User enters credentials
|
|
↓
|
|
Login.jsx validates data
|
|
↓
|
|
AuthManager.login(email, password)
|
|
↓
|
|
LoginService.login(credentials)
|
|
↓
|
|
HTTP POST to /api/v1/login
|
|
↓
|
|
Backend validates credentials
|
|
↓
|
|
Backend returns tokens and user data
|
|
↓
|
|
LoginService transforms response
|
|
↓
|
|
AuthManager stores tokens in localStorage
|
|
↓
|
|
User redirected to /dashboard
|
|
```
|
|
|
|
## Validation Rules
|
|
|
|
### Frontend Validation
|
|
|
|
1. **Email**: Required, valid email format (HTML5)
|
|
2. **Password**: Required (no client-side length check for login)
|
|
|
|
### Backend Validation
|
|
|
|
Backend performs:
|
|
- Email format validation
|
|
- Email normalization (lowercase, trim)
|
|
- Password verification against stored hash
|
|
- Rate limiting checks
|
|
- Account lockout checks
|
|
|
|
## Error Handling
|
|
|
|
### Error Messages
|
|
|
|
Errors are mapped to user-friendly messages:
|
|
|
|
| Backend Error | Frontend Message |
|
|
|--------------|------------------|
|
|
| "Invalid email or password" | "Invalid email or password. Please try again." |
|
|
| "X attempts remaining" | Original message with attempt counter |
|
|
| "locked" or "Too many" | "Account temporarily locked due to too many failed attempts. Please try again later." |
|
|
| "invalid email" | "Invalid email address." |
|
|
|
|
### Rate Limiting & Account Lockout
|
|
|
|
The backend implements sophisticated rate limiting:
|
|
|
|
**Per-IP Rate Limiting:**
|
|
- Limit: Multiple attempts per 15 minutes
|
|
- Response: `429 Too Many Requests`
|
|
- Header: `Retry-After: 900` (15 minutes)
|
|
|
|
**Per-Account Rate Limiting:**
|
|
- Limit: 5 failed attempts
|
|
- Lockout: 30 minutes after 5 failures
|
|
- Response: `429 Too Many Requests`
|
|
- Header: `Retry-After: 1800` (30 minutes)
|
|
- Warning: Shows remaining attempts when ≤ 3
|
|
|
|
**Behavior:**
|
|
1. First 2 failures: Generic error message
|
|
2. 3rd-5th failure: Shows remaining attempts
|
|
3. After 5th failure: Account locked for 30 minutes
|
|
4. Successful login: Resets all counters
|
|
|
|
### Security Event Logging
|
|
|
|
Backend logs security events for:
|
|
- Failed login attempts (with email hash)
|
|
- Successful logins (with email hash)
|
|
- IP rate limit exceeded
|
|
- Account lockouts
|
|
|
|
## Security Features
|
|
|
|
1. **Token Storage**: Tokens stored in localStorage
|
|
2. **Token Expiry**: Automatic expiry checking
|
|
3. **Rate Limiting**: Backend enforces rate limits
|
|
4. **Account Lockout**: Protects against brute force
|
|
5. **Email Normalization**: Prevents bypass via casing
|
|
6. **Secure Logging**: PII never logged, only hashes
|
|
7. **Password Hash Verification**: Uses bcrypt on backend
|
|
|
|
## Token Lifetime
|
|
|
|
- **Access Token**: 15 minutes
|
|
- **Refresh Token**: 7 days
|
|
- **Session**: 14 days (max inactivity)
|
|
|
|
## Testing the Login Flow
|
|
|
|
### 1. Prerequisites
|
|
|
|
Ensure you have a registered user. If not, register first:
|
|
|
|
```bash
|
|
# Navigate to registration
|
|
http://localhost:5173/register
|
|
|
|
# Or use curl to register via backend
|
|
curl -X POST http://localhost:8000/api/v1/register \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "test@example.com",
|
|
"password": "SecurePass123!",
|
|
"name": "Test User",
|
|
"tenant_name": "Test Corp",
|
|
"tenant_slug": "test-corp",
|
|
"agree_terms_of_service": true
|
|
}'
|
|
```
|
|
|
|
### 2. Start Services
|
|
|
|
```bash
|
|
# Backend
|
|
cd cloud/maplepress-backend
|
|
task dev
|
|
|
|
# Frontend
|
|
cd web/maplepress-frontend
|
|
npm run dev
|
|
```
|
|
|
|
### 3. Navigate to Login
|
|
|
|
Open browser to: `http://localhost:5173/login`
|
|
|
|
### 4. Fill Form
|
|
|
|
- **Email**: test@example.com
|
|
- **Password**: SecurePass123!
|
|
|
|
### 5. Submit
|
|
|
|
Click "Sign In" button
|
|
|
|
### 6. Expected Result
|
|
|
|
- Loading state appears ("Signing in...")
|
|
- Request sent to backend
|
|
- Tokens stored in localStorage
|
|
- User redirected to `/dashboard`
|
|
- Dashboard shows user information
|
|
|
|
### 7. Verify in Browser Console
|
|
|
|
```javascript
|
|
// Check stored tokens
|
|
localStorage.getItem('maplepress_access_token')
|
|
localStorage.getItem('maplepress_user')
|
|
localStorage.getItem('maplepress_tenant')
|
|
|
|
// Check service instance
|
|
window.maplePressServices.authManager.isAuthenticated() // true
|
|
window.maplePressServices.authManager.getUser()
|
|
// Returns: { id: "...", email: "test@example.com", name: "Test User", role: "..." }
|
|
|
|
window.maplePressServices.authManager.getTenant()
|
|
// Returns: { id: "...", name: null, slug: null }
|
|
// Note: name/slug are null because login endpoint doesn't provide them
|
|
```
|
|
|
|
## Error Testing
|
|
|
|
### Test Invalid Credentials
|
|
|
|
```javascript
|
|
// In login form, enter:
|
|
Email: test@example.com
|
|
Password: WrongPassword123
|
|
|
|
// Expected: "Invalid email or password. Please try again."
|
|
```
|
|
|
|
### Test Rate Limiting
|
|
|
|
```javascript
|
|
// Attempt login 3 times with wrong password
|
|
// Expected on 3rd attempt: "Invalid email or password. 2 attempts remaining before account lockout."
|
|
|
|
// Attempt 2 more times
|
|
// Expected on 5th attempt: "Account temporarily locked due to too many failed attempts. Please try again later."
|
|
```
|
|
|
|
### Test Missing Fields
|
|
|
|
```javascript
|
|
// Leave email blank
|
|
// Expected: Browser validation error (HTML5 required)
|
|
|
|
// Leave password blank
|
|
// Expected: Browser validation error (HTML5 required)
|
|
```
|
|
|
|
## Differences from Registration
|
|
|
|
| Feature | Registration | Login |
|
|
|---------|-------------|-------|
|
|
| Request Fields | 10+ fields | 2 fields only |
|
|
| Response Fields | Includes tenant name/slug | No tenant name/slug |
|
|
| Timestamp | `created_at` | `login_at` |
|
|
| Creates User | Yes | No |
|
|
| Creates Tenant | Yes | No |
|
|
| Terms Agreement | Required | Not needed |
|
|
| Organization Info | Required | Not needed |
|
|
|
|
## Token Storage Compatibility
|
|
|
|
Both registration and login use the same storage mechanism:
|
|
|
|
```javascript
|
|
// Storage Keys (7 total)
|
|
maplepress_access_token // JWT access token
|
|
maplepress_refresh_token // JWT refresh token
|
|
maplepress_access_expiry // ISO date string
|
|
maplepress_refresh_expiry // ISO date string
|
|
maplepress_user // JSON: {id, email, name, role}
|
|
maplepress_tenant // JSON: {id, name, slug}
|
|
maplepress_session_id // Session UUID
|
|
```
|
|
|
|
**Note**: After login, `tenant.name` and `tenant.slug` will be `null`. This is expected behavior. If needed, fetch tenant details separately using the `/api/v1/tenants/{id}` endpoint.
|
|
|
|
## Session Persistence
|
|
|
|
Authentication state persists across:
|
|
- Page refreshes
|
|
- Browser restarts (if localStorage not cleared)
|
|
- Tab changes
|
|
|
|
Session is cleared on:
|
|
- User logout
|
|
- Token expiry detection
|
|
- Manual localStorage clear
|
|
|
|
## Integration with Dashboard
|
|
|
|
The dashboard automatically checks authentication:
|
|
|
|
```javascript
|
|
// Dashboard.jsx
|
|
useEffect(() => {
|
|
if (!authManager.isAuthenticated()) {
|
|
navigate("/login");
|
|
return;
|
|
}
|
|
|
|
const userData = authManager.getUser();
|
|
setUser(userData);
|
|
}, [authManager, navigate]);
|
|
```
|
|
|
|
## API Client Configuration
|
|
|
|
The `ApiClient` automatically handles:
|
|
- JSON content-type headers
|
|
- Request/response transformation
|
|
- Error parsing
|
|
- Authentication headers (for protected endpoints)
|
|
|
|
**Note**: Login endpoint doesn't require authentication headers, but subsequent API calls will use the stored access token.
|
|
|
|
## Future Enhancements
|
|
|
|
### Planned Features
|
|
|
|
- [ ] "Remember Me" checkbox (longer session)
|
|
- [ ] "Forgot Password" link
|
|
- [ ] Social authentication (Google, GitHub)
|
|
- [ ] Two-factor authentication (2FA/TOTP)
|
|
- [ ] Session management (view active sessions)
|
|
- [ ] Device fingerprinting
|
|
- [ ] Suspicious login detection
|
|
|
|
### Security Improvements
|
|
|
|
- [ ] CSRF token implementation
|
|
- [ ] HTTP-only cookie option
|
|
- [ ] Session fingerprinting
|
|
- [ ] Geolocation tracking
|
|
- [ ] Email notification on new login
|
|
- [ ] Passwordless login option
|
|
|
|
## Troubleshooting
|
|
|
|
### "Invalid email or password" but credentials are correct
|
|
|
|
**Possible causes:**
|
|
1. Email case sensitivity - backend normalizes to lowercase
|
|
2. Extra whitespace in password field
|
|
3. User not yet registered
|
|
4. Account locked due to previous failed attempts
|
|
|
|
**Solution:**
|
|
- Wait 30 minutes if account is locked
|
|
- Try registering if user doesn't exist
|
|
- Check browser console for detailed errors
|
|
|
|
### Tokens not being stored
|
|
|
|
**Possible causes:**
|
|
1. localStorage disabled in browser
|
|
2. Private/Incognito mode restrictions
|
|
3. Browser extension blocking storage
|
|
|
|
**Solution:**
|
|
- Enable localStorage in browser settings
|
|
- Use regular browser window
|
|
- Disable blocking extensions temporarily
|
|
|
|
### Redirected back to login after successful login
|
|
|
|
**Possible causes:**
|
|
1. Token expiry detection triggered
|
|
2. Token format invalid
|
|
3. localStorage cleared between operations
|
|
|
|
**Solution:**
|
|
- Check browser console for errors
|
|
- Verify localStorage contains tokens
|
|
- Check token expiry dates
|
|
|
|
## Related Files
|
|
|
|
### Created Files
|
|
```
|
|
src/services/API/LoginService.js
|
|
```
|
|
|
|
### Modified Files
|
|
```
|
|
src/services/Manager/AuthManager.js
|
|
src/pages/Auth/Login.jsx
|
|
```
|
|
|
|
### Backend Reference Files
|
|
```
|
|
cloud/maplepress-backend/docs/API.md
|
|
cloud/maplepress-backend/internal/interface/http/dto/gateway/login_dto.go
|
|
cloud/maplepress-backend/internal/interface/http/handler/gateway/login_handler.go
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- [REGISTRATION_API.md](./REGISTRATION_API.md) - Registration implementation
|
|
- [ARCHITECTURE.md](./ARCHITECTURE.md) - Frontend architecture overview
|
|
- [README.md](./README.md) - Getting started guide
|
|
- [Backend API Documentation](../../cloud/maplepress-backend/docs/API.md) - Complete API reference
|
|
|
|
## Support
|
|
|
|
For issues:
|
|
1. Check backend logs: `docker logs mapleopentech_backend`
|
|
2. Check browser console for errors
|
|
3. Verify backend is running on port 8000
|
|
4. Test backend endpoint directly with curl
|
|
5. Check rate limiting status (wait 30 minutes if locked)
|