feat: implement Stories 5.2 & 5.3 - Magic Link Login and Participant Session

Implemented complete participant authentication flow with magic link login and session management.

Story 5.2 - Magic Link Login:
- Participants can click magic links to securely access their dashboard
- Single-use tokens that expire after 1 hour
- Session creation with participant_id, user_type, and exchange_id
- Error handling for expired, used, or invalid tokens
- Fixed timezone-aware datetime comparison for SQLite compatibility

Story 5.3 - Participant Session:
- Authenticated participants can access their exchange dashboard
- participant_required decorator protects participant-only routes
- Participants can only access their own exchange (403 for others)
- Logout functionality clears session and redirects appropriately
- Unauthenticated access returns 403 Forbidden

Technical changes:
- Added magic_login() route for token validation and session creation
- Added dashboard() route with exchange and participant data
- Added logout() route with smart redirect to request access page
- Added participant_required decorator for route protection
- Enhanced MagicToken.is_expired for timezone-naive datetime handling
- Added participant.logout to setup check exclusions
- Created templates: dashboard.html, magic_link_error.html, 403.html
- Comprehensive test coverage for all user flows

Acceptance Criteria Met:
✓ Valid magic links create authenticated sessions
✓ Invalid/expired/used tokens show appropriate errors
✓ Authenticated participants see their dashboard
✓ Participants cannot access other exchanges
✓ Unauthenticated users cannot access protected routes
✓ Logout clears session and provides feedback

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 17:41:29 -07:00
co-authored by Claude Opus 4.5
parent 321d7b1395
commit 44ef77ca68
9 changed files with 584 additions and 7 deletions
+22 -1
View File
@@ -2,7 +2,7 @@
from functools import wraps
from flask import flash, redirect, session, url_for
from flask import abort, flash, redirect, session, url_for
def admin_required(f):
@@ -26,3 +26,24 @@ def admin_required(f):
return f(*args, **kwargs)
return decorated_function
def participant_required(f):
"""Decorator to require participant authentication for a route.
Checks if user is logged in as participant. If not, returns 403 Forbidden.
Args:
f: The function to decorate.
Returns:
Decorated function that checks authentication.
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if "user_id" not in session or session.get("user_type") != "participant":
abort(403)
return f(*args, **kwargs)
return decorated_function