From 40082745f473945de31bfada638c56372f93e48d Mon Sep 17 00:00:00 2001 From: Livio Spring Date: Wed, 15 Jan 2025 11:39:28 +0100 Subject: [PATCH] fix(login): allow fallback to local auth in case of IdP errors (#9178) # Which Problems Are Solved The current login will always prefer external authentication (through an IdP) over local authentication. So as soon as either the user had connected to an IdP or even when the login policy was just set up to have an IdP allowed, users would be redirected to that IdP for (re)authentication. This could lead to problems, where the IdP was not available or any other error occurred in the process (such as secret expired for EntraID). Even when local authentication (passkeys or password) was allowed for the corresponding user, they would always be redirected to the IdP again, preventing any authentication. If admins were affected, they might not even be able to update the client secret of the IdP. # How the Problems Are Solved Errors during the external IdP flow are handled in an `externalAuthFailed` function, which will check if the organisation allows local authentication and if the user has set up such. If either password or passkeys is set up, the corresponding login page will be presented to the user. As already with local auth passkeys is preferred over password authentication. The user is informed that the external login failed and fail back to local auth as an error on the corresponding page in a focused mode. Any interaction or after 5 second the focus mode is disabled. # Additional Changes None. # Additional Context closes #6466 --- .../api/ui/login/change_password_handler.go | 8 +- internal/api/ui/login/device_auth.go | 8 +- .../api/ui/login/external_provider_handler.go | 119 +++++++++++++----- .../api/ui/login/init_password_handler.go | 8 +- internal/api/ui/login/init_user_handler.go | 8 +- internal/api/ui/login/invite_user_handler.go | 6 +- internal/api/ui/login/ldap_handler.go | 6 +- internal/api/ui/login/link_users_handler.go | 6 +- internal/api/ui/login/login_handler.go | 6 +- .../api/ui/login/login_success_handler.go | 6 +- internal/api/ui/login/logout_handler.go | 2 +- internal/api/ui/login/mail_verify_handler.go | 8 +- .../api/ui/login/mfa_init_done_handler.go | 3 +- internal/api/ui/login/mfa_init_sms.go | 6 +- internal/api/ui/login/mfa_init_u2f.go | 7 +- .../api/ui/login/mfa_init_verify_handler.go | 6 +- internal/api/ui/login/mfa_prompt_handler.go | 6 +- internal/api/ui/login/mfa_verify_handler.go | 6 +- .../api/ui/login/mfa_verify_otp_handler.go | 6 +- .../api/ui/login/mfa_verify_u2f_handler.go | 7 +- internal/api/ui/login/password_handler.go | 6 +- .../api/ui/login/password_reset_handler.go | 6 +- .../ui/login/passwordless_login_handler.go | 17 +-- .../ui/login/passwordless_prompt_handler.go | 6 +- .../passwordless_registration_handler.go | 11 +- internal/api/ui/login/register_handler.go | 6 +- .../api/ui/login/register_option_handler.go | 6 +- internal/api/ui/login/register_org_handler.go | 6 +- internal/api/ui/login/renderer.go | 33 +++-- internal/api/ui/login/select_user_handler.go | 2 +- internal/api/ui/login/static/i18n/bg.yaml | 4 + internal/api/ui/login/static/i18n/cs.yaml | 4 + internal/api/ui/login/static/i18n/de.yaml | 4 + internal/api/ui/login/static/i18n/en.yaml | 4 + internal/api/ui/login/static/i18n/es.yaml | 4 + internal/api/ui/login/static/i18n/fr.yaml | 4 + internal/api/ui/login/static/i18n/hu.yaml | 4 + internal/api/ui/login/static/i18n/id.yaml | 4 + internal/api/ui/login/static/i18n/it.yaml | 4 + internal/api/ui/login/static/i18n/ja.yaml | 4 + internal/api/ui/login/static/i18n/ko.yaml | 4 + internal/api/ui/login/static/i18n/mk.yaml | 4 + internal/api/ui/login/static/i18n/nl.yaml | 4 + internal/api/ui/login/static/i18n/pl.yaml | 4 + internal/api/ui/login/static/i18n/pt.yaml | 4 + internal/api/ui/login/static/i18n/ru.yaml | 4 + internal/api/ui/login/static/i18n/sv.yaml | 4 + internal/api/ui/login/static/i18n/zh.yaml | 4 + .../static/resources/scripts/error_popup.js | 30 +++++ .../static/resources/themes/scss/main.scss | 23 ++++ .../themes/scss/styles/error/error.scss | 4 + .../login/static/templates/error-message.html | 9 +- .../ui/login/static/templates/password.html | 1 + .../login/static/templates/passwordless.html | 1 + .../api/ui/login/username_change_handler.go | 9 +- internal/auth/repository/auth_request.go | 1 + .../eventsourcing/eventstore/auth_request.go | 15 ++- .../eventstore/auth_request_test.go | 80 ++++++++++++ internal/domain/auth_request.go | 1 + 59 files changed, 388 insertions(+), 195 deletions(-) create mode 100644 internal/api/ui/login/static/resources/scripts/error_popup.js diff --git a/internal/api/ui/login/change_password_handler.go b/internal/api/ui/login/change_password_handler.go index 19f2404c94..7f2b83b80f 100644 --- a/internal/api/ui/login/change_password_handler.go +++ b/internal/api/ui/login/change_password_handler.go @@ -35,10 +35,6 @@ func (l *Login) handleChangePassword(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderChangePassword(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errType, errMessage string - if err != nil { - errType, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) if authReq == nil || len(authReq.PossibleSteps) < 1 { l.renderError(w, r, authReq, err) @@ -50,7 +46,7 @@ func (l *Login) renderChangePassword(w http.ResponseWriter, r *http.Request, aut return } data := passwordData{ - baseData: l.getBaseData(r, authReq, translator, "PasswordChange.Title", "PasswordChange.Description", errType, errMessage), + baseData: l.getBaseData(r, authReq, translator, "PasswordChange.Title", "PasswordChange.Description", err), profileData: l.getProfileData(authReq), Expired: step.Expired, } @@ -75,6 +71,6 @@ func (l *Login) renderChangePassword(w http.ResponseWriter, r *http.Request, aut func (l *Login) renderChangePasswordDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "PasswordChange.Title", "PasswordChange.Description", "", "") + data := l.getUserData(r, authReq, translator, "PasswordChange.Title", "PasswordChange.Description", nil) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplChangePasswordDone], data, nil) } diff --git a/internal/api/ui/login/device_auth.go b/internal/api/ui/login/device_auth.go index e7ddaccd08..fb61102d76 100644 --- a/internal/api/ui/login/device_auth.go +++ b/internal/api/ui/login/device_auth.go @@ -22,13 +22,11 @@ const ( ) func (l *Login) renderDeviceAuthUserCode(w http.ResponseWriter, r *http.Request, err error) { - var errID, errMessage string if err != nil { logging.WithError(err).Error() - errID, errMessage = l.getErrorMessage(r, err) } translator := l.getTranslator(r.Context(), nil) - data := l.getBaseData(r, nil, translator, "DeviceAuth.Title", "DeviceAuth.UserCode.Description", errID, errMessage) + data := l.getBaseData(r, nil, translator, "DeviceAuth.Title", "DeviceAuth.UserCode.Description", err) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplDeviceAuthUserCode], data, nil) } @@ -41,7 +39,7 @@ func (l *Login) renderDeviceAuthAction(w http.ResponseWriter, r *http.Request, a ClientID string Scopes []string }{ - baseData: l.getBaseData(r, authReq, translator, "DeviceAuth.Title", "DeviceAuth.Action.Description", "", ""), + baseData: l.getBaseData(r, authReq, translator, "DeviceAuth.Title", "DeviceAuth.Action.Description", nil), AuthRequestID: authReq.ID, Username: authReq.UserName, ClientID: authReq.ApplicationID, @@ -63,7 +61,7 @@ func (l *Login) renderDeviceAuthDone(w http.ResponseWriter, r *http.Request, aut baseData Message string }{ - baseData: l.getBaseData(r, authReq, translator, "DeviceAuth.Title", "DeviceAuth.Done.Description", "", ""), + baseData: l.getBaseData(r, authReq, translator, "DeviceAuth.Title", "DeviceAuth.Done.Description", nil), } switch action { case deviceAuthAllowed: diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go index c60e0eb0bb..5481c6aed1 100644 --- a/internal/api/ui/login/external_provider_handler.go +++ b/internal/api/ui/login/external_provider_handler.go @@ -2,8 +2,10 @@ package login import ( "context" + "errors" "net/http" "net/url" + "slices" "strings" "github.com/crewjam/saml/samlsp" @@ -150,7 +152,7 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, identityProvider.ID, userAgentID) if err != nil { - l.renderLogin(w, r, authReq, err) + l.externalAuthFailed(w, r, authReq, err) return } var provider idp.Provider @@ -183,17 +185,17 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai case domain.IDPTypeUnspecified: fallthrough default: - l.renderLogin(w, r, authReq, zerrors.ThrowInvalidArgument(nil, "LOGIN-AShek", "Errors.ExternalIDP.IDPTypeNotImplemented")) + l.externalAuthFailed(w, r, authReq, zerrors.ThrowInvalidArgument(nil, "LOGIN-AShek", "Errors.ExternalIDP.IDPTypeNotImplemented")) return } if err != nil { - l.renderLogin(w, r, authReq, err) + l.externalAuthFailed(w, r, authReq, err) return } params := l.sessionParamsFromAuthRequest(r.Context(), authReq, identityProvider.ID) session, err := provider.BeginAuth(r.Context(), authReq.ID, params...) if err != nil { - l.renderLogin(w, r, authReq, err) + l.externalAuthFailed(w, r, authReq, err) return } @@ -215,7 +217,7 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai func (l *Login) handleExternalLoginCallbackForm(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { - l.renderLogin(w, r, nil, err) + l.externalAuthFailed(w, r, nil, err) return } state := r.Form.Get(queryState) @@ -223,7 +225,7 @@ func (l *Login) handleExternalLoginCallbackForm(w http.ResponseWriter, r *http.R state = r.Form.Get(queryRelayState) } if state == "" { - l.renderLogin(w, r, nil, zerrors.ThrowInvalidArgument(nil, "LOGIN-dsg3f", "Errors.AuthRequest.NotFound")) + l.externalAuthFailed(w, r, nil, zerrors.ThrowInvalidArgument(nil, "LOGIN-dsg3f", "Errors.AuthRequest.NotFound")) return } l.caches.idpFormCallbacks.Set(r.Context(), &idpFormCallback{ @@ -243,7 +245,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque // workaround because of CSRF on external identity provider flows using form_post if r.URL.Query().Get(queryMethod) == http.MethodPost { if err := l.setDataFromFormCallback(r, r.URL.Query().Get(queryState)); err != nil { - l.renderLogin(w, r, nil, err) + l.externalAuthFailed(w, r, nil, err) return } } @@ -251,7 +253,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque data := new(externalIDPCallbackData) err := l.getParseData(r, data) if err != nil { - l.renderLogin(w, r, nil, err) + l.externalAuthFailed(w, r, nil, err) return } if data.State == "" { @@ -261,12 +263,12 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } identityProvider, err := l.getIDPByID(r, authReq.SelectedIDPConfigID) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } var provider idp.Provider @@ -275,75 +277,75 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque case domain.IDPTypeOAuth: provider, err = l.oauthProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &oauth.Session{Provider: provider.(*oauth.Provider), Code: data.Code} case domain.IDPTypeOIDC: provider, err = l.oidcProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &openid.Session{Provider: provider.(*openid.Provider), Code: data.Code} case domain.IDPTypeAzureAD: provider, err = l.azureProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &azuread.Session{Provider: provider.(*azuread.Provider), Code: data.Code} case domain.IDPTypeGitHub: provider, err = l.githubProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &oauth.Session{Provider: provider.(*github.Provider).Provider, Code: data.Code} case domain.IDPTypeGitHubEnterprise: provider, err = l.githubEnterpriseProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &oauth.Session{Provider: provider.(*github.Provider).Provider, Code: data.Code} case domain.IDPTypeGitLab: provider, err = l.gitlabProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &openid.Session{Provider: provider.(*gitlab.Provider).Provider, Code: data.Code} case domain.IDPTypeGitLabSelfHosted: provider, err = l.gitlabSelfHostedProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &openid.Session{Provider: provider.(*gitlab.Provider).Provider, Code: data.Code} case domain.IDPTypeGoogle: provider, err = l.googleProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code} case domain.IDPTypeApple: provider, err = l.appleProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session = &apple.Session{Session: &openid.Session{Provider: provider.(*apple.Provider).Provider, Code: data.Code}, UserFormValue: data.User} case domain.IDPTypeSAML: provider, err = l.samlProvider(r.Context(), identityProvider) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } session, err = saml.NewSession(provider.(*saml.Provider), authReq.SAMLRequestID, r) if err != nil { - l.externalAuthFailed(w, r, authReq, nil, nil, err) + l.externalAuthCallbackFailed(w, r, authReq, nil, nil, err) return } case domain.IDPTypeJWT, @@ -351,7 +353,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque domain.IDPTypeUnspecified: fallthrough default: - l.renderLogin(w, r, authReq, zerrors.ThrowInvalidArgument(nil, "LOGIN-SFefg", "Errors.ExternalIDP.IDPTypeNotImplemented")) + l.externalAuthFailed(w, r, authReq, zerrors.ThrowInvalidArgument(nil, "LOGIN-SFefg", "Errors.ExternalIDP.IDPTypeNotImplemented")) return } @@ -361,7 +363,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque "instance", authz.GetInstance(r.Context()).InstanceID(), "providerID", identityProvider.ID, ).WithError(err).Info("external authentication failed") - l.externalAuthFailed(w, r, authReq, tokens(session), user, err) + l.externalAuthCallbackFailed(w, r, authReq, tokens(session), user, err) return } l.handleExternalUserAuthenticated(w, r, authReq, identityProvider, session, user, l.renderNextStep) @@ -619,10 +621,6 @@ func (l *Login) autoCreateExternalUser(w http.ResponseWriter, r *http.Request, a // renderExternalNotFoundOption renders a page, where the user is able to edit the IDP data, // create a new externalUser of link to existing on (based on the IDP template) func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgIAMPolicy *query.DomainPolicy, human *domain.Human, idpLink *domain.UserIDPLink, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } resourceOwner := determineResourceOwner(r.Context(), authReq) if orgIAMPolicy == nil { orgIAMPolicy, err = l.getOrgDomainPolicy(r, resourceOwner) @@ -656,7 +654,7 @@ func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Requ translator := l.getTranslator(r.Context(), authReq) data := externalNotFoundOptionData{ - baseData: l.getBaseData(r, authReq, translator, "ExternalNotFound.Title", "ExternalNotFound.Description", errID, errMessage), + baseData: l.getBaseData(r, authReq, translator, "ExternalNotFound.Title", "ExternalNotFound.Description", err), externalNotFoundOptionFormData: externalNotFoundOptionFormData{ externalRegisterFormData: externalRegisterFormData{ Email: human.EmailAddress, @@ -1215,7 +1213,7 @@ func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserG return nil } -func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, tokens *oidc.Tokens[*oidc.IDTokenClaims], user idp.User, err error) { +func (l *Login) externalAuthCallbackFailed(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, tokens *oidc.Tokens[*oidc.IDTokenClaims], user idp.User, err error) { if authReq == nil { l.renderLogin(w, r, authReq, err) return @@ -1223,7 +1221,37 @@ func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authR if _, _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, tokens, authReq, r, user, err); actionErr != nil { logging.WithError(err).Error("both external user authentication and action post authentication failed") } - l.renderLogin(w, r, authReq, err) + l.externalAuthFailed(w, r, authReq, err) +} + +func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { + if authReq == nil || authReq.LoginPolicy == nil || !authReq.LoginPolicy.AllowUsernamePassword || authReq.UserID == "" { + l.renderLogin(w, r, authReq, err) + return + } + authMethods, authMethodsError := l.query.ListUserAuthMethodTypes(setUserContext(r.Context(), authReq.UserID, ""), authReq.UserID, true, false, "") + if authMethodsError != nil { + logging.WithFields("userID", authReq.UserID).WithError(authMethodsError).Warn("unable to load user's auth methods for idp login error") + l.renderLogin(w, r, authReq, err) + return + } + passwordless := slices.Contains(authMethods.AuthMethodTypes, domain.UserAuthMethodTypePasswordless) + password := slices.Contains(authMethods.AuthMethodTypes, domain.UserAuthMethodTypePassword) + if !passwordless && !password { + l.renderLogin(w, r, authReq, err) + return + } + localAuthError := l.authRepo.RequestLocalAuth(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.AgentID) + if localAuthError != nil { + l.renderLogin(w, r, authReq, err) + return + } + err = WrapIdPError(err) + if passwordless { + l.renderPasswordlessVerification(w, r, authReq, password, err) + return + } + l.renderPassword(w, r, authReq, err) } // tokens extracts the oidc.Tokens for backwards compatibility of PostExternalAuthenticationActions @@ -1359,3 +1387,34 @@ func (l *Login) getUserLinks(ctx context.Context, userID, idpID string) (*query. }, nil, ) } + +// IdPError wraps an error from an external IDP to be able to distinguish it from other errors and to display it +// more prominent (popup style) . +// It's used if an error occurs during the login process with an external IDP and local authentication is allowed, +// respectively used as fallback. +type IdPError struct { + err *zerrors.ZitadelError +} + +func (e *IdPError) Error() string { + return e.err.Error() +} + +func (e *IdPError) Unwrap() error { + return e.err +} + +func (e *IdPError) Is(target error) bool { + _, ok := target.(*IdPError) + return ok +} + +func WrapIdPError(err error) *IdPError { + zErr := new(zerrors.ZitadelError) + id := "LOGIN-JWo3f" + // keep the original error id if there is one + if errors.As(err, &zErr) { + id = zErr.ID + } + return &IdPError{err: zerrors.CreateZitadelError(err, id, "Errors.User.ExternalIDP.LoginFailedSwitchLocal")} +} diff --git a/internal/api/ui/login/init_password_handler.go b/internal/api/ui/login/init_password_handler.go index b8c6d401c5..17ac13ff31 100644 --- a/internal/api/ui/login/init_password_handler.go +++ b/internal/api/ui/login/init_password_handler.go @@ -112,10 +112,6 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe } func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code string, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } if userID == "" && authReq != nil { userID = authReq.UserID } @@ -123,7 +119,7 @@ func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authR translator := l.getTranslator(r.Context(), authReq) data := initPasswordData{ - baseData: l.getBaseData(r, authReq, translator, "InitPassword.Title", "InitPassword.Description", errID, errMessage), + baseData: l.getBaseData(r, authReq, translator, "InitPassword.Title", "InitPassword.Description", err), profileData: l.getProfileData(authReq), UserID: userID, Code: code, @@ -155,7 +151,7 @@ func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authR func (l *Login) renderInitPasswordDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgID string) { translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "InitPasswordDone.Title", "InitPasswordDone.Description", "", "") + data := l.getUserData(r, authReq, translator, "InitPasswordDone.Title", "InitPasswordDone.Description", nil) if authReq == nil { l.customTexts(r.Context(), translator, orgID) } diff --git a/internal/api/ui/login/init_user_handler.go b/internal/api/ui/login/init_user_handler.go index 9a6d052dcd..fa4854a473 100644 --- a/internal/api/ui/login/init_user_handler.go +++ b/internal/api/ui/login/init_user_handler.go @@ -131,17 +131,13 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq * } func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, loginName string, code string, passwordSet bool, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } if authReq != nil { userID = authReq.UserID } translator := l.getTranslator(r.Context(), authReq) data := initUserData{ - baseData: l.getBaseData(r, authReq, translator, "InitUser.Title", "InitUser.Description", errID, errMessage), + baseData: l.getBaseData(r, authReq, translator, "InitUser.Title", "InitUser.Description", err), profileData: l.getProfileData(authReq), UserID: userID, Code: code, @@ -179,7 +175,7 @@ func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq * func (l *Login) renderInitUserDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgID string) { translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "InitUserDone.Title", "InitUserDone.Description", "", "") + data := l.getUserData(r, authReq, translator, "InitUserDone.Title", "InitUserDone.Description", nil) if authReq == nil { l.customTexts(r.Context(), translator, orgID) } diff --git a/internal/api/ui/login/invite_user_handler.go b/internal/api/ui/login/invite_user_handler.go index e083277c93..18ba502483 100644 --- a/internal/api/ui/login/invite_user_handler.go +++ b/internal/api/ui/login/invite_user_handler.go @@ -119,10 +119,6 @@ func (l *Login) resendUserInvite(w http.ResponseWriter, r *http.Request, authReq } func (l *Login) renderInviteUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, orgID, loginName string, code string, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } if authReq != nil { userID = authReq.UserID orgID = authReq.UserOrgID @@ -130,7 +126,7 @@ func (l *Login) renderInviteUser(w http.ResponseWriter, r *http.Request, authReq translator := l.getTranslator(r.Context(), authReq) data := inviteUserData{ - baseData: l.getBaseData(r, authReq, translator, "InviteUser.Title", "InviteUser.Description", errID, errMessage), + baseData: l.getBaseData(r, authReq, translator, "InviteUser.Title", "InviteUser.Description", err), profileData: l.getProfileData(authReq), UserID: userID, Code: code, diff --git a/internal/api/ui/login/ldap_handler.go b/internal/api/ui/login/ldap_handler.go index 0fd47c5a6a..147a319523 100644 --- a/internal/api/ui/login/ldap_handler.go +++ b/internal/api/ui/login/ldap_handler.go @@ -30,13 +30,9 @@ func (l *Login) handleLDAP(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderLDAPLogin(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } temp := l.renderer.Templates[tmplLDAPLogin] translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "Login.Title", "Login.Description", errID, errMessage) + data := l.getUserData(r, authReq, translator, "Login.Title", "Login.Description", err) l.renderer.RenderTemplate(w, r, translator, temp, data, nil) } diff --git a/internal/api/ui/login/link_users_handler.go b/internal/api/ui/login/link_users_handler.go index c720559084..0b0803f8a1 100644 --- a/internal/api/ui/login/link_users_handler.go +++ b/internal/api/ui/login/link_users_handler.go @@ -18,11 +18,7 @@ func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *domai } func (l *Login) renderLinkUsersDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errType, errMessage string - if err != nil { - errType, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "LinkingUsersDone.Title", "LinkingUsersDone.Description", errType, errMessage) + data := l.getUserData(r, authReq, translator, "LinkingUsersDone.Title", "LinkingUsersDone.Description", err) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplLinkUsersDone], data, nil) } diff --git a/internal/api/ui/login/login_handler.go b/internal/api/ui/login/login_handler.go index 059048eecb..729bd1955b 100644 --- a/internal/api/ui/login/login_handler.go +++ b/internal/api/ui/login/login_handler.go @@ -91,16 +91,12 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderLogin(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } if err == nil && singleIDPAllowed(authReq) { l.handleIDP(w, r, authReq, authReq.AllowedExternalIDPs[0].IDPConfigID) return } translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "Login.Title", "Login.Description", errID, errMessage) + data := l.getUserData(r, authReq, translator, "Login.Title", "Login.Description", err) funcs := map[string]interface{}{ "hasUsernamePasswordLogin": func() bool { return authReq != nil && authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowUsernamePassword diff --git a/internal/api/ui/login/login_success_handler.go b/internal/api/ui/login/login_success_handler.go index 00f29becfd..a18a3a2d5c 100644 --- a/internal/api/ui/login/login_success_handler.go +++ b/internal/api/ui/login/login_success_handler.go @@ -37,13 +37,9 @@ func (l *Login) handleLoginSuccess(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderSuccessAndCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) data := loginSuccessData{ - userData: l.getUserData(r, authReq, translator, "LoginSuccess.Title", "", errID, errMessage), + userData: l.getUserData(r, authReq, translator, "LoginSuccess.Title", "", err), } if authReq != nil { data.RedirectURI, err = l.authRequestCallback(r.Context(), authReq) diff --git a/internal/api/ui/login/logout_handler.go b/internal/api/ui/login/logout_handler.go index e270cd5541..9596f477af 100644 --- a/internal/api/ui/login/logout_handler.go +++ b/internal/api/ui/login/logout_handler.go @@ -14,6 +14,6 @@ func (l *Login) handleLogoutDone(w http.ResponseWriter, r *http.Request) { func (l *Login) renderLogoutDone(w http.ResponseWriter, r *http.Request) { translator := l.getTranslator(r.Context(), nil) - data := l.getUserData(r, nil, translator, "LogoutDone.Title", "LogoutDone.Description", "", "") + data := l.getUserData(r, nil, translator, "LogoutDone.Title", "LogoutDone.Description", nil) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplLogoutDone], data, nil) } diff --git a/internal/api/ui/login/mail_verify_handler.go b/internal/api/ui/login/mail_verify_handler.go index 864ff76dd2..071fe6539d 100644 --- a/internal/api/ui/login/mail_verify_handler.go +++ b/internal/api/ui/login/mail_verify_handler.go @@ -145,17 +145,13 @@ func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *d } func (l *Login) renderMailVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code string, passwordInit bool, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } if userID == "" && authReq != nil { userID = authReq.UserID } translator := l.getTranslator(r.Context(), authReq) data := mailVerificationData{ - baseData: l.getBaseData(r, authReq, translator, "EmailVerification.Title", "EmailVerification.Description", errID, errMessage), + baseData: l.getBaseData(r, authReq, translator, "EmailVerification.Title", "EmailVerification.Description", err), UserID: userID, profileData: l.getProfileData(authReq), Code: code, @@ -191,7 +187,7 @@ func (l *Login) renderMailVerification(w http.ResponseWriter, r *http.Request, a func (l *Login) renderMailVerified(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgID string) { translator := l.getTranslator(r.Context(), authReq) data := mailVerificationData{ - baseData: l.getBaseData(r, authReq, translator, "EmailVerificationDone.Title", "EmailVerificationDone.Description", "", ""), + baseData: l.getBaseData(r, authReq, translator, "EmailVerificationDone.Title", "EmailVerificationDone.Description", nil), profileData: l.getProfileData(authReq), } if authReq == nil { diff --git a/internal/api/ui/login/mfa_init_done_handler.go b/internal/api/ui/login/mfa_init_done_handler.go index 437fde29f4..ae4bab69ea 100644 --- a/internal/api/ui/login/mfa_init_done_handler.go +++ b/internal/api/ui/login/mfa_init_done_handler.go @@ -14,9 +14,8 @@ type mfaInitDoneData struct { } func (l *Login) renderMFAInitDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *mfaDoneData) { - var errType, errMessage string translator := l.getTranslator(r.Context(), authReq) - data.baseData = l.getBaseData(r, authReq, translator, "InitMFADone.Title", "InitMFADone.Description", errType, errMessage) + data.baseData = l.getBaseData(r, authReq, translator, "InitMFADone.Title", "InitMFADone.Description", nil) data.profileData = l.getProfileData(authReq) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplMFAInitDone], data, nil) } diff --git a/internal/api/ui/login/mfa_init_sms.go b/internal/api/ui/login/mfa_init_sms.go index 03f2c32014..048677f0f4 100644 --- a/internal/api/ui/login/mfa_init_sms.go +++ b/internal/api/ui/login/mfa_init_sms.go @@ -53,12 +53,8 @@ func (l *Login) handleRegisterOTPSMS(w http.ResponseWriter, r *http.Request, aut } func (l *Login) renderRegisterSMS(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *smsInitData, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) - data.baseData = l.getBaseData(r, authReq, translator, "InitMFAOTP.Title", "InitMFAOTP.Description", errID, errMessage) + data.baseData = l.getBaseData(r, authReq, translator, "InitMFAOTP.Title", "InitMFAOTP.Description", err) data.profileData = l.getProfileData(authReq) data.MFAType = domain.MFATypeOTPSMS l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplMFASMSInit], data, nil) diff --git a/internal/api/ui/login/mfa_init_u2f.go b/internal/api/ui/login/mfa_init_u2f.go index c84948796c..0e75bd1b69 100644 --- a/internal/api/ui/login/mfa_init_u2f.go +++ b/internal/api/ui/login/mfa_init_u2f.go @@ -18,21 +18,18 @@ type u2fInitData struct { } func (l *Login) renderRegisterU2F(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage, credentialData string + var credentialData string var u2f *domain.WebAuthNToken if err == nil { u2f, err = l.command.HumanAddU2FSetup(setUserContext(r.Context(), authReq.UserID, authReq.UserOrgID), authReq.UserID, authReq.UserOrgID) } - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } if u2f != nil { credentialData = base64.RawURLEncoding.EncodeToString(u2f.CredentialCreationData) } translator := l.getTranslator(r.Context(), authReq) data := &u2fInitData{ webAuthNData: webAuthNData{ - userData: l.getUserData(r, authReq, translator, "InitMFAU2F.Title", "InitMFAU2F.Description", errID, errMessage), + userData: l.getUserData(r, authReq, translator, "InitMFAU2F.Title", "InitMFAU2F.Description", err), CredentialCreationData: credentialData, }, MFAType: domain.MFATypeU2F, diff --git a/internal/api/ui/login/mfa_init_verify_handler.go b/internal/api/ui/login/mfa_init_verify_handler.go index cd6a9091e2..e3488d391c 100644 --- a/internal/api/ui/login/mfa_init_verify_handler.go +++ b/internal/api/ui/login/mfa_init_verify_handler.go @@ -66,12 +66,8 @@ func (l *Login) handleOTPVerify(w http.ResponseWriter, r *http.Request, authReq } func (l *Login) renderMFAInitVerify(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *mfaVerifyData, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) - data.baseData = l.getBaseData(r, authReq, translator, "InitMFAOTP.Title", "InitMFAOTP.Description", errID, errMessage) + data.baseData = l.getBaseData(r, authReq, translator, "InitMFAOTP.Title", "InitMFAOTP.Description", err) data.profileData = l.getProfileData(authReq) if data.MFAType == domain.MFATypeTOTP { code, err := generateQrCode(data.totpData.Url) diff --git a/internal/api/ui/login/mfa_prompt_handler.go b/internal/api/ui/login/mfa_prompt_handler.go index ce1b7240ec..ca318741b7 100644 --- a/internal/api/ui/login/mfa_prompt_handler.go +++ b/internal/api/ui/login/mfa_prompt_handler.go @@ -49,13 +49,9 @@ func (l *Login) handleMFAPromptSelection(w http.ResponseWriter, r *http.Request) } func (l *Login) renderMFAPrompt(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, mfaPromptData *domain.MFAPromptStep, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) data := mfaData{ - baseData: l.getBaseData(r, authReq, translator, "InitMFAPrompt.Title", "InitMFAPrompt.Description", errID, errMessage), + baseData: l.getBaseData(r, authReq, translator, "InitMFAPrompt.Title", "InitMFAPrompt.Description", err), profileData: l.getProfileData(authReq), } diff --git a/internal/api/ui/login/mfa_verify_handler.go b/internal/api/ui/login/mfa_verify_handler.go index cfffc6fced..34832413cf 100644 --- a/internal/api/ui/login/mfa_verify_handler.go +++ b/internal/api/ui/login/mfa_verify_handler.go @@ -62,12 +62,8 @@ func (l *Login) renderMFAVerify(w http.ResponseWriter, r *http.Request, authReq } func (l *Login) renderMFAVerifySelected(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, verificationStep *domain.MFAVerificationStep, selectedProvider domain.MFAType, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "", "", errID, errMessage) + data := l.getUserData(r, authReq, translator, "", "", err) if verificationStep == nil { l.renderError(w, r, authReq, err) return diff --git a/internal/api/ui/login/mfa_verify_otp_handler.go b/internal/api/ui/login/mfa_verify_otp_handler.go index fb77bbcba9..bd09a7652b 100644 --- a/internal/api/ui/login/mfa_verify_otp_handler.go +++ b/internal/api/ui/login/mfa_verify_otp_handler.go @@ -61,13 +61,9 @@ func (l *Login) handleOTPVerification(w http.ResponseWriter, r *http.Request, au } func (l *Login) renderOTPVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, providers []domain.MFAType, selectedProvider domain.MFAType, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) data := &mfaOTPData{ - userData: l.getUserData(r, authReq, translator, "VerifyMFAU2F.Title", "VerifyMFAU2F.Description", errID, errMessage), + userData: l.getUserData(r, authReq, translator, "VerifyMFAU2F.Title", "VerifyMFAU2F.Description", err), MFAProviders: removeSelectedProviderFromList(providers, selectedProvider), SelectedProvider: selectedProvider, } diff --git a/internal/api/ui/login/mfa_verify_u2f_handler.go b/internal/api/ui/login/mfa_verify_u2f_handler.go index 7873468616..8541c043e4 100644 --- a/internal/api/ui/login/mfa_verify_u2f_handler.go +++ b/internal/api/ui/login/mfa_verify_u2f_handler.go @@ -24,22 +24,19 @@ type mfaU2FFormData struct { } func (l *Login) renderU2FVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, providers []domain.MFAType, err error) { - var errID, errMessage, credentialData string + var credentialData string var webAuthNLogin *domain.WebAuthNLogin if err == nil { userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) webAuthNLogin, err = l.authRepo.BeginMFAU2FLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID) } - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } if webAuthNLogin != nil { credentialData = base64.RawURLEncoding.EncodeToString(webAuthNLogin.CredentialAssertionData) } translator := l.getTranslator(r.Context(), authReq) data := &mfaU2FData{ webAuthNData: webAuthNData{ - userData: l.getUserData(r, authReq, translator, "VerifyMFAU2F.Title", "VerifyMFAU2F.Description", errID, errMessage), + userData: l.getUserData(r, authReq, translator, "VerifyMFAU2F.Title", "VerifyMFAU2F.Description", err), CredentialCreationData: credentialData, }, MFAProviders: providers, diff --git a/internal/api/ui/login/password_handler.go b/internal/api/ui/login/password_handler.go index 026963bbde..a6e9199ff7 100644 --- a/internal/api/ui/login/password_handler.go +++ b/internal/api/ui/login/password_handler.go @@ -15,12 +15,8 @@ type passwordFormData struct { } func (l *Login) renderPassword(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "Password.Title", "Password.Description", errID, errMessage) + data := l.getUserData(r, authReq, translator, "Password.Title", "Password.Description", err) funcs := map[string]interface{}{ "showPasswordReset": func() bool { if authReq.LoginPolicy != nil { diff --git a/internal/api/ui/login/password_reset_handler.go b/internal/api/ui/login/password_reset_handler.go index f4f98806c7..5bdee7904c 100644 --- a/internal/api/ui/login/password_reset_handler.go +++ b/internal/api/ui/login/password_reset_handler.go @@ -30,11 +30,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderPasswordResetDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "PasswordResetDone.Title", "PasswordResetDone.Description", errID, errMessage) + data := l.getUserData(r, authReq, translator, "PasswordResetDone.Title", "PasswordResetDone.Description", err) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplPasswordResetDone], data, nil) } diff --git a/internal/api/ui/login/passwordless_login_handler.go b/internal/api/ui/login/passwordless_login_handler.go index 52b9d06fed..d64ad2c3c1 100644 --- a/internal/api/ui/login/passwordless_login_handler.go +++ b/internal/api/ui/login/passwordless_login_handler.go @@ -2,6 +2,7 @@ package login import ( "encoding/base64" + "errors" "net/http" "github.com/zitadel/zitadel/internal/domain" @@ -22,13 +23,15 @@ type passwordlessFormData struct { } func (l *Login) renderPasswordlessVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, passwordSet bool, err error) { - var errID, errMessage, credentialData string + var credentialData string var webAuthNLogin *domain.WebAuthNLogin - if err == nil { - webAuthNLogin, err = l.authRepo.BeginPasswordlessLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, authReq.AgentID) - } - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) + if err == nil || errors.Is(err, &IdPError{}) { // make sure we still proceed with the webauthn login even if the idp login failed + var creationErr error + webAuthNLogin, creationErr = l.authRepo.BeginPasswordlessLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, authReq.AgentID) + // and only overwrite the error if the webauthn creation failed + if creationErr != nil { + err = creationErr + } } if webAuthNLogin != nil { credentialData = base64.RawURLEncoding.EncodeToString(webAuthNLogin.CredentialAssertionData) @@ -39,7 +42,7 @@ func (l *Login) renderPasswordlessVerification(w http.ResponseWriter, r *http.Re translator := l.getTranslator(r.Context(), authReq) data := &passwordlessData{ webAuthNData{ - userData: l.getUserData(r, authReq, translator, "Passwordless.Title", "Passwordless.Description", errID, errMessage), + userData: l.getUserData(r, authReq, translator, "Passwordless.Title", "Passwordless.Description", err), CredentialCreationData: credentialData, }, passwordSet, diff --git a/internal/api/ui/login/passwordless_prompt_handler.go b/internal/api/ui/login/passwordless_prompt_handler.go index ee70b76126..36a3ede71e 100644 --- a/internal/api/ui/login/passwordless_prompt_handler.go +++ b/internal/api/ui/login/passwordless_prompt_handler.go @@ -27,13 +27,9 @@ func (l *Login) handlePasswordlessPrompt(w http.ResponseWriter, r *http.Request) } func (l *Login) renderPasswordlessPrompt(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) data := &passwordlessPromptData{ - userData: l.getUserData(r, authReq, translator, "PasswordlessPrompt.Title", "PasswordlessPrompt.Description", errID, errMessage), + userData: l.getUserData(r, authReq, translator, "PasswordlessPrompt.Title", "PasswordlessPrompt.Description", err), } l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplPasswordlessPrompt], data, nil) } diff --git a/internal/api/ui/login/passwordless_registration_handler.go b/internal/api/ui/login/passwordless_registration_handler.go index 976a9277b2..782d62f1fe 100644 --- a/internal/api/ui/login/passwordless_registration_handler.go +++ b/internal/api/ui/login/passwordless_registration_handler.go @@ -78,7 +78,7 @@ func (l *Login) handlePasswordlessRegistration(w http.ResponseWriter, r *http.Re } func (l *Login) renderPasswordlessRegistration(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, orgID, codeID, code string, requestedPlatformType authPlatform, err error) { - var errID, errMessage, credentialData string + var credentialData string var disabled bool if authReq != nil { userID = authReq.UserID @@ -93,7 +93,6 @@ func (l *Login) renderPasswordlessRegistration(w http.ResponseWriter, r *http.Re } } if err != nil { - errID, errMessage = l.getErrorMessage(r, err) disabled = true } if webAuthNToken != nil { @@ -102,7 +101,7 @@ func (l *Login) renderPasswordlessRegistration(w http.ResponseWriter, r *http.Re translator := l.getTranslator(r.Context(), authReq) data := &passwordlessRegistrationData{ webAuthNData{ - userData: l.getUserData(r, authReq, translator, "PasswordlessRegistration.Title", "PasswordlessRegistration.Description", errID, errMessage), + userData: l.getUserData(r, authReq, translator, "PasswordlessRegistration.Title", "PasswordlessRegistration.Description", err), CredentialCreationData: credentialData, }, code, @@ -185,13 +184,9 @@ func (l *Login) checkPasswordlessRegistration(w http.ResponseWriter, r *http.Req } func (l *Login) renderPasswordlessRegistrationDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgID string, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) data := passwordlessRegistrationDoneDate{ - userData: l.getUserData(r, authReq, translator, "PasswordlessRegistrationDone.Title", "PasswordlessRegistrationDone.Description", errID, errMessage), + userData: l.getUserData(r, authReq, translator, "PasswordlessRegistrationDone.Title", "PasswordlessRegistrationDone.Description", err), HideNextButton: authReq == nil, } if authReq == nil { diff --git a/internal/api/ui/login/register_handler.go b/internal/api/ui/login/register_handler.go index 89e0eec7b3..bd5629c432 100644 --- a/internal/api/ui/login/register_handler.go +++ b/internal/api/ui/login/register_handler.go @@ -142,10 +142,6 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderRegister(w http.ResponseWriter, r *http.Request, authRequest *domain.AuthRequest, formData *registerFormData, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authRequest) if formData == nil { formData = new(registerFormData) @@ -156,7 +152,7 @@ func (l *Login) renderRegister(w http.ResponseWriter, r *http.Request, authReque resourceOwner := determineResourceOwner(r.Context(), authRequest) data := registerData{ - baseData: l.getBaseData(r, authRequest, translator, "RegistrationUser.Title", "RegistrationUser.Description", errID, errMessage), + baseData: l.getBaseData(r, authRequest, translator, "RegistrationUser.Title", "RegistrationUser.Description", err), registerFormData: *formData, } diff --git a/internal/api/ui/login/register_option_handler.go b/internal/api/ui/login/register_option_handler.go index 7d88f76c6c..31270c0442 100644 --- a/internal/api/ui/login/register_option_handler.go +++ b/internal/api/ui/login/register_option_handler.go @@ -33,10 +33,6 @@ func (l *Login) handleRegisterOption(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderRegisterOption(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } allowed := registrationAllowed(authReq) externalAllowed := externalRegistrationAllowed(authReq) if err == nil { @@ -54,7 +50,7 @@ func (l *Login) renderRegisterOption(w http.ResponseWriter, r *http.Request, aut } translator := l.getTranslator(r.Context(), authReq) data := registerOptionData{ - baseData: l.getBaseData(r, authReq, translator, "RegisterOption.Title", "RegisterOption.Description", errID, errMessage), + baseData: l.getBaseData(r, authReq, translator, "RegisterOption.Title", "RegisterOption.Description", err), } funcs := map[string]interface{}{ "hasRegistration": func() bool { diff --git a/internal/api/ui/login/register_org_handler.go b/internal/api/ui/login/register_org_handler.go index acb032d8f1..58a49f1d08 100644 --- a/internal/api/ui/login/register_org_handler.go +++ b/internal/api/ui/login/register_org_handler.go @@ -97,16 +97,12 @@ func (l *Login) handleRegisterOrgCheck(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderRegisterOrg(w http.ResponseWriter, r *http.Request, authRequest *domain.AuthRequest, formData *registerOrgFormData, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } if formData == nil { formData = new(registerOrgFormData) } translator := l.getTranslator(r.Context(), authRequest) data := registerOrgData{ - baseData: l.getBaseData(r, authRequest, translator, "RegistrationOrg.Title", "RegistrationOrg.Description", errID, errMessage), + baseData: l.getBaseData(r, authRequest, translator, "RegistrationOrg.Title", "RegistrationOrg.Description", err), registerOrgFormData: *formData, } pwPolicy := l.getPasswordComplexityPolicy(r, "0") diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go index cb05f78323..79fc2dcf0d 100644 --- a/internal/api/ui/login/renderer.go +++ b/internal/api/ui/login/renderer.go @@ -341,7 +341,6 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq * } func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var msg string if err != nil { log := logging.WithError(err) if authReq != nil { @@ -352,17 +351,15 @@ func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, auth } else { log.Info() } - - _, msg = l.getErrorMessage(r, err) } translator := l.getTranslator(r.Context(), authReq) - data := l.getBaseData(r, authReq, translator, "Errors.Internal", "", "Internal", msg) + data := l.getBaseData(r, authReq, translator, "Errors.Internal", "", err) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplError], data, nil) } -func (l *Login) getUserData(r *http.Request, authReq *domain.AuthRequest, translator *i18n.Translator, titleI18nKey string, descriptionI18nKey string, errType, errMessage string) userData { +func (l *Login) getUserData(r *http.Request, authReq *domain.AuthRequest, translator *i18n.Translator, titleI18nKey string, descriptionI18nKey string, err error) userData { userData := userData{ - baseData: l.getBaseData(r, authReq, translator, titleI18nKey, descriptionI18nKey, errType, errMessage), + baseData: l.getBaseData(r, authReq, translator, titleI18nKey, descriptionI18nKey, err), profileData: l.getProfileData(authReq), } if authReq != nil && authReq.LinkingUsers != nil { @@ -371,7 +368,7 @@ func (l *Login) getUserData(r *http.Request, authReq *domain.AuthRequest, transl return userData } -func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, translator *i18n.Translator, titleI18nKey string, descriptionI18nKey string, errType, errMessage string) baseData { +func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, translator *i18n.Translator, titleI18nKey string, descriptionI18nKey string, err error) baseData { title := "" if titleI18nKey != "" { title = translator.LocalizeWithoutArgs(titleI18nKey) @@ -383,10 +380,16 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, transl } lang, _ := l.renderer.ReqLang(translator, r).Base() + var errID, errMessage string + var errPopup bool + if err != nil { + errID, errMessage, errPopup = l.getErrorMessage(r, err) + } baseData := baseData{ errorData: errorData{ - ErrID: errType, + ErrID: errID, ErrMessage: errMessage, + ErrPopup: errPopup, }, Lang: lang.String(), Title: title, @@ -482,14 +485,17 @@ func (l *Login) setLinksOnBaseData(baseData baseData, privacyPolicy *domain.Priv return baseData } -func (l *Login) getErrorMessage(r *http.Request, err error) (errID, errMsg string) { +func (l *Login) getErrorMessage(r *http.Request, err error) (errID, errMsg string, popup bool) { + idpErr := new(IdPError) + if errors.Is(err, idpErr) { + popup = true + } caosErr := new(zerrors.ZitadelError) if errors.As(err, &caosErr) { - localized := l.renderer.LocalizeFromRequest(l.getTranslator(r.Context(), nil), r, caosErr.Message, nil) - return caosErr.ID, localized - + localized := l.renderer.LocalizeFromRequest(l.getTranslator(r.Context(), nil), r, caosErr.Message, map[string]interface{}{"Details": caosErr.Parent}) + return caosErr.ID, localized, popup } - return "", err.Error() + return "", err.Error(), popup } func (l *Login) getTheme(r *http.Request) string { @@ -662,6 +668,7 @@ type baseData struct { type errorData struct { ErrID string ErrMessage string + ErrPopup bool } type userData struct { diff --git a/internal/api/ui/login/select_user_handler.go b/internal/api/ui/login/select_user_handler.go index 98c3993376..b15366baa1 100644 --- a/internal/api/ui/login/select_user_handler.go +++ b/internal/api/ui/login/select_user_handler.go @@ -27,7 +27,7 @@ func (l *Login) renderUserSelection(w http.ResponseWriter, r *http.Request, auth descriptionI18nKey = "SelectAccount.DescriptionLinking" } data := userSelectionData{ - baseData: l.getBaseData(r, authReq, translator, titleI18nKey, descriptionI18nKey, "", ""), + baseData: l.getBaseData(r, authReq, translator, titleI18nKey, descriptionI18nKey, nil), Users: selectionData.Users, Linking: linking, } diff --git a/internal/api/ui/login/static/i18n/bg.yaml b/internal/api/ui/login/static/i18n/bg.yaml index ad308b859d..be0b1d7f14 100644 --- a/internal/api/ui/login/static/i18n/bg.yaml +++ b/internal/api/ui/login/static/i18n/bg.yaml @@ -493,6 +493,10 @@ Errors: CreationNotAllowed: Създаването на нов потребител не е разрешено на този доставчик LinkingNotAllowed: Свързването на потребител не е разрешено на този доставчик NoOptionAllowed: Нито създаване, нито свързване е разрешено за този доставчик. Моля, свържете се с администратора. + LoginFailedSwitchLocal: | + Вход в външен доставчик на идентификация е неуспешен. Връщане към локален вход. + + Подробности за грешката: {{.Details}} GrantRequired: 'Влизането не е възможно. ' ProjectRequired: 'Влизането не е възможно. ' IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/cs.yaml b/internal/api/ui/login/static/i18n/cs.yaml index 032302e3b8..f362add6f3 100644 --- a/internal/api/ui/login/static/i18n/cs.yaml +++ b/internal/api/ui/login/static/i18n/cs.yaml @@ -505,6 +505,10 @@ Errors: CreationNotAllowed: Vytvoření nového uživatele není na tomto poskytovateli povoleno LinkingNotAllowed: Propojení uživatele není na tomto poskytovateli povoleno NoOptionAllowed: Ani vytvoření, ani propojení není povoleno pro tohoto poskytovatele. Obraťte se na svého správce. + LoginFailedSwitchLocal: | + Přihlášení u externího poskytovatele identit selhalo. Vracíme se k místnímu přihlášení. + + Podrobnosti o chybě: {{.Details}} GrantRequired: Přihlášení není možné. Uživatel musí mít alespoň jeden oprávnění na aplikaci. Prosím, kontaktujte svého správce. ProjectRequired: Přihlášení není možné. Organizace uživatele musí být přidělena k projektu. Prosím, kontaktujte svého správce. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/de.yaml b/internal/api/ui/login/static/i18n/de.yaml index 28f4d00a88..4e6782fcb8 100644 --- a/internal/api/ui/login/static/i18n/de.yaml +++ b/internal/api/ui/login/static/i18n/de.yaml @@ -504,6 +504,10 @@ Errors: CreationNotAllowed: Erstellen eines neuen Benutzers mit diesem Provider ist nicht erlaubt LinkingNotAllowed: Verknüpfen eines Benutzers mit diesem Provider ist nicht erlaubt NoOptionAllowed: Weder Erstellung noch Verknüpfung ist für diesen Provider erlaubt. Bitte wenden Sie sich an Ihren Administrator. + LoginFailedSwitchLocal: | + Anmeldung beim externen Identitätsanbieter fehlgeschlagen. Zurück zur lokalen Anmeldung. + + Fehlerdetails: {{.Details}} GrantRequired: Die Anmeldung an diese Applikation ist nicht möglich. Der Benutzer benötigt mindestens eine Berechtigung an der Applikation. Bitte wende dich an deinen Administrator. ProjectRequired: Die Anmeldung an dieser Applikation ist nicht möglich. Die Organisation des Benutzer benötigt Berechtigung auf das Projekt. Bitte wende dich an deinen Administrator. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/en.yaml b/internal/api/ui/login/static/i18n/en.yaml index 6c58b11257..bdf42ae57f 100644 --- a/internal/api/ui/login/static/i18n/en.yaml +++ b/internal/api/ui/login/static/i18n/en.yaml @@ -505,6 +505,10 @@ Errors: CreationNotAllowed: Creation of a new user is not allowed on this provider LinkingNotAllowed: Linking of a user is not allowed on this provider NoOptionAllowed: Neither creation of linking is allowed on this provider. Please contact your administrator. + LoginFailedSwitchLocal: | + Login at External IDP failed. Falling back to local login. + + Error details: {{.Details}} GrantRequired: Login not possible. The user is required to have at least one grant on the application. Please contact your administrator. ProjectRequired: Login not possible. The organization of the user must be granted to the project. Please contact your administrator. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/es.yaml b/internal/api/ui/login/static/i18n/es.yaml index de57fdcd85..c6aaac6bf0 100644 --- a/internal/api/ui/login/static/i18n/es.yaml +++ b/internal/api/ui/login/static/i18n/es.yaml @@ -488,6 +488,10 @@ Errors: CreationNotAllowed: La creación de un nuevo usuario no está permitida para este proveedor LinkingNotAllowed: La vinculación de un usuario no está permitida para este proveedor NoOptionAllowed: Ni la creación ni la vinculación están permitidas en este proveedor. Póngase en contacto con su administrador. + LoginFailedSwitchLocal: | + Error al iniciar sesión en el proveedor de identidad externo. Volviendo al inicio de sesión local. + + Detalles del error: {{.Details}} GrantRequired: El inicio de sesión no es posible. Se requiere que el usuario tenga al menos una concesión sobre la aplicación. Por favor contacta con tu administrador. ProjectRequired: El inicio de sesión no es posible. La organización del usuario debe tener el acceso concedido para el proyecto. Por favor contacta con tu administrador. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/fr.yaml b/internal/api/ui/login/static/i18n/fr.yaml index 8534085ae9..83dd64d147 100644 --- a/internal/api/ui/login/static/i18n/fr.yaml +++ b/internal/api/ui/login/static/i18n/fr.yaml @@ -506,6 +506,10 @@ Errors: CreationNotAllowed: La création d'un nouvel utilisateur n'est pas autorisée sur ce fournisseur. LinkingNotAllowed: La création d'un lien vers un utilisateur n'est pas autorisée pour ce fournisseur. NoOptionAllowed: Ni la création ni la liaison sont autorisées pour ce fournisseur. Veuillez contacter votre administrateur. + LoginFailedSwitchLocal: | + Échec de la connexion au fournisseur d'identité externe. Retour à la connexion locale. + + Détails de l'erreur: {{.Details}} GrantRequired: Connexion impossible. L'utilisateur doit avoir au moins une subvention sur l'application. Veuillez contacter votre administrateur. ProjectRequired: Connexion impossible. L'organisation de l'utilisateur doit être accordée au projet. Veuillez contacter votre administrateur. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/hu.yaml b/internal/api/ui/login/static/i18n/hu.yaml index 80ed98945c..ef2a2acab4 100644 --- a/internal/api/ui/login/static/i18n/hu.yaml +++ b/internal/api/ui/login/static/i18n/hu.yaml @@ -465,6 +465,10 @@ Errors: CreationNotAllowed: Új felhasználó létrehozása nem engedélyezett ezen a szolgáltatón LinkingNotAllowed: A felhasználó összekapcsolása nem engedélyezett ezen a szolgáltatón NoOptionAllowed: Sem új felhasználó létrehozása, sem összekapcsolás nem engedélyezett ezen a szolgáltatón. Kérjük, lépj kapcsolatba az adminisztrátoroddal. + LoginFailedSwitchLocal: | + Az egyéni azonosító szolgáltatóhoz való bejelentkezés sikertelen volt. Visszatérés a helyi bejelentkezéshez. + + Hiba részletei: {{.Details}} GrantRequired: Bejelentkezés nem lehetséges. A felhasználónak legalább egy jogosultsággal kell rendelkeznie az alkalmazáson. Kérlek, lépj kapcsolatba az adminisztrátoroddal. ProjectRequired: Bejelentkezés nem lehetséges. A felhasználó szervezetének engedélyezve kell lennie a projektre. Kérlek, lépj kapcsolatba az adminisztrátoroddal. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/id.yaml b/internal/api/ui/login/static/i18n/id.yaml index 63deb41229..7fdd1bee1a 100644 --- a/internal/api/ui/login/static/i18n/id.yaml +++ b/internal/api/ui/login/static/i18n/id.yaml @@ -464,6 +464,10 @@ Errors: CreationNotAllowed: Pembuatan pengguna baru tidak diperbolehkan pada penyedia ini LinkingNotAllowed: Menautkan pengguna tidak diperbolehkan di penyedia ini NoOptionAllowed: 'Pembuatan tautan tidak diperbolehkan pada penyedia ini. ' + LoginFailedSwitchLocal: | + Gagal masuk ke Penyedia ID Eksternal. Kembali ke login lokal. + + Detail kesalahan: {{.Details}} GrantRequired: 'Masuk tidak dapat dilakukan. ' ProjectRequired: 'Masuk tidak dapat dilakukan. ' IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/it.yaml b/internal/api/ui/login/static/i18n/it.yaml index 46e74d3b13..ca681b6e82 100644 --- a/internal/api/ui/login/static/i18n/it.yaml +++ b/internal/api/ui/login/static/i18n/it.yaml @@ -505,6 +505,10 @@ Errors: CreationNotAllowed: La creazione di un nuovo utente non è consentita su questo provider. LinkingNotAllowed: Il collegamento di un utente non è consentito su questo provider. NoOptionAllowed: Né la creazione né il collegamento sono consentiti per questo provider. Contattare l'amministratore. + LoginFailedSwitchLocal: | + Accesso al provider di identità esterno non riuscito. Ritorno all'accesso locale. + + Dettagli dell'errore: {{.Details}} GrantRequired: Accesso non possibile. L'utente deve avere almeno una sovvenzione sull'applicazione. Contatta il tuo amministratore. ProjectRequired: Accesso non possibile. L'organizzazione dell'utente deve essere concessa al progetto. Contatta il tuo amministratore. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/ja.yaml b/internal/api/ui/login/static/i18n/ja.yaml index 9ec99eb912..8d725785c6 100644 --- a/internal/api/ui/login/static/i18n/ja.yaml +++ b/internal/api/ui/login/static/i18n/ja.yaml @@ -469,6 +469,10 @@ Errors: CreationNotAllowed: このプロバイダーでは、新しいユーザーの作成は許可されていません LinkingNotAllowed: このプロバイダーでは、ユーザーのリンクが許可されていません NoOptionAllowed: このプロバイダーでは作成もリンクも許可されていません。 管理者にお問い合わせください。 + LoginFailedSwitchLocal: | + 外部IDプロバイダーへのログインに失敗しました。ローカルログインに戻ります。 + + エラーの詳細: {{.Details}} GrantRequired: ログインできません。このユーザーは、アプリケーションに少なくとも1つの権限を付与されていることが必要です。管理者にお問い合わせください。 ProjectRequired: ログインできません。ユーザーの組織がプロジェクトに権限を付与されている必要があります。管理者にお問い合わせください。 IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/ko.yaml b/internal/api/ui/login/static/i18n/ko.yaml index e62cfcb8b5..bbe7a403a0 100644 --- a/internal/api/ui/login/static/i18n/ko.yaml +++ b/internal/api/ui/login/static/i18n/ko.yaml @@ -505,6 +505,10 @@ Errors: CreationNotAllowed: 이 제공자에서는 새 사용자 생성을 허용하지 않습니다 LinkingNotAllowed: 이 제공자에서는 사용자를 연결할 수 없습니다 NoOptionAllowed: 이 제공자에서는 생성과 연결이 모두 허용되지 않습니다. 관리자에게 문의하세요. + LoginFailedSwitchLocal: | + 외부 IDP에서 로그인에 실패했습니다. 로컬 로그인으로 돌아갑니다. + + 오류 세부 정보: {{.Details}} GrantRequired: 로그인 불가. 사용자는 애플리케이션에서 최소한 하나의 권한이 필요합니다. 관리자에게 문의하세요. ProjectRequired: 로그인 불가. 사용자의 조직이 프로젝트에 허가되어야 합니다. 관리자에게 문의하세요. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/mk.yaml b/internal/api/ui/login/static/i18n/mk.yaml index dbb988a0a6..2465c935b2 100644 --- a/internal/api/ui/login/static/i18n/mk.yaml +++ b/internal/api/ui/login/static/i18n/mk.yaml @@ -506,6 +506,10 @@ Errors: CreationNotAllowed: Креирањето на нов корисник не е дозволено на овој провајдер LinkingNotAllowed: Поврзувањето на корисник не е дозволено на овој провајдер NoOptionAllowed: NНиту создавање, ниту поврзување е дозволено за овој провајдер. Ве молиме контактирајте го вашиот администратор. + LoginFailedSwitchLocal: | + Најавата во надворешен провајдер на идентитет не успеа. Враќање на локална најава. + + Детали за грешката: {{.Details}} GrantRequired: Не е можно најавување. Корисникот мора да има барем едно овластување за апликацијата. Ве молиме контактирајте го вашиот администратор. ProjectRequired: Не е можно најавување. Организацијата на корисникот мора да биде доделена на проектот. Ве молиме контактирајте го вашиот администратор. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/nl.yaml b/internal/api/ui/login/static/i18n/nl.yaml index 3bbcee94b6..2bc1137154 100644 --- a/internal/api/ui/login/static/i18n/nl.yaml +++ b/internal/api/ui/login/static/i18n/nl.yaml @@ -505,6 +505,10 @@ Errors: CreationNotAllowed: Creatie van een nieuwe gebruiker is niet toegestaan op deze Provider LinkingNotAllowed: Koppeling van een gebruiker is niet toegestaan op deze Provider NoOptionAllowed: Noch aanmaak noch koppeling is toegestaan voor deze provider. Neem contact op met uw beheerder. + LoginFailedSwitchLocal: | + Aanmelding bij externe identiteitsprovider is mislukt. Terug naar lokale aanmelding. + + Foutdetails: {{.Details}} GrantRequired: Inloggen niet mogelijk. De gebruiker moet minimaal één grant hebben op de applicatie. Neem contact op met uw beheerder. ProjectRequired: Inloggen niet mogelijk. De organisatie van de gebruiker moet toegekend zijn aan het project. Neem contact op met uw beheerder. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/pl.yaml b/internal/api/ui/login/static/i18n/pl.yaml index 2c8b4fddf0..912af49a74 100644 --- a/internal/api/ui/login/static/i18n/pl.yaml +++ b/internal/api/ui/login/static/i18n/pl.yaml @@ -506,6 +506,10 @@ Errors: CreationNotAllowed: Tworzenie nowego użytkownika nie jest dozwolone w tym Providencie LinkingNotAllowed: Linkowanie użytkownika nie jest dozwolone na tym Providencie NoOptionAllowed: Ani tworzenie, ani łączenie nie jest dozwolone dla tego dostawcy. Skontaktuj się z administratorem. + LoginFailedSwitchLocal: | + Logowanie w zewnętrznym dostawcy tożsamości nie powiodło się. Powrót do logowania lokalnego. + + Szczegóły błędu: {{.Details}} GrantRequired: Logowanie nie jest możliwe. Użytkownik musi posiadać przynajmniej jedno uprawnienie w aplikacji. Skontaktuj się z administratorem. ProjectRequired: Logowanie nie jest możliwe. Organizacja użytkownika musi zostać udzielona projektowi. Skontaktuj się z administratorem. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/pt.yaml b/internal/api/ui/login/static/i18n/pt.yaml index f03f120ed8..5f18157e67 100644 --- a/internal/api/ui/login/static/i18n/pt.yaml +++ b/internal/api/ui/login/static/i18n/pt.yaml @@ -502,6 +502,10 @@ Errors: CreationNotAllowed: A criação de um novo usuário não é permitida neste provedor LinkingNotAllowed: A vinculação de um usuário não é permitida neste provedor NoOptionAllowed: Nem criação nem vinculação são permitidas neste fornecedor. Contate o seu administrador. + LoginFailedSwitchLocal: | + Falha no login no provedor de identidade externo. Retornando ao login local. + + Detalhes do erro: {{.Details}} GrantRequired: Login não é possível. O usuário precisa ter pelo menos uma permissão no aplicativo. Entre em contato com o administrador. ProjectRequired: Login não é possível. A organização do usuário precisa ser concedida ao projeto. Entre em contato com o administrador. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/ru.yaml b/internal/api/ui/login/static/i18n/ru.yaml index 221c20a2e9..8afd3a31b6 100644 --- a/internal/api/ui/login/static/i18n/ru.yaml +++ b/internal/api/ui/login/static/i18n/ru.yaml @@ -506,6 +506,10 @@ Errors: CreationNotAllowed: Создание нового пользователя для этого провайдера запрещено LinkingNotAllowed: Привязка к этому провайдеру запрещена NoOptionAllowed: Ни создание, ни привязка пользователя к этому провайдеру невозможны. Обратитесь к администратору. + LoginFailedSwitchLocal: | + Вход в внешний поставщик идентификации не удался. Возвращаемся к локальному входу. + + Подробности об ошибке: {{.Details}} GrantRequired: Вход невозможен. Пользователь должен иметь хотя бы один допуск к приложению. Обратитесь к администратору. ProjectRequired: Вход невозможен. Организация пользователя должна иметь доступ к проекту. Обратитесь к администратору. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/sv.yaml b/internal/api/ui/login/static/i18n/sv.yaml index 26fee23551..e6c1245503 100644 --- a/internal/api/ui/login/static/i18n/sv.yaml +++ b/internal/api/ui/login/static/i18n/sv.yaml @@ -505,6 +505,10 @@ Errors: CreationNotAllowed: Det är inte tillåtet att skapa nya konton från den här externa leverantören LinkingNotAllowed: Det är inte tillåtet att koppla ihop konton från den här externa leverantören NoOptionAllowed: Varken skapande eller länkande är tillåtet för denna leverantör. Kontakta administratören. + LoginFailedSwitchLocal: | + Inloggning vid extern identitetsprovider misslyckades. Återgår till lokal inloggning. + + Felaktighetsdetaljer: {{.Details}} GrantRequired: Det går inte att logga in just nu. Användarkontot har inte tillgång till någonting i tjänsten. Ta kontakt med systemansvarig. ProjectRequired: Det går inte att logga in just nu. Användarkontots organisation har inte tillgång till tjänsten. Ta kontakt med systemansvarig. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/zh.yaml b/internal/api/ui/login/static/i18n/zh.yaml index 79db3c020e..4fcb469831 100644 --- a/internal/api/ui/login/static/i18n/zh.yaml +++ b/internal/api/ui/login/static/i18n/zh.yaml @@ -505,6 +505,10 @@ Errors: CreationNotAllowed: 不允许在该供应商上创建新用户 LinkingNotAllowed: 在此提供者上不允许链接一个用户 NoOptionAllowed: 此提供商不允许创建或链接。请联系您的管理员。 + LoginFailedSwitchLocal: | + 外部身份提供商的登录失败。返回到本地登录。 + + 错误详情: {{.Details}} GrantRequired: 无法登录,用户需要在应用程序上拥有至少一项授权,请联系您的管理员。 ProjectRequired: 无法登录,用户的组织必须授予项目,请联系您的管理员。 IdentityProvider: diff --git a/internal/api/ui/login/static/resources/scripts/error_popup.js b/internal/api/ui/login/static/resources/scripts/error_popup.js new file mode 100644 index 0000000000..e817f81ac3 --- /dev/null +++ b/internal/api/ui/login/static/resources/scripts/error_popup.js @@ -0,0 +1,30 @@ +function removeOverlay(overlay) { + if (overlay.classList.contains("show")) { + overlay.classList.remove("show"); + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("click", onClick); + } +} + +function onMouseMove() { + const overlay = document.getElementById("dialog_overlay"); + if (overlay) { + removeOverlay(overlay); + } +} + +function onClick() { + const overlay = document.getElementById("dialog_overlay"); + if (overlay) { + removeOverlay(overlay); + } +} + +window.addEventListener('DOMContentLoaded', () => { + const overlay = document.getElementById("dialog_overlay"); + if (overlay && overlay.classList.contains("show")) { + setTimeout(() => removeOverlay(overlay), 5000); + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("click", onClick); + } +}); diff --git a/internal/api/ui/login/static/resources/themes/scss/main.scss b/internal/api/ui/login/static/resources/themes/scss/main.scss index 3b0ddc0da2..e9a97df2ca 100644 --- a/internal/api/ui/login/static/resources/themes/scss/main.scss +++ b/internal/api/ui/login/static/resources/themes/scss/main.scss @@ -20,3 +20,26 @@ body { .text-align-center { text-align: center; } + +.dialog_overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: black; + z-index: 1001; + -moz-opacity: 0.8; + opacity: .80; + filter: alpha(opacity=80); +} + +.dialog_overlay.show { + display: block; +} + +.dialog_content { + position: relative; + z-index: 1002; +} \ No newline at end of file diff --git a/internal/api/ui/login/static/resources/themes/scss/styles/error/error.scss b/internal/api/ui/login/static/resources/themes/scss/styles/error/error.scss index 39982e94ba..05131ec071 100644 --- a/internal/api/ui/login/static/resources/themes/scss/styles/error/error.scss +++ b/internal/api/ui/login/static/resources/themes/scss/styles/error/error.scss @@ -6,6 +6,10 @@ margin-right: .5rem; font-size: 1.5rem; } + + .lgn-error-message { + white-space: pre-line; + } } #wa-error { diff --git a/internal/api/ui/login/static/templates/error-message.html b/internal/api/ui/login/static/templates/error-message.html index 6c56caad1c..3de5d81bf4 100644 --- a/internal/api/ui/login/static/templates/error-message.html +++ b/internal/api/ui/login/static/templates/error-message.html @@ -1,10 +1,9 @@ {{ define "error-message" }} {{if .ErrMessage }} -
+
-

- {{ .ErrMessage }} -

+

{{ .ErrMessage }}

+
{{end}} -{{ end }} \ No newline at end of file +{{end}} \ No newline at end of file diff --git a/internal/api/ui/login/static/templates/password.html b/internal/api/ui/login/static/templates/password.html index c036e3c51b..98d94f3ef8 100644 --- a/internal/api/ui/login/static/templates/password.html +++ b/internal/api/ui/login/static/templates/password.html @@ -41,4 +41,5 @@ + diff --git a/internal/api/ui/login/static/templates/passwordless.html b/internal/api/ui/login/static/templates/passwordless.html index 6a95b54079..2dc6d544a0 100644 --- a/internal/api/ui/login/static/templates/passwordless.html +++ b/internal/api/ui/login/static/templates/passwordless.html @@ -40,5 +40,6 @@ + {{template "main-bottom" .}} diff --git a/internal/api/ui/login/username_change_handler.go b/internal/api/ui/login/username_change_handler.go index f11fe43a72..b932079dd0 100644 --- a/internal/api/ui/login/username_change_handler.go +++ b/internal/api/ui/login/username_change_handler.go @@ -16,12 +16,8 @@ type changeUsernameData struct { } func (l *Login) renderChangeUsername(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { - var errID, errMessage string - if err != nil { - errID, errMessage = l.getErrorMessage(r, err) - } translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "UsernameChange.Title", "UsernameChange.Description", errID, errMessage) + data := l.getUserData(r, authReq, translator, "UsernameChange.Title", "UsernameChange.Description", err) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplChangeUsername], data, nil) } @@ -41,8 +37,7 @@ func (l *Login) handleChangeUsername(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderChangeUsernameDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { - var errType, errMessage string translator := l.getTranslator(r.Context(), authReq) - data := l.getUserData(r, authReq, translator, "UsernameChangeDone.Title", "UsernameChangeDone.Description", errType, errMessage) + data := l.getUserData(r, authReq, translator, "UsernameChangeDone.Title", "UsernameChangeDone.Description", nil) l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplChangeUsernameDone], data, nil) } diff --git a/internal/auth/repository/auth_request.go b/internal/auth/repository/auth_request.go index d89eb35a8b..c16a757a01 100644 --- a/internal/auth/repository/auth_request.go +++ b/internal/auth/repository/auth_request.go @@ -41,4 +41,5 @@ type AuthRequestRepository interface { AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) error ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error ResetSelectedIDP(ctx context.Context, authReqID, userAgentID string) error + RequestLocalAuth(ctx context.Context, authReqID, userAgentID string) error } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 813c5668f4..60486b66f9 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -563,6 +563,15 @@ func (repo *AuthRequestRepo) ResetSelectedIDP(ctx context.Context, authReqID, us return repo.AuthRequests.UpdateAuthRequest(ctx, request) } +func (repo *AuthRequestRepo) RequestLocalAuth(ctx context.Context, authReqID, userAgentID string) error { + request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) + if err != nil { + return err + } + request.RequestLocalAuth = true + return repo.AuthRequests.UpdateAuthRequest(ctx, request) +} + func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -1059,7 +1068,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth request.PreferredLanguage = gu.Ptr(language.Make(user.HumanView.PreferredLanguage)) } - isInternalLogin := request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == "" + isInternalLogin := (request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == "") || request.RequestLocalAuth idps, err := checkExternalIDPsOfUser(ctx, repo.IDPUserLinksProvider, user.ID) if err != nil { return nil, err @@ -1067,7 +1076,9 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth noLocalAuth := request.LoginPolicy != nil && !request.LoginPolicy.AllowUsernamePassword allowedLinkedIDPs := checkForAllowedIDPs(request.AllowedExternalIDPs, idps.Links) - if (!isInternalLogin || len(allowedLinkedIDPs) > 0 || noLocalAuth) && len(request.LinkingUsers) == 0 { + if (!isInternalLogin || len(allowedLinkedIDPs) > 0 || noLocalAuth) && + len(request.LinkingUsers) == 0 && + !request.RequestLocalAuth { step, err := repo.idpChecked(request, allowedLinkedIDPs, userSession) if err != nil { return nil, err diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 976ae8d8a9..7d71ddecd9 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -2263,6 +2263,86 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { []domain.NextStep{&domain.LinkUsersStep{}}, nil, }, + { + "local auth requested (passwordless and password set up), passwordless step", + fields{ + userSessionViewProvider: &mockViewUserSession{}, + userViewProvider: &mockViewUser{ + PasswordSet: true, + IsEmailVerified: true, + MFAMaxSetUp: int32(domain.MFALevelMultiFactor), + PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}}, + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, + lockoutPolicyProvider: &mockLockoutPolicy{ + policy: &query.LockoutPolicy{ + ShowFailures: true, + }, + }, + idpUserLinksProvider: &mockIDPUserLinks{ + idps: []*query.IDPUserLink{{IDPID: "IDPConfigID"}}, + }, + }, + args{ + &domain.AuthRequest{ + UserID: "UserID", + SelectedIDPConfigID: "IDPConfigID", + LoginPolicy: &domain.LoginPolicy{ + PasswordlessType: domain.PasswordlessTypeAllowed, + }, + AllowedExternalIDPs: []*domain.IDPProvider{ + { + IDPConfigID: "IDPConfigID", + }, + }, + RequestLocalAuth: true, + }, false}, + []domain.NextStep{ + &domain.PasswordlessStep{ + PasswordSet: true, + }, + }, + nil, + }, + { + "local auth requested (password set up), password step", + fields{ + userSessionViewProvider: &mockViewUserSession{}, + userViewProvider: &mockViewUser{ + PasswordSet: true, + IsEmailVerified: true, + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, + lockoutPolicyProvider: &mockLockoutPolicy{ + policy: &query.LockoutPolicy{ + ShowFailures: true, + }, + }, + idpUserLinksProvider: &mockIDPUserLinks{ + idps: []*query.IDPUserLink{{IDPID: "IDPConfigID"}}, + }, + }, + args{ + &domain.AuthRequest{ + UserID: "UserID", + SelectedIDPConfigID: "IDPConfigID", + LoginPolicy: &domain.LoginPolicy{ + PasswordlessType: domain.PasswordlessTypeAllowed, + }, + AllowedExternalIDPs: []*domain.IDPProvider{ + { + IDPConfigID: "IDPConfigID", + }, + }, + RequestLocalAuth: true, + }, false}, + []domain.NextStep{ + &domain.PasswordStep{}, + }, + nil, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/domain/auth_request.go b/internal/domain/auth_request.go index 01b6ae25da..85ec340f67 100644 --- a/internal/domain/auth_request.go +++ b/internal/domain/auth_request.go @@ -60,6 +60,7 @@ type AuthRequest struct { DefaultTranslations []*CustomText OrgTranslations []*CustomText SAMLRequestID string + RequestLocalAuth bool // orgID the policies were last loaded with policyOrgID string // SessionID is set to the computed sessionID of the login session table