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
parent 321d7b1395
commit 44ef77ca68
9 changed files with 584 additions and 7 deletions

View File

@@ -0,0 +1,48 @@
{% extends "layouts/base.html" %}
{% block title %}{{ exchange.name }} - Participant Dashboard{% endblock %}
{% block content %}
<article>
<header>
<h1>{{ exchange.name }}</h1>
<p>Welcome, {{ participant.name }}!</p>
</header>
<section>
<h2>Exchange Details</h2>
<dl>
<dt>Budget</dt>
<dd>{{ exchange.budget }}</dd>
<dt>Gift Exchange Date</dt>
<dd>{{ exchange.exchange_date.strftime('%Y-%m-%d') }}</dd>
<dt>Registration Deadline</dt>
<dd>{{ exchange.registration_close_date.strftime('%Y-%m-%d') }}</dd>
</dl>
</section>
<section>
<h2>Your Information</h2>
<dl>
<dt>Name</dt>
<dd>{{ participant.name }}</dd>
<dt>Email</dt>
<dd>{{ participant.email }}</dd>
{% if participant.gift_ideas %}
<dt>Gift Ideas</dt>
<dd>{{ participant.gift_ideas }}</dd>
{% endif %}
</dl>
</section>
<section>
<form method="POST" action="{{ url_for('participant.logout') }}">
<button type="submit">Logout</button>
</form>
</section>
</article>
{% endblock %}