diff --git a/cmd/zitadel/system-defaults.yaml b/cmd/zitadel/system-defaults.yaml index 5faee8baec..7f075bee3b 100644 --- a/cmd/zitadel/system-defaults.yaml +++ b/cmd/zitadel/system-defaults.yaml @@ -47,7 +47,7 @@ SystemDefaults: MachineKeySize: 2048 Multifactors: OTP: - Issuer: 'Zitadel' + Issuer: 'ZITADEL' VerificationKey: EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY VerificationLifetimes: diff --git a/internal/api/grpc/management/user_converter.go b/internal/api/grpc/management/user_converter.go index 5f10e61a72..645a07c0ea 100644 --- a/internal/api/grpc/management/user_converter.go +++ b/internal/api/grpc/management/user_converter.go @@ -501,8 +501,9 @@ func mfasFromModel(mfas []*usr_model.MultiFactor) []*management.UserMultiFactor func mfaFromModel(mfa *usr_model.MultiFactor) *management.UserMultiFactor { return &management.UserMultiFactor{ - State: mfaStateFromModel(mfa.State), - Type: mfaTypeFromModel(mfa.Type), + State: mfaStateFromModel(mfa.State), + Type: mfaTypeFromModel(mfa.Type), + Attribute: mfa.Attribute, } } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 7c7a7bb8ea..cd9bd0406e 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -786,7 +786,7 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve if !errors.IsNotFound(err) { return nil, err } - session = &user_view_model.UserSessionView{} + session = &user_view_model.UserSessionView{UserAgentID: agentID, UserID: user.ID} } events, err := eventProvider.UserEventsByID(ctx, user.ID, session.Sequence) if err != nil { @@ -824,7 +824,9 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve case es_model.UserRemoved: return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dG2fe", "Errors.User.NotActive") } - sessionCopy.AppendEvent(event) + if err := sessionCopy.AppendEvent(event); err != nil { + return user_view_model.UserSessionToModel(&sessionCopy), nil + } } return user_view_model.UserSessionToModel(&sessionCopy), nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 345bbfc727..c3de87ad30 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -1208,7 +1208,7 @@ func Test_userSessionByIDs(t *testing.T) { eventProvider: &mockEventErrUser{}, user: &user_model.UserView{ID: "id"}, }, - &user_model.UserSessionView{}, + &user_model.UserSessionView{UserID: "id"}, nil, }, { diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index af6d62b2b1..50ce4a1127 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -234,11 +234,11 @@ func (repo *UserRepo) ChangeMyPassword(ctx context.Context, old, new string) err return err } pwPolicyView := iam_es_model.PasswordComplexityViewToModel(policy) - _, err = repo.UserEvents.ChangePassword(ctx, pwPolicyView, authz.GetCtxData(ctx).UserID, old, new) + _, err = repo.UserEvents.ChangePassword(ctx, pwPolicyView, authz.GetCtxData(ctx).UserID, old, new, "") return err } -func (repo *UserRepo) ChangePassword(ctx context.Context, userID, old, new string) (err error) { +func (repo *UserRepo) ChangePassword(ctx context.Context, userID, old, new, userAgentID string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) @@ -249,7 +249,7 @@ func (repo *UserRepo) ChangePassword(ctx context.Context, userID, old, new strin return err } pwPolicyView := iam_es_model.PasswordComplexityViewToModel(policy) - _, err = repo.UserEvents.ChangePassword(ctx, pwPolicyView, userID, old, new) + _, err = repo.UserEvents.ChangePassword(ctx, pwPolicyView, userID, old, new, userAgentID) return err } @@ -290,12 +290,12 @@ func (repo *UserRepo) AddMyMFAOTP(ctx context.Context) (*model.OTP, error) { return repo.UserEvents.AddOTP(ctx, authz.GetCtxData(ctx).UserID, accountName) } -func (repo *UserRepo) VerifyMFAOTPSetup(ctx context.Context, userID, code string) error { - return repo.UserEvents.CheckMFAOTPSetup(ctx, userID, code) +func (repo *UserRepo) VerifyMFAOTPSetup(ctx context.Context, userID, code, userAgentID string) error { + return repo.UserEvents.CheckMFAOTPSetup(ctx, userID, code, userAgentID) } func (repo *UserRepo) VerifyMyMFAOTPSetup(ctx context.Context, code string) error { - return repo.UserEvents.CheckMFAOTPSetup(ctx, authz.GetCtxData(ctx).UserID, code) + return repo.UserEvents.CheckMFAOTPSetup(ctx, authz.GetCtxData(ctx).UserID, code, "") } func (repo *UserRepo) RemoveMyMFAOTP(ctx context.Context) error { @@ -310,12 +310,12 @@ func (repo *UserRepo) AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, er return repo.UserEvents.AddU2F(ctx, authz.GetCtxData(ctx).UserID) } -func (repo *UserRepo) VerifyMFAU2FSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error { - return repo.UserEvents.VerifyU2FSetup(ctx, userID, tokenName, credentialData) +func (repo *UserRepo) VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { + return repo.UserEvents.VerifyU2FSetup(ctx, userID, tokenName, userAgentID, credentialData) } func (repo *UserRepo) VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, credentialData []byte) error { - return repo.UserEvents.VerifyU2FSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, credentialData) + return repo.UserEvents.VerifyU2FSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, "", credentialData) } func (repo *UserRepo) RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error { @@ -334,12 +334,12 @@ func (repo *UserRepo) AddMyPasswordless(ctx context.Context) (*model.WebAuthNTok return repo.UserEvents.AddPasswordless(ctx, authz.GetCtxData(ctx).UserID) } -func (repo *UserRepo) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error { - return repo.UserEvents.VerifyPasswordlessSetup(ctx, userID, tokenName, credentialData) +func (repo *UserRepo) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { + return repo.UserEvents.VerifyPasswordlessSetup(ctx, userID, tokenName, userAgentID, credentialData) } func (repo *UserRepo) VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, credentialData []byte) error { - return repo.UserEvents.VerifyPasswordlessSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, credentialData) + return repo.UserEvents.VerifyPasswordlessSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, "", credentialData) } func (repo *UserRepo) RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error { @@ -391,7 +391,7 @@ func (repo *UserRepo) RequestPasswordReset(ctx context.Context, loginname string return repo.UserEvents.RequestSetPassword(ctx, user.ID, model.NotificationTypeEmail) } -func (repo *UserRepo) SetPassword(ctx context.Context, userID, code, password string) error { +func (repo *UserRepo) SetPassword(ctx context.Context, userID, code, password, userAgentID string) error { policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) if errors.IsNotFound(err) { policy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) @@ -400,7 +400,7 @@ func (repo *UserRepo) SetPassword(ctx context.Context, userID, code, password st return err } pwPolicyView := iam_es_model.PasswordComplexityViewToModel(policy) - return repo.UserEvents.SetPassword(ctx, pwPolicyView, userID, code, password) + return repo.UserEvents.SetPassword(ctx, pwPolicyView, userID, code, password, userAgentID) } func (repo *UserRepo) SignOut(ctx context.Context, agentID string) error { diff --git a/internal/auth/repository/eventsourcing/handler/user_session.go b/internal/auth/repository/eventsourcing/handler/user_session.go index 174a0d8394..b55aacfd63 100644 --- a/internal/auth/repository/eventsourcing/handler/user_session.go +++ b/internal/auth/repository/eventsourcing/handler/user_session.go @@ -93,7 +93,9 @@ func (u *UserSession) Reduce(event *models.Event) (err error) { return u.view.ProcessedUserSessionSequence(event.Sequence, event.CreationDate) } for _, session := range sessions { - session.AppendEvent(event) + if err := session.AppendEvent(event); err != nil { + return err + } if err := u.fillUserInfo(session, event.AggregateID); err != nil { return err } @@ -116,7 +118,9 @@ func (u *UserSession) OnSuccess() error { } func (u *UserSession) updateSession(session *view_model.UserSessionView, event *models.Event) error { - session.AppendEvent(event) + if err := session.AppendEvent(event); err != nil { + return err + } if err := u.fillUserInfo(session, event.AggregateID); err != nil { return err } diff --git a/internal/auth/repository/user.go b/internal/auth/repository/user.go index 81f6084e4c..1399a2b6a6 100644 --- a/internal/auth/repository/user.go +++ b/internal/auth/repository/user.go @@ -16,8 +16,8 @@ type UserRepository interface { SkipMFAInit(ctx context.Context, userID string) error RequestPasswordReset(ctx context.Context, username string) error - SetPassword(ctx context.Context, userID, code, password string) error - ChangePassword(ctx context.Context, userID, old, new string) error + SetPassword(ctx context.Context, userID, code, password, userAgentID string) error + ChangePassword(ctx context.Context, userID, old, new, userAgentID string) error VerifyEmail(ctx context.Context, userID, code string) error ResendEmailVerificationMail(ctx context.Context, userID string) error @@ -26,14 +26,14 @@ type UserRepository interface { ResendInitVerificationMail(ctx context.Context, userID string) error AddMFAOTP(ctx context.Context, userID string) (*model.OTP, error) - VerifyMFAOTPSetup(ctx context.Context, userID, code string) error + VerifyMFAOTPSetup(ctx context.Context, userID, code, userAgentID string) error AddMFAU2F(ctx context.Context, id string) (*model.WebAuthNToken, error) - VerifyMFAU2FSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error + VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error AddPasswordless(ctx context.Context, id string) (*model.WebAuthNToken, error) - VerifyPasswordlessSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error + VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error ChangeUsername(ctx context.Context, userID, username string) error diff --git a/internal/management/repository/eventsourcing/eventstore/user.go b/internal/management/repository/eventsourcing/eventstore/user.go index 3be494fd68..8eb7a1806b 100644 --- a/internal/management/repository/eventsourcing/eventstore/user.go +++ b/internal/management/repository/eventsourcing/eventstore/user.go @@ -217,10 +217,14 @@ func (repo *UserRepo) UserMFAs(ctx context.Context, userID string) ([]*usr_model if user.HumanView == nil { return nil, errors.ThrowPreconditionFailed(nil, "EVENT-xx0hV", "Errors.User.NotHuman") } - if user.OTPState == usr_model.MFAStateUnspecified { - return []*usr_model.MultiFactor{}, nil + mfas := make([]*usr_model.MultiFactor, 0) + if user.OTPState != usr_model.MFAStateUnspecified { + mfas = append(mfas, &usr_model.MultiFactor{Type: usr_model.MFATypeOTP, State: user.OTPState}) } - return []*usr_model.MultiFactor{{Type: usr_model.MFATypeOTP, State: user.OTPState}}, nil + for _, u2f := range user.U2FTokens { + mfas = append(mfas, &usr_model.MultiFactor{Type: usr_model.MFATypeU2F, State: u2f.State, Attribute: u2f.Name}) + } + return mfas, nil } func (repo *UserRepo) RemoveOTP(ctx context.Context, userID string) error { diff --git a/internal/setup/step1.go b/internal/setup/step1.go index a745a1a3e4..e7d982723a 100644 --- a/internal/setup/step1.go +++ b/internal/setup/step1.go @@ -107,6 +107,7 @@ func (step *Step1) orgs(ctx context.Context, orgs []Org) error { return err } step.createdOrgs[iamOrg.Name] = org + logging.LogWithFields("SETUP-HR2gh", "name", org.Name, "ID", org.AggregateID).Info("created organisation") var policy *iam_model.OrgIAMPolicyView if iamOrg.OrgIamPolicy { diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 729e781d98..ac68d86282 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -320,8 +320,32 @@ EventTypes: check: succeeded: Multifaktor OTP Verifikation erfolgreich failed: Multifaktor OTP Verifikation fehlgeschlagen + u2f: + token: + added: Multifaktor U2F Token hinzugefügt + verified: Multifaktor U2F Token verifiziert + removed: Multifaktor U2F Token entfernt + begin: + login: Multifaktor U2F Verifikation begonnen + check: + succeeded: Multifaktor U2F Verifikation erfolgreich + failed: Multifaktor U2F Verifikation fehlgeschlagen + signcount: + changed: Prüfsumme des Multifaktor U2F Tokens wurde verändert init: skipped: Multifaktor Initialisierung übersprungen + passwordless: + token: + added: Token für Passwortlos Login hinzugefügt + verified: Token für Passwortlos Login verifiziert + removed: Token für Passwortlos Login entfernt + begin: + login: Verifikation für Passwortlos Login begonnen + check: + succeeded: Verifikation für Passwortlos Login erfolgreich + failed: Verifikation für Passwortlos Login fehlgeschlagen + signcount: + changed: Prüfsumme des Tokens für Passwortlos Login wurde verändert signed: out: Benutzer erfolgreich abgemeldet locked: Benutzer gesperrt diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 79088a8465..fd6a101090 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -101,6 +101,7 @@ Errors: IDP: InvalidSearchQuery: Ungültiger Suchparameter LoginPolicy: + NotFound: Login Policy not found Invalid: Login Policy is invalid NotExisting: Login Policy not existig AlreadyExists: Login Policy already exists @@ -111,9 +112,20 @@ Errors: NotExisting: Multifactor not existing Unspecified: Multifactor invalid PasswordComplexity: - Empty: Passwort Complexity Policy is empty - NotExisting: Passwort Complexity Policy doesn't exist - AlreadyExists: Passwort Complexity Policy already exists + NotFound: Password Complexity Policy not found + Empty: Password Complexity Policy is empty + NotExisting: Password Complexity Policy doesn't exist + AlreadyExists: Password Complexity Policy already exists + PasswordLockout: + NotFound: Password Lockout Policy not found + Empty: Passwort Lockout Policy is empty + NotExisting: Passwort Lockout Policy doesn't exist + AlreadyExists: Passwort Lockout Policy already exists + PasswordAge: + NotFound: Password Age Policy not found + Empty: Password Age Policy is empty + NotExisting: Password Age Policy doesn't exist + AlreadyExists: Password Age Policy already exists OrgIAM: Empty: Org IAM Policy is empty NotExisting: Org IAM Policy doesn't exist @@ -308,8 +320,32 @@ EventTypes: check: succeeded: Multifactor OTP check succeeded failed: Multifactor OTP check failed + u2f: + token: + added: Multifactor U2F Token added + verified: Multifactor U2F Token verified + removed: Multifactor U2F Token removed + begin: + login: Multifactor U2F check started + check: + succeeded: Multifactor U2F check succeeded + failed: Multifactor U2F check failed + signcount: + changed: Checksum of the Multifactor U2F Token has been changed init: skipped: Multifactor initialisation skipped + passwordless: + token: + added: Token for Passwordless Login added + verified: Token for Passwordless Login verified + removed: Token for Passwordless Login removed + begin: + login: Passwordless Login check started + check: + succeeded: Passwordless Login check succeeded + failed: Passwordless Login check failed + signcount: + changed: Checksum of the Passwordless Login Token has been changed signed: out: User signed out locked: User locked diff --git a/internal/ui/login/handler/change_password_handler.go b/internal/ui/login/handler/change_password_handler.go index 9c8a91c8d1..d85f537e08 100644 --- a/internal/ui/login/handler/change_password_handler.go +++ b/internal/ui/login/handler/change_password_handler.go @@ -3,6 +3,7 @@ package handler import ( "net/http" + http_mw "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/auth_request/model" ) @@ -24,7 +25,8 @@ func (l *Login) handleChangePassword(w http.ResponseWriter, r *http.Request) { l.renderError(w, r, authReq, err) return } - err = l.authRepo.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.OldPassword, data.NewPassword) + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + err = l.authRepo.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.OldPassword, data.NewPassword, userAgentID) if err != nil { l.renderChangePassword(w, r, authReq, err) return diff --git a/internal/ui/login/handler/init_password_handler.go b/internal/ui/login/handler/init_password_handler.go index 3a6db0a907..21d9cfe080 100644 --- a/internal/ui/login/handler/init_password_handler.go +++ b/internal/ui/login/handler/init_password_handler.go @@ -3,6 +3,7 @@ package handler import ( "net/http" + http_mw "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/errors" ) @@ -67,7 +68,8 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *mod if authReq != nil { userOrg = authReq.UserOrgID } - err = l.authRepo.SetPassword(setContext(r.Context(), userOrg), data.UserID, data.Code, data.Password) + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + err = l.authRepo.SetPassword(setContext(r.Context(), userOrg), data.UserID, data.Code, data.Password, userAgentID) if err != nil { l.renderInitPassword(w, r, authReq, data.UserID, "", err) return diff --git a/internal/ui/login/handler/mfa_init_u2f.go b/internal/ui/login/handler/mfa_init_u2f.go index dccb3a84d4..1e5d64f08f 100644 --- a/internal/ui/login/handler/mfa_init_u2f.go +++ b/internal/ui/login/handler/mfa_init_u2f.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "net/http" + http_mw "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/auth_request/model" user_model "github.com/caos/zitadel/internal/user/model" ) @@ -12,6 +13,11 @@ const ( tmplMFAU2FInit = "mfainitu2f" ) +type u2fInitData struct { + webAuthNData + MFAType model.MFAType +} + func (l *Login) renderRegisterU2F(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { var errType, errMessage, credentialData string var u2f *user_model.WebAuthNToken @@ -24,9 +30,12 @@ func (l *Login) renderRegisterU2F(w http.ResponseWriter, r *http.Request, authRe if u2f != nil { credentialData = base64.RawURLEncoding.EncodeToString(u2f.CredentialCreationData) } - data := &webAuthNData{ - userData: l.getUserData(r, authReq, "Register WebAuthNToken", errType, errMessage), - CredentialCreationData: credentialData, + data := &u2fInitData{ + webAuthNData: webAuthNData{ + userData: l.getUserData(r, authReq, "Register WebAuthNToken", errType, errMessage), + CredentialCreationData: credentialData, + }, + MFAType: model.MFATypeU2F, } l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAU2FInit], data, nil) } @@ -38,17 +47,14 @@ func (l *Login) handleRegisterU2F(w http.ResponseWriter, r *http.Request) { l.renderError(w, r, authReq, err) return } - if data.Recreate { - l.renderRegisterU2F(w, r, authReq, nil) - return - } credData, err := base64.URLEncoding.DecodeString(data.CredentialData) if err != nil { l.renderRegisterU2F(w, r, authReq, err) return } - if err = l.authRepo.VerifyMFAU2FSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Name, credData); err != nil { + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + if err = l.authRepo.VerifyMFAU2FSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Name, userAgentID, credData); err != nil { l.renderRegisterU2F(w, r, authReq, err) return } diff --git a/internal/ui/login/handler/mfa_init_verify_handler.go b/internal/ui/login/handler/mfa_init_verify_handler.go index bc07c6ede2..92ff320541 100644 --- a/internal/ui/login/handler/mfa_init_verify_handler.go +++ b/internal/ui/login/handler/mfa_init_verify_handler.go @@ -7,6 +7,7 @@ import ( svg "github.com/ajstarks/svgo" "github.com/boombuler/barcode/qr" + http_mw "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/qrcode" ) @@ -47,7 +48,8 @@ func (l *Login) handleMFAInitVerify(w http.ResponseWriter, r *http.Request) { } func (l *Login) handleOTPVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaInitVerifyData) *mfaVerifyData { - err := l.authRepo.VerifyMFAOTPSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Code) + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + err := l.authRepo.VerifyMFAOTPSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Code, userAgentID) if err == nil { return nil } diff --git a/internal/ui/login/handler/mfa_verify_handler.go b/internal/ui/login/handler/mfa_verify_handler.go index d2819afd1c..5e3b5c5a8f 100644 --- a/internal/ui/login/handler/mfa_verify_handler.go +++ b/internal/ui/login/handler/mfa_verify_handler.go @@ -35,6 +35,15 @@ func (l *Login) handleMFAVerify(w http.ResponseWriter, r *http.Request) { } func (l *Login) renderMFAVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, verificationStep *model.MFAVerificationStep, err error) { + if verificationStep == nil { + l.renderError(w, r, authReq, err) + return + } + provider := verificationStep.MFAProviders[len(verificationStep.MFAProviders)-1] + l.renderMFAVerifySelected(w, r, authReq, verificationStep, provider, err) +} + +func (l *Login) renderMFAVerifySelected(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, verificationStep *model.MFAVerificationStep, selectedProvider model.MFAType, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -44,13 +53,23 @@ func (l *Login) renderMFAVerify(w http.ResponseWriter, r *http.Request, authReq l.renderError(w, r, authReq, err) return } - switch verificationStep.MFAProviders[len(verificationStep.MFAProviders)-1] { + switch selectedProvider { case model.MFATypeU2F: - l.renderU2FVerification(w, r, authReq, nil) + l.renderU2FVerification(w, r, authReq, removeSelectedProviderFromList(verificationStep.MFAProviders, model.MFATypeU2F), nil) return case model.MFATypeOTP: - data.MFAProviders = verificationStep.MFAProviders + data.MFAProviders = removeSelectedProviderFromList(verificationStep.MFAProviders, model.MFATypeOTP) data.SelectedMFAProvider = model.MFATypeOTP } l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAVerify], data, nil) } + +func removeSelectedProviderFromList(providers []model.MFAType, selected model.MFAType) []model.MFAType { + for i := len(providers) - 1; i >= 0; i-- { + if providers[i] == selected { + copy(providers[i:], providers[i+1:]) + return providers[:len(providers)-1] + } + } + return providers +} diff --git a/internal/ui/login/handler/mfa_verify_u2f_handler.go b/internal/ui/login/handler/mfa_verify_u2f_handler.go index 51b734ab0a..1ac79846a5 100644 --- a/internal/ui/login/handler/mfa_verify_u2f_handler.go +++ b/internal/ui/login/handler/mfa_verify_u2f_handler.go @@ -13,7 +13,18 @@ const ( tmplU2FVerification = "u2fverification" ) -func (l *Login) renderU2FVerification(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +type mfaU2FData struct { + webAuthNData + MFAProviders []model.MFAType + SelectedProvider model.MFAType +} + +type mfaU2FFormData struct { + webAuthNFormData + SelectedProvider model.MFAType `schema:"provider"` +} + +func (l *Login) renderU2FVerification(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, providers []model.MFAType, err error) { var errType, errMessage, credentialData string var webAuthNLogin *user_model.WebAuthNLogin if err == nil { @@ -26,33 +37,42 @@ func (l *Login) renderU2FVerification(w http.ResponseWriter, r *http.Request, au if webAuthNLogin != nil { credentialData = base64.RawURLEncoding.EncodeToString(webAuthNLogin.CredentialAssertionData) } - data := &webAuthNData{ - userData: l.getUserData(r, authReq, "Login WebAuthNToken", errType, errMessage), - CredentialCreationData: credentialData, + data := &mfaU2FData{ + webAuthNData: webAuthNData{ + userData: l.getUserData(r, authReq, "Login WebAuthNToken", errType, errMessage), + CredentialCreationData: credentialData, + }, + MFAProviders: providers, + SelectedProvider: -1, } l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplU2FVerification], data, nil) } func (l *Login) handleU2FVerification(w http.ResponseWriter, r *http.Request) { - formData := new(webAuthNFormData) + formData := new(mfaU2FFormData) authReq, err := l.getAuthRequestAndParseData(r, formData) if err != nil { l.renderError(w, r, authReq, err) return } - if formData.Recreate { - l.renderU2FVerification(w, r, authReq, nil) + step, ok := authReq.PossibleSteps[0].(*model.MFAVerificationStep) + if !ok { + l.renderError(w, r, authReq, err) + return + } + if formData.CredentialData == "" { + l.renderMFAVerifySelected(w, r, authReq, step, formData.SelectedProvider, nil) return } credData, err := base64.URLEncoding.DecodeString(formData.CredentialData) if err != nil { - l.renderU2FVerification(w, r, authReq, err) + l.renderU2FVerification(w, r, authReq, step.MFAProviders, err) return } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) err = l.authRepo.VerifyMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID, credData, model.BrowserInfoFromRequest(r)) if err != nil { - l.renderU2FVerification(w, r, authReq, err) + l.renderU2FVerification(w, r, authReq, step.MFAProviders, err) return } l.renderNextStep(w, r, authReq) diff --git a/internal/ui/login/handler/passwordless_login_handler.go b/internal/ui/login/handler/passwordless_login_handler.go index 116f0b9cbf..ee0ae68579 100644 --- a/internal/ui/login/handler/passwordless_login_handler.go +++ b/internal/ui/login/handler/passwordless_login_handler.go @@ -40,10 +40,6 @@ func (l *Login) handlePasswordlessVerification(w http.ResponseWriter, r *http.Re l.renderError(w, r, authReq, err) return } - if formData.Recreate { - l.renderPasswordlessVerification(w, r, authReq, nil) - return - } credData, err := base64.URLEncoding.DecodeString(formData.CredentialData) if err != nil { l.renderPasswordlessVerification(w, r, authReq, err) diff --git a/internal/ui/login/handler/webauthn.go b/internal/ui/login/handler/webauthn.go index 83ade70060..fb35ba1c75 100644 --- a/internal/ui/login/handler/webauthn.go +++ b/internal/ui/login/handler/webauthn.go @@ -8,5 +8,4 @@ type webAuthNData struct { type webAuthNFormData struct { CredentialData string `schema:"credentialData"` Name string `schema:"name"` - Recreate bool `schema:"recreate"` } diff --git a/internal/ui/login/static/i18n/de.yaml b/internal/ui/login/static/i18n/de.yaml index 165d5eb443..d0a303da13 100644 --- a/internal/ui/login/static/i18n/de.yaml +++ b/internal/ui/login/static/i18n/de.yaml @@ -63,6 +63,11 @@ InitUserDone: Title: User aktiviert Description: EMail verifiziert und Passwort erfolgreich gesetzt +MFA: + Provider0: OTP (One Time Password) + Provider1: U2F (Universal 2nd Factor) + ChooseOther: oder wähle eine andere Option aus + MFAPrompt: Title: Multifaktor hinzufügen Description: Möchtest du einen Mulitfaktor hinzufügen? @@ -78,7 +83,7 @@ MFAInitVerify: MFAInitDone: Title: Multifaktor Verifizierung erstellt - Description: Multifikator Verifizierung erfolgreich abgeschlossen. Der Multifaktor muss bei jeder Anmeldung eingegeben werden, dies beinhaltet auch den aktuellen Authentifizierungs Prozess. + Description: Multifikator Verifizierung erfolgreich abgeschlossen. Der Multifaktor muss bei jeder Anmeldung eingegeben werden. MFAInitU2F: Title: Multifaktor U2F / WebAuthN hinzufügen diff --git a/internal/ui/login/static/i18n/en.yaml b/internal/ui/login/static/i18n/en.yaml index f1c0d1f555..a66f72f465 100644 --- a/internal/ui/login/static/i18n/en.yaml +++ b/internal/ui/login/static/i18n/en.yaml @@ -63,6 +63,11 @@ InitUserDone: Title: User activated Description: Email verified and Password successfully set +MFA: + Provider0: OTP (One Time Password) + Provider1: U2F (Universal 2nd Factor) + ChooseOther: or choose an other option + MFAPrompt: Title: Multifactor Setup Description: Would you like to setup multifactor authentication? @@ -78,7 +83,7 @@ MFAInitVerify: MFAInitDone: Title: Multifcator Verification done - Description: Multifactor verification successfully done. The multifactor has to be entered on each login, even in the actual authentification process. + Description: Multifactor verification successfully done. The multifactor has to be entered on each login. MFAInitU2F: Title: Multifactor Setup U2F / WebAuthN diff --git a/internal/ui/login/static/resources/scripts/form_submit.js b/internal/ui/login/static/resources/scripts/form_submit.js index 4673db4800..1911ed75a7 100644 --- a/internal/ui/login/static/resources/scripts/form_submit.js +++ b/internal/ui/login/static/resources/scripts/form_submit.js @@ -3,6 +3,7 @@ function disableSubmit(checks, button) { let inputs = form.getElementsByTagName('input'); button.disabled = true; addRequiredEventListener(inputs, checks, form, button); + disableDoubleSubmit(form, button); } function addRequiredEventListener(inputs, checks, form, button) { @@ -20,6 +21,13 @@ function addRequiredEventListener(inputs, checks, form, button) { } } +function disableDoubleSubmit(form, button) { + form.addEventListener('submit', function() { + document.body.classList.add('waiting'); + button.disabled = true; + }) +} + function toggleButton(checks, form, inputs, button) { if (checks !== undefined) { if (checks() === false) { diff --git a/internal/ui/login/static/resources/themes/caos/css/dark.css b/internal/ui/login/static/resources/themes/caos/css/dark.css index d8fd885a54..49c8e83bed 100644 --- a/internal/ui/login/static/resources/themes/caos/css/dark.css +++ b/internal/ui/login/static/resources/themes/caos/css/dark.css @@ -75,11 +75,15 @@ font-family: Lato; font-size: 16px; font-weight: 400; + cursor: default !important; } body { margin: 0 0 100px 0; } +body.waiting * { + cursor: wait !important; +} html { width: 100%; diff --git a/internal/ui/login/static/resources/themes/caos/css/dark.css.map b/internal/ui/login/static/resources/themes/caos/css/dark.css.map index 5890330f09..2e58686f16 100644 --- a/internal/ui/login/static/resources/themes/caos/css/dark.css.map +++ b/internal/ui/login/static/resources/themes/caos/css/dark.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;EF0BT;;;AAGJ;EACI,OCnBQ;EDoBR,aC1BS;ED2BT;EACA,WEhCU;;;AFmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvDW;EDwDX;EACA;;AAEA;EACI,OC3DY;;AD8DhB;EACI;;;AAIR;EACI,kBCvEc;EDwEd,OCtEW;EDuEX;EACA;EACA;EACA;EACA,QExFU;EFyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCpFY;EDqFZ,OCxFU;EDyFV;;AAGJ;EACI,kBC3FO;ED4FP,OC7FI;ED8FJ;;AACA;EACI,kBC9FQ;;ADkGhB;EACI,kBExFW;EFyFX;;AAEA;EACI,kBE5FO;EF6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OEhGa;EFiGb,kBEhGmB;;AFkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBE9HmB;EF+HnB,OC7IQ;ED8IR,QE1JU;EF2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EEzJN;;AACA;EFoJE;IEnJA;IACA;;;AF0JA;EE7JF;;AACA;EF4JE;IE3JA;IACA;;;;AFiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WEvLE;EFwLF;;AAGJ;EACI;EACA;EACA;EACA,OE1KC;;;AFgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC/NA;;ADmOR;EACI,OExNK;EFyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC9PI;ED+PJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBExPW;;AF2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEjRO;EFkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OE7SP;;AFoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EEvUV;;AACA;EFkUM;IEjUJ;IACA;;;AFyUQ;EACI;EACA;EE9Ud;;AACA;EF2UU;IE1UR;IACA;;;AFgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OErVN;;AF0VE;EACI,OE5VL;;AFiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCxYI;;AD2YR;EACI,MC7YU;;;ADkZd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OE5aO;;;AF+aX;EACI;;;AAGJ;EACI","file":"dark.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;EACA;;;AAGJ;EACI;;AAEA;EACI;;;AAIR;EACI;EACA;EACA;EACA,kBCNc;EDOd,OCNQ;EDOR;EACA;EACA;;;AAMJ;EACI,OChBQ;EDiBR,aCvBS;EDwBT;EACA,WE9BS;EF+BT;;;AAGJ;EACI,OCxBQ;EDyBR,aC/BS;EDgCT;EACA,WErCU;;;AFwCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC5DW;ED6DX;EACA;;AAEA;EACI,OChEY;;ADmEhB;EACI;;;AAIR;EACI,kBC5Ec;ED6Ed,OC3EW;ED4EX;EACA;EACA;EACA;EACA,QE7FU;EF8FV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCzFY;ED0FZ,OC7FU;ED8FV;;AAGJ;EACI,kBChGO;EDiGP,OClGI;EDmGJ;;AACA;EACI,kBCnGQ;;ADuGhB;EACI,kBE7FW;EF8FX;;AAEA;EACI,kBEjGO;EFkGP;;AAIR;EACI;EACA;EACA;EACA;EACA,OErGa;EFsGb,kBErGmB;;AFuGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBEnImB;EFoInB,OClJQ;EDmJR,QE/JU;EFgKV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EE9JN;;AACA;EFyJE;IExJA;IACA;;;AF+JA;EElKF;;AACA;EFiKE;IEhKA;IACA;;;;AFsKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WE5LE;EF6LF;;AAGJ;EACI;EACA;EACA;EACA,OE/KC;;;AFqLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCpOA;;ADwOR;EACI,OE7NK;EF8NL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCnQI;EDoQJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBE7PW;;AFgQf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEtRO;EFuRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OElTP;;AFyTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EE5UV;;AACA;EFuUM;IEtUJ;IACA;;;AF8UQ;EACI;EACA;EEnVd;;AACA;EFgVU;IE/UR;IACA;;;AFqVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OE1VN;;AF+VE;EACI,OEjWL;;AFsWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC7YI;;ADgZR;EACI,MClZU;;;ADuZd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OEjbO;;;AFobX;EACI;;;AAGJ;EACI","file":"dark.css"} \ No newline at end of file diff --git a/internal/ui/login/static/resources/themes/caos/css/light.css b/internal/ui/login/static/resources/themes/caos/css/light.css index ed2241a8fe..c4cd152ea1 100644 --- a/internal/ui/login/static/resources/themes/caos/css/light.css +++ b/internal/ui/login/static/resources/themes/caos/css/light.css @@ -75,11 +75,15 @@ font-family: Lato; font-size: 16px; font-weight: 400; + cursor: default !important; } body { margin: 0 0 100px 0; } +body.waiting * { + cursor: wait !important; +} html { width: 100%; diff --git a/internal/ui/login/static/resources/themes/caos/css/light.css.map b/internal/ui/login/static/resources/themes/caos/css/light.css.map index 6eb1e80ecf..6c3f74693e 100644 --- a/internal/ui/login/static/resources/themes/caos/css/light.css.map +++ b/internal/ui/login/static/resources/themes/caos/css/light.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;EF0BT;;;AAGJ;EACI,OCnBQ;EDoBR,aC1BS;ED2BT;EACA,WEhCU;;;AFmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvDW;EDwDX;EACA;;AAEA;EACI,OC3DY;;AD8DhB;EACI;;;AAIR;EACI,kBCvEc;EDwEd,OCtEW;EDuEX;EACA;EACA;EACA;EACA,QExFU;EFyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCpFY;EDqFZ,OCxFU;EDyFV;;AAGJ;EACI,kBC3FO;ED4FP,OC7FI;ED8FJ;;AACA;EACI,kBC9FQ;;ADkGhB;EACI,kBExFW;EFyFX;;AAEA;EACI,kBE5FO;EF6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OEhGa;EFiGb,kBEhGmB;;AFkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBE9HmB;EF+HnB,OC7IQ;ED8IR,QE1JU;EF2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EEzJN;;AACA;EFoJE;IEnJA;IACA;;;AF0JA;EE7JF;;AACA;EF4JE;IE3JA;IACA;;;;AFiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WEvLE;EFwLF;;AAGJ;EACI;EACA;EACA;EACA,OE1KC;;;AFgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC/NA;;ADmOR;EACI,OExNK;EFyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC9PI;ED+PJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBExPW;;AF2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEjRO;EFkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OE7SP;;AFoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EEvUV;;AACA;EFkUM;IEjUJ;IACA;;;AFyUQ;EACI;EACA;EE9Ud;;AACA;EF2UU;IE1UR;IACA;;;AFgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OErVN;;AF0VE;EACI,OE5VL;;AFiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCxYI;;AD2YR;EACI,MC7YU;;;ADkZd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OE5aO;;;AF+aX;EACI;;;AAGJ;EACI;;;AGzdJ;EACI,kBFeQ;EEdR,OFac;;AERd;EACI;;AAGJ;EACI,OFGU;;AEAd;EACI;EACA;EACA;;AAEA;EACI,kBFIa;EEHb;EACA,ODyBgB;;ACtBpB;EACI,kBFVG;EEWH,ODoBgB;ECnBhB;EACA;;AACA;EACI,kBFdI;;AEkBZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,OFhCM;;AEkCN;EACI;EACA,kBDHY;;ACQhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDbY;;ACeZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC8DI;EACI,OD7Bc;EC8Bd,kBD7BoB;;AC+BpB;EACI;;AAKZ;EACI,kBD5CoB;EC6CpB,OF9EU;;AEkFV;EACI,MFnFM;;AEsFV;EACI,MFtFA;;AE0FR;EAEQ;;;AAMR;EACI,OFpGU;;AEwGb;EACI,ODhEM;;ACoEN;EACI,ODtEG;;;AC8EZ;EDrHF;;AACA;ECoHE;IDnHA;IACA;;;ACsHA;EDzHF;;AACA;ECwHE;IDvHA;IACA;;;;AC2HJ;EACI;;;AAGJ;EACI,OD5FY","file":"light.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;EACA;;;AAGJ;EACI;;AAEA;EACI;;;AAIR;EACI;EACA;EACA;EACA,kBCNc;EDOd,OCNQ;EDOR;EACA;EACA;;;AAMJ;EACI,OChBQ;EDiBR,aCvBS;EDwBT;EACA,WE9BS;EF+BT;;;AAGJ;EACI,OCxBQ;EDyBR,aC/BS;EDgCT;EACA,WErCU;;;AFwCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC5DW;ED6DX;EACA;;AAEA;EACI,OChEY;;ADmEhB;EACI;;;AAIR;EACI,kBC5Ec;ED6Ed,OC3EW;ED4EX;EACA;EACA;EACA;EACA,QE7FU;EF8FV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCzFY;ED0FZ,OC7FU;ED8FV;;AAGJ;EACI,kBChGO;EDiGP,OClGI;EDmGJ;;AACA;EACI,kBCnGQ;;ADuGhB;EACI,kBE7FW;EF8FX;;AAEA;EACI,kBEjGO;EFkGP;;AAIR;EACI;EACA;EACA;EACA;EACA,OErGa;EFsGb,kBErGmB;;AFuGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBEnImB;EFoInB,OClJQ;EDmJR,QE/JU;EFgKV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EE9JN;;AACA;EFyJE;IExJA;IACA;;;AF+JA;EElKF;;AACA;EFiKE;IEhKA;IACA;;;;AFsKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WE5LE;EF6LF;;AAGJ;EACI;EACA;EACA;EACA,OE/KC;;;AFqLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCpOA;;ADwOR;EACI,OE7NK;EF8NL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCnQI;EDoQJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBE7PW;;AFgQf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEtRO;EFuRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OElTP;;AFyTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EE5UV;;AACA;EFuUM;IEtUJ;IACA;;;AF8UQ;EACI;EACA;EEnVd;;AACA;EFgVU;IE/UR;IACA;;;AFqVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OE1VN;;AF+VE;EACI,OEjWL;;AFsWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC7YI;;ADgZR;EACI,MClZU;;;ADuZd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OEjbO;;;AFobX;EACI;;;AAGJ;EACI;;;AG9dJ;EACI,kBFeQ;EEdR,OFac;;AERd;EACI;;AAGJ;EACI,OFGU;;AEAd;EACI;EACA;EACA;;AAEA;EACI,kBFIa;EEHb;EACA,ODyBgB;;ACtBpB;EACI,kBFVG;EEWH,ODoBgB;ECnBhB;EACA;;AACA;EACI,kBFdI;;AEkBZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,OFhCM;;AEkCN;EACI;EACA,kBDHY;;ACQhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDbY;;ACeZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC8DI;EACI,OD7Bc;EC8Bd,kBD7BoB;;AC+BpB;EACI;;AAKZ;EACI,kBD5CoB;EC6CpB,OF9EU;;AEkFV;EACI,MFnFM;;AEsFV;EACI,MFtFA;;AE0FR;EAEQ;;;AAMR;EACI,OFpGU;;AEwGb;EACI,ODhEM;;ACoEN;EACI,ODtEG;;;AC8EZ;EDrHF;;AACA;ECoHE;IDnHA;IACA;;;ACsHA;EDzHF;;AACA;ECwHE;IDvHA;IACA;;;;AC2HJ;EACI;;;AAGJ;EACI,OD5FY","file":"light.css"} \ No newline at end of file diff --git a/internal/ui/login/static/resources/themes/scss/main.scss b/internal/ui/login/static/resources/themes/scss/main.scss index 9fb1bf4511..137c9081ce 100644 --- a/internal/ui/login/static/resources/themes/scss/main.scss +++ b/internal/ui/login/static/resources/themes/scss/main.scss @@ -5,10 +5,15 @@ font-family: $standardFont; font-size: 16px; font-weight: 400; + cursor: default !important; } body { margin: 0 0 100px 0; + + &.waiting * { + cursor: wait !important; + } } html { diff --git a/internal/ui/login/static/resources/themes/zitadel/css/dark.css b/internal/ui/login/static/resources/themes/zitadel/css/dark.css index f32c2bc0e0..f289f43bc1 100644 --- a/internal/ui/login/static/resources/themes/zitadel/css/dark.css +++ b/internal/ui/login/static/resources/themes/zitadel/css/dark.css @@ -75,11 +75,15 @@ font-family: Lato; font-size: 16px; font-weight: 400; + cursor: default !important; } body { margin: 0 0 100px 0; } +body.waiting * { + cursor: wait !important; +} html { width: 100%; diff --git a/internal/ui/login/static/resources/themes/zitadel/css/dark.css.map b/internal/ui/login/static/resources/themes/zitadel/css/dark.css.map index 3c47167879..bab7b8d3dc 100644 --- a/internal/ui/login/static/resources/themes/zitadel/css/dark.css.map +++ b/internal/ui/login/static/resources/themes/zitadel/css/dark.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCQc;EDPd,OCQQ;EDPR;EACA;EACA;EAEI;;;AAIR;EACI,OCFQ;EDGR,aC3BS;ED4BT;EACA,WCzBS;ED0BT;;;AAGJ;EACI,OCVQ;EDWR,aCnCS;EDoCT;EACA,WChCU;;;ADmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC9CW;ED+CX;EACA;;AAEA;EACI,OClDY;;ADqDhB;EACI;;;AAIR;EACI,kBC9Dc;ED+Dd,OC7DW;ED8DX;EACA;EACA;EACA;EACA,QCxFU;EDyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBC3EY;ED4EZ,OC/EU;EDgFV;;AAGJ;EACI,kBClFO;EDmFP,OCpFI;EDqFJ;;AACA;EACI,kBCrFQ;;ADyFhB;EACI,kBCxFW;EDyFX;;AAEA;EACI,kBC5FO;ED6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OChGa;EDiGb,kBChGmB;;ADkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBC9HmB;ED+HnB,OCpIQ;EDqIR,QC1JU;ED2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;ECzJN;;AACA;EDoJE;ICnJA;IACA;;;AD0JA;EC7JF;;AACA;ED4JE;IC3JA;IACA;;;;ADiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WCvLE;EDwLF;;AAGJ;EACI;EACA;EACA;EACA,OC1KC;;;ADgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCtNA;;AD0NR;EACI,OCxNK;EDyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCrPI;EDsPJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBCxPW;;AD2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCjRO;EDkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OC7SP;;ADoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;ECvUV;;AACA;EDkUM;ICjUJ;IACA;;;ADyUQ;EACI;EACA;EC9Ud;;AACA;ED2UU;IC1UR;IACA;;;ADgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OCrVN;;AD0VE;EACI,OC5VL;;ADiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC/XI;;ADkYR;EACI,MCpYU;;;ADyYd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OC5aO;;;AD+aX;EACI;;;AAGJ;EACI","file":"dark.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;EACA;;;AAGJ;EACI;;AAEA;EACI;;;AAIR;EACI;EACA;EACA;EACA,kBCGc;EDFd,OCGQ;EDFR;EACA;EACA;EAEI;;;AAIR;EACI,OCPQ;EDQR,aChCS;EDiCT;EACA,WC9BS;ED+BT;;;AAGJ;EACI,OCfQ;EDgBR,aCxCS;EDyCT;EACA,WCrCU;;;ADwCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCnDW;EDoDX;EACA;;AAEA;EACI,OCvDY;;AD0DhB;EACI;;;AAIR;EACI,kBCnEc;EDoEd,OClEW;EDmEX;EACA;EACA;EACA;EACA,QC7FU;ED8FV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBChFY;EDiFZ,OCpFU;EDqFV;;AAGJ;EACI,kBCvFO;EDwFP,OCzFI;ED0FJ;;AACA;EACI,kBC1FQ;;AD8FhB;EACI,kBC7FW;ED8FX;;AAEA;EACI,kBCjGO;EDkGP;;AAIR;EACI;EACA;EACA;EACA;EACA,OCrGa;EDsGb,kBCrGmB;;ADuGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBCnImB;EDoInB,OCzIQ;ED0IR,QC/JU;EDgKV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EC9JN;;AACA;EDyJE;ICxJA;IACA;;;AD+JA;EClKF;;AACA;EDiKE;IChKA;IACA;;;;ADsKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WC5LE;ED6LF;;AAGJ;EACI;EACA;EACA;EACA,OC/KC;;;ADqLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC3NA;;AD+NR;EACI,OC7NK;ED8NL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC1PI;ED2PJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBC7PW;;ADgQf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCtRO;EDuRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OClTP;;ADyTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EC5UV;;AACA;EDuUM;ICtUJ;IACA;;;AD8UQ;EACI;EACA;ECnVd;;AACA;EDgVU;IC/UR;IACA;;;ADqVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OC1VN;;AD+VE;EACI,OCjWL;;ADsWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCpYI;;ADuYR;EACI,MCzYU;;;AD8Yd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OCjbO;;;ADobX;EACI;;;AAGJ;EACI","file":"dark.css"} \ No newline at end of file diff --git a/internal/ui/login/static/resources/themes/zitadel/css/light.css b/internal/ui/login/static/resources/themes/zitadel/css/light.css index 6d23b14ad5..eae167bc73 100644 --- a/internal/ui/login/static/resources/themes/zitadel/css/light.css +++ b/internal/ui/login/static/resources/themes/zitadel/css/light.css @@ -75,11 +75,15 @@ font-family: Lato; font-size: 16px; font-weight: 400; + cursor: default !important; } body { margin: 0 0 100px 0; } +body.waiting * { + cursor: wait !important; +} html { width: 100%; diff --git a/internal/ui/login/static/resources/themes/zitadel/css/light.css.map b/internal/ui/login/static/resources/themes/zitadel/css/light.css.map index 28d354b79b..b7dfa7f778 100644 --- a/internal/ui/login/static/resources/themes/zitadel/css/light.css.map +++ b/internal/ui/login/static/resources/themes/zitadel/css/light.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCQc;EDPd,OCQQ;EDPR;EACA;EACA;EAEI;;;AAIR;EACI,OCFQ;EDGR,aC3BS;ED4BT;EACA,WCzBS;ED0BT;;;AAGJ;EACI,OCVQ;EDWR,aCnCS;EDoCT;EACA,WChCU;;;ADmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC9CW;ED+CX;EACA;;AAEA;EACI,OClDY;;ADqDhB;EACI;;;AAIR;EACI,kBC9Dc;ED+Dd,OC7DW;ED8DX;EACA;EACA;EACA;EACA,QCxFU;EDyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBC3EY;ED4EZ,OC/EU;EDgFV;;AAGJ;EACI,kBClFO;EDmFP,OCpFI;EDqFJ;;AACA;EACI,kBCrFQ;;ADyFhB;EACI,kBCxFW;EDyFX;;AAEA;EACI,kBC5FO;ED6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OChGa;EDiGb,kBChGmB;;ADkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBC9HmB;ED+HnB,OCpIQ;EDqIR,QC1JU;ED2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;ECzJN;;AACA;EDoJE;ICnJA;IACA;;;AD0JA;EC7JF;;AACA;ED4JE;IC3JA;IACA;;;;ADiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WCvLE;EDwLF;;AAGJ;EACI;EACA;EACA;EACA,OC1KC;;;ADgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCtNA;;AD0NR;EACI,OCxNK;EDyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCrPI;EDsPJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBCxPW;;AD2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCjRO;EDkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OC7SP;;ADoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;ECvUV;;AACA;EDkUM;ICjUJ;IACA;;;ADyUQ;EACI;EACA;EC9Ud;;AACA;ED2UU;IC1UR;IACA;;;ADgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OCrVN;;AD0VE;EACI,OC5VL;;ADiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC/XI;;ADkYR;EACI,MCpYU;;;ADyYd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OC5aO;;;AD+aX;EACI;;;AAGJ;EACI;;;AEzdJ;EACI,kBD2CmB;EC1CnB,ODsBc;ECpBV;;AAGJ;EACI;;AAGJ;EACI,ODYU;;ACTd;EACI,kBD4Be;EC3Bf,ODSO;ECRP;;AAEA;EACI,kBD0Ba;ECzBb;EACA,ODyBgB;;ACtBpB;EACI,kBDDG;ECEH,ODoBgB;ECnBhB;EACA;;AACA;EACI,kBDLI;;ACSZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,ODvBM;;ACyBN;EACI;EACA,kBDHY;;ACQhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDbY;;ACeZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC8DI;EACI,OD7Bc;EC8Bd,kBD7BoB;;AC+BpB;EACI;;AAKZ;EACI,kBD5CoB;EC6CpB,ODrEU;;ACyEV;EACI,MD1EM;;AC6EV;EACI,MD1DW;;ACsEnB;EACI,OD3FU;;AC+Fb;EACI,ODhEM;;ACoEN;EACI,ODtEG;;;AC8EZ;EDrHF;;AACA;ECoHE;IDnHA;IACA;;;ACsHA;EDzHF;;AACA;ECwHE;IDvHA;IACA;;;;AC2HJ;EACI;;;AAGJ;EACI,OD5FY","file":"light.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;EACA;;;AAGJ;EACI;;AAEA;EACI;;;AAIR;EACI;EACA;EACA;EACA,kBCGc;EDFd,OCGQ;EDFR;EACA;EACA;EAEI;;;AAIR;EACI,OCPQ;EDQR,aChCS;EDiCT;EACA,WC9BS;ED+BT;;;AAGJ;EACI,OCfQ;EDgBR,aCxCS;EDyCT;EACA,WCrCU;;;ADwCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCnDW;EDoDX;EACA;;AAEA;EACI,OCvDY;;AD0DhB;EACI;;;AAIR;EACI,kBCnEc;EDoEd,OClEW;EDmEX;EACA;EACA;EACA;EACA,QC7FU;ED8FV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBChFY;EDiFZ,OCpFU;EDqFV;;AAGJ;EACI,kBCvFO;EDwFP,OCzFI;ED0FJ;;AACA;EACI,kBC1FQ;;AD8FhB;EACI,kBC7FW;ED8FX;;AAEA;EACI,kBCjGO;EDkGP;;AAIR;EACI;EACA;EACA;EACA;EACA,OCrGa;EDsGb,kBCrGmB;;ADuGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBCnImB;EDoInB,OCzIQ;ED0IR,QC/JU;EDgKV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EC9JN;;AACA;EDyJE;ICxJA;IACA;;;AD+JA;EClKF;;AACA;EDiKE;IChKA;IACA;;;;ADsKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WC5LE;ED6LF;;AAGJ;EACI;EACA;EACA;EACA,OC/KC;;;ADqLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC3NA;;AD+NR;EACI,OC7NK;ED8NL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC1PI;ED2PJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBC7PW;;ADgQf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCtRO;EDuRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OClTP;;ADyTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EC5UV;;AACA;EDuUM;ICtUJ;IACA;;;AD8UQ;EACI;EACA;ECnVd;;AACA;EDgVU;IC/UR;IACA;;;ADqVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OC1VN;;AD+VE;EACI,OCjWL;;ADsWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCpYI;;ADuYR;EACI,MCzYU;;;AD8Yd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OCjbO;;;ADobX;EACI;;;AAGJ;EACI;;;AE9dJ;EACI,kBD2CmB;EC1CnB,ODsBc;ECpBV;;AAGJ;EACI;;AAGJ;EACI,ODYU;;ACTd;EACI,kBD4Be;EC3Bf,ODSO;ECRP;;AAEA;EACI,kBD0Ba;ECzBb;EACA,ODyBgB;;ACtBpB;EACI,kBDDG;ECEH,ODoBgB;ECnBhB;EACA;;AACA;EACI,kBDLI;;ACSZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,ODvBM;;ACyBN;EACI;EACA,kBDHY;;ACQhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDbY;;ACeZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC8DI;EACI,OD7Bc;EC8Bd,kBD7BoB;;AC+BpB;EACI;;AAKZ;EACI,kBD5CoB;EC6CpB,ODrEU;;ACyEV;EACI,MD1EM;;AC6EV;EACI,MD1DW;;ACsEnB;EACI,OD3FU;;AC+Fb;EACI,ODhEM;;ACoEN;EACI,ODtEG;;;AC8EZ;EDrHF;;AACA;ECoHE;IDnHA;IACA;;;ACsHA;EDzHF;;AACA;ECwHE;IDvHA;IACA;;;;AC2HJ;EACI;;;AAGJ;EACI,OD5FY","file":"light.css"} \ No newline at end of file diff --git a/internal/ui/login/static/templates/change_password_done.html b/internal/ui/login/static/templates/change_password_done.html index c450468c5d..1afe440cc3 100644 --- a/internal/ui/login/static/templates/change_password_done.html +++ b/internal/ui/login/static/templates/change_password_done.html @@ -15,9 +15,6 @@
- - {{t "Actions.Cancel"}} -
diff --git a/internal/ui/login/static/templates/mfa_init_u2f.html b/internal/ui/login/static/templates/mfa_init_u2f.html index f9f7de78c6..9739c8c537 100644 --- a/internal/ui/login/static/templates/mfa_init_u2f.html +++ b/internal/ui/login/static/templates/mfa_init_u2f.html @@ -30,7 +30,9 @@ {{ template "error-message" .}}
- + + {{t "Actions.Back"}} +
diff --git a/internal/ui/login/static/templates/mfa_init_verify.html b/internal/ui/login/static/templates/mfa_init_verify.html index f4f8839737..de9ff51e14 100644 --- a/internal/ui/login/static/templates/mfa_init_verify.html +++ b/internal/ui/login/static/templates/mfa_init_verify.html @@ -36,7 +36,7 @@ {{end}}
- + {{t "Actions.Back"}} diff --git a/internal/ui/login/static/templates/mfa_verification_u2f.html b/internal/ui/login/static/templates/mfa_verification_u2f.html index 1e44ef72dc..af9512dbd0 100644 --- a/internal/ui/login/static/templates/mfa_verification_u2f.html +++ b/internal/ui/login/static/templates/mfa_verification_u2f.html @@ -26,7 +26,15 @@ {{ template "error-message" .}}
- + {{ if .MFAProviders }} +
+

{{t "MFA.ChooseOther"}}

+ {{ range $provider := .MFAProviders}} + {{ $providerName := (t (printf "MFA.Provider%v" $provider)) }} + + {{ end }} +
+ {{ end }}
diff --git a/internal/ui/login/static/templates/mfa_verify.html b/internal/ui/login/static/templates/mfa_verify.html index 8addc37495..29a1eeabbf 100644 --- a/internal/ui/login/static/templates/mfa_verify.html +++ b/internal/ui/login/static/templates/mfa_verify.html @@ -23,7 +23,16 @@ {{ template "error-message" .}}
- + + {{ if .MFAProviders }} +
+

{{t "MFA.ChooseOther"}}

+ {{ range $provider := .MFAProviders}} + {{ $providerName := (t (printf "MFA.Provider%v" $provider)) }} + + {{ end }} +
+ {{ end }} {{t "Actions.Cancel"}} diff --git a/internal/ui/login/static/templates/passwordless.html b/internal/ui/login/static/templates/passwordless.html index a773234a8d..120889be6d 100644 --- a/internal/ui/login/static/templates/passwordless.html +++ b/internal/ui/login/static/templates/passwordless.html @@ -26,7 +26,9 @@ {{ template "error-message" .}}
- + + +
diff --git a/internal/user/repository/eventsourcing/eventstore.go b/internal/user/repository/eventsourcing/eventstore.go index 20fb408a4d..371f0c74bc 100644 --- a/internal/user/repository/eventsourcing/eventstore.go +++ b/internal/user/repository/eventsourcing/eventstore.go @@ -578,10 +578,10 @@ func (es *UserEventstore) SetOneTimePassword(ctx context.Context, policy *iam_mo if err != nil { return nil, err } - return es.changedPassword(ctx, user, policy, password.SecretString, true) + return es.changedPassword(ctx, user, policy, password.SecretString, true, "") } -func (es *UserEventstore) SetPassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, code, password string) error { +func (es *UserEventstore) SetPassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, code, password, userAgentID string) error { user, err := es.HumanByID(ctx, userID) if err != nil { return err @@ -592,7 +592,7 @@ func (es *UserEventstore) SetPassword(ctx context.Context, policy *iam_model.Pas if err := crypto.VerifyCode(user.PasswordCode.CreationDate, user.PasswordCode.Expiry, user.PasswordCode.Code, code, es.PasswordVerificationCode); err != nil { return err } - _, err = es.changedPassword(ctx, user, policy, password, false) + _, err = es.changedPassword(ctx, user, policy, password, false, userAgentID) return err } @@ -655,7 +655,7 @@ func (es *UserEventstore) ChangeMachine(ctx context.Context, machine *usr_model. return model.MachineToModel(repoUser.Machine), nil } -func (es *UserEventstore) ChangePassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, old, new string) (_ *usr_model.Password, err error) { +func (es *UserEventstore) ChangePassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, old, new, userAgentID string) (_ *usr_model.Password, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -672,10 +672,10 @@ func (es *UserEventstore) ChangePassword(ctx context.Context, policy *iam_model. if err != nil { return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-s56a3", "Errors.User.Password.Invalid") } - return es.changedPassword(ctx, user, policy, new, false) + return es.changedPassword(ctx, user, policy, new, false, userAgentID) } -func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.User, policy *iam_model.PasswordComplexityPolicyView, password string, onetime bool) (_ *usr_model.Password, err error) { +func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.User, policy *iam_model.PasswordComplexityPolicyView, password string, onetime bool, userAgentID string) (_ *usr_model.Password, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() pw := &usr_model.Password{SecretString: password} @@ -683,7 +683,7 @@ func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.U if err != nil { return nil, err } - repoPassword := model.PasswordFromModel(pw) + repoPassword := model.PasswordChangeFromModel(pw, userAgentID) repoUser := model.UserFromModel(user) agg := PasswordChangeAggregate(es.AggregateCreator(), repoUser, repoPassword) err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg) @@ -1235,7 +1235,7 @@ func (es *UserEventstore) RemoveOTP(ctx context.Context, userID string) error { return nil } -func (es *UserEventstore) CheckMFAOTPSetup(ctx context.Context, userID, code string) error { +func (es *UserEventstore) CheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID string) error { user, err := es.HumanByID(ctx, userID) if err != nil { return err @@ -1250,7 +1250,7 @@ func (es *UserEventstore) CheckMFAOTPSetup(ctx context.Context, userID, code str return err } repoUser := model.UserFromModel(user) - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAOTPVerifyAggregate(es.AggregateCreator(), repoUser)) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAOTPVerifyAggregate(es.AggregateCreator(), repoUser, userAgentID)) if err != nil { return err } @@ -1326,7 +1326,7 @@ func (es *UserEventstore) AddU2F(ctx context.Context, userID string) (*usr_model return webAuthN, nil } -func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error { +func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { user, err := es.HumanByID(ctx, userID) if err != nil { return err @@ -1337,7 +1337,7 @@ func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName return err } repoUser := model.UserFromModel(user) - repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN) + repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID) err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) if err != nil { return err @@ -1432,7 +1432,7 @@ func (es *UserEventstore) AddPasswordless(ctx context.Context, userID string) (* return webAuthN, nil } -func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error { +func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { user, err := es.HumanByID(ctx, userID) if err != nil { return err @@ -1443,7 +1443,7 @@ func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, t return err } repoUser := model.UserFromModel(user) - repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN) + repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID) err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) if err != nil { return err diff --git a/internal/user/repository/eventsourcing/eventstore_test.go b/internal/user/repository/eventsourcing/eventstore_test.go index 6eb7b0cc3d..5d4caf081c 100644 --- a/internal/user/repository/eventsourcing/eventstore_test.go +++ b/internal/user/repository/eventsourcing/eventstore_test.go @@ -1678,7 +1678,7 @@ func TestSetPassword(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.args.es.SetPassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.code, tt.args.password) + err := tt.args.es.SetPassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.code, tt.args.password, "") if tt.res.errFunc == nil && err != nil { t.Errorf("result has error: %v", err) @@ -1838,7 +1838,7 @@ func TestChangePassword(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := tt.args.es.ChangePassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.old, tt.args.new) + result, err := tt.args.es.ChangePassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.old, tt.args.new, "") if tt.res.errFunc == nil && result.AggregateID == "" { t.Errorf("result has no id") @@ -3578,7 +3578,7 @@ func TestCheckMFAOTPSetup(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.args.es.CheckMFAOTPSetup(tt.args.ctx, tt.args.userID, tt.args.code) + err := tt.args.es.CheckMFAOTPSetup(tt.args.ctx, tt.args.userID, tt.args.code, "") if tt.res.errFunc == nil && err != nil { t.Errorf("result should not get err") diff --git a/internal/user/repository/eventsourcing/model/otp.go b/internal/user/repository/eventsourcing/model/otp.go index 4b37964ad9..7d4ccf6a02 100644 --- a/internal/user/repository/eventsourcing/model/otp.go +++ b/internal/user/repository/eventsourcing/model/otp.go @@ -3,6 +3,7 @@ package model import ( "encoding/json" "github.com/caos/logging" + "github.com/caos/zitadel/internal/crypto" caos_errs "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/models" @@ -16,6 +17,10 @@ type OTP struct { State int32 `json:"-"` } +type OTPVerified struct { + UserAgentID string `json:"userAgentID,omitempty"` +} + func OTPFromModel(otp *model.OTP) *OTP { return &OTP{ ObjectRoot: otp.ObjectRoot, @@ -55,3 +60,11 @@ func (o *OTP) setData(event *es_models.Event) error { } return nil } + +func (o *OTPVerified) SetData(event *es_models.Event) error { + if err := json.Unmarshal(event.Data, o); err != nil { + logging.Log("EVEN-BF421").WithError(err).Error("could not unmarshal event data") + return caos_errs.ThrowInternal(err, "MODEL-GB6hj", "could not unmarshal event") + } + return nil +} diff --git a/internal/user/repository/eventsourcing/model/password.go b/internal/user/repository/eventsourcing/model/password.go index 3eadce508d..033cf8154b 100644 --- a/internal/user/repository/eventsourcing/model/password.go +++ b/internal/user/repository/eventsourcing/model/password.go @@ -26,6 +26,11 @@ type PasswordCode struct { NotificationType int32 `json:"notificationType,omitempty"` } +type PasswordChange struct { + Password + UserAgentID string `json:"userAgentID,omitempty"` +} + func PasswordFromModel(password *model.Password) *Password { return &Password{ ObjectRoot: password.ObjectRoot, @@ -51,6 +56,17 @@ func PasswordCodeToModel(code *PasswordCode) *model.PasswordCode { } } +func PasswordChangeFromModel(password *model.Password, userAgentID string) *PasswordChange { + return &PasswordChange{ + Password: Password{ + ObjectRoot: password.ObjectRoot, + Secret: password.SecretCrypto, + ChangeRequired: password.ChangeRequired, + }, + UserAgentID: userAgentID, + } +} + func (u *Human) appendUserPasswordChangedEvent(event *es_models.Event) error { u.Password = new(Password) err := u.Password.setData(event) @@ -84,3 +100,12 @@ func (c *PasswordCode) SetData(event *es_models.Event) error { } return nil } + +func (pw *PasswordChange) SetData(event *es_models.Event) error { + if err := json.Unmarshal(event.Data, pw); err != nil { + logging.Log("EVEN-ADs31").WithError(err).Error("could not unmarshal event data") + return caos_errs.ThrowInternal(err, "MODEL-BDd32", "could not unmarshal event") + } + pw.ObjectRoot.AppendEvent(event) + return nil +} diff --git a/internal/user/repository/eventsourcing/model/web_auth_n.go b/internal/user/repository/eventsourcing/model/web_auth_n.go index f068a92ab0..afbb602192 100644 --- a/internal/user/repository/eventsourcing/model/web_auth_n.go +++ b/internal/user/repository/eventsourcing/model/web_auth_n.go @@ -32,6 +32,7 @@ type WebAuthNVerify struct { AAGUID []byte `json:"aaguid"` SignCount uint32 `json:"signCount"` WebAuthNTokenName string `json:"webAuthNTokenName"` + UserAgentID string `json:"userAgentID,omitempty"` } type WebAuthNSignCount struct { @@ -104,7 +105,7 @@ func WebAuthNToModel(webAuthN *WebAuthNToken) *model.WebAuthNToken { } } -func WebAuthNVerifyFromModel(webAuthN *model.WebAuthNToken) *WebAuthNVerify { +func WebAuthNVerifyFromModel(webAuthN *model.WebAuthNToken, userAgentID string) *WebAuthNVerify { return &WebAuthNVerify{ WebAuthNTokenID: webAuthN.WebAuthNTokenID, KeyID: webAuthN.KeyID, @@ -113,6 +114,7 @@ func WebAuthNVerifyFromModel(webAuthN *model.WebAuthNToken) *WebAuthNVerify { SignCount: webAuthN.SignCount, AttestationType: webAuthN.AttestationType, WebAuthNTokenName: webAuthN.WebAuthNTokenName, + UserAgentID: userAgentID, } } @@ -148,6 +150,14 @@ func WebAuthNLoginToModel(webAuthN *WebAuthNLogin) *model.WebAuthNLogin { } } +func (w *WebAuthNVerify) SetData(event *es_models.Event) error { + if err := json.Unmarshal(event.Data, w); err != nil { + logging.Log("EVEN-G342rf").WithError(err).Error("could not unmarshal event data") + return caos_errs.ThrowInternal(err, "MODEL-B6641", "could not unmarshal event") + } + return nil +} + func (u *Human) appendU2FAddedEvent(event *es_models.Event) error { webauthn := new(WebAuthNToken) err := webauthn.setData(event) diff --git a/internal/user/repository/eventsourcing/user.go b/internal/user/repository/eventsourcing/user.go index b4ed0bdf78..67369de704 100644 --- a/internal/user/repository/eventsourcing/user.go +++ b/internal/user/repository/eventsourcing/user.go @@ -410,16 +410,16 @@ func SkipMFAAggregate(aggCreator *es_models.AggregateCreator, user *model.User) } } -func PasswordChangeAggregate(aggCreator *es_models.AggregateCreator, user *model.User, password *model.Password) func(ctx context.Context) (*es_models.Aggregate, error) { +func PasswordChangeAggregate(aggCreator *es_models.AggregateCreator, user *model.User, passwordChange *model.PasswordChange) func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) { - if password == nil { + if passwordChange == nil { return nil, errors.ThrowPreconditionFailed(nil, "EVENT-d9832", "Errors.Internal") } agg, err := UserAggregate(ctx, aggCreator, user) if err != nil { return nil, err } - return agg.AppendEvent(model.HumanPasswordChanged, password) + return agg.AppendEvent(model.HumanPasswordChanged, passwordChange) } } @@ -737,13 +737,13 @@ func MFAOTPAddAggregate(aggCreator *es_models.AggregateCreator, user *model.User } } -func MFAOTPVerifyAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) { +func MFAOTPVerifyAggregate(aggCreator *es_models.AggregateCreator, user *model.User, userAgentID string) func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) { agg, err := UserAggregate(ctx, aggCreator, user) if err != nil { return nil, err } - return agg.AppendEvent(model.HumanMFAOTPVerified, nil) + return agg.AppendEvent(model.HumanMFAOTPVerified, &model.OTPVerified{UserAgentID: userAgentID}) } } diff --git a/internal/user/repository/eventsourcing/user_test.go b/internal/user/repository/eventsourcing/user_test.go index 328828598e..e4ba6c34b7 100644 --- a/internal/user/repository/eventsourcing/user_test.go +++ b/internal/user/repository/eventsourcing/user_test.go @@ -1060,10 +1060,10 @@ func TestSkipMFAAggregate(t *testing.T) { func TestChangePasswordAggregate(t *testing.T) { type args struct { - ctx context.Context - user *model.User - password *model.Password - aggCreator *models.AggregateCreator + ctx context.Context + user *model.User + passwordChange *model.PasswordChange + aggCreator *models.AggregateCreator } type res struct { eventLen int @@ -1086,8 +1086,8 @@ func TestChangePasswordAggregate(t *testing.T) { Profile: &model.Profile{DisplayName: "DisplayName"}, }, }, - password: &model.Password{ChangeRequired: true}, - aggCreator: models.NewAggregateCreator("Test"), + passwordChange: &model.PasswordChange{Password: model.Password{ChangeRequired: true}}, + aggCreator: models.NewAggregateCreator("Test"), }, res: res{ eventLen: 1, @@ -1113,7 +1113,7 @@ func TestChangePasswordAggregate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - agg, err := PasswordChangeAggregate(tt.args.aggCreator, tt.args.user, tt.args.password)(tt.args.ctx) + agg, err := PasswordChangeAggregate(tt.args.aggCreator, tt.args.user, tt.args.passwordChange)(tt.args.ctx) if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen { t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) @@ -2329,7 +2329,7 @@ func TestOTPVerifyAggregate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - agg, err := MFAOTPVerifyAggregate(tt.args.aggCreator, tt.args.user)(tt.args.ctx) + agg, err := MFAOTPVerifyAggregate(tt.args.aggCreator, tt.args.user, "")(tt.args.ctx) if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen { t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) diff --git a/internal/user/repository/view/model/user_session.go b/internal/user/repository/view/model/user_session.go index f213c3505c..514c829cf6 100644 --- a/internal/user/repository/view/model/user_session.go +++ b/internal/user/repository/view/model/user_session.go @@ -81,7 +81,7 @@ func UserSessionsToModel(userSessions []*UserSessionView) []*model.UserSessionVi return result } -func (v *UserSessionView) AppendEvent(event *models.Event) { +func (v *UserSessionView) AppendEvent(event *models.Event) error { v.Sequence = event.Sequence v.ChangeDate = event.CreationDate switch event.Type { @@ -91,7 +91,10 @@ func (v *UserSessionView) AppendEvent(event *models.Event) { v.State = int32(req_model.UserSessionStateActive) case es_model.HumanExternalLoginCheckSucceeded: data := new(es_model.AuthRequest) - data.SetData(event) + err := data.SetData(event) + if err != nil { + return err + } v.ExternalLoginVerification = event.CreationDate v.SelectedIDPConfigID = data.SelectedIDPConfigID v.State = int32(req_model.UserSessionStateActive) @@ -105,15 +108,31 @@ func (v *UserSessionView) AppendEvent(event *models.Event) { v.PasswordlessVerification = time.Time{} v.MultiFactorVerification = time.Time{} case es_model.UserPasswordCheckFailed, - es_model.UserPasswordChanged, - es_model.HumanPasswordCheckFailed, - es_model.HumanPasswordChanged: + es_model.HumanPasswordCheckFailed: v.PasswordVerification = time.Time{} + case es_model.UserPasswordChanged, + es_model.HumanPasswordChanged: + data := new(es_model.PasswordChange) + err := data.SetData(event) + if err != nil { + return err + } + if v.UserAgentID != data.UserAgentID { + v.PasswordVerification = time.Time{} + } + case es_model.MFAOTPVerified, + es_model.HumanMFAOTPVerified: + data := new(es_model.OTPVerified) + err := data.SetData(event) + if err != nil { + return err + } + if v.UserAgentID == data.UserAgentID { + v.setSecondFactorVerification(event.CreationDate, req_model.MFATypeOTP) + } case es_model.MFAOTPCheckSucceeded, es_model.HumanMFAOTPCheckSucceeded: - v.SecondFactorVerification = event.CreationDate - v.SecondFactorVerificationType = int32(req_model.MFATypeOTP) - v.State = int32(req_model.UserSessionStateActive) + v.setSecondFactorVerification(event.CreationDate, req_model.MFATypeOTP) case es_model.MFAOTPCheckFailed, es_model.MFAOTPRemoved, es_model.HumanMFAOTPCheckFailed, @@ -121,10 +140,17 @@ func (v *UserSessionView) AppendEvent(event *models.Event) { es_model.HumanMFAU2FTokenCheckFailed, es_model.HumanMFAU2FTokenRemoved: v.SecondFactorVerification = time.Time{} + case es_model.HumanMFAU2FTokenVerified: + data := new(es_model.WebAuthNVerify) + err := data.SetData(event) + if err != nil { + return err + } + if v.UserAgentID == data.UserAgentID { + v.setSecondFactorVerification(event.CreationDate, req_model.MFATypeU2F) + } case es_model.HumanMFAU2FTokenCheckSucceeded: - v.SecondFactorVerification = event.CreationDate - v.SecondFactorVerificationType = int32(req_model.MFATypeU2F) - v.State = int32(req_model.UserSessionStateActive) + v.setSecondFactorVerification(event.CreationDate, req_model.MFATypeU2F) case es_model.SignedOut, es_model.HumanSignedOut, es_model.UserLocked, @@ -137,4 +163,11 @@ func (v *UserSessionView) AppendEvent(event *models.Event) { v.ExternalLoginVerification = time.Time{} v.SelectedIDPConfigID = "" } + return nil +} + +func (v *UserSessionView) setSecondFactorVerification(verificationTime time.Time, mfaType req_model.MFAType) { + v.SecondFactorVerification = verificationTime + v.SecondFactorVerificationType = int32(mfaType) + v.State = int32(req_model.UserSessionStateActive) } diff --git a/internal/user/repository/view/model/user_session_test.go b/internal/user/repository/view/model/user_session_test.go index 917666ddcd..c5abd6a958 100644 --- a/internal/user/repository/view/model/user_session_test.go +++ b/internal/user/repository/view/model/user_session_test.go @@ -1,11 +1,13 @@ package model import ( + "encoding/json" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/caos/zitadel/internal/crypto" es_models "github.com/caos/zitadel/internal/eventstore/models" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" ) @@ -59,18 +61,87 @@ func TestAppendEvent(t *testing.T) { { name: "append user password changed event", args: args{ - event: &es_models.Event{CreationDate: now(), Type: es_model.UserPasswordChanged}, - userView: &UserSessionView{PasswordVerification: now()}, + event: &es_models.Event{ + CreationDate: now(), + Type: es_model.UserPasswordChanged, + Data: func() []byte { + d, _ := json.Marshal(&es_model.Password{ + Secret: &crypto.CryptoValue{Crypted: []byte("test")}, + }) + return d + }(), + }, + userView: &UserSessionView{UserAgentID: "id", PasswordVerification: now()}, }, - result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}}, + result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: time.Time{}}, }, { name: "append human password changed event", args: args{ - event: &es_models.Event{CreationDate: now(), Type: es_model.HumanPasswordChanged}, - userView: &UserSessionView{PasswordVerification: now()}, + event: &es_models.Event{ + CreationDate: now(), + Type: es_model.HumanPasswordChanged, + Data: func() []byte { + d, _ := json.Marshal(&es_model.PasswordChange{ + Password: es_model.Password{ + Secret: &crypto.CryptoValue{Crypted: []byte("test")}, + }, + }) + return d + }(), + }, + userView: &UserSessionView{UserAgentID: "id", PasswordVerification: now()}, }, - result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}}, + result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: time.Time{}}, + }, + { + name: "append human password changed event same user agent", + args: args{ + event: &es_models.Event{ + CreationDate: now(), + Type: es_model.HumanPasswordChanged, + Data: func() []byte { + d, _ := json.Marshal(&es_model.PasswordChange{ + Password: es_model.Password{ + Secret: &crypto.CryptoValue{Crypted: []byte("test")}, + }, + UserAgentID: "id", + }) + return d + }(), + }, + userView: &UserSessionView{UserAgentID: "id", PasswordVerification: now()}, + }, + result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: now()}, + }, + { + name: "append user otp verified event", + args: args{ + event: &es_models.Event{ + CreationDate: now(), + Type: es_model.MFAOTPVerified, + Data: nil, + }, + userView: &UserSessionView{UserAgentID: "id"}, + }, + result: &UserSessionView{UserAgentID: "id", ChangeDate: now()}, + }, + { + name: "append user otp verified event same user agent", + args: args{ + event: &es_models.Event{ + CreationDate: now(), + Type: es_model.MFAOTPVerified, + Data: func() []byte { + d, _ := json.Marshal(&es_model.OTPVerified{ + UserAgentID: "id", + }) + return d + }(), + }, + userView: &UserSessionView{UserAgentID: "id"}, + }, + result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), SecondFactorVerification: now()}, }, { name: "append user otp check succeeded event", diff --git a/internal/webauthn/webauthn.go b/internal/webauthn/webauthn.go index 62af99ff38..4a38ab1b58 100644 --- a/internal/webauthn/webauthn.go +++ b/internal/webauthn/webauthn.go @@ -39,7 +39,7 @@ func (u *webUser) WebAuthnID() []byte { } func (u *webUser) WebAuthnName() string { - return u.UserName + return u.PreferredLoginName } func (u *webUser) WebAuthnDisplayName() string { diff --git a/pkg/grpc/management/proto/management.proto b/pkg/grpc/management/proto/management.proto index 94d5564652..cd02b8f79e 100644 --- a/pkg/grpc/management/proto/management.proto +++ b/pkg/grpc/management/proto/management.proto @@ -2029,6 +2029,7 @@ message UserMultiFactors { message UserMultiFactor { MfaType type = 1; MFAState state = 2; + string attribute = 3; } enum MfaType {