mirror of
https://github.com/zitadel/zitadel.git
synced 2026-08-17 16:35:14 -05:00
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:
@@ -84,7 +84,7 @@
|
||||
"set": {
|
||||
"title": "تعيين كلمة المرور",
|
||||
"description": "تعيين كلمة المرور لحسابك",
|
||||
"codeSent": "تم إرسال رمز إلى عنوان بريدك الإلكتروني.",
|
||||
"codeSent": "تم إرسال رمز جديد إلى عنوان بريدك الإلكتروني.",
|
||||
"noCodeReceived": "لم تستلم رمزاً؟",
|
||||
"resend": "إعادة إرسال الرمز",
|
||||
"submit": "متابعة",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"set": {
|
||||
"title": "パスワード設定",
|
||||
"description": "アカウントのパスワードを設定してください",
|
||||
"codeSent": "確認コードをメールアドレスに送信しました。",
|
||||
"codeSent": "新しいコードがメールアドレスに送信されました。",
|
||||
"noCodeReceived": "コードが届きませんか?",
|
||||
"resend": "コードを再送信",
|
||||
"submit": "続行",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"set": {
|
||||
"title": "Установить пароль",
|
||||
"description": "Установите пароль для вашего аккаунта",
|
||||
"codeSent": "Код отправлен на ваш адрес электронной почты.",
|
||||
"codeSent": "Новый код был отправлен на ваш адрес электронной почты.",
|
||||
"noCodeReceived": "Не получили код?",
|
||||
"resend": "Отправить код повторно",
|
||||
"submit": "Продолжить",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"set": {
|
||||
"title": "Встановити пароль",
|
||||
"description": "Встановіть пароль для свого облікового запису",
|
||||
"codeSent": "Код було надіслано на вашу електронну адресу.",
|
||||
"codeSent": "Новий код надіслано на вашу електронну адресу.",
|
||||
"noCodeReceived": "Не отримали код?",
|
||||
"resend": "Надіслати код повторно",
|
||||
"submit": "Продовжити",
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"set": {
|
||||
"title": "设置密码",
|
||||
"description": "为您的账户设置密码",
|
||||
"codeSent": "验证码已发送到您的邮箱。",
|
||||
"codeSent": "新代码已发送到您的电子邮件地址。",
|
||||
"noCodeReceived": "没有收到验证码?",
|
||||
"resend": "重发验证码",
|
||||
"submit": "继续",
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user