summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/handlers/handler_configuration.go28
-rw-r--r--internal/handlers/handler_configuration_test.go140
-rw-r--r--internal/handlers/handler_extended_configuration.go32
-rw-r--r--internal/handlers/handler_extended_configuration_test.go159
-rw-r--r--internal/handlers/handler_user_info.go18
-rw-r--r--internal/handlers/handler_user_info_test.go8
-rw-r--r--internal/handlers/types.go7
-rw-r--r--internal/server/index.go4
-rw-r--r--internal/server/server.go15
-rw-r--r--internal/suites/scenario_backend_protection_test.go5
-rw-r--r--web/public/index.html2
-rw-r--r--web/src/App.tsx17
-rw-r--r--web/src/hooks/BasePath.ts9
-rw-r--r--web/src/hooks/Configuration.ts23
-rw-r--r--web/src/models/Configuration.ts6
-rw-r--r--web/src/models/UserInfo.ts1
-rw-r--r--web/src/services/Api.ts1
-rw-r--r--web/src/services/Configuration.ts15
-rw-r--r--web/src/services/UserPreferences.ts1
-rw-r--r--web/src/setupTests.js2
-rw-r--r--web/src/views/LoginPortal/LoginPortal.tsx6
-rw-r--r--web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx6
22 files changed, 216 insertions, 289 deletions
diff --git a/internal/handlers/handler_configuration.go b/internal/handlers/handler_configuration.go
index 08d08dc79..0166cab2b 100644
--- a/internal/handlers/handler_configuration.go
+++ b/internal/handlers/handler_configuration.go
@@ -1,18 +1,30 @@
package handlers
-import "github.com/authelia/authelia/internal/middlewares"
+import (
+ "github.com/authelia/authelia/internal/authentication"
+ "github.com/authelia/authelia/internal/middlewares"
+)
-// ConfigurationBody configuration parameters exposed to the frontend.
+// ConfigurationBody the content returned by the configuration endpoint.
type ConfigurationBody struct {
- RememberMe bool `json:"remember_me"` // whether remember me is enabled or not
- ResetPassword bool `json:"reset_password"`
+ AvailableMethods MethodList `json:"available_methods"`
+ SecondFactorEnabled bool `json:"second_factor_enabled"` // whether second factor is enabled or not.
+ TOTPPeriod int `json:"totp_period"`
}
-// ConfigurationGet fetches configuration parameters for frontend mutation.
+// ConfigurationGet get the configuration accessible to authenticated users.
func ConfigurationGet(ctx *middlewares.AutheliaCtx) {
- body := ConfigurationBody{
- RememberMe: ctx.Providers.SessionProvider.RememberMe != 0,
- ResetPassword: !ctx.Configuration.AuthenticationBackend.DisableResetPassword,
+ body := ConfigurationBody{}
+ body.AvailableMethods = MethodList{authentication.TOTP, authentication.U2F}
+ body.TOTPPeriod = ctx.Configuration.TOTP.Period
+
+ if ctx.Configuration.DuoAPI != nil {
+ body.AvailableMethods = append(body.AvailableMethods, authentication.Push)
}
+
+ body.SecondFactorEnabled = ctx.Providers.Authorizer.IsSecondFactorEnabled()
+ ctx.Logger.Tracef("Second factor enabled: %v", body.SecondFactorEnabled)
+
+ ctx.Logger.Tracef("Available methods are %s", body.AvailableMethods)
ctx.SetJSONBody(body) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
}
diff --git a/internal/handlers/handler_configuration_test.go b/internal/handlers/handler_configuration_test.go
index d1b64d400..d5e821013 100644
--- a/internal/handlers/handler_configuration_test.go
+++ b/internal/handlers/handler_configuration_test.go
@@ -5,49 +5,155 @@ import (
"github.com/stretchr/testify/suite"
+ "github.com/authelia/authelia/internal/authorization"
+ "github.com/authelia/authelia/internal/configuration/schema"
"github.com/authelia/authelia/internal/mocks"
- "github.com/authelia/authelia/internal/session"
)
-type ConfigurationSuite struct {
+type SecondFactorAvailableMethodsFixture struct {
suite.Suite
-
mock *mocks.MockAutheliaCtx
}
-func (s *ConfigurationSuite) SetupTest() {
+func (s *SecondFactorAvailableMethodsFixture) SetupTest() {
s.mock = mocks.NewMockAutheliaCtx(s.T())
+ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
+ DefaultPolicy: "deny",
+ Rules: []schema.ACLRule{},
+ })
}
-func (s *ConfigurationSuite) TearDownTest() {
+func (s *SecondFactorAvailableMethodsFixture) TearDownTest() {
s.mock.Close()
}
-func (s *ConfigurationSuite) TestShouldDisableRememberMe() {
- s.mock.Ctx.Configuration.Session.RememberMeDuration = "0"
- s.mock.Ctx.Providers.SessionProvider = session.NewProvider(
- s.mock.Ctx.Configuration.Session)
+func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethods() {
+ s.mock.Ctx.Configuration = schema.Configuration{
+ TOTP: &schema.TOTPConfiguration{
+ Period: schema.DefaultTOTPConfiguration.Period,
+ },
+ }
expectedBody := ConfigurationBody{
- RememberMe: false,
- ResetPassword: true,
+ AvailableMethods: []string{"totp", "u2f"},
+ SecondFactorEnabled: false,
+ TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
}
ConfigurationGet(s.mock.Ctx)
s.mock.Assert200OK(s.T(), expectedBody)
}
-func (s *ConfigurationSuite) TestShouldDisableResetPassword() {
- s.mock.Ctx.Configuration.AuthenticationBackend.DisableResetPassword = true
+func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethodsAndMobilePush() {
+ s.mock.Ctx.Configuration = schema.Configuration{
+ DuoAPI: &schema.DuoAPIConfiguration{},
+ TOTP: &schema.TOTPConfiguration{
+ Period: schema.DefaultTOTPConfiguration.Period,
+ },
+ }
expectedBody := ConfigurationBody{
- RememberMe: true,
- ResetPassword: false,
+ AvailableMethods: []string{"totp", "u2f", "mobile_push"},
+ SecondFactorEnabled: false,
+ TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
}
ConfigurationGet(s.mock.Ctx)
s.mock.Assert200OK(s.T(), expectedBody)
}
-func TestRunHandlerConfigurationSuite(t *testing.T) {
- s := new(ConfigurationSuite)
+func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsDisabledWhenNoRuleIsSetToTwoFactor() {
+ s.mock.Ctx.Configuration = schema.Configuration{
+ TOTP: &schema.TOTPConfiguration{
+ Period: schema.DefaultTOTPConfiguration.Period,
+ },
+ }
+ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
+ DefaultPolicy: "bypass",
+ Rules: []schema.ACLRule{
+ {
+ Domains: []string{"example.com"},
+ Policy: "deny",
+ },
+ {
+ Domains: []string{"abc.example.com"},
+ Policy: "single_factor",
+ },
+ {
+ Domains: []string{"def.example.com"},
+ Policy: "bypass",
+ },
+ },
+ })
+ ConfigurationGet(s.mock.Ctx)
+ s.mock.Assert200OK(s.T(), ConfigurationBody{
+ AvailableMethods: []string{"totp", "u2f"},
+ SecondFactorEnabled: false,
+ TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
+ })
+}
+
+func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabledWhenDefaultPolicySetToTwoFactor() {
+ s.mock.Ctx.Configuration = schema.Configuration{
+ TOTP: &schema.TOTPConfiguration{
+ Period: schema.DefaultTOTPConfiguration.Period,
+ },
+ }
+ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
+ DefaultPolicy: "two_factor",
+ Rules: []schema.ACLRule{
+ {
+ Domains: []string{"example.com"},
+ Policy: "deny",
+ },
+ {
+ Domains: []string{"abc.example.com"},
+ Policy: "single_factor",
+ },
+ {
+ Domains: []string{"def.example.com"},
+ Policy: "bypass",
+ },
+ },
+ })
+ ConfigurationGet(s.mock.Ctx)
+ s.mock.Assert200OK(s.T(), ConfigurationBody{
+ AvailableMethods: []string{"totp", "u2f"},
+ SecondFactorEnabled: true,
+ TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
+ })
+}
+
+func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabledWhenSomePolicySetToTwoFactor() {
+ s.mock.Ctx.Configuration = schema.Configuration{
+ TOTP: &schema.TOTPConfiguration{
+ Period: schema.DefaultTOTPConfiguration.Period,
+ },
+ }
+ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
+ DefaultPolicy: "bypass",
+ Rules: []schema.ACLRule{
+ {
+ Domains: []string{"example.com"},
+ Policy: "deny",
+ },
+ {
+ Domains: []string{"abc.example.com"},
+ Policy: "two_factor",
+ },
+ {
+ Domains: []string{"def.example.com"},
+ Policy: "bypass",
+ },
+ },
+ })
+ ConfigurationGet(s.mock.Ctx)
+ s.mock.Assert200OK(s.T(), ConfigurationBody{
+ AvailableMethods: []string{"totp", "u2f"},
+ SecondFactorEnabled: true,
+ TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
+ })
+}
+
+func TestRunSuite(t *testing.T) {
+ s := new(SecondFactorAvailableMethodsFixture)
suite.Run(t, s)
}
diff --git a/internal/handlers/handler_extended_configuration.go b/internal/handlers/handler_extended_configuration.go
deleted file mode 100644
index 2a06f3e90..000000000
--- a/internal/handlers/handler_extended_configuration.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package handlers
-
-import (
- "github.com/authelia/authelia/internal/authentication"
- "github.com/authelia/authelia/internal/middlewares"
-)
-
-// ExtendedConfigurationBody the content returned by extended configuration endpoint.
-type ExtendedConfigurationBody struct {
- AvailableMethods MethodList `json:"available_methods"`
- DisplayName string `json:"display_name"`
- SecondFactorEnabled bool `json:"second_factor_enabled"` // whether second factor is enabled or not.
- TOTPPeriod int `json:"totp_period"`
-}
-
-// ExtendedConfigurationGet get the extended configuration accessible to authenticated users.
-func ExtendedConfigurationGet(ctx *middlewares.AutheliaCtx) {
- body := ExtendedConfigurationBody{}
- body.AvailableMethods = MethodList{authentication.TOTP, authentication.U2F}
- body.DisplayName = ctx.GetSession().DisplayName
- body.TOTPPeriod = ctx.Configuration.TOTP.Period
-
- if ctx.Configuration.DuoAPI != nil {
- body.AvailableMethods = append(body.AvailableMethods, authentication.Push)
- }
-
- body.SecondFactorEnabled = ctx.Providers.Authorizer.IsSecondFactorEnabled()
- ctx.Logger.Tracef("Second factor enabled: %v", body.SecondFactorEnabled)
-
- ctx.Logger.Tracef("Available methods are %s", body.AvailableMethods)
- ctx.SetJSONBody(body) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
-}
diff --git a/internal/handlers/handler_extended_configuration_test.go b/internal/handlers/handler_extended_configuration_test.go
deleted file mode 100644
index b974b0758..000000000
--- a/internal/handlers/handler_extended_configuration_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package handlers
-
-import (
- "testing"
-
- "github.com/stretchr/testify/suite"
-
- "github.com/authelia/authelia/internal/authorization"
- "github.com/authelia/authelia/internal/configuration/schema"
- "github.com/authelia/authelia/internal/mocks"
-)
-
-type SecondFactorAvailableMethodsFixture struct {
- suite.Suite
- mock *mocks.MockAutheliaCtx
-}
-
-func (s *SecondFactorAvailableMethodsFixture) SetupTest() {
- s.mock = mocks.NewMockAutheliaCtx(s.T())
- s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
- DefaultPolicy: "deny",
- Rules: []schema.ACLRule{},
- })
-}
-
-func (s *SecondFactorAvailableMethodsFixture) TearDownTest() {
- s.mock.Close()
-}
-
-func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethods() {
- s.mock.Ctx.Configuration = schema.Configuration{
- TOTP: &schema.TOTPConfiguration{
- Period: schema.DefaultTOTPConfiguration.Period,
- },
- }
- expectedBody := ExtendedConfigurationBody{
- AvailableMethods: []string{"totp", "u2f"},
- SecondFactorEnabled: false,
- TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
- }
-
- ExtendedConfigurationGet(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), expectedBody)
-}
-
-func (s *SecondFactorAvailableMethodsFixture) TestShouldServeDefaultMethodsAndMobilePush() {
- s.mock.Ctx.Configuration = schema.Configuration{
- DuoAPI: &schema.DuoAPIConfiguration{},
- TOTP: &schema.TOTPConfiguration{
- Period: schema.DefaultTOTPConfiguration.Period,
- },
- }
- expectedBody := ExtendedConfigurationBody{
- AvailableMethods: []string{"totp", "u2f", "mobile_push"},
- SecondFactorEnabled: false,
- TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
- }
-
- ExtendedConfigurationGet(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), expectedBody)
-}
-
-func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsDisabledWhenNoRuleIsSetToTwoFactor() {
- s.mock.Ctx.Configuration = schema.Configuration{
- TOTP: &schema.TOTPConfiguration{
- Period: schema.DefaultTOTPConfiguration.Period,
- },
- }
- s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
- DefaultPolicy: "bypass",
- Rules: []schema.ACLRule{
- {
- Domains: []string{"example.com"},
- Policy: "deny",
- },
- {
- Domains: []string{"abc.example.com"},
- Policy: "single_factor",
- },
- {
- Domains: []string{"def.example.com"},
- Policy: "bypass",
- },
- },
- })
- ExtendedConfigurationGet(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), ExtendedConfigurationBody{
- AvailableMethods: []string{"totp", "u2f"},
- SecondFactorEnabled: false,
- TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
- })
-}
-
-func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabledWhenDefaultPolicySetToTwoFactor() {
- s.mock.Ctx.Configuration = schema.Configuration{
- TOTP: &schema.TOTPConfiguration{
- Period: schema.DefaultTOTPConfiguration.Period,
- },
- }
- s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
- DefaultPolicy: "two_factor",
- Rules: []schema.ACLRule{
- {
- Domains: []string{"example.com"},
- Policy: "deny",
- },
- {
- Domains: []string{"abc.example.com"},
- Policy: "single_factor",
- },
- {
- Domains: []string{"def.example.com"},
- Policy: "bypass",
- },
- },
- })
- ExtendedConfigurationGet(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), ExtendedConfigurationBody{
- AvailableMethods: []string{"totp", "u2f"},
- SecondFactorEnabled: true,
- TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
- })
-}
-
-func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabledWhenSomePolicySetToTwoFactor() {
- s.mock.Ctx.Configuration = schema.Configuration{
- TOTP: &schema.TOTPConfiguration{
- Period: schema.DefaultTOTPConfiguration.Period,
- },
- }
- s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{
- DefaultPolicy: "bypass",
- Rules: []schema.ACLRule{
- {
- Domains: []string{"example.com"},
- Policy: "deny",
- },
- {
- Domains: []string{"abc.example.com"},
- Policy: "two_factor",
- },
- {
- Domains: []string{"def.example.com"},
- Policy: "bypass",
- },
- },
- })
- ExtendedConfigurationGet(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), ExtendedConfigurationBody{
- AvailableMethods: []string{"totp", "u2f"},
- SecondFactorEnabled: true,
- TOTPPeriod: schema.DefaultTOTPConfiguration.Period,
- })
-}
-
-func TestRunSuite(t *testing.T) {
- s := new(SecondFactorAvailableMethodsFixture)
- suite.Run(t, s)
-}
diff --git a/internal/handlers/handler_user_info.go b/internal/handlers/handler_user_info.go
index 7066c6402..410aedad5 100644
--- a/internal/handlers/handler_user_info.go
+++ b/internal/handlers/handler_user_info.go
@@ -13,7 +13,7 @@ import (
"github.com/authelia/authelia/internal/utils"
)
-func loadInfo(username string, storageProvider storage.Provider, preferences *UserPreferences, logger *logrus.Entry) []error {
+func loadInfo(username string, storageProvider storage.Provider, userInfo *UserInfo, logger *logrus.Entry) []error {
var wg sync.WaitGroup
wg.Add(3)
@@ -32,9 +32,9 @@ func loadInfo(username string, storageProvider storage.Provider, preferences *Us
}
if method == "" {
- preferences.Method = authentication.PossibleMethods[0]
+ userInfo.Method = authentication.PossibleMethods[0]
} else {
- preferences.Method = method
+ userInfo.Method = method
}
}()
@@ -53,7 +53,7 @@ func loadInfo(username string, storageProvider storage.Provider, preferences *Us
return
}
- preferences.HasU2F = true
+ userInfo.HasU2F = true
}()
go func() {
@@ -71,7 +71,7 @@ func loadInfo(username string, storageProvider storage.Provider, preferences *Us
return
}
- preferences.HasTOTP = true
+ userInfo.HasTOTP = true
}()
wg.Wait()
@@ -83,15 +83,17 @@ func loadInfo(username string, storageProvider storage.Provider, preferences *Us
func UserInfoGet(ctx *middlewares.AutheliaCtx) {
userSession := ctx.GetSession()
- preferences := UserPreferences{}
- errors := loadInfo(userSession.Username, ctx.Providers.StorageProvider, &preferences, ctx.Logger)
+ userInfo := UserInfo{}
+ errors := loadInfo(userSession.Username, ctx.Providers.StorageProvider, &userInfo, ctx.Logger)
if len(errors) > 0 {
ctx.Error(fmt.Errorf("Unable to load user information"), operationFailedMessage)
return
}
- ctx.SetJSONBody(preferences) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
+ userInfo.DisplayName = userSession.DisplayName
+
+ ctx.SetJSONBody(userInfo) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
}
// MethodBody the selected 2FA method.
diff --git a/internal/handlers/handler_user_info_test.go b/internal/handlers/handler_user_info_test.go
index 4d925787c..44702ec28 100644
--- a/internal/handlers/handler_user_info_test.go
+++ b/internal/handlers/handler_user_info_test.go
@@ -31,7 +31,7 @@ func (s *FetchSuite) TearDownTest() {
s.mock.Close()
}
-func setPreferencesExpectations(preferences UserPreferences, provider *storage.MockProvider) {
+func setPreferencesExpectations(preferences UserInfo, provider *storage.MockProvider) {
provider.
EXPECT().
LoadPreferred2FAMethod(gomock.Eq("john")).
@@ -65,7 +65,7 @@ func setPreferencesExpectations(preferences UserPreferences, provider *storage.M
}
func TestMethodSetToU2F(t *testing.T) {
- table := []UserPreferences{
+ table := []UserInfo{
{
Method: "totp",
},
@@ -97,7 +97,7 @@ func TestMethodSetToU2F(t *testing.T) {
setPreferencesExpectations(expectedPreferences, mock.StorageProviderMock)
UserInfoGet(mock.Ctx)
- actualPreferences := UserPreferences{}
+ actualPreferences := UserInfo{}
mock.GetResponseData(t, &actualPreferences)
t.Run("expected method", func(t *testing.T) {
@@ -132,7 +132,7 @@ func (s *FetchSuite) TestShouldGetDefaultPreferenceIfNotInDB() {
Return("", storage.ErrNoTOTPSecret)
UserInfoGet(s.mock.Ctx)
- s.mock.Assert200OK(s.T(), UserPreferences{Method: "totp"})
+ s.mock.Assert200OK(s.T(), UserInfo{Method: "totp"})
}
func (s *FetchSuite) TestShouldReturnError500WhenStorageFailsToLoad() {
diff --git a/internal/handlers/types.go b/internal/handlers/types.go
index b42b2889b..bcd21f62d 100644
--- a/internal/handlers/types.go
+++ b/internal/handlers/types.go
@@ -11,8 +11,11 @@ type MethodList = []string
type authorizationMatching int
-// UserPreferences is the model of user second factor preferences.
-type UserPreferences struct {
+// UserInfo is the model of user info and second factor preferences.
+type UserInfo struct {
+ // The users display name.
+ DisplayName string `json:"display_name"`
+
// The preferred 2FA method.
Method string `json:"method" valid:"required"`
diff --git a/internal/server/index.go b/internal/server/index.go
index 9c5544f10..fbf09e9cf 100644
--- a/internal/server/index.go
+++ b/internal/server/index.go
@@ -16,7 +16,7 @@ var alphaNumericRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV
// ServeIndex serve the index.html file with nonce generated for supporting
// restrictive CSP while using material-ui from the embedded virtual filesystem.
//go:generate broccoli -src ../../public_html -o public_html
-func ServeIndex(publicDir, base string) fasthttp.RequestHandler {
+func ServeIndex(publicDir, base, rememberMe, resetPassword string) fasthttp.RequestHandler {
f, err := br.Open(publicDir + "/index.html")
if err != nil {
logging.Logger().Fatalf("Unable to open index.html: %v", err)
@@ -38,7 +38,7 @@ func ServeIndex(publicDir, base string) fasthttp.RequestHandler {
ctx.SetContentType("text/html; charset=utf-8")
ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce))
- err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ CSPNonce, Base string }{CSPNonce: nonce, Base: base})
+ err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, CSPNonce, RememberMe, ResetPassword string }{Base: base, CSPNonce: nonce, RememberMe: rememberMe, ResetPassword: resetPassword})
if err != nil {
ctx.Error("An error occurred", 503)
logging.Logger().Errorf("Unable to execute template: %v", err)
diff --git a/internal/server/server.go b/internal/server/server.go
index cee96144f..9d0bfb88d 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -3,6 +3,7 @@ package server
import (
"fmt"
"os"
+ "strconv"
duoapi "github.com/duosecurity/duo_api_golang"
"github.com/fasthttp/router"
@@ -22,10 +23,15 @@ import (
func StartServer(configuration schema.Configuration, providers middlewares.Providers) {
autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers)
embeddedAssets := "/public_html"
+ rememberMe := strconv.FormatBool(configuration.Session.RememberMeDuration != "0")
+ resetPassword := strconv.FormatBool(!configuration.AuthenticationBackend.DisableResetPassword)
+
rootFiles := []string{"favicon.ico", "manifest.json", "robots.txt"}
+ serveIndexHandler := ServeIndex(embeddedAssets, configuration.Server.Path, rememberMe, resetPassword)
+
r := router.New()
- r.GET("/", ServeIndex(embeddedAssets, configuration.Server.Path))
+ r.GET("/", serveIndexHandler)
for _, f := range rootFiles {
r.GET("/"+f, fasthttpadaptor.NewFastHTTPHandler(br.Serve(embeddedAssets)))
@@ -35,9 +41,8 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
r.GET("/api/state", autheliaMiddleware(handlers.StateGet))
- r.GET("/api/configuration", autheliaMiddleware(handlers.ConfigurationGet))
- r.GET("/api/configuration/extended", autheliaMiddleware(
- middlewares.RequireFirstFactor(handlers.ExtendedConfigurationGet)))
+ r.GET("/api/configuration", autheliaMiddleware(
+ middlewares.RequireFirstFactor(handlers.ConfigurationGet)))
r.GET("/api/verify", autheliaMiddleware(handlers.VerifyGet(configuration.AuthenticationBackend)))
r.HEAD("/api/verify", autheliaMiddleware(handlers.VerifyGet(configuration.AuthenticationBackend)))
@@ -113,7 +118,7 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
r.GET("/debug/vars", expvarhandler.ExpvarHandler)
}
- r.NotFound = ServeIndex(embeddedAssets, configuration.Server.Path)
+ r.NotFound = serveIndexHandler
handler := middlewares.LogRequestMiddleware(r.Handler)
if configuration.Server.Path != "" {
diff --git a/internal/suites/scenario_backend_protection_test.go b/internal/suites/scenario_backend_protection_test.go
index 197cedd86..c4b500a27 100644
--- a/internal/suites/scenario_backend_protection_test.go
+++ b/internal/suites/scenario_backend_protection_test.go
@@ -47,10 +47,7 @@ func (s *BackendProtectionScenario) TestProtectionOfBackendEndpoints() {
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/user/info/2fa_method", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/user/info", AutheliaBaseURL), 403)
- s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/configuration/extended", AutheliaBaseURL), 403)
-
- // This is the global configuration, it's safe to let it open.
- s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/configuration", AutheliaBaseURL), 200)
+ s.AssertRequestStatusCode("GET", fmt.Sprintf("%s/api/configuration", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/u2f/identity/start", AutheliaBaseURL), 403)
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/secondfactor/u2f/identity/finish", AutheliaBaseURL), 403)
diff --git a/web/public/index.html b/web/public/index.html
index e24d9350f..4ae162c2b 100644
--- a/web/public/index.html
+++ b/web/public/index.html
@@ -25,7 +25,7 @@
<title>Login - Authelia</title>
</head>
-<body data-basepath="%PUBLIC_URL%">
+<body data-basepath="%PUBLIC_URL%" data-rememberme="{{.RememberMe}}" data-disable-resetpassword="{{.ResetPassword}}">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
diff --git a/web/src/App.tsx b/web/src/App.tsx
index cbe040f56..94796648b 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
import {
BrowserRouter as Router, Route, Switch, Redirect
} from "react-router-dom";
@@ -17,7 +17,7 @@ import NotificationsContext from './hooks/NotificationsContext';
import { Notification } from './models/Notifications';
import NotificationBar from './components/NotificationBar';
import SignOut from './views/LoginPortal/SignOut/SignOut';
-import { useConfiguration } from './hooks/Configuration';
+import { useRememberMe, useResetPassword } from './hooks/Configuration';
import '@fortawesome/fontawesome-svg-core/styles.css'
import { config as faConfig } from '@fortawesome/fontawesome-svg-core';
import { useBasePath } from './hooks/BasePath';
@@ -26,15 +26,6 @@ faConfig.autoAddCss = false;
const App: React.FC = () => {
const [notification, setNotification] = useState(null as Notification | null);
- const [configuration, fetchConfig, , fetchConfigError] = useConfiguration();
-
- useEffect(() => {
- if (fetchConfigError) {
- console.error(fetchConfigError);
- }
- }, [fetchConfigError]);
-
- useEffect(() => { fetchConfig() }, [fetchConfig]);
return (
<NotificationsContext.Provider value={{ notification, setNotification }} >
@@ -58,8 +49,8 @@ const App: React.FC = () => {
</Route>
<Route path={FirstFactorRoute}>
<LoginPortal
- rememberMe={configuration?.remember_me === true}
- resetPassword={configuration?.reset_password === true} />
+ rememberMe={useRememberMe()}
+ resetPassword={useResetPassword()} />
</Route>
<Route path="/">
<Redirect to={FirstFactorRoute} />
diff --git a/web/src/hooks/BasePath.ts b/web/src/hooks/BasePath.ts
index 3c2e6e425..938bea2cf 100644
--- a/web/src/hooks/BasePath.ts
+++ b/web/src/hooks/BasePath.ts
@@ -1,8 +1,5 @@
-export function useBasePath() {
- const basePath = document.body.getAttribute("data-basepath");
- if (basePath === null) {
- throw new Error("No base path detected");
- }
+import { useEmbeddedVariable } from "./Configuration";
- return basePath;
+export function useBasePath() {
+ return useEmbeddedVariable("basepath");
} \ No newline at end of file
diff --git a/web/src/hooks/Configuration.ts b/web/src/hooks/Configuration.ts
index 18f407596..0d445cbf3 100644
--- a/web/src/hooks/Configuration.ts
+++ b/web/src/hooks/Configuration.ts
@@ -1,10 +1,23 @@
import { useRemoteCall } from "./RemoteCall";
-import { getConfiguration, getExtendedConfiguration } from "../services/Configuration";
+import { getConfiguration } from "../services/Configuration";
-export function useConfiguration() {
- return useRemoteCall(getConfiguration, []);
+export function useEmbeddedVariable(variableName: string) {
+ const value = document.body.getAttribute(`data-${variableName}`);
+ if (value === null) {
+ throw new Error(`No ${variableName} embedded variable detected`);
+ }
+
+ return value;
+}
+
+export function useRememberMe() {
+ return useEmbeddedVariable("rememberme") === "true";
}
-export function useExtendedConfiguration() {
- return useRemoteCall(getExtendedConfiguration, []);
+export function useResetPassword() {
+ return useEmbeddedVariable("disable-resetpassword") === "true";
+}
+
+export function useConfiguration() {
+ return useRemoteCall(getConfiguration, []);
} \ No newline at end of file
diff --git a/web/src/models/Configuration.ts b/web/src/models/Configuration.ts
index 56b452979..ba04e7f96 100644
--- a/web/src/models/Configuration.ts
+++ b/web/src/models/Configuration.ts
@@ -1,13 +1,7 @@
import { SecondFactorMethod } from "./Methods";
export interface Configuration {
- remember_me: boolean;
- reset_password: boolean;
-}
-
-export interface ExtendedConfiguration {
available_methods: Set<SecondFactorMethod>;
- display_name: string;
second_factor_enabled: boolean;
totp_period: number;
} \ No newline at end of file
diff --git a/web/src/models/UserInfo.ts b/web/src/models/UserInfo.ts
index 3e10203a6..ead0752d1 100644
--- a/web/src/models/UserInfo.ts
+++ b/web/src/models/UserInfo.ts
@@ -1,6 +1,7 @@
import { SecondFactorMethod } from "./Methods";
export interface UserInfo {
+ display_name: string;
method: SecondFactorMethod;
has_u2f: boolean;
has_totp: boolean;
diff --git a/web/src/services/Api.ts b/web/src/services/Api.ts
index 77ed345b4..2f81dd343 100644
--- a/web/src/services/Api.ts
+++ b/web/src/services/Api.ts
@@ -28,7 +28,6 @@ export const UserInfoPath = basePath + "/api/user/info";
export const UserInfo2FAMethodPath = basePath + "/api/user/info/2fa_method";
export const ConfigurationPath = basePath + "/api/configuration";
-export const ExtendedConfigurationPath = basePath + "/api/configuration/extended";
export interface ErrorResponse {
status: "KO";
diff --git a/web/src/services/Configuration.ts b/web/src/services/Configuration.ts
index eb0dd002e..1c9651ab7 100644
--- a/web/src/services/Configuration.ts
+++ b/web/src/services/Configuration.ts
@@ -1,20 +1,15 @@
import { Get } from "./Client";
-import { ExtendedConfigurationPath, ConfigurationPath } from "./Api";
+import { ConfigurationPath } from "./Api";
import { toEnum, Method2FA } from "./UserPreferences";
-import { Configuration, ExtendedConfiguration } from "../models/Configuration";
+import { Configuration } from "../models/Configuration";
-export async function getConfiguration(): Promise<Configuration> {
- return Get<Configuration>(ConfigurationPath);
-}
-
-interface ExtendedConfigurationPayload {
+interface ConfigurationPayload {
available_methods: Method2FA[];
- display_name: string;
second_factor_enabled: boolean;
totp_period: number;
}
-export async function getExtendedConfiguration(): Promise<ExtendedConfiguration> {
- const config = await Get<ExtendedConfigurationPayload>(ExtendedConfigurationPath);
+export async function getConfiguration(): Promise<Configuration> {
+ const config = await Get<ConfigurationPayload>(ConfigurationPath);
return { ...config, available_methods: new Set(config.available_methods.map(toEnum)) };
} \ No newline at end of file
diff --git a/web/src/services/UserPreferences.ts b/web/src/services/UserPreferences.ts
index 471a3f304..c63db1b52 100644
--- a/web/src/services/UserPreferences.ts
+++ b/web/src/services/UserPreferences.ts
@@ -6,6 +6,7 @@ import { UserInfo } from "../models/UserInfo";
export type Method2FA = "u2f" | "totp" | "mobile_push";
export interface UserInfoPayload {
+ display_name: string;
method: Method2FA;
has_u2f: boolean;
has_totp: boolean;
diff --git a/web/src/setupTests.js b/web/src/setupTests.js
index 71a979d56..abc9fa374 100644
--- a/web/src/setupTests.js
+++ b/web/src/setupTests.js
@@ -1,4 +1,6 @@
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
document.body.setAttribute("data-basepath", "");
+document.body.setAttribute("data-rememberme", "false");
+document.body.setAttribute("data-disable-resetpassword", "false");
configure({ adapter: new Adapter() });
diff --git a/web/src/views/LoginPortal/LoginPortal.tsx b/web/src/views/LoginPortal/LoginPortal.tsx
index e309055db..82c27ccbf 100644
--- a/web/src/views/LoginPortal/LoginPortal.tsx
+++ b/web/src/views/LoginPortal/LoginPortal.tsx
@@ -13,7 +13,7 @@ import { useNotifications } from "../../hooks/NotificationsContext";
import { useRedirectionURL } from "../../hooks/RedirectionURL";
import { useUserPreferences as userUserInfo } from "../../hooks/UserInfo";
import { SecondFactorMethod } from "../../models/Methods";
-import { useExtendedConfiguration } from "../../hooks/Configuration";
+import { useConfiguration } from "../../hooks/Configuration";
import AuthenticatedView from "./AuthenticatedView/AuthenticatedView";
export interface Props {
@@ -30,7 +30,7 @@ export default function (props: Props) {
const [state, fetchState, , fetchStateError] = useAutheliaState();
const [userInfo, fetchUserInfo, , fetchUserInfoError] = userUserInfo();
- const [configuration, fetchConfiguration, , fetchConfigurationError] = useExtendedConfiguration();
+ const [configuration, fetchConfiguration, , fetchConfigurationError] = useConfiguration();
const redirect = useCallback((url: string) => history.push(url), [history]);
@@ -135,7 +135,7 @@ export default function (props: Props) {
onAuthenticationSuccess={handleAuthSuccess} /> : null}
</Route>
<Route path={AuthenticatedRoute} exact>
- {configuration ? <AuthenticatedView name={configuration.display_name} /> : null}
+ {userInfo ? <AuthenticatedView name={userInfo.display_name} /> : null}
</Route>
<Route path="/">
<Redirect to={FirstFactorRoute} />
diff --git a/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx b/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx
index bc2e0ef63..6d4e25d43 100644
--- a/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx
+++ b/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx
@@ -18,7 +18,7 @@ import {
} from "../../../Routes";
import { setPreferred2FAMethod } from "../../../services/UserPreferences";
import { UserInfo } from "../../../models/UserInfo";
-import { ExtendedConfiguration } from "../../../models/Configuration";
+import { Configuration } from "../../../models/Configuration";
import u2fApi from "u2f-api";
import { AuthenticationLevel } from "../../../services/State";
@@ -28,7 +28,7 @@ export interface Props {
authenticationLevel: AuthenticationLevel;
userInfo: UserInfo;
- configuration: ExtendedConfiguration;
+ configuration: Configuration;
onMethodChanged: (method: SecondFactorMethod) => void;
onAuthenticationSuccess: (redirectURL: string | undefined) => void;
@@ -88,7 +88,7 @@ export default function (props: Props) {
return (
<LoginLayout
id="second-factor-stage"
- title={`Hi ${props.configuration.display_name}`}
+ title={`Hi ${props.userInfo.display_name}`}
showBrand>
<MethodSelectionDialog
open={methodSelectionOpen}