[FEATURE] Allow Authelia to listen on a specified path (#1027)
* [FEATURE] Allow Authelia to listen on a specified path * Fix linting and add a couple typescript types * Template index.html to support base_url * Update docs and configuration template * Access base path from body attribute. * Update CSP * Fix unit test Also remove check for body as this will never get triggered, react itself is loaded inside the body so this has to always be successful. * Template index.html with ${PUBLIC_URL} * Define PUBLIC_URL in .env(s) * Add docs clarification Co-authored-by: Amir Zarrinkafsh <nightah@me.com> Co-authored-by: Clement Michaud <clement.michaud34@gmail.com>pull/1036/head
parent
469daedd36
commit
fcd0b5e46a
|
@ -16,6 +16,8 @@ server:
|
||||||
read_buffer_size: 4096
|
read_buffer_size: 4096
|
||||||
# Write buffer size configures the http server's maximum outgoing response size in bytes.
|
# Write buffer size configures the http server's maximum outgoing response size in bytes.
|
||||||
write_buffer_size: 4096
|
write_buffer_size: 4096
|
||||||
|
# Set the single level path Authelia listens on, must be alphanumeric chars and should not contain any slashes.
|
||||||
|
path: ""
|
||||||
|
|
||||||
# Level of verbosity for logs: info, debug, trace
|
# Level of verbosity for logs: info, debug, trace
|
||||||
log_level: debug
|
log_level: debug
|
||||||
|
|
|
@ -20,6 +20,8 @@ server:
|
||||||
read_buffer_size: 4096
|
read_buffer_size: 4096
|
||||||
# Write buffer size configures the http server's maximum outgoing response size in bytes.
|
# Write buffer size configures the http server's maximum outgoing response size in bytes.
|
||||||
write_buffer_size: 4096
|
write_buffer_size: 4096
|
||||||
|
# Set the single level path Authelia listens on, must be alphanumeric chars and should not contain any slashes.
|
||||||
|
path: ""
|
||||||
```
|
```
|
||||||
|
|
||||||
### Buffer Sizes
|
### Buffer Sizes
|
||||||
|
@ -27,3 +29,23 @@ server:
|
||||||
The read and write buffer sizes generally should be the same. This is because when Authelia verifies
|
The read and write buffer sizes generally should be the same. This is because when Authelia verifies
|
||||||
if the user is authorized to visit a URL, it also sends back nearly the same size response
|
if the user is authorized to visit a URL, it also sends back nearly the same size response
|
||||||
(write_buffer_size) as the request (read_buffer_size).
|
(write_buffer_size) as the request (read_buffer_size).
|
||||||
|
|
||||||
|
### Path
|
||||||
|
|
||||||
|
Authelia by default is served from the root `/` location, either via its own domain or subdomain.
|
||||||
|
|
||||||
|
Example: https://auth.example.com/, https://example.com/
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
path: ""
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifying this setting will allow you to serve Authelia out from a specified base path. Please note
|
||||||
|
that currently only a single level path is supported meaning slashes are not allowed, and only
|
||||||
|
alphanumeric characters are supported.
|
||||||
|
|
||||||
|
Example: https://auth.example.com/authelia/, https://example.com/authelia/
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
path: authelia
|
||||||
|
```
|
|
@ -2,6 +2,7 @@ package schema
|
||||||
|
|
||||||
// ServerConfiguration represents the configuration of the http server.
|
// ServerConfiguration represents the configuration of the http server.
|
||||||
type ServerConfiguration struct {
|
type ServerConfiguration struct {
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
ReadBufferSize int `mapstructure:"read_buffer_size"`
|
ReadBufferSize int `mapstructure:"read_buffer_size"`
|
||||||
WriteBufferSize int `mapstructure:"write_buffer_size"`
|
WriteBufferSize int `mapstructure:"write_buffer_size"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ var validKeys = []string{
|
||||||
// Server Keys.
|
// Server Keys.
|
||||||
"server.read_buffer_size",
|
"server.read_buffer_size",
|
||||||
"server.write_buffer_size",
|
"server.write_buffer_size",
|
||||||
|
"server.path",
|
||||||
|
|
||||||
// TOTP Keys.
|
// TOTP Keys.
|
||||||
"totp.issuer",
|
"totp.issuer",
|
||||||
|
|
|
@ -2,8 +2,11 @@ package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/configuration/schema"
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultReadBufferSize = 4096
|
var defaultReadBufferSize = 4096
|
||||||
|
@ -11,6 +14,16 @@ var defaultWriteBufferSize = 4096
|
||||||
|
|
||||||
// ValidateServer checks a server configuration is correct.
|
// ValidateServer checks a server configuration is correct.
|
||||||
func ValidateServer(configuration *schema.ServerConfiguration, validator *schema.StructValidator) {
|
func ValidateServer(configuration *schema.ServerConfiguration, validator *schema.StructValidator) {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(configuration.Path, "/"):
|
||||||
|
validator.Push(fmt.Errorf("server path must not contain any forward slashes"))
|
||||||
|
case !utils.IsStringAlphaNumeric(configuration.Path):
|
||||||
|
validator.Push(fmt.Errorf("server path must only be alpha numeric characters"))
|
||||||
|
case configuration.Path == "": // Don't do anything if it's blank.
|
||||||
|
default:
|
||||||
|
configuration.Path = path.Clean("/" + configuration.Path)
|
||||||
|
}
|
||||||
|
|
||||||
if configuration.ReadBufferSize == 0 {
|
if configuration.ReadBufferSize == 0 {
|
||||||
configuration.ReadBufferSize = defaultReadBufferSize
|
configuration.ReadBufferSize = defaultReadBufferSize
|
||||||
} else if configuration.ReadBufferSize < 0 {
|
} else if configuration.ReadBufferSize < 0 {
|
||||||
|
|
|
@ -12,9 +12,7 @@ import (
|
||||||
func TestShouldSetDefaultConfig(t *testing.T) {
|
func TestShouldSetDefaultConfig(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := schema.ServerConfiguration{}
|
config := schema.ServerConfiguration{}
|
||||||
|
|
||||||
ValidateServer(&config, validator)
|
ValidateServer(&config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, defaultReadBufferSize, config.ReadBufferSize)
|
assert.Equal(t, defaultReadBufferSize, config.ReadBufferSize)
|
||||||
assert.Equal(t, defaultWriteBufferSize, config.WriteBufferSize)
|
assert.Equal(t, defaultWriteBufferSize, config.WriteBufferSize)
|
||||||
|
@ -26,10 +24,28 @@ func TestShouldRaiseOnNegativeValues(t *testing.T) {
|
||||||
ReadBufferSize: -1,
|
ReadBufferSize: -1,
|
||||||
WriteBufferSize: -1,
|
WriteBufferSize: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateServer(&config, validator)
|
ValidateServer(&config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 2)
|
require.Len(t, validator.Errors(), 2)
|
||||||
assert.EqualError(t, validator.Errors()[0], "server read buffer size must be above 0")
|
assert.EqualError(t, validator.Errors()[0], "server read buffer size must be above 0")
|
||||||
assert.EqualError(t, validator.Errors()[1], "server write buffer size must be above 0")
|
assert.EqualError(t, validator.Errors()[1], "server write buffer size must be above 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldRaiseOnNonAlphanumericCharsInPath(t *testing.T) {
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
config := schema.ServerConfiguration{
|
||||||
|
Path: "app le",
|
||||||
|
}
|
||||||
|
ValidateServer(&config, validator)
|
||||||
|
require.Len(t, validator.Errors(), 1)
|
||||||
|
assert.Error(t, validator.Errors()[0], "server path must only be alpha numeric characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldRaiseOnForwardSlashInPath(t *testing.T) {
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
config := schema.ServerConfiguration{
|
||||||
|
Path: "app/le",
|
||||||
|
}
|
||||||
|
ValidateServer(&config, validator)
|
||||||
|
assert.Len(t, validator.Errors(), 1)
|
||||||
|
assert.Error(t, validator.Errors()[0], "server path must not contain any forward slashes")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StripPathMiddleware strips the first level of a path.
|
||||||
|
func StripPathMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
|
uri := ctx.Request.RequestURI()
|
||||||
|
n := bytes.IndexByte(uri[1:], '/')
|
||||||
|
|
||||||
|
if n >= 0 {
|
||||||
|
uri = uri[n+1:]
|
||||||
|
ctx.Request.SetRequestURI(string(uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
next(ctx)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,8 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ var alphaNumericRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV
|
||||||
// ServeIndex serve the index.html file with nonce generated for supporting
|
// ServeIndex serve the index.html file with nonce generated for supporting
|
||||||
// restrictive CSP while using material-ui from the embedded virtual filesystem.
|
// restrictive CSP while using material-ui from the embedded virtual filesystem.
|
||||||
//go:generate broccoli -src ../../public_html -o public_html
|
//go:generate broccoli -src ../../public_html -o public_html
|
||||||
func ServeIndex(publicDir string) fasthttp.RequestHandler {
|
func ServeIndex(publicDir, base string) fasthttp.RequestHandler {
|
||||||
f, err := br.Open(publicDir + "/index.html")
|
f, err := br.Open(publicDir + "/index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Logger().Fatalf("Unable to open index.html: %v", err)
|
logging.Logger().Fatalf("Unable to open index.html: %v", err)
|
||||||
|
@ -36,9 +36,9 @@ func ServeIndex(publicDir string) fasthttp.RequestHandler {
|
||||||
nonce := utils.RandomString(32, alphaNumericRunes)
|
nonce := utils.RandomString(32, alphaNumericRunes)
|
||||||
|
|
||||||
ctx.SetContentType("text/html; charset=utf-8")
|
ctx.SetContentType("text/html; charset=utf-8")
|
||||||
ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; style-src 'self' 'nonce-%s'", nonce))
|
ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; object-src 'none'; require-trusted-types-for 'script'; style-src 'self' 'nonce-%s'", nonce))
|
||||||
|
|
||||||
err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ CSPNonce string }{CSPNonce: nonce})
|
err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ CSPNonce, Base string }{CSPNonce: nonce, Base: base})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error("An error occurred", 503)
|
ctx.Error("An error occurred", 503)
|
||||||
logging.Logger().Errorf("Unable to execute template: %v", err)
|
logging.Logger().Errorf("Unable to execute template: %v", err)
|
||||||
|
|
|
@ -23,74 +23,74 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
|
||||||
autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers)
|
autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers)
|
||||||
embeddedAssets := "/public_html"
|
embeddedAssets := "/public_html"
|
||||||
rootFiles := []string{"favicon.ico", "manifest.json", "robots.txt"}
|
rootFiles := []string{"favicon.ico", "manifest.json", "robots.txt"}
|
||||||
|
|
||||||
// TODO: Remove in v4.18.0.
|
// TODO: Remove in v4.18.0.
|
||||||
if os.Getenv("PUBLIC_DIR") != "" {
|
if os.Getenv("PUBLIC_DIR") != "" {
|
||||||
logging.Logger().Warn("PUBLIC_DIR environment variable has been deprecated, assets are now embedded.")
|
logging.Logger().Warn("PUBLIC_DIR environment variable has been deprecated, assets are now embedded.")
|
||||||
}
|
}
|
||||||
|
|
||||||
router := router.New()
|
r := router.New()
|
||||||
|
r.GET("/", ServeIndex(embeddedAssets, configuration.Server.Path))
|
||||||
router.GET("/", ServeIndex(embeddedAssets))
|
|
||||||
|
|
||||||
for _, f := range rootFiles {
|
for _, f := range rootFiles {
|
||||||
router.GET("/"+f, fasthttpadaptor.NewFastHTTPHandler(br.Serve(embeddedAssets)))
|
r.GET("/"+f, fasthttpadaptor.NewFastHTTPHandler(br.Serve(embeddedAssets)))
|
||||||
}
|
}
|
||||||
|
|
||||||
router.GET("/static/{filepath:*}", fasthttpadaptor.NewFastHTTPHandler(br.Serve(embeddedAssets)))
|
r.GET("/static/{filepath:*}", fasthttpadaptor.NewFastHTTPHandler(br.Serve(embeddedAssets)))
|
||||||
|
|
||||||
router.GET("/api/state", autheliaMiddleware(handlers.StateGet))
|
r.GET("/api/state", autheliaMiddleware(handlers.StateGet))
|
||||||
|
|
||||||
router.GET("/api/configuration", autheliaMiddleware(handlers.ConfigurationGet))
|
r.GET("/api/configuration", autheliaMiddleware(handlers.ConfigurationGet))
|
||||||
router.GET("/api/configuration/extended", autheliaMiddleware(
|
r.GET("/api/configuration/extended", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.ExtendedConfigurationGet)))
|
middlewares.RequireFirstFactor(handlers.ExtendedConfigurationGet)))
|
||||||
|
|
||||||
router.GET("/api/verify", autheliaMiddleware(handlers.VerifyGet(configuration.AuthenticationBackend)))
|
r.GET("/api/verify", autheliaMiddleware(handlers.VerifyGet(configuration.AuthenticationBackend)))
|
||||||
router.HEAD("/api/verify", autheliaMiddleware(handlers.VerifyGet(configuration.AuthenticationBackend)))
|
r.HEAD("/api/verify", autheliaMiddleware(handlers.VerifyGet(configuration.AuthenticationBackend)))
|
||||||
|
|
||||||
router.POST("/api/firstfactor", autheliaMiddleware(handlers.FirstFactorPost(1000, true)))
|
r.POST("/api/firstfactor", autheliaMiddleware(handlers.FirstFactorPost(1000, true)))
|
||||||
router.POST("/api/logout", autheliaMiddleware(handlers.LogoutPost))
|
r.POST("/api/logout", autheliaMiddleware(handlers.LogoutPost))
|
||||||
|
|
||||||
// Only register endpoints if forgot password is not disabled.
|
// Only register endpoints if forgot password is not disabled.
|
||||||
if !configuration.AuthenticationBackend.DisableResetPassword {
|
if !configuration.AuthenticationBackend.DisableResetPassword {
|
||||||
// Password reset related endpoints.
|
// Password reset related endpoints.
|
||||||
router.POST("/api/reset-password/identity/start", autheliaMiddleware(
|
r.POST("/api/reset-password/identity/start", autheliaMiddleware(
|
||||||
handlers.ResetPasswordIdentityStart))
|
handlers.ResetPasswordIdentityStart))
|
||||||
router.POST("/api/reset-password/identity/finish", autheliaMiddleware(
|
r.POST("/api/reset-password/identity/finish", autheliaMiddleware(
|
||||||
handlers.ResetPasswordIdentityFinish))
|
handlers.ResetPasswordIdentityFinish))
|
||||||
router.POST("/api/reset-password", autheliaMiddleware(
|
r.POST("/api/reset-password", autheliaMiddleware(
|
||||||
handlers.ResetPasswordPost))
|
handlers.ResetPasswordPost))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Information about the user.
|
// Information about the user.
|
||||||
router.GET("/api/user/info", autheliaMiddleware(
|
r.GET("/api/user/info", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.UserInfoGet)))
|
middlewares.RequireFirstFactor(handlers.UserInfoGet)))
|
||||||
router.POST("/api/user/info/2fa_method", autheliaMiddleware(
|
r.POST("/api/user/info/2fa_method", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.MethodPreferencePost)))
|
middlewares.RequireFirstFactor(handlers.MethodPreferencePost)))
|
||||||
|
|
||||||
// TOTP related endpoints.
|
// TOTP related endpoints.
|
||||||
router.POST("/api/secondfactor/totp/identity/start", autheliaMiddleware(
|
r.POST("/api/secondfactor/totp/identity/start", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorTOTPIdentityStart)))
|
middlewares.RequireFirstFactor(handlers.SecondFactorTOTPIdentityStart)))
|
||||||
router.POST("/api/secondfactor/totp/identity/finish", autheliaMiddleware(
|
r.POST("/api/secondfactor/totp/identity/finish", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorTOTPIdentityFinish)))
|
middlewares.RequireFirstFactor(handlers.SecondFactorTOTPIdentityFinish)))
|
||||||
router.POST("/api/secondfactor/totp", autheliaMiddleware(
|
r.POST("/api/secondfactor/totp", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorTOTPPost(&handlers.TOTPVerifierImpl{
|
middlewares.RequireFirstFactor(handlers.SecondFactorTOTPPost(&handlers.TOTPVerifierImpl{
|
||||||
Period: uint(configuration.TOTP.Period),
|
Period: uint(configuration.TOTP.Period),
|
||||||
Skew: uint(*configuration.TOTP.Skew),
|
Skew: uint(*configuration.TOTP.Skew),
|
||||||
}))))
|
}))))
|
||||||
|
|
||||||
// U2F related endpoints.
|
// U2F related endpoints.
|
||||||
router.POST("/api/secondfactor/u2f/identity/start", autheliaMiddleware(
|
r.POST("/api/secondfactor/u2f/identity/start", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorU2FIdentityStart)))
|
middlewares.RequireFirstFactor(handlers.SecondFactorU2FIdentityStart)))
|
||||||
router.POST("/api/secondfactor/u2f/identity/finish", autheliaMiddleware(
|
r.POST("/api/secondfactor/u2f/identity/finish", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorU2FIdentityFinish)))
|
middlewares.RequireFirstFactor(handlers.SecondFactorU2FIdentityFinish)))
|
||||||
|
|
||||||
router.POST("/api/secondfactor/u2f/register", autheliaMiddleware(
|
r.POST("/api/secondfactor/u2f/register", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorU2FRegister)))
|
middlewares.RequireFirstFactor(handlers.SecondFactorU2FRegister)))
|
||||||
|
|
||||||
router.POST("/api/secondfactor/u2f/sign_request", autheliaMiddleware(
|
r.POST("/api/secondfactor/u2f/sign_request", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorU2FSignGet)))
|
middlewares.RequireFirstFactor(handlers.SecondFactorU2FSignGet)))
|
||||||
|
|
||||||
router.POST("/api/secondfactor/u2f/sign", autheliaMiddleware(
|
r.POST("/api/secondfactor/u2f/sign", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorU2FSignPost(&handlers.U2FVerifierImpl{}))))
|
middlewares.RequireFirstFactor(handlers.SecondFactorU2FSignPost(&handlers.U2FVerifierImpl{}))))
|
||||||
|
|
||||||
// Configure DUO api endpoint only if configuration exists.
|
// Configure DUO api endpoint only if configuration exists.
|
||||||
|
@ -108,21 +108,26 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
|
||||||
configuration.DuoAPI.Hostname, ""))
|
configuration.DuoAPI.Hostname, ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
router.POST("/api/secondfactor/duo", autheliaMiddleware(
|
r.POST("/api/secondfactor/duo", autheliaMiddleware(
|
||||||
middlewares.RequireFirstFactor(handlers.SecondFactorDuoPost(duoAPI))))
|
middlewares.RequireFirstFactor(handlers.SecondFactorDuoPost(duoAPI))))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If trace is set, enable pprofhandler and expvarhandler.
|
// If trace is set, enable pprofhandler and expvarhandler.
|
||||||
if configuration.LogLevel == "trace" {
|
if configuration.LogLevel == "trace" {
|
||||||
router.GET("/debug/pprof/{name?}", pprofhandler.PprofHandler)
|
r.GET("/debug/pprof/{name?}", pprofhandler.PprofHandler)
|
||||||
router.GET("/debug/vars", expvarhandler.ExpvarHandler)
|
r.GET("/debug/vars", expvarhandler.ExpvarHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
router.NotFound = ServeIndex(embeddedAssets)
|
r.NotFound = ServeIndex(embeddedAssets, configuration.Server.Path)
|
||||||
|
|
||||||
|
handler := middlewares.LogRequestMiddleware(r.Handler)
|
||||||
|
if configuration.Server.Path != "" {
|
||||||
|
handler = middlewares.StripPathMiddleware(handler)
|
||||||
|
}
|
||||||
|
|
||||||
server := &fasthttp.Server{
|
server := &fasthttp.Server{
|
||||||
ErrorHandler: autheliaErrorHandler,
|
ErrorHandler: autheliaErrorHandler,
|
||||||
Handler: middlewares.LogRequestMiddleware(router.Handler),
|
Handler: handler,
|
||||||
NoDefaultServerHeader: true,
|
NoDefaultServerHeader: true,
|
||||||
ReadBufferSize: configuration.Server.ReadBufferSize,
|
ReadBufferSize: configuration.Server.ReadBufferSize,
|
||||||
WriteBufferSize: configuration.Server.WriteBufferSize,
|
WriteBufferSize: configuration.Server.WriteBufferSize,
|
||||||
|
|
|
@ -3,8 +3,20 @@ package utils
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsStringAlphaNumeric returns false if any rune in the string is not alpha-numeric.
|
||||||
|
func IsStringAlphaNumeric(input string) bool {
|
||||||
|
for _, r := range input {
|
||||||
|
if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// IsStringInSlice checks if a single string is in an array of strings.
|
// IsStringInSlice checks if a single string is in an array of strings.
|
||||||
func IsStringInSlice(a string, list []string) (inSlice bool) {
|
func IsStringInSlice(a string, list []string) (inSlice bool) {
|
||||||
for _, b := range list {
|
for _, b := range list {
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
|
|
||||||
HOST=authelia-frontend
|
HOST=authelia-frontend
|
||||||
|
PUBLIC_URL=""
|
|
@ -0,0 +1 @@
|
||||||
|
PUBLIC_URL={{.Base}}
|
|
@ -1,21 +1,18 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<meta property="csp-nonce" content="{{.CSPNonce}}" />
|
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta property="csp-nonce" content="{{.CSPNonce}}" />
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta name="description" content="Authelia login portal for your apps" />
|
||||||
name="description"
|
|
||||||
content="Authelia login portal for your apps"
|
|
||||||
/>
|
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
@ -26,8 +23,9 @@
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>Login - Authelia</title>
|
<title>Login - Authelia</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
|
<body data-basepath="%PUBLIC_URL%">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
<!--
|
||||||
|
@ -40,5 +38,6 @@
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
-->
|
-->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -19,7 +19,8 @@ import NotificationBar from './components/NotificationBar';
|
||||||
import SignOut from './views/LoginPortal/SignOut/SignOut';
|
import SignOut from './views/LoginPortal/SignOut/SignOut';
|
||||||
import { useConfiguration } from './hooks/Configuration';
|
import { useConfiguration } from './hooks/Configuration';
|
||||||
import '@fortawesome/fontawesome-svg-core/styles.css'
|
import '@fortawesome/fontawesome-svg-core/styles.css'
|
||||||
import {config as faConfig} from '@fortawesome/fontawesome-svg-core';
|
import { config as faConfig } from '@fortawesome/fontawesome-svg-core';
|
||||||
|
import { useBasePath } from './hooks/BasePath';
|
||||||
|
|
||||||
faConfig.autoAddCss = false;
|
faConfig.autoAddCss = false;
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ const App: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationsContext.Provider value={{ notification, setNotification }} >
|
<NotificationsContext.Provider value={{ notification, setNotification }} >
|
||||||
<Router>
|
<Router basename={useBasePath()}>
|
||||||
<NotificationBar onClose={() => setNotification(null)} />
|
<NotificationBar onClose={() => setNotification(null)} />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={ResetPasswordStep1Route} exact>
|
<Route path={ResetPasswordStep1Route} exact>
|
||||||
|
@ -61,7 +62,7 @@ const App: React.FC = () => {
|
||||||
resetPassword={configuration?.reset_password === true} />
|
resetPassword={configuration?.reset_password === true} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Redirect to={FirstFactorRoute}></Redirect>
|
<Redirect to={FirstFactorRoute} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export function useBasePath() {
|
||||||
|
const basePath = document.body.getAttribute("data-basepath");
|
||||||
|
if (basePath === null) {
|
||||||
|
throw new Error("No base path detected");
|
||||||
|
}
|
||||||
|
|
||||||
|
return basePath;
|
||||||
|
}
|
|
@ -1,32 +1,34 @@
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useBasePath } from "../hooks/BasePath";
|
||||||
|
|
||||||
export const FirstFactorPath = "/api/firstfactor";
|
const basePath = useBasePath();
|
||||||
export const InitiateTOTPRegistrationPath = "/api/secondfactor/totp/identity/start";
|
|
||||||
export const CompleteTOTPRegistrationPath = "/api/secondfactor/totp/identity/finish";
|
|
||||||
|
|
||||||
export const InitiateU2FRegistrationPath = "/api/secondfactor/u2f/identity/start";
|
export const FirstFactorPath = basePath + "/api/firstfactor";
|
||||||
export const CompleteU2FRegistrationStep1Path = "/api/secondfactor/u2f/identity/finish";
|
export const InitiateTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/start";
|
||||||
export const CompleteU2FRegistrationStep2Path = "/api/secondfactor/u2f/register";
|
export const CompleteTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/finish";
|
||||||
|
|
||||||
export const InitiateU2FSignInPath = "/api/secondfactor/u2f/sign_request";
|
export const InitiateU2FRegistrationPath = basePath + "/api/secondfactor/u2f/identity/start";
|
||||||
export const CompleteU2FSignInPath = "/api/secondfactor/u2f/sign";
|
export const CompleteU2FRegistrationStep1Path = basePath + "/api/secondfactor/u2f/identity/finish";
|
||||||
|
export const CompleteU2FRegistrationStep2Path = basePath + "/api/secondfactor/u2f/register";
|
||||||
|
|
||||||
export const CompletePushNotificationSignInPath = "/api/secondfactor/duo"
|
export const InitiateU2FSignInPath = basePath + "/api/secondfactor/u2f/sign_request";
|
||||||
export const CompleteTOTPSignInPath = "/api/secondfactor/totp"
|
export const CompleteU2FSignInPath = basePath + "/api/secondfactor/u2f/sign";
|
||||||
|
|
||||||
export const InitiateResetPasswordPath = "/api/reset-password/identity/start";
|
export const CompletePushNotificationSignInPath = basePath + "/api/secondfactor/duo"
|
||||||
export const CompleteResetPasswordPath = "/api/reset-password/identity/finish";
|
export const CompleteTOTPSignInPath = basePath + "/api/secondfactor/totp"
|
||||||
|
|
||||||
|
export const InitiateResetPasswordPath = basePath + "/api/reset-password/identity/start";
|
||||||
|
export const CompleteResetPasswordPath = basePath + "/api/reset-password/identity/finish";
|
||||||
// Do the password reset during completion.
|
// Do the password reset during completion.
|
||||||
export const ResetPasswordPath = "/api/reset-password"
|
export const ResetPasswordPath = basePath + "/api/reset-password"
|
||||||
|
|
||||||
export const LogoutPath = "/api/logout";
|
export const LogoutPath = basePath + "/api/logout";
|
||||||
export const StatePath = "/api/state";
|
export const StatePath = basePath + "/api/state";
|
||||||
export const UserInfoPath = "/api/user/info";
|
export const UserInfoPath = basePath + "/api/user/info";
|
||||||
export const UserInfo2FAMethodPath = "/api/user/info/2fa_method";
|
export const UserInfo2FAMethodPath = basePath + "/api/user/info/2fa_method";
|
||||||
export const Available2FAMethodsPath = "/api/secondfactor/available";
|
|
||||||
|
|
||||||
export const ConfigurationPath = "/api/configuration";
|
export const ConfigurationPath = basePath + "/api/configuration";
|
||||||
export const ExtendedConfigurationPath = "/api/configuration/extended";
|
export const ExtendedConfigurationPath = basePath + "/api/configuration/extended";
|
||||||
|
|
||||||
export interface ErrorResponse {
|
export interface ErrorResponse {
|
||||||
status: "KO";
|
status: "KO";
|
||||||
|
|
|
@ -18,14 +18,14 @@ export async function Post<T>(path: string, body?: any) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function Get<T = undefined>(path: string) {
|
export async function Get<T = undefined>(path: string): Promise<T> {
|
||||||
const res = await axios.get<ServiceResponse<T>>(path);
|
const res = await axios.get<ServiceResponse<T>>(path);
|
||||||
|
|
||||||
if (res.status !== 200 || hasServiceError(res)) {
|
if (res.status !== 200 || hasServiceError(res)) {
|
||||||
throw new Error(`Failed GET from ${path}. Code: ${res.status}.`);
|
throw new Error(`Failed GET from ${path}. Code: ${res.status}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const d = toData(res);
|
const d = toData<T>(res);
|
||||||
if (!d) {
|
if (!d) {
|
||||||
throw new Error("unexpected type of response");
|
throw new Error("unexpected type of response");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,6 @@ export interface AutheliaState {
|
||||||
authentication_level: AuthenticationLevel
|
authentication_level: AuthenticationLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getState() {
|
export async function getState(): Promise<AutheliaState> {
|
||||||
return Get<AutheliaState>(StatePath);
|
return Get<AutheliaState>(StatePath);
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
import { configure } from 'enzyme';
|
import { configure } from 'enzyme';
|
||||||
import Adapter from 'enzyme-adapter-react-16';
|
import Adapter from 'enzyme-adapter-react-16';
|
||||||
|
document.body.setAttribute("data-basepath", "");
|
||||||
configure({ adapter: new Adapter() });
|
configure({ adapter: new Adapter() });
|
||||||
|
|
Loading…
Reference in New Issue