fix(login): invite flow instead of email verification for users with no primary method, improve sending behaviour (#11837)

Closes #10929

# Which Problems Are Solved

When a human user without a primary authentication method enters their
email on the login screen, the login historically auto-sent an email
code (send=true) and redirected them to the `/verify` flow. If the user
was newly created via the API and already received an initial
verify-email, navigating to the login page would trigger a new invite
code, silently invalidating the code they received in their first email.
Additionally instead of resending the same type of email (invitation) a
regular email verification mail was sent

# How the Problems Are Solved
- Resend an invitation email instead of a email verification if a user
has no method set (still in invitation state)
- Conditional Code Sending: Updated `loginname.ts` to check
`humanUser?.email?.isVerified`. We only auto-send a new code
(`send=true`) if the user's email is already verified. Unverified users
will be redirected with `send=false`, allowing them to safely enter the
code they already have.
- UI State Fix: Fixed an issue in `verify/page.tsx` where the send URL
parameter was being checked directly as a string ("false" is truthy). By
using the properly evaluated doSend boolean, the "Code Sent" alert now
correctly hides itself when a new code is not explicitly sent.
- Translation Updates: Refined the codeSent messaging across all locales
to specify "A new code has been sent..." to provide better context to
the user when they do explicitly request a resend.
This commit is contained in:
Max Peintner
2026-04-02 09:58:20 +02:00
committed by GitHub
parent 140f07e60b
commit 891c7473ca
15 changed files with 46 additions and 17 deletions
+1 -1
View File
@@ -84,7 +84,7 @@
"set": {
"title": "تعيين كلمة المرور",
"description": "تعيين كلمة المرور لحسابك",
"codeSent": "تم إرسال رمز إلى عنوان بريدك الإلكتروني.",
"codeSent": "تم إرسال رمز جديد إلى عنوان بريدك الإلكتروني.",
"noCodeReceived": "لم تستلم رمزاً؟",
"resend": "إعادة إرسال الرمز",
"submit": "متابعة",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Passwort festlegen",
"description": "Legen Sie das Passwort für Ihr Konto fest",
"codeSent": "Ein Code wurde an Ihre E-Mail-Adresse gesendet.",
"codeSent": "Ein neuer Code wurde an Ihre E-Mail-Adresse gesendet.",
"noCodeReceived": "Keinen Code erhalten?",
"resend": "Erneut senden",
"submit": "Weiter",
+2 -2
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Set Password",
"description": "Set the password for your account",
"codeSent": "A code has been sent to your email address.",
"codeSent": "A new code has been sent to your email address.",
"noCodeReceived": "Didn't receive a code?",
"resend": "Resend code",
"submit": "Continue",
@@ -389,7 +389,7 @@
"description": "Enter the Code provided in the verification email.",
"noCodeReceived": "Didn't receive a code?",
"resendCode": "Resend code",
"codeSent": "A code has just been sent to your email address.",
"codeSent": "A new code has just been sent to your email address.",
"submit": "Continue",
"labels": {
"code": "Code"
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Establecer Contraseña",
"description": "Establece la contraseña para tu cuenta",
"codeSent": "Se ha enviado un código a su correo electrónico.",
"codeSent": "Se ha enviado un nuevo código a su dirección de correo electrónico.",
"noCodeReceived": "¿No recibiste un código?",
"resend": "Reenviar código",
"submit": "Continuar",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Définir le mot de passe",
"description": "Définissez le mot de passe de votre compte",
"codeSent": "Un code a été envoyé à votre adresse e-mail.",
"codeSent": "Un nouveau code a été envoyé à votre adresse e-mail.",
"noCodeReceived": "Vous n'avez pas reçu de code ?",
"resend": "Renvoyer le code",
"submit": "Continuer",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Imposta Password",
"description": "Imposta la password per il tuo account",
"codeSent": "Un codice è stato inviato al tuo indirizzo email.",
"codeSent": "Un nuovo codice è stato inviato al tuo indirizzo email.",
"noCodeReceived": "Non hai ricevuto un codice?",
"resend": "Invia di nuovo",
"submit": "Continua",
+1 -1
View File
@@ -76,7 +76,7 @@
"set": {
"title": "パスワード設定",
"description": "アカウントのパスワードを設定してください",
"codeSent": "確認コードメールアドレスに送信ました。",
"codeSent": "新しいコードメールアドレスに送信されました。",
"noCodeReceived": "コードが届きませんか?",
"resend": "コードを再送信",
"submit": "続行",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Stel Wachtwoord in",
"description": "Stel het wachtwoord voor jouw account in",
"codeSent": "Er is een code naar jouw e-mailadres verzonden.",
"codeSent": "Er is een nieuwe code naar uw e-mailadres verzonden.",
"noCodeReceived": "Geen code ontvangen?",
"resend": "Code opnieuw versturen",
"submit": "Verder",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Ustaw hasło",
"description": "Ustaw hasło dla swojego konta",
"codeSent": "Kod został wysłany na twój adres e-mail.",
"codeSent": "Nowy kod został wysłany na Twój adres e-mail.",
"noCodeReceived": "Nie otrzymałeś kodu?",
"resend": "Wyślij kod ponownie",
"submit": "Kontynuuj",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Установить пароль",
"description": "Установите пароль для вашего аккаунта",
"codeSent": "Код отправлен на ваш адрес электронной почты.",
"codeSent": "Новый код был отправлен на ваш адрес электронной почты.",
"noCodeReceived": "Не получили код?",
"resend": "Отправить код повторно",
"submit": "Продолжить",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Şifre Belirle",
"description": "Hesabınız için şifre belirleyin",
"codeSent": "E-posta adresinize bir kod gönderildi.",
"codeSent": "E-posta adresinize yeni bir kod gönderildi.",
"noCodeReceived": "Kod almadınız mı?",
"resend": "Kodu tekrar gönder",
"submit": "Devam",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "Встановити пароль",
"description": "Встановіть пароль для свого облікового запису",
"codeSent": "Код було надіслано на вашу електронну адресу.",
"codeSent": "Новий код надіслано на вашу електронну адресу.",
"noCodeReceived": "Не отримали код?",
"resend": "Надіслати код повторно",
"submit": "Продовжити",
+1 -1
View File
@@ -83,7 +83,7 @@
"set": {
"title": "设置密码",
"description": "为您的账户设置密码",
"codeSent": "验证码已发送到您的邮箱。",
"codeSent": "新代码已发送到您的电子邮件地址。",
"noCodeReceived": "没有收到验证码?",
"resend": "重发验证码",
"submit": "继续",
+24 -1
View File
@@ -198,12 +198,35 @@ describe("sendLoginname", () => {
mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
});
test("should redirect to verify when user has no authentication methods", async () => {
test("should redirect to verify with send=false when user has no authentication methods and email is unverified", async () => {
mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [] });
const result = await sendLoginname({
loginName: "user@example.com",
requestId: "req123",
organization: "org123",
});
expect(result).toHaveProperty("redirect");
expect((result as any).redirect).toMatch(/^\/verify\?/);
expect((result as any).redirect).toContain("loginName=user%40example.com");
expect((result as any).redirect).toContain("send=false");
expect((result as any).redirect).toContain("invite=true");
expect((result as any).redirect).toContain("requestId=req123");
});
test("should redirect to verify with send=true when user has no authentication methods and email is already verified", async () => {
const verifiedEmailUser = {
...mockUser,
type: { case: "human", value: { email: { email: "user@example.com", isVerified: true } } },
};
mockSearchUsers.mockResolvedValue({ result: [verifiedEmailUser] });
mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [] });
const result = await sendLoginname({
loginName: "user@example.com",
requestId: "req123",
organization: "org123",
});
expect(result).toHaveProperty("redirect");
+8 -2
View File
@@ -338,10 +338,16 @@ export async function sendLoginname(command: SendLoginnameCommand) {
logger.debug("humanUser.email?.isVerified", {
isVerified: humanUser?.email?.isVerified,
});
// If the user's email is not verified, they likely already have a code from the
// initial verification email. Auto-sending a new one here invalidates their existing code
// and causes confusion. Only auto-send (`send=true`) if the email is already verified.
const shouldSend = humanUser?.email?.isVerified === true;
const params = new URLSearchParams({
loginName: (session?.factors?.user?.loginName ?? user.preferredLoginName) as string,
send: "true", // set this to true to request a new code immediately
invite: humanUser?.email?.isVerified ? "false" : "true", // sendInviteEmailCode results in an error if user is already initialized
send: shouldSend ? "true" : "false",
invite: "true", // always send invite code if user has no primary auth method
});
if (command.requestId) {