[FEATURE] Add API docs and swagger-ui (#1544)

* [FEATURE] Add API docs and swagger-ui

This change will serve out swagger-ui at the `/api/` root path.

* Update descriptions and summaries in API spec

* Utilise frontend assets from unit testing for Docker build steps

* Fix tag for /api/user/* endpoints

* Fix response schema for /api/user/info/2fa_method

* Template and inject the session name during runtime into swagger-ui

This change also factorises and renames index.go into template.go, this can now be generically utilised to template any file.

* Fix integration tests

* Add U2F endpoints

* Change swagger directory to api

This change is to more closely conform to the golang-standards project layout.

* Add authentication for u2f endpoints

* Modify u2f endpoint descriptions

* Rename and fix u2f 2fa sign endpoints

* Fix request body for /api/secondfactor/u2f/sign endpoint

Co-authored-by: James Elliott <james-d-elliott@users.noreply.github.com>
This commit is contained in:
Amir Zarrinkafsh 2021-01-03 15:28:46 +11:00 committed by GitHub
parent 689fd7cb95
commit 3487fd392e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 952 additions and 110 deletions

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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 && \

View File

@ -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 && \

View File

@ -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 && \

View File

@ -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 =====

60
api/index.html Normal file
View File

@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="{{.Base}}/api/swagger-ui.css" >
<link rel="icon" type="image/png" href="{{.Base}}/api/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="{{.Base}}/api/favicon-16x16.png" sizes="16x16" />
<style nonce="{{.CSPNonce}}">
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="{{.Base}}/api/swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="{{.Base}}/api/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script nonce="{{.CSPNonce}}">
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "{{.Base}}/api/openapi.yml",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>

752
api/openapi.yml Normal file
View File

@ -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

View File

@ -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()

View File

@ -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"

View File

@ -1,3 +1,6 @@
package server
const apiFile = "openapi.yml"
const indexFile = "index.html"
const dev = "dev"

View File

@ -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
}
}
}

View File

@ -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"))

View File

@ -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))

View File

@ -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
}
}
}

1
web/.gitignore vendored
View File

@ -17,6 +17,7 @@
.env.development.local
.env.test.local
.env.production.local
.eslintcache
npm-debug.log*
yarn-debug.log*