diff --git a/internal/api/ui/login/change_password_handler.go b/internal/api/ui/login/change_password_handler.go index 19f2404c9..7f2b83b80 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 e7ddaccd0..fb61102d7 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 c60e0eb0b..5481c6aed 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 b8c6d401c..17ac13ff3 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 9a6d052dc..fa4854a47 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 e083277c9..18ba50248 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 0fd47c5a6..147a31952 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 c72055908..0b0803f8a 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 059048eec..729bd1955 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 00f29becf..a18a3a2d5 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 e270cd554..9596f477a 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 864ff76dd..071fe6539 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 437fde29f..ae4bab69e 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 03f2c3201..048677f0f 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 c84948796..0e75bd1b6 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 cd6a9091e..e3488d391 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 ce1b7240e..ca318741b 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 cfffc6fce..34832413c 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 fb77bbcba..bd09a7652 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 787346861..8541c043e 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 026963bbd..a6e9199ff 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 f4f98806c..5bdee7904 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 52b9d06fe..d64ad2c3c 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 ee70b7612..36a3ede71 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 976a9277b..782d62f1f 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 89e0eec7b..bd5629c43 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 7d88f76c6..31270c044 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 acb032d8f..58a49f1d0 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 cb05f7832..79fc2dcf0 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 98c399337..b15366baa 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 ad308b859..be0b1d7f1 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 032302e3b..f362add6f 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 28f4d00a8..4e6782fcb 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 6c58b1125..bdf42ae57 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 de57fdcd8..c6aaac6bf 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 8534085ae..83dd64d14 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 80ed98945..ef2a2acab 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 63deb4122..7fdd1bee1 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 46e74d3b1..ca681b6e8 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 9ec99eb91..8d725785c 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 e62cfcb8b..bbe7a403a 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 dbb988a0a..2465c935b 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 3bbcee94b..2bc113715 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 2c8b4fddf..912af49a7 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 f03f120ed..5f18157e6 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 221c20a2e..8afd3a31b 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 26fee2355..e6c124550 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 79db3c020..4fcb46983 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 000000000..e817f81ac --- /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 3b0ddc0da..e9a97df2c 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 39982e94b..05131ec07 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 6c56caad1..3de5d81bf 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 c036e3c51..98d94f3ef 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 6a95b5407..2dc6d544a 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 f11fe43a7..b932079dd 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 d89eb35a8..c16a757a0 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 813c5668f..60486b66f 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 976ae8d8a..7d71ddecd 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 01b6ae25d..85ec340f6 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