Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
827
web/maplepress-frontend/docs/API/ADMIN_API.md
Normal file
827
web/maplepress-frontend/docs/API/ADMIN_API.md
Normal file
|
|
@ -0,0 +1,827 @@
|
|||
# Admin API Implementation (Account Management)
|
||||
|
||||
This document describes the implementation of the Admin API endpoints for the MaplePress frontend, integrated with the MaplePress backend API.
|
||||
|
||||
## Overview
|
||||
|
||||
The Admin API endpoints provide administrative operations for managing user accounts, specifically handling account lockouts that occur due to failed login attempts. These endpoints implement CWE-307 protection (Improper Restriction of Excessive Authentication Attempts) by allowing administrators to check lock status and manually unlock accounts.
|
||||
|
||||
**⚠️ SECURITY**: These endpoints require admin authentication and should only be accessible to users with admin or root roles.
|
||||
|
||||
## Backend API Endpoints
|
||||
|
||||
### Check Account Lock Status
|
||||
**Endpoint**: `GET /api/v1/admin/account-status`
|
||||
**Authentication**: Required (JWT token with admin role)
|
||||
**Query Parameters**: `email` (required)
|
||||
|
||||
### Unlock Locked Account
|
||||
**Endpoint**: `POST /api/v1/admin/unlock-account`
|
||||
**Authentication**: Required (JWT token with admin role)
|
||||
|
||||
**Source Files**:
|
||||
- `cloud/maplepress-backend/internal/interface/http/handler/admin/account_status_handler.go`
|
||||
- `cloud/maplepress-backend/internal/interface/http/handler/admin/unlock_account_handler.go`
|
||||
|
||||
## Request/Response Structures
|
||||
|
||||
### Check Account Status Request
|
||||
```
|
||||
GET /api/v1/admin/account-status?email=user@example.com
|
||||
```
|
||||
|
||||
**Headers Required:**
|
||||
- `Authorization: JWT {access_token}` (admin role required)
|
||||
|
||||
### Check Account Status Response
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"is_locked": true,
|
||||
"failed_attempts": 5,
|
||||
"remaining_time": "5 minutes 30 seconds",
|
||||
"remaining_seconds": 330
|
||||
}
|
||||
```
|
||||
|
||||
**When Account Not Locked:**
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"is_locked": false,
|
||||
"failed_attempts": 2
|
||||
}
|
||||
```
|
||||
|
||||
### Unlock Account Request
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**Headers Required:**
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: JWT {access_token}` (admin role required)
|
||||
|
||||
### Unlock Account Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Account unlocked successfully",
|
||||
"email": "user@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**When Account Not Locked:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Account is not locked",
|
||||
"email": "user@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend Implementation
|
||||
|
||||
### AdminService (`src/services/API/AdminService.js`)
|
||||
|
||||
Handles all admin operations for account management.
|
||||
|
||||
**Key Features:**
|
||||
- Check account lock status with detailed information
|
||||
- Unlock locked accounts (with security event logging)
|
||||
- Helper to check if account needs unlocking
|
||||
- Client-side email validation
|
||||
- Remaining time formatting
|
||||
- Admin role enforcement with clear error messages
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `getAccountStatus(email)`
|
||||
Check if a user account is locked due to failed login attempts.
|
||||
|
||||
```javascript
|
||||
import AdminService from './services/API/AdminService';
|
||||
|
||||
const status = await AdminService.getAccountStatus("user@example.com");
|
||||
console.log(status);
|
||||
// Output:
|
||||
// {
|
||||
// email: "user@example.com",
|
||||
// isLocked: true,
|
||||
// failedAttempts: 5,
|
||||
// remainingTime: "5 minutes 30 seconds",
|
||||
// remainingSeconds: 330
|
||||
// }
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `email` (string, required): User's email address to check
|
||||
|
||||
**Returns:**
|
||||
```javascript
|
||||
{
|
||||
email: string, // User's email
|
||||
isLocked: boolean, // Whether account is locked
|
||||
failedAttempts: number, // Number of failed login attempts
|
||||
remainingTime: string, // Human-readable time until unlock
|
||||
remainingSeconds: number // Seconds until automatic unlock
|
||||
}
|
||||
```
|
||||
|
||||
**Throws:**
|
||||
- "Email is required" - If email is missing
|
||||
- "Email cannot be empty" - If email is empty after trimming
|
||||
- "Invalid email format" - If email format is invalid
|
||||
- "Admin authentication required. Please log in with admin credentials." - Missing/invalid admin token (401)
|
||||
- "Access denied. Admin privileges required for this operation." - User is not admin (403)
|
||||
|
||||
#### `unlockAccount(email)`
|
||||
Unlock a user account that has been locked due to failed login attempts.
|
||||
|
||||
```javascript
|
||||
const result = await AdminService.unlockAccount("user@example.com");
|
||||
console.log(result);
|
||||
// Output:
|
||||
// {
|
||||
// success: true,
|
||||
// message: "Account unlocked successfully",
|
||||
// email: "user@example.com"
|
||||
// }
|
||||
```
|
||||
|
||||
**⚠️ SECURITY EVENT**: This operation logs a security event (`ACCOUNT_UNLOCKED`) with the admin user ID who performed the unlock operation. This creates an audit trail for security compliance.
|
||||
|
||||
**Parameters:**
|
||||
- `email` (string, required): User's email address to unlock
|
||||
|
||||
**Returns:**
|
||||
```javascript
|
||||
{
|
||||
success: boolean,
|
||||
message: string,
|
||||
email: string
|
||||
}
|
||||
```
|
||||
|
||||
**Throws:**
|
||||
- "Email is required" - If email is missing
|
||||
- "Email cannot be empty" - If email is empty after trimming
|
||||
- "Invalid email format" - If email format is invalid
|
||||
- "Account is not currently locked." - If account is not locked
|
||||
- "Admin authentication required. Please log in with admin credentials." - Missing/invalid admin token (401)
|
||||
- "Access denied. Admin privileges required for this operation." - User is not admin (403)
|
||||
|
||||
#### `needsUnlock(email)`
|
||||
Check if an account needs unlocking (is locked with remaining time).
|
||||
|
||||
```javascript
|
||||
const needs = await AdminService.needsUnlock("user@example.com");
|
||||
console.log(needs); // true or false
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `email` (string, required): User's email address to check
|
||||
|
||||
**Returns:** `boolean` - True if account is locked and needs admin unlock
|
||||
|
||||
**Use Case:** Check before showing "Unlock Account" button in admin UI.
|
||||
|
||||
#### `validateEmail(email)`
|
||||
Validate email format.
|
||||
|
||||
```javascript
|
||||
const result = AdminService.validateEmail("user@example.com");
|
||||
console.log(result); // { valid: true, error: null }
|
||||
|
||||
const invalid = AdminService.validateEmail("invalid-email");
|
||||
console.log(invalid); // { valid: false, error: "Invalid email format" }
|
||||
```
|
||||
|
||||
**Returns:** `{ valid: boolean, error: string|null }`
|
||||
|
||||
**Validation Rules:**
|
||||
- Required (non-empty)
|
||||
- Valid email format (`user@domain.com`)
|
||||
- Maximum 255 characters
|
||||
|
||||
#### `formatRemainingTime(seconds)`
|
||||
Format remaining time for display.
|
||||
|
||||
```javascript
|
||||
const formatted = AdminService.formatRemainingTime(330);
|
||||
console.log(formatted); // "5 minutes 30 seconds"
|
||||
|
||||
const oneHour = AdminService.formatRemainingTime(3661);
|
||||
console.log(oneHour); // "1 hour 1 minute 1 second"
|
||||
```
|
||||
|
||||
**Returns:** `string` (e.g., "5 minutes 30 seconds", "1 hour", "30 seconds")
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Check Account Status Flow
|
||||
```
|
||||
Admin provides email to check
|
||||
↓
|
||||
AdminService.getAccountStatus()
|
||||
↓
|
||||
Validate email format (client-side)
|
||||
↓
|
||||
ApiClient.get() with JWT token (admin role)
|
||||
↓
|
||||
Token automatically refreshed if needed
|
||||
↓
|
||||
GET /api/v1/admin/account-status?email=...
|
||||
↓
|
||||
Backend checks Redis for lock status
|
||||
↓
|
||||
Backend returns lock info and failed attempts
|
||||
↓
|
||||
AdminService transforms response
|
||||
↓
|
||||
Component displays lock status
|
||||
```
|
||||
|
||||
### Unlock Account Flow
|
||||
```
|
||||
Admin requests account unlock
|
||||
↓
|
||||
AdminService.unlockAccount()
|
||||
↓
|
||||
Validate email format (client-side)
|
||||
↓
|
||||
ApiClient.post() with JWT token (admin role)
|
||||
↓
|
||||
Token automatically refreshed if needed
|
||||
↓
|
||||
POST /api/v1/admin/unlock-account
|
||||
↓
|
||||
Backend checks if account is locked
|
||||
↓
|
||||
Backend clears lock status in Redis
|
||||
↓
|
||||
Backend logs security event (ACCOUNT_UNLOCKED)
|
||||
↓
|
||||
Backend returns success response
|
||||
↓
|
||||
AdminService transforms response
|
||||
↓
|
||||
Component displays unlock confirmation
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Error Types
|
||||
|
||||
| Error Condition | Response | Frontend Behavior |
|
||||
|----------------|----------|-------------------|
|
||||
| Missing admin authentication | 401 Unauthorized | "Admin authentication required." |
|
||||
| Insufficient privileges | 403 Forbidden | "Access denied. Admin privileges required." |
|
||||
| Invalid email | 400 Bad Request | Specific validation error |
|
||||
| Account not locked (unlock) | 200 OK | "Account is not locked" (success) |
|
||||
| Server error | 500 Internal Server Error | Generic error message |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Admin Panel - Check Account Status
|
||||
|
||||
```javascript
|
||||
import React, { useState } from 'react';
|
||||
import AdminService from '../../services/API/AdminService';
|
||||
|
||||
function AccountStatusChecker() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [status, setStatus] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleCheck = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setStatus(null);
|
||||
setLoading(true);
|
||||
|
||||
// Validate before sending
|
||||
const validation = AdminService.validateEmail(email);
|
||||
if (!validation.valid) {
|
||||
setError(validation.error);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const accountStatus = await AdminService.getAccountStatus(email);
|
||||
setStatus(accountStatus);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="account-status-checker">
|
||||
<h2>Check Account Lock Status</h2>
|
||||
|
||||
<form onSubmit={handleCheck}>
|
||||
<div>
|
||||
<label>Email Address:</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="user@example.com"
|
||||
maxLength={255}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="error">{error}</p>}
|
||||
|
||||
<button type="submit" disabled={loading}>
|
||||
{loading ? 'Checking...' : 'Check Status'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{status && (
|
||||
<div className="status-result">
|
||||
<h3>Account Status for {status.email}</h3>
|
||||
|
||||
{status.isLocked ? (
|
||||
<div className="locked-status">
|
||||
<p className="warning">🔒 Account is LOCKED</p>
|
||||
<p>Failed Attempts: {status.failedAttempts}</p>
|
||||
<p>Automatic Unlock In: {status.remainingTime}</p>
|
||||
<p>({status.remainingSeconds} seconds remaining)</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="unlocked-status">
|
||||
<p className="success">✓ Account is NOT locked</p>
|
||||
<p>Failed Attempts: {status.failedAttempts}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountStatusChecker;
|
||||
```
|
||||
|
||||
### Admin Panel - Unlock Account
|
||||
|
||||
```javascript
|
||||
import React, { useState } from 'react';
|
||||
import AdminService from '../../services/API/AdminService';
|
||||
|
||||
function AccountUnlocker() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [result, setResult] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleUnlock = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setResult(null);
|
||||
setLoading(true);
|
||||
|
||||
// Validate before sending
|
||||
const validation = AdminService.validateEmail(email);
|
||||
if (!validation.valid) {
|
||||
setError(validation.error);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm action
|
||||
const confirmed = confirm(
|
||||
`Are you sure you want to unlock the account for "${email}"?\n\n` +
|
||||
`This will:\n` +
|
||||
`- Clear all failed login attempts\n` +
|
||||
`- Remove the account lock immediately\n` +
|
||||
`- Log a security event with your admin ID\n\n` +
|
||||
`Continue?`
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const unlockResult = await AdminService.unlockAccount(email);
|
||||
setResult(unlockResult);
|
||||
|
||||
// Clear form on success
|
||||
if (unlockResult.success) {
|
||||
setEmail('');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="account-unlocker">
|
||||
<h2>Unlock User Account</h2>
|
||||
<p className="warning">
|
||||
⚠️ Admin action: This operation will be logged for security audit.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleUnlock}>
|
||||
<div>
|
||||
<label>Email Address:</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="user@example.com"
|
||||
maxLength={255}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="error">{error}</p>}
|
||||
|
||||
{result && (
|
||||
<div className={result.success ? "success" : "error"}>
|
||||
<p>{result.message}</p>
|
||||
<p>Email: {result.email}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button type="submit" disabled={loading}>
|
||||
{loading ? 'Unlocking...' : 'Unlock Account'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountUnlocker;
|
||||
```
|
||||
|
||||
### Combined Admin Panel - Status + Unlock
|
||||
|
||||
```javascript
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import AdminService from '../../services/API/AdminService';
|
||||
|
||||
function AccountManager() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [status, setStatus] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [unlocking, setUnlocking] = useState(false);
|
||||
|
||||
const checkStatus = async () => {
|
||||
if (!email) return;
|
||||
|
||||
setError('');
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const accountStatus = await AdminService.getAccountStatus(email);
|
||||
setStatus(accountStatus);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setStatus(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnlock = async () => {
|
||||
if (!confirm(`Unlock account for "${email}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setError('');
|
||||
setUnlocking(true);
|
||||
|
||||
try {
|
||||
await AdminService.unlockAccount(email);
|
||||
// Refresh status after unlock
|
||||
await checkStatus();
|
||||
alert(`Account unlocked successfully for ${email}`);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setUnlocking(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="account-manager">
|
||||
<h2>Account Management</h2>
|
||||
|
||||
<div className="search-form">
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Enter email address"
|
||||
maxLength={255}
|
||||
/>
|
||||
<button onClick={checkStatus} disabled={loading || !email}>
|
||||
{loading ? 'Checking...' : 'Check Status'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && <p className="error">{error}</p>}
|
||||
|
||||
{status && (
|
||||
<div className="account-info">
|
||||
<h3>{status.email}</h3>
|
||||
|
||||
<div className="status-details">
|
||||
<p>
|
||||
Status:{" "}
|
||||
<strong className={status.isLocked ? "text-red" : "text-green"}>
|
||||
{status.isLocked ? "🔒 LOCKED" : "✓ Not Locked"}
|
||||
</strong>
|
||||
</p>
|
||||
<p>Failed Attempts: {status.failedAttempts}</p>
|
||||
|
||||
{status.isLocked && (
|
||||
<>
|
||||
<p>Automatic Unlock In: {status.remainingTime}</p>
|
||||
<p className="text-muted">
|
||||
({status.remainingSeconds} seconds)
|
||||
</p>
|
||||
|
||||
<button
|
||||
onClick={handleUnlock}
|
||||
disabled={unlocking}
|
||||
className="btn-danger"
|
||||
>
|
||||
{unlocking ? 'Unlocking...' : 'Unlock Account Now'}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountManager;
|
||||
```
|
||||
|
||||
### Locked Users Dashboard
|
||||
|
||||
```javascript
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import AdminService from '../../services/API/AdminService';
|
||||
|
||||
function LockedUsersDashboard({ suspectedEmails }) {
|
||||
const [lockedUsers, setLockedUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const checkAllUsers = async () => {
|
||||
setLoading(true);
|
||||
const locked = [];
|
||||
|
||||
for (const email of suspectedEmails) {
|
||||
try {
|
||||
const status = await AdminService.getAccountStatus(email);
|
||||
if (status.isLocked) {
|
||||
locked.push(status);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to check ${email}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
setLockedUsers(locked);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
checkAllUsers();
|
||||
}, [suspectedEmails]);
|
||||
|
||||
const unlockUser = async (email) => {
|
||||
try {
|
||||
await AdminService.unlockAccount(email);
|
||||
// Remove from locked users list
|
||||
setLockedUsers(prev => prev.filter(user => user.email !== email));
|
||||
} catch (err) {
|
||||
alert(`Failed to unlock ${email}: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <div>Checking locked accounts...</div>;
|
||||
|
||||
if (lockedUsers.length === 0) {
|
||||
return <div>No locked accounts found.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="locked-users-dashboard">
|
||||
<h2>Locked User Accounts ({lockedUsers.length})</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Failed Attempts</th>
|
||||
<th>Unlocks In</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{lockedUsers.map(user => (
|
||||
<tr key={user.email}>
|
||||
<td>{user.email}</td>
|
||||
<td>{user.failedAttempts}</td>
|
||||
<td>{user.remainingTime}</td>
|
||||
<td>
|
||||
<button onClick={() => unlockUser(user.email)}>
|
||||
Unlock Now
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LockedUsersDashboard;
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Check Account Status
|
||||
|
||||
```javascript
|
||||
// In browser console after admin login
|
||||
|
||||
import AdminService from './services/API/AdminService';
|
||||
|
||||
// Validate email
|
||||
const validation = AdminService.validateEmail("user@example.com");
|
||||
console.log("Email valid:", validation.valid);
|
||||
|
||||
// Check account status
|
||||
const status = await AdminService.getAccountStatus("user@example.com");
|
||||
console.log("Account status:", status);
|
||||
console.log("Is locked:", status.isLocked);
|
||||
console.log("Failed attempts:", status.failedAttempts);
|
||||
|
||||
if (status.isLocked) {
|
||||
console.log("Remaining time:", status.remainingTime);
|
||||
console.log("Remaining seconds:", status.remainingSeconds);
|
||||
}
|
||||
```
|
||||
|
||||
### Test Unlock Account
|
||||
|
||||
```javascript
|
||||
// Check if needs unlock
|
||||
const needs = await AdminService.needsUnlock("user@example.com");
|
||||
console.log("Needs unlock:", needs);
|
||||
|
||||
// Unlock account
|
||||
if (needs) {
|
||||
const result = await AdminService.unlockAccount("user@example.com");
|
||||
console.log("Unlock result:", result);
|
||||
console.log("Success:", result.success);
|
||||
console.log("Message:", result.message);
|
||||
}
|
||||
```
|
||||
|
||||
### Test with curl
|
||||
|
||||
```bash
|
||||
# 1. Login as admin and get access token
|
||||
ACCESS_TOKEN=$(curl -X POST http://localhost:8000/api/v1/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "admin@example.com",
|
||||
"password": "AdminPass123!"
|
||||
}' | jq -r '.access_token')
|
||||
|
||||
# 2. Check account status
|
||||
curl -X GET "http://localhost:8000/api/v1/admin/account-status?email=user@example.com" \
|
||||
-H "Authorization: JWT $ACCESS_TOKEN" | jq
|
||||
|
||||
# Example response:
|
||||
# {
|
||||
# "email": "user@example.com",
|
||||
# "is_locked": true,
|
||||
# "failed_attempts": 5,
|
||||
# "remaining_time": "5 minutes 30 seconds",
|
||||
# "remaining_seconds": 330
|
||||
# }
|
||||
|
||||
# 3. Unlock account
|
||||
curl -X POST http://localhost:8000/api/v1/admin/unlock-account \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT $ACCESS_TOKEN" \
|
||||
-d '{
|
||||
"email": "user@example.com"
|
||||
}' | jq
|
||||
|
||||
# Example response:
|
||||
# {
|
||||
# "success": true,
|
||||
# "message": "Account unlocked successfully",
|
||||
# "email": "user@example.com"
|
||||
# }
|
||||
|
||||
# 4. Verify account is unlocked
|
||||
curl -X GET "http://localhost:8000/api/v1/admin/account-status?email=user@example.com" \
|
||||
-H "Authorization: JWT $ACCESS_TOKEN" | jq
|
||||
|
||||
# Should show:
|
||||
# {
|
||||
# "email": "user@example.com",
|
||||
# "is_locked": false,
|
||||
# "failed_attempts": 0
|
||||
# }
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
### Security and Authorization
|
||||
|
||||
1. **Admin Role Required**: Both endpoints require admin authentication
|
||||
2. **Security Event Logging**: Unlock operations log security events for audit trail
|
||||
3. **Admin User ID**: The admin who performs unlock is logged (from JWT)
|
||||
4. **Rate Limiting**: Generic rate limiting applied to prevent abuse
|
||||
|
||||
### Account Lockout Mechanism
|
||||
|
||||
- Accounts are locked after **excessive failed login attempts** (configurable)
|
||||
- Lock duration is typically **15-30 minutes** (configurable)
|
||||
- Failed attempts counter resets after successful login
|
||||
- Automatic unlock occurs when lock time expires
|
||||
- Admin can unlock immediately without waiting
|
||||
|
||||
### Integration with CWE-307 Protection
|
||||
|
||||
These endpoints are part of the security system that protects against:
|
||||
- **CWE-307**: Improper Restriction of Excessive Authentication Attempts
|
||||
- **CWE-770**: Allocation of Resources Without Limits or Throttling
|
||||
|
||||
The login rate limiter tracks:
|
||||
- Failed login attempts per email
|
||||
- Account lock status and expiry
|
||||
- Grace period for automatic unlock
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Verify Admin Role**: Always check user has admin role before showing UI
|
||||
2. **Confirm Before Unlock**: Always require confirmation before unlocking
|
||||
3. **Audit Trail**: Log all admin actions for security compliance
|
||||
4. **User Notification**: Consider notifying users when their account is unlocked by admin
|
||||
5. **Regular Review**: Periodically review locked accounts dashboard
|
||||
|
||||
## Related Files
|
||||
|
||||
### Created Files
|
||||
```
|
||||
src/services/API/AdminService.js
|
||||
docs/ADMIN_API.md
|
||||
```
|
||||
|
||||
### Backend Reference Files
|
||||
```
|
||||
cloud/maplepress-backend/internal/interface/http/handler/admin/account_status_handler.go
|
||||
cloud/maplepress-backend/internal/interface/http/handler/admin/unlock_account_handler.go
|
||||
cloud/maplepress-backend/internal/interface/http/server.go (routes)
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [LOGIN_API.md](./LOGIN_API.md) - User login with rate limiting
|
||||
- [ME_API.md](./ME_API.md) - Current user profile includes role checking
|
||||
- [USER_API.md](./USER_API.md) - User management operations
|
||||
- [FRONTEND_ARCHITECTURE.md](./FRONTEND_ARCHITECTURE.md) - Architecture overview
|
||||
- [README.md](./README.md) - Documentation index
|
||||
|
||||
## Summary
|
||||
|
||||
The Admin API implementation provides:
|
||||
|
||||
1. **Account Status Checking**: View lock status, failed attempts, and remaining time
|
||||
2. **Account Unlocking**: Manually unlock accounts with security event logging
|
||||
3. **Helper Functions**: Email validation and time formatting
|
||||
4. **Security Compliance**: Admin role enforcement and audit trail
|
||||
5. **Error Handling**: Clear error messages and graceful failures
|
||||
|
||||
Essential for managing user account security and providing admin support for locked-out users while maintaining security compliance (CWE-307 protection).
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: October 30, 2024
|
||||
**Frontend Version**: 0.0.0
|
||||
**Documentation Version**: 1.0.0
|
||||
Loading…
Add table
Add a link
Reference in a new issue