perf(server): cached openapi document (#4674)
This should lead to a small performance gain by caching the openapi.yml with etags as well as eliminating the use of nonce crypto generation when not required.pull/4682/head
parent
acaadd81cb
commit
1c3219e93f
|
@ -9,6 +9,7 @@ yaml-files:
|
||||||
- '.yamllint'
|
- '.yamllint'
|
||||||
|
|
||||||
ignore: |
|
ignore: |
|
||||||
|
api/openapi.yml
|
||||||
docs/pnpm-lock.yaml
|
docs/pnpm-lock.yaml
|
||||||
internal/configuration/test_resources/config_bad_quoting.yml
|
internal/configuration/test_resources/config_bad_quoting.yml
|
||||||
web/pnpm-lock.yaml
|
web/pnpm-lock.yaml
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Swagger UI</title>
|
<title>Swagger UI</title>
|
||||||
<link rel="stylesheet" type="text/css" href="{{.Base}}/api/swagger-ui.css" />
|
<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-32x32.png" sizes="32x32" />
|
||||||
<link rel="icon" type="image/png" href="{{.Base}}/api/favicon-16x16.png" sizes="16x16" />
|
<link rel="icon" type="image/png" href="{{ .Base }}/api/favicon-16x16.png" sizes="16x16" />
|
||||||
<style nonce="{{.CSPNonce}}">
|
<style nonce="{{ .CSPNonce }}">
|
||||||
html
|
html
|
||||||
{
|
{
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -33,13 +33,13 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="swagger-ui"></div>
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
<script src="{{.Base}}/api/swagger-ui-bundle.js" charset="UTF-8"> </script>
|
<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 src="{{ .Base }}/api/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||||
<script nonce="{{.CSPNonce}}">
|
<script nonce="{{ .CSPNonce }}">
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
// Begin Swagger UI call region
|
// Begin Swagger UI call region
|
||||||
const ui = SwaggerUIBundle({
|
const ui = SwaggerUIBundle({
|
||||||
url: "{{.Base}}/api/openapi.yml",
|
url: "{{ .Base }}/api/openapi.yml",
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
presets: [
|
presets: [
|
||||||
|
|
454
api/openapi.yml
454
api/openapi.yml
|
@ -1,4 +1,3 @@
|
||||||
# yamllint disable rule:line-length
|
|
||||||
---
|
---
|
||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
|
@ -22,18 +21,24 @@ tags:
|
||||||
description: Configuration, health and state endpoints
|
description: Configuration, health and state endpoints
|
||||||
- name: Authentication
|
- name: Authentication
|
||||||
description: Authentication and verification endpoints
|
description: Authentication and verification endpoints
|
||||||
|
{{- if .PasswordReset }}
|
||||||
- name: Password Reset
|
- name: Password Reset
|
||||||
description: Password reset endpoints
|
description: Password reset endpoints
|
||||||
- name: User Information
|
- name: User Information
|
||||||
description: User configuration endpoints
|
description: User configuration endpoints
|
||||||
|
{{- end }}
|
||||||
|
{{- if (or .TOTP .Webauthn .Duo) }}
|
||||||
- name: Second Factor
|
- name: Second Factor
|
||||||
description: TOTP, Webauthn and Duo endpoints
|
description: TOTP, Webauthn and Duo endpoints
|
||||||
externalDocs:
|
externalDocs:
|
||||||
url: https://www.authelia.com/configuration/second-factor/introduction/
|
url: https://www.authelia.com/configuration/second-factor/introduction/
|
||||||
|
{{- end }}
|
||||||
|
{{- if .OpenIDConnect }}
|
||||||
- name: OpenID Connect 1.0
|
- name: OpenID Connect 1.0
|
||||||
description: OpenID Connect 1.0 and OAuth 2.0 Endpoints
|
description: OpenID Connect 1.0 and OAuth 2.0 Endpoints
|
||||||
externalDocs:
|
externalDocs:
|
||||||
url: https://www.authelia.com/integration/openid-connect/introduction/
|
url: https://www.authelia.com/integration/openid-connect/introduction/
|
||||||
|
{{- end }}
|
||||||
paths:
|
paths:
|
||||||
/api/configuration:
|
/api/configuration:
|
||||||
get:
|
get:
|
||||||
|
@ -97,280 +102,8 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/handlers.StateResponse'
|
$ref: '#/components/schemas/handlers.StateResponse'
|
||||||
/api/verify:
|
/api/verify:
|
||||||
get:
|
{{- range $method := list "get" "head" "options" "post" "put" "patch" "delete" "trace" }}
|
||||||
tags:
|
{{ $method }}:
|
||||||
- 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:
|
|
||||||
- $ref: '#/components/parameters/originalURLParam'
|
|
||||||
- $ref: '#/components/parameters/forwardedMethodParam'
|
|
||||||
- $ref: '#/components/parameters/authParam'
|
|
||||||
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:
|
|
||||||
- $ref: '#/components/parameters/originalURLParam'
|
|
||||||
- $ref: '#/components/parameters/forwardedMethodParam'
|
|
||||||
- $ref: '#/components/parameters/authParam'
|
|
||||||
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: []
|
|
||||||
options:
|
|
||||||
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:
|
|
||||||
- $ref: '#/components/parameters/originalURLParam'
|
|
||||||
- $ref: '#/components/parameters/forwardedMethodParam'
|
|
||||||
- $ref: '#/components/parameters/authParam'
|
|
||||||
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: []
|
|
||||||
post:
|
|
||||||
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:
|
|
||||||
- $ref: '#/components/parameters/originalURLParam'
|
|
||||||
- $ref: '#/components/parameters/forwardedMethodParam'
|
|
||||||
- $ref: '#/components/parameters/authParam'
|
|
||||||
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: []
|
|
||||||
put:
|
|
||||||
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:
|
|
||||||
- $ref: '#/components/parameters/originalURLParam'
|
|
||||||
- $ref: '#/components/parameters/forwardedMethodParam'
|
|
||||||
- $ref: '#/components/parameters/authParam'
|
|
||||||
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: []
|
|
||||||
patch:
|
|
||||||
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:
|
|
||||||
- $ref: '#/components/parameters/originalURLParam'
|
|
||||||
- $ref: '#/components/parameters/forwardedMethodParam'
|
|
||||||
- $ref: '#/components/parameters/authParam'
|
|
||||||
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: []
|
|
||||||
delete:
|
|
||||||
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:
|
|
||||||
- $ref: '#/components/parameters/originalURLParam'
|
|
||||||
- $ref: '#/components/parameters/forwardedMethodParam'
|
|
||||||
- $ref: '#/components/parameters/authParam'
|
|
||||||
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: []
|
|
||||||
trace:
|
|
||||||
tags:
|
tags:
|
||||||
- Authentication
|
- Authentication
|
||||||
summary: Verification
|
summary: Verification
|
||||||
|
@ -409,6 +142,7 @@ paths:
|
||||||
description: Unauthorized
|
description: Unauthorized
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
|
{{- end }}
|
||||||
/api/firstfactor:
|
/api/firstfactor:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
@ -477,6 +211,7 @@ paths:
|
||||||
$ref: '#/components/schemas/handlers.logoutResponseBody'
|
$ref: '#/components/schemas/handlers.logoutResponseBody'
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
|
{{- if .PasswordReset }}
|
||||||
/api/reset-password/identity/start:
|
/api/reset-password/identity/start:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
@ -494,7 +229,7 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/handlers.resetPasswordStep1RequestBody'
|
$ref: '#/components/schemas/handlers.PasswordResetStep1RequestBody'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Successful Operation
|
description: Successful Operation
|
||||||
|
@ -546,7 +281,7 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/handlers.resetPasswordStep2RequestBody'
|
$ref: '#/components/schemas/handlers.PasswordResetStep2RequestBody'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Successful Operation
|
description: Successful Operation
|
||||||
|
@ -556,6 +291,7 @@ paths:
|
||||||
$ref: '#/components/schemas/middlewares.OkResponse'
|
$ref: '#/components/schemas/middlewares.OkResponse'
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
|
{{- end }}
|
||||||
/api/user/info:
|
/api/user/info:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@ -593,25 +329,6 @@ paths:
|
||||||
description: Forbidden
|
description: Forbidden
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
/api/user/info/totp:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- User Information
|
|
||||||
summary: User TOTP Configuration
|
|
||||||
description: >
|
|
||||||
The user TOTP info endpoint provides information necessary to display the TOTP component to validate their
|
|
||||||
TOTP input such as the period/frequency and number of digits.
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Successful Operation
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/handlers.UserInfoTOTP'
|
|
||||||
"403":
|
|
||||||
description: Forbidden
|
|
||||||
security:
|
|
||||||
- authelia_auth: []
|
|
||||||
/api/user/info/2fa_method:
|
/api/user/info/2fa_method:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
@ -634,6 +351,26 @@ paths:
|
||||||
description: Forbidden
|
description: Forbidden
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
|
{{- if .TOTP }}
|
||||||
|
/api/user/info/totp:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- User Information
|
||||||
|
summary: User TOTP Configuration
|
||||||
|
description: >
|
||||||
|
The user TOTP info endpoint provides information necessary to display the TOTP component to validate their
|
||||||
|
TOTP input such as the period/frequency and number of digits.
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Successful Operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/handlers.UserInfoTOTP'
|
||||||
|
"403":
|
||||||
|
description: Forbidden
|
||||||
|
security:
|
||||||
|
- authelia_auth: []
|
||||||
/api/secondfactor/totp/identity/start:
|
/api/secondfactor/totp/identity/start:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
@ -706,6 +443,8 @@ paths:
|
||||||
$ref: '#/components/schemas/middlewares.ErrorResponse'
|
$ref: '#/components/schemas/middlewares.ErrorResponse'
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Webauthn }}
|
||||||
/api/secondfactor/webauthn/assertion:
|
/api/secondfactor/webauthn/assertion:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@ -812,6 +551,8 @@ paths:
|
||||||
$ref: '#/components/schemas/middlewares.OkResponse'
|
$ref: '#/components/schemas/middlewares.OkResponse'
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Duo }}
|
||||||
/api/secondfactor/duo:
|
/api/secondfactor/duo:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
@ -875,6 +616,8 @@ paths:
|
||||||
description: Unauthorized
|
description: Unauthorized
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
|
{{- end }}
|
||||||
|
{{- if .OpenIDConnect }}
|
||||||
/.well-known/openid-configuration:
|
/.well-known/openid-configuration:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@ -1389,6 +1132,7 @@ paths:
|
||||||
description: Forbidden
|
description: Forbidden
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
|
{{- end }}
|
||||||
components:
|
components:
|
||||||
parameters:
|
parameters:
|
||||||
originalURLParam:
|
originalURLParam:
|
||||||
|
@ -1609,7 +1353,8 @@ components:
|
||||||
redirect:
|
redirect:
|
||||||
type: string
|
type: string
|
||||||
example: https://home.example.com
|
example: https://home.example.com
|
||||||
handlers.resetPasswordStep1RequestBody:
|
{{- if .PasswordReset }}
|
||||||
|
handlers.PasswordResetStep1RequestBody:
|
||||||
required:
|
required:
|
||||||
- username
|
- username
|
||||||
type: object
|
type: object
|
||||||
|
@ -1617,7 +1362,7 @@ components:
|
||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
example: john
|
example: john
|
||||||
handlers.resetPasswordStep2RequestBody:
|
handlers.PasswordResetStep2RequestBody:
|
||||||
required:
|
required:
|
||||||
- password
|
- password
|
||||||
type: object
|
type: object
|
||||||
|
@ -1625,6 +1370,8 @@ components:
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
example: password
|
example: password
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Duo }}
|
||||||
handlers.bodySignDuoRequest:
|
handlers.bodySignDuoRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1641,23 +1388,7 @@ components:
|
||||||
format: uuid
|
format: uuid
|
||||||
pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'
|
pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'
|
||||||
example: "3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c"
|
example: "3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c"
|
||||||
handlers.bodySignTOTPRequest:
|
{{- end }}
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
token:
|
|
||||||
type: string
|
|
||||||
example: "123456"
|
|
||||||
targetURL:
|
|
||||||
type: string
|
|
||||||
example: https://secure.example.com
|
|
||||||
workflow:
|
|
||||||
type: string
|
|
||||||
example: openid_connect
|
|
||||||
workflowID:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'
|
|
||||||
example: "3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c"
|
|
||||||
handlers.StateResponse:
|
handlers.StateResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1676,7 +1407,24 @@ components:
|
||||||
default_redirection_url:
|
default_redirection_url:
|
||||||
type: string
|
type: string
|
||||||
example: https://home.example.com
|
example: https://home.example.com
|
||||||
handlers.TOTPKeyResponse:
|
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
|
type: object
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
|
@ -1684,13 +1432,6 @@ components:
|
||||||
example: OK
|
example: OK
|
||||||
data:
|
data:
|
||||||
type: object
|
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:
|
handlers.UserInfo:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1719,6 +1460,19 @@ components:
|
||||||
has_duo:
|
has_duo:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
|
handlers.UserInfo.MethodBody:
|
||||||
|
required:
|
||||||
|
- method
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- "totp"
|
||||||
|
- "webauthn"
|
||||||
|
- "mobile_push"
|
||||||
|
example: totp
|
||||||
|
{{- if .TOTP }}
|
||||||
handlers.UserInfoTOTP:
|
handlers.UserInfoTOTP:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1738,36 +1492,24 @@ components:
|
||||||
description: The number of digits defined in the users TOTP configuration
|
description: The number of digits defined in the users TOTP configuration
|
||||||
type: integer
|
type: integer
|
||||||
example: 6
|
example: 6
|
||||||
handlers.UserInfo.MethodBody:
|
handlers.bodySignTOTPRequest:
|
||||||
required:
|
|
||||||
- method
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
method:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- "totp"
|
|
||||||
- "webauthn"
|
|
||||||
- "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
|
type: object
|
||||||
properties:
|
properties:
|
||||||
token:
|
token:
|
||||||
type: string
|
type: string
|
||||||
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDc5MjU1OTYsImlzcyI6IkF1dGhlbGlhIiwiYWN0aW9uIjoiUmVzZXRQYXNzd29yZCIsInVzZXJuYW1lIjoiQW1pciJ9.636yqRrUCGCe4jsMCsonleX5CYWHncYqZum-YYb6VaY
|
example: "123456"
|
||||||
middlewares.OkResponse:
|
targetURL:
|
||||||
|
type: string
|
||||||
|
example: https://secure.example.com
|
||||||
|
workflow:
|
||||||
|
type: string
|
||||||
|
example: openid_connect
|
||||||
|
workflowID:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'
|
||||||
|
example: "3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c"
|
||||||
|
handlers.TOTPKeyResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
|
@ -1775,6 +1517,15 @@ components:
|
||||||
example: OK
|
example: OK
|
||||||
data:
|
data:
|
||||||
type: object
|
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
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Webauthn }}
|
||||||
webauthn.PublicKeyCredential:
|
webauthn.PublicKeyCredential:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -2073,6 +1824,8 @@ components:
|
||||||
written:
|
written:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
|
{{- end }}
|
||||||
|
{{- if .OpenIDConnect }}
|
||||||
openid.request.consent:
|
openid.request.consent:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -3669,12 +3422,15 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/jose.spec.JWK'
|
$ref: '#/components/schemas/jose.spec.JWK'
|
||||||
|
{{- end }}
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
authelia_auth:
|
authelia_auth:
|
||||||
type: apiKey
|
type: apiKey
|
||||||
name: "{{ .Session }}"
|
name: "{{ .Session }}"
|
||||||
in: cookie
|
in: cookie
|
||||||
|
{{- if .OpenIDConnect }}
|
||||||
openid:
|
openid:
|
||||||
type: openIdConnect
|
type: openIdConnect
|
||||||
openIdConnectUrl: "{{ .BaseURL }}.well-known/openid-configuration"
|
openIdConnectUrl: "{{ .BaseURL }}.well-known/openid-configuration"
|
||||||
|
{{- end }}
|
||||||
...
|
...
|
||||||
|
|
|
@ -56,6 +56,26 @@ The following functions which mimic the behaviour of helm exist in most templati
|
||||||
- b64dec
|
- b64dec
|
||||||
- b32enc
|
- b32enc
|
||||||
- b32dec
|
- b32dec
|
||||||
|
- list
|
||||||
|
- dict
|
||||||
|
- get
|
||||||
|
- set
|
||||||
|
- isAbs
|
||||||
|
- base
|
||||||
|
- dir
|
||||||
|
- ext
|
||||||
|
- clean
|
||||||
|
- osBase
|
||||||
|
- osClean
|
||||||
|
- osDir
|
||||||
|
- osExt
|
||||||
|
- osIsAbs
|
||||||
|
- deepEqual
|
||||||
|
- typeOf
|
||||||
|
- typeIs
|
||||||
|
- typeIsLike
|
||||||
|
- kindOf
|
||||||
|
- kindIs
|
||||||
|
|
||||||
See the [Helm Documentation](https://helm.sh/docs/chart_template_guide/function_list/) for more information. Please
|
See the [Helm Documentation](https://helm.sh/docs/chart_template_guide/function_list/) for more information. Please
|
||||||
note that only the functions listed above are supported and the functions don't necessarily behave exactly the same.
|
note that only the functions listed above are supported and the functions don't necessarily behave exactly the same.
|
||||||
|
|
|
@ -396,3 +396,8 @@ func (ctx *AutheliaCtx) SetContentTypeTextHTML() {
|
||||||
func (ctx *AutheliaCtx) SetContentTypeApplicationJSON() {
|
func (ctx *AutheliaCtx) SetContentTypeApplicationJSON() {
|
||||||
ctx.SetContentTypeBytes(contentTypeApplicationJSON)
|
ctx.SetContentTypeBytes(contentTypeApplicationJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetContentTypeApplicationYAML efficiently sets the Content-Type header to 'application/yaml; charset=utf-8'.
|
||||||
|
func (ctx *AutheliaCtx) SetContentTypeApplicationYAML() {
|
||||||
|
ctx.SetContentTypeBytes(contentTypeApplicationYAML)
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,62 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestContentTypes(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
setup func(ctx *middlewares.AutheliaCtx) (err error)
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ApplicationJSON",
|
||||||
|
setup: func(ctx *middlewares.AutheliaCtx) (err error) {
|
||||||
|
ctx.SetContentTypeApplicationJSON()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
expected: "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ApplicationYAML",
|
||||||
|
setup: func(ctx *middlewares.AutheliaCtx) (err error) {
|
||||||
|
ctx.SetContentTypeApplicationYAML()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
expected: "application/yaml; charset=utf-8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TextPlain",
|
||||||
|
setup: func(ctx *middlewares.AutheliaCtx) (err error) {
|
||||||
|
ctx.SetContentTypeTextPlain()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
expected: "text/plain; charset=utf-8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TextHTML",
|
||||||
|
setup: func(ctx *middlewares.AutheliaCtx) (err error) {
|
||||||
|
ctx.SetContentTypeTextHTML()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
expected: "text/html; charset=utf-8",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
defer mock.Close()
|
||||||
|
|
||||||
|
assert.NoError(t, tc.setup(mock.Ctx))
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expected, string(mock.Ctx.Response.Header.ContentType()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssuerURL(t *testing.T) {
|
func TestIssuerURL(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -44,8 +100,8 @@ func TestIssuerURL(t *testing.T) {
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", tc.proto)
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, tc.proto)
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Host", tc.host)
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, tc.host)
|
||||||
|
|
||||||
if tc.base != "" {
|
if tc.base != "" {
|
||||||
mock.Ctx.SetUserValue("base_url", tc.base)
|
mock.Ctx.SetUserValue("base_url", tc.base)
|
||||||
|
@ -103,8 +159,8 @@ func TestShouldGetOriginalURLFromOriginalURLHeader(t *testing.T) {
|
||||||
func TestShouldGetOriginalURLFromForwardedHeadersWithoutURI(t *testing.T) {
|
func TestShouldGetOriginalURLFromForwardedHeadersWithoutURI(t *testing.T) {
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Host", "home.example.com")
|
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "home.example.com")
|
||||||
originalURL, err := mock.Ctx.GetOriginalURL()
|
originalURL, err := mock.Ctx.GetOriginalURL()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -142,7 +198,7 @@ func TestShouldOnlyFallbackToNonXForwardedHeadersWhenNil(t *testing.T) {
|
||||||
mock.Ctx.RequestCtx.Request.SetHost("localhost")
|
mock.Ctx.RequestCtx.Request.SetHost("localhost")
|
||||||
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "auth.example.com:1234")
|
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "auth.example.com:1234")
|
||||||
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-URI", "/base/2fa/one-time-password")
|
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-URI", "/base/2fa/one-time-password")
|
||||||
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-Proto", "https")
|
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
||||||
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-Method", "GET")
|
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-Method", "GET")
|
||||||
|
|
||||||
assert.Equal(t, []byte("https"), mock.Ctx.XForwardedProto())
|
assert.Equal(t, []byte("https"), mock.Ctx.XForwardedProto())
|
||||||
|
|
|
@ -88,6 +88,7 @@ var (
|
||||||
contentTypeTextPlain = []byte("text/plain; charset=utf-8")
|
contentTypeTextPlain = []byte("text/plain; charset=utf-8")
|
||||||
contentTypeTextHTML = []byte("text/html; charset=utf-8")
|
contentTypeTextHTML = []byte("text/html; charset=utf-8")
|
||||||
contentTypeApplicationJSON = []byte("application/json; charset=utf-8")
|
contentTypeApplicationJSON = []byte("application/json; charset=utf-8")
|
||||||
|
contentTypeApplicationYAML = []byte("application/yaml; charset=utf-8")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -5,15 +5,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
assetsRoot = "public_html"
|
assetsRoot = "public_html"
|
||||||
assetsSwagger = assetsRoot + "/api"
|
|
||||||
|
|
||||||
fileOpenAPI = "openapi.yml"
|
fileLogo = "logo.png"
|
||||||
fileIndexHTML = "index.html"
|
|
||||||
fileLogo = "logo.png"
|
|
||||||
|
|
||||||
extHTML = ".html"
|
extHTML = ".html"
|
||||||
extJSON = ".json"
|
extJSON = ".json"
|
||||||
|
extYML = ".yml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -52,8 +50,8 @@ var (
|
||||||
const (
|
const (
|
||||||
environment = "ENVIRONMENT"
|
environment = "ENVIRONMENT"
|
||||||
dev = "dev"
|
dev = "dev"
|
||||||
f = "false"
|
strFalse = "false"
|
||||||
t = "true"
|
strTrue = "true"
|
||||||
localhost = "localhost"
|
localhost = "localhost"
|
||||||
schemeHTTP = "http"
|
schemeHTTP = "http"
|
||||||
schemeHTTPS = "https"
|
schemeHTTPS = "https"
|
||||||
|
@ -76,7 +74,8 @@ X_AUTHELIA_HEALTHCHECK_PATH=%s
|
||||||
`
|
`
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tmplCSPSwagger = "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'; base-uri 'self'"
|
tmplCSPSwaggerNonce = "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'; base-uri 'self'"
|
||||||
|
tmplCSPSwagger = "default-src 'self'; img-src 'self' https://validator.swagger.io data:; object-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self'; base-uri 'self'"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -93,9 +93,9 @@ func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
func handleRouter(config schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
|
func handleRouter(config schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
|
||||||
optsTemplatedFile := NewTemplatedFileOptions(&config)
|
optsTemplatedFile := NewTemplatedFileOptions(&config)
|
||||||
|
|
||||||
serveIndexHandler := ServeTemplatedFile(assetsRoot, fileIndexHTML, optsTemplatedFile)
|
serveIndexHandler := ServeTemplatedFile(providers.Templates.GetAssetIndexTemplate(), optsTemplatedFile)
|
||||||
serveSwaggerHandler := ServeTemplatedFile(assetsSwagger, fileIndexHTML, optsTemplatedFile)
|
serveOpenAPIHandler := ServeTemplatedOpenAPI(providers.Templates.GetAssetOpenAPIIndexTemplate(), optsTemplatedFile)
|
||||||
serveSwaggerAPIHandler := ServeTemplatedFile(assetsSwagger, fileOpenAPI, optsTemplatedFile)
|
serveOpenAPISpecHandler := ETagRootURL(ServeTemplatedOpenAPI(providers.Templates.GetAssetOpenAPISpecTemplate(), optsTemplatedFile))
|
||||||
|
|
||||||
handlerPublicHTML := newPublicHTMLEmbeddedHandler()
|
handlerPublicHTML := newPublicHTMLEmbeddedHandler()
|
||||||
handlerLocales := newLocalesEmbeddedHandler()
|
handlerLocales := newLocalesEmbeddedHandler()
|
||||||
|
@ -126,10 +126,12 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
||||||
r.GET("/locales/{language:[a-z]{1,3}}/{namespace:[a-z]+}.json", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerLocales))
|
r.GET("/locales/{language:[a-z]{1,3}}/{namespace:[a-z]+}.json", middlewares.AssetOverride(config.Server.AssetPath, 0, handlerLocales))
|
||||||
|
|
||||||
// Swagger.
|
// Swagger.
|
||||||
r.GET("/api/", middleware(serveSwaggerHandler))
|
r.GET("/api/", middleware(serveOpenAPIHandler))
|
||||||
r.OPTIONS("/api/", policyCORSPublicGET.HandleOPTIONS)
|
r.OPTIONS("/api/", policyCORSPublicGET.HandleOPTIONS)
|
||||||
r.GET("/api/"+fileOpenAPI, policyCORSPublicGET.Middleware(middleware(serveSwaggerAPIHandler)))
|
r.GET("/api/index.html", middleware(serveOpenAPIHandler))
|
||||||
r.OPTIONS("/api/"+fileOpenAPI, policyCORSPublicGET.HandleOPTIONS)
|
r.OPTIONS("/api/index.html", policyCORSPublicGET.HandleOPTIONS)
|
||||||
|
r.GET("/api/openapi.yml", policyCORSPublicGET.Middleware(middleware(serveOpenAPISpecHandler)))
|
||||||
|
r.OPTIONS("/api/openapi.yml", policyCORSPublicGET.HandleOPTIONS)
|
||||||
|
|
||||||
for _, file := range filesSwagger {
|
for _, file := range filesSwagger {
|
||||||
r.GET("/api/"+file, handlerPublicHTML)
|
r.GET("/api/"+file, handlerPublicHTML)
|
||||||
|
|
|
@ -19,6 +19,10 @@ import (
|
||||||
|
|
||||||
// CreateDefaultServer Create Authelia's internal webserver with the given configuration and providers.
|
// CreateDefaultServer Create Authelia's internal webserver with the given configuration and providers.
|
||||||
func CreateDefaultServer(config schema.Configuration, providers middlewares.Providers) (server *fasthttp.Server, listener net.Listener, err error) {
|
func CreateDefaultServer(config schema.Configuration, providers middlewares.Providers) (server *fasthttp.Server, listener net.Listener, err error) {
|
||||||
|
if err = providers.Templates.LoadTemplatedAssets(assets); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to load templated assets")
|
||||||
|
}
|
||||||
|
|
||||||
server = &fasthttp.Server{
|
server = &fasthttp.Server{
|
||||||
ErrorHandler: handleError(),
|
ErrorHandler: handleError(),
|
||||||
Handler: handleRouter(config, providers),
|
Handler: handleRouter(config, providers),
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/logging"
|
"github.com/authelia/authelia/v4/internal/logging"
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -134,10 +135,17 @@ type TLSServerContext struct {
|
||||||
port int
|
port int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTLSServerContext(configuration schema.Configuration) (*TLSServerContext, error) {
|
func NewTLSServerContext(configuration schema.Configuration) (serverContext *TLSServerContext, err error) {
|
||||||
serverContext := new(TLSServerContext)
|
serverContext = new(TLSServerContext)
|
||||||
|
|
||||||
s, listener, err := CreateDefaultServer(configuration, middlewares.Providers{})
|
providers := middlewares.Providers{}
|
||||||
|
|
||||||
|
providers.Templates, err = templates.New(templates.Config{EmailTemplatesPath: configuration.Notifier.TemplatePath})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, listener, err := CreateDefaultServer(configuration, providers)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,56 +1,43 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha1" //nolint:gosec
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"sync"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/logging"
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServeTemplatedFile serves a templated version of a specified file,
|
// ServeTemplatedFile serves a templated version of a specified file,
|
||||||
// this is utilised to pass information between the backend and frontend
|
// this is utilised to pass information between the backend and frontend
|
||||||
// and generate a nonce to support a restrictive CSP while using material-ui.
|
// and generate a nonce to support a restrictive CSP while using material-ui.
|
||||||
func ServeTemplatedFile(publicDir, file string, opts *TemplatedFileOptions) middlewares.RequestHandler {
|
func ServeTemplatedFile(t templates.Template, opts *TemplatedFileOptions) middlewares.RequestHandler {
|
||||||
logger := logging.Logger()
|
|
||||||
|
|
||||||
a, err := assets.Open(path.Join(publicDir, file))
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatalf("Unable to open %s: %s", file, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := io.ReadAll(a)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatalf("Unable to read %s: %s", file, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := template.New("file").Parse(string(b))
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatalf("Unable to parse %s template: %s", file, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
isDevEnvironment := os.Getenv(environment) == dev
|
isDevEnvironment := os.Getenv(environment) == dev
|
||||||
|
ext := filepath.Ext(t.Name())
|
||||||
|
|
||||||
return func(ctx *middlewares.AutheliaCtx) {
|
return func(ctx *middlewares.AutheliaCtx) {
|
||||||
logoOverride := f
|
var err error
|
||||||
|
|
||||||
|
logoOverride := strFalse
|
||||||
|
|
||||||
if opts.AssetPath != "" {
|
if opts.AssetPath != "" {
|
||||||
if _, err = os.Stat(filepath.Join(opts.AssetPath, fileLogo)); err == nil {
|
if _, err = os.Stat(filepath.Join(opts.AssetPath, fileLogo)); err == nil {
|
||||||
logoOverride = t
|
logoOverride = strTrue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch extension := filepath.Ext(file); extension {
|
switch ext {
|
||||||
case extHTML:
|
case extHTML:
|
||||||
ctx.SetContentTypeTextHTML()
|
ctx.SetContentTypeTextHTML()
|
||||||
case extJSON:
|
case extJSON:
|
||||||
|
@ -62,8 +49,6 @@ func ServeTemplatedFile(publicDir, file string, opts *TemplatedFileOptions) midd
|
||||||
nonce := utils.RandomString(32, utils.CharSetAlphaNumeric)
|
nonce := utils.RandomString(32, utils.CharSetAlphaNumeric)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case publicDir == assetsSwagger:
|
|
||||||
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPSwagger, nonce, nonce))
|
|
||||||
case ctx.Configuration.Server.Headers.CSPTemplate != "":
|
case ctx.Configuration.Server.Headers.CSPTemplate != "":
|
||||||
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, strings.ReplaceAll(ctx.Configuration.Server.Headers.CSPTemplate, placeholderCSPNonce, nonce))
|
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, strings.ReplaceAll(ctx.Configuration.Server.Headers.CSPTemplate, placeholderCSPNonce, nonce))
|
||||||
case isDevEnvironment:
|
case isDevEnvironment:
|
||||||
|
@ -72,15 +57,99 @@ func ServeTemplatedFile(publicDir, file string, opts *TemplatedFileOptions) midd
|
||||||
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPDefault, nonce))
|
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPDefault, nonce))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tmpl.Execute(ctx.Response.BodyWriter(), opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride)); err != nil {
|
if err = t.Execute(ctx.Response.BodyWriter(), opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride)); err != nil {
|
||||||
ctx.RequestCtx.Error("an error occurred", 503)
|
ctx.RequestCtx.Error("an error occurred", 503)
|
||||||
logger.Errorf("Unable to execute template: %v", err)
|
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeTemplatedOpenAPI serves templated OpenAPI related files.
|
||||||
|
func ServeTemplatedOpenAPI(t templates.Template, opts *TemplatedFileOptions) middlewares.RequestHandler {
|
||||||
|
ext := filepath.Ext(t.Name())
|
||||||
|
|
||||||
|
spec := ext == extYML
|
||||||
|
|
||||||
|
return func(ctx *middlewares.AutheliaCtx) {
|
||||||
|
var nonce string
|
||||||
|
|
||||||
|
if spec {
|
||||||
|
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, tmplCSPSwagger)
|
||||||
|
} else {
|
||||||
|
nonce = utils.RandomString(32, utils.CharSetAlphaNumeric)
|
||||||
|
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPSwaggerNonce, nonce, nonce))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ext {
|
||||||
|
case extHTML:
|
||||||
|
ctx.SetContentTypeTextHTML()
|
||||||
|
case extYML:
|
||||||
|
ctx.SetContentTypeApplicationYAML()
|
||||||
|
default:
|
||||||
|
ctx.SetContentTypeTextPlain()
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err = t.Execute(ctx.Response.BodyWriter(), opts.OpenAPIData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce)); err != nil {
|
||||||
|
ctx.RequestCtx.Error("an error occurred", 503)
|
||||||
|
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ETagRootURL dynamically matches the If-None-Match header and adds the ETag header.
|
||||||
|
func ETagRootURL(next middlewares.RequestHandler) middlewares.RequestHandler {
|
||||||
|
etags := map[string][]byte{}
|
||||||
|
|
||||||
|
h := sha1.New() //nolint:gosec // Usage is for collision avoidance not security.
|
||||||
|
mu := &sync.Mutex{}
|
||||||
|
|
||||||
|
return func(ctx *middlewares.AutheliaCtx) {
|
||||||
|
k := ctx.RootURLSlash().String()
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
|
||||||
|
etag, ok := etags[k]
|
||||||
|
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
if ok && bytes.Equal(etag, ctx.Request.Header.PeekBytes(headerIfNoneMatch)) {
|
||||||
|
ctx.Response.Header.SetBytesKV(headerETag, etag)
|
||||||
|
ctx.Response.Header.SetBytesKV(headerCacheControl, headerValueCacheControlETaggedAssets)
|
||||||
|
|
||||||
|
ctx.SetStatusCode(fasthttp.StatusNotModified)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next(ctx)
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
|
||||||
|
h.Write(ctx.Response.Body())
|
||||||
|
sum := h.Sum(nil)
|
||||||
|
h.Reset()
|
||||||
|
|
||||||
|
etagNew := make([]byte, hex.EncodedLen(len(sum)))
|
||||||
|
|
||||||
|
hex.Encode(etagNew, sum)
|
||||||
|
|
||||||
|
if !ok || !bytes.Equal(etag, etagNew) {
|
||||||
|
etags[k] = etagNew
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
ctx.Response.Header.SetBytesKV(headerETag, etagNew)
|
||||||
|
ctx.Response.Header.SetBytesKV(headerCacheControl, headerValueCacheControlETaggedAssets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func writeHealthCheckEnv(disabled bool, scheme, host, path string, port int) (err error) {
|
func writeHealthCheckEnv(disabled bool, scheme, host, path string, port int) (err error) {
|
||||||
if disabled {
|
if disabled {
|
||||||
return nil
|
return nil
|
||||||
|
@ -120,11 +189,17 @@ func writeHealthCheckEnv(disabled bool, scheme, host, path string, port int) (er
|
||||||
func NewTemplatedFileOptions(config *schema.Configuration) (opts *TemplatedFileOptions) {
|
func NewTemplatedFileOptions(config *schema.Configuration) (opts *TemplatedFileOptions) {
|
||||||
opts = &TemplatedFileOptions{
|
opts = &TemplatedFileOptions{
|
||||||
AssetPath: config.Server.AssetPath,
|
AssetPath: config.Server.AssetPath,
|
||||||
DuoSelfEnrollment: f,
|
DuoSelfEnrollment: strFalse,
|
||||||
RememberMe: strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled),
|
RememberMe: strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled),
|
||||||
ResetPassword: strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable),
|
ResetPassword: strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable),
|
||||||
ResetPasswordCustomURL: config.AuthenticationBackend.PasswordReset.CustomURL.String(),
|
ResetPasswordCustomURL: config.AuthenticationBackend.PasswordReset.CustomURL.String(),
|
||||||
Theme: config.Theme,
|
Theme: config.Theme,
|
||||||
|
|
||||||
|
EndpointsPasswordReset: !(config.AuthenticationBackend.PasswordReset.Disable || config.AuthenticationBackend.PasswordReset.CustomURL.String() != ""),
|
||||||
|
EndpointsWebauthn: !config.Webauthn.Disable,
|
||||||
|
EndpointsTOTP: !config.TOTP.Disable,
|
||||||
|
EndpointsDuo: !config.DuoAPI.Disable,
|
||||||
|
EndpointsOpenIDConnect: !(config.IdentityProviders.OIDC == nil),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.DuoAPI.Disable {
|
if !config.DuoAPI.Disable {
|
||||||
|
@ -143,6 +218,12 @@ type TemplatedFileOptions struct {
|
||||||
ResetPasswordCustomURL string
|
ResetPasswordCustomURL string
|
||||||
Session string
|
Session string
|
||||||
Theme string
|
Theme string
|
||||||
|
|
||||||
|
EndpointsPasswordReset bool
|
||||||
|
EndpointsWebauthn bool
|
||||||
|
EndpointsTOTP bool
|
||||||
|
EndpointsDuo bool
|
||||||
|
EndpointsOpenIDConnect bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonData returns a TemplatedFileCommonData with the dynamic options.
|
// CommonData returns a TemplatedFileCommonData with the dynamic options.
|
||||||
|
@ -161,6 +242,22 @@ func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenAPIData returns a TemplatedFileOpenAPIData with the dynamic options.
|
||||||
|
func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, nonce string) TemplatedFileOpenAPIData {
|
||||||
|
return TemplatedFileOpenAPIData{
|
||||||
|
Base: base,
|
||||||
|
BaseURL: baseURL,
|
||||||
|
CSPNonce: nonce,
|
||||||
|
|
||||||
|
Session: options.Session,
|
||||||
|
PasswordReset: options.EndpointsPasswordReset,
|
||||||
|
Webauthn: options.EndpointsWebauthn,
|
||||||
|
TOTP: options.EndpointsTOTP,
|
||||||
|
Duo: options.EndpointsDuo,
|
||||||
|
OpenIDConnect: options.EndpointsOpenIDConnect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TemplatedFileCommonData is a struct which is used for many templated files.
|
// TemplatedFileCommonData is a struct which is used for many templated files.
|
||||||
type TemplatedFileCommonData struct {
|
type TemplatedFileCommonData struct {
|
||||||
Base string
|
Base string
|
||||||
|
@ -174,3 +271,16 @@ type TemplatedFileCommonData struct {
|
||||||
Session string
|
Session string
|
||||||
Theme string
|
Theme string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TemplatedFileOpenAPIData is a struct which is used for the OpenAPI spec file.
|
||||||
|
type TemplatedFileOpenAPIData struct {
|
||||||
|
Base string
|
||||||
|
BaseURL string
|
||||||
|
CSPNonce string
|
||||||
|
Session string
|
||||||
|
PasswordReset bool
|
||||||
|
Webauthn bool
|
||||||
|
TOTP bool
|
||||||
|
Duo bool
|
||||||
|
OpenIDConnect bool
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ const (
|
||||||
// Template File Names.
|
// Template File Names.
|
||||||
const (
|
const (
|
||||||
TemplateNameEmailIdentityVerification = "IdentityVerification"
|
TemplateNameEmailIdentityVerification = "IdentityVerification"
|
||||||
TemplateNameEmailPasswordReset = "PasswordReset"
|
|
||||||
TemplateNameEmailEvent = "Event"
|
TemplateNameEmailEvent = "Event"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -49,9 +51,97 @@ func FuncMap() map[string]any {
|
||||||
"b64dec": FuncB64Dec,
|
"b64dec": FuncB64Dec,
|
||||||
"b32enc": FuncB32Enc,
|
"b32enc": FuncB32Enc,
|
||||||
"b32dec": FuncB32Dec,
|
"b32dec": FuncB32Dec,
|
||||||
|
"list": FuncList,
|
||||||
|
"dict": FuncDict,
|
||||||
|
"get": FuncGet,
|
||||||
|
"set": FuncSet,
|
||||||
|
"isAbs": path.IsAbs,
|
||||||
|
"base": path.Base,
|
||||||
|
"dir": path.Dir,
|
||||||
|
"ext": path.Ext,
|
||||||
|
"clean": path.Clean,
|
||||||
|
"osBase": filepath.Base,
|
||||||
|
"osClean": filepath.Clean,
|
||||||
|
"osDir": filepath.Dir,
|
||||||
|
"osExt": filepath.Ext,
|
||||||
|
"osIsAbs": filepath.IsAbs,
|
||||||
|
"deepEqual": reflect.DeepEqual,
|
||||||
|
"typeOf": FuncTypeOf,
|
||||||
|
"typeIs": FuncTypeIs,
|
||||||
|
"typeIsLike": FuncTypeIsLike,
|
||||||
|
"kindOf": FuncKindOf,
|
||||||
|
"kindIs": FuncKindIs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FuncTypeIs is a helper function that provides similar functionality to the helm typeIs func.
|
||||||
|
func FuncTypeIs(is string, v any) bool {
|
||||||
|
return is == FuncTypeOf(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncTypeIsLike is a helper function that provides similar functionality to the helm typeIsLike func.
|
||||||
|
func FuncTypeIsLike(is string, v any) bool {
|
||||||
|
t := FuncTypeOf(v)
|
||||||
|
|
||||||
|
return is == t || "*"+is == t
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncTypeOf is a helper function that provides similar functionality to the helm typeOf func.
|
||||||
|
func FuncTypeOf(v any) string {
|
||||||
|
return reflect.ValueOf(v).Type().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncKindIs is a helper function that provides similar functionality to the helm kindIs func.
|
||||||
|
func FuncKindIs(is string, v any) bool {
|
||||||
|
return is == FuncKindOf(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncKindOf is a helper function that provides similar functionality to the helm kindOf func.
|
||||||
|
func FuncKindOf(v any) string {
|
||||||
|
return reflect.ValueOf(v).Kind().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncList is a helper function that provides similar functionality to the helm list func.
|
||||||
|
func FuncList(items ...any) []any {
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncDict is a helper function that provides similar functionality to the helm dict func.
|
||||||
|
func FuncDict(pairs ...any) map[string]any {
|
||||||
|
m := map[string]any{}
|
||||||
|
p := len(pairs)
|
||||||
|
|
||||||
|
for i := 0; i < p; i += 2 {
|
||||||
|
key := strval(pairs[i])
|
||||||
|
|
||||||
|
if i+1 >= p {
|
||||||
|
m[key] = ""
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m[key] = pairs[i+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncGet is a helper function that provides similar functionality to the helm get func.
|
||||||
|
func FuncGet(m map[string]any, key string) any {
|
||||||
|
if val, ok := m[key]; ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncSet is a helper function that provides similar functionality to the helm set func.
|
||||||
|
func FuncSet(m map[string]any, key string, value any) map[string]any {
|
||||||
|
m[key] = value
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// FuncB64Enc is a helper function that provides similar functionality to the helm b64enc func.
|
// FuncB64Enc is a helper function that provides similar functionality to the helm b64enc func.
|
||||||
func FuncB64Enc(input string) string {
|
func FuncB64Enc(input string) string {
|
||||||
return base64.StdEncoding.EncodeToString([]byte(input))
|
return base64.StdEncoding.EncodeToString([]byte(input))
|
||||||
|
@ -202,7 +292,7 @@ func FuncStringQuote(in ...any) string {
|
||||||
return strings.Join(out, " ")
|
return strings.Join(out, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func strval(v interface{}) string {
|
func strval(v any) string {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
return v
|
return v
|
||||||
|
@ -219,7 +309,7 @@ func strslice(v any) []string {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case []string:
|
case []string:
|
||||||
return v
|
return v
|
||||||
case []interface{}:
|
case []any:
|
||||||
b := make([]string, 0, len(v))
|
b := make([]string, 0, len(v))
|
||||||
|
|
||||||
for _, s := range v {
|
for _, s := range v {
|
||||||
|
|
|
@ -469,3 +469,97 @@ func TestFuncStringSQuote(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFuncTypeOf(t *testing.T) {
|
||||||
|
astring := "typeOfExample"
|
||||||
|
anint := 5
|
||||||
|
astringslice := []string{astring}
|
||||||
|
anintslice := []int{anint}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have any
|
||||||
|
expected string
|
||||||
|
expectedKind string
|
||||||
|
}{
|
||||||
|
{"String", astring, "string", "string"},
|
||||||
|
{"StringPtr", &astring, "*string", "ptr"},
|
||||||
|
{"StringSlice", astringslice, "[]string", "slice"},
|
||||||
|
{"StringSlicePtr", &astringslice, "*[]string", "ptr"},
|
||||||
|
{"Integer", anint, "int", "int"},
|
||||||
|
{"IntegerPtr", &anint, "*int", "ptr"},
|
||||||
|
{"IntegerSlice", anintslice, "[]int", "slice"},
|
||||||
|
{"IntegerSlicePtr", &anintslice, "*[]int", "ptr"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, FuncTypeOf(tc.have))
|
||||||
|
assert.Equal(t, tc.expectedKind, FuncKindOf(tc.have))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncTypeIs(t *testing.T) {
|
||||||
|
astring := "typeIsExample"
|
||||||
|
anint := 10
|
||||||
|
astringslice := []string{astring}
|
||||||
|
anintslice := []int{anint}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
is string
|
||||||
|
have any
|
||||||
|
expected bool
|
||||||
|
expectedLike bool
|
||||||
|
expectedKind bool
|
||||||
|
}{
|
||||||
|
{"ShouldMatchStringAsString", "string", astring, true, true, true},
|
||||||
|
{"ShouldMatchStringPtrAsString", "string", &astring, false, true, false},
|
||||||
|
{"ShouldNotMatchStringAsInt", "int", astring, false, false, false},
|
||||||
|
{"ShouldNotMatchStringSliceAsStringSlice", "[]string", astringslice, true, true, false},
|
||||||
|
{"ShouldNotMatchStringSlicePtrAsStringSlice", "[]string", &astringslice, false, true, false},
|
||||||
|
{"ShouldNotMatchStringSlicePtrAsStringSlicePtr", "*[]string", &astringslice, true, true, false},
|
||||||
|
{"ShouldNotMatchStringSliceAsString", "string", astringslice, false, false, false},
|
||||||
|
{"ShouldMatchIntAsInt", "int", anint, true, true, true},
|
||||||
|
{"ShouldMatchIntPtrAsInt", "int", &anint, false, true, false},
|
||||||
|
{"ShouldNotMatchIntAsString", "string", anint, false, false, false},
|
||||||
|
{"ShouldMatchIntegerSliceAsIntSlice", "[]int", anintslice, true, true, false},
|
||||||
|
{"ShouldMatchIntegerSlicePtrAsIntSlice", "[]int", &anintslice, false, true, false},
|
||||||
|
{"ShouldMatchIntegerSlicePtrAsIntSlicePtr", "*[]int", &anintslice, true, true, false},
|
||||||
|
{"ShouldNotMatchIntegerSliceAsInt", "int", anintslice, false, false, false},
|
||||||
|
{"ShouldMatchKindSlice", "slice", anintslice, false, false, true},
|
||||||
|
{"ShouldMatchKindPtr", "ptr", &anintslice, false, false, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, FuncTypeIs(tc.is, tc.have))
|
||||||
|
assert.Equal(t, tc.expectedLike, FuncTypeIsLike(tc.is, tc.have))
|
||||||
|
assert.Equal(t, tc.expectedKind, FuncKindIs(tc.is, tc.have))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncList(t *testing.T) {
|
||||||
|
assert.Equal(t, []any{"a", "b", "c"}, FuncList("a", "b", "c"))
|
||||||
|
assert.Equal(t, []any{1, 2, 3}, FuncList(1, 2, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncDict(t *testing.T) {
|
||||||
|
assert.Equal(t, map[string]any{"a": 1}, FuncDict("a", 1))
|
||||||
|
assert.Equal(t, map[string]any{"a": 1, "b": ""}, FuncDict("a", 1, "b"))
|
||||||
|
assert.Equal(t, map[string]any{"1": 1, "b": 2}, FuncDict(1, 1, "b", 2))
|
||||||
|
assert.Equal(t, map[string]any{"true": 1, "b": 2}, FuncDict(true, 1, "b", 2))
|
||||||
|
assert.Equal(t, map[string]any{"a": 2, "b": 3}, FuncDict("a", 1, "a", 2, "b", 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncGet(t *testing.T) {
|
||||||
|
assert.Equal(t, 123, FuncGet(map[string]any{"abc": 123}, "abc"))
|
||||||
|
assert.Equal(t, "", FuncGet(map[string]any{"abc": 123}, "123"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncSet(t *testing.T) {
|
||||||
|
assert.Equal(t, map[string]any{"abc": 123, "123": true}, FuncSet(map[string]any{"abc": 123}, "123", true))
|
||||||
|
assert.Equal(t, map[string]any{"abc": true}, FuncSet(map[string]any{"abc": 123}, "abc", true))
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates a new templates' provider.
|
// New creates a new templates' provider.
|
||||||
|
@ -23,6 +25,64 @@ type Provider struct {
|
||||||
templates Templates
|
templates Templates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadTemplatedAssets takes an embed.FS and loads each templated asset document into a Template.
|
||||||
|
func (p *Provider) LoadTemplatedAssets(fs embed.FS) (err error) {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
if data, err = fs.ReadFile("public_html/index.html"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.templates.asset.index, err = template.
|
||||||
|
New("assets/public_html/index.html").
|
||||||
|
Funcs(FuncMap()).
|
||||||
|
Parse(string(data)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, err = fs.ReadFile("public_html/api/index.html"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.templates.asset.api.index, err = template.
|
||||||
|
New("assets/public_html/api/index.html").
|
||||||
|
Funcs(FuncMap()).
|
||||||
|
Parse(string(data)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, err = fs.ReadFile("public_html/api/openapi.yml"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.templates.asset.api.spec, err = template.
|
||||||
|
New("api/public_html/openapi.yaml").
|
||||||
|
Funcs(FuncMap()).
|
||||||
|
Parse(string(data)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAssetIndexTemplate returns a Template used to generate the React index document.
|
||||||
|
func (p *Provider) GetAssetIndexTemplate() (t Template) {
|
||||||
|
return p.templates.asset.index
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAssetOpenAPIIndexTemplate returns a Template used to generate the OpenAPI index document.
|
||||||
|
func (p *Provider) GetAssetOpenAPIIndexTemplate() (t Template) {
|
||||||
|
return p.templates.asset.api.index
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAssetOpenAPISpecTemplate returns a Template used to generate the OpenAPI specification document.
|
||||||
|
func (p *Provider) GetAssetOpenAPISpecTemplate() (t Template) {
|
||||||
|
return p.templates.asset.api.spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEventEmailTemplate returns an EmailTemplate used for generic event notifications.
|
||||||
func (p *Provider) GetEventEmailTemplate() (t *EmailTemplate) {
|
func (p *Provider) GetEventEmailTemplate() (t *EmailTemplate) {
|
||||||
return p.templates.notification.event
|
return p.templates.notification.event
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,17 @@ import (
|
||||||
// Templates is the struct which holds all the *template.Template values.
|
// Templates is the struct which holds all the *template.Template values.
|
||||||
type Templates struct {
|
type Templates struct {
|
||||||
notification NotificationTemplates
|
notification NotificationTemplates
|
||||||
|
asset AssetTemplates
|
||||||
|
}
|
||||||
|
|
||||||
|
type AssetTemplates struct {
|
||||||
|
index *tt.Template
|
||||||
|
api APIAssetTemplates
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIAssetTemplates struct {
|
||||||
|
index *tt.Template
|
||||||
|
spec *tt.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotificationTemplates are the templates for the notification system.
|
// NotificationTemplates are the templates for the notification system.
|
||||||
|
|
|
@ -39,24 +39,17 @@ func isSecretEnvKey(key string) (isSecretEnvKey bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func templateExists(path string) (exists bool) {
|
func fileExists(path string) (exists bool) {
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsDir() {
|
return err == nil && !info.IsDir()
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readTemplate(name, ext, category, overridePath string) (tPath string, embed bool, data []byte, err error) {
|
func readTemplate(name, ext, category, overridePath string) (tPath string, embed bool, data []byte, err error) {
|
||||||
if overridePath != "" {
|
if overridePath != "" {
|
||||||
tPath = filepath.Join(overridePath, name+ext)
|
tPath = filepath.Join(overridePath, name+ext)
|
||||||
|
|
||||||
if templateExists(tPath) {
|
if fileExists(tPath) {
|
||||||
if data, err = os.ReadFile(tPath); err != nil {
|
if data, err = os.ReadFile(tPath); err != nil {
|
||||||
return tPath, false, nil, fmt.Errorf("failed to read template override at path '%s': %w", tPath, err)
|
return tPath, false, nil, fmt.Errorf("failed to read template override at path '%s': %w", tPath, err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue