Merge remote-tracking branch 'origin/master' into feat-settings-ui

# Conflicts:
#	go.mod
#	web/package.json
#	web/pnpm-lock.yaml
feat-otp-verification
James Elliott 2023-02-25 13:41:48 +11:00
commit e6ef74fd8e
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
9 changed files with 368 additions and 254 deletions

View File

@ -68,7 +68,7 @@ services:
volumes:
- ${PWD}/data/nginx-proxy-manager/data:/data
- ${PWD}/data/nginx-proxy-manager/letsencrypt:/etc/letsencrypt
- ${PWD}/data/nginx/snippets:/config/nginx/snippets:ro
- ${PWD}/data/nginx/snippets:/snippets:ro
environment:
TZ: 'Australia/Melbourne'
authelia:

View File

@ -42,22 +42,67 @@ bootstrapping *Authelia*.
### SWAG Caveat
One current caveat of the [SWAG] implementation is that it serves Authelia as a subpath for each domain. We
*__strongly recommend__* instead of using the out of the box method and guide for [SWAG] that you follow the
[NGINX](nginx.md) guide (which *can be used* with [SWAG]) and run Authelia as it's own subdomain.
One current caveat of the [SWAG] implementation is that it serves Authelia as a subpath for each domain by default. We
*__strongly recommend__* instead of using the defaults that you configure Authelia as a subdomain if possible.
This is partly because Webauthn requires that the domain is an exact match when registering and authenticating and it is
There are two potential ways to achieve this:
1. Adjust the default `authelia-server.conf` as per the included directions.
2. Use the supplementary configuration snippets provided officially by Authelia.
This is partly because WebAuthn requires that the domain is an exact match when registering and authenticating and it is
possible that due to web standards this will never change.
In addition this represents a bad user experience in some instances such as:
- Users sometimes visit the `https://app.example.com/authelia` URL which doesn't automatically redirect the user to
`https://app.example.com` (if they visit `https://app.example.com` then they'll be redirected to authenticate then
redirected back to their original URL).
- Administrators may wish to setup OpenID Connect 1.0 in which case it also doesn't represent a good user experience.
- Users sometimes visit the `https://app.example.com/authelia` URL which doesn't automatically redirect the user to
`https://app.example.com` (if they visit `https://app.example.com` then they'll be redirected to authenticate then
redirected back to their original URL).
- Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/open-id-connect.md) in
which case it also doesn't represent a good user experience as the `issuer` will be
`https://app.example.com/authelia` for example
- Using the [SWAG] default configurations are more difficult to support as our specific familiarity is with our own
example snippets
Taking these factors into consideration we're adapting our [SWAG] guide to use what we consider best for the users and
most easily supported. Users who wish to use the [SWAG] guide are free to do so but may not receive the same support.
#### Option 1: Adjusting the Default Configuration
Open the generated `authelia-server.conf`. Adjust the following sections. There are two snippets, one before and one
after. The only lines that change are the `set $authelia_backend` lines, and this configuration assumes you're
serving Authelia at `auth.example.com`.
```nginx
## Set $authelia_backend to route requests to the current domain by default
set $authelia_backend $http_host;
## In order for Webauthn to work with multiple domains authelia must operate on a separate subdomain
## To use authelia on a separate subdomain:
## * comment the $authelia_backend line above
## * rename /config/nginx/proxy-confs/authelia.conf.sample to /config/nginx/proxy-confs/authelia.conf
## * make sure that your dns has a cname set for authelia
## * uncomment the $authelia_backend line below and change example.com to your domain
## * restart the swag container
#set $authelia_backend authelia.example.com;
return 302 https://$authelia_backend/authelia/?rd=$target_url;
```
```nginx
## Set $authelia_backend to route requests to the current domain by default
# set $authelia_backend $http_host;
## In order for Webauthn to work with multiple domains authelia must operate on a separate subdomain
## To use authelia on a separate subdomain:
## * comment the $authelia_backend line above
## * rename /config/nginx/proxy-confs/authelia.conf.sample to /config/nginx/proxy-confs/authelia.conf
## * make sure that your dns has a cname set for authelia
## * uncomment the $authelia_backend line below and change example.com to your domain
## * restart the swag container
set $authelia_backend auth.example.com;
return 302 https://$authelia_backend/authelia/?rd=$target_url;
```
#### Option 2: Using the Authelia Supplementary Configuration Snippets
See standard [NGINX](nginx.md) guide (which *can be used* with [SWAG]) and run Authelia as it's own subdomain.
## Trusted Proxies
@ -102,6 +147,8 @@ services:
- '443:443'
volumes:
- ${PWD}/data/swag:/config
#- ${PWD}/data/nginx/snippets:/snippets:ro
## Uncomment the above line if you want to use the Authelia configuration snippets.
environment:
PUID: '1000'
PGID: '1000'

