diff --git a/.buildkite/hooks/pre-command b/.buildkite/hooks/pre-command
index 2a0229306..399bd8ed4 100755
--- a/.buildkite/hooks/pre-command
+++ b/.buildkite/hooks/pre-command
@@ -14,6 +14,12 @@ if [[ $BUILDKITE_LABEL =~ ":selenium:" ]]; then
docker tag authelia/authelia authelia:dist
fi
+if [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]] && [[ "${ARCH}" != "coverage" ]]; then
+ echo "--- :react: :swagger: Extract frontend assets"
+ buildkite-agent artifact download "authelia-public_html.tar.gz" .
+ tar xzf authelia-public_html.tar.gz
+fi
+
if [[ $BUILDKITE_LABEL =~ ":docker: Deploy Image" ]]; then
buildkite-agent artifact download "authelia-image-${ARCH}*" .
zstdcat authelia-image-"${ARCH}".tar.zst | docker load
diff --git a/.buildkite/pipeline.sh b/.buildkite/pipeline.sh
index f7f485336..dbf1db610 100755
--- a/.buildkite/pipeline.sh
+++ b/.buildkite/pipeline.sh
@@ -37,6 +37,7 @@ steps:
artifact_paths:
- "authelia-public_html.tar.gz"
- "authelia-public_html.tar.gz.sha256"
+ key: "unit-test"
if: build.env("CI_BYPASS") != "true"
- wait:
diff --git a/.buildkite/steps/buildimages.sh b/.buildkite/steps/buildimages.sh
index 8ad025f13..65aa2cfb6 100755
--- a/.buildkite/steps/buildimages.sh
+++ b/.buildkite/steps/buildimages.sh
@@ -17,6 +17,8 @@ if [[ "${BUILD_ARCH}" != "coverage" ]]; then
cat << EOF
- "authelia-${BUILD_OS}-${BUILD_ARCH}.tar.gz"
- "authelia-${BUILD_OS}-${BUILD_ARCH}.tar.gz.sha256"
+ depends_on:
+ - "unit-test"
EOF
fi
cat << EOF
diff --git a/Dockerfile b/Dockerfile
index 7a059d2a6..2826bc3a3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,3 @@
-# ========================================
-# ===== Build image for the frontend =====
-# ========================================
-FROM node:15-alpine AS builder-frontend
-
-WORKDIR /node/src/app
-COPY web .
-
-# Install the dependencies and build
-RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build
-
# =======================================
# ===== Build image for the backend =====
# =======================================
@@ -23,12 +12,12 @@ RUN apk --no-cache add gcc musl-dev
WORKDIR /go/src/app
COPY go.mod go.sum config.template.yml ./
-COPY --from=builder-frontend /node/src/app/build public_html
RUN go mod download
COPY cmd cmd
COPY internal internal
+COPY public_html public_html
# Prepare static files to be embedded in Go binary
RUN go get -u aletheia.icu/broccoli && \
diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7
index bb5d1a8b2..67132b4a2 100644
--- a/Dockerfile.arm32v7
+++ b/Dockerfile.arm32v7
@@ -1,14 +1,3 @@
-# ========================================
-# ===== Build image for the frontend =====
-# ========================================
-FROM node:15-alpine AS builder-frontend
-
-WORKDIR /node/src/app
-COPY web .
-
-# Install the dependencies and build
-RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build
-
# =======================================
# ===== Build image for the backend =====
# =======================================
@@ -26,12 +15,12 @@ RUN apk --no-cache add curl && \
WORKDIR /go/src/app
COPY go.mod go.sum config.template.yml ./
-COPY --from=builder-frontend /node/src/app/build public_html
RUN go mod download
COPY cmd cmd
COPY internal internal
+COPY public_html public_html
# Prepare static files to be embedded in Go binary
RUN go get -u aletheia.icu/broccoli && \
diff --git a/Dockerfile.arm64v8 b/Dockerfile.arm64v8
index a389b80ed..a4f655cbe 100644
--- a/Dockerfile.arm64v8
+++ b/Dockerfile.arm64v8
@@ -1,14 +1,3 @@
-# ========================================
-# ===== Build image for the frontend =====
-# ========================================
-FROM node:15-alpine AS builder-frontend
-
-WORKDIR /node/src/app
-COPY web .
-
-# Install the dependencies and build
-RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build
-
# =======================================
# ===== Build image for the backend =====
# =======================================
@@ -26,12 +15,12 @@ RUN apk --no-cache add curl && \
WORKDIR /go/src/app
COPY go.mod go.sum config.template.yml ./
-COPY --from=builder-frontend /node/src/app/build public_html
RUN go mod download
COPY cmd cmd
COPY internal internal
+COPY public_html public_html
# Prepare static files to be embedded in Go binary
RUN go get -u aletheia.icu/broccoli && \
diff --git a/Dockerfile.coverage b/Dockerfile.coverage
index 2be641565..518e7755b 100644
--- a/Dockerfile.coverage
+++ b/Dockerfile.coverage
@@ -7,7 +7,8 @@ WORKDIR /node/src/app
COPY web .
# Install the dependencies and build
-RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn coverage
+RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn coverage && \
+mkdir -p /node/src/app/build/api && cd /node/src/app/build/api/ && touch index.html openapi.yml
# =======================================
# ===== Build image for the backend =====
diff --git a/api/index.html b/api/index.html
new file mode 100644
index 000000000..070b716e4
--- /dev/null
+++ b/api/index.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+ Swagger UI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/openapi.yml b/api/openapi.yml
new file mode 100644
index 000000000..646f5dd6a
--- /dev/null
+++ b/api/openapi.yml
@@ -0,0 +1,752 @@
+---
+openapi: 3.0.0
+info:
+ title: Authelia API
+ description: Authelia is an open-source authentication and authorization server providing 2-factor authentication and single sign-on (SSO) for your applications via a web portal.
+ contact:
+ name: Authelia Support
+ url: https://github.com/authelia/authelia#contact-options
+ email: team@authelia.com
+ license:
+ name: Apache 2.0
+ url: https://www.apache.org/licenses/LICENSE-2.0
+ version: 1.0.0
+tags:
+ - name: State
+ description: Configuration, health and state endpoints
+ - name: Authentication
+ description: Authentication and verification endpoints
+ - name: Password Reset
+ description: Password reset endpoints
+ - name: User Information
+ description: User configuration endpoints
+ - name: Second Factor
+ description: TOTP, U2F and Duo endpoints
+paths:
+ /api/configuration:
+ get:
+ tags:
+ - State
+ summary: Application Configuration
+ description: The configuration endpoint provides detailed information including available second factor methods, if any second factor policies exist and the TOTP period configuration.
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.configuration.ConfigurationBody'
+ "403":
+ description: Forbidden
+ security:
+ - authelia_auth: [ ]
+ /api/health:
+ get:
+ tags:
+ - State
+ summary: Application Health
+ description: The health check endpoint provides information about the health of Authelia.
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ /api/state:
+ get:
+ tags:
+ - State
+ summary: User Application State
+ description: The state endpoint provides detailed information including the user, current authenticate level and Authelia's configured default redirection URL.
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.StateResponse'
+ /api/verify:
+ get:
+ tags:
+ - Authentication
+ summary: Verification
+ description: The verify endpoint provides the ability to verify if a user has the necessary permissions to access a specified domain.
+ parameters:
+ - name: X-Original-URL
+ in: header
+ description: Redirection URL
+ required: true
+ style: simple
+ explode: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Successful Operation
+ headers:
+ remote-user:
+ description: Username
+ schema:
+ type: string
+ example: john
+ remote-name:
+ description: Name
+ schema:
+ type: string
+ example: John Doe
+ remote-email:
+ description: Email
+ schema:
+ type: string
+ example: john.doe@authelia.com
+ remote-groups:
+ description: Comma separated list of Groups
+ schema:
+ type: string
+ example: admin,devs
+ "401":
+ description: Unauthorized
+ security:
+ - authelia_auth: []
+ head:
+ tags:
+ - Authentication
+ summary: Verification
+ description: The verify endpoint provides the ability to verify if a user has the necessary permissions to access a specified domain.
+ parameters:
+ - name: X-Original-URL
+ in: header
+ description: Redirection URL
+ required: true
+ style: simple
+ explode: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Successful Operation
+ headers:
+ remote-user:
+ description: Username
+ schema:
+ type: string
+ example: john
+ remote-name:
+ description: Name
+ schema:
+ type: string
+ example: John Doe
+ remote-email:
+ description: Email
+ schema:
+ type: string
+ example: john.doe@authelia.com
+ remote-groups:
+ description: Comma separated list of Groups
+ schema:
+ type: string
+ example: admin,devs
+ "401":
+ description: Unauthorized
+ security:
+ - authelia_auth: []
+ /api/firstfactor:
+ post:
+ tags:
+ - Authentication
+ summary: Login
+ description: The firstfactor endpoint allows a user to login and generates an authentication cookie for authorization.
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.firstFactorRequestBody'
+ responses:
+ "200":
+ description: Successful Operation
+ headers:
+ Set-Cookie:
+ style: simple
+ explode: false
+ schema:
+ type: string
+ example: authelia_session=kTTCSLupEUirZVfLeZTijezewFQnNOgs; Path=/
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.redirectResponse'
+ "401":
+ description: Unauthorized
+ security:
+ - authelia_auth: []
+ /api/logout:
+ post:
+ tags:
+ - Authentication
+ summary: Logout
+ description: The logout endpoint allows a user to logout and destroy a sesssion.
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ security:
+ - authelia_auth: [ ]
+ /api/reset-password/identity/start:
+ post:
+ tags:
+ - Password Reset
+ summary: Identity Verification Token Creation
+ description: "This endpoint is step 1 of 3 in the password reset process.\n\nIt validates the user session and sends the user an email with a token and a link to reset their password. This step also generates a session cookie for the rest of the process.\n\nThe same session cookie must be used for all steps in this process."
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.resetPasswordStep1RequestBody'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ security:
+ - authelia_auth: []
+ /api/reset-password/identity/finish:
+ post:
+ tags:
+ - Password Reset
+ summary: Identity Verification Token Validation
+ description: "This endpoint is step 2 of 3 in the password reset process.\n\nIt validates the user session and reset token.\n\nThe same session cookie must be used for all steps in this process."
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.IdentityVerificationFinishBody'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ security:
+ - authelia_auth: []
+ /api/reset-password:
+ post:
+ tags:
+ - Password Reset
+ summary: Password Reset
+ description: "This endpoint is step 3 of 3 in the password reset process.\n\nIt validates the user session and changes the password.\n\nThe same session cookie must be used for all steps in this process."
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.resetPasswordStep2RequestBody'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ security:
+ - authelia_auth: []
+ /api/user/info:
+ get:
+ tags:
+ - User Information
+ summary: User Configuration
+ description: The user info endpoint provides detailed information including a users display name, preferred and registered second factor method(s).
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.UserInfo'
+ "403":
+ description: Forbidden
+ security:
+ - authelia_auth: [ ]
+ /api/user/info/2fa_method:
+ post:
+ tags:
+ - User Information
+ summary: User Configuration
+ description: The user info 2fa_method endpoint sets the users preferred second factor method.
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.UserInfo.MethodBody'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ "403":
+ description: Forbidden
+ security:
+ - authelia_auth: [ ]
+ /api/secondfactor/totp/identity/start:
+ post:
+ tags:
+ - Second Factor
+ summary: Identity Verification TOTP Token Creation
+ description: "This endpoint performs identity verification to begin the TOTP device registration process.\n\nThe session generated from this endpoint must be utilised for the subsequent step in the `/api/secondfactor/totp/identity/finish` endpoint."
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ security:
+ - authelia_auth: []
+ /api/secondfactor/totp/identity/finish:
+ post:
+ tags:
+ - Second Factor
+ summary: Identity Verification TOTP Token Validation and Device Creation
+ description: "This endpoint performs identity and token verification, upon success also generates TOTP device secret and registers said device.\n\nThe session cookie generated from the `/api/secondfactor/totp/identity/start` endpoint must be utilised for the step here"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.IdentityVerificationFinishBody'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.TOTPKeyResponse'
+ security:
+ - authelia_auth: []
+ /api/secondfactor/totp:
+ post:
+ tags:
+ - Second Factor
+ summary: Second Factor Authentication - TOTP
+ description: "This endpoint performs second factor authentication with a TOTP key."
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.signTOTPRequestBody'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.redirectResponse'
+ "401":
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.ErrorResponse'
+ security:
+ - authelia_auth: []
+ /api/secondfactor/u2f/sign_request:
+ post:
+ tags:
+ - Second Factor
+ summary: Second Factor Authentication - U2F (Request)
+ description: "This endpoint starts the second factor authentication process with the U2F key."
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/u2f.WebSignRequest'
+ "401":
+ description: Unauthorized
+ security:
+ - authelia_auth: []
+ /api/secondfactor/u2f/sign:
+ post:
+ tags:
+ - Second Factor
+ summary: Second Factor Authentication - U2F
+ description: "This endpoint completes second factor authentication with a U2F key."
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/handlers.signU2FRequestBody"
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.redirectResponse'
+ "401":
+ description: Unauthorized
+ security:
+ - authelia_auth: []
+ /api/secondfactor/u2f/identity/start:
+ post:
+ tags:
+ - Second Factor
+ summary: Identity Verification U2F Token Creation
+ description: "This endpoint performs identity verification to begin the U2F device registration process.\n\nThe session generated from this endpoint must be utilised for the subsequent steps in the `/api/secondfactor/u2f/identity/finish` and `/api/secondfactor/u2f/register` endpoints."
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ security:
+ - authelia_auth: []
+ /api/secondfactor/u2f/identity/finish:
+ post:
+ tags:
+ - Second Factor
+ summary: Identity Verification U2F Token Validation
+ description: "This endpoint performs identity and token verification, upon success generates a U2F device registration challenge.\n\nThe session cookie generated from the `/api/secondfactor/u2f/identity/start` endpoint must be utilised for the subsequent steps here and in the `/api/secondfactor/u2f/register` endpoint."
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.IdentityVerificationFinishBody'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/u2f.WebRegisterRequest'
+ security:
+ - authelia_auth: []
+ /api/secondfactor/u2f/register:
+ post:
+ tags:
+ - Second Factor
+ summary: U2F Device Registration
+ description: "This endpoint performs U2F device registration."
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/u2f.RegisterResponse'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/middlewares.OkResponse'
+ security:
+ - authelia_auth: []
+ /api/secondfactor/duo:
+ post:
+ tags:
+ - Second Factor
+ summary: Second Factor Authentication - Duo Mobile Push
+ description: "This endpoint performs second factor authentication with a Duo Mobile Push."
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.signDuoRequestBody'
+ responses:
+ "200":
+ description: Successful Operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/handlers.redirectResponse'
+ "401":
+ description: Unauthorized
+ security:
+ - authelia_auth: []
+components:
+ schemas:
+ handlers.configuration.ConfigurationBody:
+ type: object
+ properties:
+ status:
+ type: string
+ example: OK
+ data:
+ type: object
+ properties:
+ available_methods:
+ type: array
+ items:
+ type: string
+ example: [totp, u2f, mobile_push]
+ second_factor_enabled:
+ type: boolean
+ description: If second factor is enabled.
+ totp_period:
+ type: integer
+ example: 30
+ handlers.firstFactorRequestBody:
+ required:
+ - username
+ - password
+ type: object
+ properties:
+ username:
+ type: string
+ example: john
+ password:
+ type: string
+ example: password
+ targetURL:
+ type: string
+ example: https://home.example.com
+ keepMeLoggedIn:
+ type: boolean
+ example: true
+ handlers.redirectResponse:
+ type: object
+ properties:
+ status:
+ type: string
+ example: OK
+ data:
+ type: object
+ properties:
+ redirect:
+ type: string
+ example: https://home.example.com
+ handlers.resetPasswordStep1RequestBody:
+ required:
+ - username
+ type: object
+ properties:
+ username:
+ type: string
+ example: john
+ handlers.resetPasswordStep2RequestBody:
+ required:
+ - password
+ type: object
+ properties:
+ password:
+ type: string
+ example: password
+ handlers.signDuoRequestBody:
+ type: object
+ properties:
+ targetURL:
+ type: string
+ example: https://secure.example.com
+ handlers.signTOTPRequestBody:
+ type: object
+ properties:
+ token:
+ type: string
+ example: "123456"
+ targetURL:
+ type: string
+ example: https://secure.example.com
+ handlers.signU2FRequestBody:
+ type: object
+ properties:
+ targetURL:
+ type: string
+ example: https://secure.example.com
+ signResponse:
+ type: object
+ properties:
+ clientData:
+ type: string
+ example: 6prxyWqSsR6MXFchtQRzwZVTedWq7Zdc6XreLt6xRDXKeqJN7vzKAfYcKwRD3AT57bP4YFL4hbxat4LUysBNss
+ keyHandle:
+ type: string
+ example: pWgBrwr9meS5vArdffPtD4Px6AqZS7MfGEf776Rz438ujwHjeXwQEZuK53sRQ4wjeAgRCW4wX9VRj8dyKjc273
+ signatureData:
+ type: string
+ example: p3Pe26B6T2E7EEEc59P4p869qwxy8cQAU2ttyGtGrQHb4XL2ZxCpWrawsSHNSTRZQd7jEW59Y3Ku9vSNRzj7Ly
+ handlers.StateResponse:
+ type: object
+ properties:
+ status:
+ type: string
+ example: OK
+ data:
+ type: object
+ properties:
+ username:
+ type: string
+ example: john
+ authentication_level:
+ type: integer
+ example: 1
+ default_redirection_url:
+ type: string
+ example: https://home.example.com
+ handlers.TOTPKeyResponse:
+ type: object
+ properties:
+ status:
+ type: string
+ example: OK
+ data:
+ type: object
+ properties:
+ base32_secret:
+ type: string
+ example: 5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q
+ otpauth_url:
+ type: string
+ example: otpauth://totp/auth.example.com:john?algorithm=SHA1&digits=6&issuer=auth.example.com&period=30&secret=5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q
+ handlers.UserInfo:
+ type: object
+ properties:
+ status:
+ type: string
+ example: OK
+ data:
+ type: object
+ properties:
+ display_name:
+ type: string
+ example: John Doe
+ method:
+ type: string
+ enum: [totp, u2f, mobile_push]
+ example: totp
+ has_u2f:
+ type: boolean
+ example: false
+ has_totp:
+ type: boolean
+ example: true
+ handlers.UserInfo.MethodBody:
+ required:
+ - method
+ type: object
+ properties:
+ method:
+ type: string
+ enum: [totp, u2f, mobile_push]
+ example: totp
+ middlewares.ErrorResponse:
+ type: object
+ properties:
+ status:
+ type: string
+ example: KO
+ message:
+ type: string
+ example: Authentication failed, please retry later.
+ middlewares.IdentityVerificationFinishBody:
+ required:
+ - token
+ type: object
+ properties:
+ token:
+ type: string
+ example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDc5MjU1OTYsImlzcyI6IkF1dGhlbGlhIiwiYWN0aW9uIjoiUmVzZXRQYXNzd29yZCIsInVzZXJuYW1lIjoiQW1pciJ9.636yqRrUCGCe4jsMCsonleX5CYWHncYqZum-YYb6VaY
+ middlewares.OkResponse:
+ type: object
+ properties:
+ status:
+ type: string
+ example: OK
+ data:
+ type: object
+ u2f.RegisterResponse:
+ type: object
+ properties:
+ version:
+ type: string
+ registrationData:
+ type: string
+ clientData:
+ type: string
+ u2f.WebRegisterRequest:
+ type: object
+ properties:
+ status:
+ type: string
+ example: OK
+ data:
+ type: object
+ properties:
+ appId:
+ type: string
+ example: https://auth.example.com
+ registerRequests:
+ type: array
+ items:
+ type: object
+ properties:
+ version:
+ type: string
+ example: U2F_V2
+ challenge:
+ type: string
+ example: XGYKUzSmTpM1KxxpekArviW0w0OU2pwwRAocgn8TkVQ
+ registeredKeys:
+ type: array
+ items:
+ type: object
+ properties:
+ appId:
+ type: string
+ example: https://auth.example.com
+ version:
+ type: string
+ example: U2F_V2
+ keyHandle:
+ type: string
+ example: pWgBrwr9meS5vArdffPtD4Px6AqZS7MfGEf776Rz438ujwHjeXwQEZuK53sRQ4wjeAgRCW4wX9VRj8dyKjc273
+ u2f.WebSignRequest:
+ type: object
+ properties:
+ status:
+ type: string
+ example: OK
+ data:
+ type: object
+ properties:
+ appId:
+ type: string
+ example: https://auth.example.com
+ challenge:
+ type: string
+ example: XGYKUzSmTpM1KxxpekArviW0w0OU2pwwRAocgn8TkVQ
+ registeredKeys:
+ type: array
+ items:
+ type: object
+ properties:
+ appId:
+ type: string
+ example: https://auth.example.com
+ version:
+ type: string
+ example: U2F_V2
+ keyHandle:
+ type: string
+ example: pWgBrwr9meS5vArdffPtD4Px6AqZS7MfGEf776Rz438ujwHjeXwQEZuK53sRQ4wjeAgRCW4wX9VRj8dyKjc273
+ securitySchemes:
+ authelia_auth:
+ type: apiKey
+ name: "{{.Session}}"
+ in: cookie
\ No newline at end of file
diff --git a/cmd/authelia-scripts/cmd_build.go b/cmd/authelia-scripts/cmd_build.go
index 21aa96695..ac95f6b89 100644
--- a/cmd/authelia-scripts/cmd_build.go
+++ b/cmd/authelia-scripts/cmd_build.go
@@ -17,32 +17,63 @@ func buildAutheliaBinary() {
"GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=1")
err := cmd.Run()
-
if err != nil {
- panic(err)
+ log.Fatal(err)
}
}
func buildFrontend() {
- // Install npm dependencies.
cmd := utils.CommandWithStdout("yarn", "install")
cmd.Dir = webDirectory
- if err := cmd.Run(); err != nil {
+ err := cmd.Run()
+ if err != nil {
log.Fatal(err)
}
- // Then build the frontend.
cmd = utils.CommandWithStdout("yarn", "build")
cmd.Dir = webDirectory
cmd.Env = append(os.Environ(), "INLINE_RUNTIME_CHUNK=false")
- if err := cmd.Run(); err != nil {
+ err = cmd.Run()
+ if err != nil {
log.Fatal(err)
}
- if err := os.Rename("web/build", "./public_html"); err != nil {
+ err = os.Rename("web/build", "./public_html")
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func buildSwagger() {
+ swaggerVer := "3.38.0"
+ cmd := utils.CommandWithStdout("bash", "-c", "wget -q https://github.com/swagger-api/swagger-ui/archive/v"+swaggerVer+".tar.gz -O ./v"+swaggerVer+".tar.gz")
+
+ err := cmd.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ cmd = utils.CommandWithStdout("cp", "-r", "api", "public_html")
+
+ err = cmd.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ cmd = utils.CommandWithStdout("tar", "-C", swaggerDirectory, "--exclude=index.html", "--strip-components=2", "-xf", "v"+swaggerVer+".tar.gz", "swagger-ui-"+swaggerVer+"/dist")
+
+ err = cmd.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ cmd = utils.CommandWithStdout("rm", "./v"+swaggerVer+".tar.gz")
+
+ err = cmd.Run()
+ if err != nil {
log.Fatal(err)
}
}
@@ -51,30 +82,28 @@ func generateEmbeddedAssets() {
cmd := utils.CommandWithStdout("go", "get", "-u", "aletheia.icu/broccoli")
err := cmd.Run()
-
if err != nil {
- panic(err)
+ log.Fatal(err)
}
cmd = utils.CommandWithStdout("go", "generate", ".")
cmd.Dir = "internal/configuration"
err = cmd.Run()
-
if err != nil {
- panic(err)
+ log.Fatal(err)
}
cmd = utils.CommandWithStdout("go", "generate", ".")
cmd.Dir = "internal/server"
err = cmd.Run()
-
if err != nil {
- panic(err)
+ log.Fatal(err)
}
- if err := os.Rename("./public_html", OutputDir+"/public_html"); err != nil {
+ err = os.Rename("./public_html", OutputDir+"/public_html")
+ if err != nil {
log.Fatal(err)
}
}
@@ -89,12 +118,15 @@ func Build(cobraCmd *cobra.Command, args []string) {
err := os.MkdirAll(OutputDir, os.ModePerm)
if err != nil {
- panic(err)
+ log.Fatal(err)
}
log.Debug("Building Authelia frontend...")
buildFrontend()
+ log.Debug("Building swagger-ui frontend...")
+ buildSwagger()
+
log.Debug("Building Authelia Go binary...")
generateEmbeddedAssets()
buildAutheliaBinary()
diff --git a/cmd/authelia-scripts/const.go b/cmd/authelia-scripts/const.go
index efb83cde0..fc041fdf4 100644
--- a/cmd/authelia-scripts/const.go
+++ b/cmd/authelia-scripts/const.go
@@ -12,4 +12,5 @@ var IntermediateDockerImageName = "authelia:dist"
const masterTag = "master"
const stringFalse = "false"
const stringTrue = "true"
+const swaggerDirectory = "public_html/api"
const webDirectory = "web"
diff --git a/internal/server/const.go b/internal/server/const.go
index d092ebf7d..00968a810 100644
--- a/internal/server/const.go
+++ b/internal/server/const.go
@@ -1,3 +1,6 @@
package server
+const apiFile = "openapi.yml"
+const indexFile = "index.html"
+
const dev = "dev"
diff --git a/internal/server/index.go b/internal/server/index.go
deleted file mode 100644
index ad4f36ac0..000000000
--- a/internal/server/index.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package server
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "text/template"
-
- "github.com/valyala/fasthttp"
-
- "github.com/authelia/authelia/internal/logging"
- "github.com/authelia/authelia/internal/utils"
-)
-
-var alphaNumericRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
-
-// ServeIndex serve the index.html file with nonce generated for supporting
-// restrictive CSP while using material-ui from the embedded virtual filesystem.
-//go:generate broccoli -src ../../public_html -o public_html
-func ServeIndex(publicDir, base, rememberMe, resetPassword string) fasthttp.RequestHandler {
- f, err := br.Open(publicDir + "/index.html")
- if err != nil {
- logging.Logger().Fatalf("Unable to open index.html: %v", err)
- }
-
- b, err := ioutil.ReadAll(f)
- if err != nil {
- logging.Logger().Fatalf("Unable to read index.html: %v", err)
- }
-
- tmpl, err := template.New("index").Parse(string(b))
- if err != nil {
- logging.Logger().Fatalf("Unable to parse index.html template: %v", err)
- }
-
- return func(ctx *fasthttp.RequestCtx) {
- nonce := utils.RandomString(32, alphaNumericRunes)
-
- ctx.SetContentType("text/html; charset=utf-8")
-
- if os.Getenv("ENVIRONMENT") == dev {
- ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self' 'unsafe-eval'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce))
- } else {
- ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce))
- }
-
- err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, CSPNonce, RememberMe, ResetPassword string }{Base: base, CSPNonce: nonce, RememberMe: rememberMe, ResetPassword: resetPassword})
- if err != nil {
- ctx.Error("An error occurred", 503)
- logging.Logger().Errorf("Unable to execute template: %v", err)
-
- return
- }
- }
-}
diff --git a/internal/server/public_html.gen.go b/internal/server/public_html.gen.go
index 509991096..518dbf23b 100644
--- a/internal/server/public_html.gen.go
+++ b/internal/server/public_html.gen.go
@@ -4,4 +4,4 @@ import "aletheia.icu/broccoli/fs"
// Mock the embedded filesystem for unit tests. The bundle is built from an empty file and
// allows to run the dev workflow without failure.
-var br = fs.New(false, []byte("\x1b~\x00\x80\x8d\x94n\xc2|\x84J\xf7\xbfn\xfd\xf7w;.\x8d m\xb2&\xd1Z\xec\xb2\x05\xb9\xc00\x8a\xf7(\x80^78\t(\f\f\xc3p\xc2\xc1\x06[a\xa2\xb3\xa4P\xe5\xa14\xfb\x19\xb2cp\xf6\x90-Z\xb2\x11\xe0l\xa1\x80\\\x95Vh\t\xc5\x06\x16\xfa\x8c\xc0\"!\xa5\xcf\xf7$\x9a\xb2\a`\xc6\x18\xc8~\xce8\r\x16Z\x9d\xc3\xe3\xff\x00"))
+var br = fs.New(false, []byte("\x1b\xf7\x00\x00ħ?\xf5\xbd\xaci\x936'\x9e\x8b\xe5*\xda\xfbֵ@6\x96\xa0\"e\xc9xz\x92eaH)\aA\x18a`m\xcd#\xfd\xc1\xbe\x1d\xe4h\x87:\xd9h/2~\x17\x92'w~J\x94\xe6\x178?\x80n\xbe˔\xea@\x95J/n\x82V\xfa\x02\x1etB\x81\xa0t\xa2·\xf5\xfe\x02̿\xf9\x05E\xb2Q\xcb\xe5\xea\xb6\xdfQ\xdfS\n\x0e\xff蓼\xe4\xefR-Nkʍ\x1d\xed\xd5 [&*\x0f\f\x83\xd6\xec\x92\v\x1b\x19\xb4\x1d\x91\x00"))
diff --git a/internal/server/server.go b/internal/server/server.go
index 6c75a423e..4168acadd 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -25,22 +25,28 @@ import (
// StartServer start Authelia server with the given configuration and providers.
func StartServer(configuration schema.Configuration, providers middlewares.Providers) {
autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers)
- embeddedAssets := "/public_html"
+ embeddedAssets := "/public_html/"
+ swaggerAssets := embeddedAssets + "api/"
rememberMe := strconv.FormatBool(configuration.Session.RememberMeDuration != "0")
resetPassword := strconv.FormatBool(!configuration.AuthenticationBackend.DisableResetPassword)
rootFiles := []string{"favicon.ico", "manifest.json", "robots.txt"}
- serveIndexHandler := ServeIndex(embeddedAssets, configuration.Server.Path, rememberMe, resetPassword)
+ serveIndexHandler := ServeTemplatedFile(embeddedAssets, indexFile, configuration.Server.Path, configuration.Session.Name, rememberMe, resetPassword)
+ serveSwaggerHandler := ServeTemplatedFile(swaggerAssets, indexFile, configuration.Server.Path, configuration.Session.Name, rememberMe, resetPassword)
+ serveSwaggerAPIHandler := ServeTemplatedFile(swaggerAssets, apiFile, configuration.Server.Path, configuration.Session.Name, rememberMe, resetPassword)
r := router.New()
r.GET("/", serveIndexHandler)
+ r.GET("/api/", serveSwaggerHandler)
+ r.GET("/api/"+apiFile, serveSwaggerAPIHandler)
for _, f := range rootFiles {
r.GET("/"+f, fasthttpadaptor.NewFastHTTPHandler(br.Serve(embeddedAssets)))
}
r.GET("/static/{filepath:*}", fasthttpadaptor.NewFastHTTPHandler(br.Serve(embeddedAssets)))
+ r.GET("/api/{filepath:*}", fasthttpadaptor.NewFastHTTPHandler(br.Serve(embeddedAssets)))
r.GET("/api/health", autheliaMiddleware(handlers.HealthGet))
r.GET("/api/state", autheliaMiddleware(handlers.StateGet))
diff --git a/internal/server/template.go b/internal/server/template.go
new file mode 100644
index 000000000..209727b53
--- /dev/null
+++ b/internal/server/template.go
@@ -0,0 +1,65 @@
+package server
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "text/template"
+
+ "github.com/valyala/fasthttp"
+
+ "github.com/authelia/authelia/internal/logging"
+ "github.com/authelia/authelia/internal/utils"
+)
+
+var alphaNumericRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+
+// ServeTemplatedFile serves a templated version of a specified file,
+// this is utilised to pass information between the backend and frontend
+// and generate a nonce to support a restrictive CSP while using material-ui.
+//go:generate broccoli -src ../../public_html -o public_html
+func ServeTemplatedFile(publicDir, file, base, session, rememberMe, resetPassword string) fasthttp.RequestHandler {
+ f, err := br.Open(publicDir + file)
+ if err != nil {
+ logging.Logger().Fatalf("Unable to open %s: %s", file, err)
+ }
+
+ b, err := ioutil.ReadAll(f)
+ if err != nil {
+ logging.Logger().Fatalf("Unable to read %s: %s", file, err)
+ }
+
+ tmpl, err := template.New("file").Parse(string(b))
+ if err != nil {
+ logging.Logger().Fatalf("Unable to parse %s template: %s", file, err)
+ }
+
+ return func(ctx *fasthttp.RequestCtx) {
+ nonce := utils.RandomString(32, alphaNumericRunes)
+
+ switch extension := filepath.Ext(file); extension {
+ case ".html":
+ ctx.SetContentType("text/html; charset=utf-8")
+ default:
+ ctx.SetContentType("text/plain; charset=utf-8")
+ }
+
+ switch {
+ case os.Getenv("ENVIRONMENT") == dev:
+ ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self' 'unsafe-eval'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce))
+ case publicDir == "/public_html/api/":
+ ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("base-uri 'self' ; default-src 'self' ; img-src 'self' https://validator.swagger.io data: ; object-src 'none' ; script-src 'self' 'unsafe-inline' 'nonce-%s' ; style-src 'self' 'nonce-%s'", nonce, nonce))
+ default:
+ ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self' ; object-src 'none'; style-src 'self' 'nonce-%s'", nonce))
+ }
+
+ err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, CSPNonce, Session, RememberMe, ResetPassword string }{Base: base, CSPNonce: nonce, Session: session, RememberMe: rememberMe, ResetPassword: resetPassword})
+ if err != nil {
+ ctx.Error("An error occurred", 503)
+ logging.Logger().Errorf("Unable to execute template: %v", err)
+
+ return
+ }
+ }
+}
diff --git a/web/.gitignore b/web/.gitignore
index 4d29575de..00ec607c8 100644
--- a/web/.gitignore
+++ b/web/.gitignore
@@ -17,6 +17,7 @@
.env.development.local
.env.test.local
.env.production.local
+.eslintcache
npm-debug.log*
yarn-debug.log*