diff --git a/core/controllers/AuthController.php b/core/controllers/AuthController.php index 7dcc773f..66813a09 100644 --- a/core/controllers/AuthController.php +++ b/core/controllers/AuthController.php @@ -194,7 +194,8 @@ public function forgotAction() } /** - * GET /auth/reset/cid/…/code/… -> set-a-new-password form (from the emailed link). + * GET /auth/reset/cid/…/code/… -> set-a-new-password form (from the emailed link), + * or the dead-link card when the token no longer resolves. * POST /auth/reset -> redeem the token + set the password (JSON). * * @return void @@ -229,9 +230,19 @@ public function resetAction() return; } $this->_helper->layout()->setLayout('auth'); - $this->view->title = 'Set a new password — Tiger'; - $this->view->cid = (string) $request->getParam('cid', ''); - $this->view->code = (string) $request->getParam('code', ''); + $this->view->cid = (string) $request->getParam('cid', ''); + $this->view->code = (string) $request->getParam('code', ''); + + // Check the token BEFORE rendering the form. A dead link used to render a perfectly + // ordinary set-a-new-password screen, so the user chose a password, typed it twice and + // only then learned the link had expired — and the people who hit that are the ones + // already locked out. Peek is read-only: it never consumes the challenge or costs an + // attempt, and resetPassword() still re-checks at submit (a token can expire in between). + $this->view->deadLink = !(new Tiger_Service_Authentication()) + ->resetTokenIsLive($this->view->cid); + $this->view->title = $this->view->deadLink + ? 'Link expired — Tiger' + : 'Set a new password — Tiger'; } /** diff --git a/core/languages/de/core.php b/core/languages/de/core.php index 7005e54b..33f640ff 100644 --- a/core/languages/de/core.php +++ b/core/languages/de/core.php @@ -92,6 +92,9 @@ 'core.auth.reset.new_password' => 'Neues Passwort', 'core.auth.reset.confirm_password' => 'Passwort bestätigen', 'core.auth.reset.submit' => 'Neues Passwort festlegen', + 'core.auth.reset.dead_title' => 'Dieser Link ist abgelaufen', + 'core.auth.reset.dead_body' => 'Links zum Zurücksetzen des Passworts sind einmalig nutzbar und zeitlich begrenzt. Fordern Sie einen neuen an – wir senden ihn Ihnen per E-Mail.', + 'core.auth.reset.dead_action' => 'Neuen Link anfordern', // --- Authentifizierung: Passwort vergessen --- 'core.auth.forgot.title' => 'Passwort zurücksetzen', diff --git a/core/languages/en/core.php b/core/languages/en/core.php index c381ab8b..2671a981 100644 --- a/core/languages/en/core.php +++ b/core/languages/en/core.php @@ -96,6 +96,9 @@ 'core.auth.reset.new_password' => 'New password', 'core.auth.reset.confirm_password' => 'Confirm password', 'core.auth.reset.submit' => 'Set new password', + 'core.auth.reset.dead_title' => 'This link has expired', + 'core.auth.reset.dead_body' => 'Password reset links are single-use and time-limited. Request a new one and we\'ll email it straight over.', + 'core.auth.reset.dead_action' => 'Request a new link', // --- Auth: forgot password --- 'core.auth.forgot.title' => 'Reset your password', diff --git a/core/languages/es/core.php b/core/languages/es/core.php index 7843aae0..45a0c527 100644 --- a/core/languages/es/core.php +++ b/core/languages/es/core.php @@ -92,6 +92,9 @@ 'core.auth.reset.new_password' => 'Nueva contraseña', 'core.auth.reset.confirm_password' => 'Confirmar contraseña', 'core.auth.reset.submit' => 'Establecer nueva contraseña', + 'core.auth.reset.dead_title' => 'Este enlace ha caducado', + 'core.auth.reset.dead_body' => 'Los enlaces para restablecer la contraseña son de un solo uso y caducan. Solicita uno nuevo y te lo enviaremos por correo.', + 'core.auth.reset.dead_action' => 'Solicitar un enlace nuevo', // --- Autenticación: contraseña olvidada --- 'core.auth.forgot.title' => 'Restablecer tu contraseña', diff --git a/core/languages/fr/core.php b/core/languages/fr/core.php index 968e8941..987061a2 100644 --- a/core/languages/fr/core.php +++ b/core/languages/fr/core.php @@ -92,6 +92,9 @@ 'core.auth.reset.new_password' => 'Nouveau mot de passe', 'core.auth.reset.confirm_password' => 'Confirmer le mot de passe', 'core.auth.reset.submit' => 'Définir le nouveau mot de passe', + 'core.auth.reset.dead_title' => 'Ce lien a expiré', + 'core.auth.reset.dead_body' => 'Les liens de réinitialisation sont à usage unique et à durée limitée. Demandez-en un nouveau et nous vous l\'enverrons par e-mail.', + 'core.auth.reset.dead_action' => 'Demander un nouveau lien', // --- Authentification : mot de passe oublié --- 'core.auth.forgot.title' => 'Réinitialiser votre mot de passe', diff --git a/core/languages/hi/core.php b/core/languages/hi/core.php index 88da6da7..2fce39f5 100644 --- a/core/languages/hi/core.php +++ b/core/languages/hi/core.php @@ -92,6 +92,9 @@ 'core.auth.reset.new_password' => 'नया पासवर्ड', 'core.auth.reset.confirm_password' => 'पासवर्ड की पुष्टि करें', 'core.auth.reset.submit' => 'नया पासवर्ड सेट करें', + 'core.auth.reset.dead_title' => 'यह लिंक समाप्त हो चुका है', + 'core.auth.reset.dead_body' => 'पासवर्ड रीसेट लिंक एक बार उपयोग के लिए होते हैं और इनकी एक समय-सीमा होती है। नया लिंक मांगें और हम उसे ईमेल कर देंगे।', + 'core.auth.reset.dead_action' => 'नया लिंक मांगें', // --- प्रमाणीकरण: पासवर्ड भूल गए --- 'core.auth.forgot.title' => 'अपना पासवर्ड रीसेट करें', diff --git a/core/languages/pt/core.php b/core/languages/pt/core.php index 5f4e6bd1..a115c9b1 100644 --- a/core/languages/pt/core.php +++ b/core/languages/pt/core.php @@ -92,6 +92,9 @@ 'core.auth.reset.new_password' => 'Nova senha', 'core.auth.reset.confirm_password' => 'Confirmar senha', 'core.auth.reset.submit' => 'Definir nova senha', + 'core.auth.reset.dead_title' => 'Este link expirou', + 'core.auth.reset.dead_body' => 'Os links de redefinição de senha são de uso único e têm prazo de validade. Solicite um novo e nós o enviaremos por e-mail.', + 'core.auth.reset.dead_action' => 'Solicitar um novo link', // --- Autenticação: senha esquecida --- 'core.auth.forgot.title' => 'Redefinir sua senha', diff --git a/core/views/scripts/auth/reset.phtml b/core/views/scripts/auth/reset.phtml index d0c91fd3..c10f0847 100644 --- a/core/views/scripts/auth/reset.phtml +++ b/core/views/scripts/auth/reset.phtml @@ -8,7 +8,23 @@ */ $cid = $this->escape($this->cid ?? ''); $code = $this->escape($this->code ?? ''); + +// A token that no longer resolves gets the dead-link card instead of the form — telling the +// user BEFORE they choose a password, not after they have typed it twice. The controller +// decides; the view only renders the verdict. +if (!empty($this->deadLink)): ?> +
= $this->escape($this->t('core.auth.reset.dead_body')) ?>
+