Add a link on the login pages to switch between admin and web client login

The links are hidden if only the web admin or only thw web client is
enabled and can also be controlled using the "hide_login_url" setting

Fixes #485
This commit is contained in:
Nicola Murino 2021-07-27 18:43:00 +02:00
parent 3a22aae34f
commit 90b324d707
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
13 changed files with 152 additions and 62 deletions

View file

@ -72,6 +72,7 @@ var (
ClientAuthType: 0, ClientAuthType: 0,
TLSCipherSuites: nil, TLSCipherSuites: nil,
ProxyAllowed: nil, ProxyAllowed: nil,
HideLoginURL: 0,
} }
defaultRateLimiter = common.RateLimiterConfig{ defaultRateLimiter = common.RateLimiterConfig{
Average: 0, Average: 0,
@ -876,6 +877,12 @@ func getHTTPDBindingFromEnv(idx int) {
isSet = true isSet = true
} }
hideLoginURL, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__HIDE_LOGIN_URL", idx))
if ok {
binding.HideLoginURL = int(hideLoginURL)
isSet = true
}
if isSet { if isSet {
if len(globalConf.HTTPDConfig.Bindings) > idx { if len(globalConf.HTTPDConfig.Bindings) > idx {
globalConf.HTTPDConfig.Bindings[idx] = binding globalConf.HTTPDConfig.Bindings[idx] = binding
@ -1062,7 +1069,7 @@ func setViperDefaults() {
func lookupBoolFromEnv(envName string) (bool, bool) { func lookupBoolFromEnv(envName string) (bool, bool) {
value, ok := os.LookupEnv(envName) value, ok := os.LookupEnv(envName)
if ok { if ok {
converted, err := strconv.ParseBool(value) converted, err := strconv.ParseBool(strings.TrimSpace(value))
if err == nil { if err == nil {
return converted, ok return converted, ok
} }
@ -1074,7 +1081,7 @@ func lookupBoolFromEnv(envName string) (bool, bool) {
func lookupIntFromEnv(envName string) (int64, bool) { func lookupIntFromEnv(envName string) (int64, bool) {
value, ok := os.LookupEnv(envName) value, ok := os.LookupEnv(envName)
if ok { if ok {
converted, err := strconv.ParseInt(value, 10, 64) converted, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
if err == nil { if err == nil {
return converted, ok return converted, ok
} }

View file

@ -584,14 +584,16 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ADDRESS", "127.0.0.1") os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ADDRESS", "127.0.0.1")
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__PORT", "8000") os.Setenv("SFTPGO_HTTPD__BINDINGS__1__PORT", "8000")
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS", "0") os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__HIDE_LOGIN_URL", " 1")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS", "127.0.1.1") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS", "127.0.1.1")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PORT", "9000") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PORT", "9000")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN", "0") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_CLIENT", "0") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_CLIENT", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS", "1") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS", "1 ")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE", "1") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE", "1")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES", " TLS_AES_256_GCM_SHA384 , TLS_CHACHA20_POLY1305_SHA256") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES", " TLS_AES_256_GCM_SHA384 , TLS_CHACHA20_POLY1305_SHA256")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PROXY_ALLOWED", " 192.168.9.1 , 172.16.25.0/24") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PROXY_ALLOWED", " 192.168.9.1 , 172.16.25.0/24")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__HIDE_LOGIN_URL", "3")
t.Cleanup(func() { t.Cleanup(func() {
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__ADDRESS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__ADDRESS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__PORT") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__PORT")
@ -599,6 +601,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ADDRESS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ADDRESS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__PORT") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__PORT")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__HIDE_LOGIN_URL")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PORT") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PORT")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS")
@ -607,6 +610,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PROXY_ALLOWED") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PROXY_ALLOWED")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__HIDE_LOGIN_URL")
}) })
configDir := ".." configDir := ".."
@ -621,12 +625,14 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.True(t, bindings[0].EnableWebClient) require.True(t, bindings[0].EnableWebClient)
require.Len(t, bindings[0].TLSCipherSuites, 1) require.Len(t, bindings[0].TLSCipherSuites, 1)
require.Equal(t, "TLS_AES_128_GCM_SHA256", bindings[0].TLSCipherSuites[0]) require.Equal(t, "TLS_AES_128_GCM_SHA256", bindings[0].TLSCipherSuites[0])
require.Equal(t, 0, bindings[0].HideLoginURL)
require.Equal(t, 8000, bindings[1].Port) require.Equal(t, 8000, bindings[1].Port)
require.Equal(t, "127.0.0.1", bindings[1].Address) require.Equal(t, "127.0.0.1", bindings[1].Address)
require.False(t, bindings[1].EnableHTTPS) require.False(t, bindings[1].EnableHTTPS)
require.True(t, bindings[1].EnableWebAdmin) require.True(t, bindings[1].EnableWebAdmin)
require.True(t, bindings[1].EnableWebClient) require.True(t, bindings[1].EnableWebClient)
require.Nil(t, bindings[1].TLSCipherSuites) require.Nil(t, bindings[1].TLSCipherSuites)
require.Equal(t, 1, bindings[1].HideLoginURL)
require.Equal(t, 9000, bindings[2].Port) require.Equal(t, 9000, bindings[2].Port)
require.Equal(t, "127.0.1.1", bindings[2].Address) require.Equal(t, "127.0.1.1", bindings[2].Address)
@ -640,6 +646,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.Len(t, bindings[2].ProxyAllowed, 2) require.Len(t, bindings[2].ProxyAllowed, 2)
require.Equal(t, "192.168.9.1", bindings[2].ProxyAllowed[0]) require.Equal(t, "192.168.9.1", bindings[2].ProxyAllowed[0])
require.Equal(t, "172.16.25.0/24", bindings[2].ProxyAllowed[1]) require.Equal(t, "172.16.25.0/24", bindings[2].ProxyAllowed[1])
require.Equal(t, 3, bindings[2].HideLoginURL)
} }
func TestHTTPClientCertificatesFromEnv(t *testing.T) { func TestHTTPClientCertificatesFromEnv(t *testing.T) {

View file

@ -204,6 +204,7 @@ The configuration file contains the following sections:
- `client_auth_type`, integer. Set to `1` to require client certificate authentication in addition to JWT/Web authentication. You need to define at least a certificate authority for this to work. Default: 0. - `client_auth_type`, integer. Set to `1` to require client certificate authentication in addition to JWT/Web authentication. You need to define at least a certificate authority for this to work. Default: 0.
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty. - `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
- `proxy_allowed`, list of IP addresses and IP ranges allowed to set `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`, `CF-Connecting-IP`, `True-Client-IP` headers. Any of the indicated headers, if set on requests from a connection address not in this list, will be silently ignored. Default: empty. - `proxy_allowed`, list of IP addresses and IP ranges allowed to set `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`, `CF-Connecting-IP`, `True-Client-IP` headers. Any of the indicated headers, if set on requests from a connection address not in this list, will be silently ignored. Default: empty.
- `hide_login_url`, integer. If both web admin and web client are enabled each login page will show a link to the other one. This setting allows to hide this link. 0 means that the login links are displayed on both admin and client login page. This is the default. 1 means that the login link to the web client login page is hidden on admin login page. 2 means that the login link to the web admin login page is hidden on client login page. The flags can be combined, for example 3 will disable both login links.
- `templates_path`, string. Path to the HTML web templates. This can be an absolute path or a path relative to the config dir - `templates_path`, string. Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
- `static_files_path`, string. Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir. If both `templates_path` and `static_files_path` are empty the built-in web interface will be disabled - `static_files_path`, string. Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir. If both `templates_path` and `static_files_path` are empty the built-in web interface will be disabled
- `backups_path`, string. Path to the backup directory. This can be an absolute path or a path relative to the config dir. We don't allow backups in arbitrary paths for security reasons - `backups_path`, string. Path to the backup directory. This can be an absolute path or a path relative to the config dir. We don't allow backups in arbitrary paths for security reasons

View file

@ -184,7 +184,14 @@ type Binding struct {
TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"` TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
// List of IP addresses and IP ranges allowed to set X-Forwarded-For, X-Real-IP, // List of IP addresses and IP ranges allowed to set X-Forwarded-For, X-Real-IP,
// X-Forwarded-Proto headers. // X-Forwarded-Proto headers.
ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"` ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
// If both web admin and web client are enabled each login page will show a link
// to the other one. This setting allows to hide this link:
// - 0 login links are displayed on both admin and client login page. This is the default
// - 1 the login link to the web client login page is hidden on admin login page
// - 2 the login link to the web admin login page is hidden on client login page
// The flags can be combined, for example 3 will disable both login links.
HideLoginURL int `json:"hide_login_url" mapstructure:"hide_login_url"`
allowHeadersFrom []func(net.IP) bool allowHeadersFrom []func(net.IP) bool
} }
@ -213,6 +220,26 @@ func (b *Binding) IsValid() bool {
return false return false
} }
func (b *Binding) showAdminLoginURL() bool {
if !b.EnableWebAdmin {
return false
}
if b.HideLoginURL&2 != 0 {
return false
}
return true
}
func (b *Binding) showClientLoginURL() bool {
if !b.EnableWebClient {
return false
}
if b.HideLoginURL&1 != 0 {
return false
}
return true
}
type defenderStatus struct { type defenderStatus struct {
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
} }

