Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts: # api/openapi.yml # web/src/views/DeviceRegistration/RegisterWebauthn.tsx # web/src/views/LoginPortal/SecondFactor/WebauthnMethod.tsxpull/4806/head
commit
49d421e910
|
@ -9,6 +9,7 @@ yaml-files:
|
|||
- '.yamllint'
|
||||
|
||||
ignore: |
|
||||
api/openapi.yml
|
||||
docs/pnpm-lock.yaml
|
||||
internal/configuration/test_resources/config_bad_quoting.yml
|
||||
web/pnpm-lock.yaml
|
||||
|
|
36
README.md
36
README.md
|
@ -1,5 +1,5 @@
|
|||
<p align="center">
|
||||
<img src="./docs/static/images/authelia-title.png" width="350" title="Authelia">
|
||||
<img src="https://www.authelia.com/images/authelia-title.png" width="350" title="Authelia">
|
||||
</p>
|
||||
|
||||
[![Build](https://img.shields.io/buildkite/d6543d3ece3433f46dbe5fd9fcfaf1f68a6dbc48eb1048bc22/master?logo=buildkite&style=flat-square&color=brightgreen)](https://buildkite.com/authelia/authelia)
|
||||
|
@ -24,7 +24,7 @@ Documentation is available at [https://www.authelia.com/](https://www.authelia.c
|
|||
The following is a simple diagram of the architecture:
|
||||
|
||||
<p align="center" style="margin:50px">
|
||||
<img src="./docs/static/images/archi.png"/>
|
||||
<img src="https://www.authelia.com/images/archi.png"/>
|
||||
</p>
|
||||
|
||||
**Authelia** can be installed as a standalone service from the [AUR](https://aur.archlinux.org/packages/authelia/),
|
||||
|
@ -38,15 +38,15 @@ Deployment can be orchestrated via the Helm [Chart](https://charts.authelia.com)
|
|||
and ingress configurations.
|
||||
|
||||
<p align="center">
|
||||
<img src="./docs/static/images/logos/kubernetes.png" height="100"/>
|
||||
<img src="./docs/static/images/logos/docker.logo.png" width="100">
|
||||
<img src="https://www.authelia.com/images/logos/kubernetes.png" height="100"/>
|
||||
<img src="https://www.authelia.com/images/logos/docker.logo.png" width="100">
|
||||
</p>
|
||||
|
||||
Here is what Authelia's portal looks like:
|
||||
|
||||
<p align="center">
|
||||
<img src="./docs/static/images/1FA.png" width="400" />
|
||||
<img src="./docs/static/images/2FA-METHODS.png" width="400" />
|
||||
<img src="https://www.authelia.com/images/1FA.png" width="400" />
|
||||
<img src="https://www.authelia.com/images/2FA-METHODS.png" width="400" />
|
||||
</p>
|
||||
|
||||
## Features summary
|
||||
|
@ -92,11 +92,11 @@ If you want to know more about the roadmap, follow [Roadmap](https://www.autheli
|
|||
Authelia works in combination with [nginx], [Traefik], [Caddy], [Skipper], [Envoy], or [HAProxy].
|
||||
|
||||
<p align="center">
|
||||
<img src="./docs/static/images/logos/nginx.png" height="50"/>
|
||||
<img src="./docs/static/images/logos/traefik.png" height="50"/>
|
||||
<img src="./docs/static/images/logos/caddy.png" height="50"/>
|
||||
<img src="./docs/static/images/logos/envoy.png" height="50"/>
|
||||
<img src="./docs/static/images/logos/haproxy.png" height="50"/>
|
||||
<img src="https://www.authelia.com/images/logos/nginx.png" height="50"/>
|
||||
<img src="https://www.authelia.com/images/logos/traefik.png" height="50"/>
|
||||
<img src="https://www.authelia.com/images/logos/caddy.png" height="50"/>
|
||||
<img src="https://www.authelia.com/images/logos/envoy.png" height="50"/>
|
||||
<img src="https://www.authelia.com/images/logos/haproxy.png" height="50"/>
|
||||
</p>
|
||||
|
||||
## Getting Started
|
||||
|
@ -330,17 +330,17 @@ Authelia.
|
|||
|
||||
#### Balto
|
||||
|
||||
Thank you to [<img src="./docs/static/images/logos/balto.svg" alt="Balto" width="32"> Balto](https://www.getbalto.com/)
|
||||
Thank you to [<img src="https://www.authelia.com/images/logos/balto.svg" alt="Balto" width="32"> Balto](https://www.getbalto.com/)
|
||||
for hosting our apt repository.
|
||||
|
||||
#### JetBrains
|
||||
|
||||
Thank you to [<img src="./docs/static/images/logos/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](https://www.jetbrains.com/?from=Authelia)
|
||||
Thank you to [<img src="https://www.authelia.com/images/logos/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](https://www.jetbrains.com/?from=Authelia)
|
||||
for providing us with free licenses to their great tools.
|
||||
|
||||
* [<img src="./docs/static/images/logos/intellij-idea.svg" alt="IDEA" width="32"> IDEA](http://www.jetbrains.com/idea/)
|
||||
* [<img src="./docs/static/images/logos/goland.svg" alt="GoLand" width="32"> GoLand](http://www.jetbrains.com/go/)
|
||||
* [<img src="./docs/static/images/logos/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
* [<img src="https://www.authelia.com/images/logos/intellij-idea.svg" alt="IDEA" width="32"> IDEA](http://www.jetbrains.com/idea/)
|
||||
* [<img src="https://www.authelia.com/images/logos/goland.svg" alt="GoLand" width="32"> GoLand](http://www.jetbrains.com/go/)
|
||||
* [<img src="https://www.authelia.com/images/logos/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
|
||||
#### Microsoft
|
||||
|
||||
|
@ -348,9 +348,9 @@ Our pipeline agents which we rely on for productivity are hosted on [Azure](http
|
|||
and our [git repositories](https://github.com/authelia) are hosted on [GitHub](https://github.com/?from=Authela)
|
||||
which are both [Microsoft](https://www.microsoft.com/?from=Authelia) products.
|
||||
|
||||
[<img src="./docs/static/images/logos/microsoft.svg" alt="microsoft" height="32">](https://www.microsoft.com/?from=Authelia)
|
||||
[<img src="https://www.authelia.com/images/logos/microsoft.svg" alt="microsoft" height="32">](https://www.microsoft.com/?from=Authelia)
|
||||
|
||||
[<img src="./docs/static/images/logos/azure.svg" alt="Azure" height="32">](https://azure.microsoft.com/?from=Authelia)
|
||||
[<img src="https://www.authelia.com/images/logos/azure.svg" alt="Azure" height="32">](https://azure.microsoft.com/?from=Authelia)
|
||||
|
||||
### Open Collective
|
||||
|
||||
|
|
454
api/openapi.yml
454
api/openapi.yml
|
@ -1,4 +1,3 @@
|
|||
# yamllint disable rule:line-length
|
||||
---
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
|
@ -22,18 +21,24 @@ tags:
|
|||
description: Configuration, health and state endpoints
|
||||
- name: Authentication
|
||||
description: Authentication and verification endpoints
|
||||
{{- if .PasswordReset }}
|
||||
- name: Password Reset
|
||||
description: Password reset endpoints
|
||||
- name: User Information
|
||||
description: User configuration endpoints
|
||||
{{- end }}
|
||||
{{- if (or .TOTP .Webauthn .Duo) }}
|
||||
- name: Second Factor
|
||||
description: TOTP, Webauthn and Duo endpoints
|
||||
externalDocs:
|
||||
url: https://www.authelia.com/configuration/second-factor/introduction/
|
||||
{{- end }}
|
||||
{{- if .OpenIDConnect }}
|
||||
- name: OpenID Connect 1.0
|
||||
description: OpenID Connect 1.0 and OAuth 2.0 Endpoints
|
||||
externalDocs:
|
||||
url: https://www.authelia.com/integration/openid-connect/introduction/
|
||||
{{- end }}
|
||||
paths:
|
||||
/api/configuration:
|
||||
get:
|
||||
|
@ -97,280 +102,8 @@ paths:
|
|||
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:
|
||||
- $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:
|
||||
{{- range $method := list "get" "head" "options" "post" "put" "patch" "delete" "trace" }}
|
||||
{{ $method }}:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: Verification
|
||||
|
@ -409,6 +142,7 @@ paths:
|
|||
description: Unauthorized
|
||||
security:
|
||||
- authelia_auth: []
|
||||
{{- end }}
|
||||
/api/firstfactor:
|
||||
post:
|
||||
tags:
|
||||
|
@ -477,6 +211,7 @@ paths:
|
|||
$ref: '#/components/schemas/handlers.logoutResponseBody'
|
||||
security:
|
||||
- authelia_auth: []
|
||||
{{- if .PasswordReset }}
|
||||
/api/reset-password/identity/start:
|
||||
post:
|
||||
tags:
|
||||
|
@ -494,7 +229,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/handlers.resetPasswordStep1RequestBody'
|
||||
$ref: '#/components/schemas/handlers.PasswordResetStep1RequestBody'
|
||||
responses:
|
||||
"200":
|
||||
description: Successful Operation
|
||||
|
@ -546,7 +281,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/handlers.resetPasswordStep2RequestBody'
|
||||
$ref: '#/components/schemas/handlers.PasswordResetStep2RequestBody'
|
||||
responses:
|
||||
"200":
|
||||
description: Successful Operation
|
||||
|
@ -556,6 +291,7 @@ paths:
|
|||
$ref: '#/components/schemas/middlewares.OkResponse'
|
||||
security:
|
||||
- authelia_auth: []
|
||||
{{- end }}
|
||||
/api/user/info:
|
||||
get:
|
||||
tags:
|
||||
|
@ -593,25 +329,6 @@ paths:
|
|||
description: Forbidden
|
||||
security:
|
||||
- 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:
|
||||
post:
|
||||
tags:
|
||||
|
@ -634,6 +351,26 @@ paths:
|
|||
description: Forbidden
|
||||
security:
|
||||
- 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:
|
||||
post:
|
||||
tags:
|
||||
|
@ -706,6 +443,8 @@ paths:
|
|||
$ref: '#/components/schemas/middlewares.ErrorResponse'
|
||||
security:
|
||||
- authelia_auth: []
|
||||
{{- end }}
|
||||
{{- if .Webauthn }}
|
||||
/api/secondfactor/webauthn/assertion:
|
||||
get:
|
||||
tags:
|
||||
|
@ -851,6 +590,8 @@ paths:
|
|||
- authelia_auth: []
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/deviceID'
|
||||
{{- end }}
|
||||
{{- if .Duo }}
|
||||
/api/secondfactor/duo:
|
||||
post:
|
||||
tags:
|
||||
|
@ -914,6 +655,8 @@ paths:
|
|||
description: Unauthorized
|
||||
security:
|
||||
- authelia_auth: []
|
||||
{{- end }}
|
||||
{{- if .OpenIDConnect }}
|
||||
/.well-known/openid-configuration:
|
||||
get:
|
||||
tags:
|
||||
|
@ -1428,6 +1171,7 @@ paths:
|
|||
description: Forbidden
|
||||
security:
|
||||
- authelia_auth: []
|
||||
{{- end }}
|
||||
components:
|
||||
parameters:
|
||||
deviceID:
|
||||
|
@ -1655,7 +1399,8 @@ components:
|
|||
redirect:
|
||||
type: string
|
||||
example: https://home.example.com
|
||||
handlers.resetPasswordStep1RequestBody:
|
||||
{{- if .PasswordReset }}
|
||||
handlers.PasswordResetStep1RequestBody:
|
||||
required:
|
||||
- username
|
||||
type: object
|
||||
|
@ -1663,7 +1408,7 @@ components:
|
|||
username:
|
||||
type: string
|
||||
example: john
|
||||
handlers.resetPasswordStep2RequestBody:
|
||||
handlers.PasswordResetStep2RequestBody:
|
||||
required:
|
||||
- password
|
||||
type: object
|
||||
|
@ -1671,6 +1416,8 @@ components:
|
|||
password:
|
||||
type: string
|
||||
example: password
|
||||
{{- end }}
|
||||
{{- if .Duo }}
|
||||
handlers.bodySignDuoRequest:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1687,23 +1434,7 @@ components:
|
|||
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.bodySignTOTPRequest:
|
||||
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"
|
||||
{{- end }}
|
||||
handlers.StateResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1722,7 +1453,24 @@ components:
|
|||
default_redirection_url:
|
||||
type: string
|
||||
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
|
||||
properties:
|
||||
status:
|
||||
|
@ -1730,13 +1478,6 @@ components:
|
|||
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:
|
||||
|
@ -1765,6 +1506,19 @@ components:
|
|||
has_duo:
|
||||
type: boolean
|
||||
example: true
|
||||
handlers.UserInfo.MethodBody:
|
||||
required:
|
||||
- method
|
||||
type: object
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
enum:
|
||||
- "totp"
|
||||
- "webauthn"
|
||||
- "mobile_push"
|
||||
example: totp
|
||||
{{- if .TOTP }}
|
||||
handlers.UserInfoTOTP:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1784,36 +1538,24 @@ components:
|
|||
description: The number of digits defined in the users TOTP configuration
|
||||
type: integer
|
||||
example: 6
|
||||
handlers.UserInfo.MethodBody:
|
||||
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
|
||||
handlers.bodySignTOTPRequest:
|
||||
type: object
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDc5MjU1OTYsImlzcyI6IkF1dGhlbGlhIiwiYWN0aW9uIjoiUmVzZXRQYXNzd29yZCIsInVzZXJuYW1lIjoiQW1pciJ9.636yqRrUCGCe4jsMCsonleX5CYWHncYqZum-YYb6VaY
|
||||
middlewares.OkResponse:
|
||||
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.TOTPKeyResponse:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
|
@ -1821,6 +1563,15 @@ components:
|
|||
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
|
||||
{{- end }}
|
||||
{{- if .Webauthn }}
|
||||
webauthn.PublicKeyCredential:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -2129,6 +1880,8 @@ components:
|
|||
written:
|
||||
type: boolean
|
||||
example: false
|
||||
{{- end }}
|
||||
{{- if .OpenIDConnect }}
|
||||
openid.request.consent:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -3725,12 +3478,15 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/jose.spec.JWK'
|
||||
{{- end }}
|
||||
securitySchemes:
|
||||
authelia_auth:
|
||||
type: apiKey
|
||||
name: "{{ .Session }}"
|
||||
in: cookie
|
||||
{{- if .OpenIDConnect }}
|
||||
openid:
|
||||
type: openIdConnect
|
||||
openIdConnectUrl: "{{ .BaseURL }}.well-known/openid-configuration"
|
||||
{{- end }}
|
||||
...
|
||||
|
|
|
@ -1394,15 +1394,9 @@ notifier:
|
|||
## Sets the client to public. This should typically not be set, please see the documentation for usage.
|
||||
# public: false
|
||||
|
||||
## The policy to require for this client; one_factor or two_factor.
|
||||
# authorization_policy: two_factor
|
||||
|
||||
## The consent mode controls how consent is obtained.
|
||||
# consent_mode: auto
|
||||
|
||||
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
||||
## configured as 'auto' or 'pre-configured'.
|
||||
# pre_configured_consent_duration: 1w
|
||||
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
|
||||
# redirect_uris:
|
||||
# - https://oidc.example.com:8080/oauth2/callback
|
||||
|
||||
## Audience this client is allowed to request.
|
||||
# audience: []
|
||||
|
@ -1414,10 +1408,6 @@ notifier:
|
|||
# - email
|
||||
# - profile
|
||||
|
||||
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
|
||||
# redirect_uris:
|
||||
# - https://oidc.example.com:8080/oauth2/callback
|
||||
|
||||
## Grant Types configures which grants this client can obtain.
|
||||
## It's not recommended to define this unless you know what you're doing.
|
||||
# grant_types:
|
||||
|
@ -1435,6 +1425,23 @@ notifier:
|
|||
# - query
|
||||
# - fragment
|
||||
|
||||
## The policy to require for this client; one_factor or two_factor.
|
||||
# authorization_policy: two_factor
|
||||
|
||||
## Enforces the use of PKCE for this client when set to true.
|
||||
# enforce_pkce: false
|
||||
|
||||
## Enforces the use of PKCE for this client when configured, and enforces the specified challenge method.
|
||||
## Options are 'plain' and 'S256'.
|
||||
# pkce_challenge_method: S256
|
||||
|
||||
## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256.
|
||||
# userinfo_signing_algorithm: none
|
||||
|
||||
## The consent mode controls how consent is obtained.
|
||||
# consent_mode: auto
|
||||
|
||||
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
||||
## configured as 'auto' or 'pre-configured'.
|
||||
# pre_configured_consent_duration: 1w
|
||||
...
|
||||
|
|
|
@ -404,12 +404,92 @@ useful for SPA's and CLI tools. This option requires setting the [client secret]
|
|||
In addition to the standard rules for redirect URIs, public clients can use the `urn:ietf:wg:oauth:2.0:oob` redirect
|
||||
URI.
|
||||
|
||||
#### redirect_uris
|
||||
|
||||
{{< confkey type="list(string)" required="yes" >}}
|
||||
|
||||
A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are
|
||||
case-sensitive and they differ from application to application - the community has provided
|
||||
[a list of URL´s for common applications](../../integration/openid-connect/introduction.md).
|
||||
|
||||
Some restrictions that have been placed on clients and
|
||||
their redirect URIs are as follows:
|
||||
|
||||
1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the
|
||||
attempt to authorize will fail and an error will be generated.
|
||||
2. The redirect URIs are case-sensitive.
|
||||
3. The URI must include a scheme and that scheme must be one of `http` or `https`.
|
||||
4. The client can ignore rule 3 and use `urn:ietf:wg:oauth:2.0:oob` if it is a [public](#public) client type.
|
||||
|
||||
#### audience
|
||||
|
||||
{{< confkey type="list(string)" required="no" >}}
|
||||
|
||||
A list of audiences this client is allowed to request.
|
||||
|
||||
#### scopes
|
||||
|
||||
{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}}
|
||||
|
||||
A list of scopes to allow this client to consume. See
|
||||
[scope definitions](../../integration/openid-connect/introduction.md#scope-definitions) for more information. The
|
||||
documentation for the application you want to use with Authelia will most-likely provide you with the scopes to allow.
|
||||
|
||||
#### grant_types
|
||||
|
||||
{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}}
|
||||
|
||||
A list of grant types this client can return. *It is recommended that this isn't configured at this time unless you
|
||||
know what you're doing*. Valid options are: `implicit`, `refresh_token`, `authorization_code`, `password`,
|
||||
`client_credentials`.
|
||||
|
||||
#### response_types
|
||||
|
||||
{{< confkey type="list(string)" default="code" required="no" >}}
|
||||
|
||||
A list of response types this client can return. *It is recommended that this isn't configured at this time unless you
|
||||
know what you're doing*. Valid options are: `code`, `code id_token`, `id_token`, `token id_token`, `token`,
|
||||
`token id_token code`.
|
||||
|
||||
#### response_modes
|
||||
|
||||
{{< confkey type="list(string)" default="form_post, query, fragment" required="no" >}}
|
||||
|
||||
A list of response modes this client can return. It is recommended that this isn't configured at this time unless you
|
||||
know what you're doing. Potential values are `form_post`, `query`, and `fragment`.
|
||||
|
||||
#### authorization_policy
|
||||
|
||||
{{< confkey type="string" default="two_factor" required="no" >}}
|
||||
|
||||
The authorization policy for this client: either `one_factor` or `two_factor`.
|
||||
|
||||
#### enforce_pkce
|
||||
|
||||
{{< confkey type="bool" default="false" required="no" >}}
|
||||
|
||||
This setting enforces the use of [PKCE] for this individual client. To enforce it for all clients see the global
|
||||
[enforce_pkce](#enforcepkce) setting.
|
||||
|
||||
#### pkce_challenge_method
|
||||
|
||||
{{< confkey type="string" default="" required="no" >}}
|
||||
|
||||
This setting enforces the use of the specified [PKCE] challenge method for this individual client. This setting also
|
||||
effectively enables the [enforce_pkce](#enforcepkce-1) option for this client.
|
||||
|
||||
Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the
|
||||
relying party supports it.
|
||||
|
||||
#### userinfo_signing_algorithm
|
||||
|
||||
{{< confkey type="string" default="none" required="no" >}}
|
||||
|
||||
The algorithm used to sign the userinfo endpoint responses. This can either be `none` or `RS256`.
|
||||
|
||||
See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for
|
||||
more information.
|
||||
|
||||
#### consent_mode
|
||||
|
||||
{{< confkey type="string" default="auto" required="no" >}}
|
||||
|
@ -442,69 +522,6 @@ match exactly with the granted scopes/audience.
|
|||
|
||||
[consent_mode]: #consentmode
|
||||
|
||||
#### audience
|
||||
|
||||
{{< confkey type="list(string)" required="no" >}}
|
||||
|
||||
A list of audiences this client is allowed to request.
|
||||
|
||||
#### scopes
|
||||
|
||||
{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}}
|
||||
|
||||
A list of scopes to allow this client to consume. See
|
||||
[scope definitions](../../integration/openid-connect/introduction.md#scope-definitions) for more information. The
|
||||
documentation for the application you want to use with Authelia will most-likely provide you with the scopes to allow.
|
||||
|
||||
#### redirect_uris
|
||||
|
||||
{{< confkey type="list(string)" required="yes" >}}
|
||||
|
||||
A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are
|
||||
case-sensitive and they differ from application to application - the community has provided
|
||||
[a list of URL´s for common applications](../../integration/openid-connect/introduction.md).
|
||||
|
||||
Some restrictions that have been placed on clients and
|
||||
their redirect URIs are as follows:
|
||||
|
||||
1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the
|
||||
attempt to authorize will fail and an error will be generated.
|
||||
2. The redirect URIs are case-sensitive.
|
||||
3. The URI must include a scheme and that scheme must be one of `http` or `https`.
|
||||
4. The client can ignore rule 3 and use `urn:ietf:wg:oauth:2.0:oob` if it is a [public](#public) client type.
|
||||
|
||||
#### grant_types
|
||||
|
||||
{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}}
|
||||
|
||||
A list of grant types this client can return. *It is recommended that this isn't configured at this time unless you
|
||||
know what you're doing*. Valid options are: `implicit`, `refresh_token`, `authorization_code`, `password`,
|
||||
`client_credentials`.
|
||||
|
||||
#### response_types
|
||||
|
||||
{{< confkey type="list(string)" default="code" required="no" >}}
|
||||
|
||||
A list of response types this client can return. *It is recommended that this isn't configured at this time unless you
|
||||
know what you're doing*. Valid options are: `code`, `code id_token`, `id_token`, `token id_token`, `token`,
|
||||
`token id_token code`.
|
||||
|
||||
#### response_modes
|
||||
|
||||
{{< confkey type="list(string)" default="form_post, query, fragment" required="no" >}}
|
||||
|
||||
A list of response modes this client can return. It is recommended that this isn't configured at this time unless you
|
||||
know what you're doing. Potential values are `form_post`, `query`, and `fragment`.
|
||||
|
||||
#### userinfo_signing_algorithm
|
||||
|
||||
{{< confkey type="string" default="none" required="no" >}}
|
||||
|
||||
The algorithm used to sign the userinfo endpoint responses. This can either be `none` or `RS256`.
|
||||
|
||||
See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for
|
||||
more information.
|
||||
|
||||
## Integration
|
||||
|
||||
To integrate Authelia's [OpenID Connect] implementation with a relying party please see the
|
||||
|
|
|
@ -163,9 +163,9 @@ services:
|
|||
In the [SWAG] `/config` mount which is mounted to `${PWD}/data/swag` in our example:
|
||||
|
||||
1. Create a folder named `snippets/authelia`:
|
||||
- The `mkdir -p ${PWD}/data/swag/snippets/authelia` command should achieve this on Linux.
|
||||
2. Create the `${PWD}/data/swag/nginxsnippets/authelia/location.conf` file which can be found [here](nginx.md#authelia-locationconf).
|
||||
3. Create the `${PWD}/data/swag/nginxsnippets/authelia/authrequest.conf` file which can be found [here](nginx.md#authelia-authrequestconf).
|
||||
- The `mkdir -p ${PWD}/data/swag/nginx/snippets/authelia` command should achieve this on Linux.
|
||||
2. Create the `${PWD}/data/swag/nginx/snippets/authelia/location.conf` file which can be found [here](nginx.md#authelia-locationconf).
|
||||
3. Create the `${PWD}/data/swag/nginx/snippets/authelia/authrequest.conf` file which can be found [here](nginx.md#authelia-authrequestconf).
|
||||
- Ensure you adjust the line `error_page 401 =302 https://auth.example.com/?rd=$target_url;` replacing `https://auth.example.com/` with your external Authelia URL.
|
||||
|
||||
## Protected Application
|
||||
|
@ -174,7 +174,7 @@ In the server configuration for the application you want to protect:
|
|||
|
||||
1. Edit the `/config/nginx/proxy-confs/` file for the application you wish to protect.
|
||||
2. Under the `#include /config/nginx/authelia-server.conf;` line which should be within the `server` block
|
||||
but not inside any `location` blocks add the following line: ``.
|
||||
but not inside any `location` blocks add the following line: `include /config/nginx/snippets/authelia/location.conf;`.
|
||||
3. Under the `#include /config/nginx/authelia-location.conf;` line which should be within the applications
|
||||
`location` block add the following line `include /config/nginx/snippets/authelia/authrequest.conf;`.
|
||||
|
||||
|
|
|
@ -10,14 +10,31 @@ aliases:
|
|||
---
|
||||
|
||||
The __Authelia__ team aims to abide by the [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) policy. This
|
||||
means that we use the format `major.minor.patch` for our version numbers, where a change to `major` denotes a breaking
|
||||
change which will likely require user interaction to upgrade, `minor` which denotes a new feature, and `patch` denotes a
|
||||
fix.
|
||||
means that we use the format `<major>.<minor>.<patch>` for our version numbers, where a change to `major` denotes a
|
||||
breaking change which will likely require user interaction to upgrade, `minor` which denotes a new feature, and `patch`
|
||||
denotes a fix.
|
||||
|
||||
It is therefore recommended users do not automatically upgrade the `minor` version without reading the patch notes, and
|
||||
it's critically important users do not upgrade the `major` version without reading the patch notes. You should pin your
|
||||
version to `4.37` for example to prevent automatic upgrades from negatively affecting you.
|
||||
version to `4.37` for example to prevent automatic upgrades of the `minor` version, or pin your version to `4` to
|
||||
prevent automatic upgrade of the `major` version.
|
||||
|
||||
We generally do not recommend automated upgrades of critical systems but instead recommend ensuring you are notified an
|
||||
upgrade exists.
|
||||
|
||||
## Major Version Zero
|
||||
|
||||
A major version of `v0.x.x` indicates as per the [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) policy
|
||||
that there may be breaking changes without warning. Some [components](#components) will be released under this version
|
||||
while they're in early development.
|
||||
|
||||
## Components
|
||||
|
||||
Several components may exist at various times. We aim to abide by this policy for all components related to Authelia.
|
||||
It is important to note that each component has its own version, for example the primary Authelia binary version may be
|
||||
v4.40.0 but another component such as the [Helm Chart](https://charts.authelia.com) version may be v0.9.0.
|
||||
|
||||
This means that a breaking change may occur to one but not the other as these components do not share a version.
|
||||
## Exceptions
|
||||
|
||||
There are exceptions to this versioning policy.
|
||||
|
@ -33,7 +50,7 @@ Notable Advanced Customizations:
|
|||
- Templates:
|
||||
- Email
|
||||
- Content Security Policy header
|
||||
- Localization Assets
|
||||
- Localization / Internationalization Assets
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
|
@ -47,6 +64,6 @@ Notable examples:
|
|||
- OpenID Connect 1.0
|
||||
- File Filters
|
||||
|
||||
The reasoning is as we develop these features there may be mistakes and we may need to make a change that should be
|
||||
considered breaking. As these features graduate from their status to generally available they will move into our
|
||||
standard versioning policy from this exception.
|
||||
The reasoning is as we develop these features there may be mistakes and we may need to make a change that would normally
|
||||
be considered a breaking change. As these features graduate from their status to generally available they will move into
|
||||
our standard versioning policy and lose their exception status.
|
||||
|
|
|
@ -15,3 +15,5 @@ toc: true
|
|||
## Web Portal Internationalization
|
||||
|
||||
{{% table-i18n-locales %}}
|
||||
|
||||
Information about overriding the web portal internationalization is available from the [Server Asset Overrides Reference Guide](./server-asset-overrides.md).
|
||||
|
|
|
@ -20,9 +20,9 @@ This guide effectively documents the usage of the
|
|||
|
||||
## Important Notes
|
||||
|
||||
1. The templates are not covered by our stability guarantees. While we aim to avoid changes to the templates which
|
||||
would cause users to have to manually change them changes may be necessary in order to facilitate bug fixes or
|
||||
generally improve the templates.
|
||||
1. The templates are not covered by our stability guarantees as per our [Versioning Policy]. While we aim to avoid
|
||||
changes to the templates which would cause users to have to manually change them changes may be necessary in order to
|
||||
facilitate bug fixes or generally improve the templates.
|
||||
1. It is your responsibility to ensure your templates are up to date. We make no efforts in facilitating this.
|
||||
2. We may not be able to offer any direct support in debugging these templates. We only offer support and fixes to
|
||||
the official templates.
|
||||
|
@ -82,3 +82,4 @@ Several functions are implemented with the email templates. See the
|
|||
[server_name]: ../../configuration/notifications/smtp.md#tls
|
||||
[sender]: ../../configuration/notifications/smtp.md#sender
|
||||
[identifier]: ../../configuration/notifications/smtp.md#identifier
|
||||
[Versioning Policy]: ../../policies/versioning.md
|
||||
|
|
|
@ -39,8 +39,8 @@ the language itself, or adding a variant form of that language. If you'd like su
|
|||
to make a PR. We also encourage people to make PR's for variants where the difference in the variants is significant.*
|
||||
|
||||
*__Important Note__ Users wishing to override the locales files should be aware that we do not provide any guarantee
|
||||
that the file will not change in a breaking way between releases. Users who planning to utilize these
|
||||
overrides should either check for changes to the files in the
|
||||
that the file will not change in a breaking way between releases as per our [Versioning Policy]. Users who planning to
|
||||
utilize these overrides should either check for changes to the files in the
|
||||
[en](https://github.com/authelia/authelia/tree/master/internal/server/locales/en) translation prior to upgrading or
|
||||
[Contribute](../../contributing/prologue/translations.md) their translation to ensure it is maintained.*
|
||||
|
||||
|
@ -72,12 +72,8 @@ Each file in a locale directory represents a translation namespace. The list of
|
|||
|
||||
List of supported languages and variants:
|
||||
|
||||
| Description | Language | Additional Variants | Location |
|
||||
|:---------------------:|:--------:|:-------------------:|:--------------------:|
|
||||
| English | en | N/A | locales/en/*.json |
|
||||
| Spanish | es | N/A | locales/es/*.json |
|
||||
| German | de | N/A | locales/de/*.json |
|
||||
| French | fr | N/A | locales/fr/*.json |
|
||||
| Russian | ru | N/A | locales/ru/*.json |
|
||||
| Swedish | sv | sv-SE (Sweden) | locales/sv/*.json |
|
||||
| Chinese (Traditional) | zh-TW | N/A | locales/zh-TW/*.json |
|
||||
{{% table-i18n-overrides %}}
|
||||
|
||||
More information may be available from the [Internationalization Reference Guide](./internationalization.md).
|
||||
|
||||
[Versioning Policy]: ../../policies/versioning.md
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title: "Templating"
|
||||
description: "A reference guide on the templates system"
|
||||
lead: "This section contains reference documentation for Authelia's templating capabilities."
|
||||
date: 2022-12-23T18:31:05+11:00
|
||||
date: 2022-12-23T21:58:54+11:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
|
@ -56,6 +56,28 @@ The following functions which mimic the behaviour of helm exist in most templati
|
|||
- b64dec
|
||||
- b32enc
|
||||
- b32dec
|
||||
- list
|
||||
- dict
|
||||
- get
|
||||
- set
|
||||
- isAbs
|
||||
- base
|
||||
- dir
|
||||
- ext
|
||||
- clean
|
||||
- osBase
|
||||
- osClean
|
||||
- osDir
|
||||
- osExt
|
||||
- osIsAbs
|
||||
- deepEqual
|
||||
- typeOf
|
||||
- typeIs
|
||||
- typeIsLike
|
||||
- kindOf
|
||||
- kindIs
|
||||
- default
|
||||
- empty
|
||||
|
||||
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.
|
||||
|
|
|
@ -64,7 +64,7 @@ Feature List:
|
|||
|
||||
Feature List:
|
||||
|
||||
* [Proof Key Code Exchange (PKCE)](https://www.rfc-editor.org/rfc/rfc7636.html) for Authorization Code Flow
|
||||
* [Proof Key Code Exchange (PKCE)] for Authorization Code Flow
|
||||
* Claims:
|
||||
* `preferred_username` - sending the username in this claim instead of the `sub` claim.
|
||||
|
||||
|
@ -115,8 +115,8 @@ Feature List:
|
|||
|
||||
{{< roadmap-status stage="in-progress" version="v4.38.0" >}}
|
||||
|
||||
|
||||
* [OAuth 2.0 Pushed Authorization Requests](https://www.rfc-editor.org/rfc/rfc9126.html)
|
||||
* Per-Client [Proof Key Code Exchange (PKCE)] Policy
|
||||
|
||||
### Beta 7
|
||||
|
||||
|
@ -219,3 +219,4 @@ The `preferred_username` claim was missing and was fixed.
|
|||
[OpenID Connect Core (Subject Identifier Types)]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
|
||||
[OpenID Connect Core (Pairwise Identifier Algorithm)]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
|
||||
[OpenID Connect Core (Mandatory to Implement Features for All OpenID Providers)]: https://openid.net/specs/openid-connect-core-1_0.html#ServerMTI
|
||||
[Proof Key Code Exchange (PKCE)]: https://www.rfc-editor.org/rfc/rfc7636.html
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
| Language | Locale | Override Path |
|
||||
|:--------------:|:-------------:|:----------------------------:|
|
||||
{{- range $.Site.Data.languages.languages }}
|
||||
| {{ .display }} | {{ .locale }} | locales/{{ .locale }}/*.json |
|
||||
{{- end }}
|
|
@ -47,9 +47,9 @@
|
|||
"auto-changelog": "2.4.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
"bootstrap": "5.2.3",
|
||||
"bootstrap-icons": "1.10.2",
|
||||
"bootstrap-icons": "1.10.3",
|
||||
"clipboard": "2.0.11",
|
||||
"eslint": "8.30.0",
|
||||
"eslint": "8.31.0",
|
||||
"exec-bin": "1.0.0",
|
||||
"flexsearch": "0.7.31",
|
||||
"highlight.js": "11.7.0",
|
||||
|
@ -57,14 +57,14 @@
|
|||
"instant.page": "5.1.1",
|
||||
"katex": "0.16.4",
|
||||
"lazysizes": "5.3.2",
|
||||
"markdownlint-cli2": "0.5.1",
|
||||
"markdownlint-cli2": "0.6.0",
|
||||
"netlify-plugin-submit-sitemap": "0.4.0",
|
||||
"node-fetch": "3.3.0",
|
||||
"postcss": "8.4.20",
|
||||
"postcss-cli": "10.1.0",
|
||||
"purgecss-whitelister": "2.4.0",
|
||||
"shx": "0.3.4",
|
||||
"stylelint": "14.16.0",
|
||||
"stylelint": "14.16.1",
|
||||
"stylelint-config-standard-scss": "6.1.0"
|
||||
},
|
||||
"otherDependencies": {
|
||||
|
|
|
@ -10,9 +10,9 @@ specifiers:
|
|||
auto-changelog: 2.4.0
|
||||
autoprefixer: 10.4.13
|
||||
bootstrap: 5.2.3
|
||||
bootstrap-icons: 1.10.2
|
||||
bootstrap-icons: 1.10.3
|
||||
clipboard: 2.0.11
|
||||
eslint: 8.30.0
|
||||
eslint: 8.31.0
|
||||
exec-bin: 1.0.0
|
||||
flexsearch: 0.7.31
|
||||
highlight.js: 11.7.0
|
||||
|
@ -20,14 +20,14 @@ specifiers:
|
|||
instant.page: 5.1.1
|
||||
katex: 0.16.4
|
||||
lazysizes: 5.3.2
|
||||
markdownlint-cli2: 0.5.1
|
||||
markdownlint-cli2: 0.6.0
|
||||
netlify-plugin-submit-sitemap: 0.4.0
|
||||
node-fetch: 3.3.0
|
||||
postcss: 8.4.20
|
||||
postcss-cli: 10.1.0
|
||||
purgecss-whitelister: 2.4.0
|
||||
shx: 0.3.4
|
||||
stylelint: 14.16.0
|
||||
stylelint: 14.16.1
|
||||
stylelint-config-standard-scss: 6.1.0
|
||||
|
||||
devDependencies:
|
||||
|
@ -40,9 +40,9 @@ devDependencies:
|
|||
auto-changelog: 2.4.0
|
||||
autoprefixer: 10.4.13_postcss@8.4.20
|
||||
bootstrap: 5.2.3_@popperjs+core@2.11.6
|
||||
bootstrap-icons: 1.10.2
|
||||
bootstrap-icons: 1.10.3
|
||||
clipboard: 2.0.11
|
||||
eslint: 8.30.0
|
||||
eslint: 8.31.0
|
||||
exec-bin: 1.0.0
|
||||
flexsearch: 0.7.31
|
||||
highlight.js: 11.7.0
|
||||
|
@ -50,15 +50,15 @@ devDependencies:
|
|||
instant.page: 5.1.1
|
||||
katex: 0.16.4
|
||||
lazysizes: 5.3.2
|
||||
markdownlint-cli2: 0.5.1
|
||||
markdownlint-cli2: 0.6.0
|
||||
netlify-plugin-submit-sitemap: 0.4.0
|
||||
node-fetch: 3.3.0
|
||||
postcss: 8.4.20
|
||||
postcss-cli: 10.1.0_postcss@8.4.20
|
||||
purgecss-whitelister: 2.4.0
|
||||
shx: 0.3.4
|
||||
stylelint: 14.16.0
|
||||
stylelint-config-standard-scss: 6.1.0_l3s4qeu3gkar2knsdy3w5miimm
|
||||
stylelint: 14.16.1
|
||||
stylelint-config-standard-scss: 6.1.0_vitr26fcqo6sphdfxyxll4n2gy
|
||||
|
||||
packages:
|
||||
|
||||
|
@ -1331,8 +1331,8 @@ packages:
|
|||
postcss-selector-parser: 6.0.11
|
||||
dev: true
|
||||
|
||||
/@eslint/eslintrc/1.4.0:
|
||||
resolution: {integrity: sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==}
|
||||
/@eslint/eslintrc/1.4.1:
|
||||
resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
|
@ -1683,8 +1683,8 @@ packages:
|
|||
safe-buffer: 5.2.1
|
||||
dev: true
|
||||
|
||||
/bootstrap-icons/1.10.2:
|
||||
resolution: {integrity: sha512-PTPYadRn1AMGr+QTSxe4ZCc+Wzv9DGZxbi3lNse/dajqV31n2/wl/7NX78ZpkvFgRNmH4ogdIQPQmxAfhEV6nA==}
|
||||
/bootstrap-icons/1.10.3:
|
||||
resolution: {integrity: sha512-7Qvj0j0idEm/DdX9Q0CpxAnJYqBCFCiUI6qzSPYfERMcokVuV9Mdm/AJiVZI8+Gawe4h/l6zFcOzvV7oXCZArw==}
|
||||
dev: true
|
||||
|
||||
/bootstrap/5.2.3_@popperjs+core@2.11.6:
|
||||
|
@ -2073,7 +2073,7 @@ packages:
|
|||
resolution: {integrity: sha512-tQbV/4u5WVB8HMJr08pgw0b6nG4RGt/tj+7Numvq+zqcvUFeMaIWWOUFltiU+6go8BSO2/ogsB4EasDaj0y68Q==}
|
||||
engines: {node: '>=14.16'}
|
||||
dependencies:
|
||||
globby: 13.1.2
|
||||
globby: 13.1.3
|
||||
graceful-fs: 4.2.10
|
||||
is-glob: 4.0.3
|
||||
is-path-cwd: 3.0.0
|
||||
|
@ -2159,13 +2159,13 @@ packages:
|
|||
estraverse: 5.3.0
|
||||
dev: true
|
||||
|
||||
/eslint-utils/3.0.0_eslint@8.30.0:
|
||||
/eslint-utils/3.0.0_eslint@8.31.0:
|
||||
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
|
||||
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
|
||||
peerDependencies:
|
||||
eslint: '>=5'
|
||||
dependencies:
|
||||
eslint: 8.30.0
|
||||
eslint: 8.31.0
|
||||
eslint-visitor-keys: 2.1.0
|
||||
dev: true
|
||||
|
||||
|
@ -2179,12 +2179,12 @@ packages:
|
|||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/eslint/8.30.0:
|
||||
resolution: {integrity: sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==}
|
||||
/eslint/8.31.0:
|
||||
resolution: {integrity: sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@eslint/eslintrc': 1.4.0
|
||||
'@eslint/eslintrc': 1.4.1
|
||||
'@humanwhocodes/config-array': 0.11.8
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
|
@ -2195,7 +2195,7 @@ packages:
|
|||
doctrine: 3.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 7.1.1
|
||||
eslint-utils: 3.0.0_eslint@8.30.0
|
||||
eslint-utils: 3.0.0_eslint@8.31.0
|
||||
eslint-visitor-keys: 3.3.0
|
||||
espree: 9.4.0
|
||||
esquery: 1.4.0
|
||||
|
@ -2553,6 +2553,17 @@ packages:
|
|||
slash: 4.0.0
|
||||
dev: true
|
||||
|
||||
/globby/13.1.3:
|
||||
resolution: {integrity: sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
dir-glob: 3.0.1
|
||||
fast-glob: 3.2.12
|
||||
ignore: 5.2.1
|
||||
merge2: 1.4.1
|
||||
slash: 4.0.0
|
||||
dev: true
|
||||
|
||||
/globjoin/0.1.4:
|
||||
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
|
||||
dev: true
|
||||
|
@ -3037,30 +3048,30 @@ packages:
|
|||
uc.micro: 1.0.6
|
||||
dev: true
|
||||
|
||||
/markdownlint-cli2-formatter-default/0.0.3_markdownlint-cli2@0.5.1:
|
||||
/markdownlint-cli2-formatter-default/0.0.3_markdownlint-cli2@0.6.0:
|
||||
resolution: {integrity: sha512-QEAJitT5eqX1SNboOD+SO/LNBpu4P4je8JlR02ug2cLQAqmIhh8IJnSK7AcaHBHhNADqdGydnPpQOpsNcEEqCw==}
|
||||
peerDependencies:
|
||||
markdownlint-cli2: '>=0.0.4'
|
||||
dependencies:
|
||||
markdownlint-cli2: 0.5.1
|
||||
markdownlint-cli2: 0.6.0
|
||||
dev: true
|
||||
|
||||
/markdownlint-cli2/0.5.1:
|
||||
resolution: {integrity: sha512-f3Nb1GF/c8YSrV/FntsCWzpa5mLFJRlO+wzEgv+lkNQjU6MZflUwc2FbyEDPTo6oVhP2VyUOkK0GkFgfuktl1w==}
|
||||
engines: {node: '>=14'}
|
||||
/markdownlint-cli2/0.6.0:
|
||||
resolution: {integrity: sha512-Bv20r6WGdcHMWi8QvAFZ3CBunf4i4aYmVdTfpAvXODI/1k3f09DZZ0i0LcX9ZMhlVxjoOzbVDz1NWyKc5hwTqg==}
|
||||
engines: {node: '>=14.18.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
globby: 13.1.2
|
||||
markdownlint: 0.26.2
|
||||
markdownlint-cli2-formatter-default: 0.0.3_markdownlint-cli2@0.5.1
|
||||
globby: 13.1.3
|
||||
markdownlint: 0.27.0
|
||||
markdownlint-cli2-formatter-default: 0.0.3_markdownlint-cli2@0.6.0
|
||||
micromatch: 4.0.5
|
||||
strip-json-comments: 5.0.0
|
||||
yaml: 2.1.1
|
||||
yaml: 2.2.1
|
||||
dev: true
|
||||
|
||||
/markdownlint/0.26.2:
|
||||
resolution: {integrity: sha512-2Am42YX2Ex5SQhRq35HxYWDfz1NLEOZWWN25nqd2h3AHRKsGRE+Qg1gt1++exW792eXTrR4jCNHfShfWk9Nz8w==}
|
||||
engines: {node: '>=14'}
|
||||
/markdownlint/0.27.0:
|
||||
resolution: {integrity: sha512-HtfVr/hzJJmE0C198F99JLaeada+646B5SaG2pVoEakLFI6iRGsvMqrnnrflq8hm1zQgwskEgqSnhDW11JBp0w==}
|
||||
engines: {node: '>=14.18.0'}
|
||||
dependencies:
|
||||
markdown-it: 13.0.1
|
||||
dev: true
|
||||
|
@ -3431,7 +3442,7 @@ packages:
|
|||
dependencies:
|
||||
lilconfig: 2.0.5
|
||||
postcss: 8.4.20
|
||||
yaml: 2.1.1
|
||||
yaml: 2.2.1
|
||||
dev: true
|
||||
|
||||
/postcss-media-query-parser/0.2.3:
|
||||
|
@ -3928,7 +3939,7 @@ packages:
|
|||
resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
|
||||
dev: true
|
||||
|
||||
/stylelint-config-recommended-scss/8.0.0_l3s4qeu3gkar2knsdy3w5miimm:
|
||||
/stylelint-config-recommended-scss/8.0.0_vitr26fcqo6sphdfxyxll4n2gy:
|
||||
resolution: {integrity: sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==}
|
||||
peerDependencies:
|
||||
postcss: ^8.3.3
|
||||
|
@ -3939,20 +3950,20 @@ packages:
|
|||
dependencies:
|
||||
postcss: 8.4.20
|
||||
postcss-scss: 4.0.4_postcss@8.4.20
|
||||
stylelint: 14.16.0
|
||||
stylelint-config-recommended: 9.0.0_stylelint@14.16.0
|
||||
stylelint-scss: 4.2.0_stylelint@14.16.0
|
||||
stylelint: 14.16.1
|
||||
stylelint-config-recommended: 9.0.0_stylelint@14.16.1
|
||||
stylelint-scss: 4.2.0_stylelint@14.16.1
|
||||
dev: true
|
||||
|
||||
/stylelint-config-recommended/9.0.0_stylelint@14.16.0:
|
||||
/stylelint-config-recommended/9.0.0_stylelint@14.16.1:
|
||||
resolution: {integrity: sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==}
|
||||
peerDependencies:
|
||||
stylelint: ^14.10.0
|
||||
dependencies:
|
||||
stylelint: 14.16.0
|
||||
stylelint: 14.16.1
|
||||
dev: true
|
||||
|
||||
/stylelint-config-standard-scss/6.1.0_l3s4qeu3gkar2knsdy3w5miimm:
|
||||
/stylelint-config-standard-scss/6.1.0_vitr26fcqo6sphdfxyxll4n2gy:
|
||||
resolution: {integrity: sha512-iZ2B5kQT2G3rUzx+437cEpdcnFOQkwnwqXuY8Z0QUwIHQVE8mnYChGAquyKFUKZRZ0pRnrciARlPaR1RBtPb0Q==}
|
||||
peerDependencies:
|
||||
postcss: ^8.3.3
|
||||
|
@ -3962,21 +3973,21 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
postcss: 8.4.20
|
||||
stylelint: 14.16.0
|
||||
stylelint-config-recommended-scss: 8.0.0_l3s4qeu3gkar2knsdy3w5miimm
|
||||
stylelint-config-standard: 29.0.0_stylelint@14.16.0
|
||||
stylelint: 14.16.1
|
||||
stylelint-config-recommended-scss: 8.0.0_vitr26fcqo6sphdfxyxll4n2gy
|
||||
stylelint-config-standard: 29.0.0_stylelint@14.16.1
|
||||
dev: true
|
||||
|
||||
/stylelint-config-standard/29.0.0_stylelint@14.16.0:
|
||||
/stylelint-config-standard/29.0.0_stylelint@14.16.1:
|
||||
resolution: {integrity: sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==}
|
||||
peerDependencies:
|
||||
stylelint: ^14.14.0
|
||||
dependencies:
|
||||
stylelint: 14.16.0
|
||||
stylelint-config-recommended: 9.0.0_stylelint@14.16.0
|
||||
stylelint: 14.16.1
|
||||
stylelint-config-recommended: 9.0.0_stylelint@14.16.1
|
||||
dev: true
|
||||
|
||||
/stylelint-scss/4.2.0_stylelint@14.16.0:
|
||||
/stylelint-scss/4.2.0_stylelint@14.16.1:
|
||||
resolution: {integrity: sha512-HHHMVKJJ5RM9pPIbgJ/XA67h9H0407G68Rm69H4fzFbFkyDMcTV1Byep3qdze5+fJ3c0U7mJrbj6S0Fg072uZA==}
|
||||
peerDependencies:
|
||||
stylelint: ^14.5.1
|
||||
|
@ -3986,11 +3997,11 @@ packages:
|
|||
postcss-resolve-nested-selector: 0.1.1
|
||||
postcss-selector-parser: 6.0.11
|
||||
postcss-value-parser: 4.2.0
|
||||
stylelint: 14.16.0
|
||||
stylelint: 14.16.1
|
||||
dev: true
|
||||
|
||||
/stylelint/14.16.0:
|
||||
resolution: {integrity: sha512-X6uTi9DcxjzLV8ZUAjit1vsRtSwcls0nl07c9rqOPzvpA8IvTX/xWEkBRowS0ffevRrqkHa/ThDEu86u73FQDg==}
|
||||
/stylelint/14.16.1:
|
||||
resolution: {integrity: sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
|
@ -4339,8 +4350,8 @@ packages:
|
|||
engines: {node: '>= 6'}
|
||||
dev: true
|
||||
|
||||
/yaml/2.1.1:
|
||||
resolution: {integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==}
|
||||
/yaml/2.2.1:
|
||||
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: true
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 236 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 62 KiB |
18
go.mod
18
go.mod
|
@ -5,7 +5,7 @@ go 1.19
|
|||
require (
|
||||
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||
github.com/deckarep/golang-set v1.8.0
|
||||
github.com/deckarep/golang-set/v2 v2.1.0
|
||||
github.com/duosecurity/duo_api_golang v0.0.0-20221117185402-091daa09e19d
|
||||
github.com/fasthttp/router v1.4.14
|
||||
github.com/fasthttp/session/v2 v2.4.13
|
||||
|
@ -19,16 +19,16 @@ require (
|
|||
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2
|
||||
github.com/jackc/pgx/v5 v5.2.0
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/knadh/koanf v1.4.4
|
||||
github.com/knadh/koanf v1.4.5
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/ory/fosite v0.44.0
|
||||
github.com/ory/herodot v0.9.13
|
||||
github.com/ory/x v0.0.523
|
||||
github.com/ory/x v0.0.528
|
||||
github.com/otiai10/copy v1.9.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/otp v1.4.0
|
||||
|
@ -39,11 +39,10 @@ require (
|
|||
github.com/stretchr/testify v1.8.1
|
||||
github.com/trustelem/zxcvbn v1.0.1
|
||||
github.com/valyala/fasthttp v1.43.0
|
||||
github.com/wneessen/go-mail v0.3.6
|
||||
golang.org/x/net v0.4.0
|
||||
github.com/wneessen/go-mail v0.3.7
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/term v0.3.0
|
||||
golang.org/x/text v0.5.0
|
||||
golang.org/x/term v0.4.0
|
||||
golang.org/x/text v0.6.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
@ -110,8 +109,9 @@ require (
|
|||
github.com/ysmood/leakless v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/net v0.5.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect
|
||||
|
|
36
go.sum
36
go.sum
|
@ -113,8 +113,8 @@ github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+Egvsz
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
|
||||
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
|
||||
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
|
||||
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE=
|
||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
|
@ -305,8 +305,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
|
|||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
|
@ -366,8 +366,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/knadh/koanf v1.4.4 h1:d2jY5nCCeoaiqvEKSBW9rEc93EfNy/XWgWsSB3j7JEA=
|
||||
github.com/knadh/koanf v1.4.4/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||
github.com/knadh/koanf v1.4.5 h1:yKWFswTrqFc0u7jBAoERUz30+N1b1yPXU01gAPr8IrY=
|
||||
github.com/knadh/koanf v1.4.5/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
|
@ -461,8 +461,8 @@ github.com/ory/herodot v0.9.13 h1:cN/Z4eOkErl/9W7hDIDLb79IO/bfsH+8yscBjRpB4IU=
|
|||
github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo=
|
||||
github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE=
|
||||
github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=
|
||||
github.com/ory/x v0.0.523 h1:vn8e+8tV3RqD8RlvoE6lLPUnjpjua1ExJDMFy3Z5TAQ=
|
||||
github.com/ory/x v0.0.523/go.mod h1:ayJio5x/fK4RwTgfgzs3JetOaaOSxso9hQjc3mFY8z0=
|
||||
github.com/ory/x v0.0.528 h1:26fXxJ5Zl5XDFjiCt5jdWQOI89Q2XogB1EnUeqx7P+M=
|
||||
github.com/ory/x v0.0.528/go.mod h1:XBqhPZRppPHTxtsE0l0oI/B2Onf1QJtMRGPh3CpEpA0=
|
||||
github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4=
|
||||
github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
|
@ -609,8 +609,8 @@ github.com/valyala/fasthttp v1.42.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seB
|
|||
github.com/valyala/fasthttp v1.43.0 h1:Gy4sb32C98fbzVWZlTM1oTMdLWGyvxR03VhM6cBIU4g=
|
||||
github.com/valyala/fasthttp v1.43.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/wneessen/go-mail v0.3.6 h1:hT8PMIBdcTkoiDwoUGJssPYOe1Gg1/cUcp2o9+ls63o=
|
||||
github.com/wneessen/go-mail v0.3.6/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/wneessen/go-mail v0.3.7 h1:loEAGLvsDZLSiE6c+keBfg0gpias/R3ocFU8Eoh3Pq4=
|
||||
github.com/wneessen/go-mail v0.3.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
|
@ -747,8 +747,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -849,12 +849,12 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -865,8 +865,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
|
@ -8,7 +9,6 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/notification"
|
||||
"github.com/authelia/authelia/v4/internal/ntp"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
"github.com/authelia/authelia/v4/internal/regulation"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
"github.com/authelia/authelia/v4/internal/storage"
|
||||
|
@ -43,6 +44,9 @@ func NewCmdCtx() *CmdCtx {
|
|||
cancel: cancel,
|
||||
group: group,
|
||||
log: logging.Logger(),
|
||||
providers: middlewares.Providers{
|
||||
Random: &random.Cryptographical{},
|
||||
},
|
||||
config: &schema.Configuration{},
|
||||
}
|
||||
}
|
||||
|
@ -139,48 +143,43 @@ func (ctx *CmdCtx) LoadProviders() (warns, errs []error) {
|
|||
return warns, errs
|
||||
}
|
||||
|
||||
storage := getStorageProvider(ctx)
|
||||
ctx.providers.StorageProvider = getStorageProvider(ctx)
|
||||
|
||||
providers := middlewares.Providers{
|
||||
Authorizer: authorization.NewAuthorizer(ctx.config),
|
||||
NTP: ntp.NewProvider(&ctx.config.NTP),
|
||||
PasswordPolicy: middlewares.NewPasswordPolicyProvider(ctx.config.PasswordPolicy),
|
||||
Regulator: regulation.NewRegulator(ctx.config.Regulation, storage, utils.RealClock{}),
|
||||
SessionProvider: session.NewProvider(ctx.config.Session, ctx.trusted),
|
||||
StorageProvider: storage,
|
||||
TOTP: totp.NewTimeBasedProvider(ctx.config.TOTP),
|
||||
}
|
||||
ctx.providers.Authorizer = authorization.NewAuthorizer(ctx.config)
|
||||
ctx.providers.NTP = ntp.NewProvider(&ctx.config.NTP)
|
||||
ctx.providers.PasswordPolicy = middlewares.NewPasswordPolicyProvider(ctx.config.PasswordPolicy)
|
||||
ctx.providers.Regulator = regulation.NewRegulator(ctx.config.Regulation, ctx.providers.StorageProvider, utils.RealClock{})
|
||||
ctx.providers.SessionProvider = session.NewProvider(ctx.config.Session, ctx.trusted)
|
||||
ctx.providers.TOTP = totp.NewTimeBasedProvider(ctx.config.TOTP)
|
||||
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case ctx.config.AuthenticationBackend.File != nil:
|
||||
providers.UserProvider = authentication.NewFileUserProvider(ctx.config.AuthenticationBackend.File)
|
||||
ctx.providers.UserProvider = authentication.NewFileUserProvider(ctx.config.AuthenticationBackend.File)
|
||||
case ctx.config.AuthenticationBackend.LDAP != nil:
|
||||
providers.UserProvider = authentication.NewLDAPUserProvider(ctx.config.AuthenticationBackend, ctx.trusted)
|
||||
ctx.providers.UserProvider = authentication.NewLDAPUserProvider(ctx.config.AuthenticationBackend, ctx.trusted)
|
||||
}
|
||||
|
||||
if providers.Templates, err = templates.New(templates.Config{EmailTemplatesPath: ctx.config.Notifier.TemplatePath}); err != nil {
|
||||
if ctx.providers.Templates, err = templates.New(templates.Config{EmailTemplatesPath: ctx.config.Notifier.TemplatePath}); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctx.config.Notifier.SMTP != nil:
|
||||
providers.Notifier = notification.NewSMTPNotifier(ctx.config.Notifier.SMTP, ctx.trusted)
|
||||
ctx.providers.Notifier = notification.NewSMTPNotifier(ctx.config.Notifier.SMTP, ctx.trusted)
|
||||
case ctx.config.Notifier.FileSystem != nil:
|
||||
providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
|
||||
ctx.providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
|
||||
}
|
||||
|
||||
if providers.OpenIDConnect, err = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, storage); err != nil {
|
||||
if ctx.providers.OpenIDConnect, err = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if ctx.config.Telemetry.Metrics.Enabled {
|
||||
providers.Metrics = metrics.NewPrometheus()
|
||||
ctx.providers.Metrics = metrics.NewPrometheus()
|
||||
}
|
||||
|
||||
ctx.providers = providers
|
||||
|
||||
return warns, errs
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package commands
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
|
@ -262,7 +261,7 @@ func (ctx *CmdCtx) CryptoGenerateRunE(cmd *cobra.Command, args []string) (err er
|
|||
privateKey any
|
||||
)
|
||||
|
||||
if privateKey, err = cryptoGenPrivateKeyFromCmd(cmd); err != nil {
|
||||
if privateKey, err = ctx.cryptoGenPrivateKeyFromCmd(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -279,7 +278,7 @@ func (ctx *CmdCtx) CryptoCertificateRequestRunE(cmd *cobra.Command, _ []string)
|
|||
privateKey any
|
||||
)
|
||||
|
||||
if privateKey, err = cryptoGenPrivateKeyFromCmd(cmd); err != nil {
|
||||
if privateKey, err = ctx.cryptoGenPrivateKeyFromCmd(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -326,7 +325,7 @@ func (ctx *CmdCtx) CryptoCertificateRequestRunE(cmd *cobra.Command, _ []string)
|
|||
|
||||
b.Reset()
|
||||
|
||||
if csr, err = x509.CreateCertificateRequest(rand.Reader, template, privateKey); err != nil {
|
||||
if csr, err = x509.CreateCertificateRequest(ctx.providers.Random, template, privateKey); err != nil {
|
||||
return fmt.Errorf("failed to create certificate request: %w", err)
|
||||
}
|
||||
|
||||
|
@ -366,7 +365,7 @@ func (ctx *CmdCtx) CryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string,
|
|||
signatureKey = caPrivateKey
|
||||
}
|
||||
|
||||
if template, err = cryptoGetCertificateFromCmd(cmd); err != nil {
|
||||
if template, err = ctx.cryptoGetCertificateFromCmd(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -423,7 +422,7 @@ func (ctx *CmdCtx) CryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string,
|
|||
|
||||
b.Reset()
|
||||
|
||||
if certificate, err = x509.CreateCertificate(rand.Reader, template, parent, publicKey, signatureKey); err != nil {
|
||||
if certificate, err = x509.CreateCertificate(ctx.providers.Random, template, parent, publicKey, signatureKey); err != nil {
|
||||
return fmt.Errorf("failed to create certificate: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
|
@ -130,7 +129,7 @@ func cryptoGetWritePathsFromCmd(cmd *cobra.Command) (privateKey, publicKey strin
|
|||
return filepath.Join(dir, private), filepath.Join(dir, public), nil
|
||||
}
|
||||
|
||||
func cryptoGenPrivateKeyFromCmd(cmd *cobra.Command) (privateKey any, err error) {
|
||||
func (ctx *CmdCtx) cryptoGenPrivateKeyFromCmd(cmd *cobra.Command) (privateKey any, err error) {
|
||||
switch cmd.Parent().Use {
|
||||
case cmdUseRSA:
|
||||
var (
|
||||
|
@ -141,7 +140,7 @@ func cryptoGenPrivateKeyFromCmd(cmd *cobra.Command) (privateKey any, err error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if privateKey, err = rsa.GenerateKey(rand.Reader, bits); err != nil {
|
||||
if privateKey, err = rsa.GenerateKey(ctx.providers.Random, bits); err != nil {
|
||||
return nil, fmt.Errorf("generating RSA private key resulted in an error: %w", err)
|
||||
}
|
||||
case cmdUseECDSA:
|
||||
|
@ -158,11 +157,11 @@ func cryptoGenPrivateKeyFromCmd(cmd *cobra.Command) (privateKey any, err error)
|
|||
return nil, fmt.Errorf("invalid curve '%s' was specified: curve must be P224, P256, P384, or P521", curveStr)
|
||||
}
|
||||
|
||||
if privateKey, err = ecdsa.GenerateKey(curve, rand.Reader); err != nil {
|
||||
if privateKey, err = ecdsa.GenerateKey(curve, ctx.providers.Random); err != nil {
|
||||
return nil, fmt.Errorf("generating ECDSA private key resulted in an error: %w", err)
|
||||
}
|
||||
case cmdUseEd25519:
|
||||
if _, privateKey, err = ed25519.GenerateKey(rand.Reader); err != nil {
|
||||
if _, privateKey, err = ed25519.GenerateKey(ctx.providers.Random); err != nil {
|
||||
return nil, fmt.Errorf("generating Ed25519 private key resulted in an error: %w", err)
|
||||
}
|
||||
}
|
||||
|
@ -336,7 +335,7 @@ func cryptoGetSubjectFromCmd(cmd *cobra.Command) (subject *pkix.Name, err error)
|
|||
}, nil
|
||||
}
|
||||
|
||||
func cryptoGetCertificateFromCmd(cmd *cobra.Command) (certificate *x509.Certificate, err error) {
|
||||
func (ctx *CmdCtx) cryptoGetCertificateFromCmd(cmd *cobra.Command) (certificate *x509.Certificate, err error) {
|
||||
var (
|
||||
ca bool
|
||||
subject *pkix.Name
|
||||
|
@ -378,7 +377,7 @@ func cryptoGetCertificateFromCmd(cmd *cobra.Command) (certificate *x509.Certific
|
|||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
|
||||
if serialNumber, err = rand.Int(rand.Reader, serialNumberLimit); err != nil {
|
||||
if serialNumber, err = ctx.providers.Random.IntErr(serialNumberLimit); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate serial number: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -122,6 +122,8 @@ func runServices(ctx *CmdCtx) {
|
|||
}()
|
||||
|
||||
if mainServer, mainListener, err = server.CreateDefaultServer(*ctx.config, ctx.providers); err != nil {
|
||||
ctx.log.WithError(err).Error("Create Server (main) returned error")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -146,6 +148,8 @@ func runServices(ctx *CmdCtx) {
|
|||
}()
|
||||
|
||||
if metricsServer, metricsListener, err = server.CreateMetricsServer(ctx.config.Telemetry.Metrics); err != nil {
|
||||
ctx.log.WithError(err).Error("Create Server (metrics) returned error")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -163,7 +167,11 @@ func runServices(ctx *CmdCtx) {
|
|||
if watcher, err := runServiceFileWatcher(ctx, ctx.config.AuthenticationBackend.File.Path, provider); err != nil {
|
||||
ctx.log.WithError(err).Errorf("Error opening file watcher")
|
||||
} else {
|
||||
defer watcher.Close()
|
||||
defer func(watcher *fsnotify.Watcher) {
|
||||
if err := watcher.Close(); err != nil {
|
||||
ctx.log.WithError(err).Errorf("Error closing file watcher")
|
||||
}
|
||||
}(watcher)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/validator"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
"github.com/authelia/authelia/v4/internal/storage"
|
||||
"github.com/authelia/authelia/v4/internal/totp"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
|
@ -983,7 +984,8 @@ func (ctx *CmdCtx) StorageUserTOTPExportPNGRunE(cmd *cobra.Command, _ []string)
|
|||
}
|
||||
|
||||
if dir == "" {
|
||||
dir = utils.RandomString(8, utils.CharSetAlphaNumeric)
|
||||
rand := &random.Cryptographical{}
|
||||
dir = rand.StringCustom(8, random.CharSetAlphaNumeric)
|
||||
}
|
||||
|
||||
if _, err = os.Stat(dir); !os.IsNotExist(err) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"golang.org/x/term"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
)
|
||||
|
||||
func recoverErr(i any) error {
|
||||
|
@ -77,29 +77,29 @@ func flagsGetRandomCharacters(flags *pflag.FlagSet, flagNameLength, flagNameChar
|
|||
|
||||
switch c {
|
||||
case "ascii":
|
||||
charset = utils.CharSetASCII
|
||||
charset = random.CharSetASCII
|
||||
case "alphanumeric":
|
||||
charset = utils.CharSetAlphaNumeric
|
||||
charset = random.CharSetAlphaNumeric
|
||||
case "alphanumeric-lower":
|
||||
charset = utils.CharSetAlphabeticLower + utils.CharSetNumeric
|
||||
charset = random.CharSetAlphabeticLower + random.CharSetNumeric
|
||||
case "alphanumeric-upper":
|
||||
charset = utils.CharSetAlphabeticUpper + utils.CharSetNumeric
|
||||
charset = random.CharSetAlphabeticUpper + random.CharSetNumeric
|
||||
case "alphabetic":
|
||||
charset = utils.CharSetAlphabetic
|
||||
charset = random.CharSetAlphabetic
|
||||
case "alphabetic-lower":
|
||||
charset = utils.CharSetAlphabeticLower
|
||||
charset = random.CharSetAlphabeticLower
|
||||
case "alphabetic-upper":
|
||||
charset = utils.CharSetAlphabeticUpper
|
||||
charset = random.CharSetAlphabeticUpper
|
||||
case "numeric-hex":
|
||||
charset = utils.CharSetNumericHex
|
||||
charset = random.CharSetNumericHex
|
||||
case "numeric":
|
||||
charset = utils.CharSetNumeric
|
||||
charset = random.CharSetNumeric
|
||||
case "rfc3986":
|
||||
charset = utils.CharSetRFC3986Unreserved
|
||||
charset = random.CharSetRFC3986Unreserved
|
||||
case "rfc3986-lower":
|
||||
charset = utils.CharSetAlphabeticLower + utils.CharSetNumeric + utils.CharSetSymbolicRFC3986Unreserved
|
||||
charset = random.CharSetAlphabeticLower + random.CharSetNumeric + random.CharSetSymbolicRFC3986Unreserved
|
||||
case "rfc3986-upper":
|
||||
charset = utils.CharSetAlphabeticUpper + utils.CharSetNumeric + utils.CharSetSymbolicRFC3986Unreserved
|
||||
charset = random.CharSetAlphabeticUpper + random.CharSetNumeric + random.CharSetSymbolicRFC3986Unreserved
|
||||
default:
|
||||
return "", fmt.Errorf("flag '--%s' with value '%s' is invalid, must be one of 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', or 'rfc3986'", flagNameCharSet, c)
|
||||
}
|
||||
|
@ -109,7 +109,9 @@ func flagsGetRandomCharacters(flags *pflag.FlagSet, flagNameLength, flagNameChar
|
|||
}
|
||||
}
|
||||
|
||||
return utils.RandomString(n, charset), nil
|
||||
rand := &random.Cryptographical{}
|
||||
|
||||
return rand.StringCustom(n, charset), nil
|
||||
}
|
||||
|
||||
func termReadConfirmation(flags *pflag.FlagSet, name, prompt, confirmation string) (confirmed bool, err error) {
|
||||
|
|
|
@ -1394,15 +1394,9 @@ notifier:
|
|||
## Sets the client to public. This should typically not be set, please see the documentation for usage.
|
||||
# public: false
|
||||
|
||||
## The policy to require for this client; one_factor or two_factor.
|
||||
# authorization_policy: two_factor
|
||||
|
||||
## The consent mode controls how consent is obtained.
|
||||
# consent_mode: auto
|
||||
|
||||
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
||||
## configured as 'auto' or 'pre-configured'.
|
||||
# pre_configured_consent_duration: 1w
|
||||
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
|
||||
# redirect_uris:
|
||||
# - https://oidc.example.com:8080/oauth2/callback
|
||||
|
||||
## Audience this client is allowed to request.
|
||||
# audience: []
|
||||
|
@ -1414,10 +1408,6 @@ notifier:
|
|||
# - email
|
||||
# - profile
|
||||
|
||||
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
|
||||
# redirect_uris:
|
||||
# - https://oidc.example.com:8080/oauth2/callback
|
||||
|
||||
## Grant Types configures which grants this client can obtain.
|
||||
## It's not recommended to define this unless you know what you're doing.
|
||||
# grant_types:
|
||||
|
@ -1435,6 +1425,23 @@ notifier:
|
|||
# - query
|
||||
# - fragment
|
||||
|
||||
## The policy to require for this client; one_factor or two_factor.
|
||||
# authorization_policy: two_factor
|
||||
|
||||
## Enforces the use of PKCE for this client when set to true.
|
||||
# enforce_pkce: false
|
||||
|
||||
## Enforces the use of PKCE for this client when configured, and enforces the specified challenge method.
|
||||
## Options are 'plain' and 'S256'.
|
||||
# pkce_challenge_method: S256
|
||||
|
||||
## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256.
|
||||
# userinfo_signing_algorithm: none
|
||||
|
||||
## The consent mode controls how consent is obtained.
|
||||
# consent_mode: auto
|
||||
|
||||
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
||||
## configured as 'auto' or 'pre-configured'.
|
||||
# pre_configured_consent_duration: 1w
|
||||
...
|
||||
|
|
|
@ -57,10 +57,13 @@ type OpenIDConnectClientConfiguration struct {
|
|||
ResponseTypes []string `koanf:"response_types"`
|
||||
ResponseModes []string `koanf:"response_modes"`
|
||||
|
||||
UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"`
|
||||
|
||||
Policy string `koanf:"authorization_policy"`
|
||||
|
||||
EnforcePKCE bool `koanf:"enforce_pkce"`
|
||||
|
||||
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
|
||||
UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"`
|
||||
|
||||
ConsentMode string `koanf:"consent_mode"`
|
||||
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
|
||||
}
|
||||
|
|
|
@ -43,8 +43,10 @@ var Keys = []string{
|
|||
"identity_providers.oidc.clients[].grant_types",
|
||||
"identity_providers.oidc.clients[].response_types",
|
||||
"identity_providers.oidc.clients[].response_modes",
|
||||
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
|
||||
"identity_providers.oidc.clients[].authorization_policy",
|
||||
"identity_providers.oidc.clients[].enforce_pkce",
|
||||
"identity_providers.oidc.clients[].pkce_challenge_method",
|
||||
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
|
||||
"identity_providers.oidc.clients[].consent_mode",
|
||||
"identity_providers.oidc.clients[].pre_configured_consent_duration",
|
||||
"authentication_backend.password_reset.disable",
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/go-webauthn/webauthn/protocol"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
)
|
||||
|
||||
|
@ -172,6 +171,8 @@ const (
|
|||
"invalid value: redirect uri '%s' must have the scheme but it is absent"
|
||||
errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " +
|
||||
"or 'two_factor' but it is configured as '%s'"
|
||||
errFmtOIDCClientInvalidPKCEChallengeMethod = "identity_providers: oidc: client '%s': option 'pkce_challenge_method' must be 'plain' " +
|
||||
"or 'S256' but it is configured as '%s'"
|
||||
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " +
|
||||
"'%s' but it is configured as '%s'"
|
||||
errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
|
||||
|
|
|
@ -175,6 +175,13 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.
|
|||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
|
||||
}
|
||||
|
||||
switch client.PKCEChallengeMethod {
|
||||
case "", "plain", "S256":
|
||||
break
|
||||
default:
|
||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidPKCEChallengeMethod, client.ID, client.PKCEChallengeMethod))
|
||||
}
|
||||
|
||||
validateOIDCClientConsentMode(c, config, val)
|
||||
validateOIDCClientSectorIdentifier(client, val)
|
||||
validateOIDCClientScopes(c, config, val)
|
||||
|
|
|
@ -331,6 +331,40 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
|
|||
fmt.Sprintf(errFmtOIDCClientInvalidConsentMode, "client-bad-consent-mode", strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), "cap"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "InvalidPKCEChallengeMethod",
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
ID: "client-bad-pkce-mode",
|
||||
Secret: MustDecodeSecret("$plaintext$a-secret"),
|
||||
Policy: policyTwoFactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
PKCEChallengeMethod: "abc",
|
||||
},
|
||||
},
|
||||
Errors: []string{
|
||||
fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode", "abc"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "InvalidPKCEChallengeMethodLowerCaseS256",
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
ID: "client-bad-pkce-mode-s256",
|
||||
Secret: MustDecodeSecret("$plaintext$a-secret"),
|
||||
Policy: policyTwoFactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
PKCEChallengeMethod: "s256",
|
||||
},
|
||||
},
|
||||
Errors: []string{
|
||||
fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode-s256", "s256"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
@ -609,7 +643,7 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi
|
|||
assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errFmtOIDCClientRedirectURIPublic, "client-with-bad-redirect-uri", oauth2InstalledApp))
|
||||
}
|
||||
|
||||
func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *testing.T) {
|
||||
func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidClientOptions(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := &schema.IdentityProvidersConfiguration{
|
||||
OIDC: &schema.OpenIDConnectConfiguration{
|
||||
|
@ -640,6 +674,24 @@ func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *te
|
|||
"http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "client-with-pkce-mode-plain",
|
||||
Public: true,
|
||||
Policy: "two_factor",
|
||||
RedirectURIs: []string{
|
||||
"https://pkce.com",
|
||||
},
|
||||
PKCEChallengeMethod: "plain",
|
||||
},
|
||||
{
|
||||
ID: "client-with-pkce-mode-S256",
|
||||
Public: true,
|
||||
Policy: "two_factor",
|
||||
RedirectURIs: []string{
|
||||
"https://pkce.com",
|
||||
},
|
||||
PKCEChallengeMethod: "S256",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -63,3 +63,33 @@ func TestCheckSafeRedirection_SafeRedirection(t *testing.T) {
|
|||
OK: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestShouldFailOnInvalidBody(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
||||
Username: "john",
|
||||
AuthenticationLevel: authentication.OneFactor,
|
||||
})
|
||||
defer mock.Close()
|
||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
||||
|
||||
mock.SetRequestBody(t, "not a valid json")
|
||||
|
||||
CheckSafeRedirectionPOST(mock.Ctx)
|
||||
mock.Assert200KO(t, "Operation failed.")
|
||||
}
|
||||
|
||||
func TestShouldFailOnInvalidURL(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
||||
Username: "john",
|
||||
AuthenticationLevel: authentication.OneFactor,
|
||||
})
|
||||
defer mock.Close()
|
||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
||||
|
||||
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
||||
URI: "https//invalid-url",
|
||||
})
|
||||
|
||||
CheckSafeRedirectionPOST(mock.Ctx)
|
||||
mock.Assert200KO(t, "Operation failed.")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
// "strings".
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/mocks"
|
||||
)
|
||||
|
||||
type passwordPolicyResponseBody struct {
|
||||
Status string
|
||||
Data PasswordPolicyBody
|
||||
}
|
||||
|
||||
type PasswordPolicySuite struct {
|
||||
suite.Suite
|
||||
|
||||
mock *mocks.MockAutheliaCtx
|
||||
}
|
||||
|
||||
func (s *PasswordPolicySuite) SetupTest() {
|
||||
s.mock = mocks.NewMockAutheliaCtx(s.T())
|
||||
}
|
||||
|
||||
func (s *PasswordPolicySuite) TearDownTest() {
|
||||
s.mock.Close()
|
||||
}
|
||||
|
||||
func (s *PasswordPolicySuite) TestShouldBeDisabled() {
|
||||
s.mock.Ctx.Configuration.PasswordPolicy.ZXCVBN.Enabled = false
|
||||
s.mock.Ctx.Configuration.PasswordPolicy.Standard.Enabled = false
|
||||
|
||||
PasswordPolicyConfigurationGET(s.mock.Ctx)
|
||||
|
||||
response := &passwordPolicyResponseBody{}
|
||||
err := json.Unmarshal(s.mock.Ctx.Response.Body(), response)
|
||||
|
||||
require.NoError(s.T(), err)
|
||||
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
||||
assert.Equal(s.T(), "disabled", response.Data.Mode)
|
||||
}
|
||||
|
||||
func (s *PasswordPolicySuite) TestShouldBeStandard() {
|
||||
s.mock.Ctx.Configuration.PasswordPolicy.ZXCVBN.Enabled = false
|
||||
s.mock.Ctx.Configuration.PasswordPolicy.Standard.Enabled = true
|
||||
s.mock.Ctx.Configuration.PasswordPolicy.Standard.MinLength = 4
|
||||
s.mock.Ctx.Configuration.PasswordPolicy.Standard.MaxLength = 8
|
||||
|
||||
PasswordPolicyConfigurationGET(s.mock.Ctx)
|
||||
|
||||
response := &passwordPolicyResponseBody{}
|
||||
err := json.Unmarshal(s.mock.Ctx.Response.Body(), response)
|
||||
|
||||
require.NoError(s.T(), err)
|
||||
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
||||
assert.Equal(s.T(), "standard", response.Data.Mode)
|
||||
assert.Equal(s.T(), 4, response.Data.MinLength)
|
||||
assert.Equal(s.T(), 8, response.Data.MaxLength)
|
||||
}
|
||||
|
||||
func (s *PasswordPolicySuite) TestShouldBeZXCVBN() {
|
||||
s.mock.Ctx.Configuration.PasswordPolicy.ZXCVBN.Enabled = true
|
||||
s.mock.Ctx.Configuration.PasswordPolicy.Standard.Enabled = false
|
||||
|
||||
PasswordPolicyConfigurationGET(s.mock.Ctx)
|
||||
|
||||
response := &passwordPolicyResponseBody{}
|
||||
err := json.Unmarshal(s.mock.Ctx.Response.Body(), response)
|
||||
|
||||
require.NoError(s.T(), err)
|
||||
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
||||
assert.Equal(s.T(), "zxcvbn", response.Data.Mode)
|
||||
}
|
||||
|
||||
func TestRunPasswordPolicySuite(t *testing.T) {
|
||||
s := new(PasswordPolicySuite)
|
||||
suite.Run(t, s)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/mocks"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
)
|
||||
|
||||
var okMessageBytes = []byte("{\"status\":\"OK\"}")
|
||||
|
||||
func TestHealthOk(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
||||
Username: "john",
|
||||
AuthenticationLevel: authentication.OneFactor,
|
||||
})
|
||||
defer mock.Close()
|
||||
|
||||
HealthGET(mock.Ctx)
|
||||
|
||||
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
||||
assert.Equal(t, okMessageBytes, mock.Ctx.Response.Body())
|
||||
}
|
|
@ -52,6 +52,16 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
|||
return
|
||||
}
|
||||
|
||||
if err = client.ValidateAuthorizationPolicy(requester); err != nil {
|
||||
rfc := fosite.ErrorToRFC6749Error(err)
|
||||
|
||||
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the authorization policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription())
|
||||
|
||||
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
issuer = ctx.RootURL()
|
||||
|
||||
userSession := ctx.GetSession()
|
||||
|
|
|
@ -407,3 +407,8 @@ func (ctx *AutheliaCtx) SetContentTypeTextHTML() {
|
|||
func (ctx *AutheliaCtx) SetContentTypeApplicationJSON() {
|
||||
ctx.SetContentTypeBytes(contentTypeApplicationJSON)
|
||||
}
|
||||
|
||||
// SetContentTypeApplicationYAML efficiently sets the Content-Type header to 'application/yaml; charset=utf-8'.
|
||||
func (ctx *AutheliaCtx) SetContentTypeApplicationYAML() {
|
||||
ctx.SetContentTypeBytes(contentTypeApplicationYAML)
|
||||
}
|
||||
|
|
|
@ -13,9 +13,66 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/mocks"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
"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) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
@ -44,8 +101,8 @@ func TestIssuerURL(t *testing.T) {
|
|||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
||||
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", tc.proto)
|
||||
mock.Ctx.Request.Header.Set("X-Forwarded-Host", tc.host)
|
||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, tc.proto)
|
||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, tc.host)
|
||||
|
||||
if tc.base != "" {
|
||||
mock.Ctx.SetUserValue("base_url", tc.base)
|
||||
|
@ -72,6 +129,7 @@ func TestShouldCallNextWithAutheliaCtx(t *testing.T) {
|
|||
providers := middlewares.Providers{
|
||||
UserProvider: userProvider,
|
||||
SessionProvider: sessionProvider,
|
||||
Random: random.NewMathematical(),
|
||||
}
|
||||
nextCalled := false
|
||||
|
||||
|
@ -103,8 +161,8 @@ func TestShouldGetOriginalURLFromOriginalURLHeader(t *testing.T) {
|
|||
func TestShouldGetOriginalURLFromForwardedHeadersWithoutURI(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
||||
mock.Ctx.Request.Header.Set("X-Forwarded-Host", "home.example.com")
|
||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
||||
mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "home.example.com")
|
||||
originalURL, err := mock.Ctx.GetOriginalURL()
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -142,7 +200,7 @@ func TestShouldOnlyFallbackToNonXForwardedHeadersWhenNil(t *testing.T) {
|
|||
mock.Ctx.RequestCtx.Request.SetHost("localhost")
|
||||
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-Proto", "https")
|
||||
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
|
||||
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-Method", "GET")
|
||||
|
||||
assert.Equal(t, []byte("https"), mock.Ctx.XForwardedProto())
|
||||
|
|
|
@ -88,6 +88,7 @@ var (
|
|||
contentTypeTextPlain = []byte("text/plain; charset=utf-8")
|
||||
contentTypeTextHTML = []byte("text/html; charset=utf-8")
|
||||
contentTypeApplicationJSON = []byte("application/json; charset=utf-8")
|
||||
contentTypeApplicationYAML = []byte("application/yaml; charset=utf-8")
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/mocks"
|
||||
|
@ -73,8 +74,8 @@ func TestShouldFailSendingAnEmail(t *testing.T) {
|
|||
defer mock.Close()
|
||||
|
||||
mock.Ctx.Configuration.JWTSecret = testJWTSecret
|
||||
mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
||||
mock.Ctx.Request.Header.Add("X-Forwarded-Host", "host")
|
||||
mock.Ctx.Request.Header.Add(fasthttp.HeaderXForwardedProto, "http")
|
||||
mock.Ctx.Request.Header.Add(fasthttp.HeaderXForwardedHost, "host")
|
||||
|
||||
mock.StorageMock.EXPECT().
|
||||
SaveIdentityVerification(mock.Ctx, gomock.Any()).
|
||||
|
@ -95,8 +96,8 @@ func TestShouldSucceedIdentityVerificationStartProcess(t *testing.T) {
|
|||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
|
||||
mock.Ctx.Configuration.JWTSecret = testJWTSecret
|
||||
mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
||||
mock.Ctx.Request.Header.Add("X-Forwarded-Host", "host")
|
||||
mock.Ctx.Request.Header.Add(fasthttp.HeaderXForwardedProto, "http")
|
||||
mock.Ctx.Request.Header.Add(fasthttp.HeaderXForwardedHost, "host")
|
||||
|
||||
mock.StorageMock.EXPECT().
|
||||
SaveIdentityVerification(mock.Ctx, gomock.Any()).
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
@ -62,7 +61,7 @@ func movingAverageIteration(value time.Duration, history int, successful bool, c
|
|||
}
|
||||
|
||||
func calculateActualDelay(ctx *AutheliaCtx, execDuration time.Duration, execDurationAvgMs, minDelayMs float64, maxRandomMs int64, successful bool) (actualDelayMs float64) {
|
||||
randomDelayMs, err := rand.Int(rand.Reader, big.NewInt(maxRandomMs))
|
||||
randomDelayMs, err := ctx.Providers.Random.IntErr(big.NewInt(maxRandomMs))
|
||||
if err != nil {
|
||||
return float64(maxRandomMs)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/logging"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
)
|
||||
|
||||
func TestTimingAttackDelayAverages(t *testing.T) {
|
||||
|
@ -45,7 +46,12 @@ func TestTimingAttackDelayCalculations(t *testing.T) {
|
|||
avgExecDurationMs := 1000.0
|
||||
expectedMinimumDelayMs := avgExecDurationMs - float64(execDuration.Milliseconds())
|
||||
|
||||
ctx := &AutheliaCtx{Logger: logging.Logger().WithFields(logrus.Fields{})}
|
||||
ctx := &AutheliaCtx{
|
||||
Logger: logging.Logger().WithFields(logrus.Fields{}),
|
||||
Providers: Providers{
|
||||
Random: &random.Cryptographical{},
|
||||
},
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
delay := calculateActualDelay(ctx, execDuration, avgExecDurationMs, 250, 85, false)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/notification"
|
||||
"github.com/authelia/authelia/v4/internal/ntp"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
"github.com/authelia/authelia/v4/internal/regulation"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
"github.com/authelia/authelia/v4/internal/storage"
|
||||
|
@ -44,6 +45,7 @@ type Providers struct {
|
|||
Templates *templates.Provider
|
||||
TOTP totp.Provider
|
||||
PasswordPolicy PasswordPolicyProvider
|
||||
Random random.Provider
|
||||
}
|
||||
|
||||
// RequestHandler represents an Authelia request handler.
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
"github.com/authelia/authelia/v4/internal/regulation"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
"github.com/authelia/authelia/v4/internal/templates"
|
||||
|
@ -34,6 +35,7 @@ type MockAutheliaCtx struct {
|
|||
StorageMock *MockStorage
|
||||
NotifierMock *MockNotifier
|
||||
TOTPMock *MockTOTP
|
||||
RandomMock *MockRandom
|
||||
|
||||
UserSession *session.UserSession
|
||||
|
||||
|
@ -98,6 +100,10 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
|||
mockAuthelia.TOTPMock = NewMockTOTP(mockAuthelia.Ctrl)
|
||||
providers.TOTP = mockAuthelia.TOTPMock
|
||||
|
||||
mockAuthelia.RandomMock = NewMockRandom(mockAuthelia.Ctrl)
|
||||
|
||||
providers.Random = random.NewMathematical()
|
||||
|
||||
var err error
|
||||
|
||||
if providers.Templates, err = templates.New(templates.Config{}); err != nil {
|
||||
|
|
|
@ -8,3 +8,4 @@ package mocks
|
|||
//go:generate mockgen -package mocks -destination totp.go -mock_names Provider=MockTOTP github.com/authelia/authelia/v4/internal/totp Provider
|
||||
//go:generate mockgen -package mocks -destination storage.go -mock_names Provider=MockStorage github.com/authelia/authelia/v4/internal/storage Provider
|
||||
//go:generate mockgen -package mocks -destination duo_api.go -mock_names API=MockAPI github.com/authelia/authelia/v4/internal/duo API
|
||||
//go:generate mockgen -package mocks -destination random.go -mock_names Provider=MockRandom github.com/authelia/authelia/v4/internal/random Provider
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/authelia/authelia/v4/internal/random (interfaces: Provider)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
big "math/big"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockRandom is a mock of Provider interface.
|
||||
type MockRandom struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockRandomMockRecorder
|
||||
}
|
||||
|
||||
// MockRandomMockRecorder is the mock recorder for MockRandom.
|
||||
type MockRandomMockRecorder struct {
|
||||
mock *MockRandom
|
||||
}
|
||||
|
||||
// NewMockRandom creates a new mock instance.
|
||||
func NewMockRandom(ctrl *gomock.Controller) *MockRandom {
|
||||
mock := &MockRandom{ctrl: ctrl}
|
||||
mock.recorder = &MockRandomMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockRandom) EXPECT() *MockRandomMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Bytes mocks base method.
|
||||
func (m *MockRandom) Bytes() []byte {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Bytes")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Bytes indicates an expected call of Bytes.
|
||||
func (mr *MockRandomMockRecorder) Bytes() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bytes", reflect.TypeOf((*MockRandom)(nil).Bytes))
|
||||
}
|
||||
|
||||
// BytesCustom mocks base method.
|
||||
func (m *MockRandom) BytesCustom(arg0 int, arg1 []byte) []byte {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BytesCustom", arg0, arg1)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// BytesCustom indicates an expected call of BytesCustom.
|
||||
func (mr *MockRandomMockRecorder) BytesCustom(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BytesCustom", reflect.TypeOf((*MockRandom)(nil).BytesCustom), arg0, arg1)
|
||||
}
|
||||
|
||||
// BytesCustomErr mocks base method.
|
||||
func (m *MockRandom) BytesCustomErr(arg0 int, arg1 []byte) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BytesCustomErr", arg0, arg1)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BytesCustomErr indicates an expected call of BytesCustomErr.
|
||||
func (mr *MockRandomMockRecorder) BytesCustomErr(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BytesCustomErr", reflect.TypeOf((*MockRandom)(nil).BytesCustomErr), arg0, arg1)
|
||||
}
|
||||
|
||||
// BytesErr mocks base method.
|
||||
func (m *MockRandom) BytesErr() ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BytesErr")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BytesErr indicates an expected call of BytesErr.
|
||||
func (mr *MockRandomMockRecorder) BytesErr() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BytesErr", reflect.TypeOf((*MockRandom)(nil).BytesErr))
|
||||
}
|
||||
|
||||
// Int mocks base method.
|
||||
func (m *MockRandom) Int(arg0 *big.Int) *big.Int {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Int", arg0)
|
||||
ret0, _ := ret[0].(*big.Int)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Int indicates an expected call of Int.
|
||||
func (mr *MockRandomMockRecorder) Int(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Int", reflect.TypeOf((*MockRandom)(nil).Int), arg0)
|
||||
}
|
||||
|
||||
// IntErr mocks base method.
|
||||
func (m *MockRandom) IntErr(arg0 *big.Int) (*big.Int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IntErr", arg0)
|
||||
ret0, _ := ret[0].(*big.Int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// IntErr indicates an expected call of IntErr.
|
||||
func (mr *MockRandomMockRecorder) IntErr(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntErr", reflect.TypeOf((*MockRandom)(nil).IntErr), arg0)
|
||||
}
|
||||
|
||||
// Integer mocks base method.
|
||||
func (m *MockRandom) Integer(arg0 int) int {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Integer", arg0)
|
||||
ret0, _ := ret[0].(int)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Integer indicates an expected call of Integer.
|
||||
func (mr *MockRandomMockRecorder) Integer(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Integer", reflect.TypeOf((*MockRandom)(nil).Integer), arg0)
|
||||
}
|
||||
|
||||
// IntegerErr mocks base method.
|
||||
func (m *MockRandom) IntegerErr(arg0 int) (int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IntegerErr", arg0)
|
||||
ret0, _ := ret[0].(int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// IntegerErr indicates an expected call of IntegerErr.
|
||||
func (mr *MockRandomMockRecorder) IntegerErr(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntegerErr", reflect.TypeOf((*MockRandom)(nil).IntegerErr), arg0)
|
||||
}
|
||||
|
||||
// Read mocks base method.
|
||||
func (m *MockRandom) Read(arg0 []byte) (int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Read", arg0)
|
||||
ret0, _ := ret[0].(int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Read indicates an expected call of Read.
|
||||
func (mr *MockRandomMockRecorder) Read(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockRandom)(nil).Read), arg0)
|
||||
}
|
||||
|
||||
// StringCustom mocks base method.
|
||||
func (m *MockRandom) StringCustom(arg0 int, arg1 string) string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "StringCustom", arg0, arg1)
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// StringCustom indicates an expected call of StringCustom.
|
||||
func (mr *MockRandomMockRecorder) StringCustom(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StringCustom", reflect.TypeOf((*MockRandom)(nil).StringCustom), arg0, arg1)
|
||||
}
|
||||
|
||||
// StringCustomErr mocks base method.
|
||||
func (m *MockRandom) StringCustomErr(arg0 int, arg1 string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "StringCustomErr", arg0, arg1)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// StringCustomErr indicates an expected call of StringCustomErr.
|
||||
func (mr *MockRandomMockRecorder) StringCustomErr(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StringCustomErr", reflect.TypeOf((*MockRandom)(nil).StringCustomErr), arg0, arg1)
|
||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/logging"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
"github.com/authelia/authelia/v4/internal/templates"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
@ -64,6 +65,7 @@ func NewSMTPNotifier(config *schema.SMTPNotifierConfiguration, certPool *x509.Ce
|
|||
return &SMTPNotifier{
|
||||
config: config,
|
||||
domain: domain,
|
||||
random: &random.Cryptographical{},
|
||||
tls: utils.NewTLSConfig(config.TLS, certPool),
|
||||
log: logging.Logger(),
|
||||
opts: opts,
|
||||
|
@ -74,6 +76,7 @@ func NewSMTPNotifier(config *schema.SMTPNotifierConfiguration, certPool *x509.Ce
|
|||
type SMTPNotifier struct {
|
||||
config *schema.SMTPNotifierConfiguration
|
||||
domain string
|
||||
random random.Provider
|
||||
tls *tls.Config
|
||||
log *logrus.Logger
|
||||
opts []gomail.Option
|
||||
|
@ -104,10 +107,10 @@ func (n *SMTPNotifier) StartupCheck() (err error) {
|
|||
func (n *SMTPNotifier) Send(ctx context.Context, recipient mail.Address, subject string, et *templates.EmailTemplate, data any) (err error) {
|
||||
msg := gomail.NewMsg(
|
||||
gomail.WithMIMEVersion(gomail.Mime10),
|
||||
gomail.WithBoundary(utils.RandomString(30, utils.CharSetAlphaNumeric)),
|
||||
gomail.WithBoundary(n.random.StringCustom(30, random.CharSetAlphaNumeric)),
|
||||
)
|
||||
|
||||
setMessageID(msg, n.domain)
|
||||
n.setMessageID(msg, n.domain)
|
||||
|
||||
if err = msg.From(n.config.Sender.String()); err != nil {
|
||||
return fmt.Errorf("notifier: smtp: failed to set from address: %w", err)
|
||||
|
@ -161,10 +164,10 @@ func (n *SMTPNotifier) Send(ctx context.Context, recipient mail.Address, subject
|
|||
return nil
|
||||
}
|
||||
|
||||
func setMessageID(msg *gomail.Msg, domain string) {
|
||||
rn, _ := utils.RandomInt(100000000)
|
||||
rm, _ := utils.RandomInt(10000)
|
||||
rs := utils.RandomString(17, utils.CharSetAlphaNumeric)
|
||||
func (n *SMTPNotifier) setMessageID(msg *gomail.Msg, domain string) {
|
||||
rn := n.random.Integer(100000000)
|
||||
rm := n.random.Integer(10000)
|
||||
rs := n.random.StringCustom(17, random.CharSetAlphaNumeric)
|
||||
pid := os.Getpid() + rm
|
||||
|
||||
msg.SetMessageIDWithValue(fmt.Sprintf("%d.%d%d.%s@%s", pid, rn, rm, rs, domain))
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/configuration/validator"
|
||||
)
|
||||
|
||||
func TestShouldCheckNTP(t *testing.T) {
|
||||
func TestShouldCheckNTPV4(t *testing.T) {
|
||||
config := &schema.Configuration{
|
||||
NTP: schema.NTPConfiguration{
|
||||
Address: "time.cloudflare.com:123",
|
||||
|
@ -26,3 +26,20 @@ func TestShouldCheckNTP(t *testing.T) {
|
|||
|
||||
assert.NoError(t, ntp.StartupCheck())
|
||||
}
|
||||
|
||||
func TestShouldCheckNTPV3(t *testing.T) {
|
||||
config := &schema.Configuration{
|
||||
NTP: schema.NTPConfiguration{
|
||||
Address: "time.cloudflare.com:123",
|
||||
Version: 3,
|
||||
MaximumDesync: time.Second * 3,
|
||||
},
|
||||
}
|
||||
|
||||
sv := schema.NewStructValidator()
|
||||
validator.ValidateNTP(config, sv)
|
||||
|
||||
ntp := NewProvider(&config.NTP)
|
||||
|
||||
assert.NoError(t, ntp.StartupCheck())
|
||||
}
|
||||
|
|
|
@ -9,8 +9,33 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
func TestShould(t *testing.T) {
|
||||
func TestNtpIsOffsetTooLarge(t *testing.T) {
|
||||
maxOffset, _ := utils.ParseDurationString("1s")
|
||||
assert.True(t, ntpIsOffsetTooLarge(maxOffset, time.Now(), time.Now().Add(time.Second*2)))
|
||||
assert.True(t, ntpIsOffsetTooLarge(maxOffset, time.Now().Add(time.Second*2), time.Now()))
|
||||
assert.False(t, ntpIsOffsetTooLarge(maxOffset, time.Now(), time.Now()))
|
||||
}
|
||||
|
||||
func TestNtpPacketToTime(t *testing.T) {
|
||||
resp := &ntpPacket{
|
||||
TxTimeSeconds: 60,
|
||||
TxTimeFraction: 0,
|
||||
}
|
||||
|
||||
expected := time.Unix(int64(float64(60)-ntpEpochOffset), 0)
|
||||
|
||||
ntpTime := ntpPacketToTime(resp)
|
||||
assert.Equal(t, expected, ntpTime)
|
||||
}
|
||||
|
||||
func TestLeapVersionClientMode(t *testing.T) {
|
||||
v3Noleap := uint8(27)
|
||||
v4Noleap := uint8(43)
|
||||
v3leap := uint8(91)
|
||||
v4leap := uint8(107)
|
||||
|
||||
assert.Equal(t, v3Noleap, ntpLeapVersionClientMode(false, ntpV3))
|
||||
assert.Equal(t, v4Noleap, ntpLeapVersionClientMode(false, ntpV4))
|
||||
assert.Equal(t, v3leap, ntpLeapVersionClientMode(true, ntpV3))
|
||||
assert.Equal(t, v4leap, ntpLeapVersionClientMode(true, ntpV4))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ory/fosite"
|
||||
"github.com/ory/x/errorsx"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
|
@ -18,6 +21,10 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
|
|||
SectorIdentifier: config.SectorIdentifier.String(),
|
||||
Public: config.Public,
|
||||
|
||||
EnforcePKCE: config.EnforcePKCE || config.PKCEChallengeMethod != "",
|
||||
EnforcePKCEChallengeMethod: config.PKCEChallengeMethod != "",
|
||||
PKCEChallengeMethod: config.PKCEChallengeMethod,
|
||||
|
||||
Audience: config.Audience,
|
||||
Scopes: config.Scopes,
|
||||
RedirectURIs: config.RedirectURIs,
|
||||
|
@ -39,6 +46,29 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
|
|||
return client
|
||||
}
|
||||
|
||||
// ValidateAuthorizationPolicy is a helper function to validate additional policy constraints on a per-client basis.
|
||||
func (c *Client) ValidateAuthorizationPolicy(r fosite.Requester) (err error) {
|
||||
form := r.GetRequestForm()
|
||||
|
||||
if c.EnforcePKCE {
|
||||
if form.Get("code_challenge") == "" {
|
||||
return errorsx.WithStack(fosite.ErrInvalidRequest.
|
||||
WithHint("Clients must include a code_challenge when performing the authorize code flow, but it is missing.").
|
||||
WithDebug("The server is configured in a way that enforces PKCE for this client."))
|
||||
}
|
||||
|
||||
if c.EnforcePKCEChallengeMethod {
|
||||
if method := form.Get("code_challenge_method"); method != c.PKCEChallengeMethod {
|
||||
return errorsx.WithStack(fosite.ErrInvalidRequest.
|
||||
WithHint(fmt.Sprintf("Client must use code_challenge_method=%s, %s is not allowed.", c.PKCEChallengeMethod, method)).
|
||||
WithDebug(fmt.Sprintf("The server is configured in a way that enforces PKCE %s as challenge method for this client.", c.PKCEChallengeMethod)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
|
||||
func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
|
||||
if level == authentication.NotAuthenticated {
|
||||
|
@ -48,11 +78,6 @@ func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) boo
|
|||
return authorization.IsAuthLevelSufficient(level, c.Policy)
|
||||
}
|
||||
|
||||
// GetID returns the ID.
|
||||
func (c *Client) GetID() string {
|
||||
return c.ID
|
||||
}
|
||||
|
||||
// GetSectorIdentifier returns the SectorIdentifier for this client.
|
||||
func (c *Client) GetSectorIdentifier() string {
|
||||
return c.SectorIdentifier
|
||||
|
@ -74,6 +99,11 @@ func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) Con
|
|||
return body
|
||||
}
|
||||
|
||||
// GetID returns the ID.
|
||||
func (c *Client) GetID() string {
|
||||
return c.ID
|
||||
}
|
||||
|
||||
// GetHashedSecret returns the Secret.
|
||||
func (c *Client) GetHashedSecret() []byte {
|
||||
if c.Secret == nil {
|
||||
|
|
|
@ -217,6 +217,90 @@ func TestClient_GetResponseTypes(t *testing.T) {
|
|||
assert.Equal(t, "id_token", responseTypes[1])
|
||||
}
|
||||
|
||||
func TestNewClientPKCE(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
have schema.OpenIDConnectClientConfiguration
|
||||
expectedEnforcePKCE bool
|
||||
expectedEnforcePKCEChallengeMethod bool
|
||||
expected string
|
||||
req *fosite.Request
|
||||
err string
|
||||
}{
|
||||
{
|
||||
"ShouldNotEnforcePKCEAndNotErrorOnNonPKCERequest",
|
||||
schema.OpenIDConnectClientConfiguration{},
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
&fosite.Request{},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"ShouldEnforcePKCEAndErrorOnNonPKCERequest",
|
||||
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
|
||||
true,
|
||||
false,
|
||||
"",
|
||||
&fosite.Request{},
|
||||
"invalid_request",
|
||||
},
|
||||
{
|
||||
"ShouldEnforcePKCEAndNotErrorOnPKCERequest",
|
||||
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
|
||||
true,
|
||||
false,
|
||||
"",
|
||||
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}}},
|
||||
"",
|
||||
},
|
||||
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnNonPKCERequest",
|
||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||
true,
|
||||
true,
|
||||
"S256",
|
||||
&fosite.Request{},
|
||||
"invalid_request",
|
||||
},
|
||||
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnInvalidChallengeMethod",
|
||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||
true,
|
||||
true,
|
||||
"S256",
|
||||
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}}},
|
||||
"invalid_request",
|
||||
},
|
||||
{"ShouldEnforcePKCEFromChallengeMethodAndNotErrorOnValidRequest",
|
||||
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||
true,
|
||||
true,
|
||||
"S256",
|
||||
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}, "code_challenge_method": {"S256"}}},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client := NewClient(tc.have)
|
||||
|
||||
assert.Equal(t, tc.expectedEnforcePKCE, client.EnforcePKCE)
|
||||
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod)
|
||||
assert.Equal(t, tc.expected, client.PKCEChallengeMethod)
|
||||
|
||||
if tc.req != nil {
|
||||
err := client.ValidateAuthorizationPolicy(tc.req)
|
||||
|
||||
if tc.err != "" {
|
||||
assert.EqualError(t, err, tc.err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_IsPublic(t *testing.T) {
|
||||
c := Client{}
|
||||
|
||||
|
|
|
@ -40,11 +40,10 @@ func NewConfig(config *schema.OpenIDConnectConfiguration) *Config {
|
|||
},
|
||||
}
|
||||
|
||||
prefix := "authelia_%s_"
|
||||
c.Strategy.Core = &HMACCoreStrategy{
|
||||
Enigma: &hmac.HMACStrategy{Config: c},
|
||||
Config: c,
|
||||
prefix: &prefix,
|
||||
prefix: tokenPrefixFmt,
|
||||
}
|
||||
|
||||
return c
|
||||
|
|
|
@ -106,6 +106,13 @@ const (
|
|||
JWTHeaderKeyIdentifier = "kid"
|
||||
)
|
||||
|
||||
const (
|
||||
tokenPrefixFmt = "authelia_%s_" //nolint:gosec
|
||||
tokenPrefixPartAccessToken = "at"
|
||||
tokenPrefixPartRefreshToken = "rt"
|
||||
tokenPrefixPartAuthorizeCode = "ac"
|
||||
)
|
||||
|
||||
// Paths.
|
||||
const (
|
||||
EndpointPathConsent = "/consent"
|
||||
|
|
|
@ -19,7 +19,7 @@ type HMACCoreStrategy struct {
|
|||
fosite.RefreshTokenLifespanProvider
|
||||
fosite.AuthorizeCodeLifespanProvider
|
||||
}
|
||||
prefix *string
|
||||
prefix string
|
||||
}
|
||||
|
||||
// AccessTokenSignature implements oauth2.AccessTokenStrategy.
|
||||
|
@ -34,7 +34,7 @@ func (h *HMACCoreStrategy) GenerateAccessToken(ctx context.Context, _ fosite.Req
|
|||
return "", "", err
|
||||
}
|
||||
|
||||
return h.setPrefix(token, "at"), sig, nil
|
||||
return h.setPrefix(token, tokenPrefixPartAccessToken), sig, nil
|
||||
}
|
||||
|
||||
// ValidateAccessToken implements oauth2.AccessTokenStrategy.
|
||||
|
@ -48,7 +48,7 @@ func (h *HMACCoreStrategy) ValidateAccessToken(ctx context.Context, r fosite.Req
|
|||
return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", exp))
|
||||
}
|
||||
|
||||
return h.Enigma.Validate(ctx, h.trimPrefix(token, "at"))
|
||||
return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartAccessToken))
|
||||
}
|
||||
|
||||
// RefreshTokenSignature implements oauth2.RefreshTokenStrategy.
|
||||
|
@ -63,21 +63,22 @@ func (h *HMACCoreStrategy) GenerateRefreshToken(ctx context.Context, _ fosite.Re
|
|||
return "", "", err
|
||||
}
|
||||
|
||||
return h.setPrefix(token, "rt"), sig, nil
|
||||
return h.setPrefix(token, tokenPrefixPartRefreshToken), sig, nil
|
||||
}
|
||||
|
||||
// ValidateRefreshToken implements oauth2.RefreshTokenStrategy.
|
||||
func (h *HMACCoreStrategy) ValidateRefreshToken(ctx context.Context, r fosite.Requester, token string) (err error) {
|
||||
var exp = r.GetSession().GetExpiresAt(fosite.RefreshToken)
|
||||
|
||||
if exp.IsZero() {
|
||||
return h.Enigma.Validate(ctx, h.trimPrefix(token, "rt"))
|
||||
return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartRefreshToken))
|
||||
}
|
||||
|
||||
if !exp.IsZero() && exp.Before(time.Now().UTC()) {
|
||||
if exp.Before(time.Now().UTC()) {
|
||||
return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Refresh token expired at '%s'.", exp))
|
||||
}
|
||||
|
||||
return h.Enigma.Validate(ctx, h.trimPrefix(token, "rt"))
|
||||
return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartRefreshToken))
|
||||
}
|
||||
|
||||
// AuthorizeCodeSignature implements oauth2.AuthorizeCodeStrategy.
|
||||
|
@ -92,12 +93,13 @@ func (h *HMACCoreStrategy) GenerateAuthorizeCode(ctx context.Context, _ fosite.R
|
|||
return "", "", err
|
||||
}
|
||||
|
||||
return h.setPrefix(token, "ac"), sig, nil
|
||||
return h.setPrefix(token, tokenPrefixPartAuthorizeCode), sig, nil
|
||||
}
|
||||
|
||||
// ValidateAuthorizeCode implements oauth2.AuthorizeCodeStrategy.
|
||||
func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Requester, token string) (err error) {
|
||||
var exp = r.GetSession().GetExpiresAt(fosite.AuthorizeCode)
|
||||
|
||||
if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetAuthorizeCodeLifespan(ctx)).Before(time.Now().UTC()) {
|
||||
return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", r.GetRequestedAt().Add(h.Config.GetAuthorizeCodeLifespan(ctx))))
|
||||
}
|
||||
|
@ -106,24 +108,21 @@ func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.R
|
|||
return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", exp))
|
||||
}
|
||||
|
||||
return h.Enigma.Validate(ctx, h.trimPrefix(token, "ac"))
|
||||
return h.Enigma.Validate(ctx, h.trimPrefix(token, tokenPrefixPartAuthorizeCode))
|
||||
}
|
||||
|
||||
func (h *HMACCoreStrategy) getPrefix(part string) string {
|
||||
if h.prefix == nil {
|
||||
prefix := "ory_%s_"
|
||||
h.prefix = &prefix
|
||||
} else if len(*h.prefix) == 0 {
|
||||
if len(h.prefix) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf(*h.prefix, part)
|
||||
}
|
||||
|
||||
func (h *HMACCoreStrategy) trimPrefix(token, part string) string {
|
||||
return strings.TrimPrefix(token, h.getPrefix(part))
|
||||
return fmt.Sprintf(h.prefix, part)
|
||||
}
|
||||
|
||||
func (h *HMACCoreStrategy) setPrefix(token, part string) string {
|
||||
return h.getPrefix(part) + token
|
||||
}
|
||||
|
||||
func (h *HMACCoreStrategy) trimPrefix(token, part string) string {
|
||||
return strings.TrimPrefix(token, h.getPrefix(part))
|
||||
}
|
||||
|
|
|
@ -107,6 +107,10 @@ type Client struct {
|
|||
SectorIdentifier string
|
||||
Public bool
|
||||
|
||||
EnforcePKCE bool
|
||||
EnforcePKCEChallengeMethod bool
|
||||
PKCEChallengeMethod string
|
||||
|
||||
Audience []string
|
||||
Scopes []string
|
||||
RedirectURIs []string
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package random
|
||||
|
||||
const (
|
||||
// DefaultN is the default value of n.
|
||||
DefaultN = 72
|
||||
)
|
||||
|
||||
const (
|
||||
// CharSetAlphabeticLower are literally just valid alphabetic lowercase printable ASCII chars.
|
||||
CharSetAlphabeticLower = "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
// CharSetAlphabeticUpper are literally just valid alphabetic uppercase printable ASCII chars.
|
||||
CharSetAlphabeticUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
// CharSetAlphabetic are literally just valid alphabetic printable ASCII chars.
|
||||
CharSetAlphabetic = CharSetAlphabeticLower + CharSetAlphabeticUpper
|
||||
|
||||
// CharSetNumeric are literally just valid numeric chars.
|
||||
CharSetNumeric = "0123456789"
|
||||
|
||||
// CharSetNumericHex are literally just valid hexadecimal printable ASCII chars.
|
||||
CharSetNumericHex = CharSetNumeric + "ABCDEF"
|
||||
|
||||
// CharSetSymbolic are literally just valid symbolic printable ASCII chars.
|
||||
CharSetSymbolic = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
|
||||
// CharSetSymbolicRFC3986Unreserved are RFC3986 unreserved symbol characters.
|
||||
// See https://www.rfc-editor.org/rfc/rfc3986#section-2.3.
|
||||
CharSetSymbolicRFC3986Unreserved = "-._~"
|
||||
|
||||
// CharSetAlphaNumeric are literally just valid alphanumeric printable ASCII chars.
|
||||
CharSetAlphaNumeric = CharSetAlphabetic + CharSetNumeric
|
||||
|
||||
// CharSetASCII are literally just valid printable ASCII chars.
|
||||
CharSetASCII = CharSetAlphabetic + CharSetNumeric + CharSetSymbolic
|
||||
|
||||
// CharSetRFC3986Unreserved are RFC3986 unreserved characters.
|
||||
// See https://www.rfc-editor.org/rfc/rfc3986#section-2.3.
|
||||
CharSetRFC3986Unreserved = CharSetAlphabetic + CharSetNumeric + CharSetSymbolicRFC3986Unreserved
|
||||
|
||||
// CharSetUnambiguousUpper are a set of unambiguous uppercase characters.
|
||||
CharSetUnambiguousUpper = "ABCDEFGHJKLMNOPQRTUVWYXZ2346789"
|
||||
)
|
|
@ -0,0 +1,136 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Cryptographical is the production random.Provider which uses crypto/rand.
|
||||
type Cryptographical struct{}
|
||||
|
||||
// Read implements the io.Reader interface.
|
||||
func (r *Cryptographical) Read(p []byte) (n int, err error) {
|
||||
return io.ReadFull(rand.Reader, p)
|
||||
}
|
||||
|
||||
// BytesErr returns random data as bytes with the standard random.DefaultN length and can contain any byte values
|
||||
// (including unreadable byte values). If an error is returned from the random read this function returns it.
|
||||
func (r *Cryptographical) BytesErr() (data []byte, err error) {
|
||||
data = make([]byte, DefaultN)
|
||||
|
||||
_, err = rand.Read(data)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
// Bytes returns random data as bytes with the standard random.DefaultN length and can contain any byte values
|
||||
// (including unreadable byte values). If an error is returned from the random read this function ignores it.
|
||||
func (r *Cryptographical) Bytes() (data []byte) {
|
||||
data, _ = r.BytesErr()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// BytesCustomErr returns random data as bytes with n length and can contain only byte values from the provided
|
||||
// values. If n is less than 1 then DefaultN is used instead. If an error is returned from the random read this function
|
||||
// returns it.
|
||||
func (r *Cryptographical) BytesCustomErr(n int, charset []byte) (data []byte, err error) {
|
||||
if n < 1 {
|
||||
n = DefaultN
|
||||
}
|
||||
|
||||
data = make([]byte, n)
|
||||
|
||||
if _, err = rand.Read(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := len(charset)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
data[i] = charset[data[i]%byte(t)]
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// StringCustomErr is an overload of BytesCustomWithErr which takes a characters string and returns a string.
|
||||
func (r *Cryptographical) StringCustomErr(n int, characters string) (data string, err error) {
|
||||
var d []byte
|
||||
|
||||
if d, err = r.BytesCustomErr(n, []byte(characters)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(d), nil
|
||||
}
|
||||
|
||||
// BytesCustom returns random data as bytes with n length and can contain only byte values from the provided values.
|
||||
// If n is less than 1 then DefaultN is used instead. If an error is returned from the random read this function
|
||||
// ignores it.
|
||||
func (r *Cryptographical) BytesCustom(n int, charset []byte) (data []byte) {
|
||||
data, _ = r.BytesCustomErr(n, charset)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// StringCustom is an overload of BytesCustom which takes a characters string and returns a string.
|
||||
func (r *Cryptographical) StringCustom(n int, characters string) (data string) {
|
||||
return string(r.BytesCustom(n, []byte(characters)))
|
||||
}
|
||||
|
||||
// IntErr returns a random *big.Int error combination with a maximum of max.
|
||||
func (r *Cryptographical) IntErr(max *big.Int) (value *big.Int, err error) {
|
||||
if max == nil {
|
||||
return nil, fmt.Errorf("max is required")
|
||||
}
|
||||
|
||||
if max.Sign() <= 0 {
|
||||
return nil, fmt.Errorf("max must be 1 or more")
|
||||
}
|
||||
|
||||
return rand.Int(rand.Reader, max)
|
||||
}
|
||||
|
||||
// Int returns a random *big.Int with a maximum of max.
|
||||
func (r *Cryptographical) Int(max *big.Int) (value *big.Int) {
|
||||
var err error
|
||||
|
||||
if value, err = r.IntErr(max); err != nil {
|
||||
return big.NewInt(-1)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// IntegerErr returns a random int error combination with a maximum of n.
|
||||
func (r *Cryptographical) IntegerErr(n int) (value int, err error) {
|
||||
if n <= 0 {
|
||||
return 0, fmt.Errorf("n must be more than 0")
|
||||
}
|
||||
|
||||
max := big.NewInt(int64(n))
|
||||
|
||||
var result *big.Int
|
||||
|
||||
if result, err = r.IntErr(max); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
value = int(result.Int64())
|
||||
|
||||
if value < 0 {
|
||||
return 0, fmt.Errorf("generated number is too big for int")
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Integer returns a random int with a maximum of n.
|
||||
func (r *Cryptographical) Integer(n int) (value int) {
|
||||
value, _ = r.IntegerErr(n)
|
||||
|
||||
return value
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewMathematical runs rand.Seed with the current time and returns a random.Provider, specifically *random.Mathematical.
|
||||
func NewMathematical() *Mathematical {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
return &Mathematical{}
|
||||
}
|
||||
|
||||
// Mathematical is the random.Provider which uses math/rand and is COMPLETELY UNSAFE FOR PRODUCTION IN MOST SITUATIONS.
|
||||
// Use random.Cryptographical instead.
|
||||
type Mathematical struct{}
|
||||
|
||||
// Read implements the io.Reader interface.
|
||||
func (r *Mathematical) Read(p []byte) (n int, err error) {
|
||||
return rand.Read(p) //nolint:gosec
|
||||
}
|
||||
|
||||
// BytesErr returns random data as bytes with the standard random.DefaultN length and can contain any byte values
|
||||
// (including unreadable byte values). If an error is returned from the random read this function returns it.
|
||||
func (r *Mathematical) BytesErr() (data []byte, err error) {
|
||||
data = make([]byte, DefaultN)
|
||||
|
||||
if _, err = rand.Read(data); err != nil { //nolint:gosec
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Bytes returns random data as bytes with the standard random.DefaultN length and can contain any byte values
|
||||
// (including unreadable byte values). If an error is returned from the random read this function ignores it.
|
||||
func (r *Mathematical) Bytes() (data []byte) {
|
||||
data, _ = r.BytesErr()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// BytesCustomErr returns random data as bytes with n length and can contain only byte values from the provided
|
||||
// values. If n is less than 1 then DefaultN is used instead. If an error is returned from the random read this function
|
||||
// returns it.
|
||||
func (r *Mathematical) BytesCustomErr(n int, charset []byte) (data []byte, err error) {
|
||||
if n < 1 {
|
||||
n = DefaultN
|
||||
}
|
||||
|
||||
data = make([]byte, n)
|
||||
|
||||
if _, err = rand.Read(data); err != nil { //nolint:gosec
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := len(charset)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
data[i] = charset[data[i]%byte(t)]
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// StringCustomErr is an overload of BytesCustomWithErr which takes a characters string and returns a string.
|
||||
func (r *Mathematical) StringCustomErr(n int, characters string) (data string, err error) {
|
||||
var d []byte
|
||||
|
||||
if d, err = r.BytesCustomErr(n, []byte(characters)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(d), nil
|
||||
}
|
||||
|
||||
// BytesCustom returns random data as bytes with n length and can contain only byte values from the provided values.
|
||||
// If n is less than 1 then DefaultN is used instead. If an error is returned from the random read this function
|
||||
// ignores it.
|
||||
func (r *Mathematical) BytesCustom(n int, charset []byte) (data []byte) {
|
||||
data, _ = r.BytesCustomErr(n, charset)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// StringCustom is an overload of BytesCustom which takes a characters string and returns a string.
|
||||
func (r *Mathematical) StringCustom(n int, characters string) (data string) {
|
||||
return string(r.BytesCustom(n, []byte(characters)))
|
||||
}
|
||||
|
||||
// IntErr returns a random *big.Int error combination with a maximum of max.
|
||||
func (r *Mathematical) IntErr(max *big.Int) (value *big.Int, err error) {
|
||||
if max == nil {
|
||||
return nil, fmt.Errorf("max is required")
|
||||
}
|
||||
|
||||
if max.Sign() <= 0 {
|
||||
return nil, fmt.Errorf("max must be 1 or more")
|
||||
}
|
||||
|
||||
return big.NewInt(int64(rand.Intn(max.Sign()))), nil //nolint:gosec
|
||||
}
|
||||
|
||||
// Int returns a random *big.Int with a maximum of max.
|
||||
func (r *Mathematical) Int(max *big.Int) (value *big.Int) {
|
||||
var err error
|
||||
|
||||
if value, err = r.IntErr(max); err != nil {
|
||||
return big.NewInt(-1)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// IntegerErr returns a random int error combination with a maximum of n.
|
||||
func (r *Mathematical) IntegerErr(n int) (output int, err error) {
|
||||
return r.Integer(n), nil
|
||||
}
|
||||
|
||||
// Integer returns a random int with a maximum of n.
|
||||
func (r *Mathematical) Integer(n int) int {
|
||||
return rand.Intn(n) //nolint:gosec
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Provider of random functions and functionality.
|
||||
type Provider interface {
|
||||
io.Reader
|
||||
|
||||
// BytesErr returns random data as bytes with the standard random.DefaultN length and can contain any byte values
|
||||
// (including unreadable byte values). If an error is returned from the random read this function returns it.
|
||||
BytesErr() (data []byte, err error)
|
||||
|
||||
// Bytes returns random data as bytes with the standard random.DefaultN length and can contain any byte values
|
||||
// (including unreadable byte values). If an error is returned from the random read this function ignores it.
|
||||
Bytes() (data []byte)
|
||||
|
||||
// BytesCustomErr returns random data as bytes with n length and can contain only byte values from the provided
|
||||
// values. If n is less than 1 then DefaultN is used instead. If an error is returned from the random read this function
|
||||
// returns it.
|
||||
BytesCustomErr(n int, charset []byte) (data []byte, err error)
|
||||
|
||||
// StringCustomErr is an overload of BytesCustomWithErr which takes a characters string and returns a string.
|
||||
StringCustomErr(n int, characters string) (data string, err error)
|
||||
|
||||
// BytesCustom returns random data as bytes with n length and can contain only byte values from the provided
|
||||
// values. If n is less than 1 then DefaultN is used instead.
|
||||
BytesCustom(n int, charset []byte) (data []byte)
|
||||
|
||||
// StringCustom is an overload of GenerateCustom which takes a characters string and returns a string.
|
||||
StringCustom(n int, characters string) (data string)
|
||||
|
||||
// IntErr returns a random *big.Int error combination with a maximum of max.
|
||||
IntErr(max *big.Int) (value *big.Int, err error)
|
||||
|
||||
// Int returns a random *big.Int with a maximum of max.
|
||||
Int(max *big.Int) (value *big.Int)
|
||||
|
||||
// IntegerErr returns a random int error combination with a maximum of n.
|
||||
IntegerErr(n int) (value int, err error)
|
||||
|
||||
// Integer returns a random integer with a maximum of n.
|
||||
Integer(n int) (value int)
|
||||
}
|
|
@ -6,14 +6,12 @@ import (
|
|||
|
||||
const (
|
||||
assetsRoot = "public_html"
|
||||
assetsSwagger = assetsRoot + "/api"
|
||||
|
||||
fileOpenAPI = "openapi.yml"
|
||||
fileIndexHTML = "index.html"
|
||||
fileLogo = "logo.png"
|
||||
|
||||
extHTML = ".html"
|
||||
extJSON = ".json"
|
||||
extYML = ".yml"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -52,8 +50,8 @@ var (
|
|||
const (
|
||||
environment = "ENVIRONMENT"
|
||||
dev = "dev"
|
||||
f = "false"
|
||||
t = "true"
|
||||
strFalse = "false"
|
||||
strTrue = "true"
|
||||
localhost = "localhost"
|
||||
schemeHTTP = "http"
|
||||
schemeHTTPS = "https"
|
||||
|
@ -76,7 +74,8 @@ X_AUTHELIA_HEALTHCHECK_PATH=%s
|
|||
`
|
||||
|
||||
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 (
|
||||
|
|
|
@ -93,9 +93,9 @@ func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
|||
func handleRouter(config schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
|
||||
optsTemplatedFile := NewTemplatedFileOptions(&config)
|
||||
|
||||
serveIndexHandler := ServeTemplatedFile(assetsRoot, fileIndexHTML, optsTemplatedFile)
|
||||
serveSwaggerHandler := ServeTemplatedFile(assetsSwagger, fileIndexHTML, optsTemplatedFile)
|
||||
serveSwaggerAPIHandler := ServeTemplatedFile(assetsSwagger, fileOpenAPI, optsTemplatedFile)
|
||||
serveIndexHandler := ServeTemplatedFile(providers.Templates.GetAssetIndexTemplate(), optsTemplatedFile)
|
||||
serveOpenAPIHandler := ServeTemplatedOpenAPI(providers.Templates.GetAssetOpenAPIIndexTemplate(), optsTemplatedFile)
|
||||
serveOpenAPISpecHandler := ETagRootURL(ServeTemplatedOpenAPI(providers.Templates.GetAssetOpenAPISpecTemplate(), optsTemplatedFile))
|
||||
|
||||
handlerPublicHTML := newPublicHTMLEmbeddedHandler()
|
||||
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))
|
||||
|
||||
// Swagger.
|
||||
r.GET("/api/", middleware(serveSwaggerHandler))
|
||||
r.GET("/api/", middleware(serveOpenAPIHandler))
|
||||
r.OPTIONS("/api/", policyCORSPublicGET.HandleOPTIONS)
|
||||
r.GET("/api/"+fileOpenAPI, policyCORSPublicGET.Middleware(middleware(serveSwaggerAPIHandler)))
|
||||
r.OPTIONS("/api/"+fileOpenAPI, policyCORSPublicGET.HandleOPTIONS)
|
||||
r.GET("/api/index.html", middleware(serveOpenAPIHandler))
|
||||
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 {
|
||||
r.GET("/api/"+file, handlerPublicHTML)
|
||||
|
|
|
@ -19,6 +19,10 @@ import (
|
|||
|
||||
// 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) {
|
||||
if err = providers.Templates.LoadTemplatedAssets(assets); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load templated assets: %w", err)
|
||||
}
|
||||
|
||||
server = &fasthttp.Server{
|
||||
ErrorHandler: handleError(),
|
||||
Handler: handleRouter(config, providers),
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"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/templates"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
|
@ -134,10 +135,17 @@ type TLSServerContext struct {
|
|||
port int
|
||||
}
|
||||
|
||||
func NewTLSServerContext(configuration schema.Configuration) (*TLSServerContext, error) {
|
||||
serverContext := new(TLSServerContext)
|
||||
func NewTLSServerContext(configuration schema.Configuration) (serverContext *TLSServerContext, err error) {
|
||||
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 {
|
||||
return nil, err
|
||||
|
|
|
@ -1,56 +1,43 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1" //nolint:gosec
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"sync"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"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/utils"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
"github.com/authelia/authelia/v4/internal/templates"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func ServeTemplatedFile(publicDir, file string, 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)
|
||||
}
|
||||
|
||||
func ServeTemplatedFile(t templates.Template, opts *TemplatedFileOptions) middlewares.RequestHandler {
|
||||
isDevEnvironment := os.Getenv(environment) == dev
|
||||
ext := filepath.Ext(t.Name())
|
||||
|
||||
return func(ctx *middlewares.AutheliaCtx) {
|
||||
logoOverride := f
|
||||
var err error
|
||||
|
||||
logoOverride := strFalse
|
||||
|
||||
if opts.AssetPath != "" {
|
||||
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:
|
||||
ctx.SetContentTypeTextHTML()
|
||||
case extJSON:
|
||||
|
@ -59,11 +46,9 @@ func ServeTemplatedFile(publicDir, file string, opts *TemplatedFileOptions) midd
|
|||
ctx.SetContentTypeTextPlain()
|
||||
}
|
||||
|
||||
nonce := utils.RandomString(32, utils.CharSetAlphaNumeric)
|
||||
nonce := ctx.Providers.Random.StringCustom(32, random.CharSetAlphaNumeric)
|
||||
|
||||
switch {
|
||||
case publicDir == assetsSwagger:
|
||||
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPSwagger, nonce, nonce))
|
||||
case ctx.Configuration.Server.Headers.CSPTemplate != "":
|
||||
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, strings.ReplaceAll(ctx.Configuration.Server.Headers.CSPTemplate, placeholderCSPNonce, nonce))
|
||||
case isDevEnvironment:
|
||||
|
@ -72,15 +57,99 @@ func ServeTemplatedFile(publicDir, file string, opts *TemplatedFileOptions) midd
|
|||
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)
|
||||
logger.Errorf("Unable to execute template: %v", err)
|
||||
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
||||
|
||||
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 = ctx.Providers.Random.StringCustom(32, random.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) {
|
||||
if disabled {
|
||||
return nil
|
||||
|
@ -120,11 +189,17 @@ func writeHealthCheckEnv(disabled bool, scheme, host, path string, port int) (er
|
|||
func NewTemplatedFileOptions(config *schema.Configuration) (opts *TemplatedFileOptions) {
|
||||
opts = &TemplatedFileOptions{
|
||||
AssetPath: config.Server.AssetPath,
|
||||
DuoSelfEnrollment: f,
|
||||
DuoSelfEnrollment: strFalse,
|
||||
RememberMe: strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled),
|
||||
ResetPassword: strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable),
|
||||
ResetPasswordCustomURL: config.AuthenticationBackend.PasswordReset.CustomURL.String(),
|
||||
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 {
|
||||
|
@ -143,6 +218,12 @@ type TemplatedFileOptions struct {
|
|||
ResetPasswordCustomURL string
|
||||
Session string
|
||||
Theme string
|
||||
|
||||
EndpointsPasswordReset bool
|
||||
EndpointsWebauthn bool
|
||||
EndpointsTOTP bool
|
||||
EndpointsDuo bool
|
||||
EndpointsOpenIDConnect bool
|
||||
}
|
||||
|
||||
// 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.
|
||||
type TemplatedFileCommonData struct {
|
||||
Base string
|
||||
|
@ -174,3 +271,16 @@ type TemplatedFileCommonData struct {
|
|||
Session 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
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
@ -89,7 +89,7 @@ func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUse
|
|||
s.collectScreenshot(ctx.Err(), s.Page)
|
||||
}()
|
||||
|
||||
expectedGroups := mapset.NewSetWith("dev", "admins")
|
||||
expectedGroups := mapset.NewSet("dev", "admins")
|
||||
|
||||
targetURL := fmt.Sprintf("%s/headers", PublicBaseURL)
|
||||
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL)
|
||||
|
@ -109,7 +109,7 @@ func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUse
|
|||
}
|
||||
|
||||
groups := strings.Split(payload.Headers.ForwardedGroups, ",")
|
||||
actualGroups := mapset.NewSet()
|
||||
actualGroups := mapset.NewSet[string]()
|
||||
|
||||
for _, group := range groups {
|
||||
actualGroups.Add(group)
|
||||
|
|
|
@ -792,7 +792,7 @@ func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {
|
|||
|
||||
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--config=/config/configuration.storage.yml"})
|
||||
s.Assert().EqualError(err, "exit status 1")
|
||||
s.Assert().Contains(output, "Error: command requires the use of a up to date schema version: storage schema outdated: version 0 is outdated please migrate to version 7 in order to use this command or use an older binary\n")
|
||||
s.Assert().Regexp(regexp.MustCompile(`^Error: command requires the use of a up to date schema version: storage schema outdated: version 0 is outdated please migrate to version \d+ in order to use this command or use an older binary\n`), output)
|
||||
|
||||
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "down", "--target=0", "--destroy-data", "--config=/config/configuration.storage.yml"})
|
||||
s.Assert().EqualError(err, "exit status 1")
|
||||
|
@ -1131,7 +1131,14 @@ func (s *CLISuite) TestStorage05ShouldChangeEncryptionKey() {
|
|||
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--verbose", "--config=/config/configuration.storage.yml"})
|
||||
s.Assert().NoError(err)
|
||||
|
||||
s.Assert().Contains(output, "Storage Encryption Key Validation: FAILURE\n\n\tCause: the configured encryption key does not appear to be valid for this database which may occur if the encryption key was changed in the configuration without using the cli to change it in the database.\n\nTables:\n\n\tTable (oauth2_access_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (oauth2_authorization_code_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (oauth2_openid_connect_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (oauth2_pkce_request_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (oauth2_refresh_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (totp_configurations): FAILURE\n\t\tInvalid Rows: 4\n\t\tTotal Rows: 4\n\n\tTable (webauthn_devices): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "Storage Encryption Key Validation: FAILURE\n\n\tCause: the configured encryption key does not appear to be valid for this database which may occur if the encryption key was changed in the configuration without using the cli to change it in the database.\n\nTables:\n\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_access_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_authorization_code_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_openid_connect_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_pkce_request_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_refresh_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (totp_configurations): FAILURE\n\t\tInvalid Rows: 4\n\t\tTotal Rows: 4\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (webauthn_devices): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
|
||||
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--encryption-key=apple-apple-apple-apple", "--config=/config/configuration.storage.yml"})
|
||||
s.Assert().NoError(err)
|
||||
|
@ -1141,7 +1148,14 @@ func (s *CLISuite) TestStorage05ShouldChangeEncryptionKey() {
|
|||
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--verbose", "--encryption-key=apple-apple-apple-apple", "--config=/config/configuration.storage.yml"})
|
||||
s.Assert().NoError(err)
|
||||
|
||||
s.Assert().Contains(output, "Storage Encryption Key Validation: SUCCESS\n\nTables:\n\n\tTable (oauth2_access_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (oauth2_authorization_code_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (oauth2_openid_connect_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (oauth2_pkce_request_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (oauth2_refresh_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n\n\tTable (totp_configurations): SUCCESS\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 4\n\n\tTable (webauthn_devices): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "Storage Encryption Key Validation: SUCCESS\n\nTables:\n\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_access_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_authorization_code_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_openid_connect_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_pkce_request_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (oauth2_refresh_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (totp_configurations): SUCCESS\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 4\n")
|
||||
s.Assert().Contains(output, "\n\n\tTable (webauthn_devices): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n")
|
||||
|
||||
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "change-key", "--encryption-key=apple-apple-apple-apple", "--config=/config/configuration.storage.yml"})
|
||||
s.Assert().EqualError(err, "exit status 1")
|
||||
|
|
|
@ -8,7 +8,6 @@ const (
|
|||
// Template File Names.
|
||||
const (
|
||||
TemplateNameEmailIdentityVerification = "IdentityVerification"
|
||||
TemplateNameEmailPasswordReset = "PasswordReset"
|
||||
TemplateNameEmailEvent = "Event"
|
||||
)
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"fmt"
|
||||
"hash"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -49,6 +51,28 @@ func FuncMap() map[string]any {
|
|||
"b64dec": FuncB64Dec,
|
||||
"b32enc": FuncB32Enc,
|
||||
"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,
|
||||
"default": FuncDefault,
|
||||
"empty": FuncEmpty,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,58 +226,6 @@ func FuncStringQuote(in ...any) string {
|
|||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
func strval(v interface{}) string {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return string(v)
|
||||
case fmt.Stringer:
|
||||
return v.String()
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func strslice(v any) []string {
|
||||
switch v := v.(type) {
|
||||
case []string:
|
||||
return v
|
||||
case []interface{}:
|
||||
b := make([]string, 0, len(v))
|
||||
|
||||
for _, s := range v {
|
||||
if s != nil {
|
||||
b = append(b, strval(s))
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
default:
|
||||
val := reflect.ValueOf(v)
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
l := val.Len()
|
||||
b := make([]string, 0, l)
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
value := val.Index(i).Interface()
|
||||
if value != nil {
|
||||
b = append(b, strval(value))
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
default:
|
||||
if v == nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{strval(v)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FuncIterate is a template function which takes a single uint returning a slice of units from 0 up to that number.
|
||||
func FuncIterate(count *uint) (out []uint) {
|
||||
var i uint
|
||||
|
@ -308,3 +280,107 @@ func FuncStringJoinX(elems []string, sep string, n int, p string) string {
|
|||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// FuncDefault is a helper function that provides similar functionality to the helm default func.
|
||||
func FuncDefault(d any, vals ...any) any {
|
||||
if FuncEmpty(vals) || FuncEmpty(vals[0]) {
|
||||
return d
|
||||
}
|
||||
|
||||
return vals[0]
|
||||
}
|
||||
|
||||
// FuncEmpty is a helper function that provides similar functionality to the helm empty func.
|
||||
func FuncEmpty(v any) bool {
|
||||
rv := reflect.ValueOf(v)
|
||||
if !rv.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
default:
|
||||
return rv.IsNil()
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return rv.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !rv.Bool()
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return rv.Complex() == 0
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return rv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float() == 0
|
||||
case reflect.Struct:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -357,6 +357,7 @@ func TestFuncSortAlpha(t *testing.T) {
|
|||
}{
|
||||
{"ShouldSortStrings", []string{"a", "c", "b"}, []string{"a", "b", "c"}},
|
||||
{"ShouldSortIntegers", []int{2, 3, 1}, []string{"1", "2", "3"}},
|
||||
{"ShouldSortSingleValue", 1, []string{"1"}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
@ -469,3 +470,171 @@ 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))
|
||||
}
|
||||
|
||||
func TestFuncDefault(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value []any
|
||||
have any
|
||||
expected any
|
||||
}{
|
||||
{"ShouldDefaultEmptyString", []any{""}, "default", "default"},
|
||||
{"ShouldNotDefaultString", []any{"not default"}, "default", "not default"},
|
||||
{"ShouldDefaultEmptyInteger", []any{0}, 1, 1},
|
||||
{"ShouldNotDefaultInteger", []any{20}, 1, 20},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, FuncDefault(tc.have, tc.value...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncEmpty(t *testing.T) {
|
||||
var nilv *string
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
value any
|
||||
expected bool
|
||||
}{
|
||||
{"ShouldBeEmptyNil", nilv, true},
|
||||
{"ShouldBeEmptyNilNil", nil, true},
|
||||
{"ShouldBeEmptyString", "", true},
|
||||
{"ShouldNotBeEmptyString", "abc", false},
|
||||
{"ShouldBeEmptyArray", []string{}, true},
|
||||
{"ShouldNotBeEmptyArray", []string{"abc"}, false},
|
||||
{"ShouldBeEmptyInteger", 0, true},
|
||||
{"ShouldNotBeEmptyInteger", 1, false},
|
||||
{"ShouldBeEmptyInteger8", int8(0), true},
|
||||
{"ShouldNotBeEmptyInteger8", int8(1), false},
|
||||
{"ShouldBeEmptyInteger16", int16(0), true},
|
||||
{"ShouldNotBeEmptyInteger16", int16(1), false},
|
||||
{"ShouldBeEmptyInteger32", int32(0), true},
|
||||
{"ShouldNotBeEmptyInteger32", int32(1), false},
|
||||
{"ShouldBeEmptyInteger64", int64(0), true},
|
||||
{"ShouldNotBeEmptyInteger64", int64(1), false},
|
||||
{"ShouldBeEmptyUnsignedInteger", uint(0), true},
|
||||
{"ShouldNotBeEmptyUnsignedInteger", uint(1), false},
|
||||
{"ShouldBeEmptyUnsignedInteger8", uint8(0), true},
|
||||
{"ShouldNotBeEmptyUnsignedInteger8", uint8(1), false},
|
||||
{"ShouldBeEmptyUnsignedInteger16", uint16(0), true},
|
||||
{"ShouldNotBeEmptyUnsignedInteger16", uint16(1), false},
|
||||
{"ShouldBeEmptyUnsignedInteger32", uint32(0), true},
|
||||
{"ShouldNotBeEmptyUnsignedInteger32", uint32(1), false},
|
||||
{"ShouldBeEmptyUnsignedInteger64", uint64(0), true},
|
||||
{"ShouldNotBeEmptyUnsignedInteger64", uint64(1), false},
|
||||
{"ShouldBeEmptyComplex64", complex64(complex(0, 0)), true},
|
||||
{"ShouldNotBeEmptyComplex64", complex64(complex(100000, 7.5)), false},
|
||||
{"ShouldBeEmptyComplex128", complex128(complex(0, 0)), true},
|
||||
{"ShouldNotBeEmptyComplex128", complex128(complex(100000, 7.5)), false},
|
||||
{"ShouldBeEmptyFloat32", float32(0), true},
|
||||
{"ShouldNotBeEmptyFloat32", float32(1), false},
|
||||
{"ShouldBeEmptyFloat64", float64(0), true},
|
||||
{"ShouldNotBeEmptyFloat64", float64(1), false},
|
||||
{"ShouldBeEmptyBoolean", false, true},
|
||||
{"ShouldNotBeEmptyBoolean", true, false},
|
||||
{"ShouldNotBeEmptyStruct", struct{}{}, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, FuncEmpty(tc.value))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// New creates a new templates' provider.
|
||||
|
@ -23,6 +25,64 @@ type Provider struct {
|
|||
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) {
|
||||
return p.templates.notification.event
|
||||
}
|
||||
|
|
|
@ -9,6 +9,17 @@ import (
|
|||
// Templates is the struct which holds all the *template.Template values.
|
||||
type Templates struct {
|
||||
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.
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
tt "text/template"
|
||||
)
|
||||
|
@ -39,24 +40,17 @@ func isSecretEnvKey(key string) (isSecretEnvKey bool) {
|
|||
return false
|
||||
}
|
||||
|
||||
func templateExists(path string) (exists bool) {
|
||||
func fileExists(path string) (exists bool) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
||||
func readTemplate(name, ext, category, overridePath string) (tPath string, embed bool, data []byte, err error) {
|
||||
if overridePath != "" {
|
||||
tPath = filepath.Join(overridePath, name+ext)
|
||||
|
||||
if templateExists(tPath) {
|
||||
if fileExists(tPath) {
|
||||
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)
|
||||
}
|
||||
|
@ -125,3 +119,55 @@ func loadEmailTemplate(name, overridePath string) (t *EmailTemplate, err error)
|
|||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func strval(v any) string {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return string(v)
|
||||
case fmt.Stringer:
|
||||
return v.String()
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func strslice(v any) []string {
|
||||
switch v := v.(type) {
|
||||
case []string:
|
||||
return v
|
||||
case []any:
|
||||
b := make([]string, 0, len(v))
|
||||
|
||||
for _, s := range v {
|
||||
if s != nil {
|
||||
b = append(b, strval(s))
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
default:
|
||||
val := reflect.ValueOf(v)
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
l := val.Len()
|
||||
b := make([]string, 0, l)
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
value := val.Index(i).Interface()
|
||||
if value != nil {
|
||||
b = append(b, strval(value))
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
default:
|
||||
if v == nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{strval(v)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsSecretEnvKey(t *testing.T) {
|
||||
|
@ -28,3 +33,72 @@ func TestIsSecretEnvKey(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTemplateDirectories(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name, path string
|
||||
}{
|
||||
{"Templates", "./src"},
|
||||
{"OpenAPI", "../../api"},
|
||||
{"Generators", "../../cmd/authelia-gen/templates"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
funcMap := FuncMap()
|
||||
|
||||
if tc.name == "Generators" {
|
||||
funcMap["joinX"] = FuncStringJoinX
|
||||
}
|
||||
|
||||
var (
|
||||
data []byte
|
||||
)
|
||||
|
||||
require.NoError(t, filepath.Walk(tc.path, func(path string, info fs.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := info.Name()
|
||||
|
||||
if tc.name == "Templates" {
|
||||
name = filepath.Base(filepath.Dir(path)) + "/" + name
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
data, err = os.ReadFile(path)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = template.New(tc.name).Funcs(funcMap).Parse(string(data))
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMiscTemplates(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name, path string
|
||||
}{
|
||||
{"ReactIndex", "../../web/index.html"},
|
||||
{"ViteEnv", "../../web/.env.production"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
data, err := os.ReadFile(tc.path)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = template.New(tc.name).Funcs(FuncMap()).Parse(string(data))
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,40 +97,6 @@ const (
|
|||
timeUnixEpochAsMicrosoftNTEpoch uint64 = 116444736000000000
|
||||
)
|
||||
|
||||
const (
|
||||
// CharSetAlphabeticLower are literally just valid alphabetic lowercase printable ASCII chars.
|
||||
CharSetAlphabeticLower = "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
// CharSetAlphabeticUpper are literally just valid alphabetic uppercase printable ASCII chars.
|
||||
CharSetAlphabeticUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
// CharSetAlphabetic are literally just valid alphabetic printable ASCII chars.
|
||||
CharSetAlphabetic = CharSetAlphabeticLower + CharSetAlphabeticUpper
|
||||
|
||||
// CharSetNumeric are literally just valid numeric chars.
|
||||
CharSetNumeric = "0123456789"
|
||||
|
||||
// CharSetNumericHex are literally just valid hexadecimal printable ASCII chars.
|
||||
CharSetNumericHex = CharSetNumeric + "ABCDEF"
|
||||
|
||||
// CharSetSymbolic are literally just valid symbolic printable ASCII chars.
|
||||
CharSetSymbolic = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
|
||||
// CharSetSymbolicRFC3986Unreserved are RFC3986 unreserved symbol characters.
|
||||
// See https://www.rfc-editor.org/rfc/rfc3986#section-2.3.
|
||||
CharSetSymbolicRFC3986Unreserved = "-._~"
|
||||
|
||||
// CharSetAlphaNumeric are literally just valid alphanumeric printable ASCII chars.
|
||||
CharSetAlphaNumeric = CharSetAlphabetic + CharSetNumeric
|
||||
|
||||
// CharSetASCII are literally just valid printable ASCII chars.
|
||||
CharSetASCII = CharSetAlphabetic + CharSetNumeric + CharSetSymbolic
|
||||
|
||||
// CharSetRFC3986Unreserved are RFC3986 unreserved characters.
|
||||
// See https://www.rfc-editor.org/rfc/rfc3986#section-2.3.
|
||||
CharSetRFC3986Unreserved = CharSetAlphabetic + CharSetNumeric + CharSetSymbolicRFC3986Unreserved
|
||||
)
|
||||
|
||||
var htmlEscaper = strings.NewReplacer(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
)
|
||||
|
||||
const (
|
||||
testStringInput = "abcdefghijkl"
|
||||
)
|
||||
|
||||
var r = &random.Cryptographical{}
|
||||
|
|
|
@ -573,50 +573,3 @@ loop:
|
|||
|
||||
return extKeyUsage
|
||||
}
|
||||
|
||||
// RandomString returns a random string with a given length with values from the provided characters. When crypto is set
|
||||
// to false we use math/rand and when it's set to true we use crypto/rand. The crypto option should always be set to true
|
||||
// excluding when the task is time sensitive and would not benefit from extra randomness.
|
||||
func RandomString(n int, characters string) (randomString string) {
|
||||
return string(RandomBytes(n, characters))
|
||||
}
|
||||
|
||||
// RandomBytes returns a random []byte with a given length with values from the provided characters. When crypto is set
|
||||
// to false we use math/rand and when it's set to true we use crypto/rand. The crypto option should always be set to true
|
||||
// excluding when the task is time sensitive and would not benefit from extra randomness.
|
||||
func RandomBytes(n int, characters string) (bytes []byte) {
|
||||
bytes = make([]byte, n)
|
||||
|
||||
_, _ = rand.Read(bytes)
|
||||
|
||||
for i, b := range bytes {
|
||||
bytes[i] = characters[b%byte(len(characters))]
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
func RandomInt(n int) (int, error) {
|
||||
if n <= 0 {
|
||||
return 0, fmt.Errorf("n must be more than 0")
|
||||
}
|
||||
|
||||
max := big.NewInt(int64(n))
|
||||
|
||||
if !max.IsUint64() {
|
||||
return 0, fmt.Errorf("generated max is negative")
|
||||
}
|
||||
|
||||
value, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
output := int(value.Int64())
|
||||
|
||||
if output < 0 {
|
||||
return 0, fmt.Errorf("generated number is too big for int")
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
)
|
||||
|
||||
func TestShouldHashString(t *testing.T) {
|
||||
|
@ -22,7 +24,7 @@ func TestShouldHashString(t *testing.T) {
|
|||
assert.Equal(t, "ae448ac86c4e8e4dec645729708ef41873ae79c6dff84eff73360989487f08e5", anotherSum)
|
||||
assert.NotEqual(t, sum, anotherSum)
|
||||
|
||||
randomInput := RandomString(40, CharSetAlphaNumeric)
|
||||
randomInput := r.StringCustom(40, random.CharSetAlphaNumeric)
|
||||
randomSum := HashSHA256FromString(randomInput)
|
||||
|
||||
assert.NotEqual(t, randomSum, sum)
|
||||
|
@ -38,7 +40,7 @@ func TestShouldHashPath(t *testing.T) {
|
|||
err = os.WriteFile(filepath.Join(dir, "anotherfile"), []byte("another\n"), 0600)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.WriteFile(filepath.Join(dir, "randomfile"), []byte(RandomString(40, CharSetAlphaNumeric)+"\n"), 0600)
|
||||
err = os.WriteFile(filepath.Join(dir, "randomfile"), []byte(r.StringCustom(40, random.CharSetAlphaNumeric)+"\n"), 0600)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sum, err := HashSHA256FromPath(filepath.Join(dir, "myfile"))
|
||||
|
|
|
@ -53,13 +53,6 @@ func TestStringJoinDelimitedEscaped(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestShouldNotGenerateSameRandomString(t *testing.T) {
|
||||
randomStringOne := RandomString(10, CharSetAlphaNumeric)
|
||||
randomStringTwo := RandomString(10, CharSetAlphaNumeric)
|
||||
|
||||
assert.NotEqual(t, randomStringOne, randomStringTwo)
|
||||
}
|
||||
|
||||
func TestShouldDetectAlphaNumericString(t *testing.T) {
|
||||
assert.True(t, IsStringAlphaNumeric("abc"))
|
||||
assert.True(t, IsStringAlphaNumeric("abc123"))
|
||||
|
|
|
@ -26,16 +26,15 @@
|
|||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@mui/icons-material": "5.11.0",
|
||||
"@mui/material": "5.11.2",
|
||||
"@mui/material": "5.11.3",
|
||||
"@mui/styles": "5.11.2",
|
||||
"axios": "1.2.1",
|
||||
"broadcast-channel": "4.18.1",
|
||||
"axios": "1.2.2",
|
||||
"broadcast-channel": "4.20.1",
|
||||
"classnames": "2.3.2",
|
||||
"i18next": "22.4.6",
|
||||
"i18next": "22.4.8",
|
||||
"i18next-browser-languagedetector": "7.0.1",
|
||||
"i18next-http-backend": "2.1.1",
|
||||
"qrcode.react": "3.1.0",
|
||||
"query-string": "7.1.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-i18next": "12.1.1",
|
||||
|
@ -143,24 +142,24 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "17.3.0",
|
||||
"@commitlint/config-conventional": "17.3.0",
|
||||
"@commitlint/cli": "17.4.0",
|
||||
"@commitlint/config-conventional": "17.4.0",
|
||||
"@limegrass/eslint-plugin-import-alias": "1.0.6",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@types/jest": "29.2.4",
|
||||
"@types/jest": "29.2.5",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/qrcode.react": "1.0.2",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.10",
|
||||
"@types/zxcvbn": "4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.47.1",
|
||||
"@typescript-eslint/parser": "5.47.1",
|
||||
"@vitejs/plugin-react": "3.0.0",
|
||||
"esbuild": "0.16.10",
|
||||
"@typescript-eslint/eslint-plugin": "5.48.0",
|
||||
"@typescript-eslint/parser": "5.48.0",
|
||||
"@vitejs/plugin-react": "3.0.1",
|
||||
"esbuild": "0.16.14",
|
||||
"esbuild-jest": "0.5.0",
|
||||
"eslint": "8.30.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-config-react-app": "7.0.1",
|
||||
"eslint-formatter-rdjson": "1.0.5",
|
||||
"eslint-import-resolver-typescript": "3.5.2",
|
||||
|
@ -169,7 +168,7 @@
|
|||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-react": "7.31.11",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"husky": "8.0.2",
|
||||
"husky": "8.0.3",
|
||||
"jest": "29.3.1",
|
||||
"jest-environment-jsdom": "29.3.1",
|
||||
"jest-transform-stub": "2.0.0",
|
||||
|
@ -177,7 +176,7 @@
|
|||
"prettier": "2.8.1",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "4.0.3",
|
||||
"vite": "4.0.4",
|
||||
"vite-plugin-eslint": "1.8.1",
|
||||
"vite-plugin-istanbul": "3.0.4",
|
||||
"vite-plugin-svgr": "2.4.0",
|
||||
|
|
1703
web/pnpm-lock.yaml
1703
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1 +1,7 @@
|
|||
export const Identifier = "id";
|
||||
export const Identifier: string = "id";
|
||||
|
||||
export const IdentityToken: string = "token";
|
||||
|
||||
export const RedirectionURL: string = "rd";
|
||||
|
||||
export const RequestMethod: string = "rm";
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
export function useQueryParam(queryParam: string) {
|
||||
const [searchParams] = useSearchParams();
|
||||
const value = searchParams.get(queryParam);
|
||||
return value !== "" ? (value as string) : undefined;
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import queryString from "query-string";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export function useRedirectionURL() {
|
||||
const location = useLocation();
|
||||
|
||||
const queryParams = queryString.parse(location.search);
|
||||
|
||||
return queryParams && "rd" in queryParams ? (queryParams["rd"] as string) : undefined;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import queryString from "query-string";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export function useRequestMethod() {
|
||||
const location = useLocation();
|
||||
const queryParams = queryString.parse(location.search);
|
||||
return queryParams && "rm" in queryParams ? (queryParams["rm"] as string) : undefined;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import queryString from "query-string";
|
||||
|
||||
export function extractIdentityToken(locationSearch: string) {
|
||||
const queryParams = queryString.parse(locationSearch);
|
||||
return queryParams && "token" in queryParams ? (queryParams["token"] as string) : null;
|
||||
}
|
|
@ -8,20 +8,20 @@ import makeStyles from "@mui/styles/makeStyles";
|
|||
import classnames from "classnames";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import AppStoreBadges from "@components/AppStoreBadges";
|
||||
import { GoogleAuthenticator } from "@constants/constants";
|
||||
import { IndexRoute } from "@constants/Routes";
|
||||
import { IdentityToken } from "@constants/SearchParams";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import LoginLayout from "@layouts/LoginLayout";
|
||||
import { completeTOTPRegistrationProcess } from "@services/RegisterDevice";
|
||||
import { extractIdentityToken } from "@utils/IdentityToken";
|
||||
|
||||
const RegisterOneTimePassword = function () {
|
||||
const styles = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
// The secret retrieved from the API is all is ok.
|
||||
const [secretURL, setSecretURL] = useState("empty");
|
||||
const [secretBase32, setSecretBase32] = useState(undefined as string | undefined);
|
||||
|
@ -32,7 +32,7 @@ const RegisterOneTimePassword = function () {
|
|||
|
||||
// Get the token from the query param to give it back to the API when requesting
|
||||
// the secret for OTP.
|
||||
const processToken = extractIdentityToken(location.search);
|
||||
const processToken = useQueryParam(IdentityToken);
|
||||
|
||||
const handleDoneClick = () => {
|
||||
navigate(IndexRoute);
|
||||
|
|
|
@ -10,7 +10,9 @@ import InformationIcon from "@components/InformationIcon";
|
|||
import SuccessIcon from "@components/SuccessIcon";
|
||||
import WebauthnTryIcon from "@components/WebauthnTryIcon";
|
||||
import { SettingsRoute, SettingsTwoFactorAuthenticationSubRoute } from "@constants/Routes";
|
||||
import { IdentityToken } from "@constants/SearchParams";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import LoginLayout from "@layouts/LoginLayout";
|
||||
import { AttestationPublicKeyCredential, AttestationResult, WebauthnTouchState } from "@models/Webauthn";
|
||||
import {
|
||||
|
@ -18,7 +20,6 @@ import {
|
|||
getAttestationCreationOptions,
|
||||
getAttestationPublicKeyCredentialResult,
|
||||
} from "@services/Webauthn";
|
||||
import { extractIdentityToken } from "@utils/IdentityToken";
|
||||
|
||||
const steps = ["Confirm device", "Choose name"];
|
||||
|
||||
|
@ -39,7 +40,7 @@ const RegisterWebauthn = function (props: Props) {
|
|||
const nameRef = useRef() as MutableRefObject<HTMLInputElement>;
|
||||
const [nameError, setNameError] = useState(false);
|
||||
|
||||
const processToken = extractIdentityToken(location.search);
|
||||
const processToken = useQueryParam(IdentityToken);
|
||||
|
||||
const handleBackClick = () => {
|
||||
navigate(`${SettingsRoute}${SettingsTwoFactorAuthenticationSubRoute}`);
|
||||
|
|
|
@ -9,9 +9,9 @@ import { useNavigate } from "react-router-dom";
|
|||
|
||||
import FixedTextField from "@components/FixedTextField";
|
||||
import { ResetPasswordStep1Route } from "@constants/Routes";
|
||||
import { RedirectionURL, RequestMethod } from "@constants/SearchParams";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { useRedirectionURL } from "@hooks/RedirectionURL";
|
||||
import { useRequestMethod } from "@hooks/RequestMethod";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import { useWorkflow } from "@hooks/Workflow";
|
||||
import LoginLayout from "@layouts/LoginLayout";
|
||||
import { postFirstFactor } from "@services/FirstFactor";
|
||||
|
@ -32,8 +32,8 @@ export interface Props {
|
|||
const FirstFactorForm = function (props: Props) {
|
||||
const styles = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const redirectionURL = useRedirectionURL();
|
||||
const requestMethod = useRequestMethod();
|
||||
const redirectionURL = useQueryParam(RedirectionURL);
|
||||
const requestMethod = useQueryParam(RequestMethod);
|
||||
const [workflow] = useWorkflow();
|
||||
|
||||
const loginChannel = useMemo(() => new BroadcastChannel<boolean>("login"), []);
|
||||
|
|
|
@ -10,9 +10,10 @@ import {
|
|||
SecondFactorTOTPSubRoute,
|
||||
SecondFactorWebauthnSubRoute,
|
||||
} from "@constants/Routes";
|
||||
import { RedirectionURL } from "@constants/SearchParams";
|
||||
import { useConfiguration } from "@hooks/Configuration";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { useRedirectionURL } from "@hooks/RedirectionURL";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import { useRedirector } from "@hooks/Redirector";
|
||||
import { useRouterNavigate } from "@hooks/RouterNavigate";
|
||||
import { useAutheliaState } from "@hooks/State";
|
||||
|
@ -38,7 +39,7 @@ const RedirectionErrorMessage =
|
|||
|
||||
const LoginPortal = function (props: Props) {
|
||||
const location = useLocation();
|
||||
const redirectionURL = useRedirectionURL();
|
||||
const redirectionURL = useQueryParam(RedirectionURL);
|
||||
const { createErrorNotification } = useNotifications();
|
||||
const [firstFactorDisabled, setFirstFactorDisabled] = useState(true);
|
||||
const [broadcastRedirect, setBroadcastRedirect] = useState(false);
|
||||
|
|
|
@ -34,6 +34,7 @@ const OTPDial = function (props: Props) {
|
|||
isDisabled={props.state === State.InProgress || props.state === State.Success}
|
||||
isInputNum
|
||||
hasErrored={props.state === State.Failure}
|
||||
autoComplete="one-time-code"
|
||||
inputStyle={classnames(
|
||||
styles.otpDigitInput,
|
||||
props.state === State.Failure ? styles.inputError : "",
|
||||
|
|
|
@ -2,7 +2,8 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
|
|||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useRedirectionURL } from "@hooks/RedirectionURL";
|
||||
import { RedirectionURL } from "@constants/SearchParams";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import { useUserInfoTOTPConfiguration } from "@hooks/UserInfoTOTPConfiguration";
|
||||
import { useWorkflow } from "@hooks/Workflow";
|
||||
import { completeTOTPSignIn } from "@services/OneTimePassword";
|
||||
|
@ -33,7 +34,7 @@ const OneTimePasswordMethod = function (props: Props) {
|
|||
const [state, setState] = useState(
|
||||
props.authenticationLevel === AuthenticationLevel.TwoFactor ? State.Success : State.Idle,
|
||||
);
|
||||
const redirectionURL = useRedirectionURL();
|
||||
const redirectionURL = useQueryParam(RedirectionURL);
|
||||
const [workflow, workflowID] = useWorkflow();
|
||||
const { t: translate } = useTranslation();
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ import makeStyles from "@mui/styles/makeStyles";
|
|||
import FailureIcon from "@components/FailureIcon";
|
||||
import PushNotificationIcon from "@components/PushNotificationIcon";
|
||||
import SuccessIcon from "@components/SuccessIcon";
|
||||
import { RedirectionURL } from "@constants/SearchParams";
|
||||
import { useIsMountedRef } from "@hooks/Mounted";
|
||||
import { useRedirectionURL } from "@hooks/RedirectionURL";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import { useWorkflow } from "@hooks/Workflow";
|
||||
import {
|
||||
DuoDevicePostRequest,
|
||||
|
@ -44,7 +45,7 @@ export interface Props {
|
|||
const PushNotificationMethod = function (props: Props) {
|
||||
const styles = useStyles();
|
||||
const [state, setState] = useState(State.SignInInProgress);
|
||||
const redirectionURL = useRedirectionURL();
|
||||
const redirectionURL = useQueryParam(RedirectionURL);
|
||||
const [workflow, workflowID] = useWorkflow();
|
||||
const mounted = useIsMountedRef();
|
||||
const [enroll_url, setEnrollUrl] = useState("");
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import WebauthnTryIcon from "@components/WebauthnTryIcon";
|
||||
import { RedirectionURL } from "@constants/SearchParams";
|
||||
import { useIsMountedRef } from "@hooks/Mounted";
|
||||
import { useRedirectionURL } from "@hooks/RedirectionURL";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import { useWorkflow } from "@hooks/Workflow";
|
||||
import { AssertionResult, WebauthnTouchState } from "@models/Webauthn";
|
||||
import { AuthenticationLevel } from "@services/State";
|
||||
|
@ -25,7 +26,7 @@ export interface Props {
|
|||
|
||||
const WebauthnMethod = function (props: Props) {
|
||||
const [state, setState] = useState(WebauthnTouchState.WaitTouch);
|
||||
const redirectionURL = useRedirectionURL();
|
||||
const redirectionURL = useQueryParam(RedirectionURL);
|
||||
const [workflow, workflowID] = useWorkflow();
|
||||
const mounted = useIsMountedRef();
|
||||
|
||||
|
|
|
@ -6,9 +6,10 @@ import { useTranslation } from "react-i18next";
|
|||
import { Navigate } from "react-router-dom";
|
||||
|
||||
import { IndexRoute } from "@constants/Routes";
|
||||
import { RedirectionURL } from "@constants/SearchParams";
|
||||
import { useIsMountedRef } from "@hooks/Mounted";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { useRedirectionURL } from "@hooks/RedirectionURL";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import { useRedirector } from "@hooks/Redirector";
|
||||
import LoginLayout from "@layouts/LoginLayout";
|
||||
import { signOut } from "@services/SignOut";
|
||||
|
@ -19,7 +20,7 @@ const SignOut = function (props: Props) {
|
|||
const mounted = useIsMountedRef();
|
||||
const styles = useStyles();
|
||||
const { createErrorNotification } = useNotifications();
|
||||
const redirectionURL = useRedirectionURL();
|
||||
const redirectionURL = useQueryParam(RedirectionURL);
|
||||
const redirector = useRedirector();
|
||||
const [timedOut, setTimedOut] = useState(false);
|
||||
const [safeRedirect, setSafeRedirect] = useState(false);
|
||||
|
|
|
@ -5,21 +5,21 @@ import { Button, Grid, IconButton, InputAdornment, Theme } from "@mui/material";
|
|||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import classnames from "classnames";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import FixedTextField from "@components/FixedTextField";
|
||||
import PasswordMeter from "@components/PasswordMeter";
|
||||
import { IndexRoute } from "@constants/Routes";
|
||||
import { IdentityToken } from "@constants/SearchParams";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import LoginLayout from "@layouts/LoginLayout";
|
||||
import { PasswordPolicyConfiguration, PasswordPolicyMode } from "@models/PasswordPolicy";
|
||||
import { getPasswordPolicyConfiguration } from "@services/PasswordPolicyConfiguration";
|
||||
import { completeResetPasswordProcess, resetPassword } from "@services/ResetPassword";
|
||||
import { extractIdentityToken } from "@utils/IdentityToken";
|
||||
|
||||
const ResetPasswordStep2 = function () {
|
||||
const styles = useStyles();
|
||||
const location = useLocation();
|
||||
const [formDisabled, setFormDisabled] = useState(true);
|
||||
const [password1, setPassword1] = useState("");
|
||||
const [password2, setPassword2] = useState("");
|
||||
|
@ -43,7 +43,7 @@ const ResetPasswordStep2 = function () {
|
|||
|
||||
// Get the token from the query param to give it back to the API when requesting
|
||||
// the secret for OTP.
|
||||
const processToken = extractIdentityToken(location.search);
|
||||
const processToken = useQueryParam(IdentityToken);
|
||||
|
||||
const completeProcess = useCallback(async () => {
|
||||
if (!processToken) {
|
||||
|
|
Loading…
Reference in New Issue