diff --git a/internal/httpd/server.go b/internal/httpd/server.go index 9de3c443..b9d1f38b 100644 --- a/internal/httpd/server.go +++ b/internal/httpd/server.go @@ -166,6 +166,7 @@ func (s *httpdServer) renderClientLoginPage(w http.ResponseWriter, r *http.Reque Version: version.Get().Version, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, Branding: s.binding.Branding.WebClient, FormDisabled: s.binding.isWebClientLoginFormDisabled(), @@ -445,17 +446,17 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, } ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) if err := r.ParseForm(); err != nil { - s.renderTwoFactorRecoveryPage(w, err.Error(), ipAddr) + s.renderTwoFactorRecoveryPage(w, r, err.Error(), ipAddr) return } username := claims.Username recoveryCode := strings.TrimSpace(r.Form.Get("recovery_code")) if username == "" || recoveryCode == "" { - s.renderTwoFactorRecoveryPage(w, "Invalid credentials", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, "Invalid credentials", ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { - s.renderTwoFactorRecoveryPage(w, err.Error(), ipAddr) + s.renderTwoFactorRecoveryPage(w, r, err.Error(), ipAddr) return } admin, err := dataprovider.AdminExists(username) @@ -463,11 +464,11 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, if errors.Is(err, util.ErrNotFound) { handleDefenderEventLoginFailed(ipAddr, err) //nolint:errcheck } - s.renderTwoFactorRecoveryPage(w, "Invalid credentials", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, "Invalid credentials", ipAddr) return } if !admin.Filters.TOTPConfig.Enabled { - s.renderTwoFactorRecoveryPage(w, "Two factory authentication is not enabled", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, "Two factory authentication is not enabled", ipAddr) return } for idx, code := range admin.Filters.RecoveryCodes { @@ -477,7 +478,7 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, } if code.Secret.GetPayload() == recoveryCode { if code.Used { - s.renderTwoFactorRecoveryPage(w, "This recovery code was already used", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, "This recovery code was already used", ipAddr) return } admin.Filters.RecoveryCodes[idx].Used = true @@ -492,7 +493,7 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, } } handleDefenderEventLoginFailed(ipAddr, dataprovider.ErrInvalidCredentials) //nolint:errcheck - s.renderTwoFactorRecoveryPage(w, "Invalid recovery code", ipAddr) + s.renderTwoFactorRecoveryPage(w, r, "Invalid recovery code", ipAddr) } func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http.Request) { @@ -504,18 +505,18 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http } ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) if err := r.ParseForm(); err != nil { - s.renderTwoFactorPage(w, err.Error(), ipAddr) + s.renderTwoFactorPage(w, r, err.Error(), ipAddr) return } username := claims.Username passcode := strings.TrimSpace(r.Form.Get("passcode")) if username == "" || passcode == "" { - s.renderTwoFactorPage(w, "Invalid credentials", ipAddr) + s.renderTwoFactorPage(w, r, "Invalid credentials", ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { err = handleDefenderEventLoginFailed(ipAddr, err) - s.renderTwoFactorPage(w, err.Error(), ipAddr) + s.renderTwoFactorPage(w, r, err.Error(), ipAddr) return } admin, err := dataprovider.AdminExists(username) @@ -523,11 +524,11 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http if errors.Is(err, util.ErrNotFound) { handleDefenderEventLoginFailed(ipAddr, err) //nolint:errcheck } - s.renderTwoFactorPage(w, "Invalid credentials", ipAddr) + s.renderTwoFactorPage(w, r, "Invalid credentials", ipAddr) return } if !admin.Filters.TOTPConfig.Enabled { - s.renderTwoFactorPage(w, "Two factory authentication is not enabled", ipAddr) + s.renderTwoFactorPage(w, r, "Two factory authentication is not enabled", ipAddr) return } err = admin.Filters.TOTPConfig.Secret.Decrypt() @@ -539,7 +540,7 @@ 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, "Invalid authentication code", ipAddr) + s.renderTwoFactorPage(w, r, "Invalid authentication code", ipAddr) return } s.loginAdmin(w, r, &admin, true, s.renderTwoFactorPage, ipAddr) @@ -550,34 +551,35 @@ func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Req ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) if err := r.ParseForm(); err != nil { - s.renderAdminLoginPage(w, err.Error(), ipAddr) + s.renderAdminLoginPage(w, r, err.Error(), ipAddr) return } username := strings.TrimSpace(r.Form.Get("username")) password := strings.TrimSpace(r.Form.Get("password")) if username == "" || password == "" { - s.renderAdminLoginPage(w, "Invalid credentials", ipAddr) + s.renderAdminLoginPage(w, r, "Invalid credentials", ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { - s.renderAdminLoginPage(w, err.Error(), ipAddr) + s.renderAdminLoginPage(w, r, err.Error(), ipAddr) return } admin, err := dataprovider.CheckAdminAndPass(username, password, ipAddr) if err != nil { err = handleDefenderEventLoginFailed(ipAddr, err) - s.renderAdminLoginPage(w, err.Error(), ipAddr) + s.renderAdminLoginPage(w, r, err.Error(), ipAddr) return } s.loginAdmin(w, r, &admin, false, s.renderAdminLoginPage, ipAddr) } -func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, error, ip string) { +func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, r *http.Request, error, ip string) { data := loginPage{ CurrentURL: webAdminLoginPath, Version: version.Get().Version, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, Branding: s.binding.Branding.WebAdmin, FormDisabled: s.binding.isWebAdminLoginFormDisabled(), @@ -601,7 +603,7 @@ func (s *httpdServer) handleWebAdminLogin(w http.ResponseWriter, r *http.Request http.Redirect(w, r, webAdminSetupPath, http.StatusFound) return } - s.renderAdminLoginPage(w, getFlashMessage(w, r), util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderAdminLoginPage(w, r, getFlashMessage(w, r), util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminLogout(w http.ResponseWriter, r *http.Request) { @@ -639,7 +641,7 @@ func (s *httpdServer) handleWebAdminPasswordResetPost(w http.ResponseWriter, r * ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) err := r.ParseForm() if err != nil { - s.renderResetPwdPage(w, err.Error(), ipAddr) + s.renderResetPwdPage(w, r, err.Error(), ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { @@ -650,10 +652,10 @@ func (s *httpdServer) handleWebAdminPasswordResetPost(w http.ResponseWriter, r * strings.TrimSpace(r.Form.Get("password")), true) if err != nil { if e, ok := err.(*util.ValidationError); ok { - s.renderResetPwdPage(w, e.GetErrorString(), ipAddr) + s.renderResetPwdPage(w, r, e.GetErrorString(), ipAddr) return } - s.renderResetPwdPage(w, err.Error(), ipAddr) + s.renderResetPwdPage(w, r, err.Error(), ipAddr) return } @@ -759,7 +761,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, error, ip string), + isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, r *http.Request, error, ip string), ipAddr string, ) { c := jwtTokenClaims{ @@ -782,7 +784,7 @@ func (s *httpdServer) loginAdmin( s.renderAdminSetupPage(w, r, admin.Username, err.Error()) return } - errorFunc(w, err.Error(), ipAddr) + errorFunc(w, r, err.Error(), ipAddr) return } if isSecondFactorAuth { diff --git a/internal/httpd/web.go b/internal/httpd/web.go index 1abded4f..e64f8670 100644 --- a/internal/httpd/web.go +++ b/internal/httpd/web.go @@ -44,6 +44,7 @@ type loginPage struct { Version string Error string CSRFToken string + CSPNonce string StaticURL string AltLoginURL string AltLoginName string @@ -58,6 +59,7 @@ type twoFactorPage struct { Version string Error string CSRFToken string + CSPNonce string StaticURL string RecoveryURL string Branding UIBranding @@ -67,6 +69,7 @@ type forgotPwdPage struct { CurrentURL string Error string CSRFToken string + CSPNonce string StaticURL string LoginURL string Title string @@ -77,6 +80,7 @@ type resetPwdPage struct { CurrentURL string Error string CSRFToken string + CSPNonce string StaticURL string LoginURL string Title string diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index ca491495..8dc6353f 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -32,6 +32,7 @@ import ( "github.com/go-chi/render" "github.com/sftpgo/sdk" sdkkms "github.com/sftpgo/sdk/kms" + "github.com/unrolled/secure" "github.com/drakkan/sftpgo/v2/internal/acme" "github.com/drakkan/sftpgo/v2/internal/common" @@ -180,6 +181,7 @@ type basePage struct { ConfigsTitle string Version string CSRFToken string + CSPNonce string IsEventManagerPage bool IsIPManagerPage bool IsServerManagerPage bool @@ -751,6 +753,7 @@ func (s *httpdServer) getBasePageData(title, currentURL string, r *http.Request) HasSearcher: plugin.Handler.HasSearcher(), HasExternalLogin: isLoggedInWithOIDC(r), CSRFToken: csrfToken, + CSPNonce: secure.CSPNonce(r.Context()), Branding: s.binding.Branding.WebAdmin, } } @@ -797,11 +800,12 @@ 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, error, ip string) { +func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, r *http.Request, error, ip string) { data := forgotPwdPage{ CurrentURL: webAdminForgotPwdPath, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, Title: pageForgotPwdTitle, Branding: s.binding.Branding.WebAdmin, @@ -809,11 +813,12 @@ func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, error, ip strin renderAdminTemplate(w, templateForgotPassword, data) } -func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, error, ip string) { +func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, r *http.Request, error, ip string) { data := resetPwdPage{ CurrentURL: webAdminResetPwdPath, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, Title: pageResetPwdTitle, Branding: s.binding.Branding.WebAdmin, @@ -821,12 +826,13 @@ func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, error, ip string renderAdminTemplate(w, templateResetPassword, data) } -func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, error, ip string) { +func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, r *http.Request, error, ip string) { data := twoFactorPage{ CurrentURL: webAdminTwoFactorPath, Version: version.Get().Version, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, RecoveryURL: webAdminTwoFactorRecoveryPath, Branding: s.binding.Branding.WebAdmin, @@ -834,12 +840,13 @@ func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, error, ip strin renderAdminTemplate(w, templateTwoFactor, data) } -func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, error, ip string) { +func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, error, ip string) { data := twoFactorPage{ CurrentURL: webAdminTwoFactorRecoveryPath, Version: version.Get().Version, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, Branding: s.binding.Branding.WebAdmin, } @@ -2634,7 +2641,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, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderForgotPwdPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http.Request) { @@ -2643,7 +2650,7 @@ func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) err := r.ParseForm() if err != nil { - s.renderForgotPwdPage(w, err.Error(), ipAddr) + s.renderForgotPwdPage(w, r, err.Error(), ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { @@ -2653,10 +2660,10 @@ func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http err = handleForgotPassword(r, r.Form.Get("username"), true) if err != nil { if e, ok := err.(*util.ValidationError); ok { - s.renderForgotPwdPage(w, e.GetErrorString(), ipAddr) + s.renderForgotPwdPage(w, r, e.GetErrorString(), ipAddr) return } - s.renderForgotPwdPage(w, err.Error(), ipAddr) + s.renderForgotPwdPage(w, r, err.Error(), ipAddr) return } http.Redirect(w, r, webAdminResetPwdPath, http.StatusFound) @@ -2668,17 +2675,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, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderResetPwdPage(w, r, "", 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, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderTwoFactorPage(w, r, "", 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, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderTwoFactorRecoveryPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebAdminMFA(w http.ResponseWriter, r *http.Request) { diff --git a/internal/httpd/webclient.go b/internal/httpd/webclient.go index 285ba105..f6b0545d 100644 --- a/internal/httpd/webclient.go +++ b/internal/httpd/webclient.go @@ -34,6 +34,7 @@ import ( "github.com/go-chi/render" "github.com/rs/xid" "github.com/sftpgo/sdk" + "github.com/unrolled/secure" "github.com/drakkan/sftpgo/v2/internal/common" "github.com/drakkan/sftpgo/v2/internal/dataprovider" @@ -115,6 +116,7 @@ type baseClientPage struct { ProfileTitle string Version string CSRFToken string + CSPNonce string LoggedUser *dataprovider.User Branding UIBranding } @@ -128,6 +130,7 @@ type viewPDFPage struct { Title string URL string StaticURL string + CSPNonce string Branding UIBranding } @@ -553,6 +556,7 @@ func (s *httpdServer) getBaseClientPageData(title, currentURL string, r *http.Re ProfileTitle: pageClientProfileTitle, Version: fmt.Sprintf("%v-%v", v.Version, v.CommitHash), CSRFToken: csrfToken, + CSPNonce: secure.CSPNonce(r.Context()), LoggedUser: getUserFromToken(r), Branding: s.binding.Branding.WebClient, } @@ -562,11 +566,12 @@ func (s *httpdServer) getBaseClientPageData(title, currentURL string, r *http.Re return data } -func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, error, ip string) { +func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, r *http.Request, error, ip string) { data := forgotPwdPage{ CurrentURL: webClientForgotPwdPath, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, LoginURL: webClientLoginPath, Title: pageClientForgotPwdTitle, @@ -575,11 +580,12 @@ func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, error, ip renderClientTemplate(w, templateForgotPassword, data) } -func (s *httpdServer) renderClientResetPwdPage(w http.ResponseWriter, _ *http.Request, error, ip string) { +func (s *httpdServer) renderClientResetPwdPage(w http.ResponseWriter, r *http.Request, error, ip string) { data := resetPwdPage{ CurrentURL: webClientResetPwdPath, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, LoginURL: webClientLoginPath, Title: pageClientResetPwdTitle, @@ -647,6 +653,7 @@ func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, r *http.R Version: version.Get().Version, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, RecoveryURL: webClientTwoFactorRecoveryPath, Branding: s.binding.Branding.WebClient, @@ -657,12 +664,13 @@ func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, r *http.R renderClientTemplate(w, templateTwoFactor, data) } -func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, _ *http.Request, error, ip string) { +func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, error, ip string) { data := twoFactorPage{ CurrentURL: webClientTwoFactorRecoveryPath, Version: version.Get().Version, Error: error, CSRFToken: createCSRFToken(ip), + CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, Branding: s.binding.Branding.WebClient, } @@ -1056,6 +1064,7 @@ func (s *httpdServer) handleShareViewPDF(w http.ResponseWriter, r *http.Request) URL: fmt.Sprintf("%s?path=%s&_=%d", path.Join(webClientPubSharesPath, share.ShareID, "getpdf"), url.QueryEscape(name), time.Now().UTC().Unix()), StaticURL: webStaticFilesPath, + CSPNonce: secure.CSPNonce(r.Context()), Branding: s.binding.Branding.WebClient, } renderClientTemplate(w, templateClientViewPDF, data) @@ -1618,7 +1627,7 @@ func (s *httpdServer) handleWebClientForgotPwd(w http.ResponseWriter, r *http.Re s.renderClientNotFoundPage(w, r, errors.New("this page does not exist")) return } - s.renderClientForgotPwdPage(w, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) + s.renderClientForgotPwdPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr)) } func (s *httpdServer) handleWebClientForgotPwdPost(w http.ResponseWriter, r *http.Request) { @@ -1627,7 +1636,7 @@ func (s *httpdServer) handleWebClientForgotPwdPost(w http.ResponseWriter, r *htt ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) err := r.ParseForm() if err != nil { - s.renderClientForgotPwdPage(w, err.Error(), ipAddr) + s.renderClientForgotPwdPage(w, r, err.Error(), ipAddr) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil { @@ -1638,10 +1647,10 @@ func (s *httpdServer) handleWebClientForgotPwdPost(w http.ResponseWriter, r *htt err = handleForgotPassword(r, username, false) if err != nil { if e, ok := err.(*util.ValidationError); ok { - s.renderClientForgotPwdPage(w, e.GetErrorString(), ipAddr) + s.renderClientForgotPwdPage(w, r, e.GetErrorString(), ipAddr) return } - s.renderClientForgotPwdPage(w, err.Error(), ipAddr) + s.renderClientForgotPwdPage(w, r, err.Error(), ipAddr) return } http.Redirect(w, r, webClientResetPwdPath, http.StatusFound) @@ -1668,6 +1677,7 @@ func (s *httpdServer) handleClientViewPDF(w http.ResponseWriter, r *http.Request Title: path.Base(name), URL: fmt.Sprintf("%s?path=%s&_=%d", webClientGetPDFPath, url.QueryEscape(name), time.Now().UTC().Unix()), StaticURL: webStaticFilesPath, + CSPNonce: secure.CSPNonce(r.Context()), Branding: s.binding.Branding.WebClient, } renderClientTemplate(w, templateClientViewPDF, data) diff --git a/templates/common/base.html b/templates/common/base.html index aaf82d6c..6383abb3 100644 --- a/templates/common/base.html +++ b/templates/common/base.html @@ -33,7 +33,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com). {{- end}} {{- define "theme-setup"}} - - {{- template "basejs" }} - - {{- template "basejs" }} - - {{- if not .ShareUploadBaseURL}} - {{- end}} - - diff --git a/templates/webclient/profile.html b/templates/webclient/profile.html index 7800e7cb..c456ee8c 100644 --- a/templates/webclient/profile.html +++ b/templates/webclient/profile.html @@ -139,7 +139,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com). {{- define "extra_js"}} {{- if .LoggedUser.CanManagePublicKeys}} - - - -