View file

@ -1668,3 +1668,31 @@ func TestSigningKey(t *testing.T) {
_, err = server2.tokenAuth.Decode(accessToken) _, err = server2.tokenAuth.Decode(accessToken)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestLoginLinks(t *testing.T) {
b := Binding{
EnableWebAdmin: true,
EnableWebClient: false,
}
assert.False(t, b.showClientLoginURL())
b = Binding{
EnableWebAdmin: false,
EnableWebClient: true,
}
assert.False(t, b.showAdminLoginURL())
b = Binding{
EnableWebAdmin: true,
EnableWebClient: true,
}
assert.True(t, b.showAdminLoginURL())
assert.True(t, b.showClientLoginURL())
b.HideLoginURL = 3
assert.False(t, b.showAdminLoginURL())
assert.False(t, b.showClientLoginURL())
b.HideLoginURL = 1
assert.True(t, b.showAdminLoginURL())
assert.False(t, b.showClientLoginURL())
b.HideLoginURL = 2
assert.False(t, b.showAdminLoginURL())
assert.True(t, b.showClientLoginURL())
}

View file

@ -2052,7 +2052,7 @@ paths:
patch: patch:
tags: tags:
- users API - users API
summary: Rename afile summary: Rename a file
description: Rename a file for the logged in user description: Rename a file for the logged in user
operationId: rename_user_file operationId: rename_user_file
parameters: parameters:

View file

@ -116,11 +116,29 @@ func (s *httpdServer) refreshCookie(next http.Handler) http.Handler {
}) })
} }
func (s *httpdServer) renderClientLoginPage(w http.ResponseWriter, error string) {
data := loginPage{
CurrentURL: webClientLoginPath,
Version: version.Get().Version,
Error: error,
CSRFToken: createCSRFToken(),
StaticURL: webStaticFilesPath,
}
if s.binding.showAdminLoginURL() {
data.AltLoginURL = webLoginPath
}
renderClientTemplate(w, templateClientLogin, data)
}
func (s *httpdServer) handleClientWebLogin(w http.ResponseWriter, r *http.Request) {
s.renderClientLoginPage(w, "")
}
func (s *httpdServer) handleWebClientLoginPost(w http.ResponseWriter, r *http.Request) { func (s *httpdServer) handleWebClientLoginPost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxLoginPostSize) r.Body = http.MaxBytesReader(w, r.Body, maxLoginPostSize)
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
renderClientLoginPage(w, err.Error()) s.renderClientLoginPage(w, err.Error())
return return
} }
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
@ -128,30 +146,30 @@ func (s *httpdServer) handleWebClientLoginPost(w http.ResponseWriter, r *http.Re
password := r.Form.Get("password") password := r.Form.Get("password")
if username == "" || password == "" { if username == "" || password == "" {
updateLoginMetrics(&dataprovider.User{BaseUser: sdk.BaseUser{Username: username}}, ipAddr, common.ErrNoCredentials) updateLoginMetrics(&dataprovider.User{BaseUser: sdk.BaseUser{Username: username}}, ipAddr, common.ErrNoCredentials)
renderClientLoginPage(w, "Invalid credentials") s.renderClientLoginPage(w, "Invalid credentials")
return return
} }
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
updateLoginMetrics(&dataprovider.User{BaseUser: sdk.BaseUser{Username: username}}, ipAddr, err) updateLoginMetrics(&dataprovider.User{BaseUser: sdk.BaseUser{Username: username}}, ipAddr, err)
renderClientLoginPage(w, err.Error()) s.renderClientLoginPage(w, err.Error())
return return
} }
if err := common.Config.ExecutePostConnectHook(ipAddr, common.ProtocolHTTP); err != nil { if err := common.Config.ExecutePostConnectHook(ipAddr, common.ProtocolHTTP); err != nil {
renderClientLoginPage(w, fmt.Sprintf("access denied by post connect hook: %v", err)) s.renderClientLoginPage(w, fmt.Sprintf("access denied by post connect hook: %v", err))
return return
} }
user, err := dataprovider.CheckUserAndPass(username, password, ipAddr, common.ProtocolHTTP) user, err := dataprovider.CheckUserAndPass(username, password, ipAddr, common.ProtocolHTTP)
if err != nil { if err != nil {
updateLoginMetrics(&user, ipAddr, err) updateLoginMetrics(&user, ipAddr, err)
renderClientLoginPage(w, dataprovider.ErrInvalidCredentials.Error()) s.renderClientLoginPage(w, dataprovider.ErrInvalidCredentials.Error())
return return
} }
connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, xid.New().String()) connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, xid.New().String())
if err := checkHTTPClientUser(&user, r, connectionID); err != nil { if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
updateLoginMetrics(&user, ipAddr, err) updateLoginMetrics(&user, ipAddr, err)
renderClientLoginPage(w, err.Error()) s.renderClientLoginPage(w, err.Error())
return return
} }
@ -160,7 +178,7 @@ func (s *httpdServer) handleWebClientLoginPost(w http.ResponseWriter, r *http.Re
if err != nil { if err != nil {
logger.Warn(logSender, connectionID, "unable to check fs root: %v", err) logger.Warn(logSender, connectionID, "unable to check fs root: %v", err)
updateLoginMetrics(&user, ipAddr, common.ErrInternalFailure) updateLoginMetrics(&user, ipAddr, common.ErrInternalFailure)
renderClientLoginPage(w, err.Error()) s.renderClientLoginPage(w, err.Error())
return return
} }
@ -174,7 +192,7 @@ func (s *httpdServer) handleWebClientLoginPost(w http.ResponseWriter, r *http.Re
if err != nil { if err != nil {
logger.Warn(logSender, connectionID, "unable to set client login cookie %v", err) logger.Warn(logSender, connectionID, "unable to set client login cookie %v", err)
updateLoginMetrics(&user, ipAddr, common.ErrInternalFailure) updateLoginMetrics(&user, ipAddr, common.ErrInternalFailure)
renderClientLoginPage(w, err.Error()) s.renderClientLoginPage(w, err.Error())
return return
} }
updateLoginMetrics(&user, ipAddr, err) updateLoginMetrics(&user, ipAddr, err)
@ -185,27 +203,49 @@ func (s *httpdServer) handleWebClientLoginPost(w http.ResponseWriter, r *http.Re
func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Request) { func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxLoginPostSize) r.Body = http.MaxBytesReader(w, r.Body, maxLoginPostSize)
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
renderLoginPage(w, err.Error()) s.renderAdminLoginPage(w, err.Error())
return return
} }
username := r.Form.Get("username") username := r.Form.Get("username")
password := r.Form.Get("password") password := r.Form.Get("password")
if username == "" || password == "" { if username == "" || password == "" {
renderLoginPage(w, "Invalid credentials") s.renderAdminLoginPage(w, "Invalid credentials")
return return
} }
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
renderLoginPage(w, err.Error()) s.renderAdminLoginPage(w, err.Error())
return return
} }
admin, err := dataprovider.CheckAdminAndPass(username, password, util.GetIPFromRemoteAddress(r.RemoteAddr)) admin, err := dataprovider.CheckAdminAndPass(username, password, util.GetIPFromRemoteAddress(r.RemoteAddr))
if err != nil { if err != nil {
renderLoginPage(w, err.Error()) s.renderAdminLoginPage(w, err.Error())
return return
} }
s.loginAdmin(w, r, &admin) s.loginAdmin(w, r, &admin)
} }
func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, error string) {
data := loginPage{
CurrentURL: webLoginPath,
Version: version.Get().Version,
Error: error,
CSRFToken: createCSRFToken(),
StaticURL: webStaticFilesPath,
}
if s.binding.showClientLoginURL() {
data.AltLoginURL = webClientLoginPath
}
renderAdminTemplate(w, templateLogin, data)
}
func (s *httpdServer) handleWebAdminLogin(w http.ResponseWriter, r *http.Request) {
if !dataprovider.HasAdmin() {
http.Redirect(w, r, webAdminSetupPath, http.StatusFound)
return
}
s.renderAdminLoginPage(w, "")
}
func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Request) { func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxLoginPostSize) r.Body = http.MaxBytesReader(w, r.Body, maxLoginPostSize)
if dataprovider.HasAdmin() { if dataprovider.HasAdmin() {
@ -260,7 +300,7 @@ func (s *httpdServer) loginAdmin(w http.ResponseWriter, r *http.Request, admin *
err := c.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebAdmin) err := c.createAndSetCookie(w, r, s.tokenAuth, tokenAudienceWebAdmin)
if err != nil { if err != nil {
logger.Warn(logSender, "", "unable to set admin login cookie %v", err) logger.Warn(logSender, "", "unable to set admin login cookie %v", err)
renderLoginPage(w, err.Error()) s.renderAdminLoginPage(w, err.Error())
return return
} }
@ -672,7 +712,7 @@ func (s *httpdServer) initializeRouter() {
s.router.Get(webBaseClientPath, func(w http.ResponseWriter, r *http.Request) { s.router.Get(webBaseClientPath, func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, webClientLoginPath, http.StatusMovedPermanently) http.Redirect(w, r, webClientLoginPath, http.StatusMovedPermanently)
}) })
s.router.Get(webClientLoginPath, handleClientWebLogin) s.router.Get(webClientLoginPath, s.handleClientWebLogin)
s.router.Post(webClientLoginPath, s.handleWebClientLoginPost) s.router.Post(webClientLoginPath, s.handleWebClientLoginPost)
s.router.Group(func(router chi.Router) { s.router.Group(func(router chi.Router) {
@ -706,7 +746,7 @@ func (s *httpdServer) initializeRouter() {
s.router.Get(webBaseAdminPath, func(w http.ResponseWriter, r *http.Request) { s.router.Get(webBaseAdminPath, func(w http.ResponseWriter, r *http.Request) {
s.redirectToWebPath(w, r, webLoginPath) s.redirectToWebPath(w, r, webLoginPath)
}) })
s.router.Get(webLoginPath, handleWebLogin) s.router.Get(webLoginPath, s.handleWebAdminLogin)
s.router.Post(webLoginPath, s.handleWebAdminLoginPost) s.router.Post(webLoginPath, s.handleWebAdminLoginPost)
s.router.Get(webAdminSetupPath, handleWebAdminSetupGet) s.router.Get(webAdminSetupPath, handleWebAdminSetupGet)
s.router.Post(webAdminSetupPath, s.handleWebAdminSetupPost) s.router.Post(webAdminSetupPath, s.handleWebAdminSetupPost)

View file

@ -18,11 +18,12 @@ const (
) )
type loginPage struct { type loginPage struct {
CurrentURL string CurrentURL string
Version string Version string
Error string Error string
CSRFToken string CSRFToken string
StaticURL string StaticURL string
AltLoginURL string
} }
func getSliceFromDelimitedValues(values, delimiter string) []string { func getSliceFromDelimitedValues(values, delimiter string) []string {

View file

@ -1018,17 +1018,6 @@ func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
return user, err return user, err
} }
func renderLoginPage(w http.ResponseWriter, error string) {
data := loginPage{
CurrentURL: webLoginPath,
Version: version.Get().Version,
Error: error,
CSRFToken: createCSRFToken(),
StaticURL: webStaticFilesPath,
}
renderAdminTemplate(w, templateLogin, data)
}
func handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) { func handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) {
renderChangePwdPage(w, r, "") renderChangePwdPage(w, r, "")
} }
@ -1060,14 +1049,6 @@ func handleWebLogout(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, webLoginPath, http.StatusFound) http.Redirect(w, r, webLoginPath, http.StatusFound)
} }
func handleWebLogin(w http.ResponseWriter, r *http.Request) {
if !dataprovider.HasAdmin() {
http.Redirect(w, r, webAdminSetupPath, http.StatusFound)
return
}
renderLoginPage(w, "")
}
func handleWebMaintenance(w http.ResponseWriter, r *http.Request) { func handleWebMaintenance(w http.ResponseWriter, r *http.Request) {
renderMaintenancePage(w, r, "") renderMaintenancePage(w, r, "")
} }

View file

@ -167,17 +167,6 @@ func renderClientTemplate(w http.ResponseWriter, tmplName string, data interface
} }
} }
func renderClientLoginPage(w http.ResponseWriter, error string) {
data := loginPage{
CurrentURL: webClientLoginPath,
Version: version.Get().Version,
Error: error,
CSRFToken: createCSRFToken(),
StaticURL: webStaticFilesPath,
}
renderClientTemplate(w, templateClientLogin, data)
}
func renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) { func renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) {
var errorString string var errorString string
if body != "" { if body != "" {
@ -260,10 +249,6 @@ func renderCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError stri
renderClientTemplate(w, templateClientCredentials, data) renderClientTemplate(w, templateClientCredentials, data)
} }
func handleClientWebLogin(w http.ResponseWriter, r *http.Request) {
renderClientLoginPage(w, "")
}
func handleWebClientLogout(w http.ResponseWriter, r *http.Request) { func handleWebClientLogout(w http.ResponseWriter, r *http.Request) {
c := jwtTokenClaims{} c := jwtTokenClaims{}
c.removeCookie(w, r, webBaseClientPath) c.removeCookie(w, r, webBaseClientPath)

View file

@ -191,7 +191,8 @@
"enable_https": false, "enable_https": false,
"client_auth_type": 0, "client_auth_type": 0,
"tls_cipher_suites": [], "tls_cipher_suites": [],
"proxy_allowed": [] "proxy_allowed": [],
"hide_login_url": 0
} }
], ],
"templates_path": "templates", "templates_path": "templates",

View file

@ -9,7 +9,7 @@
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
<title>SFTPGo - Login</title> <title>SFTPGo Admin - Login</title>
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" /> <link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
@ -110,6 +110,12 @@
Login Login
</button> </button>
</form> </form>
{{if .AltLoginURL}}
<hr>
<div class="text-center">
<a class="small" href="{{.AltLoginURL}}">Web Client</a>
</div>
{{end}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -9,7 +9,7 @@
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
<title>SFTPGo - Login</title> <title>SFTPGo WebClient - Login</title>
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" /> <link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
@ -110,6 +110,12 @@
Login Login
</button> </button>
</form> </form>
{{if .AltLoginURL}}
<hr>
<div class="text-center">
<a class="small" href="{{.AltLoginURL}}">Web Admin</a>
</div>
{{end}}
</div> </div>
</div> </div>
</div> </div>