# 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 (

Check Account Lock Status

setEmail(e.target.value)} placeholder="user@example.com" maxLength={255} />
{error &&

{error}

}
{status && (

Account Status for {status.email}

{status.isLocked ? (

🔒 Account is LOCKED

Failed Attempts: {status.failedAttempts}

Automatic Unlock In: {status.remainingTime}

({status.remainingSeconds} seconds remaining)

) : (

✓ Account is NOT locked

Failed Attempts: {status.failedAttempts}

)}
)}
); } 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 (

Unlock User Account

⚠️ Admin action: This operation will be logged for security audit.

setEmail(e.target.value)} placeholder="user@example.com" maxLength={255} />
{error &&

{error}

} {result && (

{result.message}

Email: {result.email}

)}
); } 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 (

Account Management

setEmail(e.target.value)} placeholder="Enter email address" maxLength={255} />
{error &&

{error}

} {status && (

{status.email}

Status:{" "} {status.isLocked ? "🔒 LOCKED" : "✓ Not Locked"}

Failed Attempts: {status.failedAttempts}

{status.isLocked && ( <>

Automatic Unlock In: {status.remainingTime}

({status.remainingSeconds} seconds)

)}
)}
); } 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
Checking locked accounts...
; if (lockedUsers.length === 0) { return
No locked accounts found.
; } return (

Locked User Accounts ({lockedUsers.length})

{lockedUsers.map(user => ( ))}
Email Failed Attempts Unlocks In Action
{user.email} {user.failedAttempts} {user.remainingTime}
); } 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