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:
parent
3a22aae34f
commit
90b324d707
13 changed files with 152 additions and 62 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
11
httpd/web.go
11
httpd/web.go
|
@ -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 {
|
||||||
|
|
|
@ -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, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue