feat: implement Story 5.1 - Magic Link Request

Allow participants to request magic links for dashboard access:

- POST /exchange/<slug>/request-access handles form submission
- Accept email, look up participant in database
- Generate token (secrets.token_urlsafe(32)), store SHA-256 hash
- Send magic link email via EmailService.send_magic_link()
- Rate limit: 3 requests per hour per email
- Always show generic success message (prevent enumeration)
- Only send email if participant exists
- Case-insensitive email lookup
- Comprehensive test suite with 7 tests

Includes:
- MagicLinkRequestForm with email validation
- request_access.html template
- GET endpoint to display request form

All tests passing (97 total), 91% coverage maintained.

🤖 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:19:56 -07:00
parent 43bfce3913
commit 321d7b1395
4 changed files with 389 additions and 1 deletions

View File

@@ -0,0 +1,38 @@
{% extends "layouts/base.html" %}
{% block title %}Request Access - {{ exchange.name }}{% endblock %}
{% block content %}
<article>
<header>
<h1>Request Access</h1>
<p>{{ exchange.name }}</p>
</header>
{% if success %}
<section>
<p><strong>Check your email!</strong></p>
<p>If you're registered for this exchange, we've sent you a magic link to access your participant dashboard.</p>
<p>The link will expire in 1 hour.</p>
</section>
{% else %}
<section>
<p>Enter your email address to receive a magic link for access to your participant dashboard.</p>
<form method="POST">
{{ form.hidden_tag() }}
<label for="email">
{{ form.email.label }}
{{ form.email(placeholder="your.email@example.com") }}
{% if form.email.errors %}
<small>{{ form.email.errors[0] }}</small>
{% endif %}
</label>
<button type="submit">Send Magic Link</button>
</form>
</section>
{% endif %}
</article>
{% endblock %}