Fix and parallelize integration tests.

pull/474/head
Clement Michaud 2019-11-30 17:49:52 +01:00 committed by Clément Michaud
parent be802cfc7b
commit b89f63e9c1
88 changed files with 1047 additions and 500 deletions

View File

@ -3,7 +3,7 @@ language: go
required: sudo required: sudo
go: go:
- '1.13' - "1.13"
services: services:
- docker - docker
@ -19,25 +19,73 @@ addons:
- libgif-dev - libgif-dev
- google-chrome-stable - google-chrome-stable
install: # Install ChromeDriver (64bits; replace 64 with 32 for 32bits). install:
- go mod download
before_script:
- export PATH=./cmd/authelia-scripts/:/tmp:$PATH
- source bootstrap.sh
jobs:
include:
- stage: build & test
before_script:
- curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
- nvm install v12 && nvm use v12
script:
- authelia-scripts --log-level debug ci
# Run all suites in a dedicated container
- &e2e-test
stage: end-to-end suite tests
env:
- SUITE_NAME=BypassAll
before_script:
# Install chrome driver
- wget -N https://chromedriver.storage.googleapis.com/78.0.3904.70/chromedriver_linux64.zip -P ~/ - wget -N https://chromedriver.storage.googleapis.com/78.0.3904.70/chromedriver_linux64.zip -P ~/
- unzip ~/chromedriver_linux64.zip -d ~/ - unzip ~/chromedriver_linux64.zip -d ~/
- rm ~/chromedriver_linux64.zip - rm ~/chromedriver_linux64.zip
- sudo mv -f ~/chromedriver /usr/local/share/ - sudo mv -f ~/chromedriver /usr/local/share/
- sudo chmod +x /usr/local/share/chromedriver - sudo chmod +x /usr/local/share/chromedriver
- sudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver - sudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver
before_script:
- export PATH=./cmd/authelia-scripts/:/tmp:$PATH
- curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
- nvm install v12 && nvm use v12 && npm i
- source bootstrap.sh
jobs:
include:
- stage: test
script: script:
- authelia-scripts --log-level debug ci # Run the suite
- CI=true authelia-scripts --log-level debug suites test $SUITE_NAME --headless
# TODO(c.michaud): check if all suites are listed based on `authelia-scripts suites list` command.
- <<: *e2e-test
env:
- SUITE_NAME=Docker
- <<: *e2e-test
env:
- SUITE_NAME=DuoPush
- <<: *e2e-test
env:
- SUITE_NAME=HighAvailability
- <<: *e2e-test
env:
- SUITE_NAME=Kubernetes
- <<: *e2e-test
env:
- SUITE_NAME=LDAP
- <<: *e2e-test
env:
- SUITE_NAME=Mariadb
- <<: *e2e-test
env:
- SUITE_NAME=NetworkACL
- <<: *e2e-test
env:
- SUITE_NAME=Postgres
- <<: *e2e-test
env:
- SUITE_NAME=ShortTimeouts
- <<: *e2e-test
env:
- SUITE_NAME=Standalone
- <<: *e2e-test
env:
- SUITE_NAME=Traefik
- &build-images - &build-images
stage: build images stage: build images
env: env:
@ -56,9 +104,9 @@ jobs:
- tar -czf authelia-linux-$ARCH.tar.gz authelia-linux-$ARCH public_html - tar -czf authelia-linux-$ARCH.tar.gz authelia-linux-$ARCH public_html
deploy: deploy:
provider: releases provider: releases
api_key: '$GITHUB_API_KEY' api_key: "$GITHUB_API_KEY"
file_glob: true file_glob: true
file: 'authelia-linux-$ARCH.tar.gz' file: "authelia-linux-$ARCH.tar.gz"
skip_cleanup: true skip_cleanup: true
on: on:
tags: true tags: true

View File

@ -7,7 +7,14 @@ FROM golang:1.13-alpine AS builder-backend
RUN apk --no-cache add gcc musl-dev RUN apk --no-cache add gcc musl-dev
WORKDIR /go/src/app WORKDIR /go/src/app
COPY . .
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download
COPY cmd cmd
COPY internal internal
# CGO_ENABLED=1 is mandatory for building go-sqlite3 # CGO_ENABLED=1 is mandatory for building go-sqlite3
RUN cd cmd/authelia && GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia RUN cd cmd/authelia && GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia
@ -16,10 +23,10 @@ RUN cd cmd/authelia && GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -tags netg
# ======================================== # ========================================
# ===== Build image for the frontend ===== # ===== Build image for the frontend =====
# ======================================== # ========================================
FROM node:11-alpine AS builder-frontend FROM node:12-alpine AS builder-frontend
WORKDIR /node/src/app WORKDIR /node/src/app
COPY client . COPY web .
# Install the dependencies and build # Install the dependencies and build
RUN npm ci && npm run build RUN npm ci && npm run build
@ -41,4 +48,4 @@ EXPOSE 9091
VOLUME /etc/authelia VOLUME /etc/authelia
VOLUME /var/lib/authelia VOLUME /var/lib/authelia
CMD ["./authelia", "-config", "/etc/authelia/config.yml"] CMD ["./authelia", "-config", "/etc/authelia/configuration.yml"]

View File

@ -8,7 +8,14 @@ COPY ./qemu-arm-static /usr/bin/qemu-arm-static
RUN apk --no-cache add gcc musl-dev RUN apk --no-cache add gcc musl-dev
WORKDIR /go/src/app WORKDIR /go/src/app
COPY . .
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download
COPY cmd cmd
COPY internal internal
# CGO_ENABLED=1 is mandatory for building go-sqlite3 # CGO_ENABLED=1 is mandatory for building go-sqlite3
RUN cd cmd/authelia && GOOS=linux GOARCH=arm CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia RUN cd cmd/authelia && GOOS=linux GOARCH=arm CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia
@ -17,10 +24,10 @@ RUN cd cmd/authelia && GOOS=linux GOARCH=arm CGO_ENABLED=1 go build -tags netgo
# ======================================== # ========================================
# ===== Build image for the frontend ===== # ===== Build image for the frontend =====
# ======================================== # ========================================
FROM node:11-alpine AS builder-frontend FROM node:12-alpine AS builder-frontend
WORKDIR /node/src/app WORKDIR /node/src/app
COPY client . COPY web .
# Install the dependencies and build # Install the dependencies and build
RUN npm ci && npm run build RUN npm ci && npm run build
@ -45,4 +52,4 @@ EXPOSE 9091
VOLUME /etc/authelia VOLUME /etc/authelia
VOLUME /var/lib/authelia VOLUME /var/lib/authelia
CMD ["./authelia", "-config", "/etc/authelia/config.yml"] CMD ["./authelia", "-config", "/etc/authelia/configuration.yml"]

View File

@ -8,7 +8,14 @@ COPY ./qemu-aarch64-static /usr/bin/qemu-aarch64-static
RUN apk --no-cache add gcc musl-dev RUN apk --no-cache add gcc musl-dev
WORKDIR /go/src/app WORKDIR /go/src/app
COPY . .
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download
COPY cmd cmd
COPY internal internal
# CGO_ENABLED=1 is mandatory for building go-sqlite3 # CGO_ENABLED=1 is mandatory for building go-sqlite3
RUN cd cmd/authelia && GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia RUN cd cmd/authelia && GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia
@ -17,10 +24,10 @@ RUN cd cmd/authelia && GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build -tags netg
# ======================================== # ========================================
# ===== Build image for the frontend ===== # ===== Build image for the frontend =====
# ======================================== # ========================================
FROM node:11-alpine AS builder-frontend FROM node:12-alpine AS builder-frontend
WORKDIR /node/src/app WORKDIR /node/src/app
COPY client . COPY web .
# Install the dependencies and build # Install the dependencies and build
RUN npm ci && npm run build RUN npm ci && npm run build
@ -45,4 +52,4 @@ EXPOSE 9091
VOLUME /etc/authelia VOLUME /etc/authelia
VOLUME /var/lib/authelia VOLUME /var/lib/authelia
CMD ["./authelia", "-config", "/etc/authelia/config.yml"] CMD ["./authelia", "-config", "/etc/authelia/configuration.yml"]

View File

@ -7,6 +7,9 @@ if [ -z "$OLD_PS1" ]; then
export PS1="(authelia) $PS1" export PS1="(authelia) $PS1"
fi fi
export USER_ID=$(id -u)
export GROUP_ID=$(id -g)
echo "[BOOTSTRAP] Checking if Go is installed..." echo "[BOOTSTRAP] Checking if Go is installed..."
if [ ! -x "$(command -v go)" ]; if [ ! -x "$(command -v go)" ];

View File

