From 3e47a4f66414fb922541d6f1cb9d4bf9d3627936 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sat, 30 Dec 2023 19:12:22 +0100 Subject: [PATCH] WebAdmin: use the new theme for the login and setup page Signed-off-by: Nicola Murino --- README.md | 6 +- internal/httpd/api_admin.go | 2 +- internal/httpd/api_user.go | 2 +- internal/httpd/api_utils.go | 6 +- internal/httpd/httpd.go | 10 +- internal/httpd/server.go | 105 +++++---- internal/httpd/web.go | 9 +- internal/httpd/webadmin.go | 93 ++++---- internal/httpd/webclient.go | 136 ++++-------- internal/util/i18n.go | 2 + static/locales/en/translation.json | 7 + static/locales/it/translation.json | 9 +- templates/common/base.html | 2 +- templates/common/forgot-password.html | 145 +++++------- templates/{webclient => common}/login.html | 0 templates/common/reset-password.html | 187 ++++++++-------- .../twofactor-recovery.html | 0 .../{webclient => common}/twofactor.html | 0 templates/webadmin/adminsetup.html | 210 +++++++++--------- templates/webadmin/baselogin.html | 90 -------- templates/webadmin/login.html | 157 +++++++------ templates/webadmin/twofactor-recovery.html | 47 ---- templates/webadmin/twofactor.html | 52 ----- templates/webclient/forgot-password.html | 59 ----- templates/webclient/reset-password.html | 97 -------- 25 files changed, 514 insertions(+), 919 deletions(-) rename templates/{webclient => common}/login.html (100%) rename templates/{webclient => common}/twofactor-recovery.html (100%) rename templates/{webclient => common}/twofactor.html (100%) delete mode 100644 templates/webadmin/baselogin.html delete mode 100644 templates/webadmin/twofactor-recovery.html delete mode 100644 templates/webadmin/twofactor.html delete mode 100644 templates/webclient/forgot-password.html delete mode 100644 templates/webclient/reset-password.html diff --git a/README.md b/README.md index efe723a8..f32abf72 100644 --- a/README.md +++ b/README.md @@ -366,7 +366,7 @@ SFTPGo makes use of the third party libraries listed inside [go.mod](./go.mod). We are very grateful to all the people who contributed with ideas and/or pull requests. -Thank you [ysura](https://www.ysura.com/) for granting us stable access to a test AWS S3 account. +Thank you to [ysura](https://www.ysura.com/) for granting us stable access to a test AWS S3 account. Thank you to [KeenThemes](https://keenthemes.com/) for granting us a custom license to use their amazing [Mega Bundle](https://keenthemes.com/products/templates-mega-bundle) for SFTPGo UI. @@ -374,7 +374,7 @@ Thank you to [KeenThemes](https://keenthemes.com/) for granting us a custom lice GNU AGPL-3.0-only -The [theme](https://keenthemes.com/products/templates-mega-bundle) used in WebClient UI is proprietary, this means: +The [theme](https://keenthemes.com/products/templates-mega-bundle) used in WebAdmin and WebClient user interfaces is proprietary, this means: - KeenThemes HTML/CSS/JS components are allowed for use only within the SFTPGo product and restricted to be used in a resealable HTML template that can compete with KeenThemes products anyhow. -- The SFTPGo WebClient UI (HTML, CSS and JS components) based on this theme is allowed for use only within the SFTPGo product and therefore cannot be used in derivative works/products without an explicit grant from the [SFTPGo Team](mailto:support@sftpgo.com). +- The SFTPGo WebAdmin and WebClient user interfaces (HTML, CSS and JS components) based on this theme are allowed for use only within the SFTPGo product and therefore cannot be used in derivative works/products without an explicit grant from the [SFTPGo Team](mailto:support@sftpgo.com). diff --git a/internal/httpd/api_admin.go b/internal/httpd/api_admin.go index 3062655d..5a7057c8 100644 --- a/internal/httpd/api_admin.go +++ b/internal/httpd/api_admin.go @@ -262,7 +262,7 @@ func resetAdminPassword(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", http.StatusBadRequest) return } - _, _, err = handleResetPassword(r, req.Code, req.Password, true) + _, _, err = handleResetPassword(r, req.Code, req.Password, req.Password, true) if err != nil { sendAPIResponse(w, r, err, "", getRespStatus(err)) return diff --git a/internal/httpd/api_user.go b/internal/httpd/api_user.go index 136a700e..c08b37a4 100644 --- a/internal/httpd/api_user.go +++ b/internal/httpd/api_user.go @@ -243,7 +243,7 @@ func resetUserPassword(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "", http.StatusBadRequest) return } - _, _, err = handleResetPassword(r, req.Code, req.Password, false) + _, _, err = handleResetPassword(r, req.Code, req.Password, req.Password, false) if err != nil { sendAPIResponse(w, r, err, "", getRespStatus(err)) return diff --git a/internal/httpd/api_utils.go b/internal/httpd/api_utils.go index ee85ff4e..74db6b70 100644 --- a/internal/httpd/api_utils.go +++ b/internal/httpd/api_utils.go @@ -721,7 +721,7 @@ func handleForgotPassword(r *http.Request, username string, isAdmin bool) error return resetCodesMgr.Add(c) } -func handleResetPassword(r *http.Request, code, newPassword string, isAdmin bool) ( +func handleResetPassword(r *http.Request, code, newPassword, confirmPassword string, isAdmin bool) ( *dataprovider.Admin, *dataprovider.User, error, ) { var admin dataprovider.Admin @@ -734,6 +734,10 @@ func handleResetPassword(r *http.Request, code, newPassword string, isAdmin bool if code == "" { return &admin, &user, util.NewValidationError("please set a confirmation code") } + if newPassword != confirmPassword { + return &admin, &user, util.NewI18nError(errors.New("the two password fields do not match"), util.I18nErrorChangePwdNoMatch) + } + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) resetCode, err := resetCodesMgr.Get(code) if err != nil { diff --git a/internal/httpd/httpd.go b/internal/httpd/httpd.go index 8241ab9d..957dbe96 100644 --- a/internal/httpd/httpd.go +++ b/internal/httpd/httpd.go @@ -441,13 +441,9 @@ func (b *UIBranding) check(isWebClient bool) { b.DefaultCSS[idx] = util.CleanPath(b.DefaultCSS[idx]) } } else { - if isWebClient { - b.DefaultCSS = []string{ - "/assets/plugins/global/plugins.bundle.css", - "/assets/css/style.bundle.css", - } - } else { - b.DefaultCSS = []string{"/css/sb-admin-2.min.css"} + b.DefaultCSS = []string{ + "/assets/plugins/global/plugins.bundle.css", + "/assets/css/style.bundle.css", } } for idx := range b.ExtraCSS { diff --git a/internal/httpd/server.go b/internal/httpd/server.go index dd23d989..1912c60f 100644 --- a/internal/httpd/server.go +++ b/internal/httpd/server.go @@ -161,7 +161,7 @@ func (s *httpdServer) refreshCookie(next http.Handler) http.Handler { } func (s *httpdServer) renderClientLoginPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { - data := clientLoginPage{ + data := loginPage{ commonBasePage: getCommonBasePage(r), Title: util.I18nLoginTitle, CurrentURL: webClientLoginPath, @@ -183,7 +183,7 @@ func (s *httpdServer) renderClientLoginPage(w http.ResponseWriter, r *http.Reque if s.binding.OIDC.isEnabled() && !s.binding.isWebClientOIDCLoginDisabled() { data.OpenIDLoginURL = webClientOIDCLoginPath } - renderClientTemplate(w, templateClientLogin, data) + renderClientTemplate(w, templateCommonLogin, data) } func (s *httpdServer) handleWebClientLogout(w http.ResponseWriter, r *http.Request) { @@ -296,14 +296,8 @@ func (s *httpdServer) handleWebClientPasswordResetPost(w http.ResponseWriter, r } newPassword := strings.TrimSpace(r.Form.Get("password")) confirmPassword := strings.TrimSpace(r.Form.Get("confirm_password")) - if newPassword != confirmPassword { - s.renderClientResetPwdPage(w, r, util.NewI18nError( - errors.New("the two password fields do not match"), - util.I18nErrorChangePwdNoMatch), ipAddr) - return - } _, user, err := handleResetPassword(r, strings.TrimSpace(r.Form.Get("code")), - newPassword, false) + newPassword, confirmPassword, false) if err != nil { s.renderClientResetPwdPage(w, r, util.NewI18nError(err, util.I18nErrorChangePwdGeneric), ipAddr) return @@ -457,17 +451,18 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, } ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) if err := r.ParseForm(); err != nil { - s.renderTwoFactorRecoveryPage(w, r, err.Error(), ipAddr) + s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr) return } username := claims.Username recoveryCode := strings.TrimSpace(r.Form.Get("recovery_code")) if username == "" || recoveryCode == "" { - s.renderTwoFactorRecoveryPage(w, r, "Invalid credentials", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), + ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { - s.renderTwoFactorRecoveryPage(w, r, err.Error(), ipAddr) + s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF), ipAddr) return } admin, err := dataprovider.AdminExists(username) @@ -475,11 +470,12 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, if errors.Is(err, util.ErrNotFound) { handleDefenderEventLoginFailed(ipAddr, err) //nolint:errcheck } - s.renderTwoFactorRecoveryPage(w, r, "Invalid credentials", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), + ipAddr) return } if !admin.Filters.TOTPConfig.Enabled { - s.renderTwoFactorRecoveryPage(w, r, "Two factory authentication is not enabled", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(util.NewValidationError("two factory authentication is not enabled"), util.I18n2FADisabled), ipAddr) return } for idx, code := range admin.Filters.RecoveryCodes { @@ -489,7 +485,8 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, } if code.Secret.GetPayload() == recoveryCode { if code.Used { - s.renderTwoFactorRecoveryPage(w, r, "This recovery code was already used", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, + util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), ipAddr) return } admin.Filters.RecoveryCodes[idx].Used = true @@ -504,7 +501,8 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, } } handleDefenderEventLoginFailed(ipAddr, dataprovider.ErrInvalidCredentials) //nolint:errcheck - s.renderTwoFactorRecoveryPage(w, r, "Invalid recovery code", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), + ipAddr) } func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http.Request) { @@ -516,18 +514,19 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http } ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) if err := r.ParseForm(); err != nil { - s.renderTwoFactorPage(w, r, err.Error(), ipAddr) + s.renderTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr) return } username := claims.Username passcode := strings.TrimSpace(r.Form.Get("passcode")) if username == "" || passcode == "" { - s.renderTwoFactorPage(w, r, "Invalid credentials", ipAddr) + s.renderTwoFactorPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), + ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { err = handleDefenderEventLoginFailed(ipAddr, err) - s.renderTwoFactorPage(w, r, err.Error(), ipAddr) + s.renderTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF), ipAddr) return } admin, err := dataprovider.AdminExists(username) @@ -535,11 +534,11 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http if errors.Is(err, util.ErrNotFound) { handleDefenderEventLoginFailed(ipAddr, err) //nolint:errcheck } - s.renderTwoFactorPage(w, r, "Invalid credentials", ipAddr) + s.renderTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCredentials), ipAddr) return } if !admin.Filters.TOTPConfig.Enabled { - s.renderTwoFactorPage(w, r, "Two factory authentication is not enabled", ipAddr) + s.renderTwoFactorPage(w, r, util.NewI18nError(common.ErrInternalFailure, util.I18n2FADisabled), ipAddr) return } err = admin.Filters.TOTPConfig.Secret.Decrypt() @@ -551,7 +550,8 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http admin.Filters.TOTPConfig.Secret.GetPayload()) if !match || err != nil { handleDefenderEventLoginFailed(ipAddr, dataprovider.ErrInvalidCredentials) //nolint:errcheck - s.renderTwoFactorPage(w, r, "Invalid authentication code", ipAddr) + s.renderTwoFactorPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), + ipAddr) return } s.loginAdmin(w, r, &admin, true, s.renderTwoFactorPage, ipAddr) @@ -562,34 +562,36 @@ func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Req ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) if err := r.ParseForm(); err != nil { - s.renderAdminLoginPage(w, r, err.Error(), ipAddr) + s.renderAdminLoginPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr) return } username := strings.TrimSpace(r.Form.Get("username")) password := strings.TrimSpace(r.Form.Get("password")) if username == "" || password == "" { - s.renderAdminLoginPage(w, r, "Invalid credentials", ipAddr) + s.renderAdminLoginPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), + ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { - s.renderAdminLoginPage(w, r, err.Error(), ipAddr) + s.renderAdminLoginPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF), ipAddr) return } admin, err := dataprovider.CheckAdminAndPass(username, password, ipAddr) if err != nil { - err = handleDefenderEventLoginFailed(ipAddr, err) - s.renderAdminLoginPage(w, r, err.Error(), ipAddr) + handleDefenderEventLoginFailed(ipAddr, err) + s.renderAdminLoginPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), + ipAddr) return } s.loginAdmin(w, r, &admin, false, s.renderAdminLoginPage, ipAddr) } -func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, r *http.Request, error, ip string) { +func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { data := loginPage{ commonBasePage: getCommonBasePage(r), Title: util.I18nLoginTitle, CurrentURL: webAdminLoginPath, - Error: error, + Error: err, CSRFToken: createCSRFToken(ip), Branding: s.binding.Branding.WebAdmin, FormDisabled: s.binding.isWebAdminLoginFormDisabled(), @@ -604,7 +606,7 @@ func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, r *http.Reques if s.binding.OIDC.hasRoles() && !s.binding.isWebAdminOIDCLoginDisabled() { data.OpenIDLoginURL = webAdminOIDCLoginPath } - renderAdminTemplate(w, templateLogin, data) + renderAdminTemplate(w, templateCommonLogin, data) } func (s *httpdServer) handleWebAdminLogin(w http.ResponseWriter, r *http.Request) { @@ -613,7 +615,8 @@ func (s *httpdServer) handleWebAdminLogin(w http.ResponseWriter, r *http.Request http.Redirect(w, r, webAdminSetupPath, http.StatusFound) return } - s.renderAdminLoginPage(w, r, getFlashMessage(w, r).ErrorString, util.GetIPFromRemoteAddress(r.RemoteAddr)) + msg := getFlashMessage(w, r) + s.renderAdminLoginPage(w, r, msg.getI18nError(), util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminLogout(w http.ResponseWriter, r *http.Request) { @@ -651,22 +654,19 @@ func (s *httpdServer) handleWebAdminPasswordResetPost(w http.ResponseWriter, r * ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) err := r.ParseForm() if err != nil { - s.renderResetPwdPage(w, r, err.Error(), ipAddr) + s.renderResetPwdPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { s.renderForbiddenPage(w, r, err.Error()) return } + newPassword := strings.TrimSpace(r.Form.Get("password")) + confirmPassword := strings.TrimSpace(r.Form.Get("confirm_password")) admin, _, err := handleResetPassword(r, strings.TrimSpace(r.Form.Get("code")), - strings.TrimSpace(r.Form.Get("password")), true) + newPassword, confirmPassword, true) if err != nil { - var e *util.ValidationError - if errors.As(err, &e) { - s.renderResetPwdPage(w, r, e.GetErrorString(), ipAddr) - return - } - s.renderResetPwdPage(w, r, err.Error(), ipAddr) + s.renderResetPwdPage(w, r, util.NewI18nError(err, util.I18nErrorChangePwdGeneric), ipAddr) return } @@ -679,12 +679,12 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req s.renderBadRequestPage(w, r, errors.New("an admin user already exists")) return } + ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) err := r.ParseForm() if err != nil { - s.renderAdminSetupPage(w, r, "", err.Error()) + s.renderAdminSetupPage(w, r, "", ipAddr, util.NewI18nError(err, util.I18nErrorInvalidForm)) return } - ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { s.renderForbiddenPage(w, r, err.Error()) return @@ -694,19 +694,26 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req confirmPassword := strings.TrimSpace(r.Form.Get("confirm_password")) installCode := strings.TrimSpace(r.Form.Get("install_code")) if installationCode != "" && installCode != resolveInstallationCode() { - s.renderAdminSetupPage(w, r, username, fmt.Sprintf("%v mismatch", installationCodeHint)) + s.renderAdminSetupPage(w, r, username, ipAddr, + util.NewI18nError( + util.NewValidationError(fmt.Sprintf("%v mismatch", installationCodeHint)), + util.I18nErrorSetupInstallCode), + ) return } if username == "" { - s.renderAdminSetupPage(w, r, username, "Please set a username") + s.renderAdminSetupPage(w, r, username, ipAddr, + util.NewI18nError(util.NewValidationError("please set a username"), util.I18nError500Message)) return } if password == "" { - s.renderAdminSetupPage(w, r, username, "Please set a password") + s.renderAdminSetupPage(w, r, username, ipAddr, + util.NewI18nError(util.NewValidationError("please set a password"), util.I18nError500Message)) return } if password != confirmPassword { - s.renderAdminSetupPage(w, r, username, "Passwords mismatch") + s.renderAdminSetupPage(w, r, username, ipAddr, + util.NewI18nError(errors.New("the two password fields do not match"), util.I18nErrorChangePwdNoMatch)) return } admin := dataprovider.Admin{ @@ -717,7 +724,7 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req } err = dataprovider.AddAdmin(&admin, username, ipAddr, "") if err != nil { - s.renderAdminSetupPage(w, r, username, err.Error()) + s.renderAdminSetupPage(w, r, username, ipAddr, util.NewI18nError(err, util.I18nError500Message)) return } s.loginAdmin(w, r, &admin, false, nil, ipAddr) @@ -772,7 +779,7 @@ func (s *httpdServer) loginUser( func (s *httpdServer) loginAdmin( w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin, - isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, r *http.Request, error, ip string), + isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string), ipAddr string, ) { c := jwtTokenClaims{ @@ -792,10 +799,10 @@ func (s *httpdServer) loginAdmin( if err != nil { logger.Warn(logSender, "", "unable to set admin login cookie %v", err) if errorFunc == nil { - s.renderAdminSetupPage(w, r, admin.Username, err.Error()) + s.renderAdminSetupPage(w, r, admin.Username, ipAddr, util.NewI18nError(err, util.I18nError500Message)) return } - errorFunc(w, r, err.Error(), ipAddr) + errorFunc(w, r, util.NewI18nError(err, util.I18nError500Message), ipAddr) return } if isSecondFactorAuth { diff --git a/internal/httpd/web.go b/internal/httpd/web.go index bdaf233c..fec879b0 100644 --- a/internal/httpd/web.go +++ b/internal/httpd/web.go @@ -49,6 +49,7 @@ const ( templateCommonCSS = "sftpgo.css" templateCommonBase = "base.html" templateCommonBaseLogin = "baselogin.html" + templateCommonLogin = "login.html" ) var ( @@ -64,7 +65,7 @@ type commonBasePage struct { type loginPage struct { commonBasePage CurrentURL string - Error string + Error *util.I18nError CSRFToken string AltLoginURL string AltLoginName string @@ -78,7 +79,7 @@ type loginPage struct { type twoFactorPage struct { commonBasePage CurrentURL string - Error string + Error *util.I18nError CSRFToken string RecoveryURL string Title string @@ -88,7 +89,7 @@ type twoFactorPage struct { type forgotPwdPage struct { commonBasePage CurrentURL string - Error string + Error *util.I18nError CSRFToken string LoginURL string Title string @@ -98,7 +99,7 @@ type forgotPwdPage struct { type resetPwdPage struct { commonBasePage CurrentURL string - Error string + Error *util.I18nError CSRFToken string LoginURL string Title string diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index d81fafee..afebc990 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -72,7 +72,6 @@ const ( const ( templateAdminDir = "webadmin" templateBase = "base.html" - templateBaseLogin = "baselogin.html" templateFsConfig = "fsconfig.html" templateSharedComponents = "sharedcomponents.html" templateUsers = "users.html" @@ -93,7 +92,6 @@ const ( templateEvents = "events.html" templateMessage = "message.html" templateStatus = "status.html" - templateLogin = "login.html" templateDefender = "defender.html" templateIPLists = "iplists.html" templateIPList = "iplist.html" @@ -119,8 +117,6 @@ const ( pageIPListsTitle = "IP Lists" pageEventsTitle = "Logs" pageConfigsTitle = "Configurations" - pageForgotPwdTitle = "Forgot password" - pageResetPwdTitle = "Reset password" pageSetupTitle = "Create first admin user" defaultQueryLimit = 1000 inversePatternType = "inverse" @@ -323,12 +319,16 @@ type ipListPage struct { } type setupPage struct { - basePage + commonBasePage + CurrentURL string + Error *util.I18nError + CSRFToken string Username string HasInstallationCode bool InstallationCodeHint string HideSupportLink bool - Error string + Title string + Branding UIBranding } type folderPage struct { @@ -506,9 +506,9 @@ func loadAdminTemplates(templatesPath string) { filepath.Join(templatesPath, templateAdminDir, templateStatus), } loginPaths := []string{ - filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), - filepath.Join(templatesPath, templateAdminDir, templateBaseLogin), - filepath.Join(templatesPath, templateAdminDir, templateLogin), + filepath.Join(templatesPath, templateCommonDir, templateCommonBase), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateCommonLogin), } maintenancePaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), @@ -536,26 +536,28 @@ func loadAdminTemplates(templatesPath string) { filepath.Join(templatesPath, templateAdminDir, templateMFA), } twoFactorPaths := []string{ - filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), - filepath.Join(templatesPath, templateAdminDir, templateBaseLogin), - filepath.Join(templatesPath, templateAdminDir, templateTwoFactor), + filepath.Join(templatesPath, templateCommonDir, templateCommonBase), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateTwoFactor), } twoFactorRecoveryPaths := []string{ - filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), - filepath.Join(templatesPath, templateAdminDir, templateBaseLogin), - filepath.Join(templatesPath, templateAdminDir, templateTwoFactorRecovery), + filepath.Join(templatesPath, templateCommonDir, templateCommonBase), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateTwoFactorRecovery), } setupPaths := []string{ - filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), - filepath.Join(templatesPath, templateAdminDir, templateBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateCommonBase), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), filepath.Join(templatesPath, templateAdminDir, templateSetup), } forgotPwdPaths := []string{ - filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), + filepath.Join(templatesPath, templateCommonDir, templateCommonBase), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), filepath.Join(templatesPath, templateCommonDir, templateForgotPassword), } resetPwdPaths := []string{ - filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), + filepath.Join(templatesPath, templateCommonDir, templateCommonBase), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), filepath.Join(templatesPath, templateCommonDir, templateResetPassword), } rolesPaths := []string{ @@ -636,7 +638,7 @@ func loadAdminTemplates(templatesPath string) { adminTemplates[templateEventActions] = eventActionsTmpl adminTemplates[templateEventAction] = eventActionTmpl adminTemplates[templateStatus] = statusTmpl - adminTemplates[templateLogin] = loginTmpl + adminTemplates[templateCommonLogin] = loginTmpl adminTemplates[templateProfile] = profileTmpl adminTemplates[templateChangePwd] = changePwdTmpl adminTemplates[templateMaintenance] = maintenanceTmpl @@ -797,36 +799,38 @@ func (s *httpdServer) renderNotFoundPage(w http.ResponseWriter, r *http.Request, s.renderMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "") } -func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, r *http.Request, error, ip string) { +func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { data := forgotPwdPage{ commonBasePage: getCommonBasePage(r), CurrentURL: webAdminForgotPwdPath, - Error: error, + Error: err, CSRFToken: createCSRFToken(ip), - Title: pageForgotPwdTitle, + LoginURL: webAdminLoginPath, + Title: util.I18nForgotPwdTitle, Branding: s.binding.Branding.WebAdmin, } renderAdminTemplate(w, templateForgotPassword, data) } -func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, r *http.Request, error, ip string) { +func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { data := resetPwdPage{ commonBasePage: getCommonBasePage(r), CurrentURL: webAdminResetPwdPath, - Error: error, + Error: err, CSRFToken: createCSRFToken(ip), - Title: pageResetPwdTitle, + LoginURL: webAdminLoginPath, + Title: util.I18nResetPwdTitle, Branding: s.binding.Branding.WebAdmin, } renderAdminTemplate(w, templateResetPassword, data) } -func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, r *http.Request, error, ip string) { +func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { data := twoFactorPage{ commonBasePage: getCommonBasePage(r), Title: pageTwoFactorTitle, CurrentURL: webAdminTwoFactorPath, - Error: error, + Error: err, CSRFToken: createCSRFToken(ip), RecoveryURL: webAdminTwoFactorRecoveryPath, Branding: s.binding.Branding.WebAdmin, @@ -834,12 +838,12 @@ func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, r *http.Request renderAdminTemplate(w, templateTwoFactor, data) } -func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, error, ip string) { +func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { data := twoFactorPage{ commonBasePage: getCommonBasePage(r), Title: pageTwoFactorRecoveryTitle, CurrentURL: webAdminTwoFactorRecoveryPath, - Error: error, + Error: err, CSRFToken: createCSRFToken(ip), Branding: s.binding.Branding.WebAdmin, } @@ -926,14 +930,18 @@ func (s *httpdServer) renderConfigsPage(w http.ResponseWriter, r *http.Request, renderAdminTemplate(w, templateConfigs, data) } -func (s *httpdServer) renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, error string) { +func (s *httpdServer) renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, ip string, err *util.I18nError) { data := setupPage{ - basePage: s.getBasePageData(pageSetupTitle, webAdminSetupPath, r), + commonBasePage: getCommonBasePage(r), + Title: util.I18nSetupTitle, + CurrentURL: webAdminSetupPath, + CSRFToken: createCSRFToken(ip), Username: username, HasInstallationCode: installationCode != "", InstallationCodeHint: installationCodeHint, HideSupportLink: hideSupportLink, - Error: error, + Error: err, + Branding: s.binding.Branding.WebAdmin, } renderAdminTemplate(w, templateSetup, data) @@ -2634,7 +2642,7 @@ func (s *httpdServer) handleWebAdminForgotPwd(w http.ResponseWriter, r *http.Req s.renderNotFoundPage(w, r, errors.New("this page does not exist")) return } - s.renderForgotPwdPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderForgotPwdPage(w, r, nil, util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http.Request) { @@ -2643,7 +2651,7 @@ func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) err := r.ParseForm() if err != nil { - s.renderForgotPwdPage(w, r, err.Error(), ipAddr) + s.renderForgotPwdPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { @@ -2652,12 +2660,7 @@ func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http } err = handleForgotPassword(r, r.Form.Get("username"), true) if err != nil { - var e *util.ValidationError - if errors.As(err, &e) { - s.renderForgotPwdPage(w, r, e.GetErrorString(), ipAddr) - return - } - s.renderForgotPwdPage(w, r, err.Error(), ipAddr) + s.renderForgotPwdPage(w, r, util.NewI18nError(err, util.I18nErrorPwdResetGeneric), ipAddr) return } http.Redirect(w, r, webAdminResetPwdPath, http.StatusFound) @@ -2669,17 +2672,17 @@ func (s *httpdServer) handleWebAdminPasswordReset(w http.ResponseWriter, r *http s.renderNotFoundPage(w, r, errors.New("this page does not exist")) return } - s.renderResetPwdPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderResetPwdPage(w, r, nil, util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminTwoFactor(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - s.renderTwoFactorPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderTwoFactorPage(w, r, nil, util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminTwoFactorRecovery(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - s.renderTwoFactorRecoveryPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderTwoFactorRecoveryPage(w, r, nil, util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminMFA(w http.ResponseWriter, r *http.Request) { @@ -2824,7 +2827,7 @@ func (s *httpdServer) handleWebAdminSetupGet(w http.ResponseWriter, r *http.Requ http.Redirect(w, r, webAdminLoginPath, http.StatusFound) return } - s.renderAdminSetupPage(w, r, "", "") + s.renderAdminSetupPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr), nil) } func (s *httpdServer) handleWebAddAdminGet(w http.ResponseWriter, r *http.Request) { diff --git a/internal/httpd/webclient.go b/internal/httpd/webclient.go index 1f1233ba..59adadca 100644 --- a/internal/httpd/webclient.go +++ b/internal/httpd/webclient.go @@ -45,23 +45,20 @@ import ( ) const ( - templateClientDir = "webclient" - templateClientBase = "base.html" - templateClientLogin = "login.html" - templateClientFiles = "files.html" - templateClientMessage = "message.html" - templateClientProfile = "profile.html" - templateClientChangePwd = "changepassword.html" - templateClientTwoFactor = "twofactor.html" - templateClientTwoFactorRecovery = "twofactor-recovery.html" - templateClientMFA = "mfa.html" - templateClientEditFile = "editfile.html" - templateClientShare = "share.html" - templateClientShares = "shares.html" - templateClientViewPDF = "viewpdf.html" - templateShareLogin = "sharelogin.html" - templateShareDownload = "sharedownload.html" - templateUploadToShare = "shareupload.html" + templateClientDir = "webclient" + templateClientBase = "base.html" + templateClientFiles = "files.html" + templateClientMessage = "message.html" + templateClientProfile = "profile.html" + templateClientChangePwd = "changepassword.html" + templateClientMFA = "mfa.html" + templateClientEditFile = "editfile.html" + templateClientShare = "share.html" + templateClientShares = "shares.html" + templateClientViewPDF = "viewpdf.html" + templateShareLogin = "sharelogin.html" + templateShareDownload = "sharedownload.html" + templateUploadToShare = "shareupload.html" ) // condResult is the result of an HTTP request precondition check. @@ -212,51 +209,6 @@ type clientSharePage struct { IsAdd bool } -// TODO: merge with loginPage once the WebAdmin supports localization -type clientLoginPage struct { - commonBasePage - CurrentURL string - Error *util.I18nError - CSRFToken string - AltLoginURL string - AltLoginName string - ForgotPwdURL string - OpenIDLoginURL string - Title string - Branding UIBranding - FormDisabled bool -} - -type clientResetPwdPage struct { - commonBasePage - CurrentURL string - Error *util.I18nError - CSRFToken string - LoginURL string - Title string - Branding UIBranding -} - -type clientTwoFactorPage struct { - commonBasePage - CurrentURL string - Error *util.I18nError - CSRFToken string - RecoveryURL string - Title string - Branding UIBranding -} - -type clientForgotPwdPage struct { - commonBasePage - CurrentURL string - Error *util.I18nError - CSRFToken string - LoginURL string - Title string - Branding UIBranding -} - type userQuotaUsage struct { QuotaSize int64 QuotaFiles int @@ -478,40 +430,40 @@ func loadClientTemplates(templatesPath string) { filepath.Join(templatesPath, templateClientDir, templateClientBase), filepath.Join(templatesPath, templateClientDir, templateClientChangePwd), } - loginPath := []string{ + loginPaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), - filepath.Join(templatesPath, templateCommonDir, templateBaseLogin), - filepath.Join(templatesPath, templateClientDir, templateClientLogin), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateCommonLogin), } - messagePath := []string{ + messagePaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), filepath.Join(templatesPath, templateClientDir, templateClientBase), filepath.Join(templatesPath, templateClientDir, templateClientMessage), } - mfaPath := []string{ + mfaPaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), filepath.Join(templatesPath, templateClientDir, templateClientBase), filepath.Join(templatesPath, templateClientDir, templateClientMFA), } - twoFactorPath := []string{ + twoFactorPaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), - filepath.Join(templatesPath, templateCommonDir, templateBaseLogin), - filepath.Join(templatesPath, templateClientDir, templateClientTwoFactor), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateTwoFactor), } - twoFactorRecoveryPath := []string{ + twoFactorRecoveryPaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), - filepath.Join(templatesPath, templateCommonDir, templateBaseLogin), - filepath.Join(templatesPath, templateClientDir, templateClientTwoFactorRecovery), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateTwoFactorRecovery), } forgotPwdPaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), - filepath.Join(templatesPath, templateCommonDir, templateBaseLogin), - filepath.Join(templatesPath, templateClientDir, templateForgotPassword), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateForgotPassword), } resetPwdPaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), - filepath.Join(templatesPath, templateCommonDir, templateBaseLogin), - filepath.Join(templatesPath, templateClientDir, templateResetPassword), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateResetPassword), } viewPDFPaths := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), @@ -519,7 +471,7 @@ func loadClientTemplates(templatesPath string) { } shareLoginPath := []string{ filepath.Join(templatesPath, templateCommonDir, templateCommonBase), - filepath.Join(templatesPath, templateCommonDir, templateBaseLogin), + filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin), filepath.Join(templatesPath, templateClientDir, templateShareLogin), } shareUploadPath := []string{ @@ -536,11 +488,11 @@ func loadClientTemplates(templatesPath string) { filesTmpl := util.LoadTemplate(nil, filesPaths...) profileTmpl := util.LoadTemplate(nil, profilePaths...) changePwdTmpl := util.LoadTemplate(nil, changePwdPaths...) - loginTmpl := util.LoadTemplate(nil, loginPath...) - messageTmpl := util.LoadTemplate(nil, messagePath...) - mfaTmpl := util.LoadTemplate(nil, mfaPath...) - twoFactorTmpl := util.LoadTemplate(nil, twoFactorPath...) - twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPath...) + loginTmpl := util.LoadTemplate(nil, loginPaths...) + messageTmpl := util.LoadTemplate(nil, messagePaths...) + mfaTmpl := util.LoadTemplate(nil, mfaPaths...) + twoFactorTmpl := util.LoadTemplate(nil, twoFactorPaths...) + twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPaths...) editFileTmpl := util.LoadTemplate(nil, editFilePath...) shareLoginTmpl := util.LoadTemplate(nil, shareLoginPath...) sharesTmpl := util.LoadTemplate(nil, sharesPaths...) @@ -554,11 +506,11 @@ func loadClientTemplates(templatesPath string) { clientTemplates[templateClientFiles] = filesTmpl clientTemplates[templateClientProfile] = profileTmpl clientTemplates[templateClientChangePwd] = changePwdTmpl - clientTemplates[templateClientLogin] = loginTmpl + clientTemplates[templateCommonLogin] = loginTmpl clientTemplates[templateClientMessage] = messageTmpl clientTemplates[templateClientMFA] = mfaTmpl - clientTemplates[templateClientTwoFactor] = twoFactorTmpl - clientTemplates[templateClientTwoFactorRecovery] = twoFactorRecoveryTmpl + clientTemplates[templateTwoFactor] = twoFactorTmpl + clientTemplates[templateTwoFactorRecovery] = twoFactorRecoveryTmpl clientTemplates[templateClientEditFile] = editFileTmpl clientTemplates[templateClientShares] = sharesTmpl clientTemplates[templateClientShare] = shareTmpl @@ -600,7 +552,7 @@ func (s *httpdServer) getBaseClientPageData(title, currentURL string, r *http.Re } func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { - data := clientForgotPwdPage{ + data := forgotPwdPage{ commonBasePage: getCommonBasePage(r), CurrentURL: webClientForgotPwdPath, Error: err, @@ -613,7 +565,7 @@ func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, r *http.R } func (s *httpdServer) renderClientResetPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { - data := clientResetPwdPage{ + data := resetPwdPage{ commonBasePage: getCommonBasePage(r), CurrentURL: webClientResetPwdPath, Error: err, @@ -679,7 +631,7 @@ func (s *httpdServer) renderClientNotFoundPage(w http.ResponseWriter, r *http.Re } func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { - data := clientTwoFactorPage{ + data := twoFactorPage{ commonBasePage: getCommonBasePage(r), Title: pageTwoFactorTitle, CurrentURL: webClientTwoFactorPath, @@ -691,11 +643,11 @@ func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, r *http.R if next := r.URL.Query().Get("next"); strings.HasPrefix(next, webClientFilesPath) { data.CurrentURL += "?next=" + url.QueryEscape(next) } - renderClientTemplate(w, templateClientTwoFactor, data) + renderClientTemplate(w, templateTwoFactor, data) } func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) { - data := clientTwoFactorPage{ + data := twoFactorPage{ commonBasePage: getCommonBasePage(r), Title: pageTwoFactorRecoveryTitle, CurrentURL: webClientTwoFactorRecoveryPath, @@ -703,7 +655,7 @@ func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, r CSRFToken: createCSRFToken(ip), Branding: s.binding.Branding.WebClient, } - renderClientTemplate(w, templateClientTwoFactorRecovery, data) + renderClientTemplate(w, templateTwoFactorRecovery, data) } func (s *httpdServer) renderClientMFAPage(w http.ResponseWriter, r *http.Request) { diff --git a/internal/util/i18n.go b/internal/util/i18n.go index 99f2498e..e8800221 100644 --- a/internal/util/i18n.go +++ b/internal/util/i18n.go @@ -21,6 +21,7 @@ import ( // localization id for the Web frontend const ( + I18nSetupTitle = "title.setup" I18nLoginTitle = "title.login" I18nShareLoginTitle = "title.share_login" I18nFilesTitle = "title.files" @@ -47,6 +48,7 @@ const ( I18nError500Title = "title.error500" I18nErrorPDFTitle = "title.errorPDF" I18nErrorEditorTitle = "title.error_editor" + I18nErrorSetupInstallCode = "setup.install_code_mismatch" I18nInvalidAuth = "general.invalid_auth_request" I18nError429Message = "general.error429" I18nError400Message = "general.error400" diff --git a/static/locales/en/translation.json b/static/locales/en/translation.json index ff4abcaa..eba031b4 100644 --- a/static/locales/en/translation.json +++ b/static/locales/en/translation.json @@ -1,5 +1,6 @@ { "title": { + "setup": "Initial Setup", "login": "Login", "share_login": "Share Login", "profile": "Profile", @@ -28,6 +29,12 @@ "errorPDF": "Unable to show PDF file", "error_editor": "Cannot open file editor" }, + "setup": { + "desc": "To start using SFTPGo you need to create an administrator user", + "submit": "Create admin and Sign in", + "install_code_mismatch": "The installation code does not match", + "help_text": "SFTPGo needs your help" + }, "login": { "username": "Username", "password": "Password", diff --git a/static/locales/it/translation.json b/static/locales/it/translation.json index f0b19ec6..a021db27 100644 --- a/static/locales/it/translation.json +++ b/static/locales/it/translation.json @@ -1,5 +1,6 @@ { "title": { + "setup": "Configurazione iniziale", "login": "Accedi", "share_login": "Accedi alla condivisione", "profile": "Profilo", @@ -28,6 +29,12 @@ "errorPDF": "Impossibile mostrare il file PDF", "error_editor": "Impossibile aprire l'editor di file" }, + "setup": { + "desc": "Per iniziare a utilizzare SFTPGo devi creare un utente amministratore", + "submit": "Crea amministratore e accedi", + "install_code_mismatch": "Il codice di installazione non corrisponde", + "help_text": "SFTPGo ha bisogno del tuo aiuto" + }, "login": { "username": "Nome utente", "password": "Password", @@ -353,7 +360,7 @@ "info": "Inserisci la password attuale, per ragioni di sicurezza, e poi la nuova password due volte, per verificare di averla scritta correttamente. Verrai disconnesso dopo aver modificato la tua password", "current": "Password attuale", "new": "Nuova password", - "confirm": "Conferma nuova password", + "confirm": "Conferma password", "save": "Modifica la mia password", "required_fields": "Si prega di fornire la password attuale e quella nuova due volte", "no_match": "I due campi della password non corrispondono", diff --git a/templates/common/base.html b/templates/common/base.html index 5425b597..b515c172 100644 --- a/templates/common/base.html +++ b/templates/common/base.html @@ -128,6 +128,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com). }; const renderI18n = () => { + $('title').text('{{.Branding.Name}} - '+$.t('{{.Title}}')); $('body').localize(); let select2elements = [].slice.call(document.querySelectorAll('[data-control="i18n-select2"]')); select2elements.map(function (element){ @@ -209,7 +210,6 @@ explicit grant from the SFTPGo Team (support@sftpgo.com). } renderI18n(); - $('title').text('{{.Branding.Name}} - '+$.t('{{.Title}}')); $.event.trigger({ type: "i18nload" }); diff --git a/templates/common/forgot-password.html b/templates/common/forgot-password.html index e9401d34..6a9cfe82 100644 --- a/templates/common/forgot-password.html +++ b/templates/common/forgot-password.html @@ -1,104 +1,59 @@ - - +{{- template "baselogin" .}} - - - - - - - - - {{.Branding.Name}} - {{.Title}} - - - - - {{- range .Branding.DefaultCSS}} - - {{- end}} - - - {{range .Branding.ExtraCSS}} - - {{end}} - - - - - -
- - -
- -
- -
-
- -
-
-
-
-

Forgot Your Password?

-

Enter your account username below, you will receive a password reset code by email.

-
- {{if .Error}} - - {{end}} -
-
- -
- - -
-
-
-
-
-
+{{- define "content"}} +
+ - - - - - - - - - - - - - - \ No newline at end of file +
+

+ Forgot Password ? +

+
+ + Enter your account username below, you will receive a password reset code by email. + +
+
+ {{- template "errmsg" .Error}} +
+ +
+
+ + +
+
+{{- end}} \ No newline at end of file diff --git a/templates/webclient/login.html b/templates/common/login.html similarity index 100% rename from templates/webclient/login.html rename to templates/common/login.html diff --git a/templates/common/reset-password.html b/templates/common/reset-password.html index ee0b32f5..b350af37 100644 --- a/templates/common/reset-password.html +++ b/templates/common/reset-password.html @@ -1,108 +1,97 @@ - - +{{- template "baselogin" .}} - - - - - - - - - {{.Branding.Name}} - {{.Title}} - - - - - {{- range .Branding.DefaultCSS}} - - {{- end}} - - - {{range .Branding.ExtraCSS}} - - {{end}} - - - - - -
- - -
- -
- -
-
- -
-
-
-
-

Reset Password

-

Check your email for the confirmation code

-
- {{if .Error}} - - {{end}} -
-
- -
-
- -
- - -
-
-
-
-
-
+{{- define "content"}} +
+ - - - - - - - - - - - - - - \ No newline at end of file +
+

+ Reset Password +

+
+ + Check your email for the confirmation code + +
+
+ {{- template "errmsg" .Error}} +
+ +
+
+
+ + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + +
+
+
+ + +
+
+{{- end}} \ No newline at end of file diff --git a/templates/webclient/twofactor-recovery.html b/templates/common/twofactor-recovery.html similarity index 100% rename from templates/webclient/twofactor-recovery.html rename to templates/common/twofactor-recovery.html diff --git a/templates/webclient/twofactor.html b/templates/common/twofactor.html similarity index 100% rename from templates/webclient/twofactor.html rename to templates/common/twofactor.html diff --git a/templates/webadmin/adminsetup.html b/templates/webadmin/adminsetup.html index 1039a38c..249dacb4 100644 --- a/templates/webadmin/adminsetup.html +++ b/templates/webadmin/adminsetup.html @@ -1,119 +1,109 @@ - - +{{- template "baselogin" .}} - - - - - - - - - SFTPGo - Setup - - - - - {{- range .Branding.DefaultCSS}} - - {{- end}} - - - - - - -
- - -
- -
- -
-
- -
-
-
-
-

To start using SFTPGo you need to create an admin user

-
- {{if .Error}} - - {{end}} -
- {{if .HasInstallationCode}} -
- -
- {{end}} -
- -
-
- -
-
- -
- - -
- {{if not .HideSupportLink}} -
- - {{end}} -
-
-
-
-
+{{- define "content"}} +
+
+
+
+ Logo +
+
+ + {{.Branding.ShortName}} +
- - - - - - - - - - - - - - \ No newline at end of file +
+

+
+ +
+
+ {{- template "errmsg" .Error}} + {{- if .HasInstallationCode}} +
+ +
+ {{- end}} +
+ +
+
+
+ + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + +
+
+
+ + +
+
+
+
+
+ +
+ {{- if not .HideSupportLink}} +
+ + + +
+ {{- end}} +
+{{- end}} \ No newline at end of file diff --git a/templates/webadmin/baselogin.html b/templates/webadmin/baselogin.html deleted file mode 100644 index c081e9ed..00000000 --- a/templates/webadmin/baselogin.html +++ /dev/null @@ -1,90 +0,0 @@ - -{{define "baselogin"}} - - - - - - - - - - - - {{.Branding.Name}} - {{template "title" .}} - - - - - {{- range .Branding.DefaultCSS}} - - {{- end}} - - - {{range .Branding.ExtraCSS}} - - {{end}} - - - - - -
- - -
- -
- -
-
- - -
- -
-
- {{template "content" .}} -
-
-
-
-
-
-
-
- - - - - - - - - - - - - - -{{end}} diff --git a/templates/webadmin/login.html b/templates/webadmin/login.html index 870a1641..e909dabb 100644 --- a/templates/webadmin/login.html +++ b/templates/webadmin/login.html @@ -1,72 +1,99 @@ -{{template "baselogin" .}} +{{- template "baselogin" .}} -{{define "title"}}Login{{end}} - -{{define "content"}} -
-

{{.Branding.ShortName}}

-
- {{if .Error}} - - {{end}} -
- {{if not .FormDisabled}} -
- -
-
- - {{if .ForgotPwdURL}} - - {{end}} -
- - - {{end}} - {{if .OpenIDLoginURL}} -
- - Login with OpenID - - {{end}} -
- {{if .AltLoginURL}} -
- - {{end}} - {{if and .Branding.DisclaimerName .Branding.DisclaimerPath}} -
- - {{end}} +{{- define "content"}} +
+
+
+
+ Logo +
+
+

+ {{.Branding.ShortName}} +

+
+
+
+ {{- template "errmsg" .Error}} + {{- if not .FormDisabled}} +
+ +
+
+
+ + + + + + + + + + + + + + +
+
+ {{- if .ForgotPwdURL}} + Forgot Password ? + {{- end}} +
+
+ {{- end}} +
+ {{- if not .FormDisabled}} + + + {{- end}} + {{- if .OpenIDLoginURL}} + + Logo + Sign in with OpenID + + {{- end}} +
+
+
+
+
+ +
+
+ {{- if .AltLoginURL}} + + + + {{- end}} + {{- if and .Branding.DisclaimerName .Branding.DisclaimerPath}} + + {{.Branding.DisclaimerName}} + + {{- end}} +
+
{{end}} \ No newline at end of file diff --git a/templates/webadmin/twofactor-recovery.html b/templates/webadmin/twofactor-recovery.html deleted file mode 100644 index 4b551300..00000000 --- a/templates/webadmin/twofactor-recovery.html +++ /dev/null @@ -1,47 +0,0 @@ - -{{template "baselogin" .}} - -{{define "title"}}{{.Title}}{{end}} - -{{define "content"}} -
-

{{.Branding.Name}}

-
- {{if .Error}} - - {{end}} -
-
- -
- - -
-
-
-

You can enter one of your recovery codes in case you lost access to your mobile device.

-
-{{end}} \ No newline at end of file diff --git a/templates/webadmin/twofactor.html b/templates/webadmin/twofactor.html deleted file mode 100644 index 0e7045d0..00000000 --- a/templates/webadmin/twofactor.html +++ /dev/null @@ -1,52 +0,0 @@ - -{{template "baselogin" .}} - -{{define "title"}}{{.Title}}{{end}} - -{{define "content"}} -
-

{{.Branding.Name}}

-
- {{if .Error}} - - {{end}} -
-
- -
- - -
-
-
-

Open the two-factor authentication app on your device to view your authentication code and verify your identity.

-
-
-
-

Having problems?

-

Enter a two-factor recovery code

-
-{{end}} \ No newline at end of file diff --git a/templates/webclient/forgot-password.html b/templates/webclient/forgot-password.html deleted file mode 100644 index 29a890dc..00000000 --- a/templates/webclient/forgot-password.html +++ /dev/null @@ -1,59 +0,0 @@ - -{{- template "baselogin" .}} - -{{- define "content"}} -
- -
-

- Forgot Password ? -

-
- - Enter your account username below, you will receive a password reset code by email. - -
-
- {{- template "errmsg" .Error}} -
- -
-
- - -
-
-{{- end}} \ No newline at end of file diff --git a/templates/webclient/reset-password.html b/templates/webclient/reset-password.html deleted file mode 100644 index f317cfc2..00000000 --- a/templates/webclient/reset-password.html +++ /dev/null @@ -1,97 +0,0 @@ - -{{- template "baselogin" .}} - -{{- define "content"}} -
- -
-

- Reset Password -

-
- - Check your email for the confirmation code - -
-
- {{- template "errmsg" .Error}} -
- -
-
-
- - - - - - - - - - - - - - -
-
-
-
- - - - - - - - - - - - - - -
-
-
- - -
-
-{{- end}} \ No newline at end of file