- Add isVerified, emailVerificationToken, emailVerifiedAt fields to User entity
- Send verification email on registration with token link
- Add /verification-email/{token} route to confirm email
- Send notification emails to organizer and staff on organizer email verification
- Add isApproved and offer fields to User entity for organizer approval workflow
- Auto-verify and auto-approve SSO Keycloak users with offer='custom'
- Add resetCode and resetCodeExpiresAt fields to User entity
- Create ForgotPasswordController with 2-step flow (email -> code + new password)
- Block forgot password for SSO users (no local password)
- Add "Mot de passe oublie" link on login page
- Create email templates: verification, reset_code, organizer_pending, organizer_request
- Add migrations for all new fields
- Add tests: ForgotPasswordControllerTest (9 tests), update RegistrationControllerTest,
update UserTest with verification, approval, offer, and reset code fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
82 lines
4.5 KiB
Twig
82 lines
4.5 KiB
Twig
{% extends 'base.html.twig' %}
|
|
|
|
{% block title %}Mot de passe oublie - E-Ticket{% endblock %}
|
|
{% block description %}Reinitialisation de votre mot de passe E-Ticket{% endblock %}
|
|
|
|
{% block body %}
|
|
<div style="max-width:28rem;margin:0 auto;padding:3rem 1rem;">
|
|
<h1 class="text-3xl font-black uppercase tracking-tighter italic" style="border-bottom:4px solid #111827;display:inline-block;margin-bottom:0.5rem;">Mot de passe oublie</h1>
|
|
<p class="font-bold text-gray-600 italic" style="margin-bottom:2rem;">
|
|
{% if step == 'email' %}
|
|
Saisissez votre email pour recevoir un code.
|
|
{% else %}
|
|
Entrez le code recu par email et votre nouveau mot de passe.
|
|
{% endif %}
|
|
</p>
|
|
|
|
{% for message in app.flashes('success') %}
|
|
<div style="border:4px solid #111827;padding:1rem 1.5rem;margin-bottom:2rem;background:#d1fae5;box-shadow:4px 4px 0 rgba(0,0,0,1);">
|
|
<p class="font-black text-sm">{{ message }}</p>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% for message in app.flashes('error') %}
|
|
<div style="border:4px solid #111827;padding:1rem 1.5rem;margin-bottom:2rem;background:#fee2e2;box-shadow:4px 4px 0 rgba(0,0,0,1);">
|
|
<p class="font-black text-sm">{{ message }}</p>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% if step == 'email' %}
|
|
<form method="post" action="{{ path('app_forgot_password') }}" style="display:flex;flex-direction:column;gap:1.5rem;">
|
|
<input type="hidden" name="step" value="email">
|
|
<div>
|
|
<label for="forgot_email" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Email</label>
|
|
<input type="email" id="forgot_email" name="email" required autofocus
|
|
value="{{ email }}"
|
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
|
class="focus:border-indigo-600"
|
|
placeholder="jean.dupont@exemple.fr">
|
|
</div>
|
|
<div>
|
|
<button type="submit"
|
|
style="width:100%;padding:0.75rem 2rem;border:3px solid #111827;box-shadow:4px 4px 0 rgba(0,0,0,1);cursor:pointer;"
|
|
class="bg-yellow-400 font-black uppercase text-sm tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
|
Envoyer le code
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
<form method="post" action="{{ path('app_forgot_password') }}" style="display:flex;flex-direction:column;gap:1.5rem;">
|
|
<input type="hidden" name="step" value="code">
|
|
<input type="hidden" name="email" value="{{ email }}">
|
|
<div>
|
|
<label for="forgot_code" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Code de verification</label>
|
|
<input type="text" id="forgot_code" name="code" required autofocus
|
|
maxlength="6" pattern="[0-9]{6}" inputmode="numeric"
|
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;text-align:center;font-size:1.5rem;letter-spacing:0.3em;"
|
|
class="focus:border-indigo-600"
|
|
placeholder="000000">
|
|
</div>
|
|
<div>
|
|
<label for="forgot_password" class="text-xs font-black uppercase tracking-widest" style="display:block;margin-bottom:0.5rem;">Nouveau mot de passe</label>
|
|
<input type="password" id="forgot_password" name="password" required minlength="8"
|
|
style="width:100%;padding:0.75rem 1rem;border:3px solid #111827;font-weight:700;outline:none;"
|
|
class="focus:border-indigo-600"
|
|
placeholder="••••••••">
|
|
</div>
|
|
<div>
|
|
<button type="submit"
|
|
style="width:100%;padding:0.75rem 2rem;border:3px solid #111827;box-shadow:4px 4px 0 rgba(0,0,0,1);cursor:pointer;"
|
|
class="bg-yellow-400 font-black uppercase text-sm tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
|
Reinitialiser le mot de passe
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{% endif %}
|
|
|
|
<div style="margin-top:2rem;text-align:center;">
|
|
<p class="text-sm font-bold text-gray-600"><a href="{{ path('app_login') }}" class="text-indigo-600 hover:underline font-black">Retour a la connexion</a></p>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|