# Me API Implementation (User Profile) This document describes the implementation of the Me API endpoint for the MaplePress frontend, integrated with the MaplePress backend API. ## Overview The Me API endpoint returns the authenticated user's profile information extracted directly from the JWT token. This is a lightweight endpoint that requires no database queries, making it ideal for displaying user information in the dashboard and verifying the current user's identity. ## Backend API Endpoint **Endpoint**: `GET /api/v1/me` **Authentication**: Required (JWT token) **Documentation**: `/cloud/maplepress-backend/docs/API.md` (lines 374-413) ### Request Structure No request body required. Authentication is provided via the Authorization header: **Headers Required:** - `Authorization: JWT {access_token}` ### Response Structure ```json { "user_id": "550e8400-e29b-41d4-a716-446655440000", "email": "john@example.com", "name": "John Doe", "role": "owner", "tenant_id": "650e8400-e29b-41d4-a716-446655440000" } ``` ### Key Features - **No Database Query**: All data extracted from JWT token claims - **Fast Response**: Minimal processing required - **Current User Only**: Returns data for the authenticated user - **Role Information**: Includes user's role for authorization checks - **Tenant Context**: Includes tenant ID for multi-tenant operations ## Frontend Implementation ### MeService (`src/services/API/MeService.js`) Handles direct communication with the backend Me API and provides utility methods for user profile operations. **Key Features:** - GET request with automatic JWT authentication - Response transformation (snake_case to camelCase) - User-friendly error message mapping - Helper methods for common use cases - Automatic token refresh integration **Methods:** #### `getMe()` Main method to fetch the current user's profile. ```javascript import MeService from './services/API/MeService'; const profile = await MeService.getMe(); console.log(profile); // Output: // { // userId: "550e8400-e29b-41d4-a716-446655440000", // email: "john@example.com", // name: "John Doe", // role: "owner", // tenantId: "650e8400-e29b-41d4-a716-446655440000" // } ``` **Returns:** ```javascript { userId: string, // User's unique identifier (UUID) email: string, // User's email address name: string, // User's full name role: string, // User's role (e.g., "owner", "admin", "user") tenantId: string // User's tenant/organization ID (UUID) } ``` **Throws:** - "Authentication required. Please log in to continue." - If JWT token is missing/invalid - "Invalid or expired authentication token. Please log in again." - If token has expired - Generic error message for other failures #### `hasRole(requiredRole)` Check if the current user has a specific role. ```javascript const isOwner = await MeService.hasRole("owner"); if (isOwner) { console.log("User is an owner"); } const isAdmin = await MeService.hasRole("admin"); ``` **Parameters:** - `requiredRole` (string): Role to check for (e.g., "owner", "admin", "user") **Returns:** `boolean` - True if user has the required role #### `getTenantId()` Get the current user's tenant ID. ```javascript const tenantId = await MeService.getTenantId(); console.log("User's tenant:", tenantId); ``` **Returns:** `string|null` - Tenant ID or null if not available #### `isCurrentUser(userId)` Verify that the current user matches a specific user ID. ```javascript const isSameUser = await MeService.isCurrentUser("550e8400-..."); if (isSameUser) { console.log("This is the current user"); } ``` **Parameters:** - `userId` (string): User ID to verify against **Returns:** `boolean` - True if the current user matches the provided ID ## Data Flow ``` User is authenticated (has JWT token) ↓ Component calls MeService.getMe() ↓ ApiClient.get("/api/v1/me") ↓ Token automatically refreshed if needed (ApiClient feature) ↓ GET /api/v1/me with Authorization header ↓ Backend JWT middleware extracts user from token ↓ Backend returns user profile from JWT claims ↓ MeService transforms response (snake_case → camelCase) ↓ Component receives user profile ``` ## Error Handling ### Error Types | Error Condition | Response | Frontend Behavior | |----------------|----------|-------------------| | Missing JWT token | 401 Unauthorized | "Authentication required. Please log in to continue." | | Invalid JWT token | 401 Unauthorized | "Invalid or expired authentication token." | | Expired JWT token | 401 Unauthorized | Token auto-refresh triggered, then retry | | Server error | 500 Internal Server Error | Generic error message | ### Error Message Mapping ```javascript // Backend error → Frontend error "unauthorized" → "Authentication required. Please log in to continue." "token" → "Invalid or expired authentication token. Please log in again." Other errors → Original error message or generic fallback ``` ## Security Features 1. **JWT Authentication Required**: Endpoint requires valid JWT token 2. **Token Validation**: Backend validates token signature and expiration 3. **No PII in Logs**: Backend uses hashed/redacted email in logs (CWE-532) 4. **Automatic Token Refresh**: ApiClient ensures token is valid before request 5. **Context Extraction**: User data extracted from secure JWT context ## Usage Examples ### Display User Profile in Dashboard ```javascript import React, { useEffect, useState } from 'react'; import MeService from '../../services/API/MeService'; function UserProfile() { const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { const fetchProfile = async () => { try { const data = await MeService.getMe(); setProfile(data); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchProfile(); }, []); if (loading) return
Loading profile...
; if (error) return
Error: {error}
; if (!profile) return null; return (

Welcome, {profile.name}!

Email: {profile.email}

Role: {profile.role}

User ID: {profile.userId}

); } export default UserProfile; ``` ### Role-Based Access Control ```javascript import React, { useEffect, useState } from 'react'; import MeService from '../../services/API/MeService'; function AdminPanel() { const [isAdmin, setIsAdmin] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { const checkAdmin = async () => { const hasAdminRole = await MeService.hasRole("owner"); setIsAdmin(hasAdminRole); setLoading(false); }; checkAdmin(); }, []); if (loading) return
Checking permissions...
; if (!isAdmin) { return
Access denied. Admin rights required.
; } return (

Admin Panel

{/* Admin content */}
); } export default AdminPanel; ``` ### Sync Profile with AuthManager ```javascript import { useAuth } from '../../services/Services'; import MeService from '../../services/API/MeService'; function Dashboard() { const { authManager } = useAuth(); useEffect(() => { const syncProfile = async () => { try { // Get fresh profile from backend const profile = await MeService.getMe(); // Get stored profile from AuthManager const storedUser = authManager.getUser(); // Check if data is in sync if (storedUser.email !== profile.email) { console.warn("Profile data mismatch detected"); // Could update AuthManager or prompt user } console.log("Profile synced successfully"); } catch (error) { console.error("Failed to sync profile:", error); } }; syncProfile(); }, [authManager]); return
Dashboard
; } ``` ### Check Tenant Context ```javascript import MeService from '../../services/API/MeService'; async function verifyTenantAccess(requiredTenantId) { const userTenantId = await MeService.getTenantId(); if (userTenantId !== requiredTenantId) { throw new Error("Access denied. Wrong tenant context."); } console.log("Tenant access verified"); return true; } // Usage try { await verifyTenantAccess("650e8400-e29b-41d4-a716-446655440000"); // Proceed with tenant-specific operations } catch (error) { console.error(error.message); // Redirect or show error } ``` ### Verify Current User ```javascript import MeService from '../../services/API/MeService'; function EditProfileButton({ profileOwnerId }) { const [canEdit, setCanEdit] = useState(false); useEffect(() => { const checkOwnership = async () => { const isOwner = await MeService.isCurrentUser(profileOwnerId); setCanEdit(isOwner); }; checkOwnership(); }, [profileOwnerId]); if (!canEdit) return null; return ; } ``` ## Testing the Me API ### 1. Prerequisites - Backend running at `http://localhost:8000` - Frontend running at `http://localhost:5173` - User logged in (valid JWT token) ### 2. Testing via Dashboard Add a profile component to the dashboard: ```javascript // In Dashboard.jsx import MeService from '../../services/API/MeService'; const [userProfile, setUserProfile] = useState(null); useEffect(() => { const loadProfile = async () => { try { const profile = await MeService.getMe(); setUserProfile(profile); console.log("Profile loaded:", profile); } catch (error) { console.error("Failed to load profile:", error); } }; loadProfile(); }, []); // In JSX {userProfile && (

{userProfile.name}

{userProfile.email}

Role: {userProfile.role}

)} ``` ### 3. Testing via Browser Console ```javascript // After logging in, open browser console // Test basic profile fetch const profile = await MeService.getMe(); console.log(profile); // Expected: User profile object // Test role check const isOwner = await MeService.hasRole("owner"); console.log("Is owner:", isOwner); // Test tenant ID const tenantId = await MeService.getTenantId(); console.log("Tenant ID:", tenantId); // Test user verification const isCurrent = await MeService.isCurrentUser(profile.userId); console.log("Is current user:", isCurrent); // Should be true ``` ### 4. Testing with curl ```bash # 1. Login and get access token ACCESS_TOKEN=$(curl -X POST http://localhost:8000/api/v1/login \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "password": "SecurePass123!" }' | jq -r '.access_token') # 2. Test me endpoint curl -X GET http://localhost:8000/api/v1/me \ -H "Authorization: JWT $ACCESS_TOKEN" | jq # Expected output: # { # "user_id": "550e8400-...", # "email": "test@example.com", # "name": "Test User", # "role": "owner", # "tenant_id": "650e8400-..." # } # 3. Test without authentication (should fail) curl -X GET http://localhost:8000/api/v1/me | jq # Expected: 401 Unauthorized # 4. Test with invalid token (should fail) curl -X GET http://localhost:8000/api/v1/me \ -H "Authorization: JWT invalid_token" | jq # Expected: 401 Unauthorized ``` ### 5. Testing Token Refresh Integration ```javascript // Test that token refresh works automatically // 1. Login // 2. Manually set token expiry to 30 seconds from now const newExpiry = new Date(Date.now() + 30000).toISOString(); localStorage.setItem('maplepress_access_expiry', newExpiry); // 3. Wait 31 seconds, then call getMe await new Promise(resolve => setTimeout(resolve, 31000)); const profile = await MeService.getMe(); // 4. Check console logs - should see automatic token refresh // 5. Response should be successful despite expired token console.log("Profile after token refresh:", profile); ``` ### 6. Testing Against Stored Data ```javascript // Compare Me API response with stored AuthManager data import { useAuth } from './services/Services'; import MeService from './services/API/MeService'; const { authManager } = useAuth(); // Get stored user const storedUser = authManager.getUser(); console.log("Stored user:", storedUser); // Get fresh profile from API const profile = await MeService.getMe(); console.log("API profile:", profile); // Compare console.log("Email match:", storedUser.email === profile.email); console.log("Name match:", storedUser.name === profile.name); console.log("Role match:", storedUser.role === profile.role); ``` ## Integration with Existing Services The MeService automatically integrates with existing infrastructure: ### Authentication (AuthManager) ```javascript // MeService uses ApiClient, which automatically: // - Adds JWT token to Authorization header // - Refreshes token if expired // - Handles 401 errors ``` ### Token Refresh (RefreshTokenService) ```javascript // Automatic token refresh before getMe request // If token expires within 1 minute, it's refreshed proactively // If 401 received, token is refreshed and request is retried ``` ### Data Consistency ```javascript // Me API returns data from JWT token // AuthManager stores data from login/register/refresh // Both should be in sync, but Me API is source of truth ``` ## Use Cases ### 1. Dashboard User Display Display the current user's name and email in the dashboard header. ```javascript const profile = await MeService.getMe(); setDashboardHeader(profile.name, profile.email); ``` ### 2. Role-Based UI Show or hide UI elements based on user role. ```javascript const isAdmin = await MeService.hasRole("owner"); setShowAdminPanel(isAdmin); ``` ### 3. Tenant Context Ensure user is operating in the correct tenant context. ```javascript const tenantId = await MeService.getTenantId(); loadTenantSpecificData(tenantId); ``` ### 4. Profile Verification Verify that the current user matches a resource owner. ```javascript const canEdit = await MeService.isCurrentUser(resourceOwnerId); ``` ### 5. Session Validation Periodically verify that the session is still valid. ```javascript setInterval(async () => { try { await MeService.getMe(); console.log("Session still valid"); } catch (error) { console.log("Session expired, logging out"); authManager.logout(); } }, 5 * 60 * 1000); // Every 5 minutes ``` ## Data Comparison: JWT vs Stored ### JWT Token Data (from Me API) - **Source**: JWT token claims - **Updated**: On login, registration, or token refresh - **Authoritative**: Yes (always current) - **Requires Network**: Yes ### AuthManager Stored Data - **Source**: localStorage (from login/register/refresh responses) - **Updated**: On login, registration, or token refresh - **Authoritative**: No (could be stale) - **Requires Network**: No **Recommendation**: Use Me API when you need authoritative current data, use AuthManager for quick access without network calls. ## Troubleshooting ### "Authentication required" error **Possible causes:** 1. User not logged in 2. Access token expired 3. Refresh token expired 4. Session invalidated **Solution:** - Check `authManager.isAuthenticated()` before calling - Login again if session expired - Check browser console for token refresh logs ### Profile data doesn't match stored data **Possible causes:** 1. Token was refreshed but AuthManager not updated 2. Profile was updated on another device 3. Data corruption in localStorage **Solution:** - Me API is source of truth - use its data - Update AuthManager with fresh data if needed - Clear localStorage and login again if corrupted ### Request succeeds but returns empty fields **Possible causes:** 1. JWT token missing required claims 2. Backend not setting context values correctly 3. Token format incorrect **Solution:** - Check token structure (decode JWT at jwt.io) - Verify backend is setting all required claims - Re-login to get fresh token ### 401 error despite valid token **Possible causes:** 1. Clock skew between client and server 2. Token signature invalid 3. Token expired but not detected **Solution:** - Check system clock synchronization - Clear tokens and login again - Verify token expiry dates in localStorage ## Related Files ### Created Files ``` src/services/API/MeService.js docs/ME_API.md ``` ### Backend Reference Files ``` cloud/maplepress-backend/docs/API.md (lines 374-413) cloud/maplepress-backend/internal/interface/http/handler/gateway/me_handler.go ``` ## Related Documentation - [LOGIN_API.md](./LOGIN_API.md) - Login and JWT token acquisition - [REFRESH_TOKEN_API.md](./REFRESH_TOKEN_API.md) - Automatic token refresh - [HELLO_API.md](./HELLO_API.md) - Simple authenticated endpoint testing - [FRONTEND_ARCHITECTURE.md](./FRONTEND_ARCHITECTURE.md) - Architecture overview - [README.md](./README.md) - Documentation index - [Backend API Documentation](../../../cloud/maplepress-backend/docs/API.md) - Complete API reference ## Summary The Me API implementation provides: 1. **User Profile Access**: Get current user's profile from JWT token 2. **Fast & Efficient**: No database queries, data from token claims 3. **Helper Methods**: Role checking, tenant ID, user verification 4. **Automatic Integration**: Works seamlessly with token refresh 5. **Error Handling**: Clear error messages and graceful failures 6. **Use Case Focused**: Methods designed for common scenarios This endpoint is essential for displaying user information in the dashboard and implementing role-based access control. --- **Last Updated**: October 30, 2024 **Frontend Version**: 0.0.0 **Documentation Version**: 1.0.0