4
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/go-asn1-ber/asn1-ber v1.5.4
github.com/go-crypt/crypt v0.2.6
github.com/go-ldap/ldap/v3 v3.4.4
github.com/go-rod/rod v0.112.5
github.com/go-rod/rod v0.112.6
github.com/go-sql-driver/mysql v1.7.0
github.com/go-webauthn/webauthn v0.8.2
github.com/golang-jwt/jwt/v4 v4.5.0
@ -33,7 +33,7 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/ory/fosite v0.44.0
github.com/ory/herodot v0.9.13
github.com/ory/x v0.0.537
github.com/ory/x v0.0.541
github.com/otiai10/copy v1.9.0
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.4.0

8
go.sum
View File

@ -160,8 +160,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-rod/rod v0.112.5 h1:2mH97UK8We4D2MfX388WqPjG1lDbxx8lLi5MzfvnEo0=
github.com/go-rod/rod v0.112.5/go.mod h1:ElViL9ABbcshNQw93+11FrYRH92RRhMKleuILo6+5V0=
github.com/go-rod/rod v0.112.6 h1:zMirUmhsBeshMWyf285BD0UGtGq54HfThLDGSjcP3lU=
github.com/go-rod/rod v0.112.6/go.mod h1:ElViL9ABbcshNQw93+11FrYRH92RRhMKleuILo6+5V0=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
@ -387,8 +387,8 @@ github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8
github.com/ory/herodot v0.9.13 h1:cN/Z4eOkErl/9W7hDIDLb79IO/bfsH+8yscBjRpB4IU=
github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo=
github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=
github.com/ory/x v0.0.537 h1:FB8Tioza6pihvy/RsVNzX08Qg3/VpIhI9vBnEQ4iFmQ=
github.com/ory/x v0.0.537/go.mod h1:CQopDsCC9t0tQsddE9UlyRFVEFd2xjKBVcw4nLMMMS0=
github.com/ory/x v0.0.541 h1:rp8AD7X5/WiZIJws5kBwFgXoSeunxNMD54QV58+pcew=
github.com/ory/x v0.0.541/go.mod h1:ktXUvx51Ok1gMGr3ysvktanqr+eiB4FXglt4nF4w2Uo=
github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4=
github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=

View File

@ -777,3 +777,12 @@ Layouts:
const (
fmtLogServerListening = "Listening for %s connections on '%s' path '%s'"
)
const (
logFieldService = "service"
logFieldFile = "file"
logFieldOP = "op"
serviceTypeServer = "server"
serviceTypeWatcher = "watcher"
)

View File