@ -96,15 +96,6 @@ func shell(cmd string) {
runCommand("bash", "-c", cmd) runCommand("bash", "-c", cmd)
} }
func buildHelperDockerImages() {
shell("docker build -t authelia-example-backend example/compose/nginx/backend")
shell("docker build -t authelia-duo-api example/compose/duo-api")
shell("docker-compose -f docker-compose.yml -f example/compose/kind/docker-compose.yml build")
shell("docker-compose -f docker-compose.yml -f example/compose/authelia/docker-compose.backend.yml build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g)")
shell("docker-compose -f docker-compose.yml -f example/compose/authelia/docker-compose.frontend.yml build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g)")
}
func prepareHostsFile() { func prepareHostsFile() {
contentBytes, err := readHostsFile() contentBytes, err := readHostsFile()
@ -209,9 +200,6 @@ func Bootstrap(cobraCmd *cobra.Command, args []string) {
log.Fatal("GOPATH is not set") log.Fatal("GOPATH is not set")
} }
bootstrapPrintln("Building development Docker images...")
buildHelperDockerImages()
createTemporaryDirectory() createTemporaryDirectory()
bootstrapPrintln("Preparing /etc/hosts to serve subdomains of example.com...") bootstrapPrintln("Preparing /etc/hosts to serve subdomains of example.com...")

View File

@ -24,7 +24,7 @@ func buildAutheliaBinary() {
func buildFrontend() { func buildFrontend() {
// Install npm dependencies // Install npm dependencies
cmd := utils.CommandWithStdout("npm", "ci") cmd := utils.CommandWithStdout("npm", "ci")
cmd.Dir = "client" cmd.Dir = "web"
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -32,13 +32,13 @@ func buildFrontend() {
// Then build the frontend // Then build the frontend
cmd = utils.CommandWithStdout("npm", "run", "build") cmd = utils.CommandWithStdout("npm", "run", "build")
cmd.Dir = "client" cmd.Dir = "web"
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := os.Rename("client/build", OutputDir+"/public_html"); err != nil { if err := os.Rename("web/build", OutputDir+"/public_html"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -18,18 +18,18 @@ const dockerPullCommandLine = "docker-compose -f docker-compose.yml " +
// RunCI run the CI scripts // RunCI run the CI scripts
func RunCI(cmd *cobra.Command, args []string) { func RunCI(cmd *cobra.Command, args []string) {
log.Info("=====> Build stage") log.Info("=====> Build stage <=====")
if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "build").Run(); err != nil { if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "build").Run(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Info("=====> Unit testing stage") log.Info("=====> Unit testing stage <=====")
if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "unittest").Run(); err != nil { if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "unittest").Run(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Info("=====> End-to-end testing stage") log.Info("=====> Build Docker stage <=====")
if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "suites", "test", "--headless", "--only-forbidden").Run(); err != nil { if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "docker", "build").Run(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -27,11 +27,9 @@ var ErrNoRunningSuite = errors.New("no running suite")
var runningSuiteFile = ".suite" var runningSuiteFile = ".suite"
var headless bool var headless bool
var onlyForbidden bool
func init() { func init() {
SuitesTestCmd.Flags().BoolVar(&headless, "headless", false, "Run tests in headless mode") SuitesTestCmd.Flags().BoolVar(&headless, "headless", false, "Run tests in headless mode")
SuitesTestCmd.Flags().BoolVar(&onlyForbidden, "only-forbidden", false, "Mocha 'only' filters are forbidden")
} }
// SuitesListCmd Command for listing the available suites // SuitesListCmd Command for listing the available suites
@ -79,17 +77,17 @@ var SuitesTeardownCmd = &cobra.Command{
runningSuite, err := getRunningSuite() runningSuite, err := getRunningSuite()
if err != nil { if err != nil {
panic(err) log.Fatal(err)
} }
if runningSuite == "" { if runningSuite == "" {
panic(ErrNoRunningSuite) log.Fatal(ErrNoRunningSuite)
} }
suiteName = runningSuite suiteName = runningSuite
} }
if err := teardownSuite(suiteName); err != nil { if err := teardownSuite(suiteName); err != nil {
panic(err) log.Fatal(err)
} }
}, },
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
@ -143,6 +141,14 @@ func runSuiteSetupTeardown(command string, suite string) error {
return utils.RunCommandWithTimeout(cmd, s.SetUpTimeout) return utils.RunCommandWithTimeout(cmd, s.SetUpTimeout)
} }
func runOnSetupTimeout(suite string) error {
cmd := utils.CommandWithStdout("go", "run", "cmd/authelia-suites/main.go", "timeout", suite)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
return utils.RunCommandWithTimeout(cmd, 15*time.Second)
}
func setupSuite(suiteName string) error { func setupSuite(suiteName string) error {
log.Infof("Setup environment for suite %s...", suiteName) log.Infof("Setup environment for suite %s...", suiteName)
signalChannel := make(chan os.Signal) signalChannel := make(chan os.Signal)
@ -156,10 +162,10 @@ func setupSuite(suiteName string) error {
}() }()
if errSetup := runSuiteSetupTeardown("setup", suiteName); errSetup != nil || interrupted { if errSetup := runSuiteSetupTeardown("setup", suiteName); errSetup != nil || interrupted {
err := teardownSuite(suiteName) if errSetup == utils.ErrTimeoutReached {
if err != nil { runOnSetupTimeout(suiteName)
log.Error(err)
} }
teardownSuite(suiteName)
return errSetup return errSetup
} }
@ -231,7 +237,7 @@ func runSuiteTests(suiteName string, withEnv bool) error {
if suite.TestTimeout > 0 { if suite.TestTimeout > 0 {
timeout = fmt.Sprintf("%ds", int64(suite.TestTimeout/time.Second)) timeout = fmt.Sprintf("%ds", int64(suite.TestTimeout/time.Second))
} }
testCmdLine := fmt.Sprintf("go test ./internal/suites -timeout %s -run '^(Test%sSuite)$'", timeout, suiteName) testCmdLine := fmt.Sprintf("go test -v ./internal/suites -timeout %s -run '^(Test%sSuite)$'", timeout, suiteName)
log.Infof("Running tests of suite %s...", suiteName) log.Infof("Running tests of suite %s...", suiteName)
log.Debugf("Running tests with command: %s", testCmdLine) log.Debugf("Running tests with command: %s", testCmdLine)

View File

@ -32,6 +32,12 @@ func main() {
Run: setupSuite, Run: setupSuite,
} }
setupTimeoutCmd := &cobra.Command{
Use: "timeout [suite]",
Short: "Run the OnSetupTimeout callback when setup times out",
Run: setupTimeoutSuite,
}
stopCmd := &cobra.Command{ stopCmd := &cobra.Command{
Use: "teardown [suite]", Use: "teardown [suite]",
Short: "Teardown the suite environment", Short: "Teardown the suite environment",
@ -39,6 +45,7 @@ func main() {
} }
rootCmd.AddCommand(startCmd) rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(setupTimeoutCmd)
rootCmd.AddCommand(stopCmd) rootCmd.AddCommand(stopCmd)
rootCmd.Execute() rootCmd.Execute()
} }
@ -101,6 +108,18 @@ func setupSuite(cmd *cobra.Command, args []string) {
log.Info("Environment is ready!") log.Info("Environment is ready!")
} }
func setupTimeoutSuite(cmd *cobra.Command, args []string) {
suiteName := args[0]
s := suites.GlobalRegistry.Get(suiteName)
if s.OnSetupTimeout == nil {
return
}
if err := s.OnSetupTimeout(); err != nil {
log.Fatal(err)
}
}
func teardownSuite(cmd *cobra.Command, args []string) { func teardownSuite(cmd *cobra.Command, args []string) {
if os.Getenv("SKIP_TEARDOWN") != "" { if os.Getenv("SKIP_TEARDOWN") != "" {
return return

View File

@ -99,7 +99,6 @@ authentication_backend:
## file: ## file:
## path: ./users_database.yml ## path: ./users_database.yml
# Access Control # Access Control
# #
# Access control is a list of rules defining the authorizations applied for one # Access control is a list of rules defining the authorizations applied for one
@ -156,47 +155,45 @@ access_control:
- domain: singlefactor.example.com - domain: singlefactor.example.com
policy: one_factor policy: one_factor
# Rules applied to 'admin' group # Rules applied to 'admins' group
- domain: 'mx2.mail.example.com' - domain: "mx2.mail.example.com"
subject: 'group:admin' subject: "groups:admins"
policy: deny policy: deny
- domain: '*.example.com' - domain: "*.example.com"
subject: 'group:admin' subject: "groups:admins"
policy: two_factor policy: two_factor
# Rules applied to 'dev' group # Rules applied to 'dev' group
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/groups/dev/.*$' - "^/groups/dev/.*$"
subject: 'group:dev' subject: "group:dev"
policy: two_factor policy: two_factor
# Rules applied to user 'john' # Rules applied to user 'john'
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/users/john/.*$' - "^/users/john/.*$"
subject: 'user:john' subject: "user:john"
policy: two_factor policy: two_factor
# Rules applied to user 'harry' # Rules applied to user 'harry'
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/users/harry/.*$' - "^/users/harry/.*$"
subject: 'user:harry' subject: "user:harry"
policy: two_factor policy: two_factor
# Rules applied to user 'bob' # Rules applied to user 'bob'
- domain: '*.mail.example.com' - domain: "*.mail.example.com"
subject: 'user:bob' subject: "user:bob"
policy: two_factor policy: two_factor
- domain: 'dev.example.com' - domain: "dev.example.com"
resources: resources:
- '^/users/bob/.*$' - "^/users/bob/.*$"
subject: 'user:bob' subject: "user:bob"
policy: two_factor policy: two_factor
# Configuration of session cookies # Configuration of session cookies
# #
# The session cookies identify the user once logged in. # The session cookies identify the user once logged in.
@ -283,7 +280,6 @@ notifier:
host: 127.0.0.1 host: 127.0.0.1
port: 1025 port: 1025
sender: admin@example.com sender: admin@example.com
# Sending an email using a Gmail account is as simple as the next section. # Sending an email using a Gmail account is as simple as the next section.
# You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en # You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
## smtp: ## smtp:

View File

@ -1,8 +1,10 @@
FROM golang:1.13-stretch FROM golang:1.13-alpine
RUN apk --no-cache add gcc musl-dev
ARG USER_ID ARG USER_ID
ARG GROUP_ID ARG GROUP_ID
RUN groupadd -g ${GROUP_ID} dev && \ RUN addgroup --gid ${GROUP_ID} dev && \
useradd -m -u $USER_ID -g $GROUP_ID dev adduser --uid ${USER_ID} -G dev -D dev
USER dev USER dev

View File

@ -1,9 +1,9 @@
FROM node:11-stretch-slim FROM node:12-alpine
ARG USER_ID ARG USER_ID
ARG GROUP_ID ARG GROUP_ID
RUN cat /etc/passwd && userdel -rf node && \ RUN deluser node && \
groupadd -g ${GROUP_ID} dev && \ addgroup --gid ${GROUP_ID} dev && \
useradd -m -u $USER_ID -g $GROUP_ID dev adduser --uid ${USER_ID} -G dev -D dev
USER dev USER dev

View File

@ -0,0 +1,14 @@
version: "3"
services:
authelia-backend:
build:
context: .
dockerfile: Dockerfile
volumes:
- "/tmp/authelia:/tmp/authelia"
environment:
- ENVIRONMENT=dev
restart: always
networks:
authelianet:
ipv4_address: 192.168.240.50

View File

@ -4,6 +4,9 @@ services:
build: build:
context: example/compose/authelia context: example/compose/authelia
dockerfile: Dockerfile.backend dockerfile: Dockerfile.backend
args:
USER_ID: ${USER_ID}
GROUP_ID: ${GROUP_ID}
command: /resources/entrypoint.sh command: /resources/entrypoint.sh
working_dir: /app working_dir: /app
volumes: volumes:

View File

@ -1,4 +1,4 @@
version: '3' version: "3"
services: services:
authelia-frontend: authelia-frontend:
image: nginx:alpine image: nginx:alpine

View File

@ -1,10 +1,13 @@
version: '3' version: "3"
services: services:
authelia-frontend: authelia-frontend:
build: build:
context: example/compose/authelia context: example/compose/authelia
dockerfile: Dockerfile.frontend dockerfile: Dockerfile.frontend
command: npm run start args:
USER_ID: ${USER_ID}
GROUP_ID: ${GROUP_ID}
command: sh -c 'npm ci && npm run start'
working_dir: /app working_dir: /app
volumes: volumes:
- "./web:/app" - "./web:/app"

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/bin/sh
set -x set -x

View File

@ -7,11 +7,11 @@ events {
http { http {
server { server {
listen 80; listen 3000;
location / { location / {
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_pass http://authelia-backend; proxy_pass http://authelia-backend:9091;
} }
} }
} }

View File

@ -1,30 +1,12 @@
#!/bin/bash #!/bin/sh
set -e set -e
# Retries a command on failure.
# $1 - the max number of attempts
# $2... - the command to run
retry() {
local -r -i max_attempts="$1"; shift
local -r cmd="$@"
local -i attempt_num=1
until $cmd
do
if ((attempt_num==max_attempts))
then
echo "Attempt $attempt_num failed and there are no more attempts left!"
return 1
else
echo "Attempt $attempt_num failed! Trying again in 10 seconds..."
sleep 10
fi
done
}
# Build the binary # Build the binary
go build -o /tmp/authelia/authelia-tmp cmd/authelia/main.go go build -o /tmp/authelia/authelia-tmp cmd/authelia/main.go
retry 3 /tmp/authelia/authelia-tmp -config /etc/authelia/configuration.yml while true;
do
/tmp/authelia/authelia-tmp -config /etc/authelia/configuration.yml
sleep 10
done

View File

@ -1,4 +1,4 @@
FROM node:8.7.0-alpine FROM node:12-alpine
WORKDIR /usr/app/src WORKDIR /usr/app/src

View File

@ -1,6 +1,7 @@
version: '3' version: "3"
services: services:
duo-api: duo-api:
image: authelia-duo-api build:
context: ./example/compose/duo-api
networks: networks:
- authelianet - authelianet

View File

@ -16,15 +16,18 @@ let permission = 'allow';
app.post('/allow', (req, res) => { app.post('/allow', (req, res) => {
permission = 'allow'; permission = 'allow';
console.log("set allowed!");
res.send('ALLOWED'); res.send('ALLOWED');
}); });
app.post('/deny', (req, res) => { app.post('/deny', (req, res) => {
permission = 'deny'; permission = 'deny';
console.log("set denied!");
res.send('DENIED'); res.send('DENIED');
}); });
app.post('/auth/v2/auth', (req, res) => { app.post('/auth/v2/auth', (req, res) => {
setTimeout(() => {
let response; let response;
if (permission == 'allow') { if (permission == 'allow') {
response = { response = {
@ -45,7 +48,8 @@ app.post('/auth/v2/auth', (req, res) => {
stat: 'OK', stat: 'OK',
}; };
} }
setTimeout(() => res.json(response), 2000); res.json(response);
}, 2000);
}); });
app.listen(port, () => console.log(`Duo API listening on port ${port}!`)); app.listen(port, () => console.log(`Duo API listening on port ${port}!`));

View File

@ -15,8 +15,8 @@ member: cn=bob,ou=users,dc=example,dc=com
objectclass: groupOfNames objectclass: groupOfNames
objectclass: top objectclass: top
dn: cn=admin,ou=groups,dc=example,dc=com dn: cn=admins,ou=groups,dc=example,dc=com
cn: admin cn: admins
member: cn=john,ou=users,dc=example,dc=com member: cn=john,ou=users,dc=example,dc=com
objectclass: groupOfNames objectclass: groupOfNames
objectclass: top objectclass: top

View File

@ -1,4 +1,4 @@
FROM nginx:alpine FROM nginx:alpine
ADD ./html /usr/share/nginx/html ADD html /usr/share/nginx/html
ADD ./nginx.conf /etc/nginx/nginx.conf ADD nginx.conf /etc/nginx/nginx.conf

View File

@ -1,10 +1,11 @@
version: '3' version: "3"
services: services:
nginx-backend: nginx-backend:
image: authelia-example-backend build:
context: example/compose/nginx/backend
labels: labels:
- traefik.frontend.rule=Host:home.example.com,public.example.com,secure.example.com,admin.example.com,singlefactor.example.com - traefik.frontend.rule=Host:home.example.com,public.example.com,secure.example.com,admin.example.com,singlefactor.example.com
- traefik.frontend.auth.forward.address=http://192.168.240.1:9091/api/verify?rd=https://login.example.com:8080/%23/ - traefik.frontend.auth.forward.address=http://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/
- traefik.frontend.auth.forward.tls.insecureSkipVerify=true - traefik.frontend.auth.forward.tls.insecureSkipVerify=true
networks: networks:
- authelianet - authelianet

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -8,7 +8,8 @@
<body> <body>
<h1>Access the secret</h1> <h1>Access the secret</h1>
<span style="font-size: 1.2em; color: red">You need to log in to access the secret!</span><br/><br/> Try to access it using <span style="font-size: 1.2em; color: red">You need to log in to access the secret!</span><br /><br /> Try to access
it using
one of the following links to test access control powered by Authelia.<br /> one of the following links to test access control powered by Authelia.<br />
<ul> <ul>
<li> <li>
@ -59,12 +60,15 @@
</li> </li>
</ul> </ul>
You can also log off by visiting the following <a href="https://login.example.com:8080/logout?rd=https://home.example.com:8080/">link</a>. You can also log off by visiting the following <a
href="https://login.example.com:8080/logout?rd=https://home.example.com:8080/">link</a>.
<h1>List of users</h1> <h1>List of users</h1>
Here is the list of credentials you can log in with to test access control.<br /> Here is the list of credentials you can log in with to test access control.<br />
<br/> Once first factor is passed, you will need to follow the links to register a secret for the second factor.<br/> Authelia <br /> Once first factor is passed, you will need to follow the links to register a secret for the second
will send you a fictituous email in a <strong>fake webmail</strong> at <a href="http://localhost:8085">http://localhost:8085</a>.<br/> factor.<br /> Authelia
will send you a fictituous email in a <strong>fake webmail</strong> at <a
href="http://localhost:8085">http://localhost:8085</a>.<br />
It will provide you with the link to complete the registration allowing you to authenticate with 2-factor. It will provide you with the link to complete the registration allowing you to authenticate with 2-factor.
<ul> <ul>
@ -86,12 +90,12 @@
- domain: singlefactor.example.com - domain: singlefactor.example.com
policy: one_factor policy: one_factor
# Rules applied to 'admin' group # Rules applied to 'admins' group
- domain: 'mx2.mail.example.com' - domain: 'mx2.mail.example.com'
subject: 'group:admin' subject: 'groups:admins'
policy: deny policy: deny
- domain: '*.example.com' - domain: '*.example.com'
subject: 'group:admin' subject: 'groups:admins'
policy: two_factor policy: two_factor
# Rules applied to 'dev' group # Rules applied to 'dev' group

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<html> <html>
<head> <head>
<title>Secret</title> <title>Secret</title>
<link rel="icon" href="/icon.png" type="image/png" /> <link rel="icon" href="/icon.png" type="image/png" />
</head> </head>
<body>
<body id="secret">
This is a very important secret!<br /> This is a very important secret!<br />
Go back to <a href="https://home.example.com:8080/">home page</a>. Go back to <a href="https://home.example.com:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -19,11 +19,10 @@ spec:
containers: containers:
- name: test-app - name: test-app
imagePullPolicy: Never imagePullPolicy: Never
image: authelia-example-backend image: nginx-backend
ports: ports:
- containerPort: 80 - containerPort: 80
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
@ -130,4 +129,3 @@ spec:
backend: backend:
serviceName: test-app-service serviceName: test-app-service
servicePort: 80 servicePort: 80

View File

@ -33,47 +33,45 @@ access_control:
- domain: singlefactor.example.com - domain: singlefactor.example.com
policy: one_factor policy: one_factor
# Rules applied to 'admin' group # Rules applied to 'admins' group
- domain: 'mx2.mail.example.com' - domain: "mx2.mail.example.com"
subject: 'group:admin' subject: "group:admins"
policy: deny policy: deny
- domain: '*.example.com' - domain: "*.example.com"
subject: 'group:admin' subject: "group:admins"
policy: two_factor policy: two_factor
# Rules applied to 'dev' group # Rules applied to 'dev' group
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/groups/dev/.*$' - "^/groups/dev/.*$"
subject: 'group:dev' subject: "group:dev"
policy: two_factor policy: two_factor
# Rules applied to user 'john' # Rules applied to user 'john'
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/users/john/.*$' - "^/users/john/.*$"
subject: 'user:john' subject: "user:john"
policy: two_factor policy: two_factor
# Rules applied to user 'harry' # Rules applied to user 'harry'
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/users/harry/.*$' - "^/users/harry/.*$"
subject: 'user:harry' subject: "user:harry"
policy: two_factor policy: two_factor
# Rules applied to user 'bob' # Rules applied to user 'bob'
- domain: '*.mail.example.com' - domain: "*.mail.example.com"
subject: 'user:bob' subject: "user:bob"
policy: two_factor policy: two_factor
- domain: 'dev.example.com' - domain: "dev.example.com"
resources: resources:
- '^/users/bob/.*$' - "^/users/bob/.*$"
subject: 'user:bob' subject: "user:bob"
policy: two_factor policy: two_factor
session: session:
secret: unsecure_password secret: unsecure_password
expiration: 3600000 # 1 hour expiration: 3600000 # 1 hour
@ -98,6 +96,6 @@ storage:
notifier: notifier:
smtp: smtp:
host: 'mailcatcher-service' host: "mailcatcher-service"
port: 1025 port: 1025
sender: admin@example.com sender: admin@example.com

View File

@ -29,5 +29,5 @@ spec:
configMap: configMap:
name: authelia-config name: authelia-config
items: items:
- key: config.yml - key: configuration.yml
path: config.yml path: configuration.yml

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
start_authelia() { start_authelia() {
kubectl create configmap authelia-config --namespace=authelia --from-file=authelia/configs/config.yml kubectl create configmap authelia-config --namespace=authelia --from-file=authelia/configs/configuration.yml
kubectl apply -f authelia kubectl apply -f authelia
} }

View File

@ -15,8 +15,8 @@ member: cn=bob,ou=users,dc=example,dc=com
objectclass: groupOfNames objectclass: groupOfNames
objectclass: top objectclass: top
dn: cn=admin,ou=groups,dc=example,dc=com dn: cn=admins,ou=groups,dc=example,dc=com
cn: admin cn: admins
member: cn=john,ou=users,dc=example,dc=com member: cn=john,ou=users,dc=example,dc=com
objectclass: groupOfNames objectclass: groupOfNames
objectclass: top objectclass: top

View File

@ -1,13 +1,13 @@
version: '3.4' version: "3.4"
services: services:
authelia: authelia:
image: clems4ever/authelia:latest image: clems4ever/authelia:latest
# Used for Docker configs # Used for Docker configs
configs: configs:
- source: authelia - source: authelia
target: /etc/authelia/config.yml target: /etc/authelia/configuration.yml
uid: '0' uid: "0"
gid: '0' gid: "0"
mode: 0444 mode: 0444
environment: environment:
- NODE_TLS_REJECT_UNAUTHORIZED=0 - NODE_TLS_REJECT_UNAUTHORIZED=0

1
go.mod
View File

@ -7,6 +7,7 @@ require (
github.com/Workiva/go-datastructures v1.0.50 github.com/Workiva/go-datastructures v1.0.50
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/cespare/reflex v0.2.0 // indirect github.com/cespare/reflex v0.2.0 // indirect
github.com/deckarep/golang-set v1.7.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect

2
go.sum
View File

@ -33,6 +33,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M=

View File

@ -128,7 +128,7 @@ users
john john
email: john.doe@authelia.com email: john.doe@authelia.com
groups: groups:
- admin - admins
- dev - dev
`) `)

View File

@ -48,44 +48,43 @@ access_control:
- domain: singlefactor.example.com - domain: singlefactor.example.com
policy: one_factor policy: one_factor
# Rules applied to 'admin' group # Rules applied to 'admins' group
- domain: 'mx2.mail.example.com' - domain: "mx2.mail.example.com"
subject: 'group:admin' subject: "groups:admins"
policy: deny policy: deny
- domain: '*.example.com' - domain: "*.example.com"
subject: 'group:admin' subject: "groups:admins"
policy: two_factor policy: two_factor
# Rules applied to 'dev' group # Rules applied to 'dev' group
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/groups/dev/.*$' - "^/groups/dev/.*$"
subject: 'group:dev' subject: "group:dev"
policy: two_factor policy: two_factor
# Rules applied to user 'john' # Rules applied to user 'john'
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/users/john/.*$' - "^/users/john/.*$"
subject: 'user:john' subject: "user:john"
policy: two_factor policy: two_factor
# Rules applied to user 'harry' # Rules applied to user 'harry'
- domain: dev.example.com - domain: dev.example.com
resources: resources:
- '^/users/harry/.*$' - "^/users/harry/.*$"
subject: 'user:harry' subject: "user:harry"
policy: two_factor policy: two_factor
# Rules applied to user 'bob' # Rules applied to user 'bob'
- domain: '*.mail.example.com' - domain: "*.mail.example.com"
subject: 'user:bob' subject: "user:bob"
policy: two_factor policy: two_factor
- domain: 'dev.example.com' - domain: "dev.example.com"
resources: resources:
- '^/users/bob/.*$' - "^/users/bob/.*$"
subject: 'user:bob' subject: "user:bob"
policy: two_factor policy: two_factor
session: session:

View File

@ -24,10 +24,11 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
bannedUntil, err := ctx.Providers.Regulator.Regulate(bodyJSON.Username) bannedUntil, err := ctx.Providers.Regulator.Regulate(bodyJSON.Username)
if err != nil {
if err == regulation.ErrUserIsBanned { if err == regulation.ErrUserIsBanned {
ctx.Error(fmt.Errorf("User %s is banned until %s", bodyJSON.Username, bannedUntil), userBannedMessage) ctx.Error(fmt.Errorf("User %s is banned until %s", bodyJSON.Username, bannedUntil), userBannedMessage)
return return
} else if err != nil { }
ctx.Error(fmt.Errorf("Unable to regulate authentication: %s", err), authenticationFailedMessage) ctx.Error(fmt.Errorf("Unable to regulate authentication: %s", err), authenticationFailedMessage)
return return
} }
@ -43,6 +44,9 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
} }
if !userPasswordOk { if !userPasswordOk {
ctx.Logger.Debugf("Mark authentication attempt made by user %s", bodyJSON.Username)
ctx.Providers.Regulator.Mark(bodyJSON.Username, false)
ctx.ReplyError(fmt.Errorf("Credentials are wrong for user %s", bodyJSON.Username), authenticationFailedMessage) ctx.ReplyError(fmt.Errorf("Credentials are wrong for user %s", bodyJSON.Username), authenticationFailedMessage)
return return
} }

View File

@ -162,7 +162,7 @@ func (s *FirstFactorSuite) TestShouldAuthenticateUser() {
GetDetails(gomock.Eq("test")). GetDetails(gomock.Eq("test")).
Return(&authentication.UserDetails{ Return(&authentication.UserDetails{
Emails: []string{"test@example.com"}, Emails: []string{"test@example.com"},
Groups: []string{"dev", "admin"}, Groups: []string{"dev", "admins"},
}, nil) }, nil)
s.mock.StorageProviderMock. s.mock.StorageProviderMock.
@ -186,7 +186,7 @@ func (s *FirstFactorSuite) TestShouldAuthenticateUser() {
assert.Equal(s.T(), "test", session.Username) assert.Equal(s.T(), "test", session.Username)
assert.Equal(s.T(), authentication.OneFactor, session.AuthenticationLevel) assert.Equal(s.T(), authentication.OneFactor, session.AuthenticationLevel)
assert.Equal(s.T(), []string{"test@example.com"}, session.Emails) assert.Equal(s.T(), []string{"test@example.com"}, session.Emails)
assert.Equal(s.T(), []string{"dev", "admin"}, session.Groups) assert.Equal(s.T(), []string{"dev", "admins"}, session.Groups)
} }

View File

@ -208,7 +208,7 @@ func (s *BasicAuthorizationSuite) TestShouldApplyDefaultPolicy() {
GetDetails(gomock.Eq("john")). GetDetails(gomock.Eq("john")).
Return(&authentication.UserDetails{ Return(&authentication.UserDetails{
Emails: []string{"john@example.com"}, Emails: []string{"john@example.com"},
Groups: []string{"dev", "admin"}, Groups: []string{"dev", "admins"},
}, nil) }, nil)
VerifyGet(mock.Ctx) VerifyGet(mock.Ctx)
@ -231,7 +231,7 @@ func (s *BasicAuthorizationSuite) TestShouldApplyPolicyOfBypassDomain() {
GetDetails(gomock.Eq("john")). GetDetails(gomock.Eq("john")).
Return(&authentication.UserDetails{ Return(&authentication.UserDetails{
Emails: []string{"john@example.com"}, Emails: []string{"john@example.com"},
Groups: []string{"dev", "admin"}, Groups: []string{"dev", "admins"},
}, nil) }, nil)
VerifyGet(mock.Ctx) VerifyGet(mock.Ctx)
@ -254,7 +254,7 @@ func (s *BasicAuthorizationSuite) TestShouldApplyPolicyOfOneFactorDomain() {
GetDetails(gomock.Eq("john")). GetDetails(gomock.Eq("john")).
Return(&authentication.UserDetails{ Return(&authentication.UserDetails{
Emails: []string{"john@example.com"}, Emails: []string{"john@example.com"},
Groups: []string{"dev", "admin"}, Groups: []string{"dev", "admins"},
}, nil) }, nil)
VerifyGet(mock.Ctx) VerifyGet(mock.Ctx)
@ -277,7 +277,7 @@ func (s *BasicAuthorizationSuite) TestShouldApplyPolicyOfTwoFactorDomain() {
GetDetails(gomock.Eq("john")). GetDetails(gomock.Eq("john")).
Return(&authentication.UserDetails{ Return(&authentication.UserDetails{
Emails: []string{"john@example.com"}, Emails: []string{"john@example.com"},
Groups: []string{"dev", "admin"}, Groups: []string{"dev", "admins"},
}, nil) }, nil)
VerifyGet(mock.Ctx) VerifyGet(mock.Ctx)
@ -300,7 +300,7 @@ func (s *BasicAuthorizationSuite) TestShouldApplyPolicyOfDenyDomain() {
GetDetails(gomock.Eq("john")). GetDetails(gomock.Eq("john")).
Return(&authentication.UserDetails{ Return(&authentication.UserDetails{
Emails: []string{"john@example.com"}, Emails: []string{"john@example.com"},
Groups: []string{"dev", "admin"}, Groups: []string{"dev", "admins"},
}, nil) }, nil)
VerifyGet(mock.Ctx) VerifyGet(mock.Ctx)

View File

@ -3,6 +3,7 @@ package server
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"github.com/clems4ever/authelia/internal/configuration/schema" "github.com/clems4ever/authelia/internal/configuration/schema"
"github.com/clems4ever/authelia/internal/duo" "github.com/clems4ever/authelia/internal/duo"
@ -27,7 +28,6 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
fmt.Println("Selected public_html directory is ", publicDir) fmt.Println("Selected public_html directory is ", publicDir)
router.GET("/", fasthttp.FSHandler(publicDir, 0)) router.GET("/", fasthttp.FSHandler(publicDir, 0))
router.NotFound = fasthttp.FSHandler(publicDir, 0)
router.ServeFiles("/static/*filepath", publicDir+"/static") router.ServeFiles("/static/*filepath", publicDir+"/static")
router.GET("/api/state", autheliaMiddleware(handlers.StateGet)) router.GET("/api/state", autheliaMiddleware(handlers.StateGet))
@ -95,6 +95,10 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
middlewares.RequireFirstFactor(handlers.SecondFactorDuoPost(duoAPI)))) middlewares.RequireFirstFactor(handlers.SecondFactorDuoPost(duoAPI))))
} }
router.NotFound = func(ctx *fasthttp.RequestCtx) {
ctx.SendFile(path.Join(publicDir, "index.html"))
}
portPattern := fmt.Sprintf(":%d", configuration.Port) portPattern := fmt.Sprintf(":%d", configuration.Port)
logging.Logger().Infof("Authelia is listening on %s", portPattern) logging.Logger().Infof("Authelia is listening on %s", portPattern)

View File

@ -0,0 +1,81 @@
###############################################################
# Authelia minimal configuration #
###############################################################
port: 9091
logs_level: debug
default_redirection_url: https://home.example.com:8080/
jwt_secret: very_important_secret
authentication_backend:
file:
path: /var/lib/authelia/users.yml
session:
secret: unsecure_session_secret
domain: example.com
expiration: 3600 # 1 hour
inactivity: 300 # 5 minutes
storage:
local:
path: /tmp/authelia/db.sqlite3
totp:
issuer: example.com
access_control:
default_policy: deny
rules:
- domain: singlefactor.example.com
policy: one_factor
- domain: public.example.com
policy: bypass
- domain: secure.example.com
policy: two_factor
- domain: "*.example.com"
subject: "group:admins"
policy: two_factor
- domain: dev.example.com
resources:
- "^/users/john/.*$"
subject: "user:john"
policy: two_factor
- domain: dev.example.com
resources:
- "^/users/harry/.*$"
subject: "user:harry"
policy: two_factor
- domain: "*.mail.example.com"
subject: "user:bob"
policy: two_factor
- domain: dev.example.com
resources:
- "^/users/bob/.*$"
subject: "user:bob"
policy: two_factor
regulation:
# Set it to 0 to disable max_retries.
max_retries: 3
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
find_time: 300
# The length of time before a banned user can login again.
ban_time: 900
notifier:
smtp:
host: smtp
port: 1025
sender: admin@example.com

View File

@ -0,0 +1,6 @@
version: "3"
services:
authelia-backend:
volumes:
- "./internal/suites/Docker/configuration.yml:/etc/authelia/configuration.yml:ro"
- "./internal/suites/Docker/users.yml:/var/lib/authelia/users.yml"

View File

@ -0,0 +1,20 @@
users:
bob:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: bob.dylan@authelia.com
groups:
- dev
harry:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: harry.potter@authelia.com
groups: []
james:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: james.dean@authelia.com
groups: []
john:
password: "{CRYPT}$6$rounds=50000$LnfgDsc2WD8F2qNf$0gcCt8jlqAGZRv2ee3mCFsfAr1P4N7kESWEf36Xtw6OjkhAcQuGVOBHXp0lFuZbppa7YlgHk3VD28aSQu9U9S1"
email: john.doe@authelia.com
groups:
- admins
- dev

View File

@ -136,9 +136,9 @@ access_control:
- domain: singlefactor.example.com - domain: singlefactor.example.com
policy: one_factor policy: one_factor
# Rules applied to 'admin' group # Rules applied to 'admins' group
- domain: mx2.mail.example.com - domain: mx2.mail.example.com
subject: "group:admin" subject: "group:admins"
policy: deny policy: deny
# Rules applied to user 'john' # Rules applied to user 'john'
@ -147,7 +147,7 @@ access_control:
policy: two_factor policy: two_factor
- domain: "*.example.com" - domain: "*.example.com"
subject: "group:admin" subject: "group:admins"
policy: two_factor policy: two_factor
# Rules applied to 'dev' group # Rules applied to 'dev' group

View File

@ -3,3 +3,4 @@ services:
authelia-backend: authelia-backend:
volumes: volumes:
- "./internal/suites/Mariadb/configuration.yml:/etc/authelia/configuration.yml:ro" - "./internal/suites/Mariadb/configuration.yml:/etc/authelia/configuration.yml:ro"
- "./internal/suites/Mariadb/users.yml:/var/lib/authelia/users.yml"

View File

@ -10,7 +10,7 @@ jwt_secret: unsecure_password
authentication_backend: authentication_backend:
file: file:
path: users.yml path: /var/lib/authelia/users.yml
session: session:
secret: unsecure_session_secret secret: unsecure_session_secret

View File

@ -4,27 +4,27 @@ import (
"context" "context"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
func (wds *WebDriverSession) doFillLoginPageAndClick(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool) { func (wds *WebDriverSession) doFillLoginPageAndClick(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool) {
usernameElement := wds.WaitElementLocatedByID(ctx, t, "username-textfield") usernameElement := wds.WaitElementLocatedByID(ctx, t, "username-textfield")
err := usernameElement.SendKeys(username) err := usernameElement.SendKeys(username)
assert.NoError(t, err) require.NoError(t, err)
passwordElement := wds.WaitElementLocatedByID(ctx, t, "password-textfield") passwordElement := wds.WaitElementLocatedByID(ctx, t, "password-textfield")
err = passwordElement.SendKeys(password) err = passwordElement.SendKeys(password)
assert.NoError(t, err) require.NoError(t, err)
if keepMeLoggedIn { if keepMeLoggedIn {
keepMeLoggedInElement := wds.WaitElementLocatedByID(ctx, t, "remember-checkbox") keepMeLoggedInElement := wds.WaitElementLocatedByID(ctx, t, "remember-checkbox")
err = keepMeLoggedInElement.Click() err = keepMeLoggedInElement.Click()
assert.NoError(t, err) require.NoError(t, err)
} }
buttonElement := wds.WaitElementLocatedByID(ctx, t, "sign-in-button") buttonElement := wds.WaitElementLocatedByID(ctx, t, "sign-in-button")
err = buttonElement.Click() err = buttonElement.Click()
assert.NoError(t, err) require.NoError(t, err)
} }
// Login 1FA // Login 1FA

View File

@ -33,7 +33,7 @@ func (de *DockerEnvironment) createCommand(cmd string) *exec.Cmd {
// Up spawn a docker environment // Up spawn a docker environment
func (de *DockerEnvironment) Up() error { func (de *DockerEnvironment) Up() error {
return de.createCommandWithStdout("up -d").Run() return de.createCommandWithStdout("up --build -d").Run()
} }
// Restart restarts a service // Restart restarts a service

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
// DuoPolicy a type of policy // DuoPolicy a type of policy
@ -26,10 +26,10 @@ func ConfigureDuo(t *testing.T, allowDeny DuoPolicy) {
} }
req, err := http.NewRequest("POST", url, nil) req, err := http.NewRequest("POST", url, nil)
assert.NoError(t, err) require.NoError(t, err)
client := NewHTTPClient() client := NewHTTPClient()
res, err := client.Do(req) res, err := client.Do(req)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, res.StatusCode) require.Equal(t, 200, res.StatusCode)
} }

View File

@ -35,31 +35,34 @@ func waitUntilServiceLogDetected(
return err return err
} }
func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment) error { func waitUntilAutheliaBackendIsReady(dockerEnvironment *DockerEnvironment) error {
log.Info("Waiting for Authelia to be ready...") return waitUntilServiceLogDetected(
err := waitUntilServiceLogDetected(
5*time.Second, 5*time.Second,
90*time.Second, 90*time.Second,
dockerEnvironment, dockerEnvironment,
"authelia-backend", "authelia-backend",
[]string{"Authelia is listening on"}) []string{"Authelia is listening on"})
if err != nil {
return err
} }
err = waitUntilServiceLogDetected( func waitUntilAutheliaFrontendIsReady(dockerEnvironment *DockerEnvironment) error {
return waitUntilServiceLogDetected(
5*time.Second, 5*time.Second,
90*time.Second, 90*time.Second,
dockerEnvironment, dockerEnvironment,
"authelia-frontend", "authelia-frontend",
[]string{"You can now view web in the browser.", "Compiled with warnings", "Compiled successfully!"}) []string{"You can now view web in the browser.", "Compiled with warnings", "Compiled successfully!"})
}
if err != nil { func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment) error {
log.Info("Waiting for Authelia to be ready...")
if err := waitUntilAutheliaBackendIsReady(dockerEnvironment); err != nil {
return err
}
if err := waitUntilAutheliaFrontendIsReady(dockerEnvironment); err != nil {
return err return err
} }
log.Info("Authelia is now ready!") log.Info("Authelia is now ready!")
return nil return nil
} }

View File

@ -9,9 +9,12 @@ import (
// Suite the definition of a suite // Suite the definition of a suite
type Suite struct { type Suite struct {
TestTimeout time.Duration
SetUp func(tmpPath string) error SetUp func(tmpPath string) error
SetUpTimeout time.Duration SetUpTimeout time.Duration
OnSetupTimeout func() error
TestTimeout time.Duration
TearDown func(tmpPath string) error TearDown func(tmpPath string) error
TearDownTimeout time.Duration TearDownTimeout time.Duration

View File

@ -2,11 +2,15 @@ package suites
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"log" "log"
"strings"
"testing" "testing"
"time" "time"
mapset "github.com/deckarep/golang-set"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/tebeka/selenium" "github.com/tebeka/selenium"
) )
@ -59,18 +63,55 @@ func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticat
s.WaitElementTextContains(ctx, s.T(), body, "httpbin:8000") s.WaitElementTextContains(ctx, s.T(), body, "httpbin:8000")
} }
type Headers struct {
ForwardedGroups string `json:"Custom-Forwarded-Groups"`
ForwardedUser string `json:"Custom-Forwarded-User"`
}
type HeadersPayload struct {
Headers Headers `json:"headers"`
}
func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUser() { func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUser() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel() defer cancel()
expectedGroups := mapset.NewSetWith("dev", "admins")
targetURL := fmt.Sprintf("%s/headers", PublicBaseURL) targetURL := fmt.Sprintf("%s/headers", PublicBaseURL)
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL) s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
s.verifyURLIs(ctx, s.T(), targetURL) s.verifyURLIs(ctx, s.T(), targetURL)
err := s.Wait(ctx, func(d selenium.WebDriver) (bool, error) {
body, err := s.WebDriver().FindElement(selenium.ByTagName, "body") body, err := s.WebDriver().FindElement(selenium.ByTagName, "body")
s.Assert().NoError(err) if err != nil {
s.WaitElementTextContains(ctx, s.T(), body, "\"Custom-Forwarded-User\": \"john\"") return false, err
s.WaitElementTextContains(ctx, s.T(), body, "\"Custom-Forwarded-Groups\": \"admins,dev\"") }
if body == nil {
return false, nil
}
content, err := body.Text()
if err != nil {
return false, err
}
payload := HeadersPayload{}
if err := json.Unmarshal([]byte(content), &payload); err != nil {
return false, err
}
groups := strings.Split(payload.Headers.ForwardedGroups, ",")
actualGroups := mapset.NewSet()
for _, group := range groups {
actualGroups.Add(group)
}
return strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups), nil
})
require.NoError(s.T(), err)
} }
func TestCustomHeadersScenario(t *testing.T) { func TestCustomHeadersScenario(t *testing.T) {

View File

@ -57,7 +57,7 @@ func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click() s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
time.Sleep(2 * time.Second) time.Sleep(1 * time.Second)
} }
// Reset password field // Reset password field

