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:
12
src/templates/errors/403.html
Normal file
12
src/templates/errors/403.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block title %}Forbidden - Sneaky Klaus{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h1>403 - Forbidden</h1>
|
||||
</header>
|
||||
<p>You don't have permission to access this page.</p>
|
||||
</article>
|
||||
{% endblock %}
|
||||
13
src/templates/errors/magic_link_error.html
Normal file
13
src/templates/errors/magic_link_error.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
|
||||
{% block title %}Magic Link Error - Sneaky Klaus{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h1>Magic Link Error</h1>
|
||||
</header>
|
||||
<p>There was a problem with your magic link.</p>
|
||||
<p>Please request a new one to access your participant dashboard.</p>
|
||||
</article>
|
||||
{% endblock %}
|
||||
48
src/templates/participant/dashboard.html
Normal file
48
src/templates/participant/dashboard.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user