@ -10,6 +10,7 @@ import (
"strings"
"sync"
"syscall"
"time"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
@ -23,11 +24,12 @@ import (
// NewServerService creates a new ServerService with the appropriate logger etc.
func NewServerService(name string, server *fasthttp.Server, listener net.Listener, paths []string, isTLS bool, log *logrus.Logger) (service *ServerService) {
return &ServerService{
name: name,
server: server,
listener: listener,
paths: paths,
isTLS: isTLS,
log: log.WithFields(map[string]any{"service": "server", "server": name}),
log: log.WithFields(map[string]any{logFieldService: serviceTypeServer, serviceTypeServer: name}),
}
}
@ -53,10 +55,11 @@ func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus
return nil, err
}
entry := log.WithFields(map[string]any{"service": "watcher", "watcher": name})
entry := log.WithFields(map[string]any{logFieldService: serviceTypeWatcher, serviceTypeWatcher: name})
if info.IsDir() {
service = &FileWatcherService{
name: name,
watcher: watcher,
reload: reload,
log: entry,
@ -64,6 +67,7 @@ func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus
}
} else {
service = &FileWatcherService{
name: name,
watcher: watcher,
reload: reload,
log: entry,
@ -86,12 +90,25 @@ type ProviderReload interface {
// Service represents the required methods to support handling a service.
type Service interface {
// ServiceType returns the type name for the Service.
ServiceType() string
// ServiceName returns the individual name for the Service.
ServiceName() string
// Run performs the running operations for the Service.
Run() (err error)
// Shutdown perform the shutdown cleanup and termination operations for the Service.
Shutdown()
// Log returns the logger configured for the service.
Log() *logrus.Entry
}
// ServerService is a Service which runs a webserver.
type ServerService struct {
name string
server *fasthttp.Server
paths []string
isTLS bool
@ -99,6 +116,16 @@ type ServerService struct {
log *logrus.Entry
}
// ServiceType returns the service type for this service, which is always 'server'.
func (service *ServerService) ServiceType() string {
return serviceTypeServer
}
// ServiceName returns the individual name for this service.
func (service *ServerService) ServiceName() string {
return service.name
}
// Run the ServerService.
func (service *ServerService) Run() (err error) {
defer func() {
@ -120,13 +147,24 @@ func (service *ServerService) Run() (err error) {
// Shutdown the ServerService.
func (service *ServerService) Shutdown() {
if err := service.server.Shutdown(); err != nil {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := service.server.ShutdownWithContext(ctx); err != nil {
service.log.WithError(err).Error("Error occurred during shutdown")
}
}
// Log returns the *logrus.Entry of the ServerService.
func (service *ServerService) Log() *logrus.Entry {
return service.log
}
// FileWatcherService is a Service that watches files for changes.
type FileWatcherService struct {
name string
watcher *fsnotify.Watcher
reload ProviderReload
@ -135,6 +173,16 @@ type FileWatcherService struct {
directory string
}
// ServiceType returns the service type for this service, which is always 'watcher'.
func (service *FileWatcherService) ServiceType() string {
return serviceTypeWatcher
}
// ServiceName returns the individual name for this service.
func (service *FileWatcherService) ServiceName() string {
return service.name
}
// Run the FileWatcherService.
func (service *FileWatcherService) Run() (err error) {
defer func() {
@ -143,7 +191,7 @@ func (service *FileWatcherService) Run() (err error) {
}
}()
service.log.WithField("file", filepath.Join(service.directory, service.file)).Info("Watching for file changes to the file")
service.log.WithField(logFieldFile, filepath.Join(service.directory, service.file)).Info("Watching file for changes")
for {
select {
@ -152,34 +200,36 @@ func (service *FileWatcherService) Run() (err error) {
return nil
}
log := service.log.WithFields(map[string]any{logFieldFile: event.Name, logFieldOP: event.Op})
if service.file != "" && service.file != filepath.Base(event.Name) {
service.log.WithFields(map[string]any{"file": event.Name, "op": event.Op}).Tracef("File modification detected to irrelevant file")
log.Trace("File modification detected to irrelevant file")
break
}
switch {
case event.Op&fsnotify.Write == fsnotify.Write, event.Op&fsnotify.Create == fsnotify.Create:
service.log.WithFields(map[string]any{"file": event.Name, "op": event.Op}).Debug("File modification was detected")
log.Debug("File modification was detected")
var reloaded bool
switch reloaded, err = service.reload.Reload(); {
case err != nil:
service.log.WithFields(map[string]any{"file": event.Name, "op": event.Op}).WithError(err).Error("Error occurred during reload")
log.WithError(err).Error("Error occurred during reload")
case reloaded:
service.log.WithField("file", event.Name).Info("Reloaded successfully")
log.Info("Reloaded successfully")
default:
service.log.WithField("file", event.Name).Debug("Reload of was triggered but it was skipped")
log.Debug("Reload was triggered but it was skipped")
}
case event.Op&fsnotify.Remove == fsnotify.Remove:
service.log.WithFields(map[string]any{"file": event.Name, "op": event.Op}).Debug("File remove was detected")
log.Debug("File remove was detected")
}
case err, ok := <-service.watcher.Errors:
if !ok {
return nil
}
service.log.WithError(err).Errorf("Error while watching files")
service.log.WithError(err).Error("Error while watching file for changes")
}
}
}
@ -191,6 +241,11 @@ func (service *FileWatcherService) Shutdown() {
}
}
// Log returns the *logrus.Entry of the FileWatcherService.
func (service *FileWatcherService) Log() *logrus.Entry {
return service.log
}
func svcSvrMainFunc(ctx *CmdCtx) (service Service) {
switch svr, listener, paths, isTLS, err := server.CreateDefaultServer(ctx.config, ctx.providers); {
case err != nil:
@ -267,34 +322,35 @@ func servicesRun(ctx *CmdCtx) {
}
}
ctx.log.Info("Startup Complete")
ctx.log.Info("Startup complete")
select {
case s := <-quit:
switch s {
case syscall.SIGINT:
ctx.log.WithField("signal", "SIGINT").Debugf("Shutdown started due to signal")
case syscall.SIGTERM:
ctx.log.WithField("signal", "SIGTERM").Debugf("Shutdown started due to signal")
}
ctx.log.WithField("signal", s.String()).Debug("Shutdown initiated due to process signal")
case <-cctx.Done():
ctx.log.Debugf("Shutdown started due to context completion")
ctx.log.Debug("Shutdown initiated due to context completion")
}
cancel()
ctx.log.Infof("Shutting down")
ctx.log.Info("Shutdown initiated")
wgShutdown := &sync.WaitGroup{}
ctx.log.Tracef("Shutdown of %d services is required", len(services))
for _, service := range services {
go func() {
wgShutdown.Add(1)
go func(service Service) {
service.Log().Trace("Shutdown of service initiated")
service.Shutdown()
wgShutdown.Done()
}()
wgShutdown.Add(1)
service.Log().Trace("Shutdown of service complete")
}(service)
}
wgShutdown.Wait()
@ -306,6 +362,8 @@ func servicesRun(ctx *CmdCtx) {
}
if err = group.Wait(); err != nil {
ctx.log.WithError(err).Errorf("Error occurred waiting for shutdown")
ctx.log.WithError(err).Error("Error occurred waiting for shutdown")
}
ctx.log.Info("Shutdown complete")
}

View File

@ -42,7 +42,7 @@ func waitUntilAutheliaBackendIsReady(dockerEnvironment *DockerEnvironment) error
90*time.Second,
dockerEnvironment,
"authelia-backend",
[]string{"Startup Complete"})
[]string{"Startup complete"})
}
func waitUntilAutheliaFrontendIsReady(dockerEnvironment *DockerEnvironment) error {

View File

@ -30,11 +30,11 @@
"@fortawesome/free-solid-svg-icons": "6.3.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@mui/icons-material": "5.11.9",
"@mui/material": "5.11.9",
"@mui/material": "5.11.10",
"@mui/styles": "5.11.9",
"@simplewebauthn/browser": "7.1.0",
"@simplewebauthn/typescript-types": "7.0.0",
"axios": "1.3.3",
"axios": "1.3.4",
"broadcast-channel": "4.20.2",
"classnames": "2.3.2",
"i18next": "22.4.10",
@ -43,7 +43,7 @@
"qrcode.react": "3.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-i18next": "12.1.5",
"react-i18next": "12.2.0",
"react-loading": "2.0.3",
"react-router-dom": "6.8.1",
"react18-input-otp": "1.1.2",
@ -154,15 +154,15 @@
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "14.0.0",
"@types/jest": "29.4.0",
"@types/node": "18.14.0",
"@types/node": "18.14.1",
"@types/qrcode.react": "1.0.2",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.52.0",
"@typescript-eslint/parser": "5.52.0",
"@typescript-eslint/eslint-plugin": "5.53.0",
"@typescript-eslint/parser": "5.53.0",
"@vitejs/plugin-react": "3.1.0",
"esbuild": "0.17.8",
"esbuild": "0.17.10",
"esbuild-jest": "0.5.0",
"eslint": "8.34.0",
"eslint-config-prettier": "8.6.0",
@ -182,7 +182,7 @@
"prettier": "2.8.4",
"react-test-renderer": "18.2.0",
"typescript": "4.9.5",
"vite": "4.1.2",
"vite": "4.1.4",
"vite-plugin-eslint": "1.8.1",
"vite-plugin-istanbul": "4.0.0",
"vite-plugin-svgr": "2.4.0",

File diff suppressed because it is too large Load Diff