View File

@ -47,7 +47,7 @@ func (s *UserPreferencesScenario) SetupTest() {
} }
func (s *UserPreferencesScenario) TestShouldRememberLastUsed2FAMethod() { func (s *UserPreferencesScenario) TestShouldRememberLastUsed2FAMethod() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
// Authenticate // Authenticate

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -24,7 +25,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -34,6 +50,7 @@ func init() {
GlobalRegistry.Register(bypassAllSuiteName, Suite{ GlobalRegistry.Register(bypassAllSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 5 * time.Minute, SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TestTimeout: 1 * time.Minute, TestTimeout: 1 * time.Minute,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,

View File

@ -0,0 +1,57 @@
package suites
import (
"fmt"
"time"
)
func init() {
dockerEnvironment := NewDockerEnvironment([]string{
"docker-compose.yml",
"internal/suites/Docker/docker-compose.yml",
"example/compose/authelia/docker-compose.backend-dist.yml",
"example/compose/authelia/docker-compose.frontend-dist.yml",
"example/compose/nginx/backend/docker-compose.yml",
"example/compose/nginx/portal/docker-compose.yml",
"example/compose/smtp/docker-compose.yml",
})
setup := func(suitePath string) error {
if err := dockerEnvironment.Up(); err != nil {
return err
}
return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
}
teardown := func(suitePath string) error {
return dockerEnvironment.Down()
}
GlobalRegistry.Register("Docker", Suite{
SetUp: setup,
SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TestTimeout: 1 * time.Minute,
TearDown: teardown,
TearDownTimeout: 2 * time.Minute,
Description: `This suite has been created to test the distributable version of Authelia
It's often useful to test this one before the Kube one.`,
})
}

View File

@ -0,0 +1,20 @@
package suites
import (
"testing"
"github.com/stretchr/testify/suite"
)
type DockerSuite struct {
*SeleniumSuite
}
func NewDockerSuite() *DockerSuite {
return &DockerSuite{SeleniumSuite: new(SeleniumSuite)}
}
func TestDockerSuite(t *testing.T) {
suite.Run(t, NewOneFactorScenario())
suite.Run(t, NewTwoFactorScenario())
}

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -22,7 +23,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -32,6 +48,7 @@ func init() {
GlobalRegistry.Register(duoPushSuiteName, Suite{ GlobalRegistry.Register(duoPushSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 5 * time.Minute, SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TestTimeout: 1 * time.Minute, TestTimeout: 1 * time.Minute,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,

View File

@ -35,15 +35,23 @@ func (s *DuoPushSuite) TearDownSuite() {
} }
} }
func (s *DuoPushSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s.doLogout(ctx, s.T())
}
func (s *DuoPushSuite) TearDownTest() { func (s *DuoPushSuite) TearDownTest() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
s.doChangeMethod(ctx, s.T(), "one-time-password") s.doChangeMethod(ctx, s.T(), "one-time-password")
s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method")
} }
func (s *DuoPushSuite) TestShouldSucceedAuthentication() { func (s *DuoPushSuite) TestShouldSucceedAuthentication() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel() defer cancel()
ConfigureDuo(s.T(), Allow) ConfigureDuo(s.T(), Allow)
@ -54,7 +62,7 @@ func (s *DuoPushSuite) TestShouldSucceedAuthentication() {
} }
func (s *DuoPushSuite) TestShouldFailAuthentication() { func (s *DuoPushSuite) TestShouldFailAuthentication() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel() defer cancel()
ConfigureDuo(s.T(), Deny) ConfigureDuo(s.T(), Deny)

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -27,7 +28,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(haDockerEnvironment) return waitUntilAutheliaBackendIsReady(haDockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := haDockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := haDockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -37,7 +53,8 @@ func init() {
GlobalRegistry.Register(highAvailabilitySuiteName, Suite{ GlobalRegistry.Register(highAvailabilitySuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 5 * time.Minute, SetUpTimeout: 5 * time.Minute,
TestTimeout: 1 * time.Minute, OnSetupTimeout: onSetupTimeout,
TestTimeout: 5 * time.Minute,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,
Description: `This suite is made to test Authelia in a *complete* Description: `This suite is made to test Authelia in a *complete*

View File

@ -39,6 +39,15 @@ func (s *HighAvailabilityWebDriverSuite) TearDownSuite() {
} }
} }
func (s *HighAvailabilityWebDriverSuite) SetupTest() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s.doLogout(ctx, s.T())
s.doVisit(s.T(), HomeBaseURL)
s.verifyIsHome(ctx, s.T())
}
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserDataInDB() { func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserDataInDB() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel() defer cancel()
@ -151,13 +160,15 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
verifyAuthorization := func(username string) func(t *testing.T) { verifyAuthorization := func(username string) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel() defer cancel()
s.doRegisterAndLogin2FA(ctx, t, username, "password", false, "") s.doRegisterAndLogin2FA(ctx, t, username, "password", false, "")
for url, authorizations := range expectedAuthorizations { for url, authorizations := range expectedAuthorizations {
t.Run(url, func(t *testing.T) {
verifyUserIsAuthorized(ctx, t, username, url, authorizations[username]) verifyUserIsAuthorized(ctx, t, username, url, authorizations[username])
})
} }
s.doLogout(ctx, t) s.doLogout(ctx, t)
@ -165,7 +176,7 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() {
} }
for _, user := range []string{UserJohn, UserBob, UserHarry} { for _, user := range []string{UserJohn, UserBob, UserHarry} {
s.T().Run(fmt.Sprintf("user %s", user), verifyAuthorization(user)) s.T().Run(fmt.Sprintf("%s", user), verifyAuthorization(user))
} }
} }

View File

@ -15,6 +15,16 @@ func init() {
kubectl := Kubectl{} kubectl := Kubectl{}
setup := func(suitePath string) error { setup := func(suitePath string) error {
cmd := utils.Shell("docker-compose -f docker-compose.yml -f example/compose/kind/docker-compose.yml build")
if err := cmd.Run(); err != nil {
return err
}
cmd = utils.Shell("docker build -t nginx-backend example/compose/nginx/backend")
if err := cmd.Run(); err != nil {
return err
}
exists, err := kind.ClusterExists() exists, err := kind.ClusterExists()
if err != nil { if err != nil {
@ -74,23 +84,24 @@ func init() {
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
kubectl.StopDashboard()
kubectl.StopProxy() kubectl.StopProxy()
return kind.DeleteCluster() return kind.DeleteCluster()
} }
GlobalRegistry.Register(kubernetesSuiteName, Suite{ GlobalRegistry.Register(kubernetesSuiteName, Suite{
TestTimeout: 60 * time.Second,
SetUp: setup, SetUp: setup,
SetUpTimeout: 10 * time.Minute, SetUpTimeout: 10 * time.Minute,
TestTimeout: 2 * time.Minute,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 10 * time.Minute, TearDownTimeout: 2 * time.Minute,
Description: "This suite has been created to test Authelia in a Kubernetes context and using nginx as the ingress controller.", Description: "This suite has been created to test Authelia in a Kubernetes context and using nginx as the ingress controller.",
}) })
} }
func loadDockerImages() error { func loadDockerImages() error {
kind := Kind{} kind := Kind{}
images := []string{"authelia:dist", "authelia-example-backend"} images := []string{"authelia:dist", "nginx-backend"}
for _, image := range images { for _, image := range images {
err := kind.LoadImage(image) err := kind.LoadImage(image)

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -25,7 +26,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -36,6 +52,7 @@ func init() {
GlobalRegistry.Register(ldapSuiteName, Suite{ GlobalRegistry.Register(ldapSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 5 * time.Minute, SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TestTimeout: 1 * time.Minute, TestTimeout: 1 * time.Minute,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -24,7 +25,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -34,7 +50,8 @@ func init() {
GlobalRegistry.Register(mariadbSuiteName, Suite{ GlobalRegistry.Register(mariadbSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 3 * time.Minute, SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,
}) })

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -25,7 +26,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -35,6 +51,7 @@ func init() {
GlobalRegistry.Register(networkACLSuiteName, Suite{ GlobalRegistry.Register(networkACLSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 5 * time.Minute, SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TestTimeout: 1 * time.Minute, TestTimeout: 1 * time.Minute,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,

View File

@ -3,7 +3,6 @@ package suites
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"testing" "testing"
"time" "time"
@ -12,66 +11,26 @@ import (
type NetworkACLSuite struct { type NetworkACLSuite struct {
suite.Suite suite.Suite
clients []*WebDriverSession
} }
func NewNetworkACLSuite() *NetworkACLSuite { func NewNetworkACLSuite() *NetworkACLSuite {
return &NetworkACLSuite{clients: make([]*WebDriverSession, 3)} return &NetworkACLSuite{}
}
func (s *NetworkACLSuite) createClient(idx int) {
wds, err := StartWebDriverWithProxy(fmt.Sprintf("http://proxy-client%d.example.com:3128", idx), 4444+idx)
if err != nil {
log.Fatal(err)
}
s.clients[idx] = wds
}
func (s *NetworkACLSuite) teardownClient(idx int) {
if err := s.clients[idx].Stop(); err != nil {
log.Fatal(err)
}
}
func (s *NetworkACLSuite) SetupSuite() {
wds, err := StartWebDriver()
if err != nil {
log.Fatal(err)
}
s.clients[0] = wds
for i := 1; i <= 2; i++ {
s.createClient(i)
}
}
func (s *NetworkACLSuite) TearDownSuite() {
if err := s.clients[0].Stop(); err != nil {
log.Fatal(err)
}
for i := 1; i <= 2; i++ {
s.teardownClient(i)
}
} }
func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() { func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
wds, err := StartWebDriver()
s.Require().NoError(err)
defer wds.Stop()
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL) targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
secret := s.clients[0].doRegisterThenLogout(ctx, s.T(), "john", "password") wds.doVisit(s.T(), targetURL)
wds.verifyIsFirstFactorPage(ctx, s.T())
s.clients[0].doVisit(s.T(), targetURL) wds.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, targetURL)
s.clients[0].verifyIsFirstFactorPage(ctx, s.T()) wds.verifySecretAuthorized(ctx, s.T())
s.clients[0].doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL)
s.clients[0].verifyIsSecondFactorPage(ctx, s.T())
s.clients[0].doValidateTOTP(ctx, s.T(), secret)
s.clients[0].verifySecretAuthorized(ctx, s.T())
} }
// from network 192.168.240.201/32 // from network 192.168.240.201/32
@ -79,13 +38,17 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon1FA() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL) wds, err := StartWebDriverWithProxy("http://proxy-client1.example.com:3128", 4444)
s.clients[1].doVisit(s.T(), targetURL) s.Require().NoError(err)
s.clients[1].verifyIsFirstFactorPage(ctx, s.T()) defer wds.Stop()
s.clients[1].doLoginOneFactor(ctx, s.T(), "john", "password", targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
wds.doVisit(s.T(), targetURL)
wds.verifyIsFirstFactorPage(ctx, s.T())
wds.doLoginOneFactor(ctx, s.T(), "john", "password",
false, fmt.Sprintf("%s/secret.html", SecureBaseURL)) false, fmt.Sprintf("%s/secret.html", SecureBaseURL))
s.clients[1].verifySecretAuthorized(ctx, s.T()) wds.verifySecretAuthorized(ctx, s.T())
} }
// from network 192.168.240.202/32 // from network 192.168.240.202/32
@ -93,8 +56,12 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon0FA() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
s.clients[2].doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL)) wds, err := StartWebDriverWithProxy("http://proxy-client2.example.com:3128", 4444)
s.clients[2].verifySecretAuthorized(ctx, s.T()) s.Require().NoError(err)
defer wds.Stop()
wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
wds.verifySecretAuthorized(ctx, s.T())
} }
func TestNetworkACLSuite(t *testing.T) { func TestNetworkACLSuite(t *testing.T) {

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -24,7 +25,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -34,7 +50,8 @@ func init() {
GlobalRegistry.Register(postgresSuiteName, Suite{ GlobalRegistry.Register(postgresSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 3 * time.Minute, SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,
}) })

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -22,7 +23,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -32,7 +48,8 @@ func init() {
GlobalRegistry.Register(shortTimeoutsSuiteName, Suite{ GlobalRegistry.Register(shortTimeoutsSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 5 * time.Minute, SetUpTimeout: 5 * time.Minute,
TestTimeout: 1 * time.Minute, OnSetupTimeout: onSetupTimeout,
TestTimeout: 3 * time.Minute,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,
Description: `This suite has been created to configure Authelia with short timeouts for sessions expiration Description: `This suite has been created to configure Authelia with short timeouts for sessions expiration

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -24,7 +25,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -35,8 +51,10 @@ func init() {
GlobalRegistry.Register(standaloneSuiteName, Suite{ GlobalRegistry.Register(standaloneSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 5 * time.Minute, SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 5 * time.Minute, TestTimeout: 2 * time.Minute,
TearDownTimeout: 2 * time.Minute,
Description: `This suite is used to test Authelia in a standalone Description: `This suite is used to test Authelia in a standalone
configuration with in-memory sessions and a local sqlite db stored on disk`, configuration with in-memory sessions and a local sqlite db stored on disk`,
}) })

View File

@ -1,6 +1,7 @@
package suites package suites
import ( import (
"fmt"
"time" "time"
) )
@ -24,7 +25,22 @@ func init() {
return err return err
} }
return waitUntilAutheliaIsReady(dockerEnvironment) return waitUntilAutheliaBackendIsReady(dockerEnvironment)
}
onSetupTimeout := func() error {
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
if err != nil {
return err
}
fmt.Println(backendLogs)
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
if err != nil {
return err
}
fmt.Println(frontendLogs)
return nil
} }
teardown := func(suitePath string) error { teardown := func(suitePath string) error {
@ -35,6 +51,7 @@ func init() {
GlobalRegistry.Register(traefikSuiteName, Suite{ GlobalRegistry.Register(traefikSuiteName, Suite{
SetUp: setup, SetUp: setup,
SetUpTimeout: 5 * time.Minute, SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: onSetupTimeout,
TearDown: teardown, TearDown: teardown,
TearDownTimeout: 2 * time.Minute, TearDownTimeout: 2 * time.Minute,
}) })

View File

@ -11,8 +11,15 @@ import (
func (wds *WebDriverSession) verifyBodyContains(ctx context.Context, t *testing.T, pattern string) { func (wds *WebDriverSession) verifyBodyContains(ctx context.Context, t *testing.T, pattern string) {
err := wds.Wait(ctx, func(wd selenium.WebDriver) (bool, error) { err := wds.Wait(ctx, func(wd selenium.WebDriver) (bool, error) {
bodyElement := wds.WaitElementLocatedByTagName(ctx, t, "body") bodyElement, err := wds.WebDriver.FindElement(selenium.ByTagName, "body")
require.NotNil(t, bodyElement)
if err != nil {
return false, err
}
if bodyElement == nil {
return false, nil
}
content, err := bodyElement.Text() content, err := bodyElement.Text()

View File

@ -6,5 +6,5 @@ import (
) )
func (wds *WebDriverSession) verifySecretAuthorized(ctx context.Context, t *testing.T) { func (wds *WebDriverSession) verifySecretAuthorized(ctx context.Context, t *testing.T) {
wds.verifyBodyContains(ctx, t, "This is a very important secret!") wds.WaitElementLocatedByID(ctx, t, "secret")
} }

View File

@ -0,0 +1,6 @@
package utils
import "errors"
// ErrTimeoutReached error thrown when a timeout is reached
var ErrTimeoutReached = errors.New("timeout reached")

View File

@ -31,10 +31,8 @@ func Command(name string, args ...string) *exec.Cmd {
// CommandWithStdout create a command forwarding stdout and stderr to the OS streams // CommandWithStdout create a command forwarding stdout and stderr to the OS streams
func CommandWithStdout(name string, args ...string) *exec.Cmd { func CommandWithStdout(name string, args ...string) *exec.Cmd {
cmd := Command(name, args...) cmd := Command(name, args...)
if log.GetLevel() > log.InfoLevel {
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
}
return cmd return cmd
} }
@ -133,7 +131,7 @@ func RunCommandWithTimeout(cmd *exec.Cmd, timeout time.Duration) error {
if err := cmd.Process.Kill(); err != nil { if err := cmd.Process.Kill(); err != nil {
return err return err
} }
return fmt.Errorf("timeout of %ds reached", int64(timeout/time.Second)) return ErrTimeoutReached
case err := <-done: case err := <-done:
return err return err
} }

View File

@ -1,8 +1,11 @@
import React, { useEffect, Fragment, ReactNode, useState } from "react"; import React, { useEffect, Fragment, ReactNode, useState, useCallback } from "react";
import { Switch, Route, Redirect, useHistory, useLocation } from "react-router"; import { Switch, Route, Redirect, useHistory, useLocation } from "react-router";
import FirstFactorForm from "./FirstFactor/FirstFactorForm"; import FirstFactorForm from "./FirstFactor/FirstFactorForm";
import SecondFactorForm from "./SecondFactor/SecondFactorForm"; import SecondFactorForm from "./SecondFactor/SecondFactorForm";
import { FirstFactorRoute, SecondFactorRoute, SecondFactorTOTPRoute, SecondFactorPushRoute, SecondFactorU2FRoute, LogoutRoute } from "../../Routes"; import {
FirstFactorRoute, SecondFactorRoute, SecondFactorTOTPRoute,
SecondFactorPushRoute, SecondFactorU2FRoute
} from "../../Routes";
import { useAutheliaState } from "../../hooks/State"; import { useAutheliaState } from "../../hooks/State";
import LoadingPage from "../LoadingPage/LoadingPage"; import LoadingPage from "../LoadingPage/LoadingPage";
import { AuthenticationLevel } from "../../services/State"; import { AuthenticationLevel } from "../../services/State";
@ -23,6 +26,8 @@ export default function () {
const [preferences, fetchPreferences, , fetchPreferencesError] = useUserPreferences(); const [preferences, fetchPreferences, , fetchPreferencesError] = useUserPreferences();
const [configuration, fetchConfiguration, , fetchConfigurationError] = useAutheliaConfiguration(); const [configuration, fetchConfiguration, , fetchConfigurationError] = useAutheliaConfiguration();
const redirect = useCallback((url: string) => history.push(url), [history]);
// Fetch the state when portal is mounted. // Fetch the state when portal is mounted.
useEffect(() => { fetchState() }, [fetchState]); useEffect(() => { fetchState() }, [fetchState]);
@ -71,19 +76,19 @@ export default function () {
if (state.authentication_level === AuthenticationLevel.Unauthenticated) { if (state.authentication_level === AuthenticationLevel.Unauthenticated) {
setFirstFactorDisabled(false); setFirstFactorDisabled(false);
history.push(`${FirstFactorRoute}${redirectionSuffix}`); redirect(`${FirstFactorRoute}${redirectionSuffix}`);
} else if (state.authentication_level >= AuthenticationLevel.OneFactor && preferences) { } else if (state.authentication_level >= AuthenticationLevel.OneFactor && preferences) {
console.log("redirect"); console.log("redirect");
if (preferences.method === SecondFactorMethod.U2F) { if (preferences.method === SecondFactorMethod.U2F) {
history.push(`${SecondFactorU2FRoute}${redirectionSuffix}`); redirect(`${SecondFactorU2FRoute}${redirectionSuffix}`);
} else if (preferences.method === SecondFactorMethod.Duo) { } else if (preferences.method === SecondFactorMethod.Duo) {
history.push(`${SecondFactorPushRoute}${redirectionSuffix}`); redirect(`${SecondFactorPushRoute}${redirectionSuffix}`);
} else { } else {
history.push(`${SecondFactorTOTPRoute}${redirectionSuffix}`); redirect(`${SecondFactorTOTPRoute}${redirectionSuffix}`);
} }
} }
} }
}, [state, redirectionURL, history.push, preferences, setFirstFactorDisabled]); }, [state, redirectionURL, redirect, preferences, setFirstFactorDisabled]);
const handleFirstFactorSuccess = async (redirectionURL: string | undefined) => { const handleFirstFactorSuccess = async (redirectionURL: string | undefined) => {
if (redirectionURL) { if (redirectionURL) {

View File

@ -1,4 +1,4 @@
import React, { ReactNode, Fragment } from "react"; import React, { ReactNode } from "react";
import { makeStyles, Typography, Link } from "@material-ui/core"; import { makeStyles, Typography, Link } from "@material-ui/core";
interface MethodContainerProps { interface MethodContainerProps {

View File

@ -31,7 +31,7 @@ export default function (props: Props) {
console.error(err); console.error(err);
createErrorNotification("There was an issue signing out"); createErrorNotification("There was an issue signing out");
} }
}, [createErrorNotification, setTimedOut]); }, [createErrorNotification, setTimedOut, mounted]);
useEffect(() => { doSignOut() }, [doSignOut]); useEffect(() => { doSignOut() }, [doSignOut]);