VYPR
Moderate severityOSV Advisory· Published Jan 15, 2026· Updated Jan 15, 2026

ZITADEL has a user enumeration vulnerability in Login UIs

CVE-2026-23511

Description

ZITADEL is an open source identity management platform. Prior to 4.9.1 and 3.4.6, a user enumeration vulnerability has been discovered in Zitadel's login interfaces. An unauthenticated attacker can exploit this flaw to confirm the existence of valid user accounts by iterating through usernames and userIDs. This vulnerability is fixed in 4.9.1 and 3.4.6.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/zitadel/zitadelGo
>= 4.0.0, < 4.9.14.9.1
github.com/zitadel/zitadelGo
< 3.4.63.4.6

Affected products

1
  • Range: 2.20.0, cnsl-feature-dev, feat-new-mail-templates-dev, …

Patches

3
b85ab69e4679

fix(login): generalize error message on code verifications

https://github.com/zitadel/zitadelLivio SpringJan 14, 2026via ghsa
5 files changed · +42 10
  • internal/api/ui/login/init_password_handler.go+10 2 modified
    @@ -5,6 +5,9 @@ import (
     	"net/http"
     	"net/url"
     
    +	"github.com/zitadel/logging"
    +
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
     	"github.com/zitadel/zitadel/internal/domain"
     	"github.com/zitadel/zitadel/internal/zerrors"
    @@ -92,7 +95,8 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *dom
     	userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
     	_, err := l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID, false)
     	if err != nil {
    -		l.renderInitPassword(w, r, authReq, data.UserID, "", err)
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", data.UserID).WithError(err).Warn("error setting initial password")
    +		l.renderInitPassword(w, r, authReq, data.UserID, "", zerrors.ThrowInvalidArgument(nil, "LOGIN-as3k2", "Errors.User.Code.Invalid"))
     		return
     	}
     	l.renderInitPasswordDone(w, r, authReq, userOrg)
    @@ -108,7 +112,11 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
     		authReqID = authReq.ID
     	}
     	_, err := l.command.RequestSetPassword(setContext(r.Context(), userOrg), userID, userOrg, domain.NotificationTypeEmail, authReqID)
    -	l.renderInitPassword(w, r, authReq, userID, "", err)
    +	if !zerrors.IsInternal(err) {
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", userID).WithError(err).Warn("error requesting password reset")
    +		err = nil // we ignore errors on resend to not leak information
    +	}
    +	l.renderInitPassword(w, r, authReq, userID, "", nil)
     }
     
     func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code string, err error) {
    
  • internal/api/ui/login/init_user_handler.go+9 1 modified
    @@ -6,6 +6,9 @@ import (
     	"net/url"
     	"strconv"
     
    +	"github.com/zitadel/logging"
    +
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
     	"github.com/zitadel/zitadel/internal/domain"
     	"github.com/zitadel/zitadel/internal/zerrors"
    @@ -109,7 +112,8 @@ func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authRe
     	userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
     	err = l.command.HumanVerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, userOrgID, data.Code, data.Password, userAgentID, initCodeGenerator)
     	if err != nil {
    -		l.renderInitUser(w, r, authReq, data.UserID, data.LoginName, "", data.PasswordSet, err)
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", data.UserID).WithError(err).Warn("error verifying initial code")
    +		l.renderInitUser(w, r, authReq, data.UserID, data.LoginName, "", data.PasswordSet, zerrors.ThrowInvalidArgument(nil, "LOGIN-lk3os", "Errors.User.Code.Invalid"))
     		return
     	}
     	l.renderInitUserDone(w, r, authReq, userOrgID)
    @@ -127,6 +131,10 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *
     		return
     	}
     	_, err = l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID, initCodeGenerator, authRequestID)
    +	if !zerrors.IsInternal(err) {
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", userID).WithError(err).Warn("error requesting init mail")
    +		err = nil // we ignore errors on resend to not leak information
    +	}
     	l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err)
     }
     
    
  • internal/api/ui/login/invite_user_handler.go+9 1 modified
    @@ -5,6 +5,9 @@ import (
     	"net/http"
     	"net/url"
     
    +	"github.com/zitadel/logging"
    +
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
     	"github.com/zitadel/zitadel/internal/domain"
     	"github.com/zitadel/zitadel/internal/zerrors"
    @@ -98,7 +101,8 @@ func (l *Login) checkUserInviteCode(w http.ResponseWriter, r *http.Request, auth
     	userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
     	_, err := l.command.VerifyInviteCodeSetPassword(setUserContext(r.Context(), data.UserID, userOrgID), data.UserID, data.Code, data.Password, userAgentID)
     	if err != nil {
    -		l.renderInviteUser(w, r, authReq, data.UserID, data.OrgID, data.LoginName, "", err)
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", data.UserID).WithError(err).Warn("error verifying initial code")
    +		l.renderInviteUser(w, r, authReq, data.UserID, data.OrgID, data.LoginName, "", zerrors.ThrowInvalidArgument(nil, "LOGIN-sjk2o", "Errors.User.Code.Invalid"))
     		return
     	}
     	if authReq == nil {
    @@ -115,6 +119,10 @@ func (l *Login) resendUserInvite(w http.ResponseWriter, r *http.Request, authReq
     		authRequestID = authReq.ID
     	}
     	_, err := l.command.ResendInviteCode(setUserContext(r.Context(), userID, userOrgID), userID, userOrgID, authRequestID)
    +	if !zerrors.IsInternal(err) {
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", userID).WithError(err).Warn("error requesting invite code")
    +		err = nil // we ignore errors on resend to not leak information
    +	}
     	l.renderInviteUser(w, r, authReq, userID, orgID, loginName, "", err)
     }
     
    
  • internal/api/ui/login/mail_verify_handler.go+7 1 modified
    @@ -9,6 +9,7 @@ import (
     
     	"github.com/zitadel/logging"
     
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
     	"github.com/zitadel/zitadel/internal/domain"
     	"github.com/zitadel/zitadel/internal/zerrors"
    @@ -121,6 +122,10 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque
     		return
     	}
     	_, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg, emailCodeGenerator, authReqID)
    +	if !zerrors.IsInternal(err) {
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", data.UserID).WithError(err).Warn("error requesting mail verification")
    +		err = nil // we ignore errors on resend to not leak information
    +	}
     	l.renderMailVerification(w, r, authReq, data.UserID, "", data.PasswordInit, err)
     }
     
    @@ -138,7 +143,8 @@ func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *d
     	userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
     	_, err = l.command.VerifyHumanEmail(setContext(r.Context(), userOrg), userID, code, userOrg, password, userAgentID, emailCodeGenerator)
     	if err != nil {
    -		l.renderMailVerification(w, r, authReq, userID, "", password != "", err)
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", userID).WithError(err).Warn("error verifying email code")
    +		l.renderMailVerification(w, r, authReq, userID, "", password != "", zerrors.ThrowInvalidArgument(nil, "LOGIN-WSdsg", "Errors.User.Code.Invalid"))
     		return
     	}
     	l.renderMailVerified(w, r, authReq, userOrg)
    
  • internal/api/ui/login/password_complexity_policy_handler.go+7 5 modified
    @@ -5,6 +5,7 @@ import (
     
     	"github.com/zitadel/logging"
     
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	iam_model "github.com/zitadel/zitadel/internal/iam/model"
     )
     
    @@ -22,12 +23,13 @@ func (l *Login) getPasswordComplexityPolicy(r *http.Request, orgID string) *iam_
     }
     
     func (l *Login) getPasswordComplexityPolicyByUserID(r *http.Request, userID string) *iam_model.PasswordComplexityPolicyView {
    +	resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
     	user, err := l.query.GetUserByID(r.Context(), false, userID)
    -	if err != nil {
    -		logging.WithFields("userID", userID).OnError(err).Error("could not load user for password complexity policy")
    -		return nil
    +	logging.WithFields("userID", userID).OnError(err).Error("could not load user for password complexity policy")
    +	if err == nil {
    +		resourceOwner = user.ResourceOwner
     	}
    -	policy, err := l.authRepo.GetMyPasswordComplexityPolicy(setContext(r.Context(), user.ResourceOwner))
    -	logging.WithFields("orgID", user.ResourceOwner, "userID", userID).OnError(err).Error("could not load password complexity policy")
    +	policy, err := l.authRepo.GetMyPasswordComplexityPolicy(setContext(r.Context(), resourceOwner))
    +	logging.WithFields("orgID", resourceOwner, "userID", userID).OnError(err).Error("could not load password complexity policy")
     	return policy
     }
    
0bb00dd9fc4e

fix(login): generalize error message on code verifications

https://github.com/zitadel/zitadelLivio SpringJan 14, 2026via ghsa
5 files changed · +42 10
  • internal/api/ui/login/init_password_handler.go+10 2 modified
    @@ -5,6 +5,9 @@ import (
     	"net/http"
     	"net/url"
     
    +	"github.com/zitadel/logging"
    +
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
     	"github.com/zitadel/zitadel/internal/domain"
     	"github.com/zitadel/zitadel/internal/zerrors"
    @@ -92,7 +95,8 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *dom
     	userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
     	_, err := l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID, false)
     	if err != nil {
    -		l.renderInitPassword(w, r, authReq, data.UserID, "", err)
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", data.UserID).WithError(err).Warn("error setting initial password")
    +		l.renderInitPassword(w, r, authReq, data.UserID, "", zerrors.ThrowInvalidArgument(nil, "LOGIN-as3k2", "Errors.User.Code.Invalid"))
     		return
     	}
     	l.renderInitPasswordDone(w, r, authReq, userOrg)
    @@ -108,7 +112,11 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
     		authReqID = authReq.ID
     	}
     	_, err := l.command.RequestSetPassword(setContext(r.Context(), userOrg), userID, userOrg, domain.NotificationTypeEmail, authReqID)
    -	l.renderInitPassword(w, r, authReq, userID, "", err)
    +	if !zerrors.IsInternal(err) {
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", userID).WithError(err).Warn("error requesting password reset")
    +		err = nil // we ignore errors on resend to not leak information
    +	}
    +	l.renderInitPassword(w, r, authReq, userID, "", nil)
     }
     
     func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code string, err error) {
    
  • internal/api/ui/login/init_user_handler.go+9 1 modified
    @@ -6,6 +6,9 @@ import (
     	"net/url"
     	"strconv"
     
    +	"github.com/zitadel/logging"
    +
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
     	"github.com/zitadel/zitadel/internal/domain"
     	"github.com/zitadel/zitadel/internal/zerrors"
    @@ -109,7 +112,8 @@ func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authRe
     	userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
     	err = l.command.HumanVerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, userOrgID, data.Code, data.Password, userAgentID, initCodeGenerator)
     	if err != nil {
    -		l.renderInitUser(w, r, authReq, data.UserID, data.LoginName, "", data.PasswordSet, err)
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", data.UserID).WithError(err).Warn("error verifying initial code")
    +		l.renderInitUser(w, r, authReq, data.UserID, data.LoginName, "", data.PasswordSet, zerrors.ThrowInvalidArgument(nil, "LOGIN-lk3os", "Errors.User.Code.Invalid"))
     		return
     	}
     	l.renderInitUserDone(w, r, authReq, userOrgID)
    @@ -127,6 +131,10 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *
     		return
     	}
     	_, err = l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID, initCodeGenerator, authRequestID)
    +	if !zerrors.IsInternal(err) {
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", userID).WithError(err).Warn("error requesting init mail")
    +		err = nil // we ignore errors on resend to not leak information
    +	}
     	l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err)
     }
     
    
  • internal/api/ui/login/invite_user_handler.go+9 1 modified
    @@ -5,6 +5,9 @@ import (
     	"net/http"
     	"net/url"
     
    +	"github.com/zitadel/logging"
    +
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
     	"github.com/zitadel/zitadel/internal/domain"
     	"github.com/zitadel/zitadel/internal/zerrors"
    @@ -98,7 +101,8 @@ func (l *Login) checkUserInviteCode(w http.ResponseWriter, r *http.Request, auth
     	userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
     	_, err := l.command.VerifyInviteCodeSetPassword(setUserContext(r.Context(), data.UserID, userOrgID), data.UserID, data.Code, data.Password, userAgentID)
     	if err != nil {
    -		l.renderInviteUser(w, r, authReq, data.UserID, data.OrgID, data.LoginName, "", err)
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", data.UserID).WithError(err).Warn("error verifying initial code")
    +		l.renderInviteUser(w, r, authReq, data.UserID, data.OrgID, data.LoginName, "", zerrors.ThrowInvalidArgument(nil, "LOGIN-sjk2o", "Errors.User.Code.Invalid"))
     		return
     	}
     	if authReq == nil {
    @@ -115,6 +119,10 @@ func (l *Login) resendUserInvite(w http.ResponseWriter, r *http.Request, authReq
     		authRequestID = authReq.ID
     	}
     	_, err := l.command.ResendInviteCode(setUserContext(r.Context(), userID, userOrgID), userID, userOrgID, authRequestID)
    +	if !zerrors.IsInternal(err) {
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", userID).WithError(err).Warn("error requesting invite code")
    +		err = nil // we ignore errors on resend to not leak information
    +	}
     	l.renderInviteUser(w, r, authReq, userID, orgID, loginName, "", err)
     }
     
    
  • internal/api/ui/login/mail_verify_handler.go+7 1 modified
    @@ -9,6 +9,7 @@ import (
     
     	"github.com/zitadel/logging"
     
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
     	"github.com/zitadel/zitadel/internal/domain"
     	"github.com/zitadel/zitadel/internal/zerrors"
    @@ -121,6 +122,10 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque
     		return
     	}
     	_, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg, emailCodeGenerator, authReqID)
    +	if !zerrors.IsInternal(err) {
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", data.UserID).WithError(err).Warn("error requesting mail verification")
    +		err = nil // we ignore errors on resend to not leak information
    +	}
     	l.renderMailVerification(w, r, authReq, data.UserID, "", data.PasswordInit, err)
     }
     
    @@ -138,7 +143,8 @@ func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *d
     	userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
     	_, err = l.command.VerifyHumanEmail(setContext(r.Context(), userOrg), userID, code, userOrg, password, userAgentID, emailCodeGenerator)
     	if err != nil {
    -		l.renderMailVerification(w, r, authReq, userID, "", password != "", err)
    +		logging.WithFields("instanceID", authz.GetInstance(r.Context()).InstanceID(), "userID", userID).WithError(err).Warn("error verifying email code")
    +		l.renderMailVerification(w, r, authReq, userID, "", password != "", zerrors.ThrowInvalidArgument(nil, "LOGIN-WSdsg", "Errors.User.Code.Invalid"))
     		return
     	}
     	l.renderMailVerified(w, r, authReq, userOrg)
    
  • internal/api/ui/login/password_complexity_policy_handler.go+7 5 modified
    @@ -5,6 +5,7 @@ import (
     
     	"github.com/zitadel/logging"
     
    +	"github.com/zitadel/zitadel/internal/api/authz"
     	iam_model "github.com/zitadel/zitadel/internal/iam/model"
     )
     
    @@ -22,12 +23,13 @@ func (l *Login) getPasswordComplexityPolicy(r *http.Request, orgID string) *iam_
     }
     
     func (l *Login) getPasswordComplexityPolicyByUserID(r *http.Request, userID string) *iam_model.PasswordComplexityPolicyView {
    +	resourceOwner := authz.GetInstance(r.Context()).DefaultOrganisationID()
     	user, err := l.query.GetUserByID(r.Context(), false, userID)
    -	if err != nil {
    -		logging.WithFields("userID", userID).OnError(err).Error("could not load user for password complexity policy")
    -		return nil
    +	logging.WithFields("userID", userID).OnError(err).Error("could not load user for password complexity policy")
    +	if err == nil {
    +		resourceOwner = user.ResourceOwner
     	}
    -	policy, err := l.authRepo.GetMyPasswordComplexityPolicy(setContext(r.Context(), user.ResourceOwner))
    -	logging.WithFields("orgID", user.ResourceOwner, "userID", userID).OnError(err).Error("could not load password complexity policy")
    +	policy, err := l.authRepo.GetMyPasswordComplexityPolicy(setContext(r.Context(), resourceOwner))
    +	logging.WithFields("orgID", resourceOwner, "userID", userID).OnError(err).Error("could not load password complexity policy")
     	return policy
     }
    
c300d4cc6a27

fix(login): correctly redirect to `/password` when `ignoreUnknownUsername` is set (#11130)

https://github.com/zitadel/zitadelMax PeintnerJan 14, 2026via ghsa
50 files changed · +3774 3336
  • apps/login/locales/de.json+6 6 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "Sitzung konnte nicht geladen werden",
           "couldNotLoadAuthMethods": "Authentifizierungsmethoden konnten nicht geladen werden",
           "failedPrecondition": "Vorbedingung fehlgeschlagen",
    -      "sessionNotValid": "Sitzung ist nicht gültig"
    +      "sessionNotValid": "Sitzung ist nicht gültig",
    +      "passwordVerificationMissing": "Passwort-Verifizierung fehlt.",
    +      "passwordVerificationTooOld": "Passwort-Verifizierung ist zu alt. Bitte verifizieren Sie Ihr Passwort erneut."
         }
       },
       "idp": {
    @@ -274,10 +276,7 @@
             "couldNotFindSession": "Sitzung konnte nicht gefunden werden",
             "couldNotUpdateSession": "Sitzung konnte nicht aktualisiert werden",
             "userNotFound": "Benutzer nicht im System gefunden",
    -        "couldNotDetermineRedirect": "Weiterleitungs-URL nach Passkey-Verifizierung konnte nicht ermittelt werden",
    -        "couldNotSetSession": "Sitzung konnte nicht gesetzt werden",
    -        "couldNotGetUser": "Benutzerdaten konnten nicht abgerufen werden",
    -        "couldNotRetrieveSessionCookie": "Sitzungs-Cookie konnte nicht abgerufen werden"
    +        "couldNotDetermineRedirect": "Weiterleitungs-URL nach Passkey-Verifizierung konnte nicht ermittelt werden"
           }
         },
         "set": {
    @@ -409,7 +408,8 @@
           "userAlreadyVerified": "Benutzer ist bereits verifiziert!",
           "couldNotResendInvite": "Einladung konnte nicht erneut gesendet werden",
           "inviteSendFailed": "Einladungs-E-Mail konnte nicht gesendet werden",
    -      "emailSendFailed": "Verifizierungs-E-Mail konnte nicht gesendet werden"
    +      "emailSendFailed": "Verifizierungs-E-Mail konnte nicht gesendet werden",
    +      "contextMissing": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
         }
       },
       "authenticator": {
    
  • apps/login/locales/en.json+5 2 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "Could not load session",
           "couldNotLoadAuthMethods": "Could not load auth methods",
           "failedPrecondition": "Failed precondition",
    -      "sessionNotValid": "Session is not valid"
    +      "sessionNotValid": "Session is not valid",
    +      "passwordVerificationMissing": "Password verification is missing.",
    +      "passwordVerificationTooOld": "Password verification is too old. Please verify your password again."
         }
       },
       "idp": {
    @@ -406,7 +408,8 @@
           "userAlreadyVerified": "User is already verified!",
           "couldNotResendInvite": "Could not resend invite",
           "inviteSendFailed": "Failed to send invitation email",
    -      "emailSendFailed": "Failed to send verification email"
    +      "emailSendFailed": "Failed to send verification email",
    +      "contextMissing": "An error occurred. Please try again."
         }
       },
       "authenticator": {
    
  • apps/login/locales/es.json+6 6 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "No se pudo cargar la sesión",
           "couldNotLoadAuthMethods": "No se pudieron cargar los métodos de autenticación",
           "failedPrecondition": "Precondición fallida",
    -      "sessionNotValid": "La sesión no es válida"
    +      "sessionNotValid": "La sesión no es válida",
    +      "passwordVerificationMissing": "Falta la verificación de la contraseña.",
    +      "passwordVerificationTooOld": "La verificación de la contraseña es demasiado antigua. Por favor, verifique su contraseña de nuevo."
         }
       },
       "idp": {
    @@ -274,10 +276,7 @@
             "couldNotFindSession": "No se pudo encontrar la sesión",
             "couldNotUpdateSession": "No se pudo actualizar la sesión",
             "userNotFound": "Usuario no encontrado en el sistema",
    -        "couldNotDetermineRedirect": "No se pudo determinar la URL de redirección después de la verificación de clave de acceso",
    -        "couldNotSetSession": "No se pudo establecer la sesión",
    -        "couldNotGetUser": "No se pudieron recuperar los datos del usuario",
    -        "couldNotRetrieveSessionCookie": "No se pudo recuperar la cookie de sesión"
    +        "couldNotDetermineRedirect": "No se pudo determinar la URL de redirección después de la verificación de clave de acceso"
           }
         },
         "set": {
    @@ -409,7 +408,8 @@
           "userAlreadyVerified": "¡El usuario ya está verificado!",
           "couldNotResendInvite": "No se pudo reenviar la invitación",
           "inviteSendFailed": "No se pudo enviar el correo de invitación",
    -      "emailSendFailed": "No se pudo enviar el correo de verificación"
    +      "emailSendFailed": "No se pudo enviar el correo electrónico de verificación",
    +      "contextMissing": "Ocurrió un error. Por favor inténtelo de nuevo."
         }
       },
       "authenticator": {
    
  • apps/login/locales/it.json+6 6 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "Impossibile caricare la sessione",
           "couldNotLoadAuthMethods": "Impossibile caricare i metodi di autenticazione",
           "failedPrecondition": "Precondizione non soddisfatta",
    -      "sessionNotValid": "La sessione non è valida"
    +      "sessionNotValid": "La sessione non è valida",
    +      "passwordVerificationMissing": "Manca la verifica della password.",
    +      "passwordVerificationTooOld": "La verifica della password è troppo vecchia. Si prega di verificare nuovamente la password."
         }
       },
       "idp": {
    @@ -274,10 +276,7 @@
             "couldNotFindSession": "Impossibile trovare la sessione",
             "couldNotUpdateSession": "Impossibile aggiornare la sessione",
             "userNotFound": "Utente non trovato nel sistema",
    -        "couldNotDetermineRedirect": "Impossibile determinare l'URL di reindirizzamento dopo la verifica passkey",
    -        "couldNotSetSession": "Impossibile impostare la sessione",
    -        "couldNotGetUser": "Impossibile recuperare i dati dell'utente",
    -        "couldNotRetrieveSessionCookie": "Impossibile recuperare il cookie di sessione"
    +        "couldNotDetermineRedirect": "Impossibile determinare l'URL di reindirizzamento dopo la verifica passkey"
           }
         },
         "set": {
    @@ -409,7 +408,8 @@
           "userAlreadyVerified": "L'utente è già verificato!",
           "couldNotResendInvite": "Impossibile reinviare l'invito",
           "inviteSendFailed": "Impossibile inviare l'email di invito",
    -      "emailSendFailed": "Impossibile inviare l'email di verifica"
    +      "emailSendFailed": "Impossibile inviare l'email di verifica",
    +      "contextMissing": "Si è verificato un errore. Riprova."
         }
       },
       "authenticator": {
    
  • apps/login/locales/ja.json+95 3 modified
    @@ -35,6 +35,22 @@
         },
         "required": {
           "loginName": "必須項目です"
    +    },
    +    "errors": {
    +      "internalError": "内部エラーが発生しました",
    +      "couldNotGetLoginSettings": "ログイン設定を取得できませんでした",
    +      "couldNotSearchUsers": "ユーザーを検索できませんでした",
    +      "couldNotGetDomain": "ドメインを取得できませんでした",
    +      "couldNotGetHost": "ホストを取得できませんでした",
    +      "couldNotStartIDPFlow": "IDPフローを開始できませんでした",
    +      "moreThanOneUserFound": "複数のユーザーが見つかりました。一意の識別子を指定してください。",
    +      "userNotFound": "ユーザーがシステムに見つかりません",
    +      "couldNotCreateSession": "ユーザーのセッションを作成できませんでした",
    +      "initialUserNotSupported": "初期ユーザーはサポートされていません",
    +      "usernamePasswordNotAllowed": "ユーザー名とパスワードは許可されていません!詳細については管理者にお問い合わせください。",
    +      "passkeysNotAllowed": "パスキーは許可されていません!詳細については管理者にお問い合わせください。",
    +      "couldNotFindIdentityProvider": "IDプロバイダーが見つかりませんでした。",
    +      "userNotActive": "ユーザーはアクティブではありません。詳細については管理者にお問い合わせください。"
         }
       },
       "password": {
    @@ -48,6 +64,13 @@
           },
           "required": {
             "password": "必須項目です"
    +      },
    +      "errors": {
    +        "couldNotVerifyPassword": "パスワードを確認できませんでした",
    +        "couldNotResetPassword": "パスワードをリセットできませんでした"
    +      },
    +      "info": {
    +        "passwordResetSent": "パスワードがリセットされました。メールを確認してください"
           }
         },
         "set": {
    @@ -66,6 +89,11 @@
             "code": "必須項目です",
             "newPassword": "パスワードを入力してください!",
             "confirmPassword": "必須項目です"
    +      },
    +      "errors": {
    +        "couldNotSetPassword": "パスワードを設定できませんでした",
    +        "couldNotResetPassword": "パスワードをリセットできませんでした",
    +        "couldNotVerifyPassword": "パスワードを確認できませんでした"
           }
         },
         "change": {
    @@ -79,7 +107,42 @@
           "required": {
             "newPassword": "新しいパスワードを入力してください!",
             "confirmPassword": "必須項目です"
    +      },
    +      "errors": {
    +        "couldNotChangePassword": "パスワードを変更できませんでした",
    +        "couldNotVerifyPassword": "パスワードを確認できませんでした",
    +        "unknownError": "不明なエラー"
           }
    +    },
    +    "errors": {
    +      "passwordVerificationMissing": "パスワード確認がありません。",
    +      "passwordVerificationTooOld": "パスワード確認が古すぎます。もう一度パスワードを確認してください。",
    +      "noHostFound": "ホストが見つかりません",
    +      "couldNotSendResetLink": "パスワードリセットリンクを送信できませんでした",
    +      "couldNotCreateSessionForUser": "ユーザーのセッションを作成できませんでした",
    +      "couldNotVerifyPassword": "パスワードを確認できませんでした",
    +      "failedToAuthenticate": "認証に失敗しました。パスワード試行回数 {maxPasswordAttempts} 回中 {failedAttempts} 回失敗しました。{lockoutMessage}",
    +      "failedToAuthenticateNoLimit": "認証に失敗しました。",
    +      "accountLockedContactAdmin": " アカウントのロックを解除するには管理者に連絡してください",
    +      "userNotFound": "ユーザーがシステムに見つかりません",
    +      "initialUserNotSupported": "初期ユーザーはサポートされていません",
    +      "userInitialStateNotSupported": "ユーザーの初期状態はサポートされていません",
    +      "codeOrVerificationRequired": "コードを提供するか、有効なユーザー確認チェックを行う必要があります",
    +      "verificationRequired": "ユーザー確認チェックを行う必要があります",
    +      "couldNotLoadSession": "セッションを読み込めませんでした",
    +      "couldNotLoadAuthMethods": "認証方法を読み込めませんでした",
    +      "failedPrecondition": "前提条件に失敗しました",
    +      "sessionNotValid": "セッションが無効です"
    +    },
    +    "complexity": {
    +      "length": "{minLength} 文字以上である必要があります。",
    +      "hasSymbol": "記号を含める必要があります。",
    +      "hasNumber": "数字を含める必要があります。",
    +      "hasUppercase": "大文字を含める必要があります。",
    +      "hasLowercase": "小文字を含める必要があります。",
    +      "equals": "パスワード確認が一致しました。",
    +      "matches": "一致します",
    +      "doesNotMatch": "一致しません"
         }
       },
       "idp": {
    @@ -194,7 +257,20 @@
           "title": "パスキーで認証",
           "description": "デバイスが指紋、顔、または画面ロックの入力を求めます",
           "usePassword": "パスワードを使用",
    -      "submit": "続行"
    +      "submit": "続行",
    +      "errors": {
    +        "couldNotRequestChallenge": "パスキーチャレンジを要求できませんでした",
    +        "couldNotVerifyPasskey": "パスキーを確認できませんでした",
    +        "noResponseReceived": "パスキー確認に失敗しました - 応答がありません",
    +        "noRedirectProvided": "パスキー確認は完了しましたが、リダイレクトが提供されませんでした",
    +        "couldNotRetrievePasskey": "パスキーの取得中にエラーが発生しました",
    +        "verificationCancelled": "パスキー確認がキャンセルされました",
    +        "verificationFailed": "パスキー確認中にエラーが発生しました",
    +        "couldNotFindSession": "セッションが見つかりませんでした",
    +        "couldNotUpdateSession": "セッションを更新できませんでした",
    +        "userNotFound": "ユーザーがシステムに見つかりません",
    +        "couldNotDetermineRedirect": "パスキー確認後のリダイレクトURLを決定できませんでした"
    +      }
         },
         "set": {
           "title": "パスキーの設定",
    @@ -261,6 +337,13 @@
           "firstname": "必須項目です",
           "lastname": "必須項目です",
           "email": "必須項目です"
    +    },
    +    "errors": {
    +      "couldNotCreateUser": "ユーザーを作成できませんでした",
    +      "couldNotCreateSession": "セッションを作成できませんでした",
    +      "userNotFound": "ユーザーがシステムに見つかりません",
    +      "couldNotLinkIDP": "IDPをユーザーにリンクできませんでした",
    +      "couldNotRegisterUser": "ユーザーを登録できませんでした"
         }
       },
       "invite": {
    @@ -318,7 +401,8 @@
           "userAlreadyVerified": "ユーザーは既に確認済みです!",
           "couldNotResendInvite": "招待を再送信できませんでした",
           "inviteSendFailed": "招待メールの送信に失敗しました",
    -      "emailSendFailed": "確認メールの送信に失敗しました"
    +      "emailSendFailed": "確認メールの送信に失敗しました",
    +      "contextMissing": "エラーが発生しました。もう一度お試しください。"
         }
       },
       "authenticator": {
    @@ -360,6 +444,14 @@
         "unknownContext": "ユーザーのコンテキストを取得できませんでした。最初にユーザー名を入力するか、検索パラメータとしてloginNameを指定してください。",
         "sessionExpired": "現在のセッションが期限切れです。再度ログインしてください。",
         "failedLoading": "データの読み込みに失敗しました。再試行してください。",
    -    "tryagain": "再試行"
    +    "tryagain": "再試行",
    +    "couldNotContinueSession": "セッションを継続できませんでした - ユーザー情報が不足しています"
    +  },
    +  "zitadel": {
    +    "errors": {
    +      "errorOccured": "エラーが発生しました",
    +      "multipleUsersFound": "複数のユーザーが見つかりました",
    +      "userNotFound": "ユーザーがシステムに見つかりません"
    +    }
       }
     }
    
  • apps/login/locales/pl.json+7 7 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "Nie można załadować sesji",
           "couldNotLoadAuthMethods": "Nie można załadować metod uwierzytelniania",
           "failedPrecondition": "Nie spełniono warunków wstępnych",
    -      "sessionNotValid": "Sesja jest nieważna"
    +      "sessionNotValid": "Sesja jest nieważna",
    +      "passwordVerificationMissing": "Brak weryfikacji hasła.",
    +      "passwordVerificationTooOld": "Weryfikacja hasła jest przestarzała. Proszę zweryfikować hasło ponownie."
         }
       },
       "idp": {
    @@ -274,10 +276,7 @@
             "couldNotFindSession": "Nie można znaleźć sesji",
             "couldNotUpdateSession": "Nie można zaktualizować sesji",
             "userNotFound": "Użytkownik nie znaleziony w systemie",
    -        "couldNotDetermineRedirect": "Nie można określić adresu URL przekierowania po weryfikacji klucza dostępu",
    -        "couldNotSetSession": "Nie można ustawić sesji",
    -        "couldNotGetUser": "Nie można pobrać danych użytkownika",
    -        "couldNotRetrieveSessionCookie": "Nie można pobrać pliku cookie sesji"
    +        "couldNotDetermineRedirect": "Nie można określić adresu URL przekierowania po weryfikacji klucza dostępu"
           }
         },
         "set": {
    @@ -409,7 +408,8 @@
           "userAlreadyVerified": "Użytkownik jest już zweryfikowany!",
           "couldNotResendInvite": "Nie udało się ponownie wysłać zaproszenia",
           "inviteSendFailed": "Nie udało się wysłać wiadomości e-mail z zaproszeniem",
    -      "emailSendFailed": "Nie udało się wysłać wiadomości e-mail weryfikacyjnej"
    +      "emailSendFailed": "Nie udało się wysłać wiadomości e-mail weryfikacyjnej",
    +      "contextMissing": "Wystąpił błąd. Spróbuj ponownie."
         }
       },
       "authenticator": {
    @@ -452,6 +452,6 @@
         "sessionExpired": "Twoja sesja wygasła. Zaloguj się ponownie.",
         "failedLoading": "Nie udało się załadować danych. Spróbuj ponownie.",
         "tryagain": "Spróbuj ponownie",
    -    "couldNotContinueSession": "Nie można kontynuować sesji - brakujące informacje o użytkowniku"
    +    "couldNotContinueSession": "Nie udało się kontynuować sesji - brak informacji o użytkowniku"
       }
     }
    
  • apps/login/locales/ru.json+6 6 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "Не удалось загрузить сессию",
           "couldNotLoadAuthMethods": "Не удалось загрузить методы аутентификации",
           "failedPrecondition": "Нарушено предварительное условие",
    -      "sessionNotValid": "Сессия недействительна"
    +      "sessionNotValid": "Сессия недействительна",
    +      "passwordVerificationMissing": "Отсутствует проверка пароля.",
    +      "passwordVerificationTooOld": "Проверка пароля устарела. Пожалуйста, подтвердите пароль еще раз."
         }
       },
       "idp": {
    @@ -274,10 +276,7 @@
             "couldNotFindSession": "Не удалось найти сеанс",
             "couldNotUpdateSession": "Не удалось обновить сеанс",
             "userNotFound": "Пользователь не найден в системе",
    -        "couldNotDetermineRedirect": "Не удалось определить URL перенаправления после проверки пасскей",
    -        "couldNotSetSession": "Не удалось установить сеанс",
    -        "couldNotGetUser": "Не удалось получить данные пользователя",
    -        "couldNotRetrieveSessionCookie": "Не удалось получить cookie сеанса"
    +        "couldNotDetermineRedirect": "Не удалось определить URL перенаправления после проверки пасскей"
           }
         },
         "set": {
    @@ -409,7 +408,8 @@
           "userAlreadyVerified": "Пользователь уже подтверждён!",
           "couldNotResendInvite": "Не удалось повторно отправить приглашение",
           "inviteSendFailed": "Не удалось отправить письмо с приглашением",
    -      "emailSendFailed": "Не удалось отправить письмо с подтверждением"
    +      "emailSendFailed": "Не удалось отправить письмо с подтверждением",
    +      "contextMissing": "Произошла ошибка. Пожалуйста, попробуйте еще раз."
         }
       },
       "authenticator": {
    
  • apps/login/locales/tr.json+41 17 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "Oturum yüklenemedi",
           "couldNotLoadAuthMethods": "Kimlik doğrulama yöntemleri yüklenemedi",
           "failedPrecondition": "Ön koşul başarısız",
    -      "sessionNotValid": "Oturum geçerli değil"
    +      "sessionNotValid": "Oturum geçerli değil",
    +      "passwordVerificationMissing": "Şifre doğrulaması eksik.",
    +      "passwordVerificationTooOld": "Şifre doğrulaması çok eski. Lütfen şifrenizi tekrar doğrulayın."
         }
       },
       "idp": {
    @@ -159,18 +161,6 @@
         "signInWithAzureAD": "AzureAD ile Giriş",
         "signInWithGithub": "GitHub ile Giriş",
         "signInWithGitlab": "GitLab ile Giriş",
    -    "loginSuccess": {
    -      "title": "Giriş başarılı",
    -      "description": "Başarıyla giriş yaptınız!"
    -    },
    -    "linkingSuccess": {
    -      "title": "Hesap bağlandı",
    -      "description": "Hesabınızı başarıyla bağladınız!"
    -    },
    -    "registerSuccess": {
    -      "title": "Kayıt başarılı",
    -      "description": "Başarıyla kayıt oldunuz!"
    -    },
         "loginError": {
           "title": "Giriş başarısız",
           "description": "Giriş yapılmaya çalışılırken bir hata oluştu."
    @@ -194,6 +184,23 @@
           "description": "Kayıt işlemini tamamlayamadık.",
           "info": "Kayıt için organizasyon belirlenemedi. Lütfen yardım için yöneticinizle iletişime geçin.",
           "backToLogin": "Girişe Dön"
    +    },
    +    "processing": {
    +      "message": "Kimlik doğrulama işleniyor...",
    +      "noRedirect": "Sunucudan yönlendirme veya hata dönmedi",
    +      "unexpectedError": "Beklenmeyen bir hata oluştu"
    +    },
    +    "errors": {
    +      "missingParameters": "Eksik gerekli parametreler",
    +      "missingIdpInfo": "IDP bilgisi eksik",
    +      "idpNotFound": "Kimlik sağlayıcı bulunamadı",
    +      "linkingNotAllowed": "Bu kimlik sağlayıcı için bağlantıya izin verilmiyor",
    +      "linkingFailed": "Kimlik sağlayıcı hesaba bağlanamadı",
    +      "autoLinkingFailed": "Hesap otomatik olarak bağlanamadı",
    +      "userCreationFailed": "Kullanıcı hesabı oluşturulamadı",
    +      "orgResolutionFailed": "Kayıt için organizasyon belirlenemedi",
    +      "sessionCreationFailed": "Oturum oluşturulamadı veya yönlendirme belirlenemedi",
    +      "unknownError": "Bilinmeyen bir hata oluştu"
         }
       },
       "ldap": {
    @@ -257,7 +264,20 @@
           "title": "Passkey ile kimlik doğrulama",
           "description": "Cihazınız parmak izinizi, yüzünüzü veya ekran kilidinizi isteyecektir",
           "usePassword": "Şifre kullan",
    -      "submit": "Devam"
    +      "submit": "Devam",
    +      "errors": {
    +        "couldNotRequestChallenge": "Passkey meydan okuması istenemedi",
    +        "couldNotVerifyPasskey": "Passkey doğrulanamadı",
    +        "noResponseReceived": "Passkey doğrulaması başarısız - yanıt alınamadı",
    +        "noRedirectProvided": "Passkey doğrulaması tamamlandı ancak yönlendirme sağlanmadı",
    +        "couldNotRetrievePasskey": "Passkey alınırken bir hata oluştu",
    +        "verificationCancelled": "Passkey doğrulaması iptal edildi",
    +        "verificationFailed": "Passkey doğrulaması sırasında bir hata oluştu",
    +        "couldNotFindSession": "Oturum bulunamadı",
    +        "couldNotUpdateSession": "Oturum güncellenemedi",
    +        "userNotFound": "Kullanıcı sistemde bulunamadı",
    +        "couldNotDetermineRedirect": "Passkey doğrulamasından sonra yönlendirme URL'si belirlenemedi"
    +      }
         },
         "set": {
           "title": "Passkey kur",
    @@ -386,7 +406,10 @@
           "couldNotCreateSession": "Oturum oluşturulamadı",
           "noHostFound": "Host bulunamadı",
           "userAlreadyVerified": "Kullanıcı zaten doğrulanmış!",
    -      "couldNotResendInvite": "Davet yeniden gönderilemedi"
    +      "couldNotResendInvite": "Davet yeniden gönderilemedi",
    +      "contextMissing": "Bir hata oluştu. Lütfen tekrar deneyin.",
    +      "inviteSendFailed": "Davet e-postası gönderilemedi",
    +      "emailSendFailed": "Doğrulama e-postası gönderilemedi"
         }
       },
       "authenticator": {
    @@ -428,6 +451,7 @@
         "unknownContext": "Kullanıcının bağlamı alınamadı. Önce kullanıcı adını girdiğinizden emin olun veya searchParam olarak bir loginName sağlayın.",
         "sessionExpired": "Mevcut oturumunuzun süresi doldu. Lütfen tekrar giriş yapın.",
         "failedLoading": "Veri yüklenemedi. Lütfen tekrar deneyin.",
    -    "tryagain": "Tekrar Dene"
    +    "tryagain": "Tekrar Dene",
    +    "couldNotContinueSession": "Oturuma devam edilemedi - kullanıcı bilgileri eksik"
       }
    -}
    \ No newline at end of file
    +}
    
  • apps/login/locales/uk.json+5 2 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "Не вдалося завантажити сеанс",
           "couldNotLoadAuthMethods": "Не вдалося завантажити методи автентифікації",
           "failedPrecondition": "Не виконано передумову",
    -      "sessionNotValid": "Сеанс недійсний"
    +      "sessionNotValid": "Сеанс недійсний",
    +      "passwordVerificationMissing": "Відсутня перевірка пароля.",
    +      "passwordVerificationTooOld": "Перевірка пароля застаріла. Будь ласка, перевірте пароль ще раз."
         }
       },
       "idp": {
    @@ -406,7 +408,8 @@
           "userAlreadyVerified": "Користувача вже підтверджено!",
           "couldNotResendInvite": "Не вдалося повторно надіслати запрошення",
           "inviteSendFailed": "Не вдалося надіслати лист-запрошення",
    -      "emailSendFailed": "Не вдалося надіслати лист підтвердження"
    +      "emailSendFailed": "Не вдалося надіслати лист підтвердження",
    +      "contextMissing": "Сталася помилка. Будь ласка, спробуйте ще раз."
         }
       },
       "authenticator": {
    
  • apps/login/locales/zh.json+6 6 modified
    @@ -147,7 +147,9 @@
           "couldNotLoadSession": "无法加载会话",
           "couldNotLoadAuthMethods": "无法加载验证方法",
           "failedPrecondition": "前置条件失败",
    -      "sessionNotValid": "会话无效"
    +      "sessionNotValid": "会话无效",
    +      "passwordVerificationMissing": "缺少密码验证。",
    +      "passwordVerificationTooOld": "密码验证已过期。请重新通过验证。"
         }
       },
       "idp": {
    @@ -274,10 +276,7 @@
             "couldNotFindSession": "找不到会话",
             "couldNotUpdateSession": "无法更新会话",
             "userNotFound": "系统中未找到用户",
    -        "couldNotDetermineRedirect": "密钥验证后无法确定重定向URL",
    -        "couldNotSetSession": "无法设置会话",
    -        "couldNotGetUser": "无法检索用户数据",
    -        "couldNotRetrieveSessionCookie": "无法检索会话Cookie"
    +        "couldNotDetermineRedirect": "密钥验证后无法确定重定向URL"
           }
         },
         "set": {
    @@ -409,7 +408,8 @@
           "userAlreadyVerified": "用户已验证!",
           "couldNotResendInvite": "无法重新发送邀请",
           "inviteSendFailed": "发送邀请邮件失败",
    -      "emailSendFailed": "发送验证邮件失败"
    +      "emailSendFailed": "发送验证邮件失败",
    +      "contextMissing": "发生错误。请重试。"
         }
       },
       "authenticator": {
    
  • apps/login/src/app/(login)/authenticator/set/page.tsx+5 0 modified
    @@ -77,6 +77,11 @@ export default async function Page(props: { searchParams: Promise<Record<string
     
       async function loadSessionById(sessionId: string, organization?: string) {
         const recent = await getSessionCookieById({ sessionId, organization });
    +
    +    if (!recent) {
    +      return undefined;
    +    }
    +
         const sessionResponse = await getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token });
         return getAuthMethodsAndUser(sessionResponse.session);
       }
    
  • apps/login/src/app/(login)/loginname/page.tsx+8 10 modified
    @@ -28,25 +28,22 @@ export default async function Page(props: { searchParams: Promise<Record<string
     
       let defaultOrganization;
       if (!organization) {
    -    const org: Organization | null = await getDefaultOrg({ serviceConfig, });
    +    const org: Organization | null = await getDefaultOrg({ serviceConfig });
         if (org) {
           defaultOrganization = org.id;
         }
       }
     
    -  const loginSettings = await getLoginSettings({ serviceConfig, organization: organization ?? defaultOrganization,
    -  });
    -
    -  const contextLoginSettings = await getLoginSettings({ serviceConfig, organization,
    -  });
    +  const loginSettings = await getLoginSettings({ serviceConfig, organization: organization ?? defaultOrganization });
     
    -  const identityProviders = await getActiveIdentityProviders({ serviceConfig, orgId: organization ?? defaultOrganization,
    +  const identityProviders = await getActiveIdentityProviders({
    +    serviceConfig,
    +    orgId: organization ?? defaultOrganization,
       }).then((resp) => {
         return resp.identityProviders;
       });
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization,
    -  });
    +  const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
     
       return (
         <DynamicTheme branding={branding}>
    @@ -64,7 +61,8 @@ export default async function Page(props: { searchParams: Promise<Record<string
               loginName={loginName}
               requestId={requestId}
               organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
    -          loginSettings={contextLoginSettings}
    +          defaultOrganization={defaultOrganization}
    +          loginSettings={loginSettings}
               suffix={suffix}
               submit={submit}
               allowRegister={!!loginSettings?.allowRegister}
    
  • apps/login/src/app/(login)/mfa/page.tsx+16 14 modified
    @@ -26,18 +26,19 @@ export default async function Page(props: { searchParams: Promise<Record<string
       const { serviceConfig } = getServiceConfig(_headers);
     
       const sessionFactors = sessionId
    -    ? await loadSessionById(serviceConfig.baseUrl, sessionId, organization)
    -    : await loadSessionByLoginname(serviceConfig.baseUrl, loginName, organization);
    +    ? await loadSessionById(sessionId, organization)
    +    : await loadSessionByLoginname(loginName, organization);
     
    -  async function loadSessionByLoginname(serviceUrl: string, loginName?: string, organization?: string) {
    -    return loadMostRecentSession({ serviceConfig, sessionParams: {
    +  async function loadSessionByLoginname(loginName?: string, organization?: string) {
    +    return loadMostRecentSession({
    +      serviceConfig,
    +      sessionParams: {
             loginName,
             organization,
           },
         }).then((session) => {
           if (session && session.factors?.user?.id) {
    -        return listAuthenticationMethodTypes({ serviceConfig, userId: session.factors.user.id,
    -        }).then((methods) => {
    +        return listAuthenticationMethodTypes({ serviceConfig, userId: session.factors.user.id }).then((methods) => {
               return {
                 factors: session?.factors,
                 authMethods: methods.authMethodTypes ?? [],
    @@ -47,14 +48,16 @@ export default async function Page(props: { searchParams: Promise<Record<string
         });
       }
     
    -  async function loadSessionById(host: string, sessionId: string, organization?: string) {
    +  async function loadSessionById(sessionId: string, organization?: string) {
         const recent = await getSessionCookieById({ sessionId, organization });
    -    return getSession({ serviceConfig, sessionId: recent.id,
    -      sessionToken: recent.token,
    -    }).then((response) => {
    +
    +    if (!recent) {
    +      return undefined;
    +    }
    +
    +    return getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token }).then((response) => {
           if (response?.session && response.session.factors?.user?.id) {
    -        return listAuthenticationMethodTypes({ serviceConfig, userId: response.session.factors.user.id,
    -        }).then((methods) => {
    +        return listAuthenticationMethodTypes({ serviceConfig, userId: response.session.factors.user.id }).then((methods) => {
               return {
                 factors: response.session?.factors,
                 authMethods: methods.authMethodTypes ?? [],
    @@ -64,8 +67,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
         });
       }
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization,
    -  });
    +  const branding = await getBrandingSettings({ serviceConfig, organization });
     
       return (
         <DynamicTheme branding={branding}>
    
  • apps/login/src/app/(login)/mfa/set/page.tsx+20 11 modified
    @@ -30,8 +30,12 @@ function isSessionValid(session: Partial<Session>): {
       verifiedAt?: Timestamp;
     } {
       const validPassword = session?.factors?.password?.verifiedAt;
    -  const validPasskey = session?.factors?.webAuthN?.verifiedAt;
    +  const validPasskey =
    +    session?.factors?.webAuthN?.verifiedAt && !!session?.factors?.webAuthN?.userVerified
    +      ? session?.factors?.webAuthN?.verifiedAt
    +      : undefined;
       const validIDP = session?.factors?.intent?.verifiedAt;
    +
       const stillValid = session.expirationDate ? timestampDate(session.expirationDate) > new Date() : true;
     
       const verifiedAt = validPassword || validPasskey || validIDP;
    @@ -59,8 +63,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
           throw Error("Could not get user id from session");
         }
     
    -    return listAuthenticationMethodTypes({ serviceConfig, userId,
    -    }).then((methods) => {
    +    return listAuthenticationMethodTypes({ serviceConfig, userId }).then((methods) => {
           return getUserByID({ serviceConfig, userId }).then((user) => {
             const humanUser = user.user?.type.case === "human" ? user.user?.type.value : undefined;
     
    @@ -77,7 +80,9 @@ export default async function Page(props: { searchParams: Promise<Record<string
       }
     
       async function loadSessionByLoginname(loginName?: string, organization?: string) {
    -    return loadMostRecentSession({ serviceConfig, sessionParams: {
    +    return loadMostRecentSession({
    +      serviceConfig,
    +      sessionParams: {
             loginName,
             organization,
           },
    @@ -88,19 +93,23 @@ export default async function Page(props: { searchParams: Promise<Record<string
     
       async function loadSessionById(sessionId: string, organization?: string) {
         const recent = await getSessionCookieById({ sessionId, organization });
    -    return getSession({ serviceConfig, sessionId: recent.id,
    -      sessionToken: recent.token,
    -    }).then((sessionResponse) => {
    +
    +    if (!recent) {
    +      return undefined;
    +    }
    +
    +    return getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token }).then((sessionResponse) => {
           return getAuthMethodsAndUser(sessionResponse.session);
         });
       }
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization,
    -  });
    -  const loginSettings = await getLoginSettings({ serviceConfig, organization: sessionWithData.factors?.user?.organizationId,
    +  const branding = await getBrandingSettings({ serviceConfig, organization });
    +  const loginSettings = await getLoginSettings({
    +    serviceConfig,
    +    organization: sessionWithData?.factors?.user?.organizationId,
       });
     
    -  const { valid } = isSessionValid(sessionWithData);
    +  const { valid } = sessionWithData ? isSessionValid(sessionWithData) : { valid: false };
     
       return (
         <DynamicTheme branding={branding}>
    
  • apps/login/src/app/(login)/otp/[method]/page.tsx+5 0 modified
    @@ -44,6 +44,11 @@ export default async function Page(props: {
     
       async function loadSessionById(sessionId: string, organization?: string) {
         const recent = await getSessionCookieById({ sessionId, organization });
    +
    +    if (!recent) {
    +      return undefined;
    +    }
    +
         return getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token }).then((response) => {
           if (response?.session) {
             return response.session;
    
  • apps/login/src/app/(login)/passkey/page.tsx+36 12 modified
    @@ -6,7 +6,8 @@ import { UserAvatar } from "@/components/user-avatar";
     import { getSessionCookieById } from "@/lib/cookies";
     import { getServiceConfig } from "@/lib/service-url";
     import { loadMostRecentSession } from "@/lib/session";
    -import { getBrandingSettings, getSession } from "@/lib/zitadel";
    +import { getBrandingSettings, getDefaultOrg, getSession } from "@/lib/zitadel";
    +import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
     import { Metadata } from "next";
     import { getTranslations } from "next-intl/server";
     import { headers } from "next/headers";
    @@ -24,23 +25,44 @@ export default async function Page(props: { searchParams: Promise<Record<string
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
     
    -  const sessionFactors = sessionId
    -    ? await loadSessionById(serviceConfig.baseUrl, sessionId, organization)
    -    : await loadMostRecentSession({ serviceConfig, sessionParams: { loginName, organization },
    -      });
    +  let defaultOrganization;
    +  if (!organization) {
    +    const org: Organization | null = await getDefaultOrg({ serviceConfig });
     
    -  async function loadSessionById(serviceUrl: string, sessionId: string, organization?: string) {
    +    if (org) {
    +      defaultOrganization = org.id;
    +    }
    +  }
    +
    +  let sessionFactors = sessionId ? await loadSessionById(sessionId, organization) : undefined;
    +
    +  if (!sessionFactors && !sessionId) {
    +    sessionFactors = await loadMostRecentSession({
    +      serviceConfig,
    +      sessionParams: { loginName, organization },
    +    }).catch(() => {
    +      // ignore error
    +      return undefined;
    +    });
    +  }
    +
    +  async function loadSessionById(sessionId: string, organization?: string) {
         const recent = await getSessionCookieById({ sessionId, organization });
    -    return getSession({ serviceConfig, sessionId: recent.id,
    -      sessionToken: recent.token,
    -    }).then((response) => {
    +
    +    if (!recent) {
    +      return undefined;
    +    }
    +
    +    return getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token }).then((response) => {
           if (response?.session) {
             return response.session;
           }
         });
       }
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization,
    +  const branding = await getBrandingSettings({
    +    serviceConfig,
    +    organization: organization ?? sessionFactors?.factors?.user?.organizationId ?? defaultOrganization,
       });
     
       return (
    @@ -54,14 +76,16 @@ export default async function Page(props: { searchParams: Promise<Record<string
               <Translated i18nKey="verify.description" namespace="passkey" />
             </p>
     
    -        {sessionFactors && (
    +        {sessionFactors ? (
               <UserAvatar
                 loginName={loginName ?? sessionFactors.factors?.user?.loginName}
                 displayName={sessionFactors.factors?.user?.displayName}
                 showDropdown
                 searchParams={searchParams}
               ></UserAvatar>
    -        )}
    +        ) : loginName ? (
    +          <UserAvatar loginName={loginName} displayName={loginName} showDropdown searchParams={searchParams}></UserAvatar>
    +        ) : null}
           </div>
     
           <div className="w-full">
    
  • apps/login/src/app/(login)/password/change/page.tsx+14 7 modified
    @@ -24,19 +24,24 @@ export default async function Page(props: { searchParams: Promise<Record<string
       const { loginName, organization, requestId } = searchParams;
     
       // also allow no session to be found (ignoreUnkownUsername)
    -  const sessionFactors = await loadMostRecentSession({ serviceConfig, sessionParams: {
    +  const sessionFactors = await loadMostRecentSession({
    +    serviceConfig,
    +    sessionParams: {
           loginName,
           organization,
         },
       });
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization,
    -  });
    +  const branding = await getBrandingSettings({ serviceConfig, organization });
     
    -  const passwordComplexity = await getPasswordComplexitySettings({ serviceConfig, organization: sessionFactors?.factors?.user?.organizationId,
    +  const passwordComplexity = await getPasswordComplexitySettings({
    +    serviceConfig,
    +    organization: sessionFactors?.factors?.user?.organizationId,
       });
     
    -  const loginSettings = await getLoginSettings({ serviceConfig, organization: sessionFactors?.factors?.user?.organizationId,
    +  const loginSettings = await getLoginSettings({
    +    serviceConfig,
    +    organization: sessionFactors?.factors?.user?.organizationId,
       });
     
       return (
    @@ -58,14 +63,16 @@ export default async function Page(props: { searchParams: Promise<Record<string
               </div>
             )}
     
    -        {sessionFactors && (
    +        {sessionFactors ? (
               <UserAvatar
                 loginName={loginName ?? sessionFactors.factors?.user?.loginName}
                 displayName={sessionFactors.factors?.user?.displayName}
                 showDropdown
                 searchParams={searchParams}
               ></UserAvatar>
    -        )}
    +        ) : loginName ? (
    +          <UserAvatar loginName={loginName} displayName={loginName} showDropdown searchParams={searchParams}></UserAvatar>
    +        ) : null}
           </div>
     
           <div className="w-full">
    
  • apps/login/src/app/(login)/password/page.tsx+15 6 modified
    @@ -25,7 +25,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
     
       let defaultOrganization;
       if (!organization) {
    -    const org: Organization | null = await getDefaultOrg({ serviceConfig, });
    +    const org: Organization | null = await getDefaultOrg({ serviceConfig });
     
         if (org) {
           defaultOrganization = org.id;
    @@ -35,7 +35,9 @@ export default async function Page(props: { searchParams: Promise<Record<string
       // also allow no session to be found (ignoreUnkownUsername)
       let sessionFactors;
       try {
    -    sessionFactors = await loadMostRecentSession({ serviceConfig, sessionParams: {
    +    sessionFactors = await loadMostRecentSession({
    +      serviceConfig,
    +      sessionParams: {
             loginName,
             organization,
           },
    @@ -45,9 +47,13 @@ export default async function Page(props: { searchParams: Promise<Record<string
         console.warn(error);
       }
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization,
    +  const branding = await getBrandingSettings({
    +    serviceConfig,
    +    organization: organization ?? sessionFactors?.factors?.user?.organizationId ?? defaultOrganization,
       });
    -  const loginSettings = await getLoginSettings({ serviceConfig, organization: organization ?? defaultOrganization,
    +  const loginSettings = await getLoginSettings({
    +    serviceConfig,
    +    organization: organization ?? sessionFactors?.factors?.user?.organizationId ?? defaultOrganization,
       });
     
       return (
    @@ -60,14 +66,16 @@ export default async function Page(props: { searchParams: Promise<Record<string
               <Translated i18nKey="verify.description" namespace="password" />
             </p>
     
    -        {sessionFactors && (
    +        {sessionFactors ? (
               <UserAvatar
                 loginName={loginName ?? sessionFactors.factors?.user?.loginName}
                 displayName={sessionFactors.factors?.user?.displayName}
                 showDropdown
                 searchParams={searchParams}
               ></UserAvatar>
    -        )}
    +        ) : loginName ? (
    +          <UserAvatar loginName={loginName} displayName={loginName} showDropdown searchParams={searchParams}></UserAvatar>
    +        ) : null}
           </div>
     
           <div className="w-full">
    @@ -85,6 +93,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
                 loginName={loginName}
                 requestId={requestId}
                 organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
    +            defaultOrganization={defaultOrganization}
                 loginSettings={loginSettings}
               />
             )}
    
  • apps/login/src/app/(login)/password/set/page.tsx+64 22 modified
    @@ -4,13 +4,22 @@ import { SetPasswordForm } from "@/components/set-password-form";
     import { Translated } from "@/components/translated";
     import { UserAvatar } from "@/components/user-avatar";
     import { getServiceConfig } from "@/lib/service-url";
    +import { UNKNOWN_USER_ID } from "@/lib/constants";
     import { loadMostRecentSession } from "@/lib/session";
    -import { getBrandingSettings, getLoginSettings, getPasswordComplexitySettings, getUserByID } from "@/lib/zitadel";
    +import {
    +  getBrandingSettings,
    +  getDefaultOrg,
    +  getLoginSettings,
    +  getPasswordComplexitySettings,
    +  getUserByID,
    +  searchUsers,
    +} from "@/lib/zitadel";
     import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
    -import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
    +import { User } from "@zitadel/proto/zitadel/user/v2/user_pb";
     import { Metadata } from "next";
     import { getTranslations } from "next-intl/server";
     import { headers } from "next/headers";
    +import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
     
     export async function generateMetadata(): Promise<Metadata> {
       const t = await getTranslations("password");
    @@ -20,39 +29,74 @@ export async function generateMetadata(): Promise<Metadata> {
     export default async function Page(props: { searchParams: Promise<Record<string | number | symbol, string | undefined>> }) {
       const searchParams = await props.searchParams;
     
    -  const { userId, loginName, organization, requestId, code, initial } = searchParams;
    +  let { userId, loginName, organization, requestId, code, initial } = searchParams;
     
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
     
    +  let defaultOrganization;
    +  if (!organization) {
    +    const org: Organization | null = await getDefaultOrg({ serviceConfig });
    +    if (org) {
    +      defaultOrganization = org.id;
    +    }
    +  }
    +
       // also allow no session to be found (ignoreUnkownUsername)
       let session: Session | undefined;
       if (loginName) {
    -    session = await loadMostRecentSession({ serviceConfig, sessionParams: {
    +    session = await loadMostRecentSession({
    +      serviceConfig,
    +      sessionParams: {
             loginName,
             organization,
           },
         });
       }
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization,
    -  });
    +  const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
     
    -  const passwordComplexity = await getPasswordComplexitySettings({ serviceConfig, organization: session?.factors?.user?.organizationId,
    +  const passwordComplexity = await getPasswordComplexitySettings({
    +    serviceConfig,
    +    organization: organization ?? session?.factors?.user?.organizationId ?? defaultOrganization,
       });
     
    -  const loginSettings = await getLoginSettings({ serviceConfig, organization,
    +  const loginSettings = await getLoginSettings({
    +    serviceConfig,
    +    organization: organization ?? session?.factors?.user?.organizationId ?? defaultOrganization,
       });
     
    +  if (!loginSettings) {
    +    return (
    +      <DynamicTheme branding={branding}>
    +        <div className="mx-auto flex max-w-sm flex-col space-y-4 pt-4">
    +          <Alert>
    +            <Translated i18nKey="errors.couldNotGetLoginSettings" namespace="loginname" />
    +          </Alert>
    +        </div>
    +      </DynamicTheme>
    +    );
    +  }
    +
       let user: User | undefined;
    -  let displayName: string | undefined;
       if (userId) {
    -    const userResponse = await getUserByID({ serviceConfig, userId,
    -    });
    +    const userResponse = await getUserByID({ serviceConfig, userId });
         user = userResponse.user;
    +  } else if (loginName) {
    +    const users = await searchUsers({
    +      serviceConfig,
    +      searchValue: loginName,
    +      loginSettings: loginSettings,
    +      organizationId: organization,
    +    });
     
    -    if (user?.type.case === "human") {
    -      displayName = (user.type.value as HumanUser).profile?.displayName;
    +    if (users.result && users.result.length === 1) {
    +      const foundUser = users.result[0];
    +      userId = foundUser.userId;
    +      user = foundUser;
    +    } else if (loginSettings?.ignoreUnknownUsernames) {
    +      // Prevent enumeration by pretending we found a user
    +      userId = UNKNOWN_USER_ID;
         }
       }
     
    @@ -80,13 +124,8 @@ export default async function Page(props: { searchParams: Promise<Record<string
                 showDropdown
                 searchParams={searchParams}
               ></UserAvatar>
    -        ) : user ? (
    -          <UserAvatar
    -            loginName={user?.preferredLoginName}
    -            displayName={displayName}
    -            showDropdown
    -            searchParams={searchParams}
    -          ></UserAvatar>
    +        ) : loginName ? (
    +          <UserAvatar loginName={loginName} displayName={loginName} showDropdown searchParams={searchParams}></UserAvatar>
             ) : null}
           </div>
     
    @@ -97,13 +136,16 @@ export default async function Page(props: { searchParams: Promise<Record<string
               </Alert>
             )}
     
    -        {passwordComplexity && (loginName ?? user?.preferredLoginName) && (userId ?? session?.factors?.user?.id) ? (
    +        {passwordComplexity &&
    +        (loginName ?? user?.preferredLoginName) &&
    +        (userId ?? session?.factors?.user?.id ?? (loginSettings?.ignoreUnknownUsernames ? UNKNOWN_USER_ID : undefined)) ? (
               <SetPasswordForm
                 code={code}
    -            userId={userId ?? (session?.factors?.user?.id as string)}
    +            userId={userId ?? (session?.factors?.user?.id as string) ?? UNKNOWN_USER_ID}
                 loginName={loginName ?? (user?.preferredLoginName as string)}
                 requestId={requestId}
                 organization={organization}
    +            defaultOrganization={defaultOrganization}
                 passwordComplexitySettings={passwordComplexity}
                 codeRequired={!(initial === "true")}
               />
    
  • apps/login/src/app/(login)/signedin/page.tsx+31 29 modified
    @@ -20,9 +20,12 @@ export async function generateMetadata(): Promise<Metadata> {
     
     async function loadSessionById(serviceConfig: ServiceConfig, sessionId: string, organization?: string) {
       const recent = await getSessionCookieById({ sessionId, organization });
    -  return getSession({ serviceConfig, sessionId: recent.id,
    -    sessionToken: recent.token,
    -  }).then((response) => {
    +
    +  if (!recent) {
    +    return undefined;
    +  }
    +
    +  return getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token }).then((response) => {
         if (response?.session) {
           return response.session;
         }
    @@ -37,8 +40,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
     
       const { loginName, requestId, organization, sessionId } = searchParams;
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization,
    -  });
    +  const branding = await getBrandingSettings({ serviceConfig, organization });
     
       // complete device authorization flow if device requestId is present
       if (requestId && requestId.startsWith("device_")) {
    @@ -49,36 +51,36 @@ export default async function Page(props: { searchParams: Promise<any> }) {
               organization: organization,
             });
     
    -    await completeDeviceAuthorization(requestId.replace("device_", ""), {
    -      sessionId: cookie.id,
    -      sessionToken: cookie.token,
    -    }).catch((err) => {
    -      return (
    -        <DynamicTheme branding={branding}>
    -          <div className="flex flex-col space-y-4">
    -            <h1>
    -              <Translated i18nKey="error.title" namespace="signedin" />
    -            </h1>
    -            <p className="ztdl-p mb-6 block">
    -              <Translated i18nKey="error.description" namespace="signedin" />
    -            </p>
    -            <Alert>{err.message}</Alert>
    -          </div>
    -          <div className="w-full"></div>
    -        </DynamicTheme>
    -      );
    -    });
    +    if (cookie) {
    +      await completeDeviceAuthorization(requestId.replace("device_", ""), {
    +        sessionId: cookie.id,
    +        sessionToken: cookie.token,
    +      }).catch((err) => {
    +        return (
    +          <DynamicTheme branding={branding}>
    +            <div className="flex flex-col space-y-4">
    +              <h1>
    +                <Translated i18nKey="error.title" namespace="signedin" />
    +              </h1>
    +              <p className="ztdl-p mb-6 block">
    +                <Translated i18nKey="error.description" namespace="signedin" />
    +              </p>
    +              <Alert>{err.message}</Alert>
    +            </div>
    +            <div className="w-full"></div>
    +          </DynamicTheme>
    +        );
    +      });
    +    }
       }
     
       const sessionFactors = sessionId
         ? await loadSessionById(serviceConfig, sessionId, organization)
    -    : await loadMostRecentSession({ serviceConfig, sessionParams: { loginName, organization },
    -      });
    +    : await loadMostRecentSession({ serviceConfig, sessionParams: { loginName, organization } });
     
       let loginSettings;
       if (!requestId) {
    -    loginSettings = await getLoginSettings({ serviceConfig, organization,
    -    });
    +    loginSettings = await getLoginSettings({ serviceConfig, organization });
       }
     
       return (
    @@ -93,7 +95,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
     
             <UserAvatar
               loginName={loginName ?? sessionFactors?.factors?.user?.loginName}
    -          displayName={sessionFactors?.factors?.user?.displayName}
    +          displayName={sessionFactors?.factors?.user?.displayName ?? loginName}
               showDropdown={!(requestId && requestId.startsWith("device_"))}
               searchParams={searchParams}
             />
    
  • apps/login/src/app/(login)/u2f/page.tsx+8 7 modified
    @@ -24,19 +24,20 @@ export default async function Page(props: { searchParams: Promise<Record<string
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
     
    -  const branding = await getBrandingSettings({ serviceConfig, organization,
    -  });
    +  const branding = await getBrandingSettings({ serviceConfig, organization });
     
       const sessionFactors = sessionId
         ? await loadSessionById(sessionId, organization)
    -    : await loadMostRecentSession({ serviceConfig, sessionParams: { loginName, organization },
    -      });
    +    : await loadMostRecentSession({ serviceConfig, sessionParams: { loginName, organization } });
     
       async function loadSessionById(sessionId: string, organization?: string) {
         const recent = await getSessionCookieById({ sessionId, organization });
    -    return getSession({ serviceConfig, sessionId: recent.id,
    -      sessionToken: recent.token,
    -    }).then((response) => {
    +
    +    if (!recent) {
    +      return undefined;
    +    }
    +
    +    return getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token }).then((response) => {
           if (response?.session) {
             return response.session;
           }
    
  • apps/login/src/app/(login)/verify/page.tsx+64 13 modified
    @@ -3,12 +3,14 @@ import { DynamicTheme } from "@/components/dynamic-theme";
     import { Translated } from "@/components/translated";
     import { UserAvatar } from "@/components/user-avatar";
     import { VerifyForm } from "@/components/verify-form";
    +import { UNKNOWN_USER_ID } from "@/lib/constants";
     import { getPublicHostWithProtocol } from "@/lib/server/host";
     import { sendEmailCode, sendInviteEmailCode } from "@/lib/server/verify";
     import { getServiceConfig } from "@/lib/service-url";
     import { loadMostRecentSession } from "@/lib/session";
    -import { getBrandingSettings, getUserByID } from "@/lib/zitadel";
    +import { getBrandingSettings, getLoginSettings, getUserByID, searchUsers } from "@/lib/zitadel";
     import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
    +import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
     import { Metadata } from "next";
     import { getTranslations } from "next-intl/server";
     import { headers } from "next/headers";
    @@ -32,6 +34,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
       let user: User | undefined;
       let human: HumanUser | undefined;
       let id: string | undefined;
    +  let loginSettings: LoginSettings | undefined;
     
       let error: string | undefined;
     
    @@ -72,6 +75,13 @@ export default async function Page(props: { searchParams: Promise<any> }) {
             loginName,
             organization,
           },
    +    }).catch(async (error) => {
    +      loginSettings = await getLoginSettings({ serviceConfig, organization });
    +      if (!loginSettings?.ignoreUnknownUsernames) {
    +        console.error("loadMostRecentSession failed", error);
    +      }
    +      // ignore error, as we might not have a session yet
    +      return undefined;
         });
     
         if (doSend && sessionFactors?.factors?.user?.id) {
    @@ -93,8 +103,39 @@ export default async function Page(props: { searchParams: Promise<any> }) {
     
       id = userId ?? sessionFactors?.factors?.user?.id;
     
    -  if (!id) {
    -    throw Error("Failed to get user id");
    +  if (!id && loginName) {
    +    if (!loginSettings) {
    +      loginSettings = await getLoginSettings({ serviceConfig, organization });
    +    }
    +
    +    if (!loginSettings) {
    +      console.error("loginSettings not found");
    +      return;
    +    }
    +
    +    const users = await searchUsers({
    +      serviceConfig,
    +      searchValue: loginName,
    +      loginSettings: loginSettings,
    +      organizationId: organization,
    +    });
    +
    +    if (users.result && users.result.length === 1) {
    +      const foundUser = users.result[0];
    +      id = foundUser.userId;
    +      user = foundUser;
    +      if (user.type.case === "human") {
    +        human = user.type.value as HumanUser;
    +      }
    +
    +      // If we found the user and need to send email, do it now
    +      if (doSend && id) {
    +        await sendEmail(id);
    +      }
    +    } else if (loginSettings?.ignoreUnknownUsernames) {
    +      // Prevent enumeration by pretending we found a user
    +      id = UNKNOWN_USER_ID;
    +    }
       }
     
       const params = new URLSearchParams({
    @@ -132,8 +173,16 @@ export default async function Page(props: { searchParams: Promise<any> }) {
                 searchParams={searchParams}
               ></UserAvatar>
             ) : (
    -          user && (
    -            <UserAvatar loginName={user.preferredLoginName} displayName={human?.profile?.displayName} showDropdown={false} />
    +          (user || loginName) && (
    +            <UserAvatar
    +              loginName={loginName ?? user?.preferredLoginName}
    +              displayName={
    +                !loginSettings?.ignoreUnknownUsernames
    +                  ? human?.profile?.displayName
    +                  : (loginName ?? user?.preferredLoginName)
    +              }
    +              showDropdown={false}
    +            />
               )
             )}
           </div>
    @@ -163,14 +212,16 @@ export default async function Page(props: { searchParams: Promise<any> }) {
               </div>
             )}
     
    -        <VerifyForm
    -          loginName={loginName}
    -          organization={organization}
    -          userId={id}
    -          code={code}
    -          isInvite={invite === "true"}
    -          requestId={requestId}
    -        />
    +        {id && (
    +          <VerifyForm
    +            loginName={loginName}
    +            organization={organization}
    +            userId={id}
    +            code={code}
    +            isInvite={invite === "true"}
    +            requestId={requestId}
    +          />
    +        )}
           </div>
         </DynamicTheme>
       );
    
  • apps/login/src/components/login-otp.tsx+3 3 modified
    @@ -1,7 +1,7 @@
     "use client";
     
     import { completeFlowOrGetUrl } from "@/lib/client";
    -import { updateSession } from "@/lib/server/session";
    +import { updateOrCreateSession } from "@/lib/server/session";
     import { create } from "@zitadel/client";
     import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
     import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
    @@ -94,7 +94,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization,
         }
     
         setLoading(true);
    -    const response = await updateSession({
    +    const response = await updateOrCreateSession({
           loginName,
           sessionId,
           organization,
    @@ -151,7 +151,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization,
           });
         }
     
    -    const response = await updateSession({
    +    const response = await updateOrCreateSession({
           loginName,
           sessionId,
           organization,
    
  • apps/login/src/components/login-passkey.test.tsx+3 3 modified
    @@ -17,7 +17,7 @@ vi.mock("@/lib/server/passkeys", () => ({
     }));
     
     vi.mock("@/lib/server/session", () => ({
    -  updateSession: vi.fn(),
    +  updateOrCreateSession: vi.fn(),
     }));
     
     // Mock navigator.credentials
    @@ -67,10 +67,10 @@ describe("LoginPasskey Component", () => {
         mockCredentialsGet.mockClear();
     
         const { sendPasskey } = await import("@/lib/server/passkeys");
    -    const { updateSession } = await import("@/lib/server/session");
    +    const { updateOrCreateSession } = await import("@/lib/server/session");
     
         mockSendPasskey = vi.mocked(sendPasskey);
    -    mockUpdateSession = vi.mocked(updateSession);
    +    mockUpdateSession = vi.mocked(updateOrCreateSession);
       });
     
       describe("Initialization and Challenge Request", () => {
    
  • apps/login/src/components/login-passkey.tsx+13 12 modified
    @@ -2,7 +2,7 @@
     
     import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
     import { sendPasskey } from "@/lib/server/passkeys";
    -import { updateSession } from "@/lib/server/session";
    +import { updateOrCreateSession } from "@/lib/server/session";
     import { create, JsonObject } from "@zitadel/client";
     import { RequestChallengesSchema, UserVerificationRequirement } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
     import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
    @@ -38,7 +38,7 @@ export function LoginPasskey({ loginName, sessionId, requestId, altPassword, org
         if (!initialized.current) {
           initialized.current = true;
           setLoading(true);
    -      updateSessionForChallenge()
    +      updateOrCreateSessionForChallenge()
             .then((response) => {
               const pK = response?.challenges?.webAuthN?.publicKeyCredentialRequestOptions?.publicKey;
     
    @@ -50,15 +50,15 @@ export function LoginPasskey({ loginName, sessionId, requestId, altPassword, org
     
               return submitLoginAndContinue(pK)
                 .catch((error) => {
    -              setError(error);
    +              setError(error instanceof Error ? error.message : String(error));
                   return;
                 })
                 .finally(() => {
                   setLoading(false);
                 });
             })
             .catch((error) => {
    -          setError(error);
    +          setError(error instanceof Error ? error.message : String(error));
               return;
             })
             .finally(() => {
    @@ -68,14 +68,14 @@ export function LoginPasskey({ loginName, sessionId, requestId, altPassword, org
         // eslint-disable-next-line react-hooks/exhaustive-deps
       }, []);
     
    -  async function updateSessionForChallenge(
    +  async function updateOrCreateSessionForChallenge(
         userVerificationRequirement: number = login
           ? UserVerificationRequirement.REQUIRED
           : UserVerificationRequirement.DISCOURAGED,
       ) {
         setError("");
         setLoading(true);
    -    const session = await updateSession({
    +    const sessionResponse = await updateOrCreateSession({
           loginName,
           sessionId,
           organization,
    @@ -87,20 +87,21 @@ export function LoginPasskey({ loginName, sessionId, requestId, altPassword, org
           }),
           requestId,
         })
    -      .catch(() => {
    +      .catch((error) => {
    +        console.error(error);
             setError(t("verify.errors.couldNotRequestChallenge"));
             return;
           })
           .finally(() => {
             setLoading(false);
           });
     
    -    if (session && "error" in session && session.error) {
    -      setError(session.error);
    +    if (sessionResponse && "error" in sessionResponse && sessionResponse.error) {
    +      setError(sessionResponse.error);
           return;
         }
     
    -    return session;
    +    return sessionResponse;
       }
     
       async function submitLogin(data: JsonObject) {
    @@ -238,7 +239,7 @@ export function LoginPasskey({ loginName, sessionId, requestId, altPassword, org
               variant={ButtonVariants.Primary}
               disabled={loading}
               onClick={async () => {
    -            const response = await updateSessionForChallenge().finally(() => {
    +            const response = await updateOrCreateSessionForChallenge().finally(() => {
                   setLoading(false);
                 });
     
    @@ -253,7 +254,7 @@ export function LoginPasskey({ loginName, sessionId, requestId, altPassword, org
     
                 return submitLoginAndContinue(pK)
                   .catch((error) => {
    -                setError(error);
    +                setError(error instanceof Error ? error.message : String(error));
                     return;
                   })
                   .finally(() => {
    
  • apps/login/src/components/password-form.tsx+6 3 modified
    @@ -23,10 +23,11 @@ type Props = {
       loginSettings: LoginSettings | undefined;
       loginName: string;
       organization?: string;
    +  defaultOrganization?: string;
       requestId?: string;
     };
     
    -export function PasswordForm({ loginSettings, loginName, organization, requestId }: Props) {
    +export function PasswordForm({ loginSettings, loginName, organization, defaultOrganization, requestId }: Props) {
       const { register, handleSubmit, formState } = useForm<Inputs>({
         mode: "onChange",
       });
    @@ -47,6 +48,7 @@ export function PasswordForm({ loginSettings, loginName, organization, requestId
         const response = await sendPassword({
           loginName,
           organization,
    +      defaultOrganization,
           checks: create(ChecksSchema, {
             password: { password: values.password },
           }),
    @@ -78,18 +80,19 @@ export function PasswordForm({ loginSettings, loginName, organization, requestId
         const response = await resetPassword({
           loginName,
           organization,
    +      defaultOrganization,
           requestId,
         })
           .catch(() => {
    -        setError(t("verify.errors.couldNotResetPassword"));
    +        setError(t("errors.couldNotSendResetLink"));
             return;
           })
           .finally(() => {
             setLoading(false);
           });
     
         if (response && "error" in response) {
    -      setError(response.error);
    +      setError(response.error as string);
           return;
         }
     
    
  • apps/login/src/components/set-password-form.tsx+7 2 modified
    @@ -31,13 +31,15 @@ type Props = {
       loginName: string;
       userId: string;
       organization?: string;
    +  defaultOrganization?: string;
       requestId?: string;
       codeRequired: boolean;
     };
     
     export function SetPasswordForm({
       passwordComplexitySettings,
       organization,
    +  defaultOrganization,
       requestId,
       loginName,
       userId,
    @@ -65,6 +67,7 @@ export function SetPasswordForm({
         const response = await resetPassword({
           loginName,
           organization,
    +      defaultOrganization,
           requestId,
         })
           .catch(() => {
    @@ -75,17 +78,19 @@ export function SetPasswordForm({
             setLoading(false);
           });
     
    -    if (response && "error" in response) {
    +    if (response && "error" in response && typeof response.error === "string") {
           setError(response.error);
           return;
         }
       }
     
       async function submitPassword(values: Inputs) {
         setLoading(true);
    -    let payload: { userId: string; password: string; code?: string } = {
    +
    +    let payload: { userId: string; password: string; code?: string; organization?: string } = {
           userId: userId,
           password: values.password,
    +      organization,
         };
     
         // this is not required for initial password setup
    
  • apps/login/src/components/username-form.tsx+13 1 modified
    @@ -22,12 +22,22 @@ type Props = {
       requestId: string | undefined;
       loginSettings: LoginSettings | undefined;
       organization?: string;
    +  defaultOrganization?: string;
       suffix?: string;
       submit: boolean;
       allowRegister: boolean;
     };
     
    -export function UsernameForm({ loginName, requestId, organization, suffix, loginSettings, submit, allowRegister }: Props) {
    +export function UsernameForm({
    +  loginName,
    +  requestId,
    +  organization,
    +  defaultOrganization,
    +  suffix,
    +  loginSettings,
    +  submit,
    +  allowRegister,
    +}: Props) {
       const { register, handleSubmit, formState } = useForm<Inputs>({
         mode: "onChange",
         defaultValues: {
    @@ -48,8 +58,10 @@ export function UsernameForm({ loginName, requestId, organization, suffix, login
         const res = await sendLoginname({
           loginName: values.loginName,
           organization,
    +      defaultOrganization,
           requestId,
           suffix,
    +      ignoreUnknownUsernames: loginSettings?.ignoreUnknownUsernames,
         })
           .catch(() => {
             setError(t("errors.internalError"));
    
  • apps/login/src/components/verify-form.tsx+14 2 modified
    @@ -2,8 +2,9 @@
     
     import { Alert, AlertType } from "@/components/alert";
     import { resendVerification, sendVerification } from "@/lib/server/verify";
    +import { UNKNOWN_USER_ID } from "@/lib/constants";
     import { useRouter } from "next/navigation";
    -import { useCallback, useEffect, useState } from "react";
    +import { useCallback, useEffect, useRef, useState } from "react";
     import { useTranslations } from "next-intl";
     import { useForm } from "react-hook-form";
     import { BackButton } from "./back-button";
    @@ -45,6 +46,13 @@ export function VerifyForm({ userId, loginName, organization, requestId, code, i
         setError("");
         setLoading(true);
     
    +    // do not send code for dummy userid that is set to prevent user enumeration
    +    if (userId === UNKNOWN_USER_ID) {
    +      await new Promise((resolve) => setTimeout(resolve, 1000));
    +      setLoading(false);
    +      return;
    +    }
    +
         const response = await resendVerification({
           userId,
           isInvite: isInvite,
    @@ -65,8 +73,11 @@ export function VerifyForm({ userId, loginName, organization, requestId, code, i
         return response;
       }
     
    +  const processedCode = useRef<string | undefined>(undefined);
    +
       const fcn = useCallback(
         async function submitCodeAndContinue(value: Inputs): Promise<boolean | void> {
    +      setError("");
           setLoading(true);
     
           const response = await sendVerification({
    @@ -98,7 +109,8 @@ export function VerifyForm({ userId, loginName, organization, requestId, code, i
       );
     
       useEffect(() => {
    -    if (code) {
    +    if (code && code !== processedCode.current) {
    +      processedCode.current = code;
           fcn({ code });
         }
       }, [code, fcn]);
    
  • apps/login/src/lib/constants.ts+1 0 added
    @@ -0,0 +1 @@
    +export const UNKNOWN_USER_ID = "000000000000000000";
    
  • apps/login/src/lib/cookies.test.ts+64 47 modified
    @@ -454,13 +454,14 @@ describe("cookies", () => {
     
           const result = await getMostRecentSessionCookie();
     
    -      expect(result.id).toBe("session-2");
    +      expect(result).toBeDefined();
    +      expect(result?.id).toBe("session-2");
         });
     
    -    it("should reject when no session cookie exists", async () => {
    +    it("should return undefined when no session cookie exists", async () => {
           mockCookies.get.mockReturnValue(undefined);
     
    -      await expect(getMostRecentSessionCookie()).rejects.toBe("no session cookie found");
    +      await expect(getMostRecentSessionCookie()).resolves.toBeUndefined();
         });
     
         it("should handle single session", async () => {
    @@ -479,7 +480,8 @@ describe("cookies", () => {
     
           const result = await getMostRecentSessionCookie();
     
    -      expect(result.id).toBe("session-1");
    +      expect(result).toBeDefined();
    +      expect(result?.id).toBe("session-1");
         });
       });
     
    @@ -511,8 +513,9 @@ describe("cookies", () => {
     
           const result = await getSessionCookieById({ sessionId: "session-1" });
     
    -      expect(result.id).toBe("session-1");
    -      expect(result.loginName).toBe("user1@example.com");
    +      expect(result).toBeDefined();
    +      expect(result?.id).toBe("session-1");
    +      expect(result?.loginName).toBe("user1@example.com");
         });
     
         it("should filter by organization when provided", async () => {
    @@ -525,19 +528,20 @@ describe("cookies", () => {
             organization: "org-2",
           });
     
    -      expect(result.id).toBe("session-2");
    -      expect(result.organization).toBe("org-2");
    +      expect(result).toBeDefined();
    +      expect(result?.id).toBe("session-2");
    +      expect(result?.organization).toBe("org-2");
         });
     
    -    it("should reject if session not found", async () => {
    +    it("should return undefined if session not found", async () => {
           mockCookies.get.mockReturnValue({
             value: JSON.stringify([session1]),
           });
     
    -      await expect(getSessionCookieById({ sessionId: "non-existent" })).rejects.toBeTypeOf("undefined");
    +      await expect(getSessionCookieById({ sessionId: "non-existent" })).resolves.toBeUndefined();
         });
     
    -    it("should reject if organization doesn't match", async () => {
    +    it("should return undefined if organization doesn't match", async () => {
           mockCookies.get.mockReturnValue({
             value: JSON.stringify([session1]),
           });
    @@ -547,13 +551,13 @@ describe("cookies", () => {
               sessionId: "session-1",
               organization: "wrong-org",
             }),
    -      ).rejects.toBeTypeOf("undefined");
    +      ).resolves.toBeUndefined();
         });
     
    -    it("should reject when no session cookie exists", async () => {
    +    it("should return undefined when no session cookie exists", async () => {
           mockCookies.get.mockReturnValue(undefined);
     
    -      await expect(getSessionCookieById({ sessionId: "session-1" })).rejects.toBeTypeOf("undefined");
    +      await expect(getSessionCookieById({ sessionId: "session-1" })).resolves.toBeUndefined();
         });
       });
     
    @@ -587,8 +591,9 @@ describe("cookies", () => {
             loginName: "user1@example.com",
           });
     
    -      expect(result.id).toBe("session-1");
    -      expect(result.loginName).toBe("user1@example.com");
    +      expect(result).toBeDefined();
    +      expect(result?.id).toBe("session-1");
    +      expect(result?.loginName).toBe("user1@example.com");
         });
     
         it("should filter by organization when provided", async () => {
    @@ -601,24 +606,23 @@ describe("cookies", () => {
             organization: "org-2",
           });
     
    -      expect(result.id).toBe("session-2");
    -      expect(result.organization).toBe("org-2");
    +      expect(result).toBeDefined();
    +      expect(result?.id).toBe("session-2");
    +      expect(result?.organization).toBe("org-2");
         });
     
    -    it("should reject if session not found", async () => {
    +    it("should return undefined if session not found", async () => {
           mockCookies.get.mockReturnValue({
             value: JSON.stringify([session1]),
           });
     
    -      await expect(getSessionCookieByLoginName({ loginName: "nonexistent@example.com" })).rejects.toBe(
    -        "no cookie found with loginName: nonexistent@example.com",
    -      );
    +      await expect(getSessionCookieByLoginName({ loginName: "nonexistent@example.com" })).resolves.toBeUndefined();
         });
     
    -    it("should reject when no session cookie exists", async () => {
    +    it("should return undefined when no session cookie exists", async () => {
           mockCookies.get.mockReturnValue(undefined);
     
    -      await expect(getSessionCookieByLoginName({ loginName: "user@example.com" })).rejects.toBe("no session cookie found");
    +      await expect(getSessionCookieByLoginName({ loginName: "user@example.com" })).resolves.toBeUndefined();
         });
       });
     
    @@ -801,6 +805,36 @@ describe("cookies", () => {
           expect(result.id).toBe("session-2");
         });
     
    +    it("should return undefined when no matching session found", async () => {
    +      const session1: Cookie = {
    +        id: "session-1",
    +        token: "token-1",
    +        loginName: "user@example.com",
    +        creationTs: "1700000000000",
    +        expirationTs: "1800000000000",
    +        changeTs: "1700000000000",
    +      };
    +
    +      const session2: Cookie = {
    +        id: "session-2",
    +        token: "token-2",
    +        loginName: "user@example.com",
    +        creationTs: "1700000001000",
    +        expirationTs: "1800000001000",
    +        changeTs: "1700000005000",
    +      };
    +
    +      mockCookies.get.mockReturnValue({
    +        value: JSON.stringify([session1, session2]),
    +      });
    +
    +      await expect(
    +        getMostRecentCookieWithLoginname({
    +          loginName: "other@example.com",
    +        }),
    +      ).resolves.toBeUndefined();
    +    });
    +
         it("should filter by organization when provided", async () => {
           const session1: Cookie = {
             id: "session-1",
    @@ -863,31 +897,14 @@ describe("cookies", () => {
           expect(result.id).toBe("session-2");
         });
     
    -    it("should reject when no matching session found", async () => {
    -      const session: Cookie = {
    -        id: "session-1",
    -        token: "token-1",
    -        loginName: "other@example.com",
    -        creationTs: "1700000000000",
    -        expirationTs: "1800000000000",
    -        changeTs: "1700000000000",
    -      };
    -
    -      mockCookies.get.mockReturnValue({
    -        value: JSON.stringify([session]),
    -      });
    -
    -      await expect(getMostRecentCookieWithLoginname({ loginName: "user@example.com" })).rejects.toBe(
    -        "Could not get the context or retrieve a session",
    -      );
    -    });
    -
    -    it("should reject when no session cookie exists", async () => {
    +    it("should return undefined when no session cookie exists", async () => {
           mockCookies.get.mockReturnValue(undefined);
     
    -      await expect(getMostRecentCookieWithLoginname({ loginName: "user@example.com" })).rejects.toBe(
    -        "Could not read session cookie",
    -      );
    +      await expect(
    +        getMostRecentCookieWithLoginname({
    +          loginName: "user@example.com",
    +        }),
    +      ).resolves.toBeUndefined();
         });
       });
     });
    
  • apps/login/src/lib/cookies.ts+58 67 modified
    @@ -157,7 +157,7 @@ export async function removeSessionFromCookie<T>({
       }
     }
     
    -export async function getMostRecentSessionCookie<T>(): Promise<Cookie> {
    +export async function getMostRecentSessionCookie<T>(): Promise<Cookie | undefined> {
       const cookiesList = await cookies();
       const stringifiedCookie = cookiesList.get("sessions");
     
    @@ -170,7 +170,7 @@ export async function getMostRecentSessionCookie<T>(): Promise<Cookie> {
     
         return latest;
       } else {
    -    return Promise.reject("no session cookie found");
    +    return undefined;
       }
     }
     
    @@ -180,7 +180,7 @@ export async function getSessionCookieById<T>({
     }: {
       sessionId: string;
       organization?: string;
    -}): Promise<SessionCookie<T>> {
    +}): Promise<SessionCookie<T> | undefined> {
       const cookiesList = await cookies();
       const stringifiedCookie = cookiesList.get("sessions");
     
    @@ -193,10 +193,10 @@ export async function getSessionCookieById<T>({
         if (found) {
           return found;
         } else {
    -      return Promise.reject();
    +      return undefined;
         }
       } else {
    -    return Promise.reject();
    +    return undefined;
       }
     }
     
    @@ -206,23 +206,18 @@ export async function getSessionCookieByLoginName<T>({
     }: {
       loginName?: string;
       organization?: string;
    -}): Promise<SessionCookie<T>> {
    +}): Promise<SessionCookie<T> | undefined> {
       const cookiesList = await cookies();
       const stringifiedCookie = cookiesList.get("sessions");
     
    -  if (stringifiedCookie?.value) {
    -    const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie?.value);
    -    const found = sessions.find((s) =>
    -      organization ? s.organization === organization && s.loginName === loginName : s.loginName === loginName,
    -    );
    -    if (found) {
    -      return found;
    -    } else {
    -      return Promise.reject("no cookie found with loginName: " + loginName);
    -    }
    -  } else {
    -    return Promise.reject("no session cookie found");
    +  if (!stringifiedCookie?.value) {
    +    return undefined;
       }
    +
    +  const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie.value);
    +  return sessions.find((s) =>
    +    organization ? s.organization === organization && s.loginName === loginName : s.loginName === loginName,
    +  );
     }
     
     /**
    @@ -234,22 +229,22 @@ export async function getAllSessionCookieIds<T>(cleanup: boolean = false): Promi
       const cookiesList = await cookies();
       const stringifiedCookie = cookiesList.get("sessions");
     
    -  if (stringifiedCookie?.value) {
    -    const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie?.value);
    -
    -    if (cleanup) {
    -      const now = new Date();
    -      return sessions
    -        .filter((session) =>
    -          session.expirationTs ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true,
    -        )
    -        .map((session) => session.id);
    -    } else {
    -      return sessions.map((session) => session.id);
    -    }
    -  } else {
    +  if (!stringifiedCookie?.value) {
         return [];
       }
    +
    +  const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie.value);
    +
    +  if (cleanup) {
    +    const now = new Date();
    +    return sessions
    +      .filter((session) =>
    +        session.expirationTs ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true,
    +      )
    +      .map((session) => session.id);
    +  }
    +
    +  return sessions.map((session) => session.id);
     }
     
     /**
    @@ -261,21 +256,21 @@ export async function getAllSessions<T>(cleanup: boolean = false): Promise<Sessi
       const cookiesList = await cookies();
       const stringifiedCookie = cookiesList.get("sessions");
     
    -  if (stringifiedCookie?.value) {
    -    const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie?.value);
    -
    -    if (cleanup) {
    -      const now = new Date();
    -      return sessions.filter((session) =>
    -        session.expirationTs ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true,
    -      );
    -    } else {
    -      return sessions;
    -    }
    -  } else {
    +  if (!stringifiedCookie?.value) {
         console.log("getAllSessions: No session cookie found, returning empty array");
         return [];
       }
    +
    +  const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie.value);
    +
    +  if (cleanup) {
    +    const now = new Date();
    +    return sessions.filter((session) =>
    +      session.expirationTs ? timestampDate(timestampFromMs(Number(session.expirationTs))) > now : true,
    +    );
    +  }
    +
    +  return sessions;
     }
     
     /**
    @@ -294,31 +289,27 @@ export async function getMostRecentCookieWithLoginname<T>({
       const cookiesList = await cookies();
       const stringifiedCookie = cookiesList.get("sessions");
     
    -  if (stringifiedCookie?.value) {
    -    const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie?.value);
    -    let filtered = sessions.filter((cookie) => {
    -      return loginName ? cookie.loginName === loginName : true;
    -    });
    +  if (!stringifiedCookie?.value) {
    +    return undefined;
    +  }
     
    -    if (organization) {
    -      filtered = filtered.filter((cookie) => {
    -        return cookie.organization === organization;
    -      });
    -    }
    +  const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie.value);
     
    -    const latest =
    -      filtered && filtered.length
    -        ? filtered.reduce((prev, current) => {
    -            return prev.changeTs > current.changeTs ? prev : current;
    -          })
    -        : undefined;
    +  let filtered = sessions;
     
    -    if (latest) {
    -      return latest;
    -    } else {
    -      return Promise.reject("Could not get the context or retrieve a session");
    -    }
    -  } else {
    -    return Promise.reject("Could not read session cookie");
    +  if (loginName) {
    +    filtered = filtered.filter((cookie) => cookie.loginName === loginName);
    +  }
    +
    +  if (organization) {
    +    filtered = filtered.filter((cookie) => cookie.organization === organization);
       }
    +
    +  if (!filtered || !filtered.length) {
    +    return undefined;
    +  }
    +
    +  return filtered.reduce((prev, current) => {
    +    return prev.changeTs > current.changeTs ? prev : current;
    +  });
     }
    
  • apps/login/src/lib/self.ts+6 10 modified
    @@ -11,21 +11,17 @@ const myUserService = async (serviceConfig: ServiceConfig, sessionToken: string)
       return createUserServiceClient(transportPromise);
     };
     
    -export async function setMyPassword({
    -  sessionId,
    -  password,
    -}: {
    -  sessionId: string;
    -  password: string;
    -}) {
    +export async function setMyPassword({ sessionId, password }: { sessionId: string; password: string }) {
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
     
       const sessionCookie = await getSessionCookieById({ sessionId });
     
    -  const { session } = await getSession({ serviceConfig, sessionId: sessionCookie.id,
    -    sessionToken: sessionCookie.token,
    -  });
    +  if (!sessionCookie) {
    +    return { error: "Could not load session cookie" };
    +  }
    +
    +  const { session } = await getSession({ serviceConfig, sessionId: sessionCookie.id, sessionToken: sessionCookie.token });
     
       if (!session) {
         return { error: "Could not load session" };
    
  • apps/login/src/lib/server/cookie.ts+54 42 modified
    @@ -3,7 +3,7 @@
     import { addSessionToCookie, updateSessionCookie } from "@/lib/cookies";
     import {
       createSessionForUserIdAndIdpIntent,
    -  createSessionFromChecks,
    +  createSessionFromChecksAndChallenges,
       getSecuritySettings,
       getSession,
       setSession,
    @@ -44,7 +44,8 @@ export async function createSessionAndUpdateCookie(command: {
       checks: Checks;
       requestId: string | undefined;
       lifetime?: Duration;
    -}): Promise<Session> {
    +  challenges?: RequestChallenges;
    +}): Promise<{ session: Session; sessionCookie: CustomCookieData; challenges?: Challenges }> {
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
     
    @@ -59,12 +60,17 @@ export async function createSessionAndUpdateCookie(command: {
         } as Duration; // for usecases where the lifetime is not specified (user discovery)
       }
     
    -  const createdSession = await createSessionFromChecks({ serviceConfig, checks: command.checks,
    +  const createdSession = await createSessionFromChecksAndChallenges({
    +    serviceConfig,
    +    checks: command.checks,
         lifetime: sessionLifetime,
    +    challenges: command.challenges,
       });
     
       if (createdSession) {
    -    return getSession({ serviceConfig, sessionId: createdSession.sessionId,
    +    return getSession({
    +      serviceConfig,
    +      sessionId: createdSession.sessionId,
           sessionToken: createdSession.sessionToken,
         }).then(async (response) => {
           if (response?.session && response.session?.factors?.user?.loginName) {
    @@ -90,7 +96,7 @@ export async function createSessionAndUpdateCookie(command: {
     
             await addSessionToCookie({ session: sessionCookie, iFrameEnabled });
     
    -        return response.session as Session;
    +        return { session: response.session as Session, sessionCookie, challenges: createdSession.challenges };
           } else {
             throw "could not get session or session does not have loginName";
           }
    @@ -128,7 +134,9 @@ export async function createSessionForIdpAndUpdateCookie({
         } as Duration;
       }
     
    -  const createdSession = await createSessionForUserIdAndIdpIntent({ serviceConfig, userId,
    +  const createdSession = await createSessionForUserIdAndIdpIntent({
    +    serviceConfig,
    +    userId,
         idpIntent,
         lifetime: sessionLifetime,
       }).catch((error: ErrorDetail | CredentialsCheckError) => {
    @@ -146,7 +154,9 @@ export async function createSessionForIdpAndUpdateCookie({
         throw "Could not create session";
       }
     
    -  const { session } = await getSession({ serviceConfig, sessionId: createdSession.sessionId,
    +  const { session } = await getSession({
    +    serviceConfig,
    +    sessionId: createdSession.sessionId,
         sessionToken: createdSession.sessionToken,
       });
     
    @@ -194,7 +204,9 @@ export async function setSessionAndUpdateCookie(command: {
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
     
    -  return setSession({ serviceConfig, sessionId: command.recentCookie.id,
    +  return setSession({
    +    serviceConfig,
    +    sessionId: command.recentCookie.id,
         sessionToken: command.recentCookie.token,
         challenges: command.challenges,
         checks: command.checks,
    @@ -217,40 +229,40 @@ export async function setSessionAndUpdateCookie(command: {
               sessionCookie.requestId = command.requestId;
             }
     
    -        return getSession({ serviceConfig, sessionId: sessionCookie.id,
    -          sessionToken: sessionCookie.token,
    -        }).then(async (response) => {
    -          if (!response?.session || !response.session.factors?.user?.loginName) {
    -            throw "could not get session or session does not have loginName";
    -          }
    -
    -          const { session } = response;
    -          const newCookie: CustomCookieData = {
    -            id: sessionCookie.id,
    -            token: updatedSession.sessionToken,
    -            creationTs: sessionCookie.creationTs,
    -            expirationTs: sessionCookie.expirationTs,
    -            // just overwrite the changeDate with the new one
    -            changeTs: updatedSession.details?.changeDate ? `${timestampMs(updatedSession.details.changeDate)}` : "",
    -            loginName: session.factors?.user?.loginName ?? "",
    -            organization: session.factors?.user?.organizationId ?? "",
    -          };
    -
    -          if (sessionCookie.requestId) {
    -            newCookie.requestId = sessionCookie.requestId;
    -          }
    -
    -          const securitySettings = await getSecuritySettings({ serviceConfig });
    -          const iFrameEnabled = !!securitySettings?.embeddedIframe?.enabled;
    -
    -          return updateSessionCookie({
    -            id: sessionCookie.id,
    -            session: newCookie,
    -            iFrameEnabled,
    -          }).then(() => {
    -            return { challenges: updatedSession.challenges, ...session };
    -          });
    -        });
    +        return getSession({ serviceConfig, sessionId: sessionCookie.id, sessionToken: sessionCookie.token }).then(
    +          async (response) => {
    +            if (!response?.session || !response.session.factors?.user?.loginName) {
    +              throw "could not get session or session does not have loginName";
    +            }
    +
    +            const { session } = response;
    +            const newCookie: CustomCookieData = {
    +              id: sessionCookie.id,
    +              token: updatedSession.sessionToken,
    +              creationTs: sessionCookie.creationTs,
    +              expirationTs: sessionCookie.expirationTs,
    +              // just overwrite the changeDate with the new one
    +              changeTs: updatedSession.details?.changeDate ? `${timestampMs(updatedSession.details.changeDate)}` : "",
    +              loginName: session.factors?.user?.loginName ?? "",
    +              organization: session.factors?.user?.organizationId ?? "",
    +            };
    +
    +            if (sessionCookie.requestId) {
    +              newCookie.requestId = sessionCookie.requestId;
    +            }
    +
    +            const securitySettings = await getSecuritySettings({ serviceConfig });
    +            const iFrameEnabled = !!securitySettings?.embeddedIframe?.enabled;
    +
    +            return updateSessionCookie({
    +              id: sessionCookie.id,
    +              session: newCookie,
    +              iFrameEnabled,
    +            }).then(() => {
    +              return { challenges: updatedSession.challenges, ...session };
    +            });
    +          },
    +        );
           } else {
             throw "Session not be set";
           }
    
  • apps/login/src/lib/server/loginname.test.ts+432 9 modified
    @@ -195,7 +195,7 @@ describe("sendLoginname", () => {
           mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
           mockSearchUsers.mockResolvedValue({ result: [mockUser] });
           mockCreate.mockReturnValue({});
    -      mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession);
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
         });
     
         test("should redirect to verify when user has no authentication methods", async () => {
    @@ -339,6 +339,27 @@ describe("sendLoginname", () => {
     
             expect(result).toEqual({ redirect: "https://idp.example.com/auth" });
           });
    +
    +      test("should NOT create session when ignoreUnknownUsernames is true", async () => {
    +        mockGetLoginSettings.mockResolvedValue({
    +          allowUsernamePassword: true,
    +          ignoreUnknownUsernames: true,
    +        });
    +        mockListAuthenticationMethodTypes.mockResolvedValue({
    +          authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +        });
    +
    +        const result = await sendLoginname({
    +          loginName: "user@example.com",
    +          requestId: "req123",
    +        });
    +
    +        expect(mockCreateSessionAndUpdateCookie).not.toHaveBeenCalled();
    +
    +        expect(result).toHaveProperty("redirect");
    +        expect((result as any).redirect).toMatch(/^\/password\?/);
    +        expect((result as any).redirect).toContain("loginName=user%40example.com");
    +      });
         });
     
         describe("Multiple authentication methods", () => {
    @@ -470,6 +491,7 @@ describe("sendLoginname", () => {
             loginName: "user@example.com",
             requestId: "req123",
             organization: "org123",
    +        ignoreUnknownUsernames: true,
           });
     
           expect(result).toBeDefined();
    @@ -646,10 +668,128 @@ describe("sendLoginname", () => {
           // Verify org discovery was NOT called since org was provided
           expect(mockGetOrgsByDomain).not.toHaveBeenCalled();
         });
    -  });
     
    -  describe("Edge cases", () => {
    -    test("should handle session creation failure", async () => {
    +    test("should redirect to password when ignoreUnknownUsernames is true, allowRegister is true, but allowUsernamePassword is false (User not found)", async () => {
    +      mockSearchUsers.mockResolvedValue({ result: [] });
    +      mockGetLoginSettings.mockResolvedValue({
    +        allowRegister: true,
    +        allowUsernamePassword: false,
    +        ignoreUnknownUsernames: true,
    +      });
    +      mockGetActiveIdentityProviders.mockResolvedValue({ identityProviders: [] });
    +
    +      const result = await sendLoginname({
    +        loginName: "user@example.com",
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      expect(result).not.toEqual({ error: "errors.userNotFound" });
    +      expect(result).toHaveProperty("redirect");
    +      expect((result as any).redirect).toMatch(/^\/password\?/);
    +    });
    +
    +    test("should redirect to password when ignoreUnknownUsernames is true, user found but rejected due to disableLoginWithEmail", async () => {
    +      const mockUser = {
    +        userId: "user123",
    +        preferredLoginName: "user",
    +        details: { resourceOwner: "org123" },
    +        type: { case: "human", value: { email: { email: "user@example.com" }, phone: { phone: "123456" } } },
    +        state: UserState.ACTIVE,
    +      };
    +
    +      mockSearchUsers.mockResolvedValue({ result: [mockUser] });
    +      mockGetLoginSettings.mockResolvedValue({
    +        disableLoginWithEmail: true,
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      const result = await sendLoginname({
    +        loginName: "user@example.com",
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      expect(result).not.toEqual({ error: "errors.userNotFound" });
    +      expect(result).toHaveProperty("redirect");
    +      expect((result as any).redirect).toMatch(/^\/password\?/);
    +    });
    +
    +    test("should redirect to password when ignoreUnknownUsernames is true and more than one user found", async () => {
    +      mockGetLoginSettings.mockResolvedValue({
    +        ignoreUnknownUsernames: true,
    +      });
    +      mockSearchUsers.mockResolvedValue({
    +        result: [
    +          { userId: "user1", preferredLoginName: "user1@example.com" },
    +          { userId: "user2", preferredLoginName: "user2@example.com" },
    +        ],
    +      });
    +
    +      const result = await sendLoginname({
    +        loginName: "user@example.com",
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      expect(result).not.toEqual({ error: "errors.moreThanOneUserFound" });
    +      expect(result).toHaveProperty("redirect");
    +      expect((result as any).redirect).toMatch(/^\/password\?/);
    +    });
    +
    +    test("should return generic error when user not active and ignoreUnknownUsernames is true", async () => {
    +      mockSearchUsers.mockResolvedValue({
    +        result: [
    +          {
    +            userId: "user1",
    +            state: UserState.ACTIVE,
    +            preferredLoginName: "user1",
    +            type: { case: "human", value: { email: { isVerified: true } } },
    +          },
    +        ],
    +      });
    +      mockGetLoginSettings.mockResolvedValue({
    +        ignoreUnknownUsernames: true,
    +        allowUsernamePassword: true,
    +      });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +      });
    +      // Mock createSessionAndUpdateCookie to fail with user not active error
    +      mockCreateSessionAndUpdateCookie.mockRejectedValue({
    +        rawMessage: "Errors.User.NotActive (SESSION-Gj4ko)",
    +      });
    +
    +      const result = await sendLoginname({ loginName: "user1", ignoreUnknownUsernames: true });
    +
    +      expect(result).toEqual({ redirect: "/password?loginName=user1" });
    +      // With ignoreUnknownUsernames: true, we skip session creation, so this mock is NOT called
    +      expect(mockCreateSessionAndUpdateCookie).not.toHaveBeenCalled();
    +    });
    +
    +    test("should NOT create session and return redirect when ignoreUnknownUsernames is true and user is valid", async () => {
    +      mockSearchUsers.mockResolvedValue({
    +        result: [
    +          {
    +            userId: "user1",
    +            state: UserState.ACTIVE,
    +            preferredLoginName: "user1",
    +            type: { case: "human", value: { email: { isVerified: true } } },
    +          },
    +        ],
    +      });
    +      mockGetLoginSettings.mockResolvedValue({
    +        ignoreUnknownUsernames: true,
    +        allowUsernamePassword: true,
    +      });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +      });
    +
    +      const result = await sendLoginname({ loginName: "user1", ignoreUnknownUsernames: true });
    +
    +      expect(result).toEqual({ redirect: "/password?loginName=user1" });
    +      expect(mockCreateSessionAndUpdateCookie).not.toHaveBeenCalled();
    +    });
    +
    +    test("should redirect to password when ignoreUnknownUsernames is true and password not allowed", async () => {
           const mockUser = {
             userId: "user123",
             preferredLoginName: "user@example.com",
    @@ -658,18 +798,81 @@ describe("sendLoginname", () => {
             state: UserState.ACTIVE,
           };
     
    -      mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
    +      const mockSession = {
    +        factors: {
    +          user: {
    +            id: "user123",
    +            loginName: "user@example.com",
    +            organizationId: "org123",
    +          },
    +        },
    +      };
    +
    +      mockGetLoginSettings.mockResolvedValue({
    +        allowUsernamePassword: false,
    +        ignoreUnknownUsernames: true,
    +      });
           mockSearchUsers.mockResolvedValue({ result: [mockUser] });
           mockCreate.mockReturnValue({});
    -      mockCreateSessionAndUpdateCookie.mockResolvedValue({ factors: {} }); // No user in session
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +      });
    +      mockListIDPLinks.mockResolvedValue({ result: [] });
    +      mockGetActiveIdentityProviders.mockResolvedValue({ identityProviders: [] });
    +
    +      const result = await sendLoginname({
    +        loginName: "user@example.com",
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      expect(result).not.toEqual({ error: "errors.usernamePasswordNotAllowed" });
    +      expect(result).toHaveProperty("redirect");
    +      expect((result as any).redirect).toMatch(/^\/password\?/);
    +    });
    +
    +    test("should redirect to password when ignoreUnknownUsernames is true and passkeys not allowed", async () => {
    +      const mockUser = {
    +        userId: "user123",
    +        preferredLoginName: "user@example.com",
    +        details: { resourceOwner: "org123" },
    +        type: { case: "human", value: { email: { email: "user@example.com" } } },
    +        state: UserState.ACTIVE,
    +      };
    +
    +      const mockSession = {
    +        factors: {
    +          user: {
    +            id: "user123",
    +            loginName: "user@example.com",
    +            organizationId: "org123",
    +          },
    +        },
    +      };
    +
    +      mockGetLoginSettings.mockResolvedValue({
    +        passkeysType: PasskeysType.NOT_ALLOWED,
    +        ignoreUnknownUsernames: true,
    +      });
    +      mockSearchUsers.mockResolvedValue({ result: [mockUser] });
    +      mockCreate.mockReturnValue({});
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSKEY],
    +      });
     
           const result = await sendLoginname({
             loginName: "user@example.com",
    +        ignoreUnknownUsernames: true,
           });
     
    -      expect(result).toEqual({ error: "errors.couldNotCreateSession" });
    +      expect(result).not.toEqual({ error: "errors.passkeysNotAllowed" });
    +      expect(result).toHaveProperty("redirect");
    +      expect((result as any).redirect).toMatch(/^\/password\?/);
         });
    +  });
     
    +  describe("Edge cases", () => {
         test("should handle initial user state", async () => {
           const mockUser = {
             userId: "user123",
    @@ -692,7 +895,7 @@ describe("sendLoginname", () => {
           mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
           mockSearchUsers.mockResolvedValue({ result: [mockUser] });
           mockCreate.mockReturnValue({});
    -      mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession);
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
     
           const result = await sendLoginname({
             loginName: "user@example.com",
    @@ -723,7 +926,7 @@ describe("sendLoginname", () => {
           mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true });
           mockSearchUsers.mockResolvedValue({ result: [mockUser] });
           mockCreate.mockReturnValue({});
    -      mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession);
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
           mockListAuthenticationMethodTypes.mockResolvedValue({
             authMethodTypes: [AuthenticationMethodType.PASSWORD],
           });
    @@ -738,5 +941,225 @@ describe("sendLoginname", () => {
           expect(result?.redirect).toContain("organization=custom-org");
           expect(result?.redirect).toContain("requestId=req123");
         });
    +
    +    test("should redirect to password with INPUT loginName when ignoreUnknownUsernames is true, even if user preferredLoginName is different", async () => {
    +      const mockUser = {
    +        userId: "user123",
    +        preferredLoginName: "user@example.com",
    +        details: { resourceOwner: "org123" },
    +        type: { case: "human", value: { email: { email: "user@example.com" } } },
    +        state: UserState.ACTIVE,
    +      };
    +
    +      const mockSession = {
    +        factors: {
    +          user: {
    +            id: "user123",
    +            loginName: "user@example.com",
    +            organizationId: "org123",
    +          },
    +        },
    +      };
    +
    +      mockGetLoginSettings.mockResolvedValue({
    +        allowUsernamePassword: true,
    +        ignoreUnknownUsernames: true,
    +      });
    +      // Mock search result returns a user with resolved/different loginName
    +      mockSearchUsers.mockResolvedValue({ result: [mockUser] });
    +      mockCreate.mockReturnValue({});
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +      });
    +
    +      // INPUT login name is just "user"
    +      const result = await sendLoginname({
    +        loginName: "user",
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      expect(result).toHaveProperty("redirect");
    +      // Expect redirect to contain input "user" not resolved "user@example.com"
    +      expect((result as any).redirect).toContain("loginName=user");
    +      expect((result as any).redirect).not.toContain("loginName=user%40example.com");
    +    });
    +
    +    test("should redirect to passkey with INPUT loginName when ignoreUnknownUsernames is true", async () => {
    +      const mockUser = {
    +        userId: "user123",
    +        preferredLoginName: "user@example.com",
    +        details: { resourceOwner: "org123" },
    +        type: { case: "human", value: { email: { email: "user@example.com" } } },
    +        state: UserState.ACTIVE,
    +      };
    +
    +      const mockSession = {
    +        factors: {
    +          user: {
    +            id: "user123",
    +            loginName: "user@example.com",
    +            organizationId: "org123",
    +          },
    +        },
    +      };
    +
    +      mockGetLoginSettings.mockResolvedValue({
    +        passkeysType: PasskeysType.ALLOWED,
    +        ignoreUnknownUsernames: true,
    +      });
    +      mockSearchUsers.mockResolvedValue({ result: [mockUser] });
    +      mockCreate.mockReturnValue({});
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSKEY],
    +      });
    +
    +      const result = await sendLoginname({
    +        loginName: "user",
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      expect(result).toHaveProperty("redirect");
    +      expect((result as any).redirect).toMatch(/^\/passkey\?/);
    +      expect((result as any).redirect).toContain("loginName=user");
    +      expect((result as any).redirect).not.toContain("loginName=user%40example.com");
    +    });
    +
    +    test("should redirect to passkey with INPUT loginName when ignoreUnknownUsernames is true (multi-method)", async () => {
    +      const mockUser = {
    +        userId: "user123",
    +        preferredLoginName: "user@example.com",
    +        details: { resourceOwner: "org123" },
    +        type: { case: "human", value: { email: { email: "user@example.com" } } },
    +        state: UserState.ACTIVE,
    +      };
    +
    +      const mockSession = {
    +        factors: {
    +          user: {
    +            id: "user123",
    +            loginName: "user@example.com",
    +            organizationId: "org123",
    +          },
    +        },
    +      };
    +
    +      mockGetLoginSettings.mockResolvedValue({
    +        passkeysType: PasskeysType.ALLOWED,
    +        ignoreUnknownUsernames: true,
    +      });
    +      mockSearchUsers.mockResolvedValue({ result: [mockUser] });
    +      mockCreate.mockReturnValue({});
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSKEY, AuthenticationMethodType.PASSWORD],
    +      });
    +
    +      const result = await sendLoginname({
    +        loginName: "user",
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      expect(result).toHaveProperty("redirect");
    +      expect((result as any).redirect).toMatch(/^\/passkey\?/);
    +      expect((result as any).redirect).toContain("loginName=user");
    +      expect((result as any).redirect).not.toContain("loginName=user%40example.com");
    +    });
    +    test("should use CONTEXT settings to HIDE username even if USER settings would show it", async () => {
    +      const mockUser = {
    +        userId: "user123",
    +        preferredLoginName: "user@example.com",
    +        details: { resourceOwner: "user-org" },
    +        type: { case: "human", value: { email: { email: "user@example.com" } } },
    +        state: UserState.ACTIVE,
    +      };
    +
    +      const mockSession = {
    +        factors: {
    +          user: {
    +            id: "user123",
    +            loginName: "user@example.com",
    +            organizationId: "user-org",
    +          },
    +        },
    +      };
    +
    +      // Mock implementation to return different settings based on organization
    +      mockGetLoginSettings.mockImplementation(async (args: any) => {
    +        if (args.organization === "context-org") {
    +          return { allowUsernamePassword: true, ignoreUnknownUsernames: true };
    +        }
    +        if (args.organization === "user-org") {
    +          return { allowUsernamePassword: true, ignoreUnknownUsernames: false };
    +        }
    +        return {};
    +      });
    +
    +      mockSearchUsers.mockResolvedValue({ result: [mockUser] });
    +      mockCreate.mockReturnValue({});
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +      });
    +
    +      const result = await sendLoginname({
    +        loginName: "input-name",
    +        organization: "context-org", // Context has ignore=true
    +        ignoreUnknownUsernames: true,
    +      });
    +
    +      expect(result).toHaveProperty("redirect");
    +      // Should result in input-name because context says HIDE
    +      expect((result as any).redirect).toContain("loginName=input-name");
    +      expect((result as any).redirect).not.toContain("loginName=user%40example.com");
    +    });
    +
    +    test("should use CONTEXT settings to SHOW username even if USER settings would hide it", async () => {
    +      const mockUser = {
    +        userId: "user123",
    +        preferredLoginName: "user@example.com",
    +        details: { resourceOwner: "user-org" },
    +        type: { case: "human", value: { email: { email: "user@example.com" } } },
    +        state: UserState.ACTIVE,
    +      };
    +
    +      const mockSession = {
    +        factors: {
    +          user: {
    +            id: "user123",
    +            loginName: "user@example.com",
    +            organizationId: "user-org",
    +          },
    +        },
    +      };
    +
    +      mockGetLoginSettings.mockImplementation(async (args: any) => {
    +        if (args.organization === "context-org") {
    +          return { allowUsernamePassword: true, ignoreUnknownUsernames: false };
    +        }
    +        if (args.organization === "user-org") {
    +          return { allowUsernamePassword: true, ignoreUnknownUsernames: true };
    +        }
    +        return {};
    +      });
    +
    +      mockSearchUsers.mockResolvedValue({ result: [mockUser] });
    +      mockCreate.mockReturnValue({});
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({ session: mockSession, sessionCookie: {} });
    +      mockListAuthenticationMethodTypes.mockResolvedValue({
    +        authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +      });
    +
    +      const result = await sendLoginname({
    +        loginName: "input-name",
    +        organization: "context-org", // Context has ignore=false
    +        ignoreUnknownUsernames: false,
    +      });
    +
    +      expect(result).toHaveProperty("redirect");
    +      // Should result in resolved name because context says SHOW
    +      expect((result as any).redirect).toContain("loginName=user%40example.com");
    +    });
       });
     });
    
  • apps/login/src/lib/server/loginname.ts+108 49 modified
    @@ -8,6 +8,7 @@ import { headers } from "next/headers";
     import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp";
     
     import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
    +import { IDPLink } from "@zitadel/proto/zitadel/user/v2/idp_pb";
     import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
     import { getServiceConfig } from "../service-url";
     import {
    @@ -23,13 +24,14 @@ import {
     } from "../zitadel";
     import { createSessionAndUpdateCookie } from "./cookie";
     import { getPublicHost } from "./host";
    -import { IDPLink } from "@zitadel/proto/zitadel/user/v2/idp_pb";
     
     export type SendLoginnameCommand = {
       loginName: string;
       requestId?: string;
       organization?: string;
    +  defaultOrganization?: string;
       suffix?: string;
    +  ignoreUnknownUsernames?: boolean;
     };
     
     const ORG_SUFFIX_REGEX = /(?<=@)(.+)/;
    @@ -81,6 +83,26 @@ export async function sendLoginname(command: SendLoginnameCommand) {
         console.log("No users found, will proceed with org discovery");
       }
     
    +  const preventUserEnumeration = (organization: string | undefined) => {
    +    if (command.ignoreUnknownUsernames) {
    +      console.log("ignoreUnknownUsernames is true, redirecting to password");
    +      const paramsPasswordDefault = new URLSearchParams({
    +        loginName: command.loginName,
    +      });
    +
    +      if (command.requestId) {
    +        paramsPasswordDefault.append("requestId", command.requestId);
    +      }
    +
    +      if (organization) {
    +        paramsPasswordDefault.append("organization", organization);
    +      }
    +
    +      return { redirect: "/password?" + paramsPasswordDefault };
    +    }
    +    return { error: t("errors.userNotFound") };
    +  };
    +
       const redirectUserToIDP = async (userId?: string, organization?: string) => {
         // If userId is provided, check for user-specific IDP links first
         let identityProviders: IDPLink[] = [];
    @@ -199,6 +221,9 @@ export async function sendLoginname(command: SendLoginnameCommand) {
     
       if (users.length > 1) {
         console.log("multiple users found, returning error");
    +    if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +      return preventUserEnumeration(command.organization);
    +    }
         return { error: t("errors.moreThanOneUserFound") };
       } else if (users.length == 1 && users[0].userId) {
         const user = users[0];
    @@ -214,56 +239,84 @@ export async function sendLoginname(command: SendLoginnameCommand) {
         // recheck login settings after user discovery, as the search might have been done without org scope
         if (userLoginSettings?.disableLoginWithEmail && userLoginSettings?.disableLoginWithPhone) {
           if (user.preferredLoginName !== concatLoginname) {
    -        return { error: t("errors.userNotFound") };
    +        return preventUserEnumeration(command.organization);
           }
         } else if (userLoginSettings?.disableLoginWithEmail) {
           if (user.preferredLoginName !== concatLoginname || humanUser?.phone?.phone !== command.loginName) {
    -        return { error: t("errors.userNotFound") };
    +        return preventUserEnumeration(command.organization);
           }
         } else if (userLoginSettings?.disableLoginWithPhone) {
           if (user.preferredLoginName !== concatLoginname || humanUser?.email?.email !== command.loginName) {
    -        return { error: t("errors.userNotFound") };
    +        return preventUserEnumeration(command.organization);
           }
         }
     
    -    const checks = create(ChecksSchema, {
    -      user: { search: { case: "userId", value: userId } },
    -    });
    +    let session;
    +    if (!userLoginSettings?.ignoreUnknownUsernames) {
    +      const checks = create(ChecksSchema, {
    +        user: { search: { case: "userId", value: userId } },
    +      });
    +
    +      const sessionOrError = await createSessionAndUpdateCookie({
    +        checks,
    +        requestId: command.requestId,
    +      }).catch((error) => {
    +        if (error?.rawMessage === "Errors.User.NotActive (SESSION-Gj4ko)") {
    +          return { error: t("errors.userNotActive") };
    +        }
    +        throw error;
    +      });
     
    -    const sessionOrError = await createSessionAndUpdateCookie({
    -      checks,
    -      requestId: command.requestId,
    -    }).catch((error) => {
    -      if (error?.rawMessage === "Errors.User.NotActive (SESSION-Gj4ko)") {
    -        return { error: t("errors.userNotActive") };
    +      if ("error" in sessionOrError) {
    +        return sessionOrError;
           }
    -      throw error;
    -    });
     
    -    if ("error" in sessionOrError) {
    -      return sessionOrError;
    +      session = sessionOrError.session;
         }
     
    -    const session = sessionOrError;
    -
    -    if (!session.factors?.user?.id) {
    +    if (session && !session.factors?.user?.id) {
           return { error: t("errors.couldNotCreateSession") };
         }
     
         // TODO: check if handling of userstate INITIAL is needed
         if (user.state === UserState.INITIAL) {
    +      if (userLoginSettings?.ignoreUnknownUsernames) {
    +        return preventUserEnumeration(command.organization);
    +      }
           return { error: t("errors.initialUserNotSupported") };
         }
     
         // Resolve organization from command or session
    -    const organization = command.organization ?? session.factors?.user?.organizationId;
    +    let organization = command.organization ?? session?.factors?.user?.organizationId ?? user.details?.resourceOwner;
    +
    +    if (userLoginSettings?.ignoreUnknownUsernames) {
    +      organization = command.organization;
    +      if (!organization && ORG_SUFFIX_REGEX.test(command.loginName)) {
    +        const matched = ORG_SUFFIX_REGEX.exec(command.loginName);
    +        const suffix = matched?.[1] ?? "";
    +        const orgs = await getOrgsByDomain({ serviceConfig, domain: suffix });
     
    -    const methods = await listAuthenticationMethodTypes({ serviceConfig, userId: session.factors?.user?.id });
    +        if (orgs.result && orgs.result.length === 1) {
    +          const orgToCheckForDiscovery = orgs.result[0].id;
    +          const orgLoginSettings = await getLoginSettings({ serviceConfig, organization: orgToCheckForDiscovery });
    +
    +          if (orgLoginSettings?.allowDomainDiscovery) {
    +            organization = orgToCheckForDiscovery;
    +          }
    +        }
    +      }
    +    }
    +
    +    const methods = await listAuthenticationMethodTypes({
    +      serviceConfig,
    +      userId: session?.factors?.user?.id ?? userId,
    +    });
     
         // always resend invite if user has no auth method set
         if (!methods.authMethodTypes || !methods.authMethodTypes.length) {
    +      console.log("humanUser.email?.isVerified", humanUser?.email?.isVerified);
           const params = new URLSearchParams({
    -        loginName: session.factors?.user?.loginName as string,
    +        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
           });
    @@ -290,17 +343,21 @@ export async function sendLoginname(command: SendLoginnameCommand) {
                   return idpResp;
                 }
     
    +            if (command.ignoreUnknownUsernames) {
    +              return preventUserEnumeration(command.organization);
    +            }
    +
                 return {
                   error: t("errors.usernamePasswordNotAllowed"),
                 };
               }
     
               const paramsPassword = new URLSearchParams({
    -            loginName: session.factors?.user?.loginName,
    +            loginName: command.ignoreUnknownUsernames
    +              ? command.loginName
    +              : (session?.factors?.user?.loginName ?? user.preferredLoginName),
               });
     
    -          // TODO: does this have to be checked in loginSettings.allowDomainDiscovery
    -
               if (organization) {
                 paramsPassword.append("organization", organization);
               }
    @@ -315,13 +372,18 @@ export async function sendLoginname(command: SendLoginnameCommand) {
     
             case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
               if (userLoginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) {
    +            if (command.ignoreUnknownUsernames) {
    +              return preventUserEnumeration(command.organization);
    +            }
                 return {
                   error: t("errors.passkeysNotAllowed"),
                 };
               }
     
               const paramsPasskey = new URLSearchParams({
    -            loginName: session.factors?.user?.loginName,
    +            loginName: command.ignoreUnknownUsernames
    +              ? command.loginName
    +              : (session?.factors?.user?.loginName ?? user.preferredLoginName),
               });
               if (command.requestId) {
                 paramsPasskey.append("requestId", command.requestId);
    @@ -346,7 +408,9 @@ export async function sendLoginname(command: SendLoginnameCommand) {
           // prefer passkey in favor of other methods
           if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSKEY)) {
             const passkeyParams = new URLSearchParams({
    -          loginName: session.factors?.user?.loginName,
    +          loginName: command.ignoreUnknownUsernames
    +            ? command.loginName
    +            : (session?.factors?.user?.loginName ?? user.preferredLoginName),
               altPassword: `${methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) && userLoginSettings?.allowUsernamePassword}`, // show alternative password option only if allowed
             });
     
    @@ -364,14 +428,19 @@ export async function sendLoginname(command: SendLoginnameCommand) {
           } else if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD)) {
             // Check if password authentication is allowed
             if (!userLoginSettings?.allowUsernamePassword) {
    +          if (command.ignoreUnknownUsernames) {
    +            return preventUserEnumeration(command.organization);
    +          }
               return {
                 error: "Username Password not allowed! Contact your administrator for more information.",
               };
             }
     
             // user has no passkey setup and login settings allow passwords
             const paramsPasswordDefault = new URLSearchParams({
    -          loginName: session.factors?.user?.loginName,
    +          loginName: command.ignoreUnknownUsernames
    +            ? command.loginName
    +            : (session?.factors?.user?.loginName ?? user.preferredLoginName),
             });
     
             if (command.requestId) {
    @@ -395,6 +464,13 @@ export async function sendLoginname(command: SendLoginnameCommand) {
       let discoveredOrganization = command.organization;
       let effectiveLoginSettings = loginSettingsByContext;
     
    +  if (!command.organization && command.defaultOrganization) {
    +    const defaultLoginSettings = await getLoginSettings({ serviceConfig, organization: command.defaultOrganization });
    +    if (defaultLoginSettings) {
    +      effectiveLoginSettings = defaultLoginSettings;
    +    }
    +  }
    +
       if (!discoveredOrganization && command.loginName && ORG_SUFFIX_REGEX.test(command.loginName)) {
         const matched = ORG_SUFFIX_REGEX.exec(command.loginName);
         const suffix = matched?.[1] ?? "";
    @@ -428,7 +504,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
           return resp;
         }
         console.log("IDP redirect failed, returning user not found");
    -    return { error: t("errors.userNotFound") };
    +
    +    return preventUserEnumeration(discoveredOrganization);
       } else if (effectiveLoginSettings?.allowRegister && effectiveLoginSettings?.allowUsernamePassword) {
         console.log("register and password both allowed");
         // do not register user if ignoreUnknownUsernames is set
    @@ -453,23 +530,5 @@ export async function sendLoginname(command: SendLoginnameCommand) {
         }
       }
     
    -  if (effectiveLoginSettings?.ignoreUnknownUsernames) {
    -    console.log("ignoreUnknownUsernames is true, redirecting to password");
    -    const paramsPasswordDefault = new URLSearchParams({
    -      loginName: command.loginName,
    -    });
    -
    -    if (command.requestId) {
    -      paramsPasswordDefault.append("requestId", command.requestId);
    -    }
    -
    -    if (discoveredOrganization) {
    -      paramsPasswordDefault.append("organization", discoveredOrganization);
    -    }
    -
    -    return { redirect: "/password?" + paramsPasswordDefault };
    -  }
    -
    -  console.log("no valid registration option found, returning user not found");
    -  return { error: t("errors.userNotFound") };
    +  return preventUserEnumeration(discoveredOrganization);
     }
    
  • apps/login/src/lib/server/passkeys.test.ts+72 9 modified
    @@ -20,10 +20,12 @@ vi.mock("../service-url", () => ({
     vi.mock("../zitadel", () => ({
       getLoginSettings: vi.fn(),
       getUserByID: vi.fn(),
    +  listUsers: vi.fn(),
     }));
     
     vi.mock("./cookie", () => ({
       setSessionAndUpdateCookie: vi.fn(),
    +  createSessionAndUpdateCookie: vi.fn(),
     }));
     
     vi.mock("../cookies", () => ({
    @@ -50,7 +52,9 @@ describe("sendPasskey", () => {
       let mockGetServiceUrlFromHeaders: any;
       let mockGetLoginSettings: any;
       let mockGetUserByID: any;
    +  let mockListUsers: any;
       let mockSetSessionAndUpdateCookie: any;
    +  let mockCreateSessionAndUpdateCookie: any;
       let mockGetSessionCookieById: any;
       let mockGetSessionCookieByLoginName: any;
       let mockGetMostRecentSessionCookie: any;
    @@ -63,8 +67,8 @@ describe("sendPasskey", () => {
         // Import mocked modules
         const { headers } = await import("next/headers");
         const { getServiceConfig } = await import("../service-url");
    -    const { getLoginSettings, getUserByID } = await import("../zitadel");
    -    const { setSessionAndUpdateCookie } = await import("./cookie");
    +    const { getLoginSettings, getUserByID, listUsers } = await import("../zitadel");
    +    const { setSessionAndUpdateCookie, createSessionAndUpdateCookie } = await import("./cookie");
         const { getSessionCookieById, getSessionCookieByLoginName, getMostRecentSessionCookie } = await import("../cookies");
         const { checkEmailVerification } = await import("../verify-helper");
         const { completeFlowOrGetUrl } = await import("../client");
    @@ -74,15 +78,23 @@ describe("sendPasskey", () => {
         mockGetServiceUrlFromHeaders = vi.mocked(getServiceConfig);
         mockGetLoginSettings = vi.mocked(getLoginSettings);
         mockGetUserByID = vi.mocked(getUserByID);
    +    mockListUsers = vi.mocked(listUsers);
         mockSetSessionAndUpdateCookie = vi.mocked(setSessionAndUpdateCookie);
    +    mockCreateSessionAndUpdateCookie = vi.mocked(createSessionAndUpdateCookie);
    +    mockCreateSessionAndUpdateCookie.mockResolvedValue({
    +      session: { id: "new-session", factors: { user: { id: "user-1", loginName: "user" } } } as any,
    +      sessionCookie: { id: "new-session", token: "token", loginName: "user" },
    +    });
         mockGetSessionCookieById = vi.mocked(getSessionCookieById);
         mockGetSessionCookieByLoginName = vi.mocked(getSessionCookieByLoginName);
         mockGetMostRecentSessionCookie = vi.mocked(getMostRecentSessionCookie);
         mockCheckEmailVerification = vi.mocked(checkEmailVerification);
         mockCompleteFlowOrGetUrl = vi.mocked(completeFlowOrGetUrl);
     
         // Default mock implementations
    -    mockHeaders.mockResolvedValue(new Headers());
    +    const headersList = new Headers();
    +    headersList.set("host", "test.com");
    +    mockHeaders.mockResolvedValue(headersList);
         mockGetServiceUrlFromHeaders.mockReturnValue({
           serviceUrl: "https://example.com",
         });
    @@ -104,15 +116,16 @@ describe("sendPasskey", () => {
           });
     
           expect(result).toEqual({
    -        error: "verify.errors.couldNotFindSession",
    +        error: "couldNotFindSession",
           });
           expect(mockGetSessionCookieById).toHaveBeenCalledWith({
             sessionId: "test-session-id",
           });
         });
     
         test("should return error when session cookie is not found by loginName", async () => {
    -      mockGetSessionCookieByLoginName.mockResolvedValue(null);
    +      mockGetSessionCookieByLoginName.mockResolvedValue(null); // Not found
    +      mockCreateSessionAndUpdateCookie.mockRejectedValue(new Error("Creation failed")); // Force creation failure
     
           const result = await sendPasskey({
             loginName: "test@example.com",
    @@ -121,7 +134,7 @@ describe("sendPasskey", () => {
           });
     
           expect(result).toEqual({
    -        error: "verify.errors.couldNotFindSession",
    +        error: "couldNotFindSession",
           });
           expect(mockGetSessionCookieByLoginName).toHaveBeenCalledWith({
             loginName: "test@example.com",
    @@ -137,7 +150,7 @@ describe("sendPasskey", () => {
           });
     
           expect(result).toEqual({
    -        error: "verify.errors.couldNotFindSession",
    +        error: "couldNotFindSession",
           });
           expect(mockGetMostRecentSessionCookie).toHaveBeenCalled();
         });
    @@ -163,7 +176,7 @@ describe("sendPasskey", () => {
           });
     
           expect(result).toEqual({
    -        error: "verify.errors.couldNotUpdateSession",
    +        error: "couldNotUpdateSession",
           });
         });
     
    @@ -176,8 +189,58 @@ describe("sendPasskey", () => {
           });
     
           expect(result).toEqual({
    -        error: "verify.errors.couldNotUpdateSession",
    +        error: "couldNotUpdateSession",
    +      });
    +    });
    +
    +    test("should fallback to createSessionAndUpdateCookie when setSessionAndUpdateCookie fails and checks are present", async () => {
    +      mockSetSessionAndUpdateCookie.mockRejectedValue(new Error("session already terminated"));
    +
    +      mockCreateSessionAndUpdateCookie.mockResolvedValue({
    +        session: {
    +          id: "new-session-123",
    +          factors: {
    +            user: {
    +              id: "user-123",
    +              loginName: "test@example.com",
    +            },
    +          },
    +        },
    +        sessionCookie: {
    +          id: "new-session-123",
    +          token: "new-token",
    +        },
           });
    +
    +      mockListUsers.mockResolvedValue({
    +        details: { totalResult: BigInt(1) },
    +        result: [{ userId: "user-123" }],
    +      });
    +
    +      mockGetUserByID.mockResolvedValue({
    +        user: {
    +          id: "user-123",
    +          type: {
    +            case: "human",
    +            value: { email: { isVerified: true } },
    +          },
    +        },
    +      });
    +
    +      mockCheckEmailVerification.mockResolvedValue(true);
    +      mockCompleteFlowOrGetUrl.mockResolvedValue({ redirect: "/dashboard" });
    +
    +      const result = await sendPasskey({
    +        sessionId: "session-123",
    +        checks: { webAuthN: { credentialAssertionData: {} } } as any,
    +      });
    +
    +      // It should succeed with the new session
    +      expect(result).toEqual({
    +        redirect: "/dashboard",
    +      });
    +
    +      expect(mockCreateSessionAndUpdateCookie).toHaveBeenCalled();
         });
       });
     
    
  • apps/login/src/lib/server/passkeys.ts+40 43 modified
    @@ -19,12 +19,13 @@ import {
     import { headers } from "next/headers";
     import { userAgent } from "next/server";
     import { getTranslations } from "next-intl/server";
    -import { getMostRecentSessionCookie, getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
    +import { getSessionCookieById } from "../cookies";
     import { getServiceConfig } from "../service-url";
     import { checkEmailVerification, checkUserVerification } from "../verify-helper";
    -import { createSessionAndUpdateCookie, setSessionAndUpdateCookie } from "./cookie";
     import { getPublicHost } from "./host";
    +import { updateOrCreateSession } from "./session";
     import { completeFlowOrGetUrl } from "../client";
    +import { createSessionAndUpdateCookie } from "./cookie";
     
     type VerifyPasskeyCommand = {
       passkeyId: string;
    @@ -74,6 +75,11 @@ export async function registerPasskeyLink(
       if (command.sessionId) {
         // Session-based flow (existing logic)
         const sessionCookie = await getSessionCookieById({ sessionId: command.sessionId });
    +
    +    if (!sessionCookie) {
    +      return { error: "Could not get session cookie" };
    +    }
    +
         session = await getSession({ serviceConfig, sessionId: sessionCookie.id, sessionToken: sessionCookie.token });
     
         if (!session?.session?.factors?.user?.id) {
    @@ -142,10 +148,11 @@ export async function registerPasskeyLink(
           },
         });
     
    -    createdSession = await createSessionAndUpdateCookie({
    +    const result = await createSessionAndUpdateCookie({
           checks,
           requestId: undefined, // No requestId in passkey registration context, TODO: consider if needed
         });
    +    createdSession = result.session;
     
         if (!createdSession) {
           return { error: "Could not create session" };
    @@ -196,6 +203,11 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
         const sessionCookie = await getSessionCookieById({
           sessionId: command.sessionId,
         });
    +
    +    if (!sessionCookie) {
    +      throw new Error("Could not get session cookie");
    +    }
    +
         const session = await getSession({ serviceConfig, sessionId: sessionCookie.id, sessionToken: sessionCookie.token });
         const userId = session?.session?.factors?.user?.id;
     
    @@ -241,56 +253,41 @@ export async function sendPasskey(command: SendPasskeyCommand) {
     
       const t = await getTranslations("passkey");
     
    -  const recentSession = sessionId
    -    ? await getSessionCookieById({ sessionId })
    -    : loginName
    -      ? await getSessionCookieByLoginName({ loginName, organization })
    -      : await getMostRecentSessionCookie();
    +  const result = await updateOrCreateSession({
    +    loginName,
    +    sessionId,
    +    organization,
    +    checks,
    +    requestId,
    +    lifetime: command.lifetime,
    +  });
     
    -  if (!recentSession) {
    -    return {
    -      error: t("verify.errors.couldNotFindSession"),
    -    };
    +  if (result.error) {
    +    // try to interpret validation errors as translation keys if possible, or fallback to generic
    +    // For now returning the error string directly as key or default
    +    return { error: result.error };
       }
     
    +  // transformation to partial session for compatibility
    +  const session = {
    +    id: result.sessionId,
    +    factors: result.factors,
    +    // @ts-ignore
    +    challenges: result.challenges,
    +  };
    +
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
    -
       const loginSettings = await getLoginSettings({ serviceConfig, organization });
     
    -  let lifetime = command.lifetime; // Use provided lifetime first
    -
    -  if (!lifetime) {
    -    lifetime = checks?.webAuthN
    -      ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
    -      : checks?.otpEmail || checks?.otpSms
    -        ? loginSettings?.secondFactorCheckLifetime
    -        : undefined;
    -  }
    -
    -  if (!lifetime || !lifetime.seconds) {
    -    console.warn("No passkey lifetime provided, defaulting to 24 hours");
    -
    -    lifetime = {
    -      seconds: BigInt(60 * 60 * 24), // default to 24 hours
    -      nanos: 0,
    -    } as Duration;
    -  }
    -
    -  const session = await setSessionAndUpdateCookie({
    -    recentCookie: recentSession,
    -    checks,
    -    requestId,
    -    lifetime,
    -  });
    -
    -  if (!session || !session?.factors?.user?.id) {
    -    return { error: t("verify.errors.couldNotUpdateSession") };
    +  const userId = session?.factors?.user?.id;
    +  if (!userId) {
    +    return { error: t("verify.errors.couldNotFindSession") };
       }
     
       let userResponse;
       try {
    -    userResponse = await getUserByID({ serviceConfig, userId: session?.factors?.user?.id });
    +    userResponse = await getUserByID({ serviceConfig, userId });
       } catch (error) {
         console.error("Error fetching user by ID:", error);
         return { error: t("verify.errors.couldNotGetUser") };
    @@ -302,7 +299,7 @@ export async function sendPasskey(command: SendPasskeyCommand) {
     
       const humanUser = userResponse.user.type.case === "human" ? userResponse.user.type.value : undefined;
     
    -  const emailVerificationCheck = checkEmailVerification(session, humanUser, organization, requestId);
    +  const emailVerificationCheck = checkEmailVerification(session as any, humanUser, organization, requestId);
     
       if (emailVerificationCheck?.redirect) {
         return emailVerificationCheck;
    
  • apps/login/src/lib/server/password-send.test.ts+156 0 added
    @@ -0,0 +1,156 @@
    +import { create } from "@zitadel/client";
    +import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
    +import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
    +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
    +import { beforeEach, describe, expect, test, vi } from "vitest";
    +import { sendPassword } from "./password";
    +
    +// Mock dependencies
    +vi.mock("next/headers", () => ({
    +  headers: vi.fn(),
    +}));
    +
    +vi.mock("@zitadel/client", () => ({
    +  create: vi.fn((schema, data) => data),
    +  ConnectError: class extends Error {
    +    code: number;
    +    constructor(msg: string, code: number) {
    +      super(msg);
    +      this.code = code;
    +    }
    +  },
    +  timestampDate: (ts: any) => new Date(ts.seconds * 1000),
    +}));
    +
    +vi.mock("@zitadel/client/v2", () => ({
    +  createUserServiceClient: vi.fn(),
    +}));
    +
    +vi.mock("../service-url", () => ({
    +  getServiceConfig: vi.fn(),
    +}));
    +
    +vi.mock("../zitadel", () => ({
    +  getLoginSettings: vi.fn(),
    +  listAuthenticationMethodTypes: vi.fn(),
    +  getSession: vi.fn(),
    +  setPassword: vi.fn(),
    +  createServerTransport: vi.fn(),
    +  listUsers: vi.fn(),
    +  getLockoutSettings: vi.fn(),
    +  getPasswordExpirySettings: vi.fn(),
    +  getUserByID: vi.fn(),
    +  searchUsers: vi.fn(),
    +}));
    +
    +vi.mock("../cookies", () => ({
    +  getSessionCookieByLoginName: vi.fn(),
    +  getSessionCookieById: vi.fn(),
    +}));
    +
    +vi.mock("../client", () => ({
    +  completeFlowOrGetUrl: vi.fn(),
    +}));
    +
    +vi.mock("@/lib/server/cookie", () => ({
    +  createSessionAndUpdateCookie: vi.fn(),
    +  setSessionAndUpdateCookie: vi.fn(),
    +}));
    +
    +vi.mock("../verify-helper", () => ({
    +  checkPasswordChangeRequired: vi.fn(),
    +  checkEmailVerification: vi.fn(),
    +  checkMFAFactors: vi.fn(),
    +}));
    +
    +vi.mock("next-intl/server", () => ({
    +  getTranslations: vi.fn(() => (key: string) => key),
    +}));
    +
    +describe("sendPassword", () => {
    +  let mockHeaders: any;
    +  let mockGetServiceConfig: any;
    +  let mockGetSessionCookieByLoginName: any;
    +  let mockSearchUsers: any;
    +  let mockGetLoginSettings: any;
    +  let mockCreateSessionAndUpdateCookie: any;
    +  let mockCompleteFlowOrGetUrl: any;
    +  let mockListAuthenticationMethodTypes: any;
    +  let mockCheckMFAFactors: any;
    +
    +  beforeEach(async () => {
    +    vi.clearAllMocks();
    +
    +    const { headers } = await import("next/headers");
    +    const { getServiceConfig } = await import("../service-url");
    +    const { getSessionCookieByLoginName } = await import("../cookies");
    +    const { getLoginSettings, listAuthenticationMethodTypes, searchUsers } = await import("../zitadel");
    +    const { createSessionAndUpdateCookie } = await import("@/lib/server/cookie");
    +    const { completeFlowOrGetUrl } = await import("../client");
    +    const { checkMFAFactors } = await import("../verify-helper");
    +
    +    mockHeaders = vi.mocked(headers);
    +    mockGetServiceConfig = vi.mocked(getServiceConfig);
    +    mockGetSessionCookieByLoginName = vi.mocked(getSessionCookieByLoginName);
    +    mockSearchUsers = vi.mocked(searchUsers);
    +    mockGetLoginSettings = vi.mocked(getLoginSettings);
    +    mockCreateSessionAndUpdateCookie = vi.mocked(createSessionAndUpdateCookie);
    +    mockCompleteFlowOrGetUrl = vi.mocked(completeFlowOrGetUrl);
    +    mockListAuthenticationMethodTypes = vi.mocked(listAuthenticationMethodTypes);
    +    mockCheckMFAFactors = vi.mocked(checkMFAFactors);
    +
    +    mockHeaders.mockResolvedValue({});
    +    mockGetServiceConfig.mockReturnValue({ serviceConfig: { baseUrl: "https://api.example.com" } });
    +  });
    +
    +  test("should create session and verify password when no session exists", async () => {
    +    // 1. No existing session
    +    mockGetSessionCookieByLoginName.mockResolvedValue(null);
    +
    +    // 2. User exists
    +    mockSearchUsers.mockResolvedValue({
    +      result: [{ userId: "user123", type: { case: "human", value: {} }, state: UserState.ACTIVE }],
    +    });
    +
    +    // 3. Login settings
    +    mockGetLoginSettings.mockResolvedValue({ passwordCheckLifetime: { seconds: BigInt(86400) } });
    +
    +    // 4. Create session success
    +    mockCreateSessionAndUpdateCookie.mockResolvedValue({
    +      session: {
    +        id: "new-session-id",
    +        factors: {
    +          user: { id: "user123", loginName: "testuser", organizationId: "org123" },
    +          password: { verifiedAt: { seconds: 100 } },
    +        },
    +      },
    +      sessionCookie: {
    +        id: "new-session-id",
    +        token: "token123",
    +        organization: "org123",
    +      },
    +    });
    +
    +    // 5. Auth methods (no MFA)
    +    mockListAuthenticationMethodTypes.mockResolvedValue({
    +      authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +    });
    +
    +    // 6. MFA check passes
    +    mockCheckMFAFactors.mockResolvedValue(null);
    +
    +    // 7. Redirect
    +    mockCompleteFlowOrGetUrl.mockResolvedValue({ redirect: "/next-page" });
    +
    +    const result = await sendPassword({
    +      loginName: "testuser",
    +      checks: create(ChecksSchema, { password: { password: "password123" } }),
    +    });
    +
    +    // Verification
    +    expect(mockSearchUsers).toHaveBeenCalledWith(expect.objectContaining({ searchValue: "testuser" }));
    +    expect(mockCreateSessionAndUpdateCookie).toHaveBeenCalled();
    +    expect(mockCompleteFlowOrGetUrl).toHaveBeenCalled();
    +    expect(result).toEqual({ redirect: "/next-page" });
    +  });
    +});
    
  • apps/login/src/lib/server/password.test.ts+497 0 added
    @@ -0,0 +1,497 @@
    +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
    +import { beforeEach, describe, expect, test, vi } from "vitest";
    +import { changePassword, checkSessionAndSetPassword, resetPassword, sendPassword } from "./password";
    +
    +// Mock dependencies
    +vi.mock("next/headers", () => ({
    +  headers: vi.fn(),
    +}));
    +
    +vi.mock("@zitadel/client", () => ({
    +  create: vi.fn(),
    +  ConnectError: class extends Error {
    +    code: number;
    +    constructor(msg: string, code: number) {
    +      super(msg);
    +      this.code = code;
    +    }
    +  },
    +  timestampDate: (ts: any) => new Date(ts.seconds * 1000),
    +}));
    +
    +vi.mock("@zitadel/client/v2", () => ({
    +  createUserServiceClient: vi.fn(),
    +}));
    +
    +vi.mock("../service-url", () => ({
    +  getServiceConfig: vi.fn(),
    +}));
    +
    +vi.mock("../zitadel", () => ({
    +  getLoginSettings: vi.fn(),
    +  listAuthenticationMethodTypes: vi.fn(),
    +  getSession: vi.fn(),
    +  setPassword: vi.fn(),
    +  createServerTransport: vi.fn(),
    +  getLockoutSettings: vi.fn(),
    +  passwordReset: vi.fn(),
    +  getUserByID: vi.fn(),
    +  setUserPassword: vi.fn(),
    +  getPasswordExpirySettings: vi.fn(),
    +  searchUsers: vi.fn(),
    +}));
    +
    +vi.mock("./cookie", () => ({
    +  createSessionAndUpdateCookie: vi.fn(),
    +  setSessionAndUpdateCookie: vi.fn(),
    +}));
    +
    +vi.mock("../cookies", () => ({
    +  getSessionCookieById: vi.fn(),
    +  getSessionCookieByLoginName: vi.fn(),
    +}));
    +
    +vi.mock("next-intl/server", () => ({
    +  getTranslations: vi.fn(() => (key: string) => key),
    +}));
    +
    +vi.mock("../verify-helper", () => ({
    +  checkEmailVerification: vi.fn(),
    +  checkMFAFactors: vi.fn(),
    +  checkPasswordChangeRequired: vi.fn(),
    +  checkUserVerification: vi.fn(),
    +}));
    +
    +vi.mock("../client", () => ({
    +  completeFlowOrGetUrl: vi.fn(),
    +}));
    +
    +describe("checkSessionAndSetPassword", () => {
    +  let mockHeaders: any;
    +  let mockGetServiceConfig: any;
    +  let mockGetSessionCookieById: any;
    +  let mockGetSession: any;
    +  let mockListAuthenticationMethodTypes: any;
    +  let mockGetLoginSettings: any;
    +  let mockSetPassword: any; // Service account
    +  let mockCreateUserServiceClient: any; // User session
    +  let mockSetPasswordUser: any;
    +
    +  beforeEach(async () => {
    +    vi.clearAllMocks();
    +
    +    const { headers } = await import("next/headers");
    +    const { getServiceConfig } = await import("../service-url");
    +    const { getSessionCookieById } = await import("../cookies");
    +    const { getSession, listAuthenticationMethodTypes, getLoginSettings, setPassword } = await import("../zitadel");
    +    const { createUserServiceClient } = await import("@zitadel/client/v2");
    +
    +    mockHeaders = vi.mocked(headers);
    +    mockGetServiceConfig = vi.mocked(getServiceConfig);
    +    mockGetSessionCookieById = vi.mocked(getSessionCookieById);
    +    mockGetSession = vi.mocked(getSession);
    +    mockListAuthenticationMethodTypes = vi.mocked(listAuthenticationMethodTypes);
    +    mockGetLoginSettings = vi.mocked(getLoginSettings);
    +    mockSetPassword = vi.mocked(setPassword);
    +    mockCreateUserServiceClient = vi.mocked(createUserServiceClient);
    +
    +    mockHeaders.mockResolvedValue({});
    +    mockGetServiceConfig.mockReturnValue({ serviceConfig: { baseUrl: "https://api.example.com" } });
    +
    +    // Default session setup
    +    mockGetSessionCookieById.mockResolvedValue({ id: "session123", token: "token123" });
    +    mockGetSession.mockResolvedValue({
    +      session: {
    +        factors: {
    +          user: { id: "user123", organizationId: "org123" },
    +          password: { verifiedAt: { seconds: Math.floor(Date.now() / 1000) } }, // Password verified recently
    +        },
    +      },
    +    });
    +
    +    // Default: Only password method
    +    mockListAuthenticationMethodTypes.mockResolvedValue({
    +      authMethodTypes: [AuthenticationMethodType.PASSWORD],
    +    });
    +
    +    // Default: No forced MFA
    +    mockGetLoginSettings.mockResolvedValue({ forceMfa: false });
    +
    +    // Mock user service client
    +    mockSetPasswordUser = vi.fn().mockResolvedValue({});
    +    mockCreateUserServiceClient.mockReturnValue({
    +      setPassword: mockSetPasswordUser,
    +    });
    +
    +    mockSetPassword.mockResolvedValue({});
    +  });
    +
    +  test("should use user session when no MFA is configured", async () => {
    +    await checkSessionAndSetPassword({ sessionId: "session123", password: "newpassword" });
    +
    +    expect(mockCreateUserServiceClient).toHaveBeenCalled();
    +    expect(mockSetPasswordUser).toHaveBeenCalled();
    +    expect(mockSetPassword).not.toHaveBeenCalled();
    +  });
    +
    +  test("should use service account when MFA is configured but NOT verified in session", async () => {
    +    // User has TOTP configured
    +    mockListAuthenticationMethodTypes.mockResolvedValue({
    +      authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.TOTP],
    +    });
    +
    +    // Session only has password verified (no OTP)
    +    const now = Math.floor(Date.now() / 1000);
    +    mockGetSession.mockResolvedValue({
    +      session: {
    +        factors: {
    +          user: { id: "user123", organizationId: "org123" },
    +          password: { verifiedAt: { seconds: now - 60 } }, // Verified 1 minute ago
    +          // otp missing
    +        },
    +      },
    +    });
    +
    +    await checkSessionAndSetPassword({ sessionId: "session123", password: "newpassword" });
    +
    +    // EXPECTATION: Should use service account (mockSetPassword)
    +    // CURRENTLY: Will fail this test and use user session
    +    expect(mockSetPassword).toHaveBeenCalled();
    +    expect(mockCreateUserServiceClient).not.toHaveBeenCalled();
    +  });
    +
    +  test("should use user session when MFA is configured AND verified in session", async () => {
    +    // User has TOTP configured
    +    mockListAuthenticationMethodTypes.mockResolvedValue({
    +      authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.TOTP],
    +    });
    +
    +    // Session has both password and OTP verified
    +    mockGetSession.mockResolvedValue({
    +      session: {
    +        factors: {
    +          user: { id: "user123", organizationId: "org123" },
    +          password: { verifiedAt: { seconds: Math.floor(Date.now() / 1000) } },
    +          totp: { verifiedAt: { seconds: Math.floor(Date.now() / 1000) } }, // Verified
    +        },
    +      },
    +    });
    +
    +    await checkSessionAndSetPassword({ sessionId: "session123", password: "newpassword" });
    +
    +    expect(mockCreateUserServiceClient).toHaveBeenCalled();
    +    expect(mockSetPassword).not.toHaveBeenCalled();
    +  });
    +
    +  test("should fail when MFA is configured but not verified, and password verification is too old", async () => {
    +    // User has TOTP configured
    +    mockListAuthenticationMethodTypes.mockResolvedValue({
    +      authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.TOTP],
    +    });
    +
    +    // Session has password verified 10 minutes ago (600 seconds)
    +    const now = Math.floor(Date.now() / 1000);
    +    mockGetSession.mockResolvedValue({
    +      session: {
    +        factors: {
    +          user: { id: "user123", organizationId: "org123" },
    +          password: { verifiedAt: { seconds: now - 600 } },
    +          // otp missing
    +        },
    +      },
    +    });
    +
    +    const result = await checkSessionAndSetPassword({ sessionId: "session123", password: "newpassword" });
    +
    +    expect(result).toEqual({ error: "errors.passwordVerificationTooOld" });
    +    expect(mockSetPassword).not.toHaveBeenCalled();
    +  });
    +
    +  test("should use service account when MFA is configured but not verified, and password verification is recent", async () => {
    +    // User has TOTP configured
    +    mockListAuthenticationMethodTypes.mockResolvedValue({
    +      authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.TOTP],
    +    });
    +
    +    // Session has password verified 1 minute ago (60 seconds)
    +    const now = Math.floor(Date.now() / 1000);
    +    mockGetSession.mockResolvedValue({
    +      session: {
    +        factors: {
    +          user: { id: "user123", organizationId: "org123" },
    +          password: { verifiedAt: { seconds: now - 60 } },
    +          // otp missing
    +        },
    +      },
    +    });
    +
    +    await checkSessionAndSetPassword({ sessionId: "session123", password: "newpassword" });
    +
    +    expect(mockSetPassword).toHaveBeenCalled();
    +  });
    +});
    +
    +describe("sendPassword", () => {
    +  let mockHeaders: any;
    +  let mockGetServiceConfig: any;
    +  let mockGetSessionCookieByLoginName: any;
    +  let mockSearchUsers: any;
    +  let mockGetLoginSettings: any;
    +  let mockCreateSessionAndUpdateCookie: any;
    +  let mockSetSessionAndUpdateCookie: any;
    +  let mockGetLockoutSettings: any;
    +
    +  beforeEach(async () => {
    +    vi.clearAllMocks();
    +
    +    const { headers } = await import("next/headers");
    +    const { getServiceConfig } = await import("../service-url");
    +    const { getSessionCookieByLoginName } = await import("../cookies");
    +    const { getLoginSettings, getLockoutSettings, searchUsers } = await import("../zitadel");
    +    const { createSessionAndUpdateCookie, setSessionAndUpdateCookie } = await import("./cookie");
    +
    +    mockHeaders = vi.mocked(headers);
    +    mockGetServiceConfig = vi.mocked(getServiceConfig);
    +    mockGetSessionCookieByLoginName = vi.mocked(getSessionCookieByLoginName);
    +    mockSearchUsers = vi.mocked(searchUsers);
    +    mockGetLoginSettings = vi.mocked(getLoginSettings);
    +    mockCreateSessionAndUpdateCookie = vi.mocked(createSessionAndUpdateCookie);
    +    mockSetSessionAndUpdateCookie = vi.mocked(setSessionAndUpdateCookie);
    +    mockCreateSessionAndUpdateCookie = vi.mocked(createSessionAndUpdateCookie);
    +    mockSetSessionAndUpdateCookie = vi.mocked(setSessionAndUpdateCookie);
    +    mockGetLockoutSettings = vi.mocked(getLockoutSettings);
    +
    +    const { completeFlowOrGetUrl } = await import("../client");
    +    vi.mocked(completeFlowOrGetUrl).mockResolvedValue({ redirect: "https://example.com" });
    +
    +    // eslint-disable-next-line
    +    const verifyHelper = await import("../verify-helper");
    +    vi.mocked(verifyHelper.checkPasswordChangeRequired).mockReturnValue(undefined);
    +    vi.mocked(verifyHelper.checkEmailVerification).mockReturnValue(undefined);
    +    vi.mocked(verifyHelper.checkMFAFactors).mockResolvedValue(undefined);
    +
    +    mockHeaders.mockResolvedValue({});
    +    mockGetServiceConfig.mockReturnValue({ serviceConfig: { baseUrl: "https://api.example.com" } });
    +  });
    +
    +  test("should return generic error when user not found and ignoreUnknownUsernames is true", async () => {
    +    mockGetSessionCookieByLoginName.mockResolvedValue(null);
    +    mockSearchUsers.mockResolvedValue({ result: [] });
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: true });
    +
    +    const result = await sendPassword({
    +      loginName: "unknown@example.com",
    +      checks: { password: { password: "password" } } as any,
    +    });
    +
    +    expect(result).toEqual({ error: "errors.failedToAuthenticateNoLimit" });
    +  });
    +
    +  test("should return specific error when user not found and ignoreUnknownUsernames is false", async () => {
    +    mockGetSessionCookieByLoginName.mockResolvedValue(null);
    +    mockSearchUsers.mockResolvedValue({ result: [] });
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: false });
    +
    +    const result = await sendPassword({
    +      loginName: "unknown@example.com",
    +      checks: { password: { password: "password" } } as any,
    +    });
    +
    +    expect(result).toEqual({ error: "errors.couldNotVerifyPassword" });
    +  });
    +
    +  test("should return generic error when password verification fails and ignoreUnknownUsernames is true", async () => {
    +    mockGetSessionCookieByLoginName.mockResolvedValue(null);
    +    mockSearchUsers.mockResolvedValue({
    +      result: [{ userId: "user123", type: { case: "human", value: {} }, state: 1 }],
    +    });
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: true });
    +    mockCreateSessionAndUpdateCookie.mockRejectedValue({ failedAttempts: 1 });
    +
    +    const result = await sendPassword({
    +      loginName: "user@example.com",
    +      checks: { password: { password: "wrong" } } as any,
    +    });
    +
    +    expect(result).toEqual({ error: "errors.failedToAuthenticateNoLimit" });
    +  });
    +
    +  test("should return generic error when session creation fails with unknown error and ignoreUnknownUsernames is true", async () => {
    +    mockGetSessionCookieByLoginName.mockResolvedValue(null);
    +    mockSearchUsers.mockResolvedValue({
    +      result: [{ userId: "user123", type: { case: "human", value: {} }, state: 1 }],
    +    });
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: true });
    +    // Simulate an error that is NOT a failed attempt error (e.g. database error)
    +    mockCreateSessionAndUpdateCookie.mockRejectedValue(new Error("Some internal error"));
    +
    +    const result = await sendPassword({
    +      loginName: "user@example.com",
    +      checks: { password: { password: "correct" } } as any,
    +    });
    +
    +    expect(result).toEqual({ error: "errors.failedToAuthenticateNoLimit" });
    +  });
    +
    +  test("should return specific error with lockout info when password verification fails and ignoreUnknownUsernames is false", async () => {
    +    mockGetSessionCookieByLoginName.mockResolvedValue(null);
    +    mockSearchUsers.mockResolvedValue({
    +      result: [{ userId: "user123", type: { case: "human", value: {} }, state: 1 }],
    +    });
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: false });
    +    mockCreateSessionAndUpdateCookie.mockRejectedValue({ failedAttempts: 1 });
    +    mockGetLockoutSettings.mockResolvedValue({ maxPasswordAttempts: BigInt(5) });
    +
    +    const result = await sendPassword({
    +      loginName: "user@example.com",
    +      checks: { password: { password: "wrong" } } as any,
    +    });
    +
    +    expect(result).toEqual({
    +      error: "errors.failedToAuthenticate",
    +    });
    +  });
    +
    +  test("should recreate session when session verification fails with keys/session terminated error and ignoreUnknownUsernames is true", async () => {
    +    mockGetSessionCookieByLoginName.mockResolvedValue({
    +      id: "session123",
    +      token: "token123",
    +      organization: "org123",
    +    });
    +
    +    const terminatedError = { message: "session already terminated" };
    +    mockSetSessionAndUpdateCookie.mockRejectedValue(terminatedError);
    +
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: true });
    +
    +    mockSearchUsers.mockResolvedValue({
    +      result: [
    +        {
    +          userId: "user123",
    +          type: {
    +            case: "human",
    +            value: {
    +              email: { email: "user@example.com", isVerified: true },
    +              phone: { phone: "+1234567890", isVerified: true },
    +            },
    +          },
    +          state: 1, // Active
    +          preferredLoginName: "user@example.com",
    +        },
    +      ],
    +    });
    +
    +    mockCreateSessionAndUpdateCookie.mockResolvedValue({
    +      session: { factors: { user: { id: "user123", loginName: "user@example.com" } } },
    +      sessionCookie: { id: "newSession", token: "newToken" },
    +    });
    +
    +    // Execute
    +    await sendPassword({
    +      loginName: "user@example.com",
    +      checks: { password: { password: "password" } } as any,
    +    });
    +
    +    expect(mockSetSessionAndUpdateCookie).toHaveBeenCalled();
    +    expect(mockCreateSessionAndUpdateCookie).toHaveBeenCalled();
    +  });
    +});
    +describe("resetPassword", () => {
    +  let mockHeaders: any;
    +  let mockGetServiceConfig: any;
    +  let mockSearchUsers: any;
    +  let mockGetLoginSettings: any;
    +  let mockPasswordReset: any;
    +
    +  beforeEach(async () => {
    +    vi.clearAllMocks();
    +
    +    const { headers } = await import("next/headers");
    +    const { getServiceConfig } = await import("../service-url");
    +    const { getLoginSettings, passwordReset, searchUsers } = await import("../zitadel");
    +
    +    mockHeaders = vi.mocked(headers);
    +    mockGetServiceConfig = vi.mocked(getServiceConfig);
    +    mockSearchUsers = vi.mocked(searchUsers);
    +    mockGetLoginSettings = vi.mocked(getLoginSettings);
    +    mockPasswordReset = vi.mocked(passwordReset);
    +
    +    mockHeaders.mockResolvedValue({ get: vi.fn(() => "example.com") });
    +    mockGetServiceConfig.mockReturnValue({ serviceConfig: { baseUrl: "https://api.example.com" } });
    +  });
    +
    +  test("should return generic success when user not found and ignoreUnknownUsernames is true", async () => {
    +    mockSearchUsers.mockResolvedValue({ result: [] });
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: true });
    +
    +    const result = await resetPassword({
    +      loginName: "unknown@example.com",
    +    });
    +
    +    expect(result).toEqual({});
    +    expect(mockPasswordReset).not.toHaveBeenCalled();
    +  });
    +
    +  test("should return specific error when user not found and ignoreUnknownUsernames is false", async () => {
    +    mockSearchUsers.mockResolvedValue({ result: [] });
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: false });
    +
    +    const result = await resetPassword({
    +      loginName: "unknown@example.com",
    +    });
    +
    +    expect(result).toEqual({ error: "errors.couldNotSendResetLink" });
    +    expect(mockPasswordReset).not.toHaveBeenCalled();
    +  });
    +});
    +
    +describe("changePassword", () => {
    +  let mockHeaders: any;
    +  let mockGetServiceConfig: any;
    +  let mockGetUserByID: any;
    +  let mockGetLoginSettings: any;
    +  let mockSetUserPassword: any;
    +
    +  beforeEach(async () => {
    +    vi.clearAllMocks();
    +
    +    const { headers } = await import("next/headers");
    +    const { getServiceConfig } = await import("../service-url");
    +    const { getUserByID, getLoginSettings, setUserPassword } = await import("../zitadel");
    +
    +    mockHeaders = vi.mocked(headers);
    +    mockGetServiceConfig = vi.mocked(getServiceConfig);
    +    mockGetUserByID = vi.mocked(getUserByID);
    +    mockGetLoginSettings = vi.mocked(getLoginSettings);
    +    mockSetUserPassword = vi.mocked(setUserPassword);
    +
    +    mockHeaders.mockResolvedValue({ get: vi.fn(() => "example.com") });
    +    mockGetServiceConfig.mockReturnValue({ serviceConfig: { baseUrl: "https://api.example.com" } });
    +  });
    +
    +  test("should return generic error when user not found and ignoreUnknownUsernames is true", async () => {
    +    mockGetUserByID.mockResolvedValue({}); // User not found
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: true });
    +
    +    const result = await changePassword({
    +      userId: "unknown",
    +      password: "newpassword",
    +    });
    +
    +    expect(result).toEqual({ error: "set.errors.couldNotSetPassword" });
    +    expect(mockSetUserPassword).not.toHaveBeenCalled();
    +  });
    +
    +  test("should return specific error when user not found and ignoreUnknownUsernames is false", async () => {
    +    mockGetUserByID.mockResolvedValue({}); // User not found
    +    mockGetLoginSettings.mockResolvedValue({ ignoreUnknownUsernames: false });
    +
    +    const result = await changePassword({
    +      userId: "unknown",
    +      password: "newpassword",
    +    });
    +
    +    expect(result).toEqual({ error: "errors.couldNotSendResetLink" });
    +    expect(mockSetUserPassword).not.toHaveBeenCalled();
    +  });
    +});
    
  • apps/login/src/lib/server/password.ts+230 87 modified
    @@ -8,35 +8,36 @@ import {
       getSession,
       getUserByID,
       listAuthenticationMethodTypes,
    -  listUsers,
       passwordReset,
    +  searchUsers,
       ServiceConfig,
       setPassword,
       setUserPassword,
     } from "@/lib/zitadel";
    -import { ConnectError, create, Duration } from "@zitadel/client";
    +import { ConnectError, create, Duration, timestampDate } from "@zitadel/client";
     import { createUserServiceClient } from "@zitadel/client/v2";
     import { Checks, ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
     import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
     import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
    -import { SetPasswordRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
    +import { AuthenticationMethodType, SetPasswordRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
    +import { getTranslations } from "next-intl/server";
     import { headers } from "next/headers";
     import { completeFlowOrGetUrl } from "../client";
     import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
     import { getServiceConfig } from "../service-url";
    -import { getPublicHostWithProtocol } from "./host";
     import {
       checkEmailVerification,
       checkMFAFactors,
       checkPasswordChangeRequired,
       checkUserVerification,
     } from "../verify-helper";
     import { createServerTransport } from "../zitadel";
    -import { getTranslations } from "next-intl/server";
    +import { getPublicHostWithProtocol } from "./host";
     
     type ResetPasswordCommand = {
       loginName: string;
       organization?: string;
    +  defaultOrganization?: string;
       requestId?: string;
     };
     
    @@ -49,12 +50,67 @@ export async function resetPassword(command: ResetPasswordCommand) {
       // Get the original host that the user sees with protocol
       const hostWithProtocol = await getPublicHostWithProtocol(_headers);
     
    -  const users = await listUsers({ serviceConfig, loginName: command.loginName, organizationId: command.organization });
    +  const loginSettings = await getLoginSettings({
    +    serviceConfig,
    +    organization: command.organization ?? command.defaultOrganization,
    +  });
    +
    +  if (!loginSettings) {
    +    return { error: t("errors.couldNotSendResetLink") };
    +  }
    +
    +  const searchResult = await searchUsers({
    +    serviceConfig,
    +    searchValue: command.loginName,
    +    organizationId: command.organization,
    +    loginSettings,
    +  });
     
    -  if (!users.details || users.details.totalResult !== BigInt(1) || !users.result[0].userId) {
    +  if (
    +    !searchResult ||
    +    !("result" in searchResult) ||
    +    !searchResult.result ||
    +    searchResult.result.length !== 1 ||
    +    !searchResult.result[0].userId
    +  ) {
    +    if (loginSettings?.ignoreUnknownUsernames) {
    +      await new Promise((resolve) => setTimeout(resolve, 2000));
    +      return {};
    +    }
         return { error: t("errors.couldNotSendResetLink") };
       }
    -  const userId = users.result[0].userId;
    +  const user = searchResult.result[0];
    +  const humanUser = user.type.case === "human" ? user.type.value : undefined;
    +
    +  const userLoginSettings = await getLoginSettings({ serviceConfig, organization: user.details?.resourceOwner });
    +
    +  if (userLoginSettings?.disableLoginWithEmail && userLoginSettings?.disableLoginWithPhone) {
    +    if (user.preferredLoginName !== command.loginName) {
    +      if (userLoginSettings?.ignoreUnknownUsernames) {
    +        await new Promise((resolve) => setTimeout(resolve, 2000));
    +        return {};
    +      }
    +      return { error: t("errors.couldNotSendResetLink") };
    +    }
    +  } else if (userLoginSettings?.disableLoginWithEmail) {
    +    if (user.preferredLoginName !== command.loginName || humanUser?.phone?.phone !== command.loginName) {
    +      if (userLoginSettings?.ignoreUnknownUsernames) {
    +        await new Promise((resolve) => setTimeout(resolve, 2000));
    +        return {};
    +      }
    +      return { error: t("errors.couldNotSendResetLink") };
    +    }
    +  } else if (userLoginSettings?.disableLoginWithPhone) {
    +    if (user.preferredLoginName !== command.loginName || humanUser?.email?.email !== command.loginName) {
    +      if (userLoginSettings?.ignoreUnknownUsernames) {
    +        await new Promise((resolve) => setTimeout(resolve, 2000));
    +        return {};
    +      }
    +      return { error: t("errors.couldNotSendResetLink") };
    +    }
    +  }
    +
    +  const userId = user.userId;
     
       const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
     
    @@ -70,6 +126,7 @@ export async function resetPassword(command: ResetPasswordCommand) {
     export type UpdateSessionCommand = {
       loginName: string;
       organization?: string;
    +  defaultOrganization?: string;
       checks: Checks;
       requestId?: string;
     };
    @@ -82,35 +139,123 @@ export async function sendPassword(command: UpdateSessionCommand): Promise<{ err
       let sessionCookie = await getSessionCookieByLoginName({
         loginName: command.loginName,
         organization: command.organization,
    -  }).catch((error) => {
    -    console.warn("Ignored error:", error);
       });
     
       let session;
    -  let user: User;
    -  let loginSettings: LoginSettings | undefined;
    +  let user: User | undefined;
    +  let loginSettingsByContext: LoginSettings | undefined;
    +  let loginSettingsByUser: LoginSettings | undefined;
    +
    +  if (sessionCookie) {
    +    try {
    +      loginSettingsByUser = await getLoginSettings({ serviceConfig, organization: sessionCookie.organization });
    +
    +      if (loginSettingsByUser) {
    +        let lifetime = loginSettingsByUser.passwordCheckLifetime;
    +
    +        if (!lifetime || !lifetime.seconds) {
    +          console.warn("No password lifetime provided, defaulting to 24 hours");
    +          lifetime = {
    +            seconds: BigInt(60 * 60 * 24), // default to 24 hours
    +            nanos: 0,
    +          } as Duration;
    +        }
    +
    +        session = await setSessionAndUpdateCookie({
    +          recentCookie: sessionCookie,
    +          checks: command.checks,
    +          requestId: command.requestId,
    +          lifetime,
    +        });
    +      } else {
    +        // Force fallback if settings can't be loaded
    +        throw new Error("Could not load login settings");
    +      }
    +    } catch {
    +      console.warn("[Password] Could not update session");
    +      // If the session was terminated or any other error occurred during update,
    +      // we fall back to creating a new session.
    +      sessionCookie = undefined;
    +      session = undefined;
    +    }
    +  }
     
       if (!sessionCookie) {
    -    const users = await listUsers({ serviceConfig, loginName: command.loginName, organizationId: command.organization });
    +    if (!loginSettingsByContext) {
    +      loginSettingsByContext = await getLoginSettings({
    +        serviceConfig,
    +        organization: command.organization ?? command.defaultOrganization,
    +      });
    +    }
     
    -    if (users.details?.totalResult == BigInt(1) && users.result[0].userId) {
    -      user = users.result[0];
    +    // Force fallback if settings can't be loaded
    +    if (!loginSettingsByContext) {
    +      // this is a fake error message to hide that the user does not even exist
    +      return { error: t("errors.couldNotVerifyPassword") };
    +    }
    +
    +    const searchResult = await searchUsers({
    +      serviceConfig,
    +      searchValue: command.loginName,
    +      organizationId: command.organization,
    +      loginSettings: loginSettingsByContext,
    +    });
    +
    +    if (
    +      searchResult &&
    +      "result" in searchResult &&
    +      searchResult.result &&
    +      searchResult.result.length === 1 &&
    +      searchResult.result[0].userId
    +    ) {
    +      user = searchResult.result[0];
    +      const humanUser = user.type.case === "human" ? user.type.value : undefined;
    +
    +      const userLoginSettings = await getLoginSettings({ serviceConfig, organization: user.details?.resourceOwner });
    +
    +      // recheck login settings after user discovery, as the search might have been done without org scope
    +      if (userLoginSettings?.disableLoginWithEmail && userLoginSettings?.disableLoginWithPhone) {
    +        if (user.preferredLoginName !== command.loginName) {
    +          // emulate user not found to prevent enumeration (use context settings not user settings)
    +          if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +            return { error: t("errors.failedToAuthenticateNoLimit") };
    +          }
    +          return { error: t("errors.couldNotVerifyPassword") };
    +        }
    +      } else if (userLoginSettings?.disableLoginWithEmail) {
    +        if (user.preferredLoginName !== command.loginName || humanUser?.phone?.phone !== command.loginName) {
    +          if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +            return { error: t("errors.failedToAuthenticateNoLimit") };
    +          }
    +          return { error: t("errors.couldNotVerifyPassword") };
    +        }
    +      } else if (userLoginSettings?.disableLoginWithPhone) {
    +        if (user.preferredLoginName !== command.loginName || humanUser?.email?.email !== command.loginName) {
    +          if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +            return { error: t("errors.failedToAuthenticateNoLimit") };
    +          }
    +          return { error: t("errors.couldNotVerifyPassword") };
    +        }
    +      }
     
           const checks = create(ChecksSchema, {
    -        user: { search: { case: "userId", value: users.result[0].userId } },
    +        user: { search: { case: "userId", value: user.userId } },
             password: { password: command.checks.password?.password },
           });
     
    -      loginSettings = await getLoginSettings({ serviceConfig, organization: command.organization });
    -
           try {
    -        session = await createSessionAndUpdateCookie({
    +        const result = await createSessionAndUpdateCookie({
               checks,
               requestId: command.requestId,
    -          lifetime: loginSettings?.passwordCheckLifetime,
    +          lifetime: loginSettingsByContext?.passwordCheckLifetime,
             });
    +        session = result.session;
    +        sessionCookie = result.sessionCookie;
           } catch (error: any) {
             if ("failedAttempts" in error && error.failedAttempts) {
    +          if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +            return { error: t("errors.failedToAuthenticateNoLimit") };
    +          }
               const lockoutSettings = await getLockoutSettings({ serviceConfig, orgId: command.organization });
     
               const hasLimit =
    @@ -126,83 +271,49 @@ export async function sendPassword(command: UpdateSessionCommand): Promise<{ err
                 }),
               };
             }
    +        if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +          return { error: t("errors.failedToAuthenticateNoLimit") };
    +        }
             return { error: t("errors.couldNotCreateSessionForUser") };
           }
         } else {
           // this is a fake error message to hide that the user does not even exist
    -      return { error: "Could not verify password" };
    -    }
    -
    -    // this is a fake error message to hide that the user does not even exist
    -    return { error: t("errors.couldNotVerifyPassword") };
    -  } else {
    -    loginSettings = await getLoginSettings({ serviceConfig, organization: sessionCookie.organization });
    -
    -    if (!loginSettings) {
    -      return { error: "Could not load login settings" };
    -    }
    -
    -    let lifetime = loginSettings.passwordCheckLifetime;
    -
    -    if (!lifetime || !lifetime.seconds) {
    -      console.warn("No password lifetime provided, defaulting to 24 hours");
    -      lifetime = {
    -        seconds: BigInt(60 * 60 * 24), // default to 24 hours
    -        nanos: 0,
    -      } as Duration;
    -    }
    -
    -    try {
    -      session = await setSessionAndUpdateCookie({
    -        recentCookie: sessionCookie,
    -        checks: command.checks,
    -        requestId: command.requestId,
    -        lifetime,
    -      });
    -    } catch (error: any) {
    -      if ("failedAttempts" in error && error.failedAttempts) {
    -        const lockoutSettings = await getLockoutSettings({ serviceConfig, orgId: command.organization });
    -
    -        const hasLimit =
    -          lockoutSettings?.maxPasswordAttempts !== undefined && lockoutSettings?.maxPasswordAttempts > BigInt(0);
    -        const locked = hasLimit && error.failedAttempts >= lockoutSettings?.maxPasswordAttempts;
    -        const messageKey = hasLimit ? "errors.failedToAuthenticate" : "errors.failedToAuthenticateNoLimit";
    -
    -        return {
    -          error: t(messageKey, {
    -            failedAttempts: error.failedAttempts,
    -            maxPasswordAttempts: hasLimit ? (lockoutSettings?.maxPasswordAttempts).toString() : "?",
    -            lockoutMessage: locked ? t("errors.accountLockedContactAdmin") : "",
    -          }),
    -        };
    +      if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +        return { error: t("errors.failedToAuthenticateNoLimit") };
           }
    -      throw error;
    +      return { error: t("errors.couldNotVerifyPassword") };
         }
    +  }
     
    -    if (!session?.factors?.user?.id) {
    -      return { error: t("errors.couldNotCreateSessionForUser") };
    +  if (!session?.factors?.user?.id) {
    +    if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +      return { error: t("errors.failedToAuthenticateNoLimit") };
         }
    +    return { error: t("errors.couldNotCreateSessionForUser") };
    +  }
     
    +  if (!user) {
         const userResponse = await getUserByID({ serviceConfig, userId: session?.factors?.user?.id });
    -
         if (!userResponse.user) {
           return { error: t("errors.userNotFound") };
         }
    -
         user = userResponse.user;
       }
     
    -  if (!loginSettings) {
    -    loginSettings = await getLoginSettings({
    -      serviceConfig,
    -      organization: command.organization ?? session.factors?.user?.organizationId,
    -    });
    -  }
    -
       if (!session?.factors?.user?.id || !sessionCookie) {
    +    if (loginSettingsByContext?.ignoreUnknownUsernames) {
    +      return { error: t("errors.failedToAuthenticateNoLimit") };
    +    }
         return { error: t("errors.couldNotCreateSessionForUser") };
       }
     
    +  if (!loginSettingsByUser) {
    +    loginSettingsByUser = await getLoginSettings({
    +      serviceConfig,
    +      organization: command.organization ?? session.factors?.user?.organizationId ?? command.defaultOrganization,
    +    });
    +  }
    +
       const humanUser = user.type.case === "human" ? user.type.value : undefined;
     
       const expirySettings = await getPasswordExpirySettings({
    @@ -251,7 +362,7 @@ export async function sendPassword(command: UpdateSessionCommand): Promise<{ err
       const mfaFactorCheck = await checkMFAFactors(
         serviceConfig,
         session,
    -    loginSettings,
    +    loginSettingsByUser,
         authMethods,
         command.organization,
         command.requestId,
    @@ -270,7 +381,7 @@ export async function sendPassword(command: UpdateSessionCommand): Promise<{ err
             requestId: command.requestId,
             organization: command.organization ?? session.factors?.user?.organizationId,
           },
    -      loginSettings?.defaultRedirectUri,
    +      loginSettingsByUser?.defaultRedirectUri,
         );
         console.log("Password auth: OIDC/SAML flow result:", result);
     
    @@ -290,7 +401,7 @@ export async function sendPassword(command: UpdateSessionCommand): Promise<{ err
           loginName: session.factors.user.loginName,
           organization: session.factors?.user?.organizationId,
         },
    -    loginSettings?.defaultRedirectUri,
    +    loginSettingsByUser?.defaultRedirectUri,
       );
     
       // Safety net - ensure we always return a valid object
    @@ -303,7 +414,7 @@ export async function sendPassword(command: UpdateSessionCommand): Promise<{ err
     }
     
     // this function lets users with code set a password or users with valid User Verification Check
    -export async function changePassword(command: { code?: string; userId: string; password: string }) {
    +export async function changePassword(command: { code?: string; userId: string; password: string; organization?: string }) {
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
       const t = await getTranslations("password");
    @@ -312,6 +423,10 @@ export async function changePassword(command: { code?: string; userId: string; p
       const { user } = await getUserByID({ serviceConfig, userId: command.userId });
     
       if (!user || user.userId !== command.userId) {
    +    const loginSettings = await getLoginSettings({ serviceConfig, organization: command.organization });
    +    if (loginSettings?.ignoreUnknownUsernames) {
    +      return { error: t("set.errors.couldNotSetPassword") };
    +    }
         return { error: t("errors.couldNotSendResetLink") };
       }
       const userId = user.userId;
    @@ -352,11 +467,9 @@ export async function checkSessionAndSetPassword({ sessionId, password }: CheckS
       const { serviceConfig } = getServiceConfig(_headers);
       const t = await getTranslations("password");
     
    -  let sessionCookie;
    -  try {
    -    sessionCookie = await getSessionCookieById({ sessionId });
    -  } catch (error) {
    -    console.error("Error getting session cookie:", error);
    +  const sessionCookie = await getSessionCookieById({ sessionId });
    +
    +  if (!sessionCookie) {
         return { error: "Could not load session cookie" };
       }
     
    @@ -407,8 +520,38 @@ export async function checkSessionAndSetPassword({ sessionId, password }: CheckS
     
       const forceMfa = !!(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly);
     
    +  const mfaFactors = authmethods.authMethodTypes.filter(
    +    (m) =>
    +      m === AuthenticationMethodType.TOTP ||
    +      m === AuthenticationMethodType.OTP_SMS ||
    +      m === AuthenticationMethodType.OTP_EMAIL ||
    +      m === AuthenticationMethodType.U2F,
    +  );
    +
    +  const hasMfa = mfaFactors.length > 0;
    +  const mfaVerified =
    +    !!session.factors?.totp?.verifiedAt ||
    +    !!session.factors?.otpSms?.verifiedAt ||
    +    !!session.factors?.otpEmail?.verifiedAt ||
    +    !!session.factors?.webAuthN?.verifiedAt;
    +
    +  // check if the password factor is set and not older than 5 minutes
    +  const passwordVerifiedAt = session.factors?.password?.verifiedAt;
    +  if (!passwordVerifiedAt) {
    +    return { error: t("errors.passwordVerificationMissing") };
    +  }
    +
    +  const passwordVerifiedDate = timestampDate(passwordVerifiedAt);
    +  const now = new Date();
    +  const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000);
    +
    +  if (passwordVerifiedDate < fiveMinutesAgo) {
    +    return { error: t("errors.passwordVerificationTooOld") };
    +  }
    +
       // if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user
    -  if (forceMfa) {
    +  // also if the user has no MFA but it is not verified, we use the service account
    +  if (forceMfa || (hasMfa && !mfaVerified)) {
         console.log("Set password using service account due to enforced MFA without existing MFA methods");
         return setPassword({ serviceConfig, payload }).catch((error) => {
           // throw error if failed precondition (ex. User is not yet initialized)
    
  • apps/login/src/lib/server/register.ts+15 12 modified
    @@ -32,7 +32,9 @@ export async function registerUser(command: RegisterUserCommand) {
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
     
    -  const addResponse = await addHumanUser({ serviceConfig, email: command.email,
    +  const addResponse = await addHumanUser({
    +    serviceConfig,
    +    email: command.email,
         firstName: command.firstName,
         lastName: command.lastName,
         password: command.password ? command.password : undefined,
    @@ -43,8 +45,7 @@ export async function registerUser(command: RegisterUserCommand) {
         return { error: t("errors.couldNotCreateUser") };
       }
     
    -  const loginSettings = await getLoginSettings({ serviceConfig, organization: command.organization,
    -  });
    +  const loginSettings = await getLoginSettings({ serviceConfig, organization: command.organization });
     
       let checkPayload: any = {
         user: { search: { case: "userId", value: addResponse.userId } },
    @@ -59,11 +60,12 @@ export async function registerUser(command: RegisterUserCommand) {
     
       const checks = create(ChecksSchema, checkPayload);
     
    -  const session = await createSessionAndUpdateCookie({
    +  const result = await createSessionAndUpdateCookie({
         checks,
         requestId: command.requestId,
         lifetime: command.password ? loginSettings?.passwordCheckLifetime : undefined,
       });
    +  const session = result.session;
     
       if (!session || !session.factors?.user) {
         return { error: t("errors.couldNotCreateSession") };
    @@ -96,8 +98,7 @@ export async function registerUser(command: RegisterUserCommand) {
     
         return { redirect: "/passkey/set?" + params };
       } else {
    -    const userResponse = await getUserByID({ serviceConfig, userId: session?.factors?.user?.id,
    -    });
    +    const userResponse = await getUserByID({ serviceConfig, userId: session?.factors?.user?.id });
     
         if (!userResponse.user) {
           return { error: t("errors.userNotFound") };
    @@ -158,7 +159,9 @@ export async function registerUserAndLinkToIDP(command: RegisterUserAndLinkToIDP
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
     
    -  const addUserResponse = await addHumanUser({ serviceConfig, email: command.email,
    +  const addUserResponse = await addHumanUser({
    +    serviceConfig,
    +    email: command.email,
         firstName: command.firstName,
         lastName: command.lastName,
         organization: command.organization,
    @@ -168,10 +171,11 @@ export async function registerUserAndLinkToIDP(command: RegisterUserAndLinkToIDP
         return { error: t("errors.couldNotCreateUser") };
       }
     
    -  const loginSettings = await getLoginSettings({ serviceConfig, organization: command.organization,
    -  });
    +  const loginSettings = await getLoginSettings({ serviceConfig, organization: command.organization });
     
    -  const idpLink = await addIDPLink({ serviceConfig, idp: {
    +  const idpLink = await addIDPLink({
    +    serviceConfig,
    +    idp: {
           id: command.idpId,
           userId: command.idpUserId,
           userName: command.idpUserName,
    @@ -215,8 +219,7 @@ export async function registerUserAndLinkToIDP(command: RegisterUserAndLinkToIDP
       // check if user has MFA methods
       let authMethods;
       if (session.factors?.user?.id) {
    -    const response = await listAuthenticationMethodTypes({ serviceConfig, userId: session.factors.user.id,
    -    });
    +    const response = await listAuthenticationMethodTypes({ serviceConfig, userId: session.factors.user.id });
         if (response.authMethodTypes && response.authMethodTypes.length) {
           authMethods = response.authMethodTypes;
         }
    
  • apps/login/src/lib/server/session.ts+105 32 modified
    @@ -1,17 +1,18 @@
     "use server";
     
    -import { setSessionAndUpdateCookie } from "@/lib/server/cookie";
    +import { createSessionAndUpdateCookie, setSessionAndUpdateCookie } from "@/lib/server/cookie";
     import {
       deleteSession,
       getLoginSettings,
       getSecuritySettings,
       humanMFAInitSkipped,
       listAuthenticationMethodTypes,
    +  listUsers,
     } from "@/lib/zitadel";
    -import { Duration } from "@zitadel/client";
    +import { create, Duration } from "@zitadel/client";
     import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
     import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
    -import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
    +import { Checks, ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
     import { headers } from "next/headers";
     import { getTranslations } from "next-intl/server";
     import { completeFlowOrGetUrl } from "../client";
    @@ -109,41 +110,69 @@ export type UpdateSessionCommand = {
       lifetime?: Duration;
     };
     
    -export async function updateSession(options: UpdateSessionCommand) {
    -  let { loginName, sessionId, organization, checks, requestId, challenges } = options;
    -  const recentSession = sessionId
    -    ? await getSessionCookieById({ sessionId })
    -    : loginName
    -      ? await getSessionCookieByLoginName({ loginName, organization })
    -      : await getMostRecentSessionCookie();
    -
    -  if (!recentSession) {
    -    return {
    -      error: "Could not find session",
    -    };
    -  }
    +export async function updateOrCreateSession(options: UpdateSessionCommand) {
    +  let { loginName, sessionId, organization, checks, requestId, challenges, lifetime } = options;
     
       const _headers = await headers();
       const { serviceConfig } = getServiceConfig(_headers);
       const host = getPublicHost(_headers);
     
    +  const t = await getTranslations("verify.errors");
    +
       if (!host) {
    -    return { error: "Could not get host" };
    +    return { error: "Could not get host" }; // Technical error, maybe leave or translate if key exists
       }
     
    -  if (host && challenges && challenges.webAuthN && !challenges.webAuthN.domain) {
    +  if (challenges && challenges.webAuthN && !challenges.webAuthN.domain) {
         const [hostname] = host.split(":");
     
         challenges.webAuthN.domain = hostname;
       }
     
    +  let recentSession = sessionId
    +    ? await getSessionCookieById({ sessionId })
    +    : loginName
    +      ? await getSessionCookieByLoginName({ loginName, organization })
    +      : await getMostRecentSessionCookie();
    +
    +  if (!recentSession) {
    +    if (!loginName) {
    +      return { error: t("couldNotFindSession") };
    +    }
    +
    +    const checks = create(ChecksSchema, {
    +      user: { search: { case: "loginName", value: loginName } },
    +    });
    +
    +    const result = await createSessionAndUpdateCookie({
    +      checks,
    +      challenges,
    +      requestId,
    +    }).catch((error) => {
    +      console.error("Could not create session", error);
    +      return undefined;
    +    });
    +
    +    if (result && "sessionCookie" in result) {
    +      recentSession = result.sessionCookie;
    +    }
    +
    +    if (!recentSession) {
    +      return {
    +        error: t("couldNotFindSession"),
    +      };
    +    }
    +  }
    +
       const loginSettings = await getLoginSettings({ serviceConfig, organization });
     
    -  let lifetime = checks?.webAuthN
    -    ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
    -    : checks?.otpEmail || checks?.otpSms
    -      ? loginSettings?.secondFactorCheckLifetime
    -      : undefined;
    +  if (!lifetime) {
    +    lifetime = checks?.webAuthN
    +      ? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
    +      : checks?.otpEmail || checks?.otpSms
    +        ? loginSettings?.secondFactorCheckLifetime
    +        : undefined;
    +  }
     
       if (!lifetime || !lifetime.seconds) {
         console.warn("No lifetime provided for session, defaulting to 24 hours");
    @@ -153,16 +182,55 @@ export async function updateSession(options: UpdateSessionCommand) {
         } as Duration;
       }
     
    -  const session = await setSessionAndUpdateCookie({
    -    recentCookie: recentSession,
    -    checks,
    -    challenges,
    -    requestId,
    -    lifetime,
    -  });
    +  let session;
    +  try {
    +    session = await setSessionAndUpdateCookie({
    +      recentCookie: recentSession,
    +      checks,
    +      challenges,
    +      requestId,
    +      lifetime,
    +    });
    +  } catch (error) {
    +    if (!checks) {
    +      throw error;
    +    }
     
    -  if (!session) {
    -    return { error: "Could not update session" };
    +    const loginNameForCreation = options.loginName || recentSession.loginName;
    +    const orgForCreation = options.organization || recentSession.organization;
    +
    +    if (!loginNameForCreation) {
    +      throw error;
    +    }
    +
    +    const users = await listUsers({
    +      serviceConfig,
    +      loginName: loginNameForCreation,
    +      organizationId: orgForCreation,
    +    });
    +
    +    if (users.details?.totalResult === BigInt(1) && users.result[0].userId) {
    +      const user = users.result[0];
    +      const newChecks = create(ChecksSchema, {
    +        ...checks,
    +        user: { search: { case: "userId", value: user.userId } } as any,
    +      });
    +
    +      const result = await createSessionAndUpdateCookie({
    +        checks: newChecks,
    +        requestId,
    +        lifetime,
    +        challenges,
    +      });
    +      // @ts-ignore
    +      session = { ...result.session, challenges: result.challenges };
    +    } else {
    +      throw error;
    +    }
    +  }
    +
    +  if (!session || ("error" in session && session.error)) {
    +    return { error: t("couldNotUpdateSession") };
       }
     
       // if password, check if user has MFA methods
    @@ -177,6 +245,7 @@ export async function updateSession(options: UpdateSessionCommand) {
       return {
         sessionId: session.id,
         factors: session.factors,
    +    // @ts-ignore
         challenges: session.challenges,
         authMethods,
       };
    @@ -194,6 +263,10 @@ export async function clearSession(options: ClearSessionOptions) {
     
       const sessionCookie = await getSessionCookieById({ sessionId });
     
    +  if (!sessionCookie) {
    +    return;
    +  }
    +
       const deleteResponse = await deleteSession({
         serviceConfig,
         sessionId: sessionCookie.id,
    
  • apps/login/src/lib/server/u2f.ts+4 0 modified
    @@ -67,6 +67,10 @@ export async function verifyU2F(command: VerifyU2FCommand) {
         sessionId: command.sessionId,
       });
     
    +  if (!sessionCookie) {
    +    return { error: "Could not get session cookie" };
    +  }
    +
       const session = await getSession({ serviceConfig, sessionId: sessionCookie.id, sessionToken: sessionCookie.token });
     
       const userId = session?.session?.factors?.user?.id;
    
  • apps/login/src/lib/server/verify.test.ts+113 0 added
    @@ -0,0 +1,113 @@
    +import { vi, describe, expect, test, beforeEach } from "vitest";
    +import { sendVerification } from "./verify";
    +
    +// Mock dependencies
    +vi.mock("@/lib/zitadel", () => ({
    +  verifyEmail: vi.fn(),
    +  verifyInviteCode: vi.fn(),
    +  getUserByID: vi.fn(),
    +  getSession: vi.fn(),
    +  listAuthenticationMethodTypes: vi.fn(),
    +  getLoginSettings: vi.fn(),
    +}));
    +
    +vi.mock("next/headers", () => ({
    +  headers: vi.fn(),
    +  cookies: vi.fn(),
    +}));
    +
    +vi.mock("../cookies", () => ({
    +  getSessionCookieByLoginName: vi.fn(),
    +}));
    +
    +vi.mock("./cookie", () => ({
    +  createSessionAndUpdateCookie: vi.fn(),
    +}));
    +
    +vi.mock("../service-url", () => ({
    +  getServiceConfig: vi.fn(() => ({ serviceConfig: {} })),
    +}));
    +
    +vi.mock("../fingerprint", () => ({
    +  getOrSetFingerprintId: vi.fn(),
    +}));
    +
    +vi.mock("next-intl/server", () => ({
    +  getTranslations: vi.fn(() => (key: string) => key),
    +}));
    +
    +import { verifyEmail, getUserByID, getSession, listAuthenticationMethodTypes } from "@/lib/zitadel";
    +import { getSessionCookieByLoginName } from "../cookies";
    +import { createSessionAndUpdateCookie } from "./cookie";
    +import { cookies } from "next/headers";
    +
    +describe("sendVerification", () => {
    +  let mockVerifyEmail: any;
    +  let mockGetUserByID: any;
    +  let mockGetSession: any;
    +  let mockListAuthenticationMethodTypes: any;
    +  let mockGetSessionCookieByLoginName: any;
    +  let mockCreateSessionAndUpdateCookie: any;
    +  let mockCookies: any;
    +
    +  beforeEach(() => {
    +    mockVerifyEmail = verifyEmail;
    +    mockGetUserByID = getUserByID;
    +    mockGetSession = getSession;
    +    mockListAuthenticationMethodTypes = listAuthenticationMethodTypes;
    +    mockGetSessionCookieByLoginName = getSessionCookieByLoginName;
    +    mockCreateSessionAndUpdateCookie = createSessionAndUpdateCookie;
    +    mockCookies = cookies;
    +
    +    // Default valid checking setup
    +    mockVerifyEmail.mockResolvedValue({});
    +    mockGetUserByID.mockResolvedValue({
    +      user: { userId: "user-1", preferredLoginName: "test@example.com" },
    +    });
    +    mockCookies.mockResolvedValue({
    +      set: vi.fn(),
    +    });
    +  });
    +
    +  test("should handle failing getSession when no auth methods exist (stale cookie scenario)", async () => {
    +    // 1. Cookie exists
    +    mockGetSessionCookieByLoginName.mockResolvedValue({
    +      id: "stale-session-id",
    +      token: "stale-token",
    +    });
    +
    +    // 2. getSession fails (throws or returns null/invalid)
    +    // Simulating call returning nothing useful or throwing
    +    mockGetSession.mockRejectedValue(new Error("Session not found"));
    +    // OR: mockGetSession.mockResolvedValue({}); // depending on how generic client behaves
    +
    +    // 3. No auth methods (user needs setup)
    +    mockListAuthenticationMethodTypes.mockResolvedValue({
    +      authMethodTypes: [],
    +    });
    +
    +    // 4. Mock session creation success
    +    mockCreateSessionAndUpdateCookie.mockResolvedValue({
    +      session: {
    +        id: "new-session-id",
    +        factors: { user: { id: "user-1", loginName: "test@example.com", organizationId: "org-1" } },
    +      },
    +    });
    +
    +    const result = await sendVerification({
    +      userId: "user-1",
    +      code: "123456",
    +      isInvite: false,
    +    });
    +
    +    // Expect redirect to authenticator setup
    +    expect(result).toEqual({
    +      redirect: "/authenticator/set?sessionId=new-session-id&loginName=test%40example.com",
    +    });
    +
    +    // Verify getSession was called (and failed, but we recovered)
    +    expect(mockGetSession).toHaveBeenCalled();
    +    // Verify creation was attempted
    +    expect(mockCreateSessionAndUpdateCookie).toHaveBeenCalled();
    +  });
    +});
    
  • apps/login/src/lib/server/verify.ts+11 8 modified
    @@ -90,18 +90,20 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
       const sessionCookie = await getSessionCookieByLoginName({
         loginName: "loginName" in command ? command.loginName : user.preferredLoginName,
         organization: command.organization,
    -  }).catch((error) => {
    -    console.warn("Ignored error:", error); // checked later
       });
     
       if (sessionCookie) {
    -    session = await getSession({ serviceConfig, sessionId: sessionCookie.id, sessionToken: sessionCookie.token }).then(
    -      (response) => {
    +    session = await getSession({ serviceConfig, sessionId: sessionCookie.id, sessionToken: sessionCookie.token })
    +      .then((response) => {
             if (response?.session) {
               return response.session;
             }
    -      },
    -    );
    +      })
    +      .catch((error) => {
    +        // user session is not found, so we create a new one
    +        console.warn("[verify] user session is not found, so we create a new one", error);
    +        return undefined;
    +      });
       }
     
       // load auth methods for user
    @@ -113,7 +115,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
     
       // if no authmethods are found on the user, redirect to set one up
       if (authMethodResponse && authMethodResponse.authMethodTypes && authMethodResponse.authMethodTypes.length == 0) {
    -    if (!sessionCookie) {
    +    if (!session) {
           const checks = create(ChecksSchema, {
             user: {
               search: {
    @@ -123,10 +125,11 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
             },
           });
     
    -      session = await createSessionAndUpdateCookie({
    +      const result = await createSessionAndUpdateCookie({
             checks,
             requestId: command.requestId,
           });
    +      session = result.session;
         }
     
         if (!session) {
    
  • apps/login/src/lib/session.ts+4 0 modified
    @@ -25,6 +25,10 @@ export async function loadMostRecentSession({
         organization: sessionParams.organization,
       });
     
    +  if (!recent) {
    +    return undefined;
    +  }
    +
       return getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token }).then(
         (resp: GetSessionResponse) => resp.session,
       );
    
  • apps/login/src/lib/zitadel.ts+4 2 modified
    @@ -197,19 +197,21 @@ export async function getPasswordComplexitySettings({
       return useCache ? cacheWrapper(callback) : callback;
     }
     
    -export async function createSessionFromChecks({
    +export async function createSessionFromChecksAndChallenges({
       serviceConfig,
       checks,
    +  challenges,
       lifetime,
     }: WithServiceConfig<{
       checks: Checks;
    +  challenges?: RequestChallenges;
       lifetime: Duration;
     }>) {
       const sessionService: Client<typeof SessionService> = await createServiceForHost(SessionService, serviceConfig);
     
       const userAgent = await getUserAgent();
     
    -  return sessionService.createSession({ checks, lifetime, userAgent }, {});
    +  return sessionService.createSession({ ...{ checks, lifetime, userAgent }, ...(challenges ? { challenges } : {}) }, {});
     }
     
     export async function createSessionForUserIdAndIdpIntent({
    
  • package.json+6 1 modified
    @@ -6,10 +6,15 @@
         "changeset": "changeset",
         "clean:all": "pnpm run clean && rm -rf .nx node_modules"
       },
    +  "pnpm": {
    +    "overrides": {
    +      "csstype": "3.1.3"
    +    }
    +  },
       "devDependencies": {
         "@changesets/cli": "^2.29.5",
         "nx": "21.6.1",
         "sass": "^1.64.1",
         "wait-on": "^9.0.1"
       }
    -}
    \ No newline at end of file
    +}
    
  • pnpm-lock.yaml+1266 2700 modified

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.