From 8529a3da7ba6aa3c8e860d4ddfe29747cd00b415 Mon Sep 17 00:00:00 2001 From: YinYin Chiu Date: Thu, 19 Sep 2024 16:03:51 +0800 Subject: [PATCH 01/31] Implement update secondary password --- .../authflowv2/settings_change_password.go | 4 +- .../service_authenticator.go | 155 ++++++++++++------ 2 files changed, 104 insertions(+), 55 deletions(-) diff --git a/pkg/auth/handler/webapp/authflowv2/settings_change_password.go b/pkg/auth/handler/webapp/authflowv2/settings_change_password.go index d90c1577ba..6105925c19 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_change_password.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_change_password.go @@ -103,14 +103,14 @@ func (h *AuthflowV2SettingsChangePasswordHandler) ServeHTTP(w http.ResponseWrite redirectURI = webappSession.RedirectURI } - input := &accountmanagement.ChangePasswordInput{ + input := &accountmanagement.ChangePrimaryPasswordInput{ OAuthSessionID: oAuthSessionID, RedirectURI: redirectURI, OldPassword: oldPassword, NewPassword: newPassword, } - changePasswordOutput, err := h.AccountManagementService.ChangePassword(s, input) + changePasswordOutput, err := h.AccountManagementService.ChangePrimaryPassword(s, input) if err != nil { return err } diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index b1c4cf7fc8..33bb9312ed 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -10,77 +10,47 @@ import ( "github.com/authgear/authgear-server/pkg/util/uuid" ) -type ChangePasswordInput struct { +type ChangePrimaryPasswordInput struct { OAuthSessionID string RedirectURI string OldPassword string NewPassword string } -type ChangePasswordOutput struct { +type ChangePrimaryPasswordOutput struct { RedirectURI string } // If have OAuthSessionID, it means the user is changing password after login with SDK. // Then do special handling such as authenticationInfo -func (s *Service) ChangePassword(resolvedSession session.ResolvedSession, input *ChangePasswordInput) (*ChangePasswordOutput, error) { - userID := resolvedSession.GetAuthenticationInfo().UserID +func (s *Service) ChangePrimaryPassword(resolvedSession session.ResolvedSession, input *ChangePrimaryPasswordInput) (*ChangePrimaryPasswordOutput, error) { redirectURI := input.RedirectURI - err := s.Database.WithTx(func() error { - ais, err := s.Authenticators.List( - userID, - authenticator.KeepType(model.AuthenticatorTypePassword), - authenticator.KeepKind(authenticator.KindPrimary), - ) - if err != nil { - return err - } - if len(ais) == 0 { - return api.ErrNoPassword - } - oldInfo := ais[0] - _, err = s.Authenticators.VerifyWithSpec(oldInfo, &authenticator.Spec{ - Password: &authenticator.PasswordSpec{ - PlainPassword: input.OldPassword, - }, - }, nil) - if err != nil { - err = api.ErrInvalidCredentials - return err - } - changed, newInfo, err := s.Authenticators.UpdatePassword(oldInfo, &authenticatorservice.UpdatePasswordOptions{ - SetPassword: true, - PlainPassword: input.NewPassword, - SetExpireAfter: true, - }) - if err != nil { - return err - } - if changed { - err = s.Authenticators.Update(newInfo) - if err != nil { - return err - } - } - // If is changing password with SDK. - if input.OAuthSessionID != "" { - authInfo := resolvedSession.GetAuthenticationInfo() - authenticationInfoEntry := authenticationinfo.NewEntry(authInfo, input.OAuthSessionID, "") - - err = s.AuthenticationInfoService.Save(authenticationInfoEntry) - if err != nil { - return err - } - redirectURI = s.UIInfoResolver.SetAuthenticationInfoInQuery(input.RedirectURI, authenticationInfoEntry) - } - return nil + _, err := s.changePassword(resolvedSession, &changePasswordInput{ + Kind: authenticator.KindPrimary, + OldPassword: input.OldPassword, + NewPassword: input.NewPassword, }) + if err != nil { return nil, err } - return &ChangePasswordOutput{RedirectURI: redirectURI}, nil + // If is changing password with SDK. + if input.OAuthSessionID != "" { + authInfo := resolvedSession.GetAuthenticationInfo() + authenticationInfoEntry := authenticationinfo.NewEntry(authInfo, input.OAuthSessionID, "") + + err = s.AuthenticationInfoService.Save(authenticationInfoEntry) + if err != nil { + return nil, err + } + redirectURI = s.UIInfoResolver.SetAuthenticationInfoInQuery(input.RedirectURI, authenticationInfoEntry) + } + + return &ChangePrimaryPasswordOutput{ + RedirectURI: redirectURI, + }, nil } type CreateAdditionalPasswordInput struct { @@ -124,3 +94,82 @@ func (s *Service) createAuthenticator(authenticatorInfo *authenticator.Info) err } return nil } + +type ChangeSecondaryPasswordInput struct { + OldPassword string + NewPassword string +} + +type ChangeSecondaryPasswordOutput struct { +} + +func (s *Service) ChangeSecondaryPassword(resolvedSession session.ResolvedSession, input *ChangeSecondaryPasswordInput) (*ChangeSecondaryPasswordOutput, error) { + _, err := s.changePassword(resolvedSession, &changePasswordInput{ + Kind: authenticator.KindSecondary, + OldPassword: input.OldPassword, + NewPassword: input.NewPassword, + }) + + if err != nil { + return nil, err + } + + return &ChangeSecondaryPasswordOutput{}, nil +} + +type changePasswordInput struct { + Kind authenticator.Kind + OldPassword string + NewPassword string +} + +type changePasswordOutput struct { +} + +func (s *Service) changePassword(resolvedSession session.ResolvedSession, input *changePasswordInput) (*changePasswordOutput, error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + + err := s.Database.WithTx(func() error { + ais, err := s.Authenticators.List( + userID, + authenticator.KeepType(model.AuthenticatorTypePassword), + authenticator.KeepKind(input.Kind), + ) + if err != nil { + return err + } + if len(ais) == 0 { + return api.ErrNoPassword + } + oldInfo := ais[0] + _, err = s.Authenticators.VerifyWithSpec(oldInfo, &authenticator.Spec{ + Password: &authenticator.PasswordSpec{ + PlainPassword: input.OldPassword, + }, + }, nil) + if err != nil { + err = api.ErrInvalidCredentials + return err + } + changed, newInfo, err := s.Authenticators.UpdatePassword(oldInfo, &authenticatorservice.UpdatePasswordOptions{ + SetPassword: true, + PlainPassword: input.NewPassword, + SetExpireAfter: true, + }) + if err != nil { + return err + } + if changed { + err = s.Authenticators.Update(newInfo) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return nil, err + } + return &changePasswordOutput{}, nil + +} From 92c418e2579abe2c3a9fc2c3f110223be2542f5e Mon Sep 17 00:00:00 2001 From: YinYin Chiu Date: Thu, 19 Sep 2024 16:06:11 +0800 Subject: [PATCH 02/31] Align terminology Change additional password to secondary password --- .../settings_mfa_create_password.go | 2 +- .../service_authenticator.go | 51 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_password.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_password.go index 404605eabf..fbb7c08edd 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_password.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_password.go @@ -96,7 +96,7 @@ func (h *AuthflowV2SettingsMFACreatePasswordHandler) ServeHTTP(w http.ResponseWr } s := session.GetSession(r.Context()) - err = h.AccountManagementService.CreateAdditionalPassword(s, accountmanagement.CreateAdditionalPasswordInput{ + _, err = h.AccountManagementService.CreateSecondaryPassword(s, accountmanagement.CreateSecondaryPasswordInput{ PlainPassword: newPassword, }) if err != nil { diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 33bb9312ed..60a3213619 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -53,11 +53,14 @@ func (s *Service) ChangePrimaryPassword(resolvedSession session.ResolvedSession, }, nil } -type CreateAdditionalPasswordInput struct { +type CreateSecondaryPasswordInput struct { PlainPassword string } -func (s *Service) CreateAdditionalPassword(resolvedSession session.ResolvedSession, input CreateAdditionalPasswordInput) error { +type CreateSecondaryPasswordOutput struct { +} + +func (s *Service) CreateSecondaryPassword(resolvedSession session.ResolvedSession, input CreateSecondaryPasswordInput) (*CreateSecondaryPasswordOutput, error) { userID := resolvedSession.GetAuthenticationInfo().UserID spec := &authenticator.Spec{ UserID: userID, @@ -70,29 +73,13 @@ func (s *Service) CreateAdditionalPassword(resolvedSession session.ResolvedSessi } info, err := s.Authenticators.NewWithAuthenticatorID(uuid.New(), spec) if err != nil { - return err + return nil, err } - return s.createAuthenticator(info) -} - -func (s *Service) createAuthenticator(authenticatorInfo *authenticator.Info) error { - err := s.Database.WithTx(func() error { - err := s.Authenticators.Create(authenticatorInfo, false) - if err != nil { - return err - } - if authenticatorInfo.Kind == authenticator.KindSecondary { - err = s.Users.UpdateMFAEnrollment(authenticatorInfo.UserID, nil) - if err != nil { - return err - } - } - return nil - }) + err = s.createAuthenticator(info) if err != nil { - return err + return nil, err } - return nil + return &CreateSecondaryPasswordOutput{}, nil } type ChangeSecondaryPasswordInput struct { @@ -173,3 +160,23 @@ func (s *Service) changePassword(resolvedSession session.ResolvedSession, input return &changePasswordOutput{}, nil } + +func (s *Service) createAuthenticator(authenticatorInfo *authenticator.Info) error { + err := s.Database.WithTx(func() error { + err := s.Authenticators.Create(authenticatorInfo, false) + if err != nil { + return err + } + if authenticatorInfo.Kind == authenticator.KindSecondary { + err = s.Users.UpdateMFAEnrollment(authenticatorInfo.UserID, nil) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return err + } + return nil +} From 807884139a171ce4a310dcf341640694f5a222c9 Mon Sep 17 00:00:00 2001 From: YinYin Chiu Date: Thu, 19 Sep 2024 16:20:13 +0800 Subject: [PATCH 03/31] Add totp secret to token --- pkg/lib/accountmanagement/redis_store.go | 31 ++++++++++++++----- pkg/lib/accountmanagement/service_identity.go | 20 ++++++------ pkg/lib/accountmanagement/token.go | 8 +++++ 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/pkg/lib/accountmanagement/redis_store.go b/pkg/lib/accountmanagement/redis_store.go index 5093f42d65..72151efeb4 100644 --- a/pkg/lib/accountmanagement/redis_store.go +++ b/pkg/lib/accountmanagement/redis_store.go @@ -31,13 +31,15 @@ type GenerateTokenOptions struct { RedirectURI string // Phone - PhoneNumber string - + IdentityPhoneNumber string // Email - Email string - + IdentityEmail string // IdentityID for updating identity IdentityID string + + // AuthenticatorID for updating identity + AuthenticatorID string + AuthenticatorTOTPSecret string } func (s *RedisStore) GenerateToken(options GenerateTokenOptions) (string, error) { @@ -48,10 +50,21 @@ func (s *RedisStore) GenerateToken(options GenerateTokenOptions) (string, error) ttl := duration.UserInteraction expireAt := now.Add(ttl) - tokenIdentity := &TokenIdentity{ - IdentityID: options.IdentityID, - PhoneNumber: options.PhoneNumber, - Email: options.Email, + var tokenIdentity *TokenIdentity + if options.IdentityID != "" || options.IdentityPhoneNumber != "" || options.IdentityEmail != "" { + tokenIdentity = &TokenIdentity{ + IdentityID: options.IdentityID, + PhoneNumber: options.IdentityPhoneNumber, + Email: options.IdentityEmail, + } + } + + var tokenAuthenticator *TokenAuthenticator + if options.AuthenticatorID != "" || options.AuthenticatorTOTPSecret != "" { + tokenAuthenticator = &TokenAuthenticator{ + AuthenticatorID: options.AuthenticatorID, + TOTPSecret: options.AuthenticatorTOTPSecret, + } } token := &Token{ @@ -68,6 +81,8 @@ func (s *RedisStore) GenerateToken(options GenerateTokenOptions) (string, error) // Identity Identity: tokenIdentity, + + Authenticator: tokenAuthenticator, } tokenBytes, err := json.Marshal(token) diff --git a/pkg/lib/accountmanagement/service_identity.go b/pkg/lib/accountmanagement/service_identity.go index 766265b50f..6266800e50 100644 --- a/pkg/lib/accountmanagement/service_identity.go +++ b/pkg/lib/accountmanagement/service_identity.go @@ -189,8 +189,8 @@ func (s *Service) StartAddIdentityEmail(resolvedSession session.ResolvedSession, return err } token, err = s.Store.GenerateToken(GenerateTokenOptions{ - UserID: userID, - Email: info.LoginID.LoginID, + UserID: userID, + IdentityEmail: info.LoginID.LoginID, }) if err != nil { return err @@ -342,9 +342,9 @@ func (s *Service) StartUpdateIdentityEmail(resolvedSession session.ResolvedSessi return err } token, err = s.Store.GenerateToken(GenerateTokenOptions{ - UserID: userID, - Email: newInfo.LoginID.LoginID, - IdentityID: newInfo.ID, + UserID: userID, + IdentityEmail: newInfo.LoginID.LoginID, + IdentityID: newInfo.ID, }) if err != nil { return err @@ -577,8 +577,8 @@ func (s *Service) StartAddIdentityPhone(resolvedSession session.ResolvedSession, return err } token, err = s.Store.GenerateToken(GenerateTokenOptions{ - UserID: userID, - PhoneNumber: info.LoginID.LoginID, + UserID: userID, + IdentityPhoneNumber: info.LoginID.LoginID, }) if err != nil { return err @@ -728,9 +728,9 @@ func (s *Service) StartUpdateIdentityPhone(resolvedSession session.ResolvedSessi return err } token, err = s.Store.GenerateToken(GenerateTokenOptions{ - UserID: userID, - PhoneNumber: info.LoginID.LoginID, - IdentityID: info.ID, + UserID: userID, + IdentityPhoneNumber: info.LoginID.LoginID, + IdentityID: info.ID, }) if err != nil { return err diff --git a/pkg/lib/accountmanagement/token.go b/pkg/lib/accountmanagement/token.go index 2d8e50bac1..775f97169f 100644 --- a/pkg/lib/accountmanagement/token.go +++ b/pkg/lib/accountmanagement/token.go @@ -22,6 +22,9 @@ type Token struct { // Adding Identity Identity *TokenIdentity `json:"token_identity,omitempty"` + + // Authenticator + Authenticator *TokenAuthenticator `json:"token_authenticator,omitempty"` } type TokenIdentity struct { @@ -30,6 +33,11 @@ type TokenIdentity struct { Email string `json:"email,omitempty"` } +type TokenAuthenticator struct { + AuthenticatorID string `json:"authenticator_id,omitempty"` + TOTPSecret string `json:"totp_secret,omitempty"` +} + func (t *Token) CheckUser(userID string) error { if subtle.ConstantTimeCompare([]byte(t.UserID), []byte(userID)) == 1 { return nil From 22e844cd00bacb532e9e5e6542c2f2c480487ca1 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 30 Sep 2024 13:05:05 +0800 Subject: [PATCH 04/31] Add start create totp in account management --- pkg/lib/accountmanagement/redis_store.go | 22 ++++++--- pkg/lib/accountmanagement/service.go | 10 ++++ .../service_authenticator.go | 49 ++++++++++++++++++- pkg/lib/accountmanagement/token.go | 9 +++- pkg/lib/deps/deps_common.go | 1 + 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/pkg/lib/accountmanagement/redis_store.go b/pkg/lib/accountmanagement/redis_store.go index 72151efeb4..c3647bcafb 100644 --- a/pkg/lib/accountmanagement/redis_store.go +++ b/pkg/lib/accountmanagement/redis_store.go @@ -37,9 +37,14 @@ type GenerateTokenOptions struct { // IdentityID for updating identity IdentityID string - // AuthenticatorID for updating identity - AuthenticatorID string - AuthenticatorTOTPSecret string + // AuthenticatorID for updating authenticator + AuthenticatorID string + AuthenticatorRecoveryCodes []string + AuthenticatorTOTPIssuer string + AuthenticatorTOTPEndUserAccountID string + AuthenticatorTOTPDisplayName string + AuthenticatorTOTPSecret string + AuthenticatorTOTPVerified bool } func (s *RedisStore) GenerateToken(options GenerateTokenOptions) (string, error) { @@ -60,10 +65,15 @@ func (s *RedisStore) GenerateToken(options GenerateTokenOptions) (string, error) } var tokenAuthenticator *TokenAuthenticator - if options.AuthenticatorID != "" || options.AuthenticatorTOTPSecret != "" { + if options.AuthenticatorID != "" || options.AuthenticatorTOTPSecret != "" || options.AuthenticatorTOTPVerified || len(options.AuthenticatorRecoveryCodes) > 0 { tokenAuthenticator = &TokenAuthenticator{ - AuthenticatorID: options.AuthenticatorID, - TOTPSecret: options.AuthenticatorTOTPSecret, + AuthenticatorID: options.AuthenticatorID, + TOTPIssuer: options.AuthenticatorTOTPIssuer, + TOTPDisplayName: options.AuthenticatorTOTPDisplayName, + TOTPEndUserAccountID: options.AuthenticatorTOTPEndUserAccountID, + TOTPSecret: options.AuthenticatorTOTPSecret, + TOTPVerified: options.AuthenticatorTOTPVerified, + RecoveryCodes: options.AuthenticatorRecoveryCodes, } } diff --git a/pkg/lib/accountmanagement/service.go b/pkg/lib/accountmanagement/service.go index 3f8f9ac9a5..3a1d2b01bb 100644 --- a/pkg/lib/accountmanagement/service.go +++ b/pkg/lib/accountmanagement/service.go @@ -15,6 +15,7 @@ import ( "github.com/authgear/authgear-server/pkg/lib/authn/authenticator" "github.com/authgear/authgear-server/pkg/lib/authn/authenticator/service" "github.com/authgear/authgear-server/pkg/lib/authn/identity" + "github.com/authgear/authgear-server/pkg/lib/authn/mfa" "github.com/authgear/authgear-server/pkg/lib/authn/otp" "github.com/authgear/authgear-server/pkg/lib/config" "github.com/authgear/authgear-server/pkg/lib/facade" @@ -24,6 +25,7 @@ import ( "github.com/authgear/authgear-server/pkg/lib/session" "github.com/authgear/authgear-server/pkg/lib/translation" "github.com/authgear/authgear-server/pkg/util/accesscontrol" + "github.com/authgear/authgear-server/pkg/util/httputil" ) type UserService interface { @@ -60,6 +62,7 @@ type EventService interface { } type AuthenticatorService interface { + New(spec *authenticator.Spec) (*authenticator.Info, error) NewWithAuthenticatorID(authenticatorID string, spec *authenticator.Spec) (*authenticator.Info, error) List(userID string, filters ...authenticator.Filter) ([]*authenticator.Info, error) Create(authenticatorInfo *authenticator.Info, markVerified bool) error @@ -72,6 +75,11 @@ type AuthenticationInfoService interface { Save(entry *authenticationinfo.Entry) error } +type MFAService interface { + GenerateRecoveryCodes() []string + ReplaceRecoveryCodes(userID string, codes []string) ([]*mfa.RecoveryCode, error) +} + type PasskeyService interface { ConsumeAttestationResponse(attestationResponse []byte) (err error) } @@ -99,6 +107,7 @@ type VerificationService interface { type Service struct { Database *appdb.Handle Config *config.AppConfig + HTTPOrigin httputil.HTTPOrigin Users UserService Store Store OAuthProvider OAuthProvider @@ -108,6 +117,7 @@ type Service struct { OTPCodeService OTPCodeService Authenticators AuthenticatorService AuthenticationInfoService AuthenticationInfoService + MFA MFAService PasskeyService PasskeyService Verification VerificationService UIInfoResolver UIInfoResolver diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 60a3213619..13d5f6815d 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -7,7 +7,7 @@ import ( "github.com/authgear/authgear-server/pkg/lib/authn/authenticator" authenticatorservice "github.com/authgear/authgear-server/pkg/lib/authn/authenticator/service" "github.com/authgear/authgear-server/pkg/lib/session" - "github.com/authgear/authgear-server/pkg/util/uuid" + "github.com/authgear/authgear-server/pkg/util/secretcode" ) type ChangePrimaryPasswordInput struct { @@ -104,6 +104,53 @@ func (s *Service) ChangeSecondaryPassword(resolvedSession session.ResolvedSessio return &ChangeSecondaryPasswordOutput{}, nil } +type StartAddTOTPAuthenticatorInput struct{} +type StartAddTOTPAuthenticatorOutput struct { + Token string + EndUserAccountID string + AuthenticatorTOTPIssuer string + AuthenticatorTOTPSecret string +} + +func (s *Service) StartAddTOTPAuthenticator(resolvedSession session.ResolvedSession, input *StartAddTOTPAuthenticatorInput) (*StartAddTOTPAuthenticatorOutput, error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + + var endUserAccountID string + err := s.Database.WithTx(func() error { + user, err := s.Users.Get(userID, accesscontrol.RoleGreatest) + if err != nil { + return err + } + endUserAccountID = user.EndUserAccountID + return nil + }) + if err != nil { + return nil, err + } + + totp, err := secretcode.NewTOTPFromRNG() + if err != nil { + return nil, err + } + token, err := s.Store.GenerateToken(GenerateTokenOptions{ + UserID: userID, + AuthenticatorTOTPIssuer: string(s.HTTPOrigin), + AuthenticatorTOTPEndUserAccountID: endUserAccountID, + AuthenticatorTOTPSecret: totp.Secret, + }) + if err != nil { + return nil, err + } + + return &StartAddTOTPAuthenticatorOutput{ + Token: token, + EndUserAccountID: endUserAccountID, + AuthenticatorTOTPIssuer: string(s.HTTPOrigin), + AuthenticatorTOTPSecret: totp.Secret, + }, nil +} + + type changePasswordInput struct { Kind authenticator.Kind OldPassword string diff --git a/pkg/lib/accountmanagement/token.go b/pkg/lib/accountmanagement/token.go index 775f97169f..b597ace5ea 100644 --- a/pkg/lib/accountmanagement/token.go +++ b/pkg/lib/accountmanagement/token.go @@ -34,8 +34,13 @@ type TokenIdentity struct { } type TokenAuthenticator struct { - AuthenticatorID string `json:"authenticator_id,omitempty"` - TOTPSecret string `json:"totp_secret,omitempty"` + AuthenticatorID string `json:"authenticator_id,omitempty"` + TOTPIssuer string `json:"totp_issuer,omitempty"` + TOTPDisplayName string `json:"totp_display_name,omitempty"` + TOTPEndUserAccountID string `json:"end_user_account_id,omitempty"` + TOTPSecret string `json:"totp_secret,omitempty"` + TOTPVerified bool `json:"totp_verified,omitempty"` + RecoveryCodes []string `json:"recovery_codes,omitempty"` } func (t *Token) CheckUser(userID string) error { diff --git a/pkg/lib/deps/deps_common.go b/pkg/lib/deps/deps_common.go index 52829f87fe..d375a05c44 100644 --- a/pkg/lib/deps/deps_common.go +++ b/pkg/lib/deps/deps_common.go @@ -328,6 +328,7 @@ var CommonDependencySet = wire.NewSet( wire.Bind(new(userimport.IdentityService), new(*facade.IdentityFacade)), wire.Bind(new(userimport.AuthenticatorService), new(*facade.AuthenticatorFacade)), wire.Bind(new(accountmanagement.IdentityService), new(*facade.IdentityFacade)), + wire.Bind(new(accountmanagement.MFAService), new(*facade.MFAFacade)), wire.Bind(new(oauth.AccessTokenEncodingIdentityService), new(*facade.IdentityFacade)), wire.Bind(new(authenticationflow.UserFacade), new(*facade.UserFacade)), wire.Bind(new(handlersaml.SAMLUserFacade), new(*facade.UserFacade)), From 426b1df5c74d20312550f40e8ad4c9c9d9d0021f Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 30 Sep 2024 13:50:11 +0800 Subject: [PATCH 05/31] Add resume create totp in account management --- .../service_authenticator.go | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 13d5f6815d..b90fc76e07 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -150,6 +150,85 @@ func (s *Service) StartAddTOTPAuthenticator(resolvedSession session.ResolvedSess }, nil } +type ResumeAddTOTPAuthenticatorInput struct { + DisplayName string + Code string +} +type ResumeAddTOTPAuthenticatorOutput struct { + Token string +} + +func (s *Service) ResumeAddTOTPAuthenticator(resolvedSession session.ResolvedSession, tokenString string, input *ResumeAddTOTPAuthenticatorInput) (output *ResumeAddTOTPAuthenticatorOutput, err error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + token, err := s.Store.GetToken(tokenString) + defer func() { + if err == nil { + _, err = s.Store.ConsumeToken(tokenString) + } + }() + + if err != nil { + return + } + + err = token.CheckUser(userID) + if err != nil { + return + } + + info, err := s.Authenticators.New( + &authenticator.Spec{ + UserID: userID, + IsDefault: false, + Kind: model.AuthenticatorKindSecondary, + Type: model.AuthenticatorTypeTOTP, + TOTP: &authenticator.TOTPSpec{ + DisplayName: input.DisplayName, + Secret: token.Authenticator.TOTPSecret, + }, + }, + ) + if err != nil { + return + } + _, err = s.Authenticators.VerifyWithSpec( + info, + &authenticator.Spec{ + UserID: userID, + IsDefault: false, + Kind: model.AuthenticatorKindSecondary, + Type: model.AuthenticatorTypeTOTP, + TOTP: &authenticator.TOTPSpec{ + DisplayName: input.DisplayName, + Code: input.Code, + }, + }, + nil, + ) + + if err != nil { + return + } + + recoveryCodes := s.MFA.GenerateRecoveryCodes() + + newToken, err := s.Store.GenerateToken(GenerateTokenOptions{ + UserID: userID, + AuthenticatorTOTPDisplayName: input.DisplayName, + AuthenticatorTOTPSecret: token.Authenticator.TOTPSecret, + AuthenticatorTOTPVerified: true, + AuthenticatorRecoveryCodes: recoveryCodes, + }) + if err != nil { + return + } + + output = &ResumeAddTOTPAuthenticatorOutput{ + Token: newToken, + } + return +} + type changePasswordInput struct { Kind authenticator.Kind From ae43cbccbd971b61cc4b112d6d9532728a597998 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 30 Sep 2024 13:52:35 +0800 Subject: [PATCH 06/31] Add finish create totp in account management --- .../service_authenticator.go | 146 ++++++++++++------ 1 file changed, 98 insertions(+), 48 deletions(-) diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index b90fc76e07..12d1dfa29c 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -7,7 +7,9 @@ import ( "github.com/authgear/authgear-server/pkg/lib/authn/authenticator" authenticatorservice "github.com/authgear/authgear-server/pkg/lib/authn/authenticator/service" "github.com/authgear/authgear-server/pkg/lib/session" + "github.com/authgear/authgear-server/pkg/util/accesscontrol" "github.com/authgear/authgear-server/pkg/util/secretcode" + "github.com/authgear/authgear-server/pkg/util/uuid" ) type ChangePrimaryPasswordInput struct { @@ -229,80 +231,128 @@ func (s *Service) ResumeAddTOTPAuthenticator(resolvedSession session.ResolvedSes return } - -type changePasswordInput struct { - Kind authenticator.Kind - OldPassword string - NewPassword string +type FinishAddTOTPAuthenticatorInput struct { } -type changePasswordOutput struct { +type FinishAddTOTPAuthenticatorOutput struct { + Info *authenticator.Info } -func (s *Service) changePassword(resolvedSession session.ResolvedSession, input *changePasswordInput) (*changePasswordOutput, error) { +func (s *Service) FinishAddTOTPAuthenticator(resolvedSession session.ResolvedSession, tokenString string, input *FinishAddTOTPAuthenticatorInput) (output *FinishAddTOTPAuthenticatorOutput, err error) { userID := resolvedSession.GetAuthenticationInfo().UserID - - err := s.Database.WithTx(func() error { - ais, err := s.Authenticators.List( - userID, - authenticator.KeepType(model.AuthenticatorTypePassword), - authenticator.KeepKind(input.Kind), - ) - if err != nil { - return err - } - if len(ais) == 0 { - return api.ErrNoPassword + token, err := s.Store.GetToken(tokenString) + defer func() { + if err == nil { + _, err = s.Store.ConsumeToken(tokenString) } - oldInfo := ais[0] - _, err = s.Authenticators.VerifyWithSpec(oldInfo, &authenticator.Spec{ - Password: &authenticator.PasswordSpec{ - PlainPassword: input.OldPassword, + }() + + if err != nil { + return + } + + err = token.CheckUser(userID) + if err != nil { + return + } + + info, err := s.Authenticators.New( + &authenticator.Spec{ + UserID: userID, + IsDefault: false, + Kind: model.AuthenticatorKindSecondary, + Type: model.AuthenticatorTypeTOTP, + TOTP: &authenticator.TOTPSpec{ + DisplayName: token.Authenticator.TOTPDisplayName, + Secret: token.Authenticator.TOTPSecret, }, - }, nil) + }, + ) + if err != nil { + return + } + err = s.Database.WithTx(func() error { + err = s.createAuthenticator(info) if err != nil { - err = api.ErrInvalidCredentials return err } - changed, newInfo, err := s.Authenticators.UpdatePassword(oldInfo, &authenticatorservice.UpdatePasswordOptions{ - SetPassword: true, - PlainPassword: input.NewPassword, - SetExpireAfter: true, - }) + + _, err = s.MFA.ReplaceRecoveryCodes(userID, token.Authenticator.RecoveryCodes) if err != nil { return err } - if changed { - err = s.Authenticators.Update(newInfo) - if err != nil { - return err - } - } + return nil }) + + output = &FinishAddTOTPAuthenticatorOutput{ + Info: info, + } + return +} + +type changePasswordInput struct { + Kind authenticator.Kind + OldPassword string + NewPassword string +} + +type changePasswordOutput struct { +} + +func (s *Service) changePassword(resolvedSession session.ResolvedSession, input *changePasswordInput) (*changePasswordOutput, error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + + ais, err := s.Authenticators.List( + userID, + authenticator.KeepType(model.AuthenticatorTypePassword), + authenticator.KeepKind(input.Kind), + ) + if err != nil { + return nil, err + } + if len(ais) == 0 { + return nil, api.ErrNoPassword + } + oldInfo := ais[0] + _, err = s.Authenticators.VerifyWithSpec(oldInfo, &authenticator.Spec{ + Password: &authenticator.PasswordSpec{ + PlainPassword: input.OldPassword, + }, + }, nil) if err != nil { + err = api.ErrInvalidCredentials return nil, err } + changed, newInfo, err := s.Authenticators.UpdatePassword(oldInfo, &authenticatorservice.UpdatePasswordOptions{ + SetPassword: true, + PlainPassword: input.NewPassword, + SetExpireAfter: true, + }) + if err != nil { + return nil, err + } + if changed { + err = s.Authenticators.Update(newInfo) + if err != nil { + return nil, err + } + } return &changePasswordOutput{}, nil } func (s *Service) createAuthenticator(authenticatorInfo *authenticator.Info) error { - err := s.Database.WithTx(func() error { - err := s.Authenticators.Create(authenticatorInfo, false) + err := s.Authenticators.Create(authenticatorInfo, true) + if err != nil { + return err + } + if authenticatorInfo.Kind == authenticator.KindSecondary { + err = s.Users.UpdateMFAEnrollment(authenticatorInfo.UserID, nil) if err != nil { return err } - if authenticatorInfo.Kind == authenticator.KindSecondary { - err = s.Users.UpdateMFAEnrollment(authenticatorInfo.UserID, nil) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - return err } + return nil } From 92c569af9b9a697579ded10417ddbde45c237bce Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 30 Sep 2024 14:00:04 +0800 Subject: [PATCH 07/31] Add settings v2 create totp page --- .../components/settings-action-item.css | 1 + pkg/auth/handler/webapp/authflowv2/deps.go | 1 + pkg/auth/handler/webapp/authflowv2/routes.go | 10 +- .../authflowv2/settings_mfa_create_totp.go | 118 ++++++++++++++++++ ...{settings_totp.go => settings_mfa_totp.go} | 30 ++++- pkg/auth/routes.go | 1 + pkg/auth/wire_handler.go | 7 ++ .../authgear/templates/en/translation.json | 3 + .../en/web/authflowv2/settings_mfa.html | 51 ++++++-- .../authflowv2/settings_mfa_create_totp.html | 59 +++++++++ ...tings_totp.html => settings_mfa_totp.html} | 16 ++- 11 files changed, 274 insertions(+), 23 deletions(-) create mode 100644 pkg/auth/handler/webapp/authflowv2/settings_mfa_create_totp.go rename pkg/auth/handler/webapp/authflowv2/{settings_totp.go => settings_mfa_totp.go} (75%) create mode 100644 resources/authgear/templates/en/web/authflowv2/settings_mfa_create_totp.html rename resources/authgear/templates/en/web/authflowv2/{settings_totp.html => settings_mfa_totp.html} (86%) diff --git a/authui/src/authflowv2/components/settings-action-item.css b/authui/src/authflowv2/components/settings-action-item.css index b28f592c5e..e45b168ec8 100644 --- a/authui/src/authflowv2/components/settings-action-item.css +++ b/authui/src/authflowv2/components/settings-action-item.css @@ -88,6 +88,7 @@ } .settings-action-item__action-button-container { + @apply flex flex-none items-center; @apply py-[var(--settings-action-item-padding-y)]; } diff --git a/pkg/auth/handler/webapp/authflowv2/deps.go b/pkg/auth/handler/webapp/authflowv2/deps.go index 3a671f4869..d4d2b35633 100644 --- a/pkg/auth/handler/webapp/authflowv2/deps.go +++ b/pkg/auth/handler/webapp/authflowv2/deps.go @@ -55,6 +55,7 @@ var DependencySet = wire.NewSet( wire.Struct(new(AuthflowV2SettingsMFACreatePasswordHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFAPasswordHandler), "*"), wire.Struct(new(AuthflowV2SettingsTOTPHandler), "*"), + wire.Struct(new(AuthflowV2SettingsMFACreateTOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsOOBOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityAddEmailHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityEditEmailHandler), "*"), diff --git a/pkg/auth/handler/webapp/authflowv2/routes.go b/pkg/auth/handler/webapp/authflowv2/routes.go index 86940a31f1..c5e8668b1d 100644 --- a/pkg/auth/handler/webapp/authflowv2/routes.go +++ b/pkg/auth/handler/webapp/authflowv2/routes.go @@ -79,12 +79,16 @@ const ( AuthflowV2RouteFinishFlow = "/authflow/v2/finish" - AuthflowV2RouteSettingsProfile = "/settings/v2/profile" - AuthflowV2RouteSettingsMFA = "/settings/mfa" + AuthflowV2RouteSettingsProfile = "/settings/v2/profile" + AuthflowV2RouteSettingsMFA = "/settings/mfa" + AuthflowV2RouteSettingsMFAViewRecoveryCode = "/settings/mfa/view_recovery_code" + // nolint: gosec AuthflowV2RouteSettingsMFACreatePassword = "/settings/mfa/create_password" // nolint: gosec - AuthflowV2RouteSettingsMFAPassword = "/settings/mfa/password" + AuthflowV2RouteSettingsMFAPassword = "/settings/mfa/password" + AuthflowV2RouteSettingsMFACreateTOTP = "/settings/mfa/create_totp" + AuthflowV2RouteSettingsMFAEnterTOTP = "/settings/mfa/enter_totp" AuthflowV2RouteSettingsIdentityListEmail = "/settings/identity/email" AuthflowV2RouteSettingsIdentityAddEmail = "/settings/identity/add_email" diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_totp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_totp.go new file mode 100644 index 0000000000..433052a817 --- /dev/null +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_totp.go @@ -0,0 +1,118 @@ +package authflowv2 + +import ( + htmltemplate "html/template" + "net/http" + + handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" + "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" + "github.com/authgear/authgear-server/pkg/lib/infra/db/appdb" + "github.com/authgear/authgear-server/pkg/lib/session" + "github.com/authgear/authgear-server/pkg/util/httproute" + coreimage "github.com/authgear/authgear-server/pkg/util/image" + "github.com/authgear/authgear-server/pkg/util/secretcode" + "github.com/authgear/authgear-server/pkg/util/template" + "github.com/authgear/authgear-server/pkg/util/validation" +) + +var TemplateWebSettingsMFACreateTOTPHTML = template.RegisterHTML( + "web/authflowv2/settings_mfa_create_totp.html", + handlerwebapp.SettingsComponents..., +) + +var AuthflowV2SettingsMFACreateTOTPSchema = validation.NewSimpleSchema(` + { + "type": "object", + "properties": { + "x_totp": { "type": "string" }, + "x_confirm_totp": { "type": "string" } + }, + "required": ["x_totp", "x_confirm_totp"] + } +`) + +func ConfigureAuthflowV2SettingsMFACreateTOTPRoute(route httproute.Route) httproute.Route { + return route. + WithMethods("OPTIONS", "POST", "GET"). + WithPathPattern(AuthflowV2RouteSettingsMFACreateTOTP) +} + +type AuthflowV2SettingsMFACreateTOTPViewModel struct { + Token string + Secret string + ImageURI htmltemplate.URL +} + +type AuthflowV2SettingsMFACreateTOTPHandler struct { + Database *appdb.Handle + ControllerFactory handlerwebapp.ControllerFactory + BaseViewModel *viewmodels.BaseViewModeler + SettingsViewModel *viewmodels.SettingsViewModeler + Renderer handlerwebapp.Renderer + + AccountManagement *accountmanagement.Service +} + +func (h *AuthflowV2SettingsMFACreateTOTPHandler) GetData(r *http.Request, rw http.ResponseWriter, tokenString string, totpSecret string, otpauthURI string) (map[string]interface{}, error) { + data := make(map[string]interface{}) + + baseViewModel := h.BaseViewModel.ViewModel(r, rw) + viewmodels.Embed(data, baseViewModel) + + img, err := secretcode.QRCodeImageFromURI(otpauthURI, 512, 512) + if err != nil { + return nil, err + } + dataURI, err := coreimage.DataURIFromImage(coreimage.CodecPNG, img) + if err != nil { + return nil, err + } + + screenViewModel := AuthflowV2SettingsMFACreateTOTPViewModel{ + Token: tokenString, + Secret: totpSecret, + ImageURI: htmltemplate.URL(dataURI), + } + viewmodels.Embed(data, screenViewModel) + + return data, nil +} + +func (h *AuthflowV2SettingsMFACreateTOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctrl, err := h.ControllerFactory.New(r, w) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer ctrl.ServeWithoutDBTx() + + ctrl.Get(func() error { + s := session.GetSession(r.Context()) + + tokenString := r.Form.Get("q_token") + token, err := h.AccountManagement.GetToken(s, tokenString) + if err != nil { + return err + } + + opts := secretcode.URIOptions{ + Issuer: string(token.Authenticator.TOTPIssuer), + AccountName: token.Authenticator.TOTPEndUserAccountID, + } + totp, err := secretcode.NewTOTPFromSecret(token.Authenticator.TOTPSecret) + if err != nil { + return err + } + totpauthURI := totp.GetURI(opts).String() + + data, err := h.GetData(r, w, tokenString, totp.Secret, totpauthURI) + if err != nil { + return err + } + + h.Renderer.RenderHTML(w, r, TemplateWebSettingsMFACreateTOTPHTML, data) + return nil + }) +} diff --git a/pkg/auth/handler/webapp/authflowv2/settings_totp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_totp.go similarity index 75% rename from pkg/auth/handler/webapp/authflowv2/settings_totp.go rename to pkg/auth/handler/webapp/authflowv2/settings_mfa_totp.go index 1760f8b272..47a16519ab 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_totp.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_totp.go @@ -2,10 +2,13 @@ package authflowv2 import ( "net/http" + "net/url" "github.com/authgear/authgear-server/pkg/api/model" handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + "github.com/authgear/authgear-server/pkg/auth/webapp" + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" "github.com/authgear/authgear-server/pkg/lib/authn/authenticator" authenticatorservice "github.com/authgear/authgear-server/pkg/lib/authn/authenticator/service" "github.com/authgear/authgear-server/pkg/lib/infra/db/appdb" @@ -14,7 +17,7 @@ import ( ) var TemplateWebSettingsTOTPHTML = template.RegisterHTML( - "web/authflowv2/settings_totp.html", + "web/authflowv2/settings_mfa_totp.html", handlerwebapp.SettingsComponents..., ) @@ -27,6 +30,8 @@ type AuthflowV2SettingsTOTPHandler struct { ControllerFactory handlerwebapp.ControllerFactory BaseViewModel *viewmodels.BaseViewModeler Renderer handlerwebapp.Renderer + + AccountManagement *accountmanagement.Service Authenticators authenticatorservice.Service } @@ -80,4 +85,27 @@ func (h *AuthflowV2SettingsTOTPHandler) ServeHTTP(w http.ResponseWriter, r *http h.Renderer.RenderHTML(w, r, TemplateWebSettingsTOTPHTML, data) return nil }) + + ctrl.PostAction("create_totp", func() error { + s := session.GetSession(r.Context()) + output, err := h.AccountManagement.StartAddTOTPAuthenticator(s, &accountmanagement.StartAddTOTPAuthenticatorInput{}) + if err != nil { + return err + } + + redirectURI, err := url.Parse(AuthflowV2RouteSettingsMFACreateTOTP) + if err != nil { + return err + } + + q := redirectURI.Query() + q.Set("q_token", output.Token) + + redirectURI.RawQuery = q.Encode() + + result := webapp.Result{RedirectURI: redirectURI.String()} + result.WriteResponse(w, r) + + return nil + }) } diff --git a/pkg/auth/routes.go b/pkg/auth/routes.go index f33e4fad36..a5d0838834 100644 --- a/pkg/auth/routes.go +++ b/pkg/auth/routes.go @@ -490,6 +490,7 @@ func NewRouter(p *deps.RootProvider, configSource *configsource.ConfigSource) *h SettingV1: p.Handler(newWebAppSettingsTOTPHandler), SettingV2: p.Handler(newWebAppAuthflowV2SettingsTOTPHandler), }) + router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFACreateTOTPRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFACreateTOTPHandler)) router.Add(webapphandler.ConfigureSettingsPasskeyRoute(webappSettingsSubRoutesRoute), &webapphandler.SettingsImplementationSwitcherHandler{ SettingV1: p.Handler(newWebAppSettingsPasskeyHandler), SettingV2: p.Handler(newWebAppAuthflowV2SettingsChangePasskeyHandler), diff --git a/pkg/auth/wire_handler.go b/pkg/auth/wire_handler.go index 833c8aa454..43e8a59e59 100644 --- a/pkg/auth/wire_handler.go +++ b/pkg/auth/wire_handler.go @@ -506,6 +506,13 @@ func newWebAppAuthflowV2SettingsTOTPHandler(p *deps.RequestProvider) http.Handle )) } +func newWebAppAuthflowV2SettingsMFACreateTOTPHandler(p *deps.RequestProvider) http.Handler { + panic(wire.Build( + DependencySet, + wire.Bind(new(http.Handler), new(*handlerwebappauthflowv2.AuthflowV2SettingsMFACreateTOTPHandler)), + )) +} + func newWebAppAuthflowV2SettingsOOBOTPHandler(p *deps.RequestProvider) http.Handler { panic(wire.Build( DependencySet, diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index 5921370376..709141d693 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1181,6 +1181,9 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Not Verified", "v2.page.settings-identity.default.verification-status-verified-label": "Verified", "v2.page.settings-mfa-create-password.default.title": "Additional password", + "v2.page.settings-mfa-create-totp.default.navbar-title": "Set up authenticator", + "v2.page.settings-mfa-create-totp.default.description": "First, download Google Authenticator App from the Google Play Store or Apple App Store.

Scan the QR code or enter the key with your authenticator device / app.", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", "v2.page.settings-mfa-password.default.additional-password-added-at": "Added at {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Additional password", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Updated at {time, datetime, short} UTC", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa.html index adc4dadc50..825ae07550 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa.html @@ -14,23 +14,44 @@ {{ if $.ShowMFA }}
{{ if $.ShowSecondaryTOTP }} - {{ $href := (call $.MakeURL "/settings/mfa/new_totp") }} - {{ if $.SecondaryPassword }} - {{ $href = (call $.MakeURL "/settings/mfa/totp") }} + {{ if $.HasSecondaryTOTP }} + {{ template "__settings_mfa_item.html" + (dict + "MaterialIconName" "phone_iphone" + "Label" (translate "v2.page.settings-mfa.default.secondary-totp-label" nil) + "IsActive" $.HasSecondaryTOTP + "Href" (call $.MakeURL "/settings/mfa/totp") + ) + }} + {{ else }} +
+ {{ $.CSRFField }} + +
{{ end }} - {{ template "__settings_mfa_item.html" - (dict - "MaterialIconName" "phone_iphone" - "Label" (translate "v2.page.settings-mfa.default.secondary-totp-label" nil) - "IsActive" $.HasSecondaryTOTP - "Href" $href - ) - }} {{ end }} {{ if $.ShowSecondaryOOBOTPEmail }} {{ $href := (call $.MakeURL "/settings/mfa/new_oob_otp_email") }} - {{ if $.SecondaryPassword }} + {{ if $.HasSecondaryOOBOTPEmail }} {{ $href = (call $.MakeURL "/settings/mfa/oob_otp_email") }} {{ end }} {{ template "__settings_mfa_item.html" @@ -45,7 +66,7 @@ {{ if $.ShowSecondaryOOBOTPSMS }} {{ $href := (call $.MakeURL "/settings/mfa/new_oob_otp_sms") }} - {{ if $.SecondaryPassword }} + {{ if $.HasSecondaryOOBOTPSMS }} {{ $href = (call $.MakeURL "/settings/mfa/oob_otp_sms") }} {{ end }} {{ template "__settings_mfa_item.html" @@ -148,3 +169,7 @@ {{ translate "v2.page.settings-mfa.default.inactive-label" nil}} {{ end }} {{ end }} + +{{ define "__settings_mfa_item_action_button.html" }} + +{{ end }} diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_totp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_totp.html new file mode 100644 index 0000000000..516c7c15cc --- /dev/null +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_totp.html @@ -0,0 +1,59 @@ +{{ template "authflowv2/__settings_page_frame.html" . }} + +{{ define "page-navbar" }} + {{ template "authflowv2/__navbar.html" + (dict + "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) + "BackHref" (call $.MakeURL "/settings/mfa/totp") + "Title" (translate "v2.page.settings-mfa-create-totp.default.navbar-title" nil) + ) + }} +{{ end }} + +{{ define "page-content" }} +
+
+

+ {{ include "v2.page.settings-mfa-create-totp.default.description" nil }} +

+ + {{ template "authflowv2/__alert_message.html" + (dict + "Type" "error" + "Classname" "mt-4" + "Message" (ternary (include "authflowv2/__error.html" .) nil (not $.display_otp_input_error)) + ) + }} +
+ + + +
+
+

{{ include "v2.page.settings-mfa-create-totp.default.raw-secret" (dict "secret" $.Secret) }}

+ + +
+ + + {{ include "v2.component.button.default.label-continue" nil }} + +
+
+{{ end }} diff --git a/resources/authgear/templates/en/web/authflowv2/settings_totp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html similarity index 86% rename from resources/authgear/templates/en/web/authflowv2/settings_totp.html rename to resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html index 19473e37c2..0a5140eafd 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_totp.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html @@ -27,13 +27,17 @@ {{ end }} - {{ $href := (call $.MakeURL "/settings/mfa/new_totp") }} - +
+ {{ $.CSRFField }} + +
{{ end }} From ff72c68ea305172d2467bebe5fe56795fd5e6283 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 30 Sep 2024 14:10:59 +0800 Subject: [PATCH 08/31] Add settings v2 enter totp page --- pkg/auth/handler/webapp/authflowv2/deps.go | 1 + .../authflowv2/settings_mfa_enter_totp.go | 132 ++++++++++++++++++ pkg/auth/routes.go | 1 + pkg/auth/wire_handler.go | 7 + .../authgear/templates/en/translation.json | 3 + .../authflowv2/settings_mfa_enter_totp.html | 79 +++++++++++ 6 files changed, 223 insertions(+) create mode 100644 pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_totp.go create mode 100644 resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_totp.html diff --git a/pkg/auth/handler/webapp/authflowv2/deps.go b/pkg/auth/handler/webapp/authflowv2/deps.go index d4d2b35633..8227a71081 100644 --- a/pkg/auth/handler/webapp/authflowv2/deps.go +++ b/pkg/auth/handler/webapp/authflowv2/deps.go @@ -56,6 +56,7 @@ var DependencySet = wire.NewSet( wire.Struct(new(AuthflowV2SettingsMFAPasswordHandler), "*"), wire.Struct(new(AuthflowV2SettingsTOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFACreateTOTPHandler), "*"), + wire.Struct(new(AuthflowV2SettingsMFAEnterTOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsOOBOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityAddEmailHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityEditEmailHandler), "*"), diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_totp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_totp.go new file mode 100644 index 0000000000..8f1549ce6d --- /dev/null +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_totp.go @@ -0,0 +1,132 @@ +package authflowv2 + +import ( + "fmt" + htmltemplate "html/template" + "net/http" + "net/url" + "time" + + handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" + "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + "github.com/authgear/authgear-server/pkg/auth/webapp" + + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" + "github.com/authgear/authgear-server/pkg/lib/infra/db/appdb" + "github.com/authgear/authgear-server/pkg/lib/session" + "github.com/authgear/authgear-server/pkg/util/clock" + "github.com/authgear/authgear-server/pkg/util/httproute" + "github.com/authgear/authgear-server/pkg/util/template" + "github.com/authgear/authgear-server/pkg/util/validation" +) + +var TemplateWebSettingsMFAEnterTOTPHTML = template.RegisterHTML( + "web/authflowv2/settings_mfa_enter_totp.html", + handlerwebapp.SettingsComponents..., +) + +var AuthflowV2SettingsMFAEnterTOTPSchema = validation.NewSimpleSchema(` + { + "type": "object", + "properties": { + "x_code": { "type": "string" } + }, + "required": ["x_code"] + } +`) + +func ConfigureAuthflowV2SettingsMFAEnterTOTPRoute(route httproute.Route) httproute.Route { + return route. + WithMethods("OPTIONS", "POST", "GET"). + WithPathPattern(AuthflowV2RouteSettingsMFAEnterTOTP) +} + +type AuthflowV2SettingsMFAEnterTOTPViewModel struct { + Secret string + ImageURI htmltemplate.URL +} + +type AuthflowV2SettingsMFAEnterTOTPHandler struct { + Database *appdb.Handle + ControllerFactory handlerwebapp.ControllerFactory + BaseViewModel *viewmodels.BaseViewModeler + SettingsViewModel *viewmodels.SettingsViewModeler + Renderer handlerwebapp.Renderer + Clock clock.Clock + + AccountManagement *accountmanagement.Service +} + +func (h *AuthflowV2SettingsMFAEnterTOTPHandler) GetData(r *http.Request, rw http.ResponseWriter) (map[string]interface{}, error) { + data := make(map[string]interface{}) + + baseViewModel := h.BaseViewModel.ViewModel(r, rw) + viewmodels.Embed(data, baseViewModel) + + return data, nil +} + +func (h *AuthflowV2SettingsMFAEnterTOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctrl, err := h.ControllerFactory.New(r, w) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer ctrl.ServeWithoutDBTx() + + ctrl.Get(func() error { + s := session.GetSession(r.Context()) + + tokenString := r.Form.Get("q_token") + _, err := h.AccountManagement.GetToken(s, tokenString) + if err != nil { + return err + } + + data, err := h.GetData(r, w) + if err != nil { + return err + } + + h.Renderer.RenderHTML(w, r, TemplateWebSettingsMFAEnterTOTPHTML, data) + return nil + }) + + ctrl.PostAction("submit", func() error { + err := AuthflowV2SettingsMFAEnterTOTPSchema.Validator().ValidateValue(handlerwebapp.FormToJSON(r.Form)) + if err != nil { + return err + } + + s := session.GetSession(r.Context()) + + tokenString := r.Form.Get("q_token") + code := r.Form.Get("x_code") + + now := h.Clock.NowUTC() + displayName := fmt.Sprintf("TOTP @ %s", now.Format(time.RFC3339)) + + output, err := h.AccountManagement.ResumeAddTOTPAuthenticator(s, tokenString, &accountmanagement.ResumeAddTOTPAuthenticatorInput{ + DisplayName: displayName, + Code: code, + }) + if err != nil { + return err + } + + redirectURI, err := url.Parse(AuthflowV2RouteSettingsMFAViewRecoveryCode) + if err != nil { + return err + } + + q := redirectURI.Query() + q.Set("q_token", output.Token) + + redirectURI.RawQuery = q.Encode() + + result := webapp.Result{RedirectURI: redirectURI.String()} + result.WriteResponse(w, r) + + return nil + }) +} diff --git a/pkg/auth/routes.go b/pkg/auth/routes.go index a5d0838834..1c4d470d00 100644 --- a/pkg/auth/routes.go +++ b/pkg/auth/routes.go @@ -491,6 +491,7 @@ func NewRouter(p *deps.RootProvider, configSource *configsource.ConfigSource) *h SettingV2: p.Handler(newWebAppAuthflowV2SettingsTOTPHandler), }) router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFACreateTOTPRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFACreateTOTPHandler)) + router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFAEnterTOTPRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFAEnterTOTPHandler)) router.Add(webapphandler.ConfigureSettingsPasskeyRoute(webappSettingsSubRoutesRoute), &webapphandler.SettingsImplementationSwitcherHandler{ SettingV1: p.Handler(newWebAppSettingsPasskeyHandler), SettingV2: p.Handler(newWebAppAuthflowV2SettingsChangePasskeyHandler), diff --git a/pkg/auth/wire_handler.go b/pkg/auth/wire_handler.go index 43e8a59e59..62162b1688 100644 --- a/pkg/auth/wire_handler.go +++ b/pkg/auth/wire_handler.go @@ -513,6 +513,13 @@ func newWebAppAuthflowV2SettingsMFACreateTOTPHandler(p *deps.RequestProvider) ht )) } +func newWebAppAuthflowV2SettingsMFAEnterTOTPHandler(p *deps.RequestProvider) http.Handler { + panic(wire.Build( + DependencySet, + wire.Bind(new(http.Handler), new(*handlerwebappauthflowv2.AuthflowV2SettingsMFAEnterTOTPHandler)), + )) +} + func newWebAppAuthflowV2SettingsOOBOTPHandler(p *deps.RequestProvider) http.Handler { panic(wire.Build( DependencySet, diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index 709141d693..6dcf079261 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1184,6 +1184,9 @@ "v2.page.settings-mfa-create-totp.default.navbar-title": "Set up authenticator", "v2.page.settings-mfa-create-totp.default.description": "First, download Google Authenticator App from the Google Play Store or Apple App Store.

Scan the QR code or enter the key with your authenticator device / app.", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.navbar-title": "Verification Code", + "v2.page.settings-mfa-enter-totp.default.description": "Enter the 6-digit code you see in the authenticator device/app.", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Rescan QR code", "v2.page.settings-mfa-password.default.additional-password-added-at": "Added at {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Additional password", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Updated at {time, datetime, short} UTC", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_totp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_totp.html new file mode 100644 index 0000000000..af349e4f19 --- /dev/null +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_totp.html @@ -0,0 +1,79 @@ +{{ template "authflowv2/__settings_page_frame.html" . }} + +{{ define "page-navbar" }} + {{ template "authflowv2/__navbar.html" + (dict + "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) + "BackHref" (call $.MakeURL "/settings/mfa") + "Title" (translate "v2.page.settings-mfa-enter-totp.default.navbar-title" nil) + ) + }} +{{ end }} + +{{ define "page-content" }} + + {{ $err_map := (resolveError $.RawError (dict + "otpInput" (dict + "by_reason" (list "InvalidCredentials") + ) + )) }} + + {{ $otp_err := index $err_map "otpInput" }} + {{ $unknown_err := index $err_map "unknown" }} + {{ $has_otp_err := not (isNil $otp_err) }} + {{ $has_unknown_err := not (isNil $unknown_err )}} + + {{ $otp_error_message := "" }} + {{ if $has_otp_err }} + {{ $otp_error_message = include "authflowv2/__error.html" (merge (dict "Error" $otp_err) $) }} + {{ end }} + + {{ $unknown_error_message := "" }} + {{ if $has_unknown_err }} + {{ $unknown_error_message = (include "authflowv2/__error.html" (merge (dict "Error" $unknown_err) $)) }} + {{ end }} + +
+
+

+ {{ include "v2.page.settings-mfa-enter-totp.default.description" nil }} +

+ + {{ template "authflowv2/__alert_message.html" + (dict + "Type" "error" + "Classname" "mt-4" + "Message" (ternary (include "authflowv2/__error.html" .) nil (not $.display_otp_input_error)) + ) + }} +
+ +
+
+ {{ $.CSRFField }} +
+ + {{ template "authflowv2/__otp_input.html" + (dict + "CSRFField" $.CSRFField + "FormName" "main-form" + "CodeLength" 6 + "AutoFocus" $.ShouldFocusInput + "Disabled" $.FailedAttemptRateLimitExceeded + "SubmitEvent" "authgear.button.setup_totp" + "ErrorMessage" $otp_error_message + "ResendButtonHidden" true + ) + }} +
+
+{{ end }} From f7c85d9458e3f8b58418eee27a9f4d55066c4ec9 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 30 Sep 2024 14:11:27 +0800 Subject: [PATCH 09/31] Add settings v2 view recovery code page --- pkg/auth/handler/webapp/authflowv2/deps.go | 1 + .../settings_mfa_view_recovery_code.go | 116 + pkg/auth/routes.go | 1 + pkg/auth/wire_gen.go | 3256 ++++++++++++++++- pkg/auth/wire_handler.go | 7 + .../authgear/templates/en/translation.json | 2 + .../settings_mfa_view_recovery_code.html | 81 + 7 files changed, 3324 insertions(+), 140 deletions(-) create mode 100644 pkg/auth/handler/webapp/authflowv2/settings_mfa_view_recovery_code.go create mode 100644 resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html diff --git a/pkg/auth/handler/webapp/authflowv2/deps.go b/pkg/auth/handler/webapp/authflowv2/deps.go index 8227a71081..c51b63e045 100644 --- a/pkg/auth/handler/webapp/authflowv2/deps.go +++ b/pkg/auth/handler/webapp/authflowv2/deps.go @@ -52,6 +52,7 @@ var DependencySet = wire.NewSet( wire.Struct(new(AuthflowV2SettingsAdvancedSettingsHandler), "*"), wire.Struct(new(AuthflowV2SettingsDeleteAccountHandler), "*"), wire.Struct(new(AuthflowV2SettingsDeleteAccountSuccessHandler), "*"), + wire.Struct(new(AuthflowV2SettingsMFAViewRecoveryCodeHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFACreatePasswordHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFAPasswordHandler), "*"), wire.Struct(new(AuthflowV2SettingsTOTPHandler), "*"), diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_view_recovery_code.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_view_recovery_code.go new file mode 100644 index 0000000000..d0a1023560 --- /dev/null +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_view_recovery_code.go @@ -0,0 +1,116 @@ +package authflowv2 + +import ( + "net/http" + + handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" + "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + "github.com/authgear/authgear-server/pkg/auth/webapp" + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" + "github.com/authgear/authgear-server/pkg/lib/session" + "github.com/authgear/authgear-server/pkg/util/httproute" + "github.com/authgear/authgear-server/pkg/util/template" +) + +var TemplateWebSettingsMFAViewRecoveryCodeHTML = template.RegisterHTML( + "web/authflowv2/settings_mfa_view_recovery_code.html", + handlerwebapp.SettingsComponents..., +) + +func ConfigureAuthflowV2SettingsMFAViewRecoveryCodeRoute(route httproute.Route) httproute.Route { + return route. + WithMethods("OPTIONS", "POST", "GET"). + WithPathPattern(AuthflowV2RouteSettingsMFAViewRecoveryCode) +} + +type AuthflowV2SettingsMFAViewRecoveryCodeViewModel struct { + RecoveryCodes []string +} + +type AuthflowV2SettingsMFAViewRecoveryCodeHandler struct { + ControllerFactory handlerwebapp.ControllerFactory + BaseViewModel *viewmodels.BaseViewModeler + Renderer handlerwebapp.Renderer + + AccountManagement *accountmanagement.Service +} + +func (h *AuthflowV2SettingsMFAViewRecoveryCodeHandler) GetData(r *http.Request, rw http.ResponseWriter, recoveryCodes []string) (map[string]interface{}, error) { + data := map[string]interface{}{} + + baseViewModel := h.BaseViewModel.ViewModel(r, rw) + viewmodels.Embed(data, baseViewModel) + + screenViewModel := AuthflowV2SettingsMFAViewRecoveryCodeViewModel{ + RecoveryCodes: handlerwebapp.FormatRecoveryCodes(recoveryCodes), + } + viewmodels.Embed(data, screenViewModel) + + return data, nil +} + +func (h *AuthflowV2SettingsMFAViewRecoveryCodeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctrl, err := h.ControllerFactory.New(r, w) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer ctrl.ServeWithoutDBTx() + + ctrl.Get(func() error { + s := session.GetSession(r.Context()) + + tokenString := r.Form.Get("q_token") + token, err := h.AccountManagement.GetToken(s, tokenString) + if err != nil { + return err + } + + data, err := h.GetData(r, w, token.Authenticator.RecoveryCodes) + if err != nil { + return err + } + + h.Renderer.RenderHTML(w, r, TemplateWebSettingsMFAViewRecoveryCodeHTML, data) + return nil + }) + + ctrl.PostAction("download", func() error { + s := session.GetSession(r.Context()) + + tokenString := r.Form.Get("q_token") + token, err := h.AccountManagement.GetToken(s, tokenString) + if err != nil { + return err + } + + data, err := h.GetData(r, w, token.Authenticator.RecoveryCodes) + if err != nil { + return err + } + + handlerwebapp.SetRecoveryCodeAttachmentHeaders(w) + h.Renderer.Render(w, r, handlerwebapp.TemplateWebDownloadRecoveryCodeTXT, data) + return nil + }) + + ctrl.PostAction("proceed", func() error { + s := session.GetSession(r.Context()) + + tokenString := r.Form.Get("q_token") + _, err := h.AccountManagement.GetToken(s, tokenString) + if err != nil { + return err + } + + _, err = h.AccountManagement.FinishAddTOTPAuthenticator(s, tokenString, &accountmanagement.FinishAddTOTPAuthenticatorInput{}) + if err != nil { + return err + } + + result := webapp.Result{RedirectURI: AuthflowV2RouteSettingsMFA} + result.WriteResponse(w, r) + + return nil + }) +} diff --git a/pkg/auth/routes.go b/pkg/auth/routes.go index 1c4d470d00..1702ece1e5 100644 --- a/pkg/auth/routes.go +++ b/pkg/auth/routes.go @@ -484,6 +484,7 @@ func NewRouter(p *deps.RootProvider, configSource *configsource.ConfigSource) *h SettingV1: p.Handler(newWebAppSettingsMFAHandler), SettingV2: p.Handler(newWebAppAuthflowV2SettingsMFAHandler), }) + router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFAViewRecoveryCodeRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFAViewRecoveryCodeHandler)) router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFACreatePassword(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFACreatePasswordHandler)) router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFAPassword(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFAPasswordHandler)) router.Add(webapphandler.ConfigureSettingsTOTPRoute(webappSettingsSubRoutesRoute), &webapphandler.SettingsImplementationSwitcherHandler{ diff --git a/pkg/auth/wire_gen.go b/pkg/auth/wire_gen.go index cb2a96ea5b..6732414226 100644 --- a/pkg/auth/wire_gen.go +++ b/pkg/auth/wire_gen.go @@ -50747,6 +50747,7 @@ func newWebAppAuthflowV2SettingsBiometricHandler(p *deps.RequestProvider) http.H accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -50756,6 +50757,7 @@ func newWebAppAuthflowV2SettingsBiometricHandler(p *deps.RequestProvider) http.H OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -52687,7 +52689,7 @@ func newWebAppAuthflowV2SettingsMFAHandler(p *deps.RequestProvider) http.Handler return authflowV2SettingsMFAHandler } -func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider) http.Handler { +func newWebAppAuthflowV2SettingsMFAViewRecoveryCodeHandler(p *deps.RequestProvider) http.Handler { appProvider := p.AppProvider factory := appProvider.LoggerFactory handle := appProvider.AppDatabase @@ -52893,12 +52895,14 @@ func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) + storageRedis := &ratelimit.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, - AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -53335,18 +53339,6 @@ func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider eventProvider := &access.EventProvider{ Store: eventStoreRedis, } - analyticredisHandle := appProvider.AnalyticRedis - meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) - writeStoreRedis := &meter.WriteStoreRedis{ - Context: contextContext, - Redis: analyticredisHandle, - AppID: appID, - Clock: clockClock, - Logger: meterStoreRedisLogger, - } - meterService := &meter.Service{ - Counter: writeStoreRedis, - } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -53356,7 +53348,6 @@ func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, - MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -53367,8 +53358,6 @@ func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, - AccessEvents: eventProvider, - MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ @@ -53627,13 +53616,6 @@ func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider LoggerFactory: factory, ControllerDeps: controllerDeps, } - biometricConfig := identityConfig.Biometric - settingsViewModeler := &viewmodels.SettingsViewModeler{ - Authenticators: service3, - MFA: mfaService, - Authentication: authenticationConfig, - Biometric: biometricConfig, - } redisStore := &accountmanagement.RedisStore{ Context: contextContext, AppID: appID, @@ -53646,6 +53628,7 @@ func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -53655,22 +53638,2912 @@ func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, } - authflowV2SettingsMFACreatePasswordHandler := &authflowv2.AuthflowV2SettingsMFACreatePasswordHandler{ - ControllerFactory: controllerFactory, - BaseViewModel: baseViewModeler, - SettingsViewModel: settingsViewModeler, - PasswordPolicy: passwordChecker, - Renderer: responseRenderer, - AccountManagementService: accountmanagementService, + authflowV2SettingsMFAViewRecoveryCodeHandler := &authflowv2.AuthflowV2SettingsMFAViewRecoveryCodeHandler{ + ControllerFactory: controllerFactory, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + AccountManagement: accountmanagementService, } - return authflowV2SettingsMFACreatePasswordHandler + return authflowV2SettingsMFAViewRecoveryCodeHandler } -func newWebAppAuthflowV2SettingsMFAPasswordHandler(p *deps.RequestProvider) http.Handler { +func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider) http.Handler { + appProvider := p.AppProvider + factory := appProvider.LoggerFactory + handle := appProvider.AppDatabase + appredisHandle := appProvider.Redis + appContext := appProvider.AppContext + config := appContext.Config + appConfig := config.AppConfig + appID := appConfig.ID + serviceLogger := webapp2.NewServiceLogger(factory) + request := p.Request + sessionStoreRedis := &webapp2.SessionStoreRedis{ + AppID: appID, + Redis: appredisHandle, + } + sessionCookieDef := webapp2.NewSessionCookieDef() + signedUpCookieDef := webapp2.NewSignedUpCookieDef() + authenticationConfig := appConfig.Authentication + cookieDef := mfa.NewDeviceTokenCookieDef(authenticationConfig) + errorTokenCookieDef := webapp2.NewErrorTokenCookieDef() + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpConfig := appConfig.HTTP + cookieManager := deps.NewCookieManager(request, trustProxy, httpConfig) + errorService := &webapp2.ErrorService{ + AppID: appID, + Cookie: errorTokenCookieDef, + RedisHandle: appredisHandle, + Cookies: cookieManager, + } + oAuthConfig := appConfig.OAuth + uiConfig := appConfig.UI + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpProto := deps.ProvideHTTPProto(request, trustProxy) + globalUIImplementation := environmentConfig.UIImplementation + globalUISettingsImplementation := environmentConfig.UISettingsImplementation + uiImplementationService := &web.UIImplementationService{ + UIConfig: uiConfig, + GlobalUIImplementation: globalUIImplementation, + GlobalUISettingsImplementation: globalUISettingsImplementation, + } + endpointsEndpoints := &endpoints.Endpoints{ + HTTPHost: httpHost, + HTTPProto: httpProto, + UIImplementationService: uiImplementationService, + } + uiService := &authenticationinfo.UIService{ + EndpointsProvider: endpointsEndpoints, + } + resolver := &oauthclient.Resolver{ + OAuthConfig: oAuthConfig, + TesterEndpoints: endpointsEndpoints, + } + logger := interaction.NewLogger(factory) + remoteIP := deps.ProvideRemoteIP(request, trustProxy) + contextContext := deps.ProvideRequestContext(request) + sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) + clockClock := _wireSystemClockValue + featureConfig := config.FeatureConfig + redisLogger := redis.NewLogger(factory) + secretConfig := config.SecretConfig + databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) + sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) + store := &redis.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Logger: redisLogger, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + userAgentString := deps.ProvideUserAgentString(request) + eventLogger := event.NewLogger(factory) + localizationConfig := appConfig.Localization + sqlBuilder := appdb.NewSQLBuilder(databaseCredentials) + storeImpl := event.NewStoreImpl(sqlBuilder, sqlExecutor) + userStore := &user.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + AppID: appID, + } + rawQueries := &user.RawQueries{ + Store: userStore, + } + identityConfig := appConfig.Identity + identityFeatureConfig := featureConfig.Identity + serviceStore := &service.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginidStore := &loginid.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginIDConfig := identityConfig.LoginID + manager := appContext.Resources + typeCheckerFactory := &loginid.TypeCheckerFactory{ + UIConfig: uiConfig, + LoginIDConfig: loginIDConfig, + Resources: manager, + } + checker := &loginid.Checker{ + Config: loginIDConfig, + TypeCheckerFactory: typeCheckerFactory, + } + normalizerFactory := &loginid.NormalizerFactory{ + Config: loginIDConfig, + } + provider := &loginid.Provider{ + Store: loginidStore, + Config: loginIDConfig, + Checker: checker, + NormalizerFactory: normalizerFactory, + Clock: clockClock, + } + oauthStore := &oauth3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + IdentityConfig: identityConfig, + } + oauthProvider := &oauth3.Provider{ + Store: oauthStore, + Clock: clockClock, + IdentityConfig: identityConfig, + } + anonymousStore := &anonymous.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + anonymousProvider := &anonymous.Provider{ + Store: anonymousStore, + Clock: clockClock, + } + biometricStore := &biometric.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + biometricProvider := &biometric.Provider{ + Store: biometricStore, + Clock: clockClock, + } + passkeyStore := &passkey.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + store2 := &passkey2.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + defaultLanguageTag := deps.ProvideDefaultLanguageTag(config) + supportedLanguageTags := deps.ProvideSupportedLanguageTags(config) + templateResolver := &template.Resolver{ + Resources: manager, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + } + engine := &template.Engine{ + Resolver: templateResolver, + } + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) + webAppCDNHost := environmentConfig.WebAppCDNHost + globalEmbeddedResourceManager := rootProvider.EmbeddedResources + staticAssetResolver := &web.StaticAssetResolver{ + Context: contextContext, + Localization: localizationConfig, + HTTPOrigin: httpOrigin, + HTTPProto: httpProto, + WebAppCDNHost: webAppCDNHost, + Resources: manager, + EmbeddedResources: globalEmbeddedResourceManager, + } + translationService := &translation.Service{ + Context: contextContext, + TemplateEngine: engine, + StaticAssets: staticAssetResolver, + } + configService := &passkey2.ConfigService{ + Request: request, + TrustProxy: trustProxy, + TranslationService: translationService, + } + passkeyService := &passkey2.Service{ + Store: store2, + ConfigService: configService, + } + passkeyProvider := &passkey.Provider{ + Store: passkeyStore, + Clock: clockClock, + Passkey: passkeyService, + } + siweStore := &siwe.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + web3Config := appConfig.Web3 + storeRedis := &siwe2.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + ratelimitLogger := ratelimit.NewLogger(factory) + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) + rateLimitsFeatureConfig := featureConfig.RateLimits + limiter := &ratelimit.Limiter{ + Logger: ratelimitLogger, + Storage: storageRedis, + AppID: appID, + Config: rateLimitsFeatureConfig, + } + siweLogger := siwe2.NewLogger(factory) + siweService := &siwe2.Service{ + RemoteIP: remoteIP, + HTTPOrigin: httpOrigin, + Web3Config: web3Config, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + NonceStore: storeRedis, + RateLimiter: limiter, + Logger: siweLogger, + } + siweProvider := &siwe.Provider{ + Store: siweStore, + Clock: clockClock, + SIWE: siweService, + } + ldapStore := &ldap.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + normalizer := &stdattrs.Normalizer{ + LoginIDNormalizerFactory: normalizerFactory, + } + ldapProvider := &ldap.Provider{ + Store: ldapStore, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + } + serviceService := &service.Service{ + Authentication: authenticationConfig, + Identity: identityConfig, + IdentityFeatureConfig: identityFeatureConfig, + Store: serviceStore, + LoginID: provider, + OAuth: oauthProvider, + Anonymous: anonymousProvider, + Biometric: biometricProvider, + Passkey: passkeyProvider, + SIWE: siweProvider, + LDAP: ldapProvider, + } + store3 := &service2.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + passwordStore := &password.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorConfig := appConfig.Authenticator + authenticatorPasswordConfig := authenticatorConfig.Password + passwordLogger := password.NewLogger(factory) + historyStore := &password.HistoryStore{ + Clock: clockClock, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorFeatureConfig := featureConfig.Authenticator + passwordChecker := password.ProvideChecker(authenticatorPasswordConfig, authenticatorFeatureConfig, historyStore) + expiry := password.ProvideExpiry(authenticatorPasswordConfig, clockClock) + housekeeperLogger := password.NewHousekeeperLogger(factory) + housekeeper := &password.Housekeeper{ + Store: historyStore, + Logger: housekeeperLogger, + Config: authenticatorPasswordConfig, + } + passwordProvider := &password.Provider{ + Store: passwordStore, + Config: authenticatorPasswordConfig, + Clock: clockClock, + Logger: passwordLogger, + PasswordHistory: historyStore, + PasswordChecker: passwordChecker, + Expiry: expiry, + Housekeeper: housekeeper, + } + store4 := &passkey3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + provider2 := &passkey3.Provider{ + Store: store4, + Clock: clockClock, + Passkey: passkeyService, + } + totpStore := &totp.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorTOTPConfig := authenticatorConfig.TOTP + totpProvider := &totp.Provider{ + Store: totpStore, + Config: authenticatorTOTPConfig, + Clock: clockClock, + } + oobStore := &oob.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + oobProvider := &oob.Provider{ + Store: oobStore, + LoginIDNormalizerFactory: normalizerFactory, + Clock: clockClock, + } + testModeConfig := appConfig.TestMode + testModeFeatureConfig := featureConfig.TestMode + codeStoreRedis := &otp.CodeStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + lookupStoreRedis := &otp.LookupStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + attemptTrackerRedis := &otp.AttemptTrackerRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + otpLogger := otp.NewLogger(factory) + otpService := &otp.Service{ + Clock: clockClock, + AppID: appID, + TestModeConfig: testModeConfig, + TestModeFeatureConfig: testModeFeatureConfig, + RemoteIP: remoteIP, + CodeStore: codeStoreRedis, + LookupStore: lookupStoreRedis, + AttemptTracker: attemptTrackerRedis, + Logger: otpLogger, + RateLimiter: limiter, + } + rateLimits := service2.RateLimits{ + IP: remoteIP, + Config: authenticationConfig, + RateLimiter: limiter, + } + authenticationLockoutConfig := authenticationConfig.Lockout + lockoutLogger := lockout.NewLogger(factory) + lockoutStorageRedis := &lockout.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + lockoutService := &lockout.Service{ + Logger: lockoutLogger, + Storage: lockoutStorageRedis, + } + serviceLockout := service2.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + service3 := &service2.Service{ + Store: store3, + Config: appConfig, + Password: passwordProvider, + Passkey: provider2, + TOTP: totpProvider, + OOBOTP: oobProvider, + OTPCodeService: otpService, + RateLimits: rateLimits, + Lockout: serviceLockout, + } + verificationConfig := appConfig.Verification + userProfileConfig := appConfig.UserProfile + storePQ := &verification.StorePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + verificationService := &verification.Service{ + Config: verificationConfig, + UserProfileConfig: userProfileConfig, + Clock: clockClock, + ClaimStore: storePQ, + } + imagesCDNHost := environmentConfig.ImagesCDNHost + pictureTransformer := &stdattrs2.PictureTransformer{ + HTTPProto: httpProto, + HTTPHost: httpHost, + ImagesCDNHost: imagesCDNHost, + } + serviceNoEvent := &stdattrs2.ServiceNoEvent{ + UserProfileConfig: userProfileConfig, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + ClaimStore: storePQ, + Transformer: pictureTransformer, + } + customattrsServiceNoEvent := &customattrs.ServiceNoEvent{ + Config: userProfileConfig, + UserQueries: rawQueries, + UserStore: userStore, + } + nftIndexerAPIEndpoint := environmentConfig.NFTIndexerAPIEndpoint + web3Service := &web3.Service{ + APIEndpoint: nftIndexerAPIEndpoint, + Web3Config: web3Config, + } + rolesgroupsStore := &rolesgroups.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + queries := &rolesgroups.Queries{ + Store: rolesgroupsStore, + } + userQueries := &user.Queries{ + RawQueries: rawQueries, + Store: userStore, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + resolverImpl := &event.ResolverImpl{ + Users: userQueries, + } + hookLogger := hook.NewLogger(factory) + hookConfig := appConfig.Hook + webHookLogger := hook.NewWebHookLogger(factory) + webhookKeyMaterials := deps.ProvideWebhookKeyMaterials(secretConfig) + webHookImpl := hook.WebHookImpl{ + Logger: webHookLogger, + Secret: webhookKeyMaterials, + } + syncHTTPClient := hook.NewSyncHTTPClient(hookConfig) + asyncHTTPClient := hook.NewAsyncHTTPClient() + eventWebHookImpl := &hook.EventWebHookImpl{ + WebHookImpl: webHookImpl, + SyncHTTP: syncHTTPClient, + AsyncHTTP: asyncHTTPClient, + } + denoHookLogger := hook.NewDenoHookLogger(factory) + denoHook := hook.DenoHook{ + Context: contextContext, + ResourceManager: manager, + Logger: denoHookLogger, + } + denoEndpoint := environmentConfig.DenoEndpoint + syncDenoClient := hook.NewSyncDenoClient(denoEndpoint, hookConfig, hookLogger) + asyncDenoClient := hook.NewAsyncDenoClient(denoEndpoint, hookLogger) + eventDenoHookImpl := &hook.EventDenoHookImpl{ + DenoHook: denoHook, + SyncDenoClient: syncDenoClient, + AsyncDenoClient: asyncDenoClient, + } + commands := &rolesgroups.Commands{ + Store: rolesgroupsStore, + } + sink := &hook.Sink{ + Logger: hookLogger, + Config: hookConfig, + Clock: clockClock, + EventWebHook: eventWebHookImpl, + EventDenoHook: eventDenoHookImpl, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + RolesAndGroups: commands, + } + auditLogger := audit.NewLogger(factory) + writeHandle := appProvider.AuditWriteDatabase + auditDatabaseCredentials := deps.ProvideAuditDatabaseCredentials(secretConfig) + auditdbSQLBuilderApp := auditdb.NewSQLBuilderApp(auditDatabaseCredentials, appID) + writeSQLExecutor := auditdb.NewWriteSQLExecutor(contextContext, writeHandle) + writeStore := &audit.WriteStore{ + SQLBuilder: auditdbSQLBuilderApp, + SQLExecutor: writeSQLExecutor, + } + auditSink := &audit.Sink{ + Logger: auditLogger, + Database: writeHandle, + Store: writeStore, + } + elasticsearchLogger := elasticsearch.NewLogger(factory) + elasticsearchServiceLogger := elasticsearch.NewElasticsearchServiceLogger(factory) + elasticsearchCredentials := deps.ProvideElasticsearchCredentials(secretConfig) + client := elasticsearch.NewClient(elasticsearchCredentials) + queue := appProvider.TaskQueue + userReindexProducer := redisqueue.NewUserReindexProducer(appredisHandle, clockClock) + elasticsearchService := elasticsearch.Service{ + Clock: clockClock, + Context: contextContext, + Database: handle, + Logger: elasticsearchServiceLogger, + AppID: appID, + Client: client, + Users: userQueries, + UserStore: userStore, + IdentityService: serviceService, + RolesGroups: rolesgroupsStore, + TaskQueue: queue, + Producer: userReindexProducer, + } + elasticsearchSink := &elasticsearch.Sink{ + Logger: elasticsearchLogger, + Service: elasticsearchService, + Database: handle, + } + eventService := event.NewService(contextContext, appID, remoteIP, userAgentString, eventLogger, handle, clockClock, localizationConfig, storeImpl, resolverImpl, sink, auditSink, elasticsearchSink) + storeDeviceTokenRedis := &mfa.StoreDeviceTokenRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + storeRecoveryCodePQ := &mfa.StoreRecoveryCodePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + mfaLockout := mfa.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + mfaService := &mfa.Service{ + IP: remoteIP, + DeviceTokens: storeDeviceTokenRedis, + RecoveryCodes: storeRecoveryCodePQ, + Clock: clockClock, + Config: authenticationConfig, + RateLimiter: limiter, + Lockout: mfaLockout, + } + messagingLogger := messaging.NewLogger(factory) + usageLogger := usage.NewLogger(factory) + usageLimiter := &usage.Limiter{ + Logger: usageLogger, + Clock: clockClock, + AppID: appID, + Redis: appredisHandle, + } + messagingConfig := appConfig.Messaging + messagingRateLimitsConfig := messagingConfig.RateLimits + messagingFeatureConfig := featureConfig.Messaging + rateLimitsEnvironmentConfig := &environmentConfig.RateLimits + limits := messaging.Limits{ + Logger: messagingLogger, + RateLimiter: limiter, + UsageLimiter: usageLimiter, + RemoteIP: remoteIP, + Config: messagingRateLimitsConfig, + FeatureConfig: messagingFeatureConfig, + EnvConfig: rateLimitsEnvironmentConfig, + } + whatsappServiceLogger := whatsapp.NewServiceLogger(factory) + devMode := environmentConfig.DevMode + featureTestModeWhatsappSuppressed := deps.ProvideTestModeWhatsappSuppressed(testModeFeatureConfig) + testModeWhatsappConfig := testModeConfig.Whatsapp + whatsappConfig := messagingConfig.Whatsapp + whatsappOnPremisesCredentials := deps.ProvideWhatsappOnPremisesCredentials(secretConfig) + tokenStore := &whatsapp.TokenStore{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + onPremisesClient := whatsapp.NewWhatsappOnPremisesClient(whatsappConfig, whatsappOnPremisesCredentials, tokenStore) + whatsappService := &whatsapp.Service{ + Context: contextContext, + Logger: whatsappServiceLogger, + DevMode: devMode, + FeatureTestModeWhatsappSuppressed: featureTestModeWhatsappSuppressed, + TestModeWhatsappConfig: testModeWhatsappConfig, + WhatsappConfig: whatsappConfig, + LocalizationConfig: localizationConfig, + OnPremisesClient: onPremisesClient, + TokenStore: tokenStore, + } + sender := &messaging.Sender{ + Limits: limits, + TaskQueue: queue, + Events: eventService, + Whatsapp: whatsappService, + MessagingFeatureConfig: messagingFeatureConfig, + } + forgotpasswordSender := &forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + rawCommands := &user.RawCommands{ + Store: userStore, + Clock: clockClock, + } + userCommands := &user.Commands{ + RawCommands: rawCommands, + RawQueries: rawQueries, + Events: eventService, + Verification: verificationService, + UserProfileConfig: userProfileConfig, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + stdattrsService := &stdattrs2.Service{ + UserProfileConfig: userProfileConfig, + ServiceNoEvent: serviceNoEvent, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + Events: eventService, + } + authorizationStore := &pq.AuthorizationStore{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + storeRedisLogger := idpsession.NewStoreRedisLogger(factory) + idpsessionStoreRedis := &idpsession.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + Logger: storeRedisLogger, + } + sessionConfig := appConfig.Session + cookieDef2 := session.NewSessionCookieDef(sessionConfig) + idpsessionManager := &idpsession.Manager{ + Store: idpsessionStoreRedis, + Config: sessionConfig, + Cookies: cookieManager, + CookieDef: cookieDef2, + } + eventStoreRedis := &access.EventStoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + eventProvider := &access.EventProvider{ + Store: eventStoreRedis, + } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } + idpsessionRand := _wireRandValue + idpsessionProvider := &idpsession.Provider{ + Context: contextContext, + RemoteIP: remoteIP, + UserAgentString: userAgentString, + AppID: appID, + Redis: appredisHandle, + Store: idpsessionStoreRedis, + AccessEvents: eventProvider, + MeterService: meterService, + TrustProxy: trustProxy, + Config: sessionConfig, + Clock: clockClock, + Random: idpsessionRand, + } + offlineGrantService := oauth2.OfflineGrantService{ + OAuthConfig: oAuthConfig, + Clock: clockClock, + IDPSessions: idpsessionProvider, + ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, + OfflineGrants: store, + } + sessionManager := &oauth2.SessionManager{ + Store: store, + Config: oAuthConfig, + Service: offlineGrantService, + } + accountDeletionConfig := appConfig.AccountDeletion + accountAnonymizationConfig := appConfig.AccountAnonymization + maxTrials := _wireMaxTrialsValue + passwordRand := password.NewRandSource() + generator := &password.Generator{ + MaxTrials: maxTrials, + Checker: passwordChecker, + Rand: passwordRand, + PasswordConfig: authenticatorPasswordConfig, + } + coordinator := &facade.Coordinator{ + Events: eventService, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + MFA: mfaService, + SendPassword: forgotpasswordSender, + UserCommands: userCommands, + UserQueries: userQueries, + RolesGroupsCommands: commands, + StdAttrsService: stdattrsService, + PasswordHistory: historyStore, + OAuth: authorizationStore, + IDPSessions: idpsessionManager, + OAuthSessions: sessionManager, + IdentityConfig: identityConfig, + AccountDeletionConfig: accountDeletionConfig, + AccountAnonymizationConfig: accountAnonymizationConfig, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + PasswordGenerator: generator, + } + identityFacade := facade.IdentityFacade{ + Coordinator: coordinator, + } + authenticatorFacade := facade.AuthenticatorFacade{ + Coordinator: coordinator, + } + anonymousStoreRedis := &anonymous.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + messageSender := &otp.MessageSender{ + AppID: appID, + Translation: translationService, + Endpoints: endpointsEndpoints, + Sender: sender, + WhatsappService: whatsappService, + } + oAuthSSOProviderCredentials := deps.ProvideOAuthSSOProviderCredentials(secretConfig) + oAuthHTTPClient := sso.ProvideOAuthHTTPClient(environmentConfig) + simpleStoreRedisFactory := &sso.SimpleStoreRedisFactory{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + } + oAuthProviderFactory := &sso.OAuthProviderFactory{ + IdentityConfig: identityConfig, + Credentials: oAuthSSOProviderCredentials, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + HTTPClient: oAuthHTTPClient, + SimpleStoreRedisFactory: simpleStoreRedisFactory, + } + webappoauthStore := &webappoauth.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } + forgotpasswordLogger := forgotpassword.NewLogger(factory) + sender2 := forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + forgotpasswordService := &forgotpassword.Service{ + Logger: forgotpasswordLogger, + Config: appConfig, + FeatureConfig: featureConfig, + Identities: serviceService, + Authenticators: authenticatorFacade, + OTPCodes: otpService, + OTPSender: messageSender, + PasswordSender: sender2, + } + responseWriter := p.ResponseWriter + nonceService := &nonce.Service{ + Cookies: cookieManager, + Request: request, + ResponseWriter: responseWriter, + } + challengeProvider := &challenge.Provider{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + userProvider := &user.Provider{ + Commands: userCommands, + Queries: userQueries, + } + authenticationinfoStoreRedis := &authenticationinfo.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + manager2 := &session.Manager{ + IDPSessions: idpsessionManager, + AccessTokenSessions: sessionManager, + Events: eventService, + } + oauthsessionStoreRedis := &oauthsession.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + interactionContext := &interaction.Context{ + Request: request, + RemoteIP: remoteIP, + Database: sqlExecutor, + Clock: clockClock, + Config: appConfig, + FeatureConfig: featureConfig, + OAuthClientResolver: resolver, + OfflineGrants: store, + Identities: identityFacade, + Authenticators: authenticatorFacade, + AnonymousIdentities: anonymousProvider, + AnonymousUserPromotionCodeStore: anonymousStoreRedis, + BiometricIdentities: biometricProvider, + OTPCodeService: otpService, + OTPSender: messageSender, + OAuthProviderFactory: oAuthProviderFactory, + OAuthRedirectURIBuilder: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + MFA: mfaFacade, + ForgotPassword: forgotpasswordService, + ResetPassword: forgotpasswordService, + Passkey: passkeyService, + Verification: verificationService, + RateLimiter: limiter, + PasswordGenerator: generator, + Nonces: nonceService, + Challenges: challengeProvider, + Users: userProvider, + StdAttrsService: stdattrsService, + Events: eventService, + CookieManager: cookieManager, + AuthenticationInfoService: authenticationinfoStoreRedis, + Sessions: idpsessionProvider, + SessionManager: manager2, + SessionCookie: cookieDef2, + OAuthSessions: oauthsessionStoreRedis, + MFADeviceTokenCookie: cookieDef, + } + interactionStoreRedis := &interaction.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + interactionService := &interaction.Service{ + Logger: logger, + Context: interactionContext, + Store: interactionStoreRedis, + } + webappService2 := &webapp2.Service2{ + Logger: serviceLogger, + Request: request, + Sessions: sessionStoreRedis, + SessionCookie: sessionCookieDef, + SignedUpCookie: signedUpCookieDef, + MFADeviceTokenCookie: cookieDef, + ErrorService: errorService, + Cookies: cookieManager, + OAuthConfig: oAuthConfig, + UIConfig: uiConfig, + TrustProxy: trustProxy, + UIInfoResolver: uiService, + OAuthClientResolver: resolver, + Graph: interactionService, + } + uiFeatureConfig := featureConfig.UI + forgotPasswordConfig := appConfig.ForgotPassword + googleTagManagerConfig := appConfig.GoogleTagManager + botProtectionConfig := appConfig.BotProtection + flashMessage := &httputil.FlashMessage{ + Cookies: cookieManager, + } + authUISentryDSN := environmentConfig.AuthUISentryDSN + authUIWindowMessageAllowedOrigins := environmentConfig.AuthUIWindowMessageAllowedOrigins + baseLogger := viewmodels.NewBaseLogger(factory) + baseViewModeler := &viewmodels.BaseViewModeler{ + TrustProxy: trustProxy, + OAuth: oAuthConfig, + AuthUI: uiConfig, + AuthUIFeatureConfig: uiFeatureConfig, + StaticAssets: staticAssetResolver, + ForgotPassword: forgotPasswordConfig, + Authentication: authenticationConfig, + GoogleTagManager: googleTagManagerConfig, + BotProtection: botProtectionConfig, + ErrorService: errorService, + Translations: translationService, + Clock: clockClock, + FlashMessage: flashMessage, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + AuthUISentryDSN: authUISentryDSN, + AuthUIWindowMessageAllowedOrigins: authUIWindowMessageAllowedOrigins, + OAuthClientResolver: resolver, + Logger: baseLogger, + } + responseRenderer := &webapp.ResponseRenderer{ + TemplateEngine: engine, + } + publisher := webapp.NewPublisher(appID, appredisHandle) + authflowNavigator := &webapp2.AuthflowNavigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + authflowV2Navigator := &authflowv2.AuthflowV2Navigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + errorRenderer := &webapp.ErrorRenderer{ + ErrorService: errorService, + UIImplementationService: uiImplementationService, + AuthflowV1Navigator: authflowNavigator, + AuthflowV2Navigator: authflowV2Navigator, + } + controllerDeps := webapp.ControllerDeps{ + Database: handle, + RedisHandle: appredisHandle, + AppID: appID, + Page: webappService2, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + Publisher: publisher, + Clock: clockClock, + TesterEndpointsProvider: endpointsEndpoints, + ErrorRenderer: errorRenderer, + TrustProxy: trustProxy, + } + controllerFactory := webapp.ControllerFactory{ + LoggerFactory: factory, + ControllerDeps: controllerDeps, + } + biometricConfig := identityConfig.Biometric + settingsViewModeler := &viewmodels.SettingsViewModeler{ + Authenticators: service3, + MFA: mfaService, + Authentication: authenticationConfig, + Biometric: biometricConfig, + } + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, + } + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } + authflowV2SettingsMFACreatePasswordHandler := &authflowv2.AuthflowV2SettingsMFACreatePasswordHandler{ + ControllerFactory: controllerFactory, + BaseViewModel: baseViewModeler, + SettingsViewModel: settingsViewModeler, + PasswordPolicy: passwordChecker, + Renderer: responseRenderer, + AccountManagementService: accountmanagementService, + } + return authflowV2SettingsMFACreatePasswordHandler +} + +func newWebAppAuthflowV2SettingsMFAPasswordHandler(p *deps.RequestProvider) http.Handler { + appProvider := p.AppProvider + handle := appProvider.AppDatabase + factory := appProvider.LoggerFactory + appredisHandle := appProvider.Redis + appContext := appProvider.AppContext + config := appContext.Config + appConfig := config.AppConfig + appID := appConfig.ID + serviceLogger := webapp2.NewServiceLogger(factory) + request := p.Request + sessionStoreRedis := &webapp2.SessionStoreRedis{ + AppID: appID, + Redis: appredisHandle, + } + sessionCookieDef := webapp2.NewSessionCookieDef() + signedUpCookieDef := webapp2.NewSignedUpCookieDef() + authenticationConfig := appConfig.Authentication + cookieDef := mfa.NewDeviceTokenCookieDef(authenticationConfig) + errorTokenCookieDef := webapp2.NewErrorTokenCookieDef() + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpConfig := appConfig.HTTP + cookieManager := deps.NewCookieManager(request, trustProxy, httpConfig) + errorService := &webapp2.ErrorService{ + AppID: appID, + Cookie: errorTokenCookieDef, + RedisHandle: appredisHandle, + Cookies: cookieManager, + } + oAuthConfig := appConfig.OAuth + uiConfig := appConfig.UI + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpProto := deps.ProvideHTTPProto(request, trustProxy) + globalUIImplementation := environmentConfig.UIImplementation + globalUISettingsImplementation := environmentConfig.UISettingsImplementation + uiImplementationService := &web.UIImplementationService{ + UIConfig: uiConfig, + GlobalUIImplementation: globalUIImplementation, + GlobalUISettingsImplementation: globalUISettingsImplementation, + } + endpointsEndpoints := &endpoints.Endpoints{ + HTTPHost: httpHost, + HTTPProto: httpProto, + UIImplementationService: uiImplementationService, + } + uiService := &authenticationinfo.UIService{ + EndpointsProvider: endpointsEndpoints, + } + resolver := &oauthclient.Resolver{ + OAuthConfig: oAuthConfig, + TesterEndpoints: endpointsEndpoints, + } + logger := interaction.NewLogger(factory) + remoteIP := deps.ProvideRemoteIP(request, trustProxy) + contextContext := deps.ProvideRequestContext(request) + sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) + clockClock := _wireSystemClockValue + featureConfig := config.FeatureConfig + redisLogger := redis.NewLogger(factory) + secretConfig := config.SecretConfig + databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) + sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) + store := &redis.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Logger: redisLogger, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + userAgentString := deps.ProvideUserAgentString(request) + eventLogger := event.NewLogger(factory) + localizationConfig := appConfig.Localization + sqlBuilder := appdb.NewSQLBuilder(databaseCredentials) + storeImpl := event.NewStoreImpl(sqlBuilder, sqlExecutor) + userStore := &user.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + AppID: appID, + } + rawQueries := &user.RawQueries{ + Store: userStore, + } + identityConfig := appConfig.Identity + identityFeatureConfig := featureConfig.Identity + serviceStore := &service.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginidStore := &loginid.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginIDConfig := identityConfig.LoginID + manager := appContext.Resources + typeCheckerFactory := &loginid.TypeCheckerFactory{ + UIConfig: uiConfig, + LoginIDConfig: loginIDConfig, + Resources: manager, + } + checker := &loginid.Checker{ + Config: loginIDConfig, + TypeCheckerFactory: typeCheckerFactory, + } + normalizerFactory := &loginid.NormalizerFactory{ + Config: loginIDConfig, + } + provider := &loginid.Provider{ + Store: loginidStore, + Config: loginIDConfig, + Checker: checker, + NormalizerFactory: normalizerFactory, + Clock: clockClock, + } + oauthStore := &oauth3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + IdentityConfig: identityConfig, + } + oauthProvider := &oauth3.Provider{ + Store: oauthStore, + Clock: clockClock, + IdentityConfig: identityConfig, + } + anonymousStore := &anonymous.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + anonymousProvider := &anonymous.Provider{ + Store: anonymousStore, + Clock: clockClock, + } + biometricStore := &biometric.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + biometricProvider := &biometric.Provider{ + Store: biometricStore, + Clock: clockClock, + } + passkeyStore := &passkey.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + store2 := &passkey2.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + defaultLanguageTag := deps.ProvideDefaultLanguageTag(config) + supportedLanguageTags := deps.ProvideSupportedLanguageTags(config) + templateResolver := &template.Resolver{ + Resources: manager, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + } + engine := &template.Engine{ + Resolver: templateResolver, + } + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) + webAppCDNHost := environmentConfig.WebAppCDNHost + globalEmbeddedResourceManager := rootProvider.EmbeddedResources + staticAssetResolver := &web.StaticAssetResolver{ + Context: contextContext, + Localization: localizationConfig, + HTTPOrigin: httpOrigin, + HTTPProto: httpProto, + WebAppCDNHost: webAppCDNHost, + Resources: manager, + EmbeddedResources: globalEmbeddedResourceManager, + } + translationService := &translation.Service{ + Context: contextContext, + TemplateEngine: engine, + StaticAssets: staticAssetResolver, + } + configService := &passkey2.ConfigService{ + Request: request, + TrustProxy: trustProxy, + TranslationService: translationService, + } + passkeyService := &passkey2.Service{ + Store: store2, + ConfigService: configService, + } + passkeyProvider := &passkey.Provider{ + Store: passkeyStore, + Clock: clockClock, + Passkey: passkeyService, + } + siweStore := &siwe.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + web3Config := appConfig.Web3 + storeRedis := &siwe2.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + ratelimitLogger := ratelimit.NewLogger(factory) + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) + rateLimitsFeatureConfig := featureConfig.RateLimits + limiter := &ratelimit.Limiter{ + Logger: ratelimitLogger, + Storage: storageRedis, + AppID: appID, + Config: rateLimitsFeatureConfig, + } + siweLogger := siwe2.NewLogger(factory) + siweService := &siwe2.Service{ + RemoteIP: remoteIP, + HTTPOrigin: httpOrigin, + Web3Config: web3Config, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + NonceStore: storeRedis, + RateLimiter: limiter, + Logger: siweLogger, + } + siweProvider := &siwe.Provider{ + Store: siweStore, + Clock: clockClock, + SIWE: siweService, + } + ldapStore := &ldap.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + normalizer := &stdattrs.Normalizer{ + LoginIDNormalizerFactory: normalizerFactory, + } + ldapProvider := &ldap.Provider{ + Store: ldapStore, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + } + serviceService := &service.Service{ + Authentication: authenticationConfig, + Identity: identityConfig, + IdentityFeatureConfig: identityFeatureConfig, + Store: serviceStore, + LoginID: provider, + OAuth: oauthProvider, + Anonymous: anonymousProvider, + Biometric: biometricProvider, + Passkey: passkeyProvider, + SIWE: siweProvider, + LDAP: ldapProvider, + } + store3 := &service2.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + passwordStore := &password.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorConfig := appConfig.Authenticator + authenticatorPasswordConfig := authenticatorConfig.Password + passwordLogger := password.NewLogger(factory) + historyStore := &password.HistoryStore{ + Clock: clockClock, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorFeatureConfig := featureConfig.Authenticator + passwordChecker := password.ProvideChecker(authenticatorPasswordConfig, authenticatorFeatureConfig, historyStore) + expiry := password.ProvideExpiry(authenticatorPasswordConfig, clockClock) + housekeeperLogger := password.NewHousekeeperLogger(factory) + housekeeper := &password.Housekeeper{ + Store: historyStore, + Logger: housekeeperLogger, + Config: authenticatorPasswordConfig, + } + passwordProvider := &password.Provider{ + Store: passwordStore, + Config: authenticatorPasswordConfig, + Clock: clockClock, + Logger: passwordLogger, + PasswordHistory: historyStore, + PasswordChecker: passwordChecker, + Expiry: expiry, + Housekeeper: housekeeper, + } + store4 := &passkey3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + provider2 := &passkey3.Provider{ + Store: store4, + Clock: clockClock, + Passkey: passkeyService, + } + totpStore := &totp.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorTOTPConfig := authenticatorConfig.TOTP + totpProvider := &totp.Provider{ + Store: totpStore, + Config: authenticatorTOTPConfig, + Clock: clockClock, + } + oobStore := &oob.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + oobProvider := &oob.Provider{ + Store: oobStore, + LoginIDNormalizerFactory: normalizerFactory, + Clock: clockClock, + } + testModeConfig := appConfig.TestMode + testModeFeatureConfig := featureConfig.TestMode + codeStoreRedis := &otp.CodeStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + lookupStoreRedis := &otp.LookupStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + attemptTrackerRedis := &otp.AttemptTrackerRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + otpLogger := otp.NewLogger(factory) + otpService := &otp.Service{ + Clock: clockClock, + AppID: appID, + TestModeConfig: testModeConfig, + TestModeFeatureConfig: testModeFeatureConfig, + RemoteIP: remoteIP, + CodeStore: codeStoreRedis, + LookupStore: lookupStoreRedis, + AttemptTracker: attemptTrackerRedis, + Logger: otpLogger, + RateLimiter: limiter, + } + rateLimits := service2.RateLimits{ + IP: remoteIP, + Config: authenticationConfig, + RateLimiter: limiter, + } + authenticationLockoutConfig := authenticationConfig.Lockout + lockoutLogger := lockout.NewLogger(factory) + lockoutStorageRedis := &lockout.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + lockoutService := &lockout.Service{ + Logger: lockoutLogger, + Storage: lockoutStorageRedis, + } + serviceLockout := service2.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + service3 := &service2.Service{ + Store: store3, + Config: appConfig, + Password: passwordProvider, + Passkey: provider2, + TOTP: totpProvider, + OOBOTP: oobProvider, + OTPCodeService: otpService, + RateLimits: rateLimits, + Lockout: serviceLockout, + } + verificationConfig := appConfig.Verification + userProfileConfig := appConfig.UserProfile + storePQ := &verification.StorePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + verificationService := &verification.Service{ + Config: verificationConfig, + UserProfileConfig: userProfileConfig, + Clock: clockClock, + ClaimStore: storePQ, + } + imagesCDNHost := environmentConfig.ImagesCDNHost + pictureTransformer := &stdattrs2.PictureTransformer{ + HTTPProto: httpProto, + HTTPHost: httpHost, + ImagesCDNHost: imagesCDNHost, + } + serviceNoEvent := &stdattrs2.ServiceNoEvent{ + UserProfileConfig: userProfileConfig, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + ClaimStore: storePQ, + Transformer: pictureTransformer, + } + customattrsServiceNoEvent := &customattrs.ServiceNoEvent{ + Config: userProfileConfig, + UserQueries: rawQueries, + UserStore: userStore, + } + nftIndexerAPIEndpoint := environmentConfig.NFTIndexerAPIEndpoint + web3Service := &web3.Service{ + APIEndpoint: nftIndexerAPIEndpoint, + Web3Config: web3Config, + } + rolesgroupsStore := &rolesgroups.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + queries := &rolesgroups.Queries{ + Store: rolesgroupsStore, + } + userQueries := &user.Queries{ + RawQueries: rawQueries, + Store: userStore, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + resolverImpl := &event.ResolverImpl{ + Users: userQueries, + } + hookLogger := hook.NewLogger(factory) + hookConfig := appConfig.Hook + webHookLogger := hook.NewWebHookLogger(factory) + webhookKeyMaterials := deps.ProvideWebhookKeyMaterials(secretConfig) + webHookImpl := hook.WebHookImpl{ + Logger: webHookLogger, + Secret: webhookKeyMaterials, + } + syncHTTPClient := hook.NewSyncHTTPClient(hookConfig) + asyncHTTPClient := hook.NewAsyncHTTPClient() + eventWebHookImpl := &hook.EventWebHookImpl{ + WebHookImpl: webHookImpl, + SyncHTTP: syncHTTPClient, + AsyncHTTP: asyncHTTPClient, + } + denoHookLogger := hook.NewDenoHookLogger(factory) + denoHook := hook.DenoHook{ + Context: contextContext, + ResourceManager: manager, + Logger: denoHookLogger, + } + denoEndpoint := environmentConfig.DenoEndpoint + syncDenoClient := hook.NewSyncDenoClient(denoEndpoint, hookConfig, hookLogger) + asyncDenoClient := hook.NewAsyncDenoClient(denoEndpoint, hookLogger) + eventDenoHookImpl := &hook.EventDenoHookImpl{ + DenoHook: denoHook, + SyncDenoClient: syncDenoClient, + AsyncDenoClient: asyncDenoClient, + } + commands := &rolesgroups.Commands{ + Store: rolesgroupsStore, + } + sink := &hook.Sink{ + Logger: hookLogger, + Config: hookConfig, + Clock: clockClock, + EventWebHook: eventWebHookImpl, + EventDenoHook: eventDenoHookImpl, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + RolesAndGroups: commands, + } + auditLogger := audit.NewLogger(factory) + writeHandle := appProvider.AuditWriteDatabase + auditDatabaseCredentials := deps.ProvideAuditDatabaseCredentials(secretConfig) + auditdbSQLBuilderApp := auditdb.NewSQLBuilderApp(auditDatabaseCredentials, appID) + writeSQLExecutor := auditdb.NewWriteSQLExecutor(contextContext, writeHandle) + writeStore := &audit.WriteStore{ + SQLBuilder: auditdbSQLBuilderApp, + SQLExecutor: writeSQLExecutor, + } + auditSink := &audit.Sink{ + Logger: auditLogger, + Database: writeHandle, + Store: writeStore, + } + elasticsearchLogger := elasticsearch.NewLogger(factory) + elasticsearchServiceLogger := elasticsearch.NewElasticsearchServiceLogger(factory) + elasticsearchCredentials := deps.ProvideElasticsearchCredentials(secretConfig) + client := elasticsearch.NewClient(elasticsearchCredentials) + queue := appProvider.TaskQueue + userReindexProducer := redisqueue.NewUserReindexProducer(appredisHandle, clockClock) + elasticsearchService := elasticsearch.Service{ + Clock: clockClock, + Context: contextContext, + Database: handle, + Logger: elasticsearchServiceLogger, + AppID: appID, + Client: client, + Users: userQueries, + UserStore: userStore, + IdentityService: serviceService, + RolesGroups: rolesgroupsStore, + TaskQueue: queue, + Producer: userReindexProducer, + } + elasticsearchSink := &elasticsearch.Sink{ + Logger: elasticsearchLogger, + Service: elasticsearchService, + Database: handle, + } + eventService := event.NewService(contextContext, appID, remoteIP, userAgentString, eventLogger, handle, clockClock, localizationConfig, storeImpl, resolverImpl, sink, auditSink, elasticsearchSink) + storeDeviceTokenRedis := &mfa.StoreDeviceTokenRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + storeRecoveryCodePQ := &mfa.StoreRecoveryCodePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + mfaLockout := mfa.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + mfaService := &mfa.Service{ + IP: remoteIP, + DeviceTokens: storeDeviceTokenRedis, + RecoveryCodes: storeRecoveryCodePQ, + Clock: clockClock, + Config: authenticationConfig, + RateLimiter: limiter, + Lockout: mfaLockout, + } + messagingLogger := messaging.NewLogger(factory) + usageLogger := usage.NewLogger(factory) + usageLimiter := &usage.Limiter{ + Logger: usageLogger, + Clock: clockClock, + AppID: appID, + Redis: appredisHandle, + } + messagingConfig := appConfig.Messaging + messagingRateLimitsConfig := messagingConfig.RateLimits + messagingFeatureConfig := featureConfig.Messaging + rateLimitsEnvironmentConfig := &environmentConfig.RateLimits + limits := messaging.Limits{ + Logger: messagingLogger, + RateLimiter: limiter, + UsageLimiter: usageLimiter, + RemoteIP: remoteIP, + Config: messagingRateLimitsConfig, + FeatureConfig: messagingFeatureConfig, + EnvConfig: rateLimitsEnvironmentConfig, + } + whatsappServiceLogger := whatsapp.NewServiceLogger(factory) + devMode := environmentConfig.DevMode + featureTestModeWhatsappSuppressed := deps.ProvideTestModeWhatsappSuppressed(testModeFeatureConfig) + testModeWhatsappConfig := testModeConfig.Whatsapp + whatsappConfig := messagingConfig.Whatsapp + whatsappOnPremisesCredentials := deps.ProvideWhatsappOnPremisesCredentials(secretConfig) + tokenStore := &whatsapp.TokenStore{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + onPremisesClient := whatsapp.NewWhatsappOnPremisesClient(whatsappConfig, whatsappOnPremisesCredentials, tokenStore) + whatsappService := &whatsapp.Service{ + Context: contextContext, + Logger: whatsappServiceLogger, + DevMode: devMode, + FeatureTestModeWhatsappSuppressed: featureTestModeWhatsappSuppressed, + TestModeWhatsappConfig: testModeWhatsappConfig, + WhatsappConfig: whatsappConfig, + LocalizationConfig: localizationConfig, + OnPremisesClient: onPremisesClient, + TokenStore: tokenStore, + } + sender := &messaging.Sender{ + Limits: limits, + TaskQueue: queue, + Events: eventService, + Whatsapp: whatsappService, + MessagingFeatureConfig: messagingFeatureConfig, + } + forgotpasswordSender := &forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + rawCommands := &user.RawCommands{ + Store: userStore, + Clock: clockClock, + } + userCommands := &user.Commands{ + RawCommands: rawCommands, + RawQueries: rawQueries, + Events: eventService, + Verification: verificationService, + UserProfileConfig: userProfileConfig, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + stdattrsService := &stdattrs2.Service{ + UserProfileConfig: userProfileConfig, + ServiceNoEvent: serviceNoEvent, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + Events: eventService, + } + authorizationStore := &pq.AuthorizationStore{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + storeRedisLogger := idpsession.NewStoreRedisLogger(factory) + idpsessionStoreRedis := &idpsession.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + Logger: storeRedisLogger, + } + sessionConfig := appConfig.Session + cookieDef2 := session.NewSessionCookieDef(sessionConfig) + idpsessionManager := &idpsession.Manager{ + Store: idpsessionStoreRedis, + Config: sessionConfig, + Cookies: cookieManager, + CookieDef: cookieDef2, + } + eventStoreRedis := &access.EventStoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + eventProvider := &access.EventProvider{ + Store: eventStoreRedis, + } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } + idpsessionRand := _wireRandValue + idpsessionProvider := &idpsession.Provider{ + Context: contextContext, + RemoteIP: remoteIP, + UserAgentString: userAgentString, + AppID: appID, + Redis: appredisHandle, + Store: idpsessionStoreRedis, + AccessEvents: eventProvider, + MeterService: meterService, + TrustProxy: trustProxy, + Config: sessionConfig, + Clock: clockClock, + Random: idpsessionRand, + } + offlineGrantService := oauth2.OfflineGrantService{ + OAuthConfig: oAuthConfig, + Clock: clockClock, + IDPSessions: idpsessionProvider, + ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, + OfflineGrants: store, + } + sessionManager := &oauth2.SessionManager{ + Store: store, + Config: oAuthConfig, + Service: offlineGrantService, + } + accountDeletionConfig := appConfig.AccountDeletion + accountAnonymizationConfig := appConfig.AccountAnonymization + maxTrials := _wireMaxTrialsValue + passwordRand := password.NewRandSource() + generator := &password.Generator{ + MaxTrials: maxTrials, + Checker: passwordChecker, + Rand: passwordRand, + PasswordConfig: authenticatorPasswordConfig, + } + coordinator := &facade.Coordinator{ + Events: eventService, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + MFA: mfaService, + SendPassword: forgotpasswordSender, + UserCommands: userCommands, + UserQueries: userQueries, + RolesGroupsCommands: commands, + StdAttrsService: stdattrsService, + PasswordHistory: historyStore, + OAuth: authorizationStore, + IDPSessions: idpsessionManager, + OAuthSessions: sessionManager, + IdentityConfig: identityConfig, + AccountDeletionConfig: accountDeletionConfig, + AccountAnonymizationConfig: accountAnonymizationConfig, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + PasswordGenerator: generator, + } + identityFacade := facade.IdentityFacade{ + Coordinator: coordinator, + } + authenticatorFacade := facade.AuthenticatorFacade{ + Coordinator: coordinator, + } + anonymousStoreRedis := &anonymous.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + messageSender := &otp.MessageSender{ + AppID: appID, + Translation: translationService, + Endpoints: endpointsEndpoints, + Sender: sender, + WhatsappService: whatsappService, + } + oAuthSSOProviderCredentials := deps.ProvideOAuthSSOProviderCredentials(secretConfig) + oAuthHTTPClient := sso.ProvideOAuthHTTPClient(environmentConfig) + simpleStoreRedisFactory := &sso.SimpleStoreRedisFactory{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + } + oAuthProviderFactory := &sso.OAuthProviderFactory{ + IdentityConfig: identityConfig, + Credentials: oAuthSSOProviderCredentials, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + HTTPClient: oAuthHTTPClient, + SimpleStoreRedisFactory: simpleStoreRedisFactory, + } + webappoauthStore := &webappoauth.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } + forgotpasswordLogger := forgotpassword.NewLogger(factory) + sender2 := forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + forgotpasswordService := &forgotpassword.Service{ + Logger: forgotpasswordLogger, + Config: appConfig, + FeatureConfig: featureConfig, + Identities: serviceService, + Authenticators: authenticatorFacade, + OTPCodes: otpService, + OTPSender: messageSender, + PasswordSender: sender2, + } + responseWriter := p.ResponseWriter + nonceService := &nonce.Service{ + Cookies: cookieManager, + Request: request, + ResponseWriter: responseWriter, + } + challengeProvider := &challenge.Provider{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + userProvider := &user.Provider{ + Commands: userCommands, + Queries: userQueries, + } + authenticationinfoStoreRedis := &authenticationinfo.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + manager2 := &session.Manager{ + IDPSessions: idpsessionManager, + AccessTokenSessions: sessionManager, + Events: eventService, + } + oauthsessionStoreRedis := &oauthsession.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + interactionContext := &interaction.Context{ + Request: request, + RemoteIP: remoteIP, + Database: sqlExecutor, + Clock: clockClock, + Config: appConfig, + FeatureConfig: featureConfig, + OAuthClientResolver: resolver, + OfflineGrants: store, + Identities: identityFacade, + Authenticators: authenticatorFacade, + AnonymousIdentities: anonymousProvider, + AnonymousUserPromotionCodeStore: anonymousStoreRedis, + BiometricIdentities: biometricProvider, + OTPCodeService: otpService, + OTPSender: messageSender, + OAuthProviderFactory: oAuthProviderFactory, + OAuthRedirectURIBuilder: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + MFA: mfaFacade, + ForgotPassword: forgotpasswordService, + ResetPassword: forgotpasswordService, + Passkey: passkeyService, + Verification: verificationService, + RateLimiter: limiter, + PasswordGenerator: generator, + Nonces: nonceService, + Challenges: challengeProvider, + Users: userProvider, + StdAttrsService: stdattrsService, + Events: eventService, + CookieManager: cookieManager, + AuthenticationInfoService: authenticationinfoStoreRedis, + Sessions: idpsessionProvider, + SessionManager: manager2, + SessionCookie: cookieDef2, + OAuthSessions: oauthsessionStoreRedis, + MFADeviceTokenCookie: cookieDef, + } + interactionStoreRedis := &interaction.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + interactionService := &interaction.Service{ + Logger: logger, + Context: interactionContext, + Store: interactionStoreRedis, + } + webappService2 := &webapp2.Service2{ + Logger: serviceLogger, + Request: request, + Sessions: sessionStoreRedis, + SessionCookie: sessionCookieDef, + SignedUpCookie: signedUpCookieDef, + MFADeviceTokenCookie: cookieDef, + ErrorService: errorService, + Cookies: cookieManager, + OAuthConfig: oAuthConfig, + UIConfig: uiConfig, + TrustProxy: trustProxy, + UIInfoResolver: uiService, + OAuthClientResolver: resolver, + Graph: interactionService, + } + uiFeatureConfig := featureConfig.UI + forgotPasswordConfig := appConfig.ForgotPassword + googleTagManagerConfig := appConfig.GoogleTagManager + botProtectionConfig := appConfig.BotProtection + flashMessage := &httputil.FlashMessage{ + Cookies: cookieManager, + } + authUISentryDSN := environmentConfig.AuthUISentryDSN + authUIWindowMessageAllowedOrigins := environmentConfig.AuthUIWindowMessageAllowedOrigins + baseLogger := viewmodels.NewBaseLogger(factory) + baseViewModeler := &viewmodels.BaseViewModeler{ + TrustProxy: trustProxy, + OAuth: oAuthConfig, + AuthUI: uiConfig, + AuthUIFeatureConfig: uiFeatureConfig, + StaticAssets: staticAssetResolver, + ForgotPassword: forgotPasswordConfig, + Authentication: authenticationConfig, + GoogleTagManager: googleTagManagerConfig, + BotProtection: botProtectionConfig, + ErrorService: errorService, + Translations: translationService, + Clock: clockClock, + FlashMessage: flashMessage, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + AuthUISentryDSN: authUISentryDSN, + AuthUIWindowMessageAllowedOrigins: authUIWindowMessageAllowedOrigins, + OAuthClientResolver: resolver, + Logger: baseLogger, + } + responseRenderer := &webapp.ResponseRenderer{ + TemplateEngine: engine, + } + publisher := webapp.NewPublisher(appID, appredisHandle) + authflowNavigator := &webapp2.AuthflowNavigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + authflowV2Navigator := &authflowv2.AuthflowV2Navigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + errorRenderer := &webapp.ErrorRenderer{ + ErrorService: errorService, + UIImplementationService: uiImplementationService, + AuthflowV1Navigator: authflowNavigator, + AuthflowV2Navigator: authflowV2Navigator, + } + controllerDeps := webapp.ControllerDeps{ + Database: handle, + RedisHandle: appredisHandle, + AppID: appID, + Page: webappService2, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + Publisher: publisher, + Clock: clockClock, + TesterEndpointsProvider: endpointsEndpoints, + ErrorRenderer: errorRenderer, + TrustProxy: trustProxy, + } + controllerFactory := webapp.ControllerFactory{ + LoggerFactory: factory, + ControllerDeps: controllerDeps, + } + biometricConfig := identityConfig.Biometric + settingsViewModeler := &viewmodels.SettingsViewModeler{ + Authenticators: service3, + MFA: mfaService, + Authentication: authenticationConfig, + Biometric: biometricConfig, + } + authflowV2SettingsMFAPasswordHandler := &authflowv2.AuthflowV2SettingsMFAPasswordHandler{ + Database: handle, + ControllerFactory: controllerFactory, + BaseViewModel: baseViewModeler, + SettingsViewModel: settingsViewModeler, + Renderer: responseRenderer, + } + return authflowV2SettingsMFAPasswordHandler +} + +func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { + appProvider := p.AppProvider + factory := appProvider.LoggerFactory + handle := appProvider.AppDatabase + appredisHandle := appProvider.Redis + appContext := appProvider.AppContext + config := appContext.Config + appConfig := config.AppConfig + appID := appConfig.ID + serviceLogger := webapp2.NewServiceLogger(factory) + request := p.Request + sessionStoreRedis := &webapp2.SessionStoreRedis{ + AppID: appID, + Redis: appredisHandle, + } + sessionCookieDef := webapp2.NewSessionCookieDef() + signedUpCookieDef := webapp2.NewSignedUpCookieDef() + authenticationConfig := appConfig.Authentication + cookieDef := mfa.NewDeviceTokenCookieDef(authenticationConfig) + errorTokenCookieDef := webapp2.NewErrorTokenCookieDef() + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpConfig := appConfig.HTTP + cookieManager := deps.NewCookieManager(request, trustProxy, httpConfig) + errorService := &webapp2.ErrorService{ + AppID: appID, + Cookie: errorTokenCookieDef, + RedisHandle: appredisHandle, + Cookies: cookieManager, + } + oAuthConfig := appConfig.OAuth + uiConfig := appConfig.UI + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpProto := deps.ProvideHTTPProto(request, trustProxy) + globalUIImplementation := environmentConfig.UIImplementation + globalUISettingsImplementation := environmentConfig.UISettingsImplementation + uiImplementationService := &web.UIImplementationService{ + UIConfig: uiConfig, + GlobalUIImplementation: globalUIImplementation, + GlobalUISettingsImplementation: globalUISettingsImplementation, + } + endpointsEndpoints := &endpoints.Endpoints{ + HTTPHost: httpHost, + HTTPProto: httpProto, + UIImplementationService: uiImplementationService, + } + uiService := &authenticationinfo.UIService{ + EndpointsProvider: endpointsEndpoints, + } + resolver := &oauthclient.Resolver{ + OAuthConfig: oAuthConfig, + TesterEndpoints: endpointsEndpoints, + } + logger := interaction.NewLogger(factory) + remoteIP := deps.ProvideRemoteIP(request, trustProxy) + contextContext := deps.ProvideRequestContext(request) + sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) + clockClock := _wireSystemClockValue + featureConfig := config.FeatureConfig + redisLogger := redis.NewLogger(factory) + secretConfig := config.SecretConfig + databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) + sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) + store := &redis.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Logger: redisLogger, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + userAgentString := deps.ProvideUserAgentString(request) + eventLogger := event.NewLogger(factory) + localizationConfig := appConfig.Localization + sqlBuilder := appdb.NewSQLBuilder(databaseCredentials) + storeImpl := event.NewStoreImpl(sqlBuilder, sqlExecutor) + userStore := &user.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + AppID: appID, + } + rawQueries := &user.RawQueries{ + Store: userStore, + } + identityConfig := appConfig.Identity + identityFeatureConfig := featureConfig.Identity + serviceStore := &service.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginidStore := &loginid.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginIDConfig := identityConfig.LoginID + manager := appContext.Resources + typeCheckerFactory := &loginid.TypeCheckerFactory{ + UIConfig: uiConfig, + LoginIDConfig: loginIDConfig, + Resources: manager, + } + checker := &loginid.Checker{ + Config: loginIDConfig, + TypeCheckerFactory: typeCheckerFactory, + } + normalizerFactory := &loginid.NormalizerFactory{ + Config: loginIDConfig, + } + provider := &loginid.Provider{ + Store: loginidStore, + Config: loginIDConfig, + Checker: checker, + NormalizerFactory: normalizerFactory, + Clock: clockClock, + } + oauthStore := &oauth3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + IdentityConfig: identityConfig, + } + oauthProvider := &oauth3.Provider{ + Store: oauthStore, + Clock: clockClock, + IdentityConfig: identityConfig, + } + anonymousStore := &anonymous.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + anonymousProvider := &anonymous.Provider{ + Store: anonymousStore, + Clock: clockClock, + } + biometricStore := &biometric.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + biometricProvider := &biometric.Provider{ + Store: biometricStore, + Clock: clockClock, + } + passkeyStore := &passkey.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + store2 := &passkey2.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + defaultLanguageTag := deps.ProvideDefaultLanguageTag(config) + supportedLanguageTags := deps.ProvideSupportedLanguageTags(config) + templateResolver := &template.Resolver{ + Resources: manager, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + } + engine := &template.Engine{ + Resolver: templateResolver, + } + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) + webAppCDNHost := environmentConfig.WebAppCDNHost + globalEmbeddedResourceManager := rootProvider.EmbeddedResources + staticAssetResolver := &web.StaticAssetResolver{ + Context: contextContext, + Localization: localizationConfig, + HTTPOrigin: httpOrigin, + HTTPProto: httpProto, + WebAppCDNHost: webAppCDNHost, + Resources: manager, + EmbeddedResources: globalEmbeddedResourceManager, + } + translationService := &translation.Service{ + Context: contextContext, + TemplateEngine: engine, + StaticAssets: staticAssetResolver, + } + configService := &passkey2.ConfigService{ + Request: request, + TrustProxy: trustProxy, + TranslationService: translationService, + } + passkeyService := &passkey2.Service{ + Store: store2, + ConfigService: configService, + } + passkeyProvider := &passkey.Provider{ + Store: passkeyStore, + Clock: clockClock, + Passkey: passkeyService, + } + siweStore := &siwe.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + web3Config := appConfig.Web3 + storeRedis := &siwe2.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + ratelimitLogger := ratelimit.NewLogger(factory) + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) + rateLimitsFeatureConfig := featureConfig.RateLimits + limiter := &ratelimit.Limiter{ + Logger: ratelimitLogger, + Storage: storageRedis, + AppID: appID, + Config: rateLimitsFeatureConfig, + } + siweLogger := siwe2.NewLogger(factory) + siweService := &siwe2.Service{ + RemoteIP: remoteIP, + HTTPOrigin: httpOrigin, + Web3Config: web3Config, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + NonceStore: storeRedis, + RateLimiter: limiter, + Logger: siweLogger, + } + siweProvider := &siwe.Provider{ + Store: siweStore, + Clock: clockClock, + SIWE: siweService, + } + ldapStore := &ldap.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + normalizer := &stdattrs.Normalizer{ + LoginIDNormalizerFactory: normalizerFactory, + } + ldapProvider := &ldap.Provider{ + Store: ldapStore, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + } + serviceService := &service.Service{ + Authentication: authenticationConfig, + Identity: identityConfig, + IdentityFeatureConfig: identityFeatureConfig, + Store: serviceStore, + LoginID: provider, + OAuth: oauthProvider, + Anonymous: anonymousProvider, + Biometric: biometricProvider, + Passkey: passkeyProvider, + SIWE: siweProvider, + LDAP: ldapProvider, + } + store3 := &service2.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + passwordStore := &password.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorConfig := appConfig.Authenticator + authenticatorPasswordConfig := authenticatorConfig.Password + passwordLogger := password.NewLogger(factory) + historyStore := &password.HistoryStore{ + Clock: clockClock, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorFeatureConfig := featureConfig.Authenticator + passwordChecker := password.ProvideChecker(authenticatorPasswordConfig, authenticatorFeatureConfig, historyStore) + expiry := password.ProvideExpiry(authenticatorPasswordConfig, clockClock) + housekeeperLogger := password.NewHousekeeperLogger(factory) + housekeeper := &password.Housekeeper{ + Store: historyStore, + Logger: housekeeperLogger, + Config: authenticatorPasswordConfig, + } + passwordProvider := &password.Provider{ + Store: passwordStore, + Config: authenticatorPasswordConfig, + Clock: clockClock, + Logger: passwordLogger, + PasswordHistory: historyStore, + PasswordChecker: passwordChecker, + Expiry: expiry, + Housekeeper: housekeeper, + } + store4 := &passkey3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + provider2 := &passkey3.Provider{ + Store: store4, + Clock: clockClock, + Passkey: passkeyService, + } + totpStore := &totp.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorTOTPConfig := authenticatorConfig.TOTP + totpProvider := &totp.Provider{ + Store: totpStore, + Config: authenticatorTOTPConfig, + Clock: clockClock, + } + oobStore := &oob.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + oobProvider := &oob.Provider{ + Store: oobStore, + LoginIDNormalizerFactory: normalizerFactory, + Clock: clockClock, + } + testModeConfig := appConfig.TestMode + testModeFeatureConfig := featureConfig.TestMode + codeStoreRedis := &otp.CodeStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + lookupStoreRedis := &otp.LookupStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + attemptTrackerRedis := &otp.AttemptTrackerRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + otpLogger := otp.NewLogger(factory) + otpService := &otp.Service{ + Clock: clockClock, + AppID: appID, + TestModeConfig: testModeConfig, + TestModeFeatureConfig: testModeFeatureConfig, + RemoteIP: remoteIP, + CodeStore: codeStoreRedis, + LookupStore: lookupStoreRedis, + AttemptTracker: attemptTrackerRedis, + Logger: otpLogger, + RateLimiter: limiter, + } + rateLimits := service2.RateLimits{ + IP: remoteIP, + Config: authenticationConfig, + RateLimiter: limiter, + } + authenticationLockoutConfig := authenticationConfig.Lockout + lockoutLogger := lockout.NewLogger(factory) + lockoutStorageRedis := &lockout.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + lockoutService := &lockout.Service{ + Logger: lockoutLogger, + Storage: lockoutStorageRedis, + } + serviceLockout := service2.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + service3 := &service2.Service{ + Store: store3, + Config: appConfig, + Password: passwordProvider, + Passkey: provider2, + TOTP: totpProvider, + OOBOTP: oobProvider, + OTPCodeService: otpService, + RateLimits: rateLimits, + Lockout: serviceLockout, + } + verificationConfig := appConfig.Verification + userProfileConfig := appConfig.UserProfile + storePQ := &verification.StorePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + verificationService := &verification.Service{ + Config: verificationConfig, + UserProfileConfig: userProfileConfig, + Clock: clockClock, + ClaimStore: storePQ, + } + imagesCDNHost := environmentConfig.ImagesCDNHost + pictureTransformer := &stdattrs2.PictureTransformer{ + HTTPProto: httpProto, + HTTPHost: httpHost, + ImagesCDNHost: imagesCDNHost, + } + serviceNoEvent := &stdattrs2.ServiceNoEvent{ + UserProfileConfig: userProfileConfig, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + ClaimStore: storePQ, + Transformer: pictureTransformer, + } + customattrsServiceNoEvent := &customattrs.ServiceNoEvent{ + Config: userProfileConfig, + UserQueries: rawQueries, + UserStore: userStore, + } + nftIndexerAPIEndpoint := environmentConfig.NFTIndexerAPIEndpoint + web3Service := &web3.Service{ + APIEndpoint: nftIndexerAPIEndpoint, + Web3Config: web3Config, + } + rolesgroupsStore := &rolesgroups.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + queries := &rolesgroups.Queries{ + Store: rolesgroupsStore, + } + userQueries := &user.Queries{ + RawQueries: rawQueries, + Store: userStore, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + resolverImpl := &event.ResolverImpl{ + Users: userQueries, + } + hookLogger := hook.NewLogger(factory) + hookConfig := appConfig.Hook + webHookLogger := hook.NewWebHookLogger(factory) + webhookKeyMaterials := deps.ProvideWebhookKeyMaterials(secretConfig) + webHookImpl := hook.WebHookImpl{ + Logger: webHookLogger, + Secret: webhookKeyMaterials, + } + syncHTTPClient := hook.NewSyncHTTPClient(hookConfig) + asyncHTTPClient := hook.NewAsyncHTTPClient() + eventWebHookImpl := &hook.EventWebHookImpl{ + WebHookImpl: webHookImpl, + SyncHTTP: syncHTTPClient, + AsyncHTTP: asyncHTTPClient, + } + denoHookLogger := hook.NewDenoHookLogger(factory) + denoHook := hook.DenoHook{ + Context: contextContext, + ResourceManager: manager, + Logger: denoHookLogger, + } + denoEndpoint := environmentConfig.DenoEndpoint + syncDenoClient := hook.NewSyncDenoClient(denoEndpoint, hookConfig, hookLogger) + asyncDenoClient := hook.NewAsyncDenoClient(denoEndpoint, hookLogger) + eventDenoHookImpl := &hook.EventDenoHookImpl{ + DenoHook: denoHook, + SyncDenoClient: syncDenoClient, + AsyncDenoClient: asyncDenoClient, + } + commands := &rolesgroups.Commands{ + Store: rolesgroupsStore, + } + sink := &hook.Sink{ + Logger: hookLogger, + Config: hookConfig, + Clock: clockClock, + EventWebHook: eventWebHookImpl, + EventDenoHook: eventDenoHookImpl, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + RolesAndGroups: commands, + } + auditLogger := audit.NewLogger(factory) + writeHandle := appProvider.AuditWriteDatabase + auditDatabaseCredentials := deps.ProvideAuditDatabaseCredentials(secretConfig) + auditdbSQLBuilderApp := auditdb.NewSQLBuilderApp(auditDatabaseCredentials, appID) + writeSQLExecutor := auditdb.NewWriteSQLExecutor(contextContext, writeHandle) + writeStore := &audit.WriteStore{ + SQLBuilder: auditdbSQLBuilderApp, + SQLExecutor: writeSQLExecutor, + } + auditSink := &audit.Sink{ + Logger: auditLogger, + Database: writeHandle, + Store: writeStore, + } + elasticsearchLogger := elasticsearch.NewLogger(factory) + elasticsearchServiceLogger := elasticsearch.NewElasticsearchServiceLogger(factory) + elasticsearchCredentials := deps.ProvideElasticsearchCredentials(secretConfig) + client := elasticsearch.NewClient(elasticsearchCredentials) + queue := appProvider.TaskQueue + userReindexProducer := redisqueue.NewUserReindexProducer(appredisHandle, clockClock) + elasticsearchService := elasticsearch.Service{ + Clock: clockClock, + Context: contextContext, + Database: handle, + Logger: elasticsearchServiceLogger, + AppID: appID, + Client: client, + Users: userQueries, + UserStore: userStore, + IdentityService: serviceService, + RolesGroups: rolesgroupsStore, + TaskQueue: queue, + Producer: userReindexProducer, + } + elasticsearchSink := &elasticsearch.Sink{ + Logger: elasticsearchLogger, + Service: elasticsearchService, + Database: handle, + } + eventService := event.NewService(contextContext, appID, remoteIP, userAgentString, eventLogger, handle, clockClock, localizationConfig, storeImpl, resolverImpl, sink, auditSink, elasticsearchSink) + storeDeviceTokenRedis := &mfa.StoreDeviceTokenRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + storeRecoveryCodePQ := &mfa.StoreRecoveryCodePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + mfaLockout := mfa.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + mfaService := &mfa.Service{ + IP: remoteIP, + DeviceTokens: storeDeviceTokenRedis, + RecoveryCodes: storeRecoveryCodePQ, + Clock: clockClock, + Config: authenticationConfig, + RateLimiter: limiter, + Lockout: mfaLockout, + } + messagingLogger := messaging.NewLogger(factory) + usageLogger := usage.NewLogger(factory) + usageLimiter := &usage.Limiter{ + Logger: usageLogger, + Clock: clockClock, + AppID: appID, + Redis: appredisHandle, + } + messagingConfig := appConfig.Messaging + messagingRateLimitsConfig := messagingConfig.RateLimits + messagingFeatureConfig := featureConfig.Messaging + rateLimitsEnvironmentConfig := &environmentConfig.RateLimits + limits := messaging.Limits{ + Logger: messagingLogger, + RateLimiter: limiter, + UsageLimiter: usageLimiter, + RemoteIP: remoteIP, + Config: messagingRateLimitsConfig, + FeatureConfig: messagingFeatureConfig, + EnvConfig: rateLimitsEnvironmentConfig, + } + whatsappServiceLogger := whatsapp.NewServiceLogger(factory) + devMode := environmentConfig.DevMode + featureTestModeWhatsappSuppressed := deps.ProvideTestModeWhatsappSuppressed(testModeFeatureConfig) + testModeWhatsappConfig := testModeConfig.Whatsapp + whatsappConfig := messagingConfig.Whatsapp + whatsappOnPremisesCredentials := deps.ProvideWhatsappOnPremisesCredentials(secretConfig) + tokenStore := &whatsapp.TokenStore{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + onPremisesClient := whatsapp.NewWhatsappOnPremisesClient(whatsappConfig, whatsappOnPremisesCredentials, tokenStore) + whatsappService := &whatsapp.Service{ + Context: contextContext, + Logger: whatsappServiceLogger, + DevMode: devMode, + FeatureTestModeWhatsappSuppressed: featureTestModeWhatsappSuppressed, + TestModeWhatsappConfig: testModeWhatsappConfig, + WhatsappConfig: whatsappConfig, + LocalizationConfig: localizationConfig, + OnPremisesClient: onPremisesClient, + TokenStore: tokenStore, + } + sender := &messaging.Sender{ + Limits: limits, + TaskQueue: queue, + Events: eventService, + Whatsapp: whatsappService, + MessagingFeatureConfig: messagingFeatureConfig, + } + forgotpasswordSender := &forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + rawCommands := &user.RawCommands{ + Store: userStore, + Clock: clockClock, + } + userCommands := &user.Commands{ + RawCommands: rawCommands, + RawQueries: rawQueries, + Events: eventService, + Verification: verificationService, + UserProfileConfig: userProfileConfig, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + stdattrsService := &stdattrs2.Service{ + UserProfileConfig: userProfileConfig, + ServiceNoEvent: serviceNoEvent, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + Events: eventService, + } + authorizationStore := &pq.AuthorizationStore{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + storeRedisLogger := idpsession.NewStoreRedisLogger(factory) + idpsessionStoreRedis := &idpsession.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + Logger: storeRedisLogger, + } + sessionConfig := appConfig.Session + cookieDef2 := session.NewSessionCookieDef(sessionConfig) + idpsessionManager := &idpsession.Manager{ + Store: idpsessionStoreRedis, + Config: sessionConfig, + Cookies: cookieManager, + CookieDef: cookieDef2, + } + eventStoreRedis := &access.EventStoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + eventProvider := &access.EventProvider{ + Store: eventStoreRedis, + } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } + idpsessionRand := _wireRandValue + idpsessionProvider := &idpsession.Provider{ + Context: contextContext, + RemoteIP: remoteIP, + UserAgentString: userAgentString, + AppID: appID, + Redis: appredisHandle, + Store: idpsessionStoreRedis, + AccessEvents: eventProvider, + MeterService: meterService, + TrustProxy: trustProxy, + Config: sessionConfig, + Clock: clockClock, + Random: idpsessionRand, + } + offlineGrantService := oauth2.OfflineGrantService{ + OAuthConfig: oAuthConfig, + Clock: clockClock, + IDPSessions: idpsessionProvider, + ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, + OfflineGrants: store, + } + sessionManager := &oauth2.SessionManager{ + Store: store, + Config: oAuthConfig, + Service: offlineGrantService, + } + accountDeletionConfig := appConfig.AccountDeletion + accountAnonymizationConfig := appConfig.AccountAnonymization + maxTrials := _wireMaxTrialsValue + passwordRand := password.NewRandSource() + generator := &password.Generator{ + MaxTrials: maxTrials, + Checker: passwordChecker, + Rand: passwordRand, + PasswordConfig: authenticatorPasswordConfig, + } + coordinator := &facade.Coordinator{ + Events: eventService, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + MFA: mfaService, + SendPassword: forgotpasswordSender, + UserCommands: userCommands, + UserQueries: userQueries, + RolesGroupsCommands: commands, + StdAttrsService: stdattrsService, + PasswordHistory: historyStore, + OAuth: authorizationStore, + IDPSessions: idpsessionManager, + OAuthSessions: sessionManager, + IdentityConfig: identityConfig, + AccountDeletionConfig: accountDeletionConfig, + AccountAnonymizationConfig: accountAnonymizationConfig, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + PasswordGenerator: generator, + } + identityFacade := facade.IdentityFacade{ + Coordinator: coordinator, + } + authenticatorFacade := facade.AuthenticatorFacade{ + Coordinator: coordinator, + } + anonymousStoreRedis := &anonymous.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + messageSender := &otp.MessageSender{ + AppID: appID, + Translation: translationService, + Endpoints: endpointsEndpoints, + Sender: sender, + WhatsappService: whatsappService, + } + oAuthSSOProviderCredentials := deps.ProvideOAuthSSOProviderCredentials(secretConfig) + oAuthHTTPClient := sso.ProvideOAuthHTTPClient(environmentConfig) + simpleStoreRedisFactory := &sso.SimpleStoreRedisFactory{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + } + oAuthProviderFactory := &sso.OAuthProviderFactory{ + IdentityConfig: identityConfig, + Credentials: oAuthSSOProviderCredentials, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + HTTPClient: oAuthHTTPClient, + SimpleStoreRedisFactory: simpleStoreRedisFactory, + } + webappoauthStore := &webappoauth.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } + forgotpasswordLogger := forgotpassword.NewLogger(factory) + sender2 := forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + forgotpasswordService := &forgotpassword.Service{ + Logger: forgotpasswordLogger, + Config: appConfig, + FeatureConfig: featureConfig, + Identities: serviceService, + Authenticators: authenticatorFacade, + OTPCodes: otpService, + OTPSender: messageSender, + PasswordSender: sender2, + } + responseWriter := p.ResponseWriter + nonceService := &nonce.Service{ + Cookies: cookieManager, + Request: request, + ResponseWriter: responseWriter, + } + challengeProvider := &challenge.Provider{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + userProvider := &user.Provider{ + Commands: userCommands, + Queries: userQueries, + } + authenticationinfoStoreRedis := &authenticationinfo.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + manager2 := &session.Manager{ + IDPSessions: idpsessionManager, + AccessTokenSessions: sessionManager, + Events: eventService, + } + oauthsessionStoreRedis := &oauthsession.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + interactionContext := &interaction.Context{ + Request: request, + RemoteIP: remoteIP, + Database: sqlExecutor, + Clock: clockClock, + Config: appConfig, + FeatureConfig: featureConfig, + OAuthClientResolver: resolver, + OfflineGrants: store, + Identities: identityFacade, + Authenticators: authenticatorFacade, + AnonymousIdentities: anonymousProvider, + AnonymousUserPromotionCodeStore: anonymousStoreRedis, + BiometricIdentities: biometricProvider, + OTPCodeService: otpService, + OTPSender: messageSender, + OAuthProviderFactory: oAuthProviderFactory, + OAuthRedirectURIBuilder: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + MFA: mfaFacade, + ForgotPassword: forgotpasswordService, + ResetPassword: forgotpasswordService, + Passkey: passkeyService, + Verification: verificationService, + RateLimiter: limiter, + PasswordGenerator: generator, + Nonces: nonceService, + Challenges: challengeProvider, + Users: userProvider, + StdAttrsService: stdattrsService, + Events: eventService, + CookieManager: cookieManager, + AuthenticationInfoService: authenticationinfoStoreRedis, + Sessions: idpsessionProvider, + SessionManager: manager2, + SessionCookie: cookieDef2, + OAuthSessions: oauthsessionStoreRedis, + MFADeviceTokenCookie: cookieDef, + } + interactionStoreRedis := &interaction.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + interactionService := &interaction.Service{ + Logger: logger, + Context: interactionContext, + Store: interactionStoreRedis, + } + webappService2 := &webapp2.Service2{ + Logger: serviceLogger, + Request: request, + Sessions: sessionStoreRedis, + SessionCookie: sessionCookieDef, + SignedUpCookie: signedUpCookieDef, + MFADeviceTokenCookie: cookieDef, + ErrorService: errorService, + Cookies: cookieManager, + OAuthConfig: oAuthConfig, + UIConfig: uiConfig, + TrustProxy: trustProxy, + UIInfoResolver: uiService, + OAuthClientResolver: resolver, + Graph: interactionService, + } + uiFeatureConfig := featureConfig.UI + forgotPasswordConfig := appConfig.ForgotPassword + googleTagManagerConfig := appConfig.GoogleTagManager + botProtectionConfig := appConfig.BotProtection + flashMessage := &httputil.FlashMessage{ + Cookies: cookieManager, + } + authUISentryDSN := environmentConfig.AuthUISentryDSN + authUIWindowMessageAllowedOrigins := environmentConfig.AuthUIWindowMessageAllowedOrigins + baseLogger := viewmodels.NewBaseLogger(factory) + baseViewModeler := &viewmodels.BaseViewModeler{ + TrustProxy: trustProxy, + OAuth: oAuthConfig, + AuthUI: uiConfig, + AuthUIFeatureConfig: uiFeatureConfig, + StaticAssets: staticAssetResolver, + ForgotPassword: forgotPasswordConfig, + Authentication: authenticationConfig, + GoogleTagManager: googleTagManagerConfig, + BotProtection: botProtectionConfig, + ErrorService: errorService, + Translations: translationService, + Clock: clockClock, + FlashMessage: flashMessage, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + AuthUISentryDSN: authUISentryDSN, + AuthUIWindowMessageAllowedOrigins: authUIWindowMessageAllowedOrigins, + OAuthClientResolver: resolver, + Logger: baseLogger, + } + responseRenderer := &webapp.ResponseRenderer{ + TemplateEngine: engine, + } + publisher := webapp.NewPublisher(appID, appredisHandle) + authflowNavigator := &webapp2.AuthflowNavigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + authflowV2Navigator := &authflowv2.AuthflowV2Navigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + errorRenderer := &webapp.ErrorRenderer{ + ErrorService: errorService, + UIImplementationService: uiImplementationService, + AuthflowV1Navigator: authflowNavigator, + AuthflowV2Navigator: authflowV2Navigator, + } + controllerDeps := webapp.ControllerDeps{ + Database: handle, + RedisHandle: appredisHandle, + AppID: appID, + Page: webappService2, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + Publisher: publisher, + Clock: clockClock, + TesterEndpointsProvider: endpointsEndpoints, + ErrorRenderer: errorRenderer, + TrustProxy: trustProxy, + } + controllerFactory := webapp.ControllerFactory{ + LoggerFactory: factory, + ControllerDeps: controllerDeps, + } + settingsTOTPHandler := &webapp.SettingsTOTPHandler{ + ControllerFactory: controllerFactory, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + Authenticators: service3, + } + return settingsTOTPHandler +} + +func newWebAppAuthflowV2SettingsTOTPHandler(p *deps.RequestProvider) http.Handler { appProvider := p.AppProvider handle := appProvider.AppDatabase factory := appProvider.LoggerFactory @@ -54610,27 +57483,59 @@ func newWebAppAuthflowV2SettingsMFAPasswordHandler(p *deps.RequestProvider) http LoggerFactory: factory, ControllerDeps: controllerDeps, } - biometricConfig := identityConfig.Biometric - settingsViewModeler := &viewmodels.SettingsViewModeler{ - Authenticators: service3, - MFA: mfaService, - Authentication: authenticationConfig, - Biometric: biometricConfig, + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, } - authflowV2SettingsMFAPasswordHandler := &authflowv2.AuthflowV2SettingsMFAPasswordHandler{ + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } + service4 := service2.Service{ + Store: store3, + Config: appConfig, + Password: passwordProvider, + Passkey: provider2, + TOTP: totpProvider, + OOBOTP: oobProvider, + OTPCodeService: otpService, + RateLimits: rateLimits, + Lockout: serviceLockout, + } + authflowV2SettingsTOTPHandler := &authflowv2.AuthflowV2SettingsTOTPHandler{ Database: handle, ControllerFactory: controllerFactory, BaseViewModel: baseViewModeler, - SettingsViewModel: settingsViewModeler, Renderer: responseRenderer, + AccountManagement: accountmanagementService, + Authenticators: service4, } - return authflowV2SettingsMFAPasswordHandler + return authflowV2SettingsTOTPHandler } -func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { +func newWebAppAuthflowV2SettingsMFACreateTOTPHandler(p *deps.RequestProvider) http.Handler { appProvider := p.AppProvider - factory := appProvider.LoggerFactory handle := appProvider.AppDatabase + factory := appProvider.LoggerFactory appredisHandle := appProvider.Redis appContext := appProvider.AppContext config := appContext.Config @@ -54833,12 +57738,14 @@ func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) + storageRedis := &ratelimit.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, - AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -55275,18 +58182,6 @@ func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { eventProvider := &access.EventProvider{ Store: eventStoreRedis, } - analyticredisHandle := appProvider.AnalyticRedis - meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) - writeStoreRedis := &meter.WriteStoreRedis{ - Context: contextContext, - Redis: analyticredisHandle, - AppID: appID, - Clock: clockClock, - Logger: meterStoreRedisLogger, - } - meterService := &meter.Service{ - Counter: writeStoreRedis, - } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -55296,7 +58191,6 @@ func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, - MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -55307,8 +58201,6 @@ func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, - AccessEvents: eventProvider, - MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ @@ -55567,16 +58459,52 @@ func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { LoggerFactory: factory, ControllerDeps: controllerDeps, } - settingsTOTPHandler := &webapp.SettingsTOTPHandler{ + biometricConfig := identityConfig.Biometric + settingsViewModeler := &viewmodels.SettingsViewModeler{ + Authenticators: service3, + MFA: mfaService, + Authentication: authenticationConfig, + Biometric: biometricConfig, + } + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, + } + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } + authflowV2SettingsMFACreateTOTPHandler := &authflowv2.AuthflowV2SettingsMFACreateTOTPHandler{ + Database: handle, ControllerFactory: controllerFactory, BaseViewModel: baseViewModeler, + SettingsViewModel: settingsViewModeler, Renderer: responseRenderer, - Authenticators: service3, + AccountManagement: accountmanagementService, } - return settingsTOTPHandler + return authflowV2SettingsMFACreateTOTPHandler } -func newWebAppAuthflowV2SettingsTOTPHandler(p *deps.RequestProvider) http.Handler { +func newWebAppAuthflowV2SettingsMFAEnterTOTPHandler(p *deps.RequestProvider) http.Handler { appProvider := p.AppProvider handle := appProvider.AppDatabase factory := appProvider.LoggerFactory @@ -55782,12 +58710,14 @@ func newWebAppAuthflowV2SettingsTOTPHandler(p *deps.RequestProvider) http.Handle Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) + storageRedis := &ratelimit.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, - AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -56224,18 +59154,6 @@ func newWebAppAuthflowV2SettingsTOTPHandler(p *deps.RequestProvider) http.Handle eventProvider := &access.EventProvider{ Store: eventStoreRedis, } - analyticredisHandle := appProvider.AnalyticRedis - meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) - writeStoreRedis := &meter.WriteStoreRedis{ - Context: contextContext, - Redis: analyticredisHandle, - AppID: appID, - Clock: clockClock, - Logger: meterStoreRedisLogger, - } - meterService := &meter.Service{ - Counter: writeStoreRedis, - } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -56245,7 +59163,6 @@ func newWebAppAuthflowV2SettingsTOTPHandler(p *deps.RequestProvider) http.Handle Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, - MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -56256,8 +59173,6 @@ func newWebAppAuthflowV2SettingsTOTPHandler(p *deps.RequestProvider) http.Handle Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, - AccessEvents: eventProvider, - MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ @@ -56516,25 +59431,50 @@ func newWebAppAuthflowV2SettingsTOTPHandler(p *deps.RequestProvider) http.Handle LoggerFactory: factory, ControllerDeps: controllerDeps, } - service4 := service2.Service{ - Store: store3, - Config: appConfig, - Password: passwordProvider, - Passkey: provider2, - TOTP: totpProvider, - OOBOTP: oobProvider, - OTPCodeService: otpService, - RateLimits: rateLimits, - Lockout: serviceLockout, + biometricConfig := identityConfig.Biometric + settingsViewModeler := &viewmodels.SettingsViewModeler{ + Authenticators: service3, + MFA: mfaService, + Authentication: authenticationConfig, + Biometric: biometricConfig, } - authflowV2SettingsTOTPHandler := &authflowv2.AuthflowV2SettingsTOTPHandler{ + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, + } + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } + authflowV2SettingsMFAEnterTOTPHandler := &authflowv2.AuthflowV2SettingsMFAEnterTOTPHandler{ Database: handle, ControllerFactory: controllerFactory, BaseViewModel: baseViewModeler, + SettingsViewModel: settingsViewModeler, Renderer: responseRenderer, - Authenticators: service4, + Clock: clockClock, + AccountManagement: accountmanagementService, } - return authflowV2SettingsTOTPHandler + return authflowV2SettingsMFAEnterTOTPHandler } func newWebAppAuthflowV2SettingsOOBOTPHandler(p *deps.RequestProvider) http.Handler { @@ -59412,6 +62352,7 @@ func newWebAppAuthflowV2SettingsChangePasskeyHandler(p *deps.RequestProvider) ht accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -59421,6 +62362,7 @@ func newWebAppAuthflowV2SettingsChangePasskeyHandler(p *deps.RequestProvider) ht OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -66145,6 +69087,7 @@ func newWebAppAuthflowV2SettingsChangePasswordHandler(p *deps.RequestProvider) h accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -66154,6 +69097,7 @@ func newWebAppAuthflowV2SettingsChangePasswordHandler(p *deps.RequestProvider) h OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -92891,11 +95835,17 @@ func newAPIAccountManagementV1IdentificationHandler(p *deps.RequestProvider) htt appContext := appProvider.AppContext config := appContext.Config appConfig := config.AppConfig + request := p.Request + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpProto := deps.ProvideHTTPProto(request, trustProxy) + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) secretConfig := config.SecretConfig databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) appID := appConfig.ID sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) - request := p.Request contextContext := deps.ProvideRequestContext(request) sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) clockClock := _wireSystemClockValue @@ -92912,9 +95862,6 @@ func newAPIAccountManagementV1IdentificationHandler(p *deps.RequestProvider) htt rawQueries := &user.RawQueries{ Store: store, } - rootProvider := appProvider.RootProvider - environmentConfig := rootProvider.EnvironmentConfig - trustProxy := environmentConfig.TrustProxy remoteIP := deps.ProvideRemoteIP(request, trustProxy) userAgentString := deps.ProvideUserAgentString(request) logger := event.NewLogger(factory) @@ -93001,9 +95948,6 @@ func newAPIAccountManagementV1IdentificationHandler(p *deps.RequestProvider) htt engine := &template.Engine{ Resolver: resolver, } - httpProto := deps.ProvideHTTPProto(request, trustProxy) - httpHost := deps.ProvideHTTPHost(request, trustProxy) - httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) webAppCDNHost := environmentConfig.WebAppCDNHost globalEmbeddedResourceManager := rootProvider.EmbeddedResources staticAssetResolver := &web.StaticAssetResolver{ @@ -93629,12 +96573,16 @@ func newAPIAccountManagementV1IdentificationHandler(p *deps.RequestProvider) htt Redis: appredisHandle, AppID: appID, } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } uiService := &authenticationinfo.UIService{ EndpointsProvider: endpointsEndpoints, } accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -93644,6 +96592,7 @@ func newAPIAccountManagementV1IdentificationHandler(p *deps.RequestProvider) htt OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -93666,11 +96615,17 @@ func newAPIAccountManagementV1IdentificationOAuthHandler(p *deps.RequestProvider appContext := appProvider.AppContext config := appContext.Config appConfig := config.AppConfig + request := p.Request + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpProto := deps.ProvideHTTPProto(request, trustProxy) + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) secretConfig := config.SecretConfig databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) appID := appConfig.ID sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) - request := p.Request contextContext := deps.ProvideRequestContext(request) sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) clockClock := _wireSystemClockValue @@ -93687,9 +96642,6 @@ func newAPIAccountManagementV1IdentificationOAuthHandler(p *deps.RequestProvider rawQueries := &user.RawQueries{ Store: store, } - rootProvider := appProvider.RootProvider - environmentConfig := rootProvider.EnvironmentConfig - trustProxy := environmentConfig.TrustProxy remoteIP := deps.ProvideRemoteIP(request, trustProxy) userAgentString := deps.ProvideUserAgentString(request) logger := event.NewLogger(factory) @@ -93776,9 +96728,6 @@ func newAPIAccountManagementV1IdentificationOAuthHandler(p *deps.RequestProvider engine := &template.Engine{ Resolver: resolver, } - httpProto := deps.ProvideHTTPProto(request, trustProxy) - httpHost := deps.ProvideHTTPHost(request, trustProxy) - httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) webAppCDNHost := environmentConfig.WebAppCDNHost globalEmbeddedResourceManager := rootProvider.EmbeddedResources staticAssetResolver := &web.StaticAssetResolver{ @@ -94404,12 +97353,16 @@ func newAPIAccountManagementV1IdentificationOAuthHandler(p *deps.RequestProvider Redis: appredisHandle, AppID: appID, } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } uiService := &authenticationinfo.UIService{ EndpointsProvider: endpointsEndpoints, } accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -94419,6 +97372,7 @@ func newAPIAccountManagementV1IdentificationOAuthHandler(p *deps.RequestProvider OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -154982,6 +157936,7 @@ func newWebAppAuthflowV2SettingsIdentityAddEmailHandler(p *deps.RequestProvider) accountmanagementService := accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -154991,6 +157946,7 @@ func newWebAppAuthflowV2SettingsIdentityAddEmailHandler(p *deps.RequestProvider) OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -155956,6 +158912,7 @@ func newWebAppAuthflowV2SettingsIdentityEditEmailHandler(p *deps.RequestProvider accountmanagementService := accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -155965,6 +158922,7 @@ func newWebAppAuthflowV2SettingsIdentityEditEmailHandler(p *deps.RequestProvider OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -157895,6 +160853,7 @@ func newWebAppAuthflowV2SettingsIdentityVerifyEmailHandler(p *deps.RequestProvid accountmanagementService := accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -157904,6 +160863,7 @@ func newWebAppAuthflowV2SettingsIdentityVerifyEmailHandler(p *deps.RequestProvid OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -158880,6 +161840,7 @@ func newWebAppAuthflowV2SettingsIdentityViewEmailHandler(p *deps.RequestProvider accountmanagementService := accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: accountmanagementRedisStore, OAuthProvider: oAuthProviderFactory, @@ -158889,6 +161850,7 @@ func newWebAppAuthflowV2SettingsIdentityViewEmailHandler(p *deps.RequestProvider OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -160825,6 +163787,7 @@ func newWebAppAuthflowV2SettingsIdentityAddPhoneHandler(p *deps.RequestProvider) accountmanagementService := accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -160834,6 +163797,7 @@ func newWebAppAuthflowV2SettingsIdentityAddPhoneHandler(p *deps.RequestProvider) OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -161800,6 +164764,7 @@ func newWebAppAuthflowV2SettingsIdentityEditPhoneHandler(p *deps.RequestProvider accountmanagementService := accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -161809,6 +164774,7 @@ func newWebAppAuthflowV2SettingsIdentityEditPhoneHandler(p *deps.RequestProvider OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -163740,6 +166706,7 @@ func newWebAppAuthflowV2SettingsIdentityViewPhoneHandler(p *deps.RequestProvider accountmanagementService := accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: accountmanagementRedisStore, OAuthProvider: oAuthProviderFactory, @@ -163749,6 +166716,7 @@ func newWebAppAuthflowV2SettingsIdentityViewPhoneHandler(p *deps.RequestProvider OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -165686,6 +168654,7 @@ func newWebAppAuthflowV2SettingsIdentityVerifyPhoneHandler(p *deps.RequestProvid accountmanagementService := accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -165695,6 +168664,7 @@ func newWebAppAuthflowV2SettingsIdentityVerifyPhoneHandler(p *deps.RequestProvid OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -166670,11 +169640,17 @@ func newWebAppAuthflowV2SettingsIdentityNewUsernameHandler(p *deps.RequestProvid appContext := appProvider.AppContext config := appContext.Config appConfig := config.AppConfig + request := p.Request + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpProto := deps.ProvideHTTPProto(request, trustProxy) + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) secretConfig := config.SecretConfig databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) appID := appConfig.ID sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) - request := p.Request contextContext := deps.ProvideRequestContext(request) sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) clockClock := _wireSystemClockValue @@ -166691,9 +169667,6 @@ func newWebAppAuthflowV2SettingsIdentityNewUsernameHandler(p *deps.RequestProvid rawQueries := &user.RawQueries{ Store: store, } - rootProvider := appProvider.RootProvider - environmentConfig := rootProvider.EnvironmentConfig - trustProxy := environmentConfig.TrustProxy remoteIP := deps.ProvideRemoteIP(request, trustProxy) userAgentString := deps.ProvideUserAgentString(request) factory := appProvider.LoggerFactory @@ -166781,9 +169754,6 @@ func newWebAppAuthflowV2SettingsIdentityNewUsernameHandler(p *deps.RequestProvid engine := &template.Engine{ Resolver: resolver, } - httpProto := deps.ProvideHTTPProto(request, trustProxy) - httpHost := deps.ProvideHTTPHost(request, trustProxy) - httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) webAppCDNHost := environmentConfig.WebAppCDNHost globalEmbeddedResourceManager := rootProvider.EmbeddedResources staticAssetResolver := &web.StaticAssetResolver{ @@ -167409,12 +170379,16 @@ func newWebAppAuthflowV2SettingsIdentityNewUsernameHandler(p *deps.RequestProvid Redis: appredisHandle, AppID: appID, } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } uiService := &authenticationinfo.UIService{ EndpointsProvider: endpointsEndpoints, } accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -167424,6 +170398,7 @@ func newWebAppAuthflowV2SettingsIdentityNewUsernameHandler(p *deps.RequestProvid OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -167458,9 +170433,6 @@ func newWebAppAuthflowV2SettingsIdentityNewUsernameHandler(p *deps.RequestProvid Redis: appredisHandle, AppID: appID, } - mfaFacade := &facade.MFAFacade{ - Coordinator: coordinator, - } forgotpasswordLogger := forgotpassword.NewLogger(factory) sender2 := forgotpassword.Sender{ AppConfg: appConfig, @@ -167645,11 +170617,17 @@ func newWebAppAuthflowV2SettingsIdentityViewUsernameHandler(p *deps.RequestProvi appContext := appProvider.AppContext config := appContext.Config appConfig := config.AppConfig + request := p.Request + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpProto := deps.ProvideHTTPProto(request, trustProxy) + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) secretConfig := config.SecretConfig databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) appID := appConfig.ID sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) - request := p.Request contextContext := deps.ProvideRequestContext(request) sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) clockClock := _wireSystemClockValue @@ -167666,9 +170644,6 @@ func newWebAppAuthflowV2SettingsIdentityViewUsernameHandler(p *deps.RequestProvi rawQueries := &user.RawQueries{ Store: store, } - rootProvider := appProvider.RootProvider - environmentConfig := rootProvider.EnvironmentConfig - trustProxy := environmentConfig.TrustProxy remoteIP := deps.ProvideRemoteIP(request, trustProxy) userAgentString := deps.ProvideUserAgentString(request) factory := appProvider.LoggerFactory @@ -167756,9 +170731,6 @@ func newWebAppAuthflowV2SettingsIdentityViewUsernameHandler(p *deps.RequestProvi engine := &template.Engine{ Resolver: resolver, } - httpProto := deps.ProvideHTTPProto(request, trustProxy) - httpHost := deps.ProvideHTTPHost(request, trustProxy) - httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) webAppCDNHost := environmentConfig.WebAppCDNHost globalEmbeddedResourceManager := rootProvider.EmbeddedResources staticAssetResolver := &web.StaticAssetResolver{ @@ -168384,12 +171356,16 @@ func newWebAppAuthflowV2SettingsIdentityViewUsernameHandler(p *deps.RequestProvi Redis: appredisHandle, AppID: appID, } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } uiService := &authenticationinfo.UIService{ EndpointsProvider: endpointsEndpoints, } accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -168399,6 +171375,7 @@ func newWebAppAuthflowV2SettingsIdentityViewUsernameHandler(p *deps.RequestProvi OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -168433,9 +171410,6 @@ func newWebAppAuthflowV2SettingsIdentityViewUsernameHandler(p *deps.RequestProvi Redis: appredisHandle, AppID: appID, } - mfaFacade := &facade.MFAFacade{ - Coordinator: coordinator, - } forgotpasswordLogger := forgotpassword.NewLogger(factory) sender2 := forgotpassword.Sender{ AppConfg: appConfig, @@ -168622,11 +171596,17 @@ func newWebAppAuthflowV2SettingsIdentityEditUsernameHandler(p *deps.RequestProvi appContext := appProvider.AppContext config := appContext.Config appConfig := config.AppConfig + request := p.Request + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpProto := deps.ProvideHTTPProto(request, trustProxy) + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) secretConfig := config.SecretConfig databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) appID := appConfig.ID sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) - request := p.Request contextContext := deps.ProvideRequestContext(request) sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) clockClock := _wireSystemClockValue @@ -168643,9 +171623,6 @@ func newWebAppAuthflowV2SettingsIdentityEditUsernameHandler(p *deps.RequestProvi rawQueries := &user.RawQueries{ Store: store, } - rootProvider := appProvider.RootProvider - environmentConfig := rootProvider.EnvironmentConfig - trustProxy := environmentConfig.TrustProxy remoteIP := deps.ProvideRemoteIP(request, trustProxy) userAgentString := deps.ProvideUserAgentString(request) factory := appProvider.LoggerFactory @@ -168733,9 +171710,6 @@ func newWebAppAuthflowV2SettingsIdentityEditUsernameHandler(p *deps.RequestProvi engine := &template.Engine{ Resolver: resolver, } - httpProto := deps.ProvideHTTPProto(request, trustProxy) - httpHost := deps.ProvideHTTPHost(request, trustProxy) - httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) webAppCDNHost := environmentConfig.WebAppCDNHost globalEmbeddedResourceManager := rootProvider.EmbeddedResources staticAssetResolver := &web.StaticAssetResolver{ @@ -169361,12 +172335,16 @@ func newWebAppAuthflowV2SettingsIdentityEditUsernameHandler(p *deps.RequestProvi Redis: appredisHandle, AppID: appID, } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } uiService := &authenticationinfo.UIService{ EndpointsProvider: endpointsEndpoints, } accountmanagementService := &accountmanagement.Service{ Database: handle, Config: appConfig, + HTTPOrigin: httpOrigin, Users: userProvider, Store: redisStore, OAuthProvider: oAuthProviderFactory, @@ -169376,6 +172354,7 @@ func newWebAppAuthflowV2SettingsIdentityEditUsernameHandler(p *deps.RequestProvi OTPCodeService: otpService, Authenticators: authenticatorFacade, AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, PasskeyService: passkeyService, Verification: verificationService, UIInfoResolver: uiService, @@ -169410,9 +172389,6 @@ func newWebAppAuthflowV2SettingsIdentityEditUsernameHandler(p *deps.RequestProvi Redis: appredisHandle, AppID: appID, } - mfaFacade := &facade.MFAFacade{ - Coordinator: coordinator, - } forgotpasswordLogger := forgotpassword.NewLogger(factory) sender2 := forgotpassword.Sender{ AppConfg: appConfig, diff --git a/pkg/auth/wire_handler.go b/pkg/auth/wire_handler.go index 62162b1688..368dccdc1a 100644 --- a/pkg/auth/wire_handler.go +++ b/pkg/auth/wire_handler.go @@ -478,6 +478,13 @@ func newWebAppAuthflowV2SettingsMFAHandler(p *deps.RequestProvider) http.Handler )) } +func newWebAppAuthflowV2SettingsMFAViewRecoveryCodeHandler(p *deps.RequestProvider) http.Handler { + panic(wire.Build( + DependencySet, + wire.Bind(new(http.Handler), new(*handlerwebappauthflowv2.AuthflowV2SettingsMFAViewRecoveryCodeHandler)), + )) +} + func newWebAppAuthflowV2SettingsMFACreatePasswordHandler(p *deps.RequestProvider) http.Handler { panic(wire.Build( DependencySet, diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index 6dcf079261..7d37b888ce 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1187,6 +1187,8 @@ "v2.page.settings-mfa-enter-totp.default.navbar-title": "Verification Code", "v2.page.settings-mfa-enter-totp.default.description": "Enter the 6-digit code you see in the authenticator device/app.", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Rescan QR code", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "If you lost access to your authentication device, use these recovery codes to recover your account. \nKeep them somewhere safe but accessible.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Recovery code", "v2.page.settings-mfa-password.default.additional-password-added-at": "Added at {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Additional password", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Updated at {time, datetime, short} UTC", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html new file mode 100644 index 0000000000..c86a8afddb --- /dev/null +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html @@ -0,0 +1,81 @@ +{{ template "authflowv2/__settings_page_frame.html" . }} + +{{ define "page-navbar" }} + {{ template "authflowv2/__navbar.html" + (dict + "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) + "BackHref" (call $.MakeURL "/settings/mfa/totp") + "Title" (translate "v2.page.settings-mfa-view-recovery-code.default.title" nil) + ) + }} +{{ end }} + +{{ define "page-content" }} + +
+
+

+ {{ include "v2.page.settings-mfa-view-recovery-code.default.storage-description" nil }} +

+ + {{ template "authflowv2/__alert_message.html" + (dict + "Type" "error" + "Classname" "mt-4" + "Message" (include "authflowv2/__error.html" .) + ) + }} +
+ +
+

{{ range $.RecoveryCodes }}{{ . }} +{{ end }}

+ + +
+ {{ if not .IsNativePlatform }} + {{/* Form with disabled turbo drive */}} +
+ {{ $.CSRFField }} + +
+ {{ end }} + +
+
+ +
+ {{ $.CSRFField }} + +
+
+{{ end }} From 3f42fb0c5840ed588c28fbf2431387c20afb7d74 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Wed, 2 Oct 2024 18:47:32 +0800 Subject: [PATCH 10/31] Generate translations --- resources/authgear/templates/de/translation.json | 8 ++++++++ resources/authgear/templates/el/translation.json | 8 ++++++++ resources/authgear/templates/en/translation.json | 8 ++++---- .../en/web/authflowv2/settings_mfa_create_totp.html | 2 +- .../en/web/authflowv2/settings_mfa_enter_totp.html | 2 +- resources/authgear/templates/es-419/translation.json | 8 ++++++++ resources/authgear/templates/es-ES/translation.json | 8 ++++++++ resources/authgear/templates/es/translation.json | 8 ++++++++ resources/authgear/templates/fil/translation.json | 8 ++++++++ resources/authgear/templates/fr/translation.json | 8 ++++++++ resources/authgear/templates/id/translation.json | 8 ++++++++ resources/authgear/templates/it/translation.json | 8 ++++++++ resources/authgear/templates/ja/translation.json | 8 ++++++++ resources/authgear/templates/ko/translation.json | 8 ++++++++ resources/authgear/templates/ms/translation.json | 8 ++++++++ resources/authgear/templates/nl/translation.json | 8 ++++++++ resources/authgear/templates/pl/translation.json | 8 ++++++++ resources/authgear/templates/pt-BR/translation.json | 8 ++++++++ resources/authgear/templates/pt-PT/translation.json | 8 ++++++++ resources/authgear/templates/pt/translation.json | 8 ++++++++ resources/authgear/templates/th/translation.json | 8 ++++++++ resources/authgear/templates/vi/translation.json | 8 ++++++++ resources/authgear/templates/zh-CN/translation.json | 8 ++++++++ resources/authgear/templates/zh-HK/translation.json | 8 ++++++++ resources/authgear/templates/zh-TW/translation.json | 8 ++++++++ 25 files changed, 182 insertions(+), 6 deletions(-) diff --git a/resources/authgear/templates/de/translation.json b/resources/authgear/templates/de/translation.json index 12d7b91c52..c4dad6ca30 100644 --- a/resources/authgear/templates/de/translation.json +++ b/resources/authgear/templates/de/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Nicht verifiziert", "v2.page.settings-identity.default.verification-status-verified-label": "Verifiziert", "v2.page.settings-mfa-create-password.default.title": "Zusätzliches Passwort", + "v2.page.settings-mfa-create-totp.default.description": "Laden Sie zuerst die Google Authenticator App aus dem Google Play Store oder dem Apple App Store herunter.

Scannen Sie den QR-Code oder geben Sie den Schlüssel mit Ihrem Authenticator-Gerät / App ein.", + "v2.page.settings-mfa-create-totp.default.title": "Authenticator einrichten", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Geben Sie den 6-stelligen Code ein, den Sie in der Authenticator-Gerät/App sehen.", + "v2.page.settings-mfa-enter-totp.default.title": "Verifizierungscode", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "QR-Code erneut scannen", "v2.page.settings-mfa-password.default.additional-password-added-at": "Hinzugefügt um {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Zusätzliches Passwort", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Aktualisiert um {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Zusätzliches Passwort entfernen", "v2.page.settings-mfa-password.default.remove-button-label": "Zusätzliches Passwort entfernen", "v2.page.settings-mfa-password.default.title": "Zusätzliches Passwort", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Wenn Sie den Zugriff auf Ihr Authentifizierungsgerät verloren haben, verwenden Sie diese Wiederherstellungscodes, um Ihr Konto wiederherzustellen. \nBewahren Sie sie an einem sicheren, aber zugänglichen Ort auf.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Wiederherstellungscode", "v2.page.settings-mfa.default.activated-label": "Aktiviert", "v2.page.settings-mfa.default.additional-password-label": "Zusätzliches Passwort", "v2.page.settings-mfa.default.inactive-label": "Inaktiv", diff --git a/resources/authgear/templates/el/translation.json b/resources/authgear/templates/el/translation.json index 180ea922c3..25dffc5872 100644 --- a/resources/authgear/templates/el/translation.json +++ b/resources/authgear/templates/el/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Μη επαληθευμένο", "v2.page.settings-identity.default.verification-status-verified-label": "Επαληθευμένο", "v2.page.settings-mfa-create-password.default.title": "Πρόσθετος κωδικός πρόσβασης", + "v2.page.settings-mfa-create-totp.default.description": "Πρώτα, κατεβάστε την εφαρμογή Google Authenticator από το Google Play Store ή το Apple App Store.

Σαρώστε τον κωδικό QR ή εισάγετε το κλειδί με τη συσκευή/εφαρμογή ελέγχου ταυτότητας.", + "v2.page.settings-mfa-create-totp.default.title": "Ρύθμιση ελέγχου ταυτότητας", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Εισάγετε τον 6ψήφιο κωδικό που βλέπετε στη συσκευή/εφαρμογή ελέγχου ταυτότητας.", + "v2.page.settings-mfa-enter-totp.default.title": "Κωδικός Επαλήθευσης", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Επανασάρωση κωδικού QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Προστέθηκε στις {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Πρόσθετος κωδικός πρόσβασης", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Ενημερώθηκε στις {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Αφαίρεση πρόσθετου κωδικού πρόσβασης", "v2.page.settings-mfa-password.default.remove-button-label": "Αφαίρεση πρόσθετου κωδικού πρόσβασης", "v2.page.settings-mfa-password.default.title": "Πρόσθετος κωδικός πρόσβασης", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Αν έχασες την πρόσβαση στη συσκευή ελέγχου ταυτότητας, χρησιμοποίησε αυτούς τους κωδικούς ανάκτησης για να ανακτήσεις τον λογαριασμό σου. \nΦύλαξέ τους σε ασφαλές αλλά προσβάσιμο μέρος.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Κωδικός Ανάκτησης", "v2.page.settings-mfa.default.activated-label": "Ενεργοποιημένο", "v2.page.settings-mfa.default.additional-password-label": "Πρόσθετος κωδικός πρόσβασης", "v2.page.settings-mfa.default.inactive-label": "Ανενεργό", diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index 7d37b888ce..a65e848637 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1181,14 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Not Verified", "v2.page.settings-identity.default.verification-status-verified-label": "Verified", "v2.page.settings-mfa-create-password.default.title": "Additional password", - "v2.page.settings-mfa-create-totp.default.navbar-title": "Set up authenticator", "v2.page.settings-mfa-create-totp.default.description": "First, download Google Authenticator App from the Google Play Store or Apple App Store.

Scan the QR code or enter the key with your authenticator device / app.", + "v2.page.settings-mfa-create-totp.default.title": "Set up authenticator", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", - "v2.page.settings-mfa-enter-totp.default.navbar-title": "Verification Code", "v2.page.settings-mfa-enter-totp.default.description": "Enter the 6-digit code you see in the authenticator device/app.", + "v2.page.settings-mfa-enter-totp.default.title": "Verification Code", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Rescan QR code", - "v2.page.settings-mfa-view-recovery-code.default.storage-description": "If you lost access to your authentication device, use these recovery codes to recover your account. \nKeep them somewhere safe but accessible.", - "v2.page.settings-mfa-view-recovery-code.default.title": "Recovery code", "v2.page.settings-mfa-password.default.additional-password-added-at": "Added at {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Additional password", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Updated at {time, datetime, short} UTC", @@ -1196,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Remove additional password", "v2.page.settings-mfa-password.default.remove-button-label": "Remove additional password", "v2.page.settings-mfa-password.default.title": "Additional password", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "If you lost access to your authentication device, use these recovery codes to recover your account. \nKeep them somewhere safe but accessible.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Recovery code", "v2.page.settings-mfa.default.activated-label": "Activated", "v2.page.settings-mfa.default.additional-password-label": "Additional password", "v2.page.settings-mfa.default.inactive-label": "Inactive", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_totp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_totp.html index 516c7c15cc..ee5bfada0c 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_totp.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_totp.html @@ -5,7 +5,7 @@ (dict "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) "BackHref" (call $.MakeURL "/settings/mfa/totp") - "Title" (translate "v2.page.settings-mfa-create-totp.default.navbar-title" nil) + "Title" (translate "v2.page.settings-mfa-create-totp.default.title" nil) ) }} {{ end }} diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_totp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_totp.html index af349e4f19..4dd1b11c74 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_totp.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_totp.html @@ -5,7 +5,7 @@ (dict "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) "BackHref" (call $.MakeURL "/settings/mfa") - "Title" (translate "v2.page.settings-mfa-enter-totp.default.navbar-title" nil) + "Title" (translate "v2.page.settings-mfa-enter-totp.default.title" nil) ) }} {{ end }} diff --git a/resources/authgear/templates/es-419/translation.json b/resources/authgear/templates/es-419/translation.json index 8bf16320bd..b25f49d9a1 100644 --- a/resources/authgear/templates/es-419/translation.json +++ b/resources/authgear/templates/es-419/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", "v2.page.settings-mfa-create-password.default.title": "Contraseña adicional", + "v2.page.settings-mfa-create-totp.default.description": "Primero, descargue la aplicación Google Authenticator desde la Google Play Store o Apple App Store.

Escanee el código QR o ingrese la clave con su dispositivo/aplicación de autenticación.", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Ingrese el código de 6 dígitos que ve en el dispositivo/aplicación de autenticación.", + "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Volver a escanear código QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Agregada a las {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Actualizada a las {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Eliminar contraseña adicional", "v2.page.settings-mfa-password.default.remove-button-label": "Eliminar contraseña adicional", "v2.page.settings-mfa-password.default.title": "Contraseña adicional", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Si perdió acceso a su dispositivo de autenticación, use estos códigos de recuperación para recuperar su cuenta. \nGuárdelos en un lugar seguro pero accesible.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Código de recuperación", "v2.page.settings-mfa.default.activated-label": "Activada", "v2.page.settings-mfa.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa.default.inactive-label": "Inactiva", diff --git a/resources/authgear/templates/es-ES/translation.json b/resources/authgear/templates/es-ES/translation.json index 65d1482335..42c04c3587 100644 --- a/resources/authgear/templates/es-ES/translation.json +++ b/resources/authgear/templates/es-ES/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", "v2.page.settings-mfa-create-password.default.title": "Contraseña adicional", + "v2.page.settings-mfa-create-totp.default.description": "Primero, descargue la aplicación Google Authenticator desde la Google Play Store o la Apple App Store.

Escanee el código QR o ingrese la clave con su dispositivo/aplicación de autenticación.", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Ingrese el código de 6 dígitos que ve en el dispositivo/aplicación de autenticación.", + "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Volver a escanear el código QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Añadida a las {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Actualizada a las {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Eliminar contraseña adicional", "v2.page.settings-mfa-password.default.remove-button-label": "Eliminar contraseña adicional", "v2.page.settings-mfa-password.default.title": "Contraseña adicional", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Si perdió el acceso a su dispositivo de autenticación, use estos códigos de recuperación para recuperar su cuenta. \nGuárdelos en un lugar seguro pero accesible.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Código de recuperación", "v2.page.settings-mfa.default.activated-label": "Activada", "v2.page.settings-mfa.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa.default.inactive-label": "Inactiva", diff --git a/resources/authgear/templates/es/translation.json b/resources/authgear/templates/es/translation.json index 93aafb981e..c72fdfbf04 100644 --- a/resources/authgear/templates/es/translation.json +++ b/resources/authgear/templates/es/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", "v2.page.settings-mfa-create-password.default.title": "Contraseña adicional", + "v2.page.settings-mfa-create-totp.default.description": "Primero, descargue la aplicación Google Authenticator desde la Google Play Store o Apple App Store.

Escanee el código QR o ingrese la clave con su dispositivo/aplicación de autenticación.", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Ingrese el código de 6 dígitos que ve en el dispositivo/aplicación de autenticación.", + "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Volver a escanear código QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Añadido a las {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Actualizado a las {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Eliminar contraseña adicional", "v2.page.settings-mfa-password.default.remove-button-label": "Eliminar contraseña adicional", "v2.page.settings-mfa-password.default.title": "Contraseña adicional", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Si perdió acceso a su dispositivo de autenticación, use estos códigos de recuperación para recuperar su cuenta. \nGuárdelos en un lugar seguro pero accesible.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Código de recuperación", "v2.page.settings-mfa.default.activated-label": "Activado", "v2.page.settings-mfa.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa.default.inactive-label": "Inactivo", diff --git a/resources/authgear/templates/fil/translation.json b/resources/authgear/templates/fil/translation.json index 15e61b7c20..8f358c9789 100644 --- a/resources/authgear/templates/fil/translation.json +++ b/resources/authgear/templates/fil/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Hindi Napatunayan", "v2.page.settings-identity.default.verification-status-verified-label": "Napatunayan", "v2.page.settings-mfa-create-password.default.title": "Karagdagang password", + "v2.page.settings-mfa-create-totp.default.description": "Una, i-download ang Google Authenticator App mula sa Google Play Store o Apple App Store.

I-scan ang QR code o ipasok ang key gamit ang iyong authenticator device / app.", + "v2.page.settings-mfa-create-totp.default.title": "Mag-set up ng authenticator", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Ipasok ang 6-digit na code na nakikita mo sa authenticator device/app.", + "v2.page.settings-mfa-enter-totp.default.title": "Code para sa Pag-verify", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "I-scan muli ang QR code", "v2.page.settings-mfa-password.default.additional-password-added-at": "Naidagdag sa {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Karagdagang password", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Na-update sa {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Alisin ang karagdagang password", "v2.page.settings-mfa-password.default.remove-button-label": "Alisin ang karagdagang password", "v2.page.settings-mfa-password.default.title": "Karagdagang password", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Kung nawalan ka ng access sa iyong authentication device, gamitin ang mga recovery code na ito para ma-recover ang iyong account. \nItagong ligtas ngunit naa-access ang mga ito.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Recovery code", "v2.page.settings-mfa.default.activated-label": "Naka-activate", "v2.page.settings-mfa.default.additional-password-label": "Karagdagang password", "v2.page.settings-mfa.default.inactive-label": "Hindi naka-activate", diff --git a/resources/authgear/templates/fr/translation.json b/resources/authgear/templates/fr/translation.json index 7f118b6b5d..4e3fc96ae1 100644 --- a/resources/authgear/templates/fr/translation.json +++ b/resources/authgear/templates/fr/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Non vérifié", "v2.page.settings-identity.default.verification-status-verified-label": "Vérifié", "v2.page.settings-mfa-create-password.default.title": "Mot de passe supplémentaire", + "v2.page.settings-mfa-create-totp.default.description": "Tout d''abord, téléchargez l''application Google Authenticator depuis le Google Play Store ou Apple App Store.

Scannez le code QR ou entrez la clé avec votre appareil/application d''authentification.", + "v2.page.settings-mfa-create-totp.default.title": "Configurer l''authentificateur", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Entrez le code à 6 chiffres que vous voyez dans l''appareil/application d''authentification.", + "v2.page.settings-mfa-enter-totp.default.title": "Code de vérification", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Rescanner le code QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Ajouté à {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Mot de passe supplémentaire", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Mis à jour à {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Supprimer le mot de passe supplémentaire", "v2.page.settings-mfa-password.default.remove-button-label": "Supprimer le mot de passe supplémentaire", "v2.page.settings-mfa-password.default.title": "Mot de passe supplémentaire", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Si vous avez perdu l''accès à votre appareil d''authentification, utilisez ces codes de récupération pour récupérer votre compte. \nConservez-les dans un endroit sûr mais accessible.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Code de récupération", "v2.page.settings-mfa.default.activated-label": "Activé", "v2.page.settings-mfa.default.additional-password-label": "Mot de passe supplémentaire", "v2.page.settings-mfa.default.inactive-label": "Inactif", diff --git a/resources/authgear/templates/id/translation.json b/resources/authgear/templates/id/translation.json index 0fa505711f..585f3138aa 100644 --- a/resources/authgear/templates/id/translation.json +++ b/resources/authgear/templates/id/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Tidak Diverifikasi", "v2.page.settings-identity.default.verification-status-verified-label": "Diverifikasi", "v2.page.settings-mfa-create-password.default.title": "Kata sandi tambahan", + "v2.page.settings-mfa-create-totp.default.description": "Pertama, unduh Aplikasi Google Authenticator dari Google Play Store atau Apple App Store.

Pindai kode QR atau masukkan kunci dengan perangkat/aplikasi pengautentikasi Anda.", + "v2.page.settings-mfa-create-totp.default.title": "Mengatur pengautentikasi", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Masukkan kode 6 digit yang Anda lihat di perangkat/aplikasi pengautentikasi.", + "v2.page.settings-mfa-enter-totp.default.title": "Kode Verifikasi", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Pindai Ulang Kode QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Ditambahkan pada {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Kata sandi tambahan", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Diperbarui pada {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Hapus kata sandi tambahan", "v2.page.settings-mfa-password.default.remove-button-label": "Hapus kata sandi tambahan", "v2.page.settings-mfa-password.default.title": "Kata sandi tambahan", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Jika Anda kehilangan akses ke perangkat autentikasi Anda, gunakan kode pemulihan ini untuk memulihkan akun Anda. \nSimpan di tempat yang aman tetapi dapat diakses.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Kode Pemulihan", "v2.page.settings-mfa.default.activated-label": "Diaktifkan", "v2.page.settings-mfa.default.additional-password-label": "Kata sandi tambahan", "v2.page.settings-mfa.default.inactive-label": "Tidak aktif", diff --git a/resources/authgear/templates/it/translation.json b/resources/authgear/templates/it/translation.json index 1228da18fc..e3f32447e8 100644 --- a/resources/authgear/templates/it/translation.json +++ b/resources/authgear/templates/it/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Non verificato", "v2.page.settings-identity.default.verification-status-verified-label": "Verificato", "v2.page.settings-mfa-create-password.default.title": "Password aggiuntiva", + "v2.page.settings-mfa-create-totp.default.description": "Per prima cosa, scarica l'app Google Authenticator dal Google Play Store o dall'Apple App Store.

Scansiona il codice QR o inserisci la chiave con il tuo dispositivo/app di autenticazione.", + "v2.page.settings-mfa-create-totp.default.title": "Imposta l''autenticatore", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Inserisci il codice a 6 cifre che vedi nel dispositivo/app di autenticazione.", + "v2.page.settings-mfa-enter-totp.default.title": "Codice di verifica", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Riscansiona il codice QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Aggiunta alle {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Password aggiuntiva", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Aggiornata alle {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Rimuovi password aggiuntiva", "v2.page.settings-mfa-password.default.remove-button-label": "Rimuovi password aggiuntiva", "v2.page.settings-mfa-password.default.title": "Password aggiuntiva", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Se hai perso l''accesso al tuo dispositivo di autenticazione, usa questi codici di recupero per recuperare il tuo account. \nConservali in un luogo sicuro ma accessibile.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Codice di recupero", "v2.page.settings-mfa.default.activated-label": "Attivata", "v2.page.settings-mfa.default.additional-password-label": "Password aggiuntiva", "v2.page.settings-mfa.default.inactive-label": "Inattiva", diff --git a/resources/authgear/templates/ja/translation.json b/resources/authgear/templates/ja/translation.json index 1117ab5081..419885aadb 100644 --- a/resources/authgear/templates/ja/translation.json +++ b/resources/authgear/templates/ja/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "未検証", "v2.page.settings-identity.default.verification-status-verified-label": "検証済み", "v2.page.settings-mfa-create-password.default.title": "追加のパスワード", + "v2.page.settings-mfa-create-totp.default.description": "最初に、Google Play ストアまたはApple App StoreからGoogle Authenticator Appをダウンロードしてください。

QRコードをスキャンするか、認証デバイス/アプリにキーを入力してください。", + "v2.page.settings-mfa-create-totp.default.title": "認証アプリの設定", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "認証デバイス/アプリに表示されている6桁のコードを入力してください。", + "v2.page.settings-mfa-enter-totp.default.title": "確認コード", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "QRコードを再スキャン", "v2.page.settings-mfa-password.default.additional-password-added-at": "追加日時: {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "追加のパスワード", "v2.page.settings-mfa-password.default.additional-password-updated-at": "更新日時: {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "追加のパスワードを削除", "v2.page.settings-mfa-password.default.remove-button-label": "追加のパスワードを削除する", "v2.page.settings-mfa-password.default.title": "追加のパスワード", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "認証デバイスにアクセスできなくなった場合は、これらの復旧コードを使ってアカウントを復旧できます。\n安全で利用可能な場所に保管してください。", + "v2.page.settings-mfa-view-recovery-code.default.title": "復旧コード", "v2.page.settings-mfa.default.activated-label": "有効化済み", "v2.page.settings-mfa.default.additional-password-label": "追加のパスワード", "v2.page.settings-mfa.default.inactive-label": "無効", diff --git a/resources/authgear/templates/ko/translation.json b/resources/authgear/templates/ko/translation.json index f1974e05a0..bac2c476b7 100644 --- a/resources/authgear/templates/ko/translation.json +++ b/resources/authgear/templates/ko/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "인증되지 않음", "v2.page.settings-identity.default.verification-status-verified-label": "인증됨", "v2.page.settings-mfa-create-password.default.title": "추가 비밀번호", + "v2.page.settings-mfa-create-totp.default.description": "첫째, Google Play 스토어 또는 Apple 앱스토어에서 Google Authenticator 앱을 다운로드하세요.

QR 코드를 스캔하거나 인증기기/앱에 키를 입력하세요.", + "v2.page.settings-mfa-create-totp.default.title": "인증기기 설정", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "인증기기/앱에 표시된 6자리 코드를 입력하세요.", + "v2.page.settings-mfa-enter-totp.default.title": "인증 코드", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "QR 코드 다시 스캔", "v2.page.settings-mfa-password.default.additional-password-added-at": "{time, datetime, short} UTC에 추가됨", "v2.page.settings-mfa-password.default.additional-password-label": "추가 비밀번호", "v2.page.settings-mfa-password.default.additional-password-updated-at": "{time, datetime, short} UTC에 업데이트됨", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "추가 비밀번호 제거", "v2.page.settings-mfa-password.default.remove-button-label": "추가 비밀번호 제거", "v2.page.settings-mfa-password.default.title": "추가 비밀번호", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "인증기기에 접근할 수 없는 경우, 이 복구 코드를 사용하여 계정을 복구할 수 있습니다.\n안전하고 접근 가능한 곳에 보관하세요.", + "v2.page.settings-mfa-view-recovery-code.default.title": "복구 코드", "v2.page.settings-mfa.default.activated-label": "활성화됨", "v2.page.settings-mfa.default.additional-password-label": "추가 비밀번호", "v2.page.settings-mfa.default.inactive-label": "비활성화", diff --git a/resources/authgear/templates/ms/translation.json b/resources/authgear/templates/ms/translation.json index 9a224ee47c..06357f1ba6 100644 --- a/resources/authgear/templates/ms/translation.json +++ b/resources/authgear/templates/ms/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Tidak Disahkan", "v2.page.settings-identity.default.verification-status-verified-label": "Disahkan", "v2.page.settings-mfa-create-password.default.title": "Kata laluan tambahan", + "v2.page.settings-mfa-create-totp.default.description": "Pertama, muat turun Aplikasi Google Authenticator dari Google Play Store atau Apple App Store.

Imbas kod QR atau masukkan kunci dengan peranti / aplikasi penguat sah anda.", + "v2.page.settings-mfa-create-totp.default.title": "Sediakan pengaut sah", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Masukkan kod 6 digit yang anda lihat dalam peranti/aplikasi pengaut sah.", + "v2.page.settings-mfa-enter-totp.default.title": "Kod Pengesahan", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Imbas semula kod QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Ditambah pada {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Kata laluan tambahan", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Dikemaskini pada {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Alih keluar kata laluan tambahan", "v2.page.settings-mfa-password.default.remove-button-label": "Buang kata laluan tambahan", "v2.page.settings-mfa-password.default.title": "Kata laluan tambahan", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Jika anda kehilangan akses kepada peranti pengesahan anda, gunakan kod pemulihan ini untuk memulihkan akaun anda. \nSimpan di tempat yang selamat tetapi boleh diakses.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Kod Pemulihan", "v2.page.settings-mfa.default.activated-label": "Diaktifkan", "v2.page.settings-mfa.default.additional-password-label": "Kata laluan tambahan", "v2.page.settings-mfa.default.inactive-label": "Tidak aktif", diff --git a/resources/authgear/templates/nl/translation.json b/resources/authgear/templates/nl/translation.json index dc70faa2c5..e5c74d5637 100644 --- a/resources/authgear/templates/nl/translation.json +++ b/resources/authgear/templates/nl/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Niet geverifieerd", "v2.page.settings-identity.default.verification-status-verified-label": "Geverifieerd", "v2.page.settings-mfa-create-password.default.title": "Aanvullend wachtwoord", + "v2.page.settings-mfa-create-totp.default.description": "Download eerst de Google Authenticator App van de Google Play Store of Apple App Store.

Scan de QR-code of voer de sleutel in met uw authenticator-apparaat / app.", + "v2.page.settings-mfa-create-totp.default.title": "Authenticator instellen", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Voer de 6-cijferige code in die u ziet in de authenticator-app.", + "v2.page.settings-mfa-enter-totp.default.title": "Verificatiecode", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "QR-code opnieuw scannen", "v2.page.settings-mfa-password.default.additional-password-added-at": "Toegevoegd op {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Aanvullend wachtwoord", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Bijgewerkt op {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Verwijder extra wachtwoord", "v2.page.settings-mfa-password.default.remove-button-label": "Verwijder aanvullend wachtwoord", "v2.page.settings-mfa-password.default.title": "Aanvullend wachtwoord", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Als u geen toegang meer hebt tot uw authenticatie-apparaat, gebruik dan deze herstelcodes om uw account te herstellen. \nBewaar ze op een veilige maar toegankelijke plek.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Herstelcode", "v2.page.settings-mfa.default.activated-label": "Geactiveerd", "v2.page.settings-mfa.default.additional-password-label": "Aanvullend wachtwoord", "v2.page.settings-mfa.default.inactive-label": "Inactief", diff --git a/resources/authgear/templates/pl/translation.json b/resources/authgear/templates/pl/translation.json index 9b97bf72f9..aa5940f64e 100644 --- a/resources/authgear/templates/pl/translation.json +++ b/resources/authgear/templates/pl/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Niezweryfikowane", "v2.page.settings-identity.default.verification-status-verified-label": "Zweryfikowane", "v2.page.settings-mfa-create-password.default.title": "Dodatkowe hasło", + "v2.page.settings-mfa-create-totp.default.description": "Najpierw pobierz aplikację Google Authenticator z Google Play Store lub Apple App Store.

Zeskanuj kod QR lub wprowadź klucz za pomocą urządzenia/aplikacji uwierzytelniającej.", + "v2.page.settings-mfa-create-totp.default.title": "Skonfiguruj uwierzytelnianie", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Wprowadź 6-cyfrowy kod, który widzisz w urządzeniu/aplikacji uwierzytelniającej.", + "v2.page.settings-mfa-enter-totp.default.title": "Kod weryfikacyjny", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Zeskanuj ponownie kod QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Dodane o {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Dodatkowe hasło", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Zaktualizowane o {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Usuń dodatkowe hasło", "v2.page.settings-mfa-password.default.remove-button-label": "Usuń dodatkowe hasło", "v2.page.settings-mfa-password.default.title": "Dodatkowe hasło", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Jeśli utraciłeś dostęp do swojego urządzenia uwierzytelniającego, użyj tych kodów odzyskiwania, aby odzyskać dostęp do konta. \nPrzechowuj je w bezpiecznym, ale dostępnym miejscu.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Kod odzyskiwania", "v2.page.settings-mfa.default.activated-label": "Aktywowane", "v2.page.settings-mfa.default.additional-password-label": "Dodatkowe hasło", "v2.page.settings-mfa.default.inactive-label": "Nieaktywne", diff --git a/resources/authgear/templates/pt-BR/translation.json b/resources/authgear/templates/pt-BR/translation.json index 2a9e3cbe7e..782399adc8 100644 --- a/resources/authgear/templates/pt-BR/translation.json +++ b/resources/authgear/templates/pt-BR/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", "v2.page.settings-mfa-create-password.default.title": "Senha adicional", + "v2.page.settings-mfa-create-totp.default.description": "Primeiro, baixe o aplicativo Google Authenticator da Google Play Store ou Apple App Store.

Escaneie o código QR ou insira a chave com seu dispositivo/aplicativo autenticador.", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Digite o código de 6 dígitos que você vê no dispositivo/aplicativo autenticador.", + "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Ler novamente o código QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Adicionada em {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Senha adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Atualizada em {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Remover senha adicional", "v2.page.settings-mfa-password.default.remove-button-label": "Remover senha adicional", "v2.page.settings-mfa-password.default.title": "Senha adicional", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Se você perder o acesso ao seu dispositivo de autenticação, use esses códigos de recuperação para recuperar sua conta. \nGuarde-os em um lugar seguro, mas acessível.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Código de recuperação", "v2.page.settings-mfa.default.activated-label": "Ativada", "v2.page.settings-mfa.default.additional-password-label": "Senha adicional", "v2.page.settings-mfa.default.inactive-label": "Inativa", diff --git a/resources/authgear/templates/pt-PT/translation.json b/resources/authgear/templates/pt-PT/translation.json index 8e5ed35683..5fef5e4a28 100644 --- a/resources/authgear/templates/pt-PT/translation.json +++ b/resources/authgear/templates/pt-PT/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", "v2.page.settings-mfa-create-password.default.title": "Palavra-passe adicional", + "v2.page.settings-mfa-create-totp.default.description": "Primeiro, descarregue a aplicação Google Authenticator da Google Play Store ou Apple App Store.

Digitalize o código QR ou introduza a chave com o seu dispositivo/aplicação de autenticação.", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Introduza o código de 6 dígitos que vê no dispositivo/aplicação de autenticação.", + "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Redigitalizar código QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Adicionada às {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Palavra-passe adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Atualizada às {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Remover palavra-passe adicional", "v2.page.settings-mfa-password.default.remove-button-label": "Remover palavra-passe adicional", "v2.page.settings-mfa-password.default.title": "Palavra-passe adicional", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Se perder o acesso ao seu dispositivo de autenticação, utilize estes códigos de recuperação para recuperar a sua conta. \nGuarde-os num local seguro, mas acessível.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Código de recuperação", "v2.page.settings-mfa.default.activated-label": "Ativada", "v2.page.settings-mfa.default.additional-password-label": "Palavra-passe adicional", "v2.page.settings-mfa.default.inactive-label": "Inativa", diff --git a/resources/authgear/templates/pt/translation.json b/resources/authgear/templates/pt/translation.json index cd2aeb920b..7190b110c9 100644 --- a/resources/authgear/templates/pt/translation.json +++ b/resources/authgear/templates/pt/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", "v2.page.settings-mfa-create-password.default.title": "Senha adicional", + "v2.page.settings-mfa-create-totp.default.description": "Primeiro, baixe o aplicativo Google Authenticator da Google Play Store ou Apple App Store.

Escaneie o código QR ou insira a chave com seu dispositivo/aplicativo autenticador.", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Digite o código de 6 dígitos que você vê no dispositivo/aplicativo autenticador.", + "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Ler novamente o código QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Adicionado em {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Senha adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Atualizado em {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Remover senha adicional", "v2.page.settings-mfa-password.default.remove-button-label": "Remover senha adicional", "v2.page.settings-mfa-password.default.title": "Senha adicional", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Se você perder o acesso ao seu dispositivo de autenticação, use esses códigos de recuperação para recuperar sua conta. \nGuarde-os em um lugar seguro, mas acessível.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Código de recuperação", "v2.page.settings-mfa.default.activated-label": "Ativado", "v2.page.settings-mfa.default.additional-password-label": "Senha adicional", "v2.page.settings-mfa.default.inactive-label": "Inativo", diff --git a/resources/authgear/templates/th/translation.json b/resources/authgear/templates/th/translation.json index dc08c0b73a..f7640821d5 100644 --- a/resources/authgear/templates/th/translation.json +++ b/resources/authgear/templates/th/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "ยังไม่ได้รับการยืนยัน", "v2.page.settings-identity.default.verification-status-verified-label": "ได้รับการยืนยันแล้ว", "v2.page.settings-mfa-create-password.default.title": "รหัสผ่านเพิ่มเติม", + "v2.page.settings-mfa-create-totp.default.description": "เริ่มแรก ดาวน์โหลดแอป Google Authenticator จาก Google Play Store หรือ Apple App Store.

สแกนรหัส QR หรือป้อนคีย์ด้วยอุปกรณ์/แอปรับรองความถูกต้อง", + "v2.page.settings-mfa-create-totp.default.title": "ตั้งค่าตัวรับรองความถูกต้อง", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "ป้อนรหัส 6 หลักที่คุณเห็นในอุปกรณ์/แอปรับรองความถูกต้อง", + "v2.page.settings-mfa-enter-totp.default.title": "รหัสยืนยัน", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "สแกนรหัส QR ใหม่", "v2.page.settings-mfa-password.default.additional-password-added-at": "เพิ่มเมื่อ {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "รหัสผ่านเพิ่มเติม", "v2.page.settings-mfa-password.default.additional-password-updated-at": "อัปเดตเมื่อ {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "ลบรหัสผ่านเพิ่มเติม", "v2.page.settings-mfa-password.default.remove-button-label": "ลบรหัสผ่านเพิ่มเติม", "v2.page.settings-mfa-password.default.title": "รหัสผ่านเพิ่มเติม", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "หากคุณสูญเสียการเข้าถึงอุปกรณ์รับรองความถูกต้อง ให้ใช้รหัสกู้คืนเหล่านี้เพื่อกู้คืนบัญชีของคุณ \nเก็บรหัสไว้ในที่ปลอดภัยแต่สามารถเข้าถึงได้", + "v2.page.settings-mfa-view-recovery-code.default.title": "รหัสกู้คืน", "v2.page.settings-mfa.default.activated-label": "เปิดใช้งานแล้ว", "v2.page.settings-mfa.default.additional-password-label": "รหัสผ่านเพิ่มเติม", "v2.page.settings-mfa.default.inactive-label": "ไม่ได้ใช้งาน", diff --git a/resources/authgear/templates/vi/translation.json b/resources/authgear/templates/vi/translation.json index 15ec6685ca..cc53c220ff 100644 --- a/resources/authgear/templates/vi/translation.json +++ b/resources/authgear/templates/vi/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "Chưa được xác minh", "v2.page.settings-identity.default.verification-status-verified-label": "Đã xác minh", "v2.page.settings-mfa-create-password.default.title": "Mật khẩu bổ sung", + "v2.page.settings-mfa-create-totp.default.description": "Đầu tiên, tải ứng dụng Google Authenticator từ Google Play Store hoặc Apple App Store.

Quét mã QR hoặc nhập khóa với thiết bị/ứng dụng xác thực của bạn.", + "v2.page.settings-mfa-create-totp.default.title": "Thiết lập xác thực", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "Nhập mã 6 chữ số bạn thấy trong thiết bị/ứng dụng xác thực.", + "v2.page.settings-mfa-enter-totp.default.title": "Mã xác minh", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Quét lại mã QR", "v2.page.settings-mfa-password.default.additional-password-added-at": "Đã thêm vào {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Mật khẩu bổ sung", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Đã cập nhật vào {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "Xóa mật khẩu bổ sung", "v2.page.settings-mfa-password.default.remove-button-label": "Xóa mật khẩu bổ sung", "v2.page.settings-mfa-password.default.title": "Mật khẩu bổ sung", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "Nếu bạn mất quyền truy cập vào thiết bị xác thực, hãy sử dụng các mã khôi phục này để khôi phục tài khoản của bạn. \nGiữ chúng ở nơi an toàn nhưng có thể truy cập.", + "v2.page.settings-mfa-view-recovery-code.default.title": "Mã khôi phục", "v2.page.settings-mfa.default.activated-label": "Đã kích hoạt", "v2.page.settings-mfa.default.additional-password-label": "Mật khẩu bổ sung", "v2.page.settings-mfa.default.inactive-label": "Không hoạt động", diff --git a/resources/authgear/templates/zh-CN/translation.json b/resources/authgear/templates/zh-CN/translation.json index 0952fcfdb8..7d33742cf3 100644 --- a/resources/authgear/templates/zh-CN/translation.json +++ b/resources/authgear/templates/zh-CN/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "未验证", "v2.page.settings-identity.default.verification-status-verified-label": "已验证", "v2.page.settings-mfa-create-password.default.title": "额外密码", + "v2.page.settings-mfa-create-totp.default.description": "首先,从Google Play商店Apple App Store下载Google Authenticator App。

使用您的身份验证器设备/应用程序扫描二维码或输入密钥。", + "v2.page.settings-mfa-create-totp.default.title": "设置身份验证器", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "输入您在身份验证器设备/应用程序中看到的6位数字代码。", + "v2.page.settings-mfa-enter-totp.default.title": "验证码", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "重新扫描二维码", "v2.page.settings-mfa-password.default.additional-password-added-at": "添加于 {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "额外密码", "v2.page.settings-mfa-password.default.additional-password-updated-at": "更新于 {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "删除额外密码", "v2.page.settings-mfa-password.default.remove-button-label": "移除额外密码", "v2.page.settings-mfa-password.default.title": "额外密码", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "如果您无法访问您的身份验证设备,请使用这些恢复代码来恢复您的账户。\n请将它们存放在安全且可访问的地方。", + "v2.page.settings-mfa-view-recovery-code.default.title": "恢复代码", "v2.page.settings-mfa.default.activated-label": "已激活", "v2.page.settings-mfa.default.additional-password-label": "额外密码", "v2.page.settings-mfa.default.inactive-label": "未激活", diff --git a/resources/authgear/templates/zh-HK/translation.json b/resources/authgear/templates/zh-HK/translation.json index c7317a8f97..9d406d7274 100644 --- a/resources/authgear/templates/zh-HK/translation.json +++ b/resources/authgear/templates/zh-HK/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "未驗證", "v2.page.settings-identity.default.verification-status-verified-label": "已驗證", "v2.page.settings-mfa-create-password.default.title": "輔助密碼", + "v2.page.settings-mfa-create-totp.default.description": "首先,從Google Play 商店Apple App Store下載Google Authenticator App。

使用您的驗證器設備/應用程式掃描QR碼或輸入密鑰。", + "v2.page.settings-mfa-create-totp.default.title": "設置驗證器", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "輸入您在驗證器設備/應用程式中看到的6位數字代碼。", + "v2.page.settings-mfa-enter-totp.default.title": "驗證碼", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "重新掃描QR碼", "v2.page.settings-mfa-password.default.additional-password-added-at": "新增時間: {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "輔助密碼", "v2.page.settings-mfa-password.default.additional-password-updated-at": "上次更新時間: {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "刪除輔助密碼", "v2.page.settings-mfa-password.default.remove-button-label": "刪除輔助密碼", "v2.page.settings-mfa-password.default.title": "輔助密碼", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "如果您無法訪問您的驗證設備,請使用這些恢復代碼來恢復您的帳戶。\n請將它們存放在安全且可訪問的地方。", + "v2.page.settings-mfa-view-recovery-code.default.title": "恢復代碼", "v2.page.settings-mfa.default.activated-label": "已啟用", "v2.page.settings-mfa.default.additional-password-label": "輔助密碼", "v2.page.settings-mfa.default.inactive-label": "未啟用", diff --git a/resources/authgear/templates/zh-TW/translation.json b/resources/authgear/templates/zh-TW/translation.json index e98932df9f..4c3219ddd3 100644 --- a/resources/authgear/templates/zh-TW/translation.json +++ b/resources/authgear/templates/zh-TW/translation.json @@ -1181,6 +1181,12 @@ "v2.page.settings-identity.default.verification-status-unverified-label": "未驗證", "v2.page.settings-identity.default.verification-status-verified-label": "已驗證", "v2.page.settings-mfa-create-password.default.title": "輔助密碼", + "v2.page.settings-mfa-create-totp.default.description": "首先,從Google Play 商店Apple App Store下載 Google Authenticator App。

使用您的驗證器設備/應用程式掃描 QR 碼或輸入密鑰。", + "v2.page.settings-mfa-create-totp.default.title": "設置驗證器", + "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-totp.default.description": "輸入您在驗證器設備/應用程式中看到的 6 位數字代碼。", + "v2.page.settings-mfa-enter-totp.default.title": "驗證碼", + "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "重新掃描 QR 碼", "v2.page.settings-mfa-password.default.additional-password-added-at": "新增時間: {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "輔助密碼", "v2.page.settings-mfa-password.default.additional-password-updated-at": "上次更新時間: {time, datetime, short} UTC", @@ -1188,6 +1194,8 @@ "v2.page.settings-mfa-password.default.delete-dialog-title": "刪除輔助密碼", "v2.page.settings-mfa-password.default.remove-button-label": "刪除輔助密碼", "v2.page.settings-mfa-password.default.title": "輔助密碼", + "v2.page.settings-mfa-view-recovery-code.default.storage-description": "如果您無法訪問您的驗證設備,請使用這些恢復代碼來恢復您的帳戶。\n請將它們存放在安全且可訪問的地方。", + "v2.page.settings-mfa-view-recovery-code.default.title": "恢復代碼", "v2.page.settings-mfa.default.activated-label": "已啟用", "v2.page.settings-mfa.default.additional-password-label": "輔助密碼", "v2.page.settings-mfa.default.inactive-label": "未啟用", From 9ecfa1075a2ddc4ee7d206cac35750776a95bde8 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Wed, 2 Oct 2024 18:53:18 +0800 Subject: [PATCH 11/31] Fix transaction not started error when update password in settings v2 --- .../service_authenticator.go | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 12d1dfa29c..0391355160 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -9,7 +9,6 @@ import ( "github.com/authgear/authgear-server/pkg/lib/session" "github.com/authgear/authgear-server/pkg/util/accesscontrol" "github.com/authgear/authgear-server/pkg/util/secretcode" - "github.com/authgear/authgear-server/pkg/util/uuid" ) type ChangePrimaryPasswordInput struct { @@ -28,10 +27,14 @@ type ChangePrimaryPasswordOutput struct { func (s *Service) ChangePrimaryPassword(resolvedSession session.ResolvedSession, input *ChangePrimaryPasswordInput) (*ChangePrimaryPasswordOutput, error) { redirectURI := input.RedirectURI - _, err := s.changePassword(resolvedSession, &changePasswordInput{ - Kind: authenticator.KindPrimary, - OldPassword: input.OldPassword, - NewPassword: input.NewPassword, + var err error + err = s.Database.WithTx(func() error { + _, err = s.changePassword(resolvedSession, &changePasswordInput{ + Kind: authenticator.KindPrimary, + OldPassword: input.OldPassword, + NewPassword: input.NewPassword, + }) + return err }) if err != nil { @@ -73,11 +76,13 @@ func (s *Service) CreateSecondaryPassword(resolvedSession session.ResolvedSessio PlainPassword: input.PlainPassword, }, } - info, err := s.Authenticators.NewWithAuthenticatorID(uuid.New(), spec) + info, err := s.Authenticators.New(spec) if err != nil { return nil, err } - err = s.createAuthenticator(info) + err = s.Database.WithTx(func() error { + return s.createAuthenticator(info) + }) if err != nil { return nil, err } @@ -93,10 +98,13 @@ type ChangeSecondaryPasswordOutput struct { } func (s *Service) ChangeSecondaryPassword(resolvedSession session.ResolvedSession, input *ChangeSecondaryPasswordInput) (*ChangeSecondaryPasswordOutput, error) { - _, err := s.changePassword(resolvedSession, &changePasswordInput{ - Kind: authenticator.KindSecondary, - OldPassword: input.OldPassword, - NewPassword: input.NewPassword, + err := s.Database.WithTx(func() error { + _, err := s.changePassword(resolvedSession, &changePasswordInput{ + Kind: authenticator.KindSecondary, + OldPassword: input.OldPassword, + NewPassword: input.NewPassword, + }) + return err }) if err != nil { From 01499ff85d42c5121b87651656a9d749592c9363 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Wed, 2 Oct 2024 11:29:34 +0800 Subject: [PATCH 12/31] Add start create oobotp in account management --- pkg/lib/accountmanagement/redis_store.go | 19 ++++++++-- .../service_authenticator.go | 37 +++++++++++++++++++ pkg/lib/accountmanagement/token.go | 22 +++++++---- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/pkg/lib/accountmanagement/redis_store.go b/pkg/lib/accountmanagement/redis_store.go index c3647bcafb..d8ada0a2a0 100644 --- a/pkg/lib/accountmanagement/redis_store.go +++ b/pkg/lib/accountmanagement/redis_store.go @@ -8,6 +8,7 @@ import ( goredis "github.com/go-redis/redis/v8" + "github.com/authgear/authgear-server/pkg/api/model" "github.com/authgear/authgear-server/pkg/lib/config" "github.com/authgear/authgear-server/pkg/lib/infra/redis" "github.com/authgear/authgear-server/pkg/lib/infra/redis/appredis" @@ -38,13 +39,20 @@ type GenerateTokenOptions struct { IdentityID string // AuthenticatorID for updating authenticator - AuthenticatorID string - AuthenticatorRecoveryCodes []string + AuthenticatorID string + AuthenticatorRecoveryCodes []string + + // TOTP AuthenticatorTOTPIssuer string AuthenticatorTOTPEndUserAccountID string AuthenticatorTOTPDisplayName string AuthenticatorTOTPSecret string AuthenticatorTOTPVerified bool + + // OOB OTP + AuthenticatorOOBOTPChannel model.AuthenticatorOOBChannel + AuthenticatorOOBOTPTarget string + AuthenticatorOOBOTPVerified bool } func (s *RedisStore) GenerateToken(options GenerateTokenOptions) (string, error) { @@ -65,15 +73,18 @@ func (s *RedisStore) GenerateToken(options GenerateTokenOptions) (string, error) } var tokenAuthenticator *TokenAuthenticator - if options.AuthenticatorID != "" || options.AuthenticatorTOTPSecret != "" || options.AuthenticatorTOTPVerified || len(options.AuthenticatorRecoveryCodes) > 0 { + if options.AuthenticatorID != "" || len(options.AuthenticatorRecoveryCodes) > 0 || options.AuthenticatorTOTPSecret != "" || options.AuthenticatorTOTPVerified || options.AuthenticatorOOBOTPChannel != "" || options.AuthenticatorOOBOTPTarget != "" || options.AuthenticatorOOBOTPVerified { tokenAuthenticator = &TokenAuthenticator{ AuthenticatorID: options.AuthenticatorID, + RecoveryCodes: options.AuthenticatorRecoveryCodes, TOTPIssuer: options.AuthenticatorTOTPIssuer, TOTPDisplayName: options.AuthenticatorTOTPDisplayName, TOTPEndUserAccountID: options.AuthenticatorTOTPEndUserAccountID, TOTPSecret: options.AuthenticatorTOTPSecret, TOTPVerified: options.AuthenticatorTOTPVerified, - RecoveryCodes: options.AuthenticatorRecoveryCodes, + OOBOTPChannel: options.AuthenticatorOOBOTPChannel, + OOBOTPTarget: options.AuthenticatorOOBOTPTarget, + OOBOTPVerified: options.AuthenticatorOOBOTPVerified, } } diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 0391355160..93d75b2dbe 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -364,3 +364,40 @@ func (s *Service) createAuthenticator(authenticatorInfo *authenticator.Info) err return nil } + +type StartAddOOBOTPAuthenticatorInput struct { + Channel model.AuthenticatorOOBChannel + Target string +} +type StartAddOOBOTPAuthenticatorOutput struct { + Token string +} + +func (s *Service) StartAddOOBOTPAuthenticator(resolvedSession session.ResolvedSession, input *StartAddOOBOTPAuthenticatorInput) (*StartAddOOBOTPAuthenticatorOutput, error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + + err := s.Database.WithTx(func() error { + err := s.sendOTPCode(userID, input.Channel, input.Target, false) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + + token, err := s.Store.GenerateToken(GenerateTokenOptions{ + UserID: userID, + AuthenticatorOOBOTPChannel: input.Channel, + AuthenticatorOOBOTPTarget: input.Target, + }) + if err != nil { + return nil, err + } + + return &StartAddOOBOTPAuthenticatorOutput{ + Token: token, + }, nil +} + diff --git a/pkg/lib/accountmanagement/token.go b/pkg/lib/accountmanagement/token.go index b597ace5ea..baf4859ec7 100644 --- a/pkg/lib/accountmanagement/token.go +++ b/pkg/lib/accountmanagement/token.go @@ -4,6 +4,7 @@ import ( "crypto/subtle" "time" + "github.com/authgear/authgear-server/pkg/api/model" "github.com/authgear/authgear-server/pkg/util/crypto" "github.com/authgear/authgear-server/pkg/util/rand" ) @@ -34,13 +35,20 @@ type TokenIdentity struct { } type TokenAuthenticator struct { - AuthenticatorID string `json:"authenticator_id,omitempty"` - TOTPIssuer string `json:"totp_issuer,omitempty"` - TOTPDisplayName string `json:"totp_display_name,omitempty"` - TOTPEndUserAccountID string `json:"end_user_account_id,omitempty"` - TOTPSecret string `json:"totp_secret,omitempty"` - TOTPVerified bool `json:"totp_verified,omitempty"` - RecoveryCodes []string `json:"recovery_codes,omitempty"` + AuthenticatorID string `json:"authenticator_id,omitempty"` + RecoveryCodes []string `json:"recovery_codes,omitempty"` + + // TOTP + TOTPIssuer string `json:"totp_issuer,omitempty"` + TOTPDisplayName string `json:"totp_display_name,omitempty"` + TOTPEndUserAccountID string `json:"end_user_account_id,omitempty"` + TOTPSecret string `json:"totp_secret,omitempty"` + TOTPVerified bool `json:"totp_verified,omitempty"` + + // OOB OTP + OOBOTPChannel model.AuthenticatorOOBChannel `json:"oob_otp_channel,omitempty"` + OOBOTPTarget string `json:"oob_otp_target,omitempty"` + OOBOTPVerified bool `json:"oob_otp_verified,omitempty"` } func (t *Token) CheckUser(userID string) error { From 5c90b35d5f6be396b946740abcbd3e542be26951 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Wed, 2 Oct 2024 11:30:35 +0800 Subject: [PATCH 13/31] Add resume create oobotp and resend oobotp in account management --- pkg/lib/accountmanagement/service.go | 8 ++- .../service_authenticator.go | 54 +++++++++++++++++++ pkg/lib/accountmanagement/service_identity.go | 8 +-- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/pkg/lib/accountmanagement/service.go b/pkg/lib/accountmanagement/service.go index 3a1d2b01bb..68f52585ed 100644 --- a/pkg/lib/accountmanagement/service.go +++ b/pkg/lib/accountmanagement/service.go @@ -305,6 +305,9 @@ func (s *Service) ResendOTPCode(resolvedSession session.ResolvedSession, tokenSt target = token.Identity.PhoneNumber channel = model.AuthenticatorOOBChannelSMS } + } else if token.Authenticator != nil { + target = token.Authenticator.OOBOTPTarget + channel = token.Authenticator.OOBOTPChannel } else { panic(fmt.Errorf("accountmanagement: unexpected token in resend otp code")) } @@ -368,13 +371,14 @@ func (s *Service) sendOTPCode(userID string, channel model.AuthenticatorOOBChann return nil } -func (s *Service) verifyOTP(userID string, channel model.AuthenticatorOOBChannel, target string, code string) error { +func (s *Service) VerifyOTP(userID string, channel model.AuthenticatorOOBChannel, target string, code string, skipConsume bool) error { err := s.OTPCodeService.VerifyOTP( otp.KindVerification(s.Config, channel), target, code, &otp.VerifyOptions{ - UserID: userID, + UserID: userID, + SkipConsume: skipConsume, }, ) if apierrors.IsKind(err, otp.InvalidOTPCode) { diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 93d75b2dbe..47cc360315 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -401,3 +401,57 @@ func (s *Service) StartAddOOBOTPAuthenticator(resolvedSession session.ResolvedSe }, nil } +type ResumeAddOOBOTPAuthenticatorInput struct { + Code string +} +type ResumeAddOOBOTPAuthenticatorOutput struct { + Token string +} + +func (s *Service) ResumeAddOOBOTPAuthenticator(resolvedSession session.ResolvedSession, tokenString string, input *ResumeAddOOBOTPAuthenticatorInput) (output *ResumeAddOOBOTPAuthenticatorOutput, err error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + token, err := s.Store.GetToken(tokenString) + defer func() { + if err == nil { + _, err = s.Store.ConsumeToken(tokenString) + } + }() + + if err != nil { + return + } + + err = token.CheckUser(userID) + if err != nil { + return + } + + err = s.VerifyOTP( + userID, + token.Authenticator.OOBOTPChannel, + token.Authenticator.OOBOTPTarget, + input.Code, + false, + ) + if err != nil { + return + } + + recoveryCodes := s.MFA.GenerateRecoveryCodes() + + newToken, err := s.Store.GenerateToken(GenerateTokenOptions{ + UserID: userID, + AuthenticatorRecoveryCodes: recoveryCodes, + AuthenticatorOOBOTPChannel: token.Authenticator.OOBOTPChannel, + AuthenticatorOOBOTPTarget: token.Authenticator.OOBOTPTarget, + AuthenticatorOOBOTPVerified: true, + }) + if err != nil { + return + } + + output = &ResumeAddOOBOTPAuthenticatorOutput{ + Token: newToken, + } + return +} diff --git a/pkg/lib/accountmanagement/service_identity.go b/pkg/lib/accountmanagement/service_identity.go index 6266800e50..1fdf8973d8 100644 --- a/pkg/lib/accountmanagement/service_identity.go +++ b/pkg/lib/accountmanagement/service_identity.go @@ -247,7 +247,7 @@ func (s *Service) ResumeAddIdentityEmail(resolvedSession session.ResolvedSession return } - err = s.verifyOTP(userID, model.AuthenticatorOOBChannelEmail, token.Identity.Email, input.Code) + err = s.VerifyOTP(userID, model.AuthenticatorOOBChannelEmail, token.Identity.Email, input.Code, false) if err != nil { return } @@ -403,7 +403,7 @@ func (s *Service) ResumeUpdateIdentityEmail(resolvedSession session.ResolvedSess return } - err = s.verifyOTP(userID, model.AuthenticatorOOBChannelEmail, token.Identity.Email, input.Code) + err = s.VerifyOTP(userID, model.AuthenticatorOOBChannelEmail, token.Identity.Email, input.Code, false) if err != nil { return } @@ -635,7 +635,7 @@ func (s *Service) ResumeAddIdentityPhone(resolvedSession session.ResolvedSession return } - err = s.verifyOTP(userID, model.AuthenticatorOOBChannelSMS, token.Identity.PhoneNumber, input.Code) + err = s.VerifyOTP(userID, model.AuthenticatorOOBChannelSMS, token.Identity.PhoneNumber, input.Code, false) if err != nil { return } @@ -788,7 +788,7 @@ func (s *Service) ResumeUpdateIdentityPhone(resolvedSession session.ResolvedSess return } - err = s.verifyOTP(userID, model.AuthenticatorOOBChannelSMS, token.Identity.PhoneNumber, input.Code) + err = s.VerifyOTP(userID, model.AuthenticatorOOBChannelSMS, token.Identity.PhoneNumber, input.Code, false) if err != nil { return } From d58d44a282f8eea7d83f158119f4571ee5c8fccb Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Wed, 2 Oct 2024 11:30:56 +0800 Subject: [PATCH 14/31] Add finish create oobotp in account management --- .../service_authenticator.go | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 47cc360315..654e828a68 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -455,3 +455,73 @@ func (s *Service) ResumeAddOOBOTPAuthenticator(resolvedSession session.ResolvedS } return } + +type FinishAddOOBOTPAuthenticatorInput struct { +} + +type FinishAddOOBOTPAuthenticatorOutput struct { + Info *authenticator.Info +} + +func (s *Service) FinishAddOOBOTPAuthenticator(resolvedSession session.ResolvedSession, tokenString string, input *FinishAddOOBOTPAuthenticatorInput) (output *FinishAddOOBOTPAuthenticatorOutput, err error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + token, err := s.Store.GetToken(tokenString) + defer func() { + if err == nil { + _, err = s.Store.ConsumeToken(tokenString) + } + }() + + if err != nil { + return + } + + err = token.CheckUser(userID) + if err != nil { + return + } + + spec := &authenticator.Spec{ + UserID: userID, + IsDefault: false, + Kind: model.AuthenticatorKindSecondary, + OOBOTP: &authenticator.OOBOTPSpec{}, + } + + switch token.Authenticator.OOBOTPChannel { + case model.AuthenticatorOOBChannelEmail: + spec.Type = model.AuthenticatorTypeOOBEmail + spec.OOBOTP.Email = token.Authenticator.OOBOTPTarget + case model.AuthenticatorOOBChannelWhatsapp: + fallthrough + case model.AuthenticatorOOBChannelSMS: + spec.Type = model.AuthenticatorTypeOOBSMS + spec.OOBOTP.Phone = token.Authenticator.OOBOTPTarget + default: + panic("unexpected channel") + } + + info, err := s.Authenticators.New(spec) + if err != nil { + return + } + + err = s.Database.WithTx(func() error { + err = s.createAuthenticator(info) + if err != nil { + return err + } + + _, err = s.MFA.ReplaceRecoveryCodes(userID, token.Authenticator.RecoveryCodes) + if err != nil { + return err + } + + return nil + }) + + output = &FinishAddOOBOTPAuthenticatorOutput{ + Info: info, + } + return +} From f539cb5929ed06a548aaeb9bd019449b2d28e0ec Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 11:38:32 +0800 Subject: [PATCH 15/31] Add settings v2 create oob otp page --- pkg/auth/handler/webapp/authflowv2/deps.go | 1 + pkg/auth/handler/webapp/authflowv2/routes.go | 1 + .../authflowv2/settings_mfa_create_oob_otp.go | 122 ++++++++++++++++++ pkg/auth/routes.go | 1 + pkg/auth/wire_handler.go | 7 + .../authgear/templates/en/translation.json | 4 + .../en/web/authflowv2/settings_mfa.html | 4 +- .../settings_mfa_create_oob_otp.html | 112 ++++++++++++++++ .../en/web/authflowv2/settings_oob_otp.html | 2 +- 9 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 pkg/auth/handler/webapp/authflowv2/settings_mfa_create_oob_otp.go create mode 100644 resources/authgear/templates/en/web/authflowv2/settings_mfa_create_oob_otp.html diff --git a/pkg/auth/handler/webapp/authflowv2/deps.go b/pkg/auth/handler/webapp/authflowv2/deps.go index c51b63e045..fef9e9fb6a 100644 --- a/pkg/auth/handler/webapp/authflowv2/deps.go +++ b/pkg/auth/handler/webapp/authflowv2/deps.go @@ -59,6 +59,7 @@ var DependencySet = wire.NewSet( wire.Struct(new(AuthflowV2SettingsMFACreateTOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFAEnterTOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsOOBOTPHandler), "*"), + wire.Struct(new(AuthflowV2SettingsMFACreateOOBOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityAddEmailHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityEditEmailHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityVerifyEmailHandler), "*"), diff --git a/pkg/auth/handler/webapp/authflowv2/routes.go b/pkg/auth/handler/webapp/authflowv2/routes.go index c5e8668b1d..9962063ad9 100644 --- a/pkg/auth/handler/webapp/authflowv2/routes.go +++ b/pkg/auth/handler/webapp/authflowv2/routes.go @@ -85,6 +85,7 @@ const ( // nolint: gosec AuthflowV2RouteSettingsMFACreatePassword = "/settings/mfa/create_password" + AuthflowV2RouteSettingsMFACreateOOBOTP = "/settings/mfa/create_oob_otp_:channel" // nolint: gosec AuthflowV2RouteSettingsMFAPassword = "/settings/mfa/password" AuthflowV2RouteSettingsMFACreateTOTP = "/settings/mfa/create_totp" diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_oob_otp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_oob_otp.go new file mode 100644 index 0000000000..97433ba6bf --- /dev/null +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_oob_otp.go @@ -0,0 +1,122 @@ +package authflowv2 + +import ( + "net/http" + "net/url" + + "github.com/authgear/authgear-server/pkg/api/model" + handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" + "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + "github.com/authgear/authgear-server/pkg/auth/webapp" + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" + "github.com/authgear/authgear-server/pkg/lib/session" + "github.com/authgear/authgear-server/pkg/util/httproute" + "github.com/authgear/authgear-server/pkg/util/template" + "github.com/authgear/authgear-server/pkg/util/validation" +) + +var TemplateWebSettingsMFACreateOOBOTPHTML = template.RegisterHTML( + "web/authflowv2/settings_mfa_create_oob_otp.html", + handlerwebapp.SettingsComponents..., +) + +var AuthflowV2SettingsMFACreateOOBOTPSchema = validation.NewSimpleSchema(` + { + "type": "object", + "properties": { + "x_target": { "type": "string" } + }, + "required": ["x_target"] + } +`) + +func ConfigureAuthflowV2SettingsMFACreateOOBOTPRoute(route httproute.Route) httproute.Route { + return route. + WithMethods("OPTIONS", "POST", "GET"). + WithPathPattern(AuthflowV2RouteSettingsMFACreateOOBOTP) +} + +type SettingsMFACreateOOBOTPViewModel struct { + OOBAuthenticatorType model.AuthenticatorType + Channel model.AuthenticatorOOBChannel +} + +type AuthflowV2SettingsMFACreateOOBOTPHandler struct { + ControllerFactory handlerwebapp.ControllerFactory + BaseViewModel *viewmodels.BaseViewModeler + Renderer handlerwebapp.Renderer + + AccountManagementService *accountmanagement.Service +} + +func NewSettingsMFACreateOOBOTPViewModel(channel model.AuthenticatorOOBChannel, authenticatorType model.AuthenticatorType) SettingsMFACreateOOBOTPViewModel { + return SettingsMFACreateOOBOTPViewModel{ + OOBAuthenticatorType: authenticatorType, + Channel: channel, + } +} + +func (h *AuthflowV2SettingsMFACreateOOBOTPHandler) GetData(r *http.Request, w http.ResponseWriter, channel model.AuthenticatorOOBChannel) (map[string]interface{}, error) { + data := make(map[string]interface{}) + + authenticatorType, err := model.GetOOBAuthenticatorType(channel) + if err != nil { + return nil, err + } + + baseViewModel := h.BaseViewModel.ViewModelForAuthFlow(r, w) + viewmodels.Embed(data, baseViewModel) + + settingsViewModel := NewSettingsMFACreateOOBOTPViewModel(channel, authenticatorType) + viewmodels.Embed(data, settingsViewModel) + + return data, nil +} + +func (h *AuthflowV2SettingsMFACreateOOBOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctrl, err := h.ControllerFactory.New(r, w) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer ctrl.ServeWithoutDBTx() + + channel := model.AuthenticatorOOBChannel(httproute.GetParam(r, "channel")) + + ctrl.Get(func() error { + data, err := h.GetData(r, w, channel) + if err != nil { + return err + } + h.Renderer.RenderHTML(w, r, TemplateWebSettingsMFACreateOOBOTPHTML, data) + return nil + }) + + ctrl.PostAction("", func() error { + err := AuthflowV2SettingsMFACreateOOBOTPSchema.Validator().ValidateValue(handlerwebapp.FormToJSON(r.Form)) + if err != nil { + return err + } + + target := r.Form.Get("x_target") + + s := session.GetSession(r.Context()) + output, err := h.AccountManagementService.StartAddOOBOTPAuthenticator(s, &accountmanagement.StartAddOOBOTPAuthenticatorInput{ + Channel: channel, + Target: target, + }) + + redirectURI, err := url.Parse(AuthflowV2RouteSettingsMFAEnterOOBOTP) + if err != nil { + return err + } + q := redirectURI.Query() + q.Set("q_token", output.Token) + redirectURI.RawQuery = q.Encode() + + result := webapp.Result{RedirectURI: redirectURI.String()} + result.WriteResponse(w, r) + + return nil + }) +} diff --git a/pkg/auth/routes.go b/pkg/auth/routes.go index 1702ece1e5..088d0d6e40 100644 --- a/pkg/auth/routes.go +++ b/pkg/auth/routes.go @@ -501,6 +501,7 @@ func NewRouter(p *deps.RootProvider, configSource *configsource.ConfigSource) *h SettingV1: p.Handler(newWebAppSettingsOOBOTPHandler), SettingV2: p.Handler(newWebAppAuthflowV2SettingsOOBOTPHandler), }) + router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFACreateOOBOTPRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler)) router.Add(webapphandler.ConfigureSettingsRecoveryCodeRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppSettingsRecoveryCodeHandler)) router.Add(webapphandler.ConfigureSettingsSessionsRoute(webappSettingsSubRoutesRoute), &webapphandler.SettingsImplementationSwitcherHandler{ SettingV1: p.Handler(newWebAppSettingsSessionsHandler), diff --git a/pkg/auth/wire_handler.go b/pkg/auth/wire_handler.go index 368dccdc1a..19565da6fa 100644 --- a/pkg/auth/wire_handler.go +++ b/pkg/auth/wire_handler.go @@ -534,6 +534,13 @@ func newWebAppAuthflowV2SettingsOOBOTPHandler(p *deps.RequestProvider) http.Hand )) } +func newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler(p *deps.RequestProvider) http.Handler { + panic(wire.Build( + DependencySet, + wire.Bind(new(http.Handler), new(*handlerwebappauthflowv2.AuthflowV2SettingsMFACreateOOBOTPHandler)), + )) +} + func newWebAppSettingsPasskeyHandler(p *deps.RequestProvider) http.Handler { panic(wire.Build( DependencySet, diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index a65e848637..aed0ae96e7 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1180,6 +1180,10 @@ "v2.page.settings-identity-view-username.default.title": "Username", "v2.page.settings-identity.default.verification-status-unverified-label": "Not Verified", "v2.page.settings-identity.default.verification-status-verified-label": "Verified", + "v2.page.settings-mfa-create-oob-otp.default.title": "2-Step Verification", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "You can receive verification codes at this email.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "You can receive verification codes at this number.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "You can receive verification codes at this number.", "v2.page.settings-mfa-create-password.default.title": "Additional password", "v2.page.settings-mfa-create-totp.default.description": "First, download Google Authenticator App from the Google Play Store or Apple App Store.

Scan the QR code or enter the key with your authenticator device / app.", "v2.page.settings-mfa-create-totp.default.title": "Set up authenticator", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa.html index 825ae07550..e5ed5ee474 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa.html @@ -50,7 +50,7 @@ {{ end }} {{ if $.ShowSecondaryOOBOTPEmail }} - {{ $href := (call $.MakeURL "/settings/mfa/new_oob_otp_email") }} + {{ $href := (call $.MakeURL "/settings/mfa/create_oob_otp_email") }} {{ if $.HasSecondaryOOBOTPEmail }} {{ $href = (call $.MakeURL "/settings/mfa/oob_otp_email") }} {{ end }} @@ -65,7 +65,7 @@ {{ end }} {{ if $.ShowSecondaryOOBOTPSMS }} - {{ $href := (call $.MakeURL "/settings/mfa/new_oob_otp_sms") }} + {{ $href := (call $.MakeURL "/settings/mfa/create_oob_otp_sms") }} {{ if $.HasSecondaryOOBOTPSMS }} {{ $href = (call $.MakeURL "/settings/mfa/oob_otp_sms") }} {{ end }} diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_oob_otp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_oob_otp.html new file mode 100644 index 0000000000..c72ce969e1 --- /dev/null +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_create_oob_otp.html @@ -0,0 +1,112 @@ +{{ template "authflowv2/__settings_page_frame.html" . }} + +{{ define "page-navbar" }} + {{ template "authflowv2/__navbar.html" + (dict + "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) + "BackHref" (call $.MakeURL "/settings/mfa") + "Title" (translate "v2.page.settings-mfa-create-oob-otp.default.title" nil) + ) + }} +{{ end }} + +{{ define "page-content" }} + + {{ $err_map := (resolveError $.RawError (dict + "otpInput" (dict + "by_reason" (list "ValidationFailed") + ) + )) }} + + {{ $otp_err := index $err_map "otpInput" }} + {{ $unknown_err := index $err_map "unknown" }} + {{ $has_otp_err := not (isNil $otp_err) }} + {{ $has_unknown_err := not (isNil $unknown_err )}} + + {{ $otp_error_message := "" }} + {{ if $has_otp_err }} + {{ $otp_error_message = include "authflowv2/__error.html" (merge (dict "Error" $otp_err) $) }} + {{ end }} + + {{ $unknown_error_message := "" }} + {{ if $has_unknown_err }} + {{ $unknown_error_message = (include "authflowv2/__error.html" (merge (dict "Error" $unknown_err) $)) }} + {{ end }} + + {{ $show_captcha := false}} + {{ if $.IsBotProtectionRequired }} + {{ $show_captcha = true }} + {{ end }} + +
+
+

+ {{- if eq $.OOBAuthenticatorType "oob_otp_sms" }} + {{- if eq .Channel "whatsapp" }} + {{ include "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle" nil }} + {{ else if eq .Channel "sms" }} + {{ include "v2.page.settings-mfa-create-oob-otp.sms.subtitle" nil }} + {{- end }} + {{- end }} + {{- if eq $.OOBAuthenticatorType "oob_otp_email" }} + {{ include "v2.page.settings-mfa-create-oob-otp.email.subtitle" nil }} + {{ end }} +

+ + {{ template "authflowv2/__alert_message.html" + (dict + "Type" "error" + "Classname" "mt-4" + "Message" $unknown_error_message + ) + }} +
+ +
+ {{ $.CSRFField }} + + {{- if eq $.OOBAuthenticatorType "oob_otp_sms" }} + {{ template "authflowv2/__phone_input.html" + (dict + "Placeholder" (include "v2.component.input.default.placeholder-phone" nil) + "PlaceholderWithExample" (include "v2.component.input.default.placeholder-phone-with-example" nil) + "IsError" $has_otp_err + "AutoFocus" $.ShouldFocusInput + "InputName" "x_target" + ) + }} + {{- end }} + + {{- if eq $.OOBAuthenticatorType "oob_otp_email" }} + + {{- end }} + + {{ if $has_otp_err }} +

+ {{ $otp_error_message }} +

+ {{ end }} + + +
+
+{{ end }} diff --git a/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html b/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html index ce0735b61b..96378e152e 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html @@ -35,7 +35,7 @@ {{ end }} - {{ $href := (call $.MakeURL (printf "/settings/mfa/new_oob_otp_%s" $.OOBOTPType)) }} + {{ $href := (call $.MakeURL (printf "/settings/mfa/create_oob_otp_%s" $.OOBOTPType)) }} Date: Thu, 3 Oct 2024 11:39:42 +0800 Subject: [PATCH 16/31] Add settings v2 enter oob otp page --- pkg/auth/handler/webapp/authflowv2/deps.go | 1 + pkg/auth/handler/webapp/authflowv2/routes.go | 1 + .../authflowv2/settings_mfa_enter_oob_otp.go | 203 ++ .../settings_mfa_view_recovery_code.go | 31 +- pkg/auth/routes.go | 1 + pkg/auth/wire_gen.go | 1930 +++++++++++++++++ pkg/auth/wire_handler.go | 7 + pkg/lib/accountmanagement/redis_store.go | 2 + .../service_authenticator.go | 25 +- pkg/lib/accountmanagement/token.go | 5 +- .../authgear/templates/en/translation.json | 5 + .../settings_mfa_enter_oob_otp.html | 91 + .../settings_mfa_view_recovery_code.html | 13 +- 13 files changed, 2302 insertions(+), 13 deletions(-) create mode 100644 pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_oob_otp.go create mode 100644 resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_oob_otp.html diff --git a/pkg/auth/handler/webapp/authflowv2/deps.go b/pkg/auth/handler/webapp/authflowv2/deps.go index fef9e9fb6a..06577a3b58 100644 --- a/pkg/auth/handler/webapp/authflowv2/deps.go +++ b/pkg/auth/handler/webapp/authflowv2/deps.go @@ -60,6 +60,7 @@ var DependencySet = wire.NewSet( wire.Struct(new(AuthflowV2SettingsMFAEnterTOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsOOBOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFACreateOOBOTPHandler), "*"), + wire.Struct(new(AuthflowV2SettingsMFAEnterOOBOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityAddEmailHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityEditEmailHandler), "*"), wire.Struct(new(AuthflowV2SettingsIdentityVerifyEmailHandler), "*"), diff --git a/pkg/auth/handler/webapp/authflowv2/routes.go b/pkg/auth/handler/webapp/authflowv2/routes.go index 9962063ad9..3d93abb79b 100644 --- a/pkg/auth/handler/webapp/authflowv2/routes.go +++ b/pkg/auth/handler/webapp/authflowv2/routes.go @@ -86,6 +86,7 @@ const ( // nolint: gosec AuthflowV2RouteSettingsMFACreatePassword = "/settings/mfa/create_password" AuthflowV2RouteSettingsMFACreateOOBOTP = "/settings/mfa/create_oob_otp_:channel" + AuthflowV2RouteSettingsMFAEnterOOBOTP = "/settings/mfa/enter_oob_otp" // nolint: gosec AuthflowV2RouteSettingsMFAPassword = "/settings/mfa/password" AuthflowV2RouteSettingsMFACreateTOTP = "/settings/mfa/create_totp" diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_oob_otp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_oob_otp.go new file mode 100644 index 0000000000..69276692eb --- /dev/null +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_oob_otp.go @@ -0,0 +1,203 @@ +package authflowv2 + +import ( + "math" + "net/http" + "net/url" + "time" + + "github.com/authgear/authgear-server/pkg/api/model" + handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" + "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + "github.com/authgear/authgear-server/pkg/auth/webapp" + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" + "github.com/authgear/authgear-server/pkg/lib/authn/otp" + "github.com/authgear/authgear-server/pkg/lib/config" + "github.com/authgear/authgear-server/pkg/lib/infra/mail" + "github.com/authgear/authgear-server/pkg/lib/session" + "github.com/authgear/authgear-server/pkg/util/clock" + "github.com/authgear/authgear-server/pkg/util/httproute" + "github.com/authgear/authgear-server/pkg/util/phone" + "github.com/authgear/authgear-server/pkg/util/template" + "github.com/authgear/authgear-server/pkg/util/validation" +) + +var TemplateWebSettingsMFAEnterOOBOTPHTML = template.RegisterHTML( + "web/authflowv2/settings_mfa_enter_oob_otp.html", + handlerwebapp.SettingsComponents..., +) + +var AuthflowV2SettingsMFAEnterOOBOTP = validation.NewSimpleSchema(` + { + "type": "object", + "properties": { + "x_code": { + "type": "string", + "format": "x_oob_otp_code" + } + }, + "required": ["x_code"] + } +`) + +var AuthflowV2SettingsIdentityResendOOBOTPSchema = validation.NewSimpleSchema(` + { + "type": "object", + "properties": { + "q_token": { "type": "string" } + }, + "required": ["q_token"] + } +`) + +func ConfigureAuthflowV2SettingsMFAEnterOOBOTPRoute(route httproute.Route) httproute.Route { + return route. + WithMethods("OPTIONS", "POST", "GET"). + WithPathPattern(AuthflowV2RouteSettingsMFAEnterOOBOTP) +} + +type SettingsMFAEnterOOBOTPViewModel struct { + AuthenticatorType string + Channel string + MaskedClaimValue string + CodeLength int + FailedAttemptRateLimitExceeded bool + ResendCooldown int +} + +func NewSettingsMFAEnterOOBOTPViewModel(tokenAuthenticator *accountmanagement.TokenAuthenticator, now time.Time, state *otp.State) SettingsMFAEnterOOBOTPViewModel { + var maskedClaimValue string + var resendCooldown int + var failedAttemptRateLimitExceeded bool + + switch tokenAuthenticator.OOBOTPChannel { + case model.AuthenticatorOOBChannelWhatsapp: + fallthrough + case model.AuthenticatorOOBChannelSMS: + maskedClaimValue = phone.Mask(tokenAuthenticator.OOBOTPTarget) + case model.AuthenticatorOOBChannelEmail: + maskedClaimValue = mail.MaskAddress(tokenAuthenticator.OOBOTPTarget) + } + + cooldown := int(math.Ceil(state.CanResendAt.Sub(now).Seconds())) + if cooldown > 0 { + resendCooldown = cooldown + } + + failedAttemptRateLimitExceeded = state.TooManyAttempts + + return SettingsMFAEnterOOBOTPViewModel{ + AuthenticatorType: tokenAuthenticator.AuthenticatorType, + Channel: string(tokenAuthenticator.OOBOTPChannel), + MaskedClaimValue: maskedClaimValue, + CodeLength: 6, + FailedAttemptRateLimitExceeded: failedAttemptRateLimitExceeded, + ResendCooldown: resendCooldown, + } +} + +type AuthflowV2SettingsMFAEnterOOBOTPHandler struct { + ControllerFactory handlerwebapp.ControllerFactory + BaseViewModel *viewmodels.BaseViewModeler + Renderer handlerwebapp.Renderer + FlashMessage handlerwebapp.FlashMessage + Clock clock.Clock + Config *config.AppConfig + OTPCode handlerwebapp.OTPCodeService + AccountManagement *accountmanagement.Service +} + +func (h *AuthflowV2SettingsMFAEnterOOBOTPHandler) GetData(r *http.Request, w http.ResponseWriter, tokenAuthenticator *accountmanagement.TokenAuthenticator) (map[string]interface{}, error) { + now := h.Clock.NowUTC() + data := make(map[string]interface{}) + + channel := tokenAuthenticator.OOBOTPChannel + target := tokenAuthenticator.OOBOTPTarget + + state, err := h.OTPCode.InspectState(otp.KindVerification(h.Config, channel), target) + if err != nil { + return nil, err + } + + baseViewModel := h.BaseViewModel.ViewModelForAuthFlow(r, w) + viewmodels.Embed(data, baseViewModel) + + screenViewModel := NewSettingsMFAEnterOOBOTPViewModel(tokenAuthenticator, now, state) + viewmodels.Embed(data, screenViewModel) + + return data, nil +} + +func (h *AuthflowV2SettingsMFAEnterOOBOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctrl, err := h.ControllerFactory.New(r, w) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer ctrl.ServeWithoutDBTx() + + ctrl.Get(func() error { + tokenString := r.Form.Get("q_token") + token, err := h.AccountManagement.GetToken(session.GetSession(r.Context()), tokenString) + if err != nil { + return err + } + + data, err := h.GetData(r, w, token.Authenticator) + if err != nil { + return err + } + + h.Renderer.RenderHTML(w, r, TemplateWebSettingsMFAEnterOOBOTPHTML, data) + + return nil + }) + + ctrl.PostAction("resend", func() error { + err := AuthflowV2SettingsIdentityResendOOBOTPSchema.Validator().ValidateValue(handlerwebapp.FormToJSON(r.Form)) + if err != nil { + return err + } + + tokenString := r.Form.Get("q_token") + err = h.AccountManagement.ResendOTPCode(session.GetSession(r.Context()), tokenString) + if err != nil { + return err + } + + h.FlashMessage.Flash(w, string(webapp.FlashMessageTypeResendCodeSuccess)) + result := webapp.Result{} + result.WriteResponse(w, r) + return nil + }) + + ctrl.PostAction("submit", func() error { + err := AuthflowV2SettingsMFAEnterOOBOTP.Validator().ValidateValue(handlerwebapp.FormToJSON(r.Form)) + if err != nil { + return err + } + + tokenString := r.Form.Get("q_token") + code := r.Form.Get("x_code") + + output, err := h.AccountManagement.ResumeAddOOBOTPAuthenticator(session.GetSession(r.Context()), tokenString, &accountmanagement.ResumeAddOOBOTPAuthenticatorInput{ + Code: code, + }) + if err != nil { + return err + } + + redirectURI, err := url.Parse(AuthflowV2RouteSettingsMFAViewRecoveryCode) + if err != nil { + return err + } + q := redirectURI.Query() + q.Set("q_token", output.Token) + redirectURI.RawQuery = q.Encode() + + result := webapp.Result{RedirectURI: redirectURI.String()} + result.WriteResponse(w, r) + + return nil + }) +} diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_view_recovery_code.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_view_recovery_code.go index d0a1023560..25d30fca41 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_mfa_view_recovery_code.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_view_recovery_code.go @@ -24,7 +24,9 @@ func ConfigureAuthflowV2SettingsMFAViewRecoveryCodeRoute(route httproute.Route) } type AuthflowV2SettingsMFAViewRecoveryCodeViewModel struct { - RecoveryCodes []string + AuthenticatorType string + Channel string + RecoveryCodes []string } type AuthflowV2SettingsMFAViewRecoveryCodeHandler struct { @@ -35,14 +37,16 @@ type AuthflowV2SettingsMFAViewRecoveryCodeHandler struct { AccountManagement *accountmanagement.Service } -func (h *AuthflowV2SettingsMFAViewRecoveryCodeHandler) GetData(r *http.Request, rw http.ResponseWriter, recoveryCodes []string) (map[string]interface{}, error) { +func (h *AuthflowV2SettingsMFAViewRecoveryCodeHandler) GetData(r *http.Request, rw http.ResponseWriter, tokenAuthenticator *accountmanagement.TokenAuthenticator) (map[string]interface{}, error) { data := map[string]interface{}{} baseViewModel := h.BaseViewModel.ViewModel(r, rw) viewmodels.Embed(data, baseViewModel) screenViewModel := AuthflowV2SettingsMFAViewRecoveryCodeViewModel{ - RecoveryCodes: handlerwebapp.FormatRecoveryCodes(recoveryCodes), + AuthenticatorType: tokenAuthenticator.AuthenticatorType, + Channel: string(tokenAuthenticator.OOBOTPChannel), + RecoveryCodes: handlerwebapp.FormatRecoveryCodes(tokenAuthenticator.RecoveryCodes), } viewmodels.Embed(data, screenViewModel) @@ -66,7 +70,7 @@ func (h *AuthflowV2SettingsMFAViewRecoveryCodeHandler) ServeHTTP(w http.Response return err } - data, err := h.GetData(r, w, token.Authenticator.RecoveryCodes) + data, err := h.GetData(r, w, token.Authenticator) if err != nil { return err } @@ -84,7 +88,7 @@ func (h *AuthflowV2SettingsMFAViewRecoveryCodeHandler) ServeHTTP(w http.Response return err } - data, err := h.GetData(r, w, token.Authenticator.RecoveryCodes) + data, err := h.GetData(r, w, token.Authenticator) if err != nil { return err } @@ -98,14 +102,23 @@ func (h *AuthflowV2SettingsMFAViewRecoveryCodeHandler) ServeHTTP(w http.Response s := session.GetSession(r.Context()) tokenString := r.Form.Get("q_token") - _, err := h.AccountManagement.GetToken(s, tokenString) + token, err := h.AccountManagement.GetToken(s, tokenString) if err != nil { return err } - _, err = h.AccountManagement.FinishAddTOTPAuthenticator(s, tokenString, &accountmanagement.FinishAddTOTPAuthenticatorInput{}) - if err != nil { - return err + if token.Authenticator.TOTPVerified { + _, err = h.AccountManagement.FinishAddTOTPAuthenticator(s, tokenString, &accountmanagement.FinishAddTOTPAuthenticatorInput{}) + if err != nil { + return err + } + } else if token.Authenticator.OOBOTPVerified { + _, err = h.AccountManagement.FinishAddOOBOTPAuthenticator(s, tokenString, &accountmanagement.FinishAddOOBOTPAuthenticatorInput{}) + if err != nil { + return err + } + } else { + panic("authflowv2: unexpected authenticator type") } result := webapp.Result{RedirectURI: AuthflowV2RouteSettingsMFA} diff --git a/pkg/auth/routes.go b/pkg/auth/routes.go index 088d0d6e40..ad5c75117d 100644 --- a/pkg/auth/routes.go +++ b/pkg/auth/routes.go @@ -502,6 +502,7 @@ func NewRouter(p *deps.RootProvider, configSource *configsource.ConfigSource) *h SettingV2: p.Handler(newWebAppAuthflowV2SettingsOOBOTPHandler), }) router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFACreateOOBOTPRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler)) + router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFAEnterOOBOTPRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFAEnterOOBOTPHandler)) router.Add(webapphandler.ConfigureSettingsRecoveryCodeRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppSettingsRecoveryCodeHandler)) router.Add(webapphandler.ConfigureSettingsSessionsRoute(webappSettingsSubRoutesRoute), &webapphandler.SettingsImplementationSwitcherHandler{ SettingV1: p.Handler(newWebAppSettingsSessionsHandler), diff --git a/pkg/auth/wire_gen.go b/pkg/auth/wire_gen.go index 6732414226..642711f63c 100644 --- a/pkg/auth/wire_gen.go +++ b/pkg/auth/wire_gen.go @@ -60438,6 +60438,1936 @@ func newWebAppAuthflowV2SettingsOOBOTPHandler(p *deps.RequestProvider) http.Hand return authflowV2SettingsOOBOTPHandler } +func newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler(p *deps.RequestProvider) http.Handler { + appProvider := p.AppProvider + factory := appProvider.LoggerFactory + handle := appProvider.AppDatabase + appredisHandle := appProvider.Redis + appContext := appProvider.AppContext + config := appContext.Config + appConfig := config.AppConfig + appID := appConfig.ID + serviceLogger := webapp2.NewServiceLogger(factory) + request := p.Request + sessionStoreRedis := &webapp2.SessionStoreRedis{ + AppID: appID, + Redis: appredisHandle, + } + sessionCookieDef := webapp2.NewSessionCookieDef() + signedUpCookieDef := webapp2.NewSignedUpCookieDef() + authenticationConfig := appConfig.Authentication + cookieDef := mfa.NewDeviceTokenCookieDef(authenticationConfig) + errorTokenCookieDef := webapp2.NewErrorTokenCookieDef() + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpConfig := appConfig.HTTP + cookieManager := deps.NewCookieManager(request, trustProxy, httpConfig) + errorService := &webapp2.ErrorService{ + AppID: appID, + Cookie: errorTokenCookieDef, + RedisHandle: appredisHandle, + Cookies: cookieManager, + } + oAuthConfig := appConfig.OAuth + uiConfig := appConfig.UI + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpProto := deps.ProvideHTTPProto(request, trustProxy) + globalUIImplementation := environmentConfig.UIImplementation + globalUISettingsImplementation := environmentConfig.UISettingsImplementation + uiImplementationService := &web.UIImplementationService{ + UIConfig: uiConfig, + GlobalUIImplementation: globalUIImplementation, + GlobalUISettingsImplementation: globalUISettingsImplementation, + } + endpointsEndpoints := &endpoints.Endpoints{ + HTTPHost: httpHost, + HTTPProto: httpProto, + UIImplementationService: uiImplementationService, + } + uiService := &authenticationinfo.UIService{ + EndpointsProvider: endpointsEndpoints, + } + resolver := &oauthclient.Resolver{ + OAuthConfig: oAuthConfig, + TesterEndpoints: endpointsEndpoints, + } + logger := interaction.NewLogger(factory) + remoteIP := deps.ProvideRemoteIP(request, trustProxy) + contextContext := deps.ProvideRequestContext(request) + sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) + clockClock := _wireSystemClockValue + featureConfig := config.FeatureConfig + redisLogger := redis.NewLogger(factory) + secretConfig := config.SecretConfig + databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) + sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) + store := &redis.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Logger: redisLogger, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + userAgentString := deps.ProvideUserAgentString(request) + eventLogger := event.NewLogger(factory) + localizationConfig := appConfig.Localization + sqlBuilder := appdb.NewSQLBuilder(databaseCredentials) + storeImpl := event.NewStoreImpl(sqlBuilder, sqlExecutor) + userStore := &user.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + AppID: appID, + } + rawQueries := &user.RawQueries{ + Store: userStore, + } + identityConfig := appConfig.Identity + identityFeatureConfig := featureConfig.Identity + serviceStore := &service.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginidStore := &loginid.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginIDConfig := identityConfig.LoginID + manager := appContext.Resources + typeCheckerFactory := &loginid.TypeCheckerFactory{ + UIConfig: uiConfig, + LoginIDConfig: loginIDConfig, + Resources: manager, + } + checker := &loginid.Checker{ + Config: loginIDConfig, + TypeCheckerFactory: typeCheckerFactory, + } + normalizerFactory := &loginid.NormalizerFactory{ + Config: loginIDConfig, + } + provider := &loginid.Provider{ + Store: loginidStore, + Config: loginIDConfig, + Checker: checker, + NormalizerFactory: normalizerFactory, + Clock: clockClock, + } + oauthStore := &oauth3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + IdentityConfig: identityConfig, + } + oauthProvider := &oauth3.Provider{ + Store: oauthStore, + Clock: clockClock, + IdentityConfig: identityConfig, + } + anonymousStore := &anonymous.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + anonymousProvider := &anonymous.Provider{ + Store: anonymousStore, + Clock: clockClock, + } + biometricStore := &biometric.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + biometricProvider := &biometric.Provider{ + Store: biometricStore, + Clock: clockClock, + } + passkeyStore := &passkey.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + store2 := &passkey2.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + defaultLanguageTag := deps.ProvideDefaultLanguageTag(config) + supportedLanguageTags := deps.ProvideSupportedLanguageTags(config) + templateResolver := &template.Resolver{ + Resources: manager, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + } + engine := &template.Engine{ + Resolver: templateResolver, + } + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) + webAppCDNHost := environmentConfig.WebAppCDNHost + globalEmbeddedResourceManager := rootProvider.EmbeddedResources + staticAssetResolver := &web.StaticAssetResolver{ + Context: contextContext, + Localization: localizationConfig, + HTTPOrigin: httpOrigin, + HTTPProto: httpProto, + WebAppCDNHost: webAppCDNHost, + Resources: manager, + EmbeddedResources: globalEmbeddedResourceManager, + } + translationService := &translation.Service{ + Context: contextContext, + TemplateEngine: engine, + StaticAssets: staticAssetResolver, + } + configService := &passkey2.ConfigService{ + Request: request, + TrustProxy: trustProxy, + TranslationService: translationService, + } + passkeyService := &passkey2.Service{ + Store: store2, + ConfigService: configService, + } + passkeyProvider := &passkey.Provider{ + Store: passkeyStore, + Clock: clockClock, + Passkey: passkeyService, + } + siweStore := &siwe.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + web3Config := appConfig.Web3 + storeRedis := &siwe2.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + ratelimitLogger := ratelimit.NewLogger(factory) + storageRedis := &ratelimit.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + rateLimitsFeatureConfig := featureConfig.RateLimits + limiter := &ratelimit.Limiter{ + Logger: ratelimitLogger, + Storage: storageRedis, + Config: rateLimitsFeatureConfig, + } + siweLogger := siwe2.NewLogger(factory) + siweService := &siwe2.Service{ + RemoteIP: remoteIP, + HTTPOrigin: httpOrigin, + Web3Config: web3Config, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + NonceStore: storeRedis, + RateLimiter: limiter, + Logger: siweLogger, + } + siweProvider := &siwe.Provider{ + Store: siweStore, + Clock: clockClock, + SIWE: siweService, + } + ldapStore := &ldap.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + normalizer := &stdattrs.Normalizer{ + LoginIDNormalizerFactory: normalizerFactory, + } + ldapProvider := &ldap.Provider{ + Store: ldapStore, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + } + serviceService := &service.Service{ + Authentication: authenticationConfig, + Identity: identityConfig, + IdentityFeatureConfig: identityFeatureConfig, + Store: serviceStore, + LoginID: provider, + OAuth: oauthProvider, + Anonymous: anonymousProvider, + Biometric: biometricProvider, + Passkey: passkeyProvider, + SIWE: siweProvider, + LDAP: ldapProvider, + } + store3 := &service2.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + passwordStore := &password.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorConfig := appConfig.Authenticator + authenticatorPasswordConfig := authenticatorConfig.Password + passwordLogger := password.NewLogger(factory) + historyStore := &password.HistoryStore{ + Clock: clockClock, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorFeatureConfig := featureConfig.Authenticator + passwordChecker := password.ProvideChecker(authenticatorPasswordConfig, authenticatorFeatureConfig, historyStore) + expiry := password.ProvideExpiry(authenticatorPasswordConfig, clockClock) + housekeeperLogger := password.NewHousekeeperLogger(factory) + housekeeper := &password.Housekeeper{ + Store: historyStore, + Logger: housekeeperLogger, + Config: authenticatorPasswordConfig, + } + passwordProvider := &password.Provider{ + Store: passwordStore, + Config: authenticatorPasswordConfig, + Clock: clockClock, + Logger: passwordLogger, + PasswordHistory: historyStore, + PasswordChecker: passwordChecker, + Expiry: expiry, + Housekeeper: housekeeper, + } + store4 := &passkey3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + provider2 := &passkey3.Provider{ + Store: store4, + Clock: clockClock, + Passkey: passkeyService, + } + totpStore := &totp.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorTOTPConfig := authenticatorConfig.TOTP + totpProvider := &totp.Provider{ + Store: totpStore, + Config: authenticatorTOTPConfig, + Clock: clockClock, + } + oobStore := &oob.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + oobProvider := &oob.Provider{ + Store: oobStore, + LoginIDNormalizerFactory: normalizerFactory, + Clock: clockClock, + } + testModeConfig := appConfig.TestMode + testModeFeatureConfig := featureConfig.TestMode + codeStoreRedis := &otp.CodeStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + lookupStoreRedis := &otp.LookupStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + attemptTrackerRedis := &otp.AttemptTrackerRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + otpLogger := otp.NewLogger(factory) + otpService := &otp.Service{ + Clock: clockClock, + AppID: appID, + TestModeConfig: testModeConfig, + TestModeFeatureConfig: testModeFeatureConfig, + RemoteIP: remoteIP, + CodeStore: codeStoreRedis, + LookupStore: lookupStoreRedis, + AttemptTracker: attemptTrackerRedis, + Logger: otpLogger, + RateLimiter: limiter, + } + rateLimits := service2.RateLimits{ + IP: remoteIP, + Config: authenticationConfig, + RateLimiter: limiter, + } + authenticationLockoutConfig := authenticationConfig.Lockout + lockoutLogger := lockout.NewLogger(factory) + lockoutStorageRedis := &lockout.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + lockoutService := &lockout.Service{ + Logger: lockoutLogger, + Storage: lockoutStorageRedis, + } + serviceLockout := service2.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + service3 := &service2.Service{ + Store: store3, + Config: appConfig, + Password: passwordProvider, + Passkey: provider2, + TOTP: totpProvider, + OOBOTP: oobProvider, + OTPCodeService: otpService, + RateLimits: rateLimits, + Lockout: serviceLockout, + } + verificationConfig := appConfig.Verification + userProfileConfig := appConfig.UserProfile + storePQ := &verification.StorePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + verificationService := &verification.Service{ + Config: verificationConfig, + UserProfileConfig: userProfileConfig, + Clock: clockClock, + ClaimStore: storePQ, + } + imagesCDNHost := environmentConfig.ImagesCDNHost + pictureTransformer := &stdattrs2.PictureTransformer{ + HTTPProto: httpProto, + HTTPHost: httpHost, + ImagesCDNHost: imagesCDNHost, + } + serviceNoEvent := &stdattrs2.ServiceNoEvent{ + UserProfileConfig: userProfileConfig, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + ClaimStore: storePQ, + Transformer: pictureTransformer, + } + customattrsServiceNoEvent := &customattrs.ServiceNoEvent{ + Config: userProfileConfig, + UserQueries: rawQueries, + UserStore: userStore, + } + nftIndexerAPIEndpoint := environmentConfig.NFTIndexerAPIEndpoint + web3Service := &web3.Service{ + APIEndpoint: nftIndexerAPIEndpoint, + Web3Config: web3Config, + } + rolesgroupsStore := &rolesgroups.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + queries := &rolesgroups.Queries{ + Store: rolesgroupsStore, + } + userQueries := &user.Queries{ + RawQueries: rawQueries, + Store: userStore, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + resolverImpl := &event.ResolverImpl{ + Users: userQueries, + } + hookLogger := hook.NewLogger(factory) + hookConfig := appConfig.Hook + webHookLogger := hook.NewWebHookLogger(factory) + webhookKeyMaterials := deps.ProvideWebhookKeyMaterials(secretConfig) + webHookImpl := hook.WebHookImpl{ + Logger: webHookLogger, + Secret: webhookKeyMaterials, + } + syncHTTPClient := hook.NewSyncHTTPClient(hookConfig) + asyncHTTPClient := hook.NewAsyncHTTPClient() + eventWebHookImpl := &hook.EventWebHookImpl{ + WebHookImpl: webHookImpl, + SyncHTTP: syncHTTPClient, + AsyncHTTP: asyncHTTPClient, + } + denoHookLogger := hook.NewDenoHookLogger(factory) + denoHook := hook.DenoHook{ + Context: contextContext, + ResourceManager: manager, + Logger: denoHookLogger, + } + denoEndpoint := environmentConfig.DenoEndpoint + syncDenoClient := hook.NewSyncDenoClient(denoEndpoint, hookConfig, hookLogger) + asyncDenoClient := hook.NewAsyncDenoClient(denoEndpoint, hookLogger) + eventDenoHookImpl := &hook.EventDenoHookImpl{ + DenoHook: denoHook, + SyncDenoClient: syncDenoClient, + AsyncDenoClient: asyncDenoClient, + } + commands := &rolesgroups.Commands{ + Store: rolesgroupsStore, + } + sink := &hook.Sink{ + Logger: hookLogger, + Config: hookConfig, + Clock: clockClock, + EventWebHook: eventWebHookImpl, + EventDenoHook: eventDenoHookImpl, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + RolesAndGroups: commands, + } + auditLogger := audit.NewLogger(factory) + writeHandle := appProvider.AuditWriteDatabase + auditDatabaseCredentials := deps.ProvideAuditDatabaseCredentials(secretConfig) + auditdbSQLBuilderApp := auditdb.NewSQLBuilderApp(auditDatabaseCredentials, appID) + writeSQLExecutor := auditdb.NewWriteSQLExecutor(contextContext, writeHandle) + writeStore := &audit.WriteStore{ + SQLBuilder: auditdbSQLBuilderApp, + SQLExecutor: writeSQLExecutor, + } + auditSink := &audit.Sink{ + Logger: auditLogger, + Database: writeHandle, + Store: writeStore, + } + elasticsearchLogger := elasticsearch.NewLogger(factory) + elasticsearchServiceLogger := elasticsearch.NewElasticsearchServiceLogger(factory) + elasticsearchCredentials := deps.ProvideElasticsearchCredentials(secretConfig) + client := elasticsearch.NewClient(elasticsearchCredentials) + queue := appProvider.TaskQueue + userReindexProducer := redisqueue.NewUserReindexProducer(appredisHandle, clockClock) + elasticsearchService := elasticsearch.Service{ + Clock: clockClock, + Context: contextContext, + Database: handle, + Logger: elasticsearchServiceLogger, + AppID: appID, + Client: client, + Users: userQueries, + UserStore: userStore, + IdentityService: serviceService, + RolesGroups: rolesgroupsStore, + TaskQueue: queue, + Producer: userReindexProducer, + } + elasticsearchSink := &elasticsearch.Sink{ + Logger: elasticsearchLogger, + Service: elasticsearchService, + Database: handle, + } + eventService := event.NewService(contextContext, appID, remoteIP, userAgentString, eventLogger, handle, clockClock, localizationConfig, storeImpl, resolverImpl, sink, auditSink, elasticsearchSink) + storeDeviceTokenRedis := &mfa.StoreDeviceTokenRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + storeRecoveryCodePQ := &mfa.StoreRecoveryCodePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + mfaLockout := mfa.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + mfaService := &mfa.Service{ + IP: remoteIP, + DeviceTokens: storeDeviceTokenRedis, + RecoveryCodes: storeRecoveryCodePQ, + Clock: clockClock, + Config: authenticationConfig, + RateLimiter: limiter, + Lockout: mfaLockout, + } + messagingLogger := messaging.NewLogger(factory) + usageLogger := usage.NewLogger(factory) + usageLimiter := &usage.Limiter{ + Logger: usageLogger, + Clock: clockClock, + AppID: appID, + Redis: appredisHandle, + } + messagingConfig := appConfig.Messaging + messagingRateLimitsConfig := messagingConfig.RateLimits + messagingFeatureConfig := featureConfig.Messaging + rateLimitsEnvironmentConfig := &environmentConfig.RateLimits + limits := messaging.Limits{ + Logger: messagingLogger, + RateLimiter: limiter, + UsageLimiter: usageLimiter, + RemoteIP: remoteIP, + Config: messagingRateLimitsConfig, + FeatureConfig: messagingFeatureConfig, + EnvConfig: rateLimitsEnvironmentConfig, + } + whatsappServiceLogger := whatsapp.NewServiceLogger(factory) + devMode := environmentConfig.DevMode + featureTestModeWhatsappSuppressed := deps.ProvideTestModeWhatsappSuppressed(testModeFeatureConfig) + testModeWhatsappConfig := testModeConfig.Whatsapp + whatsappConfig := messagingConfig.Whatsapp + whatsappOnPremisesCredentials := deps.ProvideWhatsappOnPremisesCredentials(secretConfig) + tokenStore := &whatsapp.TokenStore{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + onPremisesClient := whatsapp.NewWhatsappOnPremisesClient(whatsappConfig, whatsappOnPremisesCredentials, tokenStore) + whatsappService := &whatsapp.Service{ + Context: contextContext, + Logger: whatsappServiceLogger, + DevMode: devMode, + FeatureTestModeWhatsappSuppressed: featureTestModeWhatsappSuppressed, + TestModeWhatsappConfig: testModeWhatsappConfig, + WhatsappConfig: whatsappConfig, + LocalizationConfig: localizationConfig, + OnPremisesClient: onPremisesClient, + TokenStore: tokenStore, + } + sender := &messaging.Sender{ + Limits: limits, + TaskQueue: queue, + Events: eventService, + Whatsapp: whatsappService, + MessagingFeatureConfig: messagingFeatureConfig, + } + forgotpasswordSender := &forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + rawCommands := &user.RawCommands{ + Store: userStore, + Clock: clockClock, + } + userCommands := &user.Commands{ + RawCommands: rawCommands, + RawQueries: rawQueries, + Events: eventService, + Verification: verificationService, + UserProfileConfig: userProfileConfig, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + stdattrsService := &stdattrs2.Service{ + UserProfileConfig: userProfileConfig, + ServiceNoEvent: serviceNoEvent, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + Events: eventService, + } + authorizationStore := &pq.AuthorizationStore{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + storeRedisLogger := idpsession.NewStoreRedisLogger(factory) + idpsessionStoreRedis := &idpsession.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + Logger: storeRedisLogger, + } + sessionConfig := appConfig.Session + cookieDef2 := session.NewSessionCookieDef(sessionConfig) + idpsessionManager := &idpsession.Manager{ + Store: idpsessionStoreRedis, + Config: sessionConfig, + Cookies: cookieManager, + CookieDef: cookieDef2, + } + eventStoreRedis := &access.EventStoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + eventProvider := &access.EventProvider{ + Store: eventStoreRedis, + } + idpsessionRand := _wireRandValue + idpsessionProvider := &idpsession.Provider{ + Context: contextContext, + RemoteIP: remoteIP, + UserAgentString: userAgentString, + AppID: appID, + Redis: appredisHandle, + Store: idpsessionStoreRedis, + AccessEvents: eventProvider, + TrustProxy: trustProxy, + Config: sessionConfig, + Clock: clockClock, + Random: idpsessionRand, + } + offlineGrantService := oauth2.OfflineGrantService{ + OAuthConfig: oAuthConfig, + Clock: clockClock, + IDPSessions: idpsessionProvider, + ClientResolver: resolver, + OfflineGrants: store, + } + sessionManager := &oauth2.SessionManager{ + Store: store, + Config: oAuthConfig, + Service: offlineGrantService, + } + accountDeletionConfig := appConfig.AccountDeletion + accountAnonymizationConfig := appConfig.AccountAnonymization + maxTrials := _wireMaxTrialsValue + passwordRand := password.NewRandSource() + generator := &password.Generator{ + MaxTrials: maxTrials, + Checker: passwordChecker, + Rand: passwordRand, + PasswordConfig: authenticatorPasswordConfig, + } + coordinator := &facade.Coordinator{ + Events: eventService, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + MFA: mfaService, + SendPassword: forgotpasswordSender, + UserCommands: userCommands, + UserQueries: userQueries, + RolesGroupsCommands: commands, + StdAttrsService: stdattrsService, + PasswordHistory: historyStore, + OAuth: authorizationStore, + IDPSessions: idpsessionManager, + OAuthSessions: sessionManager, + IdentityConfig: identityConfig, + AccountDeletionConfig: accountDeletionConfig, + AccountAnonymizationConfig: accountAnonymizationConfig, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + PasswordGenerator: generator, + } + identityFacade := facade.IdentityFacade{ + Coordinator: coordinator, + } + authenticatorFacade := facade.AuthenticatorFacade{ + Coordinator: coordinator, + } + anonymousStoreRedis := &anonymous.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + messageSender := &otp.MessageSender{ + AppID: appID, + Translation: translationService, + Endpoints: endpointsEndpoints, + Sender: sender, + WhatsappService: whatsappService, + } + oAuthSSOProviderCredentials := deps.ProvideOAuthSSOProviderCredentials(secretConfig) + oAuthHTTPClient := sso.ProvideOAuthHTTPClient(environmentConfig) + simpleStoreRedisFactory := &sso.SimpleStoreRedisFactory{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + } + oAuthProviderFactory := &sso.OAuthProviderFactory{ + IdentityConfig: identityConfig, + Credentials: oAuthSSOProviderCredentials, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + HTTPClient: oAuthHTTPClient, + SimpleStoreRedisFactory: simpleStoreRedisFactory, + } + webappoauthStore := &webappoauth.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } + forgotpasswordLogger := forgotpassword.NewLogger(factory) + sender2 := forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + forgotpasswordService := &forgotpassword.Service{ + Logger: forgotpasswordLogger, + Config: appConfig, + FeatureConfig: featureConfig, + Identities: serviceService, + Authenticators: authenticatorFacade, + OTPCodes: otpService, + OTPSender: messageSender, + PasswordSender: sender2, + } + responseWriter := p.ResponseWriter + nonceService := &nonce.Service{ + Cookies: cookieManager, + Request: request, + ResponseWriter: responseWriter, + } + challengeProvider := &challenge.Provider{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + userProvider := &user.Provider{ + Commands: userCommands, + Queries: userQueries, + } + authenticationinfoStoreRedis := &authenticationinfo.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + manager2 := &session.Manager{ + IDPSessions: idpsessionManager, + AccessTokenSessions: sessionManager, + Events: eventService, + } + oauthsessionStoreRedis := &oauthsession.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + interactionContext := &interaction.Context{ + Request: request, + RemoteIP: remoteIP, + Database: sqlExecutor, + Clock: clockClock, + Config: appConfig, + FeatureConfig: featureConfig, + OAuthClientResolver: resolver, + OfflineGrants: store, + Identities: identityFacade, + Authenticators: authenticatorFacade, + AnonymousIdentities: anonymousProvider, + AnonymousUserPromotionCodeStore: anonymousStoreRedis, + BiometricIdentities: biometricProvider, + OTPCodeService: otpService, + OTPSender: messageSender, + OAuthProviderFactory: oAuthProviderFactory, + OAuthRedirectURIBuilder: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + MFA: mfaFacade, + ForgotPassword: forgotpasswordService, + ResetPassword: forgotpasswordService, + Passkey: passkeyService, + Verification: verificationService, + RateLimiter: limiter, + PasswordGenerator: generator, + Nonces: nonceService, + Challenges: challengeProvider, + Users: userProvider, + StdAttrsService: stdattrsService, + Events: eventService, + CookieManager: cookieManager, + AuthenticationInfoService: authenticationinfoStoreRedis, + Sessions: idpsessionProvider, + SessionManager: manager2, + SessionCookie: cookieDef2, + OAuthSessions: oauthsessionStoreRedis, + MFADeviceTokenCookie: cookieDef, + } + interactionStoreRedis := &interaction.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + interactionService := &interaction.Service{ + Logger: logger, + Context: interactionContext, + Store: interactionStoreRedis, + } + webappService2 := &webapp2.Service2{ + Logger: serviceLogger, + Request: request, + Sessions: sessionStoreRedis, + SessionCookie: sessionCookieDef, + SignedUpCookie: signedUpCookieDef, + MFADeviceTokenCookie: cookieDef, + ErrorService: errorService, + Cookies: cookieManager, + OAuthConfig: oAuthConfig, + UIConfig: uiConfig, + TrustProxy: trustProxy, + UIInfoResolver: uiService, + OAuthClientResolver: resolver, + Graph: interactionService, + } + uiFeatureConfig := featureConfig.UI + forgotPasswordConfig := appConfig.ForgotPassword + googleTagManagerConfig := appConfig.GoogleTagManager + botProtectionConfig := appConfig.BotProtection + flashMessage := &httputil.FlashMessage{ + Cookies: cookieManager, + } + authUISentryDSN := environmentConfig.AuthUISentryDSN + authUIWindowMessageAllowedOrigins := environmentConfig.AuthUIWindowMessageAllowedOrigins + baseLogger := viewmodels.NewBaseLogger(factory) + baseViewModeler := &viewmodels.BaseViewModeler{ + TrustProxy: trustProxy, + OAuth: oAuthConfig, + AuthUI: uiConfig, + AuthUIFeatureConfig: uiFeatureConfig, + StaticAssets: staticAssetResolver, + ForgotPassword: forgotPasswordConfig, + Authentication: authenticationConfig, + GoogleTagManager: googleTagManagerConfig, + BotProtection: botProtectionConfig, + ErrorService: errorService, + Translations: translationService, + Clock: clockClock, + FlashMessage: flashMessage, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + AuthUISentryDSN: authUISentryDSN, + AuthUIWindowMessageAllowedOrigins: authUIWindowMessageAllowedOrigins, + OAuthClientResolver: resolver, + Logger: baseLogger, + } + responseRenderer := &webapp.ResponseRenderer{ + TemplateEngine: engine, + } + publisher := webapp.NewPublisher(appID, appredisHandle) + authflowNavigator := &webapp2.AuthflowNavigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + authflowV2Navigator := &authflowv2.AuthflowV2Navigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + errorRenderer := &webapp.ErrorRenderer{ + ErrorService: errorService, + UIImplementationService: uiImplementationService, + AuthflowV1Navigator: authflowNavigator, + AuthflowV2Navigator: authflowV2Navigator, + } + controllerDeps := webapp.ControllerDeps{ + Database: handle, + RedisHandle: appredisHandle, + AppID: appID, + Page: webappService2, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + Publisher: publisher, + Clock: clockClock, + TesterEndpointsProvider: endpointsEndpoints, + ErrorRenderer: errorRenderer, + TrustProxy: trustProxy, + } + controllerFactory := webapp.ControllerFactory{ + LoggerFactory: factory, + ControllerDeps: controllerDeps, + } + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, + } + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } + authflowV2SettingsMFACreateOOBOTPHandler := &authflowv2.AuthflowV2SettingsMFACreateOOBOTPHandler{ + ControllerFactory: controllerFactory, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + AccountManagementService: accountmanagementService, + } + return authflowV2SettingsMFACreateOOBOTPHandler +} + +func newWebAppAuthflowV2SettingsMFAEnterOOBOTPHandler(p *deps.RequestProvider) http.Handler { + appProvider := p.AppProvider + factory := appProvider.LoggerFactory + handle := appProvider.AppDatabase + appredisHandle := appProvider.Redis + appContext := appProvider.AppContext + config := appContext.Config + appConfig := config.AppConfig + appID := appConfig.ID + serviceLogger := webapp2.NewServiceLogger(factory) + request := p.Request + sessionStoreRedis := &webapp2.SessionStoreRedis{ + AppID: appID, + Redis: appredisHandle, + } + sessionCookieDef := webapp2.NewSessionCookieDef() + signedUpCookieDef := webapp2.NewSignedUpCookieDef() + authenticationConfig := appConfig.Authentication + cookieDef := mfa.NewDeviceTokenCookieDef(authenticationConfig) + errorTokenCookieDef := webapp2.NewErrorTokenCookieDef() + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpConfig := appConfig.HTTP + cookieManager := deps.NewCookieManager(request, trustProxy, httpConfig) + errorService := &webapp2.ErrorService{ + AppID: appID, + Cookie: errorTokenCookieDef, + RedisHandle: appredisHandle, + Cookies: cookieManager, + } + oAuthConfig := appConfig.OAuth + uiConfig := appConfig.UI + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpProto := deps.ProvideHTTPProto(request, trustProxy) + globalUIImplementation := environmentConfig.UIImplementation + globalUISettingsImplementation := environmentConfig.UISettingsImplementation + uiImplementationService := &web.UIImplementationService{ + UIConfig: uiConfig, + GlobalUIImplementation: globalUIImplementation, + GlobalUISettingsImplementation: globalUISettingsImplementation, + } + endpointsEndpoints := &endpoints.Endpoints{ + HTTPHost: httpHost, + HTTPProto: httpProto, + UIImplementationService: uiImplementationService, + } + uiService := &authenticationinfo.UIService{ + EndpointsProvider: endpointsEndpoints, + } + resolver := &oauthclient.Resolver{ + OAuthConfig: oAuthConfig, + TesterEndpoints: endpointsEndpoints, + } + logger := interaction.NewLogger(factory) + remoteIP := deps.ProvideRemoteIP(request, trustProxy) + contextContext := deps.ProvideRequestContext(request) + sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) + clockClock := _wireSystemClockValue + featureConfig := config.FeatureConfig + redisLogger := redis.NewLogger(factory) + secretConfig := config.SecretConfig + databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) + sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) + store := &redis.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Logger: redisLogger, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + userAgentString := deps.ProvideUserAgentString(request) + eventLogger := event.NewLogger(factory) + localizationConfig := appConfig.Localization + sqlBuilder := appdb.NewSQLBuilder(databaseCredentials) + storeImpl := event.NewStoreImpl(sqlBuilder, sqlExecutor) + userStore := &user.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + AppID: appID, + } + rawQueries := &user.RawQueries{ + Store: userStore, + } + identityConfig := appConfig.Identity + identityFeatureConfig := featureConfig.Identity + serviceStore := &service.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginidStore := &loginid.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginIDConfig := identityConfig.LoginID + manager := appContext.Resources + typeCheckerFactory := &loginid.TypeCheckerFactory{ + UIConfig: uiConfig, + LoginIDConfig: loginIDConfig, + Resources: manager, + } + checker := &loginid.Checker{ + Config: loginIDConfig, + TypeCheckerFactory: typeCheckerFactory, + } + normalizerFactory := &loginid.NormalizerFactory{ + Config: loginIDConfig, + } + provider := &loginid.Provider{ + Store: loginidStore, + Config: loginIDConfig, + Checker: checker, + NormalizerFactory: normalizerFactory, + Clock: clockClock, + } + oauthStore := &oauth3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + IdentityConfig: identityConfig, + } + oauthProvider := &oauth3.Provider{ + Store: oauthStore, + Clock: clockClock, + IdentityConfig: identityConfig, + } + anonymousStore := &anonymous.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + anonymousProvider := &anonymous.Provider{ + Store: anonymousStore, + Clock: clockClock, + } + biometricStore := &biometric.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + biometricProvider := &biometric.Provider{ + Store: biometricStore, + Clock: clockClock, + } + passkeyStore := &passkey.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + store2 := &passkey2.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + defaultLanguageTag := deps.ProvideDefaultLanguageTag(config) + supportedLanguageTags := deps.ProvideSupportedLanguageTags(config) + templateResolver := &template.Resolver{ + Resources: manager, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + } + engine := &template.Engine{ + Resolver: templateResolver, + } + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) + webAppCDNHost := environmentConfig.WebAppCDNHost + globalEmbeddedResourceManager := rootProvider.EmbeddedResources + staticAssetResolver := &web.StaticAssetResolver{ + Context: contextContext, + Localization: localizationConfig, + HTTPOrigin: httpOrigin, + HTTPProto: httpProto, + WebAppCDNHost: webAppCDNHost, + Resources: manager, + EmbeddedResources: globalEmbeddedResourceManager, + } + translationService := &translation.Service{ + Context: contextContext, + TemplateEngine: engine, + StaticAssets: staticAssetResolver, + } + configService := &passkey2.ConfigService{ + Request: request, + TrustProxy: trustProxy, + TranslationService: translationService, + } + passkeyService := &passkey2.Service{ + Store: store2, + ConfigService: configService, + } + passkeyProvider := &passkey.Provider{ + Store: passkeyStore, + Clock: clockClock, + Passkey: passkeyService, + } + siweStore := &siwe.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + web3Config := appConfig.Web3 + storeRedis := &siwe2.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + ratelimitLogger := ratelimit.NewLogger(factory) + storageRedis := &ratelimit.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + rateLimitsFeatureConfig := featureConfig.RateLimits + limiter := &ratelimit.Limiter{ + Logger: ratelimitLogger, + Storage: storageRedis, + Config: rateLimitsFeatureConfig, + } + siweLogger := siwe2.NewLogger(factory) + siweService := &siwe2.Service{ + RemoteIP: remoteIP, + HTTPOrigin: httpOrigin, + Web3Config: web3Config, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + NonceStore: storeRedis, + RateLimiter: limiter, + Logger: siweLogger, + } + siweProvider := &siwe.Provider{ + Store: siweStore, + Clock: clockClock, + SIWE: siweService, + } + ldapStore := &ldap.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + normalizer := &stdattrs.Normalizer{ + LoginIDNormalizerFactory: normalizerFactory, + } + ldapProvider := &ldap.Provider{ + Store: ldapStore, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + } + serviceService := &service.Service{ + Authentication: authenticationConfig, + Identity: identityConfig, + IdentityFeatureConfig: identityFeatureConfig, + Store: serviceStore, + LoginID: provider, + OAuth: oauthProvider, + Anonymous: anonymousProvider, + Biometric: biometricProvider, + Passkey: passkeyProvider, + SIWE: siweProvider, + LDAP: ldapProvider, + } + store3 := &service2.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + passwordStore := &password.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorConfig := appConfig.Authenticator + authenticatorPasswordConfig := authenticatorConfig.Password + passwordLogger := password.NewLogger(factory) + historyStore := &password.HistoryStore{ + Clock: clockClock, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorFeatureConfig := featureConfig.Authenticator + passwordChecker := password.ProvideChecker(authenticatorPasswordConfig, authenticatorFeatureConfig, historyStore) + expiry := password.ProvideExpiry(authenticatorPasswordConfig, clockClock) + housekeeperLogger := password.NewHousekeeperLogger(factory) + housekeeper := &password.Housekeeper{ + Store: historyStore, + Logger: housekeeperLogger, + Config: authenticatorPasswordConfig, + } + passwordProvider := &password.Provider{ + Store: passwordStore, + Config: authenticatorPasswordConfig, + Clock: clockClock, + Logger: passwordLogger, + PasswordHistory: historyStore, + PasswordChecker: passwordChecker, + Expiry: expiry, + Housekeeper: housekeeper, + } + store4 := &passkey3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + provider2 := &passkey3.Provider{ + Store: store4, + Clock: clockClock, + Passkey: passkeyService, + } + totpStore := &totp.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorTOTPConfig := authenticatorConfig.TOTP + totpProvider := &totp.Provider{ + Store: totpStore, + Config: authenticatorTOTPConfig, + Clock: clockClock, + } + oobStore := &oob.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + oobProvider := &oob.Provider{ + Store: oobStore, + LoginIDNormalizerFactory: normalizerFactory, + Clock: clockClock, + } + testModeConfig := appConfig.TestMode + testModeFeatureConfig := featureConfig.TestMode + codeStoreRedis := &otp.CodeStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + lookupStoreRedis := &otp.LookupStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + attemptTrackerRedis := &otp.AttemptTrackerRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + otpLogger := otp.NewLogger(factory) + otpService := &otp.Service{ + Clock: clockClock, + AppID: appID, + TestModeConfig: testModeConfig, + TestModeFeatureConfig: testModeFeatureConfig, + RemoteIP: remoteIP, + CodeStore: codeStoreRedis, + LookupStore: lookupStoreRedis, + AttemptTracker: attemptTrackerRedis, + Logger: otpLogger, + RateLimiter: limiter, + } + rateLimits := service2.RateLimits{ + IP: remoteIP, + Config: authenticationConfig, + RateLimiter: limiter, + } + authenticationLockoutConfig := authenticationConfig.Lockout + lockoutLogger := lockout.NewLogger(factory) + lockoutStorageRedis := &lockout.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + lockoutService := &lockout.Service{ + Logger: lockoutLogger, + Storage: lockoutStorageRedis, + } + serviceLockout := service2.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + service3 := &service2.Service{ + Store: store3, + Config: appConfig, + Password: passwordProvider, + Passkey: provider2, + TOTP: totpProvider, + OOBOTP: oobProvider, + OTPCodeService: otpService, + RateLimits: rateLimits, + Lockout: serviceLockout, + } + verificationConfig := appConfig.Verification + userProfileConfig := appConfig.UserProfile + storePQ := &verification.StorePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + verificationService := &verification.Service{ + Config: verificationConfig, + UserProfileConfig: userProfileConfig, + Clock: clockClock, + ClaimStore: storePQ, + } + imagesCDNHost := environmentConfig.ImagesCDNHost + pictureTransformer := &stdattrs2.PictureTransformer{ + HTTPProto: httpProto, + HTTPHost: httpHost, + ImagesCDNHost: imagesCDNHost, + } + serviceNoEvent := &stdattrs2.ServiceNoEvent{ + UserProfileConfig: userProfileConfig, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + ClaimStore: storePQ, + Transformer: pictureTransformer, + } + customattrsServiceNoEvent := &customattrs.ServiceNoEvent{ + Config: userProfileConfig, + UserQueries: rawQueries, + UserStore: userStore, + } + nftIndexerAPIEndpoint := environmentConfig.NFTIndexerAPIEndpoint + web3Service := &web3.Service{ + APIEndpoint: nftIndexerAPIEndpoint, + Web3Config: web3Config, + } + rolesgroupsStore := &rolesgroups.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + queries := &rolesgroups.Queries{ + Store: rolesgroupsStore, + } + userQueries := &user.Queries{ + RawQueries: rawQueries, + Store: userStore, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + resolverImpl := &event.ResolverImpl{ + Users: userQueries, + } + hookLogger := hook.NewLogger(factory) + hookConfig := appConfig.Hook + webHookLogger := hook.NewWebHookLogger(factory) + webhookKeyMaterials := deps.ProvideWebhookKeyMaterials(secretConfig) + webHookImpl := hook.WebHookImpl{ + Logger: webHookLogger, + Secret: webhookKeyMaterials, + } + syncHTTPClient := hook.NewSyncHTTPClient(hookConfig) + asyncHTTPClient := hook.NewAsyncHTTPClient() + eventWebHookImpl := &hook.EventWebHookImpl{ + WebHookImpl: webHookImpl, + SyncHTTP: syncHTTPClient, + AsyncHTTP: asyncHTTPClient, + } + denoHookLogger := hook.NewDenoHookLogger(factory) + denoHook := hook.DenoHook{ + Context: contextContext, + ResourceManager: manager, + Logger: denoHookLogger, + } + denoEndpoint := environmentConfig.DenoEndpoint + syncDenoClient := hook.NewSyncDenoClient(denoEndpoint, hookConfig, hookLogger) + asyncDenoClient := hook.NewAsyncDenoClient(denoEndpoint, hookLogger) + eventDenoHookImpl := &hook.EventDenoHookImpl{ + DenoHook: denoHook, + SyncDenoClient: syncDenoClient, + AsyncDenoClient: asyncDenoClient, + } + commands := &rolesgroups.Commands{ + Store: rolesgroupsStore, + } + sink := &hook.Sink{ + Logger: hookLogger, + Config: hookConfig, + Clock: clockClock, + EventWebHook: eventWebHookImpl, + EventDenoHook: eventDenoHookImpl, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + RolesAndGroups: commands, + } + auditLogger := audit.NewLogger(factory) + writeHandle := appProvider.AuditWriteDatabase + auditDatabaseCredentials := deps.ProvideAuditDatabaseCredentials(secretConfig) + auditdbSQLBuilderApp := auditdb.NewSQLBuilderApp(auditDatabaseCredentials, appID) + writeSQLExecutor := auditdb.NewWriteSQLExecutor(contextContext, writeHandle) + writeStore := &audit.WriteStore{ + SQLBuilder: auditdbSQLBuilderApp, + SQLExecutor: writeSQLExecutor, + } + auditSink := &audit.Sink{ + Logger: auditLogger, + Database: writeHandle, + Store: writeStore, + } + elasticsearchLogger := elasticsearch.NewLogger(factory) + elasticsearchServiceLogger := elasticsearch.NewElasticsearchServiceLogger(factory) + elasticsearchCredentials := deps.ProvideElasticsearchCredentials(secretConfig) + client := elasticsearch.NewClient(elasticsearchCredentials) + queue := appProvider.TaskQueue + userReindexProducer := redisqueue.NewUserReindexProducer(appredisHandle, clockClock) + elasticsearchService := elasticsearch.Service{ + Clock: clockClock, + Context: contextContext, + Database: handle, + Logger: elasticsearchServiceLogger, + AppID: appID, + Client: client, + Users: userQueries, + UserStore: userStore, + IdentityService: serviceService, + RolesGroups: rolesgroupsStore, + TaskQueue: queue, + Producer: userReindexProducer, + } + elasticsearchSink := &elasticsearch.Sink{ + Logger: elasticsearchLogger, + Service: elasticsearchService, + Database: handle, + } + eventService := event.NewService(contextContext, appID, remoteIP, userAgentString, eventLogger, handle, clockClock, localizationConfig, storeImpl, resolverImpl, sink, auditSink, elasticsearchSink) + storeDeviceTokenRedis := &mfa.StoreDeviceTokenRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + storeRecoveryCodePQ := &mfa.StoreRecoveryCodePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + mfaLockout := mfa.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + mfaService := &mfa.Service{ + IP: remoteIP, + DeviceTokens: storeDeviceTokenRedis, + RecoveryCodes: storeRecoveryCodePQ, + Clock: clockClock, + Config: authenticationConfig, + RateLimiter: limiter, + Lockout: mfaLockout, + } + messagingLogger := messaging.NewLogger(factory) + usageLogger := usage.NewLogger(factory) + usageLimiter := &usage.Limiter{ + Logger: usageLogger, + Clock: clockClock, + AppID: appID, + Redis: appredisHandle, + } + messagingConfig := appConfig.Messaging + messagingRateLimitsConfig := messagingConfig.RateLimits + messagingFeatureConfig := featureConfig.Messaging + rateLimitsEnvironmentConfig := &environmentConfig.RateLimits + limits := messaging.Limits{ + Logger: messagingLogger, + RateLimiter: limiter, + UsageLimiter: usageLimiter, + RemoteIP: remoteIP, + Config: messagingRateLimitsConfig, + FeatureConfig: messagingFeatureConfig, + EnvConfig: rateLimitsEnvironmentConfig, + } + whatsappServiceLogger := whatsapp.NewServiceLogger(factory) + devMode := environmentConfig.DevMode + featureTestModeWhatsappSuppressed := deps.ProvideTestModeWhatsappSuppressed(testModeFeatureConfig) + testModeWhatsappConfig := testModeConfig.Whatsapp + whatsappConfig := messagingConfig.Whatsapp + whatsappOnPremisesCredentials := deps.ProvideWhatsappOnPremisesCredentials(secretConfig) + tokenStore := &whatsapp.TokenStore{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + onPremisesClient := whatsapp.NewWhatsappOnPremisesClient(whatsappConfig, whatsappOnPremisesCredentials, tokenStore) + whatsappService := &whatsapp.Service{ + Context: contextContext, + Logger: whatsappServiceLogger, + DevMode: devMode, + FeatureTestModeWhatsappSuppressed: featureTestModeWhatsappSuppressed, + TestModeWhatsappConfig: testModeWhatsappConfig, + WhatsappConfig: whatsappConfig, + LocalizationConfig: localizationConfig, + OnPremisesClient: onPremisesClient, + TokenStore: tokenStore, + } + sender := &messaging.Sender{ + Limits: limits, + TaskQueue: queue, + Events: eventService, + Whatsapp: whatsappService, + MessagingFeatureConfig: messagingFeatureConfig, + } + forgotpasswordSender := &forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + rawCommands := &user.RawCommands{ + Store: userStore, + Clock: clockClock, + } + userCommands := &user.Commands{ + RawCommands: rawCommands, + RawQueries: rawQueries, + Events: eventService, + Verification: verificationService, + UserProfileConfig: userProfileConfig, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + stdattrsService := &stdattrs2.Service{ + UserProfileConfig: userProfileConfig, + ServiceNoEvent: serviceNoEvent, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + Events: eventService, + } + authorizationStore := &pq.AuthorizationStore{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + storeRedisLogger := idpsession.NewStoreRedisLogger(factory) + idpsessionStoreRedis := &idpsession.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + Logger: storeRedisLogger, + } + sessionConfig := appConfig.Session + cookieDef2 := session.NewSessionCookieDef(sessionConfig) + idpsessionManager := &idpsession.Manager{ + Store: idpsessionStoreRedis, + Config: sessionConfig, + Cookies: cookieManager, + CookieDef: cookieDef2, + } + eventStoreRedis := &access.EventStoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + eventProvider := &access.EventProvider{ + Store: eventStoreRedis, + } + idpsessionRand := _wireRandValue + idpsessionProvider := &idpsession.Provider{ + Context: contextContext, + RemoteIP: remoteIP, + UserAgentString: userAgentString, + AppID: appID, + Redis: appredisHandle, + Store: idpsessionStoreRedis, + AccessEvents: eventProvider, + TrustProxy: trustProxy, + Config: sessionConfig, + Clock: clockClock, + Random: idpsessionRand, + } + offlineGrantService := oauth2.OfflineGrantService{ + OAuthConfig: oAuthConfig, + Clock: clockClock, + IDPSessions: idpsessionProvider, + ClientResolver: resolver, + OfflineGrants: store, + } + sessionManager := &oauth2.SessionManager{ + Store: store, + Config: oAuthConfig, + Service: offlineGrantService, + } + accountDeletionConfig := appConfig.AccountDeletion + accountAnonymizationConfig := appConfig.AccountAnonymization + maxTrials := _wireMaxTrialsValue + passwordRand := password.NewRandSource() + generator := &password.Generator{ + MaxTrials: maxTrials, + Checker: passwordChecker, + Rand: passwordRand, + PasswordConfig: authenticatorPasswordConfig, + } + coordinator := &facade.Coordinator{ + Events: eventService, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + MFA: mfaService, + SendPassword: forgotpasswordSender, + UserCommands: userCommands, + UserQueries: userQueries, + RolesGroupsCommands: commands, + StdAttrsService: stdattrsService, + PasswordHistory: historyStore, + OAuth: authorizationStore, + IDPSessions: idpsessionManager, + OAuthSessions: sessionManager, + IdentityConfig: identityConfig, + AccountDeletionConfig: accountDeletionConfig, + AccountAnonymizationConfig: accountAnonymizationConfig, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + PasswordGenerator: generator, + } + identityFacade := facade.IdentityFacade{ + Coordinator: coordinator, + } + authenticatorFacade := facade.AuthenticatorFacade{ + Coordinator: coordinator, + } + anonymousStoreRedis := &anonymous.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + messageSender := &otp.MessageSender{ + AppID: appID, + Translation: translationService, + Endpoints: endpointsEndpoints, + Sender: sender, + WhatsappService: whatsappService, + } + oAuthSSOProviderCredentials := deps.ProvideOAuthSSOProviderCredentials(secretConfig) + oAuthHTTPClient := sso.ProvideOAuthHTTPClient(environmentConfig) + simpleStoreRedisFactory := &sso.SimpleStoreRedisFactory{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + } + oAuthProviderFactory := &sso.OAuthProviderFactory{ + IdentityConfig: identityConfig, + Credentials: oAuthSSOProviderCredentials, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + HTTPClient: oAuthHTTPClient, + SimpleStoreRedisFactory: simpleStoreRedisFactory, + } + webappoauthStore := &webappoauth.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } + forgotpasswordLogger := forgotpassword.NewLogger(factory) + sender2 := forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + forgotpasswordService := &forgotpassword.Service{ + Logger: forgotpasswordLogger, + Config: appConfig, + FeatureConfig: featureConfig, + Identities: serviceService, + Authenticators: authenticatorFacade, + OTPCodes: otpService, + OTPSender: messageSender, + PasswordSender: sender2, + } + responseWriter := p.ResponseWriter + nonceService := &nonce.Service{ + Cookies: cookieManager, + Request: request, + ResponseWriter: responseWriter, + } + challengeProvider := &challenge.Provider{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + userProvider := &user.Provider{ + Commands: userCommands, + Queries: userQueries, + } + authenticationinfoStoreRedis := &authenticationinfo.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + manager2 := &session.Manager{ + IDPSessions: idpsessionManager, + AccessTokenSessions: sessionManager, + Events: eventService, + } + oauthsessionStoreRedis := &oauthsession.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + interactionContext := &interaction.Context{ + Request: request, + RemoteIP: remoteIP, + Database: sqlExecutor, + Clock: clockClock, + Config: appConfig, + FeatureConfig: featureConfig, + OAuthClientResolver: resolver, + OfflineGrants: store, + Identities: identityFacade, + Authenticators: authenticatorFacade, + AnonymousIdentities: anonymousProvider, + AnonymousUserPromotionCodeStore: anonymousStoreRedis, + BiometricIdentities: biometricProvider, + OTPCodeService: otpService, + OTPSender: messageSender, + OAuthProviderFactory: oAuthProviderFactory, + OAuthRedirectURIBuilder: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + MFA: mfaFacade, + ForgotPassword: forgotpasswordService, + ResetPassword: forgotpasswordService, + Passkey: passkeyService, + Verification: verificationService, + RateLimiter: limiter, + PasswordGenerator: generator, + Nonces: nonceService, + Challenges: challengeProvider, + Users: userProvider, + StdAttrsService: stdattrsService, + Events: eventService, + CookieManager: cookieManager, + AuthenticationInfoService: authenticationinfoStoreRedis, + Sessions: idpsessionProvider, + SessionManager: manager2, + SessionCookie: cookieDef2, + OAuthSessions: oauthsessionStoreRedis, + MFADeviceTokenCookie: cookieDef, + } + interactionStoreRedis := &interaction.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + interactionService := &interaction.Service{ + Logger: logger, + Context: interactionContext, + Store: interactionStoreRedis, + } + webappService2 := &webapp2.Service2{ + Logger: serviceLogger, + Request: request, + Sessions: sessionStoreRedis, + SessionCookie: sessionCookieDef, + SignedUpCookie: signedUpCookieDef, + MFADeviceTokenCookie: cookieDef, + ErrorService: errorService, + Cookies: cookieManager, + OAuthConfig: oAuthConfig, + UIConfig: uiConfig, + TrustProxy: trustProxy, + UIInfoResolver: uiService, + OAuthClientResolver: resolver, + Graph: interactionService, + } + uiFeatureConfig := featureConfig.UI + forgotPasswordConfig := appConfig.ForgotPassword + googleTagManagerConfig := appConfig.GoogleTagManager + botProtectionConfig := appConfig.BotProtection + flashMessage := &httputil.FlashMessage{ + Cookies: cookieManager, + } + authUISentryDSN := environmentConfig.AuthUISentryDSN + authUIWindowMessageAllowedOrigins := environmentConfig.AuthUIWindowMessageAllowedOrigins + baseLogger := viewmodels.NewBaseLogger(factory) + baseViewModeler := &viewmodels.BaseViewModeler{ + TrustProxy: trustProxy, + OAuth: oAuthConfig, + AuthUI: uiConfig, + AuthUIFeatureConfig: uiFeatureConfig, + StaticAssets: staticAssetResolver, + ForgotPassword: forgotPasswordConfig, + Authentication: authenticationConfig, + GoogleTagManager: googleTagManagerConfig, + BotProtection: botProtectionConfig, + ErrorService: errorService, + Translations: translationService, + Clock: clockClock, + FlashMessage: flashMessage, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + AuthUISentryDSN: authUISentryDSN, + AuthUIWindowMessageAllowedOrigins: authUIWindowMessageAllowedOrigins, + OAuthClientResolver: resolver, + Logger: baseLogger, + } + responseRenderer := &webapp.ResponseRenderer{ + TemplateEngine: engine, + } + publisher := webapp.NewPublisher(appID, appredisHandle) + authflowNavigator := &webapp2.AuthflowNavigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + authflowV2Navigator := &authflowv2.AuthflowV2Navigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + errorRenderer := &webapp.ErrorRenderer{ + ErrorService: errorService, + UIImplementationService: uiImplementationService, + AuthflowV1Navigator: authflowNavigator, + AuthflowV2Navigator: authflowV2Navigator, + } + controllerDeps := webapp.ControllerDeps{ + Database: handle, + RedisHandle: appredisHandle, + AppID: appID, + Page: webappService2, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + Publisher: publisher, + Clock: clockClock, + TesterEndpointsProvider: endpointsEndpoints, + ErrorRenderer: errorRenderer, + TrustProxy: trustProxy, + } + controllerFactory := webapp.ControllerFactory{ + LoggerFactory: factory, + ControllerDeps: controllerDeps, + } + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, + } + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } + authflowV2SettingsMFAEnterOOBOTPHandler := &authflowv2.AuthflowV2SettingsMFAEnterOOBOTPHandler{ + ControllerFactory: controllerFactory, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + FlashMessage: flashMessage, + Clock: clockClock, + Config: appConfig, + OTPCode: otpService, + AccountManagement: accountmanagementService, + } + return authflowV2SettingsMFAEnterOOBOTPHandler +} + func newWebAppSettingsPasskeyHandler(p *deps.RequestProvider) http.Handler { appProvider := p.AppProvider factory := appProvider.LoggerFactory diff --git a/pkg/auth/wire_handler.go b/pkg/auth/wire_handler.go index 19565da6fa..41a18ef844 100644 --- a/pkg/auth/wire_handler.go +++ b/pkg/auth/wire_handler.go @@ -541,6 +541,13 @@ func newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler(p *deps.RequestProvider) )) } +func newWebAppAuthflowV2SettingsMFAEnterOOBOTPHandler(p *deps.RequestProvider) http.Handler { + panic(wire.Build( + DependencySet, + wire.Bind(new(http.Handler), new(*handlerwebappauthflowv2.AuthflowV2SettingsMFAEnterOOBOTPHandler)), + )) +} + func newWebAppSettingsPasskeyHandler(p *deps.RequestProvider) http.Handler { panic(wire.Build( DependencySet, diff --git a/pkg/lib/accountmanagement/redis_store.go b/pkg/lib/accountmanagement/redis_store.go index d8ada0a2a0..7240d3e287 100644 --- a/pkg/lib/accountmanagement/redis_store.go +++ b/pkg/lib/accountmanagement/redis_store.go @@ -41,6 +41,7 @@ type GenerateTokenOptions struct { // AuthenticatorID for updating authenticator AuthenticatorID string AuthenticatorRecoveryCodes []string + AuthenticatorType model.AuthenticatorType // TOTP AuthenticatorTOTPIssuer string @@ -76,6 +77,7 @@ func (s *RedisStore) GenerateToken(options GenerateTokenOptions) (string, error) if options.AuthenticatorID != "" || len(options.AuthenticatorRecoveryCodes) > 0 || options.AuthenticatorTOTPSecret != "" || options.AuthenticatorTOTPVerified || options.AuthenticatorOOBOTPChannel != "" || options.AuthenticatorOOBOTPTarget != "" || options.AuthenticatorOOBOTPVerified { tokenAuthenticator = &TokenAuthenticator{ AuthenticatorID: options.AuthenticatorID, + AuthenticatorType: string(options.AuthenticatorType), RecoveryCodes: options.AuthenticatorRecoveryCodes, TOTPIssuer: options.AuthenticatorTOTPIssuer, TOTPDisplayName: options.AuthenticatorTOTPDisplayName, diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 654e828a68..3a97dc981b 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -144,6 +144,7 @@ func (s *Service) StartAddTOTPAuthenticator(resolvedSession session.ResolvedSess } token, err := s.Store.GenerateToken(GenerateTokenOptions{ UserID: userID, + AuthenticatorType: model.AuthenticatorTypeTOTP, AuthenticatorTOTPIssuer: string(s.HTTPOrigin), AuthenticatorTOTPEndUserAccountID: endUserAccountID, AuthenticatorTOTPSecret: totp.Secret, @@ -224,6 +225,7 @@ func (s *Service) ResumeAddTOTPAuthenticator(resolvedSession session.ResolvedSes newToken, err := s.Store.GenerateToken(GenerateTokenOptions{ UserID: userID, + AuthenticatorType: model.AuthenticatorType(token.Authenticator.AuthenticatorType), AuthenticatorTOTPDisplayName: input.DisplayName, AuthenticatorTOTPSecret: token.Authenticator.TOTPSecret, AuthenticatorTOTPVerified: true, @@ -387,9 +389,29 @@ func (s *Service) StartAddOOBOTPAuthenticator(resolvedSession session.ResolvedSe return nil, err } + var channel model.AuthenticatorOOBChannel + if s.Config.Authenticator.OOB.SMS.PhoneOTPMode.IsWhatsappEnabled() && input.Channel == model.AuthenticatorOOBChannelSMS { + channel = model.AuthenticatorOOBChannelWhatsapp + } else { + channel = input.Channel + } + + var authenticatorType model.AuthenticatorType + switch channel { + case model.AuthenticatorOOBChannelWhatsapp: + fallthrough + case model.AuthenticatorOOBChannelSMS: + authenticatorType = model.AuthenticatorTypeOOBSMS + case model.AuthenticatorOOBChannelEmail: + authenticatorType = model.AuthenticatorTypeOOBEmail + default: + panic("unexpected channel") + } + token, err := s.Store.GenerateToken(GenerateTokenOptions{ UserID: userID, - AuthenticatorOOBOTPChannel: input.Channel, + AuthenticatorType: authenticatorType, + AuthenticatorOOBOTPChannel: channel, AuthenticatorOOBOTPTarget: input.Target, }) if err != nil { @@ -442,6 +464,7 @@ func (s *Service) ResumeAddOOBOTPAuthenticator(resolvedSession session.ResolvedS newToken, err := s.Store.GenerateToken(GenerateTokenOptions{ UserID: userID, AuthenticatorRecoveryCodes: recoveryCodes, + AuthenticatorType: model.AuthenticatorType(token.Authenticator.AuthenticatorType), AuthenticatorOOBOTPChannel: token.Authenticator.OOBOTPChannel, AuthenticatorOOBOTPTarget: token.Authenticator.OOBOTPTarget, AuthenticatorOOBOTPVerified: true, diff --git a/pkg/lib/accountmanagement/token.go b/pkg/lib/accountmanagement/token.go index baf4859ec7..9a0b230963 100644 --- a/pkg/lib/accountmanagement/token.go +++ b/pkg/lib/accountmanagement/token.go @@ -35,8 +35,9 @@ type TokenIdentity struct { } type TokenAuthenticator struct { - AuthenticatorID string `json:"authenticator_id,omitempty"` - RecoveryCodes []string `json:"recovery_codes,omitempty"` + AuthenticatorID string `json:"authenticator_id,omitempty"` + AuthenticatorType string `json:"authenticator_type,omitempty"` + RecoveryCodes []string `json:"recovery_codes,omitempty"` // TOTP TOTPIssuer string `json:"totp_issuer,omitempty"` diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index aed0ae96e7..3ed91f629b 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1188,6 +1188,11 @@ "v2.page.settings-mfa-create-totp.default.description": "First, download Google Authenticator App from the Google Play Store or Apple App Store.

Scan the QR code or enter the key with your authenticator device / app.", "v2.page.settings-mfa-create-totp.default.title": "Set up authenticator", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "We''ve just sent a {CodeLength}-digit verification code to {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "For your security, we need to verify it''s you. We''ve just sent a {CodeLength}-digit verification code to {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "For your security, we need to verify it''s you. We''ve just sent a {CodeLength}-digit verification code to your WhatsApp number at {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "We''ve just sent a {CodeLength}-digit verification code to your WhatsApp number at {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Verification Code", "v2.page.settings-mfa-enter-totp.default.description": "Enter the 6-digit code you see in the authenticator device/app.", "v2.page.settings-mfa-enter-totp.default.title": "Verification Code", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Rescan QR code", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_oob_otp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_oob_otp.html new file mode 100644 index 0000000000..4db9bcc974 --- /dev/null +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_enter_oob_otp.html @@ -0,0 +1,91 @@ +{{ template "authflowv2/__settings_page_frame.html" . }} + +{{ define "page-navbar" }} + {{ $back_url := (call $.MakeURL "/settings/mfa") }} + {{- if eq $.AuthenticatorType "oob_otp_sms" }} + {{ $back_url = (call $.MakeURL "/settings/mfa/oob_otp_sms") }} + {{- end }} + {{- if eq $.AuthenticatorType "oob_otp_email" }} + {{ $back_url = (call $.MakeURL "/settings/mfa/oob_otp_email") }} + {{ end }} + + {{ template "authflowv2/__navbar.html" + (dict + "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) + "BackHref" $back_url + "Title" (translate "v2.page.settings-mfa-enter-oob-otp.default.title" nil) + ) + }} +{{ end }} + +{{ define "page-content" }} + + {{ $err_map := (resolveError $.RawError (dict + "otpInput" (dict + "by_reason" (list "InvalidCredentials" "InvalidVerificationCode") + ) + )) }} + + {{ $otp_err := index $err_map "otpInput" }} + {{ $unknown_err := index $err_map "unknown" }} + {{ $has_otp_err := not (isNil $otp_err) }} + {{ $has_unknown_err := not (isNil $unknown_err )}} + + {{ $otp_error_message := "" }} + {{ if $has_otp_err }} + {{ $otp_error_message = include "authflowv2/__error.html" (merge (dict "Error" $otp_err) $) }} + {{ end }} + + {{ $unknown_error_message := "" }} + {{ if $has_unknown_err }} + {{ $unknown_error_message = (include "authflowv2/__error.html" (merge (dict "Error" $unknown_err) $)) }} + {{ end }} + +
+
+

+ {{ if eq $.Channel "whatsapp" }} + {{ include "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle" . }} + {{ else }} + {{ include "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle" . }} + {{ end }} +

+ + {{ template "authflowv2/__alert_message.html" + (dict + "Type" "error" + "Classname" "mt-4" + "Message" $unknown_error_message + ) + }} +
+
+
+ {{ $.CSRFField }} +
+ + {{ template "authflowv2/__otp_input.html" + (dict + "CSRFField" $.CSRFField + "FormName" "main-form" + "CodeLength" $.CodeLength + "AutoFocus" $.ShouldFocusInput + "Disabled" $.FailedAttemptRateLimitExceeded + "ResendButtonLabel" (include "v2.component.oob-otp-resend-button.default.label" nil) + "ResendButtonLabelWithValue" (include "v2.component.oob-otp-resend-button.default.countdown-unit" nil) + "ResendButtonCooldown" $.ResendCooldown + "SubmitEvent" "authgear.button.submit_verification_code" + "ResendEvent" "authgear.button.resend_verification_code" + "ErrorMessage" $otp_error_message + ) + }} +
+
+{{ end }} diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html index c86a8afddb..f85cf163fe 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html @@ -1,10 +1,21 @@ {{ template "authflowv2/__settings_page_frame.html" . }} {{ define "page-navbar" }} + {{ $back_url := (call $.MakeURL "/settings/mfa") }} + {{- if eq $.AuthenticatorType "totp" }} + {{ $back_url = (call $.MakeURL "/settings/mfa/totp") }} + {{- end }} + {{- if eq $.AuthenticatorType "oob_otp_sms" }} + {{ $back_url = (call $.MakeURL "/settings/mfa/oob_otp_sms") }} + {{- end }} + {{- if eq $.AuthenticatorType "oob_otp_email" }} + {{ $back_url = (call $.MakeURL "/settings/mfa/oob_otp_email") }} + {{ end }} + {{ template "authflowv2/__navbar.html" (dict "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) - "BackHref" (call $.MakeURL "/settings/mfa/totp") + "BackHref" $back_url "Title" (translate "v2.page.settings-mfa-view-recovery-code.default.title" nil) ) }} From d095fc31537fd47dde59837d29b3540b252071c7 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 11:46:17 +0800 Subject: [PATCH 17/31] Generate translations --- .make-lint-translation-keys-expect | 2 +- resources/authgear/templates/de/translation.json | 13 +++++++++++-- resources/authgear/templates/el/translation.json | 13 +++++++++++-- resources/authgear/templates/en/translation.json | 4 ++-- .../authgear/templates/es-419/translation.json | 13 +++++++++++-- resources/authgear/templates/es-ES/translation.json | 13 +++++++++++-- resources/authgear/templates/es/translation.json | 13 +++++++++++-- resources/authgear/templates/fil/translation.json | 13 +++++++++++-- resources/authgear/templates/fr/translation.json | 13 +++++++++++-- resources/authgear/templates/id/translation.json | 13 +++++++++++-- resources/authgear/templates/it/translation.json | 13 +++++++++++-- resources/authgear/templates/ja/translation.json | 13 +++++++++++-- resources/authgear/templates/ko/translation.json | 13 +++++++++++-- resources/authgear/templates/ms/translation.json | 13 +++++++++++-- resources/authgear/templates/nl/translation.json | 13 +++++++++++-- resources/authgear/templates/pl/translation.json | 13 +++++++++++-- resources/authgear/templates/pt-BR/translation.json | 13 +++++++++++-- resources/authgear/templates/pt-PT/translation.json | 13 +++++++++++-- resources/authgear/templates/pt/translation.json | 13 +++++++++++-- resources/authgear/templates/th/translation.json | 13 +++++++++++-- resources/authgear/templates/vi/translation.json | 13 +++++++++++-- resources/authgear/templates/zh-CN/translation.json | 13 +++++++++++-- resources/authgear/templates/zh-HK/translation.json | 13 +++++++++++-- resources/authgear/templates/zh-TW/translation.json | 13 +++++++++++-- 24 files changed, 245 insertions(+), 47 deletions(-) diff --git a/.make-lint-translation-keys-expect b/.make-lint-translation-keys-expect index 0fc1f1f33c..9bc7db4056 100644 --- a/.make-lint-translation-keys-expect +++ b/.make-lint-translation-keys-expect @@ -29,7 +29,7 @@ resources/authgear/templates/en/web/authflowv2/forgot_password.html:159:26: temp resources/authgear/templates/en/web/authflowv2/layout.html:5:14: template translation is forbidden: `widget` resources/authgear/templates/en/web/authflowv2/login.html:252:37: translation key not defined: "%s-icon" resources/authgear/templates/en/web/authflowv2/settings_layout.html:3:14: template translation is forbidden: `widget` -resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html:38:31: translation key not defined: "/settings/mfa/new_oob_otp_%s" +resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html:38:31: translation key not defined: "/settings/mfa/create_oob_otp_%s" resources/authgear/templates/en/web/authflowv2/settings_profile.html:138:22: translation key not defined: "custom-attribute-label-%s" resources/authgear/templates/en/web/authflowv2/settings_profile.html:139:20: invalid translation key: "$labelKey" resources/authgear/templates/en/web/authflowv2/settings_profile.html:143:21: invalid translation key: "$labelKey" diff --git a/resources/authgear/templates/de/translation.json b/resources/authgear/templates/de/translation.json index c4dad6ca30..2772fe33fc 100644 --- a/resources/authgear/templates/de/translation.json +++ b/resources/authgear/templates/de/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Benutzername", "v2.page.settings-identity.default.verification-status-unverified-label": "Nicht verifiziert", "v2.page.settings-identity.default.verification-status-verified-label": "Verifiziert", + "v2.page.settings-mfa-create-oob-otp.default.title": "2-Stufen-Verifizierung", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Sie können Verifizierungscodes an diese E-Mail-Adresse erhalten.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Sie können Verifizierungscodes an diese Nummer erhalten.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Sie können Verifizierungscodes an diese Nummer erhalten.", "v2.page.settings-mfa-create-password.default.title": "Zusätzliches Passwort", "v2.page.settings-mfa-create-totp.default.description": "Laden Sie zuerst die Google Authenticator App aus dem Google Play Store oder dem Apple App Store herunter.

Scannen Sie den QR-Code oder geben Sie den Schlüssel mit Ihrem Authenticator-Gerät / App ein.", - "v2.page.settings-mfa-create-totp.default.title": "Authenticator einrichten", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Authenticator einrichten", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Wir haben gerade einen {CodeLength}-stelligen Verifizierungscode an {MaskedClaimValue} gesendet.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Zu Ihrer Sicherheit müssen wir überprüfen, ob Sie es sind. Wir haben gerade einen {CodeLength}-stelligen Verifizierungscode an {MaskedClaimValue} gesendet.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Zu Ihrer Sicherheit müssen wir überprüfen, ob Sie es sind. Wir haben gerade einen {CodeLength}-stelligen Verifizierungscode an Ihre WhatsApp-Nummer unter {MaskedClaimValue} gesendet.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Wir haben gerade einen {CodeLength}-stelligen Verifizierungscode an Ihre WhatsApp-Nummer unter {MaskedClaimValue} gesendet.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Verifizierungscode", "v2.page.settings-mfa-enter-totp.default.description": "Geben Sie den 6-stelligen Code ein, den Sie in der Authenticator-Gerät/App sehen.", - "v2.page.settings-mfa-enter-totp.default.title": "Verifizierungscode", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "QR-Code erneut scannen", + "v2.page.settings-mfa-enter-totp.default.title": "Verifizierungscode", "v2.page.settings-mfa-password.default.additional-password-added-at": "Hinzugefügt um {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Zusätzliches Passwort", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Aktualisiert um {time, datetime, short} UTC", diff --git a/resources/authgear/templates/el/translation.json b/resources/authgear/templates/el/translation.json index 25dffc5872..19ffa68c90 100644 --- a/resources/authgear/templates/el/translation.json +++ b/resources/authgear/templates/el/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Όνομα Χρήστη", "v2.page.settings-identity.default.verification-status-unverified-label": "Μη επαληθευμένο", "v2.page.settings-identity.default.verification-status-verified-label": "Επαληθευμένο", + "v2.page.settings-mfa-create-oob-otp.default.title": "Επαλήθευση Δύο Βημάτων", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Μπορείτε να λαμβάνετε κωδικούς επαλήθευσης σε αυτό το email.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Μπορείτε να λαμβάνετε κωδικούς επαλήθευσης σε αυτόν τον αριθμό.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Μπορείτε να λαμβάνετε κωδικούς επαλήθευσης σε αυτόν τον αριθμό.", "v2.page.settings-mfa-create-password.default.title": "Πρόσθετος κωδικός πρόσβασης", "v2.page.settings-mfa-create-totp.default.description": "Πρώτα, κατεβάστε την εφαρμογή Google Authenticator από το Google Play Store ή το Apple App Store.

Σαρώστε τον κωδικό QR ή εισάγετε το κλειδί με τη συσκευή/εφαρμογή ελέγχου ταυτότητας.", - "v2.page.settings-mfa-create-totp.default.title": "Ρύθμιση ελέγχου ταυτότητας", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Ρύθμιση ελέγχου ταυτότητας", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Μόλις στείλαμε έναν κωδικό επαλήθευσης {CodeLength} ψηφίων στο {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Για την ασφάλειά σας, πρέπει να επαληθεύσουμε ότι είστε εσείς. Μόλις στείλαμε έναν κωδικό επαλήθευσης {CodeLength} ψηφίων στο {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Για την ασφάλειά σας, πρέπει να επαληθεύσουμε ότι είστε εσείς. Μόλις στείλαμε έναν κωδικό επαλήθευσης {CodeLength} ψηφίων στον αριθμό WhatsApp σας στο {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Μόλις στείλαμε έναν κωδικό επαλήθευσης {CodeLength} ψηφίων στον αριθμό WhatsApp σας στο {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Κωδικός Επαλήθευσης", "v2.page.settings-mfa-enter-totp.default.description": "Εισάγετε τον 6ψήφιο κωδικό που βλέπετε στη συσκευή/εφαρμογή ελέγχου ταυτότητας.", - "v2.page.settings-mfa-enter-totp.default.title": "Κωδικός Επαλήθευσης", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Επανασάρωση κωδικού QR", + "v2.page.settings-mfa-enter-totp.default.title": "Κωδικός Επαλήθευσης", "v2.page.settings-mfa-password.default.additional-password-added-at": "Προστέθηκε στις {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Πρόσθετος κωδικός πρόσβασης", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Ενημερώθηκε στις {time, datetime, short} UTC", diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index 3ed91f629b..9f7eb1ee0c 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1186,16 +1186,16 @@ "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "You can receive verification codes at this number.", "v2.page.settings-mfa-create-password.default.title": "Additional password", "v2.page.settings-mfa-create-totp.default.description": "First, download Google Authenticator App from the Google Play Store or Apple App Store.

Scan the QR code or enter the key with your authenticator device / app.", - "v2.page.settings-mfa-create-totp.default.title": "Set up authenticator", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Set up authenticator", "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "We''ve just sent a {CodeLength}-digit verification code to {MaskedClaimValue}.", "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "For your security, we need to verify it''s you. We''ve just sent a {CodeLength}-digit verification code to {MaskedClaimValue}.", "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "For your security, we need to verify it''s you. We''ve just sent a {CodeLength}-digit verification code to your WhatsApp number at {MaskedClaimValue}.", "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "We''ve just sent a {CodeLength}-digit verification code to your WhatsApp number at {MaskedClaimValue}.", "v2.page.settings-mfa-enter-oob-otp.default.title": "Verification Code", "v2.page.settings-mfa-enter-totp.default.description": "Enter the 6-digit code you see in the authenticator device/app.", - "v2.page.settings-mfa-enter-totp.default.title": "Verification Code", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Rescan QR code", + "v2.page.settings-mfa-enter-totp.default.title": "Verification Code", "v2.page.settings-mfa-password.default.additional-password-added-at": "Added at {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Additional password", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Updated at {time, datetime, short} UTC", diff --git a/resources/authgear/templates/es-419/translation.json b/resources/authgear/templates/es-419/translation.json index b25f49d9a1..69531a435b 100644 --- a/resources/authgear/templates/es-419/translation.json +++ b/resources/authgear/templates/es-419/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nombre de usuario", "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-create-oob-otp.default.title": "Verificación de 2 pasos", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Puedes recibir códigos de verificación en este correo electrónico.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Puedes recibir códigos de verificación en este número.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Puedes recibir códigos de verificación en este número.", "v2.page.settings-mfa-create-password.default.title": "Contraseña adicional", "v2.page.settings-mfa-create-totp.default.description": "Primero, descargue la aplicación Google Authenticator desde la Google Play Store o Apple App Store.

Escanee el código QR o ingrese la clave con su dispositivo/aplicación de autenticación.", - "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Acabamos de enviar un código de verificación de {CodeLength} dígitos a {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Por tu seguridad, necesitamos verificar que eres tú. Acabamos de enviar un código de verificación de {CodeLength} dígitos a {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Por tu seguridad, necesitamos verificar que eres tú. Acabamos de enviar un código de verificación de {CodeLength} dígitos a tu número de WhatsApp en {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Acabamos de enviar un código de verificación de {CodeLength} dígitos a tu número de WhatsApp en {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Código de verificación", "v2.page.settings-mfa-enter-totp.default.description": "Ingrese el código de 6 dígitos que ve en el dispositivo/aplicación de autenticación.", - "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Volver a escanear código QR", + "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", "v2.page.settings-mfa-password.default.additional-password-added-at": "Agregada a las {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Actualizada a las {time, datetime, short} UTC", diff --git a/resources/authgear/templates/es-ES/translation.json b/resources/authgear/templates/es-ES/translation.json index 42c04c3587..b894ab8536 100644 --- a/resources/authgear/templates/es-ES/translation.json +++ b/resources/authgear/templates/es-ES/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nombre de usuario", "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-create-oob-otp.default.title": "Verificación en 2 pasos", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Puedes recibir códigos de verificación en este correo electrónico.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Puedes recibir códigos de verificación en este número.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Puedes recibir códigos de verificación en este número.", "v2.page.settings-mfa-create-password.default.title": "Contraseña adicional", "v2.page.settings-mfa-create-totp.default.description": "Primero, descargue la aplicación Google Authenticator desde la Google Play Store o la Apple App Store.

Escanee el código QR o ingrese la clave con su dispositivo/aplicación de autenticación.", - "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Acabamos de enviar un código de verificación de {CodeLength} dígitos a {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Por tu seguridad, necesitamos verificar que eres tú. Acabamos de enviar un código de verificación de {CodeLength} dígitos a {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Por tu seguridad, necesitamos verificar que eres tú. Acabamos de enviar un código de verificación de {CodeLength} dígitos a tu número de WhatsApp en {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Acabamos de enviar un código de verificación de {CodeLength} dígitos a tu número de WhatsApp en {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Código de verificación", "v2.page.settings-mfa-enter-totp.default.description": "Ingrese el código de 6 dígitos que ve en el dispositivo/aplicación de autenticación.", - "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Volver a escanear el código QR", + "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", "v2.page.settings-mfa-password.default.additional-password-added-at": "Añadida a las {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Actualizada a las {time, datetime, short} UTC", diff --git a/resources/authgear/templates/es/translation.json b/resources/authgear/templates/es/translation.json index c72fdfbf04..9e07a5d956 100644 --- a/resources/authgear/templates/es/translation.json +++ b/resources/authgear/templates/es/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nombre de usuario", "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-create-oob-otp.default.title": "Verificación de 2 pasos", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Puedes recibir códigos de verificación en este correo electrónico.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Puedes recibir códigos de verificación en este número.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Puedes recibir códigos de verificación en este número.", "v2.page.settings-mfa-create-password.default.title": "Contraseña adicional", "v2.page.settings-mfa-create-totp.default.description": "Primero, descargue la aplicación Google Authenticator desde la Google Play Store o Apple App Store.

Escanee el código QR o ingrese la clave con su dispositivo/aplicación de autenticación.", - "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Acabamos de enviar un código de verificación de {CodeLength} dígitos a {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Por tu seguridad, necesitamos verificar que eres tú. Acabamos de enviar un código de verificación de {CodeLength} dígitos a {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Por tu seguridad, necesitamos verificar que eres tú. Acabamos de enviar un código de verificación de {CodeLength} dígitos a tu número de WhatsApp en {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Acabamos de enviar un código de verificación de {CodeLength} dígitos a tu número de WhatsApp en {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Código de verificación", "v2.page.settings-mfa-enter-totp.default.description": "Ingrese el código de 6 dígitos que ve en el dispositivo/aplicación de autenticación.", - "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Volver a escanear código QR", + "v2.page.settings-mfa-enter-totp.default.title": "Código de verificación", "v2.page.settings-mfa-password.default.additional-password-added-at": "Añadido a las {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Contraseña adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Actualizado a las {time, datetime, short} UTC", diff --git a/resources/authgear/templates/fil/translation.json b/resources/authgear/templates/fil/translation.json index 8f358c9789..e32c8066c6 100644 --- a/resources/authgear/templates/fil/translation.json +++ b/resources/authgear/templates/fil/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Username", "v2.page.settings-identity.default.verification-status-unverified-label": "Hindi Napatunayan", "v2.page.settings-identity.default.verification-status-verified-label": "Napatunayan", + "v2.page.settings-mfa-create-oob-otp.default.title": "2-Hakbang na Pag-verify", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Maaari kang makatanggap ng mga code sa pag-verify sa email na ito.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Maaari kang makatanggap ng mga code sa pag-verify sa numerong ito.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Maaari kang makatanggap ng mga code sa pag-verify sa numerong ito.", "v2.page.settings-mfa-create-password.default.title": "Karagdagang password", "v2.page.settings-mfa-create-totp.default.description": "Una, i-download ang Google Authenticator App mula sa Google Play Store o Apple App Store.

I-scan ang QR code o ipasok ang key gamit ang iyong authenticator device / app.", - "v2.page.settings-mfa-create-totp.default.title": "Mag-set up ng authenticator", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Mag-set up ng authenticator", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Nagpadala kami ng {CodeLength}-digit na code sa pag-verify sa {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Para sa iyong seguridad, kailangan naming i-verify na ikaw iyan. Nagpadala kami ng {CodeLength}-digit na code sa pag-verify sa {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Para sa iyong seguridad, kailangan naming i-verify na ikaw iyan. Nagpadala kami ng {CodeLength}-digit na code sa pag-verify sa iyong WhatsApp number sa {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Nagpadala kami ng {CodeLength}-digit na code sa pag-verify sa iyong WhatsApp number sa {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Code sa Pag-verify", "v2.page.settings-mfa-enter-totp.default.description": "Ipasok ang 6-digit na code na nakikita mo sa authenticator device/app.", - "v2.page.settings-mfa-enter-totp.default.title": "Code para sa Pag-verify", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "I-scan muli ang QR code", + "v2.page.settings-mfa-enter-totp.default.title": "Code para sa Pag-verify", "v2.page.settings-mfa-password.default.additional-password-added-at": "Naidagdag sa {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Karagdagang password", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Na-update sa {time, datetime, short} UTC", diff --git a/resources/authgear/templates/fr/translation.json b/resources/authgear/templates/fr/translation.json index 4e3fc96ae1..9b547ef9ce 100644 --- a/resources/authgear/templates/fr/translation.json +++ b/resources/authgear/templates/fr/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nom d''utilisateur", "v2.page.settings-identity.default.verification-status-unverified-label": "Non vérifié", "v2.page.settings-identity.default.verification-status-verified-label": "Vérifié", + "v2.page.settings-mfa-create-oob-otp.default.title": "Vérification en 2 étapes", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Vous pouvez recevoir des codes de vérification à cette adresse e-mail.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Vous pouvez recevoir des codes de vérification à ce numéro.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Vous pouvez recevoir des codes de vérification à ce numéro.", "v2.page.settings-mfa-create-password.default.title": "Mot de passe supplémentaire", "v2.page.settings-mfa-create-totp.default.description": "Tout d''abord, téléchargez l''application Google Authenticator depuis le Google Play Store ou Apple App Store.

Scannez le code QR ou entrez la clé avec votre appareil/application d''authentification.", - "v2.page.settings-mfa-create-totp.default.title": "Configurer l''authentificateur", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Configurer l''authentificateur", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Nous venons d''envoyer un code de vérification de {CodeLength} chiffres à {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Pour votre sécurité, nous devons vérifier que c''est bien vous. Nous venons d''envoyer un code de vérification de {CodeLength} chiffres à {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Pour votre sécurité, nous devons vérifier que c''est bien vous. Nous venons d''envoyer un code de vérification de {CodeLength} chiffres à votre numéro WhatsApp {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Nous venons d''envoyer un code de vérification de {CodeLength} chiffres à votre numéro WhatsApp {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Code de vérification", "v2.page.settings-mfa-enter-totp.default.description": "Entrez le code à 6 chiffres que vous voyez dans l''appareil/application d''authentification.", - "v2.page.settings-mfa-enter-totp.default.title": "Code de vérification", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Rescanner le code QR", + "v2.page.settings-mfa-enter-totp.default.title": "Code de vérification", "v2.page.settings-mfa-password.default.additional-password-added-at": "Ajouté à {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Mot de passe supplémentaire", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Mis à jour à {time, datetime, short} UTC", diff --git a/resources/authgear/templates/id/translation.json b/resources/authgear/templates/id/translation.json index 585f3138aa..8448c56b6b 100644 --- a/resources/authgear/templates/id/translation.json +++ b/resources/authgear/templates/id/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nama Pengguna", "v2.page.settings-identity.default.verification-status-unverified-label": "Tidak Diverifikasi", "v2.page.settings-identity.default.verification-status-verified-label": "Diverifikasi", + "v2.page.settings-mfa-create-oob-otp.default.title": "Verifikasi 2-Langkah", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Anda dapat menerima kode verifikasi di email ini.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Anda dapat menerima kode verifikasi di nomor ini.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Anda dapat menerima kode verifikasi di nomor ini.", "v2.page.settings-mfa-create-password.default.title": "Kata sandi tambahan", "v2.page.settings-mfa-create-totp.default.description": "Pertama, unduh Aplikasi Google Authenticator dari Google Play Store atau Apple App Store.

Pindai kode QR atau masukkan kunci dengan perangkat/aplikasi pengautentikasi Anda.", - "v2.page.settings-mfa-create-totp.default.title": "Mengatur pengautentikasi", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Mengatur pengautentikasi", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Kami baru saja mengirim kode verifikasi {CodeLength}-digit ke {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Untuk keamanan Anda, kami perlu memverifikasi bahwa itu Anda. Kami baru saja mengirim kode verifikasi {CodeLength}-digit ke {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Untuk keamanan Anda, kami perlu memverifikasi bahwa itu Anda. Kami baru saja mengirim kode verifikasi {CodeLength}-digit ke nomor WhatsApp Anda di {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Kami baru saja mengirim kode verifikasi {CodeLength}-digit ke nomor WhatsApp Anda di {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Kode Verifikasi", "v2.page.settings-mfa-enter-totp.default.description": "Masukkan kode 6 digit yang Anda lihat di perangkat/aplikasi pengautentikasi.", - "v2.page.settings-mfa-enter-totp.default.title": "Kode Verifikasi", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Pindai Ulang Kode QR", + "v2.page.settings-mfa-enter-totp.default.title": "Kode Verifikasi", "v2.page.settings-mfa-password.default.additional-password-added-at": "Ditambahkan pada {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Kata sandi tambahan", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Diperbarui pada {time, datetime, short} UTC", diff --git a/resources/authgear/templates/it/translation.json b/resources/authgear/templates/it/translation.json index e3f32447e8..e1f830b079 100644 --- a/resources/authgear/templates/it/translation.json +++ b/resources/authgear/templates/it/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nome Utente", "v2.page.settings-identity.default.verification-status-unverified-label": "Non verificato", "v2.page.settings-identity.default.verification-status-verified-label": "Verificato", + "v2.page.settings-mfa-create-oob-otp.default.title": "Verifica in due passaggi", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Puoi ricevere i codici di verifica a questa email.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Puoi ricevere i codici di verifica a questo numero.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Puoi ricevere i codici di verifica a questo numero.", "v2.page.settings-mfa-create-password.default.title": "Password aggiuntiva", "v2.page.settings-mfa-create-totp.default.description": "Per prima cosa, scarica l'app Google Authenticator dal Google Play Store o dall'Apple App Store.

Scansiona il codice QR o inserisci la chiave con il tuo dispositivo/app di autenticazione.", - "v2.page.settings-mfa-create-totp.default.title": "Imposta l''autenticatore", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Imposta l''autenticatore", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Abbiamo appena inviato un codice di verifica di {CodeLength} cifre a {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Per la tua sicurezza, dobbiamo verificare che sei tu. Abbiamo appena inviato un codice di verifica di {CodeLength} cifre a {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Per la tua sicurezza, dobbiamo verificare che sei tu. Abbiamo appena inviato un codice di verifica di {CodeLength} cifre al tuo numero WhatsApp {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Abbiamo appena inviato un codice di verifica di {CodeLength} cifre al tuo numero WhatsApp {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Codice di verifica", "v2.page.settings-mfa-enter-totp.default.description": "Inserisci il codice a 6 cifre che vedi nel dispositivo/app di autenticazione.", - "v2.page.settings-mfa-enter-totp.default.title": "Codice di verifica", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Riscansiona il codice QR", + "v2.page.settings-mfa-enter-totp.default.title": "Codice di verifica", "v2.page.settings-mfa-password.default.additional-password-added-at": "Aggiunta alle {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Password aggiuntiva", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Aggiornata alle {time, datetime, short} UTC", diff --git a/resources/authgear/templates/ja/translation.json b/resources/authgear/templates/ja/translation.json index 419885aadb..19fe9d9028 100644 --- a/resources/authgear/templates/ja/translation.json +++ b/resources/authgear/templates/ja/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "ユーザー名", "v2.page.settings-identity.default.verification-status-unverified-label": "未検証", "v2.page.settings-identity.default.verification-status-verified-label": "検証済み", + "v2.page.settings-mfa-create-oob-otp.default.title": "2段階認証", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "この電子メールで認証コードを受け取ることができます。", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "この番号で認証コードを受け取ることができます。", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "この番号でWhatsAppの認証コードを受け取ることができます。", "v2.page.settings-mfa-create-password.default.title": "追加のパスワード", "v2.page.settings-mfa-create-totp.default.description": "最初に、Google Play ストアまたはApple App StoreからGoogle Authenticator Appをダウンロードしてください。

QRコードをスキャンするか、認証デバイス/アプリにキーを入力してください。", - "v2.page.settings-mfa-create-totp.default.title": "認証アプリの設定", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "認証アプリの設定", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "{MaskedClaimValue}に{CodeLength}桁の認証コードを送信しました。", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "セキュリティ上の理由から、あなたご自身であることを確認する必要があります。{MaskedClaimValue}に{CodeLength}桁の認証コードを送信しました。", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "セキュリティ上の理由から、あなたご自身であることを確認する必要があります。WhatsAppの番号{MaskedClaimValue}に{CodeLength}桁の認証コードを送信しました。", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "WhatsAppの番号{MaskedClaimValue}に{CodeLength}桁の認証コードを送信しました。", + "v2.page.settings-mfa-enter-oob-otp.default.title": "認証コード", "v2.page.settings-mfa-enter-totp.default.description": "認証デバイス/アプリに表示されている6桁のコードを入力してください。", - "v2.page.settings-mfa-enter-totp.default.title": "確認コード", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "QRコードを再スキャン", + "v2.page.settings-mfa-enter-totp.default.title": "確認コード", "v2.page.settings-mfa-password.default.additional-password-added-at": "追加日時: {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "追加のパスワード", "v2.page.settings-mfa-password.default.additional-password-updated-at": "更新日時: {time, datetime, short} UTC", diff --git a/resources/authgear/templates/ko/translation.json b/resources/authgear/templates/ko/translation.json index bac2c476b7..0d981bb8d6 100644 --- a/resources/authgear/templates/ko/translation.json +++ b/resources/authgear/templates/ko/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "사용자 이름", "v2.page.settings-identity.default.verification-status-unverified-label": "인증되지 않음", "v2.page.settings-identity.default.verification-status-verified-label": "인증됨", + "v2.page.settings-mfa-create-oob-otp.default.title": "2단계 인증", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "이 이메일로 인증 코드를 받을 수 있습니다.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "이 번호로 인증 코드를 받을 수 있습니다.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "이 번호로 인증 코드를 받을 수 있습니다.", "v2.page.settings-mfa-create-password.default.title": "추가 비밀번호", "v2.page.settings-mfa-create-totp.default.description": "첫째, Google Play 스토어 또는 Apple 앱스토어에서 Google Authenticator 앱을 다운로드하세요.

QR 코드를 스캔하거나 인증기기/앱에 키를 입력하세요.", - "v2.page.settings-mfa-create-totp.default.title": "인증기기 설정", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "인증기기 설정", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "{MaskedClaimValue}(으)로 {CodeLength}자리 인증 코드를 보냈습니다.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "보안을 위해 본인 확인이 필요합니다. {MaskedClaimValue}(으)로 {CodeLength}자리 인증 코드를 보냈습니다.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "보안을 위해 본인 확인이 필요합니다. {MaskedClaimValue}의 WhatsApp 번호로 {CodeLength}자리 인증 코드를 보냈습니다.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "{MaskedClaimValue}의 WhatsApp 번호로 {CodeLength}자리 인증 코드를 보냈습니다.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "인증 코드", "v2.page.settings-mfa-enter-totp.default.description": "인증기기/앱에 표시된 6자리 코드를 입력하세요.", - "v2.page.settings-mfa-enter-totp.default.title": "인증 코드", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "QR 코드 다시 스캔", + "v2.page.settings-mfa-enter-totp.default.title": "인증 코드", "v2.page.settings-mfa-password.default.additional-password-added-at": "{time, datetime, short} UTC에 추가됨", "v2.page.settings-mfa-password.default.additional-password-label": "추가 비밀번호", "v2.page.settings-mfa-password.default.additional-password-updated-at": "{time, datetime, short} UTC에 업데이트됨", diff --git a/resources/authgear/templates/ms/translation.json b/resources/authgear/templates/ms/translation.json index 06357f1ba6..23493aed24 100644 --- a/resources/authgear/templates/ms/translation.json +++ b/resources/authgear/templates/ms/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nama Pengguna", "v2.page.settings-identity.default.verification-status-unverified-label": "Tidak Disahkan", "v2.page.settings-identity.default.verification-status-verified-label": "Disahkan", + "v2.page.settings-mfa-create-oob-otp.default.title": "Pengesahan 2-Langkah", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Anda boleh menerima kod pengesahan di email ini.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Anda boleh menerima kod pengesahan di nombor ini.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Anda boleh menerima kod pengesahan di nombor ini.", "v2.page.settings-mfa-create-password.default.title": "Kata laluan tambahan", "v2.page.settings-mfa-create-totp.default.description": "Pertama, muat turun Aplikasi Google Authenticator dari Google Play Store atau Apple App Store.

Imbas kod QR atau masukkan kunci dengan peranti / aplikasi penguat sah anda.", - "v2.page.settings-mfa-create-totp.default.title": "Sediakan pengaut sah", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Sediakan pengaut sah", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Kami baru sahaja menghantar kod pengesahan {CodeLength}-digit ke {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Untuk keselamatan anda, kami perlu mengesahkan bahawa itu anda. Kami baru sahaja menghantar kod pengesahan {CodeLength}-digit ke {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Untuk keselamatan anda, kami perlu mengesahkan bahawa itu anda. Kami baru sahaja menghantar kod pengesahan {CodeLength}-digit ke nombor WhatsApp anda di {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Kami baru sahaja menghantar kod pengesahan {CodeLength}-digit ke nombor WhatsApp anda di {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Kod Pengesahan", "v2.page.settings-mfa-enter-totp.default.description": "Masukkan kod 6 digit yang anda lihat dalam peranti/aplikasi pengaut sah.", - "v2.page.settings-mfa-enter-totp.default.title": "Kod Pengesahan", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Imbas semula kod QR", + "v2.page.settings-mfa-enter-totp.default.title": "Kod Pengesahan", "v2.page.settings-mfa-password.default.additional-password-added-at": "Ditambah pada {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Kata laluan tambahan", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Dikemaskini pada {time, datetime, short} UTC", diff --git a/resources/authgear/templates/nl/translation.json b/resources/authgear/templates/nl/translation.json index e5c74d5637..e661b19b65 100644 --- a/resources/authgear/templates/nl/translation.json +++ b/resources/authgear/templates/nl/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Gebruikersnaam", "v2.page.settings-identity.default.verification-status-unverified-label": "Niet geverifieerd", "v2.page.settings-identity.default.verification-status-verified-label": "Geverifieerd", + "v2.page.settings-mfa-create-oob-otp.default.title": "2-Staps Verificatie", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Je kunt verificatiecodes ontvangen op dit e-mailadres.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Je kunt verificatiecodes ontvangen op dit nummer.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Je kunt verificatiecodes ontvangen op dit nummer.", "v2.page.settings-mfa-create-password.default.title": "Aanvullend wachtwoord", "v2.page.settings-mfa-create-totp.default.description": "Download eerst de Google Authenticator App van de Google Play Store of Apple App Store.

Scan de QR-code of voer de sleutel in met uw authenticator-apparaat / app.", - "v2.page.settings-mfa-create-totp.default.title": "Authenticator instellen", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Authenticator instellen", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "We hebben zojuist een {CodeLength}-cijferige verificatiecode naar {MaskedClaimValue} gestuurd.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Voor je veiligheid moeten we verifiëren dat het jij bent. We hebben zojuist een {CodeLength}-cijferige verificatiecode naar {MaskedClaimValue} gestuurd.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Voor je veiligheid moeten we verifiëren dat het jij bent. We hebben zojuist een {CodeLength}-cijferige verificatiecode naar je WhatsApp-nummer op {MaskedClaimValue} gestuurd.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "We hebben zojuist een {CodeLength}-cijferige verificatiecode naar je WhatsApp-nummer op {MaskedClaimValue} gestuurd.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Verificatiecode", "v2.page.settings-mfa-enter-totp.default.description": "Voer de 6-cijferige code in die u ziet in de authenticator-app.", - "v2.page.settings-mfa-enter-totp.default.title": "Verificatiecode", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "QR-code opnieuw scannen", + "v2.page.settings-mfa-enter-totp.default.title": "Verificatiecode", "v2.page.settings-mfa-password.default.additional-password-added-at": "Toegevoegd op {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Aanvullend wachtwoord", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Bijgewerkt op {time, datetime, short} UTC", diff --git a/resources/authgear/templates/pl/translation.json b/resources/authgear/templates/pl/translation.json index aa5940f64e..692bbbb20f 100644 --- a/resources/authgear/templates/pl/translation.json +++ b/resources/authgear/templates/pl/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nazwa użytkownika", "v2.page.settings-identity.default.verification-status-unverified-label": "Niezweryfikowane", "v2.page.settings-identity.default.verification-status-verified-label": "Zweryfikowane", + "v2.page.settings-mfa-create-oob-otp.default.title": "Weryfikacja dwuetapowa", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Możesz otrzymywać kody weryfikacyjne na ten adres e-mail.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Możesz otrzymywać kody weryfikacyjne na ten numer.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Możesz otrzymywać kody weryfikacyjne na ten numer.", "v2.page.settings-mfa-create-password.default.title": "Dodatkowe hasło", "v2.page.settings-mfa-create-totp.default.description": "Najpierw pobierz aplikację Google Authenticator z Google Play Store lub Apple App Store.

Zeskanuj kod QR lub wprowadź klucz za pomocą urządzenia/aplikacji uwierzytelniającej.", - "v2.page.settings-mfa-create-totp.default.title": "Skonfiguruj uwierzytelnianie", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Skonfiguruj uwierzytelnianie", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Właśnie wysłaliśmy {CodeLength}-cyfrowy kod weryfikacyjny na {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Ze względów bezpieczeństwa musimy zweryfikować, że to Ty. Właśnie wysłaliśmy {CodeLength}-cyfrowy kod weryfikacyjny na {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Ze względów bezpieczeństwa musimy zweryfikować, że to Ty. Właśnie wysłaliśmy {CodeLength}-cyfrowy kod weryfikacyjny na Twój numer WhatsApp {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Właśnie wysłaliśmy {CodeLength}-cyfrowy kod weryfikacyjny na Twój numer WhatsApp {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Kod weryfikacyjny", "v2.page.settings-mfa-enter-totp.default.description": "Wprowadź 6-cyfrowy kod, który widzisz w urządzeniu/aplikacji uwierzytelniającej.", - "v2.page.settings-mfa-enter-totp.default.title": "Kod weryfikacyjny", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Zeskanuj ponownie kod QR", + "v2.page.settings-mfa-enter-totp.default.title": "Kod weryfikacyjny", "v2.page.settings-mfa-password.default.additional-password-added-at": "Dodane o {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Dodatkowe hasło", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Zaktualizowane o {time, datetime, short} UTC", diff --git a/resources/authgear/templates/pt-BR/translation.json b/resources/authgear/templates/pt-BR/translation.json index 782399adc8..24bec7f7ec 100644 --- a/resources/authgear/templates/pt-BR/translation.json +++ b/resources/authgear/templates/pt-BR/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nome de Usuário", "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-create-oob-otp.default.title": "Verificação de 2 Etapas", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Você pode receber códigos de verificação neste e-mail.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Você pode receber códigos de verificação neste número.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Você pode receber códigos de verificação neste número.", "v2.page.settings-mfa-create-password.default.title": "Senha adicional", "v2.page.settings-mfa-create-totp.default.description": "Primeiro, baixe o aplicativo Google Authenticator da Google Play Store ou Apple App Store.

Escaneie o código QR ou insira a chave com seu dispositivo/aplicativo autenticador.", - "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Acabamos de enviar um código de verificação de {CodeLength} dígitos para {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Para sua segurança, precisamos verificar se é você. Acabamos de enviar um código de verificação de {CodeLength} dígitos para {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Para sua segurança, precisamos verificar se é você. Acabamos de enviar um código de verificação de {CodeLength} dígitos para seu número do WhatsApp em {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Acabamos de enviar um código de verificação de {CodeLength} dígitos para seu número do WhatsApp em {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Código de Verificação", "v2.page.settings-mfa-enter-totp.default.description": "Digite o código de 6 dígitos que você vê no dispositivo/aplicativo autenticador.", - "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Ler novamente o código QR", + "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", "v2.page.settings-mfa-password.default.additional-password-added-at": "Adicionada em {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Senha adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Atualizada em {time, datetime, short} UTC", diff --git a/resources/authgear/templates/pt-PT/translation.json b/resources/authgear/templates/pt-PT/translation.json index 5fef5e4a28..42c7243552 100644 --- a/resources/authgear/templates/pt-PT/translation.json +++ b/resources/authgear/templates/pt-PT/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nome de Utilizador", "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-create-oob-otp.default.title": "Verificação de 2 Passos", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Pode receber códigos de verificação neste email.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Pode receber códigos de verificação neste número.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Pode receber códigos de verificação neste número.", "v2.page.settings-mfa-create-password.default.title": "Palavra-passe adicional", "v2.page.settings-mfa-create-totp.default.description": "Primeiro, descarregue a aplicação Google Authenticator da Google Play Store ou Apple App Store.

Digitalize o código QR ou introduza a chave com o seu dispositivo/aplicação de autenticação.", - "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Acabámos de enviar um código de verificação de {CodeLength} dígitos para {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Para sua segurança, precisamos verificar que é você. Acabámos de enviar um código de verificação de {CodeLength} dígitos para {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Para sua segurança, precisamos verificar que é você. Acabámos de enviar um código de verificação de {CodeLength} dígitos para o seu número WhatsApp em {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Acabámos de enviar um código de verificação de {CodeLength} dígitos para o seu número WhatsApp em {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Código de Verificação", "v2.page.settings-mfa-enter-totp.default.description": "Introduza o código de 6 dígitos que vê no dispositivo/aplicação de autenticação.", - "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Redigitalizar código QR", + "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", "v2.page.settings-mfa-password.default.additional-password-added-at": "Adicionada às {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Palavra-passe adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Atualizada às {time, datetime, short} UTC", diff --git a/resources/authgear/templates/pt/translation.json b/resources/authgear/templates/pt/translation.json index 7190b110c9..03eb38e931 100644 --- a/resources/authgear/templates/pt/translation.json +++ b/resources/authgear/templates/pt/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Nome de Usuário", "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-create-oob-otp.default.title": "Verificação de 2 Etapas", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Você pode receber códigos de verificação neste e-mail.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Você pode receber códigos de verificação neste número.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Você pode receber códigos de verificação neste número.", "v2.page.settings-mfa-create-password.default.title": "Senha adicional", "v2.page.settings-mfa-create-totp.default.description": "Primeiro, baixe o aplicativo Google Authenticator da Google Play Store ou Apple App Store.

Escaneie o código QR ou insira a chave com seu dispositivo/aplicativo autenticador.", - "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Configurar autenticador", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Acabamos de enviar um código de verificação de {CodeLength} dígitos para {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Para sua segurança, precisamos verificar se é você. Acabamos de enviar um código de verificação de {CodeLength} dígitos para {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Para sua segurança, precisamos verificar se é você. Acabamos de enviar um código de verificação de {CodeLength} dígitos para seu número do WhatsApp em {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Acabamos de enviar um código de verificação de {CodeLength} dígitos para seu número do WhatsApp em {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Código de Verificação", "v2.page.settings-mfa-enter-totp.default.description": "Digite o código de 6 dígitos que você vê no dispositivo/aplicativo autenticador.", - "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Ler novamente o código QR", + "v2.page.settings-mfa-enter-totp.default.title": "Código de Verificação", "v2.page.settings-mfa-password.default.additional-password-added-at": "Adicionado em {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Senha adicional", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Atualizado em {time, datetime, short} UTC", diff --git a/resources/authgear/templates/th/translation.json b/resources/authgear/templates/th/translation.json index f7640821d5..8731569167 100644 --- a/resources/authgear/templates/th/translation.json +++ b/resources/authgear/templates/th/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "ชื่อผู้ใช้", "v2.page.settings-identity.default.verification-status-unverified-label": "ยังไม่ได้รับการยืนยัน", "v2.page.settings-identity.default.verification-status-verified-label": "ได้รับการยืนยันแล้ว", + "v2.page.settings-mfa-create-oob-otp.default.title": "การยืนยันตัวตนสองขั้นตอน", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "คุณสามารถรับรหัสยืนยันที่อีเมลนี้", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "คุณสามารถรับรหัสยืนยันที่หมายเลขนี้", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "คุณสามารถรับรหัสยืนยันที่หมายเลขนี้", "v2.page.settings-mfa-create-password.default.title": "รหัสผ่านเพิ่มเติม", "v2.page.settings-mfa-create-totp.default.description": "เริ่มแรก ดาวน์โหลดแอป Google Authenticator จาก Google Play Store หรือ Apple App Store.

สแกนรหัส QR หรือป้อนคีย์ด้วยอุปกรณ์/แอปรับรองความถูกต้อง", - "v2.page.settings-mfa-create-totp.default.title": "ตั้งค่าตัวรับรองความถูกต้อง", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "ตั้งค่าตัวรับรองความถูกต้อง", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "เราเพิ่งส่งรหัสยืนยัน {CodeLength} หลักไปที่ {MaskedClaimValue}", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "เพื่อความปลอดภัยของคุณ เราต้องยืนยันว่าเป็นคุณ เราเพิ่งส่งรหัสยืนยัน {CodeLength} หลักไปที่ {MaskedClaimValue}", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "เพื่อความปลอดภัยของคุณ เราต้องยืนยันว่าเป็นคุณ เราเพิ่งส่งรหัสยืนยัน {CodeLength} หลักไปที่หมายเลขWhatsApp ของคุณที่ {MaskedClaimValue}", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "เราเพิ่งส่งรหัสยืนยัน {CodeLength} หลักไปที่หมายเลขWhatsApp ของคุณที่ {MaskedClaimValue}", + "v2.page.settings-mfa-enter-oob-otp.default.title": "รหัสยืนยัน", "v2.page.settings-mfa-enter-totp.default.description": "ป้อนรหัส 6 หลักที่คุณเห็นในอุปกรณ์/แอปรับรองความถูกต้อง", - "v2.page.settings-mfa-enter-totp.default.title": "รหัสยืนยัน", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "สแกนรหัส QR ใหม่", + "v2.page.settings-mfa-enter-totp.default.title": "รหัสยืนยัน", "v2.page.settings-mfa-password.default.additional-password-added-at": "เพิ่มเมื่อ {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "รหัสผ่านเพิ่มเติม", "v2.page.settings-mfa-password.default.additional-password-updated-at": "อัปเดตเมื่อ {time, datetime, short} UTC", diff --git a/resources/authgear/templates/vi/translation.json b/resources/authgear/templates/vi/translation.json index cc53c220ff..cf560fd138 100644 --- a/resources/authgear/templates/vi/translation.json +++ b/resources/authgear/templates/vi/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "Tên người dùng", "v2.page.settings-identity.default.verification-status-unverified-label": "Chưa được xác minh", "v2.page.settings-identity.default.verification-status-verified-label": "Đã xác minh", + "v2.page.settings-mfa-create-oob-otp.default.title": "Xác minh 2 bước", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Bạn có thể nhận mã xác minh tại email này.", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Bạn có thể nhận mã xác minh tại số này.", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "Bạn có thể nhận mã xác minh tại số này.", "v2.page.settings-mfa-create-password.default.title": "Mật khẩu bổ sung", "v2.page.settings-mfa-create-totp.default.description": "Đầu tiên, tải ứng dụng Google Authenticator từ Google Play Store hoặc Apple App Store.

Quét mã QR hoặc nhập khóa với thiết bị/ứng dụng xác thực của bạn.", - "v2.page.settings-mfa-create-totp.default.title": "Thiết lập xác thực", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "Thiết lập xác thực", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "Chúng tôi vừa gửi một mã xác minh {CodeLength} chữ số đến {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "Để đảm bảo an toàn, chúng tôi cần xác minh đó là bạn. Chúng tôi vừa gửi một mã xác minh {CodeLength} chữ số đến {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "Để đảm bảo an toàn, chúng tôi cần xác minh đó là bạn. Chúng tôi vừa gửi một mã xác minh {CodeLength} chữ số đến số WhatsApp của bạn tại {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "Chúng tôi vừa gửi một mã xác minh {CodeLength} chữ số đến số WhatsApp của bạn tại {MaskedClaimValue}.", + "v2.page.settings-mfa-enter-oob-otp.default.title": "Mã xác minh", "v2.page.settings-mfa-enter-totp.default.description": "Nhập mã 6 chữ số bạn thấy trong thiết bị/ứng dụng xác thực.", - "v2.page.settings-mfa-enter-totp.default.title": "Mã xác minh", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "Quét lại mã QR", + "v2.page.settings-mfa-enter-totp.default.title": "Mã xác minh", "v2.page.settings-mfa-password.default.additional-password-added-at": "Đã thêm vào {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "Mật khẩu bổ sung", "v2.page.settings-mfa-password.default.additional-password-updated-at": "Đã cập nhật vào {time, datetime, short} UTC", diff --git a/resources/authgear/templates/zh-CN/translation.json b/resources/authgear/templates/zh-CN/translation.json index 7d33742cf3..2d60ea07dd 100644 --- a/resources/authgear/templates/zh-CN/translation.json +++ b/resources/authgear/templates/zh-CN/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "用户名", "v2.page.settings-identity.default.verification-status-unverified-label": "未验证", "v2.page.settings-identity.default.verification-status-verified-label": "已验证", + "v2.page.settings-mfa-create-oob-otp.default.title": "双重验证", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "您可以在此电子邮件地址接收验证码。", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "您可以在此号码接收验证码。", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "您可以在此号码接收验证码。", "v2.page.settings-mfa-create-password.default.title": "额外密码", "v2.page.settings-mfa-create-totp.default.description": "首先,从Google Play商店Apple App Store下载Google Authenticator App。

使用您的身份验证器设备/应用程序扫描二维码或输入密钥。", - "v2.page.settings-mfa-create-totp.default.title": "设置身份验证器", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "设置身份验证器", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "我们刚刚向{MaskedClaimValue}发送了一个{CodeLength}位数的验证码。", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "为了您的安全,我们需要验证是否是您本人。我们刚刚向{MaskedClaimValue}发送了一个{CodeLength}位数的验证码。", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "为了您的安全,我们需要验证是否是您本人。我们刚刚向您的WhatsApp号码{MaskedClaimValue}发送了一个{CodeLength}位数的验证码。", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "我们刚刚向您的WhatsApp号码{MaskedClaimValue}发送了一个{CodeLength}位数的验证码。", + "v2.page.settings-mfa-enter-oob-otp.default.title": "验证码", "v2.page.settings-mfa-enter-totp.default.description": "输入您在身份验证器设备/应用程序中看到的6位数字代码。", - "v2.page.settings-mfa-enter-totp.default.title": "验证码", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "重新扫描二维码", + "v2.page.settings-mfa-enter-totp.default.title": "验证码", "v2.page.settings-mfa-password.default.additional-password-added-at": "添加于 {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "额外密码", "v2.page.settings-mfa-password.default.additional-password-updated-at": "更新于 {time, datetime, short} UTC", diff --git a/resources/authgear/templates/zh-HK/translation.json b/resources/authgear/templates/zh-HK/translation.json index 9d406d7274..e188a60334 100644 --- a/resources/authgear/templates/zh-HK/translation.json +++ b/resources/authgear/templates/zh-HK/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "用戶名稱", "v2.page.settings-identity.default.verification-status-unverified-label": "未驗證", "v2.page.settings-identity.default.verification-status-verified-label": "已驗證", + "v2.page.settings-mfa-create-oob-otp.default.title": "雙重驗證", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "您可以在此電子郵件地址接收驗證碼。", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "您可以在此號碼接收驗證碼。", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "您可以在此號碼接收驗證碼。", "v2.page.settings-mfa-create-password.default.title": "輔助密碼", "v2.page.settings-mfa-create-totp.default.description": "首先,從Google Play 商店Apple App Store下載Google Authenticator App。

使用您的驗證器設備/應用程式掃描QR碼或輸入密鑰。", - "v2.page.settings-mfa-create-totp.default.title": "設置驗證器", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "設置驗證器", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "我們剛剛向 {MaskedClaimValue} 發送了一個 {CodeLength} 位數的驗證碼。", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "為了您的安全,我們需要驗證是否為您本人。我們剛剛向 {MaskedClaimValue} 發送了一個 {CodeLength} 位數的驗證碼。", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "為了您的安全,我們需要驗證是否為您本人。我們剛剛向您的 WhatsApp 號碼 {MaskedClaimValue} 發送了一個 {CodeLength} 位數的驗證碼。", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "我們剛剛向您的 WhatsApp 號碼 {MaskedClaimValue} 發送了一個 {CodeLength} 位數的驗證碼。", + "v2.page.settings-mfa-enter-oob-otp.default.title": "驗證碼", "v2.page.settings-mfa-enter-totp.default.description": "輸入您在驗證器設備/應用程式中看到的6位數字代碼。", - "v2.page.settings-mfa-enter-totp.default.title": "驗證碼", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "重新掃描QR碼", + "v2.page.settings-mfa-enter-totp.default.title": "驗證碼", "v2.page.settings-mfa-password.default.additional-password-added-at": "新增時間: {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "輔助密碼", "v2.page.settings-mfa-password.default.additional-password-updated-at": "上次更新時間: {time, datetime, short} UTC", diff --git a/resources/authgear/templates/zh-TW/translation.json b/resources/authgear/templates/zh-TW/translation.json index 4c3219ddd3..3756031274 100644 --- a/resources/authgear/templates/zh-TW/translation.json +++ b/resources/authgear/templates/zh-TW/translation.json @@ -1180,13 +1180,22 @@ "v2.page.settings-identity-view-username.default.title": "用戶名稱", "v2.page.settings-identity.default.verification-status-unverified-label": "未驗證", "v2.page.settings-identity.default.verification-status-verified-label": "已驗證", + "v2.page.settings-mfa-create-oob-otp.default.title": "雙重驗證", + "v2.page.settings-mfa-create-oob-otp.email.subtitle": "您可以在此電子郵件地址接收驗證碼。", + "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "您可以在此號碼接收驗證碼。", + "v2.page.settings-mfa-create-oob-otp.whatsapp.subtitle": "您可以在此號碼接收驗證碼。", "v2.page.settings-mfa-create-password.default.title": "輔助密碼", "v2.page.settings-mfa-create-totp.default.description": "首先,從Google Play 商店Apple App Store下載 Google Authenticator App。

使用您的驗證器設備/應用程式掃描 QR 碼或輸入密鑰。", - "v2.page.settings-mfa-create-totp.default.title": "設置驗證器", "v2.page.settings-mfa-create-totp.default.raw-secret": "{secret}", + "v2.page.settings-mfa-create-totp.default.title": "設置驗證器", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle": "我們剛剛向 {MaskedClaimValue} 發送了一個 {CodeLength} 位數的驗證碼。", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth": "為了您的安全,我們需要驗證是否為您本人。我們剛剛向 {MaskedClaimValue} 發送了一個 {CodeLength} 位數的驗證碼。", + "v2.page.settings-mfa-enter-oob-otp.auth-email-or-sms.subtitle-reauth--whatsapp": "為了您的安全,我們需要驗證是否為您本人。我們剛剛向您的 WhatsApp 號碼 {MaskedClaimValue} 發送了一個 {CodeLength} 位數的驗證碼。", + "v2.page.settings-mfa-enter-oob-otp.auth-whatsapp.subtitle": "我們剛剛向您的 WhatsApp 號碼 {MaskedClaimValue} 發送了一個 {CodeLength} 位數的驗證碼。", + "v2.page.settings-mfa-enter-oob-otp.default.title": "驗證碼", "v2.page.settings-mfa-enter-totp.default.description": "輸入您在驗證器設備/應用程式中看到的 6 位數字代碼。", - "v2.page.settings-mfa-enter-totp.default.title": "驗證碼", "v2.page.settings-mfa-enter-totp.default.rescan-button-label": "重新掃描 QR 碼", + "v2.page.settings-mfa-enter-totp.default.title": "驗證碼", "v2.page.settings-mfa-password.default.additional-password-added-at": "新增時間: {time, datetime, short} UTC", "v2.page.settings-mfa-password.default.additional-password-label": "輔助密碼", "v2.page.settings-mfa-password.default.additional-password-updated-at": "上次更新時間: {time, datetime, short} UTC", From 26aab57a2cbffb3067a925626174dd8bcb03f6e6 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 16:30:10 +0800 Subject: [PATCH 18/31] Add remove totp in account management --- pkg/lib/accountmanagement/errors.go | 3 +- pkg/lib/accountmanagement/service.go | 2 + .../service_authenticator.go | 46 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/pkg/lib/accountmanagement/errors.go b/pkg/lib/accountmanagement/errors.go index 3e12835a40..af3a2f7cdf 100644 --- a/pkg/lib/accountmanagement/errors.go +++ b/pkg/lib/accountmanagement/errors.go @@ -12,5 +12,6 @@ var ErrAccountManagementTokenInvalid = apierrors.Invalid.WithReason("AccountMana var ErrAccountManagementTokenNotBoundToUser = apierrors.Invalid.WithReason("AccountManagementTokenNotBoundToUser").New("token is not bound to the current user") var ErrAccountManagementIdentityNotOwnedbyToUser = apierrors.Invalid.WithReason("AccountManagementIdentityNotOwnedByUser").New("identity not owned by current user") - var ErrAccountManagementDuplicatedIdentity = apierrors.AlreadyExists.WithReason("AccountManagementDuplicatedIdentity").New("identity already exists") + +var ErrAccountManagementAuthenticatorNotOwnedbyToUser = apierrors.Invalid.WithReason("AccountManagementAuthenticatorNotOwnedByUser").New("authenticator not owned by current user") diff --git a/pkg/lib/accountmanagement/service.go b/pkg/lib/accountmanagement/service.go index 68f52585ed..6b0801616e 100644 --- a/pkg/lib/accountmanagement/service.go +++ b/pkg/lib/accountmanagement/service.go @@ -64,10 +64,12 @@ type EventService interface { type AuthenticatorService interface { New(spec *authenticator.Spec) (*authenticator.Info, error) NewWithAuthenticatorID(authenticatorID string, spec *authenticator.Spec) (*authenticator.Info, error) + Get(authenticatorID string) (*authenticator.Info, error) List(userID string, filters ...authenticator.Filter) ([]*authenticator.Info, error) Create(authenticatorInfo *authenticator.Info, markVerified bool) error Update(authenticatorInfo *authenticator.Info) error UpdatePassword(authenticatorInfo *authenticator.Info, options *service.UpdatePasswordOptions) (changed bool, info *authenticator.Info, err error) + Delete(authenticatorInfo *authenticator.Info) error VerifyWithSpec(info *authenticator.Info, spec *authenticator.Spec, options *facade.VerifyOptions) (verifyResult *service.VerifyResult, err error) } diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 3a97dc981b..a131b5c28b 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -301,6 +301,52 @@ func (s *Service) FinishAddTOTPAuthenticator(resolvedSession session.ResolvedSes return } +type DeleteTOTPAuthenticatorInput struct { + AuthenticatorID string +} + +type DeleteTOTPAuthenticatorOutput struct { + Info *authenticator.Info +} + +func (s *Service) DeleteTOTPAuthenticator(resolvedSession session.ResolvedSession, input *DeleteTOTPAuthenticatorInput) (output *DeleteTOTPAuthenticatorOutput, err error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + authenticatorID := input.AuthenticatorID + + var info *authenticator.Info + err = s.Database.WithTx(func() error { + info, err = s.prepareDeleteAuthenticator(userID, authenticatorID) + if err != nil { + return err + } + + err = s.Authenticators.Delete(info) + if err != nil { + return err + } + + return nil + }) + + output = &DeleteTOTPAuthenticatorOutput{ + Info: info, + } + return +} + +func (s *Service) prepareDeleteAuthenticator(userID string, authenticatorID string) (*authenticator.Info, error) { + info, err := s.Authenticators.Get(authenticatorID) + if err != nil { + return nil, err + } + + if info.UserID != userID { + return nil, ErrAccountManagementAuthenticatorNotOwnedbyToUser + } + + return info, nil +} + type changePasswordInput struct { Kind authenticator.Kind OldPassword string From 2aa5ca293f39141a6cdb78b40a0b8b3e63b08c5f Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 16:30:36 +0800 Subject: [PATCH 19/31] Add remove totp dialog for settings v2 --- .../webapp/authflowv2/settings_mfa_totp.go | 21 +++++++++++ .../authgear/templates/en/translation.json | 2 + .../en/web/authflowv2/settings_mfa_totp.html | 37 ++++++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_totp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_totp.go index 47a16519ab..d97c739b19 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_mfa_totp.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_totp.go @@ -13,6 +13,7 @@ import ( authenticatorservice "github.com/authgear/authgear-server/pkg/lib/authn/authenticator/service" "github.com/authgear/authgear-server/pkg/lib/infra/db/appdb" "github.com/authgear/authgear-server/pkg/lib/session" + "github.com/authgear/authgear-server/pkg/util/httputil" "github.com/authgear/authgear-server/pkg/util/template" ) @@ -108,4 +109,24 @@ func (h *AuthflowV2SettingsTOTPHandler) ServeHTTP(w http.ResponseWriter, r *http return nil }) + + ctrl.PostAction("remove", func() error { + authenticatorID := r.Form.Get("x_authenticator_id") + + s := session.GetSession(r.Context()) + + input := &accountmanagement.DeleteTOTPAuthenticatorInput{ + AuthenticatorID: authenticatorID, + } + _, err = h.AccountManagement.DeleteTOTPAuthenticator(s, input) + if err != nil { + return err + } + + redirectURI := httputil.HostRelative(r.URL).String() + result := webapp.Result{RedirectURI: redirectURI} + result.WriteResponse(w, r) + + return nil + }) } diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index 9f7eb1ee0c..2ba8976990 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Signed in Devices", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Add authenticator app/device", "v2.page.settings-totp.default.added-at-label": "Added at {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Are you sure you want to remove authenticator app/device?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Remove authenticator app/device", "v2.page.settings-totp.default.title": "Authenticator app/device", "v2.page.settings.default.account-deletion-button-label": "Delete Account", "v2.page.settings.default.advanced-settings-button-label": "Advanced Settings", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html index 0a5140eafd..3b5bade450 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html @@ -13,15 +13,38 @@
{{ if $.TOTPAuthenticators }}
    + {{ $ctx := .}} {{ range $.TOTPAuthenticators }}
  • {{ template "authflowv2/__settings_action_item.html" (dict "Label" .DisplayName "Description" (include "__settings_totp_item_added_at.html" .) - "ActionButton" (include "__settings_totp_item_delete_button.html" nil) + "ActionButton" (include "__settings_totp_item_delete_button.html" (dict "DialogID" .ID)) ) }} + + {{ template "authflowv2/__settings_dialog.html" + (dict + "Ctx" $ctx + "DialogID" .ID + "Title" (include "v2.page.settings-totp.default.remove-totp-dialog-title" nil) + "Description" (include "v2.page.settings-totp.default.remove-totp-dialog-description" nil) + "FormContent" (include "__settings_totp_dialog_remove_input.html" (dict "AuthenticatorID" .ID "CSRFField" $.CSRFField)) + "Buttons" + (list + (dict + "Type" "Destructive" + "Label" (include "v2.component.button.default.label-remove" nil) + "Value" "remove" + "Event" "authgear.button.remove_biometric" + ) + (dict + "Type" "Cancel" + "Label" (include "v2.component.button.default.label-cancel" nil) + ) + ) + )}}
  • {{ end }}
@@ -54,7 +77,17 @@ {{ end }} {{ define "__settings_totp_item_delete_button.html" }} - {{ end }} + +{{ define "__settings_totp_dialog_remove_input.html" }} + {{ $.CSRFField }} + +{{ end }} From 36f19d5b29144062f7b58960de47f5153f9afd6e Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 16:31:18 +0800 Subject: [PATCH 20/31] Generate translations --- resources/authgear/templates/de/translation.json | 2 ++ resources/authgear/templates/el/translation.json | 4 +++- resources/authgear/templates/es-419/translation.json | 2 ++ resources/authgear/templates/es-ES/translation.json | 2 ++ resources/authgear/templates/es/translation.json | 2 ++ resources/authgear/templates/fil/translation.json | 4 +++- resources/authgear/templates/fr/translation.json | 2 ++ resources/authgear/templates/id/translation.json | 2 ++ resources/authgear/templates/it/translation.json | 2 ++ resources/authgear/templates/ja/translation.json | 4 +++- resources/authgear/templates/ko/translation.json | 4 +++- resources/authgear/templates/ms/translation.json | 2 ++ resources/authgear/templates/nl/translation.json | 4 +++- resources/authgear/templates/pl/translation.json | 2 ++ resources/authgear/templates/pt-BR/translation.json | 2 ++ resources/authgear/templates/pt-PT/translation.json | 2 ++ resources/authgear/templates/pt/translation.json | 2 ++ resources/authgear/templates/th/translation.json | 2 ++ resources/authgear/templates/vi/translation.json | 2 ++ resources/authgear/templates/zh-CN/translation.json | 2 ++ resources/authgear/templates/zh-HK/translation.json | 4 +++- resources/authgear/templates/zh-TW/translation.json | 4 +++- 22 files changed, 51 insertions(+), 7 deletions(-) diff --git a/resources/authgear/templates/de/translation.json b/resources/authgear/templates/de/translation.json index 2772fe33fc..d73b7c49f4 100644 --- a/resources/authgear/templates/de/translation.json +++ b/resources/authgear/templates/de/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Angemeldete Geräte", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Authenticator-App/Gerät hinzufügen", "v2.page.settings-totp.default.added-at-label": "Hinzugefügt um {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Sind Sie sicher, dass Sie die Authenticator-App/das Gerät entfernen möchten?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Authenticator-App/Gerät entfernen", "v2.page.settings-totp.default.title": "Authenticator-App/Gerät", "v2.page.settings.default.account-deletion-button-label": "Konto löschen", "v2.page.settings.default.advanced-settings-button-label": "Erweiterte Einstellungen", diff --git a/resources/authgear/templates/el/translation.json b/resources/authgear/templates/el/translation.json index 19ffa68c90..7895be68de 100644 --- a/resources/authgear/templates/el/translation.json +++ b/resources/authgear/templates/el/translation.json @@ -1224,7 +1224,7 @@ "v2.page.settings-oob-otp.default.oob-otp-sms-button-label": "+ Προσθήκη αριθμού τηλεφώνου WhatsApp/SMS", "v2.page.settings-passkey.default.add-passkey-button-label": "+ Προσθήκη Passkey", "v2.page.settings-passkey.default.item-description": "Προστέθηκε στις {time, datetime, short} UTC", - "v2.page.settings-passkey.default.remove-passkey-dialog-description": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε αυτό το κλειδί πρόσβασης;", + "v2.page.settings-passkey.default.remove-passkey-dialog-description": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε το Passkey;", "v2.page.settings-passkey.default.remove-passkey-dialog-title": "Αφαίρεση Κλειδιού Πρόσβασης", "v2.page.settings-passkey.default.title": "Κλειδί Πρόσβασης", "v2.page.settings-profile-edit-address.default.country-label": "Χώρα", @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Συνδεδεμένες Συσκευές", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Προσθήκη εφαρμογής/συσκευής ελέγχου ταυτότητας", "v2.page.settings-totp.default.added-at-label": "Προστέθηκε στις {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε την εφαρμογή/συσκευή αυθεντικοποίησης;", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Αφαίρεση εφαρμογής/συσκευής αυθεντικοποίησης", "v2.page.settings-totp.default.title": "Εφαρμογή/συσκευή ελέγχου ταυτότητας", "v2.page.settings.default.account-deletion-button-label": "Διαγραφή Λογαριασμού", "v2.page.settings.default.advanced-settings-button-label": "Προχωρημένες Ρυθμίσεις", diff --git a/resources/authgear/templates/es-419/translation.json b/resources/authgear/templates/es-419/translation.json index 69531a435b..a5e32de319 100644 --- a/resources/authgear/templates/es-419/translation.json +++ b/resources/authgear/templates/es-419/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Dispositivos con sesión iniciada", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Agregar aplicación/dispositivo de autenticación", "v2.page.settings-totp.default.added-at-label": "Agregado a las {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "¿Estás seguro de que quieres eliminar la aplicación/dispositivo de autenticación?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Eliminar aplicación/dispositivo de autenticación", "v2.page.settings-totp.default.title": "Aplicación/dispositivo de autenticación", "v2.page.settings.default.account-deletion-button-label": "Eliminar cuenta", "v2.page.settings.default.advanced-settings-button-label": "Configuración avanzada", diff --git a/resources/authgear/templates/es-ES/translation.json b/resources/authgear/templates/es-ES/translation.json index b894ab8536..b1ea7e7678 100644 --- a/resources/authgear/templates/es-ES/translation.json +++ b/resources/authgear/templates/es-ES/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Dispositivos con sesión iniciada", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Añadir aplicación/dispositivo de autenticación", "v2.page.settings-totp.default.added-at-label": "Añadido a las {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "¿Estás seguro de que quieres eliminar la aplicación/dispositivo de autenticación?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Eliminar aplicación/dispositivo de autenticación", "v2.page.settings-totp.default.title": "Aplicación/dispositivo de autenticación", "v2.page.settings.default.account-deletion-button-label": "Eliminar cuenta", "v2.page.settings.default.advanced-settings-button-label": "Configuración avanzada", diff --git a/resources/authgear/templates/es/translation.json b/resources/authgear/templates/es/translation.json index 9e07a5d956..2e0caa4845 100644 --- a/resources/authgear/templates/es/translation.json +++ b/resources/authgear/templates/es/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Dispositivos con sesión iniciada", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Añadir aplicación/dispositivo de autenticación", "v2.page.settings-totp.default.added-at-label": "Añadido a las {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "¿Estás seguro de que quieres eliminar la aplicación/dispositivo de autenticación?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Eliminar aplicación/dispositivo de autenticación", "v2.page.settings-totp.default.title": "Aplicación/dispositivo de autenticación", "v2.page.settings.default.account-deletion-button-label": "Eliminar cuenta", "v2.page.settings.default.advanced-settings-button-label": "Configuración avanzada", diff --git a/resources/authgear/templates/fil/translation.json b/resources/authgear/templates/fil/translation.json index e32c8066c6..af09a4710d 100644 --- a/resources/authgear/templates/fil/translation.json +++ b/resources/authgear/templates/fil/translation.json @@ -1224,7 +1224,7 @@ "v2.page.settings-oob-otp.default.oob-otp-sms-button-label": "+ Magdagdag ng WhatsApp/Numero ng telepono para sa SMS", "v2.page.settings-passkey.default.add-passkey-button-label": "+ Idagdag ang Passkey", "v2.page.settings-passkey.default.item-description": "Idinagdag sa {time, datetime, short} UTC", - "v2.page.settings-passkey.default.remove-passkey-dialog-description": "Sigurado ka bang gusto mong alisin ang passkey na ito?", + "v2.page.settings-passkey.default.remove-passkey-dialog-description": "Sigurado ka bang gusto mong alisin ang Passkey?", "v2.page.settings-passkey.default.remove-passkey-dialog-title": "Alisin ang Passkey", "v2.page.settings-passkey.default.title": "Passkey", "v2.page.settings-profile-edit-address.default.country-label": "Bansa", @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Naka-sign in na mga Device", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Magdagdag ng authenticator app/device", "v2.page.settings-totp.default.added-at-label": "Naidagdag sa {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Sigurado ka bang gusto mong alisin ang authenticator app/device?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Alisin ang authenticator app/device", "v2.page.settings-totp.default.title": "Authenticator app/device", "v2.page.settings.default.account-deletion-button-label": "I-delete ang Account", "v2.page.settings.default.advanced-settings-button-label": "Mga Advanced na Setting", diff --git a/resources/authgear/templates/fr/translation.json b/resources/authgear/templates/fr/translation.json index 9b547ef9ce..f182fa5232 100644 --- a/resources/authgear/templates/fr/translation.json +++ b/resources/authgear/templates/fr/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Appareils connectés", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Ajouter une application/un appareil d''authentification", "v2.page.settings-totp.default.added-at-label": "Ajouté à {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Êtes-vous sûr de vouloir supprimer l''application/appareil d''authentification ?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Supprimer l''application/appareil d''authentification", "v2.page.settings-totp.default.title": "Application/appareil d''authentification", "v2.page.settings.default.account-deletion-button-label": "Supprimer le compte", "v2.page.settings.default.advanced-settings-button-label": "Paramètres avancés", diff --git a/resources/authgear/templates/id/translation.json b/resources/authgear/templates/id/translation.json index 8448c56b6b..6230285d0f 100644 --- a/resources/authgear/templates/id/translation.json +++ b/resources/authgear/templates/id/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Perangkat yang Masuk", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Tambahkan aplikasi/perangkat autentikator", "v2.page.settings-totp.default.added-at-label": "Ditambahkan pada {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Apakah Anda yakin ingin menghapus aplikasi/perangkat pengautentikasi?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Hapus aplikasi/perangkat pengautentikasi", "v2.page.settings-totp.default.title": "Aplikasi/perangkat autentikator", "v2.page.settings.default.account-deletion-button-label": "Hapus Akun", "v2.page.settings.default.advanced-settings-button-label": "Pengaturan Lanjutan", diff --git a/resources/authgear/templates/it/translation.json b/resources/authgear/templates/it/translation.json index e1f830b079..1ab6aba330 100644 --- a/resources/authgear/templates/it/translation.json +++ b/resources/authgear/templates/it/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Dispositivi Connessi", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Aggiungi app/dispositivo di autenticazione", "v2.page.settings-totp.default.added-at-label": "Aggiunto alle {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Sei sicuro di voler rimuovere l''app/dispositivo di autenticazione?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Rimuovi app/dispositivo di autenticazione", "v2.page.settings-totp.default.title": "App/dispositivo di autenticazione", "v2.page.settings.default.account-deletion-button-label": "Elimina Account", "v2.page.settings.default.advanced-settings-button-label": "Impostazioni avanzate", diff --git a/resources/authgear/templates/ja/translation.json b/resources/authgear/templates/ja/translation.json index 19fe9d9028..270cc5994f 100644 --- a/resources/authgear/templates/ja/translation.json +++ b/resources/authgear/templates/ja/translation.json @@ -110,7 +110,7 @@ "copied-button-label": "コピーしました", "get-new-recovery-codes-button-label": "新しいコードを取得", "download-button-label": "ダウンロード", - "disconnect-button-label": "切断", + "disconnect-button-label": "切断する", "add-button-label": "追加", "remove-button-label": "削除", "save-button-label": "保存", @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "サインインしたデバイス", "v2.page.settings-totp.default.add-authenticator-button-label": "+ 認証アプリ/デバイスを追加", "v2.page.settings-totp.default.added-at-label": "追加日時 {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "本当に認証アプリ/デバイスを削除しますか?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "認証アプリ/デバイスを削除する", "v2.page.settings-totp.default.title": "認証アプリ/デバイス", "v2.page.settings.default.account-deletion-button-label": "アカウントを削除", "v2.page.settings.default.advanced-settings-button-label": "高度な設定", diff --git a/resources/authgear/templates/ko/translation.json b/resources/authgear/templates/ko/translation.json index 0d981bb8d6..34e9c56a2e 100644 --- a/resources/authgear/templates/ko/translation.json +++ b/resources/authgear/templates/ko/translation.json @@ -110,7 +110,7 @@ "copied-button-label": "복사됨", "get-new-recovery-codes-button-label": "새 코드 받기", "download-button-label": "다운로드", - "disconnect-button-label": "연결 끊기", + "disconnect-button-label": "연결 해제", "add-button-label": "추가", "remove-button-label": "제거", "save-button-label": "저장", @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "로그인한 기기", "v2.page.settings-totp.default.add-authenticator-button-label": "+ 인증 앱/기기 추가", "v2.page.settings-totp.default.added-at-label": "{time, datetime, short} UTC에 추가됨", + "v2.page.settings-totp.default.remove-totp-dialog-description": "인증 앱/기기를 제거하시겠습니까?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "인증 앱/기기 제거", "v2.page.settings-totp.default.title": "인증 앱/기기", "v2.page.settings.default.account-deletion-button-label": "계정 삭제", "v2.page.settings.default.advanced-settings-button-label": "고급 설정", diff --git a/resources/authgear/templates/ms/translation.json b/resources/authgear/templates/ms/translation.json index 23493aed24..c4b75b4a98 100644 --- a/resources/authgear/templates/ms/translation.json +++ b/resources/authgear/templates/ms/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Peranti Dilog Masuk", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Tambah aplikasi/peranti pengesah", "v2.page.settings-totp.default.added-at-label": "Ditambah pada {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Adakah anda pasti untuk mengalih keluar aplikasi/peranti pengesah?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Alih keluar aplikasi/peranti pengesah", "v2.page.settings-totp.default.title": "Aplikasi/peranti pengesah", "v2.page.settings.default.account-deletion-button-label": "Padam Akaun", "v2.page.settings.default.advanced-settings-button-label": "Tetapan Lanjutan", diff --git a/resources/authgear/templates/nl/translation.json b/resources/authgear/templates/nl/translation.json index e661b19b65..9319104264 100644 --- a/resources/authgear/templates/nl/translation.json +++ b/resources/authgear/templates/nl/translation.json @@ -110,7 +110,7 @@ "copied-button-label": "Gekopieerd", "get-new-recovery-codes-button-label": "Nieuwe codes ophalen", "download-button-label": "Downloaden", - "disconnect-button-label": "Loskoppelen", + "disconnect-button-label": "Verwijderen", "add-button-label": "Toevoegen", "remove-button-label": "Verwijderen", "save-button-label": "Opslaan", @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Aangemelde apparaten", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Authenticator-app/apparaat toevoegen", "v2.page.settings-totp.default.added-at-label": "Toegevoegd op {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Weet je zeker dat je de authenticator-app/-apparaat wilt verwijderen?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Authenticator-app/-apparaat verwijderen", "v2.page.settings-totp.default.title": "Authenticator-app/apparaat", "v2.page.settings.default.account-deletion-button-label": "Account verwijderen", "v2.page.settings.default.advanced-settings-button-label": "Geavanceerde Instellingen", diff --git a/resources/authgear/templates/pl/translation.json b/resources/authgear/templates/pl/translation.json index 692bbbb20f..6b01f02824 100644 --- a/resources/authgear/templates/pl/translation.json +++ b/resources/authgear/templates/pl/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Zalogowane urządzenia", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Dodaj aplikację/urządzenie uwierzytelniające", "v2.page.settings-totp.default.added-at-label": "Dodano o {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Czy na pewno chcesz usunąć aplikację/urządzenie uwierzytelniające?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Usuń aplikację/urządzenie uwierzytelniające", "v2.page.settings-totp.default.title": "Aplikacja/urządzenie uwierzytelniające", "v2.page.settings.default.account-deletion-button-label": "Usuń konto", "v2.page.settings.default.advanced-settings-button-label": "Zaawansowane ustawienia", diff --git a/resources/authgear/templates/pt-BR/translation.json b/resources/authgear/templates/pt-BR/translation.json index 24bec7f7ec..8118c4e16a 100644 --- a/resources/authgear/templates/pt-BR/translation.json +++ b/resources/authgear/templates/pt-BR/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Dispositivos Conectados", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Adicionar app/dispositivo autenticador", "v2.page.settings-totp.default.added-at-label": "Adicionado em {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Tem certeza de que deseja remover o aplicativo/dispositivo autenticador?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Remover aplicativo/dispositivo autenticador", "v2.page.settings-totp.default.title": "App/dispositivo autenticador", "v2.page.settings.default.account-deletion-button-label": "Excluir Conta", "v2.page.settings.default.advanced-settings-button-label": "Configurações Avançadas", diff --git a/resources/authgear/templates/pt-PT/translation.json b/resources/authgear/templates/pt-PT/translation.json index 42c7243552..1797053874 100644 --- a/resources/authgear/templates/pt-PT/translation.json +++ b/resources/authgear/templates/pt-PT/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Dispositivos com Sessão Iniciada", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Adicionar aplicação/dispositivo de autenticação", "v2.page.settings-totp.default.added-at-label": "Adicionado em {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Tem a certeza de que deseja remover a aplicação/dispositivo de autenticação?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Remover aplicação/dispositivo de autenticação", "v2.page.settings-totp.default.title": "Aplicação/dispositivo de autenticação", "v2.page.settings.default.account-deletion-button-label": "Eliminar Conta", "v2.page.settings.default.advanced-settings-button-label": "Definições Avançadas", diff --git a/resources/authgear/templates/pt/translation.json b/resources/authgear/templates/pt/translation.json index 03eb38e931..50a3803195 100644 --- a/resources/authgear/templates/pt/translation.json +++ b/resources/authgear/templates/pt/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Dispositivos com Sessão Iniciada", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Adicionar aplicativo/dispositivo autenticador", "v2.page.settings-totp.default.added-at-label": "Adicionado em {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Tem certeza de que deseja remover o aplicativo/dispositivo autenticador?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Remover aplicativo/dispositivo autenticador", "v2.page.settings-totp.default.title": "Aplicativo/dispositivo autenticador", "v2.page.settings.default.account-deletion-button-label": "Excluir Conta", "v2.page.settings.default.advanced-settings-button-label": "Configurações Avançadas", diff --git a/resources/authgear/templates/th/translation.json b/resources/authgear/templates/th/translation.json index 8731569167..4bae9c1016 100644 --- a/resources/authgear/templates/th/translation.json +++ b/resources/authgear/templates/th/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "อุปกรณ์ที่เข้าสู่ระบบ", "v2.page.settings-totp.default.add-authenticator-button-label": "+ เพิ่มแอปพลิเคชันหรืออุปกรณ์ตรวจสอบสิทธิ์", "v2.page.settings-totp.default.added-at-label": "เพิ่มเมื่อ {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "คุณแน่ใจหรือไม่ว่าต้องการลบแอปพลิเคชันตรวจสอบสิทธิ์/อุปกรณ์?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "ลบแอปพลิเคชันตรวจสอบสิทธิ์/อุปกรณ์", "v2.page.settings-totp.default.title": "แอปพลิเคชันหรืออุปกรณ์ตรวจสอบสิทธิ์ Passkey", "v2.page.settings.default.account-deletion-button-label": "ลบบัญชี", "v2.page.settings.default.advanced-settings-button-label": "การตั้งค่าขั้นสูง", diff --git a/resources/authgear/templates/vi/translation.json b/resources/authgear/templates/vi/translation.json index cf560fd138..26054ab811 100644 --- a/resources/authgear/templates/vi/translation.json +++ b/resources/authgear/templates/vi/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "Thiết bị đã đăng nhập", "v2.page.settings-totp.default.add-authenticator-button-label": "+ Thêm ứng dụng/thiết bị xác thực", "v2.page.settings-totp.default.added-at-label": "Đã thêm vào {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "Bạn có chắc chắn muốn gỡ ứng dụng/thiết bị xác thực không?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "Gỡ ứng dụng/thiết bị xác thực", "v2.page.settings-totp.default.title": "Ứng dụng/thiết bị xác thực", "v2.page.settings.default.account-deletion-button-label": "Xóa Tài khoản", "v2.page.settings.default.advanced-settings-button-label": "Cài đặt Nâng cao", diff --git a/resources/authgear/templates/zh-CN/translation.json b/resources/authgear/templates/zh-CN/translation.json index 2d60ea07dd..a810de33a4 100644 --- a/resources/authgear/templates/zh-CN/translation.json +++ b/resources/authgear/templates/zh-CN/translation.json @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "已登录设备", "v2.page.settings-totp.default.add-authenticator-button-label": "+ 添加身份验证器应用/设备", "v2.page.settings-totp.default.added-at-label": "添加于 {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "您确定要移除身份验证器应用程序/设备吗?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "移除身份验证器应用程序/设备", "v2.page.settings-totp.default.title": "身份验证器应用/设备", "v2.page.settings.default.account-deletion-button-label": "删除账户", "v2.page.settings.default.advanced-settings-button-label": "高级设置", diff --git a/resources/authgear/templates/zh-HK/translation.json b/resources/authgear/templates/zh-HK/translation.json index e188a60334..21c48e5659 100644 --- a/resources/authgear/templates/zh-HK/translation.json +++ b/resources/authgear/templates/zh-HK/translation.json @@ -110,7 +110,7 @@ "copied-button-label": "已複製", "get-new-recovery-codes-button-label": "取得新的備用碼", "download-button-label": "下載", - "disconnect-button-label": "移除連結", + "disconnect-button-label": "解除連接", "add-button-label": "增加", "remove-button-label": "移除", "save-button-label": "儲存", @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "已登入的裝置", "v2.page.settings-totp.default.add-authenticator-button-label": "+ 新增驗證器應用程式/裝置", "v2.page.settings-totp.default.added-at-label": "新增時間: {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "您確定要移除驗證器應用程式/裝置嗎?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "移除驗證器應用程式/裝置", "v2.page.settings-totp.default.title": "驗證器應用程式/裝置", "v2.page.settings.default.account-deletion-button-label": "刪除帳戶", "v2.page.settings.default.advanced-settings-button-label": "進階設定", diff --git a/resources/authgear/templates/zh-TW/translation.json b/resources/authgear/templates/zh-TW/translation.json index 3756031274..382c216550 100644 --- a/resources/authgear/templates/zh-TW/translation.json +++ b/resources/authgear/templates/zh-TW/translation.json @@ -110,7 +110,7 @@ "copied-button-label": "已複製", "get-new-recovery-codes-button-label": "取得新的備用碼", "download-button-label": "下載", - "disconnect-button-label": "移除連結", + "disconnect-button-label": "解除連接", "add-button-label": "增加", "remove-button-label": "移除", "save-button-label": "儲存", @@ -1271,6 +1271,8 @@ "v2.page.settings-sessions.default.title": "已登入的裝置", "v2.page.settings-totp.default.add-authenticator-button-label": "+ 新增驗證器應用程式/裝置", "v2.page.settings-totp.default.added-at-label": "新增時間: {time, datetime, short} UTC", + "v2.page.settings-totp.default.remove-totp-dialog-description": "您確定要移除驗證器應用程式/裝置嗎?", + "v2.page.settings-totp.default.remove-totp-dialog-title": "移除驗證器應用程式/裝置", "v2.page.settings-totp.default.title": "驗證器應用程式/裝置", "v2.page.settings.default.account-deletion-button-label": "刪除帳戶", "v2.page.settings.default.advanced-settings-button-label": "進階設定", From 8dad0355de2a9f8920e0be10d0ffd546bf4edfa6 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 17:01:00 +0800 Subject: [PATCH 21/31] Add remove otp in account management --- .../service_authenticator.go | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index a131b5c28b..7026e2ffe6 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -594,3 +594,36 @@ func (s *Service) FinishAddOOBOTPAuthenticator(resolvedSession session.ResolvedS } return } + +type DeleteOOBOTPAuthenticatorInput struct { + AuthenticatorID string +} + +type DeleteOOBOTPAuthenticatorOutput struct { + Info *authenticator.Info +} + +func (s *Service) DeleteOOBOTPAuthenticator(resolvedSession session.ResolvedSession, input *DeleteOOBOTPAuthenticatorInput) (output *DeleteOOBOTPAuthenticatorOutput, err error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + authenticatorID := input.AuthenticatorID + + var info *authenticator.Info + err = s.Database.WithTx(func() error { + info, err = s.prepareDeleteAuthenticator(userID, authenticatorID) + if err != nil { + return err + } + + err = s.Authenticators.Delete(info) + if err != nil { + return err + } + + return nil + }) + + output = &DeleteOOBOTPAuthenticatorOutput{ + Info: info, + } + return +} From 77a0b2b3f27c3ab59635635a1e9c0e0fc10be950 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 17:01:20 +0800 Subject: [PATCH 22/31] Add remove totp dialog for settings v2 --- .../webapp/authflowv2/settings_oob_otp.go | 24 ++++++++++ pkg/auth/wire_gen.go | 28 ++++++++++++ .../authgear/templates/en/translation.json | 4 ++ .../en/web/authflowv2/settings_mfa_totp.html | 6 +-- .../en/web/authflowv2/settings_oob_otp.html | 44 ++++++++++++++++++- 5 files changed, 101 insertions(+), 5 deletions(-) diff --git a/pkg/auth/handler/webapp/authflowv2/settings_oob_otp.go b/pkg/auth/handler/webapp/authflowv2/settings_oob_otp.go index 39af3b5846..f059bb4e9a 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_oob_otp.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_oob_otp.go @@ -6,11 +6,14 @@ import ( "github.com/authgear/authgear-server/pkg/api/model" handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + "github.com/authgear/authgear-server/pkg/auth/webapp" + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" "github.com/authgear/authgear-server/pkg/lib/authn/authenticator" authenticatorservice "github.com/authgear/authgear-server/pkg/lib/authn/authenticator/service" "github.com/authgear/authgear-server/pkg/lib/infra/db/appdb" "github.com/authgear/authgear-server/pkg/lib/session" "github.com/authgear/authgear-server/pkg/util/httproute" + "github.com/authgear/authgear-server/pkg/util/httputil" "github.com/authgear/authgear-server/pkg/util/template" ) @@ -29,6 +32,7 @@ type AuthflowV2SettingsOOBOTPHandler struct { ControllerFactory handlerwebapp.ControllerFactory BaseViewModel *viewmodels.BaseViewModeler Renderer handlerwebapp.Renderer + AccountManagement *accountmanagement.Service Authenticators authenticatorservice.Service } @@ -87,4 +91,24 @@ func (h *AuthflowV2SettingsOOBOTPHandler) ServeHTTP(w http.ResponseWriter, r *ht h.Renderer.RenderHTML(w, r, TemplateWebSettingsOOBOTPHTML, data) return nil }) + + ctrl.PostAction("remove", func() error { + authenticatorID := r.Form.Get("x_authenticator_id") + + s := session.GetSession(r.Context()) + + input := &accountmanagement.DeleteOOBOTPAuthenticatorInput{ + AuthenticatorID: authenticatorID, + } + _, err = h.AccountManagement.DeleteOOBOTPAuthenticator(s, input) + if err != nil { + return err + } + + redirectURI := httputil.HostRelative(r.URL).String() + result := webapp.Result{RedirectURI: redirectURI} + result.WriteResponse(w, r) + + return nil + }) } diff --git a/pkg/auth/wire_gen.go b/pkg/auth/wire_gen.go index 642711f63c..7790daab98 100644 --- a/pkg/auth/wire_gen.go +++ b/pkg/auth/wire_gen.go @@ -60417,6 +60417,33 @@ func newWebAppAuthflowV2SettingsOOBOTPHandler(p *deps.RequestProvider) http.Hand LoggerFactory: factory, ControllerDeps: controllerDeps, } + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, + } + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } service4 := service2.Service{ Store: store3, Config: appConfig, @@ -60433,6 +60460,7 @@ func newWebAppAuthflowV2SettingsOOBOTPHandler(p *deps.RequestProvider) http.Hand ControllerFactory: controllerFactory, BaseViewModel: baseViewModeler, Renderer: responseRenderer, + AccountManagement: accountmanagementService, Authenticators: service4, } return authflowV2SettingsOOBOTPHandler diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index 2ba8976990..e5a6bea32f 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "You have {NumberOfDeviceTokens, plural, one{# trusted device} other{# trusted devices}}", "v2.page.settings-mfa.default.trusted-devices-label": "Trusted devices", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "You do not have any trusted devices that can skip 2-step verification.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Are you sure you want to remove this email?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Remove email", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Are you sure you want to remove this phone number?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Remove phone number", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/SMS message", "v2.page.settings-oob-otp.default.added-at-label": "Added at {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Add email", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html index 3b5bade450..767d319b3d 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_totp.html @@ -79,9 +79,9 @@ {{ define "__settings_totp_item_delete_button.html" }} diff --git a/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html b/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html index 96378e152e..0f7d0804f0 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html @@ -17,6 +17,7 @@
{{ if $.OOBOTPAuthenticators }}
    + {{ $ctx := .}} {{ range $.OOBOTPAuthenticators }}
  • {{ $label := .Phone }} @@ -27,9 +28,38 @@ (dict "Label" $label "Description" (include "__settings_oob_otp_item_added_at.html" .) - "ActionButton" (include "__settings_oob_otp_item_delete_button.html" nil) + "ActionButton" (include "__settings_oob_otp_item_delete_button.html" (dict "DialogID" .ID)) ) }} + + {{ $remove_dialog_title := (translate "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title" nil) }} + {{ $remove_dialog_description := (translate "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description" nil) }} + {{ if eq $.OOBOTPType "email" }} + {{ $remove_dialog_title = (translate "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title" nil) }} + {{ $remove_dialog_description = (translate "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description" nil) }} + {{ end }} + + {{ template "authflowv2/__settings_dialog.html" + (dict + "Ctx" $ctx + "DialogID" .ID + "Title" $remove_dialog_title + "Description" $remove_dialog_description + "FormContent" (include "__settings_oob_otp_dialog_remove_input" (dict "AuthenticatorID" .ID "CSRFField" $.CSRFField)) + "Buttons" + (list + (dict + "Type" "Destructive" + "Label" (include "v2.component.button.default.label-remove" nil) + "Value" "remove" + "Event" "authgear.button.remove_biometric" + ) + (dict + "Type" "Cancel" + "Label" (include "v2.component.button.default.label-cancel" nil) + ) + ) + )}}
  • {{ end }}
@@ -63,7 +93,17 @@ {{ end }} {{ define "__settings_oob_otp_item_delete_button.html" }} - {{ end }} + +{{ define "__settings_oob_otp_dialog_remove_input" }} + {{ $.CSRFField }} + +{{ end }} From cf402cf0835e08ac24e03ec5f630825997834d64 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 17:01:56 +0800 Subject: [PATCH 23/31] Generate translations --- .make-lint-translation-keys-expect | 2 +- resources/authgear/templates/de/translation.json | 4 ++++ resources/authgear/templates/el/translation.json | 4 ++++ .../templates/en/web/authflowv2/settings_oob_otp.html | 4 ++-- resources/authgear/templates/es-419/translation.json | 4 ++++ resources/authgear/templates/es-ES/translation.json | 4 ++++ resources/authgear/templates/es/translation.json | 4 ++++ resources/authgear/templates/fil/translation.json | 4 ++++ resources/authgear/templates/fr/translation.json | 4 ++++ resources/authgear/templates/id/translation.json | 4 ++++ resources/authgear/templates/it/translation.json | 4 ++++ resources/authgear/templates/ja/translation.json | 4 ++++ resources/authgear/templates/ko/translation.json | 4 ++++ resources/authgear/templates/ms/translation.json | 4 ++++ resources/authgear/templates/nl/translation.json | 4 ++++ resources/authgear/templates/pl/translation.json | 4 ++++ resources/authgear/templates/pt-BR/translation.json | 4 ++++ resources/authgear/templates/pt-PT/translation.json | 4 ++++ resources/authgear/templates/pt/translation.json | 4 ++++ resources/authgear/templates/th/translation.json | 4 ++++ resources/authgear/templates/vi/translation.json | 4 ++++ resources/authgear/templates/zh-CN/translation.json | 4 ++++ resources/authgear/templates/zh-HK/translation.json | 4 ++++ resources/authgear/templates/zh-TW/translation.json | 4 ++++ 24 files changed, 91 insertions(+), 3 deletions(-) diff --git a/.make-lint-translation-keys-expect b/.make-lint-translation-keys-expect index 9bc7db4056..2527064d0c 100644 --- a/.make-lint-translation-keys-expect +++ b/.make-lint-translation-keys-expect @@ -29,7 +29,7 @@ resources/authgear/templates/en/web/authflowv2/forgot_password.html:159:26: temp resources/authgear/templates/en/web/authflowv2/layout.html:5:14: template translation is forbidden: `widget` resources/authgear/templates/en/web/authflowv2/login.html:252:37: translation key not defined: "%s-icon" resources/authgear/templates/en/web/authflowv2/settings_layout.html:3:14: template translation is forbidden: `widget` -resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html:38:31: translation key not defined: "/settings/mfa/create_oob_otp_%s" +resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html:68:31: translation key not defined: "/settings/mfa/create_oob_otp_%s" resources/authgear/templates/en/web/authflowv2/settings_profile.html:138:22: translation key not defined: "custom-attribute-label-%s" resources/authgear/templates/en/web/authflowv2/settings_profile.html:139:20: invalid translation key: "$labelKey" resources/authgear/templates/en/web/authflowv2/settings_profile.html:143:21: invalid translation key: "$labelKey" diff --git a/resources/authgear/templates/de/translation.json b/resources/authgear/templates/de/translation.json index d73b7c49f4..257e9bca34 100644 --- a/resources/authgear/templates/de/translation.json +++ b/resources/authgear/templates/de/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Sie haben {NumberOfDeviceTokens, plural, one{# vertrauenswürdiges Gerät} other{# vertrauenswürdige Geräte}}", "v2.page.settings-mfa.default.trusted-devices-label": "Vertrauenswürdige Geräte", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Sie haben keine vertrauenswürdigen Geräte, die die 2-Stufen-Verifizierung überspringen können.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Sind Sie sicher, dass Sie diese E-Mail-Adresse entfernen möchten?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "E-Mail-Adresse entfernen", "v2.page.settings-oob-otp-email.default.title": "E-Mail", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Sind Sie sicher, dass Sie diese Telefonnummer entfernen möchten?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Telefonnummer entfernen", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/SMS-Nachricht", "v2.page.settings-oob-otp.default.added-at-label": "Hinzugefügt um {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ E-Mail hinzufügen", diff --git a/resources/authgear/templates/el/translation.json b/resources/authgear/templates/el/translation.json index 7895be68de..1f55060b32 100644 --- a/resources/authgear/templates/el/translation.json +++ b/resources/authgear/templates/el/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Έχετε {NumberOfDeviceTokens, plural, one{# έμπιστη συσκευή} other{# έμπιστες συσκευές}}", "v2.page.settings-mfa.default.trusted-devices-label": "Έμπιστες συσκευές", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Δεν έχετε καμία έμπιστη συσκευή που μπορεί να παρακάμψει την επαλήθευση ταυτότητας δύο παραγόντων (2-step verification).", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε αυτό το email;", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Αφαίρεση email", "v2.page.settings-oob-otp-email.default.title": "Ηλεκτρονικό ταχυδρομείο", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε αυτόν τον αριθμό τηλεφώνου;", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Αφαίρεση αριθμού τηλεφώνου", "v2.page.settings-oob-otp-sms.default.title": "Μήνυμα WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Προστέθηκε στις {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Προσθήκη ηλεκτρονικού ταχυδρομείου", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html b/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html index 0f7d0804f0..39eb057d27 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_oob_otp.html @@ -45,7 +45,7 @@ "DialogID" .ID "Title" $remove_dialog_title "Description" $remove_dialog_description - "FormContent" (include "__settings_oob_otp_dialog_remove_input" (dict "AuthenticatorID" .ID "CSRFField" $.CSRFField)) + "FormContent" (include "__settings_oob_otp_dialog_remove_input.html" (dict "AuthenticatorID" .ID "CSRFField" $.CSRFField)) "Buttons" (list (dict @@ -103,7 +103,7 @@ {{ end }} -{{ define "__settings_oob_otp_dialog_remove_input" }} +{{ define "__settings_oob_otp_dialog_remove_input.html" }} {{ $.CSRFField }} {{ end }} diff --git a/resources/authgear/templates/es-419/translation.json b/resources/authgear/templates/es-419/translation.json index a5e32de319..bf9e48b804 100644 --- a/resources/authgear/templates/es-419/translation.json +++ b/resources/authgear/templates/es-419/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Tienes {NumberOfDeviceTokens, plural, one{# dispositivo de confianza} other{# dispositivos de confianza}}", "v2.page.settings-mfa.default.trusted-devices-label": "Dispositivos de confianza", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "No tienes ningún dispositivo de confianza que pueda omitir la verificación de 2 pasos.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "¿Estás seguro de que deseas eliminar este correo electrónico?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Eliminar correo electrónico", "v2.page.settings-oob-otp-email.default.title": "Correo electrónico", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "¿Estás seguro de que deseas eliminar este número de teléfono?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Eliminar número de teléfono", "v2.page.settings-oob-otp-sms.default.title": "Mensaje de WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Agregado a las {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Agregar correo electrónico", diff --git a/resources/authgear/templates/es-ES/translation.json b/resources/authgear/templates/es-ES/translation.json index b1ea7e7678..3abab65348 100644 --- a/resources/authgear/templates/es-ES/translation.json +++ b/resources/authgear/templates/es-ES/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Tienes {NumberOfDeviceTokens, plural, one{# dispositivo de confianza} other{# dispositivos de confianza}}", "v2.page.settings-mfa.default.trusted-devices-label": "Dispositivos de confianza", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "No tienes ningún dispositivo de confianza que pueda omitir la verificación de 2 pasos.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "¿Estás seguro de que quieres eliminar este correo electrónico?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Eliminar correo electrónico", "v2.page.settings-oob-otp-email.default.title": "Correo electrónico", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "¿Estás seguro de que quieres eliminar este número de teléfono?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Eliminar número de teléfono", "v2.page.settings-oob-otp-sms.default.title": "Mensaje de WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Añadido a las {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Añadir correo electrónico", diff --git a/resources/authgear/templates/es/translation.json b/resources/authgear/templates/es/translation.json index 2e0caa4845..ae61640b9c 100644 --- a/resources/authgear/templates/es/translation.json +++ b/resources/authgear/templates/es/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Tienes {NumberOfDeviceTokens, plural, one{# dispositivo de confianza} other{# dispositivos de confianza}}", "v2.page.settings-mfa.default.trusted-devices-label": "Dispositivos de confianza", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "No tienes ningún dispositivo de confianza que pueda omitir la verificación de 2 pasos.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "¿Estás seguro de que quieres eliminar este correo electrónico?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Eliminar correo electrónico", "v2.page.settings-oob-otp-email.default.title": "Correo electrónico", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "¿Estás seguro de que quieres eliminar este número de teléfono?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Eliminar número de teléfono", "v2.page.settings-oob-otp-sms.default.title": "Mensaje de WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Añadido a las {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Añadir correo electrónico", diff --git a/resources/authgear/templates/fil/translation.json b/resources/authgear/templates/fil/translation.json index af09a4710d..776ba09ffa 100644 --- a/resources/authgear/templates/fil/translation.json +++ b/resources/authgear/templates/fil/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Mayroon kang {NumberOfDeviceTokens, plural, one{# pinagkakatiwalaan na device} other{# pinagkakatiwalaan na mga device}}", "v2.page.settings-mfa.default.trusted-devices-label": "Pinagkakatiwalaan na mga device", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Wala kang anumang pinagkakatiwalaan na device na maaaring laktawan ang 2-step na pag-verify.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Sigurado ka bang gusto mong alisin ang email na ito?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Alisin ang email", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Sigurado ka bang gusto mong alisin ang numerong ito ng telepono?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Alisin ang numero ng telepono", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/Mensahe sa SMS", "v2.page.settings-oob-otp.default.added-at-label": "Naidagdag sa {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Magdagdag ng email", diff --git a/resources/authgear/templates/fr/translation.json b/resources/authgear/templates/fr/translation.json index f182fa5232..b62655d7a4 100644 --- a/resources/authgear/templates/fr/translation.json +++ b/resources/authgear/templates/fr/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Vous avez {NumberOfDeviceTokens, plural, one{# appareil de confiance} other{# appareils de confiance}}", "v2.page.settings-mfa.default.trusted-devices-label": "Appareils de confiance", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Vous n''avez aucun appareil de confiance qui peut ignorer la vérification en 2 étapes.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Êtes-vous sûr de vouloir supprimer cet e-mail ?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Supprimer l''e-mail", "v2.page.settings-oob-otp-email.default.title": "Courriel", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Êtes-vous sûr de vouloir supprimer ce numéro de téléphone ?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Supprimer le numéro de téléphone", "v2.page.settings-oob-otp-sms.default.title": "Message WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Ajouté à {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Ajouter un courriel", diff --git a/resources/authgear/templates/id/translation.json b/resources/authgear/templates/id/translation.json index 6230285d0f..030b898034 100644 --- a/resources/authgear/templates/id/translation.json +++ b/resources/authgear/templates/id/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Anda memiliki {NumberOfDeviceTokens, plural, one{# perangkat terpercaya} other{# perangkat terpercaya}}", "v2.page.settings-mfa.default.trusted-devices-label": "Perangkat terpercaya", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Anda tidak memiliki perangkat terpercaya yang dapat melewati verifikasi 2-langkah.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Apakah Anda yakin ingin menghapus email ini?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Hapus email", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Apakah Anda yakin ingin menghapus nomor telepon ini?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Hapus nomor telepon", "v2.page.settings-oob-otp-sms.default.title": "Pesan WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Ditambahkan pada {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Tambahkan email", diff --git a/resources/authgear/templates/it/translation.json b/resources/authgear/templates/it/translation.json index 1ab6aba330..772d9e5c00 100644 --- a/resources/authgear/templates/it/translation.json +++ b/resources/authgear/templates/it/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Hai {NumberOfDeviceTokens, plural, one{# dispositivo attendibile} other{# dispositivi attendibili}}", "v2.page.settings-mfa.default.trusted-devices-label": "Dispositivi attendibili", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Non hai dispositivi attendibili che possano saltare la verifica in due passaggi.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Sei sicuro di voler rimuovere questa email?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Rimuovi email", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Sei sicuro di voler rimuovere questo numero di telefono?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Rimuovi numero di telefono", "v2.page.settings-oob-otp-sms.default.title": "Messaggio WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Aggiunto alle {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Aggiungi email", diff --git a/resources/authgear/templates/ja/translation.json b/resources/authgear/templates/ja/translation.json index 270cc5994f..442479fbcb 100644 --- a/resources/authgear/templates/ja/translation.json +++ b/resources/authgear/templates/ja/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "あなたは{NumberOfDeviceTokens, plural, one{# 信頼済みデバイス} other{# 信頼済みデバイス}}を持っています", "v2.page.settings-mfa.default.trusted-devices-label": "信頼済みデバイス", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "2段階認証をスキップできる信頼済みデバイスはありません。", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "このメールアドレスを削除してもよろしいですか?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "メールアドレスを削除", "v2.page.settings-oob-otp-email.default.title": "メール", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "この電話番号を削除してもよろしいですか?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "電話番号を削除", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/SMSメッセージ", "v2.page.settings-oob-otp.default.added-at-label": "追加日時 {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ メールを追加", diff --git a/resources/authgear/templates/ko/translation.json b/resources/authgear/templates/ko/translation.json index 34e9c56a2e..d895549815 100644 --- a/resources/authgear/templates/ko/translation.json +++ b/resources/authgear/templates/ko/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "신뢰할 수 있는 기기가 {NumberOfDeviceTokens, plural, one{# 개} other{# 개}} 있습니다.", "v2.page.settings-mfa.default.trusted-devices-label": "신뢰할 수 있는 기기", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "2단계 인증을 건너뛸 수 있는 신뢰할 수 있는 기기가 없습니다.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "이 이메일을 제거하시겠습니까?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "이메일 제거", "v2.page.settings-oob-otp-email.default.title": "이메일", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "이 전화번호를 제거하시겠습니까?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "전화번호 제거", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/SMS 메시지", "v2.page.settings-oob-otp.default.added-at-label": "{time, datetime, short} UTC에 추가됨", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ 이메일 추가", diff --git a/resources/authgear/templates/ms/translation.json b/resources/authgear/templates/ms/translation.json index c4b75b4a98..899d29335b 100644 --- a/resources/authgear/templates/ms/translation.json +++ b/resources/authgear/templates/ms/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Anda mempunyai {NumberOfDeviceTokens, plural, one{# peranti yang dipercayai} other{# peranti yang dipercayai}}", "v2.page.settings-mfa.default.trusted-devices-label": "Peranti yang dipercayai", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Anda tidak mempunyai sebarang peranti yang dipercayai yang boleh melangkau pengesahan 2-langkah.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Adakah anda pasti ingin mengalih keluar emel ini?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Alih keluar emel", "v2.page.settings-oob-otp-email.default.title": "E-mel", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Adakah anda pasti ingin mengalih keluar nombor telefon ini?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Alih keluar nombor telefon", "v2.page.settings-oob-otp-sms.default.title": "Mesej WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Ditambah pada {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Tambah e-mel", diff --git a/resources/authgear/templates/nl/translation.json b/resources/authgear/templates/nl/translation.json index 9319104264..a9e0a68c3a 100644 --- a/resources/authgear/templates/nl/translation.json +++ b/resources/authgear/templates/nl/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Je hebt {NumberOfDeviceTokens, plural, one{# vertrouwd apparaat} other{# vertrouwde apparaten}}", "v2.page.settings-mfa.default.trusted-devices-label": "Vertrouwde apparaten", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Je hebt geen vertrouwde apparaten die de 2-stapsverificatie kunnen overslaan.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Weet je zeker dat je dit e-mailadres wilt verwijderen?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "E-mailadres verwijderen", "v2.page.settings-oob-otp-email.default.title": "E-mail", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Weet je zeker dat je dit telefoonnummer wilt verwijderen?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Telefoonnummer verwijderen", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/SMS-bericht", "v2.page.settings-oob-otp.default.added-at-label": "Toegevoegd op {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ E-mailadres toevoegen", diff --git a/resources/authgear/templates/pl/translation.json b/resources/authgear/templates/pl/translation.json index 6b01f02824..c248edd329 100644 --- a/resources/authgear/templates/pl/translation.json +++ b/resources/authgear/templates/pl/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Masz {NumberOfDeviceTokens, plural, one{# zaufane urządzenie} other{# zaufane urządzenia}}", "v2.page.settings-mfa.default.trusted-devices-label": "Zaufane urządzenia", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Nie masz żadnych zaufanych urządzeń, które mogą pominąć weryfikację dwuetapową.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Czy na pewno chcesz usunąć ten adres e-mail?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Usuń adres e-mail", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Czy na pewno chcesz usunąć ten numer telefonu?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Usuń numer telefonu", "v2.page.settings-oob-otp-sms.default.title": "Wiadomość WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Dodano o {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Dodaj email", diff --git a/resources/authgear/templates/pt-BR/translation.json b/resources/authgear/templates/pt-BR/translation.json index 8118c4e16a..b5f07c00fc 100644 --- a/resources/authgear/templates/pt-BR/translation.json +++ b/resources/authgear/templates/pt-BR/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Você tem {NumberOfDeviceTokens, plural, one{# dispositivo confiável} other{# dispositivos confiáveis}}", "v2.page.settings-mfa.default.trusted-devices-label": "Dispositivos confiáveis", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Você não tem nenhum dispositivo confiável que possa ignorar a verificação de 2 etapas.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Tem certeza de que deseja remover este e-mail?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Remover e-mail", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Tem certeza de que deseja remover este número de telefone?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Remover número de telefone", "v2.page.settings-oob-otp-sms.default.title": "Mensagem WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Adicionado em {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Adicionar email", diff --git a/resources/authgear/templates/pt-PT/translation.json b/resources/authgear/templates/pt-PT/translation.json index 1797053874..838276c450 100644 --- a/resources/authgear/templates/pt-PT/translation.json +++ b/resources/authgear/templates/pt-PT/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Tem {NumberOfDeviceTokens, plural, one{# dispositivo confiável} other{# dispositivos confiáveis}}", "v2.page.settings-mfa.default.trusted-devices-label": "Dispositivos confiáveis", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Não tem quaisquer dispositivos confiáveis que possam ignorar a verificação de 2 passos.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Tem a certeza de que deseja remover este email?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Remover email", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Tem a certeza de que deseja remover este número de telefone?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Remover número de telefone", "v2.page.settings-oob-otp-sms.default.title": "Mensagem WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Adicionado em {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Adicionar email", diff --git a/resources/authgear/templates/pt/translation.json b/resources/authgear/templates/pt/translation.json index 50a3803195..305244fb6c 100644 --- a/resources/authgear/templates/pt/translation.json +++ b/resources/authgear/templates/pt/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Você tem {NumberOfDeviceTokens, plural, one{# dispositivo confiável} other{# dispositivos confiáveis}}", "v2.page.settings-mfa.default.trusted-devices-label": "Dispositivos confiáveis", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Você não tem nenhum dispositivo confiável que possa ignorar a verificação de 2 etapas.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Tem certeza de que deseja remover este e-mail?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Remover e-mail", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Tem certeza de que deseja remover este número de telefone?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Remover número de telefone", "v2.page.settings-oob-otp-sms.default.title": "Mensagem WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Adicionado em {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Adicionar email", diff --git a/resources/authgear/templates/th/translation.json b/resources/authgear/templates/th/translation.json index 4bae9c1016..cb5dc200e1 100644 --- a/resources/authgear/templates/th/translation.json +++ b/resources/authgear/templates/th/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "คุณมี {NumberOfDeviceTokens, plural, one{# อุปกรณ์ที่เชื่อถือได้} other{# อุปกรณ์ที่เชื่อถือได้}}", "v2.page.settings-mfa.default.trusted-devices-label": "อุปกรณ์ที่เชื่อถือได้", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "คุณไม่มีอุปกรณ์ที่เชื่อถือได้ที่สามารถข้ามการตรวจสอบสิทธิ์ 2 ขั้นตอน", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "คุณแน่ใจหรือไม่ว่าต้องการลบอีเมลนี้?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "ลบอีเมล", "v2.page.settings-oob-otp-email.default.title": "อีเมล", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "คุณแน่ใจหรือไม่ว่าต้องการลบหมายเลขโทรศัพท์นี้?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "ลบหมายเลขโทรศัพท์", "v2.page.settings-oob-otp-sms.default.title": "ข้อความ WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "เพิ่มเมื่อ {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ เพิ่มอีเมล", diff --git a/resources/authgear/templates/vi/translation.json b/resources/authgear/templates/vi/translation.json index 26054ab811..bcadb500f8 100644 --- a/resources/authgear/templates/vi/translation.json +++ b/resources/authgear/templates/vi/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "Bạn có {NumberOfDeviceTokens, plural, one{# thiết bị đáng tin cậy} other{# thiết bị đáng tin cậy}}", "v2.page.settings-mfa.default.trusted-devices-label": "Thiết bị đáng tin cậy", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "Bạn không có bất kỳ thiết bị đáng tin cậy nào có thể bỏ qua xác thực 2 bước.", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "Bạn có chắc chắn muốn xóa email này?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "Xóa email", "v2.page.settings-oob-otp-email.default.title": "Email", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "Bạn có chắc chắn muốn xóa số điện thoại này?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "Xóa số điện thoại", "v2.page.settings-oob-otp-sms.default.title": "Tin nhắn WhatsApp/SMS", "v2.page.settings-oob-otp.default.added-at-label": "Đã thêm vào {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ Thêm email", diff --git a/resources/authgear/templates/zh-CN/translation.json b/resources/authgear/templates/zh-CN/translation.json index a810de33a4..2e1ecbc7d8 100644 --- a/resources/authgear/templates/zh-CN/translation.json +++ b/resources/authgear/templates/zh-CN/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "您有 {NumberOfDeviceTokens, plural, one{#个受信任的设备} other{#个受信任的设备}}", "v2.page.settings-mfa.default.trusted-devices-label": "受信任的设备", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "您没有任何可以跳过双重验证的受信任设备。", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "您确定要删除此电子邮件吗?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "删除电子邮件", "v2.page.settings-oob-otp-email.default.title": "电子邮件", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "您确定要删除此手机号码吗?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "删除手机号码", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/短信", "v2.page.settings-oob-otp.default.added-at-label": "添加于 {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ 添加电子邮件", diff --git a/resources/authgear/templates/zh-HK/translation.json b/resources/authgear/templates/zh-HK/translation.json index 21c48e5659..ca2113e415 100644 --- a/resources/authgear/templates/zh-HK/translation.json +++ b/resources/authgear/templates/zh-HK/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "您有{NumberOfDeviceTokens}個受信任裝置", "v2.page.settings-mfa.default.trusted-devices-label": "受信任裝置", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "您沒有任何可以跳過兩個步驟驗證的受信任裝置。", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "你確定要移除這個電子郵件地址嗎?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "移除電子郵件地址", "v2.page.settings-oob-otp-email.default.title": "電子郵件", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "你確定要移除這個電話號碼嗎?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "移除電話號碼", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/短信訊息", "v2.page.settings-oob-otp.default.added-at-label": "新增時間: {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ 新增電子郵件", diff --git a/resources/authgear/templates/zh-TW/translation.json b/resources/authgear/templates/zh-TW/translation.json index 382c216550..b4d79e35a1 100644 --- a/resources/authgear/templates/zh-TW/translation.json +++ b/resources/authgear/templates/zh-TW/translation.json @@ -1217,7 +1217,11 @@ "v2.page.settings-mfa.default.trusted-devices-has-device-tokens-message": "您有{NumberOfDeviceTokens}個受信任裝置", "v2.page.settings-mfa.default.trusted-devices-label": "受信任裝置", "v2.page.settings-mfa.default.trusted-devices-no-device-tokens-message": "您沒有任何可以跳過兩個步驟驗證的受信任裝置。", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-description": "您確定要移除這個電子郵件地址嗎?", + "v2.page.settings-oob-otp-email.default.remove-oob-otp-dialog-title": "移除電子郵件地址", "v2.page.settings-oob-otp-email.default.title": "電子郵件", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-description": "您確定要移除這個手機號碼嗎?", + "v2.page.settings-oob-otp-sms.default.remove-oob-otp-dialog-title": "移除手機號碼", "v2.page.settings-oob-otp-sms.default.title": "WhatsApp/短信訊息", "v2.page.settings-oob-otp.default.added-at-label": "新增時間: {time, datetime, short} UTC", "v2.page.settings-oob-otp.default.oob-otp-email-button-label": "+ 新增電子郵件", From e5db7ed3a3e1976cde1486ad223c2cf686ad4d02 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 17:29:54 +0800 Subject: [PATCH 24/31] Add remove secondary password in account management --- .../service_authenticator.go | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 7026e2ffe6..723bfe2e1e 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -114,6 +114,50 @@ func (s *Service) ChangeSecondaryPassword(resolvedSession session.ResolvedSessio return &ChangeSecondaryPasswordOutput{}, nil } +type DeleteSecondaryPasswordInput struct { +} + +type DeleteSecondaryPasswordOutput struct { +} + +func (s *Service) DeleteSecondaryPassword(resolvedSession session.ResolvedSession, input *DeleteSecondaryPasswordInput) (*DeleteSecondaryPasswordOutput, error) { + userID := resolvedSession.GetAuthenticationInfo().UserID + + err := s.Database.WithTx(func() error { + info, err := s.prepareDeleteSecondaryPassword(userID) + if err != nil { + return err + } + + err = s.Authenticators.Delete(info) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return &DeleteSecondaryPasswordOutput{}, nil +} + +func (s *Service) prepareDeleteSecondaryPassword(userID string) (*authenticator.Info, error) { + ais, err := s.Authenticators.List( + userID, + authenticator.KeepType(model.AuthenticatorTypePassword), + authenticator.KeepKind(authenticator.KindSecondary), + ) + if err != nil { + return nil, err + } + if len(ais) == 0 { + return nil, api.ErrNoPassword + } + return ais[0], nil +} + type StartAddTOTPAuthenticatorInput struct{} type StartAddTOTPAuthenticatorOutput struct { Token string @@ -395,7 +439,6 @@ func (s *Service) changePassword(resolvedSession session.ResolvedSession, input } } return &changePasswordOutput{}, nil - } func (s *Service) createAuthenticator(authenticatorInfo *authenticator.Info) error { From 61c546554d91aeb903a9db12308364a50556a410 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 17:30:29 +0800 Subject: [PATCH 25/31] Add remove secondary password action for settings v2 --- .../authflowv2/settings_mfa_password.go | 18 ++++++++++++ pkg/auth/wire_gen.go | 28 +++++++++++++++++++ .../web/authflowv2/settings_mfa_password.html | 2 +- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_password.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_password.go index eee8da1650..26896ff97c 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_mfa_password.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_password.go @@ -5,6 +5,8 @@ import ( handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + "github.com/authgear/authgear-server/pkg/auth/webapp" + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" "github.com/authgear/authgear-server/pkg/lib/infra/db/appdb" "github.com/authgear/authgear-server/pkg/lib/session" "github.com/authgear/authgear-server/pkg/util/httproute" @@ -22,6 +24,7 @@ type AuthflowV2SettingsMFAPasswordHandler struct { BaseViewModel *viewmodels.BaseViewModeler SettingsViewModel *viewmodels.SettingsViewModeler Renderer handlerwebapp.Renderer + AccountManagement *accountmanagement.Service } func ConfigureAuthflowV2SettingsMFAPassword(route httproute.Route) httproute.Route { @@ -69,4 +72,19 @@ func (h *AuthflowV2SettingsMFAPasswordHandler) ServeHTTP(w http.ResponseWriter, h.Renderer.RenderHTML(w, r, TemplateWebSettingsMFAPasswordHTML, data) return nil }) + + ctrl.PostAction("remove", func() error { + s := session.GetSession(r.Context()) + + input := &accountmanagement.DeleteSecondaryPasswordInput{} + _, err = h.AccountManagement.DeleteSecondaryPassword(s, input) + if err != nil { + return err + } + + result := webapp.Result{RedirectURI: AuthflowV2RouteSettingsMFA} + result.WriteResponse(w, r) + + return nil + }) } diff --git a/pkg/auth/wire_gen.go b/pkg/auth/wire_gen.go index 7790daab98..2920362dc3 100644 --- a/pkg/auth/wire_gen.go +++ b/pkg/auth/wire_gen.go @@ -55584,12 +55584,40 @@ func newWebAppAuthflowV2SettingsMFAPasswordHandler(p *deps.RequestProvider) http Authentication: authenticationConfig, Biometric: biometricConfig, } + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, + } + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } authflowV2SettingsMFAPasswordHandler := &authflowv2.AuthflowV2SettingsMFAPasswordHandler{ Database: handle, ControllerFactory: controllerFactory, BaseViewModel: baseViewModeler, SettingsViewModel: settingsViewModeler, Renderer: responseRenderer, + AccountManagement: accountmanagementService, } return authflowV2SettingsMFAPasswordHandler } diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_password.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_password.html index 3acc20d6c0..a50f0789cc 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa_password.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_password.html @@ -45,7 +45,7 @@ (dict "Type" "Destructive" "Label" (include "v2.component.button.default.label-remove" nil) - "Value" "remove_secondary_password" + "Value" "remove" ) (dict "Type" "Cancel" From db4653f5b98b515eac84e167b7883b4a1f51c517 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 18:11:14 +0800 Subject: [PATCH 26/31] Add change secondary password action for settings v2 --- pkg/auth/handler/webapp/authflowv2/deps.go | 1 + pkg/auth/handler/webapp/authflowv2/routes.go | 7 +- .../settings_mfa_change_password.go | 120 +++ pkg/auth/routes.go | 1 + pkg/auth/wire_gen.go | 964 ++++++++++++++++++ pkg/auth/wire_handler.go | 7 + .../authgear/templates/en/translation.json | 1 + .../settings_mfa_change_password.html | 122 +++ .../web/authflowv2/settings_mfa_password.html | 4 +- 9 files changed, 1222 insertions(+), 5 deletions(-) create mode 100644 pkg/auth/handler/webapp/authflowv2/settings_mfa_change_password.go create mode 100644 resources/authgear/templates/en/web/authflowv2/settings_mfa_change_password.html diff --git a/pkg/auth/handler/webapp/authflowv2/deps.go b/pkg/auth/handler/webapp/authflowv2/deps.go index 06577a3b58..6dfbd2e085 100644 --- a/pkg/auth/handler/webapp/authflowv2/deps.go +++ b/pkg/auth/handler/webapp/authflowv2/deps.go @@ -54,6 +54,7 @@ var DependencySet = wire.NewSet( wire.Struct(new(AuthflowV2SettingsDeleteAccountSuccessHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFAViewRecoveryCodeHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFACreatePasswordHandler), "*"), + wire.Struct(new(AuthflowV2SettingsMFAChangePasswordHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFAPasswordHandler), "*"), wire.Struct(new(AuthflowV2SettingsTOTPHandler), "*"), wire.Struct(new(AuthflowV2SettingsMFACreateTOTPHandler), "*"), diff --git a/pkg/auth/handler/webapp/authflowv2/routes.go b/pkg/auth/handler/webapp/authflowv2/routes.go index 3d93abb79b..2b0014eb71 100644 --- a/pkg/auth/handler/webapp/authflowv2/routes.go +++ b/pkg/auth/handler/webapp/authflowv2/routes.go @@ -88,9 +88,10 @@ const ( AuthflowV2RouteSettingsMFACreateOOBOTP = "/settings/mfa/create_oob_otp_:channel" AuthflowV2RouteSettingsMFAEnterOOBOTP = "/settings/mfa/enter_oob_otp" // nolint: gosec - AuthflowV2RouteSettingsMFAPassword = "/settings/mfa/password" - AuthflowV2RouteSettingsMFACreateTOTP = "/settings/mfa/create_totp" - AuthflowV2RouteSettingsMFAEnterTOTP = "/settings/mfa/enter_totp" + AuthflowV2RouteSettingsMFAPassword = "/settings/mfa/password" + AuthflowV2RouteSettingsMFAChangePassword = "/settings/mfa/change_password" + AuthflowV2RouteSettingsMFACreateTOTP = "/settings/mfa/create_totp" + AuthflowV2RouteSettingsMFAEnterTOTP = "/settings/mfa/enter_totp" AuthflowV2RouteSettingsIdentityListEmail = "/settings/identity/email" AuthflowV2RouteSettingsIdentityAddEmail = "/settings/identity/add_email" diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_change_password.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_change_password.go new file mode 100644 index 0000000000..17c89d73b1 --- /dev/null +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_change_password.go @@ -0,0 +1,120 @@ +package authflowv2 + +import ( + "net/http" + + handlerwebapp "github.com/authgear/authgear-server/pkg/auth/handler/webapp" + "github.com/authgear/authgear-server/pkg/auth/handler/webapp/viewmodels" + "github.com/authgear/authgear-server/pkg/auth/webapp" + "github.com/authgear/authgear-server/pkg/lib/accountmanagement" + "github.com/authgear/authgear-server/pkg/lib/session" + "github.com/authgear/authgear-server/pkg/util/httproute" + pwd "github.com/authgear/authgear-server/pkg/util/password" + "github.com/authgear/authgear-server/pkg/util/template" + "github.com/authgear/authgear-server/pkg/util/validation" +) + +var TemplateWebSettingsV2MFAChangePasswordHTML = template.RegisterHTML( + "web/authflowv2/settings_mfa_change_password.html", + handlerwebapp.SettingsComponents..., +) + +var AuthflowV2SettingsMFAChangePasswordSchema = validation.NewSimpleSchema(` + { + "type": "object", + "properties": { + "x_old_password": { "type": "string" }, + "x_new_password": { "type": "string" }, + "x_confirm_password": { "type": "string" } + }, + "required": ["x_old_password", "x_new_password", "x_confirm_password"] + } +`) + +type AuthflowV2SettingsMFAChangePasswordHandler struct { + ControllerFactory handlerwebapp.ControllerFactory + BaseViewModel *viewmodels.BaseViewModeler + Renderer handlerwebapp.Renderer + AccountManagementService *accountmanagement.Service + PasswordPolicy handlerwebapp.PasswordPolicy +} + +func ConfigureAuthflowV2SettingsMFAChangePassword(route httproute.Route) httproute.Route { + return route. + WithMethods("OPTIONS", "POST", "GET"). + WithPathPattern(AuthflowV2RouteSettingsMFAChangePassword) +} + +func (h *AuthflowV2SettingsMFAChangePasswordHandler) GetData(r *http.Request, rw http.ResponseWriter) (map[string]interface{}, error) { + data := make(map[string]interface{}) + + // BaseViewModel + baseViewModel := h.BaseViewModel.ViewModel(r, rw) + viewmodels.Embed(data, baseViewModel) + + passwordPolicyViewModel := viewmodels.NewPasswordPolicyViewModel( + h.PasswordPolicy.PasswordPolicy(), + h.PasswordPolicy.PasswordRules(), + baseViewModel.RawError, + viewmodels.GetDefaultPasswordPolicyViewModelOptions(), + ) + viewmodels.Embed(data, passwordPolicyViewModel) + + viewmodels.Embed(data, handlerwebapp.ChangePasswordViewModel{ + Force: false, + }) + + return data, nil +} + +func (h *AuthflowV2SettingsMFAChangePasswordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctrl, err := h.ControllerFactory.New(r, w) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer ctrl.ServeWithoutDBTx() + + ctrl.Get(func() error { + data, err := h.GetData(r, w) + if err != nil { + return err + } + + h.Renderer.RenderHTML(w, r, TemplateWebSettingsV2MFAChangePasswordHTML, data) + + return nil + }) + + ctrl.PostAction("", func() error { + err := AuthflowV2SettingsMFAChangePasswordSchema.Validator().ValidateValue(handlerwebapp.FormToJSON(r.Form)) + if err != nil { + return err + } + + oldPassword := r.Form.Get("x_old_password") + newPassword := r.Form.Get("x_new_password") + confirmPassword := r.Form.Get("x_confirm_password") + + err = pwd.ConfirmPassword(newPassword, confirmPassword) + if err != nil { + return err + } + + s := session.GetSession(r.Context()) + input := &accountmanagement.ChangeSecondaryPasswordInput{ + OldPassword: oldPassword, + NewPassword: newPassword, + } + + _, err = h.AccountManagementService.ChangeSecondaryPassword(s, input) + if err != nil { + return err + } + + result := webapp.Result{RedirectURI: AuthflowV2RouteSettingsMFAPassword} + result.WriteResponse(w, r) + return nil + }) + +} diff --git a/pkg/auth/routes.go b/pkg/auth/routes.go index ad5c75117d..14ab72aa70 100644 --- a/pkg/auth/routes.go +++ b/pkg/auth/routes.go @@ -486,6 +486,7 @@ func NewRouter(p *deps.RootProvider, configSource *configsource.ConfigSource) *h }) router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFAViewRecoveryCodeRoute(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFAViewRecoveryCodeHandler)) router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFACreatePassword(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFACreatePasswordHandler)) + router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFAChangePassword(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFAChangePasswordHandler)) router.Add(webapphandlerauthflowv2.ConfigureAuthflowV2SettingsMFAPassword(webappSettingsSubRoutesRoute), p.Handler(newWebAppAuthflowV2SettingsMFAPasswordHandler)) router.Add(webapphandler.ConfigureSettingsTOTPRoute(webappSettingsSubRoutesRoute), &webapphandler.SettingsImplementationSwitcherHandler{ SettingV1: p.Handler(newWebAppSettingsTOTPHandler), diff --git a/pkg/auth/wire_gen.go b/pkg/auth/wire_gen.go index 2920362dc3..97332115a2 100644 --- a/pkg/auth/wire_gen.go +++ b/pkg/auth/wire_gen.go @@ -55622,6 +55622,970 @@ func newWebAppAuthflowV2SettingsMFAPasswordHandler(p *deps.RequestProvider) http return authflowV2SettingsMFAPasswordHandler } +func newWebAppAuthflowV2SettingsMFAChangePasswordHandler(p *deps.RequestProvider) http.Handler { + appProvider := p.AppProvider + factory := appProvider.LoggerFactory + handle := appProvider.AppDatabase + appredisHandle := appProvider.Redis + appContext := appProvider.AppContext + config := appContext.Config + appConfig := config.AppConfig + appID := appConfig.ID + serviceLogger := webapp2.NewServiceLogger(factory) + request := p.Request + sessionStoreRedis := &webapp2.SessionStoreRedis{ + AppID: appID, + Redis: appredisHandle, + } + sessionCookieDef := webapp2.NewSessionCookieDef() + signedUpCookieDef := webapp2.NewSignedUpCookieDef() + authenticationConfig := appConfig.Authentication + cookieDef := mfa.NewDeviceTokenCookieDef(authenticationConfig) + errorTokenCookieDef := webapp2.NewErrorTokenCookieDef() + rootProvider := appProvider.RootProvider + environmentConfig := rootProvider.EnvironmentConfig + trustProxy := environmentConfig.TrustProxy + httpConfig := appConfig.HTTP + cookieManager := deps.NewCookieManager(request, trustProxy, httpConfig) + errorService := &webapp2.ErrorService{ + AppID: appID, + Cookie: errorTokenCookieDef, + RedisHandle: appredisHandle, + Cookies: cookieManager, + } + oAuthConfig := appConfig.OAuth + uiConfig := appConfig.UI + httpHost := deps.ProvideHTTPHost(request, trustProxy) + httpProto := deps.ProvideHTTPProto(request, trustProxy) + globalUIImplementation := environmentConfig.UIImplementation + globalUISettingsImplementation := environmentConfig.UISettingsImplementation + uiImplementationService := &web.UIImplementationService{ + UIConfig: uiConfig, + GlobalUIImplementation: globalUIImplementation, + GlobalUISettingsImplementation: globalUISettingsImplementation, + } + endpointsEndpoints := &endpoints.Endpoints{ + HTTPHost: httpHost, + HTTPProto: httpProto, + UIImplementationService: uiImplementationService, + } + uiService := &authenticationinfo.UIService{ + EndpointsProvider: endpointsEndpoints, + } + resolver := &oauthclient.Resolver{ + OAuthConfig: oAuthConfig, + TesterEndpoints: endpointsEndpoints, + } + logger := interaction.NewLogger(factory) + remoteIP := deps.ProvideRemoteIP(request, trustProxy) + contextContext := deps.ProvideRequestContext(request) + sqlExecutor := appdb.NewSQLExecutor(contextContext, handle) + clockClock := _wireSystemClockValue + featureConfig := config.FeatureConfig + redisLogger := redis.NewLogger(factory) + secretConfig := config.SecretConfig + databaseCredentials := deps.ProvideDatabaseCredentials(secretConfig) + sqlBuilderApp := appdb.NewSQLBuilderApp(databaseCredentials, appID) + store := &redis.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Logger: redisLogger, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + userAgentString := deps.ProvideUserAgentString(request) + eventLogger := event.NewLogger(factory) + localizationConfig := appConfig.Localization + sqlBuilder := appdb.NewSQLBuilder(databaseCredentials) + storeImpl := event.NewStoreImpl(sqlBuilder, sqlExecutor) + userStore := &user.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + AppID: appID, + } + rawQueries := &user.RawQueries{ + Store: userStore, + } + identityConfig := appConfig.Identity + identityFeatureConfig := featureConfig.Identity + serviceStore := &service.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginidStore := &loginid.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + loginIDConfig := identityConfig.LoginID + manager := appContext.Resources + typeCheckerFactory := &loginid.TypeCheckerFactory{ + UIConfig: uiConfig, + LoginIDConfig: loginIDConfig, + Resources: manager, + } + checker := &loginid.Checker{ + Config: loginIDConfig, + TypeCheckerFactory: typeCheckerFactory, + } + normalizerFactory := &loginid.NormalizerFactory{ + Config: loginIDConfig, + } + provider := &loginid.Provider{ + Store: loginidStore, + Config: loginIDConfig, + Checker: checker, + NormalizerFactory: normalizerFactory, + Clock: clockClock, + } + oauthStore := &oauth3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + IdentityConfig: identityConfig, + } + oauthProvider := &oauth3.Provider{ + Store: oauthStore, + Clock: clockClock, + IdentityConfig: identityConfig, + } + anonymousStore := &anonymous.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + anonymousProvider := &anonymous.Provider{ + Store: anonymousStore, + Clock: clockClock, + } + biometricStore := &biometric.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + biometricProvider := &biometric.Provider{ + Store: biometricStore, + Clock: clockClock, + } + passkeyStore := &passkey.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + store2 := &passkey2.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + defaultLanguageTag := deps.ProvideDefaultLanguageTag(config) + supportedLanguageTags := deps.ProvideSupportedLanguageTags(config) + templateResolver := &template.Resolver{ + Resources: manager, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + } + engine := &template.Engine{ + Resolver: templateResolver, + } + httpOrigin := httputil.MakeHTTPOrigin(httpProto, httpHost) + webAppCDNHost := environmentConfig.WebAppCDNHost + globalEmbeddedResourceManager := rootProvider.EmbeddedResources + staticAssetResolver := &web.StaticAssetResolver{ + Context: contextContext, + Localization: localizationConfig, + HTTPOrigin: httpOrigin, + HTTPProto: httpProto, + WebAppCDNHost: webAppCDNHost, + Resources: manager, + EmbeddedResources: globalEmbeddedResourceManager, + } + translationService := &translation.Service{ + Context: contextContext, + TemplateEngine: engine, + StaticAssets: staticAssetResolver, + } + configService := &passkey2.ConfigService{ + Request: request, + TrustProxy: trustProxy, + TranslationService: translationService, + } + passkeyService := &passkey2.Service{ + Store: store2, + ConfigService: configService, + } + passkeyProvider := &passkey.Provider{ + Store: passkeyStore, + Clock: clockClock, + Passkey: passkeyService, + } + siweStore := &siwe.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + web3Config := appConfig.Web3 + storeRedis := &siwe2.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + ratelimitLogger := ratelimit.NewLogger(factory) + storageRedis := &ratelimit.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + rateLimitsFeatureConfig := featureConfig.RateLimits + limiter := &ratelimit.Limiter{ + Logger: ratelimitLogger, + Storage: storageRedis, + Config: rateLimitsFeatureConfig, + } + siweLogger := siwe2.NewLogger(factory) + siweService := &siwe2.Service{ + RemoteIP: remoteIP, + HTTPOrigin: httpOrigin, + Web3Config: web3Config, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + NonceStore: storeRedis, + RateLimiter: limiter, + Logger: siweLogger, + } + siweProvider := &siwe.Provider{ + Store: siweStore, + Clock: clockClock, + SIWE: siweService, + } + ldapStore := &ldap.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + normalizer := &stdattrs.Normalizer{ + LoginIDNormalizerFactory: normalizerFactory, + } + ldapProvider := &ldap.Provider{ + Store: ldapStore, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + } + serviceService := &service.Service{ + Authentication: authenticationConfig, + Identity: identityConfig, + IdentityFeatureConfig: identityFeatureConfig, + Store: serviceStore, + LoginID: provider, + OAuth: oauthProvider, + Anonymous: anonymousProvider, + Biometric: biometricProvider, + Passkey: passkeyProvider, + SIWE: siweProvider, + LDAP: ldapProvider, + } + store3 := &service2.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + passwordStore := &password.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorConfig := appConfig.Authenticator + authenticatorPasswordConfig := authenticatorConfig.Password + passwordLogger := password.NewLogger(factory) + historyStore := &password.HistoryStore{ + Clock: clockClock, + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorFeatureConfig := featureConfig.Authenticator + passwordChecker := password.ProvideChecker(authenticatorPasswordConfig, authenticatorFeatureConfig, historyStore) + expiry := password.ProvideExpiry(authenticatorPasswordConfig, clockClock) + housekeeperLogger := password.NewHousekeeperLogger(factory) + housekeeper := &password.Housekeeper{ + Store: historyStore, + Logger: housekeeperLogger, + Config: authenticatorPasswordConfig, + } + passwordProvider := &password.Provider{ + Store: passwordStore, + Config: authenticatorPasswordConfig, + Clock: clockClock, + Logger: passwordLogger, + PasswordHistory: historyStore, + PasswordChecker: passwordChecker, + Expiry: expiry, + Housekeeper: housekeeper, + } + store4 := &passkey3.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + provider2 := &passkey3.Provider{ + Store: store4, + Clock: clockClock, + Passkey: passkeyService, + } + totpStore := &totp.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + authenticatorTOTPConfig := authenticatorConfig.TOTP + totpProvider := &totp.Provider{ + Store: totpStore, + Config: authenticatorTOTPConfig, + Clock: clockClock, + } + oobStore := &oob.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + oobProvider := &oob.Provider{ + Store: oobStore, + LoginIDNormalizerFactory: normalizerFactory, + Clock: clockClock, + } + testModeConfig := appConfig.TestMode + testModeFeatureConfig := featureConfig.TestMode + codeStoreRedis := &otp.CodeStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + lookupStoreRedis := &otp.LookupStoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + attemptTrackerRedis := &otp.AttemptTrackerRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + otpLogger := otp.NewLogger(factory) + otpService := &otp.Service{ + Clock: clockClock, + AppID: appID, + TestModeConfig: testModeConfig, + TestModeFeatureConfig: testModeFeatureConfig, + RemoteIP: remoteIP, + CodeStore: codeStoreRedis, + LookupStore: lookupStoreRedis, + AttemptTracker: attemptTrackerRedis, + Logger: otpLogger, + RateLimiter: limiter, + } + rateLimits := service2.RateLimits{ + IP: remoteIP, + Config: authenticationConfig, + RateLimiter: limiter, + } + authenticationLockoutConfig := authenticationConfig.Lockout + lockoutLogger := lockout.NewLogger(factory) + lockoutStorageRedis := &lockout.StorageRedis{ + AppID: appID, + Redis: appredisHandle, + } + lockoutService := &lockout.Service{ + Logger: lockoutLogger, + Storage: lockoutStorageRedis, + } + serviceLockout := service2.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + service3 := &service2.Service{ + Store: store3, + Config: appConfig, + Password: passwordProvider, + Passkey: provider2, + TOTP: totpProvider, + OOBOTP: oobProvider, + OTPCodeService: otpService, + RateLimits: rateLimits, + Lockout: serviceLockout, + } + verificationConfig := appConfig.Verification + userProfileConfig := appConfig.UserProfile + storePQ := &verification.StorePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + verificationService := &verification.Service{ + Config: verificationConfig, + UserProfileConfig: userProfileConfig, + Clock: clockClock, + ClaimStore: storePQ, + } + imagesCDNHost := environmentConfig.ImagesCDNHost + pictureTransformer := &stdattrs2.PictureTransformer{ + HTTPProto: httpProto, + HTTPHost: httpHost, + ImagesCDNHost: imagesCDNHost, + } + serviceNoEvent := &stdattrs2.ServiceNoEvent{ + UserProfileConfig: userProfileConfig, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + ClaimStore: storePQ, + Transformer: pictureTransformer, + } + customattrsServiceNoEvent := &customattrs.ServiceNoEvent{ + Config: userProfileConfig, + UserQueries: rawQueries, + UserStore: userStore, + } + nftIndexerAPIEndpoint := environmentConfig.NFTIndexerAPIEndpoint + web3Service := &web3.Service{ + APIEndpoint: nftIndexerAPIEndpoint, + Web3Config: web3Config, + } + rolesgroupsStore := &rolesgroups.Store{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + Clock: clockClock, + } + queries := &rolesgroups.Queries{ + Store: rolesgroupsStore, + } + userQueries := &user.Queries{ + RawQueries: rawQueries, + Store: userStore, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + resolverImpl := &event.ResolverImpl{ + Users: userQueries, + } + hookLogger := hook.NewLogger(factory) + hookConfig := appConfig.Hook + webHookLogger := hook.NewWebHookLogger(factory) + webhookKeyMaterials := deps.ProvideWebhookKeyMaterials(secretConfig) + webHookImpl := hook.WebHookImpl{ + Logger: webHookLogger, + Secret: webhookKeyMaterials, + } + syncHTTPClient := hook.NewSyncHTTPClient(hookConfig) + asyncHTTPClient := hook.NewAsyncHTTPClient() + eventWebHookImpl := &hook.EventWebHookImpl{ + WebHookImpl: webHookImpl, + SyncHTTP: syncHTTPClient, + AsyncHTTP: asyncHTTPClient, + } + denoHookLogger := hook.NewDenoHookLogger(factory) + denoHook := hook.DenoHook{ + Context: contextContext, + ResourceManager: manager, + Logger: denoHookLogger, + } + denoEndpoint := environmentConfig.DenoEndpoint + syncDenoClient := hook.NewSyncDenoClient(denoEndpoint, hookConfig, hookLogger) + asyncDenoClient := hook.NewAsyncDenoClient(denoEndpoint, hookLogger) + eventDenoHookImpl := &hook.EventDenoHookImpl{ + DenoHook: denoHook, + SyncDenoClient: syncDenoClient, + AsyncDenoClient: asyncDenoClient, + } + commands := &rolesgroups.Commands{ + Store: rolesgroupsStore, + } + sink := &hook.Sink{ + Logger: hookLogger, + Config: hookConfig, + Clock: clockClock, + EventWebHook: eventWebHookImpl, + EventDenoHook: eventDenoHookImpl, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + RolesAndGroups: commands, + } + auditLogger := audit.NewLogger(factory) + writeHandle := appProvider.AuditWriteDatabase + auditDatabaseCredentials := deps.ProvideAuditDatabaseCredentials(secretConfig) + auditdbSQLBuilderApp := auditdb.NewSQLBuilderApp(auditDatabaseCredentials, appID) + writeSQLExecutor := auditdb.NewWriteSQLExecutor(contextContext, writeHandle) + writeStore := &audit.WriteStore{ + SQLBuilder: auditdbSQLBuilderApp, + SQLExecutor: writeSQLExecutor, + } + auditSink := &audit.Sink{ + Logger: auditLogger, + Database: writeHandle, + Store: writeStore, + } + elasticsearchLogger := elasticsearch.NewLogger(factory) + elasticsearchServiceLogger := elasticsearch.NewElasticsearchServiceLogger(factory) + elasticsearchCredentials := deps.ProvideElasticsearchCredentials(secretConfig) + client := elasticsearch.NewClient(elasticsearchCredentials) + queue := appProvider.TaskQueue + userReindexProducer := redisqueue.NewUserReindexProducer(appredisHandle, clockClock) + elasticsearchService := elasticsearch.Service{ + Clock: clockClock, + Context: contextContext, + Database: handle, + Logger: elasticsearchServiceLogger, + AppID: appID, + Client: client, + Users: userQueries, + UserStore: userStore, + IdentityService: serviceService, + RolesGroups: rolesgroupsStore, + TaskQueue: queue, + Producer: userReindexProducer, + } + elasticsearchSink := &elasticsearch.Sink{ + Logger: elasticsearchLogger, + Service: elasticsearchService, + Database: handle, + } + eventService := event.NewService(contextContext, appID, remoteIP, userAgentString, eventLogger, handle, clockClock, localizationConfig, storeImpl, resolverImpl, sink, auditSink, elasticsearchSink) + storeDeviceTokenRedis := &mfa.StoreDeviceTokenRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + storeRecoveryCodePQ := &mfa.StoreRecoveryCodePQ{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + mfaLockout := mfa.Lockout{ + Config: authenticationLockoutConfig, + RemoteIP: remoteIP, + Provider: lockoutService, + } + mfaService := &mfa.Service{ + IP: remoteIP, + DeviceTokens: storeDeviceTokenRedis, + RecoveryCodes: storeRecoveryCodePQ, + Clock: clockClock, + Config: authenticationConfig, + RateLimiter: limiter, + Lockout: mfaLockout, + } + messagingLogger := messaging.NewLogger(factory) + usageLogger := usage.NewLogger(factory) + usageLimiter := &usage.Limiter{ + Logger: usageLogger, + Clock: clockClock, + AppID: appID, + Redis: appredisHandle, + } + messagingConfig := appConfig.Messaging + messagingRateLimitsConfig := messagingConfig.RateLimits + messagingFeatureConfig := featureConfig.Messaging + rateLimitsEnvironmentConfig := &environmentConfig.RateLimits + limits := messaging.Limits{ + Logger: messagingLogger, + RateLimiter: limiter, + UsageLimiter: usageLimiter, + RemoteIP: remoteIP, + Config: messagingRateLimitsConfig, + FeatureConfig: messagingFeatureConfig, + EnvConfig: rateLimitsEnvironmentConfig, + } + whatsappServiceLogger := whatsapp.NewServiceLogger(factory) + devMode := environmentConfig.DevMode + featureTestModeWhatsappSuppressed := deps.ProvideTestModeWhatsappSuppressed(testModeFeatureConfig) + testModeWhatsappConfig := testModeConfig.Whatsapp + whatsappConfig := messagingConfig.Whatsapp + whatsappOnPremisesCredentials := deps.ProvideWhatsappOnPremisesCredentials(secretConfig) + tokenStore := &whatsapp.TokenStore{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + onPremisesClient := whatsapp.NewWhatsappOnPremisesClient(whatsappConfig, whatsappOnPremisesCredentials, tokenStore) + whatsappService := &whatsapp.Service{ + Context: contextContext, + Logger: whatsappServiceLogger, + DevMode: devMode, + FeatureTestModeWhatsappSuppressed: featureTestModeWhatsappSuppressed, + TestModeWhatsappConfig: testModeWhatsappConfig, + WhatsappConfig: whatsappConfig, + LocalizationConfig: localizationConfig, + OnPremisesClient: onPremisesClient, + TokenStore: tokenStore, + } + sender := &messaging.Sender{ + Limits: limits, + TaskQueue: queue, + Events: eventService, + Whatsapp: whatsappService, + MessagingFeatureConfig: messagingFeatureConfig, + } + forgotpasswordSender := &forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + rawCommands := &user.RawCommands{ + Store: userStore, + Clock: clockClock, + } + userCommands := &user.Commands{ + RawCommands: rawCommands, + RawQueries: rawQueries, + Events: eventService, + Verification: verificationService, + UserProfileConfig: userProfileConfig, + StandardAttributes: serviceNoEvent, + CustomAttributes: customattrsServiceNoEvent, + Web3: web3Service, + RolesAndGroups: queries, + } + stdattrsService := &stdattrs2.Service{ + UserProfileConfig: userProfileConfig, + ServiceNoEvent: serviceNoEvent, + Identities: serviceService, + UserQueries: rawQueries, + UserStore: userStore, + Events: eventService, + } + authorizationStore := &pq.AuthorizationStore{ + SQLBuilder: sqlBuilderApp, + SQLExecutor: sqlExecutor, + } + storeRedisLogger := idpsession.NewStoreRedisLogger(factory) + idpsessionStoreRedis := &idpsession.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + Logger: storeRedisLogger, + } + sessionConfig := appConfig.Session + cookieDef2 := session.NewSessionCookieDef(sessionConfig) + idpsessionManager := &idpsession.Manager{ + Store: idpsessionStoreRedis, + Config: sessionConfig, + Cookies: cookieManager, + CookieDef: cookieDef2, + } + eventStoreRedis := &access.EventStoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + eventProvider := &access.EventProvider{ + Store: eventStoreRedis, + } + idpsessionRand := _wireRandValue + idpsessionProvider := &idpsession.Provider{ + Context: contextContext, + RemoteIP: remoteIP, + UserAgentString: userAgentString, + AppID: appID, + Redis: appredisHandle, + Store: idpsessionStoreRedis, + AccessEvents: eventProvider, + TrustProxy: trustProxy, + Config: sessionConfig, + Clock: clockClock, + Random: idpsessionRand, + } + offlineGrantService := oauth2.OfflineGrantService{ + OAuthConfig: oAuthConfig, + Clock: clockClock, + IDPSessions: idpsessionProvider, + ClientResolver: resolver, + OfflineGrants: store, + } + sessionManager := &oauth2.SessionManager{ + Store: store, + Config: oAuthConfig, + Service: offlineGrantService, + } + accountDeletionConfig := appConfig.AccountDeletion + accountAnonymizationConfig := appConfig.AccountAnonymization + maxTrials := _wireMaxTrialsValue + passwordRand := password.NewRandSource() + generator := &password.Generator{ + MaxTrials: maxTrials, + Checker: passwordChecker, + Rand: passwordRand, + PasswordConfig: authenticatorPasswordConfig, + } + coordinator := &facade.Coordinator{ + Events: eventService, + Identities: serviceService, + Authenticators: service3, + Verification: verificationService, + MFA: mfaService, + SendPassword: forgotpasswordSender, + UserCommands: userCommands, + UserQueries: userQueries, + RolesGroupsCommands: commands, + StdAttrsService: stdattrsService, + PasswordHistory: historyStore, + OAuth: authorizationStore, + IDPSessions: idpsessionManager, + OAuthSessions: sessionManager, + IdentityConfig: identityConfig, + AccountDeletionConfig: accountDeletionConfig, + AccountAnonymizationConfig: accountAnonymizationConfig, + AuthenticationConfig: authenticationConfig, + Clock: clockClock, + PasswordGenerator: generator, + } + identityFacade := facade.IdentityFacade{ + Coordinator: coordinator, + } + authenticatorFacade := facade.AuthenticatorFacade{ + Coordinator: coordinator, + } + anonymousStoreRedis := &anonymous.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + messageSender := &otp.MessageSender{ + AppID: appID, + Translation: translationService, + Endpoints: endpointsEndpoints, + Sender: sender, + WhatsappService: whatsappService, + } + oAuthSSOProviderCredentials := deps.ProvideOAuthSSOProviderCredentials(secretConfig) + oAuthHTTPClient := sso.ProvideOAuthHTTPClient(environmentConfig) + simpleStoreRedisFactory := &sso.SimpleStoreRedisFactory{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + } + oAuthProviderFactory := &sso.OAuthProviderFactory{ + IdentityConfig: identityConfig, + Credentials: oAuthSSOProviderCredentials, + Clock: clockClock, + StandardAttributesNormalizer: normalizer, + HTTPClient: oAuthHTTPClient, + SimpleStoreRedisFactory: simpleStoreRedisFactory, + } + webappoauthStore := &webappoauth.Store{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + mfaFacade := &facade.MFAFacade{ + Coordinator: coordinator, + } + forgotpasswordLogger := forgotpassword.NewLogger(factory) + sender2 := forgotpassword.Sender{ + AppConfg: appConfig, + Identities: serviceService, + Sender: sender, + Translation: translationService, + } + forgotpasswordService := &forgotpassword.Service{ + Logger: forgotpasswordLogger, + Config: appConfig, + FeatureConfig: featureConfig, + Identities: serviceService, + Authenticators: authenticatorFacade, + OTPCodes: otpService, + OTPSender: messageSender, + PasswordSender: sender2, + } + responseWriter := p.ResponseWriter + nonceService := &nonce.Service{ + Cookies: cookieManager, + Request: request, + ResponseWriter: responseWriter, + } + challengeProvider := &challenge.Provider{ + Redis: appredisHandle, + AppID: appID, + Clock: clockClock, + } + userProvider := &user.Provider{ + Commands: userCommands, + Queries: userQueries, + } + authenticationinfoStoreRedis := &authenticationinfo.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + manager2 := &session.Manager{ + IDPSessions: idpsessionManager, + AccessTokenSessions: sessionManager, + Events: eventService, + } + oauthsessionStoreRedis := &oauthsession.StoreRedis{ + Context: contextContext, + Redis: appredisHandle, + AppID: appID, + } + interactionContext := &interaction.Context{ + Request: request, + RemoteIP: remoteIP, + Database: sqlExecutor, + Clock: clockClock, + Config: appConfig, + FeatureConfig: featureConfig, + OAuthClientResolver: resolver, + OfflineGrants: store, + Identities: identityFacade, + Authenticators: authenticatorFacade, + AnonymousIdentities: anonymousProvider, + AnonymousUserPromotionCodeStore: anonymousStoreRedis, + BiometricIdentities: biometricProvider, + OTPCodeService: otpService, + OTPSender: messageSender, + OAuthProviderFactory: oAuthProviderFactory, + OAuthRedirectURIBuilder: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + MFA: mfaFacade, + ForgotPassword: forgotpasswordService, + ResetPassword: forgotpasswordService, + Passkey: passkeyService, + Verification: verificationService, + RateLimiter: limiter, + PasswordGenerator: generator, + Nonces: nonceService, + Challenges: challengeProvider, + Users: userProvider, + StdAttrsService: stdattrsService, + Events: eventService, + CookieManager: cookieManager, + AuthenticationInfoService: authenticationinfoStoreRedis, + Sessions: idpsessionProvider, + SessionManager: manager2, + SessionCookie: cookieDef2, + OAuthSessions: oauthsessionStoreRedis, + MFADeviceTokenCookie: cookieDef, + } + interactionStoreRedis := &interaction.StoreRedis{ + Redis: appredisHandle, + AppID: appID, + } + interactionService := &interaction.Service{ + Logger: logger, + Context: interactionContext, + Store: interactionStoreRedis, + } + webappService2 := &webapp2.Service2{ + Logger: serviceLogger, + Request: request, + Sessions: sessionStoreRedis, + SessionCookie: sessionCookieDef, + SignedUpCookie: signedUpCookieDef, + MFADeviceTokenCookie: cookieDef, + ErrorService: errorService, + Cookies: cookieManager, + OAuthConfig: oAuthConfig, + UIConfig: uiConfig, + TrustProxy: trustProxy, + UIInfoResolver: uiService, + OAuthClientResolver: resolver, + Graph: interactionService, + } + uiFeatureConfig := featureConfig.UI + forgotPasswordConfig := appConfig.ForgotPassword + googleTagManagerConfig := appConfig.GoogleTagManager + botProtectionConfig := appConfig.BotProtection + flashMessage := &httputil.FlashMessage{ + Cookies: cookieManager, + } + authUISentryDSN := environmentConfig.AuthUISentryDSN + authUIWindowMessageAllowedOrigins := environmentConfig.AuthUIWindowMessageAllowedOrigins + baseLogger := viewmodels.NewBaseLogger(factory) + baseViewModeler := &viewmodels.BaseViewModeler{ + TrustProxy: trustProxy, + OAuth: oAuthConfig, + AuthUI: uiConfig, + AuthUIFeatureConfig: uiFeatureConfig, + StaticAssets: staticAssetResolver, + ForgotPassword: forgotPasswordConfig, + Authentication: authenticationConfig, + GoogleTagManager: googleTagManagerConfig, + BotProtection: botProtectionConfig, + ErrorService: errorService, + Translations: translationService, + Clock: clockClock, + FlashMessage: flashMessage, + DefaultLanguageTag: defaultLanguageTag, + SupportedLanguageTags: supportedLanguageTags, + AuthUISentryDSN: authUISentryDSN, + AuthUIWindowMessageAllowedOrigins: authUIWindowMessageAllowedOrigins, + OAuthClientResolver: resolver, + Logger: baseLogger, + } + responseRenderer := &webapp.ResponseRenderer{ + TemplateEngine: engine, + } + publisher := webapp.NewPublisher(appID, appredisHandle) + authflowNavigator := &webapp2.AuthflowNavigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + authflowV2Navigator := &authflowv2.AuthflowV2Navigator{ + Endpoints: endpointsEndpoints, + OAuthStateStore: webappoauthStore, + } + errorRenderer := &webapp.ErrorRenderer{ + ErrorService: errorService, + UIImplementationService: uiImplementationService, + AuthflowV1Navigator: authflowNavigator, + AuthflowV2Navigator: authflowV2Navigator, + } + controllerDeps := webapp.ControllerDeps{ + Database: handle, + RedisHandle: appredisHandle, + AppID: appID, + Page: webappService2, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + Publisher: publisher, + Clock: clockClock, + TesterEndpointsProvider: endpointsEndpoints, + ErrorRenderer: errorRenderer, + TrustProxy: trustProxy, + } + controllerFactory := webapp.ControllerFactory{ + LoggerFactory: factory, + ControllerDeps: controllerDeps, + } + redisStore := &accountmanagement.RedisStore{ + Context: contextContext, + AppID: appID, + Redis: appredisHandle, + Clock: clockClock, + } + facadeIdentityFacade := &facade.IdentityFacade{ + Coordinator: coordinator, + } + accountmanagementService := &accountmanagement.Service{ + Database: handle, + Config: appConfig, + HTTPOrigin: httpOrigin, + Users: userProvider, + Store: redisStore, + OAuthProvider: oAuthProviderFactory, + Identities: facadeIdentityFacade, + Events: eventService, + OTPSender: messageSender, + OTPCodeService: otpService, + Authenticators: authenticatorFacade, + AuthenticationInfoService: authenticationinfoStoreRedis, + MFA: mfaFacade, + PasskeyService: passkeyService, + Verification: verificationService, + UIInfoResolver: uiService, + } + authflowV2SettingsMFAChangePasswordHandler := &authflowv2.AuthflowV2SettingsMFAChangePasswordHandler{ + ControllerFactory: controllerFactory, + BaseViewModel: baseViewModeler, + Renderer: responseRenderer, + AccountManagementService: accountmanagementService, + PasswordPolicy: passwordChecker, + } + return authflowV2SettingsMFAChangePasswordHandler +} + func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { appProvider := p.AppProvider factory := appProvider.LoggerFactory diff --git a/pkg/auth/wire_handler.go b/pkg/auth/wire_handler.go index 41a18ef844..a4501babbc 100644 --- a/pkg/auth/wire_handler.go +++ b/pkg/auth/wire_handler.go @@ -499,6 +499,13 @@ func newWebAppAuthflowV2SettingsMFAPasswordHandler(p *deps.RequestProvider) http )) } +func newWebAppAuthflowV2SettingsMFAChangePasswordHandler(p *deps.RequestProvider) http.Handler { + panic(wire.Build( + DependencySet, + wire.Bind(new(http.Handler), new(*handlerwebappauthflowv2.AuthflowV2SettingsMFAChangePasswordHandler)), + )) +} + func newWebAppSettingsTOTPHandler(p *deps.RequestProvider) http.Handler { panic(wire.Build( DependencySet, diff --git a/resources/authgear/templates/en/translation.json b/resources/authgear/templates/en/translation.json index e5a6bea32f..01c58995ac 100644 --- a/resources/authgear/templates/en/translation.json +++ b/resources/authgear/templates/en/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Username", "v2.page.settings-identity.default.verification-status-unverified-label": "Not Verified", "v2.page.settings-identity.default.verification-status-verified-label": "Verified", + "v2.page.settings-mfa-change-password.default.title": "Change Password", "v2.page.settings-mfa-create-oob-otp.default.title": "2-Step Verification", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "You can receive verification codes at this email.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "You can receive verification codes at this number.", diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_change_password.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_change_password.html new file mode 100644 index 0000000000..1c61def0fd --- /dev/null +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_change_password.html @@ -0,0 +1,122 @@ +{{ template "authflowv2/__settings_page_frame.html" . }} + +{{ define "page-navbar" }} + {{ template "authflowv2/__navbar.html" + (dict + "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) + "BackHref" (call $.MakeURL "/settings") + "Title" (translate "v2.page.settings-change-password.default.title" nil) + ) + }} +{{ end }} + +{{ define "page-content" }} + +{{ $err_map := (resolveError $.RawError (dict + "oldPasswordField" (dict + "by_reason" (list "InvalidCredentials") + "by_location" (list "x_old_password") + ) + "newPasswordField" (dict + "by_reason" (list "PasswordPolicyViolated") + "by_location" (list "x_password") + ) + "confirmPasswordField" (dict + "by_reason" (list "PasswordPolicyViolated") + "by_location" (list "x_confirm_password") + ) +)) }} + +{{ $old_pw_err := index $err_map "oldPasswordField" }} +{{ $new_pw_err := index $err_map "newPasswordField" }} +{{ $confirm_pw_err := index $err_map "confirmPasswordField" }} +{{ $unknown_err := index $err_map "unknown" }} + +{{ $has_old_pw_err := not (isNil $old_pw_err )}} +{{ $has_new_pw_err := not (isNil $new_pw_err )}} +{{ $has_confirm_pw_err := not (isNil $confirm_pw_err )}} +{{ $has_unknown_err := not (isNil $unknown_err )}} + + +{{ $old_pw_error_message := ""}} +{{ if $has_old_pw_err }} + {{ $old_pw_error_message = (include "authflowv2/__error.html" (merge (dict "Error" $old_pw_err) $)) }} +{{ end }} + +{{ $new_pw_error_message := ""}} +{{ if $has_new_pw_err }} + {{ $new_pw_error_message = (include "authflowv2/__error.html" (merge (dict "Error" $new_pw_err) $)) }} +{{ end }} + +{{ $confirm_pw_error_message := ""}} +{{ if $has_confirm_pw_err }} + {{ $confirm_pw_error_message = (include "authflowv2/__error.html" (merge (dict "Error" $confirm_pw_err) $)) }} +{{ end }} + +{{ $unknown_error_message := "" }} +{{ if $has_unknown_err }} + {{ $unknown_error_message = (include "authflowv2/__error.html" (merge (dict "Error" $unknown_err) $)) }} +{{ end }} + +
+ {{ template "authflowv2/__alert_message.html" + (dict + "Type" "error" + "Classname" "mb-4" + "Message" $unknown_error_message + ) + }} +
+ {{ $.CSRFField }} + +
+ + + {{ if $.PasswordManagerUsername }} + + {{ end }} + {{ template "authflowv2/__password_field.html" (dict + "Ctx" $ + "Name" "x_old_password" + "Type" "old-password" + "AutoFocus" $.ShouldFocusInput + "Classname" "w-full" + "HasError" $has_old_pw_err + "ErrorMessage" $old_pw_error_message + ) }} + + {{ template "authflowv2/__new_password_field.html" (dict + "Ctx" $ + "NewPasswordInputName" "x_new_password" + "ConfirmPasswordInputName" "x_confirm_password" + "AutoFocus" $.ShouldFocusInput + "PasswordRules" $.PasswordRulesString + "PasswordPolicies" $.PasswordPolicies + "HasNewPasswordError" $has_new_pw_err + "NewPasswordErrorMessage" $new_pw_error_message + "HasConfirmPasswordError" $has_confirm_pw_err + "ConfirmPasswordErrorMessage" $confirm_pw_error_message + ) + }} +
+ + + +
+
+ +{{ end }} diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_password.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_password.html index a50f0789cc..4e6ec715c4 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa_password.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_password.html @@ -20,7 +20,7 @@ {{ template "authflowv2/__settings_item.html" (dict "Label" (translate "v2.page.settings-mfa-password.default.additional-password-label" nil) - "Href" (call $.MakeURL "/settings/mfa/edit_password") + "Href" (call $.MakeURL "/settings/mfa/change_password") "SupplementaryNote" (include "__settings_mfa_password_description.html" (dict "SecondaryPassword.html" $.SecondaryPassword)) ) }} @@ -68,7 +68,7 @@ }} {{ else }} {{ - $supplementaryNote = (translate "v2.page.settings-mfa-password.default.additional-password-updated-at" (dict + (translate "v2.page.settings-mfa-password.default.additional-password-updated-at" (dict "time" $.SecondaryPassword.UpdatedAt "rfc3339" (rfc3339 $.SecondaryPassword.UpdatedAt) )) From 54923d5d7c5e206863cd24780af578523abe7549 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Thu, 3 Oct 2024 18:13:35 +0800 Subject: [PATCH 27/31] Generate translations --- resources/authgear/templates/de/translation.json | 1 + resources/authgear/templates/el/translation.json | 1 + resources/authgear/templates/es-419/translation.json | 1 + resources/authgear/templates/es-ES/translation.json | 1 + resources/authgear/templates/es/translation.json | 1 + resources/authgear/templates/fil/translation.json | 1 + resources/authgear/templates/fr/translation.json | 1 + resources/authgear/templates/id/translation.json | 1 + resources/authgear/templates/it/translation.json | 1 + resources/authgear/templates/ja/translation.json | 1 + resources/authgear/templates/ko/translation.json | 1 + resources/authgear/templates/ms/translation.json | 1 + resources/authgear/templates/nl/translation.json | 1 + resources/authgear/templates/pl/translation.json | 1 + resources/authgear/templates/pt-BR/translation.json | 1 + resources/authgear/templates/pt-PT/translation.json | 1 + resources/authgear/templates/pt/translation.json | 1 + resources/authgear/templates/th/translation.json | 1 + resources/authgear/templates/vi/translation.json | 1 + resources/authgear/templates/zh-CN/translation.json | 1 + resources/authgear/templates/zh-HK/translation.json | 1 + resources/authgear/templates/zh-TW/translation.json | 1 + 22 files changed, 22 insertions(+) diff --git a/resources/authgear/templates/de/translation.json b/resources/authgear/templates/de/translation.json index 257e9bca34..1d6b87624f 100644 --- a/resources/authgear/templates/de/translation.json +++ b/resources/authgear/templates/de/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Benutzername", "v2.page.settings-identity.default.verification-status-unverified-label": "Nicht verifiziert", "v2.page.settings-identity.default.verification-status-verified-label": "Verifiziert", + "v2.page.settings-mfa-change-password.default.title": "Passwort ändern", "v2.page.settings-mfa-create-oob-otp.default.title": "2-Stufen-Verifizierung", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Sie können Verifizierungscodes an diese E-Mail-Adresse erhalten.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Sie können Verifizierungscodes an diese Nummer erhalten.", diff --git a/resources/authgear/templates/el/translation.json b/resources/authgear/templates/el/translation.json index 1f55060b32..dbf6532530 100644 --- a/resources/authgear/templates/el/translation.json +++ b/resources/authgear/templates/el/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Όνομα Χρήστη", "v2.page.settings-identity.default.verification-status-unverified-label": "Μη επαληθευμένο", "v2.page.settings-identity.default.verification-status-verified-label": "Επαληθευμένο", + "v2.page.settings-mfa-change-password.default.title": "Αλλαγή Κωδικού Πρόσβασης", "v2.page.settings-mfa-create-oob-otp.default.title": "Επαλήθευση Δύο Βημάτων", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Μπορείτε να λαμβάνετε κωδικούς επαλήθευσης σε αυτό το email.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Μπορείτε να λαμβάνετε κωδικούς επαλήθευσης σε αυτόν τον αριθμό.", diff --git a/resources/authgear/templates/es-419/translation.json b/resources/authgear/templates/es-419/translation.json index bf9e48b804..5a61a14b31 100644 --- a/resources/authgear/templates/es-419/translation.json +++ b/resources/authgear/templates/es-419/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nombre de usuario", "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-change-password.default.title": "Cambiar contraseña", "v2.page.settings-mfa-create-oob-otp.default.title": "Verificación de 2 pasos", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Puedes recibir códigos de verificación en este correo electrónico.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Puedes recibir códigos de verificación en este número.", diff --git a/resources/authgear/templates/es-ES/translation.json b/resources/authgear/templates/es-ES/translation.json index 3abab65348..f1494df2f8 100644 --- a/resources/authgear/templates/es-ES/translation.json +++ b/resources/authgear/templates/es-ES/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nombre de usuario", "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-change-password.default.title": "Cambiar contraseña", "v2.page.settings-mfa-create-oob-otp.default.title": "Verificación en 2 pasos", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Puedes recibir códigos de verificación en este correo electrónico.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Puedes recibir códigos de verificación en este número.", diff --git a/resources/authgear/templates/es/translation.json b/resources/authgear/templates/es/translation.json index ae61640b9c..a80205f246 100644 --- a/resources/authgear/templates/es/translation.json +++ b/resources/authgear/templates/es/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nombre de usuario", "v2.page.settings-identity.default.verification-status-unverified-label": "No verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-change-password.default.title": "Cambiar contraseña", "v2.page.settings-mfa-create-oob-otp.default.title": "Verificación de 2 pasos", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Puedes recibir códigos de verificación en este correo electrónico.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Puedes recibir códigos de verificación en este número.", diff --git a/resources/authgear/templates/fil/translation.json b/resources/authgear/templates/fil/translation.json index 776ba09ffa..976fc6221e 100644 --- a/resources/authgear/templates/fil/translation.json +++ b/resources/authgear/templates/fil/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Username", "v2.page.settings-identity.default.verification-status-unverified-label": "Hindi Napatunayan", "v2.page.settings-identity.default.verification-status-verified-label": "Napatunayan", + "v2.page.settings-mfa-change-password.default.title": "Palitan ang Password", "v2.page.settings-mfa-create-oob-otp.default.title": "2-Hakbang na Pag-verify", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Maaari kang makatanggap ng mga code sa pag-verify sa email na ito.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Maaari kang makatanggap ng mga code sa pag-verify sa numerong ito.", diff --git a/resources/authgear/templates/fr/translation.json b/resources/authgear/templates/fr/translation.json index b62655d7a4..74cad164f3 100644 --- a/resources/authgear/templates/fr/translation.json +++ b/resources/authgear/templates/fr/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nom d''utilisateur", "v2.page.settings-identity.default.verification-status-unverified-label": "Non vérifié", "v2.page.settings-identity.default.verification-status-verified-label": "Vérifié", + "v2.page.settings-mfa-change-password.default.title": "Changer le mot de passe", "v2.page.settings-mfa-create-oob-otp.default.title": "Vérification en 2 étapes", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Vous pouvez recevoir des codes de vérification à cette adresse e-mail.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Vous pouvez recevoir des codes de vérification à ce numéro.", diff --git a/resources/authgear/templates/id/translation.json b/resources/authgear/templates/id/translation.json index 030b898034..7864890319 100644 --- a/resources/authgear/templates/id/translation.json +++ b/resources/authgear/templates/id/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nama Pengguna", "v2.page.settings-identity.default.verification-status-unverified-label": "Tidak Diverifikasi", "v2.page.settings-identity.default.verification-status-verified-label": "Diverifikasi", + "v2.page.settings-mfa-change-password.default.title": "Ubah Kata Sandi", "v2.page.settings-mfa-create-oob-otp.default.title": "Verifikasi 2-Langkah", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Anda dapat menerima kode verifikasi di email ini.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Anda dapat menerima kode verifikasi di nomor ini.", diff --git a/resources/authgear/templates/it/translation.json b/resources/authgear/templates/it/translation.json index 772d9e5c00..abd4629468 100644 --- a/resources/authgear/templates/it/translation.json +++ b/resources/authgear/templates/it/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nome Utente", "v2.page.settings-identity.default.verification-status-unverified-label": "Non verificato", "v2.page.settings-identity.default.verification-status-verified-label": "Verificato", + "v2.page.settings-mfa-change-password.default.title": "Cambia Password", "v2.page.settings-mfa-create-oob-otp.default.title": "Verifica in due passaggi", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Puoi ricevere i codici di verifica a questa email.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Puoi ricevere i codici di verifica a questo numero.", diff --git a/resources/authgear/templates/ja/translation.json b/resources/authgear/templates/ja/translation.json index 442479fbcb..7edf8fc791 100644 --- a/resources/authgear/templates/ja/translation.json +++ b/resources/authgear/templates/ja/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "ユーザー名", "v2.page.settings-identity.default.verification-status-unverified-label": "未検証", "v2.page.settings-identity.default.verification-status-verified-label": "検証済み", + "v2.page.settings-mfa-change-password.default.title": "パスワードの変更", "v2.page.settings-mfa-create-oob-otp.default.title": "2段階認証", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "この電子メールで認証コードを受け取ることができます。", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "この番号で認証コードを受け取ることができます。", diff --git a/resources/authgear/templates/ko/translation.json b/resources/authgear/templates/ko/translation.json index d895549815..0181190026 100644 --- a/resources/authgear/templates/ko/translation.json +++ b/resources/authgear/templates/ko/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "사용자 이름", "v2.page.settings-identity.default.verification-status-unverified-label": "인증되지 않음", "v2.page.settings-identity.default.verification-status-verified-label": "인증됨", + "v2.page.settings-mfa-change-password.default.title": "비밀번호 변경", "v2.page.settings-mfa-create-oob-otp.default.title": "2단계 인증", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "이 이메일로 인증 코드를 받을 수 있습니다.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "이 번호로 인증 코드를 받을 수 있습니다.", diff --git a/resources/authgear/templates/ms/translation.json b/resources/authgear/templates/ms/translation.json index 899d29335b..7509e81d1d 100644 --- a/resources/authgear/templates/ms/translation.json +++ b/resources/authgear/templates/ms/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nama Pengguna", "v2.page.settings-identity.default.verification-status-unverified-label": "Tidak Disahkan", "v2.page.settings-identity.default.verification-status-verified-label": "Disahkan", + "v2.page.settings-mfa-change-password.default.title": "Tukar Kata Laluan", "v2.page.settings-mfa-create-oob-otp.default.title": "Pengesahan 2-Langkah", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Anda boleh menerima kod pengesahan di email ini.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Anda boleh menerima kod pengesahan di nombor ini.", diff --git a/resources/authgear/templates/nl/translation.json b/resources/authgear/templates/nl/translation.json index a9e0a68c3a..c01933ba2c 100644 --- a/resources/authgear/templates/nl/translation.json +++ b/resources/authgear/templates/nl/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Gebruikersnaam", "v2.page.settings-identity.default.verification-status-unverified-label": "Niet geverifieerd", "v2.page.settings-identity.default.verification-status-verified-label": "Geverifieerd", + "v2.page.settings-mfa-change-password.default.title": "Wachtwoord wijzigen", "v2.page.settings-mfa-create-oob-otp.default.title": "2-Staps Verificatie", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Je kunt verificatiecodes ontvangen op dit e-mailadres.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Je kunt verificatiecodes ontvangen op dit nummer.", diff --git a/resources/authgear/templates/pl/translation.json b/resources/authgear/templates/pl/translation.json index c248edd329..5d28f746cc 100644 --- a/resources/authgear/templates/pl/translation.json +++ b/resources/authgear/templates/pl/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nazwa użytkownika", "v2.page.settings-identity.default.verification-status-unverified-label": "Niezweryfikowane", "v2.page.settings-identity.default.verification-status-verified-label": "Zweryfikowane", + "v2.page.settings-mfa-change-password.default.title": "Zmień hasło", "v2.page.settings-mfa-create-oob-otp.default.title": "Weryfikacja dwuetapowa", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Możesz otrzymywać kody weryfikacyjne na ten adres e-mail.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Możesz otrzymywać kody weryfikacyjne na ten numer.", diff --git a/resources/authgear/templates/pt-BR/translation.json b/resources/authgear/templates/pt-BR/translation.json index b5f07c00fc..a38d7ef27d 100644 --- a/resources/authgear/templates/pt-BR/translation.json +++ b/resources/authgear/templates/pt-BR/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nome de Usuário", "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-change-password.default.title": "Alterar Senha", "v2.page.settings-mfa-create-oob-otp.default.title": "Verificação de 2 Etapas", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Você pode receber códigos de verificação neste e-mail.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Você pode receber códigos de verificação neste número.", diff --git a/resources/authgear/templates/pt-PT/translation.json b/resources/authgear/templates/pt-PT/translation.json index 838276c450..1aafc8ae16 100644 --- a/resources/authgear/templates/pt-PT/translation.json +++ b/resources/authgear/templates/pt-PT/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nome de Utilizador", "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-change-password.default.title": "Alterar Palavra-passe", "v2.page.settings-mfa-create-oob-otp.default.title": "Verificação de 2 Passos", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Pode receber códigos de verificação neste email.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Pode receber códigos de verificação neste número.", diff --git a/resources/authgear/templates/pt/translation.json b/resources/authgear/templates/pt/translation.json index 305244fb6c..631dce5610 100644 --- a/resources/authgear/templates/pt/translation.json +++ b/resources/authgear/templates/pt/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Nome de Usuário", "v2.page.settings-identity.default.verification-status-unverified-label": "Não Verificado", "v2.page.settings-identity.default.verification-status-verified-label": "Verificado", + "v2.page.settings-mfa-change-password.default.title": "Alterar Senha", "v2.page.settings-mfa-create-oob-otp.default.title": "Verificação de 2 Etapas", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Você pode receber códigos de verificação neste e-mail.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Você pode receber códigos de verificação neste número.", diff --git a/resources/authgear/templates/th/translation.json b/resources/authgear/templates/th/translation.json index cb5dc200e1..ce48b5e215 100644 --- a/resources/authgear/templates/th/translation.json +++ b/resources/authgear/templates/th/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "ชื่อผู้ใช้", "v2.page.settings-identity.default.verification-status-unverified-label": "ยังไม่ได้รับการยืนยัน", "v2.page.settings-identity.default.verification-status-verified-label": "ได้รับการยืนยันแล้ว", + "v2.page.settings-mfa-change-password.default.title": "เปลี่ยนรหัสผ่าน", "v2.page.settings-mfa-create-oob-otp.default.title": "การยืนยันตัวตนสองขั้นตอน", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "คุณสามารถรับรหัสยืนยันที่อีเมลนี้", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "คุณสามารถรับรหัสยืนยันที่หมายเลขนี้", diff --git a/resources/authgear/templates/vi/translation.json b/resources/authgear/templates/vi/translation.json index bcadb500f8..e77d3c0d08 100644 --- a/resources/authgear/templates/vi/translation.json +++ b/resources/authgear/templates/vi/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "Tên người dùng", "v2.page.settings-identity.default.verification-status-unverified-label": "Chưa được xác minh", "v2.page.settings-identity.default.verification-status-verified-label": "Đã xác minh", + "v2.page.settings-mfa-change-password.default.title": "Thay đổi Mật khẩu", "v2.page.settings-mfa-create-oob-otp.default.title": "Xác minh 2 bước", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "Bạn có thể nhận mã xác minh tại email này.", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "Bạn có thể nhận mã xác minh tại số này.", diff --git a/resources/authgear/templates/zh-CN/translation.json b/resources/authgear/templates/zh-CN/translation.json index 2e1ecbc7d8..7763096719 100644 --- a/resources/authgear/templates/zh-CN/translation.json +++ b/resources/authgear/templates/zh-CN/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "用户名", "v2.page.settings-identity.default.verification-status-unverified-label": "未验证", "v2.page.settings-identity.default.verification-status-verified-label": "已验证", + "v2.page.settings-mfa-change-password.default.title": "更改密码", "v2.page.settings-mfa-create-oob-otp.default.title": "双重验证", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "您可以在此电子邮件地址接收验证码。", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "您可以在此号码接收验证码。", diff --git a/resources/authgear/templates/zh-HK/translation.json b/resources/authgear/templates/zh-HK/translation.json index ca2113e415..e622327650 100644 --- a/resources/authgear/templates/zh-HK/translation.json +++ b/resources/authgear/templates/zh-HK/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "用戶名稱", "v2.page.settings-identity.default.verification-status-unverified-label": "未驗證", "v2.page.settings-identity.default.verification-status-verified-label": "已驗證", + "v2.page.settings-mfa-change-password.default.title": "更改密碼", "v2.page.settings-mfa-create-oob-otp.default.title": "雙重驗證", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "您可以在此電子郵件地址接收驗證碼。", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "您可以在此號碼接收驗證碼。", diff --git a/resources/authgear/templates/zh-TW/translation.json b/resources/authgear/templates/zh-TW/translation.json index b4d79e35a1..be09817a94 100644 --- a/resources/authgear/templates/zh-TW/translation.json +++ b/resources/authgear/templates/zh-TW/translation.json @@ -1180,6 +1180,7 @@ "v2.page.settings-identity-view-username.default.title": "用戶名稱", "v2.page.settings-identity.default.verification-status-unverified-label": "未驗證", "v2.page.settings-identity.default.verification-status-verified-label": "已驗證", + "v2.page.settings-mfa-change-password.default.title": "更改密碼", "v2.page.settings-mfa-create-oob-otp.default.title": "雙重驗證", "v2.page.settings-mfa-create-oob-otp.email.subtitle": "您可以在此電子郵件地址接收驗證碼。", "v2.page.settings-mfa-create-oob-otp.sms.subtitle": "您可以在此號碼接收驗證碼。", From 92bbdbaef3a62d988e7cbed347d4783dac1e8129 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 7 Oct 2024 13:58:37 +0800 Subject: [PATCH 28/31] Remove back action in settings v2 mfa recover code page --- .../authflowv2/settings_mfa_view_recovery_code.html | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html b/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html index f85cf163fe..6b8295fd89 100644 --- a/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html +++ b/resources/authgear/templates/en/web/authflowv2/settings_mfa_view_recovery_code.html @@ -1,21 +1,8 @@ {{ template "authflowv2/__settings_page_frame.html" . }} {{ define "page-navbar" }} - {{ $back_url := (call $.MakeURL "/settings/mfa") }} - {{- if eq $.AuthenticatorType "totp" }} - {{ $back_url = (call $.MakeURL "/settings/mfa/totp") }} - {{- end }} - {{- if eq $.AuthenticatorType "oob_otp_sms" }} - {{ $back_url = (call $.MakeURL "/settings/mfa/oob_otp_sms") }} - {{- end }} - {{- if eq $.AuthenticatorType "oob_otp_email" }} - {{ $back_url = (call $.MakeURL "/settings/mfa/oob_otp_email") }} - {{ end }} - {{ template "authflowv2/__navbar.html" (dict - "BackTitle" (translate "v2.component.navbar.default.item-back-button-label" nil) - "BackHref" $back_url "Title" (translate "v2.page.settings-mfa-view-recovery-code.default.title" nil) ) }} From 634ec0f23dc52532bb2bca2e3a568b769dbed6e0 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 7 Oct 2024 16:00:34 +0800 Subject: [PATCH 29/31] Update wire_gen --- pkg/auth/wire_gen.go | 126 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 24 deletions(-) diff --git a/pkg/auth/wire_gen.go b/pkg/auth/wire_gen.go index 97332115a2..6636149f46 100644 --- a/pkg/auth/wire_gen.go +++ b/pkg/auth/wire_gen.go @@ -52895,14 +52895,12 @@ func newWebAppAuthflowV2SettingsMFAViewRecoveryCodeHandler(p *deps.RequestProvid Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := &ratelimit.StorageRedis{ - AppID: appID, - Redis: appredisHandle, - } + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, + AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -53339,6 +53337,18 @@ func newWebAppAuthflowV2SettingsMFAViewRecoveryCodeHandler(p *deps.RequestProvid eventProvider := &access.EventProvider{ Store: eventStoreRedis, } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -53348,6 +53358,7 @@ func newWebAppAuthflowV2SettingsMFAViewRecoveryCodeHandler(p *deps.RequestProvid Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, + MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -53358,6 +53369,8 @@ func newWebAppAuthflowV2SettingsMFAViewRecoveryCodeHandler(p *deps.RequestProvid Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ @@ -55828,14 +55841,12 @@ func newWebAppAuthflowV2SettingsMFAChangePasswordHandler(p *deps.RequestProvider Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := &ratelimit.StorageRedis{ - AppID: appID, - Redis: appredisHandle, - } + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, + AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -56272,6 +56283,18 @@ func newWebAppAuthflowV2SettingsMFAChangePasswordHandler(p *deps.RequestProvider eventProvider := &access.EventProvider{ Store: eventStoreRedis, } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -56281,6 +56304,7 @@ func newWebAppAuthflowV2SettingsMFAChangePasswordHandler(p *deps.RequestProvider Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, + MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -56291,6 +56315,8 @@ func newWebAppAuthflowV2SettingsMFAChangePasswordHandler(p *deps.RequestProvider Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ @@ -58730,14 +58756,12 @@ func newWebAppAuthflowV2SettingsMFACreateTOTPHandler(p *deps.RequestProvider) ht Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := &ratelimit.StorageRedis{ - AppID: appID, - Redis: appredisHandle, - } + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, + AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -59174,6 +59198,18 @@ func newWebAppAuthflowV2SettingsMFACreateTOTPHandler(p *deps.RequestProvider) ht eventProvider := &access.EventProvider{ Store: eventStoreRedis, } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -59183,6 +59219,7 @@ func newWebAppAuthflowV2SettingsMFACreateTOTPHandler(p *deps.RequestProvider) ht Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, + MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -59193,6 +59230,8 @@ func newWebAppAuthflowV2SettingsMFACreateTOTPHandler(p *deps.RequestProvider) ht Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ @@ -59702,14 +59741,12 @@ func newWebAppAuthflowV2SettingsMFAEnterTOTPHandler(p *deps.RequestProvider) htt Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := &ratelimit.StorageRedis{ - AppID: appID, - Redis: appredisHandle, - } + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, + AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -60146,6 +60183,18 @@ func newWebAppAuthflowV2SettingsMFAEnterTOTPHandler(p *deps.RequestProvider) htt eventProvider := &access.EventProvider{ Store: eventStoreRedis, } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -60155,6 +60204,7 @@ func newWebAppAuthflowV2SettingsMFAEnterTOTPHandler(p *deps.RequestProvider) htt Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, + MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -60165,6 +60215,8 @@ func newWebAppAuthflowV2SettingsMFAEnterTOTPHandler(p *deps.RequestProvider) htt Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ @@ -61664,14 +61716,12 @@ func newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler(p *deps.RequestProvider) Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := &ratelimit.StorageRedis{ - AppID: appID, - Redis: appredisHandle, - } + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, + AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -62108,6 +62158,18 @@ func newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler(p *deps.RequestProvider) eventProvider := &access.EventProvider{ Store: eventStoreRedis, } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -62117,6 +62179,7 @@ func newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler(p *deps.RequestProvider) Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, + MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -62127,6 +62190,8 @@ func newWebAppAuthflowV2SettingsMFACreateOOBOTPHandler(p *deps.RequestProvider) Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ @@ -62627,14 +62692,12 @@ func newWebAppAuthflowV2SettingsMFAEnterOOBOTPHandler(p *deps.RequestProvider) h Clock: clockClock, } ratelimitLogger := ratelimit.NewLogger(factory) - storageRedis := &ratelimit.StorageRedis{ - AppID: appID, - Redis: appredisHandle, - } + storageRedis := ratelimit.NewAppStorageRedis(appredisHandle) rateLimitsFeatureConfig := featureConfig.RateLimits limiter := &ratelimit.Limiter{ Logger: ratelimitLogger, Storage: storageRedis, + AppID: appID, Config: rateLimitsFeatureConfig, } siweLogger := siwe2.NewLogger(factory) @@ -63071,6 +63134,18 @@ func newWebAppAuthflowV2SettingsMFAEnterOOBOTPHandler(p *deps.RequestProvider) h eventProvider := &access.EventProvider{ Store: eventStoreRedis, } + analyticredisHandle := appProvider.AnalyticRedis + meterStoreRedisLogger := meter.NewStoreRedisLogger(factory) + writeStoreRedis := &meter.WriteStoreRedis{ + Context: contextContext, + Redis: analyticredisHandle, + AppID: appID, + Clock: clockClock, + Logger: meterStoreRedisLogger, + } + meterService := &meter.Service{ + Counter: writeStoreRedis, + } idpsessionRand := _wireRandValue idpsessionProvider := &idpsession.Provider{ Context: contextContext, @@ -63080,6 +63155,7 @@ func newWebAppAuthflowV2SettingsMFAEnterOOBOTPHandler(p *deps.RequestProvider) h Redis: appredisHandle, Store: idpsessionStoreRedis, AccessEvents: eventProvider, + MeterService: meterService, TrustProxy: trustProxy, Config: sessionConfig, Clock: clockClock, @@ -63090,6 +63166,8 @@ func newWebAppAuthflowV2SettingsMFAEnterOOBOTPHandler(p *deps.RequestProvider) h Clock: clockClock, IDPSessions: idpsessionProvider, ClientResolver: resolver, + AccessEvents: eventProvider, + MeterService: meterService, OfflineGrants: store, } sessionManager := &oauth2.SessionManager{ From bc584cd58efb10a069550d098712439616bea50b Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 7 Oct 2024 17:09:33 +0800 Subject: [PATCH 30/31] Resolve lint errors --- pkg/auth/handler/webapp/authflowv2/routes.go | 3 ++- .../handler/webapp/authflowv2/settings_mfa_create_oob_otp.go | 3 +++ .../handler/webapp/authflowv2/settings_mfa_create_totp.go | 5 +++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/auth/handler/webapp/authflowv2/routes.go b/pkg/auth/handler/webapp/authflowv2/routes.go index 2b0014eb71..ad54616437 100644 --- a/pkg/auth/handler/webapp/authflowv2/routes.go +++ b/pkg/auth/handler/webapp/authflowv2/routes.go @@ -88,7 +88,8 @@ const ( AuthflowV2RouteSettingsMFACreateOOBOTP = "/settings/mfa/create_oob_otp_:channel" AuthflowV2RouteSettingsMFAEnterOOBOTP = "/settings/mfa/enter_oob_otp" // nolint: gosec - AuthflowV2RouteSettingsMFAPassword = "/settings/mfa/password" + AuthflowV2RouteSettingsMFAPassword = "/settings/mfa/password" + // nolint: gosec AuthflowV2RouteSettingsMFAChangePassword = "/settings/mfa/change_password" AuthflowV2RouteSettingsMFACreateTOTP = "/settings/mfa/create_totp" AuthflowV2RouteSettingsMFAEnterTOTP = "/settings/mfa/enter_totp" diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_oob_otp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_oob_otp.go index 97433ba6bf..307c458722 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_oob_otp.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_oob_otp.go @@ -105,6 +105,9 @@ func (h *AuthflowV2SettingsMFACreateOOBOTPHandler) ServeHTTP(w http.ResponseWrit Channel: channel, Target: target, }) + if err != nil { + return err + } redirectURI, err := url.Parse(AuthflowV2RouteSettingsMFAEnterOOBOTP) if err != nil { diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_totp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_totp.go index 433052a817..9c04978b12 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_totp.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_create_totp.go @@ -71,8 +71,9 @@ func (h *AuthflowV2SettingsMFACreateTOTPHandler) GetData(r *http.Request, rw htt } screenViewModel := AuthflowV2SettingsMFACreateTOTPViewModel{ - Token: tokenString, - Secret: totpSecret, + Token: tokenString, + Secret: totpSecret, + // nolint: gosec ImageURI: htmltemplate.URL(dataURI), } viewmodels.Embed(data, screenViewModel) From 7bca2bead7dce1fd077f0c3152ae72269e625044 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Tue, 8 Oct 2024 18:59:05 +0800 Subject: [PATCH 31/31] Skip 2fa recovery code step if disabled or exists in settings v2 --- .../authflowv2/settings_mfa_enter_oob_otp.go | 30 +++++-- .../authflowv2/settings_mfa_enter_totp.go | 30 +++++-- pkg/lib/accountmanagement/redis_store.go | 7 +- pkg/lib/accountmanagement/service.go | 1 + .../service_authenticator.go | 81 +++++++++++++------ pkg/lib/accountmanagement/token.go | 9 ++- 6 files changed, 114 insertions(+), 44 deletions(-) diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_oob_otp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_oob_otp.go index 69276692eb..778634f0c2 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_oob_otp.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_oob_otp.go @@ -177,6 +177,8 @@ func (h *AuthflowV2SettingsMFAEnterOOBOTPHandler) ServeHTTP(w http.ResponseWrite return err } + s := session.GetSession(r.Context()) + tokenString := r.Form.Get("q_token") code := r.Form.Get("x_code") @@ -187,13 +189,29 @@ func (h *AuthflowV2SettingsMFAEnterOOBOTPHandler) ServeHTTP(w http.ResponseWrite return err } - redirectURI, err := url.Parse(AuthflowV2RouteSettingsMFAViewRecoveryCode) - if err != nil { - return err + var redirectURI *url.URL + if output.RecoveryCodesCreated { + redirectURI, err = url.Parse(AuthflowV2RouteSettingsMFAViewRecoveryCode) + if err != nil { + return err + } + q := redirectURI.Query() + q.Set("q_token", output.Token) + redirectURI.RawQuery = q.Encode() + } else { + _, err = h.AccountManagement.FinishAddOOBOTPAuthenticator(s, output.Token, &accountmanagement.FinishAddOOBOTPAuthenticatorInput{}) + if err != nil { + return err + } + + redirectURI, err = url.Parse(AuthflowV2RouteSettingsMFA) + if err != nil { + return err + } + q := redirectURI.Query() + q.Set("q_token", output.Token) + redirectURI.RawQuery = q.Encode() } - q := redirectURI.Query() - q.Set("q_token", output.Token) - redirectURI.RawQuery = q.Encode() result := webapp.Result{RedirectURI: redirectURI.String()} result.WriteResponse(w, r) diff --git a/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_totp.go b/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_totp.go index 8f1549ce6d..165dd26f8b 100644 --- a/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_totp.go +++ b/pkg/auth/handler/webapp/authflowv2/settings_mfa_enter_totp.go @@ -114,16 +114,30 @@ func (h *AuthflowV2SettingsMFAEnterTOTPHandler) ServeHTTP(w http.ResponseWriter, return err } - redirectURI, err := url.Parse(AuthflowV2RouteSettingsMFAViewRecoveryCode) - if err != nil { - return err + var redirectURI *url.URL + if output.RecoveryCodesCreated { + redirectURI, err = url.Parse(AuthflowV2RouteSettingsMFAViewRecoveryCode) + if err != nil { + return err + } + q := redirectURI.Query() + q.Set("q_token", output.Token) + redirectURI.RawQuery = q.Encode() + } else { + _, err = h.AccountManagement.FinishAddTOTPAuthenticator(s, output.Token, &accountmanagement.FinishAddTOTPAuthenticatorInput{}) + if err != nil { + return err + } + + redirectURI, err = url.Parse(AuthflowV2RouteSettingsMFA) + if err != nil { + return err + } + q := redirectURI.Query() + q.Set("q_token", output.Token) + redirectURI.RawQuery = q.Encode() } - q := redirectURI.Query() - q.Set("q_token", output.Token) - - redirectURI.RawQuery = q.Encode() - result := webapp.Result{RedirectURI: redirectURI.String()} result.WriteResponse(w, r) diff --git a/pkg/lib/accountmanagement/redis_store.go b/pkg/lib/accountmanagement/redis_store.go index 7240d3e287..a4a0a31153 100644 --- a/pkg/lib/accountmanagement/redis_store.go +++ b/pkg/lib/accountmanagement/redis_store.go @@ -39,9 +39,10 @@ type GenerateTokenOptions struct { IdentityID string // AuthenticatorID for updating authenticator - AuthenticatorID string - AuthenticatorRecoveryCodes []string - AuthenticatorType model.AuthenticatorType + AuthenticatorID string + AuthenticatorRecoveryCodes []string + AuthenticatorRecoveryCodesCreated bool + AuthenticatorType model.AuthenticatorType // TOTP AuthenticatorTOTPIssuer string diff --git a/pkg/lib/accountmanagement/service.go b/pkg/lib/accountmanagement/service.go index 6b0801616e..9a6d623c69 100644 --- a/pkg/lib/accountmanagement/service.go +++ b/pkg/lib/accountmanagement/service.go @@ -80,6 +80,7 @@ type AuthenticationInfoService interface { type MFAService interface { GenerateRecoveryCodes() []string ReplaceRecoveryCodes(userID string, codes []string) ([]*mfa.RecoveryCode, error) + ListRecoveryCodes(userID string) ([]*mfa.RecoveryCode, error) } type PasskeyService interface { diff --git a/pkg/lib/accountmanagement/service_authenticator.go b/pkg/lib/accountmanagement/service_authenticator.go index 723bfe2e1e..feca02d500 100644 --- a/pkg/lib/accountmanagement/service_authenticator.go +++ b/pkg/lib/accountmanagement/service_authenticator.go @@ -210,7 +210,8 @@ type ResumeAddTOTPAuthenticatorInput struct { Code string } type ResumeAddTOTPAuthenticatorOutput struct { - Token string + Token string + RecoveryCodesCreated bool } func (s *Service) ResumeAddTOTPAuthenticator(resolvedSession session.ResolvedSession, tokenString string, input *ResumeAddTOTPAuthenticatorInput) (output *ResumeAddTOTPAuthenticatorOutput, err error) { @@ -265,22 +266,24 @@ func (s *Service) ResumeAddTOTPAuthenticator(resolvedSession session.ResolvedSes return } - recoveryCodes := s.MFA.GenerateRecoveryCodes() + recoveryCodes, recoveryCodesCreated, err := s.generateRecoveryCodes(userID) newToken, err := s.Store.GenerateToken(GenerateTokenOptions{ - UserID: userID, - AuthenticatorType: model.AuthenticatorType(token.Authenticator.AuthenticatorType), - AuthenticatorTOTPDisplayName: input.DisplayName, - AuthenticatorTOTPSecret: token.Authenticator.TOTPSecret, - AuthenticatorTOTPVerified: true, - AuthenticatorRecoveryCodes: recoveryCodes, + UserID: userID, + AuthenticatorRecoveryCodes: recoveryCodes, + AuthenticatorRecoveryCodesCreated: recoveryCodesCreated, + AuthenticatorType: model.AuthenticatorType(token.Authenticator.AuthenticatorType), + AuthenticatorTOTPDisplayName: input.DisplayName, + AuthenticatorTOTPSecret: token.Authenticator.TOTPSecret, + AuthenticatorTOTPVerified: true, }) if err != nil { return } output = &ResumeAddTOTPAuthenticatorOutput{ - Token: newToken, + Token: newToken, + RecoveryCodesCreated: recoveryCodesCreated, } return } @@ -331,9 +334,11 @@ func (s *Service) FinishAddTOTPAuthenticator(resolvedSession session.ResolvedSes return err } - _, err = s.MFA.ReplaceRecoveryCodes(userID, token.Authenticator.RecoveryCodes) - if err != nil { - return err + if token.Authenticator.RecoveryCodesCreated { + _, err = s.MFA.ReplaceRecoveryCodes(userID, token.Authenticator.RecoveryCodes) + if err != nil { + return err + } } return nil @@ -516,7 +521,8 @@ type ResumeAddOOBOTPAuthenticatorInput struct { Code string } type ResumeAddOOBOTPAuthenticatorOutput struct { - Token string + Token string + RecoveryCodesCreated bool } func (s *Service) ResumeAddOOBOTPAuthenticator(resolvedSession session.ResolvedSession, tokenString string, input *ResumeAddOOBOTPAuthenticatorInput) (output *ResumeAddOOBOTPAuthenticatorOutput, err error) { @@ -548,22 +554,24 @@ func (s *Service) ResumeAddOOBOTPAuthenticator(resolvedSession session.ResolvedS return } - recoveryCodes := s.MFA.GenerateRecoveryCodes() + recoveryCodes, recoveryCodesCreated, err := s.generateRecoveryCodes(userID) newToken, err := s.Store.GenerateToken(GenerateTokenOptions{ - UserID: userID, - AuthenticatorRecoveryCodes: recoveryCodes, - AuthenticatorType: model.AuthenticatorType(token.Authenticator.AuthenticatorType), - AuthenticatorOOBOTPChannel: token.Authenticator.OOBOTPChannel, - AuthenticatorOOBOTPTarget: token.Authenticator.OOBOTPTarget, - AuthenticatorOOBOTPVerified: true, + UserID: userID, + AuthenticatorRecoveryCodes: recoveryCodes, + AuthenticatorRecoveryCodesCreated: recoveryCodesCreated, + AuthenticatorType: model.AuthenticatorType(token.Authenticator.AuthenticatorType), + AuthenticatorOOBOTPChannel: token.Authenticator.OOBOTPChannel, + AuthenticatorOOBOTPTarget: token.Authenticator.OOBOTPTarget, + AuthenticatorOOBOTPVerified: true, }) if err != nil { return } output = &ResumeAddOOBOTPAuthenticatorOutput{ - Token: newToken, + Token: newToken, + RecoveryCodesCreated: recoveryCodesCreated, } return } @@ -624,9 +632,11 @@ func (s *Service) FinishAddOOBOTPAuthenticator(resolvedSession session.ResolvedS return err } - _, err = s.MFA.ReplaceRecoveryCodes(userID, token.Authenticator.RecoveryCodes) - if err != nil { - return err + if token.Authenticator.RecoveryCodesCreated { + _, err = s.MFA.ReplaceRecoveryCodes(userID, token.Authenticator.RecoveryCodes) + if err != nil { + return err + } } return nil @@ -670,3 +680,26 @@ func (s *Service) DeleteOOBOTPAuthenticator(resolvedSession session.ResolvedSess } return } + +func (s *Service) generateRecoveryCodes(userID string) (recoveryCodes []string, isCreated bool, err error) { + if *s.Config.Authentication.RecoveryCode.Disabled { + return nil, false, nil + } + + err = s.Database.WithTx(func() error { + existing, err := s.MFA.ListRecoveryCodes(userID) + if err != nil { + return err + } + + if len(existing) == 0 { + isCreated = true + recoveryCodes = s.MFA.GenerateRecoveryCodes() + return nil + } + + return nil + }) + + return recoveryCodes, isCreated, err +} diff --git a/pkg/lib/accountmanagement/token.go b/pkg/lib/accountmanagement/token.go index 9a0b230963..784b286f2f 100644 --- a/pkg/lib/accountmanagement/token.go +++ b/pkg/lib/accountmanagement/token.go @@ -35,9 +35,12 @@ type TokenIdentity struct { } type TokenAuthenticator struct { - AuthenticatorID string `json:"authenticator_id,omitempty"` - AuthenticatorType string `json:"authenticator_type,omitempty"` - RecoveryCodes []string `json:"recovery_codes,omitempty"` + AuthenticatorID string `json:"authenticator_id,omitempty"` + AuthenticatorType string `json:"authenticator_type,omitempty"` + + // Recovery Codes + RecoveryCodes []string `json:"recovery_codes,omitempty"` + RecoveryCodesCreated bool `json:"recovery_codes_created,omitempty"` // TOTP TOTPIssuer string `json:"totp_issuer,omitempty"`