Harden email verification resend against abuse

Add a per-address confirmation-email cooldown and disable the
resend/forgot-password submit buttons while the mutation is in
flight so callers cannot flood the mail queue or double-submit.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-07-28 10:35:12 +02:00
parent 5d0882778f
commit 82a62005f8
5 changed files with 66 additions and 12 deletions

View File

@@ -56,11 +56,12 @@ export default function ForgotPasswordPage() {
},
});
const [sendInstructions] = useMutation<ForgotPasswordPageMutation>(
sendInstructionsMutation,
);
const [sendInstructions, isSendingInstructions]
= useMutation<ForgotPasswordPageMutation>(sendInstructionsMutation);
const onSubmit = handleSubmit(({ email }) => {
if (isSendingInstructions) return;
sendInstructions({
variables: {
input: { email },
@@ -154,9 +155,9 @@ export default function ForgotPasswordPage() {
<Button
type="submit"
className="w-xs h-10 mx-auto mt-6"
disabled={formState.isSubmitting}
disabled={isSendingInstructions}
>
{formState.isSubmitting
{isSendingInstructions
? t("forgotPasswordPage.actions.sendingInstructions")
: t("forgotPasswordPage.actions.sendInstructions")}
</Button>

View File

@@ -57,11 +57,12 @@ export default function ResendVerificationEmailPage() {
},
});
const [resendVerificationEmail] = useMutation<ResendVerificationEmailPageMutation>(
resendVerificationEmailMutation,
);
const [resendVerificationEmail, isResending]
= useMutation<ResendVerificationEmailPageMutation>(resendVerificationEmailMutation);
const onSubmit = handleSubmit(({ email }) => {
if (isResending) return;
resendVerificationEmail({
variables: {
input: { email },
@@ -155,9 +156,9 @@ export default function ResendVerificationEmailPage() {
<Button
type="submit"
className="w-xs h-10 mx-auto mt-6"
disabled={formState.isSubmitting}
disabled={isResending}
>
{formState.isSubmitting
{isResending
? t("resendVerificationEmailPage.actions.sendingVerification")
: t("resendVerificationEmailPage.actions.sendVerification")}
</Button>