diff --git a/auth/auth.go b/auth/auth.go index e232eec..3688ba1 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -63,7 +63,7 @@ func (a *Auth) LoginPost(w http.ResponseWriter, r *http.Request) error { pidUser, err := a.Authboss.Storage.Server.Load(r.Context(), pid) if err == authboss.ErrUserNotFound { logger.Infof("failed to load user requested by pid: %s", pid) - data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"} + data := authboss.HTMLData{authboss.DataErr: a.Localizef(r.Context(), authboss.TxtInvalidCredentials)} return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } else if err != nil { return err @@ -85,7 +85,7 @@ func (a *Auth) LoginPost(w http.ResponseWriter, r *http.Request) error { } logger.Infof("user %s failed to log in", pid) - data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"} + data := authboss.HTMLData{authboss.DataErr: a.Localizef(r.Context(), authboss.TxtInvalidCredentials)} return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } diff --git a/authboss.go b/authboss.go index 1081914..21d7cf4 100644 --- a/authboss.go +++ b/authboss.go @@ -101,6 +101,21 @@ func (a *Authboss) VerifyPassword(user AuthableUser, password string) error { return a.Core.Hasher.CompareHashAndPassword(user.GetPassword(), password) } +// Localizef is a helper to translate a key using the translator +// If the localizer is nil or returns an empty string, +// then the original text will be returned using [fmt.Sprintf] to interpolate the args. +func (a *Authboss) Localizef(ctx context.Context, key LocalizationKey, args ...any) string { + if a.Config.Core.Localizer == nil { + return fmt.Sprintf(key.Default, args...) + } + + if translated := a.Config.Core.Localizer.Localizef(ctx, key, args...); translated != "" { + return translated + } + + return fmt.Sprintf(key.Default, args...) +} + // VerifyPassword uses authboss mechanisms to check that a password is correct. // Returns nil on success otherwise there will be an error. Simply a helper // to do the bcrypt comparison. @@ -216,7 +231,7 @@ func MountedMiddleware2(ab *Authboss, mountPathed bool, reqs MWRequirements, fai ro := RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: "please re-login", + Failure: ab.Localizef(r.Context(), TxtAuthFailed), RedirectPath: path.Join(ab.Config.Paths.Mount, fmt.Sprintf("/login?%s", vals.Encode())), } diff --git a/config.go b/config.go index 45c84d2..156d481 100644 --- a/config.go +++ b/config.go @@ -250,6 +250,9 @@ type Config struct { // also implement the ContextLogger to be able to upgrade to a // request specific logger. Logger Logger + + // Localizer is used to translate strings into different languages. + Localizer Localizer } } diff --git a/confirm/confirm.go b/confirm/confirm.go index 9b5ea18..e2ad729 100644 --- a/confirm/confirm.go +++ b/confirm/confirm.go @@ -93,7 +93,7 @@ func (c *Confirm) PreventAuth(w http.ResponseWriter, r *http.Request, handled bo ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK, - Failure: "Your account has not been confirmed, please check your e-mail.", + Failure: c.Localizef(r.Context(), authboss.TxtAccountNotConfirmed), } return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } @@ -114,7 +114,7 @@ func (c *Confirm) StartConfirmationWeb(w http.ResponseWriter, r *http.Request, h ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK, - Success: "Please verify your account, an e-mail has been sent to you.", + Success: c.Localizef(r.Context(), authboss.TxtConfirmYourAccount), } return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } @@ -157,7 +157,7 @@ func (c *Confirm) SendConfirmEmail(ctx context.Context, to, token string) { To: []string{to}, From: c.Config.Mail.From, FromName: c.Config.Mail.FromName, - Subject: c.Config.Mail.SubjectPrefix + "Confirm New Account", + Subject: c.Config.Mail.SubjectPrefix + c.Localizef(ctx, authboss.TxtConfirmEmailSubject), } logger.Infof("sending confirm e-mail to: %s", to) @@ -236,7 +236,7 @@ func (c *Confirm) Get(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Success: "You have successfully confirmed your account.", + Success: c.Localizef(r.Context(), authboss.TxtConfrimationSuccess), RedirectPath: c.Authboss.Config.Paths.ConfirmOK, } return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -256,7 +256,7 @@ func (c *Confirm) mailURL(token string) string { func (c *Confirm) invalidToken(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: "confirm token is invalid", + Failure: c.Localizef(r.Context(), authboss.TxtInvalidConfirmToken), RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK, } return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -284,7 +284,7 @@ func Middleware(ab *authboss.Authboss) func(http.Handler) http.Handler { logger.Infof("user %s prevented from accessing %s: not confirmed", user.GetPID(), r.URL.Path) ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: "Your account has not been confirmed, please check your e-mail.", + Failure: ab.Localizef(r.Context(), authboss.TxtAccountNotConfirmed), RedirectPath: ab.Config.Paths.ConfirmNotOK, } if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil { diff --git a/confirm/confirm_test.go b/confirm/confirm_test.go index b3ea9bb..a555356 100644 --- a/confirm/confirm_test.go +++ b/confirm/confirm_test.go @@ -235,7 +235,7 @@ func TestGetValidationFailure(t *testing.T) { if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK { t.Error("redir path was wrong:", p) } - if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" { + if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) { t.Error("reason for failure was wrong:", reason) } } @@ -262,7 +262,7 @@ func TestGetBase64DecodeFailure(t *testing.T) { if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK { t.Error("redir path was wrong:", p) } - if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" { + if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) { t.Error("reason for failure was wrong:", reason) } } @@ -294,7 +294,7 @@ func TestGetUserNotFoundFailure(t *testing.T) { if p := harness.redirector.Options.RedirectPath; p != harness.ab.Paths.ConfirmNotOK { t.Error("redir path was wrong:", p) } - if reason := harness.redirector.Options.Failure; reason != "confirm token is invalid" { + if reason := harness.redirector.Options.Failure; reason != harness.ab.Localizef(context.Background(), authboss.TxtInvalidConfirmToken) { t.Error("reason for failure was wrong:", reason) } } diff --git a/localizer.go b/localizer.go new file mode 100644 index 0000000..8369cd3 --- /dev/null +++ b/localizer.go @@ -0,0 +1,199 @@ +package authboss + +import ( + "context" +) + +type Localizer interface { + // Get the translation for the given text in the given context. + // If no translation is found, an empty string should be returned. + Localizef(ctx context.Context, key LocalizationKey, args ...any) string +} + +type LocalizationKey struct { + ID string + Default string +} + +var ( + TxtSuccess = LocalizationKey{ + ID: "Success", + Default: "success", + } + + // Used in the auth module + TxtInvalidCredentials = LocalizationKey{ + ID: "InvalidCredentials", + Default: "Invalid Credentials", + } + TxtAuthFailed = LocalizationKey{ + ID: "AuthFailed", + Default: "Please login", + } + + // Used in the register module + TxtUserAlreadyExists = LocalizationKey{ + ID: "UserAlreadyExists", + Default: "User already exists", + } + TxtRegisteredAndLoggedIn = LocalizationKey{ + ID: "RegisteredAndLoggedIn", + Default: "Account successfully created, you are now logged in", + } + + // Used in the confirm module + TxtConfirmYourAccount = LocalizationKey{ + ID: "ConfirmYourAccount", + Default: "Please verify your account, an e-mail has been sent to you.", + } + TxtAccountNotConfirmed = LocalizationKey{ + ID: "AccountNotConfirmed", + Default: "Your account has not been confirmed, please check your e-mail.", + } + TxtInvalidConfirmToken = LocalizationKey{ + ID: "InvalidConfirmToken", + Default: "Your confirmation token is invalid.", + } + TxtConfrimationSuccess = LocalizationKey{ + ID: "ConfrimationSuccess", + Default: "You have successfully confirmed your account.", + } + TxtConfirmEmailSubject = LocalizationKey{ + ID: "ConfirmEmailSubject", + Default: "Confirm New Account", + } + + // Used in the lock module + TxtLocked = LocalizationKey{ + ID: "Locked", + Default: "Your account has been locked, please contact the administrator.", + } + + // Used in the logout module + TxtLoggedOut = LocalizationKey{ + ID: "LoggedOut", + Default: "You have been logged out", + } + + // Used in the oauth2 module + TxtOAuth2LoginOK = LocalizationKey{ + ID: "OAuth2LoginOK", + Default: "Logged in successfully with %s.", + } + TxtOAuth2LoginNotOK = LocalizationKey{ + ID: "OAuth2LoginNotOK", + Default: "%s login cancelled or failed", + } + + // Used in the recover module + TxtRecoverInitiateSuccessFlash = LocalizationKey{ + ID: "RecoverInitiateSuccessFlash", + Default: "An email has been sent to you with further instructions on how to reset your password.", + } + TxtPasswordResetEmailSubject = LocalizationKey{ + ID: "PasswordResetEmailSubject", + Default: "Password Reset", + } + TxtRecoverSuccessMsg = LocalizationKey{ + ID: "RecoverSuccessMsg", + Default: "Successfully updated password", + } + TxtRecoverAndLoginSuccessMsg = LocalizationKey{ + ID: "RecoverAndLoginSuccessMsg", + Default: "Successfully updated password and logged in", + } + + // Used in the otp module + TxtTooManyOTPs = LocalizationKey{ + ID: "TooManyOTPs", + Default: "You cannot have more than %d one time passwords", + } + + // Used in the 2fa module + TxtEmailVerifyTriggered = LocalizationKey{ + ID: "EmailVerifyTriggered", + Default: "An e-mail has been sent to confirm 2FA activation", + } + TxtEmailVerifySubject = LocalizationKey{ + ID: "EmailVerifySubject", + Default: "Add 2FA to Account", + } + TxtInvalid2FAVerificationToken = LocalizationKey{ + ID: "Invalid2FAVerificationToken", + Default: "Invalid 2FA email verification token", + } + Txt2FAAuthorizationRequired = LocalizationKey{ + ID: "2FAAuthorizationRequired", + Default: "You must first authorize adding 2fa by e-mail", + } + TxtInvalid2FACode = LocalizationKey{ + ID: "Invalid2FACode", + Default: "2FA code was invalid", + } + TxtRepeated2FACode = LocalizationKey{ + ID: "Repeated2FACode", + Default: "2FA code was previously used", + } + TxtTOTP2FANotActive = LocalizationKey{ + ID: "TOTP2FANotActive", + Default: "TOTP 2FA is not active", + } + TxtSMSNumberRequired = LocalizationKey{ + ID: "SMSNumberRequired", + Default: "You must provide a phone number", + } + TxtSMSWaitToResend = LocalizationKey{ + ID: "SMSWaitToResend", + Default: "Please wait a few moments before resending the SMS code", + } +) + +// // Translation constants +// const ( +// TxtSuccess = "success" +// +// // Used in the auth module +// TxtInvalidCredentials = "Invalid Credentials" +// TxtAuthFailed = "Please login" +// +// // Used in the register module +// TxtUserAlreadyExists = "User already exists" +// TxtRegisteredAndLoggedIn = "Account successfully created, you are now logged in" +// +// // Used in the confirm module +// TxtConfirmYourAccount = "Please verify your account, an e-mail has been sent to you." +// TxtAccountNotConfirmed = "Your account has not been confirmed, please check your e-mail." +// TxtInvalidConfirmToken = "Your confirmation token is invalid." +// TxtConfrimationSuccess = "You have successfully confirmed your account." +// TxtConfirmEmailSubject = "Confirm New Account" +// +// // Used in the lock module +// TxtLocked = "Your account has been locked, please contact the administrator." +// +// // Used in the logout module +// TxtLoggedOut = "You have been logged out" +// +// // Used in the oauth2 module +// TxtOAuth2LoginOK = "Logged in successfully with %s." +// TxtOAuth2LoginNotOK = "%s login cancelled or failed" +// +// // Used in the recover module +// TxtRecoverInitiateSuccessFlash = "An email has been sent to you with further instructions on how to reset your password." +// TxtPasswordResetEmailSubject = "Password Reset" +// TxtRecoverSuccessMsg = "Successfully updated password" +// TxtRecoverAndLoginSuccessMsg = "Successfully updated password and logged in" +// +// // Used in the otp module +// TxtTooManyOTPs = "You cannot have more than %d one time passwords" +// +// // Used in the 2fa module +// TxtEmailVerifyTriggered = "An e-mail has been sent to confirm 2FA activation" +// TxtEmailVerifySubject = "Add 2FA to Account" +// TxtInvalid2FAVerificationToken = "Invalid 2FA email verification token" +// Txt2FAAuthorizationRequired = "You must first authorize adding 2fa by e-mail" +// TxtInvalid2FACode = "2FA code was invalid" +// TxtRepeated2FACode = "2FA code was previously used" +// TxtTOTP2FANotActive = "TOTP 2FA is not active" +// TxtSMSNumberRequired = "You must provide a phone number" +// TxtSMSWaitToResend = "Please wait a few moments before resending the SMS code" +// ) diff --git a/lock/lock.go b/lock/lock.go index 3527411..e877bcc 100644 --- a/lock/lock.go +++ b/lock/lock.go @@ -99,7 +99,7 @@ func (l *Lock) updateLockedState(w http.ResponseWriter, r *http.Request, wasCorr ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: "Your account has been locked, please contact the administrator.", + Failure: l.Localizef(r.Context(), authboss.TxtLocked), RedirectPath: l.Authboss.Config.Paths.LockNotOK, } return true, l.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -158,7 +158,7 @@ func Middleware(ab *authboss.Authboss) func(http.Handler) http.Handler { logger.Infof("user %s prevented from accessing %s: locked", user.GetPID(), r.URL.Path) ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: "Your account has been locked, please contact the administrator.", + Failure: ab.Localizef(r.Context(), authboss.TxtLocked), RedirectPath: ab.Config.Paths.LockNotOK, } if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil { diff --git a/logout/logout.go b/logout/logout.go index 1d546a3..5609a83 100644 --- a/logout/logout.go +++ b/logout/logout.go @@ -22,18 +22,18 @@ func (l *Logout) Init(ab *authboss.Authboss) error { l.Authboss = ab var logoutRouteMethod func(string, http.Handler) - switch l.Authboss.Config.Modules.LogoutMethod { + switch l.Config.Modules.LogoutMethod { case "GET": - logoutRouteMethod = l.Authboss.Config.Core.Router.Get + logoutRouteMethod = l.Config.Core.Router.Get case "POST": - logoutRouteMethod = l.Authboss.Config.Core.Router.Post + logoutRouteMethod = l.Config.Core.Router.Post case "DELETE": - logoutRouteMethod = l.Authboss.Config.Core.Router.Delete + logoutRouteMethod = l.Config.Core.Router.Delete default: - return errors.Errorf("logout wants to register a logout route but was given an invalid method: %s", l.Authboss.Config.Modules.LogoutMethod) + return errors.Errorf("logout wants to register a logout route but was given an invalid method: %s", l.Config.Modules.LogoutMethod) } - logoutRouteMethod("/logout", l.Authboss.Core.ErrorHandler.Wrap(l.Logout)) + logoutRouteMethod("/logout", l.Core.ErrorHandler.Wrap(l.Logout)) return nil } @@ -61,7 +61,7 @@ func (l *Logout) Logout(w http.ResponseWriter, r *http.Request) error { authboss.DelKnownSession(w) authboss.DelKnownCookie(w) - handled, err = l.Authboss.Events.FireAfter(authboss.EventLogout, w, r) + handled, err = l.Events.FireAfter(authboss.EventLogout, w, r) if err != nil { return err } else if handled { @@ -70,8 +70,8 @@ func (l *Logout) Logout(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - RedirectPath: l.Authboss.Paths.LogoutOK, - Success: "You have been logged out", + RedirectPath: l.Paths.LogoutOK, + Success: l.Localizef(r.Context(), authboss.TxtLoggedOut), } - return l.Authboss.Core.Redirector.Redirect(w, r, ro) + return l.Core.Redirector.Redirect(w, r, ro) } diff --git a/oauth2/oauth2.go b/oauth2/oauth2.go index 2a072dd..7d9975f 100644 --- a/oauth2/oauth2.go +++ b/oauth2/oauth2.go @@ -3,29 +3,29 @@ // only the web server flow is supported. // // The general flow looks like this: -// 1. User goes to Start handler and has his session packed with goodies -// then redirects to the OAuth service. -// 2. OAuth service returns to OAuthCallback which extracts state and -// parameters and generally checks that everything is ok. It uses the -// token received to get an access token from the oauth2 library -// 3. Calls the OAuth2Provider.FindUserDetails which should return the user's -// details in a generic form. -// 4. Passes the user details into the OAuth2ServerStorer.NewFromOAuth2 in -// order to create a user object we can work with. -// 5. Saves the user in the database, logs them in, redirects. +// 1. User goes to Start handler and has his session packed with goodies +// then redirects to the OAuth service. +// 2. OAuth service returns to OAuthCallback which extracts state and +// parameters and generally checks that everything is ok. It uses the +// token received to get an access token from the oauth2 library +// 3. Calls the OAuth2Provider.FindUserDetails which should return the user's +// details in a generic form. +// 4. Passes the user details into the OAuth2ServerStorer.NewFromOAuth2 in +// order to create a user object we can work with. +// 5. Saves the user in the database, logs them in, redirects. // // In order to do this there are a number of parts: -// 1. The configuration of a provider -// (handled by authboss.Config.Modules.OAuth2Providers). -// 2. The flow of redirection of client, parameter passing etc -// (handled by this package) -// 3. The HTTP call to the service once a token has been retrieved to -// get user details (handled by OAuth2Provider.FindUserDetails) -// 4. The creation of a user from the user details returned from the -// FindUserDetails (authboss.OAuth2ServerStorer) -// 5. The special casing of the ServerStorer implementation's Load() -// function to deal properly with incoming OAuth2 pids. See -// authboss.ParseOAuth2PID as a way to do this. +// 1. The configuration of a provider +// (handled by authboss.Config.Modules.OAuth2Providers). +// 2. The flow of redirection of client, parameter passing etc +// (handled by this package) +// 3. The HTTP call to the service once a token has been retrieved to +// get user details (handled by OAuth2Provider.FindUserDetails) +// 4. The creation of a user from the user details returned from the +// FindUserDetails (authboss.OAuth2ServerStorer) +// 5. The special casing of the ServerStorer implementation's Load() +// function to deal properly with incoming OAuth2 pids. See +// authboss.ParseOAuth2PID as a way to do this. // // Of these parts, the responsibility of the authboss library consumer // is on 1, 3, 4, and 5. Configuration of providers that should be used is @@ -61,9 +61,7 @@ const ( FormValueOAuth2Redir = "redir" ) -var ( - errOAuthStateValidation = errors.New("could not validate oauth2 state param") -) +var errOAuthStateValidation = errors.New("could not validate oauth2 state param") // OAuth2 module type OAuth2 struct { @@ -214,7 +212,7 @@ func (o *OAuth2) End(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: o.Authboss.Config.Paths.OAuth2LoginNotOK, - Failure: fmt.Sprintf("%s login cancelled or failed", strings.Title(provider)), + Failure: o.Localizef(r.Context(), authboss.TxtOAuth2LoginNotOK, provider), } return o.Authboss.Core.Redirector.Redirect(w, r, ro) } @@ -292,7 +290,7 @@ func (o *OAuth2) End(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: redirect, - Success: fmt.Sprintf("Logged in successfully with %s.", strings.Title(provider)), + Success: o.Localizef(r.Context(), authboss.TxtOAuth2LoginOK, provider), } return o.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } diff --git a/otp/otp.go b/otp/otp.go index 61195fa..112f7ec 100644 --- a/otp/otp.go +++ b/otp/otp.go @@ -118,7 +118,7 @@ func (o *OTP) LoginPost(w http.ResponseWriter, r *http.Request) error { pidUser, err := o.Authboss.Storage.Server.Load(r.Context(), pid) if err == authboss.ErrUserNotFound { logger.Infof("failed to load user requested by pid: %s", pid) - data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"} + data := authboss.HTMLData{authboss.DataErr: o.Localizef(r.Context(), authboss.TxtInvalidCredentials)} return o.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } else if err != nil { return err @@ -153,7 +153,7 @@ func (o *OTP) LoginPost(w http.ResponseWriter, r *http.Request) error { } logger.Infof("user %s failed to log in with otp", pid) - data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"} + data := authboss.HTMLData{authboss.DataErr: o.Localizef(r.Context(), authboss.TxtInvalidCredentials)} return o.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } @@ -218,7 +218,7 @@ func (o *OTP) AddPost(w http.ResponseWriter, r *http.Request) error { currentOTPs := splitOTPs(otpUser.GetOTPs()) if len(currentOTPs) >= maxOTPs { - data := authboss.HTMLData{authboss.DataValidation: fmt.Sprintf("you cannot have more than %d one time passwords", maxOTPs)} + data := authboss.HTMLData{authboss.DataValidation: o.Localizef(r.Context(), authboss.TxtTooManyOTPs, maxOTPs)} return o.Core.Responder.Respond(w, r, http.StatusOK, PageAdd, data) } diff --git a/otp/twofactor/sms2fa/sms.go b/otp/twofactor/sms2fa/sms.go index 4398943..fd5e764 100644 --- a/otp/twofactor/sms2fa/sms.go +++ b/otp/twofactor/sms2fa/sms.go @@ -263,7 +263,9 @@ func (s *SMS) PostSetup(w http.ResponseWriter, r *http.Request) error { number := smsVals.GetPhoneNumber() if len(number) == 0 { data := authboss.HTMLData{ - authboss.DataValidation: map[string][]string{FormValuePhoneNumber: {"must provide a phone number"}}, + authboss.DataValidation: map[string][]string{FormValuePhoneNumber: { + s.Localizef(r.Context(), authboss.TxtSMSNumberRequired), + }}, } return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSSetup, data) } @@ -355,7 +357,7 @@ func (s *SMSValidator) sendCode(w http.ResponseWriter, r *http.Request, user Use var data authboss.HTMLData err := s.SendCodeToUser(w, r, user.GetPID(), phoneNumber) if err == errSMSRateLimit { - data = authboss.HTMLData{authboss.DataErr: "please wait a few moments before resending SMS code"} + data = authboss.HTMLData{authboss.DataErr: s.Localizef(r.Context(), authboss.TxtSMSWaitToResend)} } else if err != nil { return err } @@ -401,7 +403,7 @@ func (s *SMSValidator) validateCode(w http.ResponseWriter, r *http.Request, user logger.Infof("user %s sms 2fa failure (wrong code)", user.GetPID()) data := authboss.HTMLData{ - authboss.DataValidation: map[string][]string{FormValueCode: {"2fa code was invalid"}}, + authboss.DataValidation: map[string][]string{FormValueCode: {s.Localizef(r.Context(), authboss.TxtInvalid2FACode)}}, } return s.Authboss.Core.Responder.Respond(w, r, http.StatusOK, s.Page, data) } diff --git a/otp/twofactor/sms2fa/sms_test.go b/otp/twofactor/sms2fa/sms_test.go index 39734f6..11da133 100644 --- a/otp/twofactor/sms2fa/sms_test.go +++ b/otp/twofactor/sms2fa/sms_test.go @@ -272,7 +272,7 @@ func TestPostSetup(t *testing.T) { t.Error("page wrong:", h.responder.Page) } validation := h.responder.Data[authboss.DataValidation].(map[string][]string) - if got := validation[FormValuePhoneNumber][0]; got != "must provide a phone number" { + if got := validation[FormValuePhoneNumber][0]; got != h.ab.Localizef(context.Background(), authboss.TxtSMSNumberRequired) { t.Error("data wrong:", got) } }) @@ -547,7 +547,7 @@ func TestValidatorPostOk(t *testing.T) { w.WriteHeader(http.StatusOK) validation := h.responder.Data[authboss.DataValidation].(map[string][]string) - if got := validation[FormValueCode][0]; got != "2fa code was invalid" { + if got := validation[FormValueCode][0]; got != h.ab.Localizef(context.Background(), authboss.TxtInvalid2FACode) { t.Error("data wrong:", got) } }) @@ -574,7 +574,7 @@ func TestValidatorPostOk(t *testing.T) { t.Error("page wrong:", h.responder.Page) } validation := h.responder.Data[authboss.DataValidation].(map[string][]string) - if got := validation[FormValueCode][0]; got != "2fa code was invalid" { + if got := validation[FormValueCode][0]; got != h.ab.Localizef(context.Background(), authboss.TxtInvalid2FACode) { t.Error("data wrong:", got) } }) diff --git a/otp/twofactor/totp2fa/totp.go b/otp/twofactor/totp2fa/totp.go index 667c5dd..60a2891 100644 --- a/otp/twofactor/totp2fa/totp.go +++ b/otp/twofactor/totp2fa/totp.go @@ -49,16 +49,7 @@ const ( DataTOTPSecret = SessionTOTPSecret ) -// validation constants -const ( - validationSuccess = "success" - validationErrRepeatCode = "2fa code was previously used" - validationErrInvalidCode = "2fa code was invalid" -) - -var ( - errNoTOTPEnabled = errors.New("user does not have totp 2fa enabled") -) +var errNoTOTPEnabled = errors.New("user does not have totp 2fa enabled") // User for TOTP type User interface { @@ -180,7 +171,6 @@ func (t *TOTP) PostSetup(w http.ResponseWriter, r *http.Request) error { Issuer: t.Authboss.Config.Modules.TOTP2FAIssuer, AccountName: user.GetEmail(), }) - if err != nil { return errors.Wrap(err, "failed to create a totp key") } @@ -278,8 +268,10 @@ func (t *TOTP) PostConfirm(w http.ResponseWriter, r *http.Request) error { ok = totp.Validate(inputCode, totpSecret) if !ok { data := authboss.HTMLData{ - authboss.DataValidation: map[string][]string{FormValueCode: {"2fa code was invalid"}}, - DataTOTPSecret: totpSecret, + authboss.DataValidation: map[string][]string{FormValueCode: { + t.Localizef(r.Context(), authboss.TxtInvalid2FACode), + }}, + DataTOTPSecret: totpSecret, } return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPConfirm, data) } @@ -333,11 +325,11 @@ func (t *TOTP) PostRemove(w http.ResponseWriter, r *http.Request) error { user, status, err := t.validate(r) switch { case err == errNoTOTPEnabled: - data := authboss.HTMLData{authboss.DataErr: "totp 2fa not active"} + data := authboss.HTMLData{authboss.DataErr: t.Localizef(r.Context(), authboss.TxtTOTP2FANotActive)} return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPRemove, data) case err != nil: return err - case status != validationSuccess: + case status != t.Localizef(r.Context(), authboss.TxtSuccess): logger.Infof("user %s totp 2fa removal failure (%s)", user.GetPID(), status) data := authboss.HTMLData{ authboss.DataValidation: map[string][]string{FormValueCode: {status}}, @@ -376,11 +368,12 @@ func (t *TOTP) PostValidate(w http.ResponseWriter, r *http.Request) error { switch { case err == errNoTOTPEnabled: logger.Infof("user %s totp failure (not enabled)", user.GetPID()) - data := authboss.HTMLData{authboss.DataErr: "totp 2fa not active"} + data := authboss.HTMLData{authboss.DataErr: t.Localizef( + r.Context(), authboss.TxtTOTP2FANotActive)} return t.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageTOTPValidate, data) case err != nil: return err - case status != validationSuccess: + case status != t.Localizef(r.Context(), authboss.TxtSuccess): r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user)) handled, err := t.Authboss.Events.FireAfter(authboss.EventAuthFail, w, r) if err != nil { @@ -478,10 +471,10 @@ func (t *TOTP) validate(r *http.Request) (User, string, error) { return nil, "", err } } else { - return user, validationErrInvalidCode, nil + return user, t.Localizef(r.Context(), authboss.TxtInvalid2FACode), nil } - return user, validationSuccess, nil + return user, t.Localizef(r.Context(), authboss.TxtSuccess), nil } input := totpCodeValues.GetCode() @@ -489,14 +482,14 @@ func (t *TOTP) validate(r *http.Request) (User, string, error) { if oneTime, ok := user.(UserOneTime); ok { oldCode := oneTime.GetTOTPLastCode() if oldCode == input { - return user, validationErrRepeatCode, nil + return user, t.Localizef(r.Context(), authboss.TxtRepeated2FACode), nil } oneTime.PutTOTPLastCode(input) } if !totp.Validate(input, secret) { - return user, validationErrInvalidCode, nil + return user, t.Localizef(r.Context(), authboss.TxtInvalid2FACode), nil } - return user, validationSuccess, nil + return user, t.Localizef(r.Context(), authboss.TxtSuccess), nil } diff --git a/otp/twofactor/totp2fa/totp_test.go b/otp/twofactor/totp2fa/totp_test.go index c552e15..c424f0e 100644 --- a/otp/twofactor/totp2fa/totp_test.go +++ b/otp/twofactor/totp2fa/totp_test.go @@ -380,7 +380,7 @@ func TestPostRemove(t *testing.T) { if h.responder.Page != PageTOTPRemove { t.Error("page wrong:", h.responder.Page) } - if got := h.responder.Data[authboss.DataErr]; got != "totp 2fa not active" { + if got := h.responder.Data[authboss.DataErr]; got != h.ab.Localizef(context.Background(), authboss.TxtTOTP2FANotActive) { t.Error("data wrong:", got) } }) @@ -404,7 +404,7 @@ func TestPostRemove(t *testing.T) { if h.responder.Page != PageTOTPRemove { t.Error("page wrong:", h.responder.Page) } - if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != "2fa code was invalid" { + if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != h.ab.Localizef(context.Background(), authboss.TxtInvalid2FACode) { t.Error("data wrong:", got) } }) @@ -486,7 +486,7 @@ func TestPostValidate(t *testing.T) { if h.responder.Page != PageTOTPValidate { t.Error("page wrong:", h.responder.Page) } - if got := h.responder.Data[authboss.DataErr]; got != "totp 2fa not active" { + if got := h.responder.Data[authboss.DataErr]; got != h.ab.Localizef(context.Background(), authboss.TxtTOTP2FANotActive) { t.Error("data wrong:", got) } }) @@ -509,7 +509,7 @@ func TestPostValidate(t *testing.T) { if h.responder.Page != PageTOTPValidate { t.Error("page wrong:", h.responder.Page) } - if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != "2fa code was invalid" { + if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != h.ab.Localizef(context.Background(), authboss.TxtInvalid2FACode) { t.Error("data wrong:", got) } }) @@ -533,7 +533,7 @@ func TestPostValidate(t *testing.T) { if h.responder.Page != PageTOTPValidate { t.Error("page wrong:", h.responder.Page) } - if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != "2fa code was previously used" { + if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != h.ab.Localizef(context.Background(), authboss.TxtRepeated2FACode) { t.Error("data wrong:", got) } }) @@ -621,7 +621,7 @@ func TestPostValidate(t *testing.T) { // Flush client state w.WriteHeader(http.StatusOK) - if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != "2fa code was invalid" { + if got := h.responder.Data[authboss.DataValidation].(map[string][]string); got[FormValueCode][0] != h.ab.Localizef(context.Background(), authboss.TxtInvalid2FACode) { t.Error("data wrong:", got) } }) diff --git a/otp/twofactor/twofactor_verify.go b/otp/twofactor/twofactor_verify.go index 76b1b46..bac7a4f 100644 --- a/otp/twofactor/twofactor_verify.go +++ b/otp/twofactor/twofactor_verify.go @@ -110,7 +110,7 @@ func (e EmailVerify) PostStart(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: e.Authboss.Config.Paths.TwoFactorEmailAuthNotOK, - Success: "An e-mail has been sent to confirm 2FA activation.", + Success: e.Localizef(ctx, authboss.TxtEmailVerifyTriggered), } return e.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } @@ -125,7 +125,7 @@ func (e EmailVerify) SendVerifyEmail(ctx context.Context, to, token string) { To: []string{to}, From: e.Config.Mail.From, FromName: e.Config.Mail.FromName, - Subject: e.Config.Mail.SubjectPrefix + "Add 2FA to Account", + Subject: e.Config.Mail.SubjectPrefix + e.Localizef(ctx, authboss.TxtEmailVerifySubject), } logger.Infof("sending add 2fa verification e-mail to: %s", to) @@ -168,7 +168,7 @@ func (e EmailVerify) End(w http.ResponseWriter, r *http.Request) error { if 1 != subtle.ConstantTimeCompare([]byte(wantToken), []byte(givenToken)) { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: "invalid 2fa e-mail verification token", + Failure: e.Localizef(r.Context(), authboss.TxtInvalid2FAVerificationToken), RedirectPath: e.Authboss.Config.Paths.TwoFactorEmailAuthNotOK, } return e.Authboss.Core.Redirector.Redirect(w, r, ro) @@ -203,7 +203,7 @@ func (e EmailVerify) Wrap(handler http.Handler) http.Handler { redirURL := path.Join(e.Authboss.Config.Paths.Mount, "2fa", e.TwofactorKind, "email/verify") ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: "You must first authorize adding 2fa by e-mail.", + Failure: e.Localizef(r.Context(), authboss.Txt2FAAuthorizationRequired), RedirectPath: redirURL, } diff --git a/otp/twofactor/twofactor_verify_test.go b/otp/twofactor/twofactor_verify_test.go index 2c189c2..c760f71 100644 --- a/otp/twofactor/twofactor_verify_test.go +++ b/otp/twofactor/twofactor_verify_test.go @@ -153,7 +153,7 @@ func TestEmailVerifyPostStart(t *testing.T) { t.Error("code wrong:", ro.Code) } - if ro.Success != "An e-mail has been sent to confirm 2FA activation." { + if ro.Success != h.ab.Localizef(context.Background(), authboss.TxtEmailVerifyTriggered) { t.Error("message was wrong:", ro.Success) } @@ -240,7 +240,7 @@ func TestEmailVerifyEndFail(t *testing.T) { t.Error("redir path wrong:", ro.RedirectPath) } - if ro.Failure != "invalid 2fa e-mail verification token" { + if ro.Failure != h.ab.Localizef(context.Background(), authboss.TxtInvalid2FAVerificationToken) { t.Error("did not get correct failure") } @@ -317,7 +317,7 @@ func TestEmailVerifyWrap(t *testing.T) { t.Error("redir path wrong:", ro.RedirectPath) } - if ro.Failure != "You must first authorize adding 2fa by e-mail." { + if ro.Failure != h.ab.Localizef(context.Background(), authboss.Txt2FAAuthorizationRequired) { t.Error("did not get correct failure") } }) diff --git a/recover/recover.go b/recover/recover.go index 386c78e..747357c 100644 --- a/recover/recover.go +++ b/recover/recover.go @@ -31,8 +31,6 @@ const ( PageRecoverStart = "recover_start" PageRecoverMiddle = "recover_middle" PageRecoverEnd = "recover_end" - - recoverInitiateSuccessFlash = "An email has been sent to you with further instructions on how to reset your password." ) func init() { @@ -49,25 +47,25 @@ type Recover struct { func (r *Recover) Init(ab *authboss.Authboss) (err error) { r.Authboss = ab - if err := r.Authboss.Config.Core.ViewRenderer.Load(PageRecoverStart, PageRecoverEnd); err != nil { + if err := r.Config.Core.ViewRenderer.Load(PageRecoverStart, PageRecoverEnd); err != nil { return err } - if err := r.Authboss.Config.Core.MailRenderer.Load(EmailRecoverHTML, EmailRecoverTxt); err != nil { + if err := r.Config.Core.MailRenderer.Load(EmailRecoverHTML, EmailRecoverTxt); err != nil { return err } - r.Authboss.Config.Core.Router.Get("/recover", r.Core.ErrorHandler.Wrap(r.StartGet)) - r.Authboss.Config.Core.Router.Post("/recover", r.Core.ErrorHandler.Wrap(r.StartPost)) - r.Authboss.Config.Core.Router.Get("/recover/end", r.Core.ErrorHandler.Wrap(r.EndGet)) - r.Authboss.Config.Core.Router.Post("/recover/end", r.Core.ErrorHandler.Wrap(r.EndPost)) + r.Config.Core.Router.Get("/recover", r.Core.ErrorHandler.Wrap(r.StartGet)) + r.Config.Core.Router.Post("/recover", r.Core.ErrorHandler.Wrap(r.StartPost)) + r.Config.Core.Router.Get("/recover/end", r.Core.ErrorHandler.Wrap(r.EndGet)) + r.Config.Core.Router.Post("/recover/end", r.Core.ErrorHandler.Wrap(r.EndPost)) return nil } // StartGet starts the recover procedure by rendering a form for the user. func (r *Recover) StartGet(w http.ResponseWriter, req *http.Request) error { - return r.Authboss.Config.Core.Responder.Respond(w, req, http.StatusOK, PageRecoverStart, nil) + return r.Config.Core.Responder.Respond(w, req, http.StatusOK, PageRecoverStart, nil) } // StartPost starts the recover procedure using values provided from the user @@ -75,7 +73,7 @@ func (r *Recover) StartGet(w http.ResponseWriter, req *http.Request) error { func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { logger := r.RequestLogger(req) - validatable, err := r.Authboss.Core.BodyReader.Read(PageRecoverStart, req) + validatable, err := r.Core.BodyReader.Read(PageRecoverStart, req) if err != nil { return err } @@ -83,33 +81,33 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { if errs := validatable.Validate(); errs != nil { logger.Info("recover validation failed") data := authboss.HTMLData{authboss.DataValidation: authboss.ErrorMap(errs)} - return r.Authboss.Core.Responder.Respond(w, req, http.StatusOK, PageRecoverStart, data) + return r.Core.Responder.Respond(w, req, http.StatusOK, PageRecoverStart, data) } recoverVals := authboss.MustHaveRecoverStartValues(validatable) - user, err := r.Authboss.Storage.Server.Load(req.Context(), recoverVals.GetPID()) + user, err := r.Storage.Server.Load(req.Context(), recoverVals.GetPID()) if err == authboss.ErrUserNotFound { logger.Infof("user %s was attempted to be recovered, user does not exist, faking successful response", recoverVals.GetPID()) ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - RedirectPath: r.Authboss.Config.Paths.RecoverOK, - Success: recoverInitiateSuccessFlash, + RedirectPath: r.Config.Paths.RecoverOK, + Success: r.Localizef(req.Context(), authboss.TxtRecoverInitiateSuccessFlash), } - return r.Authboss.Core.Redirector.Redirect(w, req, ro) + return r.Core.Redirector.Redirect(w, req, ro) } ru := authboss.MustBeRecoverable(user) req = req.WithContext(context.WithValue(req.Context(), authboss.CTXKeyUser, user)) - handled, err := r.Authboss.Events.FireBefore(authboss.EventRecoverStart, w, req) + handled, err := r.Events.FireBefore(authboss.EventRecoverStart, w, req) if err != nil { return err } else if handled { return nil } - selector, verifier, token, err := r.Authboss.Config.Core.OneTimeTokenGenerator.GenerateToken() + selector, verifier, token, err := r.Config.Core.OneTimeTokenGenerator.GenerateToken() if err != nil { return err } @@ -120,7 +118,7 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { ru.PutRecoverVerifier(verifier) ru.PutRecoverExpiry(time.Now().UTC().Add(r.Config.Modules.RecoverTokenDuration)) - if err := r.Authboss.Storage.Server.Save(req.Context(), ru); err != nil { + if err := r.Storage.Server.Save(req.Context(), ru); err != nil { return err } @@ -130,13 +128,13 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { recoveryEmailRecipients = append(recoveryEmailRecipients, ruWithSecondaries.GetSecondaryEmails()...) } - if r.Authboss.Modules.MailNoGoroutine { + if r.Modules.MailNoGoroutine { r.SendRecoverEmail(req.Context(), recoveryEmailRecipients, token) } else { go r.SendRecoverEmail(req.Context(), recoveryEmailRecipients, token) } - _, err = r.Authboss.Events.FireAfter(authboss.EventRecoverStart, w, req) + _, err = r.Events.FireAfter(authboss.EventRecoverStart, w, req) if err != nil { return err } @@ -144,24 +142,24 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { logger.Infof("user %s password recovery initiated", ru.GetPID()) ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - RedirectPath: r.Authboss.Config.Paths.RecoverOK, - Success: recoverInitiateSuccessFlash, + RedirectPath: r.Config.Paths.RecoverOK, + Success: r.Localizef(req.Context(), authboss.TxtRecoverInitiateSuccessFlash), } - return r.Authboss.Core.Redirector.Redirect(w, req, ro) + return r.Core.Redirector.Redirect(w, req, ro) } // SendRecoverEmail to a specific e-mail address passing along the encodedToken // in an escaped URL to the templates. func (r *Recover) SendRecoverEmail(ctx context.Context, to []string, encodedToken string) { - logger := r.Authboss.Logger(ctx) + logger := r.Logger(ctx) mailURL := r.mailURL(encodedToken) email := authboss.Email{ To: to, - From: r.Authboss.Config.Mail.From, - FromName: r.Authboss.Config.Mail.FromName, - Subject: r.Authboss.Config.Mail.SubjectPrefix + "Password Reset", + From: r.Config.Mail.From, + FromName: r.Config.Mail.FromName, + Subject: r.Config.Mail.SubjectPrefix + r.Localizef(ctx, authboss.TxtPasswordResetEmailSubject), } ro := authboss.EmailResponseOptions{ @@ -173,7 +171,7 @@ func (r *Recover) SendRecoverEmail(ctx context.Context, to []string, encodedToke } logger.Infof("sending recover e-mail to: %s", to) - if err := r.Authboss.Email(ctx, email, ro); err != nil { + if err := r.Email(ctx, email, ro); err != nil { logger.Errorf("failed to recover send e-mail to %s: %+v", to, err) } } @@ -181,7 +179,7 @@ func (r *Recover) SendRecoverEmail(ctx context.Context, to []string, encodedToke // EndGet shows a password recovery form, and it should have the token that // the user brought in the query parameters in it on submission. func (r *Recover) EndGet(w http.ResponseWriter, req *http.Request) error { - validatable, err := r.Authboss.Core.BodyReader.Read(PageRecoverMiddle, req) + validatable, err := r.Core.BodyReader.Read(PageRecoverMiddle, req) if err != nil { return err } @@ -193,14 +191,14 @@ func (r *Recover) EndGet(w http.ResponseWriter, req *http.Request) error { DataRecoverToken: token, } - return r.Authboss.Config.Core.Responder.Respond(w, req, http.StatusOK, PageRecoverEnd, data) + return r.Config.Core.Responder.Respond(w, req, http.StatusOK, PageRecoverEnd, data) } // EndPost retrieves the token func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { logger := r.RequestLogger(req) - validatable, err := r.Authboss.Core.BodyReader.Read(PageRecoverEnd, req) + validatable, err := r.Core.BodyReader.Read(PageRecoverEnd, req) if err != nil { return err } @@ -224,7 +222,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { return r.invalidToken(PageRecoverEnd, w, req) } - credsGenerator := r.Authboss.Core.OneTimeTokenGenerator + credsGenerator := r.Core.OneTimeTokenGenerator if len(rawToken) != credsGenerator.TokenSize() { logger.Infof("invalid recover token submitted, size was wrong: %d", len(rawToken)) @@ -234,7 +232,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { selectorBytes, verifierBytes := credsGenerator.ParseToken(string(rawToken)) selector := base64.StdEncoding.EncodeToString(selectorBytes[:]) - storer := authboss.EnsureCanRecover(r.Authboss.Config.Storage.Server) + storer := authboss.EnsureCanRecover(r.Config.Storage.Server) user, err := storer.LoadByRecoverSelector(req.Context(), selector) if err == authboss.ErrUserNotFound { logger.Info("invalid recover token submitted, user not found") @@ -261,14 +259,14 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { } req = req.WithContext(context.WithValue(req.Context(), authboss.CTXKeyUser, user)) - handled, err := r.Authboss.Events.FireBefore(authboss.EventRecoverEnd, w, req) + handled, err := r.Events.FireBefore(authboss.EventRecoverEnd, w, req) if err != nil { return err } else if handled { return nil } - pass, err := r.Authboss.Config.Core.Hasher.GenerateHash(password) + pass, err := r.Config.Core.Hasher.GenerateHash(password) if err != nil { return err } @@ -282,13 +280,13 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { return err } - successMsg := "Successfully updated password" - _, err = r.Authboss.Events.FireAfter(authboss.EventRecoverEnd, w, req) + _, err = r.Events.FireAfter(authboss.EventRecoverEnd, w, req) if err != nil { return err } - if r.Authboss.Config.Modules.RecoverLoginAfterRecovery { + successMsg := r.Localizef(req.Context(), authboss.TxtRecoverSuccessMsg) + if r.Config.Modules.RecoverLoginAfterRecovery { handled, err = r.Events.FireBefore(authboss.EventAuth, w, req) if err != nil { return err @@ -304,9 +302,9 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { } authboss.PutSession(w, authboss.SessionKey, user.GetPID()) - successMsg += " and logged in" + successMsg = r.Localizef(req.Context(), authboss.TxtRecoverAndLoginSuccessMsg) - handled, err = r.Authboss.Events.FireAfter(authboss.EventAuth, w, req) + handled, err = r.Events.FireAfter(authboss.EventAuth, w, req) if err != nil { return err } else if handled { @@ -316,16 +314,16 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - RedirectPath: r.Authboss.Config.Paths.RecoverOK, + RedirectPath: r.Config.Paths.RecoverOK, Success: successMsg, } - return r.Authboss.Config.Core.Redirector.Redirect(w, req, ro) + return r.Config.Core.Redirector.Redirect(w, req, ro) } func (r *Recover) invalidToken(page string, w http.ResponseWriter, req *http.Request) error { errorsAll := []error{errors.New("recovery token is invalid")} data := authboss.HTMLData{authboss.DataValidation: authboss.ErrorMap(errorsAll)} - return r.Authboss.Core.Responder.Respond(w, req, http.StatusOK, PageRecoverEnd, data) + return r.Core.Responder.Respond(w, req, http.StatusOK, PageRecoverEnd, data) } func (r *Recover) mailURL(token string) string { diff --git a/register/register.go b/register/register.go index 730d89d..a135149 100644 --- a/register/register.go +++ b/register/register.go @@ -107,7 +107,7 @@ func (r *Register) Post(w http.ResponseWriter, req *http.Request) error { switch { case err == authboss.ErrUserFound: logger.Infof("user %s attempted to re-register", pid) - errs = []error{errors.New("user already exists")} + errs = []error{errors.New(r.Localizef(req.Context(), authboss.TxtUserAlreadyExists))} data := authboss.HTMLData{ authboss.DataValidation: authboss.ErrorMap(errs), } @@ -134,7 +134,7 @@ func (r *Register) Post(w http.ResponseWriter, req *http.Request) error { logger.Infof("registered and logged in user %s", pid) ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Success: "Account successfully created, you are now logged in", + Success: r.Localizef(req.Context(), authboss.TxtRegisteredAndLoggedIn), RedirectPath: r.Config.Paths.RegisterOK, } return r.Config.Core.Redirector.Redirect(w, req, ro) diff --git a/register/register_test.go b/register/register_test.go index e9ee15e..78bd1ce 100644 --- a/register/register_test.go +++ b/register/register_test.go @@ -1,6 +1,7 @@ package register import ( + "context" "net/http" "net/http/httptest" "testing" @@ -277,7 +278,7 @@ func TestRegisterPostUserExists(t *testing.T) { } errList := h.responder.Data[authboss.DataValidation].(map[string][]string) - if e := errList[""][0]; e != "user already exists" { + if e := errList[""][0]; e != h.ab.Localizef(context.Background(), authboss.TxtUserAlreadyExists) { t.Error("validation error wrong:", e) }