From 7c2e8a79bf239149c7d9c313bdf9318df9d02c79 Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Mon, 27 Nov 2023 01:13:21 +0000 Subject: [PATCH 1/9] Add Translator module to translate various strings --- auth/auth.go | 7 ++++--- authboss.go | 19 ++++++++++++++++++- config.go | 3 +++ logout/logout.go | 4 +++- recover/recover.go | 15 +++++++++------ translator.go | 9 +++++++++ 6 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 translator.go diff --git a/auth/auth.go b/auth/auth.go index e232eec..457e754 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -10,7 +10,8 @@ import ( const ( // PageLogin is for identifying the login page for parsing & validation - PageLogin = "login" + PageLogin = "login" + TranslationInvalidCredentials = "Invalid Credentials" ) func init() { @@ -63,7 +64,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.Translate(r.Context(), TranslationInvalidCredentials)} return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } else if err != nil { return err @@ -85,7 +86,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.Translate(r.Context(), TranslationInvalidCredentials)} return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } diff --git a/authboss.go b/authboss.go index 1081914..3afc58f 100644 --- a/authboss.go +++ b/authboss.go @@ -17,6 +17,8 @@ import ( "golang.org/x/crypto/bcrypt" ) +const TranslateAuthFailed = "please re-login" + // Authboss contains a configuration and other details for running. type Authboss struct { Config @@ -101,6 +103,21 @@ func (a *Authboss) VerifyPassword(user AuthableUser, password string) error { return a.Core.Hasher.CompareHashAndPassword(user.GetPassword(), password) } +// Translate is a helper to translate a key using the translator +// If the translator is nil or returns an empty string, +// then the string is returned without translation +func (a *Authboss) Translate(ctx context.Context, text string) string { + if a.Config.Core.Translator == nil { + return text + } + + if translated := a.Config.Core.Translator.Translate(ctx, text); translated != "" { + return translated + } + + return text +} + // 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 +233,7 @@ func MountedMiddleware2(ab *Authboss, mountPathed bool, reqs MWRequirements, fai ro := RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: "please re-login", + Failure: ab.Translate(r.Context(), TranslateAuthFailed), RedirectPath: path.Join(ab.Config.Paths.Mount, fmt.Sprintf("/login?%s", vals.Encode())), } diff --git a/config.go b/config.go index 45c84d2..62edd57 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 + + // Translator is used to translate strings into different languages. + Translator Translator } } diff --git a/logout/logout.go b/logout/logout.go index 1d546a3..ec28386 100644 --- a/logout/logout.go +++ b/logout/logout.go @@ -8,6 +8,8 @@ import ( "github.com/volatiletech/authboss/v3" ) +const TranslateLoggedOut = "You have been logged out" + func init() { authboss.RegisterModule("logout", &Logout{}) } @@ -71,7 +73,7 @@ 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", + Success: TranslateLoggedOut, } return l.Authboss.Core.Redirector.Redirect(w, r, ro) } diff --git a/recover/recover.go b/recover/recover.go index 386c78e..16990a3 100644 --- a/recover/recover.go +++ b/recover/recover.go @@ -32,7 +32,10 @@ const ( PageRecoverMiddle = "recover_middle" PageRecoverEnd = "recover_end" - recoverInitiateSuccessFlash = "An email has been sent to you with further instructions on how to reset your password." + TranslateRecoverInitiateSuccessFlash = "An email has been sent to you with further instructions on how to reset your password." + TranslatePasswordResetEmailSubject = "Password Reset" + TranslateRecoverSuccessMsg = "Successfully updated password" + TranslateRecoverAndLoginSuccessMsg = "Successfully updated password and logged in" ) func init() { @@ -94,7 +97,7 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: r.Authboss.Config.Paths.RecoverOK, - Success: recoverInitiateSuccessFlash, + Success: TranslateRecoverInitiateSuccessFlash, } return r.Authboss.Core.Redirector.Redirect(w, req, ro) } @@ -145,7 +148,7 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: r.Authboss.Config.Paths.RecoverOK, - Success: recoverInitiateSuccessFlash, + Success: TranslateRecoverInitiateSuccessFlash, } return r.Authboss.Core.Redirector.Redirect(w, req, ro) } @@ -161,7 +164,7 @@ func (r *Recover) SendRecoverEmail(ctx context.Context, to []string, encodedToke To: to, From: r.Authboss.Config.Mail.From, FromName: r.Authboss.Config.Mail.FromName, - Subject: r.Authboss.Config.Mail.SubjectPrefix + "Password Reset", + Subject: r.Authboss.Config.Mail.SubjectPrefix + r.Translate(ctx, TranslatePasswordResetEmailSubject), } ro := authboss.EmailResponseOptions{ @@ -282,12 +285,12 @@ 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) if err != nil { return err } + successMsg := r.Translate(req.Context(), TranslateRecoverSuccessMsg) if r.Authboss.Config.Modules.RecoverLoginAfterRecovery { handled, err = r.Events.FireBefore(authboss.EventAuth, w, req) if err != nil { @@ -304,7 +307,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { } authboss.PutSession(w, authboss.SessionKey, user.GetPID()) - successMsg += " and logged in" + successMsg = r.Translate(req.Context(), TranslateRecoverAndLoginSuccessMsg) handled, err = r.Authboss.Events.FireAfter(authboss.EventAuth, w, req) if err != nil { diff --git a/translator.go b/translator.go new file mode 100644 index 0000000..b784a8b --- /dev/null +++ b/translator.go @@ -0,0 +1,9 @@ +package authboss + +import "context" + +type Translator interface { + // Get the translation for the given text in the given context. + // If no translation is found, an empty string should be returned. + Translate(ctx context.Context, txt string) string +} From c0f5b39b8b15115485ee86508e3a3cfe1b7370c9 Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Wed, 29 Nov 2023 20:27:46 +0000 Subject: [PATCH 2/9] Add translations for oauth2 module --- authboss.go | 6 +++--- oauth2/oauth2.go | 54 +++++++++++++++++++++++++----------------------- translator.go | 2 +- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/authboss.go b/authboss.go index 3afc58f..c94e234 100644 --- a/authboss.go +++ b/authboss.go @@ -106,16 +106,16 @@ func (a *Authboss) VerifyPassword(user AuthableUser, password string) error { // Translate is a helper to translate a key using the translator // If the translator is nil or returns an empty string, // then the string is returned without translation -func (a *Authboss) Translate(ctx context.Context, text string) string { +func (a *Authboss) Translate(ctx context.Context, text string, args ...any) string { if a.Config.Core.Translator == nil { - return text + return fmt.Sprintf(text, args...) } if translated := a.Config.Core.Translator.Translate(ctx, text); translated != "" { return translated } - return text + return fmt.Sprintf(text, args...) } // VerifyPassword uses authboss mechanisms to check that a password is correct. diff --git a/oauth2/oauth2.go b/oauth2/oauth2.go index 2a072dd..670e3f1 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 @@ -59,12 +59,14 @@ import ( const ( FormValueOAuth2State = "state" FormValueOAuth2Redir = "redir" -) -var ( - errOAuthStateValidation = errors.New("could not validate oauth2 state param") + // Translations + TranslationOAuth2LoginOK = "Logged in successfully with %s." + TranslationOAuth2LoginNotOK = "%s login cancelled or failed" ) +var errOAuthStateValidation = errors.New("could not validate oauth2 state param") + // OAuth2 module type OAuth2 struct { *authboss.Authboss @@ -214,7 +216,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.Translate(r.Context(), TranslationOAuth2LoginNotOK, provider), } return o.Authboss.Core.Redirector.Redirect(w, r, ro) } @@ -292,7 +294,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.Translate(r.Context(), TranslationOAuth2LoginOK, provider), } return o.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } diff --git a/translator.go b/translator.go index b784a8b..e6fb5c0 100644 --- a/translator.go +++ b/translator.go @@ -5,5 +5,5 @@ import "context" type Translator interface { // Get the translation for the given text in the given context. // If no translation is found, an empty string should be returned. - Translate(ctx context.Context, txt string) string + Translate(ctx context.Context, txt string, args ...any) string } From ba220ca8d9b25363240cfbd400d14f92eb5f94b4 Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Wed, 29 Nov 2023 20:36:44 +0000 Subject: [PATCH 3/9] Rename Translator to Localizer --- auth/auth.go | 4 ++-- authboss.go | 14 +++++++------- config.go | 4 ++-- translator.go => localizer.go | 4 ++-- oauth2/oauth2.go | 4 ++-- recover/recover.go | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) rename translator.go => localizer.go (65%) diff --git a/auth/auth.go b/auth/auth.go index 457e754..d5b7450 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -64,7 +64,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: a.Translate(r.Context(), TranslationInvalidCredentials)} + data := authboss.HTMLData{authboss.DataErr: a.Localize(r.Context(), TranslationInvalidCredentials)} return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } else if err != nil { return err @@ -86,7 +86,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: a.Translate(r.Context(), TranslationInvalidCredentials)} + data := authboss.HTMLData{authboss.DataErr: a.Localize(r.Context(), TranslationInvalidCredentials)} return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } diff --git a/authboss.go b/authboss.go index c94e234..a835266 100644 --- a/authboss.go +++ b/authboss.go @@ -103,15 +103,15 @@ func (a *Authboss) VerifyPassword(user AuthableUser, password string) error { return a.Core.Hasher.CompareHashAndPassword(user.GetPassword(), password) } -// Translate is a helper to translate a key using the translator -// If the translator is nil or returns an empty string, -// then the string is returned without translation -func (a *Authboss) Translate(ctx context.Context, text string, args ...any) string { - if a.Config.Core.Translator == nil { +// Localize 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) Localize(ctx context.Context, text string, args ...any) string { + if a.Config.Core.Localizer == nil { return fmt.Sprintf(text, args...) } - if translated := a.Config.Core.Translator.Translate(ctx, text); translated != "" { + if translated := a.Config.Core.Localizer.Localize(ctx, text); translated != "" { return translated } @@ -233,7 +233,7 @@ func MountedMiddleware2(ab *Authboss, mountPathed bool, reqs MWRequirements, fai ro := RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: ab.Translate(r.Context(), TranslateAuthFailed), + Failure: ab.Localize(r.Context(), TranslateAuthFailed), RedirectPath: path.Join(ab.Config.Paths.Mount, fmt.Sprintf("/login?%s", vals.Encode())), } diff --git a/config.go b/config.go index 62edd57..156d481 100644 --- a/config.go +++ b/config.go @@ -251,8 +251,8 @@ type Config struct { // request specific logger. Logger Logger - // Translator is used to translate strings into different languages. - Translator Translator + // Localizer is used to translate strings into different languages. + Localizer Localizer } } diff --git a/translator.go b/localizer.go similarity index 65% rename from translator.go rename to localizer.go index e6fb5c0..e4be0e9 100644 --- a/translator.go +++ b/localizer.go @@ -2,8 +2,8 @@ package authboss import "context" -type Translator interface { +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. - Translate(ctx context.Context, txt string, args ...any) string + Localize(ctx context.Context, txt string, args ...any) string } diff --git a/oauth2/oauth2.go b/oauth2/oauth2.go index 670e3f1..a7e75ce 100644 --- a/oauth2/oauth2.go +++ b/oauth2/oauth2.go @@ -216,7 +216,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: o.Translate(r.Context(), TranslationOAuth2LoginNotOK, provider), + Failure: o.Localize(r.Context(), TranslationOAuth2LoginNotOK, provider), } return o.Authboss.Core.Redirector.Redirect(w, r, ro) } @@ -294,7 +294,7 @@ func (o *OAuth2) End(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: redirect, - Success: o.Translate(r.Context(), TranslationOAuth2LoginOK, provider), + Success: o.Localize(r.Context(), TranslationOAuth2LoginOK, provider), } return o.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } diff --git a/recover/recover.go b/recover/recover.go index 16990a3..69fcef5 100644 --- a/recover/recover.go +++ b/recover/recover.go @@ -164,7 +164,7 @@ func (r *Recover) SendRecoverEmail(ctx context.Context, to []string, encodedToke To: to, From: r.Authboss.Config.Mail.From, FromName: r.Authboss.Config.Mail.FromName, - Subject: r.Authboss.Config.Mail.SubjectPrefix + r.Translate(ctx, TranslatePasswordResetEmailSubject), + Subject: r.Authboss.Config.Mail.SubjectPrefix + r.Localize(ctx, TranslatePasswordResetEmailSubject), } ro := authboss.EmailResponseOptions{ @@ -290,7 +290,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { return err } - successMsg := r.Translate(req.Context(), TranslateRecoverSuccessMsg) + successMsg := r.Localize(req.Context(), TranslateRecoverSuccessMsg) if r.Authboss.Config.Modules.RecoverLoginAfterRecovery { handled, err = r.Events.FireBefore(authboss.EventAuth, w, req) if err != nil { @@ -307,7 +307,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { } authboss.PutSession(w, authboss.SessionKey, user.GetPID()) - successMsg = r.Translate(req.Context(), TranslateRecoverAndLoginSuccessMsg) + successMsg = r.Localize(req.Context(), TranslateRecoverAndLoginSuccessMsg) handled, err = r.Authboss.Events.FireAfter(authboss.EventAuth, w, req) if err != nil { From e1423c4e7798ff0e807e7bf6ef60dc5ce672d0a1 Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Wed, 29 Nov 2023 23:32:03 +0000 Subject: [PATCH 4/9] Add translations for comfirm, lock and register modules --- confirm/confirm.go | 19 +++++++++++++------ lock/lock.go | 5 +++-- register/register.go | 7 +++++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/confirm/confirm.go b/confirm/confirm.go index 9b5ea18..23f6d92 100644 --- a/confirm/confirm.go +++ b/confirm/confirm.go @@ -32,6 +32,13 @@ const ( // DataConfirmURL is the name of the e-mail template variable // that gives the url to send to the user for confirmation. DataConfirmURL = "url" + + // Translations + TranslateConfirmYourAccount = "Please verify your account, an e-mail has been sent to you." + TranslateAccountNotConfirmed = "Your account has not been confirmed, please check your e-mail." + TranslateInvalidConfirmToken = "Your confirmation token is invalid." + TranslateConfrimationSuccess = "You have successfully confirmed your account." + TranslateConfirmEmailSubject = "Confirm New Account" ) func init() { @@ -93,7 +100,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.Localize(r.Context(), TranslateAccountNotConfirmed), } return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } @@ -114,7 +121,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.Localize(r.Context(), TranslateConfirmYourAccount), } return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } @@ -157,7 +164,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.Localize(ctx, TranslateConfirmEmailSubject), } logger.Infof("sending confirm e-mail to: %s", to) @@ -236,7 +243,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.Localize(r.Context(), TranslateConfrimationSuccess), RedirectPath: c.Authboss.Config.Paths.ConfirmOK, } return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -256,7 +263,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.Localize(r.Context(), TranslateInvalidConfirmToken), RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK, } return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -284,7 +291,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.Localize(r.Context(), TranslateAccountNotConfirmed), RedirectPath: ab.Config.Paths.ConfirmNotOK, } if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil { diff --git a/lock/lock.go b/lock/lock.go index 3527411..0b1220b 100644 --- a/lock/lock.go +++ b/lock/lock.go @@ -14,6 +14,7 @@ const ( StoreAttemptNumber = "attempt_number" StoreAttemptTime = "attempt_time" StoreLocked = "locked" + TranslationLocked = "Your account has been locked, please contact the administrator." ) func init() { @@ -99,7 +100,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.Localize(r.Context(), TranslationLocked), RedirectPath: l.Authboss.Config.Paths.LockNotOK, } return true, l.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -158,7 +159,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.Localize(r.Context(), TranslationLocked), RedirectPath: ab.Config.Paths.LockNotOK, } if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil { diff --git a/register/register.go b/register/register.go index 730d89d..4dd23e7 100644 --- a/register/register.go +++ b/register/register.go @@ -14,6 +14,9 @@ import ( // Pages const ( PageRegister = "register" + // Translations + TranslateUserAlreadyExists = "User already exists" + TranslateRegisteredAndLoggedIn = "Account successfully created, you are now logged in" ) func init() { @@ -107,7 +110,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(TranslateUserAlreadyExists)} data := authboss.HTMLData{ authboss.DataValidation: authboss.ErrorMap(errs), } @@ -134,7 +137,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: TranslateRegisteredAndLoggedIn, RedirectPath: r.Config.Paths.RegisterOK, } return r.Config.Core.Redirector.Redirect(w, req, ro) From 96080943ff2678b4261dff78bdb008a283d52450 Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Wed, 29 Nov 2023 23:36:09 +0000 Subject: [PATCH 5/9] Fix confirm and register tests --- confirm/confirm_test.go | 6 +++--- register/register_test.go | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/confirm/confirm_test.go b/confirm/confirm_test.go index b3ea9bb..a6b7d93 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.Localize(context.Background(), TranslateInvalidConfirmToken) { 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.Localize(context.Background(), TranslateInvalidConfirmToken) { 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.Localize(context.Background(), TranslateInvalidConfirmToken) { t.Error("reason for failure was wrong:", reason) } } diff --git a/register/register_test.go b/register/register_test.go index e9ee15e..423c7b4 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.Localize(context.Background(), TranslateUserAlreadyExists) { t.Error("validation error wrong:", e) } From 8f93fb075284108e84da9800a733d447993fc0cd Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Thu, 30 Nov 2023 00:29:18 +0000 Subject: [PATCH 6/9] Move translations constants to central place --- auth/auth.go | 7 +++---- authboss.go | 6 ++---- confirm/confirm.go | 19 ++++++------------- confirm/confirm_test.go | 6 +++--- localizer.go | 34 ++++++++++++++++++++++++++++++++++ lock/lock.go | 5 ++--- logout/logout.go | 4 +--- oauth2/oauth2.go | 8 ++------ recover/recover.go | 15 +++++---------- register/register.go | 7 ++----- register/register_test.go | 2 +- 11 files changed, 61 insertions(+), 52 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index d5b7450..58b9faf 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -10,8 +10,7 @@ import ( const ( // PageLogin is for identifying the login page for parsing & validation - PageLogin = "login" - TranslationInvalidCredentials = "Invalid Credentials" + PageLogin = "login" ) func init() { @@ -64,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: a.Localize(r.Context(), TranslationInvalidCredentials)} + data := authboss.HTMLData{authboss.DataErr: a.Localize(r.Context(), authboss.TxtInvalidCredentials)} return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data) } else if err != nil { return err @@ -86,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: a.Localize(r.Context(), TranslationInvalidCredentials)} + data := authboss.HTMLData{authboss.DataErr: a.Localize(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 a835266..12be83a 100644 --- a/authboss.go +++ b/authboss.go @@ -17,8 +17,6 @@ import ( "golang.org/x/crypto/bcrypt" ) -const TranslateAuthFailed = "please re-login" - // Authboss contains a configuration and other details for running. type Authboss struct { Config @@ -111,7 +109,7 @@ func (a *Authboss) Localize(ctx context.Context, text string, args ...any) strin return fmt.Sprintf(text, args...) } - if translated := a.Config.Core.Localizer.Localize(ctx, text); translated != "" { + if translated := a.Config.Core.Localizer.Localize(ctx, text, args...); translated != "" { return translated } @@ -233,7 +231,7 @@ func MountedMiddleware2(ab *Authboss, mountPathed bool, reqs MWRequirements, fai ro := RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: ab.Localize(r.Context(), TranslateAuthFailed), + Failure: ab.Localize(r.Context(), TxtAuthFailed), RedirectPath: path.Join(ab.Config.Paths.Mount, fmt.Sprintf("/login?%s", vals.Encode())), } diff --git a/confirm/confirm.go b/confirm/confirm.go index 23f6d92..9bf41e3 100644 --- a/confirm/confirm.go +++ b/confirm/confirm.go @@ -32,13 +32,6 @@ const ( // DataConfirmURL is the name of the e-mail template variable // that gives the url to send to the user for confirmation. DataConfirmURL = "url" - - // Translations - TranslateConfirmYourAccount = "Please verify your account, an e-mail has been sent to you." - TranslateAccountNotConfirmed = "Your account has not been confirmed, please check your e-mail." - TranslateInvalidConfirmToken = "Your confirmation token is invalid." - TranslateConfrimationSuccess = "You have successfully confirmed your account." - TranslateConfirmEmailSubject = "Confirm New Account" ) func init() { @@ -100,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: c.Localize(r.Context(), TranslateAccountNotConfirmed), + Failure: c.Localize(r.Context(), authboss.TxtAccountNotConfirmed), } return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } @@ -121,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: c.Localize(r.Context(), TranslateConfirmYourAccount), + Success: c.Localize(r.Context(), authboss.TxtConfirmYourAccount), } return true, c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } @@ -164,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 + c.Localize(ctx, TranslateConfirmEmailSubject), + Subject: c.Config.Mail.SubjectPrefix + c.Localize(ctx, authboss.TxtConfirmEmailSubject), } logger.Infof("sending confirm e-mail to: %s", to) @@ -243,7 +236,7 @@ func (c *Confirm) Get(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Success: c.Localize(r.Context(), TranslateConfrimationSuccess), + Success: c.Localize(r.Context(), authboss.TxtConfrimationSuccess), RedirectPath: c.Authboss.Config.Paths.ConfirmOK, } return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -263,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: c.Localize(r.Context(), TranslateInvalidConfirmToken), + Failure: c.Localize(r.Context(), authboss.TxtInvalidConfirmToken), RedirectPath: c.Authboss.Config.Paths.ConfirmNotOK, } return c.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -291,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: ab.Localize(r.Context(), TranslateAccountNotConfirmed), + Failure: ab.Localize(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 a6b7d93..595e52b 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 != harness.ab.Localize(context.Background(), TranslateInvalidConfirmToken) { + if reason := harness.redirector.Options.Failure; reason != harness.ab.Localize(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 != harness.ab.Localize(context.Background(), TranslateInvalidConfirmToken) { + if reason := harness.redirector.Options.Failure; reason != harness.ab.Localize(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 != harness.ab.Localize(context.Background(), TranslateInvalidConfirmToken) { + if reason := harness.redirector.Options.Failure; reason != harness.ab.Localize(context.Background(), authboss.TxtInvalidConfirmToken) { t.Error("reason for failure was wrong:", reason) } } diff --git a/localizer.go b/localizer.go index e4be0e9..a7d30f8 100644 --- a/localizer.go +++ b/localizer.go @@ -7,3 +7,37 @@ type Localizer interface { // If no translation is found, an empty string should be returned. Localize(ctx context.Context, txt string, args ...any) string } + +// Translation constants +const ( + // 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" +) diff --git a/lock/lock.go b/lock/lock.go index 0b1220b..5a3cce7 100644 --- a/lock/lock.go +++ b/lock/lock.go @@ -14,7 +14,6 @@ const ( StoreAttemptNumber = "attempt_number" StoreAttemptTime = "attempt_time" StoreLocked = "locked" - TranslationLocked = "Your account has been locked, please contact the administrator." ) func init() { @@ -100,7 +99,7 @@ func (l *Lock) updateLockedState(w http.ResponseWriter, r *http.Request, wasCorr ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: l.Localize(r.Context(), TranslationLocked), + Failure: l.Localize(r.Context(), authboss.TxtLocked), RedirectPath: l.Authboss.Config.Paths.LockNotOK, } return true, l.Authboss.Config.Core.Redirector.Redirect(w, r, ro) @@ -159,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: ab.Localize(r.Context(), TranslationLocked), + Failure: ab.Localize(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 ec28386..2d51708 100644 --- a/logout/logout.go +++ b/logout/logout.go @@ -8,8 +8,6 @@ import ( "github.com/volatiletech/authboss/v3" ) -const TranslateLoggedOut = "You have been logged out" - func init() { authboss.RegisterModule("logout", &Logout{}) } @@ -73,7 +71,7 @@ func (l *Logout) Logout(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: l.Authboss.Paths.LogoutOK, - Success: TranslateLoggedOut, + Success: authboss.TxtLoggedOut, } return l.Authboss.Core.Redirector.Redirect(w, r, ro) } diff --git a/oauth2/oauth2.go b/oauth2/oauth2.go index a7e75ce..2c8649a 100644 --- a/oauth2/oauth2.go +++ b/oauth2/oauth2.go @@ -59,10 +59,6 @@ import ( const ( FormValueOAuth2State = "state" FormValueOAuth2Redir = "redir" - - // Translations - TranslationOAuth2LoginOK = "Logged in successfully with %s." - TranslationOAuth2LoginNotOK = "%s login cancelled or failed" ) var errOAuthStateValidation = errors.New("could not validate oauth2 state param") @@ -216,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: o.Localize(r.Context(), TranslationOAuth2LoginNotOK, provider), + Failure: o.Localize(r.Context(), authboss.TxtOAuth2LoginNotOK, provider), } return o.Authboss.Core.Redirector.Redirect(w, r, ro) } @@ -294,7 +290,7 @@ func (o *OAuth2) End(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: redirect, - Success: o.Localize(r.Context(), TranslationOAuth2LoginOK, provider), + Success: o.Localize(r.Context(), authboss.TxtOAuth2LoginOK, provider), } return o.Authboss.Config.Core.Redirector.Redirect(w, r, ro) } diff --git a/recover/recover.go b/recover/recover.go index 69fcef5..3edbba5 100644 --- a/recover/recover.go +++ b/recover/recover.go @@ -31,11 +31,6 @@ const ( PageRecoverStart = "recover_start" PageRecoverMiddle = "recover_middle" PageRecoverEnd = "recover_end" - - TranslateRecoverInitiateSuccessFlash = "An email has been sent to you with further instructions on how to reset your password." - TranslatePasswordResetEmailSubject = "Password Reset" - TranslateRecoverSuccessMsg = "Successfully updated password" - TranslateRecoverAndLoginSuccessMsg = "Successfully updated password and logged in" ) func init() { @@ -97,7 +92,7 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: r.Authboss.Config.Paths.RecoverOK, - Success: TranslateRecoverInitiateSuccessFlash, + Success: authboss.TxtRecoverInitiateSuccessFlash, } return r.Authboss.Core.Redirector.Redirect(w, req, ro) } @@ -148,7 +143,7 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: r.Authboss.Config.Paths.RecoverOK, - Success: TranslateRecoverInitiateSuccessFlash, + Success: authboss.TxtRecoverInitiateSuccessFlash, } return r.Authboss.Core.Redirector.Redirect(w, req, ro) } @@ -164,7 +159,7 @@ func (r *Recover) SendRecoverEmail(ctx context.Context, to []string, encodedToke To: to, From: r.Authboss.Config.Mail.From, FromName: r.Authboss.Config.Mail.FromName, - Subject: r.Authboss.Config.Mail.SubjectPrefix + r.Localize(ctx, TranslatePasswordResetEmailSubject), + Subject: r.Authboss.Config.Mail.SubjectPrefix + r.Localize(ctx, authboss.TxtPasswordResetEmailSubject), } ro := authboss.EmailResponseOptions{ @@ -290,7 +285,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { return err } - successMsg := r.Localize(req.Context(), TranslateRecoverSuccessMsg) + successMsg := r.Localize(req.Context(), authboss.TxtRecoverSuccessMsg) if r.Authboss.Config.Modules.RecoverLoginAfterRecovery { handled, err = r.Events.FireBefore(authboss.EventAuth, w, req) if err != nil { @@ -307,7 +302,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { } authboss.PutSession(w, authboss.SessionKey, user.GetPID()) - successMsg = r.Localize(req.Context(), TranslateRecoverAndLoginSuccessMsg) + successMsg = r.Localize(req.Context(), authboss.TxtRecoverAndLoginSuccessMsg) handled, err = r.Authboss.Events.FireAfter(authboss.EventAuth, w, req) if err != nil { diff --git a/register/register.go b/register/register.go index 4dd23e7..b8d3620 100644 --- a/register/register.go +++ b/register/register.go @@ -14,9 +14,6 @@ import ( // Pages const ( PageRegister = "register" - // Translations - TranslateUserAlreadyExists = "User already exists" - TranslateRegisteredAndLoggedIn = "Account successfully created, you are now logged in" ) func init() { @@ -110,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(TranslateUserAlreadyExists)} + errs = []error{errors.New(authboss.TxtUserAlreadyExists)} data := authboss.HTMLData{ authboss.DataValidation: authboss.ErrorMap(errs), } @@ -137,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: TranslateRegisteredAndLoggedIn, + Success: 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 423c7b4..47e8406 100644 --- a/register/register_test.go +++ b/register/register_test.go @@ -278,7 +278,7 @@ func TestRegisterPostUserExists(t *testing.T) { } errList := h.responder.Data[authboss.DataValidation].(map[string][]string) - if e := errList[""][0]; e != h.ab.Localize(context.Background(), TranslateUserAlreadyExists) { + if e := errList[""][0]; e != h.ab.Localize(context.Background(), authboss.TxtUserAlreadyExists) { t.Error("validation error wrong:", e) } From a0994c37320167f0c904fc9158e161f5f0ab0e19 Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Thu, 30 Nov 2023 01:12:07 +0000 Subject: [PATCH 7/9] Add translation strings for otp and 2fa modules --- localizer.go | 16 +++++++++++ otp/otp.go | 6 ++--- otp/twofactor/sms2fa/sms.go | 8 +++--- otp/twofactor/sms2fa/sms_test.go | 6 ++--- otp/twofactor/totp2fa/totp.go | 37 +++++++++++--------------- otp/twofactor/totp2fa/totp_test.go | 12 ++++----- otp/twofactor/twofactor_verify.go | 8 +++--- otp/twofactor/twofactor_verify_test.go | 6 ++--- 8 files changed, 55 insertions(+), 44 deletions(-) diff --git a/localizer.go b/localizer.go index a7d30f8..1c08de8 100644 --- a/localizer.go +++ b/localizer.go @@ -10,6 +10,8 @@ type Localizer interface { // Translation constants const ( + TxtSuccess = "success" + // Used in the auth module TxtInvalidCredentials = "Invalid Credentials" TxtAuthFailed = "Please login" @@ -40,4 +42,18 @@ const ( 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/otp/otp.go b/otp/otp.go index 61195fa..de792f2 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.Localize(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.Localize(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.Localize(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..649bc89 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.Localize(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.Localize(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.Localize(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..cd789f6 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.Localize(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.Localize(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.Localize(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..eebd3c4 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.Localize(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.Localize(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.Localize(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.Localize( + 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.Localize(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.Localize(r.Context(), authboss.TxtInvalid2FACode), nil } - return user, validationSuccess, nil + return user, t.Localize(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.Localize(r.Context(), authboss.TxtRepeated2FACode), nil } oneTime.PutTOTPLastCode(input) } if !totp.Validate(input, secret) { - return user, validationErrInvalidCode, nil + return user, t.Localize(r.Context(), authboss.TxtInvalid2FACode), nil } - return user, validationSuccess, nil + return user, t.Localize(r.Context(), authboss.TxtSuccess), nil } diff --git a/otp/twofactor/totp2fa/totp_test.go b/otp/twofactor/totp2fa/totp_test.go index c552e15..aaf896b 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.Localize(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.Localize(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.Localize(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.Localize(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.Localize(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.Localize(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..5be7427 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.Localize(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.Localize(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.Localize(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.Localize(r.Context(), authboss.Txt2FAAuthorizationRequired), RedirectPath: redirURL, } diff --git a/otp/twofactor/twofactor_verify_test.go b/otp/twofactor/twofactor_verify_test.go index 2c189c2..1e00c2c 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.Localize(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.Localize(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.Localize(context.Background(), authboss.Txt2FAAuthorizationRequired) { t.Error("did not get correct failure") } }) From 3abb7664784a1842f0606b17d7e55df55d9b99ff Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Fri, 29 Dec 2023 20:43:56 +0000 Subject: [PATCH 8/9] Rename Localize to Localizef --- auth/auth.go | 4 ++-- authboss.go | 8 ++++---- confirm/confirm.go | 12 ++++++------ confirm/confirm_test.go | 6 +++--- localizer.go | 6 ++++-- lock/lock.go | 4 ++-- oauth2/oauth2.go | 4 ++-- otp/otp.go | 6 +++--- otp/twofactor/sms2fa/sms.go | 6 +++--- otp/twofactor/sms2fa/sms_test.go | 6 +++--- otp/twofactor/totp2fa/totp.go | 20 ++++++++++---------- otp/twofactor/totp2fa/totp_test.go | 12 ++++++------ otp/twofactor/twofactor_verify.go | 8 ++++---- otp/twofactor/twofactor_verify_test.go | 6 +++--- recover/recover.go | 6 +++--- register/register_test.go | 2 +- 16 files changed, 59 insertions(+), 57 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 58b9faf..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: a.Localize(r.Context(), authboss.TxtInvalidCredentials)} + 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: a.Localize(r.Context(), authboss.TxtInvalidCredentials)} + 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 12be83a..de127a9 100644 --- a/authboss.go +++ b/authboss.go @@ -101,15 +101,15 @@ func (a *Authboss) VerifyPassword(user AuthableUser, password string) error { return a.Core.Hasher.CompareHashAndPassword(user.GetPassword(), password) } -// Localize is a helper to translate a key using the translator +// 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) Localize(ctx context.Context, text string, args ...any) string { +func (a *Authboss) Localizef(ctx context.Context, text string, args ...any) string { if a.Config.Core.Localizer == nil { return fmt.Sprintf(text, args...) } - if translated := a.Config.Core.Localizer.Localize(ctx, text, args...); translated != "" { + if translated := a.Config.Core.Localizer.Localizef(ctx, text, args...); translated != "" { return translated } @@ -231,7 +231,7 @@ func MountedMiddleware2(ab *Authboss, mountPathed bool, reqs MWRequirements, fai ro := RedirectOptions{ Code: http.StatusTemporaryRedirect, - Failure: ab.Localize(r.Context(), TxtAuthFailed), + Failure: ab.Localizef(r.Context(), TxtAuthFailed), RedirectPath: path.Join(ab.Config.Paths.Mount, fmt.Sprintf("/login?%s", vals.Encode())), } diff --git a/confirm/confirm.go b/confirm/confirm.go index 9bf41e3..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: c.Localize(r.Context(), authboss.TxtAccountNotConfirmed), + 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: c.Localize(r.Context(), authboss.TxtConfirmYourAccount), + 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 + c.Localize(ctx, authboss.TxtConfirmEmailSubject), + 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: c.Localize(r.Context(), authboss.TxtConfrimationSuccess), + 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: c.Localize(r.Context(), authboss.TxtInvalidConfirmToken), + 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: ab.Localize(r.Context(), authboss.TxtAccountNotConfirmed), + 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 595e52b..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 != harness.ab.Localize(context.Background(), authboss.TxtInvalidConfirmToken) { + 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 != harness.ab.Localize(context.Background(), authboss.TxtInvalidConfirmToken) { + 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 != harness.ab.Localize(context.Background(), authboss.TxtInvalidConfirmToken) { + 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 index 1c08de8..a009466 100644 --- a/localizer.go +++ b/localizer.go @@ -1,11 +1,13 @@ package authboss -import "context" +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. - Localize(ctx context.Context, txt string, args ...any) string + Localizef(ctx context.Context, txt string, args ...any) string } // Translation constants diff --git a/lock/lock.go b/lock/lock.go index 5a3cce7..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: l.Localize(r.Context(), authboss.TxtLocked), + 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: ab.Localize(r.Context(), authboss.TxtLocked), + 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/oauth2/oauth2.go b/oauth2/oauth2.go index 2c8649a..7d9975f 100644 --- a/oauth2/oauth2.go +++ b/oauth2/oauth2.go @@ -212,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: o.Localize(r.Context(), authboss.TxtOAuth2LoginNotOK, provider), + Failure: o.Localizef(r.Context(), authboss.TxtOAuth2LoginNotOK, provider), } return o.Authboss.Core.Redirector.Redirect(w, r, ro) } @@ -290,7 +290,7 @@ func (o *OAuth2) End(w http.ResponseWriter, r *http.Request) error { ro := authboss.RedirectOptions{ Code: http.StatusTemporaryRedirect, RedirectPath: redirect, - Success: o.Localize(r.Context(), authboss.TxtOAuth2LoginOK, 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 de792f2..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: o.Localize(r.Context(), authboss.TxtInvalidCredentials)} + 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: o.Localize(r.Context(), authboss.TxtInvalidCredentials)} + 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: o.Localize(r.Context(), authboss.TxtTooManyOTPs, 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 649bc89..fd5e764 100644 --- a/otp/twofactor/sms2fa/sms.go +++ b/otp/twofactor/sms2fa/sms.go @@ -264,7 +264,7 @@ func (s *SMS) PostSetup(w http.ResponseWriter, r *http.Request) error { if len(number) == 0 { data := authboss.HTMLData{ authboss.DataValidation: map[string][]string{FormValuePhoneNumber: { - s.Localize(r.Context(), authboss.TxtSMSNumberRequired), + s.Localizef(r.Context(), authboss.TxtSMSNumberRequired), }}, } return s.Core.Responder.Respond(w, r, http.StatusOK, PageSMSSetup, data) @@ -357,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: s.Localize(r.Context(), authboss.TxtSMSWaitToResend)} + data = authboss.HTMLData{authboss.DataErr: s.Localizef(r.Context(), authboss.TxtSMSWaitToResend)} } else if err != nil { return err } @@ -403,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: {s.Localize(r.Context(), authboss.TxtInvalid2FACode)}}, + 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 cd789f6..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 != h.ab.Localize(context.Background(), authboss.TxtSMSNumberRequired) { + 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 != h.ab.Localize(context.Background(), authboss.TxtInvalid2FACode) { + 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 != h.ab.Localize(context.Background(), authboss.TxtInvalid2FACode) { + 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 eebd3c4..60a2891 100644 --- a/otp/twofactor/totp2fa/totp.go +++ b/otp/twofactor/totp2fa/totp.go @@ -269,7 +269,7 @@ func (t *TOTP) PostConfirm(w http.ResponseWriter, r *http.Request) error { if !ok { data := authboss.HTMLData{ authboss.DataValidation: map[string][]string{FormValueCode: { - t.Localize(r.Context(), authboss.TxtInvalid2FACode), + t.Localizef(r.Context(), authboss.TxtInvalid2FACode), }}, DataTOTPSecret: totpSecret, } @@ -325,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: t.Localize(r.Context(), authboss.TxtTOTP2FANotActive)} + 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 != t.Localize(r.Context(), authboss.TxtSuccess): + 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}}, @@ -368,12 +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: t.Localize( + 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 != t.Localize(r.Context(), authboss.TxtSuccess): + 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 { @@ -471,10 +471,10 @@ func (t *TOTP) validate(r *http.Request) (User, string, error) { return nil, "", err } } else { - return user, t.Localize(r.Context(), authboss.TxtInvalid2FACode), nil + return user, t.Localizef(r.Context(), authboss.TxtInvalid2FACode), nil } - return user, t.Localize(r.Context(), authboss.TxtSuccess), nil + return user, t.Localizef(r.Context(), authboss.TxtSuccess), nil } input := totpCodeValues.GetCode() @@ -482,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, t.Localize(r.Context(), authboss.TxtRepeated2FACode), nil + return user, t.Localizef(r.Context(), authboss.TxtRepeated2FACode), nil } oneTime.PutTOTPLastCode(input) } if !totp.Validate(input, secret) { - return user, t.Localize(r.Context(), authboss.TxtInvalid2FACode), nil + return user, t.Localizef(r.Context(), authboss.TxtInvalid2FACode), nil } - return user, t.Localize(r.Context(), authboss.TxtSuccess), 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 aaf896b..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 != h.ab.Localize(context.Background(), authboss.TxtTOTP2FANotActive) { + 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] != h.ab.Localize(context.Background(), authboss.TxtInvalid2FACode) { + 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 != h.ab.Localize(context.Background(), authboss.TxtTOTP2FANotActive) { + 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] != h.ab.Localize(context.Background(), authboss.TxtInvalid2FACode) { + 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] != h.ab.Localize(context.Background(), authboss.TxtRepeated2FACode) { + 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] != h.ab.Localize(context.Background(), authboss.TxtInvalid2FACode) { + 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 5be7427..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: e.Localize(ctx, authboss.TxtEmailVerifyTriggered), + 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 + e.Localize(ctx, authboss.TxtEmailVerifySubject), + 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: e.Localize(r.Context(), authboss.TxtInvalid2FAVerificationToken), + 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: e.Localize(r.Context(), authboss.Txt2FAAuthorizationRequired), + 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 1e00c2c..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 != h.ab.Localize(context.Background(), authboss.TxtEmailVerifyTriggered) { + 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 != h.ab.Localize(context.Background(), authboss.TxtInvalid2FAVerificationToken) { + 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 != h.ab.Localize(context.Background(), authboss.Txt2FAAuthorizationRequired) { + 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 3edbba5..267a0aa 100644 --- a/recover/recover.go +++ b/recover/recover.go @@ -159,7 +159,7 @@ func (r *Recover) SendRecoverEmail(ctx context.Context, to []string, encodedToke To: to, From: r.Authboss.Config.Mail.From, FromName: r.Authboss.Config.Mail.FromName, - Subject: r.Authboss.Config.Mail.SubjectPrefix + r.Localize(ctx, authboss.TxtPasswordResetEmailSubject), + Subject: r.Authboss.Config.Mail.SubjectPrefix + r.Localizef(ctx, authboss.TxtPasswordResetEmailSubject), } ro := authboss.EmailResponseOptions{ @@ -285,7 +285,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { return err } - successMsg := r.Localize(req.Context(), authboss.TxtRecoverSuccessMsg) + successMsg := r.Localizef(req.Context(), authboss.TxtRecoverSuccessMsg) if r.Authboss.Config.Modules.RecoverLoginAfterRecovery { handled, err = r.Events.FireBefore(authboss.EventAuth, w, req) if err != nil { @@ -302,7 +302,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { } authboss.PutSession(w, authboss.SessionKey, user.GetPID()) - successMsg = r.Localize(req.Context(), authboss.TxtRecoverAndLoginSuccessMsg) + successMsg = r.Localizef(req.Context(), authboss.TxtRecoverAndLoginSuccessMsg) handled, err = r.Authboss.Events.FireAfter(authboss.EventAuth, w, req) if err != nil { diff --git a/register/register_test.go b/register/register_test.go index 47e8406..78bd1ce 100644 --- a/register/register_test.go +++ b/register/register_test.go @@ -278,7 +278,7 @@ func TestRegisterPostUserExists(t *testing.T) { } errList := h.responder.Data[authboss.DataValidation].(map[string][]string) - if e := errList[""][0]; e != h.ab.Localize(context.Background(), authboss.TxtUserAlreadyExists) { + if e := errList[""][0]; e != h.ab.Localizef(context.Background(), authboss.TxtUserAlreadyExists) { t.Error("validation error wrong:", e) } From 6d4f345ef741b782de1176d9136e0ba5285ae527 Mon Sep 17 00:00:00 2001 From: Stephen Afam-Osemene Date: Fri, 29 Dec 2023 23:20:30 +0000 Subject: [PATCH 9/9] Use custom struct as localization key --- authboss.go | 8 +- localizer.go | 200 ++++++++++++++++++++++++++++++++++++------- logout/logout.go | 20 ++--- recover/recover.go | 78 ++++++++--------- register/register.go | 4 +- 5 files changed, 224 insertions(+), 86 deletions(-) diff --git a/authboss.go b/authboss.go index de127a9..21d7cf4 100644 --- a/authboss.go +++ b/authboss.go @@ -104,16 +104,16 @@ func (a *Authboss) VerifyPassword(user AuthableUser, password string) error { // 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, text string, args ...any) string { +func (a *Authboss) Localizef(ctx context.Context, key LocalizationKey, args ...any) string { if a.Config.Core.Localizer == nil { - return fmt.Sprintf(text, args...) + return fmt.Sprintf(key.Default, args...) } - if translated := a.Config.Core.Localizer.Localizef(ctx, text, args...); translated != "" { + if translated := a.Config.Core.Localizer.Localizef(ctx, key, args...); translated != "" { return translated } - return fmt.Sprintf(text, args...) + return fmt.Sprintf(key.Default, args...) } // VerifyPassword uses authboss mechanisms to check that a password is correct. diff --git a/localizer.go b/localizer.go index a009466..8369cd3 100644 --- a/localizer.go +++ b/localizer.go @@ -7,55 +7,193 @@ import ( 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, txt string, args ...any) string + Localizef(ctx context.Context, key LocalizationKey, args ...any) string } -// Translation constants -const ( - TxtSuccess = "success" +type LocalizationKey struct { + ID string + Default string +} + +var ( + TxtSuccess = LocalizationKey{ + ID: "Success", + Default: "success", + } // Used in the auth module - TxtInvalidCredentials = "Invalid Credentials" - TxtAuthFailed = "Please login" + TxtInvalidCredentials = LocalizationKey{ + ID: "InvalidCredentials", + Default: "Invalid Credentials", + } + TxtAuthFailed = LocalizationKey{ + ID: "AuthFailed", + Default: "Please login", + } // Used in the register module - TxtUserAlreadyExists = "User already exists" - TxtRegisteredAndLoggedIn = "Account successfully created, you are now logged in" + 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 = "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" + 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 = "Your account has been locked, please contact the administrator." + TxtLocked = LocalizationKey{ + ID: "Locked", + Default: "Your account has been locked, please contact the administrator.", + } // Used in the logout module - TxtLoggedOut = "You have been logged out" + TxtLoggedOut = LocalizationKey{ + ID: "LoggedOut", + Default: "You have been logged out", + } // Used in the oauth2 module - TxtOAuth2LoginOK = "Logged in successfully with %s." - TxtOAuth2LoginNotOK = "%s login cancelled or failed" + 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 = "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" + 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 = "You cannot have more than %d one time passwords" + TxtTooManyOTPs = LocalizationKey{ + ID: "TooManyOTPs", + Default: "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" + 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/logout/logout.go b/logout/logout.go index 2d51708..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: authboss.TxtLoggedOut, + 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/recover/recover.go b/recover/recover.go index 267a0aa..747357c 100644 --- a/recover/recover.go +++ b/recover/recover.go @@ -47,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 @@ -73,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 } @@ -81,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: authboss.TxtRecoverInitiateSuccessFlash, + 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 } @@ -118,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 } @@ -128,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 } @@ -142,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: authboss.TxtRecoverInitiateSuccessFlash, + 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 + r.Localizef(ctx, authboss.TxtPasswordResetEmailSubject), + From: r.Config.Mail.From, + FromName: r.Config.Mail.FromName, + Subject: r.Config.Mail.SubjectPrefix + r.Localizef(ctx, authboss.TxtPasswordResetEmailSubject), } ro := authboss.EmailResponseOptions{ @@ -171,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) } } @@ -179,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 } @@ -191,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 } @@ -222,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)) @@ -232,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") @@ -259,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 } @@ -280,13 +280,13 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { return err } - _, err = r.Authboss.Events.FireAfter(authboss.EventRecoverEnd, w, req) + _, err = r.Events.FireAfter(authboss.EventRecoverEnd, w, req) if err != nil { return err } successMsg := r.Localizef(req.Context(), authboss.TxtRecoverSuccessMsg) - if r.Authboss.Config.Modules.RecoverLoginAfterRecovery { + if r.Config.Modules.RecoverLoginAfterRecovery { handled, err = r.Events.FireBefore(authboss.EventAuth, w, req) if err != nil { return err @@ -304,7 +304,7 @@ func (r *Recover) EndPost(w http.ResponseWriter, req *http.Request) error { authboss.PutSession(w, authboss.SessionKey, user.GetPID()) 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 { @@ -314,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 b8d3620..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(authboss.TxtUserAlreadyExists)} + 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: authboss.TxtRegisteredAndLoggedIn, + Success: r.Localizef(req.Context(), authboss.TxtRegisteredAndLoggedIn), RedirectPath: r.Config.Paths.RegisterOK, } return r.Config.Core.Redirector.Redirect(w, req, ro)