This document describes the secure authentication system implemented for the advisor portal. The system uses email verification with time-limited codes to ensure that only authorized advisors can access administrative features.
The system uses email verification with time-limited codes instead of simple email whitelist checking because:
Prevents Email Theft: Someone with access to the whitelist cannot simply enter a whitelisted email and gain access - they need access to the actual email inbox to receive the verification code.
Time-Limited Access: Codes expire after 15 minutes, limiting the window of opportunity for potential attacks.
Rate Limiting: After 5 failed verification attempts, accounts are temporarily locked for 30 minutes.
Session Management: Authenticated sessions last 1 hour before requiring re-authentication.
Audit Trail: All authentication attempts are logged with timestamps for security monitoring.
Administrators can manage the advisor whitelist through the App Management page (only visible in advisor mode):
models/advisor_auth.py)The AdvisorAuth model stores:
routes/advisor_auth.py)Authentication Endpoints:
POST /api/advisor-auth/request-code - Request access codePOST /api/advisor-auth/verify-code - Verify code and create sessionGET /api/advisor-auth/verify-session - Check current session validityPOST /api/advisor-auth/logout - Logout and clear sessionAdmin Endpoints (require admin token):
GET /api/advisor-auth/whitelist - List all whitelisted advisorsPOST /api/advisor-auth/whitelist - Add single advisorPOST /api/advisor-auth/whitelist/bulk - Bulk add advisors via CSVDELETE /api/advisor-auth/whitelist/:id - Remove advisorauth.py)@require_advisor Decorator:
@require_advisor
def protected_route():
# This route requires valid advisor authentication
pass
The decorator:
X-Advisor-Token header or session tokenrequest.advisorcomponents/AdvisorAuthModal.jsx)A multi-step modal that handles:
pages/AppManagementPage.jsx)Admin interface for managing advisor whitelist:
services/api.js)New methods:
requestAdvisorCode(email) - Request verification codeverifyAdvisorCode(email, code) - Verify codeverifyAdvisorSession() - Check session validitylogoutAdvisor() - LogoutgetAdvisorWhitelist() - Admin: get whitelistaddAdvisorToWhitelist(email) - Admin: add advisorbulkAddAdvisorsToWhitelist(file) - Admin: bulk addremoveAdvisorFromWhitelist(id) - Admin: remove advisorToken management:
X-Advisor-Token headerAppShell:
AdvisorAuthModalApp.jsx:
useAppController.js:
A new migration file creates the advisor_auth table:
# Run migration (when backend is running)
flask db upgrade
Migration file: migrations/versions/a1b2c3d4e5f6_add_advisor_authentication_table.py
The system includes a placeholder for email sending. To enable email:
send_access_code_email() in routes/advisor_auth.py:def send_access_code_email(email, code):
# Example with SendGrid
import sendgrid
from sendgrid.helpers.mail import Mail
sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))
message = Mail(
from_email='noreply@university.edu',
to_emails=email,
subject='Your Advisor Portal Access Code',
html_content=f'<p>Your access code is: <strong>{code}</strong></p><p>This code expires in 15 minutes.</p>'
)
sg.send(message)
return True
.env:SENDGRID_API_KEY=your_api_key_here
secrets moduleCreate a CSV file with an email column:
email
advisor1@university.edu
advisor2@university.edu
advisor3@university.edu
Alternative (if no header):
advisor1@university.edu
advisor2@university.edu
advisor3@university.edu
The system will:
# Add yourself to whitelist (requires admin token)
curl -X POST http://localhost:5000/api/advisor-auth/whitelist \
-H "Content-Type: application/json" \
-H "X-Admin-Token: your_admin_token" \
-d '{"email": "your@email.edu"}'
curl -X POST http://localhost:5000/api/advisor-auth/request-code \
-H "Content-Type: application/json" \
-d '{"email": "your@email.edu"}'
curl -X POST http://localhost:5000/api/advisor-auth/verify-code \
-H "Content-Type: application/json" \
-d '{"email": "your@email.edu", "code": "123456"}'
.envUPDATE advisor_auth SET locked_until = NULL, failed_attempts = 0 WHERE email = 'advisor@university.edu';
advisorTokenmodels/advisor_auth.py - New modelroutes/advisor_auth.py - New routesauth.py - Added @require_advisor decoratorroutes/__init__.py - Registered advisor_auth blueprintapp.py - Added X-Advisor-Token to CORS headersmodels/__init__.py - Exported AdvisorAuth modelmigrations/versions/a1b2c3d4e5f6_add_advisor_authentication_table.py - Migrationcomponents/AdvisorAuthModal.jsx - New modalpages/AppManagementPage.jsx - New pageservices/api.js - Added advisor auth methodsviews/AppShell.jsx - Replaced Switch button with Settings cogApp.jsx - Integrated advisor authcontrollers/useAppController.js - Added App Management tabThe admin mode badge can be removed from AdminBadge.jsx or wherever it was displayed, as the settings cog now serves as the access point for advisor features.
Implementation Date: November 20, 2025 Author: GitHub Copilot Status: Complete - Ready for Testing