diff --git a/.all-contributorsrc b/.all-contributorsrc
index 72f97d226..7dcf00357 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -795,6 +795,34 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "LongerHV",
+ "name": "Michał Mieszczak",
+ "avatar_url": "https://avatars.githubusercontent.com/u/46924944?v=4",
+ "profile": "https://github.com/LongerHV",
+ "contributions": [
+ "ideas",
+ "code"
+ ]
+ },
+ {
+ "login": "paul-ohl",
+ "name": "Paul Ohl",
+ "avatar_url": "https://avatars.githubusercontent.com/u/37795294?v=4",
+ "profile": "https://github.com/paul-ohl",
+ "contributions": [
+ "doc"
+ ]
+ },
+ {
+ "login": "smkent",
+ "name": "Stephen Kent",
+ "avatar_url": "https://avatars.githubusercontent.com/u/2831985?v=4",
+ "profile": "https://github.com/smkent",
+ "contributions": [
+ "ideas"
+ ]
}
],
"contributorsPerLine": 7
diff --git a/.codecov.yml b/.codecov.yml
index 4287a1ca8..6fec44956 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -16,10 +16,12 @@ coverage:
default: false
backend:
base: auto
+ threshold: 0.15%
flags:
- backend
frontend:
base: auto
+ threshold: 0.15%
flags:
- frontend
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
new file mode 100644
index 000000000..13ee8d8b9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -0,0 +1,144 @@
+---
+name: Bug Report
+description: Report a bug
+labels:
+ - type/bug/unconfirmed
+ - status/needs-triage
+ - priority/4/normal
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report. If you are unsure if this is actually a bug we generally recommend creating a [Question and Answer Discussion](https://github.com/authelia/authelia/discussions/new?category=q-a) first.
+
+ Please review the following requirements before submitting this issue type:
+
+ 1. Please ensure you do not report security vulnerabilities via this method. See our [Security Policy](https://www.authelia.com/security-policy).
+ 2. Please try to give as much information as possible for us to be able to reproduce the issue and provide a quick fix.
+ 3. Please ensure an issue does not already exist for this potential bug.
+ 4. Please only provide specific versions. Latest is not a version.
+ 5. Please read the [Troubleshooting Sanitization](https://www.authelia.com/r/sanitize) reference guide if you plan on removing or adjusting any values for the logs or configuration files.
+ 6. Please consider including a [HTTP Archive File](https://www.authelia.com/r/har) if you're having redirection issues.
+ - type: dropdown
+ id: version
+ attributes:
+ label: Version
+ description: What version(s) of Authelia can you reproduce this bug on?
+ multiple: true
+ options:
+ - v4.37.2
+ - v4.37.1
+ - v4.37.0
+ - v4.36.9
+ - v4.36.8
+ - v4.36.7
+ - v4.36.6
+ - v4.36.5
+ - v4.36.4
+ - v4.36.3
+ - v4.36.2
+ - v4.36.1
+ - v4.36.0
+ - v4.35.6
+ - v4.35.5
+ - v4.35.4
+ - v4.35.3
+ - v4.35.2
+ - v4.35.1
+ - v4.35.0
+ - v4.34.6
+ - v4.34.5
+ - v4.34.4
+ - v4.34.3
+ - v4.34.2
+ - v4.34.1
+ - v4.34.0
+ - v4.33.2
+ - v4.33.1
+ - v4.33.0
+ - v4.32.2
+ - v4.32.1
+ - v4.32.0
+ validations:
+ required: true
+ - type: dropdown
+ id: deployment
+ attributes:
+ label: Deployment Method
+ description: How are you deploying Authelia?
+ options:
+ - Docker
+ - Kubernetes
+ - Bare-metal
+ - Other
+ validations:
+ required: true
+ - type: dropdown
+ id: proxy
+ attributes:
+ label: Reverse Proxy
+ description: What reverse proxy are you using?
+ options:
+ - Caddy
+ - Traefik
+ - Envoy
+ - Istio
+ - NGINX
+ - SWAG
+ - NGINX Proxy Manager
+ - HAProxy
+ validations:
+ required: true
+ - type: input
+ id: proxy-version
+ attributes:
+ label: Reverse Proxy Version
+ description: What is the version of your reverse proxy?
+ placeholder: x.x.x
+ validations:
+ required: false
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: Describe the bug
+ validations:
+ required: true
+ - type: textarea
+ id: reproduction
+ attributes:
+ label: Reproduction
+ description: Describe how we can reproduce this issue
+ validations:
+ required: true
+ - type: textarea
+ id: expectations
+ attributes:
+ label: Expectations
+ description: Describe the desired or expected results
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Logs
+ description: Provide the logs (the template will automatically put this content in a code block)
+ render: shell
+ validations:
+ required: false
+ - type: textarea
+ id: configuration
+ attributes:
+ label: Configuration
+ description: Provide the Authelia configuration file (the template will automatically put this content in a code block)
+ render: yaml
+ validations:
+ required: false
+ - type: textarea
+ id: documentation
+ attributes:
+ label: Documentation
+ description: Provide any relevant specification or other documentation if applicable
+ validations:
+ required: false
+...
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 76a60c8f8..000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-name: Bug Report
-about: Use this template to report bugs other than security vulnerabilities
-labels: Possible Bug
----
-## Bug Report
-
-
-### Description
-
-
-
-### Expected Behaviour
-
-_N/A_
-
-
-### Reproduction Steps
-
-_N/A_
-
-
-### Additional Information
-
-_N/A_
-
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 899f94db9..2a89a14ac 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,6 +1,15 @@
---
blank_issues_enabled: false
contact_links:
+ - name: Idea
+ url: https://github.com/authelia/authelia/discussions/new?category=ideas
+ about: Submit an Idea for Voting
+ - name: Question
+ url: https://github.com/authelia/authelia/discussions/new?category=q-a
+ about: Ask a Question
+ - name: Discussion
+ url: https://github.com/authelia/authelia/discussions/new
+ about: Start a Discussion related to Ideas, Polls, Show and Tell, or General Topics
- name: Documentation
url: https://www.authelia.com/
about: Read the Documentation
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
new file mode 100644
index 000000000..44832359d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -0,0 +1,47 @@
+---
+name: Feature Request
+description: Submit a Feature Request
+labels:
+ - type/feature
+ - status/needs-design
+ - priority/4/normal
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this feature request. A feature request is created as issue for the purpose of tracking the design and implementation of a feature.
+
+ Please review the following requirements before submitting this issue type:
+
+ 1. Ensure there are no other similar feature requests.
+ 2. Make sure you've checked the [Documentation](https://www.authelia.com) doesn't clearly document the features existence already.
+ 3. Consider creating an [Idea Discussion](https://github.com/authelia/authelia/discussions/new?category=ideas) which can be voted on instead if one doesn't exist.
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: Describe the feature
+ validations:
+ required: true
+ - type: textarea
+ id: use-case
+ attributes:
+ label: Use Case
+ description: Provide a use case
+ validations:
+ required: true
+ - type: textarea
+ id: details
+ attributes:
+ label: Details
+ description: Describe the feature in detail
+ validations:
+ required: false
+ - type: textarea
+ id: documentation
+ attributes:
+ label: Documentation
+ description: Provide any relevant specification or other documentation if applicable
+ validations:
+ required: false
+...
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 4354ccba9..000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-name: Feature Request
-about: Use this template to request features
-labels: Feature Request
----
-## Feature Request
-
-
-### Description
-
-
-
-### Use Case
-
-_N/A_
-
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/misc.md b/.github/ISSUE_TEMPLATE/misc.md
deleted file mode 100644
index 23a941098..000000000
--- a/.github/ISSUE_TEMPLATE/misc.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: Miscellaneous
-about: Use this template for everything other than feature requests, security vulnerabilities, or bug reports such as questions
----
-
\ No newline at end of file
diff --git a/.github/pre-commit b/.github/pre-commit
index d38e39af9..8ed8ea753 100755
--- a/.github/pre-commit
+++ b/.github/pre-commit
@@ -1,7 +1,6 @@
#!/bin/sh
-if [[ ! -z "$NO_HOOK" ]]
-then
+if [ -n "$NO_HOOK" ]; then
exit 0
fi
@@ -9,4 +8,4 @@ fi
. "$(dirname "$0")/required-apps"
golangci-lint run -v --fix && \
-cd web && ${PMGR} lint
+cd web && "${PMGR}" lint
diff --git a/.github/required-apps b/.github/required-apps
index 2de152a46..f4d8368db 100644
--- a/.github/required-apps
+++ b/.github/required-apps
@@ -1,19 +1,19 @@
#!/bin/sh
-PMGR=pnpm
+export PMGR=pnpm
-if [[ ! -x "$(command -v golangci-lint)" ]]; then
+if [ ! -x "$(command -v golangci-lint)" ]; then
echo "You must install golangci-lint."
exit 1
fi
-if [[ ! -x "$(command -v pnpm)" ]]; then
- PMGR=yarn
- if [[ ! -x "$(command -v yarn)" ]]; then
- PMGR=npm
- if [[ ! -x "$(command -v npm)" ]]; then
+if [ ! -x "$(command -v pnpm)" ]; then
+ export PMGR=yarn
+ if [ ! -x "$(command -v yarn)" ]; then
+ export PMGR=npm
+ if [ ! -x "$(command -v npm)" ]; then
echo "You must install a node package manager."
exit 1
fi
fi
-fi
\ No newline at end of file
+fi
diff --git a/.renovaterc b/.renovaterc
index 09fe00a98..9ddef69e8 100644
--- a/.renovaterc
+++ b/.renovaterc
@@ -2,16 +2,16 @@
"extends": [
"config:base",
":semanticCommitTypeAll(build)",
- ":separatePatchReleases"
+ ":separatePatchReleases",
+ "workarounds:doNotUpgradeFromAlpineStableToEdge"
],
"ignorePaths": [
"docs/**"
],
"ignorePresets": [
- ":combinePatchMinorReleases",
- "helpers:disableTypesNodeMajor",
":prHourlyLimit2",
- ":semanticPrefixFixDepsChoreOthers"
+ ":semanticPrefixFixDepsChoreOthers",
+ "workarounds:all"
],
"enabledManagers": [
"bundler",
@@ -24,6 +24,13 @@
"dependencies"
],
"packageRules": [
+ {
+ "matchUpdateTypes": ["digest", "minor", "patch"],
+ "matchCurrentVersion": "!/^0/",
+ "automerge": true,
+ "automergeType": "pr",
+ "platformAutomerge": true
+ },
{
"datasources": [
"docker"
@@ -47,14 +54,6 @@
"addLabels": [
"javascript"
]
- },
- {
- "datasources": [
- "rubygems"
- ],
- "addLabels": [
- "ruby"
- ]
}
],
"postUpdateOptions": [
diff --git a/.yamllint.yml b/.yamllint.yml
index 3d31542b4..d7fc7209f 100644
--- a/.yamllint.yml
+++ b/.yamllint.yml
@@ -6,7 +6,8 @@ ignore: |
internal/configuration/test_resources/config_bad_quoting.yml
web/pnpm-lock.yaml
web/node_modules/
-
+ .github/ISSUE_TEMPLATE/feature-request.yml
+ .github/ISSUE_TEMPLATE/bug-report.yml
rules:
document-end:
level: warning
diff --git a/Dockerfile.coverage b/Dockerfile.coverage
index 110b772bd..6db2f7178 100644
--- a/Dockerfile.coverage
+++ b/Dockerfile.coverage
@@ -1,7 +1,7 @@
# ========================================
# ===== Build image for the frontend =====
# ========================================
-FROM node:18-alpine AS builder-frontend
+FROM node:19-alpine AS builder-frontend
WORKDIR /node/src/app
@@ -15,7 +15,7 @@ RUN yarn global add pnpm && \
# =======================================
# ===== Build image for the backend =====
# =======================================
-FROM golang:1.19.1-alpine AS builder-backend
+FROM golang:1.19.3-alpine AS builder-backend
WORKDIR /go/src/app
diff --git a/Dockerfile.dev b/Dockerfile.dev
index 26592e422..11f74ea67 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -1,7 +1,7 @@
# ========================================
# ===== Build image for the frontend =====
# ========================================
-FROM node:18-alpine AS builder-frontend
+FROM node:19-alpine AS builder-frontend
WORKDIR /node/src/app
@@ -13,7 +13,7 @@ RUN yarn install --frozen-lockfile && yarn build
# =======================================
# ===== Build image for the backend =====
# =======================================
-FROM golang:1.19.1-alpine AS builder-backend
+FROM golang:1.19.3-alpine AS builder-backend
WORKDIR /go/src/app
diff --git a/README.md b/README.md
index 589de509e..9faedeae5 100644
--- a/README.md
+++ b/README.md
@@ -16,9 +16,8 @@
[![Matrix](https://img.shields.io/matrix/authelia-support:matrix.org?label=matrix&logo=matrix&style=flat-square&color=blue)](https://matrix.to/#/#support:authelia.com)
**Authelia** is an open-source authentication and authorization server providing two-factor authentication and single
-sign-on (SSO) for your applications via a web portal. It acts as a companion for reverse proxies like [nginx],
-[Traefik], [caddy] or [HAProxy] to let them know whether requests should either be allowed or redirected to Authelia's
-portal for authentication.
+sign-on (SSO) for your applications via a web portal. It acts as a companion for [reverse proxies](#proxy-support) by
+allowing, denying, or redirecting requests.
Documentation is available at [https://www.authelia.com/](https://www.authelia.com/).
@@ -31,9 +30,12 @@ The following is a simple diagram of the architecture:
**Authelia** can be installed as a standalone service from the [AUR](https://aur.archlinux.org/packages/authelia/),
[APT](https://apt.authelia.com/stable/debian/packages/authelia/),
[FreeBSD Ports](https://svnweb.freebsd.org/ports/head/www/authelia/), or using a
-[Static binary](https://github.com/authelia/authelia/releases/latest),
-[.deb package]((https://github.com/authelia/authelia/releases/latest)), [Docker] or [Kubernetes] either manually or via
-the Helm [Chart](https://charts.authelia.com) (beta) leveraging ingress controllers and ingress configurations.
+[static binary](https://github.com/authelia/authelia/releases/latest),
+[.deb package]((https://github.com/authelia/authelia/releases/latest)), as a container on [Docker] or [Kubernetes].
+
+
+Deployment can be orchestrated via the Helm [Chart](https://charts.authelia.com) (beta) leveraging ingress controllers
+and ingress configurations.
@@ -70,13 +72,14 @@ This is a list of the key features of Authelia:
* Curated configuration from [LinuxServer](https://www.linuxserver.io/) via their
[Swag](https://docs.linuxserver.io/general/swag) container as well as a
[guide](https://blog.linuxserver.io/2020/08/26/setting-up-authelia/).
-* Compatible with [caddy] using the [forward_auth](https://caddyserver.com/docs/caddyfile/directives/forward_auth)
+* Compatible with [Caddy] using the [forward_auth](https://caddyserver.com/docs/caddyfile/directives/forward_auth)
directive.
* Kubernetes Support:
- * Compatible with the [ingress-nginx](https://github.com/kubernetes/ingress-nginx), the
- [Traefik Kubernetes CRD](https://doc.traefik.io/traefik/providers/kubernetes-crd/), and the
- [Traefik Kubernetes Ingress](https://doc.traefik.io/traefik/providers/kubernetes-crd/) Kubernetes ingress
- controllers out of the box.
+ * Compatible with several Kubernetes ingress controllers:
+ * [ingress-nginx](https://www.authelia.com/integration/kubernetes/nginx-ingress/)
+ * [Traefik Kubernetes CRD](https://www.authelia.com/integration/kubernetes/traefik-ingress/#ingressroute)
+ * [Traefik Kubernetes Ingress](https://www.authelia.com/integration/kubernetes/traefik-ingress/#ingress)
+ * [Istio](https://www.authelia.com/integration/kubernetes/istio/)
* Beta support for installing via Helm using our [Charts](https://charts.authelia.com).
* Beta support for [OpenID Connect](https://www.authelia.com/roadmap/active/openid-connect/).
@@ -86,22 +89,14 @@ If you want to know more about the roadmap, follow [Roadmap](https://www.autheli
## Proxy support
-Authelia works in combination with [nginx], [Traefik], [caddy] or [HAProxy]. It can be deployed on bare metal with
-Docker or on top of [Kubernetes].
+Authelia works in combination with [nginx], [Traefik], [Caddy], [Skipper], [Envoy], or [HAProxy].
-
-
-
-
-***Help Wanted:*** Assistance would be appreciated in getting Authelia working with
-[Envoy](https://www.envoyproxy.io/).
-
-
+
## Getting Started
@@ -200,113 +195,120 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
@@ -389,10 +391,12 @@ Companies contributing to Authelia via Open Collective will have a special menti
[Webauthn]: https://www.yubico.com/authentication-standards/webauthn/
[auth_request]: https://nginx.org/en/docs/http/ngx_http_auth_request_module.html
[config.template.yml]: ./config.template.yml
-[nginx]: https://www.nginx.com/
-[Traefik]: https://traefik.io/
-[caddy]: https://caddyserver.com/
-[HAProxy]: https://www.haproxy.org/
+[nginx]: https://www.authelia.com/integration/proxies/nginx/
+[Traefik]: https://www.authelia.com/integration/proxies/traefik/
+[Caddy]: https://www.authelia.com/integration/proxies/caddy/
+[Skipper]: https://www.authelia.com/integration/proxies/skipper/
+[Envoy]: https://www.authelia.com/integration/proxies/envoy/
+[HAProxy]: https://www.authelia.com/integration/proxies/haproxy/
[Docker]: https://docker.com/
[Kubernetes]: https://kubernetes.io/
[security]: https://github.com/authelia/authelia/security/policy
diff --git a/api/openapi.yml b/api/openapi.yml
index 10ec36b9d..15443a24e 100644
--- a/api/openapi.yml
+++ b/api/openapi.yml
@@ -176,7 +176,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/handlers.firstFactorRequestBody'
+ $ref: '#/components/schemas/handlers.bodyFirstFactorRequest'
responses:
"200":
description: Successful Operation
@@ -446,7 +446,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/handlers.signTOTPRequestBody'
+ $ref: '#/components/schemas/handlers.bodySignTOTPRequest'
responses:
"200":
description: Successful Operation
@@ -579,7 +579,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/handlers.signDuoRequestBody'
+ $ref: '#/components/schemas/handlers.bodySignDuoRequest'
responses:
"200":
description: Successful Operation
@@ -785,7 +785,7 @@ components:
items:
type: string
example: push
- handlers.firstFactorRequestBody:
+ handlers.bodyFirstFactorRequest:
required:
- username
- password
@@ -800,6 +800,12 @@ components:
targetURL:
type: string
example: https://home.example.com
+ workflow:
+ type: string
+ example: openid_connect
+ workflowID:
+ type: string
+ example: 3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c
requestMethod:
type: string
example: GET
@@ -852,13 +858,21 @@ components:
password:
type: string
example: password
- handlers.signDuoRequestBody:
+ handlers.bodySignDuoRequest:
type: object
properties:
targetURL:
type: string
example: https://secure.example.com
- handlers.signTOTPRequestBody:
+ passcode:
+ type: string
+ workflow:
+ type: string
+ example: openid_connect
+ workflowID:
+ type: string
+ example: 3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c
+ handlers.bodySignTOTPRequest:
type: object
properties:
token:
@@ -867,6 +881,12 @@ components:
targetURL:
type: string
example: https://secure.example.com
+ workflow:
+ type: string
+ example: openid_connect
+ workflowID:
+ type: string
+ example: 3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c
handlers.StateResponse:
type: object
properties:
@@ -1047,6 +1067,12 @@ components:
userHandle:
type: string
format: byte
+ workflow:
+ type: string
+ example: openid_connect
+ workflowID:
+ type: string
+ example: 3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c
webauthn.PublicKeyCredentialCreationOptions:
type: object
properties:
diff --git a/cmd/authelia-gen/cmd_all.go b/cmd/authelia-gen/cmd_all.go
deleted file mode 100644
index aba00a5a3..000000000
--- a/cmd/authelia-gen/cmd_all.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package main
-
-import (
- "github.com/spf13/cobra"
-)
-
-func newAllCmd() *cobra.Command {
- cmd := &cobra.Command{
- Use: "all",
- Short: "Run all generators with default options",
- RunE: allRunE,
-
- DisableAutoGenTag: true,
- }
-
- return cmd
-}
-
-func allRunE(cmd *cobra.Command, args []string) (err error) {
- for _, subCmd := range cmd.Parent().Commands() {
- if subCmd == cmd || subCmd.Use == "completion" || subCmd.Use == "help [command]" {
- continue
- }
-
- switch {
- case subCmd.RunE != nil:
- if err = subCmd.RunE(subCmd, args); err != nil {
- return err
- }
- case subCmd.Run != nil:
- subCmd.Run(subCmd, args)
- }
- }
-
- return nil
-}
diff --git a/cmd/authelia-gen/cmd_code.go b/cmd/authelia-gen/cmd_code.go
index f0eea6b02..1359298c5 100644
--- a/cmd/authelia-gen/cmd_code.go
+++ b/cmd/authelia-gen/cmd_code.go
@@ -1,34 +1,330 @@
package main
import (
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/mail"
+ "net/url"
+ "os"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "strings"
+ "time"
+
"github.com/spf13/cobra"
+
+ "github.com/authelia/authelia/v4/internal/configuration/schema"
)
func newCodeCmd() *cobra.Command {
cmd := &cobra.Command{
- Use: "code",
+ Use: cmdUseCode,
Short: "Generate code",
- RunE: codeRunE,
+ RunE: rootSubCommandsRunE,
DisableAutoGenTag: true,
}
- cmd.AddCommand(newCodeKeysCmd())
+ cmd.AddCommand(newCodeKeysCmd(), newCodeServerCmd(), newCodeScriptsCmd())
return cmd
}
-func codeRunE(cmd *cobra.Command, args []string) (err error) {
- for _, subCmd := range cmd.Commands() {
- switch {
- case subCmd.RunE != nil:
- if err = subCmd.RunE(subCmd, args); err != nil {
- return err
- }
- case subCmd.Run != nil:
- subCmd.Run(subCmd, args)
- }
+func newCodeServerCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseServer,
+ Short: "Generate the Authelia server files",
+ RunE: codeServerRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func newCodeScriptsCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseCodeScripts,
+ Short: "Generate the generated portion of the authelia-scripts command",
+ RunE: codeScriptsRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func newCodeKeysCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseKeys,
+ Short: "Generate the list of valid configuration keys",
+ RunE: codeKeysRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func codeServerRunE(cmd *cobra.Command, args []string) (err error) {
+ data := TemplateCSP{
+ PlaceholderNONCE: codeCSPNonce,
+ TemplateDefault: buildCSP(codeCSPProductionDefaultSrc, codeCSPValuesCommon, codeCSPValuesProduction),
+ TemplateDevelopment: buildCSP(codeCSPDevelopmentDefaultSrc, codeCSPValuesCommon, codeCSPValuesDevelopment),
+ }
+
+ var outputPath string
+
+ if outputPath, err = getPFlagPath(cmd.Flags(), cmdFlagRoot, cmdFlagFileServerGenerated); err != nil {
+ return err
+ }
+
+ var f *os.File
+
+ if f, err = os.Create(outputPath); err != nil {
+ return fmt.Errorf("failed to create file '%s': %w", outputPath, err)
+ }
+
+ if err = tmplServer.Execute(f, data); err != nil {
+ _ = f.Close()
+
+ return fmt.Errorf("failed to write output file '%s': %w", outputPath, err)
+ }
+
+ if err = f.Close(); err != nil {
+ return fmt.Errorf("failed to close output file '%s': %w", outputPath, err)
}
return nil
}
+
+func codeScriptsRunE(cmd *cobra.Command, args []string) (err error) {
+ var (
+ root, pathScriptsGen string
+ resp *http.Response
+ )
+
+ data := &tmplScriptsGEnData{}
+
+ if root, err = cmd.Flags().GetString(cmdFlagRoot); err != nil {
+ return err
+ }
+
+ if pathScriptsGen, err = cmd.Flags().GetString(cmdFlagFileScriptsGen); err != nil {
+ return err
+ }
+
+ if data.Package, err = cmd.Flags().GetString(cmdFlagPackageScriptsGen); err != nil {
+ return err
+ }
+
+ if resp, err = http.Get("https://api.github.com/repos/swagger-api/swagger-ui/tags"); err != nil {
+ return fmt.Errorf("failed to get latest version of the Swagger UI: %w", err)
+ }
+
+ defer resp.Body.Close()
+
+ var (
+ respJSON []GitHubTagsJSON
+ respRaw []byte
+ )
+
+ if respRaw, err = io.ReadAll(resp.Body); err != nil {
+ return fmt.Errorf("failed to get latest version of the Swagger UI: %w", err)
+ }
+
+ if err = json.Unmarshal(respRaw, &respJSON); err != nil {
+ return fmt.Errorf("failed to get latest version of the Swagger UI: %w", err)
+ }
+
+ if len(respJSON) < 1 {
+ return fmt.Errorf("failed to get latest version of the Swagger UI: the api returned zero results")
+ }
+
+ if strings.HasPrefix(respJSON[0].Name, "v") {
+ data.VersionSwaggerUI = respJSON[0].Name[1:]
+ } else {
+ data.VersionSwaggerUI = respJSON[0].Name
+ }
+
+ fullPathScriptsGen := filepath.Join(root, pathScriptsGen)
+
+ var f *os.File
+
+ if f, err = os.Create(fullPathScriptsGen); err != nil {
+ return fmt.Errorf("failed to create file '%s': %w", fullPathScriptsGen, err)
+ }
+
+ if err = tmplScriptsGen.Execute(f, data); err != nil {
+ _ = f.Close()
+
+ return fmt.Errorf("failed to write output file '%s': %w", fullPathScriptsGen, err)
+ }
+
+ if err = f.Close(); err != nil {
+ return fmt.Errorf("failed to close output file '%s': %w", fullPathScriptsGen, err)
+ }
+
+ return nil
+}
+
+func codeKeysRunE(cmd *cobra.Command, args []string) (err error) {
+ var (
+ pathCodeConfigKeys, root string
+
+ f *os.File
+ )
+
+ data := tmplConfigurationKeysData{
+ Timestamp: time.Now(),
+ Keys: readTags("", reflect.TypeOf(schema.Configuration{})),
+ }
+
+ if root, err = cmd.Flags().GetString(cmdFlagRoot); err != nil {
+ return err
+ }
+
+ if pathCodeConfigKeys, err = cmd.Flags().GetString(cmdFlagFileConfigKeys); err != nil {
+ return err
+ }
+
+ if data.Package, err = cmd.Flags().GetString(cmdFlagPackageConfigKeys); err != nil {
+ return err
+ }
+
+ fullPathCodeConfigKeys := filepath.Join(root, pathCodeConfigKeys)
+
+ if f, err = os.Create(fullPathCodeConfigKeys); err != nil {
+ return fmt.Errorf("failed to create file '%s': %w", fullPathCodeConfigKeys, err)
+ }
+
+ if err = tmplCodeConfigurationSchemaKeys.Execute(f, data); err != nil {
+ _ = f.Close()
+
+ return fmt.Errorf("failed to write output file '%s': %w", fullPathCodeConfigKeys, err)
+ }
+
+ if err = f.Close(); err != nil {
+ return fmt.Errorf("failed to close output file '%s': %w", fullPathCodeConfigKeys, err)
+ }
+
+ return nil
+}
+
+var decodedTypes = []reflect.Type{
+ reflect.TypeOf(mail.Address{}),
+ reflect.TypeOf(regexp.Regexp{}),
+ reflect.TypeOf(url.URL{}),
+ reflect.TypeOf(time.Duration(0)),
+ reflect.TypeOf(schema.Address{}),
+ reflect.TypeOf(rsa.PrivateKey{}),
+ reflect.TypeOf(ecdsa.PrivateKey{}),
+}
+
+func containsType(needle reflect.Type, haystack []reflect.Type) (contains bool) {
+ for _, t := range haystack {
+ if needle.Kind() == reflect.Ptr {
+ if needle.Elem() == t {
+ return true
+ }
+ } else if needle == t {
+ return true
+ }
+ }
+
+ return false
+}
+
+//nolint:gocyclo
+func readTags(prefix string, t reflect.Type) (tags []string) {
+ tags = make([]string, 0)
+
+ if t.Kind() != reflect.Struct {
+ if t.Kind() == reflect.Slice {
+ tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, "", true), t.Elem())...)
+ }
+
+ return
+ }
+
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+
+ tag := field.Tag.Get("koanf")
+
+ if tag == "" {
+ tags = append(tags, prefix)
+
+ continue
+ }
+
+ switch field.Type.Kind() {
+ case reflect.Struct:
+ if !containsType(field.Type, decodedTypes) {
+ tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, false), field.Type)...)
+
+ continue
+ }
+ case reflect.Slice:
+ switch field.Type.Elem().Kind() {
+ case reflect.Struct:
+ if !containsType(field.Type.Elem(), decodedTypes) {
+ tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false))
+ tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, true), field.Type.Elem())...)
+
+ continue
+ }
+ case reflect.Slice:
+ tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, true), field.Type.Elem())...)
+ }
+ case reflect.Ptr:
+ switch field.Type.Elem().Kind() {
+ case reflect.Struct:
+ if !containsType(field.Type.Elem(), decodedTypes) {
+ tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, false), field.Type.Elem())...)
+
+ continue
+ }
+ case reflect.Slice:
+ if field.Type.Elem().Elem().Kind() == reflect.Struct {
+ if !containsType(field.Type.Elem(), decodedTypes) {
+ tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, true), field.Type.Elem())...)
+
+ continue
+ }
+ }
+ }
+ }
+
+ tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false))
+ }
+
+ return tags
+}
+
+func getKeyNameFromTagAndPrefix(prefix, name string, slice bool) string {
+ nameParts := strings.SplitN(name, ",", 2)
+
+ if prefix == "" {
+ return nameParts[0]
+ }
+
+ if len(nameParts) == 2 && nameParts[1] == "squash" {
+ return prefix
+ }
+
+ if slice {
+ if name == "" {
+ return fmt.Sprintf("%s[]", prefix)
+ }
+
+ return fmt.Sprintf("%s.%s[]", prefix, nameParts[0])
+ }
+
+ return fmt.Sprintf("%s.%s", prefix, nameParts[0])
+}
diff --git a/cmd/authelia-gen/cmd_code_keys.go b/cmd/authelia-gen/cmd_code_keys.go
deleted file mode 100644
index 4599ae148..000000000
--- a/cmd/authelia-gen/cmd_code_keys.go
+++ /dev/null
@@ -1,173 +0,0 @@
-package main
-
-import (
- "fmt"
- "net/mail"
- "net/url"
- "os"
- "reflect"
- "regexp"
- "strings"
- "text/template"
- "time"
-
- "github.com/spf13/cobra"
-
- "github.com/authelia/authelia/v4/internal/configuration/schema"
-)
-
-func newCodeKeysCmd() *cobra.Command {
- cmd := &cobra.Command{
- Use: "keys",
- Short: "Generate the list of valid configuration keys",
- RunE: codeKeysRunE,
-
- DisableAutoGenTag: true,
- }
-
- cmd.Flags().StringP("file", "f", "./internal/configuration/schema/keys.go", "Sets the path of the keys file")
- cmd.Flags().String("package", "schema", "Sets the package name of the keys file")
-
- return cmd
-}
-
-func codeKeysRunE(cmd *cobra.Command, args []string) (err error) {
- var (
- file string
-
- f *os.File
- )
-
- data := keysTemplateStruct{
- Timestamp: time.Now(),
- Keys: readTags("", reflect.TypeOf(schema.Configuration{})),
- }
-
- if file, err = cmd.Flags().GetString("file"); err != nil {
- return err
- }
-
- if data.Package, err = cmd.Flags().GetString("package"); err != nil {
- return err
- }
-
- if f, err = os.Create(file); err != nil {
- return fmt.Errorf("failed to create file '%s': %w", file, err)
- }
-
- var (
- content []byte
- tmpl *template.Template
- )
-
- if content, err = templatesFS.ReadFile("templates/config_keys.go.tmpl"); err != nil {
- return err
- }
-
- if tmpl, err = template.New("keys").Parse(string(content)); err != nil {
- return err
- }
-
- return tmpl.Execute(f, data)
-}
-
-type keysTemplateStruct struct {
- Timestamp time.Time
- Keys []string
- Package string
-}
-
-var decodedTypes = []reflect.Type{
- reflect.TypeOf(mail.Address{}),
- reflect.TypeOf(regexp.Regexp{}),
- reflect.TypeOf(url.URL{}),
- reflect.TypeOf(time.Duration(0)),
- reflect.TypeOf(schema.Address{}),
-}
-
-func containsType(needle reflect.Type, haystack []reflect.Type) (contains bool) {
- for _, t := range haystack {
- if needle.Kind() == reflect.Ptr {
- if needle.Elem() == t {
- return true
- }
- } else if needle == t {
- return true
- }
- }
-
- return false
-}
-
-func readTags(prefix string, t reflect.Type) (tags []string) {
- tags = make([]string, 0)
-
- for i := 0; i < t.NumField(); i++ {
- field := t.Field(i)
-
- tag := field.Tag.Get("koanf")
-
- if tag == "" {
- tags = append(tags, prefix)
-
- continue
- }
-
- switch field.Type.Kind() {
- case reflect.Struct:
- if !containsType(field.Type, decodedTypes) {
- tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, false), field.Type)...)
-
- continue
- }
- case reflect.Slice:
- if field.Type.Elem().Kind() == reflect.Struct {
- if !containsType(field.Type.Elem(), decodedTypes) {
- tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false))
- tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, true), field.Type.Elem())...)
-
- continue
- }
- }
- case reflect.Ptr:
- switch field.Type.Elem().Kind() {
- case reflect.Struct:
- if !containsType(field.Type.Elem(), decodedTypes) {
- tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, false), field.Type.Elem())...)
-
- continue
- }
- case reflect.Slice:
- if field.Type.Elem().Elem().Kind() == reflect.Struct {
- if !containsType(field.Type.Elem(), decodedTypes) {
- tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, true), field.Type.Elem())...)
-
- continue
- }
- }
- }
- }
-
- tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false))
- }
-
- return tags
-}
-
-func getKeyNameFromTagAndPrefix(prefix, name string, slice bool) string {
- nameParts := strings.SplitN(name, ",", 2)
-
- if prefix == "" {
- return nameParts[0]
- }
-
- if len(nameParts) == 2 && nameParts[1] == "squash" {
- return prefix
- }
-
- if slice {
- return fmt.Sprintf("%s.%s[]", prefix, nameParts[0])
- }
-
- return fmt.Sprintf("%s.%s", prefix, nameParts[0])
-}
diff --git a/cmd/authelia-gen/cmd_commit_msg.go b/cmd/authelia-gen/cmd_commit_msg.go
new file mode 100644
index 000000000..cadf70d68
--- /dev/null
+++ b/cmd/authelia-gen/cmd_commit_msg.go
@@ -0,0 +1,220 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+// CommitMessageTmpl is a template data structure which is used to generate files with commit message information.
+type CommitMessageTmpl struct {
+ Scopes ScopesTmpl
+ Types TypesTmpl
+}
+
+// TypesTmpl is a template data structure which is used to generate files with commit message types.
+type TypesTmpl struct {
+ List []string
+ Details []NameDescriptionTmpl
+}
+
+// ScopesTmpl is a template data structure which is used to generate files with commit message scopes.
+type ScopesTmpl struct {
+ All []string
+ Packages []string
+ Extra []NameDescriptionTmpl
+}
+
+// NameDescriptionTmpl is a template item which includes a name, description and list of scopes.
+type NameDescriptionTmpl struct {
+ Name string
+ Description string
+ Scopes []string
+}
+
+func newCommitLintCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseCommitLint,
+ Short: "Generate commit lint files",
+ RunE: commitLintRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+var commitScopesExtra = []NameDescriptionTmpl{
+ {"api", "used for changes that change the openapi specification", nil},
+ {"cmd", "used for changes to the `%s` top level binaries", nil},
+ {"web", "used for changes to the React based frontend", nil},
+}
+
+var commitTypes = []NameDescriptionTmpl{
+ {"build", "Changes that affect the build system or external dependencies", []string{"bundler", "deps", "docker", "go", "npm"}},
+ {"ci", "Changes to our CI configuration files and scripts", []string{"autheliabot", "buildkite", "codecov", "golangci-lint", "renovate", "reviewdog"}},
+ {"docs", "Documentation only changes", nil},
+ {"feat", "A new feature", nil},
+ {"fix", "A bug fix", nil},
+ {"i18n", "Updating translations or internationalization settings", nil},
+ {"perf", "A code change that improves performance", nil},
+ {"refactor", "A code change that neither fixes a bug nor adds a feature", nil},
+ {"release", "Releasing a new version of Authelia", nil},
+ {"test", "Adding missing tests or correcting existing tests", nil},
+}
+
+var commitTypesExtra = []string{"revert"}
+
+func getGoPackages(dir string) (pkgs []string, err error) {
+ var (
+ entries []os.DirEntry
+ entriesSub []os.DirEntry
+ )
+
+ if entries, err = os.ReadDir(dir); err != nil {
+ return nil, fmt.Errorf("failed to detect go packages in directory '%s': %w", dir, err)
+ }
+
+ for _, entry := range entries {
+ if !entry.IsDir() {
+ continue
+ }
+
+ if entriesSub, err = os.ReadDir(filepath.Join(dir, entry.Name())); err != nil {
+ continue
+ }
+
+ for _, entrySub := range entriesSub {
+ if entrySub.IsDir() {
+ continue
+ }
+
+ if strings.HasSuffix(entrySub.Name(), ".go") {
+ pkgs = append(pkgs, entry.Name())
+ break
+ }
+ }
+ }
+
+ return pkgs, nil
+}
+
+func commitLintRunE(cmd *cobra.Command, args []string) (err error) {
+ var root, pathCommitLintConfig, pathDocsCommitMessageGuidelines string
+
+ if root, err = cmd.Flags().GetString(cmdFlagRoot); err != nil {
+ return err
+ }
+
+ if pathCommitLintConfig, err = cmd.Flags().GetString(cmdFlagFileConfigCommitLint); err != nil {
+ return err
+ }
+
+ if pathDocsCommitMessageGuidelines, err = cmd.Flags().GetString(cmdFlagFileDocsCommitMsgGuidelines); err != nil {
+ return err
+ }
+
+ data := &CommitMessageTmpl{
+ Scopes: ScopesTmpl{
+ All: []string{},
+ Packages: []string{},
+ Extra: []NameDescriptionTmpl{},
+ },
+ Types: TypesTmpl{
+ List: []string{},
+ Details: []NameDescriptionTmpl{},
+ },
+ }
+
+ var (
+ cmds []string
+ pkgs []string
+ )
+
+ if cmds, err = getGoPackages(filepath.Join(root, subPathCmd)); err != nil {
+ return err
+ }
+
+ if pkgs, err = getGoPackages(filepath.Join(root, subPathInternal)); err != nil {
+ return err
+ }
+
+ data.Scopes.All = append(data.Scopes.All, pkgs...)
+ data.Scopes.Packages = append(data.Scopes.Packages, pkgs...)
+
+ for _, scope := range commitScopesExtra {
+ switch scope.Name {
+ case subPathCmd:
+ data.Scopes.Extra = append(data.Scopes.Extra, NameDescriptionTmpl{Name: scope.Name, Description: fmt.Sprintf(scope.Description, strings.Join(cmds, "|"))})
+ default:
+ data.Scopes.Extra = append(data.Scopes.Extra, scope)
+ }
+
+ data.Scopes.All = append(data.Scopes.All, scope.Name)
+ }
+
+ for _, cType := range commitTypes {
+ data.Types.List = append(data.Types.List, cType.Name)
+ data.Types.Details = append(data.Types.Details, cType)
+
+ data.Scopes.All = append(data.Scopes.All, cType.Scopes...)
+ }
+
+ data.Types.List = append(data.Types.List, commitTypesExtra...)
+
+ sort.Slice(data.Scopes.All, func(i, j int) bool {
+ return data.Scopes.All[i] < data.Scopes.All[j]
+ })
+
+ sort.Slice(data.Scopes.Packages, func(i, j int) bool {
+ return data.Scopes.Packages[i] < data.Scopes.Packages[j]
+ })
+
+ sort.Slice(data.Scopes.Extra, func(i, j int) bool {
+ return data.Scopes.Extra[i].Name < data.Scopes.Extra[j].Name
+ })
+
+ sort.Slice(data.Types.List, func(i, j int) bool {
+ return data.Types.List[i] < data.Types.List[j]
+ })
+
+ sort.Slice(data.Types.Details, func(i, j int) bool {
+ return data.Types.Details[i].Name < data.Types.Details[j].Name
+ })
+
+ var f *os.File
+
+ fullPathCommitLintConfig := filepath.Join(root, pathCommitLintConfig)
+
+ if f, err = os.Create(fullPathCommitLintConfig); err != nil {
+ return fmt.Errorf("failed to create output file '%s': %w", fullPathCommitLintConfig, err)
+ }
+
+ if err = tmplDotCommitLintRC.Execute(f, data); err != nil {
+ return fmt.Errorf("failed to write output file '%s': %w", fullPathCommitLintConfig, err)
+ }
+
+ if err = f.Close(); err != nil {
+ return fmt.Errorf("failed to close output file '%s': %w", fullPathCommitLintConfig, err)
+ }
+
+ fullPathDocsCommitMessageGuidelines := filepath.Join(root, pathDocsCommitMessageGuidelines)
+
+ if f, err = os.Create(fullPathDocsCommitMessageGuidelines); err != nil {
+ return fmt.Errorf("failed to create output file '%s': %w", fullPathDocsCommitMessageGuidelines, err)
+ }
+
+ if err = tmplDocsCommitMessageGuidelines.Execute(f, data); err != nil {
+ return fmt.Errorf("failed to write output file '%s': %w", fullPathDocsCommitMessageGuidelines, err)
+ }
+
+ if err = f.Close(); err != nil {
+ return fmt.Errorf("failed to close output file '%s': %w", fullPathDocsCommitMessageGuidelines, err)
+ }
+
+ return nil
+}
diff --git a/cmd/authelia-gen/cmd_docs.go b/cmd/authelia-gen/cmd_docs.go
index 8fa4b26b6..54d1135f1 100644
--- a/cmd/authelia-gen/cmd_docs.go
+++ b/cmd/authelia-gen/cmd_docs.go
@@ -6,30 +6,14 @@ import (
func newDocsCmd() *cobra.Command {
cmd := &cobra.Command{
- Use: "docs",
+ Use: cmdUseDocs,
Short: "Generate docs",
- RunE: docsRunE,
+ RunE: rootSubCommandsRunE,
DisableAutoGenTag: true,
}
- cmd.PersistentFlags().StringP("cwd", "C", "", "Sets the CWD for git commands")
- cmd.AddCommand(newDocsCLICmd(), newDocsDateCmd())
+ cmd.AddCommand(newDocsCLICmd(), newDocsDataCmd(), newDocsDateCmd())
return cmd
}
-
-func docsRunE(cmd *cobra.Command, args []string) (err error) {
- for _, subCmd := range cmd.Commands() {
- switch {
- case subCmd.RunE != nil:
- if err = subCmd.RunE(subCmd, args); err != nil {
- return err
- }
- case subCmd.Run != nil:
- subCmd.Run(subCmd, args)
- }
- }
-
- return nil
-}
diff --git a/cmd/authelia-gen/cmd_docs_cli.go b/cmd/authelia-gen/cmd_docs_cli.go
index 5e0c01bb7..178583749 100644
--- a/cmd/authelia-gen/cmd_docs_cli.go
+++ b/cmd/authelia-gen/cmd_docs_cli.go
@@ -16,52 +16,50 @@ import (
func newDocsCLICmd() *cobra.Command {
cmd := &cobra.Command{
- Use: "cli",
+ Use: cmdUseDocsCLI,
Short: "Generate CLI docs",
RunE: docsCLIRunE,
DisableAutoGenTag: true,
}
- cmd.Flags().StringP("directory", "d", "./docs/content/en/reference/cli", "The directory to store the markdown in")
-
return cmd
}
func docsCLIRunE(cmd *cobra.Command, args []string) (err error) {
- var root string
+ var outputPath string
- if root, err = cmd.Flags().GetString("directory"); err != nil {
+ if outputPath, err = getPFlagPath(cmd.Flags(), cmdFlagRoot, cmdFlagDocs, cmdFlagDocsContent, cmdFlagDocsCLIReference); err != nil {
return err
}
- if err = os.MkdirAll(root, 0775); err != nil {
+ if err = os.MkdirAll(outputPath, 0775); err != nil {
if !os.IsExist(err) {
return err
}
}
- if err = genCLIDoc(commands.NewRootCmd(), filepath.Join(root, "authelia")); err != nil {
+ if err = genCLIDoc(commands.NewRootCmd(), filepath.Join(outputPath, "authelia")); err != nil {
return err
}
- if err = genCLIDocWriteIndex(root, "authelia"); err != nil {
+ if err = genCLIDocWriteIndex(outputPath, "authelia"); err != nil {
return err
}
- if err = genCLIDoc(cmdscripts.NewRootCmd(), filepath.Join(root, "authelia-scripts")); err != nil {
+ if err = genCLIDoc(cmdscripts.NewRootCmd(), filepath.Join(outputPath, "authelia-scripts")); err != nil {
return err
}
- if err = genCLIDocWriteIndex(root, "authelia-scripts"); err != nil {
+ if err = genCLIDocWriteIndex(outputPath, "authelia-scripts"); err != nil {
return err
}
- if err = genCLIDoc(newRootCmd(), filepath.Join(root, "authelia-gen")); err != nil {
+ if err = genCLIDoc(newRootCmd(), filepath.Join(outputPath, cmdUseRoot)); err != nil {
return err
}
- if err = genCLIDocWriteIndex(root, "authelia-gen"); err != nil {
+ if err = genCLIDocWriteIndex(outputPath, cmdUseRoot); err != nil {
return err
}
@@ -69,6 +67,16 @@ func docsCLIRunE(cmd *cobra.Command, args []string) (err error) {
}
func genCLIDoc(cmd *cobra.Command, path string) (err error) {
+ if _, err = os.Stat(path); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ if err == nil || !os.IsNotExist(err) {
+ if err = os.RemoveAll(path); err != nil {
+ return fmt.Errorf("failed to remove docs: %w", err)
+ }
+ }
+
if err = os.Mkdir(path, 0755); err != nil {
if !os.IsExist(err) {
return err
diff --git a/cmd/authelia-gen/cmd_docs_data.go b/cmd/authelia-gen/cmd_docs_data.go
new file mode 100644
index 000000000..b0eddf21b
--- /dev/null
+++ b/cmd/authelia-gen/cmd_docs_data.go
@@ -0,0 +1,132 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "reflect"
+ "strings"
+
+ "github.com/spf13/cobra"
+
+ "github.com/authelia/authelia/v4/internal/configuration"
+ "github.com/authelia/authelia/v4/internal/configuration/schema"
+)
+
+func newDocsDataCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseDocsData,
+ Short: "Generate docs data files",
+ RunE: rootSubCommandsRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ cmd.AddCommand(newDocsDataMiscCmd(), newDocsDataKeysCmd())
+
+ return cmd
+}
+
+func newDocsDataMiscCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseDocsDataMisc,
+ Short: "Generate docs data file misc.json",
+ RunE: docsDataMiscRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func docsDataMiscRunE(cmd *cobra.Command, args []string) (err error) {
+ data := DocsDataMisc{
+ CSP: TemplateCSP{
+ PlaceholderNONCE: codeCSPNonce,
+ TemplateDefault: buildCSP(codeCSPProductionDefaultSrc, codeCSPValuesCommon, codeCSPValuesProduction),
+ TemplateDevelopment: buildCSP(codeCSPDevelopmentDefaultSrc, codeCSPValuesCommon, codeCSPValuesDevelopment),
+ },
+ }
+
+ data.CSP.TemplateDefault = strings.ReplaceAll(data.CSP.TemplateDefault, "%s", codeCSPNonce)
+ data.CSP.TemplateDevelopment = strings.ReplaceAll(data.CSP.TemplateDevelopment, "%s", codeCSPNonce)
+
+ var (
+ outputPath string
+ dataJSON []byte
+ )
+
+ if outputPath, err = getPFlagPath(cmd.Flags(), cmdFlagRoot, cmdFlagDocs, cmdFlagDocsData, cmdFlagDocsDataMisc); err != nil {
+ return err
+ }
+
+ if dataJSON, err = json.Marshal(data); err != nil {
+ return err
+ }
+
+ if err = os.WriteFile(outputPath, dataJSON, 0600); err != nil {
+ return fmt.Errorf("failed to write file '%s': %w", outputPath, err)
+ }
+
+ return nil
+}
+
+func newDocsDataKeysCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseKeys,
+ Short: "Generate the docs data file for configuration keys",
+ RunE: docsKeysRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func docsKeysRunE(cmd *cobra.Command, args []string) (err error) {
+ //nolint:prealloc
+ var (
+ data []ConfigurationKey
+ )
+
+ keys := readTags("", reflect.TypeOf(schema.Configuration{}))
+
+ for _, key := range keys {
+ if strings.Contains(key, "[]") {
+ continue
+ }
+
+ ck := ConfigurationKey{
+ Path: key,
+ Secret: configuration.IsSecretKey(key),
+ }
+
+ switch {
+ case ck.Secret:
+ ck.Env = configuration.ToEnvironmentSecretKey(key, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)
+ default:
+ ck.Env = configuration.ToEnvironmentKey(key, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)
+ }
+
+ data = append(data, ck)
+ }
+
+ var (
+ dataJSON []byte
+ outputPath string
+ )
+
+ if outputPath, err = getPFlagPath(cmd.Flags(), cmdFlagRoot, cmdFlagDocs, cmdFlagDocsData, cmdFlagDocsDataKeys); err != nil {
+ return err
+ }
+
+ if dataJSON, err = json.Marshal(data); err != nil {
+ return err
+ }
+
+ if err = os.WriteFile(outputPath, dataJSON, 0600); err != nil {
+ return fmt.Errorf("failed to write file '%s': %w", outputPath, err)
+ }
+
+ return nil
+}
diff --git a/cmd/authelia-gen/cmd_docs_date.go b/cmd/authelia-gen/cmd_docs_date.go
index 7d9f83f85..9eea8ccad 100644
--- a/cmd/authelia-gen/cmd_docs_date.go
+++ b/cmd/authelia-gen/cmd_docs_date.go
@@ -17,14 +17,13 @@ import (
func newDocsDateCmd() *cobra.Command {
cmd := &cobra.Command{
- Use: "date",
+ Use: cmdUseDocsDate,
Short: "Generate doc dates",
RunE: docsDateRunE,
DisableAutoGenTag: true,
}
- cmd.Flags().StringP("directory", "d", "./docs/content", "The directory to modify")
cmd.Flags().String("commit-until", "HEAD", "The commit to check the logs until")
cmd.Flags().String("commit-since", "", "The commit to check the logs since")
@@ -33,14 +32,14 @@ func newDocsDateCmd() *cobra.Command {
func docsDateRunE(cmd *cobra.Command, args []string) (err error) {
var (
- dir, cwd, commitUtil, commitSince, commitFilter string
+ pathDocsContent, cwd, commitUtil, commitSince, commitFilter string
)
- if dir, err = cmd.Flags().GetString("directory"); err != nil {
+ if pathDocsContent, err = getPFlagPath(cmd.Flags(), cmdFlagRoot, cmdFlagDocs, cmdFlagDocsContent); err != nil {
return err
}
- if cwd, err = cmd.Flags().GetString("cwd"); err != nil {
+ if cwd, err = cmd.Flags().GetString(cmdFlagCwd); err != nil {
return err
}
@@ -56,7 +55,7 @@ func docsDateRunE(cmd *cobra.Command, args []string) (err error) {
commitFilter = fmt.Sprintf("%s...%s", commitUtil, commitSince)
}
- return filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
+ return filepath.Walk(pathDocsContent, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
@@ -76,7 +75,7 @@ func docsDateRunE(cmd *cobra.Command, args []string) (err error) {
return nil
}
- frontmatter := map[string]interface{}{}
+ frontmatter := map[string]any{}
if err = yaml.Unmarshal(frontmatterBytes, frontmatter); err != nil {
return err
@@ -163,7 +162,7 @@ func replaceDates(path string, date time.Time, dateGit *time.Time) {
for scanner.Scan() {
if found < 2 && frontmatter < 2 {
switch {
- case scanner.Text() == frontmatterDelimiterLine:
+ case scanner.Text() == delimiterLineFrontMatter:
buf.Write(scanner.Bytes())
frontmatter++
case frontmatter != 0 && strings.HasPrefix(scanner.Text(), "date: "):
@@ -207,13 +206,13 @@ func getFrontmatter(path string) []byte {
for scanner.Scan() {
if start {
- if scanner.Text() == frontmatterDelimiterLine {
+ if scanner.Text() == delimiterLineFrontMatter {
break
}
buf.Write(scanner.Bytes())
buf.Write(newline)
- } else if scanner.Text() == frontmatterDelimiterLine {
+ } else if scanner.Text() == delimiterLineFrontMatter {
start = true
}
}
diff --git a/cmd/authelia-gen/cmd_github.go b/cmd/authelia-gen/cmd_github.go
new file mode 100644
index 000000000..99d80378a
--- /dev/null
+++ b/cmd/authelia-gen/cmd_github.go
@@ -0,0 +1,236 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+func newGitHubCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseGitHub,
+ Short: "Generate GitHub files",
+ RunE: rootSubCommandsRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ cmd.AddCommand(newGitHubIssueTemplatesCmd())
+
+ return cmd
+}
+
+func newGitHubIssueTemplatesCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseGitHubIssueTemplates,
+ Short: "Generate GitHub issue templates",
+ RunE: rootSubCommandsRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ cmd.AddCommand(newGitHubIssueTemplatesBugReportCmd(), newGitHubIssueTemplatesFeatureCmd())
+
+ return cmd
+}
+
+func newGitHubIssueTemplatesFeatureCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseGitHubIssueTemplatesFR,
+ Short: "Generate GitHub feature request issue template",
+ RunE: cmdGitHubIssueTemplatesFeatureRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func newGitHubIssueTemplatesBugReportCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseGitHubIssueTemplatesBR,
+ Short: "Generate GitHub bug report issue template",
+ RunE: cmdGitHubIssueTemplatesBugReportRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func cmdGitHubIssueTemplatesFeatureRunE(cmd *cobra.Command, args []string) (err error) {
+ var (
+ cwd, file, root string
+ tags, tagsFuture []string
+ latestMajor, latestMinor, latestPatch, versions int
+ )
+
+ if cwd, err = cmd.Flags().GetString(cmdFlagCwd); err != nil {
+ return err
+ }
+
+ if root, err = cmd.Flags().GetString(cmdFlagRoot); err != nil {
+ return err
+ }
+
+ if file, err = cmd.Flags().GetString(cmdFlagFeatureRequest); err != nil {
+ return err
+ }
+
+ if versions, err = cmd.Flags().GetInt(cmdFlagVersions); err != nil {
+ return err
+ }
+
+ if tags, err = getGitTags(cwd); err != nil {
+ return err
+ }
+
+ latest := tags[0]
+
+ if _, err = fmt.Sscanf(latest, "v%d.%d.%d", &latestMajor, &latestMinor, &latestPatch); err != nil {
+ return fmt.Errorf("error occurred parsing version as semver: %w", err)
+ }
+
+ var (
+ minor int
+ )
+
+ for minor = latestMinor + 1; minor < latestMinor+versions; minor++ {
+ tagsFuture = append(tagsFuture, fmt.Sprintf("v%d.%d.0", latestMajor, minor))
+ }
+
+ tagsFuture = append(tagsFuture, fmt.Sprintf("v%d.0.0", latestMajor+1))
+
+ var (
+ f *os.File
+ )
+
+ fullPath := filepath.Join(root, file)
+
+ if f, err = os.Create(fullPath); err != nil {
+ return fmt.Errorf("failed to create file '%s': %w", fullPath, err)
+ }
+
+ data := &tmplIssueTemplateData{
+ Labels: []string{labelTypeFeature.String(), labelStatusNeedsDesign.String(), labelPriorityNormal.String()},
+ Versions: tagsFuture,
+ }
+
+ if err = tmplIssueTemplateFeature.Execute(f, data); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func cmdGitHubIssueTemplatesBugReportRunE(cmd *cobra.Command, args []string) (err error) {
+ var (
+ cwd, file, dirRoot string
+ latestMinor, versions int
+
+ tags []string
+ )
+
+ if cwd, err = cmd.Flags().GetString(cmdFlagCwd); err != nil {
+ return err
+ }
+
+ if dirRoot, err = cmd.Flags().GetString(cmdFlagRoot); err != nil {
+ return err
+ }
+
+ if file, err = cmd.Flags().GetString(cmdFlagBugReport); err != nil {
+ return err
+ }
+
+ if versions, err = cmd.Flags().GetInt(cmdFlagVersions); err != nil {
+ return err
+ }
+
+ if tags, err = getGitTags(cwd); err != nil {
+ return err
+ }
+
+ latest := tags[0]
+
+ latestParts := strings.Split(latest, ".")
+
+ if len(latestParts) < 2 {
+ return fmt.Errorf("error extracting latest minor version from tag: %s does not appear to be a semver", latest)
+ }
+
+ if latestMinor, err = strconv.Atoi(latestParts[1]); err != nil {
+ return fmt.Errorf("error extracting latest minor version from tag: %w", err)
+ }
+
+ //nolint:prealloc
+ var (
+ tagsRecent []string
+ parts []string
+ minor int
+ )
+
+ for _, tag := range tags {
+ if parts = strings.Split(tag, "."); len(parts) < 2 {
+ return fmt.Errorf("error extracting minor version from tag: %s does not appear to be a semver", tag)
+ }
+
+ if minor, err = strconv.Atoi(parts[1]); err != nil {
+ return fmt.Errorf("error extracting minor version from tag: %w", err)
+ }
+
+ if minor < latestMinor-versions {
+ break
+ }
+
+ tagsRecent = append(tagsRecent, tag)
+ }
+
+ var (
+ f *os.File
+ )
+
+ fullPath := filepath.Join(dirRoot, file)
+
+ if f, err = os.Create(fullPath); err != nil {
+ return fmt.Errorf("failed to create file '%s': %w", fullPath, err)
+ }
+
+ data := &tmplIssueTemplateData{
+ Labels: []string{labelTypeBugUnconfirmed.String(), labelStatusNeedsTriage.String(), labelPriorityNormal.String()},
+ Versions: tagsRecent,
+ Proxies: []string{"Caddy", "Traefik", "Envoy", "Istio", "NGINX", "SWAG", "NGINX Proxy Manager", "HAProxy"},
+ }
+
+ if err = tmplGitHubIssueTemplateBug.Execute(f, data); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func getGitTags(cwd string) (tags []string, err error) {
+ var (
+ args []string
+ tagsOutput []byte
+ )
+
+ if len(cwd) != 0 {
+ args = append(args, "-C", cwd)
+ }
+
+ args = append(args, "tag", "--sort=-creatordate")
+
+ cmd := exec.Command("git", args...)
+
+ if tagsOutput, err = cmd.Output(); err != nil {
+ return nil, err
+ }
+
+ return strings.Split(string(tagsOutput), "\n"), nil
+}
diff --git a/cmd/authelia-gen/cmd_locales.go b/cmd/authelia-gen/cmd_locales.go
new file mode 100644
index 000000000..0fcc390c3
--- /dev/null
+++ b/cmd/authelia-gen/cmd_locales.go
@@ -0,0 +1,215 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "golang.org/x/text/language"
+ "golang.org/x/text/language/display"
+
+ "github.com/authelia/authelia/v4/internal/utils"
+)
+
+func newLocalesCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseLocales,
+ Short: "Generate locales files",
+ RunE: localesRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func localesRunE(cmd *cobra.Command, args []string) (err error) {
+ var (
+ root, pathLocales string
+ pathWebI18NIndex, pathDocsDataLanguages string
+ )
+
+ if root, err = cmd.Flags().GetString(cmdFlagRoot); err != nil {
+ return err
+ }
+
+ if pathLocales, err = cmd.Flags().GetString(cmdFlagDirLocales); err != nil {
+ return err
+ }
+
+ if pathWebI18NIndex, err = cmd.Flags().GetString(cmdFlagFileWebI18N); err != nil {
+ return err
+ }
+
+ if pathDocsDataLanguages, err = getPFlagPath(cmd.Flags(), cmdFlagRoot, cmdFlagDocs, cmdFlagDocsData, cmdFlagDocsDataLanguages); err != nil {
+ return err
+ }
+
+ data, err := getLanguages(filepath.Join(root, pathLocales))
+ if err != nil {
+ return err
+ }
+
+ fullPathWebI18NIndex := filepath.Join(root, pathWebI18NIndex)
+
+ var (
+ f *os.File
+ dataJSON []byte
+ )
+
+ if f, err = os.Create(fullPathWebI18NIndex); err != nil {
+ return fmt.Errorf("failed to create file '%s': %w", fullPathWebI18NIndex, err)
+ }
+
+ if err = tmplWebI18NIndex.Execute(f, data); err != nil {
+ return err
+ }
+
+ if dataJSON, err = json.Marshal(data); err != nil {
+ return err
+ }
+
+ fullPathDocsDataLanguages := filepath.Join(root, pathDocsDataLanguages)
+
+ if err = os.WriteFile(fullPathDocsDataLanguages, dataJSON, 0600); err != nil {
+ return fmt.Errorf("failed to write file '%s': %w", fullPathDocsDataLanguages, err)
+ }
+
+ return nil
+}
+
+//nolint:gocyclo
+func getLanguages(dir string) (languages *Languages, err error) {
+ //nolint:prealloc
+ var locales []string
+
+ languages = &Languages{
+ Defaults: DefaultsLanguages{
+ Namespace: localeNamespaceDefault,
+ },
+ }
+
+ var defaultTag language.Tag
+
+ if defaultTag, err = language.Parse(localeDefault); err != nil {
+ return nil, fmt.Errorf("failed to parse default language: %w", err)
+ }
+
+ languages.Defaults.Language = Language{
+ Display: display.English.Tags().Name(defaultTag),
+ Locale: localeDefault,
+ }
+
+ if err = filepath.Walk(dir, func(path string, info fs.FileInfo, errWalk error) (err error) {
+ if errWalk != nil {
+ return errWalk
+ }
+
+ nameLower := strings.ToLower(info.Name())
+ ext := filepath.Ext(nameLower)
+ ns := strings.Replace(nameLower, ext, "", 1)
+
+ if ext != ".json" {
+ return nil
+ }
+
+ if !utils.IsStringInSlice(ns, languages.Namespaces) {
+ languages.Namespaces = append(languages.Namespaces, ns)
+ }
+
+ fdir, _ := filepath.Split(path)
+
+ locale := filepath.Base(fdir)
+
+ if utils.IsStringInSlice(locale, locales) {
+ for i, l := range languages.Languages {
+ if l.Locale == locale {
+ if utils.IsStringInSlice(ns, languages.Languages[i].Namespaces) {
+ break
+ }
+
+ languages.Languages[i].Namespaces = append(languages.Languages[i].Namespaces, ns)
+ break
+ }
+ }
+
+ return nil
+ }
+
+ var localeReal string
+
+ parts := strings.SplitN(locale, "-", 2)
+ if len(parts) == 2 && strings.EqualFold(parts[0], parts[1]) {
+ localeReal = parts[0]
+ } else {
+ localeReal = locale
+ }
+
+ var tag language.Tag
+
+ if tag, err = language.Parse(localeReal); err != nil {
+ return fmt.Errorf("failed to parse language '%s': %w", localeReal, err)
+ }
+
+ l := Language{
+ Display: display.English.Tags().Name(tag),
+ Locale: localeReal,
+ Namespaces: []string{ns},
+ Fallbacks: []string{languages.Defaults.Language.Locale},
+ Tag: tag,
+ }
+
+ languages.Languages = append(languages.Languages, l)
+
+ locales = append(locales, l.Locale)
+
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+
+ var langs []Language //nolint:prealloc
+
+ for i, lang := range languages.Languages {
+ p := lang.Tag.Parent()
+
+ if p.String() == "und" || strings.Contains(p.String(), "-") {
+ continue
+ }
+
+ if utils.IsStringInSlice(p.String(), locales) {
+ continue
+ }
+
+ if p.String() != lang.Locale {
+ lang.Fallbacks = append([]string{p.String()}, lang.Fallbacks...)
+ }
+
+ languages.Languages[i] = lang
+
+ l := Language{
+ Display: display.English.Tags().Name(p),
+ Locale: p.String(),
+ Namespaces: lang.Namespaces,
+ Fallbacks: []string{languages.Defaults.Language.Locale},
+ Tag: p,
+ }
+
+ langs = append(langs, l)
+
+ locales = append(locales, l.Locale)
+ }
+
+ languages.Languages = append(languages.Languages, langs...)
+
+ sort.Slice(languages.Languages, func(i, j int) bool {
+ return languages.Languages[i].Locale == localeDefault || languages.Languages[i].Locale < languages.Languages[j].Locale
+ })
+
+ return languages, nil
+}
diff --git a/cmd/authelia-gen/cmd_root.go b/cmd/authelia-gen/cmd_root.go
new file mode 100644
index 000000000..d0809957c
--- /dev/null
+++ b/cmd/authelia-gen/cmd_root.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/spf13/cobra"
+
+ "github.com/authelia/authelia/v4/internal/utils"
+)
+
+var rootCmd *cobra.Command
+
+func init() {
+ rootCmd = newRootCmd()
+}
+
+func newRootCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: cmdUseRoot,
+ Short: "Authelia's generator tooling",
+ RunE: rootSubCommandsRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ cmd.PersistentFlags().StringP(cmdFlagCwd, "C", "", "Sets the CWD for git commands")
+ cmd.PersistentFlags().StringP(cmdFlagRoot, "d", dirCurrent, "The repository root")
+ cmd.PersistentFlags().StringSliceP(cmdFlagExclude, "X", nil, "Sets the names of excluded generators")
+ cmd.PersistentFlags().String(cmdFlagFeatureRequest, fileGitHubIssueTemplateFR, "Sets the path of the feature request issue template file")
+ cmd.PersistentFlags().String(cmdFlagBugReport, fileGitHubIssueTemplateBR, "Sets the path of the bug report issue template file")
+ cmd.PersistentFlags().Int(cmdFlagVersions, 5, "the maximum number of minor versions to list in output templates")
+ cmd.PersistentFlags().String(cmdFlagDirLocales, dirLocales, "The locales directory in relation to the root")
+ cmd.PersistentFlags().String(cmdFlagFileWebI18N, fileWebI18NIndex, "The i18n typescript configuration file in relation to the root")
+ cmd.PersistentFlags().String(cmdFlagDocsDataLanguages, fileDocsDataLanguages, "The languages docs data file in relation to the docs data folder")
+ cmd.PersistentFlags().String(cmdFlagDocsDataMisc, fileDocsDataMisc, "The misc docs data file in relation to the docs data folder")
+ cmd.PersistentFlags().String(cmdFlagDocsCLIReference, dirDocsCLIReference, "The directory to store the markdown in")
+ cmd.PersistentFlags().String(cmdFlagDocs, dirDocs, "The directory with the docs")
+ cmd.PersistentFlags().String(cmdFlagDocsContent, dirDocsContent, "The directory with the docs content")
+ cmd.PersistentFlags().String(cmdFlagDocsData, dirDocsData, "The directory with the docs data")
+ cmd.PersistentFlags().String(cmdFlagFileConfigKeys, fileCodeConfigKeys, "Sets the path of the keys file")
+ cmd.PersistentFlags().String(cmdFlagDocsDataKeys, fileDocsDataConfigKeys, "Sets the path of the docs keys file")
+ cmd.PersistentFlags().String(cmdFlagPackageConfigKeys, pkgConfigSchema, "Sets the package name of the keys file")
+ cmd.PersistentFlags().String(cmdFlagFileScriptsGen, fileScriptsGen, "Sets the path of the authelia-scripts gen file")
+ cmd.PersistentFlags().String(cmdFlagFileServerGenerated, fileServerGenerated, "Sets the path of the server generated file")
+ cmd.PersistentFlags().String(cmdFlagPackageScriptsGen, pkgScriptsGen, "Sets the package name of the authelia-scripts gen file")
+ cmd.PersistentFlags().String(cmdFlagFileConfigCommitLint, fileCICommitLintConfig, "The commit lint javascript configuration file in relation to the root")
+ cmd.PersistentFlags().String(cmdFlagFileDocsCommitMsgGuidelines, fileDocsCommitMessageGuidelines, "The commit message guidelines documentation file in relation to the root")
+
+ cmd.AddCommand(newCodeCmd(), newDocsCmd(), newGitHubCmd(), newLocalesCmd(), newCommitLintCmd())
+
+ return cmd
+}
+
+func rootSubCommandsRunE(cmd *cobra.Command, args []string) (err error) {
+ var exclude []string
+
+ if exclude, err = cmd.Flags().GetStringSlice(cmdFlagExclude); err != nil {
+ return err
+ }
+
+ subCmds := cmd.Commands()
+
+ switch cmd.Use {
+ case cmdUseRoot:
+ sort.Slice(subCmds, func(i, j int) bool {
+ switch subCmds[j].Use {
+ case cmdUseDocs:
+ // Ensure `docs` subCmd is last.
+ return true
+ default:
+ return subCmds[i].Use < subCmds[j].Use
+ }
+ })
+ case cmdUseDocs:
+ sort.Slice(subCmds, func(i, j int) bool {
+ switch subCmds[j].Use {
+ case cmdUseDocsDate:
+ // Ensure `date` subCmd is last.
+ return true
+ default:
+ return subCmds[i].Use < subCmds[j].Use
+ }
+ })
+ default:
+ sort.Slice(subCmds, func(i, j int) bool {
+ return subCmds[i].Use < subCmds[j].Use
+ })
+ }
+
+ for _, subCmd := range subCmds {
+ if subCmd.Use == cmdUseCompletion || strings.HasPrefix(subCmd.Use, "help ") || utils.IsStringSliceContainsAny([]string{resolveCmdName(subCmd), subCmd.Use}, exclude) {
+ continue
+ }
+
+ rootCmd.SetArgs(rootCmdGetArgs(subCmd, args))
+
+ if err = rootCmd.Execute(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func resolveCmdName(cmd *cobra.Command) string {
+ parent := cmd.Parent()
+
+ if parent != nil && parent.Use != cmd.Use && parent.Use != cmdUseRoot {
+ return resolveCmdName(parent) + "." + cmd.Use
+ }
+
+ return cmd.Use
+}
+
+func rootCmdGetArgs(cmd *cobra.Command, args []string) []string {
+ for {
+ if cmd == rootCmd {
+ break
+ }
+
+ args = append([]string{cmd.Use}, args...)
+
+ cmd = cmd.Parent()
+ }
+
+ return args
+}
diff --git a/cmd/authelia-gen/const.go b/cmd/authelia-gen/const.go
index 71f3ada18..f68364e9e 100644
--- a/cmd/authelia-gen/const.go
+++ b/cmd/authelia-gen/const.go
@@ -1,7 +1,113 @@
package main
const (
- dateFmtRFC2822 = "Mon, _2 Jan 2006 15:04:05 -0700"
- dateFmtYAML = "2006-01-02T15:04:05-07:00"
- frontmatterDelimiterLine = "---"
+ dirCurrent = "./"
+ dirLocales = "internal/server/locales"
+
+ subPathCmd = "cmd"
+ subPathInternal = "internal"
+
+ fileCICommitLintConfig = "web/.commitlintrc.js"
+ fileWebI18NIndex = "web/src/i18n/index.ts"
+
+ fileDocsCommitMessageGuidelines = "docs/content/en/contributing/guidelines/commit-message.md"
+
+ fileCodeConfigKeys = "internal/configuration/schema/keys.go"
+ fileServerGenerated = "internal/server/gen.go"
+ fileScriptsGen = "cmd/authelia-scripts/cmd/gen.go"
+
+ dirDocs = "docs"
+ dirDocsContent = "content"
+ dirDocsData = "data"
+ dirDocsCLIReference = "en/reference/cli"
+
+ fileDocsDataLanguages = "languages.json"
+ fileDocsDataMisc = "misc.json"
+ fileDocsDataConfigKeys = "configkeys.json"
+
+ fileGitHubIssueTemplateFR = ".github/ISSUE_TEMPLATE/feature-request.yml"
+ fileGitHubIssueTemplateBR = ".github/ISSUE_TEMPLATE/bug-report.yml"
+)
+
+const (
+ dateFmtRFC2822 = "Mon, _2 Jan 2006 15:04:05 -0700"
+ dateFmtYAML = "2006-01-02T15:04:05-07:00"
+)
+
+const (
+ delimiterLineFrontMatter = "---"
+
+ localeDefault = "en"
+ localeNamespaceDefault = "portal"
+)
+
+const (
+ pkgConfigSchema = "schema"
+ pkgScriptsGen = "cmd"
+)
+
+const (
+ cmdUseRoot = "authelia-gen"
+ cmdUseCompletion = "completion"
+ cmdUseDocs = "docs"
+ cmdUseDocsDate = "date"
+ cmdUseDocsCLI = "cli"
+ cmdUseDocsData = "data"
+ cmdUseDocsDataMisc = "misc"
+ cmdUseGitHub = "github"
+ cmdUseGitHubIssueTemplates = "issue-templates"
+ cmdUseGitHubIssueTemplatesFR = "feature-request"
+ cmdUseGitHubIssueTemplatesBR = "bug-report"
+ cmdUseLocales = "locales"
+ cmdUseCommitLint = "commit-lint"
+ cmdUseCode = "code"
+ cmdUseCodeScripts = "scripts"
+ cmdUseKeys = "keys"
+ cmdUseServer = "server"
+)
+
+const (
+ cmdFlagRoot = "dir.root"
+ cmdFlagExclude = "exclude"
+ cmdFlagVersions = "versions"
+ cmdFlagDirLocales = "dir.locales"
+ cmdFlagDocsCLIReference = "dir.docs.cli-reference"
+ cmdFlagDocsContent = "dir.docs.content"
+ cmdFlagDocsData = "dir.docs.data"
+ cmdFlagDocs = "dir.docs"
+ cmdFlagDocsDataLanguages = "file.docs.data.languages"
+ cmdFlagDocsDataMisc = "file.docs.data.misc"
+ cmdFlagDocsDataKeys = "file.docs.data.keys"
+ cmdFlagCwd = "cwd"
+ cmdFlagFileConfigKeys = "file.configuration-keys"
+ cmdFlagFileScriptsGen = "file.scripts.gen"
+ cmdFlagFileServerGenerated = "file.server.generated"
+ cmdFlagFileConfigCommitLint = "file.commit-lint-config"
+ cmdFlagFileDocsCommitMsgGuidelines = "file.docs-commit-msg-guidelines"
+ cmdFlagFileWebI18N = "file.web-i18n"
+ cmdFlagFeatureRequest = "file.feature-request"
+ cmdFlagBugReport = "file.bug-report"
+ cmdFlagPackageConfigKeys = "package.configuration.keys"
+ cmdFlagPackageScriptsGen = "package.scripts.gen"
+)
+
+const (
+ codeCSPProductionDefaultSrc = "'self'"
+ codeCSPDevelopmentDefaultSrc = "'self' 'unsafe-eval'"
+ codeCSPNonce = "${NONCE}"
+)
+
+var (
+ codeCSPValuesCommon = []CSPValue{
+ {Name: "default-src", Value: ""},
+ {Name: "frame-src", Value: "'none'"},
+ {Name: "object-src", Value: "'none'"},
+ {Name: "style-src", Value: "'self' 'nonce-%s'"},
+ {Name: "frame-ancestors", Value: "'none'"},
+ {Name: "base-uri", Value: "'self'"},
+ }
+
+ codeCSPValuesProduction = []CSPValue{}
+
+ codeCSPValuesDevelopment = []CSPValue{}
)
diff --git a/cmd/authelia-gen/helpers.go b/cmd/authelia-gen/helpers.go
new file mode 100644
index 000000000..09ac84e8f
--- /dev/null
+++ b/cmd/authelia-gen/helpers.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/spf13/pflag"
+)
+
+func getPFlagPath(flags *pflag.FlagSet, flagNames ...string) (fullPath string, err error) {
+ if len(flagNames) == 0 {
+ return "", fmt.Errorf("no flag names")
+ }
+
+ var p string
+
+ for i, flagName := range flagNames {
+ if p, err = flags.GetString(flagName); err != nil {
+ return "", fmt.Errorf("failed to lookup flag '%s': %w", flagName, err)
+ }
+
+ if i == 0 {
+ fullPath = p
+ } else {
+ fullPath = filepath.Join(fullPath, p)
+ }
+ }
+
+ return fullPath, nil
+}
+
+func buildCSP(defaultSrc string, ruleSets ...[]CSPValue) string {
+ var rules []string
+
+ for _, ruleSet := range ruleSets {
+ for _, rule := range ruleSet {
+ switch rule.Name {
+ case "default-src":
+ rules = append(rules, fmt.Sprintf("%s %s", rule.Name, defaultSrc))
+ default:
+ rules = append(rules, fmt.Sprintf("%s %s", rule.Name, rule.Value))
+ }
+ }
+ }
+
+ return strings.Join(rules, "; ")
+}
diff --git a/cmd/authelia-gen/main.go b/cmd/authelia-gen/main.go
index 15a8d4586..ffbdca170 100644
--- a/cmd/authelia-gen/main.go
+++ b/cmd/authelia-gen/main.go
@@ -1,29 +1,7 @@
package main
-import (
- "embed"
-
- "github.com/spf13/cobra"
-)
-
-//go:embed templates/*
-var templatesFS embed.FS
-
func main() {
- if err := newRootCmd().Execute(); err != nil {
+ if err := rootCmd.Execute(); err != nil {
panic(err)
}
}
-
-func newRootCmd() *cobra.Command {
- cmd := &cobra.Command{
- Use: "authelia-gen",
- Short: "Authelia's generator tooling",
-
- DisableAutoGenTag: true,
- }
-
- cmd.AddCommand(newAllCmd(), newCodeCmd(), newDocsCmd())
-
- return cmd
-}
diff --git a/cmd/authelia-gen/templates.go b/cmd/authelia-gen/templates.go
new file mode 100644
index 000000000..794add4b3
--- /dev/null
+++ b/cmd/authelia-gen/templates.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "embed"
+ "fmt"
+ "strings"
+ "text/template"
+)
+
+//go:embed templates/*
+var templatesFS embed.FS
+
+var (
+ funcMap = template.FuncMap{
+ "stringsContains": strings.Contains,
+ "join": strings.Join,
+ "joinX": fmJoinX,
+ }
+
+ tmplCodeConfigurationSchemaKeys = template.Must(newTMPL("internal_configuration_schema_keys.go"))
+ tmplGitHubIssueTemplateBug = template.Must(newTMPL("github_issue_template_bug_report.yml"))
+ tmplIssueTemplateFeature = template.Must(newTMPL("github_issue_template_feature.yml"))
+ tmplWebI18NIndex = template.Must(newTMPL("web_i18n_index.ts"))
+ tmplDotCommitLintRC = template.Must(newTMPL("dot_commitlintrc.js"))
+ tmplDocsCommitMessageGuidelines = template.Must(newTMPL("docs-contributing-development-commitmsg.md"))
+ tmplScriptsGen = template.Must(newTMPL("cmd-authelia-scripts-gen.go"))
+ tmplServer = template.Must(newTMPL("server_gen.go"))
+)
+
+func fmJoinX(elems []string, sep string, n int, p string) string {
+ buf := strings.Builder{}
+
+ c := 0
+ e := len(elems) - 1
+
+ for i := 0; i <= e; i++ {
+ if c+len(elems[i])+1 > n {
+ c = 0
+
+ buf.WriteString(p)
+ }
+
+ c += len(elems[i]) + 1
+
+ buf.WriteString(elems[i])
+
+ if i < e {
+ buf.WriteString(sep)
+ }
+ }
+
+ return buf.String()
+}
+
+func newTMPL(name string) (tmpl *template.Template, err error) {
+ return template.New(name).Funcs(funcMap).Parse(mustLoadTmplFS(name))
+}
+
+func mustLoadTmplFS(tmpl string) string {
+ var (
+ content []byte
+ err error
+ )
+
+ if content, err = templatesFS.ReadFile(fmt.Sprintf("templates/%s.tmpl", tmpl)); err != nil {
+ panic(err)
+ }
+
+ return string(content)
+}
diff --git a/cmd/authelia-gen/templates/cmd-authelia-scripts-gen.go.tmpl b/cmd/authelia-gen/templates/cmd-authelia-scripts-gen.go.tmpl
new file mode 100644
index 000000000..5955d5f7d
--- /dev/null
+++ b/cmd/authelia-gen/templates/cmd-authelia-scripts-gen.go.tmpl
@@ -0,0 +1,11 @@
+// Code generated by go generate. DO NOT EDIT.
+//
+// Run the following command to generate this file:
+// go run ./cmd/authelia-gen code scripts
+//
+
+package {{ .Package }}
+
+const (
+ versionSwaggerUI = "{{ .VersionSwaggerUI }}"
+)
diff --git a/cmd/authelia-gen/templates/docs-contributing-development-commitmsg.md.tmpl b/cmd/authelia-gen/templates/docs-contributing-development-commitmsg.md.tmpl
new file mode 100644
index 000000000..28f51882c
--- /dev/null
+++ b/cmd/authelia-gen/templates/docs-contributing-development-commitmsg.md.tmpl
@@ -0,0 +1,139 @@
+---
+title: "Commit Message"
+description: "Authelia Development Commit Message Guidelines"
+lead: "This section covers the git commit message guidelines we use for development."
+date: 2021-01-30T19:29:07+11:00
+draft: false
+images: []
+menu:
+ contributing:
+ parent: "guidelines"
+weight: 320
+toc: true
+aliases:
+ - /docs/contributing/commitmsg-guidelines.html
+ - /contributing/development/guidelines-commit-message/
+---
+
+The reasons for these conventions are as follows:
+
+* simple navigation though git history
+* easier to read git history
+
+## Commit Message Format
+
+Each commit message consists of a __header__, a __body__, and a __footer__.
+
+```bash
+
+
+
+
+
+```
+
+The `header` is mandatory and must conform to the [Commit Message Header](#commit-message-header) format. The header
+cannot be longer than 72 characters.
+
+The `body` is mandatory for all commits except for those of type "docs". When the body is present it must be at least 20
+characters long and must conform to the [Commit Message Body](#commit-message-body) format.
+
+The `footer` is optional. The [Commit Message Footer](#commit-message-footer) format describes what the footer is used
+for, and the structure it must have.
+
+### Commit Message Header
+
+```text
+():
+ │ │ │
+ │ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
+ │ │
+ │ └─⫸ Commit Scope: {{ joinX .Scopes.All "|" 70 "\n │ " }}
+ │
+ └─⫸ Commit Type: {{ join .Types.List "|" }}
+```
+
+The `` and `` fields are mandatory, the `()` field is optional.
+
+#### Allowed type values:
+{{ range .Types.Details }}
+* __{{ .Name }}__ {{ .Description }}
+{{- if .Scopes }}
+ (example scopes: {{ join .Scopes ", " }})
+{{- end }}
+{{- end }}
+
+#### Allowed scope values:
+
+The scope should be the name of the package affected (as perceived by the person reading the changelog generated from
+commit messages).
+{{ range .Scopes.Packages }}
+* {{ . }}
+{{- end }}
+
+There are currently a few exceptions to the "use package name" rule:
+{{ range .Scopes.Extra }}
+* `{{ .Name }}`: {{ .Description }}
+{{- end }}
+* none/empty string: useful for `test`, `refactor` and changes that are done across multiple packages
+ (e.g. `test: add missing unit tests`) and for docs changes that are not related to a specific package
+ (e.g. `docs: fix typo in tutorial`).
+
+#### Summary
+
+Use the summary field to provide a succinct description of the change:
+
+* use the imperative, present tense: "change" not "changed" nor "changes"
+* don't capitalize the first letter
+* no dot (.) at the end
+
+### Commit Message Body
+
+Just as in the summary, use the imperative, present tense: "fix" not "fixed" nor "fixes".
+
+Explain the motivation for the change in the commit message body. This commit message should explain *why* you are
+making the change. You can include a comparison of the previous behavior with the new behavior in order to illustrate
+the impact of the change.
+
+### Commit Message Footer
+
+The footer can contain information about breaking changes and is also the place to reference GitHub issues and other PRs
+that this commit closes or is related to.
+
+```text
+BREAKING CHANGE:
+
+
+
+
+Fixes #
+```
+
+Breaking Change section should start with the phrase "BREAKING CHANGE: " followed by a summary of the breaking change, a
+blank line, and a detailed description of the breaking change that also includes migration instructions.
+
+### Revert Commits
+
+If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit.
+
+The content of the commit message body should contain:
+
+* information about the SHA of the commit being reverted in the following format: `This reverts commit `,
+* a clear description of the reason for reverting the commit message.
+
+## Commit Message Examples
+
+```bash
+fix(logging): disabled colored logging outputs when file is specified
+
+In some scenarios if a user has a log_file_path specified and a TTY seems to be detected this causes terminal coloring outputs to be written to the file.
+This in turn will cause issues when attempting to utilise the log with the provided fail2ban regexes.
+
+We now override any TTY detection/logging treatments and disable coloring/removal of the timestamp when a user is utilising the text based logger to a file.
+
+Fixes #1480.
+```
+
+This document is based on [AngularJS Git Commit Message Format].
+
+[AngularJS Git Commit Message Format]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
diff --git a/cmd/authelia-gen/templates/dot_commitlintrc.js.tmpl b/cmd/authelia-gen/templates/dot_commitlintrc.js.tmpl
new file mode 100644
index 000000000..e1bd35519
--- /dev/null
+++ b/cmd/authelia-gen/templates/dot_commitlintrc.js.tmpl
@@ -0,0 +1,25 @@
+module.exports = {
+ extends: ["@commitlint/config-conventional"],
+ rules: {
+ "body-max-line-length": [2, "always", "Infinity"],
+ "body-min-length": [2, "always", 20],
+ "header-case": [2, "always", "lower-case"],
+ "header-max-length": [2, "always", 72],
+ "type-enum": [
+ 2,
+ "always",
+ ["{{ join .Types.List "\", \"" }}"],
+ ],
+ "scope-enum": [
+ 2,
+ "always",
+ [
+ {{- range .Scopes.All }}
+ "{{ . }}",
+ {{- end }}
+ ],
+ ],
+ },
+ defaultIgnores: true,
+ helpUrl: "https://www.authelia.com/contributing/guidelines/commit-message/",
+};
diff --git a/cmd/authelia-gen/templates/github_issue_template_bug_report.yml.tmpl b/cmd/authelia-gen/templates/github_issue_template_bug_report.yml.tmpl
new file mode 100644
index 000000000..5b30bf63d
--- /dev/null
+++ b/cmd/authelia-gen/templates/github_issue_template_bug_report.yml.tmpl
@@ -0,0 +1,109 @@
+---
+name: Bug Report
+description: Report a bug
+labels:
+{{- range .Labels }}
+ - {{ . }}
+{{- end }}
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report. If you are unsure if this is actually a bug we generally recommend creating a [Question and Answer Discussion](https://github.com/authelia/authelia/discussions/new?category=q-a) first.
+
+ Please review the following requirements before submitting this issue type:
+
+ 1. Please ensure you do not report security vulnerabilities via this method. See our [Security Policy](https://www.authelia.com/security-policy).
+ 2. Please try to give as much information as possible for us to be able to reproduce the issue and provide a quick fix.
+ 3. Please ensure an issue does not already exist for this potential bug.
+ 4. Please only provide specific versions. Latest is not a version.
+ 5. Please read the [Troubleshooting Sanitization](https://www.authelia.com/r/sanitize) reference guide if you plan on removing or adjusting any values for the logs or configuration files.
+ 6. Please consider including a [HTTP Archive File](https://www.authelia.com/r/har) if you're having redirection issues.
+ - type: dropdown
+ id: version
+ attributes:
+ label: Version
+ description: What version(s) of Authelia can you reproduce this bug on?
+ multiple: true
+ options:
+ {{- range .Versions }}
+ - {{ . }}
+ {{- end }}
+ validations:
+ required: true
+ - type: dropdown
+ id: deployment
+ attributes:
+ label: Deployment Method
+ description: How are you deploying Authelia?
+ options:
+ - Docker
+ - Kubernetes
+ - Bare-metal
+ - Other
+ validations:
+ required: true
+ - type: dropdown
+ id: proxy
+ attributes:
+ label: Reverse Proxy
+ description: What reverse proxy are you using?
+ options:
+ {{- range .Proxies }}
+ - {{ . }}
+ {{- end }}
+ validations:
+ required: true
+ - type: input
+ id: proxy-version
+ attributes:
+ label: Reverse Proxy Version
+ description: What is the version of your reverse proxy?
+ placeholder: x.x.x
+ validations:
+ required: false
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: Describe the bug
+ validations:
+ required: true
+ - type: textarea
+ id: reproduction
+ attributes:
+ label: Reproduction
+ description: Describe how we can reproduce this issue
+ validations:
+ required: true
+ - type: textarea
+ id: expectations
+ attributes:
+ label: Expectations
+ description: Describe the desired or expected results
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Logs
+ description: Provide the logs (the template will automatically put this content in a code block)
+ render: shell
+ validations:
+ required: false
+ - type: textarea
+ id: configuration
+ attributes:
+ label: Configuration
+ description: Provide the Authelia configuration file (the template will automatically put this content in a code block)
+ render: yaml
+ validations:
+ required: false
+ - type: textarea
+ id: documentation
+ attributes:
+ label: Documentation
+ description: Provide any relevant specification or other documentation if applicable
+ validations:
+ required: false
+...
diff --git a/cmd/authelia-gen/templates/github_issue_template_feature.yml.tmpl b/cmd/authelia-gen/templates/github_issue_template_feature.yml.tmpl
new file mode 100644
index 000000000..0e3ca87c5
--- /dev/null
+++ b/cmd/authelia-gen/templates/github_issue_template_feature.yml.tmpl
@@ -0,0 +1,47 @@
+---
+name: Feature Request
+description: Submit a Feature Request
+labels:
+{{- range .Labels }}
+ - {{ . }}
+{{- end }}
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this feature request. A feature request is created as issue for the purpose of tracking the design and implementation of a feature.
+
+ Please review the following requirements before submitting this issue type:
+
+ 1. Ensure there are no other similar feature requests.
+ 2. Make sure you've checked the [Documentation](https://www.authelia.com) doesn't clearly document the features existence already.
+ 3. Consider creating an [Idea Discussion](https://github.com/authelia/authelia/discussions/new?category=ideas) which can be voted on instead if one doesn't exist.
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: Describe the feature
+ validations:
+ required: true
+ - type: textarea
+ id: use-case
+ attributes:
+ label: Use Case
+ description: Provide a use case
+ validations:
+ required: true
+ - type: textarea
+ id: details
+ attributes:
+ label: Details
+ description: Describe the feature in detail
+ validations:
+ required: false
+ - type: textarea
+ id: documentation
+ attributes:
+ label: Documentation
+ description: Provide any relevant specification or other documentation if applicable
+ validations:
+ required: false
+...
diff --git a/cmd/authelia-gen/templates/config_keys.go.tmpl b/cmd/authelia-gen/templates/internal_configuration_schema_keys.go.tmpl
similarity index 100%
rename from cmd/authelia-gen/templates/config_keys.go.tmpl
rename to cmd/authelia-gen/templates/internal_configuration_schema_keys.go.tmpl
diff --git a/cmd/authelia-gen/templates/server_gen.go.tmpl b/cmd/authelia-gen/templates/server_gen.go.tmpl
new file mode 100644
index 000000000..b1d96e86c
--- /dev/null
+++ b/cmd/authelia-gen/templates/server_gen.go.tmpl
@@ -0,0 +1,13 @@
+// Code generated by go generate. DO NOT EDIT.
+//
+// Run the following command to generate this file:
+// go run ./cmd/authelia-gen code server
+//
+
+package server
+
+const (
+ placeholderCSPNonce = "{{ .PlaceholderNONCE }}"
+ tmplCSPDefault = "{{ .TemplateDefault }}"
+ tmplCSPDevelopment = "{{ .TemplateDevelopment }}"
+)
diff --git a/cmd/authelia-gen/templates/web_i18n_index.ts.tmpl b/cmd/authelia-gen/templates/web_i18n_index.ts.tmpl
new file mode 100644
index 000000000..e7180fb36
--- /dev/null
+++ b/cmd/authelia-gen/templates/web_i18n_index.ts.tmpl
@@ -0,0 +1,57 @@
+// Code generated by go generate. DO NOT EDIT.
+//
+// Run the following command to generate this file:
+// go run ./cmd/authelia-gen locales
+//
+
+import i18n from "i18next";
+import LanguageDetector from "i18next-browser-languagedetector";
+import Backend from "i18next-http-backend";
+import { initReactI18next } from "react-i18next";
+
+import LocalStorageCustomDetector from "@i18n/detectors/localStorageCustom";
+import { getBasePath } from "@utils/BasePath";
+
+const basePath = getBasePath();
+
+const CustomLanguageDetector = new LanguageDetector();
+
+CustomLanguageDetector.addDetector(LocalStorageCustomDetector);
+
+i18n.use(Backend)
+ .use(CustomLanguageDetector)
+ .use(initReactI18next)
+ .init({
+ detection: {
+ order: ["querystring", "localStorageCustom", "navigator"],
+ lookupQuerystring: "lng",
+ lookupLocalStorage: "lng",
+ },
+ backend: {
+ loadPath: basePath + "/locales/{{"{{lng}}"}}/{{"{{ns}}"}}.json",
+ },
+ load: "all",
+ ns: [{{ range $i, $value := .Namespaces }}{{ if eq $i 0 }}"{{ $value }}"{{ else }}, "{{ $value }}"{{ end }}{{ end }}],
+ defaultNS: "{{ .Defaults.Namespace }}",
+ fallbackLng: {
+ default: ["{{ .Defaults.Language.Locale }}"],
+ {{- range .Languages }}
+ {{- if and (not (eq .Locale "en")) (not (eq (len .Fallbacks) 0)) }}
+ {{ if stringsContains .Locale "-" }}"{{ .Locale }}"{{ else }}{{ .Locale }}{{ end }}: [{{ range $i, $value := .Fallbacks }}{{ if eq $i 0 }}"{{ $value }}"{{ else }}, "{{ $value }}"{{ end }}{{ end }}],
+ {{- end }}
+ {{- end }}
+ },
+ supportedLngs: [
+ {{- range $i, $value := .Languages }}
+ "{{ $value.Locale }}",
+ {{- end }}
+ ],
+ lowerCaseLng: false,
+ nonExplicitSupportedLngs: true,
+ interpolation: {
+ escapeValue: false,
+ },
+ debug: false,
+ });
+
+export default i18n;
diff --git a/cmd/authelia-gen/types.go b/cmd/authelia-gen/types.go
new file mode 100644
index 000000000..99e13fd45
--- /dev/null
+++ b/cmd/authelia-gen/types.go
@@ -0,0 +1,145 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "golang.org/x/text/language"
+)
+
+type tmplIssueTemplateData struct {
+ Labels []string
+ Versions []string
+ Proxies []string
+}
+
+type tmplConfigurationKeysData struct {
+ Timestamp time.Time
+ Keys []string
+ Package string
+}
+
+type tmplScriptsGEnData struct {
+ Package string
+ VersionSwaggerUI string
+}
+
+// GitHubTagsJSON represents the JSON struct for the GitHub Tags API.
+type GitHubTagsJSON struct {
+ Name string `json:"name"`
+}
+
+// DocsDataMisc represents the docs misc data schema.
+type DocsDataMisc struct {
+ CSP TemplateCSP `json:"csp"`
+}
+
+// TemplateCSP represents the CSP template vars.
+type TemplateCSP struct {
+ TemplateDefault string `json:"default"`
+ TemplateDevelopment string `json:"development"`
+ PlaceholderNONCE string `json:"nonce"`
+}
+
+// ConfigurationKey is the docs json model for the Authelia configuration keys.
+type ConfigurationKey struct {
+ Path string `json:"path"`
+ Secret bool `json:"secret"`
+ Env string `json:"env"`
+}
+
+// Languages is the docs json model for the Authelia languages configuration.
+type Languages struct {
+ Defaults DefaultsLanguages `json:"defaults"`
+ Namespaces []string `json:"namespaces"`
+ Languages []Language `json:"languages"`
+}
+
+type DefaultsLanguages struct {
+ Language Language `json:"language"`
+ Namespace string `json:"namespace"`
+}
+
+// Language is the docs json model for a language.
+type Language struct {
+ Display string `json:"display"`
+ Locale string `json:"locale"`
+ Namespaces []string `json:"namespaces,omitempty"`
+ Fallbacks []string `json:"fallbacks,omitempty"`
+
+ Tag language.Tag `json:"-"`
+}
+
+const (
+ labelAreaPrefixPriority = "priority"
+ labelAreaPrefixType = "type"
+ labelAreaPrefixStatus = "status"
+)
+
+type labelPriority int
+
+//nolint:deadcode // Kept for future use.
+const (
+ labelPriorityCritical labelPriority = iota
+ labelPriorityHigh
+ labelPriorityMedium
+ labelPriorityNormal
+ labelPriorityLow
+)
+
+var labelPriorityDescriptions = [...]string{
+ "Critical",
+ "High",
+ "Medium",
+ "Normal",
+ "Low",
+}
+
+func (p labelPriority) String() string {
+ return fmt.Sprintf("%s/%d/%s", labelAreaPrefixPriority, p+1, strings.ToLower(labelPriorityDescriptions[p]))
+}
+
+func (p labelPriority) Description() string {
+ return labelPriorityDescriptions[p]
+}
+
+type labelStatus int
+
+const (
+ labelStatusNeedsDesign labelStatus = iota
+ labelStatusNeedsTriage
+)
+
+var labelStatusDescriptions = [...]string{
+ "needs-design",
+ "needs-triage",
+}
+
+func (s labelStatus) String() string {
+ return fmt.Sprintf("%s/%s", labelAreaPrefixStatus, labelStatusDescriptions[s])
+}
+
+type labelType int
+
+//nolint:deadcode // Kept for future use.
+const (
+ labelTypeFeature labelType = iota
+ labelTypeBugUnconfirmed
+ labelTypeBug
+)
+
+var labelTypeDescriptions = [...]string{
+ "feature",
+ "bug/unconfirmed",
+ "bug",
+}
+
+func (t labelType) String() string {
+ return fmt.Sprintf("%s/%s", labelAreaPrefixType, labelTypeDescriptions[t])
+}
+
+type CSPValue struct {
+ Name string
+ Value string
+}
diff --git a/cmd/authelia-scripts/cmd/bootstrap.go b/cmd/authelia-scripts/cmd/bootstrap.go
index befd60994..47a630818 100644
--- a/cmd/authelia-scripts/cmd/bootstrap.go
+++ b/cmd/authelia-scripts/cmd/bootstrap.go
@@ -168,8 +168,8 @@ func pnpmInstall() {
shell(fmt.Sprintf("cd %s/web && pnpm install", cwd))
}
-func bootstrapPrintln(args ...interface{}) {
- a := make([]interface{}, 0)
+func bootstrapPrintln(args ...any) {
+ a := make([]any, 0)
a = append(a, "[BOOTSTRAP]")
a = append(a, args...)
fmt.Println(a...)
diff --git a/cmd/authelia-scripts/cmd/build.go b/cmd/authelia-scripts/cmd/build.go
index 1949b2bdf..fa72b83f6 100644
--- a/cmd/authelia-scripts/cmd/build.go
+++ b/cmd/authelia-scripts/cmd/build.go
@@ -40,7 +40,7 @@ func cmdBuildRun(cobraCmd *cobra.Command, args []string) {
cmdCleanRun(cobraCmd, args)
- xflags, err := getXFlags(branch, os.Getenv("BUILDKITE_BUILD_NUMBER"), "")
+ buildMetaData, err := getBuild(branch, os.Getenv("BUILDKITE_BUILD_NUMBER"), "")
if err != nil {
log.Fatal(err)
}
@@ -62,11 +62,11 @@ func cmdBuildRun(cobraCmd *cobra.Command, args []string) {
if buildkite {
log.Info("Building Authelia Go binaries with gox...")
- buildAutheliaBinaryGOX(xflags)
+ buildAutheliaBinaryGOX(buildMetaData.XFlags())
} else {
log.Info("Building Authelia Go binary...")
- buildAutheliaBinaryGO(xflags)
+ buildAutheliaBinaryGO(buildMetaData.XFlags())
}
cleanAssets()
@@ -146,8 +146,7 @@ func buildFrontend(branch string) {
}
func buildSwagger() {
- swaggerVer := "4.13.0"
- cmd := utils.CommandWithStdout("bash", "-c", "wget -q https://github.com/swagger-api/swagger-ui/archive/v"+swaggerVer+".tar.gz -O ./v"+swaggerVer+".tar.gz")
+ cmd := utils.CommandWithStdout("bash", "-c", "wget -q https://github.com/swagger-api/swagger-ui/archive/v"+versionSwaggerUI+".tar.gz -O ./v"+versionSwaggerUI+".tar.gz")
err := cmd.Run()
if err != nil {
@@ -161,14 +160,14 @@ func buildSwagger() {
log.Fatal(err)
}
- cmd = utils.CommandWithStdout("tar", "-C", "internal/server/public_html/api", "--exclude=index.html", "--strip-components=2", "-xf", "v"+swaggerVer+".tar.gz", "swagger-ui-"+swaggerVer+"/dist")
+ cmd = utils.CommandWithStdout("tar", "-C", "internal/server/public_html/api", "--exclude=index.html", "--strip-components=2", "-xf", "v"+versionSwaggerUI+".tar.gz", "swagger-ui-"+versionSwaggerUI+"/dist")
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
- cmd = utils.CommandWithStdout("rm", "./v"+swaggerVer+".tar.gz")
+ cmd = utils.CommandWithStdout("rm", "./v"+versionSwaggerUI+".tar.gz")
err = cmd.Run()
if err != nil {
diff --git a/cmd/authelia-scripts/cmd/docker.go b/cmd/authelia-scripts/cmd/docker.go
index d37b12285..57456f4ca 100644
--- a/cmd/authelia-scripts/cmd/docker.go
+++ b/cmd/authelia-scripts/cmd/docker.go
@@ -13,15 +13,17 @@ import (
var container string
-var containers = []string{"dev", "coverage"}
-var defaultContainer = "dev"
-var ciBranch = os.Getenv("BUILDKITE_BRANCH")
-var ciPullRequest = os.Getenv("BUILDKITE_PULL_REQUEST")
-var ciTag = os.Getenv("BUILDKITE_TAG")
-var dockerTags = regexp.MustCompile(`v(?P(?P(?P\d+)\.\d+)\.\d+.*)`)
-var ignoredSuffixes = regexp.MustCompile("alpha|beta")
-var publicRepo = regexp.MustCompile(`.*:.*`)
-var tags = dockerTags.FindStringSubmatch(ciTag)
+var (
+ containers = []string{"dev", "coverage"}
+ defaultContainer = "dev"
+ ciBranch = os.Getenv("BUILDKITE_BRANCH")
+ ciPullRequest = os.Getenv("BUILDKITE_PULL_REQUEST")
+ ciTag = os.Getenv("BUILDKITE_TAG")
+ dockerTags = regexp.MustCompile(`v(?P(?P(?P\d+)\.\d+)\.\d+.*)`)
+ ignoredSuffixes = regexp.MustCompile("alpha|beta")
+ publicRepo = regexp.MustCompile(`.*:.*`)
+ tags = dockerTags.FindStringSubmatch(ciTag)
+)
func newDockerCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
@@ -98,15 +100,14 @@ func cmdDockerPushManifestRun(_ *cobra.Command, _ []string) {
log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3])
login(docker, dockerhub)
login(docker, ghcr)
- deployManifest(docker, tags[1])
- publishDockerReadme(docker)
- if !ignoredSuffixes.MatchString(ciTag) {
- deployManifest(docker, tags[2])
- deployManifest(docker, tags[3])
- deployManifest(docker, "latest")
- publishDockerReadme(docker)
+ if ignoredSuffixes.MatchString(ciTag) {
+ deployManifest(docker, tags[1])
+ } else {
+ deployManifest(docker, tags[1], tags[2], tags[3], "latest")
}
+
+ publishDockerReadme(docker)
} else {
log.Fatal("Docker manifest will not be published, the specified tag does not conform to the standard")
}
@@ -143,13 +144,12 @@ func dockerBuildOfficialImage(arch string) error {
filename := "Dockerfile"
dockerfile := fmt.Sprintf("%s.%s", filename, arch)
- flags, err := getXFlags(ciBranch, os.Getenv("BUILDKITE_BUILD_NUMBER"), "")
+ buildMetaData, err := getBuild(ciBranch, os.Getenv("BUILDKITE_BUILD_NUMBER"), "")
if err != nil {
log.Fatal(err)
}
- return docker.Build(IntermediateDockerImageName, dockerfile, ".",
- strings.Join(flags, " "))
+ return docker.Build(IntermediateDockerImageName, dockerfile, ".", buildMetaData)
}
func login(docker *Docker, registry string) {
@@ -181,13 +181,17 @@ func login(docker *Docker, registry string) {
}
}
-func deployManifest(docker *Docker, tag string) {
- log.Infof("Docker manifest %s:%s will be deployed on %s and %s", DockerImageName, tag, dockerhub, ghcr)
+func deployManifest(docker *Docker, tag ...string) {
+ tags = make([]string, 0)
- dockerhub := dockerhub + "/" + DockerImageName + ":" + tag
- ghcr := ghcr + "/" + DockerImageName + ":" + tag
+ log.Infof("The following Docker manifest(s) will be deployed on %s and %s", dockerhub, ghcr)
- if err := docker.Manifest(dockerhub, ghcr); err != nil {
+ for _, t := range tag {
+ log.Infof("- %s:%s", DockerImageName, t)
+ tags = append(tags, dockerhub+"/"+DockerImageName+":"+t, ghcr+"/"+DockerImageName+":"+t)
+ }
+
+ if err := docker.Manifest(tags); err != nil {
log.Fatal(err)
}
}
diff --git a/cmd/authelia-scripts/cmd/gen.go b/cmd/authelia-scripts/cmd/gen.go
new file mode 100644
index 000000000..10f06b412
--- /dev/null
+++ b/cmd/authelia-scripts/cmd/gen.go
@@ -0,0 +1,11 @@
+// Code generated by go generate. DO NOT EDIT.
+//
+// Run the following command to generate this file:
+// go run ./cmd/authelia-gen code scripts
+//
+
+package cmd
+
+const (
+ versionSwaggerUI = "4.15.2"
+)
diff --git a/cmd/authelia-scripts/cmd/helpers.go b/cmd/authelia-scripts/cmd/helpers.go
index 728ade11e..12f7e7dc8 100644
--- a/cmd/authelia-scripts/cmd/helpers.go
+++ b/cmd/authelia-scripts/cmd/helpers.go
@@ -2,66 +2,63 @@ package cmd
import (
"fmt"
- "strings"
+ "strconv"
"time"
"github.com/authelia/authelia/v4/internal/utils"
)
-func getXFlags(branch, build, extra string) (flags []string, err error) {
- if branch == "" {
- out, _, err := utils.RunCommandAndReturnOutput("git rev-parse --abbrev-ref HEAD")
- if err != nil {
- return flags, fmt.Errorf("error getting branch with git rev-parse: %w", err)
+func getBuild(branch, buildNumber, extra string) (b *Build, err error) {
+ var out string
+
+ b = &Build{
+ Branch: branch,
+ Extra: extra,
+ }
+
+ if buildNumber != "" {
+ if b.Number, err = strconv.Atoi(buildNumber); err != nil {
+ return nil, fmt.Errorf("error parsing provided build number: %w", err)
+ }
+ }
+
+ if b.Branch == "" {
+ if out, _, err = utils.RunCommandAndReturnOutput("git rev-parse --abbrev-ref HEAD"); err != nil {
+ return nil, fmt.Errorf("error getting branch with git rev-parse: %w", err)
}
if out == "" {
- branch = "master"
+ b.Branch = "master"
} else {
- branch = out
+ b.Branch = out
}
}
- gitTagCommit, _, err := utils.RunCommandAndReturnOutput("git rev-list --tags --max-count=1")
- if err != nil {
- return flags, fmt.Errorf("error getting tag commit with git rev-list: %w", err)
+ var (
+ gitTagCommit string
+ )
+
+ if gitTagCommit, _, err = utils.RunCommandAndReturnOutput("git rev-list --tags --max-count=1"); err != nil {
+ return nil, fmt.Errorf("error getting tag commit with git rev-list: %w", err)
}
- tag, _, err := utils.RunCommandAndReturnOutput(fmt.Sprintf("git describe --tags --abbrev=0 %s", gitTagCommit))
- if err != nil {
- return flags, fmt.Errorf("error getting tag with git describe: %w", err)
+ if b.Tag, _, err = utils.RunCommandAndReturnOutput(fmt.Sprintf("git describe --tags --abbrev=0 %s", gitTagCommit)); err != nil {
+ return nil, fmt.Errorf("error getting tag with git describe: %w", err)
}
- commit, _, err := utils.RunCommandAndReturnOutput("git rev-parse HEAD")
- if err != nil {
- return flags, fmt.Errorf("error getting commit with git rev-parse: %w", err)
+ if b.Commit, _, err = utils.RunCommandAndReturnOutput("git rev-parse HEAD"); err != nil {
+ return nil, fmt.Errorf("error getting commit with git rev-parse: %w", err)
}
- var states []string
-
- if gitTagCommit == commit {
- states = append(states, "tagged")
- } else {
- states = append(states, "untagged")
+ if gitTagCommit == b.Commit {
+ b.Tagged = true
}
- if _, exitCode, _ := utils.RunCommandAndReturnOutput("git diff --quiet"); exitCode != 0 {
- states = append(states, "dirty")
- } else {
- states = append(states, "clean")
+ if _, exitCode, _ := utils.RunCommandAndReturnOutput("git diff --quiet"); exitCode == 0 {
+ b.Clean = true
}
- if build == "" {
- build = "manual"
- }
+ b.Date = time.Now()
- return []string{
- fmt.Sprintf(fmtLDFLAGSX, "BuildBranch", branch),
- fmt.Sprintf(fmtLDFLAGSX, "BuildTag", tag),
- fmt.Sprintf(fmtLDFLAGSX, "BuildCommit", commit),
- fmt.Sprintf(fmtLDFLAGSX, "BuildDate", time.Now().Format("Mon, 02 Jan 2006 15:04:05 -0700")),
- fmt.Sprintf(fmtLDFLAGSX, "BuildState", strings.Join(states, " ")),
- fmt.Sprintf(fmtLDFLAGSX, "BuildExtra", extra),
- fmt.Sprintf(fmtLDFLAGSX, "BuildNumber", build),
- }, nil
+ return b, nil
}
diff --git a/cmd/authelia-scripts/cmd/helpers_docker.go b/cmd/authelia-scripts/cmd/helpers_docker.go
index 89d098238..f05eba9ac 100644
--- a/cmd/authelia-scripts/cmd/helpers_docker.go
+++ b/cmd/authelia-scripts/cmd/helpers_docker.go
@@ -1,6 +1,13 @@
package cmd
import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
+
"github.com/authelia/authelia/v4/internal/utils"
)
@@ -8,11 +15,20 @@ import (
type Docker struct{}
// Build build a docker image.
-func (d *Docker) Build(tag, dockerfile, target, ldflags string) error {
- return utils.CommandWithStdout(
- "docker", "build", "-t", tag, "-f", dockerfile,
- "--progress=plain", "--build-arg", "LDFLAGS_EXTRA="+ldflags,
- target).Run()
+func (d *Docker) Build(tag, dockerfile, target string, buildMetaData *Build) error {
+ args := []string{"build", "-t", tag, "-f", dockerfile, "--progress=plain"}
+
+ for label, value := range buildMetaData.ContainerLabels() {
+ if value == "" {
+ continue
+ }
+
+ args = append(args, "--label", fmt.Sprintf("%s=%s", label, value))
+ }
+
+ args = append(args, "--build-arg", "LDFLAGS_EXTRA="+strings.Join(buildMetaData.XFlags(), " "), target)
+
+ return utils.CommandWithStdout("docker", args...).Run()
}
// Tag tag a docker image.
@@ -26,11 +42,107 @@ func (d *Docker) Login(username, password, registry string) error {
}
// Manifest push a docker manifest to dockerhub.
-func (d *Docker) Manifest(tag1, tag2 string) error {
- return utils.CommandWithStdout("docker", "build", "-t", tag1, "-t", tag2, "--platform", "linux/amd64,linux/arm/v7,linux/arm64", "--builder", "buildx", "--push", ".").Run()
+func (d *Docker) Manifest(tags []string) error {
+ args := []string{"build"}
+
+ for _, tag := range tags {
+ args = append(args, "-t", tag)
+ }
+
+ annotations := ""
+
+ buildMetaData, err := getBuild(ciBranch, os.Getenv("BUILDKITE_BUILD_NUMBER"), "")
+ if err != nil {
+ return err
+ }
+
+ for label, value := range buildMetaData.ContainerLabels() {
+ if value == "" {
+ continue
+ }
+
+ annotations += fmt.Sprintf("annotation.%s=%s,", label, value)
+ args = append(args, "--label", fmt.Sprintf("%s=%s", label, value))
+ }
+
+ var baseImageTag string
+
+ from, err := getDockerfileDirective("Dockerfile", "FROM")
+ if err == nil {
+ baseImageTag = from[strings.IndexRune(from, ':')+1:]
+ args = append(args, "--label", "org.opencontainers.image.base.name=docker.io/library/alpine:"+baseImageTag)
+ }
+
+ resp, err := http.Get("https://hub.docker.com/v2/repositories/library/alpine/tags/" + baseImageTag + "/images")
+ if err != nil {
+ return err
+ }
+
+ defer resp.Body.Close()
+
+ images := DockerImages{}
+
+ if err = json.NewDecoder(resp.Body).Decode(&images); err != nil {
+ return err
+ }
+
+ var (
+ digestAMD64, digestARM, digestARM64 string
+ )
+
+ for _, platform := range []string{"linux/amd64", "linux/arm/v7", "linux/arm64"} {
+ for _, image := range images {
+ if !image.Match(platform) {
+ continue
+ }
+
+ switch platform {
+ case "linux/amd64":
+ digestAMD64 = image.Digest
+ case "linux/arm/v7":
+ digestARM = image.Digest
+ case "linux/arm64":
+ digestARM64 = image.Digest
+ }
+ }
+ }
+
+ finalArgs := make([]string, len(args))
+
+ copy(finalArgs, args)
+
+ finalArgs = append(finalArgs, "--output", "type=image,\"name="+dockerhub+"/"+DockerImageName+","+ghcr+"/"+DockerImageName+"\","+annotations+"annotation.org.opencontainers.image.base.name=docker.io/library/alpine:"+baseImageTag+",annotation[linux/amd64].org.opencontainers.image.base.digest="+digestAMD64+",annotation[linux/arm/v7].org.opencontainers.image.base.digest="+digestARM+",annotation[linux/arm64].org.opencontainers.image.base.digest="+digestARM64, "--platform", "linux/amd64,linux/arm/v7,linux/arm64", "--builder", "buildx", "--push", ".")
+
+ if err = utils.CommandWithStdout("docker", finalArgs...).Run(); err != nil {
+ return err
+ }
+
+ return nil
}
// PublishReadme push README.md to dockerhub.
func (d *Docker) PublishReadme() error {
return utils.CommandWithStdout("bash", "-c", `token=$(curl -fs --retry 3 -H "Content-Type: application/json" -X "POST" -d '{"username": "'$DOCKER_USERNAME'", "password": "'$DOCKER_PASSWORD'"}' https://hub.docker.com/v2/users/login/ | jq -r .token) && jq -n --arg msg "$(cat README.md | sed -r 's/(\ ' | sed '/Thanks goes to/,/### Backers/{/### Backers/!d}')" '{"registry":"registry-1.docker.io","full_description": $msg }' | curl -fs --retry 3 -o /dev/null -L -X "PATCH" -H "Content-Type: application/json" -H "Authorization: JWT $token" -d @- https://hub.docker.com/v2/repositories/authelia/authelia/`).Run()
}
+
+func getDockerfileDirective(filePath, directive string) (from string, err error) {
+ var f *os.File
+
+ if f, err = os.Open(filePath); err != nil {
+ return "", err
+ }
+
+ defer f.Close()
+
+ s := bufio.NewScanner(f)
+
+ for s.Scan() {
+ data := s.Text()
+
+ if strings.HasPrefix(data, directive+" ") {
+ return data[5:], nil
+ }
+ }
+
+ return "", nil
+}
diff --git a/cmd/authelia-scripts/cmd/types.go b/cmd/authelia-scripts/cmd/types.go
index 4da3d28fc..e18f95391 100644
--- a/cmd/authelia-scripts/cmd/types.go
+++ b/cmd/authelia-scripts/cmd/types.go
@@ -1,7 +1,130 @@
package cmd
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/authelia/authelia/v4/internal/utils"
+)
+
// HostEntry represents an entry in /etc/hosts.
type HostEntry struct {
Domain string
IP string
}
+
+// DockerImages represents some of the data from the docker images API.
+type DockerImages []DockerImage
+
+// DockerImage represents some of the data from the docker images API.
+type DockerImage struct {
+ Architecture string `json:"architecture"`
+ Variant any `json:"variant"`
+ Digest string `json:"digest"`
+ OS string `json:"os"`
+}
+
+// Match returns true if this image matches the platform.
+func (d DockerImage) Match(platform string) bool {
+ parts := []string{d.OS, d.Architecture}
+
+ if strings.Join(parts, "/") == platform {
+ return true
+ }
+
+ if d.Variant == nil {
+ return false
+ }
+
+ parts = append(parts, d.Variant.(string))
+
+ return strings.Join(parts, "/") == platform
+}
+
+// Build represents a builds metadata.
+type Build struct {
+ Branch string
+ Tag string
+ Commit string
+ Tagged bool
+ Clean bool
+ Extra string
+ Number int
+ Date time.Time
+}
+
+// States returns the state tags for this Build.
+func (b Build) States() []string {
+ var states []string
+
+ if b.Tagged {
+ states = append(states, "tagged")
+ } else {
+ states = append(states, "untagged")
+ }
+
+ if b.Clean {
+ states = append(states, "clean")
+ } else {
+ states = append(states, "dirty")
+ }
+
+ return states
+}
+
+// State returns the state tags string for this Build.
+func (b Build) State() string {
+ return strings.Join(b.States(), " ")
+}
+
+// XFlags returns the XFlags for this Build.
+func (b Build) XFlags() []string {
+ return []string{
+ fmt.Sprintf(fmtLDFLAGSX, "BuildBranch", b.Branch),
+ fmt.Sprintf(fmtLDFLAGSX, "BuildTag", b.Tag),
+ fmt.Sprintf(fmtLDFLAGSX, "BuildCommit", b.Commit),
+ fmt.Sprintf(fmtLDFLAGSX, "BuildDate", b.Date.Format("Mon, 02 Jan 2006 15:04:05 -0700")),
+ fmt.Sprintf(fmtLDFLAGSX, "BuildState", b.State()),
+ fmt.Sprintf(fmtLDFLAGSX, "BuildExtra", b.Extra),
+ fmt.Sprintf(fmtLDFLAGSX, "BuildNumber", strconv.Itoa(b.Number)),
+ }
+}
+
+// ContainerLabels returns the container labels for this Build.
+func (b Build) ContainerLabels() (labels map[string]string) {
+ var version string
+
+ switch {
+ case b.Clean && b.Tagged:
+ version = utils.VersionAdv(b.Tag, b.State(), b.Commit, b.Branch, b.Extra)
+ case b.Clean:
+ version = fmt.Sprintf("%s-pre+%s.%s", b.Tag, b.Branch, b.Commit)
+ case b.Tagged:
+ version = fmt.Sprintf("%s-dirty", b.Tag)
+ default:
+ version = fmt.Sprintf("%s-dirty+%s.%s", b.Tag, b.Branch, b.Commit)
+ }
+
+ if strings.HasPrefix(version, "v") && len(version) > 1 {
+ version = version[1:]
+ }
+
+ labels = map[string]string{
+ "org.opencontainers.image.created": b.Date.Format(time.RFC3339),
+ "org.opencontainers.image.authors": "",
+ "org.opencontainers.image.url": "https://github.com/authelia/authelia/pkgs/container/authelia",
+ "org.opencontainers.image.documentation": "https://www.authelia.com",
+ "org.opencontainers.image.source": fmt.Sprintf("https://github.com/authelia/authelia/tree/%s", b.Commit),
+ "org.opencontainers.image.version": version,
+ "org.opencontainers.image.revision": b.Commit,
+ "org.opencontainers.image.vendor": "Authelia",
+ "org.opencontainers.image.licenses": "Apache-2.0",
+ "org.opencontainers.image.ref.name": "",
+ "org.opencontainers.image.title": "authelia",
+ "org.opencontainers.image.description": "Authelia is an open-source authentication and authorization server providing two-factor authentication and single sign-on (SSO) for your applications via a web portal.",
+ }
+
+ return labels
+}
diff --git a/cmd/authelia-scripts/cmd/xflags.go b/cmd/authelia-scripts/cmd/xflags.go
index a2876211f..f9f91f735 100644
--- a/cmd/authelia-scripts/cmd/xflags.go
+++ b/cmd/authelia-scripts/cmd/xflags.go
@@ -37,10 +37,10 @@ func cmdXFlagsRun(cobraCmd *cobra.Command, _ []string) {
log.Fatal(err)
}
- flags, err := getXFlags("", build, extra)
+ buildMetaData, err := getBuild("", build, extra)
if err != nil {
log.Fatal(err)
}
- fmt.Println(strings.Join(flags, " "))
+ fmt.Println(strings.Join(buildMetaData.XFlags(), " "))
}
diff --git a/config.template.yml b/config.template.yml
index a878e05f4..4940b3f33 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -96,10 +96,10 @@ server:
# timeouts:
## Read timeout.
- # read: 2s
+ # read: 6s
## Write timeout.
- # write: 2s
+ # write: 6s
## Idle timeout.
# idle: 30s
@@ -148,10 +148,10 @@ telemetry:
# timeouts:
## Read timeout.
- # read: 2s
+ # read: 6s
## Write timeout.
- # write: 2s
+ # write: 6s
## Idle timeout.
# idle: 30s
@@ -218,13 +218,13 @@ webauthn:
##
## Parameters used to contact the Duo API. Those are generated when you protect an application of type
## "Partner Auth API" in the management panel.
-duo_api:
- disable: false
- hostname: api-123456789.example.com
- integration_key: ABCDEF
+# duo_api:
+ # disable: false
+ # hostname: api-123456789.example.com
+ # integration_key: ABCDEF
## Secret can also be set using a secret: https://www.authelia.com/c/secrets
- secret_key: 1234567890abcdefghifjkl
- enable_self_enrollment: false
+ # secret_key: 1234567890abcdefghifjkl
+ # enable_self_enrollment: false
##
## NTP Configuration
@@ -281,7 +281,7 @@ authentication_backend:
## This is the recommended Authentication Provider in production
## because it allows Authelia to offload the stateful operations
## onto the LDAP service.
- ldap:
+ # ldap:
## The LDAP implementation, this affects elements like the attribute utilised for resetting a password.
## Acceptable options are as follows:
## - 'activedirectory' - For Microsoft Active Directory.
@@ -291,47 +291,127 @@ authentication_backend:
## Depending on the option here certain other values in this section have a default value, notably all of the
## attribute mappings have a default value that this config overrides, you can read more about these default values
## at https://www.authelia.com/c/ldap#defaults
- implementation: custom
+ # implementation: custom
## The url to the ldap server. Format: ://[:].
## Scheme can be ldap or ldaps in the format (port optional).
- url: ldap://127.0.0.1
+ # url: ldap://127.0.0.1
## The dial timeout for LDAP.
- timeout: 5s
+ # timeout: 5s
## Use StartTLS with the LDAP connection.
- start_tls: false
+ # start_tls: false
- tls:
- ## Server Name for certificate validation (in case it's not set correctly in the URL).
+ # tls:
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host portion of the url option.
# server_name: ldap.example.com
- ## Skip verifying the server certificate (to allow a self-signed certificate).
- ## In preference to setting this we strongly recommend you add the public portion of the certificate to the
- ## certificates directory which is defined by the `certificates_directory` option at the top of the config.
- skip_verify: false
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
+ # skip_verify: false
- ## Minimum TLS version for either Secure LDAP or LDAP StartTLS.
- minimum_version: TLS1.2
+ ## Minimum TLS version for the connection.
+ # minimum_version: TLS1.2
+
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
## The distinguished name of the container searched for objects in the directory information tree.
## See also: additional_users_dn, additional_groups_dn.
- base_dn: dc=example,dc=com
+ # base_dn: dc=example,dc=com
## The attribute holding the username of the user. This attribute is used to populate the username in the session
- ## information. It was introduced due to #561 to handle case insensitive search queries. For you information,
- ## Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually uses 'uid'. Beware that this
- ## attribute holds the unique identifiers for the users binding the user and the configuration stored in database.
- ## Therefore only single value attributes are allowed and the value must never be changed once attributed to a user
- ## otherwise it would break the configuration for that user. Technically, non-unique attributes like 'mail' can also
- ## be used but we don't recommend using them, we instead advise to use the attributes mentioned above
- ## (sAMAccountName and uid) to follow https://www.ietf.org/rfc/rfc2307.txt.
+ ## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually
+ ## uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and the
+ ## configuration stored in database. Therefore only single value attributes are allowed and the value must never be
+ ## changed once attributed to a user otherwise it would break the configuration for that user. Technically,
+ ## non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead advise to use
+ ## a filter to perform alternative lookups and the attributes mentioned above (sAMAccountName and uid) to
+ ## follow https://www.ietf.org/rfc/rfc2307.txt.
# username_attribute: uid
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
- additional_users_dn: ou=users
+ # additional_users_dn: ou=users
## The users filter used in search queries to find the user profile based on input filled in login form.
## Various placeholders are available in the user filter which you can read about in the documentation which can
@@ -345,11 +425,11 @@ authentication_backend:
##
## To allow sign in both with username and email, one can use a filter like
## (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
- users_filter: (&({username_attribute}={input})(objectClass=person))
+ # users_filter: (&({username_attribute}={input})(objectClass=person))
## The additional_groups_dn is prefixed to base_dn and delimited by a comma when searching for groups.
## i.e. with this set to OU=Groups and base_dn set to DC=a,DC=com; OU=Groups,DC=a,DC=com is searched for groups.
- additional_groups_dn: ou=groups
+ # additional_groups_dn: ou=groups
## The groups filter used in search queries to find the groups based on relevant authenticated user.
## Various placeholders are available in the groups filter which you can read about in the documentation which can
@@ -357,7 +437,7 @@ authentication_backend:
##
## If your groups use the `groupOfUniqueNames` structure use this instead:
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
- groups_filter: (&(member={dn})(objectClass=groupOfNames))
+ # groups_filter: (&(member={dn})(objectClass=groupOfNames))
## The attribute holding the name of the group.
# group_name_attribute: cn
@@ -371,12 +451,12 @@ authentication_backend:
## Follow referrals returned by the server.
## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
- permit_referrals: false
+ # permit_referrals: false
## The username and password of the admin user.
- user: cn=admin,dc=example,dc=com
+ # user: cn=admin,dc=example,dc=com
## Password can also be set using a secret: https://www.authelia.com/c/secrets
- password: password
+ # password: password
##
## File (Authentication Provider)
@@ -391,14 +471,38 @@ authentication_backend:
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
# file:
- # path: /config/users_database.yml
- # password:
- # algorithm: argon2id
- # iterations: 1
- # key_length: 32
- # salt_length: 16
- # memory: 1024
- # parallelism: 8
+ # path: /config/users_database.yml
+ # watch: false
+ # search:
+ # email: false
+ # case_insensitive: false
+ # password:
+ # algorithm: argon2
+ # argon2:
+ # variant: argon2id
+ # iterations: 3
+ # memory: 65536
+ # parallelism: 4
+ # key_length: 32
+ # salt_length: 16
+ # scrypt:
+ # iterations: 16
+ # block_size: 8
+ # parallelism: 1
+ # key_length: 32
+ # salt_length: 16
+ # pbkdf2:
+ # variant: sha512
+ # iterations: 310000
+ # salt_length: 16
+ # sha2crypt:
+ # variant: sha512
+ # iterations: 50000
+ # salt_length: 16
+ # bcrypt:
+ # variant: standard
+ # cost: 12
+
##
## Password Policy Configuration.
@@ -443,7 +547,7 @@ password_policy:
## to anyone. Otherwise restrictions follow the rules defined.
##
## Note: One can use the wildcard * to match any subdomain.
-## It must stand at the beginning of the pattern. (example: *.mydomain.com)
+## It must stand at the beginning of the pattern. (example: *.example.com)
##
## Note: You must put patterns containing wildcards between simple quotes for the YAML to be syntactically correct.
##
@@ -466,18 +570,18 @@ access_control:
## resource if there is no policy to be applied to the user.
default_policy: deny
- networks:
- - name: internal
- networks:
- - 10.10.0.0/16
- - 192.168.2.0/24
- - name: VPN
- networks: 10.9.0.0/16
+ # networks:
+ # - name: internal
+ # networks:
+ # - 10.10.0.0/16
+ # - 192.168.2.0/24
+ # - name: VPN
+ # networks: 10.9.0.0/16
- rules:
+ # rules:
## Rules applied to everyone
- - domain: 'public.example.com'
- policy: bypass
+ # - domain: 'public.example.com'
+ # policy: bypass
## Domain Regex examples. Generally we recommend just using a standard domain.
# - domain_regex: '^(?P\w+)\.example\.com$'
@@ -485,70 +589,70 @@ access_control:
# - domain_regex: '^(?P\w+)\.example\.com$'
# policy: one_factor
# - domain_regex:
- # - '^appgroup-.*\.example\.com$'
- # - '^appgroup2-.*\.example\.com$'
+ # - '^appgroup-.*\.example\.com$'
+ # - '^appgroup2-.*\.example\.com$'
# policy: one_factor
# - domain_regex: '^.*\.example\.com$'
# policy: two_factor
- - domain: 'secure.example.com'
- policy: one_factor
- ## Network based rule, if not provided any network matches.
- networks:
- - internal
- - VPN
- - 192.168.1.0/24
- - 10.0.0.1
+ # - domain: 'secure.example.com'
+ # policy: one_factor
+ ## Network based rule, if not provided any network matches.
+ # networks:
+ # - internal
+ # - VPN
+ # - 192.168.1.0/24
+ # - 10.0.0.1
- - domain:
- - 'secure.example.com'
- - 'private.example.com'
- policy: two_factor
+ # - domain:
+ # - 'secure.example.com'
+ # - 'private.example.com'
+ # policy: two_factor
- - domain: 'singlefactor.example.com'
- policy: one_factor
+ # - domain: 'singlefactor.example.com'
+ # policy: one_factor
## Rules applied to 'admins' group
- - domain: 'mx2.mail.example.com'
- subject: 'group:admins'
- policy: deny
+ # - domain: 'mx2.mail.example.com'
+ # subject: 'group:admins'
+ # policy: deny
- - domain: '*.example.com'
- subject:
- - 'group:admins'
- - 'group:moderators'
- policy: two_factor
+ # - domain: '*.example.com'
+ # subject:
+ # - 'group:admins'
+ # - 'group:moderators'
+ # policy: two_factor
## Rules applied to 'dev' group
- - domain: 'dev.example.com'
- resources:
- - '^/groups/dev/.*$'
- subject: 'group:dev'
- policy: two_factor
+ # - domain: 'dev.example.com'
+ # resources:
+ # - '^/groups/dev/.*$'
+ # subject: 'group:dev'
+ # policy: two_factor
## Rules applied to user 'john'
- - domain: 'dev.example.com'
- resources:
- - '^/users/john/.*$'
- subject: 'user:john'
- policy: two_factor
+ # - domain: 'dev.example.com'
+ # resources:
+ # - '^/users/john/.*$'
+ # subject: 'user:john'
+ # policy: two_factor
## Rules applied to user 'harry'
- - domain: 'dev.example.com'
- resources:
- - '^/users/harry/.*$'
- subject: 'user:harry'
- policy: two_factor
+ # - domain: 'dev.example.com'
+ # resources:
+ # - '^/users/harry/.*$'
+ # subject: 'user:harry'
+ # policy: two_factor
## Rules applied to user 'bob'
- - domain: '*.mail.example.com'
- subject: 'user:bob'
- policy: two_factor
- - domain: 'dev.example.com'
- resources:
- - '^/users/bob/.*$'
- subject: 'user:bob'
- policy: two_factor
+ # - domain: '*.mail.example.com'
+ # subject: 'user:bob'
+ # policy: two_factor
+ # - domain: 'dev.example.com'
+ # resources:
+ # - '^/users/bob/.*$'
+ # subject: 'user:bob'
+ # policy: two_factor
##
## Session Provider Configuration
@@ -594,9 +698,9 @@ session:
##
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
- redis:
- host: 127.0.0.1
- port: 6379
+ # redis:
+ # host: 127.0.0.1
+ # port: 6379
## Use a unix socket instead
# host: /var/run/redis/redis.sock
@@ -604,30 +708,110 @@ session:
# username: authelia
## Password can also be set using a secret: https://www.authelia.com/c/secrets
- password: authelia
+ # password: authelia
## This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
- database_index: 0
+ # database_index: 0
## The maximum number of concurrent active connections to Redis.
- maximum_active_connections: 8
+ # maximum_active_connections: 8
## The target number of idle connections to have open ready for work. Useful when opening connections is slow.
- minimum_idle_connections: 0
+ # minimum_idle_connections: 0
## The Redis TLS configuration. If defined will require a TLS connection to the Redis instance(s).
# tls:
- ## Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host option.
# server_name: myredis.example.com
- ## Skip verifying the server certificate (to allow a self-signed certificate).
- ## In preference to setting this we strongly recommend you add the public portion of the certificate to the
- ## certificates directory which is defined by the `certificates_directory` option at the top of the config.
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
# skip_verify: false
## Minimum TLS version for the connection.
# minimum_version: TLS1.2
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
+
## The Redis HA configuration options.
## This provides specific options to Redis Sentinel, sentinel_name must be defined (Master Name).
# high_availability:
@@ -644,10 +828,10 @@ session:
## If the host in the above section is defined, it will be combined with this list to connect to sentinel.
## For high availability to be used you must have either defined; the host above or at least one node below.
# nodes:
- # - host: sentinel-node1
- # port: 6379
- # - host: sentinel-node2
- # port: 6379
+ # - host: sentinel-node1
+ # port: 6379
+ # - host: sentinel-node2
+ # port: 6379
## Choose the host with the lowest latency.
# route_by_latency: false
@@ -677,7 +861,7 @@ regulation:
## Storage Provider Configuration
##
## The available providers are: `local`, `mysql`, `postgres`. You must use one and only one of these providers.
-storage:
+# storage:
## The encryption key that is used to encrypt sensitive information in the database. Must be a string with a minimum
## length of 20. Please see the docs if you configure this with an undesirable key and need to change it.
# encryption_key: you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this
@@ -691,37 +875,219 @@ storage:
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
# local:
- # path: /config/db.sqlite3
+ ## Path to the SQLite3 Database.
+ # path: /config/db.sqlite3
##
## MySQL / MariaDB (Storage Provider)
##
- mysql:
- host: 127.0.0.1
- port: 3306
- database: authelia
- username: authelia
+ # mysql:
+ # host: 127.0.0.1
+ # port: 3306
+ # database: authelia
+ # username: authelia
## Password can also be set using a secret: https://www.authelia.com/c/secrets
- password: mypassword
- timeout: 5s
+ # password: mypassword
+ # timeout: 5s
+
+ ## MySQL TLS settings. Configuring this requires TLS.
+ # tls:
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host option.
+ # server_name: mysql.example.com
+
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
+ # skip_verify: false
+
+ ## Minimum TLS version for the connection.
+ # minimum_version: TLS1.2
+
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
##
## PostgreSQL (Storage Provider)
##
# postgres:
- # host: 127.0.0.1
- # port: 5432
- # database: authelia
- # schema: public
- # username: authelia
- # ## Password can also be set using a secret: https://www.authelia.com/c/secrets
- # password: mypassword
- # timeout: 5s
- # ssl:
- # mode: disable
- # root_certificate: disable
- # certificate: disable
- # key: disable
+ # host: 127.0.0.1
+ # port: 5432
+ # database: authelia
+ # schema: public
+ # username: authelia
+ ## Password can also be set using a secret: https://www.authelia.com/c/secrets
+ # password: mypassword
+ # timeout: 5s
+
+ ## PostgreSQL TLS settings. Configuring this requires TLS.
+ # tls:
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host option.
+ # server_name: postgres.example.com
+
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
+ # skip_verify: false
+
+ ## Minimum TLS version for the connection.
+ # minimum_version: TLS1.2
+
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
##
## Notification Provider
@@ -738,7 +1104,7 @@ notifier:
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
# filesystem:
- # filename: /config/notification.txt
+ # filename: /config/notification.txt
##
## SMTP (Notification Provider)
@@ -750,55 +1116,135 @@ notifier:
## (only works for unauthenticated connections)
## - validate the SMTP server x509 certificate during the TLS handshake against the hosts trusted certificates
## (configure in tls section)
- smtp:
+ # smtp:
## The SMTP host to connect to.
- host: 127.0.0.1
+ # host: 127.0.0.1
## The port to connect to the SMTP host on.
- port: 1025
+ # port: 1025
## The connection timeout.
- timeout: 5s
+ # timeout: 5s
## The username used for SMTP authentication.
- username: test
+ # username: test
## The password used for SMTP authentication.
## Can also be set using a secret: https://www.authelia.com/c/secrets
- password: password
+ # password: password
## The sender is used to is used for the MAIL FROM command and the FROM header.
## If this is not defined and the username is an email, we use the username as this value. This can either be just
## an email address or the RFC5322 'Name ' format.
- sender: "Authelia "
+ # sender: "Authelia "
## HELO/EHLO Identifier. Some SMTP Servers may reject the default of localhost.
- identifier: localhost
+ # identifier: localhost
## Subject configuration of the emails sent. {title} is replaced by the text from the notifier.
- subject: "[Authelia] {title}"
+ # subject: "[Authelia] {title}"
## This address is used during the startup check to verify the email configuration is correct.
## It's not important what it is except if your email server only allows local delivery.
- startup_check_address: test@authelia.com
+ # startup_check_address: test@authelia.com
## By default we require some form of TLS. This disables this check though is not advised.
- disable_require_tls: false
+ # disable_require_tls: false
## Disables sending HTML formatted emails.
- disable_html_emails: false
+ # disable_html_emails: false
- tls:
- ## Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
+ # tls:
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host option.
# server_name: smtp.example.com
- ## Skip verifying the server certificate (to allow a self-signed certificate).
- ## In preference to setting this we strongly recommend you add the public portion of the certificate to the
- ## certificates directory which is defined by the `certificates_directory` option at the top of the config.
- skip_verify: false
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
+ # skip_verify: false
- ## Minimum TLS version for either StartTLS or SMTPS.
- minimum_version: TLS1.2
+ ## Minimum TLS version for the connection.
+ # minimum_version: TLS1.2
+
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
##
## Identity Providers
@@ -815,36 +1261,79 @@ notifier:
## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets
# hmac_secret: this_is_a_secret_abc123abc123abc
+ ## The issuer_certificate_chain is an optional PEM encoded certificate chain. It's used in conjunction with the
+ ## issuer_private_key to sign JWT's. All certificates in the chain must be within the validity period, and every
+ ## certificate included must be signed by the certificate immediately after it if provided.
+ # issuer_certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
## The issuer_private_key is used to sign the JWT forged by OpenID Connect.
## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets
# issuer_private_key: |
- # -----BEGIN RSA PRIVATE KEY-----
- # MXIEogIB$AKCAQEAxZVJP3WF//PG2fLQoEC9DtdiFG/+00vqlbVzz47nyxKONIPI
- # lmL3UdmqpGTKMe/5Brqse4ZAKlQHiDbwzK9ypnfigtHuvh/JO0S7ChP70RC67ed1
- # HV1nyfz5eW3llbtGJPrlYLqITNgctHp6zmRUFtSzPj9qFvozI93LJi492yL1+vu8
- # Un3Dm8+Qq6XM2tPdEcldB/dtBwOWoF+8eOOVsu0TDuB5bwlhBVGJuSAuzBPRS2bF
- # Ga4uk0JDdkDOMCEQxC5uWDFxgfERSMFyfLVWD47woDbuWEBq10c0z+dpWPMp7Ain
- # YnnkqicwCN88Z0zid6MmMQ65F4+9Hc+qC/p6xwIDAQABAoIBAGlhaAHKor+Su3o/
- # AXqXTL5/rbYMzbLQiLt0XeJT69jpeqMTroZXHmWvXE3128mqnf0yzw/K2Ko6yxGh
- # i+j/onya8FqpsVYCCgfsbn2/js1AyRJeIp6Y1ORsYnqbXJnxmkXa80AV/OBPW2/+
- # 60TtSdQrebY3iFPc+i2k+9bPTvpyyDLKlz8UwdZG+k5uyYNIyQTccz+PjwsIvDij
- # 7tKYamhhLN3QXt3/aZTFpjTgezP4WyriZxjWrddHowc47q2rwNS95ND39JcysJAc
- # 0Pcbu8A5lVa7Fx33uOtzDfKWIW7xVEN+OtPgN+FbTjXcXk5IZedl+pW5lU5P++G/
- # ZPvz+WECgYEA9g6HwdODW3e68bOqsFoKg35+vfUFMzlyMF8HFylNVfnLpTEDr637
- # owzMFvcUxVd71b+gV5nnnbI+riUFIgyR8vhCjhy4moopDPahC4/KwN4NG6uz+i1h
- # AB6D5+zn2BjnO/5xMMFGlApWtRNmJVGYlNDj3bXKh2VXzzy03VNeD8kCgYEAzZFL
- # OlzoRB1HKpTWIECcuvxofMxLOLb3zs0k2t/FYNYIpovmGWCCAULz13y53e5+/+5m
- # 7I9VUZJFaIhaZ36qVBApCKdru69pZMkWCcQO9jELFcx51Ez7OgJWzu7GS1QJCPKC
- # fEDxI0rZK21j93/Sl/nUnEir7CYpQ+wvCaGuHg8CgYAXgbncfY1+DokwkB6NbHy2
- # pT4Mfbz6cNGE538w6kQ2I4AeDvmwLentYMqaow478CinegAiflSPTzkHwAemghbr
- # ZGZPV1UXhn13fJRUG2+eT1hnPVcbXnx223N0k8Bud6qXo65CnyRT/kzcTbcjd5Eh
- # Hne2daicmMTzynPo9Q72aQKBgBmobO9X8VWvIdbaxO85oVZlctVA2pK1o7CYQmVf
- # UM+JZ4MCKzI3rYJizPS0iK5+ujNPmmEkcs2/qBIoEsCgOrpLWhPOcc/3UPxXbPzD
- # D+sCrBOIdhxdj23qJNOnUfDNCGOpgUfpAzAYg4q8GKInvi1h7XukRnEvQi9MJ4LY
- # P1dZAoGASGcGnTMkmeSXP8ux+dvQJAiJskn/sJIgBZ5uq5GRCeLBUosRSVxM75UK
- # vAh/c/RBj+pYXVKuPuHGZCQJxsdcRXzXNGouUtgbaYML5Me/Hagt20QzDRBfuGBg
- # qeZBJaXhjElvw6PUWtg4x+LYRCBpq/bS3LK3ozZrSTukVkKDegw=
- # -----END RSA PRIVATE KEY-----
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
## The lifespans configure the expiration for these token types.
# access_token_lifespan: 1h
@@ -866,17 +1355,17 @@ notifier:
# cors:
## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on.
# endpoints:
- # - authorization
- # - token
- # - revocation
- # - introspection
- # - userinfo
+ # - authorization
+ # - token
+ # - revocation
+ # - introspection
+ # - userinfo
## List of allowed origins.
## Any origin with https is permitted unless this option is configured or the
## allowed_origins_from_client_redirect_uris option is enabled.
# allowed_origins:
- # - https://example.com
+ # - https://example.com
## Automatically adds the origin portion of all redirect URI's on all clients to the list of allowed_origins,
## provided they have the scheme http or https and do not have the hostname of localhost.
@@ -905,10 +1394,12 @@ notifier:
## The policy to require for this client; one_factor or two_factor.
# authorization_policy: two_factor
- ## By default users cannot remember pre-configured consents. Setting this value to a period of time using a
- ## duration notation will enable users to remember consent for this client. The time configured is the amount
- ## of time the pre-configured consent is valid for granting new authorizations to the user.
- # pre_configured_consent_duration:
+ ## 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
## Audience this client is allowed to request.
# audience: []
diff --git a/crowdin.yml b/crowdin.yml
index 51597bf01..88613ec99 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -6,14 +6,4 @@ files:
- source: /internal/server/locales/en/*
translation: /internal/server/locales/%locale%/%original_file_name%
skip_untranslated_files: true
- languages_mapping:
- locale:
- "de-DE": de
- "en-EN": en
- "es-ES": es
- "fr-FR": fr
- "nl-NL": nl
- "pt-PT": pt
- "ru-RU": ru
- "zh-CH": zh
...
diff --git a/docs/config/_default/menus/menus.en.toml b/docs/config/_default/menus/menus.en.toml
index 2cb6e6245..af2244358 100644
--- a/docs/config/_default/menus/menus.en.toml
+++ b/docs/config/_default/menus/menus.en.toml
@@ -48,18 +48,18 @@
weight = 10
[[footer]]
- name = "Privacy"
- url = "/information/privacy-policy"
+ name = "Privacy Policy"
+ url = "/policies/privacy"
weight = 10
[[footer]]
- name = "Code of Conduct"
- url = "/information/code-of-conduct"
+ name = "Security Policy"
+ url = "/policies/security"
weight = 20
[[footer]]
- name = "Security"
- url = "/information/security"
+ name = "Code of Conduct"
+ url = "/code-of-conduct"
weight = 30
[[footer]]
diff --git a/docs/content/en/blog/pre-release-notes-4.37/index.md b/docs/content/en/blog/pre-release-notes-4.37/index.md
new file mode 100644
index 000000000..4880cd459
--- /dev/null
+++ b/docs/content/en/blog/pre-release-notes-4.37/index.md
@@ -0,0 +1,133 @@
+---
+title: "4.37: Pre-Release Notes"
+description: "Authelia 4.37 is just around the corner. This version has several additional features and improvements to existing features. In this blog post we'll discuss the new features and roughly what it means for users."
+lead: "Pre-Release Notes for 4.37"
+excerpt: "Authelia 4.37 is just around the corner. This version has several additional features and improvements to existing features. In this blog post we'll discuss the new features and roughly what it means for users."
+date: 2022-09-26T06:55:09+10:00
+draft: false
+images: []
+categories: ["News", "Release Notes"]
+tags: ["releases", "pre-release-notes"]
+contributors: ["James Elliott"]
+pinned: false
+homepage: false
+---
+
+Authelia [4.37](https://github.com/authelia/authelia/milestone/16) is just around the corner. This version has several
+additional features and improvements to existing features. In this blog post we'll discuss the new features and roughly
+what it means for users.
+
+_**Note:** These features are still subject to change however it represents the most likely features._
+
+This blog entry (and technically the blog itself) is part of a new effort I'm making for which I'm not entirely sure how
+useful it'll be but I'd love to hear your feedback regardless. We don't use any analytics or interactive components to
+gauge the consumption or reception of the website so it is invaluable to get this feedback.
+
+## Envoy Support
+
+We'll be supporting [Envoy] and [Istio] in this release. Support for this proxy mostly completes our proxy support status
+with all major proxies supported excluding Microsoft IIS.
+
+[Envoy]: https://www.envoyproxy.io/
+[Istio]: https://istio.io/
+
+## OpenID Connect Improvements
+
+Several items from the [OpenID Connect Roadmap](../../roadmap/active/openid-connect.md) are being checked off in this
+release.
+
+### Hashed Client Secrets
+
+We'll be supporting hashed OpenID Connect client secrets in this release. People will still be able to use plaintext
+secrets if they wish however we'll be recommending people utilize PBKDF2, BCrypt or SHA512 SHA2CRYPT (see
+[Password Algorithms](#password-algorithms) for a full compatibility list). This doesn't change anything for OpenID
+Connect Relying Parties, it only requires a change in the Authelia configuration.
+
+### Consent Modes
+
+Currently we support an explicit consent mode, and a pre-configured consent mode if the pre-configured duration is set.
+In this release we're planning to support an implicit consent mode which will never ask users for any consent. In
+addition it will make the consent mode configuration slightly more explicit.
+
+### JWKS Certificate Chain
+
+Currently we do not support JWKS certificates, we only support private keys. We will support advertising the Certificate
+Chain via the JWKS endpoint in this release. This means when provided with a Certificate Chain will be able to
+theoretically validate the level of trust associated with the JWKS.
+
+Some applications theoretically require this, most probably don't support it at all. However the beauty of this change
+is that if it's not supported by the other party they can just ignore it. We've yet to have users request this but it's
+likely inevitable that someone will ask or some third party will require it at some point, so we're preemptively
+implementing it.
+
+## Container Annotations / Labels
+
+In this release we're going to start adding the [OCI Image Format Specification]'s set of [Annotations] to all of our
+images.
+
+For the time being we will also add the [Annotations] as container labels. This is because [Annotations] are a
+relatively unsupported specification at this stage. A majority of use cases for the [Annotations] either actually use
+labels or fallback to labels.
+
+[OCI Image Format Specification]: https://github.com/opencontainers/image-spec
+[Annotations]: https://github.com/opencontainers/image-spec/blob/main/annotations.md
+
+## Password Algorithms
+
+Several new password hashing algorithms will be supported in this release. The list of supported algorithms will become:
+
+* Argon2:
+ * Argon2id (previously supported)
+ * Argon2i
+ * Argon2d
+* PBKDF2:
+ * SHA1
+ * SHA224
+ * SHA256
+ * SHA384
+ * SHA512
+* SCrypt
+* BCrypt
+* SHA2 CRYPT:
+ * SHA256
+ * SHA512 (previously supported)
+
+## Users YAML File Authentication Backend
+
+In addition to the [Password Algorithms](#password-algorithms) changes we'll also be adding a few major features to the
+Users YAML File Authentication Backend.
+
+### Automatic Reload
+
+Administrators will be able to allow automatic reload the YAML file with the users database for deployments of the YAML
+File backend. This change will not extend to the main configuration file at this time.
+
+### Email Lookup
+
+Administrators will be able to allow users to use their email or their username to login similar to how this can be done
+with an LDAP filter already, bringing feature parity to the YAML File backend.
+
+## Mutual TLS Support
+
+This release will add support for Mutual TLS for Redis, LDAP, and SMTP. This improves compatibility with these systems
+when password authentication is not desired.
+
+## Query Parameter Authorization Criteria
+
+We'll be adding a very specific query parameter matcher to the access control rules in this release. It will allow
+individually targeting specific query arguments and testing if they exist/don't exist, equal/don't equal a value, or if
+they match/don't match a specific Regular Expression.
+
+This rule type takes a performance hit when compared to the resources rule type, so the resources rule type is generally
+encouraged. However for complex matching of query parameters a Regular Expression is hard to get exactly right. This
+feature alleviates this issue.
+
+## Compatibility Features
+
+Compatibility Features are toggle on features which allows operation with a third party. Usually they're implemented to
+allow ignoring a specific specification the third party has not implemented correctly.
+
+We'll be adding the following compatibility features in this release:
+
+* LDAP Servers which do not support querying the RootDSE for supported controls or extensions.
+* SMTP Servers which advertise support for STARTTLS but do not actually support it.
diff --git a/docs/content/en/blog/say-hello-to-the-new-website/index.md b/docs/content/en/blog/say-hello-to-the-new-website/index.md
index 0ba089942..22799a8f1 100644
--- a/docs/content/en/blog/say-hello-to-the-new-website/index.md
+++ b/docs/content/en/blog/say-hello-to-the-new-website/index.md
@@ -47,4 +47,4 @@ future.
[Hugo]: https://gohugo.io/
[Doks]: https://getdoks.org/
-[Documentation Contributing]: ../../contributing/prologue/documentation.md
+[Documentation Contributing]: ../../contributing/prologue/documentation-contributions.md
diff --git a/docs/content/en/configuration/first-factor/file.md b/docs/content/en/configuration/first-factor/file.md
index 786db99a4..3ced89c64 100644
--- a/docs/content/en/configuration/first-factor/file.md
+++ b/docs/content/en/configuration/first-factor/file.md
@@ -20,13 +20,36 @@ aliases:
authentication_backend:
file:
path: /config/users.yml
+ watch: false
+ search:
+ email: false
+ case_insensitive: false
password:
- algorithm: argon2id
- iterations: 3
- key_length: 32
- salt_length: 16
- parallelism: 4
- memory: 64
+ algorithm: argon2
+ argon2:
+ variant: argon2id
+ iterations: 3
+ memory: 65536
+ parallelism: 4
+ key_length: 32
+ salt_length: 16
+ scrypt:
+ iterations: 16
+ block_size: 8
+ parallelism: 1
+ key_length: 32
+ salt_length: 16
+ pbkdf2:
+ variant: sha512
+ iterations: 310000
+ salt_length: 16
+ sha2crypt:
+ variant: sha512
+ iterations: 50000
+ salt_length: 16
+ bcrypt:
+ variant: standard
+ cost: 12
```
## Options
@@ -39,70 +62,37 @@ The path to the file with the user details list. Supported file types are:
* [YAML File](../../reference/guides/passwords.md#yaml-format)
-### password
+### watch
-#### algorithm
+{{< confkey type="boolean" default="false" required="no" >}}
-{{< confkey type="string" default="argon2id" required="no" >}}
+Enables reloading the database by watching it for changes.
-Controls the hashing algorithm used for hashing new passwords. Value must be one of:
+### search
-* `argon2id` for the [Argon2] `id` variant
-* `sha512` for the [SHA Crypt] `SHA512` variant
+Username searching functionality options.
-#### iterations
+*__Important Note:__ This functionality is experimental.*
-{{< confkey type="integer" required="no" >}}
+#### email
-Controls the number of hashing iterations done by the other hashing settings ([Argon2] parameter `t`, [SHA Crypt]
-parameter `rounds`). This affects the effective cost of hashing.
+{{< confkey type="boolean" default="false" required="no" >}}
-| Algorithm | Minimum | Default | Recommended |
-|:---------:|:-------:|:-------:|:------------------------------------------------------------------------------------------:|
-| argon2id | 1 | 3 | [See Recommendations](../../reference/guides/passwords.md#recommended-parameters-argon2id) |
-| sha512 | 1000 | 50000 | [See Recommendations](../../reference/guides/passwords.md#recommended-parameters-sha512) |
+Allows users to login using their email address. If enabled two users must not have the same emails and their usernames
+must not be an email.
-#### key_length
+*__Note:__ Emails are always checked using case-insensitive lookup.*
-{{< confkey type="integer" default="32" required="no" >}}
+#### case_insensitive
-*__Important:__ This setting is specific to the `argon2id` algorithm and unused with the `sha512` algorithm.*
+{{< confkey type="boolean" default="false" required="no" >}}
-Sets the key length of the [Argon2] hash output. The minimum value is `16` with the recommended value of `32` being set
-as the default.
+Enabling this search option allows users to login with their username regardless of case. If enabled users must only
+have lowercase usernames.
-#### salt_length
+*__Note:__ Emails are always checked using case-insensitive lookup.*
-{{< confkey type="integer" default="16" required="no" >}}
-
-Controls the length of the random salt added to each password before hashing. There is not a compelling reason to have
-this set to anything other than `16`, however the minimum is `8` with the recommended value of `16` being set as the
-default.
-
-#### parallelism
-
-{{< confkey type="integer" default="4" required="no" >}}
-
-*__Important:__ This setting is specific to the `argon2id` algorithm and unused with the `sha512` algorithm.*
-
-Sets the number of threads used by [Argon2] when hashing passwords ([Argon2] parameter `p`). The minimum value is `1`
-with the recommended value of `4` being set as the default. This affects the effective cost of hashing.
-
-#### memory
-
-{{< confkey type="integer" default="64" required="no" >}}
-
-*__Important:__ This setting is specific to the `argon2id` algorithm and unused with the `sha512` algorithm.*
-
-Sets the amount of memory in megabytes allocated to a single password hashing calculation ([Argon2] parameter `m`). This
-affects the effective cost of hashing.
-
-This memory is released by go after the hashing process completes, however the operating system may not reclaim the
-memory until a later time such as when the system is experiencing memory pressure which may cause the appearance of more
-memory being in use than Authelia is actually actively using. Authelia will typically reuse this memory if it has not be
-reclaimed as long as another hashing calculation is not still utilizing it.
-
-## Reference
+## Password Options
A [reference guide](../../reference/guides/passwords.md) exists specifically for choosing password hashing values. This
section contains far more information than is practical to include in this configuration document. See the
@@ -110,5 +100,164 @@ section contains far more information than is practical to include in this confi
This guide contains examples such as the [User / Password File](../../reference/guides/passwords.md#user--password-file).
+### algorithm
+
+{{< confkey type="string" default="argon2" required="no" >}}
+
+Controls the hashing algorithm used for hashing new passwords. Value must be one of:
+
+* `argon2` for the [Argon2](#argon2) algorithm
+* `scrypt` for the [Scrypt](#scrypt) algorithm
+* `pbkdf2` for the [PBKDF2](#pbkdf2) algorithm
+* `sha2crypt` for the [SHA2Crypt](#sha2crypt) algorithm
+* `bcrypt` for the [Bcrypt](#bcrypt) algorithm
+
+### argon2
+
+The [Argon2] algorithm implementation. This is one of the only algorithms that was designed purely with password hashing
+in mind and is subsequently one of the best algorithms to date for security.
+
+#### variant
+
+{{< confkey type="string" default="argon2id" required="no" >}}
+
+Controls the variant when hashing passwords using [Argon2]. Recommended `argon2id`.
+Permitted values `argon2id`, `argon2i`, `argon2d`.
+
+#### iterations
+
+{{< confkey type="integer" default="3" required="no" >}}
+
+Controls the number of iterations when hashing passwords using [Argon2].
+
+#### memory
+
+{{< confkey type="integer" default="65536" required="no" >}}
+
+Controls the amount of memory in kibibytes when hashing passwords using [Argon2].
+
+#### parallelism
+
+{{< confkey type="integer" default="4" required="no" >}}
+
+Controls the parallelism factor when hashing passwords using [Argon2].
+
+#### key_length
+
+{{< confkey type="integer" default="32" required="no" >}}
+
+Controls the output key length when hashing passwords using [Argon2].
+
+#### salt_length
+
+{{< confkey type="integer" default="16" required="no" >}}
+
+Controls the output salt length when hashing passwords using [Argon2].
+
+### scrypt
+
+The [Scrypt] algorithm implementation.
+
+#### iterations
+
+{{< confkey type="integer" default="16" required="no" >}}
+
+Controls the number of iterations when hashing passwords using [Scrypt].
+
+#### block_size
+
+{{< confkey type="integer" default="8" required="no" >}}
+
+Controls the block size when hashing passwords using [Scrypt].
+
+#### parallelism
+
+{{< confkey type="integer" default="1" required="no" >}}
+
+Controls the parallelism factor when hashing passwords using [Scrypt].
+
+#### key_length
+
+{{< confkey type="integer" default="32" required="no" >}}
+
+Controls the output key length when hashing passwords using [Scrypt].
+
+#### salt_length
+
+{{< confkey type="integer" default="16" required="no" >}}
+
+Controls the output salt length when hashing passwords using [Scrypt].
+
+### pbkdf2
+
+The [PBKDF2] algorithm implementation.
+
+#### variant
+
+{{< confkey type="string" default="sha512" required="no" >}}
+
+Controls the variant when hashing passwords using [PBKDF2]. Recommended `sha512`.
+Permitted values `sha1`, `sha224`, `sha256`, `sha384`, `sha512`.
+
+#### iterations
+
+{{< confkey type="integer" default="310000" required="no" >}}
+
+Controls the number of iterations when hashing passwords using [PBKDF2].
+
+#### salt_length
+
+{{< confkey type="integer" default="16" required="no" >}}
+
+Controls the output salt length when hashing passwords using [PBKDF2].
+
+### sha2crypt
+
+The [SHA2 Crypt] algorithm implementation.
+
+#### variant
+
+{{< confkey type="string" default="sha512" required="no" >}}
+
+Controls the variant when hashing passwords using [SHA2 Crypt]. Recommended `sha512`.
+Permitted values `sha256`, `sha512`.
+
+#### iterations
+
+{{< confkey type="integer" default="50000" required="no" >}}
+
+Controls the number of iterations when hashing passwords using [SHA2 Crypt].
+
+#### salt_length
+
+{{< confkey type="integer" default="16" required="no" >}}
+
+Controls the output salt length when hashing passwords using [SHA2 Crypt].
+
+### bcrypt
+
+The [Bcrypt] algorithm implementation.
+
+#### variant
+
+{{< confkey type="string" default="standard" required="no" >}}
+
+Controls the variant when hashing passwords using [Bcrypt]. Recommended `standard`.
+Permitted values `standard`, `sha256`.
+
+*__Important Note:__ The `sha256` variant is a special variant designed by
+[Passlib](https://passlib.readthedocs.io/en/stable/lib/passlib.hash.bcrypt_sha256.html). This variant passes the
+password through a SHA256 HMAC before passing it to the [Bcrypt] algorithm, effectively bypassing the 72 byte password
+truncation that [Bcrypt] does. It is not supported by many other systems.*
+
+#### cost
+
+{{< confkey type="integer" default="12" required="no" >}}
+
+Controls the hashing cost when hashing passwords using [Bcrypt].
+
[Argon2]: https://www.rfc-editor.org/rfc/rfc9106.html
-[SHA Crypt]: https://www.akkadia.org/drepper/SHA-crypt.txt
+[Scrypt]: https://en.wikipedia.org/wiki/Scrypt
+[PBKDF2]: https://www.ietf.org/rfc/rfc2898.html
+[SHA2 Crypt]: https://www.akkadia.org/drepper/SHA-crypt.txt
+[Bcrypt]: https://en.wikipedia.org/wiki/Bcrypt
diff --git a/docs/content/en/configuration/first-factor/ldap.md b/docs/content/en/configuration/first-factor/ldap.md
index d7474d1a9..9fb9127f3 100644
--- a/docs/content/en/configuration/first-factor/ldap.md
+++ b/docs/content/en/configuration/first-factor/ldap.md
@@ -28,13 +28,81 @@ authentication_backend:
server_name: ldap.example.com
skip_verify: false
minimum_version: TLS1.2
+ maximum_version: TLS1.3
+ certificate_chain: |
+ -----BEGIN CERTIFICATE-----
+ MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ qocikt3WAdU^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ DO NOT USE==
+ -----END RSA PRIVATE KEY-----
base_dn: DC=example,DC=com
- additional_users_dn: ou=users
+ additional_users_dn: OU=users
users_filter: (&({username_attribute}={input})(objectClass=person))
username_attribute: uid
mail_attribute: mail
display_name_attribute: displayName
- additional_groups_dn: ou=groups
+ additional_groups_dn: OU=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
permit_referrals: false
@@ -198,6 +266,13 @@ server and utilizing a service account.*
Permits binding to the server without a password. For this option to be enabled both the [password](#password)
configuration option must be blank and the [password_reset disable](introduction.md#disable) option must be `true`.
+### permit_feature_detection_failure
+
+{{< confkey type="boolean" default="false" required="no" >}}
+
+Authelia searches for the RootDSE to discover supported controls and extensions. This option is a compatability option
+which *__should not__* be enabled unless the LDAP server returns an error when searching for the RootDSE.
+
### user
{{< confkey type="string" required="yes" >}}
@@ -214,7 +289,7 @@ especially for containerized deployments.*
The password paired with the [user](#user) used to bind to the LDAP server for lookup and password change operations.
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters and the user password is changed to this value.
## Refresh Interval
diff --git a/docs/content/en/configuration/identity-providers/open-id-connect.md b/docs/content/en/configuration/identity-providers/open-id-connect.md
index dde841d34..9baad93f0 100644
--- a/docs/content/en/configuration/identity-providers/open-id-connect.md
+++ b/docs/content/en/configuration/identity-providers/open-id-connect.md
@@ -33,33 +33,72 @@ The following snippet provides a sample-configuration for the OIDC identity prov
identity_providers:
oidc:
hmac_secret: this_is_a_secret_abc123abc123abc
+ issuer_certificate_chain: |
+ -----BEGIN CERTIFICATE-----
+ MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ qocikt3WAdU^invalid DO NOT USE=
+ -----END CERTIFICATE-----
issuer_private_key: |
-----BEGIN RSA PRIVATE KEY-----
- MXIEogIB$AKCAQEAxZVJP3WF//PG2fLQoEC9DtdiFG/+00vqlbVzz47nyxKONIPI
- lmL3UdmqpGTKMe/5Brqse4ZAKlQHiDbwzK9ypnfigtHuvh/JO0S7ChP70RC67ed1
- HV1nyfz5eW3llbtGJPrlYLqITNgctHp6zmRUFtSzPj9qFvozI93LJi492yL1+vu8
- Un3Dm8+Qq6XM2tPdEcldB/dtBwOWoF+8eOOVsu0TDuB5bwlhBVGJuSAuzBPRS2bF
- Ga4uk0JDdkDOMCEQxC5uWDFxgfERSMFyfLVWD47woDbuWEBq10c0z+dpWPMp7Ain
- YnnkqicwCN88Z0zid6MmMQ65F4+9Hc+qC/p6xwIDAQABAoIBAGlhaAHKor+Su3o/
- AXqXTL5/rbYMzbLQiLt0XeJT69jpeqMTroZXHmWvXE3128mqnf0yzw/K2Ko6yxGh
- i+j/onya8FqpsVYCCgfsbn2/js1AyRJeIp6Y1ORsYnqbXJnxmkXa80AV/OBPW2/+
- 60TtSdQrebY3iFPc+i2k+9bPTvpyyDLKlz8UwdZG+k5uyYNIyQTccz+PjwsIvDij
- 7tKYamhhLN3QXt3/aZTFpjTgezP4WyriZxjWrddHowc47q2rwNS95ND39JcysJAc
- 0Pcbu8A5lVa7Fx33uOtzDfKWIW7xVEN+OtPgN+FbTjXcXk5IZedl+pW5lU5P++G/
- ZPvz+WECgYEA9g6HwdODW3e68bOqsFoKg35+vfUFMzlyMF8HFylNVfnLpTEDr637
- owzMFvcUxVd71b+gV5nnnbI+riUFIgyR8vhCjhy4moopDPahC4/KwN4NG6uz+i1h
- AB6D5+zn2BjnO/5xMMFGlApWtRNmJVGYlNDj3bXKh2VXzzy03VNeD8kCgYEAzZFL
- OlzoRB1HKpTWIECcuvxofMxLOLb3zs0k2t/FYNYIpovmGWCCAULz13y53e5+/+5m
- 7I9VUZJFaIhaZ36qVBApCKdru69pZMkWCcQO9jELFcx51Ez7OgJWzu7GS1QJCPKC
- fEDxI0rZK21j93/Sl/nUnEir7CYpQ+wvCaGuHg8CgYAXgbncfY1+DokwkB6NbHy2
- pT4Mfbz6cNGE538w6kQ2I4AeDvmwLentYMqaow478CinegAiflSPTzkHwAemghbr
- ZGZPV1UXhn13fJRUG2+eT1hnPVcbXnx223N0k8Bud6qXo65CnyRT/kzcTbcjd5Eh
- Hne2daicmMTzynPo9Q72aQKBgBmobO9X8VWvIdbaxO85oVZlctVA2pK1o7CYQmVf
- UM+JZ4MCKzI3rYJizPS0iK5+ujNPmmEkcs2/qBIoEsCgOrpLWhPOcc/3UPxXbPzD
- D+sCrBOIdhxdj23qJNOnUfDNCGOpgUfpAzAYg4q8GKInvi1h7XukRnEvQi9MJ4LY
- P1dZAoGASGcGnTMkmeSXP8ux+dvQJAiJskn/sJIgBZ5uq5GRCeLBUosRSVxM75UK
- vAh/c/RBj+pYXVKuPuHGZCQJxsdcRXzXNGouUtgbaYML5Me/Hagt20QzDRBfuGBg
- qeZBJaXhjElvw6PUWtg4x+LYRCBpq/bS3LK3ozZrSTukVkKDegw=
+ MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ DO NOT USE==
-----END RSA PRIVATE KEY-----
access_token_lifespan: 1h
authorize_code_lifespan: 1m
@@ -79,11 +118,12 @@ identity_providers:
clients:
- id: myapp
description: My Application
- secret: this_is_a_secret
+ secret: '$plaintext$this_is_a_secret'
sector_identifier: ''
public: false
authorization_policy: two_factor
- pre_configured_consent_duration: ''
+ consent_mode: explicit
+ pre_configured_consent_duration: 1w
audience: []
scopes:
- openid
@@ -117,9 +157,26 @@ The HMAC secret used to sign the [JWT]'s. The provided string is hashed to a SHA
purpose of meeting the required format.
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters.
+### issuer_certificate_chain
+
+{{< confkey type="string" required="no" >}}
+
+The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648])
+encoded PEM format used to sign/encrypt the [OpenID Connect] [JWT]'s. When configured it enables the [x5c] and [x5t]
+JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
+as per [RFC7517].
+
+[RFC7517]: https://www.rfc-editor.org/rfc/rfc7517
+[x5c]: https://www.rfc-editor.org/rfc/rfc7517#section-4.7
+[x5t]: https://www.rfc-editor.org/rfc/rfc7517#section-4.8
+
+The first certificate in the chain must have the public key for the [issuer_private_key](#issuer_private_key), each
+certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the
+certificate immediately following it if present.
+
### issuer_private_key
{{< confkey type="string" required="yes" >}}
@@ -127,9 +184,16 @@ characters.
*__Important Note:__ This can also be defined using a [secret](../methods/secrets.md) which is __strongly recommended__
especially for containerized deployments.*
-The private key in DER base64 ([RFC4648]) encoded PEM format used to encrypt the [OpenID Connect] [JWT]'s. The key must
-be generated by the administrator and can be done by following the
-[Generating an RSA Keypair](../miscellaneous/guides.md#generating-an-rsa-keypair) guide.
+The private key used to sign/encrypt the [OpenID Connect] issued [JWT]'s. The key must be generated by the administrator
+and can be done by following the [Generating an RSA Keypair](../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
+
+The private key *__MUST__*:
+* Be a PEM block encoded in the DER base64 format ([RFC4648]).
+* Be an RSA Key.
+* Have a key size of at least 2048 bits.
+
+If the [issuer_certificate_chain](#issuer_certificate_chain) is provided the private key must include matching public
+key data for the first certificate in the chain.
### access_token_lifespan
@@ -288,10 +352,10 @@ A friendly description for this client shown in the UI. This defaults to the sam
{{< confkey type="string" required="situational" >}}
The shared secret between Authelia and the application consuming this client. This secret must match the secret
-configured in the application. Currently this is stored in plain text.
+configured in the application.
This secret must be generated by the administrator and can be done by following the
-[Generating a Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) guide.
+[Generating Client Secrets](../../integration/openid-connect/specific-information.md#generating-client-secrets) guide.
This must be provided when the client is a confidential client type, and must be blank when using the public client
type. To set the client type to public see the [public](#public) configuration option.
@@ -345,19 +409,38 @@ URI.
The authorization policy for this client: either `one_factor` or `two_factor`.
+#### consent_mode
+
+{{< confkey type="string" default="auto" required="no" >}}
+
+Configures the consent mode. The following table describes the different modes:
+
+| Value | Description |
+|:--------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
+| auto | Automatically determined (default). Uses `explicit` unless [pre_configured_consent_duration] is specified in which case uses `pre-configured`. |
+| explicit | Requires the user provide unique explicit consent for every authorization. |
+| implicit | Automatically assumes consent for every authorization, never asking the user if they wish to give consent. *__Note:__* this option is not technically part of the specification. |
+| pre-configured | Allows the end-user to remember their consent for the [pre_configured_consent_duration]. |
+
+[pre_configured_consent_duration]: #pre_configured_consent_duration
+
#### pre_configured_consent_duration
-{{< confkey type="duration" required="no" >}}
+{{< confkey type="duration" default="1w" required="no" >}}
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
-Configuring this enables users of this client to remember their consent as a pre-configured consent. The period of time
-dictates how long a users choice to remember the pre-configured consent lasts.
+Specifying this in the configuration without a consent [consent_mode] enables the `pre-configured` mode. If this is
+specified as well as the [consent_mode] then it only has an effect if the [consent_mode] is `pre-configured` or `auto`.
+
+The period of time dictates how long a users choice to remember the pre-configured consent lasts.
Pre-configured consents are only valid if the subject, client id are exactly the same and the requested scopes/audience
match exactly with the granted scopes/audience.
+[consent_mode]: #consent_mode
+
#### audience
{{< confkey type="list(string)" required="no" >}}
diff --git a/docs/content/en/configuration/methods/environment.md b/docs/content/en/configuration/methods/environment.md
index 4fc3ccd49..e5e4686bb 100644
--- a/docs/content/en/configuration/methods/environment.md
+++ b/docs/content/en/configuration/methods/environment.md
@@ -50,3 +50,7 @@ Can be replaced by this environment variable configuration:
AUTHELIA_LOG_LEVEL=info
AUTHELIA_SERVER_BUFFERS_READ=4096
```
+
+## Environment Variables
+
+{{% table-config-keys secrets="false" %}}
diff --git a/docs/content/en/configuration/methods/secrets.md b/docs/content/en/configuration/methods/secrets.md
index 10c9096fe..25802b9bb 100644
--- a/docs/content/en/configuration/methods/secrets.md
+++ b/docs/content/en/configuration/methods/secrets.md
@@ -52,33 +52,32 @@ Here is the list of the environment variables which are considered secrets and c
secrets can be loaded into the configuration if they end with one of the suffixes above, you can set the value of any
other configuration using the environment but instead of loading a file the value of the environment variable is used.
-| Configuration Key | Environment Variable |
-|:---------------------------------------------------:|:--------------------------------------------------------:|
-| [server.tls.key] | AUTHELIA_SERVER_TLS_KEY_FILE |
-| [jwt_secret] | AUTHELIA_JWT_SECRET_FILE |
-| [duo_api.secret_key] | AUTHELIA_DUO_API_SECRET_KEY_FILE |
-| [session.secret] | AUTHELIA_SESSION_SECRET_FILE |
-| [session.redis.password] | AUTHELIA_SESSION_REDIS_PASSWORD_FILE |
-| [session.redis.high_availability.sentinel_password] | AUTHELIA_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE |
-| [storage.encryption_key] | AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE |
-| [storage.mysql.password] | AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE |
-| [storage.postgres.password] | AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE |
-| [notifier.smtp.password] | AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE |
-| [authentication_backend.ldap.password] | AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE |
-| [identity_providers.oidc.issuer_private_key] | AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE |
-| [identity_providers.oidc.hmac_secret] | AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE |
+{{% table-config-keys secrets="true" %}}
[server.tls.key]: ../miscellaneous/server.md#key
[jwt_secret]: ../miscellaneous/introduction.md#jwt_secret
+[duo_api.integration_key]: ../second-factor/duo.md#integration_key
[duo_api.secret_key]: ../second-factor/duo.md#secret_key
[session.secret]: ../session/introduction.md#secret
[session.redis.password]: ../session/redis.md#password
+[session.redis.tls.certificate_chain]: ../session/redis.md#tls
+[session.redis.tls.private_key]: ../session/redis.md#tls
[session.redis.high_availability.sentinel_password]: ../session/redis.md#sentinel_password
[storage.encryption_key]: ../storage/introduction.md#encryption_key
[storage.mysql.password]: ../storage/mysql.md#password
+[storage.mysql.tls.certificate_chain]: ../storage/mysql.md#tls
+[storage.mysql.tls.private_key]: ../storage/mysql.md#tls
[storage.postgres.password]: ../storage/postgres.md#password
+[storage.postgres.tls.certificate_chain]: ../storage/postgres.md#tls
+[storage.postgres.tls.private_key]: ../storage/postgres.md#tls
+[storage.postgres.ssl.key]: ../storage/postgres.md
[notifier.smtp.password]: ../notifications/smtp.md#password
+[notifier.smtp.tls.certificate_chain]: ../notifications/smtp.md#tls
+[notifier.smtp.tls.private_key]: ../notifications/smtp.md#tls
[authentication_backend.ldap.password]: ../first-factor/ldap.md#password
+[authentication_backend.ldap.tls.certificate_chain]: ../first-factor/ldap.md#tls
+[authentication_backend.ldap.tls.private_key]: ../first-factor/ldap.md#tls
+[identity_providers.oidc.issuer_certificate_chain]: ../identity-providers/open-id-connect.md#issuer_certificate_chain
[identity_providers.oidc.issuer_private_key]: ../identity-providers/open-id-connect.md#issuer_private_key
[identity_providers.oidc.hmac_secret]: ../identity-providers/open-id-connect.md#hmac_secret
diff --git a/docs/content/en/configuration/miscellaneous/guides.md b/docs/content/en/configuration/miscellaneous/guides.md
deleted file mode 100644
index 20e04b6ed..000000000
--- a/docs/content/en/configuration/miscellaneous/guides.md
+++ /dev/null
@@ -1,77 +0,0 @@
----
-title: "Guides"
-description: "Miscellaneous Guides for Configuration."
-lead: "This section contains miscellaneous guides used in the configuration."
-date: 2022-06-15T17:51:47+10:00
-draft: false
-images: []
-menu:
- configuration:
- parent: "miscellaneous"
-weight: 199500
-toc: true
----
-
-## Generating a Random Alphanumeric String
-
-Some sections of the configuration recommend generating a random string. There are many ways to accomplish this, one
-possible way on Linux is utilizing the following command which prints a string with a length in characters of
-`${LENGTH}` to `stdout`. The string will only contain alphanumeric characters.
-
-```bash
-LENGTH=64
-tr -cd '[:alnum:]' < /dev/urandom | fold -w "${LENGTH}" | head -n 1 | tr -d '\n' ; echo
-```
-
-## Generating an RSA Keypair
-
-Some sections of the configuration need an RSA keypair. There are many ways to achieve this, this section explains two
-such ways.
-
-### openssl
-
-The `openssl` command on Linux can be used to generate a RSA 4096 bit keypair:
-
-```bash
-openssl genrsa -out private.pem 4096
-openssl rsa -in private.pem -outform PEM -pubout -out public.pem
-```
-
-### authelia
-
-The __Authelia__ docker container or CLI binary can be used to generate a RSA 4096 bit keypair:
-
-```bash
-docker run -u "$(id -u):$(id -g)" -v "$(pwd)":/keys authelia/authelia:latest authelia crypto pair rsa generate --bits 4096 --directory /keys
-```
-
-```bash
-authelia crypto pair rsa generate --directory /path/to/keys
-```
-
-## Generating an RSA Self-Signed Certificate
-
-Some sections of the configuration need a certificate and it may be possible to use a self-signed certificate. There are
-many ways to achieve this, this section explains two such ways.
-
-### openssl
-
-The `openssl` command on Linux can be used to generate a RSA 4096 bit self-signed certificate for the domain
-`example.com`:
-
-```bash
-openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -subj '/CN=example.com'
-```
-
-### authelia
-
-The __Authelia__ docker container or binary can be used to generate a RSA 4096 bit self-signed certificate for the
-domain `example.com`:
-
-```bash
-docker run -u "$(id -u):$(id -g)" -v "$(pwd)":/keys authelia/authelia authelia crypto certificate rsa generate --common-name example.com --directory /keys
-```
-
-```bash
-authelia crypto certificate rsa generate --common-name example.com --directory /path/to/keys
-```
diff --git a/docs/content/en/configuration/miscellaneous/introduction.md b/docs/content/en/configuration/miscellaneous/introduction.md
index 67b436b90..d8de18f72 100644
--- a/docs/content/en/configuration/miscellaneous/introduction.md
+++ b/docs/content/en/configuration/miscellaneous/introduction.md
@@ -73,7 +73,7 @@ default_2fa_method: totp
especially for containerized deployments.*
Defines the secret used to craft JWT tokens leveraged by the identity verification process. This can a random string.
-It's strongly recommended this is a [Random Alphanumeric String](guides.md#generating-a-random-alphanumeric-string) with
+It's strongly recommended this is a [Random Alphanumeric String](../../reference/guides/generating-secure-values.md/#generating-a-random-alphanumeric-string) with
64 or more characters.
### theme
diff --git a/docs/content/en/configuration/miscellaneous/server.md b/docs/content/en/configuration/miscellaneous/server.md
index 2143bd390..39340d5bf 100644
--- a/docs/content/en/configuration/miscellaneous/server.md
+++ b/docs/content/en/configuration/miscellaneous/server.md
@@ -35,9 +35,9 @@ server:
read: 4096
write: 4096
timeouts:
- read: 10s
- write: 10s
- idle: 10s
+ read: 6s
+ write: 6s
+ idle: 30s
```
## Options
@@ -130,7 +130,7 @@ security on lower areas of the OSI model. However it required, if you specify bo
[tls certificate](#certificate) options, Authelia will listen for TLS connections.
The key must be generated by the administrator and can be done by following the
-[Generating an RSA Self Signed Certificate](../miscellaneous/guides.md#generating-an-rsa-self-signed-certificate)
+[Generating an RSA Self Signed Certificate](../../reference/guides/generating-secure-values.md#generating-an-rsa-self-signed-certificate)
guide provided a self-signed certificate is fit for purpose. If a self-signed certificate is fit for purpose is beyond
the scope of the documentation and if it is not fit for purpose we instead recommend generating a certificate signing
request or obtaining a certificate signed by one of the many ACME certificate providers. Methods to achieve this are
@@ -161,11 +161,11 @@ or intermediate certificates. If no item is provided mutual TLS is disabled.
{{< confkey type="string" required="no" >}}
-This customizes the value of the Content-Security-Policy header. It will replace all instances of `${NONCE}` with the
-nonce value of the Authelia react bundle. This is an advanced option to customize and you should do sufficient research
-about how browsers utilize and understand this header before attempting to customize it.
+This customizes the value of the Content-Security-Policy header. It will replace all instances of the below placeholder
+with the nonce value of the Authelia react bundle. This is an advanced option to customize and you should do sufficient
+research about how browsers utilize and understand this header before attempting to customize it.
-For example, the default CSP template is `default-src 'self'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-${NONCE}'; frame-ancestors 'none'; base-uri 'self'`.
+{{< csp >}}
### buffers
diff --git a/docs/content/en/configuration/notifications/smtp.md b/docs/content/en/configuration/notifications/smtp.md
index c29dede50..511bb668b 100644
--- a/docs/content/en/configuration/notifications/smtp.md
+++ b/docs/content/en/configuration/notifications/smtp.md
@@ -31,11 +31,80 @@ notifier:
subject: "[Authelia] {title}"
startup_check_address: test@authelia.com
disable_require_tls: false
+ disable_starttls: false
disable_html_emails: false
tls:
server_name: smtp.example.com
skip_verify: false
minimum_version: TLS1.2
+ maximum_version: TLS1.3
+ certificate_chain: |
+ -----BEGIN CERTIFICATE-----
+ MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ qocikt3WAdU^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ DO NOT USE==
+ -----END RSA PRIVATE KEY-----
```
## Options
@@ -60,7 +129,7 @@ The port the SMTP service is listening on.
A connection is securely established with TLS after a succesful STARTTLS negotiation.
-[Port 465 is an exception][docs-security-smtp-port] when supported by the mail server as a `submissions` service port.
+[Port 465 is an exception][docs-security-smtp-port] when supported by the mail server as a `submissions` service port.
STARTTLS negotiation is not required for this port, the connection is implicitly established with TLS.
[docs-security-smtp-port]: ../../overview/security/measures.md#smtp-ports
@@ -87,7 +156,7 @@ especially for containerized deployments.*
The password paired with the [username](#username) sent for authentication with the SMTP server.
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters and the user password is changed to this value.
### sender
@@ -132,6 +201,18 @@ to leave this as is, but you can customize it if you have issues or you desire t
For security reasons the default settings for Authelia require the SMTP connection is encrypted by TLS. See [security]
for more information. This option disables this measure (not recommended).
+### disable_starttls
+
+{{< confkey type="boolean" default="false" required="no" >}}
+
+Some SMTP servers ignore SMTP specifications and claim to support STARTTLS when they in fact do not.
+For security reasons Authelia refuses to send messages to these servers.
+This option disables this measure and is enabled *__AT YOUR OWN RISK__*. It's *__strongly recommended__*
+that instead of enabling this option you either fix the issue with the SMTP server's configuration or
+have the administrators of the server fix it. If the issue can't be fixed by configuration we recommend
+lodging an issue with the authors of the SMTP server.
+See [security] for more information.
+
### disable_html_emails
{{< confkey type="boolean" default="false" required="no" >}}
diff --git a/docs/content/en/configuration/prologue/common.md b/docs/content/en/configuration/prologue/common.md
index fc3469331..6222e5448 100644
--- a/docs/content/en/configuration/prologue/common.md
+++ b/docs/content/en/configuration/prologue/common.md
@@ -129,10 +129,39 @@ instead you should tweak the `server_name` option, and the global option
{{< confkey type="string" default="TLS1.2" required="no" >}}
-The key `minimum_version` controls the minimum TLS version Authelia will use when opening TLS connections.
-The possible values are `TLS1.3`, `TLS1.2`, `TLS1.1`, `TLS1.0`. Anything other than `TLS1.3` or `TLS1.2`
+Controls the minimum TLS version Authelia will use when performing TLS handshakes.
+The possible values are `TLS1.3`, `TLS1.2`, `TLS1.1`, `TLS1.0`, `SSL3.0`. Anything other than `TLS1.3` or `TLS1.2`
are very old and deprecated. You should avoid using these and upgrade your backend service instead of decreasing
-this value.
+this value. At the time of this writing `SSL3.0` will always produce errors.
+
+### maximum_version
+
+{{< confkey type="string" default="TLS1.3" required="no" >}}
+
+Controls the maximum TLS version Authelia will use when performing TLS handshakes.
+The possible values are `TLS1.3`, `TLS1.2`, `TLS1.1`, `TLS1.0`, `SSL3.0`. Anything other than `TLS1.3` or `TLS1.2`
+are very old and deprecated. You should avoid using these and upgrade your backend service instead of decreasing
+this value. At the time of this writing `SSL3.0` will always produce errors.
+
+### certificate_chain
+
+{{< confkey type="string" required="no" >}}
+
+The certificate chain/bundle to be used with the [private_key](#private_key) to perform mutual TLS authentication with
+the server.
+
+The value must be one or more certificates encoded in the DER base64 ([RFC4648]) encoded PEM format.
+
+### private_key
+
+{{< confkey type="string" required="yes" >}}
+
+*__Important Note:__ This can also be defined using a [secret](../methods/secrets.md) which is __strongly recommended__
+especially for containerized deployments.*
+
+The private key to be used with the [certificate_chain](#certificate_chain) for mutual TLS authentication.
+
+The value must be one private key encoded in the DER base64 ([RFC4648]) encoded PEM format.
## Server Buffers
@@ -152,7 +181,7 @@ Configures the maximum response size. The default of 4096 is generally sufficien
### read
-{{< confkey type="duration" default="2s" required="no" >}}
+{{< confkey type="duration" default="6s" required="no" >}}
*__Note:__ This setting uses the [duration notation format](#duration-notation-format). Please see the
[common options](#duration-notation-format) documentation for information on this format.*
@@ -161,7 +190,7 @@ Configures the server read timeout.
### write
-{{< confkey type="duration" default="2s" required="no" >}}
+{{< confkey type="duration" default="6s" required="no" >}}
*__Note:__ This setting uses the [duration notation format](#duration-notation-format). Please see the
[common options](#duration-notation-format) documentation for information on this format.*
diff --git a/docs/content/en/configuration/security/access-control.md b/docs/content/en/configuration/security/access-control.md
index db0cc513d..4be5546a2 100644
--- a/docs/content/en/configuration/security/access-control.md
+++ b/docs/content/en/configuration/security/access-control.md
@@ -42,6 +42,17 @@ access_control:
- HEAD
resources:
- '^/api.*'
+ query:
+ - - operator: 'present'
+ key: 'secure'
+ - operator: 'absent'
+ key: 'insecure'
+ - - operator: 'pattern'
+ key: 'token'
+ value: '^(abc123|zyx789)$'
+ - operator: 'not pattern'
+ key: 'random'
+ value: '^(1|2)$'
```
## Options
@@ -51,11 +62,11 @@ access_control:
{{< confkey type="string" default="deny" required="no" >}}
The default [policy](#policies) defines the policy applied if no [rules](#rules) section apply to the information known
-about the request. It is recommended that this is configured to [deny](#deny) for security reasons. Sites which you do
+about the request. It is recommended that this is configured to [deny] for security reasons. Sites which you do
not wish to secure at all with Authelia should not be configured in your reverse proxy to perform authentication with
Authelia at all for performance reasons.
-See [Policies](#policies) for more information.
+See the [policies] section for more information.
### networks (global)
@@ -66,8 +77,8 @@ The main/global networks section contains a list of networks with a name label t
complicated network related configuration a lot cleaner and easier to read.
This section has two options, `name` and `networks`. Where the `networks` section is a list of IP addresses in CIDR
-notation and where `name` is a friendly name to label the collection of networks for reuse in the [networks](#networks)
-section of the [rules](#rules) section below.
+notation and where `name` is a friendly name to label the collection of networks for reuse in the [networks] section of
+the [rules] section below.
This configuration option *does nothing* by itself, it's only useful if you use these aliases in the [rules](#networks)
section below.
@@ -77,7 +88,7 @@ section below.
{{< confkey type="list" required="no" >}}
The rules have many configuration options. A rule matches when all criteria of the rule match the request excluding the
-`policy` which is the [policy](#policies) applied to the request.
+[policy] which is the [policy](#policies) applied to the request.
A rule defines two primary things:
@@ -86,29 +97,29 @@ A rule defines two primary things:
The criteria is broken into several parts:
-* [domain](#domain): domain or list of domains targeted by the request.
-* [domain_regex](#domain_regex): regex form of [domain](#domain).
-* [resources](#resources): pattern or list of patterns that the path should match.
-* [subject](#subject): the user or group of users to define the policy for.
-* [networks](#networks): the network addresses, ranges (CIDR notation) or groups from where the request originates.
-* [methods](#methods): the http methods used in the request.
+* [domain]: domain or list of domains targeted by the request.
+* [domain_regex]: regex form of [domain].
+* [resources]: pattern or list of patterns that the path should match.
+* [subject]: the user or group of users to define the policy for.
+* [networks]: the network addresses, ranges (CIDR notation) or groups from where the request originates.
+* [methods]: the http methods used in the request.
-A rule is matched when all criteria of the rule match. Rules are evaluated in sequential order, and the first rule that
-is a match for a given request is the rule applied; subsequent rules have *no effect*. This is particularly
-__important__ for bypass rules. Bypass rules should generally appear near the top of the rules list. However you need to
-carefully evaluate your rule list __in order__ to see which rule matches a particular scenario. A comprehensive
-understanding of how rules apply is also recommended.
+A rule is matched when all criteria of the rule match. Rules are evaluated in sequential order as per
+[Rule Matching Concept 1]. It's *__strongly recommended__* that individuals read the [Rule Matching](#rule-matching)
+section.
+
+[rules]: #rules
#### domain
{{< confkey type="list(string)" required="yes" >}}
-*__Required:__ This criteria and/or the [domain_regex](#domain_regex) criteria are required.*
+*__Required:__ This criteria and/or the [domain_regex] criteria are required.*
This criteria matches the domain name and has two methods of configuration, either as a single string or as a list of
strings. When it's a list of strings the rule matches when __any__ of the domains in the list match the request domain.
-When used in conjunction with [domain_regex](#domain_regex) the rule will match when either the [domain](#domain) or the
-[domain_regex](#domain_regex) criteria matches.
+When used in conjunction with [domain_regex] the rule will match when either the [domain] or the [domain_regex] criteria
+matches.
Rules may start with a few different wildcards:
@@ -117,9 +128,8 @@ Rules may start with a few different wildcards:
string __must__ be quoted like `"*.example.com"`.
* The user wildcard is `{user}.`, which when in front of a domain dynamically matches the username of the user. For
example `{user}.example.com` would match `fred.example.com` if the user logged in was named `fred`. *__Warning:__ this is
- officially deprecated as the [domain_regex](#domain_regex) criteria completely replaces the functionality in a much
- more useful way. It is strongly recommended you do not use this as it will be removed in a future version, most likely
- v5.0.0.*
+ officially deprecated as the [domain_regex] criteria completely replaces the functionality in a much more useful
+ way. It is strongly recommended you do not use this as it will be removed in a future version, most likely v5.0.0.*
* The group wildcard is `{group}.`, which when in front of a domain dynamically matches if the logged in user has the
group in that location. For example `{group}.example.com` would match `admins.example.com` if the user logged in was
in the following groups `admins,users,people` because `admins` is in the list.
@@ -129,6 +139,8 @@ or subdomains of that domain. This is because a website can only write cookies f
theoretically possible for us to do this with multiple domains however we would have to be security conscious in our
implementation, and it is not currently a priority.
+[domain]: #domain
+
##### Examples
*Single domain of `*.example.com` matched. All rules in this list are effectively the same rule just expressed in
@@ -158,7 +170,7 @@ access_control:
policy: bypass
```
-*Multiple domains matched either via a static domain or via a [domain_regex](#domain_regex). This rule will match
+*Multiple domains matched either via a static domain or via a [domain_regex]. This rule will match
either `apple.example.com`, `pub-data.example.com`, or `img-data.example.com`.*
```yaml
@@ -173,20 +185,20 @@ access_control:
{{< confkey type="list(string)" required="yes" >}}
-*__Required:__ This criteria and/or the [domain](#domain) criteria are required.*
+*__Required:__ This criteria and/or the [domain] criteria are required.*
-*__Important Note:__ If you intend to use this criteria with a bypass rule please read
-[bypass and subjects](#bypass-and-user-identity) before doing so.*
+*__Important Note:__ If you intend to use this criteria with a bypass rule please read [Rule Matching Concept 2].*
*__Important Note:__ to utilize regex you must escape it properly. See
[regular expressions](../prologue/common.md#regular-expressions) for more information.*
This criteria matches the domain name and has two methods of configuration, either as a single string or as a list of
strings. When it's a list of strings the rule matches when __any__ of the domains in the list match the request domain.
-When used in conjunction with [domain](#domain) the rule will match when either the [domain](#domain) or the
-[domain_regex](#domain_regex) criteria matches.
+When used in conjunction with [domain] the rule will match when either the [domain] or the [domain_regex] criteria matches.
-In addition to standard regex patterns this criteria can match some [Named Regex Groups](#named-regex-groups).
+In addition to standard regex patterns this criteria can match some [Named Regex Groups].
+
+[domain_regex]: #domain_regex
##### Examples
@@ -222,15 +234,14 @@ access_control:
The specific [policy](#policies) to apply to the selected rule. This is not criteria for a match, this is the action to
take when a match is made.
+[policy]: #policy
+
#### subject
{{< confkey type="list(list(string))" required="no" >}}
-*__Note:__ this rule criteria __may not__ be used for the `bypass` policy the minimum required authentication level to
-identify the subject is `one_factor`. We have taken an opinionated stance on preventing this configuration as it could
-result in problematic security scenarios with badly thought out configurations and cannot see a likely configuration
-scenario that would require users to do this. If you have a scenario in mind please open an
-[issue](https://github.com/authelia/authelia/issues/new) on GitHub.*
+*__Note:__ this rule criteria __may not__ be used for the [bypass] policy the minimum required authentication level to
+identify the subject is [one_factor]. See [Rule Matching Concept 2] for more information.*
This criteria matches identifying characteristics about the subject. Currently this is either user or groups the user
belongs to. This allows you to effectively control exactly what each user is authorized to access or to specifically
@@ -241,6 +252,8 @@ The format of this rule is unique in as much as it is a list of lists. The logic
`OR` and `AND` logic. The first level of the list defines the `OR` logic, and the second level defines the `AND` logic.
Additionally each level of these lists does not have to be explicitly defined.
+[subject]: #subject
+
##### Examples
*Matches when the user has the username `john`, __or__ the user is in the groups `admin` __and__ `app-name`, __or__ the
@@ -306,6 +319,8 @@ relevant methods are listed in this table:
| [RFC5789] | PATCH | [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) |
| [RFC4918] | PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK | |
+[methods]: #methods
+
##### Examples
*Bypass `OPTIONS` requests to the `example.com` domain.*
@@ -341,6 +356,8 @@ privileges when a user is on the local networks.
There are a large number of scenarios regarding networks and the order of the rules. This provides a lot of flexibility
for administrators to tune the security to their specific needs if desired.
+[networks]: #networks
+
##### Examples
*Require [two_factor](#two_factor) for all clients other than internal clients and `112.134.145.167`. The first two
@@ -394,6 +411,8 @@ It's important when configuring resource rules that you enclose them in quotes o
with escaping the expressions. Failure to do so may prevent Authelia from starting. It's technically optional but will
likely save you a lot of time if you do it for all resource rules.
+[resources]: #resources
+
##### Examples
*Applies the [bypass](#bypass) policy when the domain is `app.example.com` and the url is `/api`, or starts with either
@@ -408,59 +427,176 @@ access_control:
- '^/api([/?].*)?$'
```
+#### query
+
+{{< confkey type="list(list(object))" required="no" >}}
+
+The query criteria is an advanced criteria which can allow configuration of rules that match specific query argument
+keys against various rules. It's recommended to use [resources](#resources) rules instead for basic needs.
+
+The format of this rule is unique in as much as it is a list of lists. The logic behind this format is to allow for both
+`OR` and `AND` logic. The first level of the list defines the `OR` logic, and the second level defines the `AND` logic.
+Additionally each level of these lists does not have to be explicitly defined.
+
+##### key
+
+{{< confkey type="string" required="yes" >}}
+
+The query argument key to check.
+
+##### value
+
+{{< confkey type="string" required="situational" >}}
+
+The value to match against. This is required unless the operator is `absent` or `present`. It's recommended this value
+is always quoted as per the examples.
+
+##### operator
+
+{{< confkey type="string" required="situational" >}}
+
+The rule operator for this rule. Valid operators can be found in the
+[Rule Operators](../../reference/guides/rule-operators.md#operators) reference guide.
+
+If [key](#key) and [value](#value) are specified this defaults to `equal`, otherwise if [key](#key) is specified it
+defaults to `present`.
+
+
+##### Examples
+
+```yaml
+access_control:
+ rules:
+ - domain: app.example.com
+ policy: bypass
+ query:
+ - - operator: 'present'
+ key: 'secure'
+ - operator: 'absent'
+ key: 'insecure'
+ - - operator: 'pattern'
+ key: 'token'
+ value: '^(abc123|zyx789)$'
+ - operator: 'not pattern'
+ key: 'random'
+ value: '^(1|2)$'
+```
+
## Policies
The policy of the first matching rule in the configured list decides the policy applied to the request, if no rule
matches the request the [default_policy](#default_policy) is applied.
+[policies]: #policies
+
### deny
This is the policy applied by default, and is what we recommend as the default policy for all installs. Its effect
is literally to deny the user access to the resource. Additionally you can use this policy to conditionally deny
access in desired situations. Examples include denying access to an API that has no authentication mechanism built in.
+[deny]: #deny
+
### bypass
This policy skips all authentication and allows anyone to use the resource. This policy is not available with a rule
-that includes a [subject](#subject) restriction because the minimum authentication level required to obtain information
-about the subject is [one_factor](#one_factor).
+that includes a [subject] restriction because the minimum authentication level required to obtain information
+about the subject is [one_factor]. See [Rule Matching Concept 2] for more information.
-#### bypass and user identity
-
-The [bypass](#bypass) policy cannot be used when the rule uses a criteria that requires we know the users identity. This
-means:
-
-* If the rule defines [subjects](#subject) criteria
-* If the rule defines [domain regex](#domain_regex) criteria which contains either the user or group named match groups
-
-This is because these criteria types require knowing who the user is in order to determine if their identity matches the
-request. This information can only be known after 1FA, which means the minimum policy that can be used logically is
-[one_factor](#one_factor).
+[bypass]: #bypass
### one_factor
This policy requires the user at minimum complete 1FA successfully (username and password). This means if they have
performed 2FA then they will be allowed to access the resource.
+[one_factor]: #one_factor
+
### two_factor
This policy requires the user to complete 2FA successfully. This is currently the highest level of authentication
policy available.
+[two_factor]: #two_factor
+
+## Rule Matching
+
+There are two important concepts to understand when it comes to rule matching. This section covers these concepts.
+
+You can easily evaluate if your access control rules section matches a given request, and why it doesn't match using the
+[authelia access-control check-policy](../../reference/cli/authelia/authelia_access-control_check-policy.md) command.
+
+### Rule Matching Concept 1: Sequential Order
+
+Rules are matched in sequential order. The first entry in the list where all criteria match is the rule which is applied.
+Some rule criteria additionally allow for a list of criteria, when one of these criteria in the list match a request that
+criteria is considered a match for that specific rule.
+
+This is particularly __important__ for bypass rules. Bypass rules should generally appear near the top of the rules
+list. However you need to carefully evaluate your rule list __in order__ to see which rule matches a particular
+scenario. A comprehensive understanding of how rules apply is also recommended.
+
+For example the following rule will consider requests for either `example.com` or any subdomain of `example.com` a match
+if they have a path of exactly `/api` or if they start with `/api/`. This means that the second rule for
+`app.example.com` will not be considered if the request is to `https://app.example.com/api` because the first rule is
+a match for that request.
+
+```yaml
+- domains:
+ - 'example.com'
+ - '*.example.com'
+ policy: bypass
+ resources:
+ - '^/api$'
+ - '^/api/'
+- domains:
+ - 'app.example.com'
+ policy: two_factor
+```
+
+[Rule Matching Concept 1]: #rule-matching-concept-1-sequential-order
+
+### Rule Matching Concept 2: Subject Criteria Requires Authentication
+
+Rules that have subject reliant elements require authentication to determine if they match. Due to this these rules
+must not be used with the [bypass] policy. The criteria which have subject reliant elements are:
+
+* The [subject] criteria itself
+* The [domain_regex] criteria when it contains the [Named Regex Groups].
+
+In addition if the rule has a subject criteria but all other criteria match then the user will be immediately forwarded
+for authentication if no prior rules match the request per [Rule Matching Concept 1]. This means if you have two
+identical rules, and one of them has a subject based reliant criteria, and the other one is a [bypass] rule then the
+[bypass] rule should generally come first.
+
+[Rule Matching Concept 2]: #rule-matching-concept-2-subject-criteria-requires-authentication
+
## Named Regex Groups
Some criteria allow matching named regex groups. These are the groups we accept:
-| Group Name | Match Value |
-|:----------:|:-----------------:|
-| User | username |
-| Group | groups (contains) |
+| Group Name | Match Value | Match Type |
+|:----------:|:-----------:|:-----------:|
+| User | username | Equals |
+| Group | groups | Has (Equal) |
-For the group name `Group` the regex pattern matches if the user has the specific group name matching the pattern. Both
-regex groups are case-insensitive due to the fact that the regex groups are used in domain criteria and domain names
+Named regex groups are represented with the syntax `(?P\w+)` where `User` is the group name from the table above,
+and `\w+` is the pattern for the area of the pattern that should be compared to the match value.
+
+The match type `Equals` matches if the value extracted from the pattern is equal to the match value. The match type
+`Has (Equal)` matches if the value extracted from the pattern is equal to one of the values in the match value (the
+match value is a list/slice).
+
+The regex groups are case-insensitive due to the fact that the regex groups are used in domain criteria and domain names
should not be compared in a case-sensitive way as per the [RFC4343](https://www.rfc-editor.org/rfc/rfc4343.html)
abstract and [RFC3986 Section 3.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2).
+We do not currently apply any other normalization to usernames or groups when matching these groups. As such it's
+generally *__not recommended__* to use these patterns with usernames or groups which contain characters that are not
+alphanumeric (including spaces).
+
+[Named Regex Groups]: #named-regex-groups
+
## Detailed example
Here is a detailed example of an example access control section:
diff --git a/docs/content/en/configuration/session/introduction.md b/docs/content/en/configuration/session/introduction.md
index 494ab0832..6bc2a3baa 100644
--- a/docs/content/en/configuration/session/introduction.md
+++ b/docs/content/en/configuration/session/introduction.md
@@ -88,7 +88,7 @@ especially for containerized deployments.*
The secret key used to encrypt session data in Redis.
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters.
### expiration
diff --git a/docs/content/en/configuration/session/redis.md b/docs/content/en/configuration/session/redis.md
index dd9e2bbc5..fa6b9dd8a 100644
--- a/docs/content/en/configuration/session/redis.md
+++ b/docs/content/en/configuration/session/redis.md
@@ -34,6 +34,74 @@ session:
server_name: myredis.example.com
skip_verify: false
minimum_version: TLS1.2
+ maximum_version: TLS1.3
+ certificate_chain: |
+ -----BEGIN CERTIFICATE-----
+ MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ qocikt3WAdU^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ DO NOT USE==
+ -----END RSA PRIVATE KEY-----
high_availability:
sentinel_name: mysentinel
# If `sentinel_username` is supplied, Authelia will connect using ACL-based
@@ -86,7 +154,7 @@ especially for containerized deployments.*
The password for [redis authentication](https://redis.io/commands/auth).
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters and the user password is changed to this value.
### database_index
@@ -145,7 +213,7 @@ authenticate to the Redis Sentinel with ACL-based authentication. Otherwise, thi
authentication.
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters and the user password is changed to this value.
#### nodes
diff --git a/docs/content/en/configuration/storage/introduction.md b/docs/content/en/configuration/storage/introduction.md
index 516e41ce9..d2c09e830 100644
--- a/docs/content/en/configuration/storage/introduction.md
+++ b/docs/content/en/configuration/storage/introduction.md
@@ -44,10 +44,10 @@ value, and use that to encrypt the data with the AES-GCM 256bit algorithm.
The minimum length of this key is 20 characters.
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters.
-See [securty measures](../../overview/security/measures.md#storage-security-measures) for more information.
+See [security measures](../../overview/security/measures.md#storage-security-measures) for more information.
### postgres
diff --git a/docs/content/en/configuration/storage/migrations.md b/docs/content/en/configuration/storage/migrations.md
index b3cc68287..fbc84df83 100644
--- a/docs/content/en/configuration/storage/migrations.md
+++ b/docs/content/en/configuration/storage/migrations.md
@@ -34,3 +34,4 @@ this instance if you wanted to downgrade to pre1 you would need to use an Authel
| 3 | 4.34.2 | WebAuthn - fix V2 migration kid column length and provide migration path for anyone on V2 |
| 4 | 4.35.0 | Added OpenID Connect storage tables and opaque user identifier tables |
| 5 | 4.35.1 | Fixed the oauth2_consent_session table to accept NULL subjects for users who are not yet signed in |
+| 6 | 4.37.0 | Adjusted the OpenID Connect tables to allow pre-configured consent improvements |
diff --git a/docs/content/en/configuration/storage/mysql.md b/docs/content/en/configuration/storage/mysql.md
index a8ba2f64a..15aa648ee 100644
--- a/docs/content/en/configuration/storage/mysql.md
+++ b/docs/content/en/configuration/storage/mysql.md
@@ -17,12 +17,12 @@ aliases:
## Version support
-When using MySQL or MariaDB we recommend using the latest version that is officially supported by the MySQL or MariaDB
-developers. We also suggest checking out [PostgreSQL](postgres.md) as an alternative.
+When using [MySQL] or [MariaDB] we recommend using the latest version that is officially supported by the [MySQL] or
+[MariaDB] developers. We also suggest checking out [PostgreSQL](postgres.md) as an alternative.
-The oldest versions that have been tested are MySQL 5.7 and MariaDB 10.6.
+The oldest versions that have been tested are [MySQL] 5.7 and [MariaDB] 10.6.
-If using MySQL 5.7 or MariaDB 10.6 you may be required to adjust the `explicit_defaults_for_timestamp` setting. This
+If using [MySQL] 5.7 or [MariaDB] 10.6 you may be required to adjust the `explicit_defaults_for_timestamp` setting. This
will be evident when the container starts with an error similar to `Error 1067: Invalid default value for 'exp'`. You
can adjust this setting in the mysql.cnf file like so:
@@ -43,6 +43,78 @@ storage:
username: authelia
password: mypassword
timeout: 5s
+ tls:
+ server_name: mysql.example.com
+ skip_verify: false
+ minimum_version: TLS1.2
+ maximum_version: TLS1.3
+ certificate_chain: |
+ -----BEGIN CERTIFICATE-----
+ MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ qocikt3WAdU^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ DO NOT USE==
+ -----END RSA PRIVATE KEY-----
```
## Options
@@ -55,12 +127,22 @@ See the [encryption_key docs](introduction.md#encryption_key).
{{< confkey type="string" default="localhost" required="no" >}}
-The database server host.
+The database server host. This can also be a unix socket.
If utilising an IPv6 literal address it must be enclosed by square brackets and quoted:
```yaml
-host: "[fd00:1111:2222:3333::1]"
+storage:
+ mysql:
+ host: "[fd00:1111:2222:3333::1]"
+```
+
+If utilizing a unix socket it must have the `/` prefix:
+
+```yaml
+storage:
+ mysql:
+ host: /var/run/mysqld.sock
```
### port
@@ -92,7 +174,7 @@ especially for containerized deployments.*
The password paired with the [username](#username) used to connect to the database.
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters and the user password is changed to this value.
### timeout
@@ -100,3 +182,11 @@ characters and the user password is changed to this value.
{{< confkey type="duration" default="5s" required="no" >}}
The SQL connection timeout.
+
+### tls
+
+If defined enables connecting to [MySQL] or [MariaDB] over a TLS socket, and additionally controls the TLS connection
+validation process. You can see how to configure the tls section [here](../prologue/common.md#tls-configuration).
+
+[MySQL]: https://www.mysql.com/
+[MariaDB]: https://mariadb.org/
diff --git a/docs/content/en/configuration/storage/postgres.md b/docs/content/en/configuration/storage/postgres.md
index 3008c81fc..4c3adbc60 100644
--- a/docs/content/en/configuration/storage/postgres.md
+++ b/docs/content/en/configuration/storage/postgres.md
@@ -16,10 +16,10 @@ aliases:
## Version support
-See [PostgreSQL support](https://www.postgresql.org/support/versioning/) for the versions supported by PostgreSQL. We
-recommend the *current minor* version of one of the versions supported by PostgreSQL.
+See [PostgreSQL support](https://www.postgresql.org/support/versioning/) for the versions supported by [PostgreSQL]. We
+recommend the *current minor* version of one of the versions supported by [PostgreSQL].
-The versions of PostgreSQL that should be supported by Authelia are:
+The versions of [PostgreSQL] that should be supported by Authelia are:
* 14
* 13
@@ -40,11 +40,78 @@ storage:
schema: public
username: authelia
password: mypassword
- ssl:
- mode: disable
- root_certificate: /path/to/root_cert.pem
- certificate: /path/to/cert.pem
- key: /path/to/key.pem
+ tls:
+ server_name: psotgres.example.com
+ skip_verify: false
+ minimum_version: TLS1.2
+ maximum_version: TLS1.3
+ certificate_chain: |
+ -----BEGIN CERTIFICATE-----
+ MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ qocikt3WAdU^invalid DO NOT USE=
+ -----END CERTIFICATE-----
+ private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ DO NOT USE==
+ -----END RSA PRIVATE KEY-----
```
## Options
@@ -57,12 +124,22 @@ See the [encryption_key docs](introduction.md#encryption_key).
{{< confkey type="string" required="yes" >}}
-The database server host.
+The database server host. This can also be a unix socket.
If utilising an IPv6 literal address it must be enclosed by square brackets and quoted:
```yaml
-host: "[fd00:1111:2222:3333::1]"
+storage:
+ postgres:
+ host: "[fd00:1111:2222:3333::1]"
+```
+
+If utilizing a unix socket it must have the `/` prefix:
+
+```yaml
+storage:
+ postgres:
+ host: /var/run/postgres.sock
```
### port
@@ -101,7 +178,7 @@ especially for containerized deployments.*
The password paired with the [username](#username) used to connect to the database.
It's __strongly recommended__ this is a
-[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
+[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters and the user password is changed to this value.
### timeout
@@ -110,32 +187,9 @@ characters and the user password is changed to this value.
The SQL connection timeout.
-### ssl
+### tls
-#### mode
+If defined enables connecting to [PostgreSQL] over a TLS socket, and additionally controls the TLS connection
+validation process. You can see how to configure the tls section [here](../prologue/common.md#tls-configuration).
-{{< confkey type="string" default="disable" required="no" >}}
-
-SSL mode configures how to handle SSL connections with Postgres.
-Valid options are 'disable', 'require', 'verify-ca', or 'verify-full'.
-See the [PostgreSQL Documentation](https://www.postgresql.org/docs/12/libpq-ssl.html)
-or [pgx - PostgreSQL Driver and Toolkit Documentation](https://pkg.go.dev/github.com/jackc/pgx?tab=doc)
-for more information.
-
-#### root_certificate
-
-{{< confkey type="string" required="no" >}}
-
-The optional location of the root certificate file encoded in the PEM format for validation purposes.
-
-#### certificate
-
-{{< confkey type="string" required="no" >}}
-
-The optional location of the certificate file encoded in the PEM format for validation purposes.
-
-#### key
-
-{{< confkey type="string" required="no" >}}
-
-The optional location of the key file encoded in the PEM format for authentication purposes.
+[PostgreSQL]: https://www.postgresql.org/
diff --git a/docs/content/en/configuration/telemetry/metrics.md b/docs/content/en/configuration/telemetry/metrics.md
index 42c209082..4917637e1 100644
--- a/docs/content/en/configuration/telemetry/metrics.md
+++ b/docs/content/en/configuration/telemetry/metrics.md
@@ -25,8 +25,8 @@ telemetry:
read: 4096
write: 4096
timeouts:
- read: 2s
- write: 2s
+ read: 6s
+ write: 6s
idle: 30s
```
diff --git a/docs/content/en/contributing/guidelines/_index.md b/docs/content/en/contributing/guidelines/_index.md
new file mode 100644
index 000000000..21c0642c5
--- /dev/null
+++ b/docs/content/en/contributing/guidelines/_index.md
@@ -0,0 +1,9 @@
+---
+title: "Guidelines"
+description: "Contributing Guidelines"
+lead: ""
+date: 2022-06-15T17:51:47+10:00
+draft: false
+images: []
+weight: 300
+---
diff --git a/docs/content/en/contributing/development/guidelines-commit-message.md b/docs/content/en/contributing/guidelines/commit-message.md
similarity index 86%
rename from docs/content/en/contributing/development/guidelines-commit-message.md
rename to docs/content/en/contributing/guidelines/commit-message.md
index 45b3e6408..8683eb194 100644
--- a/docs/content/en/contributing/development/guidelines-commit-message.md
+++ b/docs/content/en/contributing/guidelines/commit-message.md
@@ -1,5 +1,5 @@
---
-title: "Commit Message Guidelines"
+title: "Commit Message"
description: "Authelia Development Commit Message Guidelines"
lead: "This section covers the git commit message guidelines we use for development."
date: 2021-01-30T19:29:07+11:00
@@ -7,11 +7,12 @@ draft: false
images: []
menu:
contributing:
- parent: "development"
-weight: 231
+ parent: "guidelines"
+weight: 320
toc: true
aliases:
- /docs/contributing/commitmsg-guidelines.html
+ - /contributing/development/guidelines-commit-message/
---
The reasons for these conventions are as follows:
@@ -47,11 +48,13 @@ for, and the structure it must have.
│ │ │
│ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
│ │
- │ └─⫸ Commit Scope: api|authentication|authorization|cmd|commands|configuration|duo|
- │ handlers|logging|middlewares|mocks|model|notification|ntp|oidc|
- │ regulation|server|session|storage|suites|templates|utils|web
+ │ └─⫸ Commit Scope: api|autheliabot|authentication|authorization|buildkite|bundler|cmd|
+ │ codecov|commands|configuration|deps|docker|duo|go|golangci-lint|
+ │ handlers|logging|metrics|middlewares|mocks|model|notification|npm|ntp|
+ │ oidc|regulation|renovate|reviewdog|server|session|storage|suites|
+ │ templates|totp|utils|web
│
- └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|release|test
+ └─⫸ Commit Type: build|ci|docs|feat|fix|i18n|perf|refactor|release|revert|test
```
The `` and `` fields are mandatory, the `()` field is optional.
@@ -65,6 +68,7 @@ The `` and `` fields are mandatory, the `()` field is opti
* __docs__ Documentation only changes
* __feat__ A new feature
* __fix__ A bug fix
+* __i18n__ Updating translations or internationalization settings
* __perf__ A code change that improves performance
* __refactor__ A code change that neither fixes a bug nor adds a feature
* __release__ Releasing a new version of Authelia
@@ -101,7 +105,7 @@ commit messages).
There are currently a few exceptions to the "use package name" rule:
* `api`: used for changes that change the openapi specification
-* `cmd`: used for changes to the `authelia|authelia-scripts|authelia-suites` top level binaries
+* `cmd`: used for changes to the `authelia|authelia-gen|authelia-scripts|authelia-suites` top level binaries
* `web`: used for changes to the React based frontend
* none/empty string: useful for `test`, `refactor` and changes that are done across multiple packages
(e.g. `test: add missing unit tests`) and for docs changes that are not related to a specific package
diff --git a/docs/content/en/contributing/guidelines/documentation.md b/docs/content/en/contributing/guidelines/documentation.md
new file mode 100644
index 000000000..5d22f567f
--- /dev/null
+++ b/docs/content/en/contributing/guidelines/documentation.md
@@ -0,0 +1,32 @@
+---
+title: "Documentation"
+description: "Authelia Development Documentation Guidelines"
+lead: "This section covers the guidelines we use when writing documentation."
+date: 2022-10-02T14:32:16+11:00
+draft: false
+images: []
+menu:
+ contributing:
+ parent: "guidelines"
+weight: 320
+toc: true
+---
+
+## Domains
+
+Always use the generic domain (or subdomain of) `example.com` in documentation.
+
+If it's necessary to utilize more than one domain please ask for specific feedback in any PR.
+
+## Certificates
+
+When including certificates in documentation always ensure they are valid for 1 year starting at `Jan 1 00:00:00 1970`.
+This ensures the certificate is not valid for multiple reasons.
+
+In addition the guidance for [Private Keys](#private-keys) should be followed.
+
+## Private Keys
+
+Always append invalid data to the END of the PEM block before the base64 padding `=` (if present). The suggested
+text is `^invalid DO NOT USE`. This both has an invalid base64 character `^` and has information to communicate that
+users should not use the PEM block.
diff --git a/docs/content/en/contributing/guidelines/introduction.md b/docs/content/en/contributing/guidelines/introduction.md
new file mode 100644
index 000000000..293d8a097
--- /dev/null
+++ b/docs/content/en/contributing/guidelines/introduction.md
@@ -0,0 +1,21 @@
+---
+title: "Guidelines"
+description: "An introduction into guidelines for contributing to the Authelia project."
+lead: "An introduction into guidelines for contributing to the Authelia project."
+date: 2022-10-02T14:32:16+11:00
+draft: false
+images: []
+menu:
+ contributing:
+ parent: "guidelines"
+weight: 310
+toc: true
+---
+
+The guidelines section contains various guidelines for contributing to Authelia. We implement various guidelines via
+automatic processes that will provide feedback in the PR, but this does not cover every situation. You will find both
+those which are automated and those which are not in this section.
+
+While it's expected that people aim to follow all of these guidelines we understand that there are logical exceptions to
+all guidelines and if it makes sense we're likely to agree with you. So if you find a situation where it doesn't make
+sense to follow one just let us know your reasoning when you make a PR if it's not obvious.
diff --git a/docs/content/en/contributing/development/guidelines-pull-request.md b/docs/content/en/contributing/guidelines/pull-request.md
similarity index 89%
rename from docs/content/en/contributing/development/guidelines-pull-request.md
rename to docs/content/en/contributing/guidelines/pull-request.md
index cd0e67be6..d3f2830ab 100644
--- a/docs/content/en/contributing/development/guidelines-pull-request.md
+++ b/docs/content/en/contributing/guidelines/pull-request.md
@@ -1,5 +1,5 @@
---
-title: "Pull Request Guidelines"
+title: "Pull Request"
description: "Authelia Development Pull Request Guidelines"
lead: "This section covers the pull request guidelines."
date: 2022-06-15T17:51:47+10:00
@@ -7,9 +7,11 @@ draft: false
images: []
menu:
contributing:
- parent: "development"
-weight: 232
+ parent: "guidelines"
+weight: 320
toc: true
+aliases:
+ - /contributing/development/guidelines-pull-request/
---
[Pull Request] guidelines are in place in order to maintain consistency and clearly communicate our process for
diff --git a/docs/content/en/contributing/development/guidelines-style.md b/docs/content/en/contributing/guidelines/style.md
similarity index 97%
rename from docs/content/en/contributing/development/guidelines-style.md
rename to docs/content/en/contributing/guidelines/style.md
index a990cddcb..9d559985b 100644
--- a/docs/content/en/contributing/development/guidelines-style.md
+++ b/docs/content/en/contributing/guidelines/style.md
@@ -1,5 +1,5 @@
---
-title: "Style Guidelines"
+title: "Style"
description: "Authelia Development Style Guidelines"
lead: "This section covers the style guidelines we use for development."
date: 2021-04-11T21:25:03+10:00
@@ -7,17 +7,18 @@ draft: false
images: []
menu:
contributing:
- parent: "development"
-weight: 230
+ parent: "guidelines"
+weight: 320
toc: true
aliases:
- /docs/contributing/style-guide.html
+ - /contributing/development/guidelines-style/
---
This is a general guide to the code style we aim to abide by. This is by no means an exhaustive list and we're
constantly changing and improving it. This is also a work in progress document.
-For our commit messages please see our [Commit Message Guidelines](guidelines-commit-message.md).
+For our commit messages please see our [Commit Message Guidelines](../guidelines/commit-message.md).
## Tools
diff --git a/docs/content/en/contributing/prologue/documentation.md b/docs/content/en/contributing/prologue/documentation-contributions.md
similarity index 94%
rename from docs/content/en/contributing/prologue/documentation.md
rename to docs/content/en/contributing/prologue/documentation-contributions.md
index 8053eef7d..d07bdbffb 100644
--- a/docs/content/en/contributing/prologue/documentation.md
+++ b/docs/content/en/contributing/prologue/documentation-contributions.md
@@ -1,5 +1,5 @@
---
-title: "Documentation"
+title: "Documentation Contributions"
description: "Information on contributing documentation to the Authelia project."
lead: "Authelia has great documentation however there are always things that can be added. This section describes the contribution process for the documentation even though it's incredibly easy."
date: 2022-06-15T17:51:47+10:00
@@ -10,6 +10,8 @@ menu:
parent: "prologue"
weight: 130
toc: true
+alias:
+ - /contributing/prologue/documentation
---
## Introduction
@@ -38,14 +40,12 @@ It's relatively easy to run the __Authelia__ website locally to test out the cha
The following steps will allow you to run the website on the localhost and view it live in your browser:
1. Run the following commands:
-
- ```bash
- git clone https://github.com/authelia/authelia.git
- cd authelia/docs
- npm run install
- npm run start
- ```
-
+ ```bash
+ git clone https://github.com/authelia/authelia.git
+ cd authelia/docs
+ npm run install
+ npm run start
+ ```
2. Visit [http://localhost:1313/](http://localhost:1313/) in your browser.
3. Modify pages to see the effects live in your browser.
diff --git a/docs/content/en/contributing/prologue/introduction.md b/docs/content/en/contributing/prologue/introduction.md
index 7de7c4aee..b6ca82b8c 100644
--- a/docs/content/en/contributing/prologue/introduction.md
+++ b/docs/content/en/contributing/prologue/introduction.md
@@ -18,7 +18,7 @@ Contributions to __Authelia__ have four main forms:
* Utilizing GitHub [Issues] or [Discussions] to make commentary about bugs, ideas, etc.
* [Development](../development/introduction.md)
-* [Documentation](../documentation/#introduction)
+* [Documentation](documentation-contributions.md)
* [Financial](financial.md)
We encourage our community to be part of __Authelia__ by contributing in these ways. We encourage anyone who wishes to
diff --git a/docs/content/en/integration/kubernetes/istio.md b/docs/content/en/integration/kubernetes/istio.md
new file mode 100644
index 000000000..9a0d162f7
--- /dev/null
+++ b/docs/content/en/integration/kubernetes/istio.md
@@ -0,0 +1,93 @@
+---
+title: "Istio"
+description: "A guide to integrating Authelia with the Istio Kubernetes Ingress."
+lead: "A guide to integrating Authelia with the Istio Kubernetes Ingress."
+date: 2022-10-02T13:59:09+11:00
+draft: false
+images: []
+menu:
+integration:
+parent: "kubernetes"
+weight: 551
+toc: true
+---
+
+Istio uses [Envoy](../proxies/envoy.md) as an Ingress. This means it has a relatively comprehensive integration option.
+Istio is supported with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter.
+
+[external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz
+
+## Example
+
+This example assumes that you have deployed an Authelia pod and you have configured it to be served on the URL
+`https://auth.example.com` and there is a Kubernetes Service with the name `authelia` in the `default` namespace with
+TCP port `80` configured to route to the Authelia pod's HTTP port and that your cluster is configured with the default
+DNS domain name of `cluster.local`.
+
+### Operator
+
+This is an example IstioOperator manifest adjusted to authenticate with Authelia. This example only shows the necessary
+portions of the resource that you add as well as context. You will need to adapt it to your needs.
+
+```yaml
+apiVersion: install.istio.io/v1alpha1
+kind: IstioOperator
+spec:
+ meshConfig:
+ extensionProviders:
+ - name: 'authelia'
+ envoyExtAuthzHttp:
+ service: 'authelia.default.svc.cluster.local'
+ port: 80
+ pathPrefix: '/api/verify/'
+ includeRequestHeadersInCheck:
+ - accept
+ - cookie
+ - proxy-authorization
+ headersToUpstreamOnAllow:
+ - 'authorization'
+ - 'proxy-authorization'
+ - 'remote-*'
+ - 'authelia-*'
+ includeAdditionalHeadersInCheck:
+ X-Authelia-URL: 'https://auth.example.com/'
+ X-Forwarded-Method: '%REQ(:METHOD)%'
+ X-Forwarded-Proto: '%REQ(:SCHEME)%'
+ X-Forwarded-Host: '%REQ(:AUTHORITY)%'
+ X-Forwarded-URI: '%REQ(:PATH)%'
+ X-Forwarded-For: '%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%'
+ headersToDownstreamOnDeny:
+ - set-cookie
+ headersToDownstreamOnAllow:
+ - set-cookie
+```
+
+### Authorization Policy
+
+The following [Authorization Policy] applies the above filter extension provider to the `nextcloud.example.com` domain:
+
+```yaml
+apiVersion: security.istio.io/v1beta1
+kind: AuthorizationPolicy
+metadata:
+ name: nextcloud
+ namespace: apps
+spec:
+ action: CUSTOM
+ provider:
+ name: 'authelia'
+ rules:
+ - to:
+ - operation:
+ hosts:
+ - 'nextcloud.example.com'
+```
+
+## See Also
+
+- Istio [External Authentication](https://istio.io/latest/docs/tasks/security/authorization/authz-custom/) Documentation
+- Istio [Authorization Policy] Documentation
+- Istio [IstioOperator Options](https://istio.io/latest/docs/reference/config/istio.operator.v1alpha1/) Documentation
+- Istio [MeshConfig Extension Provider EnvoyExtAuthz HTTP Provider](https://istio.io/latest/docs/reference/config/istio.mesh.v1alpha1/#MeshConfig-ExtensionProvider-EnvoyExternalAuthorizationHttpProvider) Documentation
+
+[Authorization Policy]: https://istio.io/latest/docs/reference/config/security/authorization-policy/
diff --git a/docs/content/en/integration/kubernetes/nginx-ingress.md b/docs/content/en/integration/kubernetes/nginx-ingress.md
index c50f74f0d..614626e60 100644
--- a/docs/content/en/integration/kubernetes/nginx-ingress.md
+++ b/docs/content/en/integration/kubernetes/nginx-ingress.md
@@ -8,7 +8,7 @@ images: []
menu:
integration:
parent: "kubernetes"
-weight: 551
+weight: 552
toc: true
---
diff --git a/docs/content/en/integration/ldap/introduction.md b/docs/content/en/integration/ldap/introduction.md
index 3d20552f3..596caa125 100644
--- a/docs/content/en/integration/ldap/introduction.md
+++ b/docs/content/en/integration/ldap/introduction.md
@@ -14,4 +14,151 @@ toc: true
## UNDER CONSTRUCTION
-This section of the documentation is under construction.
+This section is still a work in progress.
+
+## Configuration
+
+### OpenLDAP
+
+**Tested:**
+* Version: [v2.5.13](https://www.openldap.org/software/release/announce_lts.html)
+* Container `bitnami/openldap:2.5.13-debian-11-r7`
+
+Create within OpenLDAP, either via CLI or with a GUI management application like
+[phpLDAPadmin](http://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page) or [LDAP Admin](http://www.ldapadmin.org/)
+a basic user with a complex password.
+
+*Make note of its CN.* You can also create a group to use within Authelia if you would like granular control of who can
+login, and reference it within the filters below.
+
+### Authelia
+
+In your Authelia configuration you will need to enter and update the following variables -
+* url `ldap://OpenLDAP:1389` - servers dns name & port.
+ *tip: if you have Authelia on a container network that is routable, you can just use the container name*
+* server_name `ldap01.example.com` - servers name
+* base_dn `DC=example,DC=com` - common name of domain root.
+* groups_filter `DC=example,DC=com` - replace relevant section with your own domain in common name format, same as base_dn.
+* user `authelia` - username for Authelia service account
+* password `SUPER_COMPLEX_PASSWORD` - password for Authelia service account
+
+```yaml
+ ldap:
+ implementation: custom
+ url: ldap://OpenLDAP:1389
+ timeout: 5s
+ start_tls: false
+ tls:
+ server_name: ldap01.example.com
+ skip_verify: true
+ minimum_version: TLS1.2
+ base_dn: DC=example,DC=com
+ additional_users_dn: OU=users
+ users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
+ username_attribute: uid
+ mail_attribute: mail
+ display_name_attribute: displayName
+ additional_groups_dn: OU=groups
+ groups_filter: (&(member=UID={input},OU=users,DC=example,DC=com)(objectClass=groupOfNames))
+ group_name_attribute: cn
+ user: UID=authelia,OU=service accounts,DC=example,DC=com
+ password: "SUPER_COMPLEX_PASSWORD"
+```
+Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
+Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
+
+### FreeIPA
+
+**Tested:**
+* Version: [v4.9.9](https://www.freeipa.org/page/Releases/4.9.9)
+* Container: `freeipa/freeipa-server:fedora-36-4.9.9`
+
+Create within FreeIPA, either via CLI or within its GUI management application `https://server_ip` a basic user with a
+complex password.
+
+*Make note of its CN.* You can also create a group to use within Authelia if you would like granular control of who can
+login, and reference it within the filters below.
+
+### Authelia
+
+In your Authelia configuration you will need to enter and update the following variables -
+* url `ldap://ldap` - servers dns name. Port will assume 389 as standard. Specify custom port with `:port` if needed.
+* server_name `ldap01.example.com` - servers name
+* base_dn `DC=example,DC=com` - common name of domain root.
+* groups_filter `DC=example,DC=com` - replace relevant section with your own domain in common name format, same as base_dn.
+* user `authelia` - username for Authelia service account
+* password `SUPER_COMPLEX_PASSWORD` - password for Authelia service account
+
+```yaml
+ ldap:
+ implementation: custom
+ url: ldaps://ldap.example.com
+ timeout: 5s
+ start_tls: false
+ tls:
+ server_name: ldap.example.com
+ skip_verify: true
+ minimum_version: TLS1.2
+ base_dn: dc=example,DC=com
+ username_attribute: uid
+ additional_users_dn: CN=users,CN=accounts
+ users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
+ additional_groups_dn: OU=groups
+ groups_filter: (&(member=UID={input},CN=users,CN=accounts,DC=example,DC=com)(objectClass=groupOfNames))
+ group_name_attribute: cn
+ mail_attribute: mail
+ display_name_attribute: displayName
+ user: UID=authelia,CN=users,CN=accounts,DC=example,DC=com
+ password: "SUPER_COMPLEX_PASSWORD"
+```
+Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
+Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
+
+### lldap
+
+**Tested:**
+* Version: [v0.4.0](https://github.com/nitnelave/lldap/releases/tag/v0.4.07)
+
+Create within lldap, a basic user with a complex password, and add to the group "lldap_password_manager"
+You can also create a group to use within Authelia if you would like granular control of who can login, and reference it
+within the filters below.
+
+### Authelia
+
+In your Authelia configuration you will need to enter and update the following variables -
+* url `ldap://OpenLDAP:1389` - servers dns name & port.
+ *tip: if you have Authelia on a container network that is routable, you can just use the container name*
+* base_dn `DC=example,DC=com` - common name of domain root.
+* user `authelia` - username for Authelia service account.
+* password `SUPER_COMPLEX_PASSWORD` - password for Authelia service account,
+
+```yaml
+ldap:
+ implementation: custom
+ url: ldap://lldap:3890
+ timeout: 5s
+ start_tls: false
+ base_dn: dc=example,DC=com
+ username_attribute: uid
+ additional_users_dn: OU=people
+ # To allow sign in both with username and email, one can use a filter like
+ # (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
+ users_filter: (&({username_attribute}={input})(objectClass=person))
+ additional_groups_dn: OU=groups
+ groups_filter: (member={dn})
+ group_name_attribute: cn
+ mail_attribute: mail
+ display_name_attribute: displayName
+ # The username and password of the admin or service user.
+ user: UID=authelia,OU=people,DC=example,DC=com
+ password: "SUPER_COMPLEX_PASSWORD"
+```
+Following this, restart Authelia, and you should be able to begin using lldap integration for your user logins, with
+Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
+
+## See Also
+
+[Authelia]: https://www.authelia.com
+[Bitnami OpenLDAP]: https://hub.docker.com/r/bitnami/openldap/
+[FreeIPA]: https://www.freeipa.org/page/Main_Page
+[lldap]: https://github.com/nitnelave/lldap
diff --git a/docs/content/en/integration/openid-connect/apache-guacamole/index.md b/docs/content/en/integration/openid-connect/apache-guacamole/index.md
index 26bd069d0..8ff5bd875 100644
--- a/docs/content/en/integration/openid-connect/apache-guacamole/index.md
+++ b/docs/content/en/integration/openid-connect/apache-guacamole/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -59,7 +66,7 @@ The following YAML configuration is an example __Authelia__
```yaml
- id: guacamole
description: Apache Guacamole
- secret: guacamole_client_secret
+ secret: '$plaintext$guacamole_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/argocd/index.md b/docs/content/en/integration/openid-connect/argocd/index.md
index 64a3b7bc2..ecc8efca1 100644
--- a/docs/content/en/integration/openid-connect/argocd/index.md
+++ b/docs/content/en/integration/openid-connect/argocd/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -62,7 +69,7 @@ which will operate with the above example:
```yaml
- id: argocd
description: Argo CD
- secret: argocd_client_secret
+ secret: '$plaintext$argocd_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/bookstack/index.md b/docs/content/en/integration/openid-connect/bookstack/index.md
index c26e341c5..e972dc209 100644
--- a/docs/content/en/integration/openid-connect/bookstack/index.md
+++ b/docs/content/en/integration/openid-connect/bookstack/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -33,6 +40,12 @@ This example makes the following assumptions:
* __Client ID:__ `bookstack`
* __Client Secret:__ `bookstack_client_secret`
+*__Important Note:__ [BookStack] does not properly URL encode the secret per [RFC6749 Appendix B] at the time this
+article was last modified (noted at the bottom). This means you'll either have to use only alphanumeric characters for
+the secret or URL encode the secret yourself.*
+
+[RFC6749 Appendix B]: https://www.rfc-editor.org/rfc/rfc6749#appendix-B
+
## Configuration
### Application
@@ -58,7 +71,7 @@ which will operate with the above example:
```yaml
- id: bookstack
description: BookStack
- secret: bookstack_client_secret
+ secret: '$plaintext$bookstack_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/cloudflare-zerotrust/index.md b/docs/content/en/integration/openid-connect/cloudflare-zerotrust/index.md
index 1193f7b8f..54b6838e1 100644
--- a/docs/content/en/integration/openid-connect/cloudflare-zerotrust/index.md
+++ b/docs/content/en/integration/openid-connect/cloudflare-zerotrust/index.md
@@ -20,9 +20,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -31,8 +38,11 @@ This example makes the following assumptions:
* __Client ID:__ `cloudflare`
* __Client Secret:__ `cloudflare_client_secret`
-*__Important Note:__ Cloudflare does not properly URL encode the secret. This means you'll either have to use
-only alphanumeric characters for the secret or URL encode it yourself.*
+*__Important Note:__ [Cloudflare Zero Trust] does not properly URL encode the secret per [RFC6749 Appendix B] at the
+time this article was last modified (noted at the bottom). This means you'll either have to use only alphanumeric
+characters for the secret or URL encode the secret yourself.*
+
+[RFC6749 Appendix B]: https://www.rfc-editor.org/rfc/rfc6749#appendix-B
## Configuration
@@ -69,7 +79,7 @@ which will operate with the above example:
```yaml
- id: cloudflare
description: Cloudflare ZeroTrust
- secret: cloudflare_client_secret
+ secret: '$plaintext$cloudflare_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/gitea/index.md b/docs/content/en/integration/openid-connect/gitea/index.md
index 3c395c414..86e3a96d5 100644
--- a/docs/content/en/integration/openid-connect/gitea/index.md
+++ b/docs/content/en/integration/openid-connect/gitea/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -79,7 +86,7 @@ will operate with the above example:
```yaml
- id: gitea
description: Gitea
- secret: gitea_client_secret
+ secret: '$plaintext$gitea_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/gitlab/index.md b/docs/content/en/integration/openid-connect/gitlab/index.md
index 3c3beaf2a..2719fdb87 100644
--- a/docs/content/en/integration/openid-connect/gitlab/index.md
+++ b/docs/content/en/integration/openid-connect/gitlab/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -75,7 +82,7 @@ which will operate with the above example:
```yaml
- id: gitlab
description: GitLab
- secret: gitlab_client_secret
+ secret: '$plaintext$gitlab_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/grafana/index.md b/docs/content/en/integration/openid-connect/grafana/index.md
index a0fe50571..4359be920 100644
--- a/docs/content/en/integration/openid-connect/grafana/index.md
+++ b/docs/content/en/integration/openid-connect/grafana/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -93,7 +100,7 @@ which will operate with the above example:
```yaml
- id: grafana
description: Grafana
- secret: grafana_client_secret
+ secret: '$plaintext$grafana_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/harbor/index.md b/docs/content/en/integration/openid-connect/harbor/index.md
index 2b32ca0ff..782861f4b 100644
--- a/docs/content/en/integration/openid-connect/harbor/index.md
+++ b/docs/content/en/integration/openid-connect/harbor/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -66,7 +73,7 @@ which will operate with the above example:
```yaml
- id: harbor
description: Harbor
- secret: harbor_client_secret
+ secret: '$plaintext$harbor_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/hashicorp-vault/index.md b/docs/content/en/integration/openid-connect/hashicorp-vault/index.md
index dd0a53c3b..572070354 100644
--- a/docs/content/en/integration/openid-connect/hashicorp-vault/index.md
+++ b/docs/content/en/integration/openid-connect/hashicorp-vault/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -49,7 +56,7 @@ which will operate with the above example:
```yaml
- id: vault
description: HashiCorp Vault
- secret: vault_client_secret
+ secret: '$plaintext$vault_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/komga/index.md b/docs/content/en/integration/openid-connect/komga/index.md
index 1e9ff3911..93ca8cdd9 100644
--- a/docs/content/en/integration/openid-connect/komga/index.md
+++ b/docs/content/en/integration/openid-connect/komga/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -71,14 +78,14 @@ which will operate with the above example:
```yaml
- id: komga
description: Komga
- secret: komga_client_secret
+ secret: '$plaintext$komga_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
- https://komga.example.com/login/oauth2/code/authelia
scopes:
- openid
- - preferred_username
+ - profile
- email
grant_types:
- authorization_code
diff --git a/docs/content/en/integration/openid-connect/nextcloud/index.md b/docs/content/en/integration/openid-connect/nextcloud/index.md
index dbbf81710..f9d577fb5 100644
--- a/docs/content/en/integration/openid-connect/nextcloud/index.md
+++ b/docs/content/en/integration/openid-connect/nextcloud/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -87,7 +94,7 @@ which will operate with the above example:
```yaml
- id: nextcloud
description: NextCloud
- secret: nextcloud_client_secret
+ secret: '$plaintext$nextcloud_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/outline/index.md b/docs/content/en/integration/openid-connect/outline/index.md
index be37bde99..6ddcccee5 100644
--- a/docs/content/en/integration/openid-connect/outline/index.md
+++ b/docs/content/en/integration/openid-connect/outline/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -66,7 +73,7 @@ which will operate with the above example:
```yaml
- id: outline
description: Outline
- secret: outline_client_secret
+ secret: '$plaintext$outline_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/portainer/index.md b/docs/content/en/integration/openid-connect/portainer/index.md
index 7e9c6afd4..fa35c9829 100644
--- a/docs/content/en/integration/openid-connect/portainer/index.md
+++ b/docs/content/en/integration/openid-connect/portainer/index.md
@@ -24,9 +24,16 @@ aliases:
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -67,7 +74,7 @@ which will operate with the above example:
```yaml
- id: portainer
description: Portainer
- secret: portainer_client_secret
+ secret: '$plaintext$portainer_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/proxmox/index.md b/docs/content/en/integration/openid-connect/proxmox/index.md
index eee1d305a..a0db22e60 100644
--- a/docs/content/en/integration/openid-connect/proxmox/index.md
+++ b/docs/content/en/integration/openid-connect/proxmox/index.md
@@ -22,11 +22,21 @@ aliases:
* [Proxmox]
* 7.1-10
-## Before You Begin
+### Common Notes
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Specific Notes
+
+*__Important Note:__ [Proxmox] requires you create the Realm prior to adding the provider. This is not covered in this
+guide.*
+
+### Assumptions
This example makes the following assumptions:
@@ -34,6 +44,7 @@ This example makes the following assumptions:
* __Authelia Root URL:__ `https://auth.example.com`
* __Client ID:__ `proxmox`
* __Client Secret:__ `proxmox_client_secret`
+* __Realm__ `authelia`
## Configuration
@@ -47,14 +58,14 @@ To configure [Proxmox] to utilize Authelia as an [OpenID Connect] Provider:
4. Add an OpenID Connect Server
5. Set the following values:
1. Issuer URL: `https://auth.example.com`
- 2. Realm: anything you wish
+ 2. Realm: `authelia`
3. Client ID: `proxmox`
4. Client Key: `proxmox_client_secret`
5. Username Claim `preferred_username`
6. Scopes: `openid profile email`
7. Enable *Autocreate Users* if you want users to automatically be created in [Proxmox].
-{{< figure src="proxmox.gif" alt="Proxmox" width="736" style="padding-right: 10px" >}}
+{{< figure src="proxmox.png" alt="Proxmox" width="736" style="padding-right: 10px" >}}
### Authelia
@@ -65,7 +76,7 @@ which will operate with the above example:
```yaml
- id: proxmox
description: Proxmox
- secret: proxmox_client_secret
+ secret: '$plaintext$proxmox_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/proxmox/proxmox.gif b/docs/content/en/integration/openid-connect/proxmox/proxmox.gif
deleted file mode 100644
index d6f82a2db..000000000
Binary files a/docs/content/en/integration/openid-connect/proxmox/proxmox.gif and /dev/null differ
diff --git a/docs/content/en/integration/openid-connect/proxmox/proxmox.png b/docs/content/en/integration/openid-connect/proxmox/proxmox.png
new file mode 100644
index 000000000..72c552a54
Binary files /dev/null and b/docs/content/en/integration/openid-connect/proxmox/proxmox.png differ
diff --git a/docs/content/en/integration/openid-connect/seafile/index.md b/docs/content/en/integration/openid-connect/seafile/index.md
index f7a690a2f..5fad7db35 100644
--- a/docs/content/en/integration/openid-connect/seafile/index.md
+++ b/docs/content/en/integration/openid-connect/seafile/index.md
@@ -16,15 +16,22 @@ community: true
## Tested Versions
* [Authelia]
- * [v4.35.5](https://github.com/authelia/authelia/releases/tag/v4.35.5)
+ * [v4.36.9](https://github.com/authelia/authelia/releases/tag/v4.36.9)
* [Seafile] Server
- * 9.0.4
+ * [9.0.9](https://manual.seafile.com/changelog/server-changelog/#909-2022-09-22)
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -39,7 +46,10 @@ This example makes the following assumptions:
To configure [Seafile] to utilize Authelia as an [OpenID Connect] Provider:
-1. Edit your [Seafile] `seahub_settings.py` configuration file and add configure the following:
+1. [Seafile] may require some dependencies such as `requests_oauthlib` to be manually installed.
+ See the [Seafile] documentation in the [see also](#see-also) section for more information.
+
+2. Edit your [Seafile] `seahub_settings.py` configuration file and add configure the following:
```python
ENABLE_OAUTH = True
@@ -52,15 +62,14 @@ OAUTH_AUTHORIZATION_URL = 'https://auth.example.com/api/oidc/authorization'
OAUTH_TOKEN_URL = 'https://auth.example.com/api/oidc/token'
OAUTH_USER_INFO_URL = 'https://auth.example.com/api/oidc/userinfo'
OAUTH_SCOPE = [
- "openid",
- "profile",
- "email",
- "groups",
+ "openid",
+ "profile",
+ "email",
]
OAUTH_ATTRIBUTE_MAP = {
- "id": (True, "preferred_username"),
+ "email": (True, "email"),
"name": (False, "name"),
- "email": (False, "email"),
+ "id": (False, "not used"),
}
```
@@ -73,7 +82,7 @@ which will operate with the above example:
```yaml
- id: seafile
description: Seafile
- secret: seafile_client_secret
+ secret: '$plaintext$seafile_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/specific-information.md b/docs/content/en/integration/openid-connect/specific-information.md
new file mode 100644
index 000000000..a89bf29b6
--- /dev/null
+++ b/docs/content/en/integration/openid-connect/specific-information.md
@@ -0,0 +1,39 @@
+---
+title: "Specific Information"
+description: "Specific information regarding integrating the Authelia OpenID Connect Provider with an OpenID Connect relying party"
+lead: "Specific information regarding integrating the Authelia OpenID Connect Provider with an OpenID Connect relying party."
+date: 2022-10-20T15:27:09+11:00
+draft: false
+images: []
+menu:
+ integration:
+ parent: "openid-connect"
+weight: 615
+toc: true
+---
+
+## Generating Client Secrets
+
+We strongly recommend the following guidelines for generating client secrets:
+
+1. Each client should have a unique secret.
+2. Each secret should be randomly generated.
+3. Each secret should have a length above 40 characters.
+4. The secrets should be stored in the configuration in a supported hash format. *__Note:__ This does not mean you
+ configure the relying party / client application with the hashed version, just the secret value in the Authelia
+ configuration.*
+5. Secrets should only have alphanumeric characters as some implementations do not appropriately encode the secret
+ when using it to access the token endpoint.
+
+Authelia provides an easy way to perform such actions via the [Generating a Random Password Hash] guide. Users can
+perform a command such as `authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72` command to
+both generate a client secret with 72 characters which is printed and is to be used with the relying party and hash it
+using PBKDF2 which can be stored in the Authelia configuration.
+
+[Generating a Random Password Hash]: ../../reference/guides/generating-secure-values.md#generating-a-random-password-hash
+
+### Plaintext
+
+Authelia supports storing the plaintext secret in the configuration. This may be discontinued in the future. Plaintext
+is either denoted by the `$plaintext$` prefix where everything after the prefix is the secret. In addition if the secret
+does not start with the `$` character it's considered as a plaintext secret for the time being but is deprecated.
diff --git a/docs/content/en/integration/openid-connect/synapse/index.md b/docs/content/en/integration/openid-connect/synapse/index.md
index bbdac2740..66be51962 100644
--- a/docs/content/en/integration/openid-connect/synapse/index.md
+++ b/docs/content/en/integration/openid-connect/synapse/index.md
@@ -22,9 +22,16 @@ community: true
## Before You Begin
-You are required to utilize a unique client id and a unique and random client secret for all [OpenID Connect] relying
-parties. You should not use the client secret in this example, you should randomly generate one yourself. You may also
-choose to utilize a different client id, it's completely up to you.
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Assumptions
This example makes the following assumptions:
@@ -69,7 +76,7 @@ which will operate with the above example:
```yaml
- id: synapse
description: Synapse
- secret: synapse_client_secret
+ secret: '$plaintext$synapse_client_secret'
public: false
authorization_policy: two_factor
redirect_uris:
diff --git a/docs/content/en/integration/openid-connect/synology-dsm/client.png b/docs/content/en/integration/openid-connect/synology-dsm/client.png
new file mode 100644
index 000000000..9442ebfd2
Binary files /dev/null and b/docs/content/en/integration/openid-connect/synology-dsm/client.png differ
diff --git a/docs/content/en/integration/openid-connect/synology-dsm/index.md b/docs/content/en/integration/openid-connect/synology-dsm/index.md
new file mode 100644
index 000000000..8e73ae933
--- /dev/null
+++ b/docs/content/en/integration/openid-connect/synology-dsm/index.md
@@ -0,0 +1,100 @@
+---
+title: "Synology DSM"
+description: "Integrating Synology DSM with the Authelia OpenID Connect Provider."
+lead: ""
+date: 2022-10-18T21:22:13+11:00
+draft: false
+images: []
+menu:
+integration:
+parent: "openid-connect"
+weight: 620
+toc: true
+community: true
+---
+
+## Tested Versions
+
+* [Authelia]
+ * [v4.35.6](https://github.com/authelia/authelia/releases/tag/v4.35.6)
+* [Synology DSM]
+ * v7.1
+
+## Before You Begin
+
+### Common Notes
+
+1. You are *__required__* to utilize a unique client id for every client.
+2. The client id on this page is merely an example and you can theoretically use any alphanumeric string.
+3. You *__should not__* use the client secret in this example, We *__strongly recommend__* reading the
+ [Generating Client Secrets] guide instead.
+
+[Generating Client Secrets]: ../specific-information.md#generating-client-secrets
+
+### Specific Notes
+
+*__Important Note:__ [Synology DSM] does not support automatically creating users via [OpenID Connect]. It is therefore
+recommended that you ensure Authelia and [Synology DSM] share a LDAP server.*
+
+### Assumptions
+
+This example makes the following assumptions:
+
+* __Application Root URL:__ `https://dsm.example.com/`
+* __Authelia Root URL:__ `https://auth.example.com`
+* __Client ID:__ `synology-dsm`
+* __Client Secret:__ `synology-dsm_client_secret`
+
+## Configuration
+
+### Application
+
+To configure [Synology DSM] to utilize Authelia as an [OpenID Connect] Provider:
+
+1. Go to DSM.
+2. Go to `Control Panel`.
+3. Go To `Domain/LDAP`.
+4. Go to `SSO Client`.
+5. Check the `Enable OpenID Connect SSO service` checkbox in the `OpenID Connect SSO Service` section.
+6. Configure the following values:
+ * Profile: `OIDC`
+ * Name: `Authelia`
+ * Well Known URL: `https://auth.example.com/.well-known/openid-configuration`
+ * Application ID: `synology-dsm`
+ * Application Key: `synology-dsm_client_secret`
+ * Redirect URL: `https://dsm.example.com`
+ * Authorisation Scope: `openid profile groups email`
+ * Username Claim: `preferred_username`
+7. Save the settings.
+
+{{< figure src="client.png" alt="Synology" width="736" >}}
+
+### Authelia
+
+The following YAML configuration is an example __Authelia__
+[client configuration](../../../configuration/identity-providers/open-id-connect.md#clients) for use with [Synology DSM]
+which will operate with the above example:
+
+```yaml
+- id: synology-dsm
+ description: Synology DSM
+ secret: '$plaintext$synology-dsm_client_secret'
+ public: false
+ authorization_policy: two_factor
+ redirect_uris:
+ - https://dsm.example.com
+ scopes:
+ - openid
+ - profile
+ - groups
+ - email
+ userinfo_signing_algorithm: none
+```
+
+## See Also
+
+* [Synology DSM SSO Client Documentation](https://kb.synology.com/en-af/DSM/help/DSM/AdminCenter/file_directory_service_sso?version=7)
+
+[Authelia]: https://www.authelia.com
+[Synology DSM]: https://www.synology.com/en-global/dsm
+[OpenID Connect]: ../../openid-connect/introduction.md
diff --git a/docs/content/en/integration/prologue/get-started.md b/docs/content/en/integration/prologue/get-started.md
index 48746cae3..5d41d4c7c 100644
--- a/docs/content/en/integration/prologue/get-started.md
+++ b/docs/content/en/integration/prologue/get-started.md
@@ -22,12 +22,12 @@ obviously choose a different path if you are so inclined.
Forwarded Authentication is a simple per-request authorization flow that checks the metadata of a request and a session
cookie to determine if a user must be forwarded to the authentication portal.
-Due to the fact a cookie is use, it's an intentional design decision that *__ALL__* applications/domains protected via
+Due to the fact a cookie is used, it's an intentional design decision that *__ALL__* applications/domains protected via
this method *__MUST__* use secure schemes (`https` and `wss`) for all of their communication.
### OpenID Connect
-Only requires Authelia to be use a secure scheme (`https`).
+Only requires Authelia to be accessible via a secure scheme (`https`).
## Configuration
diff --git a/docs/content/en/integration/proxies/caddy.md b/docs/content/en/integration/proxies/caddy.md
index 55b2e502a..904fb17df 100644
--- a/docs/content/en/integration/proxies/caddy.md
+++ b/docs/content/en/integration/proxies/caddy.md
@@ -151,6 +151,7 @@ example.com {
}
```
{{< /details >}}
+
### Advanced example
The advanced example allows for more flexible customization, however the [basic example](#basic-examples) should be
diff --git a/docs/content/en/integration/proxies/envoy.md b/docs/content/en/integration/proxies/envoy.md
index 342ec0c14..b03b09a7e 100644
--- a/docs/content/en/integration/proxies/envoy.md
+++ b/docs/content/en/integration/proxies/envoy.md
@@ -14,20 +14,13 @@ aliases:
- /i/envoy
---
-[Envoy] is probably supported by __Authelia__.
+[Envoy] is supported by __Authelia__.
*__Important:__ When using these guides it's important to recognize that we cannot provide a guide for every possible
method of deploying a proxy. These guides show a suggested setup only and you need to understand the proxy
configuration and customize it to your needs. To-that-end we include links to the official proxy documentation
throughout this documentation and in the [See Also](#see-also) section.*
-## UNDER CONSTRUCTION
-
-It's currently not certain, but fairly likely that [Envoy] is supported by __Authelia__. We wish to add documentation
-and thus if anyone has this working please let us know.
-
-We will aim to perform documentation for this on our own but there is no current timeframe.
-
## Get Started
It's __*strongly recommended*__ that users setting up *Authelia* for the first time take a look at our
@@ -44,10 +37,210 @@ how you can configure multiple IP ranges. You should customize this example to f
You should only include the specific IP address ranges of the trusted proxies within your architecture and should not
trust entire subnets unless that subnet only has trusted proxies and no other services.*
-## Potential
+## Configuration
-Support for [Envoy] should be possible via [Envoy]'s
-[external authorization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz).
+Below you will find commented examples of the following configuration:
+
+* Authelia Portal
+* Protected Endpoint (Nextcloud)
+
+### Example
+
+Support for [Envoy] is possible with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter.
+
+[external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz
+
+{{< details "docker-compose.yaml" >}}
+```yaml
+---
+version: "3.8"
+networks:
+ net:
+ driver: bridge
+services:
+ envoy:
+ container_name: envoy
+ image: envoyproxy/envoy:v1.24
+ restart: unless-stopped
+ networks:
+ net: {}
+ ports:
+ - '80:8080'
+ - '443:8443'
+ volumes:
+ - ${PWD}/data/envoy/envoy.yaml:/etc/envoy/envoy.yaml:ro
+ - ${PWD}/data/certificates:/certificates:ro
+ authelia:
+ container_name: authelia
+ image: authelia/authelia
+ restart: unless-stopped
+ networks:
+ net: {}
+ expose:
+ - 9091
+ volumes:
+ - ${PWD}/data/authelia/config:/config
+ environment:
+ TZ: "Australia/Melbourne"
+ nextcloud:
+ container_name: nextcloud
+ image: linuxserver/nextcloud
+ restart: unless-stopped
+ networks:
+ net: {}
+ expose:
+ - 443
+ volumes:
+ - ${PWD}/data/nextcloud/config:/config
+ - ${PWD}/data/nextcloud/data:/data
+ environment:
+ PUID: "1000"
+ PGID: "1000"
+ TZ: "Australia/Melbourne"
+```
+{{< /details >}}
+
+{{< details "envoy.yaml" >}}
+```yaml
+static_resources:
+ listeners:
+ - name: listener_http
+ address:
+ socket_address:
+ address: 0.0.0.0
+ port_value: 8080
+ filter_chains:
+ - filters:
+ - name: envoy.filters.network.http_connection_manager
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+ codec_type: auto
+ stat_prefix: ingress_http
+ route_config:
+ name: local_route
+ virtual_hosts:
+ - name: backend
+ domains: ["*"]
+ routes:
+ - match:
+ prefix: "/"
+ redirect:
+ https_redirect: true
+ http_filters:
+ - name: envoy.filters.http.router
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+ - name: listener_https
+ address:
+ socket_address:
+ address: 0.0.0.0
+ port_value: 8443
+ filter_chains:
+ - filters:
+ - name: envoy.filters.network.http_connection_manager
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+ stat_prefix: ingress_http
+ use_remote_address: true
+ skip_xff_append: false
+ route_config:
+ name: local_route
+ virtual_hosts:
+ - name: whoami_service
+ domains: ["nextcloud.example.com"]
+ routes:
+ - match:
+ prefix: "/"
+ route:
+ cluster: nextcloud
+ - name: authelia_service
+ domains: ["auth.example.com"]
+ typed_per_filter_config:
+ envoy.filters.http.ext_authz:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
+ disabled: true
+ routes:
+ - match:
+ prefix: "/"
+ route:
+ cluster: authelia
+ http_filters:
+ - name: envoy.filters.http.ext_authz
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
+ http_service:
+ path_prefix: '/api/verify/'
+ server_uri:
+ uri: authelia:9091
+ cluster: authelia
+ timeout: 0.25s
+ authorization_request:
+ allowed_headers:
+ patterns:
+ - exact: accept
+ - exact: cookie
+ - exact: proxy-authorization
+ headers_to_add:
+ - key: X-Authelia-URL
+ value: 'https://auth.example.com/'
+ - key: X-Forwarded-Method
+ value: '%REQ(:METHOD)%'
+ - key: X-Forwarded-Proto
+ value: '%REQ(:SCHEME)%'
+ - key: X-Forwarded-Host
+ value: '%REQ(:AUTHORITY)%'
+ - key: X-Forwarded-Uri
+ value: '%REQ(:PATH)%'
+ - key: X-Forwarded-For
+ value: '%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%'
+ authorization_response:
+ allowed_upstream_headers:
+ patterns:
+ - exact: authorization
+ - exact: proxy-authorization
+ - prefix: remote-
+ - prefix: authelia-
+ allowed_client_headers:
+ patterns:
+ - exact: set-cookie
+ allowed_client_headers_on_success:
+ patterns:
+ - exact: set-cookie
+ failure_mode_allow: false
+ - name: envoy.filters.http.router
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+ clusters:
+ - name: nextcloud
+ connect_timeout: 0.25s
+ type: LOGICAL_DNS
+ dns_lookup_family: V4_ONLY
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: nextcloud
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: nextcloud
+ port_value: 80
+ - name: authelia
+ connect_timeout: 0.25s
+ type: LOGICAL_DNS
+ dns_lookup_family: V4_ONLY
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: authelia
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: authelia
+ port_value: 9091
+```
+{{< /details >}}
## See Also
diff --git a/docs/content/en/integration/proxies/fowarded-headers/index.md b/docs/content/en/integration/proxies/fowarded-headers/index.md
index 733f6bf92..a209ac9b0 100644
--- a/docs/content/en/integration/proxies/fowarded-headers/index.md
+++ b/docs/content/en/integration/proxies/fowarded-headers/index.md
@@ -13,17 +13,17 @@ toc: true
---
The`X-Forwarded-*` headers presented to __Authelia__ must be from trusted sources. As such you must ensure that the
-reverse proxies and load balances utilized with __Authelia__ are configured to remove and replace specific headers when
+reverse proxies and load balancers utilized with __Authelia__ are configured to remove and replace specific headers when
they come directly from clients and not from proxies in your trusted environment.
Some proxies require users explicitly configure the proxy to trust another proxy, however some implicitly trust all
-headers regardless of the source and you have to manually
+headers regardless of the source so you will have to manually configure them.
## Network Rules
In particular this is important for [Access Control Rules](../../../configuration/security/access-control.md#rules) as
the [network criteria](../../../configuration/security/access-control.md#networks) relies on the [X-Forwarded-For]
-header. This header is expected to have a true representation of the clients actual IP address.
+header. This header is expected to have a true representation of the client's actual IP address.
If this is not removed from non-trusted proxies a user could theoretically hijack any rule that contains this criteria
to potentially skip an authentication criteria depending on how it is configured.
@@ -70,8 +70,8 @@ Steps:
10. Set the `Then` section `Header name` to `X-Forwarded-For`.
11. Click `Save`.
-{{< figure src="cloudflare_1.png" alt="Steps 1 to 4" width="736" style="padding-right: 10px" >}}
-{{< figure src="cloudflare_2.png" alt="Steps 5 to 11" width="736" style="padding-right: 10px" >}}
+{{< figure src="cloudflare_1.png" alt="Steps 1 to 4" width="736" >}}
+{{< figure src="cloudflare_2.png" alt="Steps 5 to 11" width="736" >}}
Criteria:
diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager.md b/docs/content/en/integration/proxies/nginx-proxy-manager.md
deleted file mode 100644
index 07c33f6ca..000000000
--- a/docs/content/en/integration/proxies/nginx-proxy-manager.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: "NGINX Proxy Manager"
-description: "An integration guide for Authelia and the NGINX Proxy Manager reverse proxy"
-lead: "A guide on integrating Authelia with NGINX Proxy Manager."
-date: 2022-06-15T17:51:47+10:00
-draft: false
-images: []
-menu:
- integration:
- parent: "proxies"
-weight: 352
-toc: true
-aliases:
- - /i/npm
----
-
-[NGINX Proxy Manager] is supported by __Authelia__. It's a [NGINX] proxy with a configuration UI.
-
-*__Important:__ When using these guides it's important to recognize that we cannot provide a guide for every possible
-method of deploying a proxy. These guides show a suggested setup only and you need to understand the proxy
-configuration and customize it to your needs. To-that-end we include links to the official proxy documentation
-throughout this documentation and in the [See Also](#see-also) section.*
-
-## UNDER CONSTRUCTION
-
-While this proxy is supported we don't have any specific documentation for it at the present time. Please see the
-[NGINX integration documentation](nginx.md) for hints on how to configure this.
-
-## Get Started
-
-It's __*strongly recommended*__ that users setting up *Authelia* for the first time take a look at our
-[Get Started](../prologue/get-started.md) guide. This takes you through various steps which are essential to
-bootstrapping *Authelia*.
-
-## Requirements
-
-[NGINX Proxy Manager] supports the required [NGINX](nginx.md#requirements) requirements for __Authelia__ out-of-the-box.
-
-## Trusted Proxies
-
-*__Important:__ You should read the [Forwarded Headers] section and this section as part of any proxy configuration.
-Especially if you have never read it before.*
-
-To configure trusted proxies for [NGINX Proxy Manager] see the [NGINX] section on
-[Trusted Proxies](nginx.md#trusted-proxies). Adapting this to [NGINX Proxy Manager] is beyond the scope of
-this documentation.
-
-## See Also
-
-* [NGINX Proxy Manager Documentation](https://nginxproxymanager.com/setup/)
-* [NGINX ngx_http_auth_request_module Module Documentation](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html)
-* [Forwarded Headers]
-
-[NGINX Proxy Manager]: https://nginxproxymanager.com/
-[NGINX]: https://www.nginx.com/
-[Forwarded Headers]: fowarded-headers
diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager/authelia.advanced.png b/docs/content/en/integration/proxies/nginx-proxy-manager/authelia.advanced.png
new file mode 100644
index 000000000..12eda80a6
Binary files /dev/null and b/docs/content/en/integration/proxies/nginx-proxy-manager/authelia.advanced.png differ
diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager/authelia.details.png b/docs/content/en/integration/proxies/nginx-proxy-manager/authelia.details.png
new file mode 100644
index 000000000..f916c0d1c
Binary files /dev/null and b/docs/content/en/integration/proxies/nginx-proxy-manager/authelia.details.png differ
diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager/index.md b/docs/content/en/integration/proxies/nginx-proxy-manager/index.md
new file mode 100644
index 000000000..e0bdf2ceb
--- /dev/null
+++ b/docs/content/en/integration/proxies/nginx-proxy-manager/index.md
@@ -0,0 +1,244 @@
+---
+title: "NGINX Proxy Manager"
+description: "An integration guide for Authelia and the NGINX Proxy Manager reverse proxy"
+lead: "A guide on integrating Authelia with NGINX Proxy Manager."
+date: 2022-10-08T12:43:26+11:00
+draft: false
+images: []
+menu:
+ integration:
+ parent: "proxies"
+weight: 352
+toc: true
+aliases:
+ - /i/npm
+---
+
+[NGINX Proxy Manager] is supported by __Authelia__. It's a [NGINX] proxy with a configuration UI.
+
+*__Important:__ When using these guides it's important to recognize that we cannot provide a guide for every possible
+method of deploying a proxy. These guides show a suggested setup only and you need to understand the proxy
+configuration and customize it to your needs. To-that-end we include links to the official proxy documentation
+throughout this documentation and in the [See Also](#see-also) section.*
+
+## Get Started
+
+It's __*strongly recommended*__ that users setting up *Authelia* for the first time take a look at our
+[Get Started](../prologue/get-started.md) guide. This takes you through various steps which are essential to
+bootstrapping *Authelia*.
+
+## Requirements
+
+[NGINX Proxy Manager] supports the required [NGINX](nginx.md#requirements) requirements for __Authelia__ out-of-the-box.
+
+## Trusted Proxies
+
+*__Important:__ You should read the [Forwarded Headers] section and this section as part of any proxy configuration.
+Especially if you have never read it before.*
+
+To configure trusted proxies for [NGINX Proxy Manager] see the [NGINX] section on
+[Trusted Proxies](nginx.md#trusted-proxies). Adapting this to [NGINX Proxy Manager] is beyond the scope of
+this documentation.
+
+## Docker Compose
+
+The following docker compose example has various applications suitable for setting up an example environment.
+
+{{< details "docker-compose.yaml" >}}
+```yaml
+---
+version: "3.8"
+
+networks:
+ net:
+ driver: bridge
+
+services:
+ nginx:
+ container_name: nginx
+ image: jc21/nginx-proxy-manager
+ restart: unless-stopped
+ networks:
+ net:
+ aliases: []
+ ports:
+ - '80:80'
+ - '81:81'
+ - '443:443'
+ volumes:
+ - ${PWD}/data/nginx-proxy-manager/data:/data
+ - ${PWD}/data/nginx-proxy-manager/letsencrypt:/etc/letsencrypt
+ - ${PWD}/data/nginx/snippets:/config/nginx/snippets:ro
+ environment:
+ TZ: 'Australia/Melbourne'
+ authelia:
+ container_name: authelia
+ image: authelia/authelia
+ restart: unless-stopped
+ networks:
+ net:
+ aliases: []
+ expose:
+ - 9091
+ volumes:
+ - ${PWD}/data/authelia/config:/config
+ environment:
+ TZ: 'Australia/Melbourne'
+ nextcloud:
+ container_name: nextcloud
+ image: lscr.io/linuxserver/nextcloud
+ restart: unless-stopped
+ networks:
+ net:
+ aliases: []
+ expose:
+ - 443
+ volumes:
+ - ${PWD}/data/nextcloud/config:/config
+ - ${PWD}/data/nextcloud/data:/data
+ environment:
+ PUID: '1000'
+ PGID: '1000'
+ TZ: 'Australia/Melbourne'
+ whoami:
+ container_name: whoami
+ image: docker.io/traefik/whoami
+ restart: unless-stopped
+ networks:
+ net:
+ aliases: []
+ expose:
+ - 80
+ environment:
+ TZ: 'Australia/Melbourne'
+...
+```
+{{< /details >}}
+
+## Configuration
+
+### Assumptions
+
+*__Important:__ Our examples make assumptions about your configuration. These assumptions represent sections that
+either most likely require an adjustment, or may require an adjustment if you're not configuring it in the same way.*
+
+* The domain for Authelia is `auth.example.com` which shoud be adjusted in all examples and snippets to your actual
+ domain.
+* The required configuration snippets are mounted in the container or otherwise available in the `/snippets/` directory.
+ If you choose a different directory you're required to adjust every instance of `/snippets/` appropriately to your
+ needs.
+* You have not configured the Authelia configuration YAML with a server TLS certificate/key.
+* You are running Authelia on the default port.
+* You are running Authelia with the `container_name` of `authelia` or the Authelia process is otherwise resolvable by
+ [NGINX Proxy Manager] as `authelia`.
+* If you want to use a [Custom Location](#protected-application-custom-locations) and wish for it to be protected you
+ follow the [Protected Application Custom Location](#protected-application-custom-locations) guide.
+
+### Snippets
+
+The examples assume you've mounted a volume containing the relevant
+[NGINX Snippets](nginx.md#supporting-configuration-snippets) from the [NGINX Integration Guide](nginx.md). The suggested
+snippets are the `proxy.conf`, `authelia-location.conf`, and `authelia-authrequest.conf`. It may be fine to substitute
+the standard variant of the `proxy.conf` for the headers only variant but this is untested.
+
+These snippets make the addition of a protected proxy host substantially easier.
+
+### Authelia Portal
+
+The Authelia portal requires minimal configuration.
+
+1. Create a new `Proxy Host`.
+2. Set the following items in the `Details` tab:
+ * Domain Names: `auth.example.com`
+ * Scheme: `http`
+ * Forward Hostname / IP: `authelia`
+ * Forward Port: `9091`
+3. Configure your `SSL` tab to:
+ * Serve a valid certificate.
+ * Force SSL: `true`
+4. Configure your `Advanced` tab:
+```nginx
+location / {
+ include /snippets/proxy.conf;
+ proxy_pass $forward_scheme://$server:$port;
+}
+```
+
+#### Authelia Portal Screenshots
+
+Authelia Portal `Details` tab example:
+
+{{< figure src="authelia.details.png" alt="Step 2" width="450" >}}
+
+Authelia Portal `Advanced` tab example:
+
+{{< figure src="authelia.advanced.png" alt="Step 4" width="450" >}}
+
+### Protected Application
+
+The following example shows how to configure a protected application. We often use Nextcloud for such examples.
+
+1. Create a new `Proxy Host`.
+2. Set the following items in the `Details` tab:
+ * Domain Names: `nextcloud.example.com`
+ * Scheme: `http`
+ * Forward Hostname / IP: `nextcloud`
+ * Forward Port: `80`
+3. Configure your `SSL` tab to:
+ * Serve a valid certificate.
+ * Force SSL: `true`
+4. Configure your `Advanced` tab:
+```nginx
+include /snippets/authelia-location.conf;
+
+location / {
+ include /snippets/proxy.conf;
+ include /snippets/authelia-authrequest.conf;
+ proxy_pass $forward_scheme://$server:$port;
+}
+```
+
+#### Protected Application Screenshots
+
+Protected Application (Nextcloud) `Details` tab example:
+
+{{< figure src="nextcloud.details.png" alt="Step 2" width="450" >}}
+
+Protected Application (Nextcloud) `Advanced` tab example:
+
+{{< figure src="protectedapp.advanced.png" alt="Step 4" width="450" >}}
+
+#### Protected Application Custom Locations
+
+It's important to note if you define locations in the `Custom Locations` tab of a proxy host that they will not be
+checked with Authelia for authorization effectively bypassing the authorization policies you implement. If you want a
+custom location then you can also define this in the advanced tab.
+
+To replicate the `Custom Location` tab below a location block can be *__ADDED__* to the
+[Protected Application](#protected-application) `Advanced` tab:
+
+```nginx
+location /custom {
+ include /snippets/proxy.conf;
+ include /snippets/authelia-authrequest.conf;
+ proxy_pass http://192.168.1.20:8080;
+}
+```
+
+{{< figure src="protectedapp.customlocation.png" alt="Custom Location" width="450" >}}
+
+#### Proxy Hosts Screenshot
+
+The following screenshot shows an example of following the directions for the Authelia Portal and two applications:
+
+{{< figure src="proxyhosts.png" alt="Step 4" width="450" >}}
+
+## See Also
+
+* [NGINX Proxy Manager Documentation](https://nginxproxymanager.com/setup/)
+* [NGINX ngx_http_auth_request_module Module Documentation](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html)
+* [Forwarded Headers]
+
+[NGINX Proxy Manager]: https://nginxproxymanager.com/
+[NGINX]: https://www.nginx.com/
+[Forwarded Headers]: ../fowarded-headers
diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager/nextcloud.details.png b/docs/content/en/integration/proxies/nginx-proxy-manager/nextcloud.details.png
new file mode 100644
index 000000000..909e86d3a
Binary files /dev/null and b/docs/content/en/integration/proxies/nginx-proxy-manager/nextcloud.details.png differ
diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager/protectedapp.advanced.png b/docs/content/en/integration/proxies/nginx-proxy-manager/protectedapp.advanced.png
new file mode 100644
index 000000000..596ff5460
Binary files /dev/null and b/docs/content/en/integration/proxies/nginx-proxy-manager/protectedapp.advanced.png differ
diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager/protectedapp.customlocation.png b/docs/content/en/integration/proxies/nginx-proxy-manager/protectedapp.customlocation.png
new file mode 100644
index 000000000..2a5e635cd
Binary files /dev/null and b/docs/content/en/integration/proxies/nginx-proxy-manager/protectedapp.customlocation.png differ
diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager/proxyhosts.png b/docs/content/en/integration/proxies/nginx-proxy-manager/proxyhosts.png
new file mode 100644
index 000000000..6888ae832
Binary files /dev/null and b/docs/content/en/integration/proxies/nginx-proxy-manager/proxyhosts.png differ
diff --git a/docs/content/en/integration/proxies/nginx.md b/docs/content/en/integration/proxies/nginx.md
index a9e0d8697..08dc7c691 100644
--- a/docs/content/en/integration/proxies/nginx.md
+++ b/docs/content/en/integration/proxies/nginx.md
@@ -87,7 +87,6 @@ services:
- '443:443'
volumes:
- ${PWD}/data/nginx/snippets:/config/nginx/snippets:ro
- - ${PWD}/data/certificates:/config/nginx/certificates:ro
- ${PWD}/data/nginx/site-confs:/config/nginx/site-confs:ro
environment:
TZ: 'Australia/Melbourne'
@@ -140,10 +139,10 @@ services:
Below you will find commented examples of the following configuration:
-* [Authelia Portal](#authelia-portal)
+* [Authelia Portal](#standard-example)
* Running in Docker
* Has the container name `authelia`
-* [Protected Endpoint (Nextcloud)](#protected-endpoint)
+* [Protected Endpoint (Nextcloud)](#standard-example)
* Running in Docker
* Has the container name `nextcloud`
* [Supporting Configuration Snippets](#supporting-configuration-snippets)
@@ -152,6 +151,15 @@ Below you will find commented examples of the following configuration:
[resolver](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) which is standard
* [NGINX] shares a network with the `authelia` and `nextcloud` containers
+### Assumptions
+
+* Authelia is accessible to [NGINX] process with the hostname `authelia` on port `9091` making the URL
+ `http://authelia:9091`. If this is not the case adjust all instances of this as appropriate.
+* The [NGINX] configuration is in the folder `/config/nginx`. If this is not the case adjust all instances of this as
+ appropriate.
+* The URL you wish Authelia to be accessible on is `https://auth.example.com`. If this is not the case adjust all
+ instances of this as appropriate.
+
### Standard Example
This example is for using the __Authelia__ portal redirection flow on a specific endpoint. It requires you to have the
@@ -160,6 +168,10 @@ This example is for using the __Authelia__ portal redirection flow on a specific
files exist in the `/config/nginx/snippets/` directory. The `/config/nginx/snippets/ssl.conf` snippet is expected to have
the configuration for TLS or SSL but is not included as part of the examples.
+The directive `include /config/nginx/snippets/authelia-authrequest.conf;` within the `location` block is what directs
+[NGINX] to perform authorization with Authelia. Every `location` block you wish for Authelia to perform authorization for
+should include this directive.
+
{{< details "/config/nginx/site-confs/auth.conf (Authelia Portal)" >}}
```nginx
server {
@@ -175,11 +187,15 @@ server {
include /config/nginx/snippets/ssl.conf;
+ set $upstream http://authelia:9091;
+
location / {
include /config/nginx/snippets/proxy.conf;
+ proxy_pass $upstream;
+ }
- set $upstream_authelia http://authelia:9091;
- proxy_pass $upstream_authelia;
+ location /api/verify {
+ proxy_pass $upstream;
}
}
```
@@ -206,7 +222,6 @@ server {
location / {
include /config/nginx/snippets/proxy.conf;
include /config/nginx/snippets/authelia-authrequest.conf;
-
proxy_pass $upstream;
}
}
@@ -234,7 +249,6 @@ server {
location / {
include /config/nginx/snippets/proxy.conf;
include /config/nginx/snippets/authelia-authrequest.conf;
-
proxy_pass $upstream;
}
}
@@ -268,12 +282,12 @@ server {
include /config/nginx/snippets/ssl.conf;
include /config/nginx/snippets/authelia-location-basic.conf; # Use the "basic" endpoint
+ set $upstream https://nextcloud;
+
location / {
include /config/nginx/snippets/proxy.conf;
include /config/nginx/snippets/authelia-authrequest-basic.conf;
-
- set $upstream_nextcloud https://nextcloud;
- proxy_pass $upstream_nextcloud;
+ proxy_pass $upstream;
}
}
```
@@ -293,10 +307,15 @@ The following is an example `proxy.conf`. The important directives include the `
[Trusted Proxies](#trusted-proxies) section to understand, or set the `X-Forwarded-Proto`, `X-Forwarded-Host`,
`X-Forwarded-Uri`, and `X-Forwarded-For` headers.
+##### Standard Variant
+
+Generally this variant is the suggested variant.
+
{{< details "/config/nginx/snippets/proxy.conf" >}}
```nginx
## Headers
proxy_set_header Host $host;
+proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Uri $request_uri;
@@ -332,6 +351,24 @@ proxy_connect_timeout 360;
```
{{< /details >}}
+##### Headers Only Variant
+
+Generally the [standard variant](#standard-variant) is the suggested variant. This variant only contains the required
+headers for Authelia to operate.
+
+{{< details "/config/nginx/snippets/proxy.conf" >}}
+```nginx
+## Headers
+proxy_set_header Host $host;
+proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
+proxy_set_header X-Forwarded-Proto $scheme;
+proxy_set_header X-Forwarded-Host $http_host;
+proxy_set_header X-Forwarded-Uri $request_uri;
+proxy_set_header X-Forwarded-Ssl on;
+proxy_set_header X-Forwarded-For $remote_addr;
+```
+{{< /details >}}
+
#### authelia-location.conf
*The following snippet is used within the `server` block of a virtual host as a supporting endpoint used by
diff --git a/docs/content/en/integration/proxies/support.md b/docs/content/en/integration/proxies/support.md
index ac47789a6..c2e7925c2 100644
--- a/docs/content/en/integration/proxies/support.md
+++ b/docs/content/en/integration/proxies/support.md
@@ -15,19 +15,19 @@ aliases:
- /docs/home/supported-proxies.html
---
-| Proxy | [Standard](#standard) | [Kubernetes](#kubernetes) | [XHR Redirect](#xhr-redirect) | [Request Method](#request-method) |
-|:---------------------:|:----------------------------------------------------------------:|:------------------------------------------------------------------------------------:|:------------------------------------:|:------------------------------------:|
-| [Traefik] | [ ](traefik.md) | [ ](../../integration/kubernetes/traefik-ingress.md) | | |
-| [NGINX] | [ ](nginx.md) | [ ](../../integration/kubernetes/nginx-ingress.md) | | |
-| [NGINX Proxy Manager] | [ ](nginx-proxy-manager.md) | | | |
-| [SWAG] | [ ](swag.md) | | | |
-| [HAProxy] | [ ](haproxy.md) | | | |
-| [Caddy] | [ ](caddy.md) | | | |
-| [Traefik] 1.x | [ ](traefikv1.md) | | | |
-| [Envoy] | [ ](envoy.md) | | | |
-| [Skipper] | [ ](skipper.md) | | | |
-| [Apache] | [ ](#apache) | | | |
-| [IIS] | [ ](#iis) | | | |
+| Proxy | [Standard](#standard) | [Kubernetes](#kubernetes) | [XHR Redirect](#xhr-redirect) | [Request Method](#request-method) |
+|:---------------------:|:----------------------------------------------------------------:|:------------------------------------------------------------------------------------:|:-----------------------------------------------------:|:-----------------------------------------------------:|
+| [Traefik] | [ ](traefik.md) | [ ](../../integration/kubernetes/traefik-ingress.md) | | |
+| [NGINX] | [ ](nginx.md) | [ ](../../integration/kubernetes/nginx-ingress.md) | | |
+| [NGINX Proxy Manager] | [ ](nginx-proxy-manager.md) | | | |
+| [SWAG] | [ ](swag.md) | | | |
+| [HAProxy] | [ ](haproxy.md) | | | |
+| [Caddy] | [ ](caddy.md) | | | |
+| [Traefik] 1.x | [ ](traefikv1.md) | | | |
+| [Envoy] | [ ](envoy.md) | [ ](../../integration/kubernetes/istio.md) | | |
+| [Skipper] | [ ](skipper.md) | | | |
+| [Apache] | [ ](#apache) | | | |
+| [IIS] | [ ](#iis) | | | |
Legend:
@@ -87,8 +87,9 @@ available in [Kubernetes]. You would likely have to build your own [HAProxy] ima
### Envoy
-[Envoy] is currently not documented, however a small pending feature will add complete support for [Envoy]. This is
-possible via [Envoy]'s [external authorization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz).
+[Envoy] is supported with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter.
+
+[external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz
### Caddy
diff --git a/docs/content/en/integration/proxies/swag.md b/docs/content/en/integration/proxies/swag.md
index f8097d9b4..f6610c604 100644
--- a/docs/content/en/integration/proxies/swag.md
+++ b/docs/content/en/integration/proxies/swag.md
@@ -40,6 +40,19 @@ bootstrapping *Authelia*.
[SWAG] supports the required [NGINX](nginx.md#requirements) requirements for __Authelia__ out-of-the-box.
+### SWAG Caveat
+
+One current caveat of the [SWAG] implementation is that it serves Authelia as a subpath for each domain. We
+*__strongly recommend__* instead of using the out of the box method and guide for [SWAG] that you follow the
+[NGINX](nginx.md) guide (which *can be used* with [SWAG]) and run Authelia as it's own subdomain.
+
+This is partly because Webauthn requires that the domain is an exact match when registering and authenticating and it is
+possible that due to web standards this will never change.
+
+In addition this represents a bad user experience in some instances as users sometimes visit the
+`https://app.example.com/auth` URL which doesn't automatically redirect the user to `https://app.example.com` (if they
+visit `https://app.example.com` then they'll be redirected to authenticate then redirected back to their original URL).
+
## Trusted Proxies
*__Important:__ You should read the [Forwarded Headers] section and this section as part of any proxy configuration.
diff --git a/docs/content/en/integration/proxies/traefik.md b/docs/content/en/integration/proxies/traefik.md
index 7723ff643..cc9317e97 100644
--- a/docs/content/en/integration/proxies/traefik.md
+++ b/docs/content/en/integration/proxies/traefik.md
@@ -90,7 +90,7 @@ networks:
services:
traefik:
container_name: traefik
- image: traefik:v2.8
+ image: traefik:v2.9
restart: unless-stopped
command:
- '--api=true'
@@ -227,7 +227,7 @@ networks:
services:
traefik:
container_name: traefik
- image: traefik:v2.6
+ image: traefik:v2.9
restart: unless-stopped
command:
- '--api=true'
diff --git a/docs/content/en/overview/prologue/architecture/index.md b/docs/content/en/overview/prologue/architecture/index.md
index 9b012bcde..d4e36f494 100644
--- a/docs/content/en/overview/prologue/architecture/index.md
+++ b/docs/content/en/overview/prologue/architecture/index.md
@@ -54,5 +54,5 @@ connections. Please note that it has been decided that we won't support websites
risk due to misconfiguration (see [#590](https://github.com/authelia/authelia/issues/590)).
If a self-signed certificate is required, the
-[Generating an RSA Self-Signed Certificate](../../../configuration/miscellaneous/guides.md#generating-an-rsa-self-signed-certificate)
+[Generating an RSA Self-Signed Certificate](../../../reference/guides/generating-secure-values.md#generating-an-rsa-self-signed-certificate)
guide should be followed.
diff --git a/docs/content/en/overview/prologue/supported-proxies.md b/docs/content/en/overview/prologue/supported-proxies.md
index 3aa9de3e1..01a12fcc5 100644
--- a/docs/content/en/overview/prologue/supported-proxies.md
+++ b/docs/content/en/overview/prologue/supported-proxies.md
@@ -14,28 +14,28 @@ toc: false
The following table is a support matrix for Authelia features and specific reverse proxies.
-| Proxy | Standard | Kubernetes | XHR Redirect | Request Method |
-|:---------------------:|:-------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------:|:------------------------------------:|:------------------------------------:|
-| [Traefik] | [ ](../../integration/proxies/traefik.md) | [ ](../../integration/kubernetes/traefik-ingress.md) | | |
-| [NGINX] | [ ](../../integration/proxies/nginx.md) | [ ](../../integration/kubernetes/nginx-ingress.md) | | |
-| [NGINX Proxy Manager] | [ ](../../integration/proxies/nginx-proxy-manager.md) | | | |
-| [SWAG] | [ ](../../integration/proxies/swag.md) | | | |
-| [HAProxy] | [ ](../../integration/proxies/haproxy.md) | | | |
-| [Caddy] | [ ](../../integration/proxies/caddy.md) | | | |
-| [Traefik] 1.x | [ ](../../integration/proxies/traefikv1.md) | | | |
-| [Envoy] | [ ](../../integration/proxies/envoy.md) | | | |
-| [Skipper] | [ ](../../integration/proxies/skipper.md) | | | |
-| [Apache] | | | | |
-| [IIS] | | | | |
+| Proxy | [Standard](#standard) | [Kubernetes](#kubernetes) | [XHR Redirect](#xhr-redirect) | [Request Method](#request-method) |
+|:---------------------:|:-------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------:|:-----------------------------------------------------:|:-----------------------------------------------------:|
+| [Traefik] | [ ](../../integration/proxies/traefik.md) | [ ](../../integration/kubernetes/traefik-ingress.md) | | |
+| [NGINX] | [ ](../../integration/proxies/nginx.md) | [ ](../../integration/kubernetes/nginx-ingress.md) | | |
+| [NGINX Proxy Manager] | [ ](../../integration/proxies/nginx-proxy-manager.md) | | | |
+| [SWAG] | [ ](../../integration/proxies/swag.md) | | | |
+| [HAProxy] | [ ](../../integration/proxies/haproxy.md) | | | |
+| [Caddy] | [ ](../../integration/proxies/caddy.md) | | | |
+| [Traefik] 1.x | [ ](../../integration/proxies/traefikv1.md) | | | |
+| [Envoy] | [ ](../../integration/proxies/envoy.md) | [ ](../../integration/kubernetes/istio.md) | | |
+| [Skipper] | [ ](../../integration/proxies/skipper.md) | | | |
+| [Apache] | | | | |
+| [IIS] | | | | |
Legend:
-| Icon | Meaning |
-|:------------------------------------:|:-------------------:|
-| | Supported |
+| Icon | Meaning |
+|-------------------------------------:|:-------------------:|
+| | Supported |
| | Unknown |
| | Partially Supported |
-| | Not Supported |
+| | Not Supported |
## More Information
diff --git a/docs/content/en/information/privacy-policy.md b/docs/content/en/policies/privacy.md
similarity index 92%
rename from docs/content/en/information/privacy-policy.md
rename to docs/content/en/policies/privacy.md
index e646d854b..0435cd528 100644
--- a/docs/content/en/information/privacy-policy.md
+++ b/docs/content/en/policies/privacy.md
@@ -29,6 +29,6 @@ __TLDR__: We do not use cookies and we do not collect any personal data.
## Contact us
-[Contact us](contact.md) if you have any questions.
+[Contact us](../information/contact.md) if you have any questions.
__Effective Date:__ *16th May 2022*
diff --git a/docs/content/en/information/security.md b/docs/content/en/policies/security.md
similarity index 93%
rename from docs/content/en/information/security.md
rename to docs/content/en/policies/security.md
index f1a22ed2b..bbd332090 100644
--- a/docs/content/en/information/security.md
+++ b/docs/content/en/policies/security.md
@@ -1,10 +1,11 @@
---
-title: "Security"
-description: "Report security issues."
+title: "Security Policy"
+description: "The Authelia Security Policy which is essential reading for reporting security issues"
date: 2022-06-15T17:51:47+10:00
draft: false
images: []
aliases:
+ - /security-policy
- /security
- /security.html
---
@@ -36,7 +37,7 @@ This is the preferred method of reporting.
### Chat
-If you wish to chat directly instead of sending an email please use one of the [chat options](contact.md#chat) but it
+If you wish to chat directly instead of sending an email please use one of the [chat options](../information/contact.md#chat) but it
is vital that when you do that you only do so privately with one of the maintainers. In order to start a private
discussion you should ask to have a private discussion with a team member without mentioning the reason why you wish to
have a private discussion so that provided the bug is confirmed we can coordinate the release of fixes and information
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen.md
index f9194a274..1dc7be5b0 100644
--- a/docs/content/en/reference/cli/authelia-gen/authelia-gen.md
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen.md
@@ -16,15 +16,43 @@ toc: true
Authelia's generator tooling
+```
+authelia-gen [flags]
+```
+
### Options
```
- -h, --help help for authelia-gen
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ -h, --help help for authelia-gen
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
```
### SEE ALSO
-* [authelia-gen all](authelia-gen_all.md) - Run all generators with default options
* [authelia-gen code](authelia-gen_code.md) - Generate code
+* [authelia-gen commit-lint](authelia-gen_commit-lint.md) - Generate commit lint files
* [authelia-gen docs](authelia-gen_docs.md) - Generate docs
+* [authelia-gen github](authelia-gen_github.md) - Generate GitHub files
+* [authelia-gen locales](authelia-gen_locales.md) - Generate locales files
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_all.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_all.md
deleted file mode 100644
index b1c144577..000000000
--- a/docs/content/en/reference/cli/authelia-gen/authelia-gen_all.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: "authelia-gen all"
-description: "Reference for the authelia-gen all command."
-lead: ""
-date: 2022-06-15T17:51:47+10:00
-draft: false
-images: []
-menu:
- reference:
- parent: "cli-authelia-gen"
-weight: 330
-toc: true
----
-
-## authelia-gen all
-
-Run all generators with default options
-
-```
-authelia-gen all [flags]
-```
-
-### Options
-
-```
- -h, --help help for all
-```
-
-### SEE ALSO
-
-* [authelia-gen](authelia-gen.md) - Authelia's generator tooling
-
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_code.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_code.md
index 166e8fe64..65adbfe01 100644
--- a/docs/content/en/reference/cli/authelia-gen/authelia-gen_code.md
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_code.md
@@ -26,8 +26,37 @@ authelia-gen code [flags]
-h, --help help for code
```
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
### SEE ALSO
* [authelia-gen](authelia-gen.md) - Authelia's generator tooling
* [authelia-gen code keys](authelia-gen_code_keys.md) - Generate the list of valid configuration keys
+* [authelia-gen code scripts](authelia-gen_code_scripts.md) - Generate the generated portion of the authelia-scripts command
+* [authelia-gen code server](authelia-gen_code_server.md) - Generate the Authelia server files
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_keys.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_keys.md
index da85af909..2b0a9ea98 100644
--- a/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_keys.md
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_keys.md
@@ -23,9 +23,34 @@ authelia-gen code keys [flags]
### Options
```
- -f, --file string Sets the path of the keys file (default "./internal/configuration/schema/keys.go")
- -h, --help help for keys
- --package string Sets the package name of the keys file (default "schema")
+ -h, --help help for keys
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
```
### SEE ALSO
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_scripts.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_scripts.md
new file mode 100644
index 000000000..b482188e3
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_scripts.md
@@ -0,0 +1,59 @@
+---
+title: "authelia-gen code scripts"
+description: "Reference for the authelia-gen code scripts command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen code scripts
+
+Generate the generated portion of the authelia-scripts command
+
+```
+authelia-gen code scripts [flags]
+```
+
+### Options
+
+```
+ -h, --help help for scripts
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen code](authelia-gen_code.md) - Generate code
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_server.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_server.md
new file mode 100644
index 000000000..75d4b7c7c
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_code_server.md
@@ -0,0 +1,59 @@
+---
+title: "authelia-gen code server"
+description: "Reference for the authelia-gen code server command."
+lead: ""
+date: 2022-06-15T17:51:47+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen code server
+
+Generate the Authelia server files
+
+```
+authelia-gen code server [flags]
+```
+
+### Options
+
+```
+ -h, --help help for server
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen code](authelia-gen_code.md) - Generate code
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_commit-lint.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_commit-lint.md
new file mode 100644
index 000000000..c559fd142
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_commit-lint.md
@@ -0,0 +1,59 @@
+---
+title: "authelia-gen commit-lint"
+description: "Reference for the authelia-gen commit-lint command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen commit-lint
+
+Generate commit lint files
+
+```
+authelia-gen commit-lint [flags]
+```
+
+### Options
+
+```
+ -h, --help help for commit-lint
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen](authelia-gen.md) - Authelia's generator tooling
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs.md
index 015fc5362..5c28d1603 100644
--- a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs.md
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs.md
@@ -23,13 +23,40 @@ authelia-gen docs [flags]
### Options
```
- -C, --cwd string Sets the CWD for git commands
- -h, --help help for docs
+ -h, --help help for docs
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
```
### SEE ALSO
* [authelia-gen](authelia-gen.md) - Authelia's generator tooling
* [authelia-gen docs cli](authelia-gen_docs_cli.md) - Generate CLI docs
+* [authelia-gen docs data](authelia-gen_docs_data.md) - Generate docs data files
* [authelia-gen docs date](authelia-gen_docs_date.md) - Generate doc dates
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_cli.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_cli.md
index 936d7b7d7..8f4dfa7b6 100644
--- a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_cli.md
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_cli.md
@@ -23,14 +23,34 @@ authelia-gen docs cli [flags]
### Options
```
- -d, --directory string The directory to store the markdown in (default "./docs/content/en/reference/cli")
- -h, --help help for cli
+ -h, --help help for cli
```
### Options inherited from parent commands
```
- -C, --cwd string Sets the CWD for git commands
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
```
### SEE ALSO
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data.md
new file mode 100644
index 000000000..2ba5ed90a
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data.md
@@ -0,0 +1,61 @@
+---
+title: "authelia-gen docs data"
+description: "Reference for the authelia-gen docs data command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen docs data
+
+Generate docs data files
+
+```
+authelia-gen docs data [flags]
+```
+
+### Options
+
+```
+ -h, --help help for data
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen docs](authelia-gen_docs.md) - Generate docs
+* [authelia-gen docs data keys](authelia-gen_docs_data_keys.md) - Generate the docs data file for configuration keys
+* [authelia-gen docs data misc](authelia-gen_docs_data_misc.md) - Generate docs data file misc.json
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data_keys.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data_keys.md
new file mode 100644
index 000000000..82932eedd
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data_keys.md
@@ -0,0 +1,59 @@
+---
+title: "authelia-gen docs data keys"
+description: "Reference for the authelia-gen docs data keys command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen docs data keys
+
+Generate the docs data file for configuration keys
+
+```
+authelia-gen docs data keys [flags]
+```
+
+### Options
+
+```
+ -h, --help help for keys
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen docs data](authelia-gen_docs_data.md) - Generate docs data files
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data_misc.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data_misc.md
new file mode 100644
index 000000000..cdcb1042e
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_data_misc.md
@@ -0,0 +1,59 @@
+---
+title: "authelia-gen docs data misc"
+description: "Reference for the authelia-gen docs data misc command."
+lead: ""
+date: 2022-06-15T17:51:47+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen docs data misc
+
+Generate docs data file misc.json
+
+```
+authelia-gen docs data misc [flags]
+```
+
+### Options
+
+```
+ -h, --help help for misc
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen docs data](authelia-gen_docs_data.md) - Generate docs data files
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_date.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_date.md
index 3aa716197..4110ca86c 100644
--- a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_date.md
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_date.md
@@ -2,7 +2,7 @@
title: "authelia-gen docs date"
description: "Reference for the authelia-gen docs date command."
lead: ""
-date: 2022-09-01T12:28:38+10:00
+date: 2022-06-15T17:51:47+10:00
draft: false
images: []
menu:
@@ -25,14 +25,34 @@ authelia-gen docs date [flags]
```
--commit-since string The commit to check the logs since
--commit-until string The commit to check the logs until (default "HEAD")
- -d, --directory string The directory to modify (default "./docs/content")
-h, --help help for date
```
### Options inherited from parent commands
```
- -C, --cwd string Sets the CWD for git commands
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
```
### SEE ALSO
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_time.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_time.md
deleted file mode 100644
index 25402c2ea..000000000
--- a/docs/content/en/reference/cli/authelia-gen/authelia-gen_docs_time.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: "authelia-gen docs time"
-description: "Reference for the authelia-gen docs time command."
-lead: ""
-date: 2022-06-15T17:51:47+10:00
-draft: false
-images: []
-menu:
- reference:
- parent: "cli-authelia-gen"
-weight: 330
-toc: true
----
-
-## authelia-gen docs time
-
-Generate doc timestamps
-
-```
-authelia-gen docs time [flags]
-```
-
-### Options
-
-```
- --commit-since string The commit to check the logs since
- --commit-until string The commit to check the logs until (default "HEAD")
- -d, --directory string The directory to modify (default "./docs/content")
- -h, --help help for time
-```
-
-### Options inherited from parent commands
-
-```
- -C, --cwd string Sets the CWD for git commands
-```
-
-### SEE ALSO
-
-* [authelia-gen docs](authelia-gen_docs.md) - Generate docs
-
-###### Auto generated by spf13/cobra on 12-Jun-2022
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_github.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_github.md
new file mode 100644
index 000000000..432b603e9
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_github.md
@@ -0,0 +1,60 @@
+---
+title: "authelia-gen github"
+description: "Reference for the authelia-gen github command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen github
+
+Generate GitHub files
+
+```
+authelia-gen github [flags]
+```
+
+### Options
+
+```
+ -h, --help help for github
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen](authelia-gen.md) - Authelia's generator tooling
+* [authelia-gen github issue-templates](authelia-gen_github_issue-templates.md) - Generate GitHub issue templates
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates.md
new file mode 100644
index 000000000..bc855e6f0
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates.md
@@ -0,0 +1,61 @@
+---
+title: "authelia-gen github issue-templates"
+description: "Reference for the authelia-gen github issue-templates command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen github issue-templates
+
+Generate GitHub issue templates
+
+```
+authelia-gen github issue-templates [flags]
+```
+
+### Options
+
+```
+ -h, --help help for issue-templates
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen github](authelia-gen_github.md) - Generate GitHub files
+* [authelia-gen github issue-templates bug-report](authelia-gen_github_issue-templates_bug-report.md) - Generate GitHub bug report issue template
+* [authelia-gen github issue-templates feature-request](authelia-gen_github_issue-templates_feature-request.md) - Generate GitHub feature request issue template
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates_bug-report.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates_bug-report.md
new file mode 100644
index 000000000..0f6c5be4e
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates_bug-report.md
@@ -0,0 +1,59 @@
+---
+title: "authelia-gen github issue-templates bug-report"
+description: "Reference for the authelia-gen github issue-templates bug-report command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen github issue-templates bug-report
+
+Generate GitHub bug report issue template
+
+```
+authelia-gen github issue-templates bug-report [flags]
+```
+
+### Options
+
+```
+ -h, --help help for bug-report
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen github issue-templates](authelia-gen_github_issue-templates.md) - Generate GitHub issue templates
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates_feature-request.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates_feature-request.md
new file mode 100644
index 000000000..63ca5cb17
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_github_issue-templates_feature-request.md
@@ -0,0 +1,59 @@
+---
+title: "authelia-gen github issue-templates feature-request"
+description: "Reference for the authelia-gen github issue-templates feature-request command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen github issue-templates feature-request
+
+Generate GitHub feature request issue template
+
+```
+authelia-gen github issue-templates feature-request [flags]
+```
+
+### Options
+
+```
+ -h, --help help for feature-request
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen github issue-templates](authelia-gen_github_issue-templates.md) - Generate GitHub issue templates
+
diff --git a/docs/content/en/reference/cli/authelia-gen/authelia-gen_locales.md b/docs/content/en/reference/cli/authelia-gen/authelia-gen_locales.md
new file mode 100644
index 000000000..69d169ec1
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia-gen/authelia-gen_locales.md
@@ -0,0 +1,59 @@
+---
+title: "authelia-gen locales"
+description: "Reference for the authelia-gen locales command."
+lead: ""
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia-gen"
+weight: 330
+toc: true
+---
+
+## authelia-gen locales
+
+Generate locales files
+
+```
+authelia-gen locales [flags]
+```
+
+### Options
+
+```
+ -h, --help help for locales
+```
+
+### Options inherited from parent commands
+
+```
+ -C, --cwd string Sets the CWD for git commands
+ --dir.docs string The directory with the docs (default "docs")
+ --dir.docs.cli-reference string The directory to store the markdown in (default "en/reference/cli")
+ --dir.docs.content string The directory with the docs content (default "content")
+ --dir.docs.data string The directory with the docs data (default "data")
+ --dir.locales string The locales directory in relation to the root (default "internal/server/locales")
+ -d, --dir.root string The repository root (default "./")
+ -X, --exclude strings Sets the names of excluded generators
+ --file.bug-report string Sets the path of the bug report issue template file (default ".github/ISSUE_TEMPLATE/bug-report.yml")
+ --file.commit-lint-config string The commit lint javascript configuration file in relation to the root (default "web/.commitlintrc.js")
+ --file.configuration-keys string Sets the path of the keys file (default "internal/configuration/schema/keys.go")
+ --file.docs-commit-msg-guidelines string The commit message guidelines documentation file in relation to the root (default "docs/content/en/contributing/guidelines/commit-message.md")
+ --file.docs.data.keys string Sets the path of the docs keys file (default "configkeys.json")
+ --file.docs.data.languages string The languages docs data file in relation to the docs data folder (default "languages.json")
+ --file.docs.data.misc string The misc docs data file in relation to the docs data folder (default "misc.json")
+ --file.feature-request string Sets the path of the feature request issue template file (default ".github/ISSUE_TEMPLATE/feature-request.yml")
+ --file.scripts.gen string Sets the path of the authelia-scripts gen file (default "cmd/authelia-scripts/cmd/gen.go")
+ --file.server.generated string Sets the path of the server generated file (default "internal/server/gen.go")
+ --file.web-i18n string The i18n typescript configuration file in relation to the root (default "web/src/i18n/index.ts")
+ --package.configuration.keys string Sets the package name of the keys file (default "schema")
+ --package.scripts.gen string Sets the package name of the authelia-scripts gen file (default "cmd")
+ --versions int the maximum number of minor versions to list in output templates (default 5)
+```
+
+### SEE ALSO
+
+* [authelia-gen](authelia-gen.md) - Authelia's generator tooling
+
diff --git a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_certificates.md b/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_certificates.md
deleted file mode 100644
index 62a98ca28..000000000
--- a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_certificates.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: "authelia-scripts certificates"
-description: "Reference for the authelia-scripts certificates command."
-lead: ""
-date: 2022-06-15T17:51:47+10:00
-lastmod: 2022-06-03T11:17:29+10:00
-draft: false
-images: []
-menu:
- reference:
- parent: "cli-authelia-scripts"
-weight: 330
-toc: true
----
-
-## authelia-scripts certificates
-
-Commands related to certificate generation
-
-### Options
-
-```
- -h, --help help for certificates
- --host strings Comma-separated hostnames and IPs to generate a certificate for
-```
-
-### Options inherited from parent commands
-
-```
- --buildkite Set CI flag for Buildkite
- --log-level string Set the log level for the command (default "info")
-```
-
-### SEE ALSO
-
-* [authelia-scripts](authelia-scripts.md) -
-* [authelia-scripts certificates generate](authelia-scripts_certificates_generate.md) - Generate a self-signed certificate
-
-###### Auto generated by spf13/cobra on 3-Jun-2022
diff --git a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_certificates_generate.md b/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_certificates_generate.md
deleted file mode 100644
index 685f7c1a2..000000000
--- a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_certificates_generate.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-title: "authelia-scripts certificates generate"
-description: "Reference for the authelia-scripts certificates generate command."
-lead: ""
-date: 2022-06-15T17:51:47+10:00
-lastmod: 2022-06-03T11:17:29+10:00
-draft: false
-images: []
-menu:
- reference:
- parent: "cli-authelia-scripts"
-weight: 330
-toc: true
----
-
-## authelia-scripts certificates generate
-
-Generate a self-signed certificate
-
-```
-authelia-scripts certificates generate [flags]
-```
-
-### Options
-
-```
- --ca Whether this cert should be its own Certificate Authority
- --dir string Target directory where the certificate and keys will be stored
- --duration duration Duration that certificate is valid for (default 8760h0m0s)
- --ecdsa-curve string ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521
- --ed25519 Generate an Ed25519 key
- -h, --help help for generate
- --rsa-bits int Size of RSA key to generate. Ignored if --ecdsa-curve is set (default 2048)
- --start-date string Creation date formatted as Jan 1 15:04:05 2011
-```
-
-### Options inherited from parent commands
-
-```
- --buildkite Set CI flag for Buildkite
- --host strings Comma-separated hostnames and IPs to generate a certificate for
- --log-level string Set the log level for the command (default "info")
-```
-
-### SEE ALSO
-
-* [authelia-scripts certificates](authelia-scripts_certificates.md) - Commands related to certificate generation
-
-###### Auto generated by spf13/cobra on 3-Jun-2022
diff --git a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_hash-password.md b/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_hash-password.md
deleted file mode 100644
index c8236e2c1..000000000
--- a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_hash-password.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-title: "authelia-scripts hash-password"
-description: "Reference for the authelia-scripts hash-password command."
-lead: ""
-date: 2022-06-15T17:51:47+10:00
-lastmod: 2022-06-03T11:17:29+10:00
-draft: false
-images: []
-menu:
- reference:
- parent: "cli-authelia-scripts"
-weight: 330
-toc: true
----
-
-## authelia-scripts hash-password
-
-Hash a password to be used in file-based users database. Default algorithm is argon2id.
-
-```
-authelia-scripts hash-password [flags] --
-```
-
-### Options
-
-```
- -c, --config strings Configuration files
- -h, --help help for hash-password
- -i, --iterations int set the number of hashing iterations (default 3)
- -k, --key-length int [argon2id] set the key length param (default 32)
- -m, --memory int [argon2id] set the amount of memory param (in MB) (default 64)
- -p, --parallelism int [argon2id] set the parallelism param (default 4)
- -s, --salt string set the salt string
- -l, --salt-length int set the auto-generated salt length (default 16)
- -z, --sha512 use sha512 as the algorithm (changes iterations to 50000, change with -i)
-```
-
-### Options inherited from parent commands
-
-```
- --buildkite Set CI flag for Buildkite
- --log-level string Set the log level for the command (default "info")
-```
-
-### SEE ALSO
-
-* [authelia-scripts](authelia-scripts.md) -
-
-###### Auto generated by spf13/cobra on 3-Jun-2022
diff --git a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_rsa.md b/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_rsa.md
deleted file mode 100644
index 80196b3f5..000000000
--- a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_rsa.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-title: "authelia-scripts rsa"
-description: "Reference for the authelia-scripts rsa command."
-lead: ""
-date: 2022-06-15T17:51:47+10:00
-lastmod: 2022-06-03T11:17:29+10:00
-draft: false
-images: []
-menu:
- reference:
- parent: "cli-authelia-scripts"
-weight: 330
-toc: true
----
-
-## authelia-scripts rsa
-
-Commands related to rsa keypair generation
-
-### Options
-
-```
- -h, --help help for rsa
-```
-
-### Options inherited from parent commands
-
-```
- --buildkite Set CI flag for Buildkite
- --log-level string Set the log level for the command (default "info")
-```
-
-### SEE ALSO
-
-* [authelia-scripts](authelia-scripts.md) -
-* [authelia-scripts rsa generate](authelia-scripts_rsa_generate.md) - Generate a RSA keypair
-
-###### Auto generated by spf13/cobra on 3-Jun-2022
diff --git a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_rsa_generate.md b/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_rsa_generate.md
deleted file mode 100644
index 8707c1c67..000000000
--- a/docs/content/en/reference/cli/authelia-scripts/authelia-scripts_rsa_generate.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: "authelia-scripts rsa generate"
-description: "Reference for the authelia-scripts rsa generate command."
-lead: ""
-date: 2022-06-15T17:51:47+10:00
-lastmod: 2022-06-03T11:17:29+10:00
-draft: false
-images: []
-menu:
- reference:
- parent: "cli-authelia-scripts"
-weight: 330
-toc: true
----
-
-## authelia-scripts rsa generate
-
-Generate a RSA keypair
-
-```
-authelia-scripts rsa generate [flags]
-```
-
-### Options
-
-```
- -d, --dir string Target directory where the keypair will be stored
- -h, --help help for generate
- -b, --key-size int Sets the key size in bits (default 2048)
-```
-
-### Options inherited from parent commands
-
-```
- --buildkite Set CI flag for Buildkite
- --log-level string Set the log level for the command (default "info")
-```
-
-### SEE ALSO
-
-* [authelia-scripts rsa](authelia-scripts_rsa.md) - Commands related to rsa keypair generation
-
-###### Auto generated by spf13/cobra on 3-Jun-2022
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto.md b/docs/content/en/reference/cli/authelia/authelia_crypto.md
index 8ce6ba2b0..f979b583f 100644
--- a/docs/content/en/reference/cli/authelia/authelia_crypto.md
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto.md
@@ -38,5 +38,7 @@ authelia crypto --help
* [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown)
* [authelia crypto certificate](authelia_crypto_certificate.md) - Perform certificate cryptographic operations
+* [authelia crypto hash](authelia_crypto_hash.md) - Perform cryptographic hash operations
* [authelia crypto pair](authelia_crypto_pair.md) - Perform key pair cryptographic operations
+* [authelia crypto rand](authelia_crypto_rand.md) - Generate a cryptographically secure random string
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash.md
new file mode 100644
index 000000000..7068a1ca9
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash.md
@@ -0,0 +1,42 @@
+---
+title: "authelia crypto hash"
+description: "Reference for the authelia crypto hash command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto hash
+
+Perform cryptographic hash operations
+
+### Synopsis
+
+Perform cryptographic hash operations.
+
+This subcommand allows preforming hashing cryptographic tasks.
+
+### Examples
+
+```
+authelia crypto hash --help
+```
+
+### Options
+
+```
+ -h, --help help for hash
+```
+
+### SEE ALSO
+
+* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations
+* [authelia crypto hash generate](authelia_crypto_hash_generate.md) - Generate cryptographic hash digests
+* [authelia crypto hash validate](authelia_crypto_hash_validate.md) - Perform cryptographic hash validations
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate.md
new file mode 100644
index 000000000..9f0ed8d61
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate.md
@@ -0,0 +1,58 @@
+---
+title: "authelia crypto hash generate"
+description: "Reference for the authelia crypto hash generate command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto hash generate
+
+Generate cryptographic hash digests
+
+### Synopsis
+
+Generate cryptographic hash digests.
+
+This subcommand allows generating cryptographic hash digests.
+
+See the help for the subcommands if you want to override the configuration or defaults.
+
+```
+authelia crypto hash generate [flags]
+```
+
+### Examples
+
+```
+authelia crypto hash generate --help
+```
+
+### Options
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ -h, --help help for generate
+ --no-confirm skip the password confirmation prompt
+ --password string manually supply the password rather than using the terminal prompt
+ --random uses a randomly generated password
+ --random.characters string sets the explicit characters for the random string
+ --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
+ --random.length int sets the character length for the random string (default 72)
+```
+
+### SEE ALSO
+
+* [authelia crypto hash](authelia_crypto_hash.md) - Perform cryptographic hash operations
+* [authelia crypto hash generate argon2](authelia_crypto_hash_generate_argon2.md) - Generate cryptographic Argon2 hash digests
+* [authelia crypto hash generate bcrypt](authelia_crypto_hash_generate_bcrypt.md) - Generate cryptographic bcrypt hash digests
+* [authelia crypto hash generate pbkdf2](authelia_crypto_hash_generate_pbkdf2.md) - Generate cryptographic PBKDF2 hash digests
+* [authelia crypto hash generate scrypt](authelia_crypto_hash_generate_scrypt.md) - Generate cryptographic scrypt hash digests
+* [authelia crypto hash generate sha2crypt](authelia_crypto_hash_generate_sha2crypt.md) - Generate cryptographic SHA2 Crypt hash digests
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_argon2.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_argon2.md
new file mode 100644
index 000000000..b4cea0659
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_argon2.md
@@ -0,0 +1,63 @@
+---
+title: "authelia crypto hash generate argon2"
+description: "Reference for the authelia crypto hash generate argon2 command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto hash generate argon2
+
+Generate cryptographic Argon2 hash digests
+
+### Synopsis
+
+Generate cryptographic Argon2 hash digests.
+
+This subcommand allows generating cryptographic Argon2 hash digests.
+
+```
+authelia crypto hash generate argon2 [flags]
+```
+
+### Examples
+
+```
+authelia crypto hash generate argon2 --help
+```
+
+### Options
+
+```
+ -h, --help help for argon2
+ -i, --iterations int number of iterations (default 3)
+ -k, --key-size int key size in bytes (default 32)
+ -m, --memory int memory in kibibytes (default 65536)
+ -p, --parallelism int parallelism or threads (default 4)
+ --profile string profile to use, options are low-memory and recommended
+ -s, --salt-size int salt size in bytes (default 16)
+ -v, --variant string variant, options are 'argon2id', 'argon2i', and 'argon2d' (default "argon2id")
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ --no-confirm skip the password confirmation prompt
+ --password string manually supply the password rather than using the terminal prompt
+ --random uses a randomly generated password
+ --random.characters string sets the explicit characters for the random string
+ --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
+ --random.length int sets the character length for the random string (default 72)
+```
+
+### SEE ALSO
+
+* [authelia crypto hash generate](authelia_crypto_hash_generate.md) - Generate cryptographic hash digests
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_bcrypt.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_bcrypt.md
new file mode 100644
index 000000000..fdf1beb07
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_bcrypt.md
@@ -0,0 +1,58 @@
+---
+title: "authelia crypto hash generate bcrypt"
+description: "Reference for the authelia crypto hash generate bcrypt command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto hash generate bcrypt
+
+Generate cryptographic bcrypt hash digests
+
+### Synopsis
+
+Generate cryptographic bcrypt hash digests.
+
+This subcommand allows generating cryptographic bcrypt hash digests.
+
+```
+authelia crypto hash generate bcrypt [flags]
+```
+
+### Examples
+
+```
+authelia crypto hash generate bcrypt --help
+```
+
+### Options
+
+```
+ -i, --cost int hashing cost (default 12)
+ -h, --help help for bcrypt
+ -v, --variant string variant, options are 'standard' and 'sha256' (default "standard")
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ --no-confirm skip the password confirmation prompt
+ --password string manually supply the password rather than using the terminal prompt
+ --random uses a randomly generated password
+ --random.characters string sets the explicit characters for the random string
+ --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
+ --random.length int sets the character length for the random string (default 72)
+```
+
+### SEE ALSO
+
+* [authelia crypto hash generate](authelia_crypto_hash_generate.md) - Generate cryptographic hash digests
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_pbkdf2.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_pbkdf2.md
new file mode 100644
index 000000000..56c977f35
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_pbkdf2.md
@@ -0,0 +1,59 @@
+---
+title: "authelia crypto hash generate pbkdf2"
+description: "Reference for the authelia crypto hash generate pbkdf2 command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto hash generate pbkdf2
+
+Generate cryptographic PBKDF2 hash digests
+
+### Synopsis
+
+Generate cryptographic PBKDF2 hash digests.
+
+This subcommand allows generating cryptographic PBKDF2 hash digests.
+
+```
+authelia crypto hash generate pbkdf2 [flags]
+```
+
+### Examples
+
+```
+authelia crypto hash generate pbkdf2 --help
+```
+
+### Options
+
+```
+ -h, --help help for pbkdf2
+ -i, --iterations int number of iterations (default 310000)
+ -s, --salt-size int salt size in bytes (default 16)
+ -v, --variant string variant, options are 'sha1', 'sha224', 'sha256', 'sha384', and 'sha512' (default "sha512")
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ --no-confirm skip the password confirmation prompt
+ --password string manually supply the password rather than using the terminal prompt
+ --random uses a randomly generated password
+ --random.characters string sets the explicit characters for the random string
+ --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
+ --random.length int sets the character length for the random string (default 72)
+```
+
+### SEE ALSO
+
+* [authelia crypto hash generate](authelia_crypto_hash_generate.md) - Generate cryptographic hash digests
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_scrypt.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_scrypt.md
new file mode 100644
index 000000000..ca078fedf
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_scrypt.md
@@ -0,0 +1,61 @@
+---
+title: "authelia crypto hash generate scrypt"
+description: "Reference for the authelia crypto hash generate scrypt command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto hash generate scrypt
+
+Generate cryptographic scrypt hash digests
+
+### Synopsis
+
+Generate cryptographic scrypt hash digests.
+
+This subcommand allows generating cryptographic scrypt hash digests.
+
+```
+authelia crypto hash generate scrypt [flags]
+```
+
+### Examples
+
+```
+authelia crypto hash generate scrypt --help
+```
+
+### Options
+
+```
+ -r, --block-size int block size (default 8)
+ -h, --help help for scrypt
+ -i, --iterations int number of iterations (default 16)
+ -k, --key-size int key size in bytes (default 32)
+ -p, --parallelism int parallelism or threads (default 1)
+ -s, --salt-size int salt size in bytes (default 16)
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ --no-confirm skip the password confirmation prompt
+ --password string manually supply the password rather than using the terminal prompt
+ --random uses a randomly generated password
+ --random.characters string sets the explicit characters for the random string
+ --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
+ --random.length int sets the character length for the random string (default 72)
+```
+
+### SEE ALSO
+
+* [authelia crypto hash generate](authelia_crypto_hash_generate.md) - Generate cryptographic hash digests
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_sha2crypt.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_sha2crypt.md
new file mode 100644
index 000000000..6a35cab21
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_sha2crypt.md
@@ -0,0 +1,59 @@
+---
+title: "authelia crypto hash generate sha2crypt"
+description: "Reference for the authelia crypto hash generate sha2crypt command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto hash generate sha2crypt
+
+Generate cryptographic SHA2 Crypt hash digests
+
+### Synopsis
+
+Generate cryptographic SHA2 Crypt hash digests.
+
+This subcommand allows generating cryptographic SHA2 Crypt hash digests.
+
+```
+authelia crypto hash generate sha2crypt [flags]
+```
+
+### Examples
+
+```
+authelia crypto hash generate sha2crypt --help
+```
+
+### Options
+
+```
+ -h, --help help for sha2crypt
+ -i, --iterations int number of iterations (default 50000)
+ -s, --salt-size int salt size in bytes (default 16)
+ -v, --variant string variant, options are sha256 and sha512 (default "sha512")
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ --no-confirm skip the password confirmation prompt
+ --password string manually supply the password rather than using the terminal prompt
+ --random uses a randomly generated password
+ --random.characters string sets the explicit characters for the random string
+ --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
+ --random.length int sets the character length for the random string (default 72)
+```
+
+### SEE ALSO
+
+* [authelia crypto hash generate](authelia_crypto_hash_generate.md) - Generate cryptographic hash digests
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_validate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_validate.md
new file mode 100644
index 000000000..839569b71
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_validate.md
@@ -0,0 +1,46 @@
+---
+title: "authelia crypto hash validate"
+description: "Reference for the authelia crypto hash validate command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto hash validate
+
+Perform cryptographic hash validations
+
+### Synopsis
+
+Perform cryptographic hash validations.
+
+This subcommand allows preforming cryptographic hash validations. i.e. checking hash digests against a password.
+
+```
+authelia crypto hash validate [flags] --
+```
+
+### Examples
+
+```
+authelia crypto hash validate --help
+authelia crypto hash validate '$5$rounds=500000$WFjMpdCQxIkbNl0k$M0qZaZoK8Gwdh8Cw5diHgGfe5pE0iJvxcVG3.CVnQe.' -- 'p@ssw0rd'
+```
+
+### Options
+
+```
+ -h, --help help for validate
+ --password string manually supply the password rather than using the terminal prompt
+```
+
+### SEE ALSO
+
+* [authelia crypto hash](authelia_crypto_hash.md) - Perform cryptographic hash operations
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_rand.md b/docs/content/en/reference/cli/authelia/authelia_crypto_rand.md
new file mode 100644
index 000000000..fdf6d8664
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_crypto_rand.md
@@ -0,0 +1,55 @@
+---
+title: "authelia crypto rand"
+description: "Reference for the authelia crypto rand command."
+lead: ""
+date: 2022-10-17T21:51:59+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia crypto rand
+
+Generate a cryptographically secure random string
+
+### Synopsis
+
+Generate a cryptographically secure random string.
+
+This subcommand allows generating cryptographically secure random strings for use for encryption keys, HMAC keys, etc.
+
+```
+authelia crypto rand [flags]
+```
+
+### Examples
+
+```
+authelia crypto rand --help
+authelia crypto rand --length 80
+authelia crypto rand -n 80
+authelia crypto rand --charset alphanumeric
+authelia crypto rand --charset alphabetic
+authelia crypto rand --charset ascii
+authelia crypto rand --charset numeric
+authelia crypto rand --charset numeric-hex
+authelia crypto rand --characters 0123456789ABCDEF
+```
+
+### Options
+
+```
+ --characters string sets the explicit characters for the random string
+ -c, --charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
+ -h, --help help for rand
+ -n, --length int sets the character length for the random string (default 72)
+```
+
+### SEE ALSO
+
+* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_hash-password.md b/docs/content/en/reference/cli/authelia/authelia_hash-password.md
index e0fc9c883..7c7f97048 100644
--- a/docs/content/en/reference/cli/authelia/authelia_hash-password.md
+++ b/docs/content/en/reference/cli/authelia/authelia_hash-password.md
@@ -21,7 +21,7 @@ Hash a password to be used in file-based users database
Hash a password to be used in file-based users database.
```
-authelia hash-password [flags] --
+authelia hash-password [flags] -- [password]
```
### Examples
@@ -38,13 +38,13 @@ authelia hash-password --key-length=64 -- 'mypass'
### Options
```
- -c, --config strings Configuration files
+ -c, --config strings configuration files to load (default [configuration.yml])
-h, --help help for hash-password
-i, --iterations int set the number of hashing iterations (default 3)
-k, --key-length int [argon2id] set the key length param (default 32)
- -m, --memory int [argon2id] set the amount of memory param (in MB) (default 64)
+ -m, --memory int [argon2id] set the amount of memory param (in MB) (default 65536)
+ --no-confirm skip the password confirmation prompt
-p, --parallelism int [argon2id] set the parallelism param (default 4)
- -s, --salt string set the salt string
-l, --salt-length int set the auto-generated salt length (default 16)
-z, --sha512 use sha512 as the algorithm (changes iterations to 50000, change with -i)
```
diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user.md b/docs/content/en/reference/cli/authelia/authelia_storage_user.md
index 14561dddb..90fc3c917 100644
--- a/docs/content/en/reference/cli/authelia/authelia_storage_user.md
+++ b/docs/content/en/reference/cli/authelia/authelia_storage_user.md
@@ -62,4 +62,5 @@ authelia storage user --help
* [authelia storage](authelia_storage.md) - Manage the Authelia storage
* [authelia storage user identifiers](authelia_storage_user_identifiers.md) - Manage user opaque identifiers
* [authelia storage user totp](authelia_storage_user_totp.md) - Manage TOTP configurations
+* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage Webauthn devices
diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn.md
new file mode 100644
index 000000000..65ee8c777
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn.md
@@ -0,0 +1,65 @@
+---
+title: "authelia storage user webauthn"
+description: "Reference for the authelia storage user webauthn command."
+lead: ""
+date: 2022-06-15T17:51:47+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia storage user webauthn
+
+Manage Webauthn devices
+
+### Synopsis
+
+Manage Webauthn devices.
+
+This subcommand allows interacting with Webauthn devices.
+
+### Examples
+
+```
+authelia storage user webauthn --help
+```
+
+### Options
+
+```
+ -h, --help help for webauthn
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ --encryption-key string the storage encryption key to use
+ --mysql.database string the MySQL database name (default "authelia")
+ --mysql.host string the MySQL hostname
+ --mysql.password string the MySQL password
+ --mysql.port int the MySQL port (default 3306)
+ --mysql.username string the MySQL username (default "authelia")
+ --postgres.database string the PostgreSQL database name (default "authelia")
+ --postgres.host string the PostgreSQL hostname
+ --postgres.password string the PostgreSQL password
+ --postgres.port int the PostgreSQL port (default 5432)
+ --postgres.schema string the PostgreSQL schema name (default "public")
+ --postgres.ssl.certificate string the PostgreSQL ssl certificate file location
+ --postgres.ssl.key string the PostgreSQL ssl key file location
+ --postgres.ssl.mode string the PostgreSQL ssl mode (default "disable")
+ --postgres.ssl.root_certificate string the PostgreSQL ssl root certificate file location
+ --postgres.username string the PostgreSQL username (default "authelia")
+ --sqlite.path string the SQLite database path
+```
+
+### SEE ALSO
+
+* [authelia storage user](authelia_storage_user.md) - Manages user settings
+* [authelia storage user webauthn delete](authelia_storage_user_webauthn_delete.md) - Delete a WebAuthn device
+* [authelia storage user webauthn list](authelia_storage_user_webauthn_list.md) - List WebAuthn devices
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_delete.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_delete.md
new file mode 100644
index 000000000..cf2545624
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_delete.md
@@ -0,0 +1,78 @@
+---
+title: "authelia storage user webauthn delete"
+description: "Reference for the authelia storage user webauthn delete command."
+lead: ""
+date: 2022-06-15T17:51:47+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia storage user webauthn delete
+
+Delete a WebAuthn device
+
+### Synopsis
+
+Delete a WebAuthn device.
+
+This subcommand allows deleting a WebAuthn device directly from the database.
+
+```
+authelia storage user webauthn delete [username] [flags]
+```
+
+### Examples
+
+```
+authelia storage user webauthn delete john --all
+authelia storage user webauthn delete john --all --config config.yml
+authelia storage user webauthn delete john --all --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
+authelia storage user webauthn delete john --description Primary
+authelia storage user webauthn delete john --description Primary --config config.yml
+authelia storage user webauthn delete john --description Primary --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
+authelia storage user webauthn delete --kid abc123
+authelia storage user webauthn delete --kid abc123 --config config.yml
+authelia storage user webauthn delete --kid abc123 --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
+```
+
+### Options
+
+```
+ --all delete all of the users webauthn devices
+ --description string delete a users webauthn device by description
+ -h, --help help for delete
+ --kid string delete a users webauthn device by key id
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ --encryption-key string the storage encryption key to use
+ --mysql.database string the MySQL database name (default "authelia")
+ --mysql.host string the MySQL hostname
+ --mysql.password string the MySQL password
+ --mysql.port int the MySQL port (default 3306)
+ --mysql.username string the MySQL username (default "authelia")
+ --postgres.database string the PostgreSQL database name (default "authelia")
+ --postgres.host string the PostgreSQL hostname
+ --postgres.password string the PostgreSQL password
+ --postgres.port int the PostgreSQL port (default 5432)
+ --postgres.schema string the PostgreSQL schema name (default "public")
+ --postgres.ssl.certificate string the PostgreSQL ssl certificate file location
+ --postgres.ssl.key string the PostgreSQL ssl key file location
+ --postgres.ssl.mode string the PostgreSQL ssl mode (default "disable")
+ --postgres.ssl.root_certificate string the PostgreSQL ssl root certificate file location
+ --postgres.username string the PostgreSQL username (default "authelia")
+ --sqlite.path string the SQLite database path
+```
+
+### SEE ALSO
+
+* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage Webauthn devices
+
diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_list.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_list.md
new file mode 100644
index 000000000..185e8af15
--- /dev/null
+++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_list.md
@@ -0,0 +1,72 @@
+---
+title: "authelia storage user webauthn list"
+description: "Reference for the authelia storage user webauthn list command."
+lead: ""
+date: 2022-06-15T17:51:47+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "cli-authelia"
+weight: 330
+toc: true
+---
+
+## authelia storage user webauthn list
+
+List WebAuthn devices
+
+### Synopsis
+
+List WebAuthn devices.
+
+This subcommand allows listing WebAuthn devices.
+
+```
+authelia storage user webauthn list [username] [flags]
+```
+
+### Examples
+
+```
+authelia storage user webauthn list
+authelia storage user webauthn list john
+authelia storage user webauthn list --config config.yml
+authelia storage user webauthn list john --config config.yml
+authelia storage user webauthn list --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
+authelia storage user webauthn list john --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
+```
+
+### Options
+
+```
+ -h, --help help for list
+```
+
+### Options inherited from parent commands
+
+```
+ -c, --config strings configuration files to load (default [configuration.yml])
+ --encryption-key string the storage encryption key to use
+ --mysql.database string the MySQL database name (default "authelia")
+ --mysql.host string the MySQL hostname
+ --mysql.password string the MySQL password
+ --mysql.port int the MySQL port (default 3306)
+ --mysql.username string the MySQL username (default "authelia")
+ --postgres.database string the PostgreSQL database name (default "authelia")
+ --postgres.host string the PostgreSQL hostname
+ --postgres.password string the PostgreSQL password
+ --postgres.port int the PostgreSQL port (default 5432)
+ --postgres.schema string the PostgreSQL schema name (default "public")
+ --postgres.ssl.certificate string the PostgreSQL ssl certificate file location
+ --postgres.ssl.key string the PostgreSQL ssl key file location
+ --postgres.ssl.mode string the PostgreSQL ssl mode (default "disable")
+ --postgres.ssl.root_certificate string the PostgreSQL ssl root certificate file location
+ --postgres.username string the PostgreSQL username (default "authelia")
+ --sqlite.path string the SQLite database path
+```
+
+### SEE ALSO
+
+* [authelia storage user webauthn](authelia_storage_user_webauthn.md) - Manage Webauthn devices
+
diff --git a/docs/content/en/reference/guides/domain-sanitizaiton.md b/docs/content/en/reference/guides/domain-sanitizaiton.md
deleted file mode 100644
index 950e751b6..000000000
--- a/docs/content/en/reference/guides/domain-sanitizaiton.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: "Domain Sanitization"
-description: "This guide describes and helps users sanitize provided files to hide their domain"
-lead: "This guide describes and helps users sanitize provided files to hide their domain."
-date: 2022-08-26T13:50:51+10:00
-draft: false
-images: []
-menu:
- reference:
- parent: "guides"
-weight: 220
-toc: true
----
-
-Some users may wish to hide their domain in files provided during troubleshooting. While this is discouraged, if a user
-decides to perform this action it's critical for these purposes that you hide your domain in a very specific
-way. Most editors allow replacing all instances of a value, utilizing this is essential to making troubleshooting
-possible.
-
-## General Rule
-
-Only replace the purchased portion of domains. For example if you have `auth.abc123.com` and `app.abc123.com` they
-should become `auth.example.com` and `app.example.com`, i.e. replace all instances of `abc123.com` with `example.com`.
-
-## Multiple Domains
-
-*__Replacement Value:__* `example#.com` (where `#` is a unique number per domain)
-
-In instances where there are multiple domains it's recommended these domains are replaced with `example1.com`,
-`example2.com`, etc.
diff --git a/docs/content/en/reference/guides/generating-secure-values.md b/docs/content/en/reference/guides/generating-secure-values.md
new file mode 100644
index 000000000..0bdd574dd
--- /dev/null
+++ b/docs/content/en/reference/guides/generating-secure-values.md
@@ -0,0 +1,151 @@
+---
+title: "Generating Secure Values"
+description: "A reference guide on generating secure values such as password hashes, password strings, and cryptography keys"
+lead: "This section contains reference documentation for generating secure values such as password hashes, password strings, and cryptography keys."
+date: 2022-10-23T18:09:19+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "guides"
+weight: 220
+toc: true
+---
+
+## Generating a Random Password Hash
+
+Often times it's required that a random password is generated. While you could randomly generate a string then hash it,
+we provide a convenience layer for this purpose.
+
+### authelia
+
+The __Authelia__ docker container or CLI binary can be used to generate a random alphanumeric string and output the
+the string and the hash at the same time.
+
+Use the `authelia crypto hash generate --help` command or see the [authelia crypto hash generate] reference guide for
+more information on all available options and algorithms.
+
+##### Using Docker
+
+```bash
+docker run authelia/authelia:latest authelia crypto hash generate argon2 --random --random.length 64 --random.charset alphanumeric
+```
+
+##### Using the Binary
+
+```bash
+authelia crypto hash generate argon2 --random --random.length 64 --random.charset alphanumeric
+```
+
+## Generating a Random Alphanumeric String
+
+Some sections of the configuration recommend generating a random string. There are many ways to accomplish this and the
+following methods are merely suggestions.
+
+### authelia
+
+The __Authelia__ docker container or CLI binary can be used to generate a random alphanumeric string.
+
+Use the `authelia crypto rand --help` command or see the [authelia crypto rand] reference guide for more information on
+all available options.
+
+##### Using Docker
+
+```bash
+docker run authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric
+```
+
+##### Using the Binary
+
+```bash
+authelia crypto rand --length 64 --charset alphanumeric
+```
+
+### openssl
+
+The `openssl` command on Linux can be used to generate a random alphanumeric string:
+
+```bash
+openssl rand -hex 64
+```
+
+### Linux
+
+Basic Linux commands can be used to generate a random alphanumeric string:
+
+```bash
+LENGTH=64
+tr -cd '[:alnum:]' < /dev/urandom | fold -w "${LENGTH}" | head -n 1 | tr -d '\n' ; echo
+```
+
+## Generating an RSA Keypair
+
+Some sections of the configuration need an RSA keypair. There are many ways to achieve this, this section explains two
+such ways.
+
+### authelia
+
+The __Authelia__ docker container or CLI binary can be used to generate a RSA 4096 bit keypair.
+
+Use the `authelia crypto pair --help` command or see the [authelia crypto pair] reference guide for more
+information on all available options.
+
+##### Using Docker
+
+```bash
+docker run -u "$(id -u):$(id -g)" -v "$(pwd)":/keys authelia/authelia:latest authelia crypto pair rsa generate --bits 4096 --directory /keys
+```
+
+##### Using the Binary
+
+```bash
+authelia crypto pair rsa generate --directory /path/to/keys
+```
+
+### openssl
+
+The `openssl` command on Linux can be used to generate a RSA 4096 bit keypair:
+
+```bash
+openssl genrsa -out private.pem 4096
+openssl rsa -in private.pem -outform PEM -pubout -out public.pem
+```
+
+## Generating an RSA Self-Signed Certificate
+
+Some sections of the configuration need a certificate and it may be possible to use a self-signed certificate. There are
+many ways to achieve this, this section explains two such ways.
+
+### authelia
+
+The __Authelia__ docker container or binary can be used to generate a RSA 4096 bit self-signed certificate for the
+domain `example.com`.
+
+Use the `authelia crypto certificate --help` command or see the [authelia crypto certificate] reference guide for more
+information on all available options.
+
+##### Using Docker
+
+```bash
+docker run -u "$(id -u):$(id -g)" -v "$(pwd)":/keys authelia/authelia authelia crypto certificate rsa generate --common-name example.com --directory /keys
+```
+
+##### Using the Binary
+
+```bash
+authelia crypto certificate rsa generate --common-name example.com --directory /path/to/keys
+```
+
+### openssl
+
+The `openssl` command on Linux can be used to generate a RSA 4096 bit self-signed certificate for the domain
+`example.com`:
+
+```bash
+openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -subj '/CN=example.com'
+```
+
+[authelia crypto hash generate]: ../cli/authelia/authelia_crypto_hash_generate.md
+[authelia crypto rand]: ../cli/authelia/authelia_crypto_rand.md
+[authelia crypto pair]: ../cli/authelia/authelia_crypto_pair.md
+[authelia crypto certificate]: ../cli/authelia/authelia_crypto_certificate.md
diff --git a/docs/content/en/reference/guides/http-archive-file.md b/docs/content/en/reference/guides/http-archive-file.md
index 735676b34..ff18947e5 100644
--- a/docs/content/en/reference/guides/http-archive-file.md
+++ b/docs/content/en/reference/guides/http-archive-file.md
@@ -29,9 +29,7 @@ present in an environment.
The following section outlines some helpful information if you wish to sanitize your HAR file to share it with others.
-### Domain
-
-See the dedicated [Domain Sanitization](domain-sanitizaiton.md) reference guide.
+For generic sanitization information see the [Troubleshooting Sanitization guide](troubleshooting-sanitizaiton.md).
### Security Sensitive Information
diff --git a/docs/content/en/reference/guides/internationalization.md b/docs/content/en/reference/guides/internationalization.md
new file mode 100644
index 000000000..eb204bee9
--- /dev/null
+++ b/docs/content/en/reference/guides/internationalization.md
@@ -0,0 +1,17 @@
+---
+title: "Internationalization"
+description: "A collection of internationalization reference information"
+lead: "This section contains internationalization references for Authelia."
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "guides"
+weight: 220
+toc: true
+---
+
+## Web Portal Internationalization
+
+{{% table-i18n-locales %}}
diff --git a/docs/content/en/reference/guides/ldap.md b/docs/content/en/reference/guides/ldap.md
index 0867471b8..80a5eccb2 100644
--- a/docs/content/en/reference/guides/ldap.md
+++ b/docs/content/en/reference/guides/ldap.md
@@ -94,11 +94,18 @@ accounts. The active directory example has two attribute filters that accomplish
be appreciated). The userAccountControl filter checks that the account is not disabled and the pwdLastSet makes sure that
value is not 0 which means the password requires changing at the next login.
-| Implementation | Users Filter | Groups Filter |
-|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------:|
-| custom | N/A | N/A |
-| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))) | (&(member={dn})(objectClass=group)(objectCategory=group)) |
+| Implementation | Users Filter | Groups Filter |
+|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------------------------------------:|
+| custom | N/A | N/A |
+| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))) | (&(member={dn})(sAMAccountType=268435456)) |
-*__Note:__* The Active Directory filter `(sAMAccountType=805306368)` is exactly the same as
-`(&(objectCategory=person)(objectClass=user))` except that the former is more performant, you can read more about this
-and other Active Directory filters on the [TechNet wiki](https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx).
+##### Microsoft Active Directory sAMAccountType
+
+| Account Type Value | Description | Equivalent Filter |
+|:------------------:|:--------------------------:|:----------------------------------------------:|
+| 268435456 | Normal Group Objects | N/A |
+| 805306368 | Normal User Accounts | `(&(objectCategory=person)(objectClass=user))` |
+
+*__References:__*
+- Account Type Values: [Microsoft Learn](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/e742be45-665d-4576-b872-0bc99d1e1fbe).
+- LDAP Syntax Filters: [Microsoft TechNet Wiki](https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx)
diff --git a/docs/content/en/reference/guides/passwords.md b/docs/content/en/reference/guides/passwords.md
index 87a56bd85..42c204f25 100644
--- a/docs/content/en/reference/guides/passwords.md
+++ b/docs/content/en/reference/guides/passwords.md
@@ -25,6 +25,7 @@ The format of the [YAML] file is as follows:
```yaml
users:
john:
+ disabled: false
displayname: "John Doe"
password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email: john.doe@authelia.com
@@ -32,17 +33,20 @@ users:
- admins
- dev
harry:
+ disabled: false
displayname: "Harry Potter"
password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email: harry.potter@authelia.com
groups: []
bob:
+ disabled: false
displayname: "Bob Dylan"
password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email: bob.dylan@authelia.com
groups:
- dev
james:
+ disabled: false
displayname: "James Dean"
password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
email: james.dean@authelia.com
@@ -52,25 +56,33 @@ users:
The file contains hashed passwords instead of plain text passwords for security reasons.
-You can use Authelia binary or docker image to generate the hash of any password. The [hash-password] command has many
-tunable options, you can view them with the `authelia hash-password --help` command. For example if you wanted to
-improve the entropy you could generate a 16 byte salt and provide it with the `--salt` flag.
+You can use Authelia binary or docker image to generate the hash of any password. The [crypt hash generate] command has
+many supported algorithms. To view them run the `authelia crypto hash generate --help` command. To see the tunable
+options for an algorithm subcommand include that command before `--help`. For example for the [Argon2] algorithm use the
+`authelia crypto hash generate argon2 --help` command to see the available options.
-Example: `authelia hash-password --salt abcdefghijklhijl -- 'password'`.
-
-Passwords passed to [hash-password] should be single quoted if using special characters to prevent parameter
-substitution. In addition the password should be the last parameter, and should be after a `--`. For instance to
-generate a hash with the docker image just run:
+Passwords passed to [crypt hash generate] should be single quoted if using the `--password` parameter instead of the
+console prompt, especially if it has special characters to prevent parameter substitution. For instance to generate an
+[Argon2] hash with the docker image just run:
```bash
-$ docker run authelia/authelia:latest authelia hash-password -- 'password'
-Password hash: $argon2id$v=19$m=65536$3oc26byQuSkQqksq$zM1QiTvVPrMfV6BVLs2t4gM+af5IN7euO0VB6+Q8ZFs
+$ docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'password'
+Digest: $argon2id$v=19$m=65536,t=3,p=4$Hjc8e7WYcBFcJmEDUOsS9A$ozM7RyZR1EyDR8cuyVpDDfmLrGPGFgo5E2NNqRumui4
```
You may also use the `--config` flag to point to your existing configuration. When used, the values defined in the
-config will be used instead.
+config will be used instead. For example to generate the password with a configuration file named `configuration.yml`
+in the current directory:
-See the [full CLI reference documentation](../cli/authelia/authelia_hash-password.md).
+```bash
+$ docker run -v ./configuration.yml:/configuration.yml -it authelia/authelia:latest authelia crypto hash generate --config /configuration.yml
+Enter Password:
+Confirm Password:
+
+Digest: $argon2id$v=19$m=65536,t=3,p=4$Hjc8e7WYcBFcJmEDUOsS9A$ozM7RyZR1EyDR8cuyVpDDfmLrGPGFgo5E2NNqRumui4
+```
+
+See the [full CLI reference documentation](../cli/authelia/authelia_crypto_hash_generate.md).
### Cost
@@ -88,11 +100,11 @@ all algorithms. The main cost type measurements are:
* CPU
* Memory
-*__Important Note:__ When using algorithms that use a memory cost like [Argon2] it should be noted that this memory is
-released by Go after the hashing process completes, however the operating system may not reclaim the memory until a
-later time such as when the system is experiencing memory pressure which may cause the appearance of more memory being
-in use than Authelia is actually actively using. Authelia will typically reuse this memory if it has not be reclaimed as
-long as another hashing calculation is not still utilizing it.*
+*__Important Note:__ When using algorithms that use a memory cost like [Argon2] and [Scrypt] it should be noted that
+this memory is released by Go after the hashing process completes, however the operating system may not reclaim the
+memory until a later time such as when the system is experiencing memory pressure which may cause the appearance of more
+memory being in use than Authelia is actually actively using. Authelia will typically reuse this memory if it has not be
+reclaimed as long as another hashing calculation is not still utilizing it.*
To get a rough estimate of how much memory should be utilized with these algorithms you can utilize the following
command:
@@ -114,7 +126,7 @@ widely considered to be the best hashing algorithm, and in 2015 won the [Passwor
customizable parameters including a memory parameter allowing the [cost](#cost) of computing a hash to scale into the
future with better hardware which makes it harder to brute-force.
-For backwards compatibility and user choice support for the [SHA Crypt] algorithm (`SHA512` variant) is still available.
+For backwards compatibility and user choice support for the [SHA2 Crypt] algorithm (`SHA512` variant) is still available.
While it's a reasonable hashing function given high enough iterations, as hardware improves it has a higher chance of
being brute-forced since it only allows scaling the CPU [cost](#cost) whereas [Argon2] allows scaling both for CPU and
Memory [cost](#cost).
@@ -123,10 +135,21 @@ Memory [cost](#cost).
The algorithm that a hash is utilizing is identifiable by its prefix:
-| Algorithm | Variant | Prefix |
-|:-----------:|:--------:|:------------:|
-| [Argon2] | `id` | `$argon2id$` |
-| [SHA Crypt] | `SHA512` | `$6$` |
+| Algorithm | Variant | Prefix |
+|:------------:|:----------:|:-----------------:|
+| [Argon2] | `argon2id` | `$argon2id$` |
+| [Argon2] | `argon2i` | `$argon2i$` |
+| [Argon2] | `argon2d` | `$argon2d$` |
+| [Scrypt] | N/A | `$scrypt$` |
+| [PBKDF2] | `sha1` | `$pbkdf2$` |
+| [PBKDF2] | `sha224` | `$pbkdf2-sha224$` |
+| [PBKDF2] | `sha256` | `$pbkdf2-sha256$` |
+| [PBKDF2] | `sha384` | `$pbkdf2-sha384$` |
+| [PBKDF2] | `sha512` | `$pbkdf2-sha512$` |
+| [SHA2 Crypt] | `SHA256` | `$5$` |
+| [SHA2 Crypt] | `SHA512` | `$6$` |
+| [Bcrypt] | `standard` | `$2b$` |
+| [Bcrypt] | `sha256` | `$bcrypt-sha256$` |
See the [Crypt (C) Wiki page](https://en.wikipedia.org/wiki/Crypt_(C)) for more information.
@@ -140,27 +163,44 @@ adequately determine the [cost](#cost).
While there are recommended parameters for each algorithm it's your responsibility to tune these individually for your
particular system.
-#### Recommended Parameters: Argon2id
+#### Algorithm Choice
+
+We generally discourage [Bcrypt] except when needed for interoperability with legacy systems. The `argon2id` variant of
+the [Argon2] algorithm is the best choice of the algorithms available, but it's important to note that the `argon2id`
+variant is the most resilient variant, followed by the `argon2d` variant and the `argon2i` variant not being recommended.
+It's strongly recommended if you're unsure that you use `argon2id`. [Scrypt] is a likely second best algorithm. [PBKDF2]
+is practically the only choice when it comes to [FIPS-140 compliance]. The `sha512` variant of the [SHA2 Crypt]
+algorithm is also a reasonable option, but is mainly available for backwards compatability.
+
+All other algorithms and variants available exist only for interoperability and we discourage their use if a better
+algorithm is available in your scenario.
+
+#### Recommended Parameters: Argon2
This table adapts the [RFC9106 Parameter Choice] recommendations to our configuration options:
-| Situation | Iterations (t) | Parallelism (p) | Memory (m) | Salt Size | Key Size |
-|:-----------:|:--------------:|:---------------:|:----------:|:---------:|:--------:|
-| Low Memory | 3 | 4 | 64 | 16 | 32 |
-| Recommended | 1 | 4 | 2048 | 16 | 32 |
+| Situation | Variant | Iterations (t) | Parallelism (p) | Memory (m) | Salt Size | Key Size |
+|:-----------:|:--------:|:--------------:|:---------------:|:----------:|:---------:|:--------:|
+| Low Memory | argon2id | 3 | 4 | 65536 | 16 | 32 |
+| Recommended | argon2id | 1 | 4 | 2097152 | 16 | 32 |
-#### Recommended Parameters: SHA512
+#### Recommended Parameters: SHA2 Crypt
-This table suggests the parameters for the [SHA Crypt] (`SHA512` variant) algorithm:
+This table suggests the parameters for the [SHA2 Crypt] algorithm:
-| Situation | Iterations (rounds) | Salt Size |
-|:------------:|:-------------------:|:---------:|
-| Standard CPU | 50000 | 16 |
-| High End CPU | 150000 | 16 |
+| Situation | Variant | Iterations (rounds) | Salt Size |
+|:------------:|:-------:|:-------------------:|:---------:|
+| Standard CPU | sha512 | 50000 | 16 |
+| High End CPU | sha512 | 150000 | 16 |
+
+[Argon2]: https://www.rfc-editor.org/rfc/rfc9106.html
+[Scrypt]: https://en.wikipedia.org/wiki/Scrypt
+[PBKDF2]: https://www.ietf.org/rfc/rfc2898.html
+[SHA2 Crypt]: https://www.akkadia.org/drepper/SHA-crypt.txt
+[Bcrypt]: https://en.wikipedia.org/wiki/Bcrypt
+[FIPS-140 compliance]: https://csrc.nist.gov/publications/detail/fips/140/2/final
[RFC9106 Parameter Choice]: https://www.rfc-editor.org/rfc/rfc9106.html#section-4
[YAML]: https://yaml.org/
-[Argon2]: https://www.rfc-editor.org/rfc/rfc9106.html
-[SHA Crypt]: https://www.akkadia.org/drepper/SHA-crypt.txt
-[hash-password]: ../cli/authelia/authelia_hash-password.md
+[crypt hash generate]: ../cli/authelia/authelia_crypto_hash_generate.md
[Password Hashing Competition]: https://en.wikipedia.org/wiki/Password_Hashing_Competition
diff --git a/docs/content/en/reference/guides/rule-operators.md b/docs/content/en/reference/guides/rule-operators.md
new file mode 100644
index 000000000..7bd4bff85
--- /dev/null
+++ b/docs/content/en/reference/guides/rule-operators.md
@@ -0,0 +1,134 @@
+---
+title: "Access Control Rule Guide"
+description: "A reference guide on access control rule operators"
+lead: "This section contains a reference guide on access control rule operators."
+date: 2022-10-19T14:09:22+11:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "guides"
+weight: 220
+toc: true
+---
+
+## Operators
+
+Rule operators are effectively words which alter the behaviour of particular access control rules. The following table
+is a guide on their use.
+
+| Operator | Effect |
+|:-------------:|:--------------------------------------------------------------:|
+| `equal` | Matches when the item value is equal to the provided value |
+| `not equal` | Matches when the item value is not equal to the provided value |
+| `present` | Matches when the item is present with any value |
+| `absent` | Matches when the item is not present at all |
+| `pattern` | Matches when the item matches the regex pattern |
+| `not pattern` | Matches when the item doesn't match the regex pattern |
+
+
+## Multi-level Logical Criteria
+
+Criteria which is described as multi-level logical criteria indicates that it is a list of lists. The first level i.e.
+the list least indented to the right will be referred to the `OR-list`, and the list most indented to the right will be
+referred to the `AND-list`.
+
+The OR-list matches if any of the criteria from it's AND-list's matches; in other words, a *__logical OR__*. The
+AND-list matches if all of it's criteria matches the given request; in other words, a *__logical AND__*.
+
+In addition to these rules, if the AND-list only needs one item, it can be represented without the second level.
+
+### Examples
+
+#### List of Lists
+
+The following examples show various abstract examples to express a rule that matches either c, or a AND b;
+i.e `(a AND b) OR (c)`. In relation to access control rules all of these should be treated the same. This format should
+not be used for the configuration item type `list(list(object))`, see [List of List Objects](#list-of-list-objects)
+instead.
+
+##### Fully Expressed
+
+```yaml
+rule:
+ - - 'a'
+ - 'b'
+ - - 'c'
+```
+
+##### Omitted Level
+
+```yaml
+rule:
+ - - 'a'
+ - 'b'
+ - 'c'
+```
+
+##### Compact
+
+```yaml
+rule:
+ - ['a', 'b']
+ - ['c']
+```
+
+##### Compact with Omitted Level
+
+```yaml
+rule:
+ - ['a', 'b']
+ - 'c'
+```
+
+##### Super Compact
+
+```yaml
+rule: [['a', 'b'], ['c']]
+```
+
+#### List of List Objects
+
+The following examples show various abstract examples that mirror the above rules however the AND-list is a list of
+objects where the key is named `value`. This format should only be used for the configuration item type
+`list(list(object))`, see [List of Lists](#list-of-lists) if you're not looking for a `list(list(object))`
+
+##### Fully Expressed
+
+```yaml
+rule:
+ - - value: 'a'
+ - value: 'b'
+ - - value: 'c'
+```
+
+##### Omitted Level
+
+```yaml
+rule:
+ - - 'a'
+ - 'b'
+ - value: 'c'
+```
+
+##### Compact
+
+```yaml
+rule:
+ - ['a', 'b']
+ - ['c']
+```
+
+##### Compact with Omitted Level
+
+```yaml
+rule:
+ - ['a', 'b']
+ - 'c'
+```
+
+##### Super Compact
+
+```yaml
+rule: [['a', 'b'], ['c']]
+```
diff --git a/docs/content/en/reference/guides/troubleshooting-sanitizaiton.md b/docs/content/en/reference/guides/troubleshooting-sanitizaiton.md
new file mode 100644
index 000000000..a73df0959
--- /dev/null
+++ b/docs/content/en/reference/guides/troubleshooting-sanitizaiton.md
@@ -0,0 +1,43 @@
+---
+title: "Troubleshooting Sanitization"
+description: "This guide describes and helps users sanitize provided files to hide privacy related values for troubleshooting"
+lead: "This guide describes and helps users sanitize provided files to hide information for privacy."
+date: 2022-09-16T14:21:05+10:00
+draft: false
+images: []
+menu:
+ reference:
+ parent: "guides"
+weight: 220
+toc: true
+aliases:
+ - /r/sanitize
+ - /reference/guides/domain-sanitizaiton
+---
+
+Some users may wish to hide their domain in files provided during troubleshooting. While this is discouraged, if a user
+decides to perform this action it's critical for these purposes that you hide your domain in a very specific
+way. Most editors allow replacing all instances of a value, utilizing this is essential to making troubleshooting
+possible.
+
+## General Rules
+
+1. Only replace the purchased portion of domains:
+ - For example if you have `auth.abc123.com` and `app.abc123.com` they
+ should become `auth.example.com` and `app.example.com`, i.e. replace all instances of `abc123.com` with `example.com`.
+2. Make sure value replaced is replaced with a unique value:
+ - For example if you replace `abc123.com` with `example.com` DO NOT replace any other value other than `abc123.com` with
+ `example.com`. The same rule applies to IP addresses, usernames, and groups.
+3. Make sure the value replaced is replaced across logs, configuration, and any references:
+ - For example if you replace `abc123.com` with `example.com` in your configuration, make exactly the same replacement
+ for the log files.
+4. Make sure this consistency is followed for all communication regarding a single issue.
+
+## Multiple Domains
+
+*__Replacement Value:__* `example#.com` (where `#` is a unique number per domain)
+
+In instances where there are multiple domains it's recommended these domains are replaced with `example1.com`,
+`example2.com`, etc.
+
+## Specific Values
diff --git a/docs/content/en/roadmap/active/dashboard-control-panel.md b/docs/content/en/roadmap/active/dashboard-control-panel.md
index ff6ae1b8d..4ea61c4c8 100644
--- a/docs/content/en/roadmap/active/dashboard-control-panel.md
+++ b/docs/content/en/roadmap/active/dashboard-control-panel.md
@@ -26,7 +26,7 @@ due to how important or difficult to implement they are.
### Initial Implementation
-{{< roadmap-status >}}
+{{< roadmap-status stage="in-progress" version="v4.38.0" >}}
Add control panel with the ability to control all of the current settings, with the added benefit of being able to
register multiple WebAuthn keys.
diff --git a/docs/content/en/roadmap/active/openid-connect.md b/docs/content/en/roadmap/active/openid-connect.md
index 54b464e90..180a5a24a 100644
--- a/docs/content/en/roadmap/active/openid-connect.md
+++ b/docs/content/en/roadmap/active/openid-connect.md
@@ -93,6 +93,26 @@ Feature List:
### Beta 5
+{{< roadmap-status stage="complete" version="v4.37.0" >}}
+
+Feature List:
+
+* [JWK's backed by X509 Certificate Chains](https://www.rfc-editor.org/rfc/rfc7517#section-4.7)
+* Hashed Client Secrets
+* Per-Client [Consent](https://openid.net/specs/openid-connect-core-1_0.html#Consent) Mode:
+ * Explicit:
+ * The default
+ * Always asks for end-user consent
+ * Implicit:
+ * Not expressly standards compliant
+ * Never asks for end-user consent
+ * Not compatible with the consent prompt type
+ * Pre-Configured:
+ * Allows users to save consent sessions for a duration configured by the administrator
+ * Operates nearly identically to the explicit consent mode
+
+### Beta 6
+
{{< roadmap-status >}}
Feature List:
@@ -102,7 +122,7 @@ Feature List:
See [OpenID Connect Core (Mandatory to Implement Features for All OpenID Providers)].
-### Beta 6
+### Beta 7
{{< roadmap-status >}}
@@ -110,7 +130,6 @@ Feature List:
* Revoke Tokens on User Logout or Expiration
* [JSON Web Key Rotation](https://openid.net/specs/openid-connect-messages-1_0-20.html#rotate.sig.keys)
-* Hashed Client Secrets
### General Availability
diff --git a/docs/content/en/roadmap/active/webauthn.md b/docs/content/en/roadmap/active/webauthn.md
index 08040a167..65f19262d 100644
--- a/docs/content/en/roadmap/active/webauthn.md
+++ b/docs/content/en/roadmap/active/webauthn.md
@@ -41,7 +41,7 @@ Implement [WebAuthn] as a replacement for [FIDO U2F] with backwards compatibilit
### Multi Device Registration
-{{< roadmap-status >}}
+{{< roadmap-status stage="in-progress" version="v4.38.0" >}}
Implement multi device registration as part of the user interface. This is technically implemented for the most part in
the backend, it's just the public facing interface elements remaining.
diff --git a/docs/data/configkeys.json b/docs/data/configkeys.json
new file mode 100644
index 000000000..20377f12a
--- /dev/null
+++ b/docs/data/configkeys.json
@@ -0,0 +1 @@
+[{"path":"theme","secret":false,"env":"AUTHELIA_THEME"},{"path":"certificates_directory","secret":false,"env":"AUTHELIA_CERTIFICATES_DIRECTORY"},{"path":"jwt_secret","secret":true,"env":"AUTHELIA_JWT_SECRET_FILE"},{"path":"default_redirection_url","secret":false,"env":"AUTHELIA_DEFAULT_REDIRECTION_URL"},{"path":"default_2fa_method","secret":false,"env":"AUTHELIA_DEFAULT_2FA_METHOD"},{"path":"log.level","secret":false,"env":"AUTHELIA_LOG_LEVEL"},{"path":"log.format","secret":false,"env":"AUTHELIA_LOG_FORMAT"},{"path":"log.file_path","secret":false,"env":"AUTHELIA_LOG_FILE_PATH"},{"path":"log.keep_stdout","secret":false,"env":"AUTHELIA_LOG_KEEP_STDOUT"},{"path":"identity_providers.oidc.hmac_secret","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE"},{"path":"identity_providers.oidc.issuer_certificate_chain","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_CERTIFICATE_CHAIN_FILE"},{"path":"identity_providers.oidc.issuer_private_key","secret":true,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE"},{"path":"identity_providers.oidc.access_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ACCESS_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.authorize_code_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_AUTHORIZE_CODE_LIFESPAN"},{"path":"identity_providers.oidc.id_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ID_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.refresh_token_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_REFRESH_TOKEN_LIFESPAN"},{"path":"identity_providers.oidc.enable_client_debug_messages","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_CLIENT_DEBUG_MESSAGES"},{"path":"identity_providers.oidc.minimum_parameter_entropy","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_MINIMUM_PARAMETER_ENTROPY"},{"path":"identity_providers.oidc.enforce_pkce","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENFORCE_PKCE"},{"path":"identity_providers.oidc.enable_pkce_plain_challenge","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_ENABLE_PKCE_PLAIN_CHALLENGE"},{"path":"identity_providers.oidc.cors.endpoints","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ENDPOINTS"},{"path":"identity_providers.oidc.cors.allowed_origins","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ALLOWED_ORIGINS"},{"path":"identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ALLOWED_ORIGINS_FROM_CLIENT_REDIRECT_URIS"},{"path":"identity_providers.oidc.clients","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_CLIENTS"},{"path":"authentication_backend.password_reset.disable","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_DISABLE"},{"path":"authentication_backend.password_reset.custom_url","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_PASSWORD_RESET_CUSTOM_URL"},{"path":"authentication_backend.refresh_interval","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_REFRESH_INTERVAL"},{"path":"authentication_backend.file.path","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PATH"},{"path":"authentication_backend.file.watch","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_WATCH"},{"path":"authentication_backend.file.password.algorithm","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ALGORITHM"},{"path":"authentication_backend.file.password.argon2.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_VARIANT"},{"path":"authentication_backend.file.password.argon2.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_ITERATIONS"},{"path":"authentication_backend.file.password.argon2.memory","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_MEMORY"},{"path":"authentication_backend.file.password.argon2.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_PARALLELISM"},{"path":"authentication_backend.file.password.argon2.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_KEY_LENGTH"},{"path":"authentication_backend.file.password.argon2.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ARGON2_SALT_LENGTH"},{"path":"authentication_backend.file.password.sha2crypt.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_VARIANT"},{"path":"authentication_backend.file.password.sha2crypt.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_ITERATIONS"},{"path":"authentication_backend.file.password.sha2crypt.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SHA2CRYPT_SALT_LENGTH"},{"path":"authentication_backend.file.password.pbkdf2.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_VARIANT"},{"path":"authentication_backend.file.password.pbkdf2.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_ITERATIONS"},{"path":"authentication_backend.file.password.pbkdf2.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PBKDF2_SALT_LENGTH"},{"path":"authentication_backend.file.password.bcrypt.variant","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_VARIANT"},{"path":"authentication_backend.file.password.bcrypt.cost","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_BCRYPT_COST"},{"path":"authentication_backend.file.password.scrypt.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_ITERATIONS"},{"path":"authentication_backend.file.password.scrypt.block_size","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_BLOCK_SIZE"},{"path":"authentication_backend.file.password.scrypt.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_PARALLELISM"},{"path":"authentication_backend.file.password.scrypt.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_KEY_LENGTH"},{"path":"authentication_backend.file.password.scrypt.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SCRYPT_SALT_LENGTH"},{"path":"authentication_backend.file.password.iterations","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_ITERATIONS"},{"path":"authentication_backend.file.password.memory","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_MEMORY"},{"path":"authentication_backend.file.password.parallelism","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_PARALLELISM"},{"path":"authentication_backend.file.password.key_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_KEY_LENGTH"},{"path":"authentication_backend.file.password.salt_length","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_PASSWORD_SALT_LENGTH"},{"path":"authentication_backend.file.search.email","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_EMAIL"},{"path":"authentication_backend.file.search.case_insensitive","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_FILE_SEARCH_CASE_INSENSITIVE"},{"path":"authentication_backend.ldap.implementation","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_IMPLEMENTATION"},{"path":"authentication_backend.ldap.url","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_URL"},{"path":"authentication_backend.ldap.timeout","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TIMEOUT"},{"path":"authentication_backend.ldap.start_tls","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_START_TLS"},{"path":"authentication_backend.ldap.tls.minimum_version","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MINIMUM_VERSION"},{"path":"authentication_backend.ldap.tls.maximum_version","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_MAXIMUM_VERSION"},{"path":"authentication_backend.ldap.tls.skip_verify","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SKIP_VERIFY"},{"path":"authentication_backend.ldap.tls.server_name","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_SERVER_NAME"},{"path":"authentication_backend.ldap.tls.private_key","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_PRIVATE_KEY_FILE"},{"path":"authentication_backend.ldap.tls.certificate_chain","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"authentication_backend.ldap.base_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_BASE_DN"},{"path":"authentication_backend.ldap.additional_users_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_USERS_DN"},{"path":"authentication_backend.ldap.users_filter","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USERS_FILTER"},{"path":"authentication_backend.ldap.additional_groups_dn","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDITIONAL_GROUPS_DN"},{"path":"authentication_backend.ldap.groups_filter","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUPS_FILTER"},{"path":"authentication_backend.ldap.group_name_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_GROUP_NAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.username_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USERNAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.mail_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_MAIL_ATTRIBUTE"},{"path":"authentication_backend.ldap.display_name_attribute","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_DISPLAY_NAME_ATTRIBUTE"},{"path":"authentication_backend.ldap.permit_referrals","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_REFERRALS"},{"path":"authentication_backend.ldap.permit_unauthenticated_bind","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_UNAUTHENTICATED_BIND"},{"path":"authentication_backend.ldap.permit_feature_detection_failure","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PERMIT_FEATURE_DETECTION_FAILURE"},{"path":"authentication_backend.ldap.user","secret":false,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USER"},{"path":"authentication_backend.ldap.password","secret":true,"env":"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE"},{"path":"session.name","secret":false,"env":"AUTHELIA_SESSION_NAME"},{"path":"session.domain","secret":false,"env":"AUTHELIA_SESSION_DOMAIN"},{"path":"session.same_site","secret":false,"env":"AUTHELIA_SESSION_SAME_SITE"},{"path":"session.secret","secret":true,"env":"AUTHELIA_SESSION_SECRET_FILE"},{"path":"session.expiration","secret":false,"env":"AUTHELIA_SESSION_EXPIRATION"},{"path":"session.inactivity","secret":false,"env":"AUTHELIA_SESSION_INACTIVITY"},{"path":"session.remember_me_duration","secret":false,"env":"AUTHELIA_SESSION_REMEMBER_ME_DURATION"},{"path":"session.redis.host","secret":false,"env":"AUTHELIA_SESSION_REDIS_HOST"},{"path":"session.redis.port","secret":false,"env":"AUTHELIA_SESSION_REDIS_PORT"},{"path":"session.redis.username","secret":false,"env":"AUTHELIA_SESSION_REDIS_USERNAME"},{"path":"session.redis.password","secret":true,"env":"AUTHELIA_SESSION_REDIS_PASSWORD_FILE"},{"path":"session.redis.database_index","secret":false,"env":"AUTHELIA_SESSION_REDIS_DATABASE_INDEX"},{"path":"session.redis.maximum_active_connections","secret":false,"env":"AUTHELIA_SESSION_REDIS_MAXIMUM_ACTIVE_CONNECTIONS"},{"path":"session.redis.minimum_idle_connections","secret":false,"env":"AUTHELIA_SESSION_REDIS_MINIMUM_IDLE_CONNECTIONS"},{"path":"session.redis.tls.minimum_version","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_MINIMUM_VERSION"},{"path":"session.redis.tls.maximum_version","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_MAXIMUM_VERSION"},{"path":"session.redis.tls.skip_verify","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_SKIP_VERIFY"},{"path":"session.redis.tls.server_name","secret":false,"env":"AUTHELIA_SESSION_REDIS_TLS_SERVER_NAME"},{"path":"session.redis.tls.private_key","secret":true,"env":"AUTHELIA_SESSION_REDIS_TLS_PRIVATE_KEY_FILE"},{"path":"session.redis.tls.certificate_chain","secret":true,"env":"AUTHELIA_SESSION_REDIS_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"session.redis.high_availability.sentinel_name","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_NAME"},{"path":"session.redis.high_availability.sentinel_username","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_USERNAME"},{"path":"session.redis.high_availability.sentinel_password","secret":true,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE"},{"path":"session.redis.high_availability.nodes","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_NODES"},{"path":"session.redis.high_availability.route_by_latency","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_BY_LATENCY"},{"path":"session.redis.high_availability.route_randomly","secret":false,"env":"AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_ROUTE_RANDOMLY"},{"path":"totp.disable","secret":false,"env":"AUTHELIA_TOTP_DISABLE"},{"path":"totp.issuer","secret":false,"env":"AUTHELIA_TOTP_ISSUER"},{"path":"totp.algorithm","secret":false,"env":"AUTHELIA_TOTP_ALGORITHM"},{"path":"totp.digits","secret":false,"env":"AUTHELIA_TOTP_DIGITS"},{"path":"totp.period","secret":false,"env":"AUTHELIA_TOTP_PERIOD"},{"path":"totp.skew","secret":false,"env":"AUTHELIA_TOTP_SKEW"},{"path":"totp.secret_size","secret":false,"env":"AUTHELIA_TOTP_SECRET_SIZE"},{"path":"duo_api.disable","secret":false,"env":"AUTHELIA_DUO_API_DISABLE"},{"path":"duo_api.hostname","secret":false,"env":"AUTHELIA_DUO_API_HOSTNAME"},{"path":"duo_api.integration_key","secret":true,"env":"AUTHELIA_DUO_API_INTEGRATION_KEY_FILE"},{"path":"duo_api.secret_key","secret":true,"env":"AUTHELIA_DUO_API_SECRET_KEY_FILE"},{"path":"duo_api.enable_self_enrollment","secret":false,"env":"AUTHELIA_DUO_API_ENABLE_SELF_ENROLLMENT"},{"path":"access_control.default_policy","secret":false,"env":"AUTHELIA_ACCESS_CONTROL_DEFAULT_POLICY"},{"path":"access_control.networks","secret":false,"env":"AUTHELIA_ACCESS_CONTROL_NETWORKS"},{"path":"access_control.rules","secret":false,"env":"AUTHELIA_ACCESS_CONTROL_RULES"},{"path":"ntp.address","secret":false,"env":"AUTHELIA_NTP_ADDRESS"},{"path":"ntp.version","secret":false,"env":"AUTHELIA_NTP_VERSION"},{"path":"ntp.max_desync","secret":false,"env":"AUTHELIA_NTP_MAX_DESYNC"},{"path":"ntp.disable_startup_check","secret":false,"env":"AUTHELIA_NTP_DISABLE_STARTUP_CHECK"},{"path":"ntp.disable_failure","secret":false,"env":"AUTHELIA_NTP_DISABLE_FAILURE"},{"path":"regulation.max_retries","secret":false,"env":"AUTHELIA_REGULATION_MAX_RETRIES"},{"path":"regulation.find_time","secret":false,"env":"AUTHELIA_REGULATION_FIND_TIME"},{"path":"regulation.ban_time","secret":false,"env":"AUTHELIA_REGULATION_BAN_TIME"},{"path":"storage.local.path","secret":false,"env":"AUTHELIA_STORAGE_LOCAL_PATH"},{"path":"storage.mysql.host","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_HOST"},{"path":"storage.mysql.port","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_PORT"},{"path":"storage.mysql.database","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_DATABASE"},{"path":"storage.mysql.username","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_USERNAME"},{"path":"storage.mysql.password","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE"},{"path":"storage.mysql.timeout","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TIMEOUT"},{"path":"storage.mysql.tls.minimum_version","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_MINIMUM_VERSION"},{"path":"storage.mysql.tls.maximum_version","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_MAXIMUM_VERSION"},{"path":"storage.mysql.tls.skip_verify","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_SKIP_VERIFY"},{"path":"storage.mysql.tls.server_name","secret":false,"env":"AUTHELIA_STORAGE_MYSQL_TLS_SERVER_NAME"},{"path":"storage.mysql.tls.private_key","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_TLS_PRIVATE_KEY_FILE"},{"path":"storage.mysql.tls.certificate_chain","secret":true,"env":"AUTHELIA_STORAGE_MYSQL_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"storage.postgres.host","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_HOST"},{"path":"storage.postgres.port","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_PORT"},{"path":"storage.postgres.database","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_DATABASE"},{"path":"storage.postgres.username","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_USERNAME"},{"path":"storage.postgres.password","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE"},{"path":"storage.postgres.timeout","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TIMEOUT"},{"path":"storage.postgres.schema","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SCHEMA"},{"path":"storage.postgres.tls.minimum_version","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_MINIMUM_VERSION"},{"path":"storage.postgres.tls.maximum_version","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_MAXIMUM_VERSION"},{"path":"storage.postgres.tls.skip_verify","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_SKIP_VERIFY"},{"path":"storage.postgres.tls.server_name","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_SERVER_NAME"},{"path":"storage.postgres.tls.private_key","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_PRIVATE_KEY_FILE"},{"path":"storage.postgres.tls.certificate_chain","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"storage.postgres.ssl.mode","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_MODE"},{"path":"storage.postgres.ssl.root_certificate","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_ROOT_CERTIFICATE"},{"path":"storage.postgres.ssl.certificate","secret":false,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_CERTIFICATE"},{"path":"storage.postgres.ssl.key","secret":true,"env":"AUTHELIA_STORAGE_POSTGRES_SSL_KEY_FILE"},{"path":"storage.encryption_key","secret":true,"env":"AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE"},{"path":"notifier.disable_startup_check","secret":false,"env":"AUTHELIA_NOTIFIER_DISABLE_STARTUP_CHECK"},{"path":"notifier.filesystem.filename","secret":false,"env":"AUTHELIA_NOTIFIER_FILESYSTEM_FILENAME"},{"path":"notifier.smtp.host","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_HOST"},{"path":"notifier.smtp.port","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_PORT"},{"path":"notifier.smtp.timeout","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TIMEOUT"},{"path":"notifier.smtp.username","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_USERNAME"},{"path":"notifier.smtp.password","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE"},{"path":"notifier.smtp.identifier","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_IDENTIFIER"},{"path":"notifier.smtp.sender","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_SENDER"},{"path":"notifier.smtp.subject","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_SUBJECT"},{"path":"notifier.smtp.startup_check_address","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_STARTUP_CHECK_ADDRESS"},{"path":"notifier.smtp.disable_require_tls","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_REQUIRE_TLS"},{"path":"notifier.smtp.disable_html_emails","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_HTML_EMAILS"},{"path":"notifier.smtp.disable_starttls","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_DISABLE_STARTTLS"},{"path":"notifier.smtp.tls.minimum_version","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_MINIMUM_VERSION"},{"path":"notifier.smtp.tls.maximum_version","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_MAXIMUM_VERSION"},{"path":"notifier.smtp.tls.skip_verify","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_SKIP_VERIFY"},{"path":"notifier.smtp.tls.server_name","secret":false,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_SERVER_NAME"},{"path":"notifier.smtp.tls.private_key","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_PRIVATE_KEY_FILE"},{"path":"notifier.smtp.tls.certificate_chain","secret":true,"env":"AUTHELIA_NOTIFIER_SMTP_TLS_CERTIFICATE_CHAIN_FILE"},{"path":"notifier.template_path","secret":false,"env":"AUTHELIA_NOTIFIER_TEMPLATE_PATH"},{"path":"server.host","secret":false,"env":"AUTHELIA_SERVER_HOST"},{"path":"server.port","secret":false,"env":"AUTHELIA_SERVER_PORT"},{"path":"server.path","secret":false,"env":"AUTHELIA_SERVER_PATH"},{"path":"server.asset_path","secret":false,"env":"AUTHELIA_SERVER_ASSET_PATH"},{"path":"server.enable_pprof","secret":false,"env":"AUTHELIA_SERVER_ENABLE_PPROF"},{"path":"server.enable_expvars","secret":false,"env":"AUTHELIA_SERVER_ENABLE_EXPVARS"},{"path":"server.disable_healthcheck","secret":false,"env":"AUTHELIA_SERVER_DISABLE_HEALTHCHECK"},{"path":"server.tls.certificate","secret":false,"env":"AUTHELIA_SERVER_TLS_CERTIFICATE"},{"path":"server.tls.key","secret":true,"env":"AUTHELIA_SERVER_TLS_KEY_FILE"},{"path":"server.tls.client_certificates","secret":false,"env":"AUTHELIA_SERVER_TLS_CLIENT_CERTIFICATES"},{"path":"server.headers.csp_template","secret":false,"env":"AUTHELIA_SERVER_HEADERS_CSP_TEMPLATE"},{"path":"server.buffers.read","secret":false,"env":"AUTHELIA_SERVER_BUFFERS_READ"},{"path":"server.buffers.write","secret":false,"env":"AUTHELIA_SERVER_BUFFERS_WRITE"},{"path":"server.timeouts.read","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_READ"},{"path":"server.timeouts.write","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_WRITE"},{"path":"server.timeouts.idle","secret":false,"env":"AUTHELIA_SERVER_TIMEOUTS_IDLE"},{"path":"telemetry.metrics.enabled","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_ENABLED"},{"path":"telemetry.metrics.address","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_ADDRESS"},{"path":"telemetry.metrics.buffers.read","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_BUFFERS_READ"},{"path":"telemetry.metrics.buffers.write","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_BUFFERS_WRITE"},{"path":"telemetry.metrics.timeouts.read","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_READ"},{"path":"telemetry.metrics.timeouts.write","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_WRITE"},{"path":"telemetry.metrics.timeouts.idle","secret":false,"env":"AUTHELIA_TELEMETRY_METRICS_TIMEOUTS_IDLE"},{"path":"webauthn.disable","secret":false,"env":"AUTHELIA_WEBAUTHN_DISABLE"},{"path":"webauthn.display_name","secret":false,"env":"AUTHELIA_WEBAUTHN_DISPLAY_NAME"},{"path":"webauthn.attestation_conveyance_preference","secret":false,"env":"AUTHELIA_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE"},{"path":"webauthn.user_verification","secret":false,"env":"AUTHELIA_WEBAUTHN_USER_VERIFICATION"},{"path":"webauthn.timeout","secret":false,"env":"AUTHELIA_WEBAUTHN_TIMEOUT"},{"path":"password_policy.standard.enabled","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_ENABLED"},{"path":"password_policy.standard.min_length","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_MIN_LENGTH"},{"path":"password_policy.standard.max_length","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_MAX_LENGTH"},{"path":"password_policy.standard.require_uppercase","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_UPPERCASE"},{"path":"password_policy.standard.require_lowercase","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_LOWERCASE"},{"path":"password_policy.standard.require_number","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_NUMBER"},{"path":"password_policy.standard.require_special","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_STANDARD_REQUIRE_SPECIAL"},{"path":"password_policy.zxcvbn.enabled","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_ZXCVBN_ENABLED"},{"path":"password_policy.zxcvbn.min_score","secret":false,"env":"AUTHELIA_PASSWORD_POLICY_ZXCVBN_MIN_SCORE"}]
\ No newline at end of file
diff --git a/docs/data/languages.json b/docs/data/languages.json
index 3578e9dde..cc3952d0e 100644
--- a/docs/data/languages.json
+++ b/docs/data/languages.json
@@ -1 +1 @@
-{"defaultLocale":"en","defaultNamespace":"portal","namespaces":["portal"],"languages":[{"display":"German","locale":"de","namespaces":["portal"],"fallbacks":["en"]},{"display":"English","locale":"en","namespaces":["portal"],"fallbacks":["en"]},{"display":"Spanish","locale":"es","namespaces":["portal"],"fallbacks":["en"]},{"display":"French","locale":"fr","namespaces":["portal"],"fallbacks":["en"]},{"display":"Russian","locale":"ru","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish","locale":"sv","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish (Sweden)","locale":"sv-SE","namespaces":["portal"],"fallbacks":["sv","en"]},{"display":"Chinese (Taiwan)","locale":"zh-TW","namespaces":["portal"],"fallbacks":["en"]}]}
\ No newline at end of file
+{"defaults":{"language":{"display":"English","locale":"en"},"namespace":"portal"},"namespaces":["portal"],"languages":[{"display":"English","locale":"en","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic","locale":"ar","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic (Saudi Arabia)","locale":"ar-SA","namespaces":["portal"],"fallbacks":["ar","en"]},{"display":"Czech","locale":"cs","namespaces":["portal"],"fallbacks":["en"]},{"display":"Czech (Czechia)","locale":"cs-CZ","namespaces":["portal"],"fallbacks":["cs","en"]},{"display":"Danish","locale":"da","namespaces":["portal"],"fallbacks":["en"]},{"display":"Danish (Denmark)","locale":"da-DK","namespaces":["portal"],"fallbacks":["da","en"]},{"display":"German","locale":"de","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek","locale":"el","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek (Greece)","locale":"el-GR","namespaces":["portal"],"fallbacks":["el","en"]},{"display":"Spanish","locale":"es","namespaces":["portal"],"fallbacks":["en"]},{"display":"Finnish","locale":"fi","namespaces":["portal"],"fallbacks":["en"]},{"display":"French","locale":"fr","namespaces":["portal"],"fallbacks":["en"]},{"display":"Italian","locale":"it","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese","locale":"ja","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese (Japan)","locale":"ja-JP","namespaces":["portal"],"fallbacks":["ja","en"]},{"display":"Norwegian Bokmål","locale":"nb","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål (Norway)","locale":"nb-NO","namespaces":["portal"],"fallbacks":["nb","en"]},{"display":"Dutch","locale":"nl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål","locale":"no","namespaces":["portal"],"fallbacks":["en"]},{"display":"Polish","locale":"pl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Portuguese","locale":"pt","namespaces":["portal"],"fallbacks":["en"]},{"display":"Brazilian Portuguese","locale":"pt-BR","namespaces":["portal"],"fallbacks":["en"]},{"display":"Romanian","locale":"ro","namespaces":["portal"],"fallbacks":["en"]},{"display":"Russian","locale":"ru","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish","locale":"sv","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish (Sweden)","locale":"sv-SE","namespaces":["portal"],"fallbacks":["sv","en"]},{"display":"Ukrainian","locale":"uk","namespaces":["portal"],"fallbacks":["en"]},{"display":"Ukrainian (Ukraine)","locale":"uk-UA","namespaces":["portal"],"fallbacks":["uk","en"]},{"display":"Chinese","locale":"zh","namespaces":["portal"],"fallbacks":["en"]},{"display":"Chinese (China)","locale":"zh-CN","namespaces":["portal"],"fallbacks":["zh","en"]},{"display":"Chinese (Taiwan)","locale":"zh-TW","namespaces":["portal"],"fallbacks":["en"]}]}
\ No newline at end of file
diff --git a/docs/data/misc.json b/docs/data/misc.json
new file mode 100644
index 000000000..f20002991
--- /dev/null
+++ b/docs/data/misc.json
@@ -0,0 +1 @@
+{"csp":{"default":"default-src 'self'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-${NONCE}'; frame-ancestors 'none'; base-uri 'self'","development":"default-src 'self' 'unsafe-eval'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-${NONCE}'; frame-ancestors 'none'; base-uri 'self'","nonce":"${NONCE}"}}
\ No newline at end of file
diff --git a/docs/layouts/index.headers b/docs/layouts/index.headers
index 0497a1a72..796227e08 100644
--- a/docs/layouts/index.headers
+++ b/docs/layouts/index.headers
@@ -3,7 +3,7 @@
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
- Content-Security-Policy: default-src 'self'; script-src 'self' https://*.netlify.app 'unsafe-inline' 'sha512-RGGByJUOP98hE4wFZM78RM/3MijWJs0Tm0DbfrFhCDCXKXfDx60fii+syp5iMs3UcNX/1H4zJNgmqSejfhHrYw==' 'sha512-+T2H7TEv2U6umnIOWYijvTIrzdCZUYhm/FZo4YYQzKAHf8NWs+38cn3t9fdz2rCm2HqHDkthZZXnY4EWPdWnMA==' 'sha512-okYuGnNmmUuCX64AD7FVra0445z43U8riOY3jZue+WZ2KeVOWLo17hE/wZXGUIJh9WBiSHZ2epTd36MMP6R66w==' 'sha512-bv9WRsSROhTW5djDurORNUCGITVeRfjDXkhqg4Ez/4vTY6FcaVBPy4MXpn4EGC3J3oZNcxpfQIScElDKlmiLhw==' 'sha512-RBYr6Ld4w1yVqaACrgrBLQfPgGhj/1jyacA74WxJ1KM6KVcSWymwrdDwb3HDcdpwiNJ5yssot1He0U9vXoQVlg==' 'sha256-aWZ3y/RxbBYKHXH0z8+8ljrHG1mSBvyzSfxSMjBSaXk=' 'sha256-vOgyKS2vkH4n5TxBJpeh9SgzrE6LVGsAeOAvEST6oCc='; style-src 'self' https://*.netlify.app 'unsafe-inline'; img-src 'self' https://*.netlify.app data:; connect-src 'self' https://*.netlify.app; font-src 'self' https://*.netlify.app; manifest-src 'self' https://*.netlify.app; object-src 'none'; frame-src https://app.netlify.com; frame-ancestors 'none'; base-uri 'none'; require-trusted-types-for 'script'
+ Content-Security-Policy: default-src 'self'; script-src 'self' https://*.netlify.app 'unsafe-inline' 'sha512-RGGByJUOP98hE4wFZM78RM/3MijWJs0Tm0DbfrFhCDCXKXfDx60fii+syp5iMs3UcNX/1H4zJNgmqSejfhHrYw==' 'sha512-+T2H7TEv2U6umnIOWYijvTIrzdCZUYhm/FZo4YYQzKAHf8NWs+38cn3t9fdz2rCm2HqHDkthZZXnY4EWPdWnMA==' 'sha512-okYuGnNmmUuCX64AD7FVra0445z43U8riOY3jZue+WZ2KeVOWLo17hE/wZXGUIJh9WBiSHZ2epTd36MMP6R66w==' 'sha512-bv9WRsSROhTW5djDurORNUCGITVeRfjDXkhqg4Ez/4vTY6FcaVBPy4MXpn4EGC3J3oZNcxpfQIScElDKlmiLhw==' 'sha512-RBYr6Ld4w1yVqaACrgrBLQfPgGhj/1jyacA74WxJ1KM6KVcSWymwrdDwb3HDcdpwiNJ5yssot1He0U9vXoQVlg==' 'sha256-aWZ3y/RxbBYKHXH0z8+8ljrHG1mSBvyzSfxSMjBSaXk=' 'sha256-vOgyKS2vkH4n5TxBJpeh9SgzrE6LVGsAeOAvEST6oCc='; style-src 'self' https://*.netlify.app 'unsafe-inline'; img-src 'self' https://*.netlify.app data:; connect-src 'self' https://*.netlify.app; font-src 'self' https://*.netlify.app; manifest-src 'self' https://*.netlify.app; object-src 'none'; frame-src https://app.netlify.com; frame-ancestors 'none'; base-uri 'none'
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin
Feature-Policy: geolocation 'self'
diff --git a/docs/layouts/index.redirects b/docs/layouts/index.redirects
index 9ae778ce3..d116bbe08 100644
--- a/docs/layouts/index.redirects
+++ b/docs/layouts/index.redirects
@@ -8,8 +8,11 @@
/l/charts https://charts.authelia.com
/l/translate https://translate.authelia.com
/l/github https://github.com/authelia/authelia
-/l/bug https://github.com/authelia/authelia/issues/new?assignees=&labels=Possible+Bug&template=bug_report.md
-/l/fr https://github.com/authelia/authelia/issues/new?assignees=&labels=Feature+Request&template=feature_request.md
+/l/bug https://github.com/authelia/authelia/issues/new?assignees=&labels=type%2Fbug%2Funconfirmed%2Cstatus%2Fneeds-triage%2Cpriority%2F4%2Fnormal&template=bug-report.yml
+/l/fr https://github.com/authelia/authelia/issues/new?assignees=&labels=type%2Ffeature%2Cstatus%2Fneeds-design%2Cpriority%2F4%2Fnormal&template=feature-request.yml
+
+## Short Links (docs)
+/c/acl-match-concept-2 /configuration/security/access-control/#rule-matching-concept-2-subject-criteria-requires-authentication
## The following maps the old site to the new one.
/docs/ /
diff --git a/docs/layouts/policies/list.html b/docs/layouts/policies/list.html
new file mode 100644
index 000000000..41b36f8c7
--- /dev/null
+++ b/docs/layouts/policies/list.html
@@ -0,0 +1,22 @@
+{{ define "main" }}
+
+
+
+ {{ if eq .CurrentSection .FirstSection }}{{ .Section | humanize }}{{ else }}{{ .Title }}{{ end }}
+ {{ .Content }}
+
+ {{ $currentSection := .CurrentSection }}
+ {{ range where .Site.RegularPages.ByWeight "Section" .Section }}
+ {{ if in (.RelPermalink | string) $currentSection.RelPermalink }}
+
+ {{ end }}
+ {{ end }}
+
+
+
+
+{{ end }}
diff --git a/docs/layouts/shortcodes/csp.html b/docs/layouts/shortcodes/csp.html
new file mode 100644
index 000000000..b5465737e
--- /dev/null
+++ b/docs/layouts/shortcodes/csp.html
@@ -0,0 +1,2 @@
+Placeholder Value: {{ $.Site.Data.misc.csp.nonce }}
+Default Template: {{ $.Site.Data.misc.csp.default }}
diff --git a/docs/layouts/shortcodes/table-config-keys.html b/docs/layouts/shortcodes/table-config-keys.html
new file mode 100644
index 000000000..6f74b1c3e
--- /dev/null
+++ b/docs/layouts/shortcodes/table-config-keys.html
@@ -0,0 +1,14 @@
+{{ $secrets := .Get "secrets" }}
+| Configuration Key | Environment Variable |
+|:-----------------:|:--------------------:|
+{{- range $.Site.Data.configkeys }}
+{{- if eq "true" $secrets }}
+{{- if .secret }}
+| [{{ .path }}] | {{ .env }} |
+{{- end }}
+{{- else }}
+{{- if not .secret }}
+| {{ .path }} | {{ .env }} |
+{{- end }}
+{{- end }}
+{{- end }}
diff --git a/docs/layouts/shortcodes/table-i18n-locales.html b/docs/layouts/shortcodes/table-i18n-locales.html
new file mode 100644
index 000000000..246df3805
--- /dev/null
+++ b/docs/layouts/shortcodes/table-i18n-locales.html
@@ -0,0 +1,5 @@
+| Language | Locale | Namespaces | Fallbacks |
+|:--------:|:------:|:----------:|:---------:|
+{{- range $.Site.Data.languages.languages }}
+|{{ .display }}|{{ .locale }}|{{ range $i, $v := .namespaces }}{{ if eq $i 0 }}{{ $v }}{{ else }}, {{ $v }}{{ end }}{{ end }}|{{ range $i, $v := .fallbacks }}{{ if eq $i 0 }}{{ $v }}{{ else }}, {{ $v }}{{ end }}{{ end }}|
+{{- end }}
diff --git a/docs/package.json b/docs/package.json
index a896d171c..1e9c47f9d 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -38,36 +38,36 @@
"version": "auto-changelog -p && git add CHANGELOG.md"
},
"devDependencies": {
- "@babel/cli": "7.18.10",
- "@babel/core": "7.18.13",
- "@babel/preset-env": "7.18.10",
- "@fullhuman/postcss-purgecss": "4.1.3",
- "@hyas/images": "0.2.3",
+ "@babel/cli": "7.19.3",
+ "@babel/core": "7.20.2",
+ "@babel/preset-env": "7.20.2",
+ "@fullhuman/postcss-purgecss": "5.0.0",
+ "@hyas/images": "0.3.2",
"@popperjs/core": "2.11.6",
"auto-changelog": "2.4.0",
- "autoprefixer": "10.4.8",
- "bootstrap": "5.2.0",
- "bootstrap-icons": "1.9.1",
+ "autoprefixer": "10.4.13",
+ "bootstrap": "5.2.2",
+ "bootstrap-icons": "1.10.0",
"clipboard": "2.0.11",
- "eslint": "8.23.0",
+ "eslint": "8.27.0",
"exec-bin": "1.0.0",
- "flexsearch": "0.7.21",
+ "flexsearch": "0.7.31",
"highlight.js": "11.6.0",
- "hugo-installer": "3.1.0",
+ "hugo-installer": "4.0.1",
"instant.page": "5.1.1",
- "katex": "0.16.2",
+ "katex": "0.16.3",
"lazysizes": "5.3.2",
"markdownlint-cli2": "0.5.1",
"netlify-plugin-submit-sitemap": "0.4.0",
- "node-fetch": "3.2.10",
- "postcss": "8.4.16",
+ "node-fetch": "3.3.0",
+ "postcss": "8.4.19",
"postcss-cli": "10.0.0",
"purgecss-whitelister": "2.4.0",
"shx": "0.3.4",
- "stylelint": "14.11.0",
- "stylelint-config-standard-scss": "4.0.0"
+ "stylelint": "14.14.1",
+ "stylelint-config-standard-scss": "6.1.0"
},
"otherDependencies": {
- "hugo": "0.102.2"
+ "hugo": "0.105.0"
}
}
diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml
index 18a8c9690..acc309003 100644
--- a/docs/pnpm-lock.yaml
+++ b/docs/pnpm-lock.yaml
@@ -1,64 +1,64 @@
lockfileVersion: 5.4
specifiers:
- '@babel/cli': 7.18.10
- '@babel/core': 7.18.13
- '@babel/preset-env': 7.18.10
- '@fullhuman/postcss-purgecss': 4.1.3
- '@hyas/images': 0.2.3
+ '@babel/cli': 7.19.3
+ '@babel/core': 7.20.2
+ '@babel/preset-env': 7.20.2
+ '@fullhuman/postcss-purgecss': 5.0.0
+ '@hyas/images': 0.3.2
'@popperjs/core': 2.11.6
auto-changelog: 2.4.0
- autoprefixer: 10.4.8
- bootstrap: 5.2.0
- bootstrap-icons: 1.9.1
+ autoprefixer: 10.4.13
+ bootstrap: 5.2.2
+ bootstrap-icons: 1.10.0
clipboard: 2.0.11
- eslint: 8.23.0
+ eslint: 8.27.0
exec-bin: 1.0.0
- flexsearch: 0.7.21
+ flexsearch: 0.7.31
highlight.js: 11.6.0
- hugo-installer: 3.1.0
+ hugo-installer: 4.0.1
instant.page: 5.1.1
- katex: 0.16.2
+ katex: 0.16.3
lazysizes: 5.3.2
markdownlint-cli2: 0.5.1
netlify-plugin-submit-sitemap: 0.4.0
- node-fetch: 3.2.10
- postcss: 8.4.16
+ node-fetch: 3.3.0
+ postcss: 8.4.19
postcss-cli: 10.0.0
purgecss-whitelister: 2.4.0
shx: 0.3.4
- stylelint: 14.11.0
- stylelint-config-standard-scss: 4.0.0
+ stylelint: 14.14.1
+ stylelint-config-standard-scss: 6.1.0
devDependencies:
- '@babel/cli': 7.18.10_@babel+core@7.18.13
- '@babel/core': 7.18.13
- '@babel/preset-env': 7.18.10_@babel+core@7.18.13
- '@fullhuman/postcss-purgecss': 4.1.3_postcss@8.4.16
- '@hyas/images': 0.2.3
+ '@babel/cli': 7.19.3_@babel+core@7.20.2
+ '@babel/core': 7.20.2
+ '@babel/preset-env': 7.20.2_@babel+core@7.20.2
+ '@fullhuman/postcss-purgecss': 5.0.0_postcss@8.4.19
+ '@hyas/images': 0.3.2
'@popperjs/core': 2.11.6
auto-changelog: 2.4.0
- autoprefixer: 10.4.8_postcss@8.4.16
- bootstrap: 5.2.0_@popperjs+core@2.11.6
- bootstrap-icons: 1.9.1
+ autoprefixer: 10.4.13_postcss@8.4.19
+ bootstrap: 5.2.2_@popperjs+core@2.11.6
+ bootstrap-icons: 1.10.0
clipboard: 2.0.11
- eslint: 8.23.0
+ eslint: 8.27.0
exec-bin: 1.0.0
- flexsearch: 0.7.21
+ flexsearch: 0.7.31
highlight.js: 11.6.0
- hugo-installer: 3.1.0
+ hugo-installer: 4.0.1
instant.page: 5.1.1
- katex: 0.16.2
+ katex: 0.16.3
lazysizes: 5.3.2
markdownlint-cli2: 0.5.1
netlify-plugin-submit-sitemap: 0.4.0
- node-fetch: 3.2.10
- postcss: 8.4.16
- postcss-cli: 10.0.0_postcss@8.4.16
+ node-fetch: 3.3.0
+ postcss: 8.4.19
+ postcss-cli: 10.0.0_postcss@8.4.19
purgecss-whitelister: 2.4.0
shx: 0.3.4
- stylelint: 14.11.0
- stylelint-config-standard-scss: 4.0.0_74u2qasxp5mudia6vln5kcfuni
+ stylelint: 14.14.1
+ stylelint-config-standard-scss: 6.1.0_ave2i6l4ingtbwj4aquhd5witq
packages:
@@ -70,14 +70,14 @@ packages:
'@jridgewell/trace-mapping': 0.3.13
dev: true
- /@babel/cli/7.18.10_@babel+core@7.18.13:
- resolution: {integrity: sha512-dLvWH+ZDFAkd2jPBSghrsFBuXrREvFwjpDycXbmUoeochqKYe4zNSLEJYErpLg8dvxvZYe79/MkN461XCwpnGw==}
+ /@babel/cli/7.19.3_@babel+core@7.20.2:
+ resolution: {integrity: sha512-643/TybmaCAe101m2tSVHi9UKpETXP9c/Ff4mD2tAwkdP6esKIfaauZFc67vGEM6r9fekbEGid+sZhbEnSe3dg==}
engines: {node: '>=6.9.0'}
hasBin: true
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@jridgewell/trace-mapping': 0.3.13
commander: 4.1.1
convert-source-map: 1.8.0
@@ -97,25 +97,25 @@ packages:
'@babel/highlight': 7.18.6
dev: true
- /@babel/compat-data/7.18.8:
- resolution: {integrity: sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==}
+ /@babel/compat-data/7.20.1:
+ resolution: {integrity: sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==}
engines: {node: '>=6.9.0'}
dev: true
- /@babel/core/7.18.13:
- resolution: {integrity: sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==}
+ /@babel/core/7.20.2:
+ resolution: {integrity: sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.2.0
'@babel/code-frame': 7.18.6
- '@babel/generator': 7.18.13
- '@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.13
- '@babel/helper-module-transforms': 7.18.9
- '@babel/helpers': 7.18.9
- '@babel/parser': 7.18.13
+ '@babel/generator': 7.20.4
+ '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.2
+ '@babel/helper-module-transforms': 7.20.2
+ '@babel/helpers': 7.20.1
+ '@babel/parser': 7.20.3
'@babel/template': 7.18.10
- '@babel/traverse': 7.18.13
- '@babel/types': 7.18.13
+ '@babel/traverse': 7.20.1
+ '@babel/types': 7.20.2
convert-source-map: 1.8.0
debug: 4.3.4
gensync: 1.0.0-beta.2
@@ -125,11 +125,11 @@ packages:
- supports-color
dev: true
- /@babel/generator/7.18.13:
- resolution: {integrity: sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==}
+ /@babel/generator/7.20.4:
+ resolution: {integrity: sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
'@jridgewell/gen-mapping': 0.3.2
jsesc: 2.5.2
dev: true
@@ -138,7 +138,7 @@ packages:
resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
/@babel/helper-builder-binary-assignment-operator-visitor/7.18.6:
@@ -146,59 +146,59 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-explode-assignable-expression': 7.18.6
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
- /@babel/helper-compilation-targets/7.18.9_@babel+core@7.18.13:
- resolution: {integrity: sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==}
+ /@babel/helper-compilation-targets/7.20.0_@babel+core@7.20.2:
+ resolution: {integrity: sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
- '@babel/compat-data': 7.18.8
- '@babel/core': 7.18.13
+ '@babel/compat-data': 7.20.1
+ '@babel/core': 7.20.2
'@babel/helper-validator-option': 7.18.6
- browserslist: 4.21.3
+ browserslist: 4.21.4
semver: 6.3.0
dev: true
- /@babel/helper-create-class-features-plugin/7.18.6_@babel+core@7.18.13:
+ /@babel/helper-create-class-features-plugin/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-environment-visitor': 7.18.9
- '@babel/helper-function-name': 7.18.9
- '@babel/helper-member-expression-to-functions': 7.18.6
+ '@babel/helper-function-name': 7.19.0
+ '@babel/helper-member-expression-to-functions': 7.18.9
'@babel/helper-optimise-call-expression': 7.18.6
- '@babel/helper-replace-supers': 7.18.9
+ '@babel/helper-replace-supers': 7.19.1
'@babel/helper-split-export-declaration': 7.18.6
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/helper-create-regexp-features-plugin/7.18.6_@babel+core@7.18.13:
- resolution: {integrity: sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==}
+ /@babel/helper-create-regexp-features-plugin/7.19.0_@babel+core@7.20.2:
+ resolution: {integrity: sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-annotate-as-pure': 7.18.6
regexpu-core: 5.1.0
dev: true
- /@babel/helper-define-polyfill-provider/0.3.2_@babel+core@7.18.13:
- resolution: {integrity: sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==}
+ /@babel/helper-define-polyfill-provider/0.3.3_@babel+core@7.20.2:
+ resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==}
peerDependencies:
'@babel/core': ^7.4.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
debug: 4.3.4
lodash.debounce: 4.0.8
resolve: 1.22.0
@@ -216,57 +216,50 @@ packages:
resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
- /@babel/helper-function-name/7.18.9:
- resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==}
+ /@babel/helper-function-name/7.19.0:
+ resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.10
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
/@babel/helper-hoist-variables/7.18.6:
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
- dev: true
-
- /@babel/helper-member-expression-to-functions/7.18.6:
- resolution: {integrity: sha512-CeHxqwwipekotzPDUuJOfIMtcIHBuc7WAzLmTYWctVigqS5RktNMQ5bEwQSuGewzYnCtTWa3BARXeiLxDTv+Ng==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
/@babel/helper-member-expression-to-functions/7.18.9:
resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
/@babel/helper-module-imports/7.18.6:
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
- /@babel/helper-module-transforms/7.18.9:
- resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==}
+ /@babel/helper-module-transforms/7.20.2:
+ resolution: {integrity: sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-module-imports': 7.18.6
- '@babel/helper-simple-access': 7.18.6
+ '@babel/helper-simple-access': 7.20.2
'@babel/helper-split-export-declaration': 7.18.6
- '@babel/helper-validator-identifier': 7.18.6
+ '@babel/helper-validator-identifier': 7.19.1
'@babel/template': 7.18.10
- '@babel/traverse': 7.18.13
- '@babel/types': 7.18.13
+ '@babel/traverse': 7.20.1
+ '@babel/types': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -275,38 +268,25 @@ packages:
resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
- /@babel/helper-plugin-utils/7.18.9:
- resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==}
+ /@babel/helper-plugin-utils/7.20.2:
+ resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==}
engines: {node: '>=6.9.0'}
dev: true
- /@babel/helper-remap-async-to-generator/7.18.9_@babel+core@7.18.13:
+ /@babel/helper-remap-async-to-generator/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-wrap-function': 7.18.11
- '@babel/types': 7.18.13
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /@babel/helper-replace-supers/7.18.6:
- resolution: {integrity: sha512-fTf7zoXnUGl9gF25fXCWE26t7Tvtyn6H4hkLSYhATwJvw2uYxd3aoXplMSe0g9XbwK7bmxNes7+FGO0rB/xC0g==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@babel/helper-environment-visitor': 7.18.9
- '@babel/helper-member-expression-to-functions': 7.18.6
- '@babel/helper-optimise-call-expression': 7.18.6
- '@babel/traverse': 7.18.13
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -318,40 +298,53 @@ packages:
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-member-expression-to-functions': 7.18.9
'@babel/helper-optimise-call-expression': 7.18.6
- '@babel/traverse': 7.18.13
- '@babel/types': 7.18.13
+ '@babel/traverse': 7.20.1
+ '@babel/types': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/helper-simple-access/7.18.6:
- resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==}
+ /@babel/helper-replace-supers/7.19.1:
+ resolution: {integrity: sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/helper-environment-visitor': 7.18.9
+ '@babel/helper-member-expression-to-functions': 7.18.9
+ '@babel/helper-optimise-call-expression': 7.18.6
+ '@babel/traverse': 7.20.1
+ '@babel/types': 7.20.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/helper-simple-access/7.20.2:
+ resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.20.2
dev: true
/@babel/helper-skip-transparent-expression-wrappers/7.18.9:
resolution: {integrity: sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
/@babel/helper-split-export-declaration/7.18.6:
resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
- /@babel/helper-string-parser/7.18.10:
- resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==}
+ /@babel/helper-string-parser/7.19.4:
+ resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
engines: {node: '>=6.9.0'}
dev: true
- /@babel/helper-validator-identifier/7.18.6:
- resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==}
+ /@babel/helper-validator-identifier/7.19.1:
+ resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
engines: {node: '>=6.9.0'}
dev: true
@@ -364,21 +357,21 @@ packages:
resolution: {integrity: sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/helper-function-name': 7.18.9
+ '@babel/helper-function-name': 7.19.0
'@babel/template': 7.18.10
- '@babel/traverse': 7.18.13
- '@babel/types': 7.18.13
+ '@babel/traverse': 7.20.1
+ '@babel/types': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/helpers/7.18.9:
- resolution: {integrity: sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==}
+ /@babel/helpers/7.20.1:
+ resolution: {integrity: sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.10
- '@babel/traverse': 7.18.13
- '@babel/types': 7.18.13
+ '@babel/traverse': 7.20.1
+ '@babel/types': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -387,822 +380,820 @@ packages:
resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/helper-validator-identifier': 7.18.6
+ '@babel/helper-validator-identifier': 7.19.1
chalk: 2.4.2
js-tokens: 4.0.0
dev: true
- /@babel/parser/7.18.13:
- resolution: {integrity: sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==}
+ /@babel/parser/7.20.3:
+ resolution: {integrity: sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
- '@babel/types': 7.18.13
+ '@babel/types': 7.20.2
dev: true
- /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.13.0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.18.9
- '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.18.13
+ '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-async-generator-functions/7.18.10_@babel+core@7.18.13:
- resolution: {integrity: sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==}
+ /@babel/plugin-proposal-async-generator-functions/7.20.1_@babel+core@7.20.2:
+ resolution: {integrity: sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-environment-visitor': 7.18.9
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.18.13
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-class-properties/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-class-properties/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-class-static-block/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-class-static-block/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.12.0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-dynamic-import/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-dynamic-import/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-export-namespace-from/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-proposal-export-namespace-from/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-json-strings/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-json-strings/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-logical-assignment-operators/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-proposal-logical-assignment-operators/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-nullish-coalescing-operator/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-nullish-coalescing-operator/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-numeric-separator/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-numeric-separator/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-object-rest-spread/7.18.9_@babel+core@7.18.13:
- resolution: {integrity: sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==}
+ /@babel/plugin-proposal-object-rest-spread/7.20.2_@babel+core@7.20.2:
+ resolution: {integrity: sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/compat-data': 7.18.8
- '@babel/core': 7.18.13
- '@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.18.13
- '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.18.13
+ '@babel/compat-data': 7.20.1
+ '@babel/core': 7.20.2
+ '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.2
+ '@babel/plugin-transform-parameters': 7.20.3_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-optional-catch-binding/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-optional-catch-binding/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-optional-chaining/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-proposal-optional-chaining/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.18.9
- '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.18.13
+ '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.2
dev: true
- /@babel/plugin-proposal-private-methods/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-private-methods/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-private-property-in-object/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-private-property-in-object/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-annotate-as-pure': 7.18.6
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.18.13
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-unicode-property-regex/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-proposal-unicode-property-regex/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==}
engines: {node: '>=4'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-create-regexp-features-plugin': 7.19.0_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.18.13:
+ /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.2:
resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.18.13:
+ /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.20.2:
resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.18.13:
+ /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.20.2:
resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.18.13:
+ /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.20.2:
resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.18.13:
+ /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.20.2:
resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-import-assertions/7.18.6_@babel+core@7.18.13:
- resolution: {integrity: sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==}
+ /@babel/plugin-syntax-import-assertions/7.20.0_@babel+core@7.20.2:
+ resolution: {integrity: sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.18.13:
+ /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.20.2:
resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.18.13:
+ /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.20.2:
resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.18.13:
+ /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.20.2:
resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.18.13:
+ /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.20.2:
resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.18.13:
+ /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.20.2:
resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.18.13:
+ /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.20.2:
resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.18.13:
+ /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.20.2:
resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.18.13:
+ /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.20.2:
resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.18.13:
+ /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.20.2:
resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-arrow-functions/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-arrow-functions/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-async-to-generator/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-async-to-generator/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-module-imports': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.18.13
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-block-scoped-functions/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-block-scoped-functions/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-block-scoping/7.18.9_@babel+core@7.18.13:
- resolution: {integrity: sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==}
+ /@babel/plugin-transform-block-scoping/7.20.2_@babel+core@7.20.2:
+ resolution: {integrity: sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-classes/7.18.9_@babel+core@7.18.13:
- resolution: {integrity: sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==}
+ /@babel/plugin-transform-classes/7.20.2_@babel+core@7.20.2:
+ resolution: {integrity: sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-annotate-as-pure': 7.18.6
+ '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.2
'@babel/helper-environment-visitor': 7.18.9
- '@babel/helper-function-name': 7.18.9
+ '@babel/helper-function-name': 7.19.0
'@babel/helper-optimise-call-expression': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-replace-supers': 7.18.9
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/helper-replace-supers': 7.19.1
'@babel/helper-split-export-declaration': 7.18.6
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-computed-properties/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-transform-computed-properties/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-destructuring/7.18.9_@babel+core@7.18.13:
- resolution: {integrity: sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==}
+ /@babel/plugin-transform-destructuring/7.20.2_@babel+core@7.20.2:
+ resolution: {integrity: sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-dotall-regex/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-dotall-regex/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-create-regexp-features-plugin': 7.19.0_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-duplicate-keys/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-transform-duplicate-keys/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-exponentiation-operator/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-exponentiation-operator/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-builder-binary-assignment-operator-visitor': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.18.13:
+ /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.20.2:
resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-function-name/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-transform-function-name/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.13
- '@babel/helper-function-name': 7.18.9
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.2
+ '@babel/helper-function-name': 7.19.0
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-literals/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-transform-literals/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-member-expression-literals/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-member-expression-literals/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-modules-amd/7.18.6_@babel+core@7.18.13:
- resolution: {integrity: sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==}
+ /@babel/plugin-transform-modules-amd/7.19.6_@babel+core@7.20.2:
+ resolution: {integrity: sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-module-transforms': 7.18.9
- '@babel/helper-plugin-utils': 7.18.9
- babel-plugin-dynamic-import-node: 2.3.3
+ '@babel/core': 7.20.2
+ '@babel/helper-module-transforms': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-modules-commonjs/7.18.6_@babel+core@7.18.13:
- resolution: {integrity: sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==}
+ /@babel/plugin-transform-modules-commonjs/7.19.6_@babel+core@7.20.2:
+ resolution: {integrity: sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-module-transforms': 7.18.9
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-simple-access': 7.18.6
- babel-plugin-dynamic-import-node: 2.3.3
+ '@babel/core': 7.20.2
+ '@babel/helper-module-transforms': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/helper-simple-access': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-modules-systemjs/7.18.9_@babel+core@7.18.13:
- resolution: {integrity: sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==}
+ /@babel/plugin-transform-modules-systemjs/7.19.6_@babel+core@7.20.2:
+ resolution: {integrity: sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.20.2
'@babel/helper-hoist-variables': 7.18.6
- '@babel/helper-module-transforms': 7.18.9
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-validator-identifier': 7.18.6
- babel-plugin-dynamic-import-node: 2.3.3
+ '@babel/helper-module-transforms': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/helper-validator-identifier': 7.19.1
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-modules-umd/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-modules-umd/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-module-transforms': 7.18.9
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-module-transforms': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-named-capturing-groups-regex/7.18.6_@babel+core@7.18.13:
- resolution: {integrity: sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==}
+ /@babel/plugin-transform-named-capturing-groups-regex/7.19.1_@babel+core@7.20.2:
+ resolution: {integrity: sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-create-regexp-features-plugin': 7.19.0_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-new-target/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-new-target/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-object-super/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-object-super/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-replace-supers': 7.18.6
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/helper-replace-supers': 7.18.9
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-parameters/7.18.8_@babel+core@7.18.13:
- resolution: {integrity: sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==}
+ /@babel/plugin-transform-parameters/7.20.3_@babel+core@7.20.2:
+ resolution: {integrity: sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-property-literals/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-property-literals/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-regenerator/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-regenerator/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
regenerator-transform: 0.15.0
dev: true
- /@babel/plugin-transform-reserved-words/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-reserved-words/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-shorthand-properties/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-shorthand-properties/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-spread/7.18.9_@babel+core@7.18.13:
- resolution: {integrity: sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==}
+ /@babel/plugin-transform-spread/7.19.0_@babel+core@7.20.2:
+ resolution: {integrity: sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.18.9
dev: true
- /@babel/plugin-transform-sticky-regex/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-sticky-regex/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-template-literals/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-transform-template-literals/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-typeof-symbol/7.18.9_@babel+core@7.18.13:
+ /@babel/plugin-transform-typeof-symbol/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-unicode-escapes/7.18.10_@babel+core@7.18.13:
+ /@babel/plugin-transform-unicode-escapes/7.18.10_@babel+core@7.20.2:
resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/plugin-transform-unicode-regex/7.18.6_@babel+core@7.18.13:
+ /@babel/plugin-transform-unicode-regex/7.18.6_@babel+core@7.20.2:
resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.20.2
+ '@babel/helper-create-regexp-features-plugin': 7.19.0_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
dev: true
- /@babel/preset-env/7.18.10_@babel+core@7.18.13:
- resolution: {integrity: sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==}
+ /@babel/preset-env/7.20.2_@babel+core@7.20.2:
+ resolution: {integrity: sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/compat-data': 7.18.8
- '@babel/core': 7.18.13
- '@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.13
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/compat-data': 7.20.1
+ '@babel/core': 7.20.2
+ '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
'@babel/helper-validator-option': 7.18.6
- '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-proposal-async-generator-functions': 7.18.10_@babel+core@7.18.13
- '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-class-static-block': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-dynamic-import': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-export-namespace-from': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-proposal-json-strings': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-logical-assignment-operators': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-object-rest-spread': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-proposal-optional-catch-binding': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-private-property-in-object': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.18.13
- '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.18.13
- '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.18.13
- '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.18.13
- '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.18.13
- '@babel/plugin-syntax-import-assertions': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.18.13
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.18.13
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.18.13
- '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.18.13
- '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.18.13
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.18.13
- '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.18.13
- '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.18.13
- '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.18.13
- '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-async-to-generator': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-block-scoping': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-classes': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-computed-properties': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-destructuring': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-duplicate-keys': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-exponentiation-operator': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.18.13
- '@babel/plugin-transform-function-name': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-literals': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-modules-amd': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-modules-commonjs': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-modules-systemjs': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-modules-umd': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-named-capturing-groups-regex': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-new-target': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.18.13
- '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-regenerator': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-reserved-words': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-spread': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-sticky-regex': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-typeof-symbol': 7.18.9_@babel+core@7.18.13
- '@babel/plugin-transform-unicode-escapes': 7.18.10_@babel+core@7.18.13
- '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.18.13
- '@babel/preset-modules': 0.1.5_@babel+core@7.18.13
- '@babel/types': 7.18.10
- babel-plugin-polyfill-corejs2: 0.3.2_@babel+core@7.18.13
- babel-plugin-polyfill-corejs3: 0.5.3_@babel+core@7.18.13
- babel-plugin-polyfill-regenerator: 0.4.0_@babel+core@7.18.13
- core-js-compat: 3.22.8
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-proposal-async-generator-functions': 7.20.1_@babel+core@7.20.2
+ '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-class-static-block': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-dynamic-import': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-export-namespace-from': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-proposal-json-strings': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-logical-assignment-operators': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-object-rest-spread': 7.20.2_@babel+core@7.20.2
+ '@babel/plugin-proposal-optional-catch-binding': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-private-property-in-object': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.2
+ '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.20.2
+ '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.2
+ '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.2
+ '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.2
+ '@babel/plugin-syntax-import-assertions': 7.20.0_@babel+core@7.20.2
+ '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.2
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.2
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.2
+ '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.2
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.2
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.2
+ '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.2
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.2
+ '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.20.2
+ '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-async-to-generator': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-block-scoping': 7.20.2_@babel+core@7.20.2
+ '@babel/plugin-transform-classes': 7.20.2_@babel+core@7.20.2
+ '@babel/plugin-transform-computed-properties': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-transform-destructuring': 7.20.2_@babel+core@7.20.2
+ '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-duplicate-keys': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-transform-exponentiation-operator': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.20.2
+ '@babel/plugin-transform-function-name': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-transform-literals': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-modules-amd': 7.19.6_@babel+core@7.20.2
+ '@babel/plugin-transform-modules-commonjs': 7.19.6_@babel+core@7.20.2
+ '@babel/plugin-transform-modules-systemjs': 7.19.6_@babel+core@7.20.2
+ '@babel/plugin-transform-modules-umd': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-named-capturing-groups-regex': 7.19.1_@babel+core@7.20.2
+ '@babel/plugin-transform-new-target': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-parameters': 7.20.3_@babel+core@7.20.2
+ '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-regenerator': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-reserved-words': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-spread': 7.19.0_@babel+core@7.20.2
+ '@babel/plugin-transform-sticky-regex': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-transform-typeof-symbol': 7.18.9_@babel+core@7.20.2
+ '@babel/plugin-transform-unicode-escapes': 7.18.10_@babel+core@7.20.2
+ '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.20.2
+ '@babel/preset-modules': 0.1.5_@babel+core@7.20.2
+ '@babel/types': 7.20.2
+ babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.20.2
+ babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.20.2
+ babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.20.2
+ core-js-compat: 3.25.2
semver: 6.3.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/preset-modules/0.1.5_@babel+core@7.18.13:
+ /@babel/preset-modules/0.1.5_@babel+core@7.20.2:
resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.18.13
- '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.18.13
- '@babel/types': 7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-plugin-utils': 7.20.2
+ '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.2
+ '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.2
+ '@babel/types': 7.20.2
esutils: 2.0.3
dev: true
@@ -1218,59 +1209,50 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
- '@babel/parser': 7.18.13
- '@babel/types': 7.18.13
+ '@babel/parser': 7.20.3
+ '@babel/types': 7.20.2
dev: true
- /@babel/traverse/7.18.13:
- resolution: {integrity: sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==}
+ /@babel/traverse/7.20.1:
+ resolution: {integrity: sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
- '@babel/generator': 7.18.13
+ '@babel/generator': 7.20.4
'@babel/helper-environment-visitor': 7.18.9
- '@babel/helper-function-name': 7.18.9
+ '@babel/helper-function-name': 7.19.0
'@babel/helper-hoist-variables': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
- '@babel/parser': 7.18.13
- '@babel/types': 7.18.13
+ '@babel/parser': 7.20.3
+ '@babel/types': 7.20.2
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/types/7.18.10:
- resolution: {integrity: sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==}
+ /@babel/types/7.20.2:
+ resolution: {integrity: sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/helper-string-parser': 7.18.10
- '@babel/helper-validator-identifier': 7.18.6
+ '@babel/helper-string-parser': 7.19.4
+ '@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
dev: true
- /@babel/types/7.18.13:
- resolution: {integrity: sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==}
- engines: {node: '>=6.9.0'}
- dependencies:
- '@babel/helper-string-parser': 7.18.10
- '@babel/helper-validator-identifier': 7.18.6
- to-fast-properties: 2.0.0
- dev: true
-
- /@csstools/selector-specificity/2.0.2_pnx64jze6bptzcedy5bidi3zdi:
+ /@csstools/selector-specificity/2.0.2_45y636a2vqremknoajyxd5nkzy:
resolution: {integrity: sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==}
engines: {node: ^12 || ^14 || >=16}
peerDependencies:
postcss: ^8.2
postcss-selector-parser: ^6.0.10
dependencies:
- postcss: 8.4.16
+ postcss: 8.4.19
postcss-selector-parser: 6.0.10
dev: true
- /@eslint/eslintrc/1.3.1:
- resolution: {integrity: sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==}
+ /@eslint/eslintrc/1.3.3:
+ resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
ajv: 6.12.6
@@ -1286,17 +1268,17 @@ packages:
- supports-color
dev: true
- /@fullhuman/postcss-purgecss/4.1.3_postcss@8.4.16:
- resolution: {integrity: sha512-jqcsyfvq09VOsMXxJMPLRF6Fhg/NNltzWKnC9qtzva+QKTxerCO4esG6je7hbnmkpZtaDyPTwMBj9bzfWorsrw==}
+ /@fullhuman/postcss-purgecss/5.0.0_postcss@8.4.19:
+ resolution: {integrity: sha512-onDS/b/2pMRzqSoj4qOs2tYFmOpaspjTAgvACIHMPiicu1ptajiBruTrjBzTKdxWdX0ldaBb7wj8nEaTLyFkJw==}
peerDependencies:
postcss: ^8.0.0
dependencies:
- postcss: 8.4.16
- purgecss: 4.1.3
+ postcss: 8.4.19
+ purgecss: 5.0.0
dev: true
- /@humanwhocodes/config-array/0.10.4:
- resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==}
+ /@humanwhocodes/config-array/0.11.7:
+ resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==}
engines: {node: '>=10.10.0'}
dependencies:
'@humanwhocodes/object-schema': 1.2.1
@@ -1306,10 +1288,6 @@ packages:
- supports-color
dev: true
- /@humanwhocodes/gitignore-to-minimatch/1.0.2:
- resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==}
- dev: true
-
/@humanwhocodes/module-importer/1.0.1:
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
engines: {node: '>=12.22'}
@@ -1319,8 +1297,8 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true
- /@hyas/images/0.2.3:
- resolution: {integrity: sha512-PoGQ8DC3erHHS9sAlsrrGOlfqV3RgmIFYhSWcigB/7tE6tUNj4j57T4MjmlEyNcawlVsHvCaoYqPaElQn24aaQ==}
+ /@hyas/images/0.3.2:
+ resolution: {integrity: sha512-3khtRISGu4euI2JMViqsb25QtoqlIGAcHyKgSLSj9d3JP4aD//M+PfrzNnIi/NzGu4nTiBfOBtDmR+RijBNK7g==}
dev: true
/@jridgewell/gen-mapping/0.1.1:
@@ -1392,14 +1370,14 @@ packages:
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
dev: true
- /@sindresorhus/is/4.6.0:
- resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
- engines: {node: '>=10'}
+ /@sindresorhus/is/5.3.0:
+ resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==}
+ engines: {node: '>=14.16'}
dev: true
- /@szmarczak/http-timer/4.0.6:
- resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
- engines: {node: '>=10'}
+ /@szmarczak/http-timer/5.0.1:
+ resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
+ engines: {node: '>=14.16'}
dependencies:
defer-to-connect: 2.0.1
dev: true
@@ -1463,12 +1441,12 @@ packages:
hasBin: true
dev: true
- /aggregate-error/3.1.0:
- resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
- engines: {node: '>=8'}
+ /aggregate-error/4.0.1:
+ resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==}
+ engines: {node: '>=12'}
dependencies:
- clean-stack: 2.2.0
- indent-string: 4.0.0
+ clean-stack: 4.2.0
+ indent-string: 5.0.0
dev: true
/ajv/6.12.6:
@@ -1549,60 +1527,54 @@ packages:
- encoding
dev: true
- /autoprefixer/10.4.8_postcss@8.4.16:
- resolution: {integrity: sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw==}
+ /autoprefixer/10.4.13_postcss@8.4.19:
+ resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
peerDependencies:
postcss: ^8.1.0
dependencies:
- browserslist: 4.21.3
- caniuse-lite: 1.0.30001374
+ browserslist: 4.21.4
+ caniuse-lite: 1.0.30001427
fraction.js: 4.2.0
normalize-range: 0.1.2
picocolors: 1.0.0
- postcss: 8.4.16
+ postcss: 8.4.19
postcss-value-parser: 4.2.0
dev: true
- /babel-plugin-dynamic-import-node/2.3.3:
- resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==}
- dependencies:
- object.assign: 4.1.2
- dev: true
-
- /babel-plugin-polyfill-corejs2/0.3.2_@babel+core@7.18.13:
- resolution: {integrity: sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==}
+ /babel-plugin-polyfill-corejs2/0.3.3_@babel+core@7.20.2:
+ resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/compat-data': 7.18.8
- '@babel/core': 7.18.13
- '@babel/helper-define-polyfill-provider': 0.3.2_@babel+core@7.18.13
+ '@babel/compat-data': 7.20.1
+ '@babel/core': 7.20.2
+ '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.2
semver: 6.3.0
transitivePeerDependencies:
- supports-color
dev: true
- /babel-plugin-polyfill-corejs3/0.5.3_@babel+core@7.18.13:
- resolution: {integrity: sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==}
+ /babel-plugin-polyfill-corejs3/0.6.0_@babel+core@7.20.2:
+ resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-define-polyfill-provider': 0.3.2_@babel+core@7.18.13
- core-js-compat: 3.22.8
+ '@babel/core': 7.20.2
+ '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.2
+ core-js-compat: 3.25.2
transitivePeerDependencies:
- supports-color
dev: true
- /babel-plugin-polyfill-regenerator/0.4.0_@babel+core@7.18.13:
- resolution: {integrity: sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==}
+ /babel-plugin-polyfill-regenerator/0.4.1_@babel+core@7.20.2:
+ resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-define-polyfill-provider': 0.3.2_@babel+core@7.18.13
+ '@babel/core': 7.20.2
+ '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -1631,14 +1603,14 @@ packages:
safe-buffer: 5.2.1
dev: true
- /bootstrap-icons/1.9.1:
- resolution: {integrity: sha512-d4ZkO30MIkAhQ2nNRJqKXJVEQorALGbLWTuRxyCTJF96lRIV6imcgMehWGJUiJMJhglN0o2tqLIeDnMdiQEE9g==}
+ /bootstrap-icons/1.10.0:
+ resolution: {integrity: sha512-SNQ3EUv5cKuoqkS6tebZztusF1P8hKyCVVSlYjm6d5H2fa1v32w72oPjujrTlGy2g9LiZ0tR/uFMEZwO71GHPQ==}
dev: true
- /bootstrap/5.2.0_@popperjs+core@2.11.6:
- resolution: {integrity: sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==}
+ /bootstrap/5.2.2_@popperjs+core@2.11.6:
+ resolution: {integrity: sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==}
peerDependencies:
- '@popperjs/core': ^2.11.5
+ '@popperjs/core': ^2.11.6
dependencies:
'@popperjs/core': 2.11.6
dev: true
@@ -1650,6 +1622,12 @@ packages:
concat-map: 0.0.1
dev: true
+ /brace-expansion/2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
/braces/3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
@@ -1657,15 +1635,15 @@ packages:
fill-range: 7.0.1
dev: true
- /browserslist/4.21.3:
- resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==}
+ /browserslist/4.21.4:
+ resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
- caniuse-lite: 1.0.30001374
- electron-to-chromium: 1.4.211
+ caniuse-lite: 1.0.30001427
+ electron-to-chromium: 1.4.261
node-releases: 2.0.6
- update-browserslist-db: 1.0.5_browserslist@4.21.3
+ update-browserslist-db: 1.0.9_browserslist@4.21.4
dev: true
/buffer-alloc-unsafe/1.1.0:
@@ -1694,8 +1672,8 @@ packages:
ieee754: 1.2.1
dev: true
- /cacheable-lookup/5.0.4:
- resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
+ /cacheable-lookup/6.1.0:
+ resolution: {integrity: sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==}
engines: {node: '>=10.6.0'}
dev: true
@@ -1712,13 +1690,6 @@ packages:
responselike: 2.0.0
dev: true
- /call-bind/1.0.2:
- resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
- dependencies:
- function-bind: 1.1.1
- get-intrinsic: 1.1.1
- dev: true
-
/callsites/3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -1738,8 +1709,8 @@ packages:
engines: {node: '>=6'}
dev: true
- /caniuse-lite/1.0.30001374:
- resolution: {integrity: sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==}
+ /caniuse-lite/1.0.30001427:
+ resolution: {integrity: sha512-lfXQ73oB9c8DP5Suxaszm+Ta2sr/4tf8+381GkIm1MLj/YdLf+rEDyDSRCzeltuyTVGm+/s18gdZ0q+Wmp8VsQ==}
dev: true
/chalk/2.4.2:
@@ -1774,9 +1745,11 @@ packages:
fsevents: 2.3.2
dev: true
- /clean-stack/2.2.0:
- resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
- engines: {node: '>=6'}
+ /clean-stack/4.2.0:
+ resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==}
+ engines: {node: '>=12'}
+ dependencies:
+ escape-string-regexp: 5.0.0
dev: true
/clipboard/2.0.11:
@@ -1853,6 +1826,11 @@ packages:
engines: {node: '>= 12'}
dev: true
+ /commander/9.4.1:
+ resolution: {integrity: sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==}
+ engines: {node: ^12.20.0 || >=14}
+ dev: true
+
/compress-brotli/1.3.8:
resolution: {integrity: sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==}
engines: {node: '>= 12'}
@@ -1871,11 +1849,10 @@ packages:
safe-buffer: 5.1.2
dev: true
- /core-js-compat/3.22.8:
- resolution: {integrity: sha512-pQnwg4xtuvc2Bs/5zYQPaEYYSuTxsF7LBWF0SvnVhthZo/Qe+rJpcEekrdNK5DWwDJ0gv0oI9NNX5Mppdy0ctg==}
+ /core-js-compat/3.25.2:
+ resolution: {integrity: sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==}
dependencies:
- browserslist: 4.21.3
- semver: 7.0.0
+ browserslist: 4.21.4
dev: true
/core-util-is/1.0.3:
@@ -2012,26 +1989,18 @@ packages:
engines: {node: '>=10'}
dev: true
- /define-properties/1.1.4:
- resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
- engines: {node: '>= 0.4'}
+ /del/7.0.0:
+ resolution: {integrity: sha512-tQbV/4u5WVB8HMJr08pgw0b6nG4RGt/tj+7Numvq+zqcvUFeMaIWWOUFltiU+6go8BSO2/ogsB4EasDaj0y68Q==}
+ engines: {node: '>=14.16'}
dependencies:
- has-property-descriptors: 1.0.0
- object-keys: 1.1.1
- dev: true
-
- /del/6.0.0:
- resolution: {integrity: sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==}
- engines: {node: '>=10'}
- dependencies:
- globby: 11.1.0
+ globby: 13.1.2
graceful-fs: 4.2.10
is-glob: 4.0.3
- is-path-cwd: 2.2.0
- is-path-inside: 3.0.3
- p-map: 4.0.0
+ is-path-cwd: 3.0.0
+ is-path-inside: 4.0.0
+ p-map: 5.5.0
rimraf: 3.0.2
- slash: 3.0.0
+ slash: 4.0.0
dev: true
/delegate/3.2.0:
@@ -2057,8 +2026,8 @@ packages:
esutils: 2.0.3
dev: true
- /electron-to-chromium/1.4.211:
- resolution: {integrity: sha512-BZSbMpyFQU0KBJ1JG26XGeFI3i4op+qOYGxftmZXFZoHkhLgsSv4DHDJfl8ogII3hIuzGt51PaZ195OVu0yJ9A==}
+ /electron-to-chromium/1.4.261:
+ resolution: {integrity: sha512-fVXliNUGJ7XUVJSAasPseBbVgJIeyw5M1xIkgXdTSRjlmCqBbiSTsEdLOCJS31Fc8B7CaloQ/BFAg8By3ODLdg==}
dev: true
/emoji-regex/8.0.0:
@@ -2097,6 +2066,11 @@ packages:
engines: {node: '>=10'}
dev: true
+ /escape-string-regexp/5.0.0:
+ resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+ engines: {node: '>=12'}
+ dev: true
+
/eslint-scope/7.1.1:
resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2105,13 +2079,13 @@ packages:
estraverse: 5.3.0
dev: true
- /eslint-utils/3.0.0_eslint@8.23.0:
+ /eslint-utils/3.0.0_eslint@8.27.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5'
dependencies:
- eslint: 8.23.0
+ eslint: 8.27.0
eslint-visitor-keys: 2.1.0
dev: true
@@ -2125,15 +2099,15 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
- /eslint/8.23.0:
- resolution: {integrity: sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==}
+ /eslint/8.27.0:
+ resolution: {integrity: sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
- '@eslint/eslintrc': 1.3.1
- '@humanwhocodes/config-array': 0.10.4
- '@humanwhocodes/gitignore-to-minimatch': 1.0.2
+ '@eslint/eslintrc': 1.3.3
+ '@humanwhocodes/config-array': 0.11.7
'@humanwhocodes/module-importer': 1.0.1
+ '@nodelib/fs.walk': 1.2.8
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
@@ -2141,7 +2115,7 @@ packages:
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.1.1
- eslint-utils: 3.0.0_eslint@8.23.0
+ eslint-utils: 3.0.0_eslint@8.27.0
eslint-visitor-keys: 3.3.0
espree: 9.4.0
esquery: 1.4.0
@@ -2149,15 +2123,15 @@ packages:
fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1
find-up: 5.0.0
- functional-red-black-tree: 1.0.1
glob-parent: 6.0.2
globals: 13.15.0
- globby: 11.1.0
grapheme-splitter: 1.0.4
ignore: 5.2.0
import-fresh: 3.3.0
imurmurhash: 0.1.4
is-glob: 4.0.3
+ is-path-inside: 3.0.3
+ js-sdsl: 4.1.4
js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
@@ -2215,8 +2189,8 @@ packages:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true
- /fast-glob/3.2.11:
- resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
+ /fast-glob/3.2.12:
+ resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
engines: {node: '>=8.6.0'}
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -2316,8 +2290,13 @@ packages:
resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==}
dev: true
- /flexsearch/0.7.21:
- resolution: {integrity: sha512-W7cHV7Hrwjid6lWmy0IhsWDFQboWSng25U3VVywpHOTJnnAZNPScog67G+cVpeX9f7yDD21ih0WDrMMT+JoaYg==}
+ /flexsearch/0.7.31:
+ resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==}
+ dev: true
+
+ /form-data-encoder/2.1.2:
+ resolution: {integrity: sha512-FCaIOVTRA9E0siY6FeXid7D5yrCqpsErplUkE2a1BEiKj1BE9z6FbKB4ntDTwC4NVLie9p+4E9nX4mWwEOT05A==}
+ engines: {node: '>= 14.17'}
dev: true
/formdata-polyfill/4.0.10:
@@ -2364,10 +2343,6 @@ packages:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
- /functional-red-black-tree/1.0.1:
- resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==}
- dev: true
-
/gensync/1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@@ -2378,14 +2353,6 @@ packages:
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
- /get-intrinsic/1.1.1:
- resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}
- dependencies:
- function-bind: 1.1.1
- has: 1.0.3
- has-symbols: 1.0.3
- dev: true
-
/get-stdin/9.0.0:
resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==}
engines: {node: '>=12'}
@@ -2406,6 +2373,11 @@ packages:
pump: 3.0.0
dev: true
+ /get-stream/6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+ dev: true
+
/glob-all/3.3.0:
resolution: {integrity: sha512-30gCh9beSb+YSAh0vsoIlBRm4bSlyMa+5nayax1EJhjwYrCohX0aDxcxvWVe3heOrJikbHgRs75Af6kPLcumew==}
hasBin: true
@@ -2439,6 +2411,17 @@ packages:
path-is-absolute: 1.0.1
dev: true
+ /glob/8.0.3:
+ resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 5.1.0
+ once: 1.4.0
+ dev: true
+
/global-modules/2.0.0:
resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}
engines: {node: '>=6'}
@@ -2473,7 +2456,7 @@ packages:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
- fast-glob: 3.2.11
+ fast-glob: 3.2.12
ignore: 5.2.0
merge2: 1.4.1
slash: 3.0.0
@@ -2484,7 +2467,7 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
dir-glob: 3.0.1
- fast-glob: 3.2.11
+ fast-glob: 3.2.12
ignore: 5.2.0
merge2: 1.4.1
slash: 4.0.0
@@ -2508,21 +2491,22 @@ packages:
delegate: 3.2.0
dev: true
- /got/11.8.5:
- resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==}
- engines: {node: '>=10.19.0'}
+ /got/12.4.1:
+ resolution: {integrity: sha512-Sz1ojLt4zGNkcftIyJKnulZT/yEDvifhUjccHA8QzOuTgPs/+njXYNMFE3jR4/2OODQSSbH8SdnoLCkbh41ieA==}
+ engines: {node: '>=14.16'}
dependencies:
- '@sindresorhus/is': 4.6.0
- '@szmarczak/http-timer': 4.0.6
+ '@sindresorhus/is': 5.3.0
+ '@szmarczak/http-timer': 5.0.1
'@types/cacheable-request': 6.0.2
- '@types/responselike': 1.0.0
- cacheable-lookup: 5.0.4
+ cacheable-lookup: 6.1.0
cacheable-request: 7.0.2
decompress-response: 6.0.0
- http2-wrapper: 1.0.3
- lowercase-keys: 2.0.0
- p-cancelable: 2.1.1
- responselike: 2.0.0
+ form-data-encoder: 2.1.2
+ get-stream: 6.0.1
+ http2-wrapper: 2.1.11
+ lowercase-keys: 3.0.0
+ p-cancelable: 3.0.0
+ responselike: 3.0.0
dev: true
/graceful-fs/4.2.10:
@@ -2561,17 +2545,6 @@ packages:
engines: {node: '>=8'}
dev: true
- /has-property-descriptors/1.0.0:
- resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
- dependencies:
- get-intrinsic: 1.1.1
- dev: true
-
- /has-symbols/1.0.3:
- resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
- engines: {node: '>= 0.4'}
- dev: true
-
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
@@ -2595,8 +2568,9 @@ packages:
lru-cache: 6.0.0
dev: true
- /hpagent/0.1.2:
- resolution: {integrity: sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==}
+ /hpagent/1.0.0:
+ resolution: {integrity: sha512-SCleE2Uc1bM752ymxg8QXYGW0TWtAV4ZW3TqH1aOnyi6T6YW2xadCcclm5qeVjvMvfQ2RKNtZxO7uVb9CTPt1A==}
+ engines: {node: '>=14'}
dev: true
/html-tags/3.2.0:
@@ -2608,25 +2582,25 @@ packages:
resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==}
dev: true
- /http2-wrapper/1.0.3:
- resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
+ /http2-wrapper/2.1.11:
+ resolution: {integrity: sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==}
engines: {node: '>=10.19.0'}
dependencies:
quick-lru: 5.1.1
resolve-alpn: 1.2.1
dev: true
- /hugo-installer/3.1.0:
- resolution: {integrity: sha512-E3yc8m1NRZBROOx0tx5WtUfAa9EcBbjbM4YSfy3KLGXQfHXIpbfD3oQ5YIKvih8aKuyyRtKzBZIdiOHfWAvvtg==}
+ /hugo-installer/4.0.1:
+ resolution: {integrity: sha512-pkp1RO7+ekQ0vw1aqgBMK+dD2dqioIWVbwWKsJsKLOpzfFc78gK68Cweoi/g+CftoiMFO7cyGx/2MgkHCMqaLQ==}
hasBin: true
dependencies:
decompress: 4.2.1
- del: 6.0.0
- got: 11.8.5
- hpagent: 0.1.2
+ del: 7.0.0
+ got: 12.4.1
+ hpagent: 1.0.0
object-path: 0.11.8
semver: 7.3.7
- yargs: 16.2.0
+ yargs: 17.5.1
dev: true
/ieee754/1.2.1:
@@ -2661,6 +2635,11 @@ packages:
engines: {node: '>=8'}
dev: true
+ /indent-string/5.0.0:
+ resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
+ engines: {node: '>=12'}
+ dev: true
+
/inflight/1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
@@ -2734,9 +2713,9 @@ packages:
engines: {node: '>=0.12.0'}
dev: true
- /is-path-cwd/2.2.0:
- resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
- engines: {node: '>=6'}
+ /is-path-cwd/3.0.0:
+ resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: true
/is-path-inside/3.0.3:
@@ -2744,6 +2723,11 @@ packages:
engines: {node: '>=8'}
dev: true
+ /is-path-inside/4.0.0:
+ resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==}
+ engines: {node: '>=12'}
+ dev: true
+
/is-plain-obj/1.1.0:
resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
engines: {node: '>=0.10.0'}
@@ -2767,6 +2751,10 @@ packages:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
+ /js-sdsl/4.1.4:
+ resolution: {integrity: sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==}
+ dev: true
+
/js-tokens/4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true
@@ -2823,8 +2811,8 @@ packages:
graceful-fs: 4.2.10
dev: true
- /katex/0.16.2:
- resolution: {integrity: sha512-70DJdQAyh9EMsthw3AaQlDyFf54X7nWEUIa5W+rq8XOpEk//w5Th7/8SqFqpvi/KZ2t6MHUj4f9wLmztBmAYQA==}
+ /katex/0.16.3:
+ resolution: {integrity: sha512-3EykQddareoRmbtNiNEDgl3IGjryyrp2eg/25fHDEnlHymIDi33bptkMv6K4EOC2LZCybLW/ZkEo6Le+EM9pmA==}
hasBin: true
dependencies:
commander: 8.3.0
@@ -2842,8 +2830,8 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /known-css-properties/0.25.0:
- resolution: {integrity: sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==}
+ /known-css-properties/0.26.0:
+ resolution: {integrity: sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==}
dev: true
/lazysizes/5.3.2:
@@ -2915,6 +2903,11 @@ packages:
engines: {node: '>=8'}
dev: true
+ /lowercase-keys/3.0.0:
+ resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dev: true
+
/lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@@ -3046,6 +3039,13 @@ packages:
brace-expansion: 1.1.11
dev: true
+ /minimatch/5.1.0:
+ resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==}
+ engines: {node: '>=10'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
/minimist-options/4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
engines: {node: '>= 6'}
@@ -3080,7 +3080,7 @@ packages:
/netlify-plugin-submit-sitemap/0.4.0:
resolution: {integrity: sha512-5ntDtSKZRHaCDrDXh4sH4V7lNEEsoi01lsmSUuqJ/ikPHf0XEErjsKba8TsM3iaZRYEHI9bQse3BWgguwuwIIQ==}
dependencies:
- node-fetch: 3.2.10
+ node-fetch: 3.3.0
dev: true
/node-domexception/1.0.0:
@@ -3100,8 +3100,8 @@ packages:
whatwg-url: 5.0.0
dev: true
- /node-fetch/3.2.10:
- resolution: {integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==}
+ /node-fetch/3.3.0:
+ resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
data-uri-to-buffer: 4.0.0
@@ -3152,26 +3152,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /object-keys/1.1.1:
- resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
- engines: {node: '>= 0.4'}
- dev: true
-
/object-path/0.11.8:
resolution: {integrity: sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==}
engines: {node: '>= 10.12.0'}
dev: true
- /object.assign/4.1.2:
- resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==}
- engines: {node: '>= 0.4'}
- dependencies:
- call-bind: 1.0.2
- define-properties: 1.1.4
- has-symbols: 1.0.3
- object-keys: 1.1.1
- dev: true
-
/once/1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -3190,9 +3175,9 @@ packages:
word-wrap: 1.2.3
dev: true
- /p-cancelable/2.1.1:
- resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
- engines: {node: '>=8'}
+ /p-cancelable/3.0.0:
+ resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
+ engines: {node: '>=12.20'}
dev: true
/p-limit/2.3.0:
@@ -3223,11 +3208,11 @@ packages:
p-limit: 3.1.0
dev: true
- /p-map/4.0.0:
- resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
- engines: {node: '>=10'}
+ /p-map/5.5.0:
+ resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==}
+ engines: {node: '>=12'}
dependencies:
- aggregate-error: 3.1.0
+ aggregate-error: 4.0.1
dev: true
/p-try/2.2.0:
@@ -3322,7 +3307,7 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /postcss-cli/10.0.0_postcss@8.4.16:
+ /postcss-cli/10.0.0_postcss@8.4.19:
resolution: {integrity: sha512-Wjy/00wBBEgQqnSToznxLWDnATznokFGXsHtF/3G8glRZpz5KYlfHcBW/VMJmWAeF2x49zjgy4izjM3/Wx1dKA==}
engines: {node: '>=14'}
hasBin: true
@@ -3335,9 +3320,9 @@ packages:
get-stdin: 9.0.0
globby: 13.1.2
picocolors: 1.0.0
- postcss: 8.4.16
- postcss-load-config: 4.0.1_postcss@8.4.16
- postcss-reporter: 7.0.5_postcss@8.4.16
+ postcss: 8.4.19
+ postcss-load-config: 4.0.1_postcss@8.4.19
+ postcss-reporter: 7.0.5_postcss@8.4.19
pretty-hrtime: 1.0.3
read-cache: 1.0.0
slash: 4.0.0
@@ -3346,7 +3331,7 @@ packages:
- ts-node
dev: true
- /postcss-load-config/4.0.1_postcss@8.4.16:
+ /postcss-load-config/4.0.1_postcss@8.4.19:
resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
engines: {node: '>= 14'}
peerDependencies:
@@ -3359,7 +3344,7 @@ packages:
optional: true
dependencies:
lilconfig: 2.0.5
- postcss: 8.4.16
+ postcss: 8.4.19
yaml: 2.1.1
dev: true
@@ -3367,14 +3352,14 @@ packages:
resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==}
dev: true
- /postcss-reporter/7.0.5_postcss@8.4.16:
+ /postcss-reporter/7.0.5_postcss@8.4.19:
resolution: {integrity: sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==}
engines: {node: '>=10'}
peerDependencies:
postcss: ^8.1.0
dependencies:
picocolors: 1.0.0
- postcss: 8.4.16
+ postcss: 8.4.19
thenby: 1.3.4
dev: true
@@ -3382,22 +3367,22 @@ packages:
resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==}
dev: true
- /postcss-safe-parser/6.0.0_postcss@8.4.16:
+ /postcss-safe-parser/6.0.0_postcss@8.4.19:
resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.3.3
dependencies:
- postcss: 8.4.16
+ postcss: 8.4.19
dev: true
- /postcss-scss/4.0.4_postcss@8.4.16:
+ /postcss-scss/4.0.4_postcss@8.4.19:
resolution: {integrity: sha512-aBBbVyzA8b3hUL0MGrpydxxXKXFZc5Eqva0Q3V9qsBOLEMsjb6w49WfpsoWzpEgcqJGW4t7Rio8WXVU9Gd8vWg==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.3.3
dependencies:
- postcss: 8.4.16
+ postcss: 8.4.19
dev: true
/postcss-selector-parser/6.0.10:
@@ -3412,8 +3397,8 @@ packages:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: true
- /postcss/8.4.16:
- resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
+ /postcss/8.4.19:
+ resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.4
@@ -3455,13 +3440,13 @@ packages:
scss-parser: 1.0.3
dev: true
- /purgecss/4.1.3:
- resolution: {integrity: sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==}
+ /purgecss/5.0.0:
+ resolution: {integrity: sha512-RAnuxrGuVyLLTr8uMbKaxDRGWMgK5CCYDfRyUNNcaz5P3kGgD2b7ymQGYEyo2ST7Tl/ScwFgf5l3slKMxHSbrw==}
hasBin: true
dependencies:
- commander: 8.3.0
- glob: 7.2.3
- postcss: 8.4.16
+ commander: 9.4.1
+ glob: 8.0.3
+ postcss: 8.4.19
postcss-selector-parser: 6.0.10
dev: true
@@ -3588,7 +3573,7 @@ packages:
dev: true
/require-directory/2.1.1:
- resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
@@ -3630,6 +3615,13 @@ packages:
lowercase-keys: 2.0.0
dev: true
+ /responselike/3.0.0:
+ resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
+ engines: {node: '>=14.16'}
+ dependencies:
+ lowercase-keys: 3.0.0
+ dev: true
+
/reusify/1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -3685,11 +3677,6 @@ packages:
hasBin: true
dev: true
- /semver/7.0.0:
- resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==}
- hasBin: true
- dev: true
-
/semver/7.3.7:
resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
engines: {node: '>=10'}
@@ -3842,49 +3829,55 @@ packages:
resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
dev: true
- /stylelint-config-recommended-scss/6.0.0_74u2qasxp5mudia6vln5kcfuni:
- resolution: {integrity: sha512-6QOe2/OzXV2AP5FE12A7+qtKdZik7Saf42SMMl84ksVBBPpTdrV+9HaCbPYiRMiwELY9hXCVdH4wlJ+YJb5eig==}
+ /stylelint-config-recommended-scss/8.0.0_ave2i6l4ingtbwj4aquhd5witq:
+ resolution: {integrity: sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==}
peerDependencies:
- stylelint: ^14.4.0
+ postcss: ^8.3.3
+ stylelint: ^14.10.0
+ peerDependenciesMeta:
+ postcss:
+ optional: true
dependencies:
- postcss-scss: 4.0.4_postcss@8.4.16
- stylelint: 14.11.0
- stylelint-config-recommended: 7.0.0_stylelint@14.11.0
- stylelint-scss: 4.2.0_stylelint@14.11.0
- transitivePeerDependencies:
- - postcss
+ postcss: 8.4.19
+ postcss-scss: 4.0.4_postcss@8.4.19
+ stylelint: 14.14.1
+ stylelint-config-recommended: 9.0.0_stylelint@14.14.1
+ stylelint-scss: 4.2.0_stylelint@14.14.1
dev: true
- /stylelint-config-recommended/7.0.0_stylelint@14.11.0:
- resolution: {integrity: sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==}
+ /stylelint-config-recommended/9.0.0_stylelint@14.14.1:
+ resolution: {integrity: sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==}
peerDependencies:
- stylelint: ^14.4.0
+ stylelint: ^14.10.0
dependencies:
- stylelint: 14.11.0
+ stylelint: 14.14.1
dev: true
- /stylelint-config-standard-scss/4.0.0_74u2qasxp5mudia6vln5kcfuni:
- resolution: {integrity: sha512-xizu8PTEyB6zYXBiVg6VtvUYn9m57x+6ZtaOdaxsfpbe5eagLPGNlbYnKfm/CfN69ArUpnwR6LjgsTHzlGbtXQ==}
+ /stylelint-config-standard-scss/6.1.0_ave2i6l4ingtbwj4aquhd5witq:
+ resolution: {integrity: sha512-iZ2B5kQT2G3rUzx+437cEpdcnFOQkwnwqXuY8Z0QUwIHQVE8mnYChGAquyKFUKZRZ0pRnrciARlPaR1RBtPb0Q==}
peerDependencies:
- stylelint: ^14.4.0
+ postcss: ^8.3.3
+ stylelint: ^14.14.0
+ peerDependenciesMeta:
+ postcss:
+ optional: true
dependencies:
- stylelint: 14.11.0
- stylelint-config-recommended-scss: 6.0.0_74u2qasxp5mudia6vln5kcfuni
- stylelint-config-standard: 25.0.0_stylelint@14.11.0
- transitivePeerDependencies:
- - postcss
+ postcss: 8.4.19
+ stylelint: 14.14.1
+ stylelint-config-recommended-scss: 8.0.0_ave2i6l4ingtbwj4aquhd5witq
+ stylelint-config-standard: 29.0.0_stylelint@14.14.1
dev: true
- /stylelint-config-standard/25.0.0_stylelint@14.11.0:
- resolution: {integrity: sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==}
+ /stylelint-config-standard/29.0.0_stylelint@14.14.1:
+ resolution: {integrity: sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==}
peerDependencies:
- stylelint: ^14.4.0
+ stylelint: ^14.14.0
dependencies:
- stylelint: 14.11.0
- stylelint-config-recommended: 7.0.0_stylelint@14.11.0
+ stylelint: 14.14.1
+ stylelint-config-recommended: 9.0.0_stylelint@14.14.1
dev: true
- /stylelint-scss/4.2.0_stylelint@14.11.0:
+ /stylelint-scss/4.2.0_stylelint@14.14.1:
resolution: {integrity: sha512-HHHMVKJJ5RM9pPIbgJ/XA67h9H0407G68Rm69H4fzFbFkyDMcTV1Byep3qdze5+fJ3c0U7mJrbj6S0Fg072uZA==}
peerDependencies:
stylelint: ^14.5.1
@@ -3894,21 +3887,21 @@ packages:
postcss-resolve-nested-selector: 0.1.1
postcss-selector-parser: 6.0.10
postcss-value-parser: 4.2.0
- stylelint: 14.11.0
+ stylelint: 14.14.1
dev: true
- /stylelint/14.11.0:
- resolution: {integrity: sha512-OTLjLPxpvGtojEfpESWM8Ir64Z01E89xsisaBMUP/ngOx1+4VG2DPRcUyCCiin9Rd3kPXPsh/uwHd9eqnvhsYA==}
+ /stylelint/14.14.1:
+ resolution: {integrity: sha512-Jnftu+lSD8cSpcV/+Z2nfgfgFpTIS1FcujezXPngtoIQ6wtwutL22MsNE0dJuMiM1h1790g2qIjAyUZCMrX4sw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
hasBin: true
dependencies:
- '@csstools/selector-specificity': 2.0.2_pnx64jze6bptzcedy5bidi3zdi
+ '@csstools/selector-specificity': 2.0.2_45y636a2vqremknoajyxd5nkzy
balanced-match: 2.0.0
colord: 2.9.3
cosmiconfig: 7.0.1
css-functions-list: 3.1.0
debug: 4.3.4
- fast-glob: 3.2.11
+ fast-glob: 3.2.12
fastest-levenshtein: 1.0.16
file-entry-cache: 6.0.1
global-modules: 2.0.0
@@ -3919,25 +3912,25 @@ packages:
import-lazy: 4.0.0
imurmurhash: 0.1.4
is-plain-object: 5.0.0
- known-css-properties: 0.25.0
+ known-css-properties: 0.26.0
mathml-tag-names: 2.1.3
meow: 9.0.0
micromatch: 4.0.5
normalize-path: 3.0.0
picocolors: 1.0.0
- postcss: 8.4.16
+ postcss: 8.4.19
postcss-media-query-parser: 0.2.3
postcss-resolve-nested-selector: 0.1.1
- postcss-safe-parser: 6.0.0_postcss@8.4.16
+ postcss-safe-parser: 6.0.0_postcss@8.4.19
postcss-selector-parser: 6.0.10
postcss-value-parser: 4.2.0
resolve-from: 5.0.0
string-width: 4.2.3
strip-ansi: 6.0.1
style-search: 0.1.0
- supports-hyperlinks: 2.2.0
+ supports-hyperlinks: 2.3.0
svg-tags: 1.0.0
- table: 6.8.0
+ table: 6.8.1
v8-compile-cache: 2.3.0
write-file-atomic: 4.0.2
transitivePeerDependencies:
@@ -3958,8 +3951,8 @@ packages:
has-flag: 4.0.0
dev: true
- /supports-hyperlinks/2.2.0:
- resolution: {integrity: sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==}
+ /supports-hyperlinks/2.3.0:
+ resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
@@ -3975,8 +3968,8 @@ packages:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
dev: true
- /table/6.8.0:
- resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==}
+ /table/6.8.1:
+ resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==}
engines: {node: '>=10.0.0'}
dependencies:
ajv: 8.11.0
@@ -4008,7 +4001,7 @@ packages:
dev: true
/through/2.3.8:
- resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=}
+ resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: true
/tiny-emitter/2.1.0:
@@ -4114,13 +4107,13 @@ packages:
engines: {node: '>= 10.0.0'}
dev: true
- /update-browserslist-db/1.0.5_browserslist@4.21.3:
- resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==}
+ /update-browserslist-db/1.0.9_browserslist@4.21.4:
+ resolution: {integrity: sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
dependencies:
- browserslist: 4.21.3
+ browserslist: 4.21.4
escalade: 3.1.1
picocolors: 1.0.0
dev: true
@@ -4283,19 +4276,6 @@ packages:
yargs-parser: 18.1.3
dev: true
- /yargs/16.2.0:
- resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
- engines: {node: '>=10'}
- dependencies:
- cliui: 7.0.4
- escalade: 3.1.1
- get-caller-file: 2.0.5
- require-directory: 2.1.1
- string-width: 4.2.3
- y18n: 5.0.8
- yargs-parser: 20.2.9
- dev: true
-
/yargs/17.5.1:
resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==}
engines: {node: '>=12'}
@@ -4310,7 +4290,7 @@ packages:
dev: true
/yauzl/2.10.0:
- resolution: {integrity: sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=}
+ resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
dependencies:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
diff --git a/examples/compose/lite/authelia/users_database.yml b/examples/compose/lite/authelia/users_database.yml
index 8785bebe9..08944d06e 100644
--- a/examples/compose/lite/authelia/users_database.yml
+++ b/examples/compose/lite/authelia/users_database.yml
@@ -8,6 +8,7 @@
# List of users
users:
authelia:
+ disabled: false
displayname: "Authelia User"
# Password is authelia
password: "$6$rounds=50000$BpLnfgDsc2WD8F2q$Zis.ixdg9s/UOJYrs56b5QEZFiZECu0qZVNsIYxBaNJ7ucIL.nlxVCT5tqh8KHG8X4tlwCFm5r6NTOZZ5qRFN/" # yamllint disable-line rule:line-length
diff --git a/examples/compose/lite/docker-compose.yml b/examples/compose/lite/docker-compose.yml
index 4bef2c181..7ed0312ed 100644
--- a/examples/compose/lite/docker-compose.yml
+++ b/examples/compose/lite/docker-compose.yml
@@ -26,6 +26,7 @@ services:
- 9091
restart: unless-stopped
healthcheck:
+ ## In production the healthcheck section should be commented.
disable: true
environment:
- TZ=Australia/Melbourne
@@ -44,7 +45,7 @@ services:
- TZ=Australia/Melbourne
traefik:
- image: traefik:v2.8
+ image: traefik:v2.9.4
container_name: traefik
volumes:
- ./traefik:/etc/traefik
diff --git a/examples/compose/local/authelia/users_database.yml b/examples/compose/local/authelia/users_database.yml
index f1811d933..1ca38bd6d 100644
--- a/examples/compose/local/authelia/users_database.yml
+++ b/examples/compose/local/authelia/users_database.yml
@@ -8,6 +8,7 @@
# List of users
users:
:
+ disabled: false
displayname: ""
password: ""
email: @example.com
diff --git a/examples/compose/local/docker-compose.yml b/examples/compose/local/docker-compose.yml
index 7ac79f340..f3453c8c1 100644
--- a/examples/compose/local/docker-compose.yml
+++ b/examples/compose/local/docker-compose.yml
@@ -26,12 +26,13 @@ services:
- 9091
restart: unless-stopped
healthcheck:
+ ## In production the healthcheck section should be commented.
disable: true
environment:
- TZ=Australia/Melbourne
traefik:
- image: traefik:v2.8
+ image: traefik:v2.9.4
container_name: traefik
volumes:
- ./traefik:/etc/traefik
diff --git a/go.mod b/go.mod
index d1b910ddc..4afe02097 100644
--- a/go.mod
+++ b/go.mod
@@ -6,38 +6,40 @@ 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/duosecurity/duo_api_golang v0.0.0-20220902131320-61f4e624f85c
- github.com/fasthttp/router v1.4.12
+ github.com/duosecurity/duo_api_golang v0.0.0-20220927171823-f4576e85b96c
+ github.com/fasthttp/router v1.4.13
github.com/fasthttp/session/v2 v2.4.13
+ github.com/fsnotify/fsnotify v1.6.0
github.com/go-asn1-ber/asn1-ber v1.5.4
+ github.com/go-crypt/crypt v0.1.14
github.com/go-ldap/ldap/v3 v3.4.4
- github.com/go-rod/rod v0.109.3
+ github.com/go-rod/rod v0.112.0
github.com/go-sql-driver/mysql v1.6.0
- github.com/go-webauthn/webauthn v0.4.0
+ github.com/go-webauthn/webauthn v0.5.0
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
- github.com/jackc/pgx/v4 v4.17.2
+ github.com/jackc/pgx/v5 v5.0.4
github.com/jmoiron/sqlx v1.3.5
- github.com/knadh/koanf v1.4.3
- github.com/mattn/go-sqlite3 v2.0.3+incompatible
+ github.com/knadh/koanf v1.4.4
+ 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.42.2
github.com/ory/herodot v0.9.13
- github.com/otiai10/copy v1.7.0
+ github.com/otiai10/copy v1.9.0
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.3.0
- github.com/prometheus/client_golang v1.13.0
- github.com/simia-tech/crypt v0.5.1
+ github.com/prometheus/client_golang v1.14.0
github.com/sirupsen/logrus v1.9.0
- github.com/spf13/cobra v1.5.0
+ github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
- github.com/stretchr/testify v1.8.0
+ github.com/stretchr/testify v1.8.1
github.com/trustelem/zxcvbn v1.0.1
- github.com/valyala/fasthttp v1.40.0
- golang.org/x/sync v0.0.0-20220907140024-f12130a52804
- golang.org/x/text v0.3.7
+ github.com/valyala/fasthttp v1.41.0
+ golang.org/x/sync v0.1.0
+ golang.org/x/term v0.2.0
+ golang.org/x/text v0.4.0
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -55,24 +57,20 @@ require (
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
- github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
+ github.com/go-crypt/x v0.1.3 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
- github.com/go-webauthn/revoke v0.1.3 // indirect
+ github.com/go-webauthn/revoke v0.1.6 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/protobuf v1.5.2 // indirect
+ github.com/google/go-tpm v0.3.3 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
- github.com/inconshreveable/mousetrap v1.0.0 // indirect
- github.com/jackc/chunkreader/v2 v2.0.1 // indirect
- github.com/jackc/pgconn v1.13.0 // indirect
- github.com/jackc/pgio v1.0.0 // indirect
+ github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
- github.com/jackc/pgproto3/v2 v2.3.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
- github.com/jackc/pgtype v1.12.0 // indirect
github.com/jandelgado/gcov2lcov v1.0.5 // indirect
- github.com/klauspost/compress v1.15.0 // indirect
+ github.com/klauspost/compress v1.15.9 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattn/goveralls v0.0.6 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@@ -86,7 +84,7 @@ require (
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/prometheus/client_model v0.2.0 // indirect
+ github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -103,12 +101,11 @@ require (
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/gson v0.7.1 // indirect
github.com/ysmood/leakless v0.8.0 // indirect
- golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
- golang.org/x/mod v0.5.0 // indirect
- golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
- golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
- golang.org/x/tools v0.1.7 // indirect
- golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+ golang.org/x/crypto v0.1.0 // indirect
+ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
+ golang.org/x/net v0.1.0 // indirect
+ golang.org/x/sys v0.2.0 // indirect
+ golang.org/x/tools v0.1.12 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
google.golang.org/grpc v1.42.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
@@ -116,4 +113,4 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
)
-replace github.com/mattn/go-sqlite3 v2.0.3+incompatible => github.com/mattn/go-sqlite3 v1.14.14
+exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible
diff --git a/go.sum b/go.sum
index b4ff24bab..ee6a1f735 100644
--- a/go.sum
+++ b/go.sum
@@ -44,11 +44,8 @@ github.com/DataDog/datadog-go v4.0.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 h1:vdT7QwBhJJEVNFMBNhRSFDRCB6O16T28VhvqRgqFyn8=
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4/go.mod h1:SvXOG8ElV28oAiG9zv91SDe5+9PfIr7PPccpr8YyXNs=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
-github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
-github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
-github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
@@ -133,7 +130,6 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
@@ -190,8 +186,8 @@ github.com/docker/docker v17.12.0-ce-rc1.0.20201201034508-7d75c1d40d88+incompati
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/duosecurity/duo_api_golang v0.0.0-20220902131320-61f4e624f85c h1:WzcpjPMDu3E/aPDXFFDuHQcrBAdiAMSgHl1CwAQ/yE0=
-github.com/duosecurity/duo_api_golang v0.0.0-20220902131320-61f4e624f85c/go.mod h1:jI+QUTOK3wqIOrUl0Cwnwlgc/P6vs6pZOuQY3aKggwg=
+github.com/duosecurity/duo_api_golang v0.0.0-20220927171823-f4576e85b96c h1:SndVaMnkMWHtn0TyWx8kX7Oees7wPhCCx+wFDWHXsac=
+github.com/duosecurity/duo_api_golang v0.0.0-20220927171823-f4576e85b96c/go.mod h1:jI+QUTOK3wqIOrUl0Cwnwlgc/P6vs6pZOuQY3aKggwg=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
@@ -215,8 +211,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
-github.com/fasthttp/router v1.4.12 h1:QEgK+UKARaC1bAzJgnIhdUMay6nwp+YFq6VGPlyKN1o=
-github.com/fasthttp/router v1.4.12/go.mod h1:41Qdc4Z4T2pWVVtATHCnoUnOtxdBoeKEYJTXhHwbxCQ=
+github.com/fasthttp/router v1.4.13 h1:42M7+7tNO6clb5seb4HhXlBIX1lnNv8DLhiT6jUv75A=
+github.com/fasthttp/router v1.4.13/go.mod h1:mVhHMaSQA2Hi1HeuL/ZMuZpsZWk5bya75EpaDr3fO7E=
github.com/fasthttp/session/v2 v2.4.13 h1:I/j3w8UrXX1haXE+iraAbQuGihNVeTq6b8sp6L3ZJ6Q=
github.com/fasthttp/session/v2 v2.4.13/go.mod h1:bAE6Bjl6ofQbkOpqcSuOVt/1R1LnbNLnFMHjGQcYP5M=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -229,8 +225,8 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
-github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -239,6 +235,10 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
+github.com/go-crypt/crypt v0.1.14 h1:Pd8iBYlbwDXJNi0lz8CS/qYvpvxCfP0XO/f5PYvVQ4o=
+github.com/go-crypt/crypt v0.1.14/go.mod h1:VNLdWMD0go46arq5WVZB2MV/9Vw02FOWhKDORXl7K2c=
+github.com/go-crypt/x v0.1.3 h1:3YSlHqOZsw4gcPzfqrcc5kg4GIhTKmkjl/ZVqJ3CbbU=
+github.com/go-crypt/x v0.1.3/go.mod h1:/6X1DjQki055ajXV/7pCHZM0OmMR1+csiXFkxK73Kc8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -311,8 +311,8 @@ github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7
github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
-github.com/go-rod/rod v0.109.3 h1:MxuSJGK9lEUq07K+QPfnxnuvQpsQT+YI4SoQjSE0LVg=
-github.com/go-rod/rod v0.109.3/go.mod h1:GZDtmEs6RpF6kBRYpGCZXxXlKNneKVPiKOjaMbmVVjE=
+github.com/go-rod/rod v0.112.0 h1:U9Yc+quw4hxZ6GrdbWFBeylvaYElEKM9ijFW2LYkGlA=
+github.com/go-rod/rod v0.112.0/go.mod h1:GZDtmEs6RpF6kBRYpGCZXxXlKNneKVPiKOjaMbmVVjE=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -321,10 +321,10 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
-github.com/go-webauthn/revoke v0.1.3 h1:SU+/fD+Py4MMTqKnMfBJ5Td4skcvfWtjQbZnvT9ppB0=
-github.com/go-webauthn/revoke v0.1.3/go.mod h1:Vzr3vqXqGQVS21hYpbetmpIoN4HiSjv2gcZI7ltlneI=
-github.com/go-webauthn/webauthn v0.4.0 h1:nZFPH1yOXWpzBVpRFLtfeBb42NXz+T/xtSN+PZJXMDE=
-github.com/go-webauthn/webauthn v0.4.0/go.mod h1:x/nbCjR4E4b30gQ3Ws4zVpjN4ovkr4Ekjq64OExWi6U=
+github.com/go-webauthn/revoke v0.1.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU=
+github.com/go-webauthn/revoke v0.1.6/go.mod h1:TB4wuW4tPlwgF3znujA96F70/YSQXHPPWl7vgY09Iy8=
+github.com/go-webauthn/webauthn v0.5.0 h1:Tbmp37AGIhYbQmcy2hEffo3U3cgPClqvxJ7cLUnF7Rc=
+github.com/go-webauthn/webauthn v0.5.0/go.mod h1:0CBq/jNfPS9l033j4AxMk8K8MluiMsde9uGNSPFLEVE=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/attrs v0.1.0/go.mod h1:fmNpaWyHM0tRm8gCZWKx8yY9fvaNLo2PyzBNSrBZ5Hw=
github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY=
@@ -579,8 +579,6 @@ github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdK
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
-github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid/v3 v3.1.2/go.mod h1:xPwMqoocQ1L5G6pXX5BcE7N5jlzn2o19oqAKxwZW/kI=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -646,6 +644,12 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
+github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
+github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw=
+github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo=
+github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4=
+github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0=
+github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -742,15 +746,15 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
+github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/inhies/go-bytesize v0.0.0-20201103132853-d0aed0d254f8/go.mod h1:KrtyD5PFj++GKkFS/7/RRrfnRhAMGQwy75GLCHWrCNs=
github.com/instana/go-sensor v1.29.0/go.mod h1:Uh9j3eF2mBw/FLk2MxISmVDIj8mtJBFRj2S19M6CVyQ=
github.com/instana/testify v1.6.2-0.20200721153833-94b1851f4d65/go.mod h1:nYhEREG/B7HUY7P+LKOrqy53TpIqmJ9JyUShcaEKtGw=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
-github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
@@ -762,16 +766,8 @@ github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.6.0/go.mod h1:yeseQo4xhQbgyJs2c87RAXOH2i624N0Fh1KSPJya7qo=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
-github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
-github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
-github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
-github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
-github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
-github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
-github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
@@ -782,9 +778,6 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
-github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
@@ -796,9 +789,6 @@ github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLD
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
-github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
-github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
@@ -810,15 +800,13 @@ github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oA
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
-github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
-github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
+github.com/jackc/pgx/v5 v5.0.4 h1:r5O6y84qHX/z/HZV40JBdx2obsHz7/uRj5b+CcYEdeY=
+github.com/jackc/pgx/v5 v5.0.4/go.mod h1:U0ynklHtgg43fue9Ly30w3OCSTDPlXjig9ghrNGaguQ=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jandelgado/gcov2lcov v1.0.4-0.20210120124023-b83752c6dc08/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss=
github.com/jandelgado/gcov2lcov v1.0.4/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss=
github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0=
@@ -870,12 +858,13 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
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 v0.14.1-0.20201201075439-e0853799f9ec/go.mod h1:H5mEFsTeWizwFXHKtsITL5ipsLTuAMQoGuQpp+1JL9U=
github.com/knadh/koanf v1.2.2/go.mod h1:xpPTwMhsA/aaQLAilyCCqfpEiY1gpa160AiCuWHJUjY=
-github.com/knadh/koanf v1.4.3 h1:rSJcSH5LSFhvzBRsAYfT3k7eLP0I4UxeZqjtAatk+wc=
-github.com/knadh/koanf v1.4.3/go.mod h1:5FAkuykKXZvLqhAbP4peWgM5CTcZmn7L1d27k/a+kfg=
+github.com/knadh/koanf v1.4.4 h1:d2jY5nCCeoaiqvEKSBW9rEc93EfNy/XWgWsSB3j7JEA=
+github.com/knadh/koanf v1.4.4/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -884,6 +873,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
@@ -897,7 +887,6 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
@@ -958,9 +947,9 @@ github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOq
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
-github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/mattn/goveralls v0.0.6 h1:cr8Y0VMo/MnEZBjxNN/vh6G90SZ7IMb6lms1dzMoO+Y=
github.com/mattn/goveralls v0.0.6/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw=
@@ -1017,7 +1006,6 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -1102,13 +1090,13 @@ github.com/ory/x v0.0.205/go.mod h1:A1s4iwmFIppRXZLF3J9GGWeY/HpREVm0Dk5z/787iek=
github.com/ory/x v0.0.214/go.mod h1:aRl57gzyD4GF0HQCekovXhv0xTZgAgiht3o8eVhsm9Q=
github.com/ory/x v0.0.288 h1:WoEEgDg2QrJeNpPRXV9J19ZkHfxXEjO5oJA5Fm/tPs0=
github.com/ory/x v0.0.288/go.mod h1:APpShLyJcVzKw1kTgrHI+j/L9YM+8BRjHlcYObc7C1U=
-github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
-github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
+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=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
-github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
-github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
+github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4=
+github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -1155,15 +1143,16 @@ github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66Id
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
-github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
-github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
+github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
+github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
+github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@@ -1201,6 +1190,7 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@@ -1238,7 +1228,6 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
@@ -1247,8 +1236,6 @@ github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UD
github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/simia-tech/crypt v0.5.1 h1:rQa3qz8Xx4zNu4Uwtl4e6l7AKZBhYLrawZGfZjRLJYU=
-github.com/simia-tech/crypt v0.5.1/go.mod h1:VUAuUEkBhS6nI4JupmA8WBg+tkcZCSANpFSMLpsJAcQ=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
@@ -1291,8 +1278,8 @@ github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
-github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
+github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
+github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@@ -1316,7 +1303,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -1325,8 +1312,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
-github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -1363,8 +1351,9 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc=
github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
+github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY=
+github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
@@ -1489,14 +1478,10 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
-golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
+golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1536,8 +1521,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q=
-golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1598,8 +1583,10 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
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.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1621,8 +1608,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A=
-golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1714,27 +1701,32 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+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.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
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.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
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=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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=
@@ -1838,14 +1830,14 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
@@ -1967,8 +1959,8 @@ gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUy
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -2013,7 +2005,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
diff --git a/internal/authentication/const.go b/internal/authentication/const.go
index 61a5d246a..48d31c186 100644
--- a/internal/authentication/const.go
+++ b/internal/authentication/const.go
@@ -65,6 +65,10 @@ const (
ldapAttributeUserPassword = "userPassword"
)
+const (
+ ldapBaseObjectFilter = "(objectClass=*)"
+)
+
const (
ldapPlaceholderInput = "{input}"
ldapPlaceholderDistinguishedName = "{dn}"
@@ -75,37 +79,22 @@ const (
none = "none"
)
-// CryptAlgo the crypt representation of an algorithm used in the prefix of the hash.
-type CryptAlgo string
-
const (
- // HashingAlgorithmArgon2id Argon2id hash identifier.
- HashingAlgorithmArgon2id CryptAlgo = argon2id
- // HashingAlgorithmSHA512 SHA512 hash identifier.
- HashingAlgorithmSHA512 CryptAlgo = "6"
+ hashArgon2 = "argon2"
+ hashSHA2Crypt = "sha2crypt"
+ hashPBKDF2 = "pbkdf2"
+ hashSCrypt = "scrypt"
+ hashBCrypt = "bcrypt"
)
-// These are the default values from the upstream crypt module we use them to for GetInt
-// and they need to be checked when updating github.com/simia-tech/crypt.
-const (
- HashingDefaultArgon2idTime = 1
- HashingDefaultArgon2idMemory = 32 * 1024
- HashingDefaultArgon2idParallelism = 4
- HashingDefaultArgon2idKeyLength = 32
- HashingDefaultSHA512Iterations = 5000
+var (
+ // ErrUserNotFound indicates the user wasn't found in the authentication backend.
+ ErrUserNotFound = errors.New("user not found")
+
+ // ErrNoContent is returned when the file is empty.
+ ErrNoContent = errors.New("no file content")
)
-// HashingPossibleSaltCharacters represents valid hashing runes.
-var HashingPossibleSaltCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"
-
-// ErrUserNotFound indicates the user wasn't found in the authentication backend.
-var ErrUserNotFound = errors.New("user not found")
-
-const argon2id = "argon2id"
-const sha512 = "sha512"
-
-const testPassword = "my;secure*password"
-
const fileAuthenticationMode = 0600
// OWASP recommends to escape some special characters.
diff --git a/internal/authentication/file_user_provider.go b/internal/authentication/file_user_provider.go
index c4ea7af4e..1099b47be 100644
--- a/internal/authentication/file_user_provider.go
+++ b/internal/authentication/file_user_provider.go
@@ -2,13 +2,13 @@ package authentication
import (
_ "embed" // Embed users_database.template.yml.
+ "errors"
"fmt"
"os"
- "strings"
"sync"
+ "time"
- "github.com/asaskevich/govalidator"
- "gopkg.in/yaml.v3"
+ "github.com/go-crypt/crypt"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/logging"
@@ -16,196 +16,188 @@ import (
// FileUserProvider is a provider reading details from a file.
type FileUserProvider struct {
- configuration *schema.FileAuthenticationBackendConfiguration
- database *DatabaseModel
- lock *sync.Mutex
-}
-
-// UserDetailsModel is the model of user details in the file database.
-type UserDetailsModel struct {
- HashedPassword string `yaml:"password" valid:"required"`
- DisplayName string `yaml:"displayname" valid:"required"`
- Email string `yaml:"email"`
- Groups []string `yaml:"groups"`
-}
-
-// DatabaseModel is the model of users file database.
-type DatabaseModel struct {
- Users map[string]UserDetailsModel `yaml:"users" valid:"required"`
+ config *schema.FileAuthenticationBackend
+ hash crypt.Hash
+ database *FileUserDatabase
+ mutex *sync.Mutex
+ timeoutReload time.Time
}
// NewFileUserProvider creates a new instance of FileUserProvider.
-func NewFileUserProvider(configuration *schema.FileAuthenticationBackendConfiguration) *FileUserProvider {
- logger := logging.Logger()
-
- errs := checkDatabase(configuration.Path)
- if errs != nil {
- for _, err := range errs {
- logger.Error(err)
- }
-
- os.Exit(1)
- }
-
- database, err := readDatabase(configuration.Path)
- if err != nil {
- // Panic since the file does not exist when Authelia is starting.
- panic(err)
- }
-
- // Early check whether hashed passwords are correct for all users.
- err = checkPasswordHashes(database)
- if err != nil {
- panic(err)
- }
-
+func NewFileUserProvider(config *schema.FileAuthenticationBackend) (provider *FileUserProvider) {
return &FileUserProvider{
- configuration: configuration,
- database: database,
- lock: &sync.Mutex{},
+ config: config,
+ mutex: &sync.Mutex{},
+ timeoutReload: time.Now().Add(-1 * time.Second),
}
}
-func checkPasswordHashes(database *DatabaseModel) error {
- for u, v := range database.Users {
- v.HashedPassword = strings.ReplaceAll(v.HashedPassword, "{CRYPT}", "")
- _, err := ParseHash(v.HashedPassword)
+// Reload the database.
+func (p *FileUserProvider) Reload() (reloaded bool, err error) {
+ now := time.Now()
- if err != nil {
- return fmt.Errorf("Unable to parse hash of user %s: %s", u, err)
- }
+ p.mutex.Lock()
- database.Users[u] = v
+ defer p.mutex.Unlock()
+
+ if now.Before(p.timeoutReload) {
+ return false, nil
+ }
+
+ switch err = p.database.Load(); {
+ case err == nil:
+ p.setTimeoutReload(now)
+ case errors.Is(err, ErrNoContent):
+ return false, nil
+ default:
+ return false, fmt.Errorf("failed to reload: %w", err)
+ }
+
+ p.setTimeoutReload(now)
+
+ return true, nil
+}
+
+// CheckUserPassword checks if provided password matches for the given user.
+func (p *FileUserProvider) CheckUserPassword(username string, password string) (match bool, err error) {
+ var details DatabaseUserDetails
+
+ if details, err = p.database.GetUserDetails(username); err != nil {
+ return false, err
+ }
+
+ if details.Disabled {
+ return false, ErrUserNotFound
+ }
+
+ return details.Digest.MatchAdvanced(password)
+}
+
+// GetDetails retrieve the groups a user belongs to.
+func (p *FileUserProvider) GetDetails(username string) (details *UserDetails, err error) {
+ var d DatabaseUserDetails
+
+ if d, err = p.database.GetUserDetails(username); err != nil {
+ return nil, err
+ }
+
+ if d.Disabled {
+ return nil, ErrUserNotFound
+ }
+
+ return d.ToUserDetails(), nil
+}
+
+// UpdatePassword update the password of the given user.
+func (p *FileUserProvider) UpdatePassword(username string, newPassword string) (err error) {
+ var details DatabaseUserDetails
+
+ if details, err = p.database.GetUserDetails(username); err != nil {
+ return err
+ }
+
+ if details.Disabled {
+ return ErrUserNotFound
+ }
+
+ if details.Digest, err = p.hash.Hash(newPassword); err != nil {
+ return err
+ }
+
+ p.database.SetUserDetails(details.Username, &details)
+
+ p.mutex.Lock()
+
+ p.setTimeoutReload(time.Now())
+
+ p.mutex.Unlock()
+
+ if err = p.database.Save(); err != nil {
+ return err
}
return nil
}
-func checkDatabase(path string) []error {
- _, err := os.Stat(path)
- if err != nil {
- errs := []error{
- fmt.Errorf("Unable to find database file: %v", path),
- fmt.Errorf("Generating database file: %v", path),
+// StartupCheck implements the startup check provider interface.
+func (p *FileUserProvider) StartupCheck() (err error) {
+ if err = checkDatabase(p.config.Path); err != nil {
+ logging.Logger().WithError(err).Errorf("Error checking user authentication YAML database")
+
+ return fmt.Errorf("one or more errors occurred checking the authentication database")
+ }
+
+ if p.hash, err = NewFileCryptoHashFromConfig(p.config.Password); err != nil {
+ return err
+ }
+
+ p.database = NewFileUserDatabase(p.config.Path, p.config.Search.Email, p.config.Search.CaseInsensitive)
+
+ if err = p.database.Load(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (p *FileUserProvider) setTimeoutReload(now time.Time) {
+ p.timeoutReload = now.Add(time.Second / 2)
+}
+
+// NewFileCryptoHashFromConfig returns a crypt.Hash given a valid configuration.
+func NewFileCryptoHashFromConfig(config schema.Password) (hash crypt.Hash, err error) {
+ switch config.Algorithm {
+ case hashArgon2, "":
+ hash = crypt.NewArgon2Hash().
+ WithVariant(crypt.NewArgon2Variant(config.Argon2.Variant)).
+ WithT(config.Argon2.Iterations).
+ WithM(config.Argon2.Memory).
+ WithP(config.Argon2.Parallelism).
+ WithK(config.Argon2.KeyLength).
+ WithS(config.Argon2.SaltLength)
+ case hashSHA2Crypt:
+ hash = crypt.NewSHA2CryptHash().
+ WithVariant(crypt.NewSHA2CryptVariant(config.SHA2Crypt.Variant)).
+ WithRounds(config.SHA2Crypt.Iterations).
+ WithSaltLength(config.SHA2Crypt.SaltLength)
+ case hashPBKDF2:
+ hash = crypt.NewPBKDF2Hash().
+ WithVariant(crypt.NewPBKDF2Variant(config.PBKDF2.Variant)).
+ WithIterations(config.PBKDF2.Iterations).
+ WithSaltLength(config.PBKDF2.SaltLength)
+ case hashSCrypt:
+ hash = crypt.NewScryptHash().
+ WithLN(config.SCrypt.Iterations).
+ WithP(config.SCrypt.Parallelism).
+ WithR(config.SCrypt.BlockSize)
+ case hashBCrypt:
+ hash = crypt.NewBcryptHash().
+ WithVariant(crypt.NewBcryptVariant(config.BCrypt.Variant)).
+ WithCost(config.BCrypt.Cost)
+ default:
+ return nil, fmt.Errorf("algorithm '%s' is unknown", config.Algorithm)
+ }
+
+ if err = hash.Validate(); err != nil {
+ return nil, fmt.Errorf("failed to validate hash settings: %w", err)
+ }
+
+ return hash, nil
+}
+
+func checkDatabase(path string) (err error) {
+ if _, err = os.Stat(path); os.IsNotExist(err) {
+ if err = os.WriteFile(path, userYAMLTemplate, 0600); err != nil {
+ return fmt.Errorf("user authentication database file doesn't exist at path '%s' and could not be generated: %w", path, err)
}
- err := generateDatabaseFromTemplate(path)
- if err != nil {
- errs = append(errs, err)
- } else {
- errs = append(errs, fmt.Errorf("Generated database at: %v", path))
- }
-
- return errs
+ return fmt.Errorf("user authentication database file doesn't exist at path '%s' and has been generated", path)
+ } else if err != nil {
+ return fmt.Errorf("error checking user authentication database file: %w", err)
}
return nil
}
//go:embed users_database.template.yml
-var cfg []byte
-
-func generateDatabaseFromTemplate(path string) error {
- err := os.WriteFile(path, cfg, 0600)
- if err != nil {
- return fmt.Errorf("Unable to generate %v: %v", path, err)
- }
-
- return nil
-}
-
-func readDatabase(path string) (*DatabaseModel, error) {
- content, err := os.ReadFile(path)
- if err != nil {
- return nil, fmt.Errorf("Unable to read database from file %s: %s", path, err)
- }
-
- db := DatabaseModel{}
-
- err = yaml.Unmarshal(content, &db)
- if err != nil {
- return nil, fmt.Errorf("Unable to parse database: %s", err)
- }
-
- ok, err := govalidator.ValidateStruct(db)
- if err != nil {
- return nil, fmt.Errorf("Invalid schema of database: %s", err)
- }
-
- if !ok {
- return nil, fmt.Errorf("The database format is invalid: %s", err)
- }
-
- return &db, nil
-}
-
-// CheckUserPassword checks if provided password matches for the given user.
-func (p *FileUserProvider) CheckUserPassword(username string, password string) (bool, error) {
- if details, ok := p.database.Users[username]; ok {
- ok, err := CheckPassword(password, details.HashedPassword)
- if err != nil {
- return false, err
- }
-
- return ok, nil
- }
-
- return false, ErrUserNotFound
-}
-
-// GetDetails retrieve the groups a user belongs to.
-func (p *FileUserProvider) GetDetails(username string) (*UserDetails, error) {
- if details, ok := p.database.Users[username]; ok {
- return &UserDetails{
- Username: username,
- DisplayName: details.DisplayName,
- Groups: details.Groups,
- Emails: []string{details.Email},
- }, nil
- }
-
- return nil, fmt.Errorf("User '%s' does not exist in database", username)
-}
-
-// UpdatePassword update the password of the given user.
-func (p *FileUserProvider) UpdatePassword(username string, newPassword string) error {
- details, ok := p.database.Users[username]
- if !ok {
- return ErrUserNotFound
- }
-
- algorithm, err := ConfigAlgoToCryptoAlgo(p.configuration.Password.Algorithm)
- if err != nil {
- return err
- }
-
- hash, err := HashPassword(
- newPassword, "", algorithm, p.configuration.Password.Iterations,
- p.configuration.Password.Memory*1024, p.configuration.Password.Parallelism,
- p.configuration.Password.KeyLength, p.configuration.Password.SaltLength)
-
- if err != nil {
- return err
- }
-
- details.HashedPassword = hash
-
- p.lock.Lock()
- p.database.Users[username] = details
-
- b, err := yaml.Marshal(p.database)
- if err != nil {
- p.lock.Unlock()
- return err
- }
-
- err = os.WriteFile(p.configuration.Path, b, fileAuthenticationMode)
- p.lock.Unlock()
-
- return err
-}
-
-// StartupCheck implements the startup check provider interface.
-func (p *FileUserProvider) StartupCheck() (err error) {
- return nil
-}
+var userYAMLTemplate []byte
diff --git a/internal/authentication/file_user_provider_database.go b/internal/authentication/file_user_provider_database.go
new file mode 100644
index 000000000..4d710e34b
--- /dev/null
+++ b/internal/authentication/file_user_provider_database.go
@@ -0,0 +1,325 @@
+package authentication
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "sync"
+
+ "github.com/asaskevich/govalidator"
+ "github.com/go-crypt/crypt"
+ "gopkg.in/yaml.v3"
+)
+
+// NewFileUserDatabase creates a new FileUserDatabase.
+func NewFileUserDatabase(filePath string, searchEmail, searchCI bool) (database *FileUserDatabase) {
+ return &FileUserDatabase{
+ RWMutex: &sync.RWMutex{},
+ Path: filePath,
+ Users: map[string]DatabaseUserDetails{},
+ Emails: map[string]string{},
+ Aliases: map[string]string{},
+ SearchEmail: searchEmail,
+ SearchCI: searchCI,
+ }
+}
+
+// FileUserDatabase is a user details database that is concurrency safe database and can be reloaded.
+type FileUserDatabase struct {
+ *sync.RWMutex
+
+ Path string
+ Users map[string]DatabaseUserDetails
+ Emails map[string]string
+ Aliases map[string]string
+
+ SearchEmail bool
+ SearchCI bool
+}
+
+// Save the database to disk.
+func (m *FileUserDatabase) Save() (err error) {
+ m.RLock()
+
+ defer m.RUnlock()
+
+ if err = m.ToDatabaseModel().Write(m.Path); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Load the database from disk.
+func (m *FileUserDatabase) Load() (err error) {
+ yml := &DatabaseModel{Users: map[string]UserDetailsModel{}}
+
+ if err = yml.Read(m.Path); err != nil {
+ return fmt.Errorf("error reading the authentication database: %w", err)
+ }
+
+ m.Lock()
+
+ defer m.Unlock()
+
+ if err = yml.ReadToFileUserDatabase(m); err != nil {
+ return fmt.Errorf("error decoding the authentication database: %w", err)
+ }
+
+ return m.LoadAliases()
+}
+
+// LoadAliases performs the loading of alias information from the database.
+func (m *FileUserDatabase) LoadAliases() (err error) {
+ if m.SearchEmail || m.SearchCI {
+ for k, user := range m.Users {
+ if m.SearchEmail && user.Email != "" {
+ if err = m.loadAliasEmail(k, user); err != nil {
+ return err
+ }
+ }
+
+ if m.SearchCI {
+ if err = m.loadAlias(k); err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (m *FileUserDatabase) loadAlias(k string) (err error) {
+ u := strings.ToLower(k)
+
+ if u != k {
+ return fmt.Errorf("error loading authentication database: username '%s' is not lowercase but this is required when case-insensitive search is enabled", k)
+ }
+
+ for username, details := range m.Users {
+ if k == username {
+ continue
+ }
+
+ if strings.EqualFold(u, details.Email) {
+ return fmt.Errorf("error loading authentication database: username '%s' is configured as an email for user with username '%s' which isn't allowed when case-insensitive search is enabled", u, username)
+ }
+ }
+
+ m.Aliases[u] = k
+
+ return nil
+}
+
+func (m *FileUserDatabase) loadAliasEmail(k string, user DatabaseUserDetails) (err error) {
+ e := strings.ToLower(user.Email)
+
+ var duplicates []string
+
+ for username, details := range m.Users {
+ if k == username {
+ continue
+ }
+
+ if strings.EqualFold(e, details.Email) {
+ duplicates = append(duplicates, username)
+ }
+ }
+
+ if len(duplicates) != 0 {
+ duplicates = append(duplicates, k)
+
+ return fmt.Errorf("error loading authentication database: email '%s' is configured for for more than one user (users are '%s') which isn't allowed when email search is enabled", e, strings.Join(duplicates, "', '"))
+ }
+
+ if _, ok := m.Users[e]; ok && k != e {
+ return fmt.Errorf("error loading authentication database: email '%s' is also a username which isn't allowed when email search is enabled", e)
+ }
+
+ m.Emails[e] = k
+
+ return nil
+}
+
+// GetUserDetails get a DatabaseUserDetails given a username as a value type where the username must be the users actual
+// username.
+func (m *FileUserDatabase) GetUserDetails(username string) (user DatabaseUserDetails, err error) {
+ m.RLock()
+
+ defer m.RUnlock()
+
+ u := strings.ToLower(username)
+
+ if m.SearchEmail {
+ if key, ok := m.Emails[u]; ok {
+ return m.Users[key], nil
+ }
+ }
+
+ if m.SearchCI {
+ if key, ok := m.Aliases[u]; ok {
+ return m.Users[key], nil
+ }
+ }
+
+ if details, ok := m.Users[username]; ok {
+ return details, nil
+ }
+
+ return user, ErrUserNotFound
+}
+
+// SetUserDetails sets the DatabaseUserDetails for a given user.
+func (m *FileUserDatabase) SetUserDetails(username string, details *DatabaseUserDetails) {
+ if details == nil {
+ return
+ }
+
+ m.Lock()
+
+ m.Users[username] = *details
+
+ m.Unlock()
+}
+
+// ToDatabaseModel converts the FileUserDatabase into the DatabaseModel for saving.
+func (m *FileUserDatabase) ToDatabaseModel() (model *DatabaseModel) {
+ model = &DatabaseModel{
+ Users: map[string]UserDetailsModel{},
+ }
+
+ m.RLock()
+
+ for user, details := range m.Users {
+ model.Users[user] = details.ToUserDetailsModel()
+ }
+
+ m.RUnlock()
+
+ return model
+}
+
+// DatabaseUserDetails is the model of user details in the file database.
+type DatabaseUserDetails struct {
+ Username string
+ Digest crypt.Digest
+ Disabled bool
+ DisplayName string
+ Email string
+ Groups []string
+}
+
+// ToUserDetails converts DatabaseUserDetails into a *UserDetails given a username.
+func (m DatabaseUserDetails) ToUserDetails() (details *UserDetails) {
+ return &UserDetails{
+ Username: m.Username,
+ DisplayName: m.DisplayName,
+ Emails: []string{m.Email},
+ Groups: m.Groups,
+ }
+}
+
+// ToUserDetailsModel converts DatabaseUserDetails into a UserDetailsModel.
+func (m DatabaseUserDetails) ToUserDetailsModel() (model UserDetailsModel) {
+ return UserDetailsModel{
+ HashedPassword: m.Digest.Encode(),
+ DisplayName: m.DisplayName,
+ Email: m.Email,
+ Groups: m.Groups,
+ }
+}
+
+// DatabaseModel is the model of users file database.
+type DatabaseModel struct {
+ Users map[string]UserDetailsModel `yaml:"users" valid:"required"`
+}
+
+// ReadToFileUserDatabase reads the DatabaseModel into a FileUserDatabase.
+func (m *DatabaseModel) ReadToFileUserDatabase(db *FileUserDatabase) (err error) {
+ users := map[string]DatabaseUserDetails{}
+
+ var udm *DatabaseUserDetails
+
+ for user, details := range m.Users {
+ if udm, err = details.ToDatabaseUserDetailsModel(user); err != nil {
+ return fmt.Errorf("failed to parse hash for user '%s': %w", user, err)
+ }
+
+ users[user] = *udm
+ }
+
+ db.Users = users
+
+ return nil
+}
+
+// Read a DatabaseModel from disk.
+func (m *DatabaseModel) Read(filePath string) (err error) {
+ var (
+ content []byte
+ ok bool
+ )
+
+ if content, err = os.ReadFile(filePath); err != nil {
+ return fmt.Errorf("failed to read the '%s' file: %w", filePath, err)
+ }
+
+ if len(content) == 0 {
+ return ErrNoContent
+ }
+
+ if err = yaml.Unmarshal(content, m); err != nil {
+ return fmt.Errorf("could not parse the YAML database: %w", err)
+ }
+
+ if ok, err = govalidator.ValidateStruct(m); err != nil {
+ return fmt.Errorf("could not validate the schema: %w", err)
+ }
+
+ if !ok {
+ return fmt.Errorf("the schema is invalid")
+ }
+
+ return nil
+}
+
+// Write a DatabaseModel to disk.
+func (m *DatabaseModel) Write(fileName string) (err error) {
+ var (
+ data []byte
+ )
+
+ if data, err = yaml.Marshal(m); err != nil {
+ return err
+ }
+
+ return os.WriteFile(fileName, data, fileAuthenticationMode)
+}
+
+// UserDetailsModel is the model of user details in the file database.
+type UserDetailsModel struct {
+ HashedPassword string `yaml:"password" valid:"required"`
+ DisplayName string `yaml:"displayname" valid:"required"`
+ Email string `yaml:"email"`
+ Groups []string `yaml:"groups"`
+ Disabled bool `yaml:"disabled"`
+}
+
+// ToDatabaseUserDetailsModel converts a UserDetailsModel into a *DatabaseUserDetails.
+func (m UserDetailsModel) ToDatabaseUserDetailsModel(username string) (model *DatabaseUserDetails, err error) {
+ var d crypt.Digest
+
+ if d, err = crypt.Decode(m.HashedPassword); err != nil {
+ return nil, err
+ }
+
+ return &DatabaseUserDetails{
+ Username: username,
+ Digest: d,
+ Disabled: m.Disabled,
+ DisplayName: m.DisplayName,
+ Email: m.Email,
+ Groups: m.Groups,
+ }, nil
+}
diff --git a/internal/authentication/file_user_provider_test.go b/internal/authentication/file_user_provider_test.go
index 01c6baa87..4b6039f69 100644
--- a/internal/authentication/file_user_provider_test.go
+++ b/internal/authentication/file_user_provider_test.go
@@ -3,6 +3,7 @@ package authentication
import (
"log"
"os"
+ "regexp"
"runtime"
"strings"
"testing"
@@ -39,24 +40,16 @@ func TestShouldErrorPermissionsOnLocalFS(t *testing.T) {
}
_ = os.Mkdir("/tmp/noperms/", 0000)
- errors := checkDatabase("/tmp/noperms/users_database.yml")
+ err := checkDatabase("/tmp/noperms/users_database.yml")
- require.Len(t, errors, 3)
-
- require.EqualError(t, errors[0], "Unable to find database file: /tmp/noperms/users_database.yml")
- require.EqualError(t, errors[1], "Generating database file: /tmp/noperms/users_database.yml")
- require.EqualError(t, errors[2], "Unable to generate /tmp/noperms/users_database.yml: open /tmp/noperms/users_database.yml: permission denied")
+ require.EqualError(t, err, "error checking user authentication database file: stat /tmp/noperms/users_database.yml: permission denied")
}
func TestShouldErrorAndGenerateUserDB(t *testing.T) {
- errors := checkDatabase("./nonexistent.yml")
+ err := checkDatabase("./nonexistent.yml")
_ = os.Remove("./nonexistent.yml")
- require.Len(t, errors, 3)
-
- require.EqualError(t, errors[0], "Unable to find database file: ./nonexistent.yml")
- require.EqualError(t, errors[1], "Generating database file: ./nonexistent.yml")
- require.EqualError(t, errors[2], "Generated database at: ./nonexistent.yml")
+ require.EqualError(t, err, "user authentication database file doesn't exist at path './nonexistent.yml' and has been generated")
}
func TestShouldCheckUserArgon2idPasswordIsCorrect(t *testing.T) {
@@ -64,6 +57,9 @@ func TestShouldCheckUserArgon2idPasswordIsCorrect(t *testing.T) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("john", "password")
assert.NoError(t, err)
@@ -75,7 +71,11 @@ func TestShouldCheckUserSHA512PasswordIsCorrect(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
+
provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("harry", "password")
assert.NoError(t, err)
@@ -87,7 +87,11 @@ func TestShouldCheckUserPasswordIsWrong(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
+
provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("john", "wrong_password")
assert.NoError(t, err)
@@ -99,8 +103,11 @@ func TestShouldCheckUserPasswordIsWrongForEnumerationCompare(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
+
provider := NewFileUserProvider(&config)
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("enumeration", "wrong_password")
assert.NoError(t, err)
assert.False(t, ok)
@@ -111,7 +118,11 @@ func TestShouldCheckUserPasswordOfUserThatDoesNotExist(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
+
provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("fake", "password")
assert.Error(t, err)
assert.Equal(t, false, ok)
@@ -123,12 +134,16 @@ func TestShouldRetrieveUserDetails(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
+
provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
details, err := provider.GetDetails("john")
assert.NoError(t, err)
- assert.Equal(t, details.Username, "john")
- assert.Equal(t, details.Emails, []string{"john.doe@authelia.com"})
- assert.Equal(t, details.Groups, []string{"admins", "dev"})
+ assert.Equal(t, "john", details.Username)
+ assert.Equal(t, []string{"john.doe@authelia.com"}, details.Emails)
+ assert.Equal(t, []string{"admins", "dev"}, details.Groups)
})
}
@@ -136,12 +151,19 @@ func TestShouldUpdatePassword(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
+
provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
err := provider.UpdatePassword("harry", "newpassword")
assert.NoError(t, err)
// Reset the provider to force a read from disk.
provider = NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("harry", "newpassword")
assert.NoError(t, err)
assert.True(t, ok)
@@ -153,17 +175,24 @@ func TestShouldUpdatePasswordHashingAlgorithmToArgon2id(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
+
provider := NewFileUserProvider(&config)
- assert.True(t, strings.HasPrefix(provider.database.Users["harry"].HashedPassword, "$6$"))
+
+ assert.NoError(t, provider.StartupCheck())
+
+ assert.True(t, strings.HasPrefix(provider.database.Users["harry"].Digest.Encode(), "$6$"))
err := provider.UpdatePassword("harry", "newpassword")
assert.NoError(t, err)
// Reset the provider to force a read from disk.
provider = NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("harry", "newpassword")
assert.NoError(t, err)
assert.True(t, ok)
- assert.True(t, strings.HasPrefix(provider.database.Users["harry"].HashedPassword, "$argon2id$"))
+ assert.True(t, strings.HasPrefix(provider.database.Users["harry"].Digest.Encode(), "$argon2id$"))
})
}
@@ -171,20 +200,26 @@ func TestShouldUpdatePasswordHashingAlgorithmToSHA512(t *testing.T) {
WithDatabase(UserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
- config.Password.Algorithm = "sha512"
- config.Password.Iterations = 50000
+ config.Password.Algorithm = "sha2crypt"
+ config.Password.SHA2Crypt.Iterations = 50000
provider := NewFileUserProvider(&config)
- assert.True(t, strings.HasPrefix(provider.database.Users["john"].HashedPassword, "$argon2id$"))
+
+ assert.NoError(t, provider.StartupCheck())
+
+ assert.True(t, strings.HasPrefix(provider.database.Users["john"].Digest.Encode(), "$argon2id$"))
err := provider.UpdatePassword("john", "newpassword")
assert.NoError(t, err)
// Reset the provider to force a read from disk.
provider = NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("john", "newpassword")
assert.NoError(t, err)
assert.True(t, ok)
- assert.True(t, strings.HasPrefix(provider.database.Users["john"].HashedPassword, "$6$"))
+ assert.True(t, strings.HasPrefix(provider.database.Users["john"].Digest.Encode(), "$6$"))
})
}
@@ -192,9 +227,10 @@ func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) {
WithDatabase(MalformedUserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
- assert.PanicsWithError(t, "Unable to parse database: yaml: line 4: mapping values are not allowed in this context", func() {
- NewFileUserProvider(&config)
- })
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error reading the authentication database: could not parse the YAML database: yaml: line 4: mapping values are not allowed in this context")
})
}
@@ -202,9 +238,10 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) {
WithDatabase(BadSchemaUserDatabaseContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
- assert.PanicsWithError(t, "Invalid schema of database: Users: non zero value required", func() {
- NewFileUserProvider(&config)
- })
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error reading the authentication database: could not validate the schema: Users: non zero value required")
})
}
@@ -212,9 +249,10 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadSHA512HashesForTheFirstTime(t *tes
WithDatabase(BadSHA512HashContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
- assert.PanicsWithError(t, "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/)", func() {
- NewFileUserProvider(&config)
- })
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error decoding the authentication database: failed to parse hash for user 'john': sha2crypt decode error: provided encoded hash has an invalid option: option 'rounds00000' is invalid")
})
}
@@ -222,9 +260,10 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSettingsForTheFirstTim
WithDatabase(BadArgon2idHashSettingsContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
- assert.PanicsWithError(t, "Unable to parse hash of user john: Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)", func() {
- NewFileUserProvider(&config)
- })
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error decoding the authentication database: failed to parse hash for user 'john': argon2 decode error: provided encoded hash has an invalid option: option 'm65536' is invalid")
})
}
@@ -232,9 +271,10 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashKeyForTheFirstTime(t *
WithDatabase(BadArgon2idHashKeyContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
- assert.PanicsWithError(t, "Unable to parse hash of user john: Hash key contains invalid base64 characters", func() {
- NewFileUserProvider(&config)
- })
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error decoding the authentication database: failed to parse hash for user 'john': argon2 decode error: provided encoded hash has a key value that can't be decoded: illegal base64 data at input byte 0")
})
}
@@ -242,9 +282,10 @@ func TestShouldRaiseWhenLoadingDatabaseWithBadArgon2idHashSaltForTheFirstTime(t
WithDatabase(BadArgon2idHashSaltContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
- assert.PanicsWithError(t, "Unable to parse hash of user john: Salt contains invalid base64 characters", func() {
- NewFileUserProvider(&config)
- })
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error decoding the authentication database: failed to parse hash for user 'john': argon2 decode error: provided encoded hash has a salt value that can't be decoded: illegal base64 data at input byte 0")
})
}
@@ -252,7 +293,11 @@ func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
WithDatabase(UserDatabaseWithoutCryptContent, func(path string) {
config := DefaultFileAuthenticationBackendConfiguration
config.Path = path
+
provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
ok, err := provider.CheckUserPassword("john", "password")
assert.NoError(t, err)
@@ -260,17 +305,141 @@ func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
})
}
+func TestShouldNotAllowLoginOfDisabledUsers(t *testing.T) {
+ WithDatabase(UserDatabaseContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+
+ provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
+ ok, err := provider.CheckUserPassword("dis", "password")
+
+ assert.False(t, ok)
+ assert.EqualError(t, err, "user not found")
+ })
+}
+
+func TestShouldErrorOnInvalidCaseSensitiveFile(t *testing.T) {
+ WithDatabase(UserDatabaseContentInvalidSearchCaseInsenstive, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ config.Search.Email = false
+ config.Search.CaseInsensitive = true
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error loading authentication database: username 'JOHN' is not lowercase but this is required when case-insensitive search is enabled")
+ })
+}
+
+func TestShouldErrorOnDuplicateEmail(t *testing.T) {
+ WithDatabase(UserDatabaseContentInvalidSearchEmail, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ config.Search.Email = true
+ config.Search.CaseInsensitive = false
+
+ provider := NewFileUserProvider(&config)
+
+ err := provider.StartupCheck()
+ assert.Regexp(t, regexp.MustCompile(`^error loading authentication database: email 'john.doe@authelia.com' is configured for for more than one user \(users are '(harry|john)', '(harry|john)'\) which isn't allowed when email search is enabled$`), err.Error())
+ })
+}
+
+func TestShouldNotErrorOnEmailAsUsername(t *testing.T) {
+ WithDatabase(UserDatabaseContentSearchEmailAsUsername, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ config.Search.Email = true
+ config.Search.CaseInsensitive = false
+
+ provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+ })
+}
+
+func TestShouldErrorOnEmailAsUsernameWithDuplicateEmail(t *testing.T) {
+ WithDatabase(UserDatabaseContentInvalidSearchEmailAsUsername, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ config.Search.Email = true
+ config.Search.CaseInsensitive = false
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error loading authentication database: email 'john.doe@authelia.com' is also a username which isn't allowed when email search is enabled")
+ })
+}
+
+func TestShouldErrorOnEmailAsUsernameWithDuplicateEmailCase(t *testing.T) {
+ WithDatabase(UserDatabaseContentInvalidSearchEmailAsUsernameCase, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ config.Search.Email = false
+ config.Search.CaseInsensitive = true
+
+ provider := NewFileUserProvider(&config)
+
+ assert.EqualError(t, provider.StartupCheck(), "error loading authentication database: username 'john.doe@authelia.com' is configured as an email for user with username 'john' which isn't allowed when case-insensitive search is enabled")
+ })
+}
+
+func TestShouldAllowLookupByEmail(t *testing.T) {
+ WithDatabase(UserDatabaseContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ config.Search.Email = true
+
+ provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
+ ok, err := provider.CheckUserPassword("john", "password")
+
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ ok, err = provider.CheckUserPassword("john.doe@authelia.com", "password")
+
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ ok, err = provider.CheckUserPassword("JOHN.doe@authelia.com", "password")
+
+ assert.NoError(t, err)
+ assert.True(t, ok)
+ })
+}
+
+func TestShouldAllowLookupCI(t *testing.T) {
+ WithDatabase(UserDatabaseContent, func(path string) {
+ config := DefaultFileAuthenticationBackendConfiguration
+ config.Path = path
+ config.Search.CaseInsensitive = true
+
+ provider := NewFileUserProvider(&config)
+
+ assert.NoError(t, provider.StartupCheck())
+
+ ok, err := provider.CheckUserPassword("john", "password")
+
+ assert.NoError(t, err)
+ assert.True(t, ok)
+
+ ok, err = provider.CheckUserPassword("John", "password")
+
+ assert.NoError(t, err)
+ assert.True(t, ok)
+ })
+}
+
var (
- DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackendConfiguration{
- Path: "",
- Password: &schema.PasswordConfiguration{
- Iterations: schema.DefaultCIPasswordConfiguration.Iterations,
- KeyLength: schema.DefaultCIPasswordConfiguration.KeyLength,
- SaltLength: schema.DefaultCIPasswordConfiguration.SaltLength,
- Algorithm: schema.DefaultCIPasswordConfiguration.Algorithm,
- Memory: schema.DefaultCIPasswordConfiguration.Memory,
- Parallelism: schema.DefaultCIPasswordConfiguration.Parallelism,
- },
+ DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackend{
+ Path: "",
+ Password: schema.DefaultCIPasswordConfig,
}
)
@@ -306,7 +475,99 @@ users:
enumeration:
displayname: "Enumeration"
password: "$argon2id$v=19$m=131072,p=8$BpLnfgDsc2WD8F2q$O126GHPeZ5fwj7OLSs7PndXsTbje76R+QW9/EGfhkJg"
- email: james.dean@authelia.com
+ email: enumeration@authelia.com
+
+
+ dis:
+ displayname: "Enumeration"
+ password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ disabled: true
+ email: disabled@authelia.com
+`)
+
+var UserDatabaseContentInvalidSearchCaseInsenstive = []byte(`
+users:
+ john:
+ displayname: "John Doe"
+ password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: john.doe@authelia.com
+ groups:
+ - admins
+ - dev
+
+ JOHN:
+ displayname: "Harry Potter"
+ password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+ email: harry.potter@authelia.com
+ groups: []
+`)
+
+var UserDatabaseContentInvalidSearchEmail = []byte(`
+users:
+ john:
+ displayname: "John Doe"
+ password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: john.doe@authelia.com
+ groups:
+ - admins
+ - dev
+
+ harry:
+ displayname: "Harry Potter"
+ password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+ email: john.doe@authelia.com
+ groups: []
+`)
+
+var UserDatabaseContentSearchEmailAsUsername = []byte(`
+users:
+ john.doe@authelia.com:
+ displayname: "John Doe"
+ password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: john.doe@authelia.com
+ groups:
+ - admins
+ - dev
+
+ harry:
+ displayname: "Harry Potter"
+ password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+ email: harry.potter@authelia.com
+ groups: []
+`)
+
+var UserDatabaseContentInvalidSearchEmailAsUsername = []byte(`
+users:
+ john.doe@authelia.com:
+ displayname: "John Doe"
+ password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: john@authelia.com
+ groups:
+ - admins
+ - dev
+
+ harry:
+ displayname: "Harry Potter"
+ password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+ email: john.doe@authelia.com
+ groups: []
+`)
+
+var UserDatabaseContentInvalidSearchEmailAsUsernameCase = []byte(`
+users:
+ john.doe@authelia.com:
+ displayname: "John Doe"
+ password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
+ email: JOHN@authelia.com
+ groups:
+ - admins
+ - dev
+
+ john:
+ displayname: "John Potter"
+ password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+ email: john.doe@authelia.com
+ groups: []
`)
var MalformedUserDatabaseContent = []byte(`
@@ -385,6 +646,7 @@ users:
- admins
- dev
`)
+
var BadArgon2idHashSaltContent = []byte(`
users:
john:
diff --git a/internal/authentication/ldap_user_provider.go b/internal/authentication/ldap_user_provider.go
index 0c4b2ed9f..65f635bf7 100644
--- a/internal/authentication/ldap_user_provider.go
+++ b/internal/authentication/ldap_user_provider.go
@@ -17,7 +17,7 @@ import (
// LDAPUserProvider is a UserProvider that connects to LDAP servers like ActiveDirectory, OpenLDAP, OpenDJ, FreeIPA, etc.
type LDAPUserProvider struct {
- config schema.LDAPAuthenticationBackendConfiguration
+ config schema.LDAPAuthenticationBackend
tlsConfig *tls.Config
dialOpts []ldap.DialOpt
log *logrus.Logger
@@ -42,18 +42,18 @@ type LDAPUserProvider struct {
}
// NewLDAPUserProvider creates a new instance of LDAPUserProvider.
-func NewLDAPUserProvider(config schema.AuthenticationBackendConfiguration, certPool *x509.CertPool) (provider *LDAPUserProvider) {
+func NewLDAPUserProvider(config schema.AuthenticationBackend, certPool *x509.CertPool) (provider *LDAPUserProvider) {
provider = newLDAPUserProvider(*config.LDAP, config.PasswordReset.Disable, certPool, nil)
return provider
}
-func newLDAPUserProvider(config schema.LDAPAuthenticationBackendConfiguration, disableResetPassword bool, certPool *x509.CertPool, factory LDAPClientFactory) (provider *LDAPUserProvider) {
+func newLDAPUserProvider(config schema.LDAPAuthenticationBackend, disableResetPassword bool, certPool *x509.CertPool, factory LDAPClientFactory) (provider *LDAPUserProvider) {
if config.TLS == nil {
- config.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
+ config.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS
}
- tlsConfig := utils.NewTLSConfig(config.TLS, tls.VersionTLS12, certPool)
+ tlsConfig := utils.NewTLSConfig(config.TLS, certPool)
var dialOpts = []ldap.DialOpt{
ldap.DialWithDialer(&net.Dialer{Timeout: config.Timeout}),
@@ -126,28 +126,31 @@ func (p *LDAPUserProvider) GetDetails(username string) (details *UserDetails, er
}
var (
- filter string
- searchRequest *ldap.SearchRequest
- searchResult *ldap.SearchResult
+ request *ldap.SearchRequest
+ result *ldap.SearchResult
)
- if filter, err = p.resolveGroupsFilter(username, profile); err != nil {
- return nil, fmt.Errorf("unable to create group filter for user '%s'. Cause: %w", username, err)
- }
-
// Search for the users groups.
- searchRequest = ldap.NewSearchRequest(
+ request = ldap.NewSearchRequest(
p.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
- 0, 0, false, filter, p.groupsAttributes, nil,
+ 0, 0, false, p.resolveGroupsFilter(username, profile), p.groupsAttributes, nil,
)
- if searchResult, err = p.search(client, searchRequest); err != nil {
+ p.log.
+ WithField("base_dn", request.BaseDN).
+ WithField("filter", request.Filter).
+ WithField("attr", request.Attributes).
+ WithField("scope", request.Scope).
+ WithField("deref", request.DerefAliases).
+ Trace("Performing group search")
+
+ if result, err = p.search(client, request); err != nil {
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
}
groups := make([]string, 0)
- for _, res := range searchResult.Entries {
+ for _, res := range result.Entries {
if len(res.Attributes) == 0 {
p.log.Warningf("No groups retrieved from LDAP for user %s", username)
break
@@ -254,35 +257,35 @@ func (p *LDAPUserProvider) connectCustom(url, username, password string, startTL
return client, nil
}
-func (p *LDAPUserProvider) search(client LDAPClient, searchRequest *ldap.SearchRequest) (searchResult *ldap.SearchResult, err error) {
- if searchResult, err = client.Search(searchRequest); err != nil {
+func (p *LDAPUserProvider) search(client LDAPClient, request *ldap.SearchRequest) (result *ldap.SearchResult, err error) {
+ if result, err = client.Search(request); err != nil {
if referral, ok := p.getReferral(err); ok {
- if searchResult == nil {
- searchResult = &ldap.SearchResult{
+ if result == nil {
+ result = &ldap.SearchResult{
Referrals: []string{referral},
}
} else {
- searchResult.Referrals = append(searchResult.Referrals, referral)
+ result.Referrals = append(result.Referrals, referral)
}
}
}
- if !p.config.PermitReferrals || len(searchResult.Referrals) == 0 {
+ if !p.config.PermitReferrals || len(result.Referrals) == 0 {
if err != nil {
return nil, err
}
- return searchResult, nil
+ return result, nil
}
- if err = p.searchReferrals(searchRequest, searchResult); err != nil {
+ if err = p.searchReferrals(request, result); err != nil {
return nil, err
}
- return searchResult, nil
+ return result, nil
}
-func (p *LDAPUserProvider) searchReferral(referral string, searchRequest *ldap.SearchRequest, searchResult *ldap.SearchResult) (err error) {
+func (p *LDAPUserProvider) searchReferral(referral string, request *ldap.SearchRequest, searchResult *ldap.SearchResult) (err error) {
var (
client LDAPClient
result *ldap.SearchResult
@@ -294,7 +297,7 @@ func (p *LDAPUserProvider) searchReferral(referral string, searchRequest *ldap.S
defer client.Close()
- if result, err = client.Search(searchRequest); err != nil {
+ if result, err = client.Search(request); err != nil {
return fmt.Errorf("error occurred performing search on referred LDAP server '%s': %w", referral, err)
}
@@ -307,9 +310,9 @@ func (p *LDAPUserProvider) searchReferral(referral string, searchRequest *ldap.S
return nil
}
-func (p *LDAPUserProvider) searchReferrals(searchRequest *ldap.SearchRequest, searchResult *ldap.SearchResult) (err error) {
- for i := 0; i < len(searchResult.Referrals); i++ {
- if err = p.searchReferral(searchResult.Referrals[i], searchRequest, searchResult); err != nil {
+func (p *LDAPUserProvider) searchReferrals(request *ldap.SearchRequest, result *ldap.SearchResult) (err error) {
+ for i := 0; i < len(result.Referrals); i++ {
+ if err = p.searchReferral(result.Referrals[i], request, result); err != nil {
return err
}
}
@@ -318,33 +321,39 @@ func (p *LDAPUserProvider) searchReferrals(searchRequest *ldap.SearchRequest, se
}
func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (profile *ldapUserProfile, err error) {
- userFilter := p.resolveUsersFilter(username)
-
// Search for the given username.
- searchRequest := ldap.NewSearchRequest(
+ request := ldap.NewSearchRequest(
p.usersBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
- 1, 0, false, userFilter, p.usersAttributes, nil,
+ 1, 0, false, p.resolveUsersFilter(username), p.usersAttributes, nil,
)
- var searchResult *ldap.SearchResult
+ p.log.
+ WithField("base_dn", request.BaseDN).
+ WithField("filter", request.Filter).
+ WithField("attr", request.Attributes).
+ WithField("scope", request.Scope).
+ WithField("deref", request.DerefAliases).
+ Trace("Performing user search")
- if searchResult, err = p.search(client, searchRequest); err != nil {
+ var result *ldap.SearchResult
+
+ if result, err = p.search(client, request); err != nil {
return nil, fmt.Errorf("cannot find user DN of user '%s'. Cause: %w", username, err)
}
- if len(searchResult.Entries) == 0 {
+ if len(result.Entries) == 0 {
return nil, ErrUserNotFound
}
- if len(searchResult.Entries) > 1 {
- return nil, fmt.Errorf("there were %d users found when searching for '%s' but there should only be 1", len(searchResult.Entries), username)
+ if len(result.Entries) > 1 {
+ return nil, fmt.Errorf("there were %d users found when searching for '%s' but there should only be 1", len(result.Entries), username)
}
userProfile := ldapUserProfile{
- DN: searchResult.Entries[0].DN,
+ DN: result.Entries[0].DN,
}
- for _, attr := range searchResult.Entries[0].Attributes {
+ for _, attr := range result.Entries[0].Attributes {
attrs := len(attr.Values)
if attr.Name == p.config.UsernameAttribute {
@@ -398,7 +407,7 @@ func (p *LDAPUserProvider) resolveUsersFilter(username string) (filter string) {
return filter
}
-func (p *LDAPUserProvider) resolveGroupsFilter(username string, profile *ldapUserProfile) (filter string, err error) { //nolint:unparam
+func (p *LDAPUserProvider) resolveGroupsFilter(username string, profile *ldapUserProfile) (filter string) {
filter = p.config.GroupsFilter
if p.groupsFilterReplacementInput {
@@ -418,7 +427,7 @@ func (p *LDAPUserProvider) resolveGroupsFilter(username string, profile *ldapUse
p.log.Tracef("Computed groups filter is %s", filter)
- return filter, nil
+ return filter
}
func (p *LDAPUserProvider) modify(client LDAPClient, modifyRequest *ldap.ModifyRequest) (err error) {
diff --git a/internal/authentication/ldap_user_provider_startup.go b/internal/authentication/ldap_user_provider_startup.go
index bcf1bad61..a3a2e760a 100644
--- a/internal/authentication/ldap_user_provider_startup.go
+++ b/internal/authentication/ldap_user_provider_startup.go
@@ -1,6 +1,7 @@
package authentication
import (
+ "fmt"
"strings"
"github.com/go-ldap/ldap/v3"
@@ -46,18 +47,24 @@ func (p *LDAPUserProvider) StartupCheck() (err error) {
func (p *LDAPUserProvider) getServerSupportedFeatures(client LDAPClient) (features LDAPSupportedFeatures, err error) {
var (
- searchRequest *ldap.SearchRequest
- searchResult *ldap.SearchResult
+ request *ldap.SearchRequest
+ result *ldap.SearchResult
)
- searchRequest = ldap.NewSearchRequest("", ldap.ScopeBaseObject, ldap.NeverDerefAliases,
- 1, 0, false, "(objectClass=*)", []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute}, nil)
+ request = ldap.NewSearchRequest("", ldap.ScopeBaseObject, ldap.NeverDerefAliases,
+ 1, 0, false, ldapBaseObjectFilter, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute}, nil)
- if searchResult, err = client.Search(searchRequest); err != nil {
- return features, err
+ if result, err = client.Search(request); err != nil {
+ if p.config.PermitFeatureDetectionFailure {
+ p.log.WithError(err).Warnf("Error occurred during RootDSE search. This may result in reduced functionality.")
+
+ return features, nil
+ }
+
+ return features, fmt.Errorf("error occurred during RootDSE search: %w", err)
}
- if len(searchResult.Entries) != 1 {
+ if len(result.Entries) != 1 {
p.log.Errorf("The LDAP Server did not respond appropriately to a RootDSE search. This may result in reduced functionality.")
return features, nil
@@ -65,7 +72,7 @@ func (p *LDAPUserProvider) getServerSupportedFeatures(client LDAPClient) (featur
var controlTypeOIDs, extensionOIDs []string
- controlTypeOIDs, extensionOIDs, features = ldapGetFeatureSupportFromEntry(searchResult.Entries[0])
+ controlTypeOIDs, extensionOIDs, features = ldapGetFeatureSupportFromEntry(result.Entries[0])
controlTypes, extensions := none, none
@@ -123,7 +130,7 @@ func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
}
if p.config.AdditionalGroupsDN != "" {
- p.groupsBaseDN = ldap.EscapeFilter(p.config.AdditionalGroupsDN + "," + p.config.BaseDN)
+ p.groupsBaseDN = p.config.AdditionalGroupsDN + "," + p.config.BaseDN
} else {
p.groupsBaseDN = p.config.BaseDN
}
diff --git a/internal/authentication/ldap_user_provider_test.go b/internal/authentication/ldap_user_provider_test.go
index a63cb1091..d28d05e9c 100644
--- a/internal/authentication/ldap_user_provider_test.go
+++ b/internal/authentication/ldap_user_provider_test.go
@@ -23,7 +23,7 @@ func TestShouldCreateRawConnectionWhenSchemeIsLDAP(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -55,7 +55,7 @@ func TestShouldCreateTLSConnectionWhenSchemeIsLDAPS(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldaps://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -105,7 +105,7 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) {
mockFactory := NewMockLDAPClientFactory(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldaps://127.0.0.1:389",
GroupsFilter: "(|(member={dn})(uid={username})(uid={input}))",
},
@@ -120,10 +120,10 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) {
Emails: []string{"john.doe@authelia.com"},
}
- filter, _ := ldapClient.resolveGroupsFilter("john", &profile)
+ filter := ldapClient.resolveGroupsFilter("john", &profile)
assert.Equal(t, "(|(member=cn=john \\28external\\29,dc=example,dc=com)(uid=john)(uid=john))", filter)
- filter, _ = ldapClient.resolveGroupsFilter("john#=(abc,def)", &profile)
+ filter = ldapClient.resolveGroupsFilter("john#=(abc,def)", &profile)
assert.Equal(t, "(|(member=cn=john \\28external\\29,dc=example,dc=com)(uid=john)(uid=john\\#\\=\\28abc\\,def\\29))", filter)
}
@@ -140,7 +140,7 @@ func NewExtendedSearchRequestMatcher(filter, base string, scope, derefAliases in
return &ExtendedSearchRequestMatcher{filter, base, scope, derefAliases, typesOnly, attributes}
}
-func (e *ExtendedSearchRequestMatcher) Matches(x interface{}) bool {
+func (e *ExtendedSearchRequestMatcher) Matches(x any) bool {
sr := x.(*ldap.SearchRequest)
if e.filter != sr.Filter || e.baseDN != sr.BaseDN || e.scope != sr.Scope || e.derefAliases != sr.DerefAliases ||
@@ -163,7 +163,7 @@ func TestShouldCheckLDAPServerExtensions(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
@@ -228,7 +228,7 @@ func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntry(t
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
@@ -294,7 +294,7 @@ func TestShouldCheckLDAPServerControlTypes(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
@@ -359,7 +359,7 @@ func TestShouldNotEnablePasswdModifyExtensionOrControlTypes(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
@@ -424,7 +424,7 @@ func TestShouldReturnCheckServerConnectError(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
@@ -457,7 +457,7 @@ func TestShouldReturnCheckServerSearchError(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
@@ -489,7 +489,7 @@ func TestShouldReturnCheckServerSearchError(t *testing.T) {
gomock.InOrder(dialURL, connBind, searchOIDs, connClose)
err := ldapClient.StartupCheck()
- assert.EqualError(t, err, "could not perform the search")
+ assert.EqualError(t, err, "error occurred during RootDSE search: could not perform the search")
assert.False(t, ldapClient.features.Extensions.PwdModifyExOp)
}
@@ -502,7 +502,7 @@ func NewSearchRequestMatcher(expected string) *SearchRequestMatcher {
return &SearchRequestMatcher{expected}
}
-func (srm *SearchRequestMatcher) Matches(x interface{}) bool {
+func (srm *SearchRequestMatcher) Matches(x any) bool {
sr := x.(*ldap.SearchRequest)
return sr.Filter == srm.expected
}
@@ -519,7 +519,7 @@ func TestShouldEscapeUserInput(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
@@ -553,7 +553,7 @@ func TestShouldReturnEmailWhenAttributeSameAsUsername(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -624,7 +624,7 @@ func TestShouldReturnUsernameAndBlankDisplayNameWhenAttributesTheSame(t *testing
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -695,7 +695,7 @@ func TestShouldReturnBlankEmailAndDisplayNameWhenAttrsLenZero(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -769,7 +769,7 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsernameAttribute: "uid",
@@ -820,7 +820,7 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -893,7 +893,7 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -954,7 +954,7 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -1027,7 +1027,7 @@ func TestShouldReturnUsernameFromLDAPWithReferrals(t *testing.T) {
mockClientReferral := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -1119,7 +1119,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndResult(t *testing.T)
mockClientReferralAlt := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -1244,7 +1244,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsErr(t *testing.T) {
mockClientReferral := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -1331,7 +1331,7 @@ func TestShouldNotUpdateUserPasswordConnect(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -1398,7 +1398,7 @@ func TestShouldNotUpdateUserPasswordGetDetails(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -1475,7 +1475,7 @@ func TestShouldUpdateUserPassword(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -1582,7 +1582,7 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "activedirectory",
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
@@ -1692,7 +1692,7 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {
mockClientReferral := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "activedirectory",
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
@@ -1820,7 +1820,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "activedirectory",
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
@@ -1939,7 +1939,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi
mockClientReferral := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "activedirectory",
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
@@ -2071,7 +2071,7 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "activedirectory",
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
@@ -2185,7 +2185,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -2292,7 +2292,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferrals(t *testing.T
mockClientReferral := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -2419,7 +2419,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithoutReferrals(t *testin
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -2532,7 +2532,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralConne
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -2650,7 +2650,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralPassw
mockClientReferral := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -2781,7 +2781,7 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHints(t *testing
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "activedirectory",
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
@@ -2892,7 +2892,7 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHintsDeprecated(
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "activedirectory",
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
@@ -3003,7 +3003,7 @@ func TestShouldUpdateUserPasswordActiveDirectory(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "activedirectory",
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
@@ -3114,7 +3114,7 @@ func TestShouldUpdateUserPasswordBasic(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
Implementation: "custom",
URL: "ldap://127.0.0.1:389",
User: "uid=admin,dc=example,dc=com",
@@ -3222,7 +3222,7 @@ func TestShouldReturnErrorWhenMultipleUsernameAttributes(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3288,7 +3288,7 @@ func TestShouldReturnErrorWhenZeroUsernameAttributes(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3354,7 +3354,7 @@ func TestShouldReturnErrorWhenUsernameAttributeNotReturned(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3416,7 +3416,7 @@ func TestShouldReturnErrorWhenMultipleUsersFound(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3499,7 +3499,7 @@ func TestShouldReturnErrorWhenNoDN(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3565,7 +3565,7 @@ func TestShouldCheckValidUserPassword(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3633,7 +3633,7 @@ func TestShouldNotCheckValidUserPasswordWithConnectError(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3672,7 +3672,7 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3740,7 +3740,7 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3815,7 +3815,7 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {
mockFactory := NewMockLDAPClientFactory(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3853,7 +3853,7 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
@@ -3936,7 +3936,7 @@ func TestShouldReturnLDAPSAlreadySecuredWhenStartTLSAttempted(t *testing.T) {
mockClient := NewMockLDAPClient(ctrl)
ldapClient := newLDAPUserProvider(
- schema.LDAPAuthenticationBackendConfiguration{
+ schema.LDAPAuthenticationBackend{
URL: "ldaps://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
diff --git a/internal/authentication/password_hash.go b/internal/authentication/password_hash.go
deleted file mode 100644
index 31f78366f..000000000
--- a/internal/authentication/password_hash.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package authentication
-
-import (
- "crypto/subtle"
- "errors"
- "fmt"
- "strconv"
- "strings"
-
- "github.com/simia-tech/crypt"
-
- "github.com/authelia/authelia/v4/internal/utils"
-)
-
-// PasswordHash represents all characteristics of a password hash.
-// Authelia only supports salted SHA512 or salted argon2id method, i.e., $6$ mode or $argon2id$ mode.
-type PasswordHash struct {
- Algorithm CryptAlgo
- Iterations int
- Salt string
- Key string
- KeyLength int
- Memory int
- Parallelism int
-}
-
-// ConfigAlgoToCryptoAlgo returns a CryptAlgo and nil error if valid, otherwise it returns argon2id and an error.
-func ConfigAlgoToCryptoAlgo(fromConfig string) (CryptAlgo, error) {
- switch fromConfig {
- case argon2id:
- return HashingAlgorithmArgon2id, nil
- case sha512:
- return HashingAlgorithmSHA512, nil
- default:
- return HashingAlgorithmArgon2id, errors.New("Invalid algorithm in configuration. It should be `argon2id` or `sha512`")
- }
-}
-
-// ParseHash extracts all characteristics of a hash given its string representation.
-func ParseHash(hash string) (passwordHash *PasswordHash, err error) {
- parts := strings.Split(hash, "$")
-
- // This error can be ignored as it's always nil.
- c, parameters, salt, key, _ := crypt.DecodeSettings(hash)
- code := CryptAlgo(c)
- h := &PasswordHash{}
-
- h.Salt = salt
- h.Key = key
-
- if h.Key != parts[len(parts)-1] {
- return nil, fmt.Errorf("Hash key is not the last parameter, the hash is likely malformed (%s)", hash)
- }
-
- if h.Key == "" {
- return nil, fmt.Errorf("Hash key contains no characters or the field length is invalid (%s)", hash)
- }
-
- switch code {
- case HashingAlgorithmSHA512:
- h.Iterations = parameters.GetInt("rounds", HashingDefaultSHA512Iterations)
- h.Algorithm = HashingAlgorithmSHA512
-
- if parameters["rounds"] != "" && parameters["rounds"] != strconv.Itoa(h.Iterations) {
- return nil, fmt.Errorf("SHA512 iterations is not numeric (%s)", parameters["rounds"])
- }
- case HashingAlgorithmArgon2id:
- _, err = crypt.Base64Encoding.DecodeString(h.Salt)
- if err != nil {
- return nil, errors.New("Salt contains invalid base64 characters")
- }
-
- version := parameters.GetInt("v", 0)
- if version < 19 {
- if version == 0 {
- return nil, fmt.Errorf("Argon2id version parameter not found (%s)", hash)
- }
-
- return nil, fmt.Errorf("Argon2id versions less than v19 are not supported (hash is version %d)", version)
- } else if version > 19 {
- return nil, fmt.Errorf("Argon2id versions greater than v19 are not supported (hash is version %d)", version)
- }
-
- h.Algorithm = HashingAlgorithmArgon2id
- h.Memory = parameters.GetInt("m", HashingDefaultArgon2idMemory)
- h.Iterations = parameters.GetInt("t", HashingDefaultArgon2idTime)
- h.Parallelism = parameters.GetInt("p", HashingDefaultArgon2idParallelism)
- h.KeyLength = parameters.GetInt("k", HashingDefaultArgon2idKeyLength)
-
- decodedKey, err := crypt.Base64Encoding.DecodeString(h.Key)
-
- if err != nil {
- return nil, errors.New("Hash key contains invalid base64 characters")
- }
-
- if len(decodedKey) != h.KeyLength {
- return nil, fmt.Errorf("Argon2id key length parameter (%d) does not match the actual key length (%d)", h.KeyLength, len(decodedKey))
- }
- default:
- return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$) and salted argon2id ($argon2id$), not $%s$", code)
- }
-
- return h, nil
-}
-
-// HashPassword generate a salt and hash the password with the salt and a constant number of rounds.
-func HashPassword(password, salt string, algorithm CryptAlgo, iterations, memory, parallelism, keyLength, saltLength int) (hash string, err error) {
- var settings string
-
- if algorithm != HashingAlgorithmArgon2id && algorithm != HashingAlgorithmSHA512 {
- return "", fmt.Errorf("Hashing algorithm input of '%s' is invalid, only values of %s and %s are supported", algorithm, HashingAlgorithmArgon2id, HashingAlgorithmSHA512)
- }
-
- if algorithm == HashingAlgorithmArgon2id {
- err := validateArgon2idSettings(memory, parallelism, iterations, keyLength)
- if err != nil {
- return "", err
- }
- }
-
- if algorithm != HashingAlgorithmSHA512 {
- err = validateSalt(salt, saltLength)
- if err != nil {
- return "", err
- }
- }
-
- if salt == "" {
- salt = crypt.Base64Encoding.EncodeToString(utils.RandomBytes(saltLength, HashingPossibleSaltCharacters, true))
- }
-
- settings = getCryptSettings(salt, algorithm, iterations, memory, parallelism, keyLength)
-
- // This error can be ignored because we check for it before a user gets here.
- hash, _ = crypt.Crypt(password, settings)
-
- return hash, nil
-}
-
-// CheckPassword check a password against a hash.
-func CheckPassword(password, hash string) (ok bool, err error) {
- expectedHash, err := ParseHash(hash)
- if err != nil {
- return false, err
- }
-
- passwordHashString, err := HashPassword(password, expectedHash.Salt, expectedHash.Algorithm, expectedHash.Iterations, expectedHash.Memory, expectedHash.Parallelism, expectedHash.KeyLength, len(expectedHash.Salt))
- if err != nil {
- return false, err
- }
-
- passwordHash, err := ParseHash(passwordHashString)
- if err != nil {
- return false, err
- }
-
- return subtle.ConstantTimeCompare([]byte(passwordHash.Key), []byte(expectedHash.Key)) == 1, nil
-}
-
-func getCryptSettings(salt string, algorithm CryptAlgo, iterations, memory, parallelism, keyLength int) (settings string) {
- switch algorithm {
- case HashingAlgorithmArgon2id:
- settings, _ = crypt.Argon2idSettings(memory, iterations, parallelism, keyLength, salt)
- case HashingAlgorithmSHA512:
- settings = fmt.Sprintf("$6$rounds=%d$%s", iterations, salt)
- default:
- panic("invalid password hashing algorithm provided")
- }
-
- return settings
-}
-
-// validateSalt checks the salt input and settings are valid and returns it and a nil error if they are, otherwise returns an error.
-func validateSalt(salt string, saltLength int) error {
- if salt == "" {
- if saltLength < 8 {
- return fmt.Errorf("Salt length input of %d is invalid, it must be 8 or higher", saltLength)
- }
-
- return nil
- }
-
- decodedSalt, err := crypt.Base64Encoding.DecodeString(salt)
- if err != nil {
- return fmt.Errorf("Salt input of %s is invalid, only base64 strings are valid for input", salt)
- }
-
- if len(decodedSalt) < 8 {
- return fmt.Errorf("Salt input of %s is invalid (%d characters), it must be 8 or more characters", decodedSalt, len(decodedSalt))
- }
-
- return nil
-}
-
-// validateArgon2idSettings checks the argon2id settings are valid.
-func validateArgon2idSettings(memory, parallelism, iterations, keyLength int) error {
- // Caution: Increasing any of the values in the below block has a high chance in old passwords that cannot be verified.
- if memory < 8 {
- return fmt.Errorf("Memory (argon2id) input of %d is invalid, it must be 8 or higher", memory)
- }
-
- if parallelism < 1 {
- return fmt.Errorf("Parallelism (argon2id) input of %d is invalid, it must be 1 or higher", parallelism)
- }
-
- if memory < parallelism*8 {
- return fmt.Errorf("Memory (argon2id) input of %d is invalid with a parallelism input of %d, it must be %d (parallelism * 8) or higher", memory, parallelism, parallelism*8)
- }
-
- if keyLength < 16 {
- return fmt.Errorf("Key length (argon2id) input of %d is invalid, it must be 16 or higher", keyLength)
- }
-
- if iterations < 1 {
- return fmt.Errorf("Iterations (argon2id) input of %d is invalid, it must be 1 or more", iterations)
- }
-
- // Caution: Increasing any of the values in the above block has a high chance in old passwords that cannot be verified.
- return nil
-}
diff --git a/internal/authentication/password_hash_test.go b/internal/authentication/password_hash_test.go
deleted file mode 100644
index 3cd3ade7a..000000000
--- a/internal/authentication/password_hash_test.go
+++ /dev/null
@@ -1,322 +0,0 @@
-package authentication
-
-import (
- "fmt"
- "testing"
-
- "github.com/simia-tech/crypt"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/authelia/authelia/v4/internal/configuration/schema"
- "github.com/authelia/authelia/v4/internal/utils"
-)
-
-func TestShouldHashSHA512Password(t *testing.T) {
- hash, err := HashPassword("password", "aFr56HjK3DrB8t3S", HashingAlgorithmSHA512, 50000, 0, 0, 0, 16)
-
- assert.NoError(t, err)
-
- code, parameters, salt, hash, _ := crypt.DecodeSettings(hash)
-
- assert.Equal(t, "6", code)
- assert.Equal(t, "aFr56HjK3DrB8t3S", salt)
- assert.Equal(t, "zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1", hash)
- assert.Equal(t, schema.DefaultPasswordSHA512Configuration.Iterations, parameters.GetInt("rounds", HashingDefaultSHA512Iterations))
-}
-
-func TestShouldHashArgon2idPassword(t *testing.T) {
- hash, err := HashPassword("password", "BpLnfgDsc2WD8F2q", HashingAlgorithmArgon2id,
- schema.DefaultCIPasswordConfiguration.Iterations, schema.DefaultCIPasswordConfiguration.Memory*1024,
- schema.DefaultCIPasswordConfiguration.Parallelism, schema.DefaultCIPasswordConfiguration.KeyLength,
- schema.DefaultCIPasswordConfiguration.SaltLength)
-
- assert.NoError(t, err)
-
- code, parameters, salt, key, err := crypt.DecodeSettings(hash)
-
- assert.NoError(t, err)
- assert.Equal(t, argon2id, code)
- assert.Equal(t, "BpLnfgDsc2WD8F2q", salt)
- assert.Equal(t, "kYempka60N8ETZ+EedP+Fn3z83mEPMl08RQEXTwY6u0", key)
- assert.Equal(t, schema.DefaultCIPasswordConfiguration.Iterations, parameters.GetInt("t", HashingDefaultArgon2idTime))
- assert.Equal(t, schema.DefaultCIPasswordConfiguration.Memory*1024, parameters.GetInt("m", HashingDefaultArgon2idMemory))
- assert.Equal(t, schema.DefaultCIPasswordConfiguration.Parallelism, parameters.GetInt("p", HashingDefaultArgon2idParallelism))
- assert.Equal(t, schema.DefaultCIPasswordConfiguration.KeyLength, parameters.GetInt("k", HashingDefaultArgon2idKeyLength))
-}
-
-func TestShouldValidateArgon2idHashWithTEqualOne(t *testing.T) {
- hash := "$argon2id$v=19$m=1024,t=1,p=1,k=16$c2FsdG9uY2U$Sk4UjzxXdCrBcyyMYiPEsQ"
- valid, err := CheckPassword("apple", hash)
- assert.True(t, valid)
- assert.NoError(t, err)
-}
-
-// This checks the method of hashing (for argon2id) supports all the characters we allow in Authelia's hash function.
-func TestArgon2idHashSaltValidValues(t *testing.T) {
- var err error
-
- var hash string
-
- datas := utils.SliceString(HashingPossibleSaltCharacters, 16)
-
- for _, salt := range datas {
- hash, err = HashPassword("password", salt, HashingAlgorithmArgon2id, 1, 8, 1, 32, 16)
- assert.NoError(t, err)
- assert.Equal(t, fmt.Sprintf("$argon2id$v=19$m=8,t=1,p=1$%s$", salt), hash[0:44])
- }
-}
-
-// This checks the method of hashing (for sha512) supports all the characters we allow in Authelia's hash function.
-func TestSHA512HashSaltValidValues(t *testing.T) {
- var err error
-
- var hash string
-
- datas := utils.SliceString(HashingPossibleSaltCharacters, 16)
-
- for _, salt := range datas {
- hash, err = HashPassword("password", salt, HashingAlgorithmSHA512, 1000, 0, 0, 0, 16)
- assert.NoError(t, err)
- assert.Equal(t, fmt.Sprintf("$6$rounds=1000$%s$", salt), hash[0:32])
- }
-}
-
-func TestShouldNotHashPasswordWithNonExistentAlgorithm(t *testing.T) {
- hash, err := HashPassword("password", "BpLnfgDsc2WD8F2q", "bogus",
- schema.DefaultCIPasswordConfiguration.Iterations, schema.DefaultCIPasswordConfiguration.Memory*1024,
- schema.DefaultCIPasswordConfiguration.Parallelism, schema.DefaultCIPasswordConfiguration.KeyLength,
- schema.DefaultCIPasswordConfiguration.SaltLength)
-
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Hashing algorithm input of 'bogus' is invalid, only values of argon2id and 6 are supported")
-}
-
-func TestShouldNotHashArgon2idPasswordDueToMemoryParallelismMismatch(t *testing.T) {
- hash, err := HashPassword("password", "BpLnfgDsc2WD8F2q", HashingAlgorithmArgon2id,
- schema.DefaultCIPasswordConfiguration.Iterations, 8, 2,
- schema.DefaultCIPasswordConfiguration.KeyLength, schema.DefaultCIPasswordConfiguration.SaltLength)
-
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Memory (argon2id) input of 8 is invalid with a parallelism input of 2, it must be 16 (parallelism * 8) or higher")
-}
-
-func TestShouldNotHashArgon2idPasswordDueToMemoryLessThanEight(t *testing.T) {
- hash, err := HashPassword("password", "BpLnfgDsc2WD8F2q", HashingAlgorithmArgon2id,
- schema.DefaultCIPasswordConfiguration.Iterations, 1, schema.DefaultCIPasswordConfiguration.Parallelism,
- schema.DefaultCIPasswordConfiguration.KeyLength, schema.DefaultCIPasswordConfiguration.SaltLength)
-
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Memory (argon2id) input of 1 is invalid, it must be 8 or higher")
-}
-
-func TestShouldNotHashArgon2idPasswordDueToKeyLengthLessThanSixteen(t *testing.T) {
- hash, err := HashPassword("password", "BpLnfgDsc2WD8F2q", HashingAlgorithmArgon2id,
- schema.DefaultCIPasswordConfiguration.Iterations, schema.DefaultCIPasswordConfiguration.Memory*1024,
- schema.DefaultCIPasswordConfiguration.Parallelism, 5, schema.DefaultCIPasswordConfiguration.SaltLength)
-
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Key length (argon2id) input of 5 is invalid, it must be 16 or higher")
-}
-
-func TestShouldNotHashArgon2idPasswordDueParallelismLessThanOne(t *testing.T) {
- hash, err := HashPassword("password", "BpLnfgDsc2WD8F2q", HashingAlgorithmArgon2id,
- schema.DefaultCIPasswordConfiguration.Iterations, schema.DefaultCIPasswordConfiguration.Memory*1024, -1,
- schema.DefaultCIPasswordConfiguration.KeyLength, schema.DefaultCIPasswordConfiguration.SaltLength)
-
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Parallelism (argon2id) input of -1 is invalid, it must be 1 or higher")
-}
-
-func TestShouldNotHashArgon2idPasswordDueIterationsLessThanOne(t *testing.T) {
- hash, err := HashPassword("password", "BpLnfgDsc2WD8F2q", HashingAlgorithmArgon2id,
- 0, schema.DefaultCIPasswordConfiguration.Memory*1024, schema.DefaultCIPasswordConfiguration.Parallelism,
- schema.DefaultCIPasswordConfiguration.KeyLength, schema.DefaultCIPasswordConfiguration.SaltLength)
-
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Iterations (argon2id) input of 0 is invalid, it must be 1 or more")
-}
-
-func TestShouldNotHashPasswordDueToSaltLength(t *testing.T) {
- hash, err := HashPassword("password", "", HashingAlgorithmArgon2id,
- schema.DefaultCIPasswordConfiguration.Iterations, schema.DefaultCIPasswordConfiguration.Memory*1024,
- schema.DefaultCIPasswordConfiguration.Parallelism, schema.DefaultCIPasswordConfiguration.KeyLength, 0)
-
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Salt length input of 0 is invalid, it must be 8 or higher")
-}
-
-func TestShouldNotHashPasswordDueToSaltCharLengthTooShort(t *testing.T) {
- // The salt 'YQ' is the base64 value for 'a' which is why the length is 1.
- hash, err := HashPassword("password", "YQ", HashingAlgorithmArgon2id,
- schema.DefaultCIPasswordConfiguration.Iterations, schema.DefaultCIPasswordConfiguration.Memory*1024,
- schema.DefaultCIPasswordConfiguration.Parallelism, schema.DefaultCIPasswordConfiguration.KeyLength,
- schema.DefaultCIPasswordConfiguration.SaltLength)
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Salt input of a is invalid (1 characters), it must be 8 or more characters")
-}
-
-func TestShouldNotHashPasswordWithNonBase64CharsInSalt(t *testing.T) {
- hash, err := HashPassword("password", "abc&123", HashingAlgorithmArgon2id,
- schema.DefaultCIPasswordConfiguration.Iterations, schema.DefaultCIPasswordConfiguration.Memory*1024,
- schema.DefaultCIPasswordConfiguration.Parallelism, schema.DefaultCIPasswordConfiguration.KeyLength,
- schema.DefaultCIPasswordConfiguration.SaltLength)
- assert.Equal(t, "", hash)
- assert.EqualError(t, err, "Salt input of abc&123 is invalid, only base64 strings are valid for input")
-}
-
-func TestShouldNotParseHashWithNoneBase64CharsInKey(t *testing.T) {
- passwordHash, err := ParseHash("$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
- assert.EqualError(t, err, "Hash key contains invalid base64 characters")
- assert.Nil(t, passwordHash)
-}
-
-func TestShouldNotParseHashWithNoneBase64CharsInSalt(t *testing.T) {
- passwordHash, err := ParseHash("$argon2id$v=19$m=65536$^^wTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY")
- assert.EqualError(t, err, "Salt contains invalid base64 characters")
- assert.Nil(t, passwordHash)
-}
-
-func TestShouldNotParseWithMalformedHash(t *testing.T) {
- hashExtraField := "$argon2id$v=19$m=65536,t=3,p=2$abc$BpLnfgDsc2WD8F2q$^^vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
- hashMissingSaltAndParams := "$argon2id$v=1$2t9X8nNCN2n3/kFYJ3xWNBg5k/rO782Qr7JJoJIK7G4"
- hashMissingSalt := "$argon2id$v=1$m=65536,t=3,p=2$2t9X8nNCN2n3/kFYJ3xWNBg5k/rO782Qr7JJoJIK7G4"
-
- passwordHash, err := ParseHash(hashExtraField)
- assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashExtraField))
- assert.Nil(t, passwordHash)
-
- passwordHash, err = ParseHash(hashMissingSaltAndParams)
- assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashMissingSaltAndParams))
- assert.Nil(t, passwordHash)
-
- passwordHash, err = ParseHash(hashMissingSalt)
- assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hashMissingSalt))
- assert.Nil(t, passwordHash)
-}
-
-func TestShouldNotParseHashWithEmptyKey(t *testing.T) {
- hash := "$argon2id$v=19$m=65536$fvwTFoFjITudo57a$"
- passwordHash, err := ParseHash(hash)
- assert.EqualError(t, err, fmt.Sprintf("Hash key contains no characters or the field length is invalid (%s)", hash))
- assert.Nil(t, passwordHash)
-}
-
-func TestShouldNotParseArgon2idHashWithEmptyVersion(t *testing.T) {
- hash := "$argon2id$m=65536$fvwTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY"
- passwordHash, err := ParseHash(hash)
- assert.EqualError(t, err, fmt.Sprintf("Argon2id version parameter not found (%s)", hash))
- assert.Nil(t, passwordHash)
-}
-
-func TestShouldNotParseArgon2idHashWithWrongKeyLength(t *testing.T) {
- hash := "$argon2id$v=19$m=65536,k=50$fvwTFoFjITudo57a$Z4NH/EKkdv6PJ01Ye1twJ61fsmRJujZZn1IXdUOyrJY"
- passwordHash, err := ParseHash(hash)
- assert.EqualError(t, err, "Argon2id key length parameter (50) does not match the actual key length (32)")
- assert.Nil(t, passwordHash)
-}
-
-func TestShouldParseArgon2idHash(t *testing.T) {
- passwordHash, err := ParseHash("$argon2id$v=19$m=65536,t=3,p=4$NEwwcVNuQWlQMFpkMndxdg$LlHjiLxPB94pdmOiNwr7Bgy+uy3huSv6y9phCQ+mLls")
- assert.NoError(t, err)
- assert.Equal(t, schema.DefaultCIPasswordConfiguration.Iterations, passwordHash.Iterations)
- assert.Equal(t, schema.DefaultCIPasswordConfiguration.Parallelism, passwordHash.Parallelism)
- assert.Equal(t, schema.DefaultCIPasswordConfiguration.KeyLength, passwordHash.KeyLength)
- assert.Equal(t, schema.DefaultCIPasswordConfiguration.Memory*1024, passwordHash.Memory)
-}
-
-func TestShouldCheckSHA512Password(t *testing.T) {
- ok, err := CheckPassword("password", "$6$rounds=50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
- assert.NoError(t, err)
- assert.True(t, ok)
-}
-
-func TestShouldCheckArgon2idPassword(t *testing.T) {
- ok, err := CheckPassword("password", "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
- assert.NoError(t, err)
- assert.True(t, ok)
-}
-
-func TestCannotParseSHA512Hash(t *testing.T) {
- ok, err := CheckPassword("password", "$6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
-
- assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($6$roSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1)")
- assert.False(t, ok)
-}
-
-func TestCannotParseArgon2idHash(t *testing.T) {
- ok, err := CheckPassword("password", "$argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
-
- assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)")
- assert.False(t, ok)
-}
-
-func TestOnlySupportSHA512AndArgon2id(t *testing.T) {
- ok, err := CheckPassword("password", "$8$rounds=50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
-
- assert.EqualError(t, err, "Authelia only supports salted SHA512 hashing ($6$) and salted argon2id ($argon2id$), not $8$")
- assert.False(t, ok)
-}
-
-func TestCannotFindNumberOfRounds(t *testing.T) {
- hash := "$6$rounds50000$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1"
- ok, err := CheckPassword("password", hash)
-
- assert.EqualError(t, err, fmt.Sprintf("Hash key is not the last parameter, the hash is likely malformed (%s)", hash))
- assert.False(t, ok)
-}
-
-func TestCannotMatchArgon2idParamPattern(t *testing.T) {
- ok, err := CheckPassword("password", "$argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
-
- assert.EqualError(t, err, "Hash key is not the last parameter, the hash is likely malformed ($argon2id$v=19$m65536,t3,p2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM)")
- assert.False(t, ok)
-}
-
-func TestArgon2idVersionLessThanSupported(t *testing.T) {
- ok, err := CheckPassword("password", "$argon2id$v=18$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
-
- assert.EqualError(t, err, "Argon2id versions less than v19 are not supported (hash is version 18)")
- assert.False(t, ok)
-}
-
-func TestArgon2idVersionGreaterThanSupported(t *testing.T) {
- ok, err := CheckPassword("password", "$argon2id$v=20$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM")
-
- assert.EqualError(t, err, "Argon2id versions greater than v19 are not supported (hash is version 20)")
- assert.False(t, ok)
-}
-
-func TestNumberOfRoundsNotInt(t *testing.T) {
- ok, err := CheckPassword("password", "$6$rounds=abc$aFr56HjK3DrB8t3S$zhPQiS85cgBlNhUKKE6n/AHMlpqrvYSnSL3fEVkK0yHFQ.oFFAd8D4OhPAy18K5U61Z2eBhxQXExGU/eknXlY1")
-
- assert.EqualError(t, err, "SHA512 iterations is not numeric (abc)")
- assert.False(t, ok)
-}
-
-func TestShouldCheckPasswordArgon2idHashedWithAuthelia(t *testing.T) {
- password := testPassword
- hash, err := HashPassword(password, "", HashingAlgorithmArgon2id, schema.DefaultCIPasswordConfiguration.Iterations,
- schema.DefaultCIPasswordConfiguration.Memory*1024, schema.DefaultCIPasswordConfiguration.Parallelism,
- schema.DefaultCIPasswordConfiguration.KeyLength, schema.DefaultCIPasswordConfiguration.SaltLength)
-
- assert.NoError(t, err)
-
- equal, err := CheckPassword(password, hash)
-
- require.NoError(t, err)
- assert.True(t, equal)
-}
-
-func TestShouldCheckPasswordSHA512HashedWithAuthelia(t *testing.T) {
- password := testPassword
- hash, err := HashPassword(password, "", HashingAlgorithmSHA512, schema.DefaultPasswordSHA512Configuration.Iterations,
- 0, 0, 0, schema.DefaultPasswordSHA512Configuration.SaltLength)
-
- assert.NoError(t, err)
-
- equal, err := CheckPassword(password, hash)
-
- require.NoError(t, err)
- assert.True(t, equal)
-}
diff --git a/internal/authentication/users_database.template.yml b/internal/authentication/users_database.template.yml
index a6a691080..9e1b2043e 100644
--- a/internal/authentication/users_database.template.yml
+++ b/internal/authentication/users_database.template.yml
@@ -8,6 +8,7 @@
users:
authelia:
+ disabled: false
displayname: "Test User"
password: "$argon2id$v=19$m=32768,t=1,p=8$eUhVT1dQa082YVk2VUhDMQ$E8QI4jHbUBt3EdsU1NFDu4Bq5jObKNx7nBKSn1EYQxk" # Password is 'authelia'
email: authelia@authelia.com
diff --git a/internal/authorization/access_control_domain.go b/internal/authorization/access_control_domain.go
index dfe72264b..463e3293d 100644
--- a/internal/authorization/access_control_domain.go
+++ b/internal/authorization/access_control_domain.go
@@ -9,7 +9,7 @@ import (
)
// NewAccessControlDomain creates a new SubjectObjectMatcher that matches the domain as a basic string.
-func NewAccessControlDomain(domain string) AccessControlDomain {
+func NewAccessControlDomain(domain string) (subjcets bool, rule AccessControlDomain) {
m := &AccessControlDomainMatcher{}
domain = strings.ToLower(domain)
@@ -19,20 +19,20 @@ func NewAccessControlDomain(domain string) AccessControlDomain {
m.Name = domain[1:]
case strings.HasPrefix(domain, "{user}"):
m.UserWildcard = true
- m.Name = domain[7:]
+ m.Name = domain[6:]
case strings.HasPrefix(domain, "{group}"):
m.GroupWildcard = true
- m.Name = domain[8:]
+ m.Name = domain[7:]
default:
m.Name = domain
}
- return AccessControlDomain{m}
+ return m.UserWildcard || m.GroupWildcard, AccessControlDomain{m}
}
// NewAccessControlDomainRegex creates a new SubjectObjectMatcher that matches the domain either in a basic way or
// dynamic User/Group subexpression group way.
-func NewAccessControlDomainRegex(pattern regexp.Regexp) AccessControlDomain {
+func NewAccessControlDomainRegex(pattern regexp.Regexp) (subjects bool, rule AccessControlDomain) {
var iuser, igroup = -1, -1
for i, group := range pattern.SubexpNames() {
@@ -45,10 +45,10 @@ func NewAccessControlDomainRegex(pattern regexp.Regexp) AccessControlDomain {
}
if iuser != -1 || igroup != -1 {
- return AccessControlDomain{RegexpGroupStringSubjectMatcher{pattern, iuser, igroup}}
+ return true, AccessControlDomain{RegexpGroupStringSubjectMatcher{pattern, iuser, igroup}}
}
- return AccessControlDomain{RegexpStringSubjectMatcher{pattern}}
+ return false, AccessControlDomain{RegexpStringSubjectMatcher{pattern}}
}
// AccessControlDomainMatcher is the basic domain matcher.
@@ -65,11 +65,19 @@ func (m AccessControlDomainMatcher) IsMatch(domain string, subject Subject) (mat
case m.Wildcard:
return strings.HasSuffix(domain, m.Name)
case m.UserWildcard:
- return domain == fmt.Sprintf("%s.%s", subject.Username, m.Name)
- case m.GroupWildcard:
- prefix, suffix := domainToPrefixSuffix(domain)
+ if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
+ return true
+ }
- return suffix == m.Name && utils.IsStringInSliceFold(prefix, subject.Groups)
+ return domain == fmt.Sprintf("%s%s", subject.Username, m.Name)
+ case m.GroupWildcard:
+ if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
+ return true
+ }
+
+ i := strings.Index(domain, ".")
+
+ return domain[i:] == m.Name && utils.IsStringInSliceFold(domain[:i], subject.Groups)
default:
return strings.EqualFold(domain, m.Name)
}
diff --git a/internal/authorization/access_control_query.go b/internal/authorization/access_control_query.go
new file mode 100644
index 000000000..e83b08736
--- /dev/null
+++ b/internal/authorization/access_control_query.go
@@ -0,0 +1,119 @@
+package authorization
+
+import (
+ "fmt"
+ "regexp"
+
+ "github.com/authelia/authelia/v4/internal/configuration/schema"
+)
+
+// NewAccessControlQuery creates a new AccessControlQuery rule type.
+func NewAccessControlQuery(config [][]schema.ACLQueryRule) (rules []AccessControlQuery) {
+ if len(config) == 0 {
+ return nil
+ }
+
+ for i := 0; i < len(config); i++ {
+ var rule []ObjectMatcher
+
+ for j := 0; j < len(config[i]); j++ {
+ subRule, err := NewAccessControlQueryObjectMatcher(config[i][j])
+ if err != nil {
+ continue
+ }
+
+ rule = append(rule, subRule)
+ }
+
+ rules = append(rules, AccessControlQuery{Rules: rule})
+ }
+
+ return rules
+}
+
+// AccessControlQuery represents an ACL query args rule.
+type AccessControlQuery struct {
+ Rules []ObjectMatcher
+}
+
+// IsMatch returns true if this rule matches the object.
+func (acq AccessControlQuery) IsMatch(object Object) (isMatch bool) {
+ for _, rule := range acq.Rules {
+ if !rule.IsMatch(object) {
+ return false
+ }
+ }
+
+ return true
+}
+
+// NewAccessControlQueryObjectMatcher creates a new ObjectMatcher rule type from a schema.ACLQueryRule.
+func NewAccessControlQueryObjectMatcher(rule schema.ACLQueryRule) (matcher ObjectMatcher, err error) {
+ switch rule.Operator {
+ case operatorPresent, operatorAbsent:
+ return &AccessControlQueryMatcherPresent{key: rule.Key, present: rule.Operator == operatorPresent}, nil
+ case operatorEqual, operatorNotEqual:
+ if value, ok := rule.Value.(string); ok {
+ return &AccessControlQueryMatcherEqual{key: rule.Key, value: value, equal: rule.Operator == operatorEqual}, nil
+ } else {
+ return nil, fmt.Errorf("rule value is not a string and is instead %T", rule.Value)
+ }
+ case operatorPattern, operatorNotPattern:
+ if pattern, ok := rule.Value.(*regexp.Regexp); ok {
+ return &AccessControlQueryMatcherPattern{key: rule.Key, pattern: pattern, match: rule.Operator == operatorPattern}, nil
+ } else {
+ return nil, fmt.Errorf("rule value is not a *regexp.Regexp and is instead %T", rule.Value)
+ }
+ default:
+ return nil, fmt.Errorf("invalid operator: %s", rule.Operator)
+ }
+}
+
+// AccessControlQueryMatcherEqual is a rule type that checks the equality of a query parameter.
+type AccessControlQueryMatcherEqual struct {
+ key, value string
+ equal bool
+}
+
+// IsMatch returns true if this rule matches the object.
+func (acl AccessControlQueryMatcherEqual) IsMatch(object Object) (isMatch bool) {
+ switch {
+ case acl.equal:
+ return object.URL.Query().Get(acl.key) == acl.value
+ default:
+ return object.URL.Query().Get(acl.key) != acl.value
+ }
+}
+
+// AccessControlQueryMatcherPresent is a rule type that checks the presence of a query parameter.
+type AccessControlQueryMatcherPresent struct {
+ key string
+ present bool
+}
+
+// IsMatch returns true if this rule matches the object.
+func (acl AccessControlQueryMatcherPresent) IsMatch(object Object) (isMatch bool) {
+ switch {
+ case acl.present:
+ return object.URL.Query().Has(acl.key)
+ default:
+ return !object.URL.Query().Has(acl.key)
+ }
+}
+
+// AccessControlQueryMatcherPattern is a rule type that checks a query parameter against regex.
+type AccessControlQueryMatcherPattern struct {
+ key string
+ pattern *regexp.Regexp
+ match bool
+}
+
+// IsMatch returns true if this rule matches the object.
+func (acl AccessControlQueryMatcherPattern) IsMatch(object Object) (isMatch bool) {
+ switch {
+ case acl.match:
+ return acl.pattern.MatchString(object.URL.Query().Get(acl.key))
+ default:
+ return !acl.pattern.MatchString(object.URL.Query().Get(acl.key))
+ }
+}
diff --git a/internal/authorization/access_control_resource.go b/internal/authorization/access_control_resource.go
index c9141f863..2c4aa9d1c 100644
--- a/internal/authorization/access_control_resource.go
+++ b/internal/authorization/access_control_resource.go
@@ -5,7 +5,7 @@ import (
)
// NewAccessControlResource creates a AccessControlResource or AccessControlResourceGroup.
-func NewAccessControlResource(pattern regexp.Regexp) AccessControlResource {
+func NewAccessControlResource(pattern regexp.Regexp) (subjects bool, rule AccessControlResource) {
var iuser, igroup = -1, -1
for i, group := range pattern.SubexpNames() {
@@ -18,10 +18,10 @@ func NewAccessControlResource(pattern regexp.Regexp) AccessControlResource {
}
if iuser != -1 || igroup != -1 {
- return AccessControlResource{RegexpGroupStringSubjectMatcher{pattern, iuser, igroup}}
+ return true, AccessControlResource{RegexpGroupStringSubjectMatcher{pattern, iuser, igroup}}
}
- return AccessControlResource{RegexpStringSubjectMatcher{pattern}}
+ return false, AccessControlResource{RegexpStringSubjectMatcher{pattern}}
}
// AccessControlResource represents an ACL resource that matches without named groups.
diff --git a/internal/authorization/access_control_rule.go b/internal/authorization/access_control_rule.go
index 077e8fe8d..9d86b80cf 100644
--- a/internal/authorization/access_control_rule.go
+++ b/internal/authorization/access_control_rule.go
@@ -20,22 +20,34 @@ func NewAccessControlRules(config schema.AccessControlConfiguration) (rules []*A
// NewAccessControlRule parses a schema ACL and generates an internal ACL.
func NewAccessControlRule(pos int, rule schema.ACLRule, networksMap map[string][]*net.IPNet, networksCacheMap map[string]*net.IPNet) *AccessControlRule {
- return &AccessControlRule{
- Position: pos,
- Domains: schemaDomainsToACL(rule.Domains, rule.DomainsRegex),
- Resources: schemaResourcesToACL(rule.Resources),
- Methods: schemaMethodsToACL(rule.Methods),
- Networks: schemaNetworksToACL(rule.Networks, networksMap, networksCacheMap),
- Subjects: schemaSubjectsToACL(rule.Subjects),
- Policy: StringToLevel(rule.Policy),
+ r := &AccessControlRule{
+ Position: pos,
+ Query: NewAccessControlQuery(rule.Query),
+ Methods: schemaMethodsToACL(rule.Methods),
+ Networks: schemaNetworksToACL(rule.Networks, networksMap, networksCacheMap),
+ Subjects: schemaSubjectsToACL(rule.Subjects),
+ Policy: StringToLevel(rule.Policy),
}
+
+ if len(r.Subjects) != 0 {
+ r.HasSubjects = true
+ }
+
+ ruleAddDomain(rule.Domains, r)
+ ruleAddDomainRegex(rule.DomainsRegex, r)
+ ruleAddResources(rule.Resources, r)
+
+ return r
}
// AccessControlRule controls and represents an ACL internally.
type AccessControlRule struct {
+ HasSubjects bool
+
Position int
Domains []AccessControlDomain
Resources []AccessControlResource
+ Query []AccessControlQuery
Methods []string
Networks []*net.IPNet
Subjects []AccessControlSubjects
@@ -44,37 +56,42 @@ type AccessControlRule struct {
// IsMatch returns true if all elements of an AccessControlRule match the object and subject.
func (acr *AccessControlRule) IsMatch(subject Subject, object Object) (match bool) {
- if !isMatchForDomains(subject, object, acr) {
+ if !acr.MatchesDomains(subject, object) {
return false
}
- if !isMatchForResources(subject, object, acr) {
+ if !acr.MatchesResources(subject, object) {
return false
}
- if !isMatchForMethods(object, acr) {
+ if !acr.MatchesQuery(object) {
return false
}
- if !isMatchForNetworks(subject, acr) {
+ if !acr.MatchesMethods(object) {
return false
}
- if !isMatchForSubjects(subject, acr) {
+ if !acr.MatchesNetworks(subject) {
+ return false
+ }
+
+ if !acr.MatchesSubjects(subject) {
return false
}
return true
}
-func isMatchForDomains(subject Subject, object Object, acl *AccessControlRule) (match bool) {
+// MatchesDomains returns true if the rule matches the domains.
+func (acr *AccessControlRule) MatchesDomains(subject Subject, object Object) (matches bool) {
// If there are no domains in this rule then the domain condition is a match.
- if len(acl.Domains) == 0 {
+ if len(acr.Domains) == 0 {
return true
}
// Iterate over the domains until we find a match (return true) or until we exit the loop (return false).
- for _, domain := range acl.Domains {
+ for _, domain := range acr.Domains {
if domain.IsMatch(subject, object) {
return true
}
@@ -83,14 +100,15 @@ func isMatchForDomains(subject Subject, object Object, acl *AccessControlRule) (
return false
}
-func isMatchForResources(subject Subject, object Object, acl *AccessControlRule) (match bool) {
+// MatchesResources returns true if the rule matches the resources.
+func (acr *AccessControlRule) MatchesResources(subject Subject, object Object) (matches bool) {
// If there are no resources in this rule then the resource condition is a match.
- if len(acl.Resources) == 0 {
+ if len(acr.Resources) == 0 {
return true
}
// Iterate over the resources until we find a match (return true) or until we exit the loop (return false).
- for _, resource := range acl.Resources {
+ for _, resource := range acr.Resources {
if resource.IsMatch(subject, object) {
return true
}
@@ -99,23 +117,42 @@ func isMatchForResources(subject Subject, object Object, acl *AccessControlRule)
return false
}
-func isMatchForMethods(object Object, acl *AccessControlRule) (match bool) {
- // If there are no methods in this rule then the method condition is a match.
- if len(acl.Methods) == 0 {
+// MatchesQuery returns true if the rule matches the query arguments.
+func (acr *AccessControlRule) MatchesQuery(object Object) (match bool) {
+ // If there are no query rules in this rule then the query condition is a match.
+ if len(acr.Query) == 0 {
return true
}
- return utils.IsStringInSlice(object.Method, acl.Methods)
+ // Iterate over the queries until we find a match (return true) or until we exit the loop (return false).
+ for _, query := range acr.Query {
+ if query.IsMatch(object) {
+ return true
+ }
+ }
+
+ return false
}
-func isMatchForNetworks(subject Subject, acl *AccessControlRule) (match bool) {
+// MatchesMethods returns true if the rule matches the method.
+func (acr *AccessControlRule) MatchesMethods(object Object) (match bool) {
+ // If there are no methods in this rule then the method condition is a match.
+ if len(acr.Methods) == 0 {
+ return true
+ }
+
+ return utils.IsStringInSlice(object.Method, acr.Methods)
+}
+
+// MatchesNetworks returns true if the rule matches the networks.
+func (acr *AccessControlRule) MatchesNetworks(subject Subject) (match bool) {
// If there are no networks in this rule then the network condition is a match.
- if len(acl.Networks) == 0 {
+ if len(acr.Networks) == 0 {
return true
}
// Iterate over the networks until we find a match (return true) or until we exit the loop (return false).
- for _, network := range acl.Networks {
+ for _, network := range acr.Networks {
if network.Contains(subject.IP) {
return true
}
@@ -124,25 +161,26 @@ func isMatchForNetworks(subject Subject, acl *AccessControlRule) (match bool) {
return false
}
-// Same as isExactMatchForSubjects except it theoretically matches if subject is anonymous since they'd need to authenticate.
-func isMatchForSubjects(subject Subject, acl *AccessControlRule) (match bool) {
+// MatchesSubjects returns true if the rule matches the subjects.
+func (acr *AccessControlRule) MatchesSubjects(subject Subject) (match bool) {
if subject.IsAnonymous() {
return true
}
- return isExactMatchForSubjects(subject, acl)
+ return acr.MatchesSubjectExact(subject)
}
-func isExactMatchForSubjects(subject Subject, acl *AccessControlRule) (match bool) {
+// MatchesSubjectExact returns true if the rule matches the subjects exactly.
+func (acr *AccessControlRule) MatchesSubjectExact(subject Subject) (match bool) {
// If there are no subjects in this rule then the subject condition is a match.
- if len(acl.Subjects) == 0 {
+ if len(acr.Subjects) == 0 {
return true
} else if subject.IsAnonymous() {
return false
}
// Iterate over the subjects until we find a match (return true) or until we exit the loop (return false).
- for _, subjectRule := range acl.Subjects {
+ for _, subjectRule := range acr.Subjects {
if subjectRule.IsMatch(subject) {
return true
}
diff --git a/internal/authorization/authorizer.go b/internal/authorization/authorizer.go
index d08ec3a5b..a3546083f 100644
--- a/internal/authorization/authorizer.go
+++ b/internal/authorization/authorizer.go
@@ -1,6 +1,8 @@
package authorization
import (
+ "github.com/sirupsen/logrus"
+
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/logging"
)
@@ -10,15 +12,17 @@ type Authorizer struct {
defaultPolicy Level
rules []*AccessControlRule
mfa bool
- configuration *schema.Configuration
+ config *schema.Configuration
+ log *logrus.Logger
}
-// NewAuthorizer create an instance of authorizer with a given access control configuration.
-func NewAuthorizer(configuration *schema.Configuration) (authorizer *Authorizer) {
+// NewAuthorizer create an instance of authorizer with a given access control config.
+func NewAuthorizer(config *schema.Configuration) (authorizer *Authorizer) {
authorizer = &Authorizer{
- defaultPolicy: StringToLevel(configuration.AccessControl.DefaultPolicy),
- rules: NewAccessControlRules(configuration.AccessControl),
- configuration: configuration,
+ defaultPolicy: StringToLevel(config.AccessControl.DefaultPolicy),
+ rules: NewAccessControlRules(config.AccessControl),
+ config: config,
+ log: logging.Logger(),
}
if authorizer.defaultPolicy == TwoFactor {
@@ -35,8 +39,8 @@ func NewAuthorizer(configuration *schema.Configuration) (authorizer *Authorizer)
}
}
- if authorizer.configuration.IdentityProviders.OIDC != nil {
- for _, client := range authorizer.configuration.IdentityProviders.OIDC.Clients {
+ if authorizer.config.IdentityProviders.OIDC != nil {
+ for _, client := range authorizer.config.IdentityProviders.OIDC.Clients {
if client.Policy == twoFactor {
authorizer.mfa = true
@@ -54,24 +58,21 @@ func (p Authorizer) IsSecondFactorEnabled() bool {
}
// GetRequiredLevel retrieve the required level of authorization to access the object.
-func (p Authorizer) GetRequiredLevel(subject Subject, object Object) (bool, Level) {
- logger := logging.Logger()
-
- logger.Debugf("Check authorization of subject %s and object %s (method %s).",
+func (p Authorizer) GetRequiredLevel(subject Subject, object Object) (hasSubjects bool, level Level) {
+ p.log.Debugf("Check authorization of subject %s and object %s (method %s).",
subject.String(), object.String(), object.Method)
for _, rule := range p.rules {
if rule.IsMatch(subject, object) {
- logger.Tracef(traceFmtACLHitMiss, "HIT", rule.Position, subject.String(), object.String(), object.Method)
+ p.log.Tracef(traceFmtACLHitMiss, "HIT", rule.Position, subject, object, object.Method)
- return len(rule.Subjects) > 0, rule.Policy
+ return rule.HasSubjects, rule.Policy
}
- logger.Tracef(traceFmtACLHitMiss, "MISS", rule.Position, subject.String(), object.String(), object.Method)
+ p.log.Tracef(traceFmtACLHitMiss, "MISS", rule.Position, subject, object, object.Method)
}
- logger.Debugf("No matching rule for subject %s and url %s... Applying default policy.",
- subject.String(), object.String())
+ p.log.Debugf("No matching rule for subject %s and url %s (method %s) applying default policy", subject, object, object.Method)
return false, p.defaultPolicy
}
@@ -87,12 +88,13 @@ func (p Authorizer) GetRuleMatchResults(subject Subject, object Object) (results
Rule: rule,
Skipped: skipped,
- MatchDomain: isMatchForDomains(subject, object, rule),
- MatchResources: isMatchForResources(subject, object, rule),
- MatchMethods: isMatchForMethods(object, rule),
- MatchNetworks: isMatchForNetworks(subject, rule),
- MatchSubjects: isMatchForSubjects(subject, rule),
- MatchSubjectsExact: isExactMatchForSubjects(subject, rule),
+ MatchDomain: rule.MatchesDomains(subject, object),
+ MatchResources: rule.MatchesResources(subject, object),
+ MatchQuery: rule.MatchesQuery(object),
+ MatchMethods: rule.MatchesMethods(object),
+ MatchNetworks: rule.MatchesNetworks(subject),
+ MatchSubjects: rule.MatchesSubjects(subject),
+ MatchSubjectsExact: rule.MatchesSubjectExact(subject),
}
skipped = skipped || results[i].IsMatch()
diff --git a/internal/authorization/authorizer_test.go b/internal/authorization/authorizer_test.go
index 264d940c2..495a40f9a 100644
--- a/internal/authorization/authorizer_test.go
+++ b/internal/authorization/authorizer_test.go
@@ -151,17 +151,17 @@ func (s *AuthorizerSuite) TestShouldCheckDynamicDomainRules() {
WithDefaultPolicy(deny).
WithRule(schema.ACLRule{
Domains: []string{"{user}.example.com"},
- Policy: bypass,
+ Policy: oneFactor,
}).
WithRule(schema.ACLRule{
Domains: []string{"{group}.example.com"},
- Policy: bypass,
+ Policy: oneFactor,
}).
Build()
- tester.CheckAuthorizations(s.T(), UserWithGroups, "https://john.example.com/", "GET", Bypass)
- tester.CheckAuthorizations(s.T(), UserWithGroups, "https://dev.example.com/", "GET", Bypass)
- tester.CheckAuthorizations(s.T(), UserWithGroups, "https://admins.example.com/", "GET", Bypass)
+ tester.CheckAuthorizations(s.T(), UserWithGroups, "https://john.example.com/", "GET", OneFactor)
+ tester.CheckAuthorizations(s.T(), UserWithGroups, "https://dev.example.com/", "GET", OneFactor)
+ tester.CheckAuthorizations(s.T(), UserWithGroups, "https://admins.example.com/", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), UserWithGroups, "https://othergroup.example.com/", "GET", Denied)
}
@@ -208,6 +208,129 @@ func (s *AuthorizerSuite) TestShouldCheckFactorsPolicy() {
tester.CheckAuthorizations(s.T(), UserWithGroups, "https://example.com/", "GET", Denied)
}
+func (s *AuthorizerSuite) TestShouldCheckQueryPolicy() {
+ tester := NewAuthorizerBuilder().
+ WithDefaultPolicy(deny).
+ WithRule(schema.ACLRule{
+ Domains: []string{"one.example.com"},
+ Query: [][]schema.ACLQueryRule{
+ {
+ {
+ Operator: operatorEqual,
+ Key: "test",
+ Value: "two",
+ },
+ {
+ Operator: operatorAbsent,
+ Key: "admin",
+ },
+ },
+ {
+ {
+ Operator: operatorPresent,
+ Key: "public",
+ },
+ },
+ },
+ Policy: oneFactor,
+ }).
+ WithRule(schema.ACLRule{
+ Domains: []string{"two.example.com"},
+ Query: [][]schema.ACLQueryRule{
+ {
+ {
+ Operator: operatorEqual,
+ Key: "test",
+ Value: "one",
+ },
+ },
+ {
+ {
+ Operator: operatorEqual,
+ Key: "test",
+ Value: "two",
+ },
+ },
+ },
+ Policy: twoFactor,
+ }).
+ WithRule(schema.ACLRule{
+ Domains: []string{"three.example.com"},
+ Query: [][]schema.ACLQueryRule{
+ {
+ {
+ Operator: operatorNotEqual,
+ Key: "test",
+ Value: "one",
+ },
+ {
+ Operator: operatorNotEqual,
+ Key: "test",
+ Value: "two",
+ },
+ },
+ },
+ Policy: twoFactor,
+ }).
+ WithRule(schema.ACLRule{
+ Domains: []string{"four.example.com"},
+ Query: [][]schema.ACLQueryRule{
+ {
+ {
+ Operator: operatorPattern,
+ Key: "test",
+ Value: regexp.MustCompile(`^(one|two|three)$`),
+ },
+ },
+ },
+ Policy: twoFactor,
+ }).
+ WithRule(schema.ACLRule{
+ Domains: []string{"five.example.com"},
+ Query: [][]schema.ACLQueryRule{
+ {
+ {
+ Operator: operatorNotPattern,
+ Key: "test",
+ Value: regexp.MustCompile(`^(one|two|three)$`),
+ },
+ },
+ },
+ Policy: twoFactor,
+ }).
+ Build()
+
+ testCases := []struct {
+ name, requestURL string
+ expected Level
+ }{
+ {"ShouldDenyAbsentRule", "https://one.example.com/?admin=true", Denied},
+ {"ShouldAllow1FAPresentRule", "https://one.example.com/?public=true", OneFactor},
+ {"ShouldAllow1FAEqualRule", "https://one.example.com/?test=two", OneFactor},
+ {"ShouldDenyAbsentRuleWithMatchingPresentRule", "https://one.example.com/?test=two&admin=true", Denied},
+ {"ShouldAllow2FARuleWithOneMatchingEqual", "https://two.example.com/?test=one&admin=true", TwoFactor},
+ {"ShouldAllow2FARuleWithAnotherMatchingEqual", "https://two.example.com/?test=two&admin=true", TwoFactor},
+ {"ShouldDenyRuleWithNotMatchingEqual", "https://two.example.com/?test=three&admin=true", Denied},
+ {"ShouldDenyRuleWithNotMatchingNotEqualAND1", "https://three.example.com/?test=one", Denied},
+ {"ShouldDenyRuleWithNotMatchingNotEqualAND2", "https://three.example.com/?test=two", Denied},
+ {"ShouldAllowRuleWithMatchingNotEqualAND", "https://three.example.com/?test=three", TwoFactor},
+ {"ShouldAllowRuleWithMatchingPatternOne", "https://four.example.com/?test=one", TwoFactor},
+ {"ShouldAllowRuleWithMatchingPatternTwo", "https://four.example.com/?test=two", TwoFactor},
+ {"ShouldAllowRuleWithMatchingPatternThree", "https://four.example.com/?test=three", TwoFactor},
+ {"ShouldDenyRuleWithNotMatchingPattern", "https://four.example.com/?test=five", Denied},
+ {"ShouldAllowRuleWithMatchingNotPattern", "https://five.example.com/?test=five", TwoFactor},
+ {"ShouldDenyRuleWithNotMatchingNotPatternOne", "https://five.example.com/?test=one", Denied},
+ {"ShouldDenyRuleWithNotMatchingNotPatternTwo", "https://five.example.com/?test=two", Denied},
+ {"ShouldDenyRuleWithNotMatchingNotPatternThree", "https://five.example.com/?test=three", Denied},
+ }
+
+ for _, tc := range testCases {
+ s.T().Run(tc.name, func(t *testing.T) {
+ tester.CheckAuthorizations(t, UserWithGroups, tc.requestURL, "GET", tc.expected)
+ })
+ }
+}
+
func (s *AuthorizerSuite) TestShouldCheckRulePrecedence() {
tester := NewAuthorizerBuilder().
WithDefaultPolicy(deny).
@@ -278,7 +401,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainMatching() {
s.Require().Len(tester.rules[0].Domains, 1)
- s.Assert().Equal("public.example.com", tester.configuration.AccessControl.Rules[0].Domains[0])
+ s.Assert().Equal("public.example.com", tester.config.AccessControl.Rules[0].Domains[0])
ruleMatcher0, ok := tester.rules[0].Domains[0].Matcher.(*AccessControlDomainMatcher)
s.Require().True(ok)
@@ -289,7 +412,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainMatching() {
s.Require().Len(tester.rules[1].Domains, 1)
- s.Assert().Equal("one-factor.example.com", tester.configuration.AccessControl.Rules[1].Domains[0])
+ s.Assert().Equal("one-factor.example.com", tester.config.AccessControl.Rules[1].Domains[0])
ruleMatcher1, ok := tester.rules[1].Domains[0].Matcher.(*AccessControlDomainMatcher)
s.Require().True(ok)
@@ -300,7 +423,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainMatching() {
s.Require().Len(tester.rules[2].Domains, 1)
- s.Assert().Equal("two-factor.example.com", tester.configuration.AccessControl.Rules[2].Domains[0])
+ s.Assert().Equal("two-factor.example.com", tester.config.AccessControl.Rules[2].Domains[0])
ruleMatcher2, ok := tester.rules[2].Domains[0].Matcher.(*AccessControlDomainMatcher)
s.Require().True(ok)
@@ -311,7 +434,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainMatching() {
s.Require().Len(tester.rules[3].Domains, 1)
- s.Assert().Equal("*.example.com", tester.configuration.AccessControl.Rules[3].Domains[0])
+ s.Assert().Equal("*.example.com", tester.config.AccessControl.Rules[3].Domains[0])
ruleMatcher3, ok := tester.rules[3].Domains[0].Matcher.(*AccessControlDomainMatcher)
s.Require().True(ok)
@@ -322,7 +445,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainMatching() {
s.Require().Len(tester.rules[4].Domains, 1)
- s.Assert().Equal("*.example.com", tester.configuration.AccessControl.Rules[4].Domains[0])
+ s.Assert().Equal("*.example.com", tester.config.AccessControl.Rules[4].Domains[0])
ruleMatcher4, ok := tester.rules[4].Domains[0].Matcher.(*AccessControlDomainMatcher)
s.Require().True(ok)
@@ -375,7 +498,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainRegexMatching() {
s.Require().Len(tester.rules[0].Domains, 1)
- s.Assert().Equal("^.*\\.example.com$", tester.configuration.AccessControl.Rules[0].DomainsRegex[0].String())
+ s.Assert().Equal("^.*\\.example.com$", tester.config.AccessControl.Rules[0].DomainsRegex[0].String())
ruleMatcher0, ok := tester.rules[0].Domains[0].Matcher.(RegexpStringSubjectMatcher)
s.Require().True(ok)
@@ -383,7 +506,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainRegexMatching() {
s.Require().Len(tester.rules[1].Domains, 1)
- s.Assert().Equal("^.*\\.example2.com$", tester.configuration.AccessControl.Rules[1].DomainsRegex[0].String())
+ s.Assert().Equal("^.*\\.example2.com$", tester.config.AccessControl.Rules[1].DomainsRegex[0].String())
ruleMatcher1, ok := tester.rules[1].Domains[0].Matcher.(RegexpStringSubjectMatcher)
s.Require().True(ok)
@@ -391,7 +514,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainRegexMatching() {
s.Require().Len(tester.rules[2].Domains, 1)
- s.Assert().Equal("^(?P[a-zA-Z0-9]+)\\.regex.com$", tester.configuration.AccessControl.Rules[2].DomainsRegex[0].String())
+ s.Assert().Equal("^(?P[a-zA-Z0-9]+)\\.regex.com$", tester.config.AccessControl.Rules[2].DomainsRegex[0].String())
ruleMatcher2, ok := tester.rules[2].Domains[0].Matcher.(RegexpGroupStringSubjectMatcher)
s.Require().True(ok)
@@ -399,7 +522,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainRegexMatching() {
s.Require().Len(tester.rules[3].Domains, 1)
- s.Assert().Equal("^group-(?P[a-zA-Z0-9]+)\\.regex.com$", tester.configuration.AccessControl.Rules[3].DomainsRegex[0].String())
+ s.Assert().Equal("^group-(?P[a-zA-Z0-9]+)\\.regex.com$", tester.config.AccessControl.Rules[3].DomainsRegex[0].String())
ruleMatcher3, ok := tester.rules[3].Domains[0].Matcher.(RegexpGroupStringSubjectMatcher)
s.Require().True(ok)
@@ -407,7 +530,7 @@ func (s *AuthorizerSuite) TestShouldCheckDomainRegexMatching() {
s.Require().Len(tester.rules[4].Domains, 1)
- s.Assert().Equal("^.*\\.(one|two).com$", tester.configuration.AccessControl.Rules[4].DomainsRegex[0].String())
+ s.Assert().Equal("^.*\\.(one|two).com$", tester.config.AccessControl.Rules[4].DomainsRegex[0].String())
ruleMatcher4, ok := tester.rules[4].Domains[0].Matcher.(RegexpStringSubjectMatcher)
s.Require().True(ok)
@@ -454,30 +577,30 @@ func (s *AuthorizerSuite) TestShouldCheckResourceSubjectMatching() {
// Accessing an invalid users Personal page.
tester.CheckAuthorizations(s.T(), John, "https://id.example.com/invaliduser/personal", "GET", Denied)
tester.CheckAuthorizations(s.T(), Bob, "https://id.example.com/invaliduser/personal", "GET", Denied)
- tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/invaliduser/personal", "GET", Denied)
+ tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/invaliduser/personal", "GET", OneFactor)
// Accessing another users Personal page.
tester.CheckAuthorizations(s.T(), John, "https://id.example.com/bob/personal", "GET", Denied)
- tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/bob/personal", "GET", Denied)
+ tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/bob/personal", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), John, "https://id.example.com/Bob/personal", "GET", Denied)
- tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/Bob/personal", "GET", Denied)
+ tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/Bob/personal", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), Bob, "https://id.example.com/john/personal", "GET", Denied)
- tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/john/personal", "GET", Denied)
+ tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/john/personal", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), Bob, "https://id.example.com/John/personal", "GET", Denied)
- tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/John/personal", "GET", Denied)
+ tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/John/personal", "GET", OneFactor)
// Accessing a Group page.
tester.CheckAuthorizations(s.T(), John, "https://id.example.com/dev/group", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), John, "https://id.example.com/admins/group", "GET", OneFactor)
tester.CheckAuthorizations(s.T(), Bob, "https://id.example.com/dev/group", "GET", Denied)
tester.CheckAuthorizations(s.T(), Bob, "https://id.example.com/admins/group", "GET", Denied)
- tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/dev/group", "GET", Denied)
- tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/admins/group", "GET", Denied)
+ tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/dev/group", "GET", OneFactor)
+ tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/admins/group", "GET", OneFactor)
// Accessing an invalid group's Group page.
tester.CheckAuthorizations(s.T(), John, "https://id.example.com/invalidgroup/group", "GET", Denied)
tester.CheckAuthorizations(s.T(), Bob, "https://id.example.com/invalidgroup/group", "GET", Denied)
- tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/invalidgroup/group", "GET", Denied)
+ tester.CheckAuthorizations(s.T(), AnonymousUser, "https://id.example.com/invalidgroup/group", "GET", OneFactor)
s.Require().Len(tester.rules, 3)
diff --git a/internal/authorization/const.go b/internal/authorization/const.go
index c86440207..afda76888 100644
--- a/internal/authorization/const.go
+++ b/internal/authorization/const.go
@@ -26,6 +26,15 @@ const (
deny = "deny"
)
+const (
+ operatorPresent = "present"
+ operatorAbsent = "absent"
+ operatorEqual = "equal"
+ operatorNotEqual = "not equal"
+ operatorPattern = "pattern"
+ operatorNotPattern = "not pattern"
+)
+
const (
subexpNameUser = "User"
subexpNameGroup = "Group"
@@ -36,4 +45,4 @@ var (
IdentitySubexpNames = []string{subexpNameUser, subexpNameGroup}
)
-const traceFmtACLHitMiss = "ACL %s Position %d for subject %s and object %s (Method %s)"
+const traceFmtACLHitMiss = "ACL %s Position %d for subject %s and object %s (method %s)"
diff --git a/internal/authorization/regexp.go b/internal/authorization/regexp.go
index 7983d842f..f26c5fcbf 100644
--- a/internal/authorization/regexp.go
+++ b/internal/authorization/regexp.go
@@ -16,6 +16,14 @@ type RegexpGroupStringSubjectMatcher struct {
// IsMatch returns true if the underlying pattern matches the input given the subject.
func (r RegexpGroupStringSubjectMatcher) IsMatch(input string, subject Subject) (match bool) {
+ if !r.Pattern.MatchString(input) {
+ return false
+ }
+
+ if subject.IsAnonymous() {
+ return true
+ }
+
matches := r.Pattern.FindAllStringSubmatch(input, -1)
if matches == nil {
return false
diff --git a/internal/authorization/types.go b/internal/authorization/types.go
index d02a1081a..4e26a2db3 100644
--- a/internal/authorization/types.go
+++ b/internal/authorization/types.go
@@ -24,6 +24,11 @@ type SubjectObjectMatcher interface {
IsMatch(subject Subject, object Object) (match bool)
}
+// ObjectMatcher is a matcher that takes an object.
+type ObjectMatcher interface {
+ IsMatch(object Object) (match bool)
+}
+
// Subject represents the identity of a user for the purposes of ACL matching.
type Subject struct {
Username string
@@ -43,7 +48,7 @@ func (s Subject) IsAnonymous() bool {
// Object represents a protected object for the purposes of ACL matching.
type Object struct {
- URL url.URL
+ URL *url.URL
Domain string
Path string
@@ -63,7 +68,7 @@ func NewObjectRaw(targetURL *url.URL, method []byte) (object Object) {
// NewObject creates a new Object type from a URL and a method header.
func NewObject(targetURL *url.URL, method string) (object Object) {
return Object{
- URL: *targetURL,
+ URL: targetURL,
Domain: targetURL.Hostname(),
Path: utils.URLPathFullClean(targetURL),
Method: method,
@@ -78,6 +83,7 @@ type RuleMatchResult struct {
MatchDomain bool
MatchResources bool
+ MatchQuery bool
MatchMethods bool
MatchNetworks bool
MatchSubjects bool
diff --git a/internal/authorization/util.go b/internal/authorization/util.go
index 877e46ca5..50be55e48 100644
--- a/internal/authorization/util.go
+++ b/internal/authorization/util.go
@@ -70,24 +70,40 @@ func schemaSubjectToACLSubject(subjectRule string) (subject SubjectMatcher) {
return nil
}
-func schemaDomainsToACL(domainRules []string, domainRegexRules []regexp.Regexp) (domains []AccessControlDomain) {
+func ruleAddDomain(domainRules []string, rule *AccessControlRule) {
for _, domainRule := range domainRules {
- domains = append(domains, NewAccessControlDomain(domainRule))
- }
+ subjects, r := NewAccessControlDomain(domainRule)
- for _, domainRegexRule := range domainRegexRules {
- domains = append(domains, NewAccessControlDomainRegex(domainRegexRule))
- }
+ rule.Domains = append(rule.Domains, r)
- return domains
+ if !rule.HasSubjects && subjects {
+ rule.HasSubjects = true
+ }
+ }
}
-func schemaResourcesToACL(resourceRules []regexp.Regexp) (resources []AccessControlResource) {
- for _, resourceRule := range resourceRules {
- resources = append(resources, NewAccessControlResource(resourceRule))
- }
+func ruleAddDomainRegex(exps []regexp.Regexp, rule *AccessControlRule) {
+ for _, exp := range exps {
+ subjects, r := NewAccessControlDomainRegex(exp)
- return resources
+ rule.Domains = append(rule.Domains, r)
+
+ if !rule.HasSubjects && subjects {
+ rule.HasSubjects = true
+ }
+ }
+}
+
+func ruleAddResources(exps []regexp.Regexp, rule *AccessControlRule) {
+ for _, exp := range exps {
+ subjects, r := NewAccessControlResource(exp)
+
+ rule.Resources = append(rule.Resources, r)
+
+ if !rule.HasSubjects && subjects {
+ rule.HasSubjects = true
+ }
+ }
}
func schemaMethodsToACL(methodRules []string) (methods []string) {
diff --git a/internal/commands/acl.go b/internal/commands/acl.go
index 0b0851b55..ad6e7d3fc 100644
--- a/internal/commands/acl.go
+++ b/internal/commands/acl.go
@@ -56,7 +56,7 @@ func newAccessControlCheckCommand() (cmd *cobra.Command) {
}
func accessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
- configs, err := cmd.Flags().GetStringSlice("config")
+ configs, err := cmd.Flags().GetStringSlice(cmdFlagNameConfig)
if err != nil {
return err
}
diff --git a/internal/commands/configuration.go b/internal/commands/configuration.go
index fb65fea69..3bbaad050 100644
--- a/internal/commands/configuration.go
+++ b/internal/commands/configuration.go
@@ -15,9 +15,9 @@ import (
// cmdWithConfigFlags is used for commands which require access to the configuration to add the flag to the command.
func cmdWithConfigFlags(cmd *cobra.Command, persistent bool, configs []string) {
if persistent {
- cmd.PersistentFlags().StringSliceP("config", "c", configs, "configuration files to load")
+ cmd.PersistentFlags().StringSliceP(cmdFlagNameConfig, "c", configs, "configuration files to load")
} else {
- cmd.Flags().StringSliceP("config", "c", configs, "configuration files to load")
+ cmd.Flags().StringSliceP(cmdFlagNameConfig, "c", configs, "configuration files to load")
}
}
@@ -33,7 +33,7 @@ func newCmdWithConfigPreRun(ensureConfigExists, validateKeys, validateConfigurat
logger = logging.Logger()
- if configs, err = cmd.Flags().GetStringSlice("config"); err != nil {
+ if configs, err = cmd.Flags().GetStringSlice(cmdFlagNameConfig); err != nil {
logger.Fatalf("Error reading flags: %v", err)
}
diff --git a/internal/commands/const.go b/internal/commands/const.go
index ad9cc1879..fad673679 100644
--- a/internal/commands/const.go
+++ b/internal/commands/const.go
@@ -176,6 +176,43 @@ This subcommand allows manually adding an opaque identifier for a user to the da
authelia storage user identifiers add john --identifier f0919359-9d15-4e15-bcba-83b41620a073 --config config.yml
authelia storage user identifiers add john --identifier f0919359-9d15-4e15-bcba-83b41620a073 --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw`
+ cmdAutheliaStorageUserWebAuthnShort = "Manage Webauthn devices"
+
+ cmdAutheliaStorageUserWebAuthnLong = `Manage Webauthn devices.
+
+This subcommand allows interacting with Webauthn devices.`
+
+ cmdAutheliaStorageUserWebAuthnExample = `authelia storage user webauthn --help`
+
+ cmdAutheliaStorageUserWebAuthnListShort = "List WebAuthn devices"
+
+ cmdAutheliaStorageUserWebAuthnListLong = `List WebAuthn devices.
+
+This subcommand allows listing WebAuthn devices.`
+
+ cmdAutheliaStorageUserWebAuthnListExample = `authelia storage user webauthn list
+authelia storage user webauthn list john
+authelia storage user webauthn list --config config.yml
+authelia storage user webauthn list john --config config.yml
+authelia storage user webauthn list --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
+authelia storage user webauthn list john --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw`
+
+ cmdAutheliaStorageUserWebAuthnDeleteShort = "Delete a WebAuthn device"
+
+ cmdAutheliaStorageUserWebAuthnDeleteLong = `Delete a WebAuthn device.
+
+This subcommand allows deleting a WebAuthn device directly from the database.`
+
+ cmdAutheliaStorageUserWebAuthnDeleteExample = `authelia storage user webauthn delete john --all
+authelia storage user webauthn delete john --all --config config.yml
+authelia storage user webauthn delete john --all --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
+authelia storage user webauthn delete john --description Primary
+authelia storage user webauthn delete john --description Primary --config config.yml
+authelia storage user webauthn delete john --description Primary --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw
+authelia storage user webauthn delete --kid abc123
+authelia storage user webauthn delete --kid abc123 --config config.yml
+authelia storage user webauthn delete --kid abc123 --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw`
+
cmdAutheliaStorageUserTOTPShort = "Manage TOTP configurations"
cmdAutheliaStorageUserTOTPLong = `Manage TOTP configurations.
@@ -310,6 +347,57 @@ This subcommand allows preforming cryptographic certificate, key pair, etc tasks
cmdAutheliaCryptoExample = `authelia crypto --help`
+ cmdAutheliaCryptoRandShort = "Generate a cryptographically secure random string"
+
+ cmdAutheliaCryptoRandLong = `Generate a cryptographically secure random string.
+
+This subcommand allows generating cryptographically secure random strings for use for encryption keys, HMAC keys, etc.`
+
+ cmdAutheliaCryptoRandExample = `authelia crypto rand --help
+authelia crypto rand --length 80
+authelia crypto rand -n 80
+authelia crypto rand --charset alphanumeric
+authelia crypto rand --charset alphabetic
+authelia crypto rand --charset ascii
+authelia crypto rand --charset numeric
+authelia crypto rand --charset numeric-hex
+authelia crypto rand --characters 0123456789ABCDEF`
+
+ cmdAutheliaCryptoHashShort = "Perform cryptographic hash operations"
+
+ cmdAutheliaCryptoHashLong = `Perform cryptographic hash operations.
+
+This subcommand allows preforming hashing cryptographic tasks.`
+
+ cmdAutheliaCryptoHashExample = `authelia crypto hash --help`
+
+ cmdAutheliaCryptoHashValidateShort = "Perform cryptographic hash validations"
+
+ cmdAutheliaCryptoHashValidateLong = `Perform cryptographic hash validations.
+
+This subcommand allows preforming cryptographic hash validations. i.e. checking hash digests against a password.`
+
+ cmdAutheliaCryptoHashValidateExample = `authelia crypto hash validate --help
+authelia crypto hash validate '$5$rounds=500000$WFjMpdCQxIkbNl0k$M0qZaZoK8Gwdh8Cw5diHgGfe5pE0iJvxcVG3.CVnQe.' -- 'p@ssw0rd'`
+
+ cmdAutheliaCryptoHashGenerateShort = "Generate cryptographic hash digests"
+
+ cmdAutheliaCryptoHashGenerateLong = `Generate cryptographic hash digests.
+
+This subcommand allows generating cryptographic hash digests.
+
+See the help for the subcommands if you want to override the configuration or defaults.`
+
+ cmdAutheliaCryptoHashGenerateExample = `authelia crypto hash generate --help`
+
+ fmtCmdAutheliaCryptoHashGenerateSubShort = "Generate cryptographic %s hash digests"
+
+ fmtCmdAutheliaCryptoHashGenerateSubLong = `Generate cryptographic %s hash digests.
+
+This subcommand allows generating cryptographic %s hash digests.`
+
+ fmtCmdAutheliaCryptoHashGenerateSubExample = `authelia crypto hash generate %s --help`
+
cmdAutheliaCryptoCertificateShort = "Perform certificate cryptographic operations"
cmdAutheliaCryptoCertificateLong = `Perform certificate cryptographic operations.
@@ -324,11 +412,7 @@ This subcommand allows preforming certificate cryptographic tasks.`
This subcommand allows preforming %s certificate cryptographic tasks.`
- cmdAutheliaCryptoCertificateRSAExample = `authelia crypto certificate rsa --help`
-
- cmdAutheliaCryptoCertificateECDSAExample = `authelia crypto certificate ecdsa --help`
-
- cmdAutheliaCryptoCertificateEd25519Example = `authelia crypto certificate ed25519 --help`
+ fmtCmdAutheliaCryptoCertificateSubExample = `authelia crypto certificate %s --help`
fmtCmdAutheliaCryptoCertificateGenerateRequestShort = "Generate an %s private key and %s"
@@ -444,11 +528,49 @@ const (
cmdFlagNamePKCS8 = "pkcs8"
cmdFlagNameBits = "bits"
cmdFlagNameCurve = "curve"
+
+ cmdFlagNamePassword = "password"
+ cmdFlagNameRandom = "random"
+ cmdFlagNameRandomLength = "random.length"
+ cmdFlagNameRandomCharSet = "random.charset"
+ cmdFlagNameRandomCharacters = "random.characters"
+ cmdFlagNameNoConfirm = "no-confirm"
+ cmdFlagNameVariant = "variant"
+ cmdFlagNameCost = "cost"
+ cmdFlagNameIterations = "iterations"
+ cmdFlagNameParallelism = "parallelism"
+ cmdFlagNameBlockSize = "block-size"
+ cmdFlagNameMemory = "memory"
+ cmdFlagNameKeySize = "key-size"
+ cmdFlagNameSaltSize = "salt-size"
+ cmdFlagNameProfile = "profile"
+ cmdFlagNameSHA512 = "sha512"
+ cmdFlagNameConfig = "config"
+
+ cmdFlagNameCharSet = "charset"
+ cmdFlagValueCharSet = "alphanumeric"
+ cmdFlagUsageCharset = "sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986'"
+ cmdFlagNameCharacters = "characters"
+ cmdFlagUsageCharacters = "sets the explicit characters for the random string"
+ cmdFlagNameLength = "length"
+ cmdFlagUsageLength = "sets the character length for the random string"
)
const (
+ cmdUseHashPassword = "hash-password [flags] -- [password]"
+ cmdUseHash = "hash"
+ cmdUseHashArgon2 = "argon2"
+ cmdUseHashSHA2Crypt = "sha2crypt"
+ cmdUseHashPBKDF2 = "pbkdf2"
+ cmdUseHashBCrypt = "bcrypt"
+ cmdUseHashSCrypt = "scrypt"
+
+ cmdUseCrypto = "crypto"
+ cmdUseRand = "rand"
cmdUseCertificate = "certificate"
cmdUseGenerate = "generate"
+ cmdUseValidate = "validate"
+ cmdUseFmtValidate = "%s [flags] -- "
cmdUseRequest = "request"
cmdUsePair = "pair"
cmdUseRSA = "rsa"
@@ -459,6 +581,8 @@ const (
const (
cryptoCertPubCertOut = "certificate"
cryptoCertCSROut = "certificate signing request"
+
+ prefixFilePassword = "authentication_backend.file.password"
)
var (
diff --git a/internal/commands/crypto.go b/internal/commands/crypto.go
index 63a331e5e..642f442d4 100644
--- a/internal/commands/crypto.go
+++ b/internal/commands/crypto.go
@@ -17,7 +17,7 @@ import (
func newCryptoCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
- Use: "crypto",
+ Use: cmdUseCrypto,
Short: cmdAutheliaCryptoShort,
Long: cmdAutheliaCryptoLong,
Example: cmdAutheliaCryptoExample,
@@ -27,13 +27,46 @@ func newCryptoCmd() (cmd *cobra.Command) {
}
cmd.AddCommand(
+ newCryptoRandCmd(),
newCryptoCertificateCmd(),
+ newCryptoHashCmd(),
newCryptoPairCmd(),
)
return cmd
}
+func newCryptoRandCmd() (cmd *cobra.Command) {
+ cmd = &cobra.Command{
+ Use: cmdUseRand,
+ Short: cmdAutheliaCryptoRandShort,
+ Long: cmdAutheliaCryptoRandLong,
+ Example: cmdAutheliaCryptoRandExample,
+ Args: cobra.NoArgs,
+ RunE: func(cmd *cobra.Command, args []string) (err error) {
+ var (
+ random string
+ )
+
+ if random, err = flagsGetRandomCharacters(cmd.Flags(), cmdFlagNameLength, cmdFlagNameCharSet, cmdFlagNameCharacters); err != nil {
+ return err
+ }
+
+ fmt.Printf("Random Value: %s\n", random)
+
+ return nil
+ },
+
+ DisableAutoGenTag: true,
+ }
+
+ cmd.Flags().StringP(cmdFlagNameCharSet, "c", cmdFlagValueCharSet, cmdFlagUsageCharset)
+ cmd.Flags().String(cmdFlagNameCharacters, "", cmdFlagUsageCharacters)
+ cmd.Flags().IntP(cmdFlagNameLength, "n", 72, cmdFlagUsageLength)
+
+ return cmd
+}
+
func newCryptoCertificateCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseCertificate,
@@ -55,26 +88,13 @@ func newCryptoCertificateCmd() (cmd *cobra.Command) {
}
func newCryptoCertificateSubCmd(use string) (cmd *cobra.Command) {
- var (
- example, useFmt string
- )
-
- useFmt = fmtCryptoUse(use)
-
- switch use {
- case cmdUseRSA:
- example = cmdAutheliaCryptoCertificateRSAExample
- case cmdUseECDSA:
- example = cmdAutheliaCryptoCertificateECDSAExample
- case cmdUseEd25519:
- example = cmdAutheliaCryptoCertificateEd25519Example
- }
+ useFmt := fmtCryptoCertificateUse(use)
cmd = &cobra.Command{
Use: use,
Short: fmt.Sprintf(fmtCmdAutheliaCryptoCertificateSubShort, useFmt),
Long: fmt.Sprintf(fmtCmdAutheliaCryptoCertificateSubLong, useFmt, useFmt),
- Example: example,
+ Example: fmt.Sprintf(fmtCmdAutheliaCryptoCertificateSubExample, use),
Args: cobra.NoArgs,
DisableAutoGenTag: true,
@@ -98,7 +118,7 @@ func newCryptoCertificateRequestCmd(algorithm string) (cmd *cobra.Command) {
cmdFlagsCryptoCertificateCommon(cmd)
cmdFlagsCryptoCertificateRequest(cmd)
- algorithmFmt := fmtCryptoUse(algorithm)
+ algorithmFmt := fmtCryptoCertificateUse(algorithm)
cmd.Short = fmt.Sprintf(fmtCmdAutheliaCryptoCertificateGenerateRequestShort, algorithmFmt, cryptoCertCSROut)
cmd.Long = fmt.Sprintf(fmtCmdAutheliaCryptoCertificateGenerateRequestLong, algorithmFmt, cryptoCertCSROut, algorithmFmt, cryptoCertCSROut)
@@ -146,7 +166,7 @@ func newCryptoPairSubCmd(use string) (cmd *cobra.Command) {
example, useFmt string
)
- useFmt = fmtCryptoUse(use)
+ useFmt = fmtCryptoCertificateUse(use)
switch use {
case cmdUseRSA:
@@ -184,7 +204,7 @@ func newCryptoGenerateCmd(category, algorithm string) (cmd *cobra.Command) {
cmdFlagsCryptoPrivateKey(cmd)
- algorithmFmt := fmtCryptoUse(algorithm)
+ algorithmFmt := fmtCryptoCertificateUse(algorithm)
switch category {
case cmdUseCertificate:
@@ -235,7 +255,7 @@ func newCryptoGenerateCmd(category, algorithm string) (cmd *cobra.Command) {
func cryptoGenerateRunE(cmd *cobra.Command, args []string) (err error) {
var (
- privateKey interface{}
+ privateKey any
)
if privateKey, err = cryptoGenPrivateKeyFromCmd(cmd); err != nil {
@@ -251,7 +271,7 @@ func cryptoGenerateRunE(cmd *cobra.Command, args []string) (err error) {
func cryptoCertificateRequestRunE(cmd *cobra.Command, _ []string) (err error) {
var (
- privateKey interface{}
+ privateKey any
)
if privateKey, err = cryptoGenPrivateKeyFromCmd(cmd); err != nil {
@@ -320,10 +340,10 @@ func cryptoCertificateRequestRunE(cmd *cobra.Command, _ []string) (err error) {
return nil
}
-func cryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string, privateKey interface{}) (err error) {
+func cryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string, privateKey any) (err error) {
var (
template, caCertificate, parent *x509.Certificate
- publicKey, caPrivateKey, signatureKey interface{}
+ publicKey, caPrivateKey, signatureKey any
)
if publicKey = utils.PublicKeyFromPrivateKey(privateKey); publicKey == nil {
@@ -412,7 +432,7 @@ func cryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string, privateKey in
return nil
}
-func cryptoPairGenerateRunE(cmd *cobra.Command, _ []string, privateKey interface{}) (err error) {
+func cryptoPairGenerateRunE(cmd *cobra.Command, _ []string, privateKey any) (err error) {
var (
privateKeyPath, publicKeyPath string
pkcs8 bool
@@ -451,7 +471,7 @@ func cryptoPairGenerateRunE(cmd *cobra.Command, _ []string, privateKey interface
return err
}
- var publicKey interface{}
+ var publicKey any
if publicKey = utils.PublicKeyFromPrivateKey(privateKey); publicKey == nil {
return fmt.Errorf("failed to obtain public key from private key")
diff --git a/internal/commands/crypto_hash.go b/internal/commands/crypto_hash.go
new file mode 100644
index 000000000..ec5ce26eb
--- /dev/null
+++ b/internal/commands/crypto_hash.go
@@ -0,0 +1,519 @@
+package commands
+
+import (
+ "fmt"
+ "strings"
+ "syscall"
+
+ "github.com/go-crypt/crypt"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+ "golang.org/x/term"
+
+ "github.com/authelia/authelia/v4/internal/authentication"
+ "github.com/authelia/authelia/v4/internal/configuration"
+ "github.com/authelia/authelia/v4/internal/configuration/schema"
+ "github.com/authelia/authelia/v4/internal/configuration/validator"
+)
+
+func newHashPasswordCmd() (cmd *cobra.Command) {
+ cmd = &cobra.Command{
+ Use: cmdUseHashPassword,
+ Short: cmdAutheliaHashPasswordShort,
+ Long: cmdAutheliaHashPasswordLong,
+ Example: cmdAutheliaHashPasswordExample,
+ Args: cobra.MaximumNArgs(1),
+ RunE: cmdHashPasswordRunE,
+
+ DisableAutoGenTag: true,
+ }
+
+ cmdFlagConfig(cmd)
+
+ cmd.Flags().BoolP(cmdFlagNameSHA512, "z", false, fmt.Sprintf("use sha512 as the algorithm (changes iterations to %d, change with -i)", schema.DefaultPasswordConfig.SHA2Crypt.Iterations))
+ cmd.Flags().IntP(cmdFlagNameIterations, "i", schema.DefaultPasswordConfig.Argon2.Iterations, "set the number of hashing iterations")
+ cmd.Flags().IntP(cmdFlagNameMemory, "m", schema.DefaultPasswordConfig.Argon2.Memory, "[argon2id] set the amount of memory param (in MB)")
+ cmd.Flags().IntP(cmdFlagNameParallelism, "p", schema.DefaultPasswordConfig.Argon2.Parallelism, "[argon2id] set the parallelism param")
+ cmd.Flags().IntP("key-length", "k", schema.DefaultPasswordConfig.Argon2.KeyLength, "[argon2id] set the key length param")
+ cmd.Flags().IntP("salt-length", "l", schema.DefaultPasswordConfig.Argon2.SaltLength, "set the auto-generated salt length")
+ cmd.Flags().Bool(cmdFlagNameNoConfirm, false, "skip the password confirmation prompt")
+
+ return cmd
+}
+
+func cmdHashPasswordRunE(cmd *cobra.Command, args []string) (err error) {
+ var (
+ flagsMap map[string]string
+ sha512 bool
+ )
+
+ if sha512, err = cmd.Flags().GetBool(cmdFlagNameSHA512); err != nil {
+ return err
+ }
+
+ switch {
+ case sha512:
+ flagsMap = map[string]string{
+ cmdFlagNameIterations: prefixFilePassword + ".sha2crypt.iterations",
+ "salt-length": prefixFilePassword + ".sha2crypt.salt_length",
+ }
+ default:
+ flagsMap = map[string]string{
+ cmdFlagNameIterations: prefixFilePassword + ".argon2.iterations",
+ "key-length": prefixFilePassword + ".argon2.key_length",
+ "salt-length": prefixFilePassword + ".argon2.salt_length",
+ cmdFlagNameParallelism: prefixFilePassword + ".argon2.parallelism",
+ cmdFlagNameMemory: prefixFilePassword + ".argon2.memory",
+ }
+ }
+
+ return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
+}
+
+func newCryptoHashCmd() (cmd *cobra.Command) {
+ cmd = &cobra.Command{
+ Use: cmdUseHash,
+ Short: cmdAutheliaCryptoHashShort,
+ Long: cmdAutheliaCryptoHashLong,
+ Example: cmdAutheliaCryptoHashExample,
+ Args: cobra.NoArgs,
+
+ DisableAutoGenTag: true,
+ }
+
+ cmd.AddCommand(
+ newCryptoHashValidateCmd(),
+ newCryptoHashGenerateCmd(),
+ )
+
+ return cmd
+}
+
+func newCryptoHashGenerateCmd() (cmd *cobra.Command) {
+ cmd = &cobra.Command{
+ Use: cmdUseGenerate,
+ Short: cmdAutheliaCryptoHashGenerateShort,
+ Long: cmdAutheliaCryptoHashGenerateLong,
+ Example: cmdAutheliaCryptoHashGenerateExample,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return cmdCryptoHashGenerateFinish(cmd, args, map[string]string{})
+ },
+
+ DisableAutoGenTag: true,
+ }
+
+ cmdFlagConfig(cmd)
+ cmdFlagPassword(cmd, true)
+ cmdFlagRandomPassword(cmd)
+
+ for _, use := range []string{cmdUseHashArgon2, cmdUseHashSHA2Crypt, cmdUseHashPBKDF2, cmdUseHashBCrypt, cmdUseHashSCrypt} {
+ cmd.AddCommand(newCryptoHashGenerateSubCmd(use))
+ }
+
+ return cmd
+}
+
+func newCryptoHashGenerateSubCmd(use string) (cmd *cobra.Command) {
+ useFmt := fmtCryptoHashUse(use)
+
+ cmd = &cobra.Command{
+ Use: use,
+ Short: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubShort, useFmt),
+ Long: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubLong, useFmt, useFmt),
+ Example: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubExample, use),
+ Args: cobra.NoArgs,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return nil
+ },
+
+ DisableAutoGenTag: true,
+ }
+
+ switch use {
+ case cmdUseHashArgon2:
+ cmdFlagIterations(cmd, schema.DefaultPasswordConfig.Argon2.Iterations)
+ cmdFlagParallelism(cmd, schema.DefaultPasswordConfig.Argon2.Parallelism)
+ cmdFlagKeySize(cmd, schema.DefaultPasswordConfig.Argon2.KeyLength)
+ cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.Argon2.SaltLength)
+
+ cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.Argon2.Variant, "variant, options are 'argon2id', 'argon2i', and 'argon2d'")
+ cmd.Flags().IntP(cmdFlagNameMemory, "m", schema.DefaultPasswordConfig.Argon2.Memory, "memory in kibibytes")
+ cmd.Flags().String(cmdFlagNameProfile, "", "profile to use, options are low-memory and recommended")
+
+ cmd.RunE = cryptoHashGenerateArgon2RunE
+ case cmdUseHashSHA2Crypt:
+ cmdFlagIterations(cmd, schema.DefaultPasswordConfig.SHA2Crypt.Iterations)
+ cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.SHA2Crypt.SaltLength)
+
+ cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.SHA2Crypt.Variant, "variant, options are sha256 and sha512")
+
+ cmd.RunE = cryptoHashGenerateSHA2CryptRunE
+ case cmdUseHashPBKDF2:
+ cmdFlagIterations(cmd, schema.DefaultPasswordConfig.PBKDF2.Iterations)
+ cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.PBKDF2.SaltLength)
+
+ cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.PBKDF2.Variant, "variant, options are 'sha1', 'sha224', 'sha256', 'sha384', and 'sha512'")
+
+ cmd.RunE = cryptoHashGeneratePBKDF2RunE
+ case cmdUseHashBCrypt:
+ cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.BCrypt.Variant, "variant, options are 'standard' and 'sha256'")
+ cmd.Flags().IntP(cmdFlagNameCost, "i", schema.DefaultPasswordConfig.BCrypt.Cost, "hashing cost")
+
+ cmd.RunE = cryptoHashGenerateBCryptRunE
+ case cmdUseHashSCrypt:
+ cmdFlagIterations(cmd, schema.DefaultPasswordConfig.SCrypt.Iterations)
+ cmdFlagKeySize(cmd, schema.DefaultPasswordConfig.SCrypt.KeyLength)
+ cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.SCrypt.SaltLength)
+ cmdFlagParallelism(cmd, schema.DefaultPasswordConfig.SCrypt.Parallelism)
+
+ cmd.Flags().IntP(cmdFlagNameBlockSize, "r", schema.DefaultPasswordConfig.SCrypt.BlockSize, "block size")
+
+ cmd.RunE = cryptoHashGenerateSCryptRunE
+ }
+
+ return cmd
+}
+
+func cryptoHashGenerateArgon2RunE(cmd *cobra.Command, args []string) (err error) {
+ flagsMap := map[string]string{
+ cmdFlagNameVariant: prefixFilePassword + ".argon2.variant",
+ cmdFlagNameIterations: prefixFilePassword + ".argon2.iterations",
+ cmdFlagNameMemory: prefixFilePassword + ".argon2.memory",
+ cmdFlagNameParallelism: prefixFilePassword + ".argon2.parallelism",
+ cmdFlagNameKeySize: prefixFilePassword + ".argon2.key_length",
+ cmdFlagNameSaltSize: prefixFilePassword + ".argon2.salt_length",
+ }
+
+ return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
+}
+
+func cryptoHashGenerateSHA2CryptRunE(cmd *cobra.Command, args []string) (err error) {
+ flagsMap := map[string]string{
+ cmdFlagNameVariant: prefixFilePassword + ".sha2crypt.variant",
+ cmdFlagNameIterations: prefixFilePassword + ".sha2crypt.iterations",
+ cmdFlagNameSaltSize: prefixFilePassword + ".sha2crypt.salt_length",
+ }
+
+ return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
+}
+
+func cryptoHashGeneratePBKDF2RunE(cmd *cobra.Command, args []string) (err error) {
+ flagsMap := map[string]string{
+ cmdFlagNameVariant: prefixFilePassword + ".pbkdf2.variant",
+ cmdFlagNameIterations: prefixFilePassword + ".pbkdf2.iterations",
+ cmdFlagNameKeySize: prefixFilePassword + ".pbkdf2.key_length",
+ cmdFlagNameSaltSize: prefixFilePassword + ".pbkdf2.salt_length",
+ }
+
+ return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
+}
+
+func cryptoHashGenerateBCryptRunE(cmd *cobra.Command, args []string) (err error) {
+ flagsMap := map[string]string{
+ cmdFlagNameVariant: prefixFilePassword + ".bcrypt.variant",
+ cmdFlagNameCost: prefixFilePassword + ".bcrypt.cost",
+ }
+
+ return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
+}
+
+func cryptoHashGenerateSCryptRunE(cmd *cobra.Command, args []string) (err error) {
+ flagsMap := map[string]string{
+ cmdFlagNameIterations: prefixFilePassword + ".scrypt.iterations",
+ cmdFlagNameBlockSize: prefixFilePassword + ".scrypt.block_size",
+ cmdFlagNameParallelism: prefixFilePassword + ".scrypt.parallelism",
+ cmdFlagNameKeySize: prefixFilePassword + ".scrypt.key_length",
+ cmdFlagNameSaltSize: prefixFilePassword + ".scrypt.salt_length",
+ }
+
+ return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
+}
+
+func newCryptoHashValidateCmd() (cmd *cobra.Command) {
+ cmd = &cobra.Command{
+ Use: fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate),
+ Short: cmdAutheliaCryptoHashValidateShort,
+ Long: cmdAutheliaCryptoHashValidateLong,
+ Example: cmdAutheliaCryptoHashValidateExample,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) (err error) {
+ var (
+ password string
+ valid bool
+ )
+
+ if password, _, err = cmdCryptoHashGetPassword(cmd, args, false, false); err != nil {
+ return fmt.Errorf("error occurred trying to obtain the password: %w", err)
+ }
+
+ if len(password) == 0 {
+ return fmt.Errorf("no password provided")
+ }
+
+ if valid, err = crypt.CheckPassword(password, args[0]); err != nil {
+ return fmt.Errorf("error occurred trying to validate the password against the digest: %w", err)
+ }
+
+ switch {
+ case valid:
+ fmt.Println("The password matches the digest.")
+ default:
+ fmt.Println("The password does not match the digest.")
+ }
+
+ return nil
+ },
+
+ DisableAutoGenTag: true,
+ }
+
+ cmdFlagPassword(cmd, false)
+
+ return cmd
+}
+
+func cmdCryptoHashGenerateFinish(cmd *cobra.Command, args []string, flagsMap map[string]string) (err error) {
+ var (
+ algorithm string
+ configs []string
+
+ c schema.Password
+ )
+
+ if configs, err = cmd.Flags().GetStringSlice(cmdFlagNameConfig); err != nil {
+ return err
+ }
+
+ // Skip config if the flag wasn't set and the default is non-existent.
+ if !cmd.Flags().Changed(cmdFlagNameConfig) {
+ configs = configFilterExisting(configs)
+ }
+
+ legacy := cmd.Use == cmdUseHashPassword
+
+ switch {
+ case cmd.Use == cmdUseGenerate:
+ break
+ case legacy:
+ if sha512, _ := cmd.Flags().GetBool(cmdFlagNameSHA512); sha512 {
+ algorithm = cmdUseHashSHA2Crypt
+ } else {
+ algorithm = cmdUseHashArgon2
+ }
+ default:
+ algorithm = cmd.Use
+ }
+
+ if c, err = cmdCryptoHashGetConfig(algorithm, configs, cmd.Flags(), flagsMap); err != nil {
+ return err
+ }
+
+ if legacy && algorithm == cmdUseHashArgon2 && cmd.Flags().Changed(cmdFlagNameMemory) {
+ c.Argon2.Memory *= 1024
+ }
+
+ var (
+ hash crypt.Hash
+ digest crypt.Digest
+ password string
+ random bool
+ )
+
+ if password, random, err = cmdCryptoHashGetPassword(cmd, args, legacy, !legacy); err != nil {
+ return err
+ }
+
+ if len(password) == 0 {
+ return fmt.Errorf("no password provided")
+ }
+
+ if hash, err = authentication.NewFileCryptoHashFromConfig(c); err != nil {
+ return err
+ }
+
+ if digest, err = hash.Hash(password); err != nil {
+ return err
+ }
+
+ if random {
+ fmt.Printf("Random Password: %s\n", password)
+ }
+
+ fmt.Printf("Digest: %s\n", digest.Encode())
+
+ return nil
+}
+
+func cmdCryptoHashGetConfig(algorithm string, configs []string, flags *pflag.FlagSet, flagsMap map[string]string) (c schema.Password, err error) {
+ mapDefaults := map[string]interface{}{
+ prefixFilePassword + ".algorithm": schema.DefaultPasswordConfig.Algorithm,
+ prefixFilePassword + ".argon2.variant": schema.DefaultPasswordConfig.Argon2.Variant,
+ prefixFilePassword + ".argon2.iterations": schema.DefaultPasswordConfig.Argon2.Iterations,
+ prefixFilePassword + ".argon2.memory": schema.DefaultPasswordConfig.Argon2.Memory,
+ prefixFilePassword + ".argon2.parallelism": schema.DefaultPasswordConfig.Argon2.Parallelism,
+ prefixFilePassword + ".argon2.key_length": schema.DefaultPasswordConfig.Argon2.KeyLength,
+ prefixFilePassword + ".argon2.salt_length": schema.DefaultPasswordConfig.Argon2.SaltLength,
+ prefixFilePassword + ".sha2crypt.variant": schema.DefaultPasswordConfig.SHA2Crypt.Variant,
+ prefixFilePassword + ".sha2crypt.iterations": schema.DefaultPasswordConfig.SHA2Crypt.Iterations,
+ prefixFilePassword + ".sha2crypt.salt_length": schema.DefaultPasswordConfig.SHA2Crypt.SaltLength,
+ prefixFilePassword + ".pbkdf2.variant": schema.DefaultPasswordConfig.PBKDF2.Variant,
+ prefixFilePassword + ".pbkdf2.iterations": schema.DefaultPasswordConfig.PBKDF2.Iterations,
+ prefixFilePassword + ".pbkdf2.salt_length": schema.DefaultPasswordConfig.PBKDF2.SaltLength,
+ prefixFilePassword + ".bcrypt.variant": schema.DefaultPasswordConfig.BCrypt.Variant,
+ prefixFilePassword + ".bcrypt.cost": schema.DefaultPasswordConfig.BCrypt.Cost,
+ prefixFilePassword + ".scrypt.iterations": schema.DefaultPasswordConfig.SCrypt.Iterations,
+ prefixFilePassword + ".scrypt.block_size": schema.DefaultPasswordConfig.SCrypt.BlockSize,
+ prefixFilePassword + ".scrypt.parallelism": schema.DefaultPasswordConfig.SCrypt.Parallelism,
+ prefixFilePassword + ".scrypt.key_length": schema.DefaultPasswordConfig.SCrypt.KeyLength,
+ prefixFilePassword + ".scrypt.salt_length": schema.DefaultPasswordConfig.SCrypt.SaltLength,
+ }
+
+ sources := configuration.NewDefaultSourcesWithDefaults(configs,
+ configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter,
+ configuration.NewMapSource(mapDefaults),
+ configuration.NewCommandLineSourceWithMapping(flags, flagsMap, false, false),
+ )
+
+ if algorithm != "" {
+ alg := map[string]interface{}{prefixFilePassword + ".algorithm": algorithm}
+
+ sources = append(sources, configuration.NewMapSource(alg))
+ }
+
+ val := schema.NewStructValidator()
+
+ if _, err = configuration.LoadAdvanced(val, prefixFilePassword, &c, sources...); err != nil {
+ return schema.Password{}, fmt.Errorf("error occurred loading configuration: %w", err)
+ }
+
+ validator.ValidatePasswordConfiguration(&c, val)
+
+ errs := val.Errors()
+
+ if len(errs) != 0 {
+ for i, e := range errs {
+ if i == 0 {
+ err = e
+ continue
+ }
+
+ err = fmt.Errorf("%v, %w", err, e)
+ }
+
+ return schema.Password{}, fmt.Errorf("errors occurred validating the password configuration: %w", err)
+ }
+
+ return c, nil
+}
+
+func cmdCryptoHashGetPassword(cmd *cobra.Command, args []string, useArgs, useRandom bool) (password string, random bool, err error) {
+ if useRandom {
+ if random, err = cmd.Flags().GetBool(cmdFlagNameRandom); err != nil {
+ return
+ }
+ }
+
+ switch {
+ case random:
+ password, err = flagsGetRandomCharacters(cmd.Flags(), cmdFlagNameRandomLength, cmdFlagNameRandomCharSet, cmdFlagNameCharacters)
+
+ return
+ case cmd.Flags().Changed(cmdFlagNamePassword):
+ password, err = cmd.Flags().GetString(cmdFlagNamePassword)
+
+ return
+ case useArgs && len(args) != 0:
+ password, err = strings.Join(args, " "), nil
+
+ return
+ }
+
+ var (
+ data []byte
+ noConfirm bool
+ )
+
+ if data, err = hashReadPasswordWithPrompt("Enter Password: "); err != nil {
+ err = fmt.Errorf("failed to read the password from the terminal: %w", err)
+
+ return
+ }
+
+ password = string(data)
+
+ if cmd.Use == fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate) {
+ fmt.Println("")
+
+ return
+ }
+
+ if noConfirm, err = cmd.Flags().GetBool(cmdFlagNameNoConfirm); err == nil && !noConfirm {
+ if data, err = hashReadPasswordWithPrompt("Confirm Password: "); err != nil {
+ err = fmt.Errorf("failed to read the password from the terminal: %w", err)
+ return
+ }
+
+ if password != string(data) {
+ fmt.Println("")
+
+ err = fmt.Errorf("the password did not match the confirmation password")
+
+ return
+ }
+ }
+
+ fmt.Println("")
+
+ return
+}
+
+func hashReadPasswordWithPrompt(prompt string) (data []byte, err error) {
+ fmt.Print(prompt)
+
+ if data, err = term.ReadPassword(int(syscall.Stdin)); err != nil { //nolint:unconvert,nolintlint
+ if err.Error() == "inappropriate ioctl for device" {
+ return nil, fmt.Errorf("the terminal doesn't appear to be interactive either use the '--password' flag or use an interactive terminal: %w", err)
+ }
+
+ return nil, err
+ }
+
+ fmt.Println("")
+
+ return data, nil
+}
+
+func cmdFlagConfig(cmd *cobra.Command) {
+ cmd.PersistentFlags().StringSliceP(cmdFlagNameConfig, "c", []string{"configuration.yml"}, "configuration files to load")
+}
+
+func cmdFlagPassword(cmd *cobra.Command, noConfirm bool) {
+ cmd.PersistentFlags().String(cmdFlagNamePassword, "", "manually supply the password rather than using the terminal prompt")
+
+ if noConfirm {
+ cmd.PersistentFlags().Bool(cmdFlagNameNoConfirm, false, "skip the password confirmation prompt")
+ }
+}
+
+func cmdFlagRandomPassword(cmd *cobra.Command) {
+ cmd.PersistentFlags().Bool(cmdFlagNameRandom, false, "uses a randomly generated password")
+ cmd.PersistentFlags().String(cmdFlagNameRandomCharSet, cmdFlagValueCharSet, cmdFlagUsageCharset)
+ cmd.PersistentFlags().String(cmdFlagNameRandomCharacters, "", cmdFlagUsageCharacters)
+ cmd.PersistentFlags().Int(cmdFlagNameRandomLength, 72, cmdFlagUsageLength)
+}
+
+func cmdFlagIterations(cmd *cobra.Command, value int) {
+ cmd.Flags().IntP(cmdFlagNameIterations, "i", value, "number of iterations")
+}
+
+func cmdFlagKeySize(cmd *cobra.Command, value int) {
+ cmd.Flags().IntP(cmdFlagNameKeySize, "k", value, "key size in bytes")
+}
+
+func cmdFlagSaltSize(cmd *cobra.Command, value int) {
+ cmd.Flags().IntP(cmdFlagNameSaltSize, "s", value, "salt size in bytes")
+}
+
+func cmdFlagParallelism(cmd *cobra.Command, value int) {
+ cmd.Flags().IntP(cmdFlagNameParallelism, "p", value, "parallelism or threads")
+}
diff --git a/internal/commands/crypto_helper.go b/internal/commands/crypto_helper.go
index 96e23f673..d85c70798 100644
--- a/internal/commands/crypto_helper.go
+++ b/internal/commands/crypto_helper.go
@@ -130,7 +130,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 interface{}, err error) {
+func cryptoGenPrivateKeyFromCmd(cmd *cobra.Command) (privateKey any, err error) {
switch cmd.Parent().Use {
case cmdUseRSA:
var (
@@ -170,7 +170,7 @@ func cryptoGenPrivateKeyFromCmd(cmd *cobra.Command) (privateKey interface{}, err
return privateKey, nil
}
-func cryptoGetCAFromCmd(cmd *cobra.Command) (privateKey interface{}, cert *x509.Certificate, err error) {
+func cryptoGetCAFromCmd(cmd *cobra.Command) (privateKey any, cert *x509.Certificate, err error) {
if !cmd.Flags().Changed(cmdFlagNamePathCA) {
return nil, nil, nil
}
@@ -180,7 +180,7 @@ func cryptoGetCAFromCmd(cmd *cobra.Command) (privateKey interface{}, cert *x509.
ok bool
- certificate interface{}
+ certificate any
)
if dir, err = cmd.Flags().GetString(cmdFlagNamePathCA); err != nil {
@@ -416,7 +416,20 @@ func cryptoGetCertificateFromCmd(cmd *cobra.Command) (certificate *x509.Certific
return certificate, nil
}
-func fmtCryptoUse(use string) string {
+func fmtCryptoHashUse(use string) string {
+ switch use {
+ case cmdUseHashArgon2:
+ return "Argon2"
+ case cmdUseHashSHA2Crypt:
+ return "SHA2 Crypt"
+ case cmdUseHashPBKDF2:
+ return "PBKDF2"
+ default:
+ return use
+ }
+}
+
+func fmtCryptoCertificateUse(use string) string {
switch use {
case cmdUseEd25519:
return "Ed25519"
diff --git a/internal/commands/hash.go b/internal/commands/hash.go
deleted file mode 100644
index 33608c67f..000000000
--- a/internal/commands/hash.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package commands
-
-import (
- "fmt"
-
- "github.com/simia-tech/crypt"
- "github.com/spf13/cobra"
-
- "github.com/authelia/authelia/v4/internal/authentication"
- "github.com/authelia/authelia/v4/internal/configuration"
- "github.com/authelia/authelia/v4/internal/configuration/schema"
- "github.com/authelia/authelia/v4/internal/configuration/validator"
-)
-
-func newHashPasswordCmd() (cmd *cobra.Command) {
- cmd = &cobra.Command{
- Use: "hash-password [flags] -- ",
- Short: cmdAutheliaHashPasswordShort,
- Long: cmdAutheliaHashPasswordLong,
- Example: cmdAutheliaHashPasswordExample,
- Args: cobra.MinimumNArgs(1),
- RunE: cmdHashPasswordRunE,
-
- DisableAutoGenTag: true,
- }
-
- cmd.Flags().BoolP("sha512", "z", false, fmt.Sprintf("use sha512 as the algorithm (changes iterations to %d, change with -i)", schema.DefaultPasswordSHA512Configuration.Iterations))
- cmd.Flags().IntP("iterations", "i", schema.DefaultPasswordConfiguration.Iterations, "set the number of hashing iterations")
- cmd.Flags().StringP("salt", "s", "", "set the salt string")
- cmd.Flags().IntP("memory", "m", schema.DefaultPasswordConfiguration.Memory, "[argon2id] set the amount of memory param (in MB)")
- cmd.Flags().IntP("parallelism", "p", schema.DefaultPasswordConfiguration.Parallelism, "[argon2id] set the parallelism param")
- cmd.Flags().IntP("key-length", "k", schema.DefaultPasswordConfiguration.KeyLength, "[argon2id] set the key length param")
- cmd.Flags().IntP("salt-length", "l", schema.DefaultPasswordConfiguration.SaltLength, "set the auto-generated salt length")
- cmd.Flags().StringSliceP("config", "c", []string{}, "Configuration files")
-
- return cmd
-}
-
-func cmdHashPasswordRunE(cmd *cobra.Command, args []string) (err error) {
- salt, _ := cmd.Flags().GetString("salt")
- sha512, _ := cmd.Flags().GetBool("sha512")
- configs, _ := cmd.Flags().GetStringSlice("config")
-
- mapDefaults := map[string]interface{}{
- "authentication_backend.file.password.algorithm": schema.DefaultPasswordConfiguration.Algorithm,
- "authentication_backend.file.password.iterations": schema.DefaultPasswordConfiguration.Iterations,
- "authentication_backend.file.password.key_length": schema.DefaultPasswordConfiguration.KeyLength,
- "authentication_backend.file.password.salt_length": schema.DefaultPasswordConfiguration.SaltLength,
- "authentication_backend.file.password.parallelism": schema.DefaultPasswordConfiguration.Parallelism,
- "authentication_backend.file.password.memory": schema.DefaultPasswordConfiguration.Memory,
- }
-
- if sha512 {
- mapDefaults["authentication_backend.file.password.algorithm"] = schema.DefaultPasswordSHA512Configuration.Algorithm
- mapDefaults["authentication_backend.file.password.iterations"] = schema.DefaultPasswordSHA512Configuration.Iterations
- mapDefaults["authentication_backend.file.password.salt_length"] = schema.DefaultPasswordSHA512Configuration.SaltLength
- }
-
- mapCLI := map[string]string{
- "iterations": "authentication_backend.file.password.iterations",
- "key-length": "authentication_backend.file.password.key_length",
- "salt-length": "authentication_backend.file.password.salt_length",
- "parallelism": "authentication_backend.file.password.parallelism",
- "memory": "authentication_backend.file.password.memory",
- }
-
- sources := configuration.NewDefaultSourcesWithDefaults(configs,
- configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter,
- configuration.NewMapSource(mapDefaults),
- configuration.NewCommandLineSourceWithMapping(cmd.Flags(), mapCLI, false, false),
- )
-
- val := schema.NewStructValidator()
-
- if _, config, err = configuration.Load(val, sources...); err != nil {
- return fmt.Errorf("error occurred loading configuration: %w", err)
- }
-
- var (
- hash string
- algorithm authentication.CryptAlgo
- )
-
- p := config.AuthenticationBackend.File.Password
-
- switch p.Algorithm {
- case "sha512":
- algorithm = authentication.HashingAlgorithmSHA512
- default:
- algorithm = authentication.HashingAlgorithmArgon2id
- }
-
- validator.ValidatePasswordConfiguration(p, val)
-
- errs := val.Errors()
-
- if len(errs) != 0 {
- for i, e := range errs {
- if i == 0 {
- err = e
- continue
- }
-
- err = fmt.Errorf("%v, %w", err, e)
- }
-
- return fmt.Errorf("errors occurred validating the password configuration: %w", err)
- }
-
- if salt != "" {
- salt = crypt.Base64Encoding.EncodeToString([]byte(salt))
- }
-
- if hash, err = authentication.HashPassword(args[0], salt, algorithm, p.Iterations, p.Memory*1024, p.Parallelism, p.KeyLength, p.SaltLength); err != nil {
- return fmt.Errorf("error during password hashing: %w", err)
- }
-
- fmt.Printf("Password hash: %s\n", hash)
-
- return nil
-}
diff --git a/internal/commands/helpers.go b/internal/commands/helpers.go
index 01f23e8f8..0bfec419d 100644
--- a/internal/commands/helpers.go
+++ b/internal/commands/helpers.go
@@ -1,6 +1,8 @@
package commands
import (
+ "crypto/x509"
+
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/metrics"
@@ -17,11 +19,22 @@ import (
)
func getStorageProvider() (provider storage.Provider) {
+ switch {
+ case config.Storage.Local == nil:
+ return getStorageProviderWithPool(nil)
+ default:
+ caCertPool, _, _ := utils.NewX509CertPool(config.CertificatesDirectory)
+
+ return getStorageProviderWithPool(caCertPool)
+ }
+}
+
+func getStorageProviderWithPool(caCertPool *x509.CertPool) (provider storage.Provider) {
switch {
case config.Storage.PostgreSQL != nil:
- return storage.NewPostgreSQLProvider(config)
+ return storage.NewPostgreSQLProvider(config, caCertPool)
case config.Storage.MySQL != nil:
- return storage.NewMySQLProvider(config)
+ return storage.NewMySQLProvider(config, caCertPool)
case config.Storage.Local != nil:
return storage.NewSQLiteProvider(config)
default:
@@ -31,12 +44,12 @@ func getStorageProvider() (provider storage.Provider) {
func getProviders() (providers middlewares.Providers, warnings []error, errors []error) {
// TODO: Adjust this so the CertPool can be used like a provider.
- autheliaCertPool, warnings, errors := utils.NewX509CertPool(config.CertificatesDirectory)
+ caCertPool, warnings, errors := utils.NewX509CertPool(config.CertificatesDirectory)
if len(warnings) != 0 || len(errors) != 0 {
return providers, warnings, errors
}
- storageProvider := getStorageProvider()
+ storageProvider := getStorageProviderWithPool(caCertPool)
var (
userProvider authentication.UserProvider
@@ -47,7 +60,7 @@ func getProviders() (providers middlewares.Providers, warnings []error, errors [
case config.AuthenticationBackend.File != nil:
userProvider = authentication.NewFileUserProvider(config.AuthenticationBackend.File)
case config.AuthenticationBackend.LDAP != nil:
- userProvider = authentication.NewLDAPUserProvider(config.AuthenticationBackend, autheliaCertPool)
+ userProvider = authentication.NewLDAPUserProvider(config.AuthenticationBackend, caCertPool)
}
templatesProvider, err := templates.New(templates.Config{EmailTemplatesPath: config.Notifier.TemplatePath})
@@ -59,7 +72,7 @@ func getProviders() (providers middlewares.Providers, warnings []error, errors [
switch {
case config.Notifier.SMTP != nil:
- notifier = notification.NewSMTPNotifier(config.Notifier.SMTP, autheliaCertPool, templatesProvider)
+ notifier = notification.NewSMTPNotifier(config.Notifier.SMTP, caCertPool, templatesProvider)
case config.Notifier.FileSystem != nil:
notifier = notification.NewFileNotifier(*config.Notifier.FileSystem)
}
@@ -68,7 +81,7 @@ func getProviders() (providers middlewares.Providers, warnings []error, errors [
clock := utils.RealClock{}
authorizer := authorization.NewAuthorizer(config)
- sessionProvider := session.NewProvider(config.Session, autheliaCertPool)
+ sessionProvider := session.NewProvider(config.Session, caCertPool)
regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)
oidcProvider, err := oidc.NewOpenIDConnectProvider(config.IdentityProviders.OIDC, storageProvider)
diff --git a/internal/commands/root.go b/internal/commands/root.go
index c532d562e..e9639be44 100644
--- a/internal/commands/root.go
+++ b/internal/commands/root.go
@@ -6,14 +6,17 @@ import (
"net"
"os"
"os/signal"
+ "path/filepath"
"strings"
"syscall"
+ "github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/valyala/fasthttp"
"golang.org/x/sync/errgroup"
+ "github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/logging"
"github.com/authelia/authelia/v4/internal/middlewares"
@@ -42,12 +45,12 @@ func NewRootCmd() (cmd *cobra.Command) {
cmdWithConfigFlags(cmd, false, []string{})
cmd.AddCommand(
+ newAccessControlCommand(),
newBuildInfoCmd(),
newCryptoCmd(),
newHashPasswordCmd(),
newStorageCmd(),
newValidateConfigCmd(),
- newAccessControlCommand(),
)
return cmd
@@ -83,11 +86,11 @@ func cmdRootRun(_ *cobra.Command, _ []string) {
doStartupChecks(config, &providers, logger)
- runServers(config, providers, logger)
+ runServices(config, providers, logger)
}
//nolint:gocyclo // Complexity is required in this function.
-func runServers(config *schema.Configuration, providers middlewares.Providers, log *logrus.Logger) {
+func runServices(config *schema.Configuration, providers middlewares.Providers, log *logrus.Logger) {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
@@ -151,6 +154,15 @@ func runServers(config *schema.Configuration, providers middlewares.Providers, l
return nil
})
+ if config.AuthenticationBackend.File != nil && config.AuthenticationBackend.File.Watch {
+ provider := providers.UserProvider.(*authentication.FileUserProvider)
+ if watcher, err := runServiceFileWatcher(g, log, config.AuthenticationBackend.File.Path, provider); err != nil {
+ log.WithError(err).Errorf("Error opening file watcher")
+ } else {
+ defer watcher.Close()
+ }
+ }
+
select {
case s := <-quit:
switch s {
@@ -181,11 +193,84 @@ func runServers(config *schema.Configuration, providers middlewares.Providers, l
}
}
+ if err = providers.StorageProvider.Close(); err != nil {
+ log.WithError(err).Errorf("Error occurred closing the database connection")
+ }
+
if err = g.Wait(); err != nil {
log.WithError(err).Errorf("Error occurred waiting for shutdown")
}
}
+type ReloadFilter func(path string) (skipped bool)
+
+type ProviderReload interface {
+ Reload() (reloaded bool, err error)
+}
+
+func runServiceFileWatcher(g *errgroup.Group, log *logrus.Logger, path string, reload ProviderReload) (watcher *fsnotify.Watcher, err error) {
+ if watcher, err = fsnotify.NewWatcher(); err != nil {
+ return nil, err
+ }
+
+ failed := make(chan struct{})
+
+ var directory, filename string
+
+ if path != "" {
+ directory, filename = filepath.Dir(path), filepath.Base(path)
+ }
+
+ g.Go(func() error {
+ for {
+ select {
+ case <-failed:
+ return nil
+ case event, ok := <-watcher.Events:
+ if !ok {
+ return nil
+ }
+
+ if filename != filepath.Base(event.Name) {
+ log.WithField("file", event.Name).WithField("op", event.Op).Tracef("File modification detected to irrelevant file")
+ break
+ }
+
+ switch {
+ case event.Op&fsnotify.Write == fsnotify.Write, event.Op&fsnotify.Create == fsnotify.Create:
+ log.WithField("file", event.Name).WithField("op", event.Op).Debug("File modification detected")
+
+ switch reloaded, err := reload.Reload(); {
+ case err != nil:
+ log.WithField("file", event.Name).WithField("op", event.Op).WithError(err).Error("Error occurred reloading file")
+ case reloaded:
+ log.WithField("file", event.Name).Info("Reloaded file successfully")
+ default:
+ log.WithField("file", event.Name).Debug("Reload of file was triggered but it was skipped")
+ }
+ case event.Op&fsnotify.Remove == fsnotify.Remove:
+ log.WithField("file", event.Name).WithField("op", event.Op).Debug("Remove of file was detected")
+ }
+ case err, ok := <-watcher.Errors:
+ if !ok {
+ return nil
+ }
+ log.WithError(err).Errorf("Error while watching files")
+ }
+ }
+ })
+
+ if err := watcher.Add(directory); err != nil {
+ failed <- struct{}{}
+
+ return nil, err
+ }
+
+ log.WithField("directory", directory).WithField("file", filename).Debug("Directory is being watched for changes to the file")
+
+ return watcher, nil
+}
+
func doStartupChecks(config *schema.Configuration, providers *middlewares.Providers, log *logrus.Logger) {
var (
failures []string
diff --git a/internal/commands/storage.go b/internal/commands/storage.go
index adfd1c28c..92190149a 100644
--- a/internal/commands/storage.go
+++ b/internal/commands/storage.go
@@ -117,6 +117,7 @@ func newStorageUserCmd() (cmd *cobra.Command) {
cmd.AddCommand(
newStorageUserIdentifiersCmd(),
newStorageUserTOTPCmd(),
+ newStorageUserWebAuthnCmd(),
)
return cmd
@@ -211,6 +212,58 @@ func newStorageUserIdentifiersAddCmd() (cmd *cobra.Command) {
return cmd
}
+func newStorageUserWebAuthnCmd() (cmd *cobra.Command) {
+ cmd = &cobra.Command{
+ Use: "webauthn",
+ Short: cmdAutheliaStorageUserWebAuthnShort,
+ Long: cmdAutheliaStorageUserWebAuthnLong,
+ Example: cmdAutheliaStorageUserWebAuthnExample,
+
+ DisableAutoGenTag: true,
+ }
+
+ cmd.AddCommand(
+ newStorageUserWebAuthnListCmd(),
+ newStorageUserWebAuthnDeleteCmd(),
+ )
+
+ return cmd
+}
+
+func newStorageUserWebAuthnListCmd() (cmd *cobra.Command) {
+ cmd = &cobra.Command{
+ Use: "list [username]",
+ Short: cmdAutheliaStorageUserWebAuthnListShort,
+ Long: cmdAutheliaStorageUserWebAuthnListLong,
+ Example: cmdAutheliaStorageUserWebAuthnListExample,
+ RunE: storageWebAuthnListRunE,
+ Args: cobra.MaximumNArgs(1),
+
+ DisableAutoGenTag: true,
+ }
+
+ return cmd
+}
+
+func newStorageUserWebAuthnDeleteCmd() (cmd *cobra.Command) {
+ cmd = &cobra.Command{
+ Use: "delete [username]",
+ Short: cmdAutheliaStorageUserWebAuthnDeleteShort,
+ Long: cmdAutheliaStorageUserWebAuthnDeleteLong,
+ Example: cmdAutheliaStorageUserWebAuthnDeleteExample,
+ RunE: storageWebAuthnDeleteRunE,
+ Args: cobra.MaximumNArgs(1),
+
+ DisableAutoGenTag: true,
+ }
+
+ cmd.Flags().Bool("all", false, "delete all of the users webauthn devices")
+ cmd.Flags().String("description", "", "delete a users webauthn device by description")
+ cmd.Flags().String("kid", "", "delete a users webauthn device by key id")
+
+ return cmd
+}
+
func newStorageUserTOTPCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "totp",
diff --git a/internal/commands/storage_run.go b/internal/commands/storage_run.go
index b911fd64f..4b11c5e72 100644
--- a/internal/commands/storage_run.go
+++ b/internal/commands/storage_run.go
@@ -29,13 +29,13 @@ import (
func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) {
var configs []string
- if configs, err = cmd.Flags().GetStringSlice("config"); err != nil {
+ if configs, err = cmd.Flags().GetStringSlice(cmdFlagNameConfig); err != nil {
return err
}
sources := make([]configuration.Source, 0, len(configs)+3)
- if cmd.Flags().Changed("config") {
+ if cmd.Flags().Changed(cmdFlagNameConfig) {
for _, configFile := range configs {
if _, err := os.Stat(configFile); os.IsNotExist(err) {
return fmt.Errorf("could not load the provided configuration file %s: %w", configFile, err)
@@ -205,6 +205,187 @@ func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (er
return nil
}
+func storageWebAuthnListRunE(cmd *cobra.Command, args []string) (err error) {
+ if len(args) == 0 || args[0] == "" {
+ return storageWebAuthnListAllRunE(cmd, args)
+ }
+
+ var (
+ provider storage.Provider
+ ctx = context.Background()
+ )
+
+ provider = getStorageProvider()
+
+ defer func() {
+ _ = provider.Close()
+ }()
+
+ var devices []model.WebauthnDevice
+
+ user := args[0]
+
+ devices, err = provider.LoadWebauthnDevicesByUsername(ctx, user)
+
+ switch {
+ case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebauthnDevice)):
+ return fmt.Errorf("user '%s' has no webauthn devices", user)
+ case err != nil:
+ return fmt.Errorf("can't list devices for user '%s': %w", user, err)
+ default:
+ fmt.Printf("Webauthn Devices for user '%s':\n\n", user)
+ fmt.Printf("ID\tKID\tDescription\n")
+
+ for _, device := range devices {
+ fmt.Printf("%d\t%s\t%s", device.ID, device.KID, device.Description)
+ }
+ }
+
+ return nil
+}
+
+func storageWebAuthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
+ var (
+ provider storage.Provider
+ ctx = context.Background()
+ )
+
+ provider = getStorageProvider()
+
+ defer func() {
+ _ = provider.Close()
+ }()
+
+ var devices []model.WebauthnDevice
+
+ limit := 10
+
+ output := strings.Builder{}
+
+ for page := 0; true; page++ {
+ if devices, err = provider.LoadWebauthnDevices(ctx, limit, page); err != nil {
+ return fmt.Errorf("failed to list devices: %w", err)
+ }
+
+ if page == 0 && len(devices) == 0 {
+ return errors.New("no webauthn devices in database")
+ }
+
+ for _, device := range devices {
+ output.WriteString(fmt.Sprintf("%d\t%s\t%s\t%s\n", device.ID, device.KID, device.Description, device.Username))
+ }
+
+ if len(devices) < limit {
+ break
+ }
+ }
+
+ fmt.Printf("Webauthn Devices:\n\nID\tKID\tDescription\tUsername\n")
+ fmt.Println(output.String())
+
+ return nil
+}
+
+func storageWebAuthnDeleteRunE(cmd *cobra.Command, args []string) (err error) {
+ var (
+ provider storage.Provider
+ ctx = context.Background()
+ )
+
+ provider = getStorageProvider()
+
+ defer func() {
+ _ = provider.Close()
+ }()
+
+ var (
+ all, byKID bool
+ description, kid, user string
+ )
+
+ if all, byKID, description, kid, user, err = storageWebAuthnDeleteGetAndValidateConfig(cmd, args); err != nil {
+ return err
+ }
+
+ if byKID {
+ if err = provider.DeleteWebauthnDevice(ctx, kid); err != nil {
+ return fmt.Errorf("failed to delete WebAuthn device with kid '%s': %w", kid, err)
+ }
+
+ fmt.Printf("Deleted WebAuthn device with kid '%s'", kid)
+ } else {
+ err = provider.DeleteWebauthnDeviceByUsername(ctx, user, description)
+
+ if all {
+ if err != nil {
+ return fmt.Errorf("failed to delete all WebAuthn devices with username '%s': %w", user, err)
+ }
+
+ fmt.Printf("Deleted all WebAuthn devices for user '%s'", user)
+ } else {
+ if err != nil {
+ return fmt.Errorf("failed to delete WebAuthn device with username '%s' and description '%s': %w", user, description, err)
+ }
+
+ fmt.Printf("Deleted WebAuthn device with username '%s' and description '%s'", user, description)
+ }
+ }
+
+ return nil
+}
+
+func storageWebAuthnDeleteGetAndValidateConfig(cmd *cobra.Command, args []string) (all, byKID bool, description, kid, user string, err error) {
+ if len(args) != 0 {
+ user = args[0]
+ }
+
+ flags := 0
+
+ if cmd.Flags().Changed("all") {
+ if all, err = cmd.Flags().GetBool("all"); err != nil {
+ return
+ }
+
+ flags++
+ }
+
+ if cmd.Flags().Changed("description") {
+ if description, err = cmd.Flags().GetString("description"); err != nil {
+ return
+ }
+
+ flags++
+ }
+
+ if byKID = cmd.Flags().Changed("kid"); byKID {
+ if kid, err = cmd.Flags().GetString("kid"); err != nil {
+ return
+ }
+
+ flags++
+ }
+
+ if flags > 1 {
+ err = fmt.Errorf("must only supply one of the flags --all, --description, and --kid but %d were specified", flags)
+
+ return
+ }
+
+ if flags == 0 {
+ err = fmt.Errorf("must supply one of the flags --all, --description, or --kid")
+
+ return
+ }
+
+ if !byKID && len(user) == 0 {
+ err = fmt.Errorf("must supply the username or the --kid flag")
+
+ return
+ }
+
+ return
+}
+
func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
var (
provider storage.Provider
@@ -406,7 +587,7 @@ func storageTOTPExportGetConfigFromFlags(cmd *cobra.Command) (format, dir string
break
case storageTOTPExportFormatPNG:
if dir == "" {
- dir = utils.RandomString(8, utils.AlphaNumericCharacters, false)
+ dir = utils.RandomString(8, utils.CharSetAlphaNumeric, false)
}
if _, err = os.Stat(dir); !os.IsNotExist(err) {
diff --git a/internal/commands/util.go b/internal/commands/util.go
index 26c308648..e4af026b4 100644
--- a/internal/commands/util.go
+++ b/internal/commands/util.go
@@ -2,9 +2,14 @@ package commands
import (
"fmt"
+ "os"
+
+ "github.com/spf13/pflag"
+
+ "github.com/authelia/authelia/v4/internal/utils"
)
-func recoverErr(i interface{}) error {
+func recoverErr(i any) error {
switch v := i.(type) {
case nil:
return nil
@@ -16,3 +21,81 @@ func recoverErr(i interface{}) error {
return fmt.Errorf("recovered panic with unknown type: %v", v)
}
}
+
+func configFilterExisting(configs []string) (finalConfigs []string) {
+ var err error
+
+ for _, c := range configs {
+ if _, err = os.Stat(c); err == nil || !os.IsNotExist(err) {
+ finalConfigs = append(finalConfigs, c)
+ }
+ }
+
+ return finalConfigs
+}
+
+//nolint:gocyclo
+func flagsGetRandomCharacters(flags *pflag.FlagSet, flagNameLength, flagNameCharSet, flagNameCharacters string) (r string, err error) {
+ var (
+ n int
+ charset string
+ )
+
+ if n, err = flags.GetInt(flagNameLength); err != nil {
+ return "", err
+ }
+
+ if n < 1 {
+ return "", fmt.Errorf("flag --%s with value '%d' is invalid: must be at least 1", flagNameLength, n)
+ }
+
+ useCharSet, useCharacters := flags.Changed(flagNameCharSet), flags.Changed(flagNameCharacters)
+
+ if useCharSet && useCharacters {
+ return "", fmt.Errorf("flag --%s and flag --%s are mutually exclusive, only one may be used", flagNameCharSet, flagNameCharacters)
+ }
+
+ switch {
+ case useCharSet, !useCharSet && !useCharacters:
+ var c string
+
+ if c, err = flags.GetString(flagNameCharSet); err != nil {
+ return "", err
+ }
+
+ switch c {
+ case "ascii":
+ charset = utils.CharSetASCII
+ case "alphanumeric":
+ charset = utils.CharSetAlphaNumeric
+ case "alphanumeric-lower":
+ charset = utils.CharSetAlphabeticLower + utils.CharSetNumeric
+ case "alphanumeric-upper":
+ charset = utils.CharSetAlphabeticUpper + utils.CharSetNumeric
+ case "alphabetic":
+ charset = utils.CharSetAlphabetic
+ case "alphabetic-lower":
+ charset = utils.CharSetAlphabeticLower
+ case "alphabetic-upper":
+ charset = utils.CharSetAlphabeticUpper
+ case "numeric-hex":
+ charset = utils.CharSetNumericHex
+ case "numeric":
+ charset = utils.CharSetNumeric
+ case "rfc3986":
+ charset = utils.CharSetRFC3986Unreserved
+ case "rfc3986-lower":
+ charset = utils.CharSetAlphabeticLower + utils.CharSetNumeric + utils.CharSetSymbolicRFC3986Unreserved
+ case "rfc3986-upper":
+ charset = utils.CharSetAlphabeticUpper + utils.CharSetNumeric + utils.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)
+ }
+ case useCharacters:
+ if charset, err = flags.GetString(flagNameCharacters); err != nil {
+ return "", err
+ }
+ }
+
+ return utils.RandomString(n, charset, true), nil
+}
diff --git a/internal/commands/validate.go b/internal/commands/validate.go
index 8a8cbf4b9..b55377373 100644
--- a/internal/commands/validate.go
+++ b/internal/commands/validate.go
@@ -31,7 +31,7 @@ func cmdValidateConfigRunE(cmd *cobra.Command, _ []string) (err error) {
val *schema.StructValidator
)
- if configs, err = cmd.Flags().GetStringSlice("config"); err != nil {
+ if configs, err = cmd.Flags().GetStringSlice(cmdFlagNameConfig); err != nil {
return err
}
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index a878e05f4..4940b3f33 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -96,10 +96,10 @@ server:
# timeouts:
## Read timeout.
- # read: 2s
+ # read: 6s
## Write timeout.
- # write: 2s
+ # write: 6s
## Idle timeout.
# idle: 30s
@@ -148,10 +148,10 @@ telemetry:
# timeouts:
## Read timeout.
- # read: 2s
+ # read: 6s
## Write timeout.
- # write: 2s
+ # write: 6s
## Idle timeout.
# idle: 30s
@@ -218,13 +218,13 @@ webauthn:
##
## Parameters used to contact the Duo API. Those are generated when you protect an application of type
## "Partner Auth API" in the management panel.
-duo_api:
- disable: false
- hostname: api-123456789.example.com
- integration_key: ABCDEF
+# duo_api:
+ # disable: false
+ # hostname: api-123456789.example.com
+ # integration_key: ABCDEF
## Secret can also be set using a secret: https://www.authelia.com/c/secrets
- secret_key: 1234567890abcdefghifjkl
- enable_self_enrollment: false
+ # secret_key: 1234567890abcdefghifjkl
+ # enable_self_enrollment: false
##
## NTP Configuration
@@ -281,7 +281,7 @@ authentication_backend:
## This is the recommended Authentication Provider in production
## because it allows Authelia to offload the stateful operations
## onto the LDAP service.
- ldap:
+ # ldap:
## The LDAP implementation, this affects elements like the attribute utilised for resetting a password.
## Acceptable options are as follows:
## - 'activedirectory' - For Microsoft Active Directory.
@@ -291,47 +291,127 @@ authentication_backend:
## Depending on the option here certain other values in this section have a default value, notably all of the
## attribute mappings have a default value that this config overrides, you can read more about these default values
## at https://www.authelia.com/c/ldap#defaults
- implementation: custom
+ # implementation: custom
## The url to the ldap server. Format: ://[:].
## Scheme can be ldap or ldaps in the format (port optional).
- url: ldap://127.0.0.1
+ # url: ldap://127.0.0.1
## The dial timeout for LDAP.
- timeout: 5s
+ # timeout: 5s
## Use StartTLS with the LDAP connection.
- start_tls: false
+ # start_tls: false
- tls:
- ## Server Name for certificate validation (in case it's not set correctly in the URL).
+ # tls:
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host portion of the url option.
# server_name: ldap.example.com
- ## Skip verifying the server certificate (to allow a self-signed certificate).
- ## In preference to setting this we strongly recommend you add the public portion of the certificate to the
- ## certificates directory which is defined by the `certificates_directory` option at the top of the config.
- skip_verify: false
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
+ # skip_verify: false
- ## Minimum TLS version for either Secure LDAP or LDAP StartTLS.
- minimum_version: TLS1.2
+ ## Minimum TLS version for the connection.
+ # minimum_version: TLS1.2
+
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
## The distinguished name of the container searched for objects in the directory information tree.
## See also: additional_users_dn, additional_groups_dn.
- base_dn: dc=example,dc=com
+ # base_dn: dc=example,dc=com
## The attribute holding the username of the user. This attribute is used to populate the username in the session
- ## information. It was introduced due to #561 to handle case insensitive search queries. For you information,
- ## Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually uses 'uid'. Beware that this
- ## attribute holds the unique identifiers for the users binding the user and the configuration stored in database.
- ## Therefore only single value attributes are allowed and the value must never be changed once attributed to a user
- ## otherwise it would break the configuration for that user. Technically, non-unique attributes like 'mail' can also
- ## be used but we don't recommend using them, we instead advise to use the attributes mentioned above
- ## (sAMAccountName and uid) to follow https://www.ietf.org/rfc/rfc2307.txt.
+ ## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually
+ ## uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and the
+ ## configuration stored in database. Therefore only single value attributes are allowed and the value must never be
+ ## changed once attributed to a user otherwise it would break the configuration for that user. Technically,
+ ## non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead advise to use
+ ## a filter to perform alternative lookups and the attributes mentioned above (sAMAccountName and uid) to
+ ## follow https://www.ietf.org/rfc/rfc2307.txt.
# username_attribute: uid
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
- additional_users_dn: ou=users
+ # additional_users_dn: ou=users
## The users filter used in search queries to find the user profile based on input filled in login form.
## Various placeholders are available in the user filter which you can read about in the documentation which can
@@ -345,11 +425,11 @@ authentication_backend:
##
## To allow sign in both with username and email, one can use a filter like
## (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
- users_filter: (&({username_attribute}={input})(objectClass=person))
+ # users_filter: (&({username_attribute}={input})(objectClass=person))
## The additional_groups_dn is prefixed to base_dn and delimited by a comma when searching for groups.
## i.e. with this set to OU=Groups and base_dn set to DC=a,DC=com; OU=Groups,DC=a,DC=com is searched for groups.
- additional_groups_dn: ou=groups
+ # additional_groups_dn: ou=groups
## The groups filter used in search queries to find the groups based on relevant authenticated user.
## Various placeholders are available in the groups filter which you can read about in the documentation which can
@@ -357,7 +437,7 @@ authentication_backend:
##
## If your groups use the `groupOfUniqueNames` structure use this instead:
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
- groups_filter: (&(member={dn})(objectClass=groupOfNames))
+ # groups_filter: (&(member={dn})(objectClass=groupOfNames))
## The attribute holding the name of the group.
# group_name_attribute: cn
@@ -371,12 +451,12 @@ authentication_backend:
## Follow referrals returned by the server.
## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
- permit_referrals: false
+ # permit_referrals: false
## The username and password of the admin user.
- user: cn=admin,dc=example,dc=com
+ # user: cn=admin,dc=example,dc=com
## Password can also be set using a secret: https://www.authelia.com/c/secrets
- password: password
+ # password: password
##
## File (Authentication Provider)
@@ -391,14 +471,38 @@ authentication_backend:
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
# file:
- # path: /config/users_database.yml
- # password:
- # algorithm: argon2id
- # iterations: 1
- # key_length: 32
- # salt_length: 16
- # memory: 1024
- # parallelism: 8
+ # path: /config/users_database.yml
+ # watch: false
+ # search:
+ # email: false
+ # case_insensitive: false
+ # password:
+ # algorithm: argon2
+ # argon2:
+ # variant: argon2id
+ # iterations: 3
+ # memory: 65536
+ # parallelism: 4
+ # key_length: 32
+ # salt_length: 16
+ # scrypt:
+ # iterations: 16
+ # block_size: 8
+ # parallelism: 1
+ # key_length: 32
+ # salt_length: 16
+ # pbkdf2:
+ # variant: sha512
+ # iterations: 310000
+ # salt_length: 16
+ # sha2crypt:
+ # variant: sha512
+ # iterations: 50000
+ # salt_length: 16
+ # bcrypt:
+ # variant: standard
+ # cost: 12
+
##
## Password Policy Configuration.
@@ -443,7 +547,7 @@ password_policy:
## to anyone. Otherwise restrictions follow the rules defined.
##
## Note: One can use the wildcard * to match any subdomain.
-## It must stand at the beginning of the pattern. (example: *.mydomain.com)
+## It must stand at the beginning of the pattern. (example: *.example.com)
##
## Note: You must put patterns containing wildcards between simple quotes for the YAML to be syntactically correct.
##
@@ -466,18 +570,18 @@ access_control:
## resource if there is no policy to be applied to the user.
default_policy: deny
- networks:
- - name: internal
- networks:
- - 10.10.0.0/16
- - 192.168.2.0/24
- - name: VPN
- networks: 10.9.0.0/16
+ # networks:
+ # - name: internal
+ # networks:
+ # - 10.10.0.0/16
+ # - 192.168.2.0/24
+ # - name: VPN
+ # networks: 10.9.0.0/16
- rules:
+ # rules:
## Rules applied to everyone
- - domain: 'public.example.com'
- policy: bypass
+ # - domain: 'public.example.com'
+ # policy: bypass
## Domain Regex examples. Generally we recommend just using a standard domain.
# - domain_regex: '^(?P\w+)\.example\.com$'
@@ -485,70 +589,70 @@ access_control:
# - domain_regex: '^(?P\w+)\.example\.com$'
# policy: one_factor
# - domain_regex:
- # - '^appgroup-.*\.example\.com$'
- # - '^appgroup2-.*\.example\.com$'
+ # - '^appgroup-.*\.example\.com$'
+ # - '^appgroup2-.*\.example\.com$'
# policy: one_factor
# - domain_regex: '^.*\.example\.com$'
# policy: two_factor
- - domain: 'secure.example.com'
- policy: one_factor
- ## Network based rule, if not provided any network matches.
- networks:
- - internal
- - VPN
- - 192.168.1.0/24
- - 10.0.0.1
+ # - domain: 'secure.example.com'
+ # policy: one_factor
+ ## Network based rule, if not provided any network matches.
+ # networks:
+ # - internal
+ # - VPN
+ # - 192.168.1.0/24
+ # - 10.0.0.1
- - domain:
- - 'secure.example.com'
- - 'private.example.com'
- policy: two_factor
+ # - domain:
+ # - 'secure.example.com'
+ # - 'private.example.com'
+ # policy: two_factor
- - domain: 'singlefactor.example.com'
- policy: one_factor
+ # - domain: 'singlefactor.example.com'
+ # policy: one_factor
## Rules applied to 'admins' group
- - domain: 'mx2.mail.example.com'
- subject: 'group:admins'
- policy: deny
+ # - domain: 'mx2.mail.example.com'
+ # subject: 'group:admins'
+ # policy: deny
- - domain: '*.example.com'
- subject:
- - 'group:admins'
- - 'group:moderators'
- policy: two_factor
+ # - domain: '*.example.com'
+ # subject:
+ # - 'group:admins'
+ # - 'group:moderators'
+ # policy: two_factor
## Rules applied to 'dev' group
- - domain: 'dev.example.com'
- resources:
- - '^/groups/dev/.*$'
- subject: 'group:dev'
- policy: two_factor
+ # - domain: 'dev.example.com'
+ # resources:
+ # - '^/groups/dev/.*$'
+ # subject: 'group:dev'
+ # policy: two_factor
## Rules applied to user 'john'
- - domain: 'dev.example.com'
- resources:
- - '^/users/john/.*$'
- subject: 'user:john'
- policy: two_factor
+ # - domain: 'dev.example.com'
+ # resources:
+ # - '^/users/john/.*$'
+ # subject: 'user:john'
+ # policy: two_factor
## Rules applied to user 'harry'
- - domain: 'dev.example.com'
- resources:
- - '^/users/harry/.*$'
- subject: 'user:harry'
- policy: two_factor
+ # - domain: 'dev.example.com'
+ # resources:
+ # - '^/users/harry/.*$'
+ # subject: 'user:harry'
+ # policy: two_factor
## Rules applied to user 'bob'
- - domain: '*.mail.example.com'
- subject: 'user:bob'
- policy: two_factor
- - domain: 'dev.example.com'
- resources:
- - '^/users/bob/.*$'
- subject: 'user:bob'
- policy: two_factor
+ # - domain: '*.mail.example.com'
+ # subject: 'user:bob'
+ # policy: two_factor
+ # - domain: 'dev.example.com'
+ # resources:
+ # - '^/users/bob/.*$'
+ # subject: 'user:bob'
+ # policy: two_factor
##
## Session Provider Configuration
@@ -594,9 +698,9 @@ session:
##
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
- redis:
- host: 127.0.0.1
- port: 6379
+ # redis:
+ # host: 127.0.0.1
+ # port: 6379
## Use a unix socket instead
# host: /var/run/redis/redis.sock
@@ -604,30 +708,110 @@ session:
# username: authelia
## Password can also be set using a secret: https://www.authelia.com/c/secrets
- password: authelia
+ # password: authelia
## This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
- database_index: 0
+ # database_index: 0
## The maximum number of concurrent active connections to Redis.
- maximum_active_connections: 8
+ # maximum_active_connections: 8
## The target number of idle connections to have open ready for work. Useful when opening connections is slow.
- minimum_idle_connections: 0
+ # minimum_idle_connections: 0
## The Redis TLS configuration. If defined will require a TLS connection to the Redis instance(s).
# tls:
- ## Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host option.
# server_name: myredis.example.com
- ## Skip verifying the server certificate (to allow a self-signed certificate).
- ## In preference to setting this we strongly recommend you add the public portion of the certificate to the
- ## certificates directory which is defined by the `certificates_directory` option at the top of the config.
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
# skip_verify: false
## Minimum TLS version for the connection.
# minimum_version: TLS1.2
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
+
## The Redis HA configuration options.
## This provides specific options to Redis Sentinel, sentinel_name must be defined (Master Name).
# high_availability:
@@ -644,10 +828,10 @@ session:
## If the host in the above section is defined, it will be combined with this list to connect to sentinel.
## For high availability to be used you must have either defined; the host above or at least one node below.
# nodes:
- # - host: sentinel-node1
- # port: 6379
- # - host: sentinel-node2
- # port: 6379
+ # - host: sentinel-node1
+ # port: 6379
+ # - host: sentinel-node2
+ # port: 6379
## Choose the host with the lowest latency.
# route_by_latency: false
@@ -677,7 +861,7 @@ regulation:
## Storage Provider Configuration
##
## The available providers are: `local`, `mysql`, `postgres`. You must use one and only one of these providers.
-storage:
+# storage:
## The encryption key that is used to encrypt sensitive information in the database. Must be a string with a minimum
## length of 20. Please see the docs if you configure this with an undesirable key and need to change it.
# encryption_key: you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this
@@ -691,37 +875,219 @@ storage:
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
# local:
- # path: /config/db.sqlite3
+ ## Path to the SQLite3 Database.
+ # path: /config/db.sqlite3
##
## MySQL / MariaDB (Storage Provider)
##
- mysql:
- host: 127.0.0.1
- port: 3306
- database: authelia
- username: authelia
+ # mysql:
+ # host: 127.0.0.1
+ # port: 3306
+ # database: authelia
+ # username: authelia
## Password can also be set using a secret: https://www.authelia.com/c/secrets
- password: mypassword
- timeout: 5s
+ # password: mypassword
+ # timeout: 5s
+
+ ## MySQL TLS settings. Configuring this requires TLS.
+ # tls:
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host option.
+ # server_name: mysql.example.com
+
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
+ # skip_verify: false
+
+ ## Minimum TLS version for the connection.
+ # minimum_version: TLS1.2
+
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
##
## PostgreSQL (Storage Provider)
##
# postgres:
- # host: 127.0.0.1
- # port: 5432
- # database: authelia
- # schema: public
- # username: authelia
- # ## Password can also be set using a secret: https://www.authelia.com/c/secrets
- # password: mypassword
- # timeout: 5s
- # ssl:
- # mode: disable
- # root_certificate: disable
- # certificate: disable
- # key: disable
+ # host: 127.0.0.1
+ # port: 5432
+ # database: authelia
+ # schema: public
+ # username: authelia
+ ## Password can also be set using a secret: https://www.authelia.com/c/secrets
+ # password: mypassword
+ # timeout: 5s
+
+ ## PostgreSQL TLS settings. Configuring this requires TLS.
+ # tls:
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host option.
+ # server_name: postgres.example.com
+
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
+ # skip_verify: false
+
+ ## Minimum TLS version for the connection.
+ # minimum_version: TLS1.2
+
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
##
## Notification Provider
@@ -738,7 +1104,7 @@ notifier:
## Important: Kubernetes (or HA) users must read https://www.authelia.com/t/statelessness
##
# filesystem:
- # filename: /config/notification.txt
+ # filename: /config/notification.txt
##
## SMTP (Notification Provider)
@@ -750,55 +1116,135 @@ notifier:
## (only works for unauthenticated connections)
## - validate the SMTP server x509 certificate during the TLS handshake against the hosts trusted certificates
## (configure in tls section)
- smtp:
+ # smtp:
## The SMTP host to connect to.
- host: 127.0.0.1
+ # host: 127.0.0.1
## The port to connect to the SMTP host on.
- port: 1025
+ # port: 1025
## The connection timeout.
- timeout: 5s
+ # timeout: 5s
## The username used for SMTP authentication.
- username: test
+ # username: test
## The password used for SMTP authentication.
## Can also be set using a secret: https://www.authelia.com/c/secrets
- password: password
+ # password: password
## The sender is used to is used for the MAIL FROM command and the FROM header.
## If this is not defined and the username is an email, we use the username as this value. This can either be just
## an email address or the RFC5322 'Name ' format.
- sender: "Authelia "
+ # sender: "Authelia "
## HELO/EHLO Identifier. Some SMTP Servers may reject the default of localhost.
- identifier: localhost
+ # identifier: localhost
## Subject configuration of the emails sent. {title} is replaced by the text from the notifier.
- subject: "[Authelia] {title}"
+ # subject: "[Authelia] {title}"
## This address is used during the startup check to verify the email configuration is correct.
## It's not important what it is except if your email server only allows local delivery.
- startup_check_address: test@authelia.com
+ # startup_check_address: test@authelia.com
## By default we require some form of TLS. This disables this check though is not advised.
- disable_require_tls: false
+ # disable_require_tls: false
## Disables sending HTML formatted emails.
- disable_html_emails: false
+ # disable_html_emails: false
- tls:
- ## Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
+ # tls:
+ ## The server subject name to check the servers certificate against during the validation process.
+ ## This option is not required if the certificate has a SAN which matches the host option.
# server_name: smtp.example.com
- ## Skip verifying the server certificate (to allow a self-signed certificate).
- ## In preference to setting this we strongly recommend you add the public portion of the certificate to the
- ## certificates directory which is defined by the `certificates_directory` option at the top of the config.
- skip_verify: false
+ ## Skip verifying the server certificate entirely. In preference to setting this we strongly recommend you add the
+ ## certificate or the certificate of the authority signing the certificate to the certificates directory which is
+ ## defined by the `certificates_directory` option at the top of the configuration.
+ ## It's important to note the public key should be added to the directory, not the private key.
+ ## This option is strongly discouraged but may be useful in some self-signed situations where validation is not
+ ## important to the administrator.
+ # skip_verify: false
- ## Minimum TLS version for either StartTLS or SMTPS.
- minimum_version: TLS1.2
+ ## Minimum TLS version for the connection.
+ # minimum_version: TLS1.2
+
+ ## Maximum TLS version for the connection.
+ # maximum_version: TLS1.3
+
+ ## The certificate chain used with the private_key if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
+ ## The private key used with the certificate_chain if the server requests TLS Client Authentication
+ ## i.e. Mutual TLS.
+ # private_key: |
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
##
## Identity Providers
@@ -815,36 +1261,79 @@ notifier:
## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets
# hmac_secret: this_is_a_secret_abc123abc123abc
+ ## The issuer_certificate_chain is an optional PEM encoded certificate chain. It's used in conjunction with the
+ ## issuer_private_key to sign JWT's. All certificates in the chain must be within the validity period, and every
+ ## certificate included must be signed by the certificate immediately after it if provided.
+ # issuer_certificate_chain: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+ # /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+ # LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+ # 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+ # kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+ # Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+ # AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+ # AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+ # /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+ # lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+ # wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+ # OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ # ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+ # -----BEGIN CERTIFICATE-----
+ # MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
+ # EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+ # MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ # ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
+ # zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
+ # 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
+ # kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
+ # ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
+ # Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+ # AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+ # Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
+ # kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
+ # 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
+ # HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
+ # D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
+ # 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
+ # qocikt3WAdU^invalid DO NOT USE=
+ # -----END CERTIFICATE-----
+
## The issuer_private_key is used to sign the JWT forged by OpenID Connect.
## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets
# issuer_private_key: |
- # -----BEGIN RSA PRIVATE KEY-----
- # MXIEogIB$AKCAQEAxZVJP3WF//PG2fLQoEC9DtdiFG/+00vqlbVzz47nyxKONIPI
- # lmL3UdmqpGTKMe/5Brqse4ZAKlQHiDbwzK9ypnfigtHuvh/JO0S7ChP70RC67ed1
- # HV1nyfz5eW3llbtGJPrlYLqITNgctHp6zmRUFtSzPj9qFvozI93LJi492yL1+vu8
- # Un3Dm8+Qq6XM2tPdEcldB/dtBwOWoF+8eOOVsu0TDuB5bwlhBVGJuSAuzBPRS2bF
- # Ga4uk0JDdkDOMCEQxC5uWDFxgfERSMFyfLVWD47woDbuWEBq10c0z+dpWPMp7Ain
- # YnnkqicwCN88Z0zid6MmMQ65F4+9Hc+qC/p6xwIDAQABAoIBAGlhaAHKor+Su3o/
- # AXqXTL5/rbYMzbLQiLt0XeJT69jpeqMTroZXHmWvXE3128mqnf0yzw/K2Ko6yxGh
- # i+j/onya8FqpsVYCCgfsbn2/js1AyRJeIp6Y1ORsYnqbXJnxmkXa80AV/OBPW2/+
- # 60TtSdQrebY3iFPc+i2k+9bPTvpyyDLKlz8UwdZG+k5uyYNIyQTccz+PjwsIvDij
- # 7tKYamhhLN3QXt3/aZTFpjTgezP4WyriZxjWrddHowc47q2rwNS95ND39JcysJAc
- # 0Pcbu8A5lVa7Fx33uOtzDfKWIW7xVEN+OtPgN+FbTjXcXk5IZedl+pW5lU5P++G/
- # ZPvz+WECgYEA9g6HwdODW3e68bOqsFoKg35+vfUFMzlyMF8HFylNVfnLpTEDr637
- # owzMFvcUxVd71b+gV5nnnbI+riUFIgyR8vhCjhy4moopDPahC4/KwN4NG6uz+i1h
- # AB6D5+zn2BjnO/5xMMFGlApWtRNmJVGYlNDj3bXKh2VXzzy03VNeD8kCgYEAzZFL
- # OlzoRB1HKpTWIECcuvxofMxLOLb3zs0k2t/FYNYIpovmGWCCAULz13y53e5+/+5m
- # 7I9VUZJFaIhaZ36qVBApCKdru69pZMkWCcQO9jELFcx51Ez7OgJWzu7GS1QJCPKC
- # fEDxI0rZK21j93/Sl/nUnEir7CYpQ+wvCaGuHg8CgYAXgbncfY1+DokwkB6NbHy2
- # pT4Mfbz6cNGE538w6kQ2I4AeDvmwLentYMqaow478CinegAiflSPTzkHwAemghbr
- # ZGZPV1UXhn13fJRUG2+eT1hnPVcbXnx223N0k8Bud6qXo65CnyRT/kzcTbcjd5Eh
- # Hne2daicmMTzynPo9Q72aQKBgBmobO9X8VWvIdbaxO85oVZlctVA2pK1o7CYQmVf
- # UM+JZ4MCKzI3rYJizPS0iK5+ujNPmmEkcs2/qBIoEsCgOrpLWhPOcc/3UPxXbPzD
- # D+sCrBOIdhxdj23qJNOnUfDNCGOpgUfpAzAYg4q8GKInvi1h7XukRnEvQi9MJ4LY
- # P1dZAoGASGcGnTMkmeSXP8ux+dvQJAiJskn/sJIgBZ5uq5GRCeLBUosRSVxM75UK
- # vAh/c/RBj+pYXVKuPuHGZCQJxsdcRXzXNGouUtgbaYML5Me/Hagt20QzDRBfuGBg
- # qeZBJaXhjElvw6PUWtg4x+LYRCBpq/bS3LK3ozZrSTukVkKDegw=
- # -----END RSA PRIVATE KEY-----
+ # -----BEGIN RSA PRIVATE KEY-----
+ # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
+ # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
+ # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
+ # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
+ # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
+ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
+ # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
+ # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
+ # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
+ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
+ # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
+ # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
+ # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
+ # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
+ # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
+ # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
+ # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
+ # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
+ # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
+ # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
+ # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
+ # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
+ # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
+ # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
+ # DO NOT USE==
+ # -----END RSA PRIVATE KEY-----
## The lifespans configure the expiration for these token types.
# access_token_lifespan: 1h
@@ -866,17 +1355,17 @@ notifier:
# cors:
## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on.
# endpoints:
- # - authorization
- # - token
- # - revocation
- # - introspection
- # - userinfo
+ # - authorization
+ # - token
+ # - revocation
+ # - introspection
+ # - userinfo
## List of allowed origins.
## Any origin with https is permitted unless this option is configured or the
## allowed_origins_from_client_redirect_uris option is enabled.
# allowed_origins:
- # - https://example.com
+ # - https://example.com
## Automatically adds the origin portion of all redirect URI's on all clients to the list of allowed_origins,
## provided they have the scheme http or https and do not have the hostname of localhost.
@@ -905,10 +1394,12 @@ notifier:
## The policy to require for this client; one_factor or two_factor.
# authorization_policy: two_factor
- ## By default users cannot remember pre-configured consents. Setting this value to a period of time using a
- ## duration notation will enable users to remember consent for this client. The time configured is the amount
- ## of time the pre-configured consent is valid for granting new authorizations to the user.
- # pre_configured_consent_duration:
+ ## 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
## Audience this client is allowed to request.
# audience: []
diff --git a/internal/configuration/const.go b/internal/configuration/const.go
index 097da815c..a51fb8cbc 100644
--- a/internal/configuration/const.go
+++ b/internal/configuration/const.go
@@ -31,8 +31,9 @@ const (
errFmtSecretIOIssue = "secrets: error loading secret path %s into key '%s': %v"
errFmtGenerateConfiguration = "error occurred generating configuration: %+v"
- errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s: %w"
- errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s: %w"
+ errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s%s: %w"
+ errFmtDecodeHookCouldNotParseBasic = "could not decode to a %s%s: %w"
+ errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s%s: %w"
)
-var secretSuffixes = []string{"key", "secret", "password", "token"}
+var secretSuffixes = []string{"key", "secret", "password", "token", "certificate_chain"}
diff --git a/internal/configuration/decode_hooks.go b/internal/configuration/decode_hooks.go
index 406933f96..a5872627f 100644
--- a/internal/configuration/decode_hooks.go
+++ b/internal/configuration/decode_hooks.go
@@ -1,13 +1,18 @@
package configuration
import (
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "crypto/x509"
"fmt"
"net/mail"
"net/url"
"reflect"
"regexp"
+ "strings"
"time"
+ "github.com/go-crypt/crypt"
"github.com/mitchellh/mapstructure"
"github.com/authelia/authelia/v4/internal/configuration/schema"
@@ -16,18 +21,18 @@ import (
// StringToMailAddressHookFunc decodes a string into a mail.Address or *mail.Address.
func StringToMailAddressHookFunc() mapstructure.DecodeHookFuncType {
- return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
var ptr bool
if f.Kind() != reflect.String {
return data, nil
}
- kindStr := "mail.Address (RFC5322)"
+ prefixType := ""
if t.Kind() == reflect.Ptr {
ptr = true
- kindStr = "*" + kindStr
+ prefixType = "*"
}
expectedType := reflect.TypeOf(mail.Address{})
@@ -44,7 +49,7 @@ func StringToMailAddressHookFunc() mapstructure.DecodeHookFuncType {
if dataStr != "" {
if result, err = mail.ParseAddress(dataStr); err != nil {
- return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err)
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType.String()+" (RFC5322)", err)
}
}
@@ -62,18 +67,18 @@ func StringToMailAddressHookFunc() mapstructure.DecodeHookFuncType {
// StringToURLHookFunc converts string types into a url.URL or *url.URL.
func StringToURLHookFunc() mapstructure.DecodeHookFuncType {
- return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
var ptr bool
if f.Kind() != reflect.String {
return data, nil
}
- kindStr := "url.URL"
+ prefixType := ""
if t.Kind() == reflect.Ptr {
ptr = true
- kindStr = "*" + kindStr
+ prefixType = "*"
}
expectedType := reflect.TypeOf(url.URL{})
@@ -90,7 +95,7 @@ func StringToURLHookFunc() mapstructure.DecodeHookFuncType {
if dataStr != "" {
if result, err = url.Parse(dataStr); err != nil {
- return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err)
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
}
}
@@ -108,7 +113,7 @@ func StringToURLHookFunc() mapstructure.DecodeHookFuncType {
// ToTimeDurationHookFunc converts string and integer types to a time.Duration.
func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
- return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
var ptr bool
switch f.Kind() {
@@ -119,11 +124,11 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
return data, nil
}
- kindStr := "time.Duration"
+ prefixType := ""
if t.Kind() == reflect.Ptr {
ptr = true
- kindStr = "*" + kindStr
+ prefixType = "*"
}
expectedType := reflect.TypeOf(time.Duration(0))
@@ -141,7 +146,7 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
dataStr := data.(string)
if result, err = utils.ParseDurationString(dataStr); err != nil {
- return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err)
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
}
case f.Kind() == reflect.Int:
seconds := data.(int)
@@ -169,18 +174,18 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
// StringToRegexpHookFunc decodes a string into a *regexp.Regexp or regexp.Regexp.
func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {
- return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
var ptr bool
if f.Kind() != reflect.String {
return data, nil
}
- kindStr := "regexp.Regexp"
+ prefixType := ""
if t.Kind() == reflect.Ptr {
ptr = true
- kindStr = "*" + kindStr
+ prefixType = "*"
}
expectedType := reflect.TypeOf(regexp.Regexp{})
@@ -197,7 +202,7 @@ func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {
if dataStr != "" {
if result, err = regexp.Compile(dataStr); err != nil {
- return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err)
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
}
}
@@ -206,7 +211,7 @@ func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {
}
if result == nil {
- return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseEmptyValue, kindStr, errDecodeNonPtrMustHaveValue)
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseEmptyValue, prefixType, expectedType, errDecodeNonPtrMustHaveValue)
}
return *result, nil
@@ -215,18 +220,18 @@ func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {
// StringToAddressHookFunc decodes a string into an Address or *Address.
func StringToAddressHookFunc() mapstructure.DecodeHookFuncType {
- return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
var ptr bool
if f.Kind() != reflect.String {
return data, nil
}
- kindStr := "Address"
+ prefixType := ""
if t.Kind() == reflect.Ptr {
ptr = true
- kindStr = "*" + kindStr
+ prefixType = "*"
}
expectedType := reflect.TypeOf(schema.Address{})
@@ -242,7 +247,7 @@ func StringToAddressHookFunc() mapstructure.DecodeHookFuncType {
var result *schema.Address
if result, err = schema.NewAddressFromString(dataStr); err != nil {
- return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err)
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
}
if ptr {
@@ -252,3 +257,280 @@ func StringToAddressHookFunc() mapstructure.DecodeHookFuncType {
return *result, nil
}
}
+
+// StringToX509CertificateHookFunc decodes strings to x509.Certificate's.
+func StringToX509CertificateHookFunc() mapstructure.DecodeHookFuncType {
+ return func(f reflect.Type, t reflect.Type, data any) (value interface{}, err error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+
+ if t.Kind() != reflect.Ptr {
+ return data, nil
+ }
+
+ expectedType := reflect.TypeOf(x509.Certificate{})
+
+ if t.Elem() != expectedType {
+ return data, nil
+ }
+
+ dataStr := data.(string)
+
+ var result *x509.Certificate
+
+ if dataStr == "" {
+ return result, nil
+ }
+
+ var i interface{}
+
+ if i, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, err)
+ }
+
+ switch r := i.(type) {
+ case *x509.Certificate:
+ return r, nil
+ default:
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))
+ }
+ }
+}
+
+// StringToX509CertificateChainHookFunc decodes strings to schema.X509CertificateChain's.
+func StringToX509CertificateChainHookFunc() mapstructure.DecodeHookFuncType {
+ return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ var ptr bool
+
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+
+ prefixType := ""
+
+ if t.Kind() == reflect.Ptr {
+ ptr = true
+ prefixType = "*"
+ }
+
+ expectedType := reflect.TypeOf(schema.X509CertificateChain{})
+
+ if ptr && t.Elem() != expectedType {
+ return data, nil
+ } else if !ptr && t != expectedType {
+ return data, nil
+ }
+
+ dataStr := data.(string)
+
+ var result *schema.X509CertificateChain
+
+ if dataStr == "" && ptr {
+ return result, nil
+ }
+
+ if result, err = schema.NewX509CertificateChain(dataStr); err != nil {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, prefixType, expectedType, err)
+ }
+
+ if ptr {
+ return result, nil
+ }
+
+ if result == nil {
+ return schema.X509CertificateChain{}, nil
+ }
+
+ return *result, nil
+ }
+}
+
+// StringToTLSVersionHookFunc decodes strings to schema.TLSVersion's.
+func StringToTLSVersionHookFunc() mapstructure.DecodeHookFuncType {
+ return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ var ptr bool
+
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+
+ prefixType := ""
+
+ if t.Kind() == reflect.Ptr {
+ ptr = true
+ prefixType = "*"
+ }
+
+ expectedType := reflect.TypeOf(schema.TLSVersion{})
+
+ if ptr && t.Elem() != expectedType {
+ return data, nil
+ } else if !ptr && t != expectedType {
+ return data, nil
+ }
+
+ dataStr := data.(string)
+
+ var result *schema.TLSVersion
+
+ if result, err = schema.NewTLSVersion(dataStr); err != nil {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
+ }
+
+ if ptr {
+ return result, nil
+ }
+
+ return *result, nil
+ }
+}
+
+// StringToCryptoPrivateKeyHookFunc decodes strings to schema.CryptographicPrivateKey's.
+func StringToCryptoPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
+ return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+
+ field, _ := reflect.TypeOf(schema.TLSConfig{}).FieldByName("PrivateKey")
+ expectedType := field.Type
+
+ if t != expectedType {
+ return data, nil
+ }
+
+ dataStr := data.(string)
+
+ var i any
+
+ if i, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "", expectedType, err)
+ }
+
+ if result, ok := i.(schema.CryptographicPrivateKey); !ok {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "", expectedType, fmt.Errorf("the data is for a %T not a %s", i, expectedType))
+ } else {
+ return result, nil
+ }
+ }
+}
+
+// StringToPrivateKeyHookFunc decodes strings to rsa.PrivateKey's.
+func StringToPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
+ return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+
+ if t.Kind() != reflect.Ptr {
+ return data, nil
+ }
+
+ expectedTypeRSA := reflect.TypeOf(rsa.PrivateKey{})
+ expectedTypeECDSA := reflect.TypeOf(ecdsa.PrivateKey{})
+
+ var (
+ i any
+ expectedType reflect.Type
+ )
+
+ dataStr := data.(string)
+
+ switch t.Elem() {
+ case expectedTypeRSA:
+ var result *rsa.PrivateKey
+
+ if dataStr == "" {
+ return result, nil
+ }
+
+ expectedType = expectedTypeRSA
+ case expectedTypeECDSA:
+ var result *ecdsa.PrivateKey
+
+ if dataStr == "" {
+ return result, nil
+ }
+
+ expectedType = expectedTypeECDSA
+ default:
+ return data, nil
+ }
+
+ if i, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, err)
+ }
+
+ switch r := i.(type) {
+ case *rsa.PrivateKey:
+ if expectedType != expectedTypeRSA {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))
+ }
+
+ if err = r.Validate(); err != nil {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, err)
+ }
+
+ return r, nil
+ case *ecdsa.PrivateKey:
+ if expectedType != expectedTypeECDSA {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))
+ }
+
+ return r, nil
+ default:
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))
+ }
+ }
+}
+
+// StringToPasswordDigestHookFunc decodes a string into a crypt.Digest.
+func StringToPasswordDigestHookFunc(plaintext bool) mapstructure.DecodeHookFuncType {
+ return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
+ var ptr bool
+
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+
+ prefixType := ""
+
+ if t.Kind() == reflect.Ptr {
+ ptr = true
+ prefixType = "*"
+ }
+
+ expectedType := reflect.TypeOf(schema.PasswordDigest{})
+
+ if ptr && t.Elem() != expectedType {
+ return data, nil
+ } else if !ptr && t != expectedType {
+ return data, nil
+ }
+
+ dataStr := data.(string)
+
+ var result *schema.PasswordDigest
+
+ if !strings.HasPrefix(dataStr, "$") {
+ dataStr = fmt.Sprintf(crypt.StorageFormatSimple, crypt.AlgorithmPrefixPlainText, dataStr)
+ }
+
+ if dataStr != "" {
+ if result, err = schema.NewPasswordDigest(dataStr, plaintext); err != nil {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType.String(), err)
+ }
+ }
+
+ if ptr {
+ return result, nil
+ }
+
+ if result == nil {
+ return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseEmptyValue, prefixType, expectedType.String(), errDecodeNonPtrMustHaveValue)
+ }
+
+ return *result, nil
+ }
+}
diff --git a/internal/configuration/decode_hooks_test.go b/internal/configuration/decode_hooks_test.go
index cbf653394..b13d350c7 100644
--- a/internal/configuration/decode_hooks_test.go
+++ b/internal/configuration/decode_hooks_test.go
@@ -1,6 +1,11 @@
package configuration_test
import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
"net/mail"
"net/url"
"reflect"
@@ -18,8 +23,8 @@ import (
func TestStringToMailAddressHookFunc(t *testing.T) {
testCases := []struct {
desc string
- have interface{}
- want interface{}
+ have any
+ want any
err string
decode bool
}{
@@ -73,8 +78,8 @@ func TestStringToMailAddressHookFunc(t *testing.T) {
func TestStringToMailAddressHookFuncPointer(t *testing.T) {
testCases := []struct {
desc string
- have interface{}
- want interface{}
+ have any
+ want any
err string
decode bool
}{
@@ -134,8 +139,8 @@ func TestStringToMailAddressHookFuncPointer(t *testing.T) {
func TestStringToURLHookFunc(t *testing.T) {
testCases := []struct {
desc string
- have interface{}
- want interface{}
+ have any
+ want any
err string
decode bool
}{
@@ -207,8 +212,8 @@ func TestStringToURLHookFunc(t *testing.T) {
func TestStringToURLHookFuncPointer(t *testing.T) {
testCases := []struct {
desc string
- have interface{}
- want interface{}
+ have any
+ want any
err string
decode bool
}{
@@ -280,8 +285,8 @@ func TestStringToURLHookFuncPointer(t *testing.T) {
func TestToTimeDurationHookFunc(t *testing.T) {
testCases := []struct {
desc string
- have interface{}
- want interface{}
+ have any
+ want any
err string
decode bool
}{
@@ -400,8 +405,8 @@ func TestToTimeDurationHookFunc(t *testing.T) {
func TestToTimeDurationHookFuncPointer(t *testing.T) {
testCases := []struct {
desc string
- have interface{}
- want interface{}
+ have any
+ want any
err string
decode bool
}{
@@ -521,8 +526,8 @@ func TestToTimeDurationHookFuncPointer(t *testing.T) {
func TestStringToRegexpFunc(t *testing.T) {
testCases := []struct {
desc string
- have interface{}
- want interface{}
+ have any
+ want any
err string
decode bool
wantGrps []string
@@ -635,8 +640,8 @@ func TestStringToRegexpFunc(t *testing.T) {
func TestStringToRegexpFuncPointers(t *testing.T) {
testCases := []struct {
desc string
- have interface{}
- want interface{}
+ have any
+ want any
err string
decode bool
wantGrps []string
@@ -770,8 +775,8 @@ func TestStringToAddressHookFunc(t *testing.T) {
testCases := []struct {
name string
- have interface{}
- expected interface{}
+ have any
+ expected any
err string
decode bool
}{
@@ -833,7 +838,7 @@ func TestStringToAddressHookFunc(t *testing.T) {
name: "ShouldFailDecode",
have: "tcp://&!@^#*&!@#&*@!:2020",
expected: schema.Address{},
- err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [://][:]: parse \"tcp://&!@^\": invalid character \"^\" in host name",
+ err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a schema.Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [://][:]: parse \"tcp://&!@^\": invalid character \"^\" in host name",
decode: false,
},
}
@@ -862,6 +867,506 @@ func TestStringToAddressHookFunc(t *testing.T) {
}
}
+func TestStringToPrivateKeyHookFunc(t *testing.T) {
+ var (
+ nilRSA *rsa.PrivateKey
+ nilECDSA *ecdsa.PrivateKey
+ nilCert *x509.Certificate
+ )
+
+ testCases := []struct {
+ desc string
+ have any
+ want any
+ err string
+ decode bool
+ }{
+ {
+ desc: "ShouldDecodeRSAPrivateKey",
+ have: x509PrivateKeyRSA1,
+ want: MustParseRSAPrivateKey(x509PrivateKeyRSA1),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeECDSAPrivateKey",
+ have: x509PrivateKeyEC1,
+ want: MustParseECDSAPrivateKey(x509PrivateKeyEC1),
+ decode: true,
+ },
+ {
+ desc: "ShouldNotDecodeToECDSAPrivateKey",
+ have: x509PrivateKeyRSA1,
+ want: &ecdsa.PrivateKey{},
+ decode: true,
+ err: "could not decode to a *ecdsa.PrivateKey: the data is for a *rsa.PrivateKey not a *ecdsa.PrivateKey",
+ },
+ {
+ desc: "ShouldNotDecodeEmptyRSAKey",
+ have: "",
+ want: nilRSA,
+ decode: true,
+ },
+ {
+ desc: "ShouldNotDecodeEmptyECDSAKey",
+ have: "",
+ want: nilECDSA,
+ decode: true,
+ },
+ {
+ desc: "ShouldNotDecodeECDSAKeyToRSAKey",
+ have: x509PrivateKeyEC1,
+ want: nilRSA,
+ decode: true,
+ err: "could not decode to a *rsa.PrivateKey: the data is for a *ecdsa.PrivateKey not a *rsa.PrivateKey",
+ },
+ {
+ desc: "ShouldNotDecodeRSAKeyToECDSAKey",
+ have: x509PrivateKeyRSA1,
+ want: nilECDSA,
+ decode: true,
+ err: "could not decode to a *ecdsa.PrivateKey: the data is for a *rsa.PrivateKey not a *ecdsa.PrivateKey",
+ },
+ {
+ desc: "ShouldNotDecodeBadRSAPrivateKey",
+ have: x509PrivateKeyRSA2,
+ want: nilRSA,
+ decode: true,
+ err: "could not decode to a *rsa.PrivateKey: failed to parse PEM block containing the key",
+ },
+ {
+ desc: "ShouldNotDecodeBadECDSAPrivateKey",
+ have: x509PrivateKeyEC2,
+ want: nilECDSA,
+ decode: true,
+ err: "could not decode to a *ecdsa.PrivateKey: failed to parse PEM block containing the key",
+ },
+ {
+ desc: "ShouldNotDecodeCertificateToRSAPrivateKey",
+ have: x509CertificateRSA1,
+ want: nilRSA,
+ decode: true,
+ err: "could not decode to a *rsa.PrivateKey: the data is for a *x509.Certificate not a *rsa.PrivateKey",
+ },
+ {
+ desc: "ShouldNotDecodeCertificateToECDSAPrivateKey",
+ have: x509CertificateRSA1,
+ want: nilECDSA,
+ decode: true,
+ err: "could not decode to a *ecdsa.PrivateKey: the data is for a *x509.Certificate not a *ecdsa.PrivateKey",
+ },
+ {
+ desc: "ShouldNotDecodeRSAKeyToCertificate",
+ have: x509PrivateKeyRSA1,
+ want: nilCert,
+ decode: false,
+ },
+ {
+ desc: "ShouldNotDecodeECDSAKeyToCertificate",
+ have: x509PrivateKeyEC1,
+ want: nilCert,
+ decode: false,
+ },
+ }
+
+ hook := configuration.StringToPrivateKeyHookFunc()
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
+ switch {
+ case !tc.decode:
+ assert.NoError(t, err)
+ assert.Equal(t, tc.have, result)
+ case tc.err == "":
+ assert.NoError(t, err)
+ require.Equal(t, tc.want, result)
+ default:
+ assert.EqualError(t, err, tc.err)
+ assert.Nil(t, result)
+ }
+ })
+ }
+}
+
+func TestStringToX509CertificateHookFunc(t *testing.T) {
+ var nilkey *x509.Certificate
+
+ testCases := []struct {
+ desc string
+ have any
+ want any
+ err string
+ decode bool
+ }{
+ {
+ desc: "ShouldDecodeRSACertificate",
+ have: x509CertificateRSA1,
+ want: MustParseX509Certificate(x509CertificateRSA1),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeECDSACertificate",
+ have: x509CACertificateECDSA,
+ want: MustParseX509Certificate(x509CACertificateECDSA),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeRSACACertificate",
+ have: x509CACertificateRSA,
+ want: MustParseX509Certificate(x509CACertificateRSA),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeECDSACACertificate",
+ have: x509CACertificateECDSA,
+ want: MustParseX509Certificate(x509CACertificateECDSA),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeEmptyCertificateToNil",
+ have: "",
+ want: nilkey,
+ decode: true,
+ },
+ {
+ desc: "ShouldNotDecodeECDSAKeyToCertificate",
+ have: x509PrivateKeyEC1,
+ want: nilkey,
+ decode: true,
+ err: "could not decode to a *x509.Certificate: the data is for a *ecdsa.PrivateKey not a *x509.Certificate",
+ },
+ {
+ desc: "ShouldNotDecodeBadRSAPrivateKeyToCertificate",
+ have: x509PrivateKeyRSA2,
+ want: nilkey,
+ decode: true,
+ err: "could not decode to a *x509.Certificate: failed to parse PEM block containing the key",
+ },
+ }
+
+ hook := configuration.StringToX509CertificateHookFunc()
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
+ switch {
+ case !tc.decode:
+ assert.NoError(t, err)
+ assert.Equal(t, tc.have, result)
+ case tc.err == "":
+ assert.NoError(t, err)
+ require.Equal(t, tc.want, result)
+ default:
+ assert.EqualError(t, err, tc.err)
+ assert.Nil(t, result)
+ }
+ })
+ }
+}
+
+func TestStringToX509CertificateChainHookFunc(t *testing.T) {
+ var nilkey *schema.X509CertificateChain
+
+ testCases := []struct {
+ desc string
+ have any
+ expected any
+ err, verr string
+ decode bool
+ }{
+ {
+ desc: "ShouldDecodeRSACertificate",
+ have: x509CertificateRSA1,
+ expected: MustParseX509CertificateChain(x509CertificateRSA1),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeRSACertificateNoPtr",
+ have: x509CertificateRSA1,
+ expected: *MustParseX509CertificateChain(x509CertificateRSA1),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeRSACertificateChain",
+ have: BuildChain(x509CertificateRSA1, x509CACertificateRSA),
+ expected: MustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateRSA),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeRSACertificateChainNoPtr",
+ have: BuildChain(x509CertificateRSA1, x509CACertificateRSA),
+ expected: *MustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateRSA),
+ decode: true,
+ },
+ {
+ desc: "ShouldNotDecodeBadRSACertificateChain",
+ have: BuildChain(x509CertificateRSA1, x509CACertificateECDSA),
+ expected: MustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateECDSA),
+ verr: "certificate #1 in chain is not signed properly by certificate #2 in chain: x509: signature algorithm specifies an RSA public key, but have public key of type *ecdsa.PublicKey",
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeECDSACertificate",
+ have: x509CACertificateECDSA,
+ expected: MustParseX509CertificateChain(x509CACertificateECDSA),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeRSACACertificate",
+ have: x509CACertificateRSA,
+ expected: MustParseX509CertificateChain(x509CACertificateRSA),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeECDSACACertificate",
+ have: x509CACertificateECDSA,
+ expected: MustParseX509CertificateChain(x509CACertificateECDSA),
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeEmptyCertificateToNil",
+ have: "",
+ expected: nilkey,
+ decode: true,
+ },
+ {
+ desc: "ShouldDecodeEmptyCertificateToEmptyStruct",
+ have: "",
+ expected: schema.X509CertificateChain{},
+ decode: true,
+ },
+ {
+ desc: "ShouldNotDecodeECDSAKeyToCertificate",
+ have: x509PrivateKeyEC1,
+ expected: nilkey,
+ decode: true,
+ err: "could not decode to a *schema.X509CertificateChain: the PEM data chain contains a EC PRIVATE KEY but only certificates are expected",
+ },
+ {
+ desc: "ShouldNotDecodeBadRSAPrivateKeyToCertificate",
+ have: x509PrivateKeyRSA2,
+ expected: nilkey,
+ decode: true,
+ err: "could not decode to a *schema.X509CertificateChain: invalid PEM block",
+ },
+ }
+
+ hook := configuration.StringToX509CertificateChainHookFunc()
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
+ switch {
+ case !tc.decode:
+ assert.NoError(t, err)
+ assert.Equal(t, tc.have, actual)
+ case tc.err == "":
+ assert.NoError(t, err)
+ require.Equal(t, tc.expected, actual)
+
+ if tc.expected == nilkey {
+ break
+ }
+
+ switch chain := actual.(type) {
+ case *schema.X509CertificateChain:
+ require.NotNil(t, chain)
+ if tc.verr == "" {
+ assert.NoError(t, chain.Validate())
+ } else {
+ assert.EqualError(t, chain.Validate(), tc.verr)
+ }
+ case schema.X509CertificateChain:
+ require.NotNil(t, chain)
+ if tc.verr == "" {
+ assert.NoError(t, chain.Validate())
+ } else {
+ assert.EqualError(t, chain.Validate(), tc.verr)
+ }
+ }
+ default:
+ assert.EqualError(t, err, tc.err)
+ assert.Nil(t, actual)
+ }
+ })
+ }
+}
+
+var (
+ x509PrivateKeyRSA1 = `
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
+cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
+ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
+Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
+rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
+EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
+L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
+Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
+9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
+8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
+I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
+CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
+hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
+jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
+E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
+CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
+jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
+Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
+Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
+bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
+otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
+HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
+tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
+USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
+1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
+-----END RSA PRIVATE KEY-----`
+
+ x509PrivateKeyRSA2 = `
+-----BEGIN RSA PRIVATE KEY-----
+bad key
+-----END RSA PRIVATE KEY-----`
+
+ x509PrivateKeyEC1 = `
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMn970LSn8aKVhBM4vyUmpZyEdCT4riN+Lp4QU04zUhYoAoGCCqGSM49
+AwEHoUQDQgAEMD69n22nd78GmaRDzy/s7muqhbc/OEnFS2mNtiRAA5FaX+kbkCB5
+8pu/k2jkaSVNZtBYKPVAibHkhvakjVb66A==
+-----END EC PRIVATE KEY-----`
+
+ x509PrivateKeyEC2 = `
+-----BEGIN EC PRIVATE KEY-----
+bad key
+-----END EC PRIVATE KEY-----`
+
+ x509CertificateRSA1 = `
+-----BEGIN CERTIFICATE-----
+MIIC5TCCAc2gAwIBAgIQfBUmKLmEvMqS6S9auKCY2DANBgkqhkiG9w0BAQsFADAT
+MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMjA5MDgxMDA5MThaFw0yMzA5MDgxMDA5
+MThaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEApqno1cOpDcgKmOJqeDQGIGH5/ZnqcJ4xud6eOUfbDqel3b0RkAQX
+mFYWEDO/PDOAOjYk/xSwZGo3jDofOHGhrKstQqLdweHGfme5NXYHJda7nGv/OY5q
+zUuEG4xBVgUsvbshWZ18H+bIQpwiP6tDAabxc0B7J15F1pArK8QN4pDTfsqZDwMi
+Qyo638XfUbDzEVZRbdDKxHz5g0w2vFdXon8uOxRRb0+zlHF9nM4PiESNgiUIYeua
+8Q5yP10SY2k9zlQ/OFJ4XhQmioCJvNjJE/TSc5/ECub2n7hTZhN5TGKagukZ5NAy
+KgbvNYW+CN+H4pFJt/9WptiDfBqhlUvjnwIDAQABozUwMzAOBgNVHQ8BAf8EBAMC
+BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B
+AQsFAAOCAQEAH9veGzfWqXxsa5s2KHV2Jzed9V8KSs1Qy9QKRez1i2OMvMPh2DRM
+RLzAAp/XigjxLQF/LFXuoFW0Qg8BRb44iRgZrCiqVOUnd3xTrS/CcFExnpQI4F12
+/U70o97rkTonCOHmUUW6vQfWSXR/GU3/faRLJjiqcpWLZhTQNrnsip1ym3B2NMdk
+gMKkT8Acx1DX48MvTE4+DyqCS8TlJbacBJ2RFFELKu3jYnVNyrb0ywLxoCtWqBBE
+veVj+VMn9hNY1u5uydLsUDOlT5QyQcEuUzjjdhsJKEgDE5daNtB2OJJnd9IOMzUA
+hasPZETCCKabTpWiEPw1Cn/ZRqya0SZqFg==
+-----END CERTIFICATE-----
+`
+
+ x509CACertificateRSA = `
+-----BEGIN CERTIFICATE-----
+MIIDBDCCAeygAwIBAgIRAJfz0dHS9UkDngE55lUPdu4wDQYJKoZIhvcNAQELBQAw
+EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNMjIwOTA4MDk1OTI1WhcNMjMwOTA4MDk1
+OTI1WjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALfivbwq9r5X8N+NSbNHVuKbCb9f9vD5Xw2pOjSVvVjFkWQ1YKJu
+JGx9yskhHBZTBt76cInipA+0PqCBrBrjij1lh2StvzRVuQwgFG6H01LxBPi0JyYv
+Is94F6PHr6fSBgFWB5GNQ797KQIOdIr057uEFbp0eBMxxqiQ9gdyD0HPretrx1Uy
+kHuF6jck958combn9luHW0i53mt8706j7UAhxFqu9YUeklTM1VqUiRm5+nJKIdNA
+LiDMGVAuoxjhF6aIgY0yh5mL5mKtYYzhtA8WryrMzBgFRUGzHCSI1TNisA8wSf2T
+Z2JhbFHrFPR5fiSqAEHok3UXu++wsfl/lisCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
+AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+OSXG42bCPNuWeP0ahScUMVjxe/wwDQYJKoZIhvcNAQELBQADggEBAFRnubHiYy1H
+PGODKA++UY9eAmmaCJxzuWpY8FY9fBz8/VBzcp8vaURPmmQ/34QcqfaSHDM2jIaL
+dQ2o9Ae5NjbRzLB6a5DcVO50oHG4BHP1ix4Bt3POr8J80KgA9pOIyAQqbAlFBSzQ
+l9yrzVULyf+qpUmByRf5qy2kQJOBfMJbn5j+BprWKwbcI8OAZWWSLItTXqJDrFTk
+OMZK4wZ6KiZM07KWMlwW/CE0QRzDk5MXfbwRt4D8pyx6rGKqI7QRusjm5osIpHZV
+26FdBdBvEhq4i8UsmDsQqH3iMY1AKmojZToZb5rStOZWHO/BZZ7nT2bscNjwm0E8
+6E2l6czk8ss=
+-----END CERTIFICATE-----`
+
+ x509CACertificateECDSA = `
+-----BEGIN CERTIFICATE-----
+MIIBdzCCAR2gAwIBAgIQUzb62irYb/7B2H0c1AbriDAKBggqhkjOPQQDAjATMREw
+DwYDVQQKEwhBdXRoZWxpYTAeFw0yMjA5MDgxMDEzNDZaFw0yMzA5MDgxMDEzNDZa
+MBMxETAPBgNVBAoTCEF1dGhlbGlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
+b/EiIBpmifCI34JdI7luetygue2rTtoNH0QXhtrjMuZNugT29LUz+DobZQxvGsOY
+4TXzAQXq4gnTb7enNWFgsaNTMFEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdJQQIMAYG
+BFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUxlDPBKHKawuvhtQTN874
+TeCEKjkwCgYIKoZIzj0EAwIDSAAwRQIgAQeV01FZ/VkSERwaRKTeXAXxmKyc/05O
+uDv6M2spMi0CIQC8uOSMcv11vp1ylsGg38N6XYA+GQa1BHRd79+91hC+7w==
+-----END CERTIFICATE-----`
+)
+
+func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
+ block, _ := pem.Decode([]byte(data))
+ if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
+ panic("not pem encoded")
+ }
+
+ if block.Type != "RSA PRIVATE KEY" {
+ panic("not rsa private key")
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ return key
+}
+
+func MustParseECDSAPrivateKey(data string) *ecdsa.PrivateKey {
+ block, _ := pem.Decode([]byte(data))
+ if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
+ panic("not pem encoded")
+ }
+
+ if block.Type != "EC PRIVATE KEY" {
+ panic("not ecdsa private key")
+ }
+
+ key, err := x509.ParseECPrivateKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ return key
+}
+
+func MustParseX509Certificate(data string) *x509.Certificate {
+ block, _ := pem.Decode([]byte(data))
+ if block == nil || len(block.Bytes) == 0 {
+ panic("not a PEM")
+ }
+
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ return cert
+}
+
+func BuildChain(pems ...string) string {
+ buf := bytes.Buffer{}
+
+ for i, data := range pems {
+ if i != 0 {
+ buf.WriteString("\n")
+ }
+
+ buf.WriteString(data)
+ }
+
+ return buf.String()
+}
+
+func MustParseX509CertificateChain(datas ...string) *schema.X509CertificateChain {
+ chain, err := schema.NewX509CertificateChain(BuildChain(datas...))
+ if err != nil {
+ panic(err)
+ }
+
+ return chain
+}
+
func testInt32Ptr(i int32) *int32 {
return &i
}
diff --git a/internal/configuration/deprecation.go b/internal/configuration/deprecation.go
index 040d346c2..fbe881ef1 100644
--- a/internal/configuration/deprecation.go
+++ b/internal/configuration/deprecation.go
@@ -10,7 +10,7 @@ type Deprecation struct {
Key string
NewKey string
AutoMap bool
- MapFunc func(value interface{}) interface{}
+ MapFunc func(value any) any
ErrText string
}
diff --git a/internal/configuration/helpers.go b/internal/configuration/helpers.go
index 0f5e0b215..22b793e9a 100644
--- a/internal/configuration/helpers.go
+++ b/internal/configuration/helpers.go
@@ -12,15 +12,12 @@ func getEnvConfigMap(keys []string, prefix, delimiter string) (keyMap map[string
for _, key := range keys {
if strings.Contains(key, delimiter) {
- originalKey := prefix + strings.ToUpper(strings.ReplaceAll(key, constDelimiter, delimiter))
- keyMap[originalKey] = key
+ keyMap[ToEnvironmentKey(key, prefix, delimiter)] = key
}
// Secret envs should be ignored by the env parser.
- if isSecretKey(key) {
- originalKey := strings.ToUpper(strings.ReplaceAll(key, constDelimiter, delimiter)) + constSecretSuffix
-
- ignoredKeys = append(ignoredKeys, prefix+originalKey)
+ if IsSecretKey(key) {
+ ignoredKeys = append(ignoredKeys, ToEnvironmentSecretKey(key, prefix, delimiter))
}
}
@@ -31,7 +28,7 @@ func getSecretConfigMap(keys []string, prefix, delimiter string) (keyMap map[str
keyMap = make(map[string]string)
for _, key := range keys {
- if isSecretKey(key) {
+ if IsSecretKey(key) {
originalKey := strings.ToUpper(strings.ReplaceAll(key, constDelimiter, delimiter)) + constSecretSuffix
keyMap[prefix+originalKey] = key
@@ -41,7 +38,22 @@ func getSecretConfigMap(keys []string, prefix, delimiter string) (keyMap map[str
return keyMap
}
-func isSecretKey(key string) (isSecretKey bool) {
+// ToEnvironmentKey converts a key into the environment variable name.
+func ToEnvironmentKey(key, prefix, delimiter string) string {
+ return prefix + strings.ToUpper(strings.ReplaceAll(key, constDelimiter, delimiter))
+}
+
+// ToEnvironmentSecretKey converts a key into the environment variable name.
+func ToEnvironmentSecretKey(key, prefix, delimiter string) string {
+ return prefix + strings.ToUpper(strings.ReplaceAll(key, constDelimiter, delimiter)) + constSecretSuffix
+}
+
+// IsSecretKey returns true if the provided key is a secret enabled key.
+func IsSecretKey(key string) (isSecretKey bool) {
+ if strings.Contains(key, "[]") {
+ return false
+ }
+
return utils.IsStringInSliceSuffix(key, secretSuffixes)
}
diff --git a/internal/configuration/helpers_test.go b/internal/configuration/helpers_test.go
index b244d4dcf..cc8cd1180 100644
--- a/internal/configuration/helpers_test.go
+++ b/internal/configuration/helpers_test.go
@@ -7,12 +7,12 @@ import (
)
func TestIsSecretKey(t *testing.T) {
- assert.True(t, isSecretKey("my_fake_token"))
- assert.False(t, isSecretKey("my_fake_tokenz"))
- assert.True(t, isSecretKey("my_.fake.secret"))
- assert.True(t, isSecretKey("my.password"))
- assert.False(t, isSecretKey("my.passwords"))
- assert.False(t, isSecretKey("my.passwords"))
+ assert.True(t, IsSecretKey("my_fake_token"))
+ assert.False(t, IsSecretKey("my_fake_tokenz"))
+ assert.True(t, IsSecretKey("my_.fake.secret"))
+ assert.True(t, IsSecretKey("my.password"))
+ assert.False(t, IsSecretKey("my.passwords"))
+ assert.False(t, IsSecretKey("my.passwords"))
}
func TestGetEnvConfigMaps(t *testing.T) {
diff --git a/internal/configuration/koanf_callbacks.go b/internal/configuration/koanf_callbacks.go
index 26d11ebcc..d62984eb1 100644
--- a/internal/configuration/koanf_callbacks.go
+++ b/internal/configuration/koanf_callbacks.go
@@ -11,8 +11,8 @@ import (
)
// koanfEnvironmentCallback returns a koanf callback to map the environment vars to Configuration keys.
-func koanfEnvironmentCallback(keyMap map[string]string, ignoredKeys []string, prefix, delimiter string) func(key, value string) (finalKey string, finalValue interface{}) {
- return func(key, value string) (finalKey string, finalValue interface{}) {
+func koanfEnvironmentCallback(keyMap map[string]string, ignoredKeys []string, prefix, delimiter string) func(key, value string) (finalKey string, finalValue any) {
+ return func(key, value string) (finalKey string, finalValue any) {
if k, ok := keyMap[key]; ok {
return k, value
}
@@ -33,8 +33,8 @@ func koanfEnvironmentCallback(keyMap map[string]string, ignoredKeys []string, pr
}
// koanfEnvironmentSecretsCallback returns a koanf callback to map the environment vars to Configuration keys.
-func koanfEnvironmentSecretsCallback(keyMap map[string]string, validator *schema.StructValidator) func(key, value string) (finalKey string, finalValue interface{}) {
- return func(key, value string) (finalKey string, finalValue interface{}) {
+func koanfEnvironmentSecretsCallback(keyMap map[string]string, validator *schema.StructValidator) func(key, value string) (finalKey string, finalValue any) {
+ return func(key, value string) (finalKey string, finalValue any) {
k, ok := keyMap[key]
if !ok {
return "", nil
@@ -50,8 +50,8 @@ func koanfEnvironmentSecretsCallback(keyMap map[string]string, validator *schema
}
}
-func koanfCommandLineWithMappingCallback(mapping map[string]string, includeValidKeys, includeUnchangedKeys bool) func(flag *pflag.Flag) (string, interface{}) {
- return func(flag *pflag.Flag) (string, interface{}) {
+func koanfCommandLineWithMappingCallback(mapping map[string]string, includeValidKeys, includeUnchangedKeys bool) func(flag *pflag.Flag) (string, any) {
+ return func(flag *pflag.Flag) (string, any) {
if !includeUnchangedKeys && !flag.Changed {
return "", nil
}
diff --git a/internal/configuration/koanf_callbacks_test.go b/internal/configuration/koanf_callbacks_test.go
index 107884ab7..43461fe28 100644
--- a/internal/configuration/koanf_callbacks_test.go
+++ b/internal/configuration/koanf_callbacks_test.go
@@ -16,7 +16,7 @@ import (
func TestKoanfEnvironmentCallback(t *testing.T) {
var (
key string
- value interface{}
+ value any
)
keyMap := map[string]string{
@@ -46,7 +46,7 @@ func TestKoanfEnvironmentCallback(t *testing.T) {
func TestKoanfSecretCallbackWithValidSecrets(t *testing.T) {
var (
key string
- value interface{}
+ value any
)
keyMap := map[string]string{
diff --git a/internal/configuration/koanf_util.go b/internal/configuration/koanf_util.go
index 589b4b05e..e656cf25c 100644
--- a/internal/configuration/koanf_util.go
+++ b/internal/configuration/koanf_util.go
@@ -15,13 +15,13 @@ func koanfGetKeys(ko *koanf.Koanf) (keys []string) {
keys = ko.Keys()
for key, value := range ko.All() {
- slc, ok := value.([]interface{})
+ slc, ok := value.([]any)
if !ok {
continue
}
for _, item := range slc {
- m, mok := item.(map[string]interface{})
+ m, mok := item.(map[string]any)
if !mok {
continue
}
@@ -53,7 +53,7 @@ func koanfRemapKeys(val *schema.StructValidator, ko *koanf.Koanf, ds map[string]
return final, nil
}
-func koanfRemapKeysStandard(keys map[string]interface{}, val *schema.StructValidator, ds map[string]Deprecation) (keysFinal map[string]interface{}) {
+func koanfRemapKeysStandard(keys map[string]any, val *schema.StructValidator, ds map[string]Deprecation) (keysFinal map[string]interface{}) {
var (
ok bool
d Deprecation
diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go
index 111e5c041..e64a3b661 100644
--- a/internal/configuration/provider.go
+++ b/internal/configuration/provider.go
@@ -19,7 +19,7 @@ func Load(val *schema.StructValidator, sources ...Source) (keys []string, config
}
// LoadAdvanced is intended to give more flexibility over loading a particular path to a specific interface.
-func LoadAdvanced(val *schema.StructValidator, path string, result interface{}, sources ...Source) (keys []string, err error) {
+func LoadAdvanced(val *schema.StructValidator, path string, result any, sources ...Source) (keys []string, err error) {
if val == nil {
return keys, errNoValidator
}
@@ -44,7 +44,7 @@ func LoadAdvanced(val *schema.StructValidator, path string, result interface{},
return koanfGetKeys(final), nil
}
-func mapHasKey(k string, m map[string]interface{}) bool {
+func mapHasKey(k string, m map[string]any) bool {
if _, ok := m[k]; ok {
return true
}
@@ -52,7 +52,7 @@ func mapHasKey(k string, m map[string]interface{}) bool {
return false
}
-func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o interface{}) {
+func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o any) {
c := koanf.UnmarshalConf{
DecoderConfig: &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
@@ -61,6 +61,12 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o inte
StringToURLHookFunc(),
StringToRegexpHookFunc(),
StringToAddressHookFunc(),
+ StringToX509CertificateHookFunc(),
+ StringToX509CertificateChainHookFunc(),
+ StringToPrivateKeyHookFunc(),
+ StringToCryptoPrivateKeyHookFunc(),
+ StringToTLSVersionHookFunc(),
+ StringToPasswordDigestHookFunc(true),
ToTimeDurationHookFunc(),
),
Metadata: nil,
diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go
index 3f7aa7b45..b5eb50ecc 100644
--- a/internal/configuration/provider_test.go
+++ b/internal/configuration/provider_test.go
@@ -48,7 +48,7 @@ func TestShouldErrorSecretNotExist(t *testing.T) {
errFmt := utils.GetExpectedErrTxt("filenotfound")
- // ignore the errors before this as they are checked by the valdator.
+ // ignore the errors before this as they are checked by the validator.
assert.EqualError(t, errs[0], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "authentication"), "authentication_backend.ldap.password", fmt.Sprintf(errFmt, filepath.Join(dir, "authentication"))))
assert.EqualError(t, errs[1], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "duo"), "duo_api.secret_key", fmt.Sprintf(errFmt, filepath.Join(dir, "duo"))))
assert.EqualError(t, errs[2], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "jwt"), "jwt_secret", fmt.Sprintf(errFmt, filepath.Join(dir, "jwt"))))
@@ -219,6 +219,23 @@ func TestShouldLoadURLList(t *testing.T) {
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
}
+func TestShouldConfigureConsent(t *testing.T) {
+ testReset()
+
+ val := schema.NewStructValidator()
+ keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
+
+ assert.NoError(t, err)
+
+ validator.ValidateKeys(keys, DefaultEnvPrefix, val)
+
+ assert.Len(t, val.Errors(), 0)
+ assert.Len(t, val.Warnings(), 0)
+
+ require.Len(t, config.IdentityProviders.OIDC.Clients, 1)
+ assert.Equal(t, config.IdentityProviders.OIDC.Clients[0].ConsentMode, "explicit")
+}
+
func TestShouldValidateAndRaiseErrorsOnBadConfiguration(t *testing.T) {
testReset()
diff --git a/internal/configuration/schema/access_control.go b/internal/configuration/schema/access_control.go
index 7b12fe129..79e1c93fa 100644
--- a/internal/configuration/schema/access_control.go
+++ b/internal/configuration/schema/access_control.go
@@ -19,13 +19,21 @@ type ACLNetwork struct {
// ACLRule represents one ACL rule entry.
type ACLRule struct {
- Domains []string `koanf:"domain"`
- DomainsRegex []regexp.Regexp `koanf:"domain_regex"`
- Policy string `koanf:"policy"`
- Subjects [][]string `koanf:"subject"`
- Networks []string `koanf:"networks"`
- Resources []regexp.Regexp `koanf:"resources"`
- Methods []string `koanf:"methods"`
+ Domains []string `koanf:"domain"`
+ DomainsRegex []regexp.Regexp `koanf:"domain_regex"`
+ Policy string `koanf:"policy"`
+ Subjects [][]string `koanf:"subject"`
+ Networks []string `koanf:"networks"`
+ Resources []regexp.Regexp `koanf:"resources"`
+ Methods []string `koanf:"methods"`
+ Query [][]ACLQueryRule `koanf:"query"`
+}
+
+// ACLQueryRule represents the ACL query criteria.
+type ACLQueryRule struct {
+ Operator string `koanf:"operator"`
+ Key string `koanf:"key"`
+ Value any `koanf:"value"`
}
// DefaultACLNetwork represents the default configuration related to access control network group configuration.
diff --git a/internal/configuration/schema/authentication.go b/internal/configuration/schema/authentication.go
index 895cc7711..9d118c091 100644
--- a/internal/configuration/schema/authentication.go
+++ b/internal/configuration/schema/authentication.go
@@ -1,12 +1,100 @@
package schema
import (
+ "crypto/tls"
"net/url"
"time"
)
-// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server.
-type LDAPAuthenticationBackendConfiguration struct {
+// AuthenticationBackend represents the configuration related to the authentication backend.
+type AuthenticationBackend struct {
+ PasswordReset PasswordResetAuthenticationBackend `koanf:"password_reset"`
+
+ RefreshInterval string `koanf:"refresh_interval"`
+
+ File *FileAuthenticationBackend `koanf:"file"`
+ LDAP *LDAPAuthenticationBackend `koanf:"ldap"`
+}
+
+// PasswordResetAuthenticationBackend represents the configuration related to password reset functionality.
+type PasswordResetAuthenticationBackend struct {
+ Disable bool `koanf:"disable"`
+ CustomURL url.URL `koanf:"custom_url"`
+}
+
+// FileAuthenticationBackend represents the configuration related to file-based backend.
+type FileAuthenticationBackend struct {
+ Path string `koanf:"path"`
+ Watch bool `koanf:"watch"`
+ Password Password `koanf:"password"`
+
+ Search FileSearchAuthenticationBackend `koanf:"search"`
+}
+
+// FileSearchAuthenticationBackend represents the configuration related to file-based backend searching.
+type FileSearchAuthenticationBackend struct {
+ Email bool `koanf:"email"`
+ CaseInsensitive bool `koanf:"case_insensitive"`
+}
+
+// Password represents the configuration related to password hashing.
+type Password struct {
+ Algorithm string `koanf:"algorithm"`
+
+ Argon2 Argon2Password `koanf:"argon2"`
+ SHA2Crypt SHA2CryptPassword `koanf:"sha2crypt"`
+ PBKDF2 PBKDF2Password `koanf:"pbkdf2"`
+ BCrypt BCryptPassword `koanf:"bcrypt"`
+ SCrypt SCryptPassword `koanf:"scrypt"`
+
+ Iterations int `koanf:"iterations"`
+ Memory int `koanf:"memory"`
+ Parallelism int `koanf:"parallelism"`
+ KeyLength int `koanf:"key_length"`
+ SaltLength int `koanf:"salt_length"`
+}
+
+// Argon2Password represents the argon2 hashing settings.
+type Argon2Password struct {
+ Variant string `koanf:"variant"`
+ Iterations int `koanf:"iterations"`
+ Memory int `koanf:"memory"`
+ Parallelism int `koanf:"parallelism"`
+ KeyLength int `koanf:"key_length"`
+ SaltLength int `koanf:"salt_length"`
+}
+
+// SHA2CryptPassword represents the sha2crypt hashing settings.
+type SHA2CryptPassword struct {
+ Variant string `koanf:"variant"`
+ Iterations int `koanf:"iterations"`
+ SaltLength int `koanf:"salt_length"`
+}
+
+// PBKDF2Password represents the PBKDF2 hashing settings.
+type PBKDF2Password struct {
+ Variant string `koanf:"variant"`
+ Iterations int `koanf:"iterations"`
+ SaltLength int `koanf:"salt_length"`
+}
+
+// BCryptPassword represents the bcrypt hashing settings.
+type BCryptPassword struct {
+ Variant string `koanf:"variant"`
+ Cost int `koanf:"cost"`
+}
+
+// SCryptPassword represents the scrypt hashing settings.
+type SCryptPassword struct {
+ Iterations int `koanf:"iterations"`
+ BlockSize int `koanf:"block_size"`
+ Parallelism int `koanf:"parallelism"`
+ KeyLength int `koanf:"key_length"`
+ SaltLength int `koanf:"salt_length"`
+}
+
+// LDAPAuthenticationBackend represents the configuration related to LDAP server.
+type LDAPAuthenticationBackend struct {
Implementation string `koanf:"implementation"`
URL string `koanf:"url"`
Timeout time.Duration `koanf:"timeout"`
@@ -26,91 +114,87 @@ type LDAPAuthenticationBackendConfiguration struct {
MailAttribute string `koanf:"mail_attribute"`
DisplayNameAttribute string `koanf:"display_name_attribute"`
- PermitReferrals bool `koanf:"permit_referrals"`
- PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"`
+ PermitReferrals bool `koanf:"permit_referrals"`
+ PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"`
+ PermitFeatureDetectionFailure bool `koanf:"permit_feature_detection_failure"`
User string `koanf:"user"`
Password string `koanf:"password"`
}
-// FileAuthenticationBackendConfiguration represents the configuration related to file-based backend.
-type FileAuthenticationBackendConfiguration struct {
- Path string `koanf:"path"`
- Password *PasswordConfiguration `koanf:"password"`
+// DefaultPasswordConfig represents the default configuration related to Argon2id hashing.
+var DefaultPasswordConfig = Password{
+ Algorithm: argon2,
+ Argon2: Argon2Password{
+ Variant: argon2id,
+ Iterations: 3,
+ Memory: 64 * 1024,
+ Parallelism: 4,
+ KeyLength: 32,
+ SaltLength: 16,
+ },
+ SHA2Crypt: SHA2CryptPassword{
+ Variant: sha512,
+ Iterations: 50000,
+ SaltLength: 16,
+ },
+ PBKDF2: PBKDF2Password{
+ Variant: sha512,
+ Iterations: 310000,
+ SaltLength: 16,
+ },
+ BCrypt: BCryptPassword{
+ Variant: "standard",
+ Cost: 12,
+ },
+ SCrypt: SCryptPassword{
+ Iterations: 16,
+ BlockSize: 8,
+ Parallelism: 1,
+ KeyLength: 32,
+ SaltLength: 16,
+ },
}
-// PasswordConfiguration represents the configuration related to password hashing.
-type PasswordConfiguration struct {
- Iterations int `koanf:"iterations"`
- KeyLength int `koanf:"key_length"`
- SaltLength int `koanf:"salt_length"`
- Algorithm string `koanf:"algorithm"`
- Memory int `koanf:"memory"`
- Parallelism int `koanf:"parallelism"`
+// DefaultCIPasswordConfig represents the default configuration related to Argon2id hashing for CI.
+var DefaultCIPasswordConfig = Password{
+ Algorithm: argon2,
+ Argon2: Argon2Password{
+ Iterations: 3,
+ Memory: 64,
+ Parallelism: 4,
+ KeyLength: 32,
+ SaltLength: 16,
+ },
+ SHA2Crypt: SHA2CryptPassword{
+ Variant: sha512,
+ Iterations: 50000,
+ SaltLength: 16,
+ },
}
-// AuthenticationBackendConfiguration represents the configuration related to the authentication backend.
-type AuthenticationBackendConfiguration struct {
- LDAP *LDAPAuthenticationBackendConfiguration `koanf:"ldap"`
- File *FileAuthenticationBackendConfiguration `koanf:"file"`
-
- PasswordReset PasswordResetAuthenticationBackendConfiguration `koanf:"password_reset"`
-
- RefreshInterval string `koanf:"refresh_interval"`
-}
-
-// PasswordResetAuthenticationBackendConfiguration represents the configuration related to password reset functionality.
-type PasswordResetAuthenticationBackendConfiguration struct {
- Disable bool `koanf:"disable"`
- CustomURL url.URL `koanf:"custom_url"`
-}
-
-// DefaultPasswordConfiguration represents the default configuration related to Argon2id hashing.
-var DefaultPasswordConfiguration = PasswordConfiguration{
- Iterations: 3,
- KeyLength: 32,
- SaltLength: 16,
- Algorithm: argon2id,
- Memory: 64,
- Parallelism: 4,
-}
-
-// DefaultCIPasswordConfiguration represents the default configuration related to Argon2id hashing for CI.
-var DefaultCIPasswordConfiguration = PasswordConfiguration{
- Iterations: 3,
- KeyLength: 32,
- SaltLength: 16,
- Algorithm: argon2id,
- Memory: 64,
- Parallelism: 4,
-}
-
-// DefaultPasswordSHA512Configuration represents the default configuration related to SHA512 hashing.
-var DefaultPasswordSHA512Configuration = PasswordConfiguration{
- Iterations: 50000,
- SaltLength: 16,
- Algorithm: "sha512",
-}
-
-// DefaultLDAPAuthenticationBackendConfiguration represents the default LDAP config.
-var DefaultLDAPAuthenticationBackendConfiguration = LDAPAuthenticationBackendConfiguration{
- Implementation: LDAPImplementationCustom,
+// DefaultLDAPAuthenticationBackendConfigurationImplementationCustom represents the default LDAP config.
+var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuthenticationBackend{
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayName",
GroupNameAttribute: "cn",
Timeout: time.Second * 5,
TLS: &TLSConfig{
- MinimumVersion: "TLS1.2",
+ MinimumVersion: TLSVersion{tls.VersionTLS12},
},
}
-// DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration represents the default LDAP config for the MSAD Implementation.
-var DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration = LDAPAuthenticationBackendConfiguration{
+// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the MSAD Implementation.
+var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0)))",
UsernameAttribute: "sAMAccountName",
MailAttribute: "mail",
DisplayNameAttribute: "displayName",
- GroupsFilter: "(&(member={dn})(objectClass=group))",
+ GroupsFilter: "(&(member={dn})(sAMAccountType=268435456))",
GroupNameAttribute: "cn",
+ Timeout: time.Second * 5,
+ TLS: &TLSConfig{
+ MinimumVersion: TLSVersion{tls.VersionTLS12},
+ },
}
diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go
index 27ed0f142..3dc891d9a 100644
--- a/internal/configuration/schema/configuration.go
+++ b/internal/configuration/schema/configuration.go
@@ -8,19 +8,19 @@ type Configuration struct {
DefaultRedirectionURL string `koanf:"default_redirection_url"`
Default2FAMethod string `koanf:"default_2fa_method"`
- Log LogConfiguration `koanf:"log"`
- IdentityProviders IdentityProvidersConfiguration `koanf:"identity_providers"`
- AuthenticationBackend AuthenticationBackendConfiguration `koanf:"authentication_backend"`
- Session SessionConfiguration `koanf:"session"`
- TOTP TOTPConfiguration `koanf:"totp"`
- DuoAPI DuoAPIConfiguration `koanf:"duo_api"`
- AccessControl AccessControlConfiguration `koanf:"access_control"`
- NTP NTPConfiguration `koanf:"ntp"`
- Regulation RegulationConfiguration `koanf:"regulation"`
- Storage StorageConfiguration `koanf:"storage"`
- Notifier NotifierConfiguration `koanf:"notifier"`
- Server ServerConfiguration `koanf:"server"`
- Telemetry TelemetryConfig `koanf:"telemetry"`
- Webauthn WebauthnConfiguration `koanf:"webauthn"`
- PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
+ Log LogConfiguration `koanf:"log"`
+ IdentityProviders IdentityProvidersConfiguration `koanf:"identity_providers"`
+ AuthenticationBackend AuthenticationBackend `koanf:"authentication_backend"`
+ Session SessionConfiguration `koanf:"session"`
+ TOTP TOTPConfiguration `koanf:"totp"`
+ DuoAPI DuoAPIConfiguration `koanf:"duo_api"`
+ AccessControl AccessControlConfiguration `koanf:"access_control"`
+ NTP NTPConfiguration `koanf:"ntp"`
+ Regulation RegulationConfiguration `koanf:"regulation"`
+ Storage StorageConfiguration `koanf:"storage"`
+ Notifier NotifierConfiguration `koanf:"notifier"`
+ Server ServerConfiguration `koanf:"server"`
+ Telemetry TelemetryConfig `koanf:"telemetry"`
+ Webauthn WebauthnConfiguration `koanf:"webauthn"`
+ PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
}
diff --git a/internal/configuration/schema/const.go b/internal/configuration/schema/const.go
index 8ee9867b2..3015c1527 100644
--- a/internal/configuration/schema/const.go
+++ b/internal/configuration/schema/const.go
@@ -1,13 +1,50 @@
package schema
import (
+ "errors"
"regexp"
"time"
)
-const argon2id = "argon2id"
+const (
+ argon2 = "argon2"
+ argon2id = "argon2id"
+ sha512 = "sha512"
+)
-// ProfileRefreshDisabled represents a value for refresh_interval that disables the check entirely.
+const (
+ // TLSVersion13 is the textual representation of TLS 1.3.
+ TLSVersion13 = "TLS1.3"
+
+ // TLSVersion12 is the textual representation of TLS 1.2.
+ TLSVersion12 = "TLS1.2"
+
+ // TLSVersion11 is the textual representation of TLS 1.1.
+ TLSVersion11 = "TLS1.1"
+
+ // TLSVersion10 is the textual representation of TLS 1.0.
+ TLSVersion10 = "TLS1.0"
+
+ // SSLVersion30 is the textual representation of SSL 3.0.
+ SSLVersion30 = "SSL3.0"
+
+ // Version13 is the textual representation of version 1.3.
+ Version13 = "1.3"
+
+ // Version12 is the textual representation of version 1.2.
+ Version12 = "1.2"
+
+ // Version11 is the textual representation of version 1.1.
+ Version11 = "1.1"
+
+ // Version10 is the textual representation of version 1.0.
+ Version10 = "1.0"
+)
+
+// ErrTLSVersionNotSupported returned when an unknown TLS version supplied.
+var ErrTLSVersionNotSupported = errors.New("supplied tls version isn't supported")
+
+// ProfileRefreshDisabled represents a Value for refresh_interval that disables the check entirely.
const ProfileRefreshDisabled = "disable"
const (
@@ -57,3 +94,8 @@ const (
// regexpHasScheme checks if a string has a scheme. Valid characters for schemes include alphanumeric, hyphen,
// period, and plus characters.
var regexpHasScheme = regexp.MustCompile(`^[-+.a-zA-Z\d]+://`)
+
+const (
+ blockCERTIFICATE = "CERTIFICATE"
+ blockRSAPRIVATEKEY = "RSA PRIVATE KEY"
+)
diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go
index 0bfb969d9..04c6f35f9 100644
--- a/internal/configuration/schema/identity_providers.go
+++ b/internal/configuration/schema/identity_providers.go
@@ -1,6 +1,7 @@
package schema
import (
+ "crypto/rsa"
"net/url"
"time"
)
@@ -12,8 +13,9 @@ type IdentityProvidersConfiguration struct {
// OpenIDConnectConfiguration configuration for OpenID Connect.
type OpenIDConnectConfiguration struct {
- HMACSecret string `koanf:"hmac_secret"`
- IssuerPrivateKey string `koanf:"issuer_private_key"`
+ HMACSecret string `koanf:"hmac_secret"`
+ IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain"`
+ IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key"`
AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"`
AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"`
@@ -41,11 +43,11 @@ type OpenIDConnectCORSConfiguration struct {
// OpenIDConnectClientConfiguration configuration for an OpenID Connect client.
type OpenIDConnectClientConfiguration struct {
- ID string `koanf:"id"`
- Description string `koanf:"description"`
- Secret string `koanf:"secret"`
- SectorIdentifier url.URL `koanf:"sector_identifier"`
- Public bool `koanf:"public"`
+ ID string `koanf:"id"`
+ Description string `koanf:"description"`
+ Secret *PasswordDigest `koanf:"secret"`
+ SectorIdentifier url.URL `koanf:"sector_identifier"`
+ Public bool `koanf:"public"`
RedirectURIs []string `koanf:"redirect_uris"`
@@ -59,7 +61,8 @@ type OpenIDConnectClientConfiguration struct {
Policy string `koanf:"authorization_policy"`
- PreConfiguredConsentDuration *time.Duration `koanf:"pre_configured_consent_duration"`
+ ConsentMode string `koanf:"consent_mode"`
+ ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
}
// DefaultOpenIDConnectConfiguration contains defaults for OIDC.
@@ -71,6 +74,8 @@ var DefaultOpenIDConnectConfiguration = OpenIDConnectConfiguration{
EnforcePKCE: "public_clients_only",
}
+var defaultOIDCClientConsentPreConfiguredDuration = time.Hour * 24 * 7
+
// DefaultOpenIDConnectClientConfiguration contains defaults for OIDC Clients.
var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
Policy: "two_factor",
@@ -79,5 +84,7 @@ var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
ResponseTypes: []string{"code"},
ResponseModes: []string{"form_post", "query", "fragment"},
- UserinfoSigningAlgorithm: "none",
+ UserinfoSigningAlgorithm: "none",
+ ConsentMode: "auto",
+ ConsentPreConfiguredDuration: &defaultOIDCClientConsentPreConfiguredDuration,
}
diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go
index 07639a456..bcd577f39 100644
--- a/internal/configuration/schema/keys.go
+++ b/internal/configuration/schema/keys.go
@@ -18,6 +18,7 @@ var Keys = []string{
"log.file_path",
"log.keep_stdout",
"identity_providers.oidc.hmac_secret",
+ "identity_providers.oidc.issuer_certificate_chain",
"identity_providers.oidc.issuer_private_key",
"identity_providers.oidc.access_token_lifespan",
"identity_providers.oidc.authorize_code_lifespan",
@@ -44,14 +45,50 @@ var Keys = []string{
"identity_providers.oidc.clients[].response_modes",
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
"identity_providers.oidc.clients[].authorization_policy",
+ "identity_providers.oidc.clients[].consent_mode",
"identity_providers.oidc.clients[].pre_configured_consent_duration",
+ "authentication_backend.password_reset.disable",
+ "authentication_backend.password_reset.custom_url",
+ "authentication_backend.refresh_interval",
+ "authentication_backend.file.path",
+ "authentication_backend.file.watch",
+ "authentication_backend.file.password.algorithm",
+ "authentication_backend.file.password.argon2.variant",
+ "authentication_backend.file.password.argon2.iterations",
+ "authentication_backend.file.password.argon2.memory",
+ "authentication_backend.file.password.argon2.parallelism",
+ "authentication_backend.file.password.argon2.key_length",
+ "authentication_backend.file.password.argon2.salt_length",
+ "authentication_backend.file.password.sha2crypt.variant",
+ "authentication_backend.file.password.sha2crypt.iterations",
+ "authentication_backend.file.password.sha2crypt.salt_length",
+ "authentication_backend.file.password.pbkdf2.variant",
+ "authentication_backend.file.password.pbkdf2.iterations",
+ "authentication_backend.file.password.pbkdf2.salt_length",
+ "authentication_backend.file.password.bcrypt.variant",
+ "authentication_backend.file.password.bcrypt.cost",
+ "authentication_backend.file.password.scrypt.iterations",
+ "authentication_backend.file.password.scrypt.block_size",
+ "authentication_backend.file.password.scrypt.parallelism",
+ "authentication_backend.file.password.scrypt.key_length",
+ "authentication_backend.file.password.scrypt.salt_length",
+ "authentication_backend.file.password.iterations",
+ "authentication_backend.file.password.memory",
+ "authentication_backend.file.password.parallelism",
+ "authentication_backend.file.password.key_length",
+ "authentication_backend.file.password.salt_length",
+ "authentication_backend.file.search.email",
+ "authentication_backend.file.search.case_insensitive",
"authentication_backend.ldap.implementation",
"authentication_backend.ldap.url",
"authentication_backend.ldap.timeout",
"authentication_backend.ldap.start_tls",
"authentication_backend.ldap.tls.minimum_version",
+ "authentication_backend.ldap.tls.maximum_version",
"authentication_backend.ldap.tls.skip_verify",
"authentication_backend.ldap.tls.server_name",
+ "authentication_backend.ldap.tls.private_key",
+ "authentication_backend.ldap.tls.certificate_chain",
"authentication_backend.ldap.base_dn",
"authentication_backend.ldap.additional_users_dn",
"authentication_backend.ldap.users_filter",
@@ -63,18 +100,9 @@ var Keys = []string{
"authentication_backend.ldap.display_name_attribute",
"authentication_backend.ldap.permit_referrals",
"authentication_backend.ldap.permit_unauthenticated_bind",
+ "authentication_backend.ldap.permit_feature_detection_failure",
"authentication_backend.ldap.user",
"authentication_backend.ldap.password",
- "authentication_backend.file.path",
- "authentication_backend.file.password.iterations",
- "authentication_backend.file.password.key_length",
- "authentication_backend.file.password.salt_length",
- "authentication_backend.file.password.algorithm",
- "authentication_backend.file.password.memory",
- "authentication_backend.file.password.parallelism",
- "authentication_backend.password_reset.disable",
- "authentication_backend.password_reset.custom_url",
- "authentication_backend.refresh_interval",
"session.name",
"session.domain",
"session.same_site",
@@ -90,8 +118,11 @@ var Keys = []string{
"session.redis.maximum_active_connections",
"session.redis.minimum_idle_connections",
"session.redis.tls.minimum_version",
+ "session.redis.tls.maximum_version",
"session.redis.tls.skip_verify",
"session.redis.tls.server_name",
+ "session.redis.tls.private_key",
+ "session.redis.tls.certificate_chain",
"session.redis.high_availability.sentinel_name",
"session.redis.high_availability.sentinel_username",
"session.redis.high_availability.sentinel_password",
@@ -124,6 +155,10 @@ var Keys = []string{
"access_control.rules[].networks",
"access_control.rules[].resources",
"access_control.rules[].methods",
+ "access_control.rules[].query[][].operator",
+ "access_control.rules[].query[][].key",
+ "access_control.rules[].query[][].value",
+ "access_control.rules[].query",
"ntp.address",
"ntp.version",
"ntp.max_desync",
@@ -139,6 +174,12 @@ var Keys = []string{
"storage.mysql.username",
"storage.mysql.password",
"storage.mysql.timeout",
+ "storage.mysql.tls.minimum_version",
+ "storage.mysql.tls.maximum_version",
+ "storage.mysql.tls.skip_verify",
+ "storage.mysql.tls.server_name",
+ "storage.mysql.tls.private_key",
+ "storage.mysql.tls.certificate_chain",
"storage.postgres.host",
"storage.postgres.port",
"storage.postgres.database",
@@ -146,6 +187,12 @@ var Keys = []string{
"storage.postgres.password",
"storage.postgres.timeout",
"storage.postgres.schema",
+ "storage.postgres.tls.minimum_version",
+ "storage.postgres.tls.maximum_version",
+ "storage.postgres.tls.skip_verify",
+ "storage.postgres.tls.server_name",
+ "storage.postgres.tls.private_key",
+ "storage.postgres.tls.certificate_chain",
"storage.postgres.ssl.mode",
"storage.postgres.ssl.root_certificate",
"storage.postgres.ssl.certificate",
@@ -164,9 +211,13 @@ var Keys = []string{
"notifier.smtp.startup_check_address",
"notifier.smtp.disable_require_tls",
"notifier.smtp.disable_html_emails",
+ "notifier.smtp.disable_starttls",
"notifier.smtp.tls.minimum_version",
+ "notifier.smtp.tls.maximum_version",
"notifier.smtp.tls.skip_verify",
"notifier.smtp.tls.server_name",
+ "notifier.smtp.tls.private_key",
+ "notifier.smtp.tls.certificate_chain",
"notifier.template_path",
"server.host",
"server.port",
diff --git a/internal/configuration/schema/notifier.go b/internal/configuration/schema/notifier.go
index 00c45e8f7..a95b935f1 100644
--- a/internal/configuration/schema/notifier.go
+++ b/internal/configuration/schema/notifier.go
@@ -1,6 +1,7 @@
package schema
import (
+ "crypto/tls"
"net/mail"
"time"
)
@@ -23,6 +24,7 @@ type SMTPNotifierConfiguration struct {
StartupCheckAddress mail.Address `koanf:"startup_check_address"`
DisableRequireTLS bool `koanf:"disable_require_tls"`
DisableHTMLEmails bool `koanf:"disable_html_emails"`
+ DisableStartTLS bool `koanf:"disable_starttls"`
TLS *TLSConfig `koanf:"tls"`
}
@@ -41,6 +43,6 @@ var DefaultSMTPNotifierConfiguration = SMTPNotifierConfiguration{
Identifier: "localhost",
StartupCheckAddress: mail.Address{Name: "Authelia Test", Address: "test@authelia.com"},
TLS: &TLSConfig{
- MinimumVersion: "TLS1.2",
+ MinimumVersion: TLSVersion{tls.VersionTLS12},
},
}
diff --git a/internal/configuration/schema/server.go b/internal/configuration/schema/server.go
index 57073ab1a..c6ad3e5d5 100644
--- a/internal/configuration/schema/server.go
+++ b/internal/configuration/schema/server.go
@@ -42,8 +42,8 @@ var DefaultServerConfiguration = ServerConfiguration{
Write: 4096,
},
Timeouts: ServerTimeouts{
- Read: time.Second * 2,
- Write: time.Second * 2,
+ Read: time.Second * 6,
+ Write: time.Second * 6,
Idle: time.Second * 30,
},
}
diff --git a/internal/configuration/schema/session.go b/internal/configuration/schema/session.go
index 6b55e8df3..2a935ee9a 100644
--- a/internal/configuration/schema/session.go
+++ b/internal/configuration/schema/session.go
@@ -1,6 +1,7 @@
package schema
import (
+ "crypto/tls"
"time"
)
@@ -54,3 +55,10 @@ var DefaultSessionConfiguration = SessionConfiguration{
RememberMeDuration: time.Hour * 24 * 30,
SameSite: "lax",
}
+
+// DefaultRedisConfiguration is the default redis configuration.
+var DefaultRedisConfiguration = RedisSessionConfiguration{
+ TLS: &TLSConfig{
+ MinimumVersion: TLSVersion{Value: tls.VersionTLS12},
+ },
+}
diff --git a/internal/configuration/schema/shared.go b/internal/configuration/schema/shared.go
index 336121839..13d56b037 100644
--- a/internal/configuration/schema/shared.go
+++ b/internal/configuration/schema/shared.go
@@ -6,9 +6,20 @@ import (
// TLSConfig is a representation of the TLS configuration.
type TLSConfig struct {
- MinimumVersion string `koanf:"minimum_version"`
- SkipVerify bool `koanf:"skip_verify"`
- ServerName string `koanf:"server_name"`
+ MinimumVersion TLSVersion `koanf:"minimum_version"`
+ MaximumVersion TLSVersion `koanf:"maximum_version"`
+
+ SkipVerify bool `koanf:"skip_verify"`
+ ServerName string `koanf:"server_name"`
+
+ PrivateKey CryptographicPrivateKey `koanf:"private_key"`
+ CertificateChain X509CertificateChain `koanf:"certificate_chain"`
+}
+
+// TLSCertificateConfig is a representation of the TLS Certificate configuration.
+type TLSCertificateConfig struct {
+ Key CryptographicPrivateKey `koanf:"key"`
+ CertificateChain X509CertificateChain `koanf:"certificate_chain"`
}
// ServerTimeouts represents server timeout configurations.
diff --git a/internal/configuration/schema/storage.go b/internal/configuration/schema/storage.go
index de4eae7bb..d8eb93808 100644
--- a/internal/configuration/schema/storage.go
+++ b/internal/configuration/schema/storage.go
@@ -1,6 +1,9 @@
package schema
-import "time"
+import (
+ "crypto/tls"
+ "time"
+)
// LocalStorageConfiguration represents the configuration when using local storage.
type LocalStorageConfiguration struct {
@@ -20,6 +23,8 @@ type SQLStorageConfiguration struct {
// MySQLStorageConfiguration represents the configuration of a MySQL database.
type MySQLStorageConfiguration struct {
SQLStorageConfiguration `koanf:",squash"`
+
+ TLS *TLSConfig `koanf:"tls"`
}
// PostgreSQLStorageConfiguration represents the configuration of a PostgreSQL database.
@@ -27,7 +32,9 @@ type PostgreSQLStorageConfiguration struct {
SQLStorageConfiguration `koanf:",squash"`
Schema string `koanf:"schema"`
- SSL PostgreSQLSSLStorageConfiguration `koanf:"ssl"`
+ TLS *TLSConfig `koanf:"tls"`
+
+ SSL *PostgreSQLSSLStorageConfiguration `koanf:"ssl"`
}
// PostgreSQLSSLStorageConfiguration represents the SSL configuration of a PostgreSQL database.
@@ -52,10 +59,20 @@ var DefaultSQLStorageConfiguration = SQLStorageConfiguration{
Timeout: 5 * time.Second,
}
+// DefaultMySQLStorageConfiguration represents the default MySQL configuration.
+var DefaultMySQLStorageConfiguration = MySQLStorageConfiguration{
+ TLS: &TLSConfig{
+ MinimumVersion: TLSVersion{tls.VersionTLS12},
+ },
+}
+
// DefaultPostgreSQLStorageConfiguration represents the default PostgreSQL configuration.
var DefaultPostgreSQLStorageConfiguration = PostgreSQLStorageConfiguration{
Schema: "public",
- SSL: PostgreSQLSSLStorageConfiguration{
+ TLS: &TLSConfig{
+ MinimumVersion: TLSVersion{tls.VersionTLS12},
+ },
+ SSL: &PostgreSQLSSLStorageConfiguration{
Mode: "disable",
},
}
diff --git a/internal/configuration/schema/telemetry.go b/internal/configuration/schema/telemetry.go
index 3de20bc3d..3b0dcf3b5 100644
--- a/internal/configuration/schema/telemetry.go
+++ b/internal/configuration/schema/telemetry.go
@@ -28,8 +28,8 @@ var DefaultTelemetryConfig = TelemetryConfig{
Write: 4096,
},
Timeouts: ServerTimeouts{
- Read: time.Second * 2,
- Write: time.Second * 2,
+ Read: time.Second * 6,
+ Write: time.Second * 6,
Idle: time.Second * 30,
},
},
diff --git a/internal/configuration/schema/types.go b/internal/configuration/schema/types.go
index 5fb6de70e..ee4ed3934 100644
--- a/internal/configuration/schema/types.go
+++ b/internal/configuration/schema/types.go
@@ -1,11 +1,21 @@
package schema
import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/pem"
"fmt"
"net"
"net/url"
"strconv"
"strings"
+ "time"
+
+ "github.com/go-crypt/crypt"
)
// NewAddressFromString returns an *Address and error depending on the ability to parse the string as an Address.
@@ -99,3 +109,271 @@ func (a Address) HostPort() string {
func (a Address) Listener() (net.Listener, error) {
return net.Listen(a.Scheme, a.HostPort())
}
+
+// NewPasswordDigest returns a new PasswordDigest.
+func NewPasswordDigest(value string, plaintext bool) (digest *PasswordDigest, err error) {
+ var d crypt.Digest
+
+ switch {
+ case plaintext:
+ d, err = crypt.DecodeWithPlainText(value)
+ default:
+ d, err = crypt.Decode(value)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &PasswordDigest{d}, err
+}
+
+// PasswordDigest is a configuration type for the crypt.Digest.
+type PasswordDigest struct {
+ crypt.Digest
+}
+
+// NewX509CertificateChain creates a new *X509CertificateChain from a given string, parsing each PEM block one by one.
+func NewX509CertificateChain(in string) (chain *X509CertificateChain, err error) {
+ if in == "" {
+ return nil, nil
+ }
+
+ chain = &X509CertificateChain{
+ certs: []*x509.Certificate{},
+ }
+
+ data := []byte(in)
+
+ var (
+ block *pem.Block
+ cert *x509.Certificate
+ )
+
+ for {
+ block, data = pem.Decode(data)
+
+ if block == nil || len(block.Bytes) == 0 {
+ return nil, fmt.Errorf("invalid PEM block")
+ }
+
+ if block.Type != blockCERTIFICATE {
+ return nil, fmt.Errorf("the PEM data chain contains a %s but only certificates are expected", block.Type)
+ }
+
+ if cert, err = x509.ParseCertificate(block.Bytes); err != nil {
+ return nil, fmt.Errorf("the PEM data chain contains an invalid certificate: %w", err)
+ }
+
+ chain.certs = append(chain.certs, cert)
+
+ if len(data) == 0 {
+ break
+ }
+ }
+
+ return chain, nil
+}
+
+// NewTLSVersion returns a new TLSVersion given a string.
+func NewTLSVersion(input string) (version *TLSVersion, err error) {
+ switch strings.ReplaceAll(strings.ToUpper(input), " ", "") {
+ case TLSVersion13, Version13:
+ return &TLSVersion{tls.VersionTLS13}, nil
+ case TLSVersion12, Version12:
+ return &TLSVersion{tls.VersionTLS12}, nil
+ case TLSVersion11, Version11:
+ return &TLSVersion{tls.VersionTLS11}, nil
+ case TLSVersion10, Version10:
+ return &TLSVersion{tls.VersionTLS10}, nil
+ case SSLVersion30:
+ return &TLSVersion{tls.VersionSSL30}, nil //nolint:staticcheck
+ }
+
+ return nil, ErrTLSVersionNotSupported
+}
+
+// TLSVersion is a struct which handles tls.Config versions.
+type TLSVersion struct {
+ Value uint16
+}
+
+// MaxVersion returns the value of this as a MaxVersion value.
+func (v *TLSVersion) MaxVersion() uint16 {
+ if v.Value == 0 {
+ return tls.VersionTLS13
+ }
+
+ return v.Value
+}
+
+// MinVersion returns the value of this as a MinVersion value.
+func (v *TLSVersion) MinVersion() uint16 {
+ if v.Value == 0 {
+ return tls.VersionTLS12
+ }
+
+ return v.Value
+}
+
+// String provides the Stringer.
+func (v *TLSVersion) String() string {
+ switch v.Value {
+ case tls.VersionTLS10:
+ return TLSVersion10
+ case tls.VersionTLS11:
+ return TLSVersion11
+ case tls.VersionTLS12:
+ return TLSVersion12
+ case tls.VersionTLS13:
+ return TLSVersion13
+ case tls.VersionSSL30: //nolint:staticcheck
+ return SSLVersion30
+ default:
+ return ""
+ }
+}
+
+// CryptographicPrivateKey represents the actual crypto.PrivateKey interface.
+type CryptographicPrivateKey interface {
+ Public() crypto.PublicKey
+ Equal(x crypto.PrivateKey) bool
+}
+
+// X509CertificateChain is a helper struct that holds a list of *x509.Certificate's.
+type X509CertificateChain struct {
+ certs []*x509.Certificate
+}
+
+// Thumbprint returns the Thumbprint for the first certificate.
+func (c *X509CertificateChain) Thumbprint(hash crypto.Hash) []byte {
+ if len(c.certs) == 0 {
+ return nil
+ }
+
+ h := hash.New()
+
+ h.Write(c.certs[0].Raw)
+
+ return h.Sum(nil)
+}
+
+// HasCertificates returns true if the chain has any certificates.
+func (c *X509CertificateChain) HasCertificates() (has bool) {
+ return len(c.certs) != 0
+}
+
+// Equal checks if the provided *x509.Certificate is equal to the first *x509.Certificate in the chain.
+func (c *X509CertificateChain) Equal(other *x509.Certificate) (equal bool) {
+ if len(c.certs) == 0 {
+ return false
+ }
+
+ return c.certs[0].Equal(other)
+}
+
+// EqualKey checks if the provided key (public or private) has a public key equal to the first public key in this chain.
+//
+//nolint:gocyclo // This is an adequately clear function even with the complexity.
+func (c *X509CertificateChain) EqualKey(other any) (equal bool) {
+ if len(c.certs) == 0 || other == nil {
+ return false
+ }
+
+ switch key := other.(type) {
+ case *rsa.PublicKey:
+ return key.Equal(c.certs[0].PublicKey)
+ case rsa.PublicKey:
+ return key.Equal(c.certs[0].PublicKey)
+ case *rsa.PrivateKey:
+ return key.PublicKey.Equal(c.certs[0].PublicKey)
+ case rsa.PrivateKey:
+ return key.PublicKey.Equal(c.certs[0].PublicKey)
+ case *ecdsa.PublicKey:
+ return key.Equal(c.certs[0].PublicKey)
+ case ecdsa.PublicKey:
+ return key.Equal(c.certs[0].PublicKey)
+ case *ecdsa.PrivateKey:
+ return key.PublicKey.Equal(c.certs[0].PublicKey)
+ case ecdsa.PrivateKey:
+ return key.PublicKey.Equal(c.certs[0].PublicKey)
+ case *ed25519.PublicKey:
+ return key.Equal(c.certs[0].PublicKey)
+ case ed25519.PublicKey:
+ return key.Equal(c.certs[0].PublicKey)
+ case *ed25519.PrivateKey:
+ switch pub := key.Public().(type) {
+ case *ed25519.PublicKey:
+ return pub.Equal(c.certs[0].PublicKey)
+ case ed25519.PublicKey:
+ return pub.Equal(c.certs[0].PublicKey)
+ default:
+ return false
+ }
+ case ed25519.PrivateKey:
+ switch pub := key.Public().(type) {
+ case *ed25519.PublicKey:
+ return pub.Equal(c.certs[0].PublicKey)
+ case ed25519.PublicKey:
+ return pub.Equal(c.certs[0].PublicKey)
+ default:
+ return false
+ }
+ default:
+ return false
+ }
+}
+
+// Certificates for this X509CertificateChain.
+func (c *X509CertificateChain) Certificates() (certificates []*x509.Certificate) {
+ return c.certs
+}
+
+// CertificatesRaw for this X509CertificateChain.
+func (c *X509CertificateChain) CertificatesRaw() (certificates [][]byte) {
+ if !c.HasCertificates() {
+ return nil
+ }
+
+ for _, cert := range c.certs {
+ certificates = append(certificates, cert.Raw)
+ }
+
+ return certificates
+}
+
+// Leaf returns the first certificate if available for use with tls.Certificate.
+func (c *X509CertificateChain) Leaf() (leaf *x509.Certificate) {
+ if !c.HasCertificates() {
+ return nil
+ }
+
+ return c.certs[0]
+}
+
+// Validate the X509CertificateChain ensuring the certificates were provided in the correct order
+// (with nth being signed by the nth+1), and that all of the certificates are valid based on the current time.
+func (c *X509CertificateChain) Validate() (err error) {
+ n := len(c.certs)
+ now := time.Now()
+
+ for i, cert := range c.certs {
+ if !cert.NotBefore.IsZero() && cert.NotBefore.After(now) {
+ return fmt.Errorf("certificate #%d in chain is invalid before %d but the time is %d", i+1, cert.NotBefore.Unix(), now.Unix())
+ }
+
+ if cert.NotAfter.Before(now) {
+ return fmt.Errorf("certificate #%d in chain is invalid after %d but the time is %d", i+1, cert.NotAfter.Unix(), now.Unix())
+ }
+
+ if i+1 >= n {
+ break
+ }
+
+ if err = cert.CheckSignatureFrom(c.certs[i+1]); err != nil {
+ return fmt.Errorf("certificate #%d in chain is not signed properly by certificate #%d in chain: %w", i+1, i+2, err)
+ }
+ }
+
+ return nil
+}
diff --git a/internal/configuration/schema/types_test.go b/internal/configuration/schema/types_test.go
index 9d5da92c5..ab83941a1 100644
--- a/internal/configuration/schema/types_test.go
+++ b/internal/configuration/schema/types_test.go
@@ -1,10 +1,17 @@
package schema
import (
+ "crypto"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "fmt"
"net"
+ "regexp"
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestNewAddressFromString(t *testing.T) {
@@ -48,3 +55,499 @@ func TestNewAddressFromString(t *testing.T) {
})
}
}
+
+func TestNewX509CertificateChain(t *testing.T) {
+ testCases := []struct {
+ name string
+ have string
+ thumbprintSHA256 string
+ err string
+ }{
+ {"ShouldParseCertificate", x509CertificateRSA,
+ "956e53c127b18143241fb75d2149369d41ad1e4094c40cedcef22e468b37886b", ""},
+ {"ShouldParseCertificateChain", x509CertificateRSA + "\n" + x509CACertificateRSA,
+ "956e53c127b18143241fb75d2149369d41ad1e4094c40cedcef22e468b37886b", ""},
+ {"ShouldNotParseInvalidCertificate", x509CertificateRSAInvalid, "",
+ "the PEM data chain contains an invalid certificate: x509: malformed certificate"},
+ {"ShouldNotParseInvalidCertificateBlock", x509CertificateRSAInvalidBlock, "", "invalid PEM block"},
+ {"ShouldNotParsePrivateKey", x509PrivateKeyRSA, "",
+ "the PEM data chain contains a RSA PRIVATE KEY but only certificates are expected"},
+ {"ShouldNotParseEmptyPEMBlock", x509CertificateEmpty, "", "invalid PEM block"},
+ {"ShouldNotParseEmptyData", "", "", ""},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ actual, err := NewX509CertificateChain(tc.have)
+
+ switch len(tc.err) {
+ case 0:
+ switch len(tc.have) {
+ case 0:
+ assert.Nil(t, actual)
+ default:
+ assert.NotNil(t, actual)
+
+ assert.Equal(t, tc.thumbprintSHA256, fmt.Sprintf("%x", actual.Thumbprint(crypto.SHA256)))
+ assert.True(t, actual.HasCertificates())
+ }
+ assert.NoError(t, err)
+ default:
+ assert.Nil(t, actual)
+ assert.EqualError(t, err, tc.err)
+ }
+ })
+ }
+}
+
+func TestX509CertificateChain(t *testing.T) {
+ chain := &X509CertificateChain{}
+
+ assert.Nil(t, chain.Thumbprint(crypto.SHA256))
+ assert.False(t, chain.HasCertificates())
+ assert.Len(t, chain.Certificates(), 0)
+
+ assert.False(t, chain.Equal(nil))
+ assert.False(t, chain.Equal(&x509.Certificate{}))
+
+ assert.False(t, chain.EqualKey(nil))
+ assert.False(t, chain.EqualKey(&rsa.PrivateKey{}))
+
+ cert := MustParseCertificate(x509CertificateRSA)
+ cacert := MustParseCertificate(x509CACertificateRSA)
+
+ chain = MustParseX509CertificateChain(x509CertificateRSA + "\n" + x509CACertificateRSA)
+ key := MustParseRSAPrivateKey(x509PrivateKeyRSA)
+
+ thumbprint := chain.Thumbprint(crypto.SHA256)
+ assert.NotNil(t, thumbprint)
+ assert.Equal(t, "956e53c127b18143241fb75d2149369d41ad1e4094c40cedcef22e468b37886b", fmt.Sprintf("%x", thumbprint))
+
+ assert.True(t, chain.Equal(cert))
+ assert.False(t, chain.Equal(cacert))
+ assert.True(t, chain.EqualKey(key))
+
+ assert.NoError(t, chain.Validate())
+
+ chain = MustParseX509CertificateChain(x509CertificateRSA + "\n" + x509CertificateRSA)
+ assert.EqualError(t, chain.Validate(), "certificate #1 in chain is not signed properly by certificate #2 in chain: x509: invalid signature: parent certificate cannot sign this kind of certificate")
+
+ chain = MustParseX509CertificateChain(x509CertificateRSAExpired + "\n" + x509CACertificateRSAExpired)
+
+ err := chain.Validate()
+ require.NotNil(t, err)
+ assert.Regexp(t, regexp.MustCompile(`^certificate #1 in chain is invalid after 31536000 but the time is \d+$`), err.Error())
+
+ chain = MustParseX509CertificateChain(x509CertificateRSANotBefore + "\n" + x509CACertificateRSAotBefore)
+
+ err = chain.Validate()
+ require.NotNil(t, err)
+ assert.Regexp(t, regexp.MustCompile(`^certificate #1 in chain is invalid before 13569465600 but the time is \d+$`), err.Error())
+}
+
+func MustParseX509CertificateChain(data string) *X509CertificateChain {
+ chain, err := NewX509CertificateChain(data)
+ if err != nil {
+ panic(err)
+ }
+
+ if chain == nil {
+ panic("nil chain")
+ }
+
+ return chain
+}
+
+func MustParseCertificate(data string) *x509.Certificate {
+ block, x := pem.Decode([]byte(data))
+ if block == nil {
+ panic("not pem")
+ }
+
+ if len(x) != 0 {
+ panic("extra data")
+ }
+
+ if block.Type != blockCERTIFICATE {
+ panic(fmt.Sprintf("not certifiate block: %s", block.Type))
+ }
+
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ return cert
+}
+
+func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
+ block, x := pem.Decode([]byte(data))
+ if block == nil {
+ panic("not pem")
+ }
+
+ if len(x) != 0 {
+ panic("extra data")
+ }
+
+ if block.Type != blockRSAPRIVATEKEY {
+ panic(fmt.Sprintf("not rsa private key block: %s", block.Type))
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ return key
+}
+
+var (
+ // Valid from 2022 to 2122 (years).
+ x509CertificateRSA = `-----BEGIN CERTIFICATE-----
+MIIC6DCCAdCgAwIBAgIRAIxvm0gFgsbh3D22rSZLuFQwDQYJKoZIhvcNAQELBQAw
+EzERMA8GA1UEChMIQXV0aGVsaWEwIBcNMjIxMDAyMDAzMDQyWhgPMjEyMjA5MDgw
+MDMwNDJaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAy71EOkV3jOpVQtVTH5HYcI4PryUCiAEyxAIuO+66gaAa4aCd
+UCRr8iO/pt5nOwPxjPo+hMHhkcKpX7evj+wgYXAccpIQFSCYWTJkaXFL0jL7yFuE
+5xpjgRM/x6FfK0IbN5WmVWO9EjesbyMCyDoYpjwzIrxnB70F9Y0nrXst1SnW/Sy0
+01BQZNzD1tky1KDvEkw7L5mMPZFZMr5wV+ELvbo1LLvvrGYhhzbXWk7pPbxT0gAa
+7yVvQbDKuCDqssAUyQa2JdlDaQocpldtK6l+dc3IsSWKd2UMouta75ngr9E1igy3
+t7owMRqH8NjwKHt6KQeDVSdBnWNjG572vaRimQIDAQABozUwMzAOBgNVHQ8BAf8E
+BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG
+9w0BAQsFAAOCAQEAaZJ09yGa+DGr/iTqshGdPKNCtcf/CXCkL52xiI7DzLxDt30P
+8vCuXXrrTGBY7eWYupcNy/MyqaUrz1ED+map3nQzZQBJ9vWIfr01B9phkg/WSaNJ
+1DlYtbPYzr86BlGP1V5d3Wv6JqF3tkWHI0kI38CT68fWdDKrfa5j3JdZGIVJW+51
+U0IE3Nqhfc76YzwQ3sNX5FT2Fr55RowH+l5OBPk0Bcztq58XmyPR/bvPfDASt8iS
+DBT+0iiDiwk6LvOkasL8p7nuh5Grc9LMEYXY/QMUbkIWhIVRFlqyJA9s8vGHx1D4
+96iYKudj+yvO17Szzr/NNmcwETbCs4j6P6QeiA==
+-----END CERTIFICATE-----`
+
+ // Private Key for x509CertificateRSA.
+ x509PrivateKeyRSA = `-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAy71EOkV3jOpVQtVTH5HYcI4PryUCiAEyxAIuO+66gaAa4aCd
+UCRr8iO/pt5nOwPxjPo+hMHhkcKpX7evj+wgYXAccpIQFSCYWTJkaXFL0jL7yFuE
+5xpjgRM/x6FfK0IbN5WmVWO9EjesbyMCyDoYpjwzIrxnB70F9Y0nrXst1SnW/Sy0
+01BQZNzD1tky1KDvEkw7L5mMPZFZMr5wV+ELvbo1LLvvrGYhhzbXWk7pPbxT0gAa
+7yVvQbDKuCDqssAUyQa2JdlDaQocpldtK6l+dc3IsSWKd2UMouta75ngr9E1igy3
+t7owMRqH8NjwKHt6KQeDVSdBnWNjG572vaRimQIDAQABAoIBAA/EhhM8bRQqzo5t
+lBFNaELNu8kCRD/iV9tzj8BzqVt+2JW9qG8bYn9K5Po1HCglFfyjIVOE7cAqIJGX
+1a59x8PCuXDkfPolm6TLkZnXeta5u2K2MoLwN+M1aio5AvSGGTUkD8tr/KX8SQwQ
+2ZZFaML0xcBadF7U8jEey4NRlSp5/voiIAB+FrJHepZBz2XJYCX5s2vYLPMn+51R
+1HyO0n2aQ9H1Na8aBjTfAp9GDKJWBV3bSM7cVaLGlMFj/HNXUNVnSsVsJj0tdWKz
+K6r9zPskLnS+eNjCgqrOtZSqJ7M3PL0/PoTFPrr1Fevr+soKWCaPF94Ib94O9SEq
+scvP3kECgYEA0HBdGab0HjcZgFtsIaMm+eBcDhUmUrvMPUw6FmspKnc8wplscONW
+wrDGhR0dpT8+aAMD5jFC2pvyHjI5AWkW+53LB15j6SVzUlUMfS3VTwE2prLtDHDs
+nCDW2+fXY2kjv45efZGpMGbLJVePx2RCPzUlAlc14lzxnHgpo7eho1cCgYEA+jpi
+Eo/Jqa5CNd4hrXqFxZTFtU2Mn38ZKI3QK/l47/347yHLebjsYIIwJRoHenxWxNlz
+Y+BZ38vkP+f9BGAVGiRcyMmIJU0X305wKwl26Y2Q/tEm2OpwmDboD2pL9byi9vfY
+bz7pQGK/l9j86KofRwVJJRLsofPI1SsjnC8c448CgYAkpg0IjJ1RjriSJADwLSKW
+PseQxlE1rMVtZbC07mSPjeWGBbnWY3KGytQs5YCn5GXRne4alEC/9Tlt68CwKc0b
+spPXGNaSUL5lFIUcoWlm+bylNMKPNG+1x+RfR/VMCll5vcuJYooP85L2Xt3t3gfz
+2yFFtxXHVjY5H7uaiJgIAwKBgQDvkGXEj5TqtsL8/6YOiHb6Kuz+Hzi6mtxjTyI2
+d6mpWuWxTBGaf8kOvJWLb9gpFFGeNPGcdXaWJIZqCJjcT4Dkflu2f/uwepaYXGhX
+S8Bk6fwfee5PTmRt1mNmHsaKhgcfmznDh9+YnPIBVuULe5RmUlEtBWk3xEZKj/qP
+1Ss7UQKBgAwZQz+h5Z/XOJH3Qs5nJBKAZUiYkj3ux7G6tjx0cz7XcUYd/6enBpkY
+JeqVHB6G+bMRLwb+Hc5Vgpbd5GdaUWo8udaghHgSGPUVcn0lK38XhYek6ACGz7Lo
+xEfgtKoBlUq+uPb8H05HY0t9KybA3LA5wkRYYnJ17/nkZtrrJAmX
+-----END RSA PRIVATE KEY-----`
+
+ // Valid from 2022 to 2122 (years).
+ x509CACertificateRSA = `-----BEGIN CERTIFICATE-----
+MIIDBTCCAe2gAwIBAgIQAK/NIAl3Bdg4Xk0y/ZGL7jANBgkqhkiG9w0BAQsFADAT
+MREwDwYDVQQKEwhBdXRoZWxpYTAgFw0yMjEwMDIwMDMwMjFaGA8yMTIyMDkwODAw
+MzAyMVowEzERMA8GA1UEChMIQXV0aGVsaWEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQCg7jdO1HmydfkPzTtz57pvAS3YOdBT0hlNjJ4N2lrKhNnixrzK
++4R1dWQDP2SHbZQ0TskF8eQ8HhTr7AsApotTthJFkUgV2g+bv7wVroz0Hok5xtd4
+bnpOvG3YUCP13Nk3ZVxdQXqR3/G3MrbyiXVPcgU+0giJ8EBykbtMu8L79/1iyk+m
+w4fZfzTOeorRgspO3z3+pTAib2MCTA7bby1dX9qI/ysFPLdbJYfNQDxij8SzNLyJ
+EkQ4kh3jKXf1VcZjbQTtYTZ3JJDqM08OxGMKuXUxPHd72Xlb+Fzql8LjYdEy/YKA
+3r8FMf14lzcjvxtLnFXh//hiXh4+xgXMkrLZAgMBAAGjUzBRMA4GA1UdDwEB/wQE
+AwICpDAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FGKpXiZA+8VQyMBqTTep+dVTthSbMA0GCSqGSIb3DQEBCwUAA4IBAQAE4DJg+Rb4
+iiocvxxQ85lhh94ql++E8MKuzIdN7ORs+ybUnsDD1WFDebubroTQuTSBkFrNuGNJ
+8B7NZsHiWWLvNsrnxxeC5CicqfhSDti0rKWsbGyeoq7Kqok5E4pwOzeRsxL2e/Hm
+G6LsUQuQMUG2vxKNynqmJS4VpgSVkiGhUfURFuRRDuRpVQ/XTl7jDIGf/ls7TAZq
+1AnmnSi4Cqy4hrTnwYUYkFCcH69onUKAoaVNl1eAH7ogxakz32WyWObY98NBrjzA
+I6VQlaQNSHtdFqDpu7NWJZZZSgN4BknbMYQEPNYCm701cPB4ahJbpg5C3pVPFSql
+Bc9iI6nN3PCr
+-----END CERTIFICATE-----`
+
+ /*
+ // Private Key for x509CACertificateRSA.
+ x509CAPrivateKeyRSA = `-----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEAoO43TtR5snX5D807c+e6bwEt2DnQU9IZTYyeDdpayoTZ4sa8
+ yvuEdXVkAz9kh22UNE7JBfHkPB4U6+wLAKaLU7YSRZFIFdoPm7+8Fa6M9B6JOcbX
+ eG56Trxt2FAj9dzZN2VcXUF6kd/xtzK28ol1T3IFPtIIifBAcpG7TLvC+/f9YspP
+ psOH2X80znqK0YLKTt89/qUwIm9jAkwO228tXV/aiP8rBTy3WyWHzUA8Yo/EszS8
+ iRJEOJId4yl39VXGY20E7WE2dySQ6jNPDsRjCrl1MTx3e9l5W/hc6pfC42HRMv2C
+ gN6/BTH9eJc3I78bS5xV4f/4Yl4ePsYFzJKy2QIDAQABAoIBADlsZw3Y4Ufdsq6B
+ w/oasLqVSB+EmaKfMGosh+VXidgDyZ+S3KDtWJl09uf1wdBVOHHlvvNBGfidn0eD
+ pXVo+AQ5zpFGQtuRQMqJgvqVmzQshTi5i/8sJLZdpDBwgDRlxphusaORDsRojV6a
+ WQ94HwTnIZoF5ggaU1TOTXAW+39er+3CAkHZlqeCHliSdVWdAPy2AGTOKqTP3Pko
+ owbHkuCw53oWsDB9N8zqdVF+UBht0sFOQ8tEHq0OY1HJRtPhfvcFno9rADsna/Tg
+ 5m0sWUwP+uQ2+n18ahqunclzANu/w1SE+DVvXmeKdWMXEyuyL7muKsLgW5Ksa4jR
+ h6gAwBUCgYEAwmigbqAWrVhh5W1t388WcfsD0M7a4vMg7a3L5Im5mThgUHu3K5bM
+ EYLR8RnReEUdxdF86ASup4ddyVlz7z+YuQ9+XdTwMLK5Zujqfu9U/vsLHBc8h9eP
+ B7C6etfnTBfQeQryPaako8mixBpS0/pQhDhBHpWcYLLTh+uE17+C2U8CgYEA0+pe
+ EzWj7RHZiaHp6WWOqGuaCa3JR0uh2zns1gVx0cCdaCYFI+fOmrihrvBCYTXCiDZ4
+ k4HGD48i1R8nUsV6HJfw0yhR2UmB46TnFYb0RgzuJhXlXtlivYVbSDhyzl1NBafC
+ 9zuCITMqGw921w/unOamMD94VSBfxT4EMBpvV1cCgYEAlhNuxfePigHQkOwJBd03
+ 1oWQTIFjOA+4O8MOwz4OqNl8gKUAogWnQ11Z9GWZ7t5sPWmaowH6UhmNrQIBHZBa
+ tYHga08WnIFb3rWvUI4xbyUdTnIhqDwfjjA/xNUnGPbJWKe6mR0ru8TMgdZQWpPB
+ 1FAY9SNJtNxXr3WA94w/1sECgYBu0cEgioyPDSaVsvZ/93wC10JWjWsUvZiG7GPO
+ CErdRb0LGdbWUALbJnJm6X3NGDACy3mCqfrJaDDvArutrVeOXGa0BgHHf4lNYo71
+ 0v0rJNflUs4AK+5W7cYunlZrVJ9StchfQd9rPTZnsE6VaN9/bZ663HYxDh0HKMdH
+ 4IsZQQKBgQCsBJ2bP9dc7QcqMXs8k37USIMs4nz6YWkV0Q74bBhbbnKIhY4mxhwf
+ 5WMBUntxYQT0yyygA8q/wJrjiWI9dr+Le0VAtg4Wn8CW0bNYvnMySn2++2E8m6jM
+ DBXePomOtkIwEdLGcO8csBLYQKn4x/5ONpYI2QAifIB8Gdxqnp5clg==
+ -----END RSA PRIVATE KEY-----`
+ */
+
+ // Valid from 1970 to 1971 (years).
+ x509CertificateRSAExpired = `-----BEGIN CERTIFICATE-----
+MIIC5jCCAc6gAwIBAgIRAPKEPEnRO1hurtNAdEuDJA8wDQYJKoZIhvcNAQELBQAw
+EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAN+qPAlnoqHMeBeXUF7qnaZXvHS4p8m2N9+hU8us6GYl3mYdFRDy
+PGBYWewtIE0RsexBa7UYOV6IXdfheipsmRZZzUxjPbP/VfNuafxdZMVgQzWZZAtt
+JJHRhLBATSfkutoPe3eUXxFonEhvl5ErU4327M9cZlLPRsIiVoTWOmigTT0jctx+
+u/3IyEVtV982SpttYnpCZ9lCvaSgjpvf1Mim+dbGF0KPKitAbuFnNpWsbRzIYfiy
+rGMvxuftkywJ/e6Lx34HJjq/4+K1qII9clIiwAxa1RTnLbBuSLzVHxmj3L5hQhap
+jf7HMhLReW2XLJNw4xUShSKpvapBRGbly18CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+AQELBQADggEBAE5cRfJFTGOozfa9vOedNjif6MLqCtnVv9SRy7k/pdz0KSpYfpRo
+dPJ0kBIV2JwIVAXfge6SNn5DK/fyDzERq/kQaCzgBZ6//hCf6Y1BApGajLvOmhPx
+KijvMtqQ8IZnqwrURN04pQCt936grsas3Y6haxNyfdJDnQ5FgTe6kDOJU+rXyLp1
+N7H6hMXAd9+T++F50PM8AwtRZM6jSUVEhyrniKQSdkOQnXO5ng9if/7GntNzn56o
+7cV3sBeenxEmvaXsR30C+A+Ankxr8HBlVOCYcJpbtsCmOB2PVRq9Q5KJAylHRWE1
+JedOdWjWvrVaP2IqRopS9mV3Ckf1E19YWFg=
+-----END CERTIFICATE-----`
+
+ /*
+ // Private Key for x509CertificateRSAExpired.
+ x509PrivateKeyRSAExpired = `-----BEGIN RSA PRIVATE KEY-----
+ MIIEowIBAAKCAQEA36o8CWeiocx4F5dQXuqdple8dLinybY336FTy6zoZiXeZh0V
+ EPI8YFhZ7C0gTRGx7EFrtRg5Xohd1+F6KmyZFlnNTGM9s/9V825p/F1kxWBDNZlk
+ C20kkdGEsEBNJ+S62g97d5RfEWicSG+XkStTjfbsz1xmUs9GwiJWhNY6aKBNPSNy
+ 3H67/cjIRW1X3zZKm21iekJn2UK9pKCOm9/UyKb51sYXQo8qK0Bu4Wc2laxtHMhh
+ +LKsYy/G5+2TLAn97ovHfgcmOr/j4rWogj1yUiLADFrVFOctsG5IvNUfGaPcvmFC
+ FqmN/scyEtF5bZcsk3DjFRKFIqm9qkFEZuXLXwIDAQABAoIBADBgYrHqD3wNfKAl
+ o0WUW1riOSnJ0sjHN9iPzU8NbArEABF4Etlie3qfQXva2tSwkho2oDRANBBlUF7k
+ LwdEC+yQqd3uzSbEgHOxmwzxql0ikAbk0YXDKpi7h4aTsdyCFYQauyrHFbTvOnZU
+ ZKUKiPz4vomvQ5Z/rJ9KzAnZSDLeqbJfBXPPitlE8DAiYypGKDUmX0unMJh/x0Pw
+ mIP/DTd+nMl+QpoSR0nS8r8Pr+4oBJ8K6k9Oni2DKdIW8IvoQJBBa9cm8Y0fHkSl
+ hB7fncY5bE0lOZ8jBlSNuGfZHjVihwBA+rYAcWpyzdBx3SHRSe5AH4RKBPaERgSt
+ SBV35PECgYEA7ayQs2SMggOiEK4Wf9AzywieaHiHa2ObJRg2dHlIgVkUDL3zB/b6
+ 57jPMXAtMyGQDW6pZF6Oq3mgYP2A9alB6QKjpX1OGFmqZJxtRMAm0KPs2C2inWzg
+ dz9OW18jDlKKsHR00JktqsNgOZC8ldE2cyqgwBNXT/P9GyUMC9RmYhkCgYEA8Okk
+ 9u8IoIHJEWbtmmh0d1CEmPz4zQosTgUl2GLbNaCE/zDvA82YUQmi1yaF1FHjXMoa
+ tD0Plkixoj/ezASeSE+duVpgXflYL4IHbqQq9JQg39vyaSuU1g3wP+hnmnCT62vb
+ z4v7ugDLLkSlvNeEQLR3GvZvInZnfdwI5/mjeDcCgYA1UGlhJGP0YjY/gZ2gbCbC
+ G5vVGXxfFYfeyVClzfL6uO2rcgyLM9bSlf08PMqW1qeGq9Upo6BjTLQyLYt5D8+u
+ Ih5tZ+9VvP9g9En6ixPp52ugjpQUtjCf7z53dp7ZfqCHtofhpwq8bHkwUIxNGxIY
+ wW4vx+blE3kqVqQeHzYcOQKBgBgBAv/fvVpQ1Dn5qX8THVeuHCgqPJghhVyYwraW
+ 0wS648WRmJ8mYyDf9uu9GOSY7DCYqqR+2Qi+YYSrHIXzh9nopOyNBsEWUSUarabm
+ kKkiAUyM29CC2Sei5+dWPsxynyp76sD5T7Gu1o/boy/3wWO5F40GNPiYF6PAwtpq
+ U1FtAoGBANfr5OcnCIdtHLCEVRCaJdzTkQj5X1g9dF0D/gWkBIF0hibcs9yV2i1Z
+ JtxBrOvctkRsY7/C8dCms1gkfwDyTpKuMk9iDd3wfDGP3LdD1+V10pCm5ShHIGNm
+ /pRFpN45nR5iCX9mnvr8YJLUsrBkh7N4c4ao8xsXzOLBOk8WvtXL
+ -----END RSA PRIVATE KEY-----`
+ */
+
+ // Valid from 1970 to 1971 (years).
+ x509CACertificateRSAExpired = `-----BEGIN CERTIFICATE-----
+MIIDAzCCAeugAwIBAgIQB07G1WhPAiAHaM6FogkZ7TANBgkqhkiG9w0BAQsFADAT
+MREwDwYDVQQKEwhBdXRoZWxpYTAeFw03MDAxMDEwMDAwMDBaFw03MTAxMDEwMDAw
+MDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArly1jZXqLaAQJwTaoT0QaePV7SejQZj418Id7X7Gx1bgjmIS0mL8
+058c8Av1ThRaUmVAMkbBc4MED9KnKKckU+qMBV0+2xovrn7TvAKHG3yLUtasn/Uz
+2UzBXMgbCIMVCloWkPQ6BuSaNsZnSBmTj/IC16bAZwm5lIS9ZjzQ+QnZg8x5ftNR
+Jx8Gar8xb4tQbhb6uZU5zxfuV1+4qk04lf6E48IVrf57NBXpJhdzxuvdBj0l46k3
+zf44gybrXpr9O0n0Eb/H8lkIoIDM+vanoRvdg868QQw8C/r06M4E8gJJzZk7Ad2c
+oCq66peom6eLUSo2DVfVmmQ2KjLUyW0L6QIDAQABo1MwUTAOBgNVHQ8BAf8EBAMC
+AqQwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSQ
+qzXTP6jV517cAknynvP0vr4ChzANBgkqhkiG9w0BAQsFAAOCAQEAGvX14cJM+uxh
+rSBYa3qHEfSvlSwbQsCxZv5VXLJj6SLYUXqMLxCuPH16ZKD8MygmnF5/8Mp3ZIdF
+Aesehtaoth1H06q6iXWw+wgT9fZKGIHL4RftvjFWncD+Lbk8hP/DkLqsGt+Mj23u
+JAByhiG8QmbXu/X7kfXSvXjhQ7f7C+bNKxb03r7mT5gI8mCUp5MyLp3DPk3dKTwa
+uby/wjlFMHi92HjfQ6mCn5ijc/ltiMh1wtXf53IEESYvrWV5ABjV8xnumI4j4idB
+7yHjCn5id379go8e8a+W8jODNzUORzSio8tDhL4c13tiD4PzlMJ1tUr+CIoCzqB5
+m99SvwJ74A==
+-----END CERTIFICATE-----`
+
+ /*
+ // Private Key for x509CACertificateRSAExpired.
+ x509CAPrivateKeyRSAExpired = `-----BEGIN RSA PRIVATE KEY-----
+ MIIEowIBAAKCAQEArly1jZXqLaAQJwTaoT0QaePV7SejQZj418Id7X7Gx1bgjmIS
+ 0mL8058c8Av1ThRaUmVAMkbBc4MED9KnKKckU+qMBV0+2xovrn7TvAKHG3yLUtas
+ n/Uz2UzBXMgbCIMVCloWkPQ6BuSaNsZnSBmTj/IC16bAZwm5lIS9ZjzQ+QnZg8x5
+ ftNRJx8Gar8xb4tQbhb6uZU5zxfuV1+4qk04lf6E48IVrf57NBXpJhdzxuvdBj0l
+ 46k3zf44gybrXpr9O0n0Eb/H8lkIoIDM+vanoRvdg868QQw8C/r06M4E8gJJzZk7
+ Ad2coCq66peom6eLUSo2DVfVmmQ2KjLUyW0L6QIDAQABAoIBAAiOZBpekO9MO36u
+ rkvbQ0Lu+0B4AXrmls9/pxhQcFC34q0aAvJwCRgZZsIg1BjQxt3kOhI9hqC0fS6J
+ l8pW6WF00QoyWTNHRa+6aYmAVkDzC6M1BaOT1MeFDLgQ2cLBK/cmFJVoZrCP50Fo
+ 2wieuK8HoTwT4r0rrP+sw96QfXC7BjC1VSL9GXYemKz0RXEUvXXmzGGc9YE8vCt/
+ PXOb4TV30TIQrivkywSTJi8A1jUjYI2rPgo6JCl6GZGmc7hVX4jJ9lbBhUH76ozO
+ KS1Yzo/veWL4rVspc2exT5cuX7JIuFCjVi0Nlv1MKv39jpfTfKQh0ug6pHlxUzqX
+ Rl6Ln0ECgYEAwX0HtsmKMoSIrU4k90TKyhzjNoL8EaMKrSoZ4nK599igd8g+y6eD
+ jc1qO60lOHObyLPFor0rQT7K2KCD7GKYSX5+sh9leQEASl7cJkSAG17WBlrf9Pas
+ nUXjTRx3moEILAWmuov4UrYpkuEFk65d98xP3uPtDylFj57Bc+a8DOcCgYEA5rHK
+ qdjE8c0/1kgmItDJjmKxrJ5K5hY4w7SkpZeg4Rwr2WAjv3je/nx34D8S7m6R2uzp
+ NQYAAHXdzHt4iegupyW/3UXJboEscSTuC6/v3llawAozh02nDsMrdC1LreQ1IiFy
+ mKDmPZWxiAZXxEJ0hi0YMCcQnBY673eAleostq8CgYEAtia/mVvYh0Bv7z9O253e
+ jzFs0ce0B+KGzYiB/8XjvyknwDw6qbzUwy0romyZSrDDasma+F7AFtdHXXKXX3Ve
+ SmoUWhnmjGjd3iW5eSkptRqtwCPTDKkgzZqapuBy1Hg+ujrDwIC+0Rb+wnCmsGYJ
+ vpuQYZQPeyNugguBsVv5kucCgYBToTRE6k5LEgsIVVNt356RvXmHiELCsl+VotDl
+ Ltilgp7qyI1tBhZgzyJt6q+kO/UoFiZckHZDtHbZgBEsfT0cXvT09C2Xn8BKrAaX
+ ugoM4vuhDpGrhR0AnwQLs7fxq/8PBm0So5GT1cZr91Ct1yGC2qogGqlMzEpFMV8t
+ +ZyIBQKBgFyI2cZ3/uHQMkWUguml9w134bIGpqGdp8jf9YTvWs3Ax8/qxAVmFmtm
+ fot+QiamambblrhdT6pau11Mp06FzytorQH0qKd8mPAqvqtvcSoDZzqXjrkUnZIx
+ uLUcfb41clRhlfGDUj5MWimfi7d/Vakh86zAa8pg5WBCtXr0bZ/j
+ -----END RSA PRIVATE KEY-----`
+ */
+
+ // Valid from 2400 to 2401 (years).
+ x509CertificateRSANotBefore = `-----BEGIN CERTIFICATE-----
+MIIC6TCCAdGgAwIBAgIQYQWHYM90zNnwv22xhOPkszANBgkqhkiG9w0BAQsFADAT
+MREwDwYDVQQKEwhBdXRoZWxpYTAiGA8yNDAwMDEwMTAwMDAwMFoYDzI0MDAxMjMx
+MDAwMDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAPFYYindnRfs8aJfXbaX5IRVj10uKlT4i0BwJ5IYaC4O/3UQ
+km7do8lL2Ea2N2L5tQJhk2d+yoWGPeaUyuYP692jPA+4BW6RPuroSPB9WEU+x1ir
+it/AzJtavg0Lu2fGZkXxAZJj2MlrXT7csaGwRAvvPEHS6EJW4UtERYIqfpKGB39I
+sUhKNvY3edF9sosUAJmiZ8Q4K/uYoyCxyiE1QKLaiIjcZJxtzXkzwVBy1ZlmG+r5
+VNNguQQFsS8f7uRlOmo0o3hDG9dByUn7PgFEExbgBtdmNoIPk/pfMFM8NIHK+wOC
+q/SO2e/MX0IhJZXfq2VTZFgrisPovg8GpHSHRCkCAwEAAaM1MDMwDgYDVR0PAQH/
+BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZI
+hvcNAQELBQADggEBADBTRbfg/UQeJpdMogm9tleXJBcHqgOgiBxkKYxGSlRg4vlr
+tM8USAr24whLvb8KDhT6PaSY8wyPuCxqwqiKR84eselxOAcgDLV9n36OcWRm+oFl
+Th1S1JUtjbrctU7i6pp4BwUmBwkVALrbrj+cGVG7uBfbP8L/onmjg9KkY5ttnbyb
+Qi/Pv9zYLEo394hL3oeaphkP0iE6cHOvII/qFhnpLREINGp3g8V2I46Id/xYDi22
+WRabgMuFGpel7Q26yh+YXyHoIkKdiOXMNNXTsuvp5EDBGybTIXWK3xrqTsREMVDr
+EmiaOgL8c7+PpSWuUggJLb/JXDYnPtvekH3gPao=
+-----END CERTIFICATE-----`
+
+ /*
+ // Private Key for x509CertificateRSANotBefore.
+ x509PrivateKeyRSANotBefore = `-----BEGIN RSA PRIVATE KEY-----
+ MIIEowIBAAKCAQEA8VhiKd2dF+zxol9dtpfkhFWPXS4qVPiLQHAnkhhoLg7/dRCS
+ bt2jyUvYRrY3Yvm1AmGTZ37KhYY95pTK5g/r3aM8D7gFbpE+6uhI8H1YRT7HWKuK
+ 38DMm1q+DQu7Z8ZmRfEBkmPYyWtdPtyxobBEC+88QdLoQlbhS0RFgip+koYHf0ix
+ SEo29jd50X2yixQAmaJnxDgr+5ijILHKITVAotqIiNxknG3NeTPBUHLVmWYb6vlU
+ 02C5BAWxLx/u5GU6ajSjeEMb10HJSfs+AUQTFuAG12Y2gg+T+l8wUzw0gcr7A4Kr
+ 9I7Z78xfQiElld+rZVNkWCuKw+i+DwakdIdEKQIDAQABAoIBACGcZHdeJK2bUv+A
+ 9oUiXDHN1JxufHi+8G218NzYx1F6xzrfZvVHqrKy/FjEsav4CKxfOG8Wak/0JRTC
+ rgsiNn/0Zr3tq9v9IF0IonfTjQJ/vrVrlniY2iXcmlEozB2ktMOSz9w6SYurhx3l
+ EFvrN17OH38vRydOACxCQsfg8SWofY6SV0gcvlCcuM4lKBiuOBWGcf+xwIs3B+Bs
+ Frd282jRWtlcYd+zDE+vLxugNizLGpRKCMEdcKPRw9fkBKDI/f56WegNTUZYYFrV
+ LEmYIbOwMawvbi0mOdLsp27CfmeUjkEbwzgdNwjFrWIFAk0wT3QvDrKxDYDLM2Z3
+ +PtBMwECgYEA9ICYgzPMbN3CsQ+eWSQXXNk35V7PlMl1DC4UIHhi53BMT1ZhvkHf
+ D+eqXQ3BSqOUR7b417VBGkK8UtQuQXh9FwwVU0RhVkjpX0nTBhe8gGF3f3094rX4
+ Ckhm8XYQEWCUA9HNhCW+KSNVWqgw9Qi0awEY7HaiR628br39/EckvokCgYEA/LHI
+ HA9ixEBeTjds52rK6n9bPHeI87qxF62lLQYXvosyJij9/ruUfXwfJjG3EvCfcW7N
+ fr2EvgzPbCozC1V6gI9k5CXhOsf+wD6M8A7g7YHUa/dPq2B8bfqaMD0vW7OoZiLQ
+ NpfMtBvZxd1wukPGypLGWabPLo8u6bSfxTqz8KECgYAudnmFBUTls0aaKyOmQOuH
+ o2ex2NCNr7Lke6UrfnUdEgQOV5X/d7kR5q5DPKfsrSUyc5zaMQGMIf5zpwqbOnBa
+ /trWlfoBUZ23k+ncEIqrwtnYik5GVNor6hJV9F+dTcMS7r2lTR7T5nkD305eYicW
+ 5oB7/xdbk7JpQQWQ+VwMMQKBgQC3hbKs1mvH1mvnaI+aftACgR5VCweW4/bsGHwG
+ +A7Unyl713eowrk0ban9xkuM4N8btfpe2uuGT61xhDBwQdNnfT0sCWrLkyasnoEj
+ c9reA9Wv1/yvnbKg+Ul0UWuMsS1TiGMp0xOjlzqRXqMZVFITG4gc4m5EBU9wAnOq
+ /VhkIQKBgBUwpoL+K1OswQKV/SHH7n5b/By02mLuMkQR0NlpWRJY6eTDk3FAUkHn
+ +T996U0a+7OY+mATVqrfLBcsa6i+HGpb4jZL+kkdtfmtHUnN8YOAwopWp3uoPRmF
+ lpakAQq8NPVcrX6PLDkHeOlKhE4ercYFqcCRKcnMB3nD+re6x5l6
+ -----END RSA PRIVATE KEY-----`
+ */
+
+ // Valid from 2400 to 2401 (years).
+ x509CACertificateRSAotBefore = `-----BEGIN CERTIFICATE-----
+MIIDBzCCAe+gAwIBAgIQeR2/TbyH9gEzyjuTijMGVzANBgkqhkiG9w0BAQsFADAT
+MREwDwYDVQQKEwhBdXRoZWxpYTAiGA8yNDAwMDEwMTAwMDAwMFoYDzI0MDAxMjMx
+MDAwMDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAJ7vpRSDXVvwOLGmjbZdoG25OdsWgmhVAWpFCUifotqorz+z
++VgwPnvVDp1cTp07y+mDJK++GNBOG1pS5G4or6Y1HAlT3nGpE0FYhrtDBQmhvqV0
+6mPM/Dq5JIuGiju4LX0KBlaFJugJevw3ySnoPUu0BQ9mTZUgggNwetqsAX7TioFj
+TkVIMtgranigOvWjJQyLlmiK1TOgMqgWYNR20SE4CkmIp9SjOdeW7kNVMOojRx9a
+VgElA2TN57/Je+/tLbvTDDCP9o59SIXxn5N6JQ2/XdDZPNBHrxnmchQVDXWCkP0A
+gkV7V1dl8ur85iEdN+F31Kvd0nzCCaC3YUMxgmcCAwEAAaNTMFEwDgYDVR0PAQH/
+BAQDAgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQUB4/oEXJWhMwKBpMesSOnQdD+G0UwDQYJKoZIhvcNAQELBQADggEBAIlzOnWB
+xIhMm3zpLfpJGBi62d9J7Rlf5NitWoztyHdJpQ9y99s67QonR7UY7n11PMdtsLja
+hWy1/iZ1o5F2zGKPzSS8pscdIuo4B+TocLiHkEx7ttyQ0MepoDt1RlTOjqilqbfD
+A4GyGidns1VZuH8wP8NpZNlWajsXgvkYT433RzPgKe7qoI3DFQwc72SBuZSHHyjE
+9SVgdN0KmfFXMum4BurwftelF1etGR+4II3cDG80CH2ZvYdqCURPoa+ny/qqMtzq
+W2CnwP59TrotQgKCFJS5EdL3MXaZSvK9z2LERdxDvp4OSoJYoxSMJawfkVwZ15rk
+apA21VwIrpFg54A=
+-----END CERTIFICATE-----`
+
+ /*
+ // Private Key for x509CACertificateRSAotBefore.
+ x509CAPrivateKeyRSANotBefore = `-----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEAnu+lFINdW/A4saaNtl2gbbk52xaCaFUBakUJSJ+i2qivP7P5
+ WDA+e9UOnVxOnTvL6YMkr74Y0E4bWlLkbiivpjUcCVPecakTQViGu0MFCaG+pXTq
+ Y8z8Orkki4aKO7gtfQoGVoUm6Al6/DfJKeg9S7QFD2ZNlSCCA3B62qwBftOKgWNO
+ RUgy2CtqeKA69aMlDIuWaIrVM6AyqBZg1HbRITgKSYin1KM515buQ1Uw6iNHH1pW
+ ASUDZM3nv8l77+0tu9MMMI/2jn1IhfGfk3olDb9d0Nk80EevGeZyFBUNdYKQ/QCC
+ RXtXV2Xy6vzmIR034XfUq93SfMIJoLdhQzGCZwIDAQABAoIBAEneATBOeYZwWDkg
+ un5Gd3hnfN85T/SjhVvZqB3rq6nKemC2Ca4WBgRRmlBChXsIPpZR0CwpwqiVlJrf
+ KbGVEUXDKzuekiTrOrrFJSFFXcMDPHLzqrglnhjA0Z5TMk3dJK8XiKiPi+yN823j
+ k4f5mvtjOHLWzjn/+M0WatLU3IEPnqpnE+pEKrkZQa7Mg+xHprvt67Q4aCgP5lfy
+ A04eoUo7+TMRsK718vb02E81ZQSLgSbQMd0W8Dkt7vRkYRNL0OKBlPQcP9qZlw5s
+ swy4ne9jgmJKY3mmdnURTjvdJb20dUsSSzseZ8Tj6UYUDntXrB62YhZvC0ZRhGY/
+ Nnf10IECgYEAzSi1l1G6ZblV2g2jPqqD4EsdUvitnN5t592dw52+SyizNj7j+pLt
+ OPi+bt5HW4orHQHlPi6wt1BQIZ7UVHmljKQq7ByxOX0SRrRg428JPlFMjCp3bG1t
+ zmRQwADGkfqn3JQcmY9VDjtn5oCx18bNpDR1gNiK6zImK/jmWBOiaKcCgYEAxlKN
+ vYeG70ZrtVBz6jmX6yOtN8/hBA0mifZVHmkKX6earU3Ds2Uj2Hg+AgMqoFD2aRSp
+ wodEYzV6hSpvdLzqBi6SklnfF4NBqJ51TEFBWaVMUZjTwConOcc+vvvSDbCkmnoF
+ yTcqVm2p91HD7859ACcO+m8nsFJGldbJFl8RkEECgYEAkSLajEkyH2Kk3JTHRr7k
+ eplJDniEgbRNdjmusUN36r3JQnftWkf08FfwiIhRXO37IBNGNN5c/+IePhqZxYUl
+ W8CL6OtHaQ8VDdXvsRXNKTvkdkhYoeksRFVtVtd1orH7bK2PKgdfOalHEKc8qRSo
+ SCEge101sbuRi4wSkH6bZ4MCgYAerDXv0j40U5fk+wRyfWXZoDLyJtyOW9pSDB8u
+ DODl2m45z4UtAb+Bg1dTyFmXYe46Yk+/HlydW3APmHiUfYNUYW+Z4vx2Dn7hLWDG
+ 4nDRBJfBJvnZBqv6a65wq1HZfDB5E9ZBQJ7zrxJShfrf4/fBRkkywm5I/vCbzBRd
+ uWZmAQKBgQCJE5rx3rWZz+srunmmkg5LXBMveD+1HRvlwlj/gELG2b2ytNUmnVey
+ 2naApHnvW7lZdrADpbzLKGEDB/EsaIPJjqQw45OoIZwPdM4bm0/w5c1ZLnStXGCz
+ Th/7Sva6x6FW7tHY6ldqybcMj8w3kA4ByQEOg2BtPnWTm1NX/qcr8Q==
+ -----END RSA PRIVATE KEY-----`
+ */
+
+ x509CertificateRSAInvalid = `-----BEGIN CERTIFICATE-----
+mIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urpfO4E7owDQYJKoZIhvcNAQELBQAw
+EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIwFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+/Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39n0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+/ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I=
+-----END CERTIFICATE-----`
+
+ x509CertificateRSAInvalidBlock = `-----BEGIN CERTIFICATE-----
+MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
+EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
+MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
+/Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
+LuYx2rBYSlMSN5UZQm/RxMtXf^K2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
+91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
+kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
+Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
+/ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
+lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
+wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
+OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
+ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I=
+-----END CERTIFICATE-----`
+
+ x509CertificateEmpty = `-----BEGIN CERTIFICATE-----
+-----END CERTIFICATE-----`
+)
diff --git a/internal/configuration/sources.go b/internal/configuration/sources.go
index 8798083aa..e7b5e4ccc 100644
--- a/internal/configuration/sources.go
+++ b/internal/configuration/sources.go
@@ -36,7 +36,7 @@ func NewYAMLFileSources(paths []string) (sources []*YAMLFileSource) {
}
// Name of the Source.
-func (s YAMLFileSource) Name() (name string) {
+func (s *YAMLFileSource) Name() (name string) {
return fmt.Sprintf("yaml file(%s)", s.path)
}
@@ -64,7 +64,7 @@ func NewEnvironmentSource(prefix, delimiter string) (source *EnvironmentSource)
}
// Name of the Source.
-func (s EnvironmentSource) Name() (name string) {
+func (s *EnvironmentSource) Name() (name string) {
return "environment"
}
@@ -90,7 +90,7 @@ func NewSecretsSource(prefix, delimiter string) (source *SecretsSource) {
}
// Name of the Source.
-func (s SecretsSource) Name() (name string) {
+func (s *SecretsSource) Name() (name string) {
return "secrets"
}
@@ -127,7 +127,7 @@ func NewCommandLineSourceWithMapping(flags *pflag.FlagSet, mapping map[string]st
}
// Name of the Source.
-func (s CommandLineSource) Name() (name string) {
+func (s *CommandLineSource) Name() (name string) {
return "command-line"
}
@@ -145,8 +145,8 @@ func (s *CommandLineSource) Load(_ *schema.StructValidator) (err error) {
return s.koanf.Load(posflag.Provider(s.flags, ".", s.koanf), nil)
}
-// NewMapSource returns a new map[string]interface{} source.
-func NewMapSource(m map[string]interface{}) (source *MapSource) {
+// NewMapSource returns a new map[string]any source.
+func NewMapSource(m map[string]any) (source *MapSource) {
return &MapSource{
m: m,
koanf: koanf.New(constDelimiter),
@@ -154,7 +154,7 @@ func NewMapSource(m map[string]interface{}) (source *MapSource) {
}
// Name of the Source.
-func (s MapSource) Name() (name string) {
+func (s *MapSource) Name() (name string) {
return "map"
}
diff --git a/internal/configuration/test_resources/config.yml b/internal/configuration/test_resources/config.yml
index fbe2eca0a..ea48a847f 100644
--- a/internal/configuration/test_resources/config.yml
+++ b/internal/configuration/test_resources/config.yml
@@ -18,6 +18,35 @@ duo_api:
authentication_backend:
ldap:
url: ldap://127.0.0.1
+ tls:
+ private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
+ cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
+ ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
+ Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
+ rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
+ EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
+ L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
+ Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
+ 9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
+ 8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
+ I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
+ CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
+ hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
+ jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
+ E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
+ CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
+ jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
+ Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
+ Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
+ bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
+ otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
+ HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
+ tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
+ USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
+ 1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
+ -----END RSA PRIVATE KEY-----
base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users
diff --git a/internal/configuration/test_resources/config_oidc.yml b/internal/configuration/test_resources/config_oidc.yml
index 974df781e..94a76e0bb 100644
--- a/internal/configuration/test_resources/config_oidc.yml
+++ b/internal/configuration/test_resources/config_oidc.yml
@@ -130,4 +130,8 @@ identity_providers:
allowed_origins:
- https://google.com
- https://example.com
+ clients:
+ - id: abc
+ secret: '123'
+ consent_mode: explicit
...
diff --git a/internal/configuration/types.go b/internal/configuration/types.go
index 90dbaa63c..5946ea7f2 100644
--- a/internal/configuration/types.go
+++ b/internal/configuration/types.go
@@ -38,11 +38,11 @@ type SecretsSource struct {
type CommandLineSource struct {
koanf *koanf.Koanf
flags *pflag.FlagSet
- callback func(flag *pflag.Flag) (string, interface{})
+ callback func(flag *pflag.Flag) (string, any)
}
// MapSource loads configuration from the command line flags.
type MapSource struct {
- m map[string]interface{}
+ m map[string]any
koanf *koanf.Koanf
}
diff --git a/internal/configuration/validator/access_control.go b/internal/configuration/validator/access_control.go
index 3bd475cb2..9509c4cab 100644
--- a/internal/configuration/validator/access_control.go
+++ b/internal/configuration/validator/access_control.go
@@ -3,6 +3,7 @@ package validator
import (
"fmt"
"net"
+ "regexp"
"strings"
"github.com/authelia/authelia/v4/internal/authorization"
@@ -103,6 +104,8 @@ func ValidateRules(config *schema.Configuration, validator *schema.StructValidat
validateMethods(rulePosition, rule, validator)
+ validateQuery(i, rule, config, validator)
+
if rule.Policy == policyBypass {
validateBypass(rulePosition, rule, validator)
}
@@ -149,3 +152,60 @@ func validateMethods(rulePosition int, rule schema.ACLRule, validator *schema.St
}
}
}
+
+//nolint:gocyclo
+func validateQuery(i int, rule schema.ACLRule, config *schema.Configuration, validator *schema.StructValidator) {
+ for j := 0; j < len(config.AccessControl.Rules[i].Query); j++ {
+ for k := 0; k < len(config.AccessControl.Rules[i].Query[j]); k++ {
+ if config.AccessControl.Rules[i].Query[j][k].Operator == "" {
+ if config.AccessControl.Rules[i].Query[j][k].Key != "" {
+ switch config.AccessControl.Rules[i].Query[j][k].Value {
+ case "", nil:
+ config.AccessControl.Rules[i].Query[j][k].Operator = operatorPresent
+ default:
+ config.AccessControl.Rules[i].Query[j][k].Operator = operatorEqual
+ }
+ }
+ } else if !utils.IsStringInSliceFold(config.AccessControl.Rules[i].Query[j][k].Operator, validACLRuleOperators) {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalid, ruleDescriptor(i+1, rule), config.AccessControl.Rules[i].Query[j][k].Operator, strings.Join(validACLRuleOperators, "', '")))
+ }
+
+ if config.AccessControl.Rules[i].Query[j][k].Key == "" {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalidNoValue, ruleDescriptor(i+1, rule), "key"))
+ }
+
+ op := config.AccessControl.Rules[i].Query[j][k].Operator
+
+ if op == "" {
+ continue
+ }
+
+ switch v := config.AccessControl.Rules[i].Query[j][k].Value.(type) {
+ case nil:
+ if op != operatorAbsent && op != operatorPresent {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalidNoValueOperator, ruleDescriptor(i+1, rule), "value", op))
+ }
+ case string:
+ switch op {
+ case operatorPresent, operatorAbsent:
+ if v != "" {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalidValue, ruleDescriptor(i+1, rule), "value", op))
+ }
+ case operatorPattern, operatorNotPattern:
+ var (
+ pattern *regexp.Regexp
+ err error
+ )
+
+ if pattern, err = regexp.Compile(v); err != nil {
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalidValueParse, ruleDescriptor(i+1, rule), "value", err))
+ } else {
+ config.AccessControl.Rules[i].Query[j][k].Value = pattern
+ }
+ }
+ default:
+ validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalidValueType, ruleDescriptor(i+1, rule), v))
+ }
+ }
+ }
+}
diff --git a/internal/configuration/validator/access_control_test.go b/internal/configuration/validator/access_control_test.go
index 5af97a0a9..ae7dabb18 100644
--- a/internal/configuration/validator/access_control_test.go
+++ b/internal/configuration/validator/access_control_test.go
@@ -62,7 +62,7 @@ func (suite *AccessControl) TestShouldValidateEitherDomainsOrDomainsRegex() {
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() {
- suite.config.AccessControl.DefaultPolicy = testInvalidPolicy
+ suite.config.AccessControl.DefaultPolicy = testInvalid
ValidateAccessControl(suite.config, suite.validator)
@@ -135,7 +135,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() {
suite.config.AccessControl.Rules = []schema.ACLRule{
{
Domains: []string{"public.example.com"},
- Policy: testInvalidPolicy,
+ Policy: testInvalid,
},
}
@@ -183,7 +183,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() {
func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() {
domains := []string{"public.example.com"}
- subjects := [][]string{{"invalid"}}
+ subjects := [][]string{{testInvalid}}
suite.config.AccessControl.Rules = []schema.ACLRule{
{
Domains: domains,
@@ -201,6 +201,165 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() {
suite.Assert().EqualError(suite.validator.Errors()[1], fmt.Sprintf(errAccessControlRuleBypassPolicyInvalidWithSubjects, ruleDescriptor(1, suite.config.AccessControl.Rules[0])))
}
+func (suite *AccessControl) TestShouldRaiseErrorBypassWithSubjectDomainRegexGroup() {
+ suite.config.AccessControl.Rules = []schema.ACLRule{
+ {
+ DomainsRegex: MustCompileRegexps([]string{`^(?P\w+)\.example\.com$`}),
+ Policy: "bypass",
+ },
+ }
+
+ ValidateRules(suite.config, suite.validator)
+
+ suite.Require().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: 'policy' option 'bypass' is not supported when 'domain_regex' option contains the user or group named matches. For more information see: https://www.authelia.com/c/acl-match-concept-2")
+}
+
+func (suite *AccessControl) TestShouldSetQueryDefaults() {
+ domains := []string{"public.example.com"}
+ suite.config.AccessControl.Rules = []schema.ACLRule{
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "", Key: "example"},
+ },
+ {
+ {Operator: "", Key: "example", Value: "test"},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "pattern", Key: "a", Value: "^(x|y|z)$"},
+ },
+ },
+ },
+ }
+
+ ValidateRules(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Equal("present", suite.config.AccessControl.Rules[0].Query[0][0].Operator)
+ suite.Assert().Equal("equal", suite.config.AccessControl.Rules[0].Query[1][0].Operator)
+
+ suite.Require().Len(suite.config.AccessControl.Rules, 2)
+ suite.Require().Len(suite.config.AccessControl.Rules[1].Query, 1)
+ suite.Require().Len(suite.config.AccessControl.Rules[1].Query[0], 1)
+
+ t := ®exp.Regexp{}
+
+ suite.Assert().IsType(t, suite.config.AccessControl.Rules[1].Query[0][0].Value)
+}
+
+func (suite *AccessControl) TestShouldErrorOnInvalidRulesQuery() {
+ domains := []string{"public.example.com"}
+ suite.config.AccessControl.Rules = []schema.ACLRule{
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "equal", Key: "example"},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "present"},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "present", Key: "a"},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "absent", Key: "a"},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "not", Key: "a", Value: "a"},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "pattern", Key: "a", Value: "(bad pattern"},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "present", Key: "a", Value: "not good"},
+ },
+ },
+ },
+ {
+ Domains: domains,
+ Policy: "bypass",
+ Query: [][]schema.ACLQueryRule{
+ {
+ {Operator: "present", Key: "a", Value: 5},
+ },
+ },
+ },
+ }
+
+ ValidateRules(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 7)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'query' option 'value' is invalid: must have a value when the operator is 'equal'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #2 (domain 'public.example.com'): 'query' option 'key' is invalid: must have a value")
+ suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #5 (domain 'public.example.com'): 'query' option 'key' is invalid: must have a value")
+ suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #6 (domain 'public.example.com'): 'query' option 'operator' with value 'not' is invalid: must be one of 'present', 'absent', 'equal', 'not equal', 'pattern', 'not pattern'")
+ suite.Assert().EqualError(suite.validator.Errors()[4], "access control: rule #7 (domain 'public.example.com'): 'query' option 'value' is invalid: error parsing regexp: missing closing ): `(bad pattern`")
+ suite.Assert().EqualError(suite.validator.Errors()[5], "access control: rule #8 (domain 'public.example.com'): 'query' option 'value' is invalid: must not have a value when the operator is 'present'")
+ suite.Assert().EqualError(suite.validator.Errors()[6], "access control: rule #9 (domain 'public.example.com'): 'query' option 'value' is invalid: expected type was string but got int")
+}
+
func TestAccessControl(t *testing.T) {
suite.Run(t, new(AccessControl))
}
@@ -216,3 +375,13 @@ func TestShouldReturnCorrectResultsForValidNetworkGroups(t *testing.T) {
assert.True(t, validNetwork)
assert.False(t, invalidNetwork)
}
+
+func MustCompileRegexps(exps []string) (regexps []regexp.Regexp) {
+ regexps = make([]regexp.Regexp, len(exps))
+
+ for i, exp := range exps {
+ regexps[i] = *regexp.MustCompile(exp)
+ }
+
+ return regexps
+}
diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go
index 59ea0518a..62fe289b2 100644
--- a/internal/configuration/validator/authentication.go
+++ b/internal/configuration/validator/authentication.go
@@ -5,26 +5,18 @@ import (
"net/url"
"strings"
+ "github.com/go-crypt/crypt"
+
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// ValidateAuthenticationBackend validates and updates the authentication backend configuration.
-func ValidateAuthenticationBackend(config *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
+func ValidateAuthenticationBackend(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP == nil && config.File == nil {
validator.Push(fmt.Errorf(errFmtAuthBackendNotConfigured))
}
- if config.LDAP != nil && config.File != nil {
- validator.Push(fmt.Errorf(errFmtAuthBackendMultipleConfigured))
- }
-
- if config.File != nil {
- validateFileAuthenticationBackend(config.File, validator)
- } else if config.LDAP != nil {
- validateLDAPAuthenticationBackend(config, validator)
- }
-
if config.RefreshInterval == "" {
config.RefreshInterval = schema.RefreshIntervalDefault
} else {
@@ -42,110 +34,322 @@ func ValidateAuthenticationBackend(config *schema.AuthenticationBackendConfigura
validator.Push(fmt.Errorf(errFmtAuthBackendPasswordResetCustomURLScheme, config.PasswordReset.CustomURL.String(), config.PasswordReset.CustomURL.Scheme))
}
}
+
+ if config.LDAP != nil && config.File != nil {
+ validator.Push(fmt.Errorf(errFmtAuthBackendMultipleConfigured))
+ }
+
+ if config.File != nil {
+ validateFileAuthenticationBackend(config.File, validator)
+ }
+
+ if config.LDAP != nil {
+ validateLDAPAuthenticationBackend(config, validator)
+ }
}
// validateFileAuthenticationBackend validates and updates the file authentication backend configuration.
-func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
+func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackend, validator *schema.StructValidator) {
if config.Path == "" {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPathNotConfigured))
}
- if config.Password == nil {
- config.Password = &schema.DefaultPasswordConfiguration
- } else {
- ValidatePasswordConfiguration(config.Password, validator)
- }
+ ValidatePasswordConfiguration(&config.Password, validator)
}
// ValidatePasswordConfiguration validates the file auth backend password configuration.
-func ValidatePasswordConfiguration(config *schema.PasswordConfiguration, validator *schema.StructValidator) {
- // Salt Length.
+func ValidatePasswordConfiguration(config *schema.Password, validator *schema.StructValidator) {
+ validateFileAuthenticationBackendPasswordConfigLegacy(config)
+
switch {
- case config.SaltLength == 0:
- config.SaltLength = schema.DefaultPasswordConfiguration.SaltLength
- case config.SaltLength < 8:
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordSaltLength, config.SaltLength))
- }
-
- switch config.Algorithm {
- case "":
- config.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
- fallthrough
- case hashArgon2id:
- validateFileAuthenticationBackendArgon2id(config, validator)
- case hashSHA512:
- validateFileAuthenticationBackendSHA512(config)
+ case config.Algorithm == "":
+ config.Algorithm = schema.DefaultPasswordConfig.Algorithm
+ case utils.IsStringInSlice(config.Algorithm, validHashAlgorithms):
+ break
default:
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm))
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm, strings.Join(validHashAlgorithms, "', '")))
}
- if config.Iterations < 1 {
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Iterations))
+ validateFileAuthenticationBackendPasswordConfigArgon2(config, validator)
+ validateFileAuthenticationBackendPasswordConfigSHA2Crypt(config, validator)
+ validateFileAuthenticationBackendPasswordConfigPBKDF2(config, validator)
+ validateFileAuthenticationBackendPasswordConfigBCrypt(config, validator)
+ validateFileAuthenticationBackendPasswordConfigSCrypt(config, validator)
+}
+
+//nolint:gocyclo // Function is well formed.
+func validateFileAuthenticationBackendPasswordConfigArgon2(config *schema.Password, validator *schema.StructValidator) {
+ switch {
+ case config.Argon2.Variant == "":
+ config.Argon2.Variant = schema.DefaultPasswordConfig.Argon2.Variant
+ case utils.IsStringInSlice(config.Argon2.Variant, validArgon2Variants):
+ break
+ default:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashArgon2, config.Argon2.Variant, strings.Join(validArgon2Variants, "', '")))
+ }
+
+ switch {
+ case config.Argon2.Iterations == 0:
+ config.Argon2.Iterations = schema.DefaultPasswordConfig.Argon2.Iterations
+ case config.Argon2.Iterations < crypt.Argon2IterationsMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "iterations", config.Argon2.Iterations, crypt.Argon2IterationsMin))
+ case config.Argon2.Iterations > crypt.Argon2IterationsMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "iterations", config.Argon2.Iterations, crypt.Argon2IterationsMax))
+ }
+
+ switch {
+ case config.Argon2.Parallelism == 0:
+ config.Argon2.Parallelism = schema.DefaultPasswordConfig.Argon2.Parallelism
+ case config.Argon2.Parallelism < crypt.Argon2ParallelismMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "parallelism", config.Argon2.Parallelism, crypt.Argon2ParallelismMin))
+ case config.Argon2.Parallelism > crypt.Argon2ParallelismMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "parallelism", config.Argon2.Parallelism, crypt.Argon2ParallelismMax))
+ }
+
+ switch {
+ case config.Argon2.Memory == 0:
+ config.Argon2.Memory = schema.DefaultPasswordConfig.Argon2.Memory
+ case config.Argon2.Memory < 0:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "memory", config.Argon2.Parallelism, 1))
+ case config.Argon2.Memory < (crypt.Argon2MemoryMinParallelismMultiplier * config.Argon2.Parallelism):
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2MemoryTooLow, config.Argon2.Memory, config.Argon2.Parallelism*crypt.Argon2MemoryMinParallelismMultiplier, config.Argon2.Parallelism, crypt.Argon2MemoryMinParallelismMultiplier))
+ }
+
+ switch {
+ case config.Argon2.KeyLength == 0:
+ config.Argon2.KeyLength = schema.DefaultPasswordConfig.Argon2.KeyLength
+ case config.Argon2.KeyLength < crypt.Argon2KeySizeMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "key_length", config.Argon2.KeyLength, crypt.Argon2KeySizeMin))
+ case config.Argon2.KeyLength > crypt.Argon2KeySizeMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "key_length", config.Argon2.KeyLength, crypt.Argon2KeySizeMax))
+ }
+
+ switch {
+ case config.Argon2.SaltLength == 0:
+ config.Argon2.SaltLength = schema.DefaultPasswordConfig.Argon2.SaltLength
+ case config.Argon2.SaltLength < crypt.Argon2SaltSizeMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "salt_length", config.Argon2.SaltLength, crypt.Argon2SaltSizeMin))
+ case config.Argon2.SaltLength > crypt.Argon2SaltSizeMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "salt_length", config.Argon2.SaltLength, crypt.Argon2SaltSizeMax))
}
}
-func validateFileAuthenticationBackendSHA512(config *schema.PasswordConfiguration) {
- // Iterations (time).
- if config.Iterations == 0 {
- config.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
- }
-}
-func validateFileAuthenticationBackendArgon2id(config *schema.PasswordConfiguration, validator *schema.StructValidator) {
- // Iterations (time).
- if config.Iterations == 0 {
- config.Iterations = schema.DefaultPasswordConfiguration.Iterations
+func validateFileAuthenticationBackendPasswordConfigSHA2Crypt(config *schema.Password, validator *schema.StructValidator) {
+ switch {
+ case config.SHA2Crypt.Variant == "":
+ config.SHA2Crypt.Variant = schema.DefaultPasswordConfig.SHA2Crypt.Variant
+ case utils.IsStringInSlice(config.SHA2Crypt.Variant, validSHA2CryptVariants):
+ break
+ default:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashSHA2Crypt, config.SHA2Crypt.Variant, strings.Join(validSHA2CryptVariants, "', '")))
}
- // Parallelism.
- if config.Parallelism == 0 {
- config.Parallelism = schema.DefaultPasswordConfiguration.Parallelism
- } else if config.Parallelism < 1 {
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidParallelism, config.Parallelism))
+ switch {
+ case config.SHA2Crypt.Iterations == 0:
+ config.SHA2Crypt.Iterations = schema.DefaultPasswordConfig.SHA2Crypt.Iterations
+ case config.SHA2Crypt.Iterations < crypt.SHA2CryptIterationsMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSHA2Crypt, "iterations", config.SHA2Crypt.Iterations, crypt.SHA2CryptIterationsMin))
+ case config.SHA2Crypt.Iterations > crypt.SHA2CryptIterationsMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSHA2Crypt, "iterations", config.SHA2Crypt.Iterations, crypt.SHA2CryptIterationsMax))
}
- // Memory.
- if config.Memory == 0 {
- config.Memory = schema.DefaultPasswordConfiguration.Memory
- } else if config.Memory < config.Parallelism*8 {
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidMemory, config.Parallelism, config.Parallelism*8, config.Memory))
- }
-
- // Key Length.
- if config.KeyLength == 0 {
- config.KeyLength = schema.DefaultPasswordConfiguration.KeyLength
- } else if config.KeyLength < 16 {
- validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength, config.KeyLength))
+ switch {
+ case config.SHA2Crypt.SaltLength == 0:
+ config.SHA2Crypt.SaltLength = schema.DefaultPasswordConfig.SHA2Crypt.SaltLength
+ case config.SHA2Crypt.SaltLength < crypt.SHA2CryptSaltSizeMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSHA2Crypt, "salt_length", config.SHA2Crypt.SaltLength, crypt.SHA2CryptSaltSizeMin))
+ case config.SHA2Crypt.SaltLength > crypt.SHA2CryptSaltSizeMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSHA2Crypt, "salt_length", config.SHA2Crypt.SaltLength, crypt.SHA2CryptSaltSizeMax))
}
}
-func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
- if config.LDAP.Timeout == 0 {
- config.LDAP.Timeout = schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout
+func validateFileAuthenticationBackendPasswordConfigPBKDF2(config *schema.Password, validator *schema.StructValidator) {
+ switch {
+ case config.PBKDF2.Variant == "":
+ config.PBKDF2.Variant = schema.DefaultPasswordConfig.PBKDF2.Variant
+ case utils.IsStringInSlice(config.PBKDF2.Variant, validPBKDF2Variants):
+ break
+ default:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashPBKDF2, config.PBKDF2.Variant, strings.Join(validPBKDF2Variants, "', '")))
}
+ switch {
+ case config.PBKDF2.Iterations == 0:
+ config.PBKDF2.Iterations = schema.DefaultPasswordConfig.PBKDF2.Iterations
+ case config.PBKDF2.Iterations < crypt.PBKDF2IterationsMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashPBKDF2, "iterations", config.PBKDF2.Iterations, crypt.PBKDF2IterationsMin))
+ case config.PBKDF2.Iterations > crypt.PBKDF2IterationsMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashPBKDF2, "iterations", config.PBKDF2.Iterations, crypt.PBKDF2IterationsMax))
+ }
+
+ switch {
+ case config.PBKDF2.SaltLength == 0:
+ config.PBKDF2.SaltLength = schema.DefaultPasswordConfig.PBKDF2.SaltLength
+ case config.PBKDF2.SaltLength < crypt.PBKDF2SaltSizeMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashPBKDF2, "salt_length", config.PBKDF2.SaltLength, crypt.PBKDF2SaltSizeMin))
+ case config.PBKDF2.SaltLength > crypt.PBKDF2SaltSizeMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashPBKDF2, "salt_length", config.PBKDF2.SaltLength, crypt.PBKDF2SaltSizeMax))
+ }
+}
+
+func validateFileAuthenticationBackendPasswordConfigBCrypt(config *schema.Password, validator *schema.StructValidator) {
+ switch {
+ case config.BCrypt.Variant == "":
+ config.BCrypt.Variant = schema.DefaultPasswordConfig.BCrypt.Variant
+ case utils.IsStringInSlice(config.BCrypt.Variant, validBCryptVariants):
+ break
+ default:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashBCrypt, config.BCrypt.Variant, strings.Join(validBCryptVariants, "', '")))
+ }
+
+ switch {
+ case config.BCrypt.Cost == 0:
+ config.BCrypt.Cost = schema.DefaultPasswordConfig.BCrypt.Cost
+ case config.BCrypt.Cost < crypt.BcryptCostMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashBCrypt, "cost", config.BCrypt.Cost, crypt.BcryptCostMin))
+ case config.BCrypt.Cost > crypt.BcryptCostMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashBCrypt, "cost", config.BCrypt.Cost, crypt.BcryptCostMax))
+ }
+}
+
+func validateFileAuthenticationBackendPasswordConfigSCrypt(config *schema.Password, validator *schema.StructValidator) {
+ switch {
+ case config.SCrypt.Iterations == 0:
+ config.SCrypt.Iterations = schema.DefaultPasswordConfig.SCrypt.Iterations
+ case config.SCrypt.Iterations < crypt.ScryptIterationsMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "iterations", config.SCrypt.Iterations, crypt.ScryptIterationsMin))
+ }
+
+ switch {
+ case config.SCrypt.BlockSize == 0:
+ config.SCrypt.BlockSize = schema.DefaultPasswordConfig.SCrypt.BlockSize
+ case config.SCrypt.BlockSize < crypt.ScryptBlockSizeMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "block_size", config.SCrypt.BlockSize, crypt.ScryptBlockSizeMin))
+ case config.SCrypt.BlockSize > crypt.ScryptBlockSizeMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSCrypt, "block_size", config.SCrypt.BlockSize, crypt.ScryptBlockSizeMax))
+ }
+
+ switch {
+ case config.SCrypt.Parallelism == 0:
+ config.SCrypt.Parallelism = schema.DefaultPasswordConfig.SCrypt.Parallelism
+ case config.SCrypt.Parallelism < crypt.ScryptParallelismMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "parallelism", config.SCrypt.Parallelism, crypt.ScryptParallelismMin))
+ }
+
+ switch {
+ case config.SCrypt.KeyLength == 0:
+ config.SCrypt.KeyLength = schema.DefaultPasswordConfig.SCrypt.KeyLength
+ case config.SCrypt.KeyLength < crypt.ScryptKeySizeMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "key_length", config.SCrypt.KeyLength, crypt.ScryptKeySizeMin))
+ case config.SCrypt.KeyLength > crypt.ScryptKeySizeMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSCrypt, "key_length", config.SCrypt.KeyLength, crypt.ScryptKeySizeMax))
+ }
+
+ switch {
+ case config.SCrypt.SaltLength == 0:
+ config.SCrypt.SaltLength = schema.DefaultPasswordConfig.SCrypt.SaltLength
+ case config.SCrypt.SaltLength < crypt.ScryptSaltSizeMin:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "salt_length", config.SCrypt.SaltLength, crypt.ScryptSaltSizeMin))
+ case config.SCrypt.SaltLength > crypt.ScryptSaltSizeMax:
+ validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSCrypt, "salt_length", config.SCrypt.SaltLength, crypt.ScryptSaltSizeMax))
+ }
+}
+
+//nolint:gocyclo // Function is clear enough.
+func validateFileAuthenticationBackendPasswordConfigLegacy(config *schema.Password) {
+ switch config.Algorithm {
+ case hashLegacySHA512:
+ config.Algorithm = hashSHA2Crypt
+
+ if config.SHA2Crypt.Variant == "" {
+ config.SHA2Crypt.Variant = schema.DefaultPasswordConfig.SHA2Crypt.Variant
+ }
+
+ if config.Iterations > 0 && config.SHA2Crypt.Iterations == 0 {
+ config.SHA2Crypt.Iterations = config.Iterations
+ }
+
+ if config.SaltLength > 0 && config.SHA2Crypt.SaltLength == 0 {
+ if config.SaltLength > 16 {
+ config.SHA2Crypt.SaltLength = 16
+ } else {
+ config.SHA2Crypt.SaltLength = config.SaltLength
+ }
+ }
+ case hashLegacyArgon2id:
+ config.Algorithm = hashArgon2
+
+ if config.Argon2.Variant == "" {
+ config.Argon2.Variant = schema.DefaultPasswordConfig.Argon2.Variant
+ }
+
+ if config.Iterations > 0 && config.Argon2.Memory == 0 {
+ config.Argon2.Iterations = config.Iterations
+ }
+
+ if config.Memory > 0 && config.Argon2.Memory == 0 {
+ config.Argon2.Memory = config.Memory * 1024
+ }
+
+ if config.Parallelism > 0 && config.Argon2.Parallelism == 0 {
+ config.Argon2.Parallelism = config.Parallelism
+ }
+
+ if config.KeyLength > 0 && config.Argon2.KeyLength == 0 {
+ config.Argon2.KeyLength = config.KeyLength
+ }
+
+ if config.SaltLength > 0 && config.Argon2.SaltLength == 0 {
+ config.Argon2.SaltLength = config.SaltLength
+ }
+ }
+}
+
+func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP.Implementation == "" {
- config.LDAP.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
+ config.LDAP.Implementation = schema.LDAPImplementationCustom
}
- if config.LDAP.TLS == nil {
- config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
- } else if config.LDAP.TLS.MinimumVersion == "" {
- config.LDAP.TLS.MinimumVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion
- }
-
- if _, err := utils.TLSStringToTLSConfigVersion(config.LDAP.TLS.MinimumVersion); err != nil {
- validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSMinVersion, config.LDAP.TLS.MinimumVersion, err))
- }
+ var implementation *schema.LDAPAuthenticationBackend
switch config.LDAP.Implementation {
case schema.LDAPImplementationCustom:
- setDefaultImplementationCustomLDAPAuthenticationBackend(config.LDAP)
+ implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom
case schema.LDAPImplementationActiveDirectory:
- setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(config.LDAP)
+ implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory
default:
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join([]string{schema.LDAPImplementationCustom, schema.LDAPImplementationActiveDirectory}, "', '")))
}
+ configDefaultTLS := &schema.TLSConfig{}
+
+ if implementation != nil {
+ if config.LDAP.Timeout == 0 {
+ config.LDAP.Timeout = implementation.Timeout
+ }
+
+ configDefaultTLS = &schema.TLSConfig{
+ MinimumVersion: implementation.TLS.MinimumVersion,
+ MaximumVersion: implementation.TLS.MaximumVersion,
+ }
+
+ setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config.LDAP, implementation)
+ }
+
+ if config.LDAP.URL == "" {
+ validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
+ } else {
+ configDefaultTLS.ServerName = validateLDAPAuthenticationBackendURL(config.LDAP, validator)
+ }
+
+ if config.LDAP.TLS == nil {
+ config.LDAP.TLS = &schema.TLSConfig{}
+ }
+
+ if err := ValidateTLSConfig(config.LDAP.TLS, configDefaultTLS); err != nil {
+ validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSConfigInvalid, err))
+ }
+
if strings.Contains(config.LDAP.UsersFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))
}
@@ -158,16 +362,40 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackendConfi
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{1}", "{username}"))
}
- if config.LDAP.URL == "" {
- validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
- } else {
- validateLDAPAuthenticationBackendURL(config.LDAP, validator)
- }
-
validateLDAPRequiredParameters(config, validator)
}
-func validateLDAPAuthenticationBackendURL(config *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
+func ldapImplementationShouldSetStr(config, implementation string) bool {
+ return config == "" && implementation != ""
+}
+
+func setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config *schema.LDAPAuthenticationBackend, implementation *schema.LDAPAuthenticationBackend) {
+ if ldapImplementationShouldSetStr(config.UsersFilter, implementation.UsersFilter) {
+ config.UsersFilter = implementation.UsersFilter
+ }
+
+ if ldapImplementationShouldSetStr(config.UsernameAttribute, implementation.UsernameAttribute) {
+ config.UsernameAttribute = implementation.UsernameAttribute
+ }
+
+ if ldapImplementationShouldSetStr(config.DisplayNameAttribute, implementation.DisplayNameAttribute) {
+ config.DisplayNameAttribute = implementation.DisplayNameAttribute
+ }
+
+ if ldapImplementationShouldSetStr(config.MailAttribute, implementation.MailAttribute) {
+ config.MailAttribute = implementation.MailAttribute
+ }
+
+ if ldapImplementationShouldSetStr(config.GroupsFilter, implementation.GroupsFilter) {
+ config.GroupsFilter = implementation.GroupsFilter
+ }
+
+ if ldapImplementationShouldSetStr(config.GroupNameAttribute, implementation.GroupNameAttribute) {
+ config.GroupNameAttribute = implementation.GroupNameAttribute
+ }
+}
+
+func validateLDAPAuthenticationBackendURL(config *schema.LDAPAuthenticationBackend, validator *schema.StructValidator) (hostname string) {
var (
parsedURL *url.URL
err error
@@ -186,12 +414,11 @@ func validateLDAPAuthenticationBackendURL(config *schema.LDAPAuthenticationBacke
}
config.URL = parsedURL.String()
- if config.TLS.ServerName == "" {
- config.TLS.ServerName = parsedURL.Hostname()
- }
+
+ return parsedURL.Hostname()
}
-func validateLDAPRequiredParameters(config *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
+func validateLDAPRequiredParameters(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP.PermitUnauthenticatedBind {
if config.LDAP.Password != "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithPassword))
@@ -237,47 +464,3 @@ func validateLDAPRequiredParameters(config *schema.AuthenticationBackendConfigur
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter))
}
}
-
-func setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration) {
- if config.UsersFilter == "" {
- config.UsersFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter
- }
-
- if config.UsernameAttribute == "" {
- config.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute
- }
-
- if config.DisplayNameAttribute == "" {
- config.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute
- }
-
- if config.MailAttribute == "" {
- config.MailAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute
- }
-
- if config.GroupsFilter == "" {
- config.GroupsFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter
- }
-
- if config.GroupNameAttribute == "" {
- config.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute
- }
-}
-
-func setDefaultImplementationCustomLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration) {
- if config.UsernameAttribute == "" {
- config.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute
- }
-
- if config.GroupNameAttribute == "" {
- config.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.GroupNameAttribute
- }
-
- if config.MailAttribute == "" {
- config.MailAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.MailAttribute
- }
-
- if config.DisplayNameAttribute == "" {
- config.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.DisplayNameAttribute
- }
-}
diff --git a/internal/configuration/validator/authentication_test.go b/internal/configuration/validator/authentication_test.go
index 9df81a38b..5f589063c 100644
--- a/internal/configuration/validator/authentication_test.go
+++ b/internal/configuration/validator/authentication_test.go
@@ -1,6 +1,7 @@
package validator
import (
+ "crypto/tls"
"net/url"
"testing"
"time"
@@ -14,22 +15,28 @@ import (
func TestShouldRaiseErrorWhenBothBackendsProvided(t *testing.T) {
validator := schema.NewStructValidator()
- backendConfig := schema.AuthenticationBackendConfiguration{}
+ backendConfig := schema.AuthenticationBackend{}
- backendConfig.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
- backendConfig.File = &schema.FileAuthenticationBackendConfiguration{
+ backendConfig.LDAP = &schema.LDAPAuthenticationBackend{}
+ backendConfig.File = &schema.FileAuthenticationBackend{
Path: "/tmp",
}
ValidateAuthenticationBackend(&backendConfig, validator)
- require.Len(t, validator.Errors(), 1)
+ require.Len(t, validator.Errors(), 7)
assert.EqualError(t, validator.Errors()[0], "authentication_backend: please ensure only one of the 'file' or 'ldap' backend is configured")
+ assert.EqualError(t, validator.Errors()[1], "authentication_backend: ldap: option 'url' is required")
+ assert.EqualError(t, validator.Errors()[2], "authentication_backend: ldap: option 'user' is required")
+ assert.EqualError(t, validator.Errors()[3], "authentication_backend: ldap: option 'password' is required")
+ assert.EqualError(t, validator.Errors()[4], "authentication_backend: ldap: option 'base_dn' is required")
+ assert.EqualError(t, validator.Errors()[5], "authentication_backend: ldap: option 'users_filter' is required")
+ assert.EqualError(t, validator.Errors()[6], "authentication_backend: ldap: option 'groups_filter' is required")
}
func TestShouldRaiseErrorWhenNoBackendProvided(t *testing.T) {
validator := schema.NewStructValidator()
- backendConfig := schema.AuthenticationBackendConfiguration{}
+ backendConfig := schema.AuthenticationBackend{}
ValidateAuthenticationBackend(&backendConfig, validator)
@@ -39,23 +46,18 @@ func TestShouldRaiseErrorWhenNoBackendProvided(t *testing.T) {
type FileBasedAuthenticationBackend struct {
suite.Suite
- config schema.AuthenticationBackendConfiguration
+ config schema.AuthenticationBackend
validator *schema.StructValidator
}
func (suite *FileBasedAuthenticationBackend) SetupTest() {
+ password := schema.DefaultPasswordConfig
+
suite.validator = schema.NewStructValidator()
- suite.config = schema.AuthenticationBackendConfiguration{}
- suite.config.File = &schema.FileAuthenticationBackendConfiguration{Path: "/a/path", Password: &schema.PasswordConfiguration{
- Algorithm: schema.DefaultPasswordConfiguration.Algorithm,
- Iterations: schema.DefaultPasswordConfiguration.Iterations,
- Parallelism: schema.DefaultPasswordConfiguration.Parallelism,
- Memory: schema.DefaultPasswordConfiguration.Memory,
- KeyLength: schema.DefaultPasswordConfiguration.KeyLength,
- SaltLength: schema.DefaultPasswordConfiguration.SaltLength,
- }}
- suite.config.File.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
+ suite.config = schema.AuthenticationBackend{}
+ suite.config.File = &schema.FileAuthenticationBackend{Path: "/a/path", Password: password}
}
+
func (suite *FileBasedAuthenticationBackend) TestShouldValidateCompleteConfiguration() {
ValidateAuthenticationBackend(&suite.config, suite.validator)
@@ -74,20 +76,8 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenNoPathProvi
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: option 'path' is required")
}
-func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenMemoryNotMoreThanEightTimesParallelism() {
- suite.config.File.Password.Memory = 8
- suite.config.File.Password.Parallelism = 2
-
- ValidateAuthenticationBackend(&suite.config, suite.validator)
-
- suite.Assert().Len(suite.validator.Warnings(), 0)
- suite.Require().Len(suite.validator.Errors(), 1)
-
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'memory' must at least be parallelism multiplied by 8 when using algorithm 'argon2id' with parallelism 2 it should be at least 16 but it is configured as '8'")
-}
-
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWhenBlank() {
- suite.config.File.Password = &schema.PasswordConfiguration{}
+ suite.config.File.Password = schema.Password{}
suite.Assert().Equal(0, suite.config.File.Password.KeyLength)
suite.Assert().Equal(0, suite.config.File.Password.Iterations)
@@ -101,51 +91,384 @@ func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWh
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.KeyLength, suite.config.File.Password.KeyLength)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.config.File.Password.Iterations)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.config.File.Password.SaltLength)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.config.File.Password.Algorithm)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.config.File.Password.Memory)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.config.File.Password.Parallelism)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.KeyLength, suite.config.File.Password.KeyLength)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.Iterations, suite.config.File.Password.Iterations)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.SaltLength, suite.config.File.Password.SaltLength)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.Algorithm, suite.config.File.Password.Algorithm)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.Memory, suite.config.File.Password.Memory)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.Parallelism, suite.config.File.Password.Parallelism)
}
-func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWhenOnlySHA512Set() {
- suite.config.File.Password = &schema.PasswordConfiguration{}
+func (suite *FileBasedAuthenticationBackend) TestShouldMigrateLegacyConfigurationSHA512() {
+ suite.config.File.Password = schema.Password{}
suite.Assert().Equal("", suite.config.File.Password.Algorithm)
- suite.config.File.Password.Algorithm = "sha512"
+
+ suite.config.File.Password = schema.Password{
+ Algorithm: digestSHA512,
+ Iterations: 1000000,
+ SaltLength: 8,
+ }
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
- suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.KeyLength, suite.config.File.Password.KeyLength)
- suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Iterations, suite.config.File.Password.Iterations)
- suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.SaltLength, suite.config.File.Password.SaltLength)
- suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Algorithm, suite.config.File.Password.Algorithm)
- suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Memory, suite.config.File.Password.Memory)
- suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Parallelism, suite.config.File.Password.Parallelism)
+ suite.Assert().Equal(hashSHA2Crypt, suite.config.File.Password.Algorithm)
+ suite.Assert().Equal(digestSHA512, suite.config.File.Password.SHA2Crypt.Variant)
+ suite.Assert().Equal(1000000, suite.config.File.Password.SHA2Crypt.Iterations)
+ suite.Assert().Equal(8, suite.config.File.Password.SHA2Crypt.SaltLength)
}
-func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenKeyLengthTooLow() {
- suite.config.File.Password.KeyLength = 1
+
+func (suite *FileBasedAuthenticationBackend) TestShouldMigrateLegacyConfigurationSHA512ButNotOverride() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+
+ suite.config.File.Password = schema.Password{
+ Algorithm: digestSHA512,
+ Iterations: 1000000,
+ SaltLength: 8,
+ SHA2Crypt: schema.SHA2CryptPassword{
+ Variant: digestSHA256,
+ Iterations: 50000,
+ SaltLength: 12,
+ },
+ }
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Equal(hashSHA2Crypt, suite.config.File.Password.Algorithm)
+ suite.Assert().Equal(digestSHA256, suite.config.File.Password.SHA2Crypt.Variant)
+ suite.Assert().Equal(50000, suite.config.File.Password.SHA2Crypt.Iterations)
+ suite.Assert().Equal(12, suite.config.File.Password.SHA2Crypt.SaltLength)
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldMigrateLegacyConfigurationSHA512Alt() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+
+ suite.config.File.Password = schema.Password{
+ Algorithm: digestSHA512,
+ Iterations: 1000000,
+ SaltLength: 64,
+ }
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Equal(hashSHA2Crypt, suite.config.File.Password.Algorithm)
+ suite.Assert().Equal(digestSHA512, suite.config.File.Password.SHA2Crypt.Variant)
+ suite.Assert().Equal(1000000, suite.config.File.Password.SHA2Crypt.Iterations)
+ suite.Assert().Equal(16, suite.config.File.Password.SHA2Crypt.SaltLength)
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldMigrateLegacyConfigurationArgon2() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+
+ suite.config.File.Password = schema.Password{
+ Algorithm: "argon2id",
+ Iterations: 4,
+ Memory: 1024,
+ Parallelism: 4,
+ KeyLength: 64,
+ SaltLength: 64,
+ }
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Equal("argon2", suite.config.File.Password.Algorithm)
+ suite.Assert().Equal("argon2id", suite.config.File.Password.Argon2.Variant)
+ suite.Assert().Equal(4, suite.config.File.Password.Argon2.Iterations)
+ suite.Assert().Equal(1048576, suite.config.File.Password.Argon2.Memory)
+ suite.Assert().Equal(4, suite.config.File.Password.Argon2.Parallelism)
+ suite.Assert().Equal(64, suite.config.File.Password.Argon2.KeyLength)
+ suite.Assert().Equal(64, suite.config.File.Password.Argon2.SaltLength)
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldMigrateLegacyConfigurationArgon2ButNotOverride() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+
+ suite.config.File.Password = schema.Password{
+ Algorithm: "argon2id",
+ Iterations: 4,
+ Memory: 1024,
+ Parallelism: 4,
+ KeyLength: 64,
+ SaltLength: 64,
+ Argon2: schema.Argon2Password{
+ Variant: "argon2d",
+ Iterations: 1,
+ Memory: 2048,
+ Parallelism: 1,
+ KeyLength: 32,
+ SaltLength: 32,
+ },
+ }
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Equal("argon2", suite.config.File.Password.Algorithm)
+ suite.Assert().Equal("argon2d", suite.config.File.Password.Argon2.Variant)
+ suite.Assert().Equal(1, suite.config.File.Password.Argon2.Iterations)
+ suite.Assert().Equal(2048, suite.config.File.Password.Argon2.Memory)
+ suite.Assert().Equal(1, suite.config.File.Password.Argon2.Parallelism)
+ suite.Assert().Equal(32, suite.config.File.Password.Argon2.KeyLength)
+ suite.Assert().Equal(32, suite.config.File.Password.Argon2.SaltLength)
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldMigrateLegacyConfigurationWhenOnlySHA512Set() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+ suite.config.File.Password.Algorithm = digestSHA512
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Equal(hashSHA2Crypt, suite.config.File.Password.Algorithm)
+ suite.Assert().Equal(digestSHA512, suite.config.File.Password.SHA2Crypt.Variant)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.SHA2Crypt.Iterations, suite.config.File.Password.SHA2Crypt.Iterations)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.SHA2Crypt.SaltLength, suite.config.File.Password.SHA2Crypt.SaltLength)
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidArgon2Variant() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+ suite.config.File.Password.Algorithm = "argon2"
+ suite.config.File.Password.Argon2.Variant = testInvalid
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'key_length' must be 16 or more when using algorithm 'argon2id' but it is configured as '1'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'variant' is configured as 'invalid' but must be one of the following values: 'argon2id', 'id', 'argon2i', 'i', 'argon2d', 'd'")
}
-func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSaltLengthTooLow() {
- suite.config.File.Password.SaltLength = -1
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptVariant() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+ suite.config.File.Password.Algorithm = hashSHA2Crypt
+ suite.config.File.Password.SHA2Crypt.Variant = testInvalid
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'salt_length' must be 2 or more but it is configured a '-1'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'variant' is configured as 'invalid' but must be one of the following values: 'sha256', 'sha512'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptSaltLength() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+ suite.config.File.Password.Algorithm = hashSHA2Crypt
+ suite.config.File.Password.SHA2Crypt.SaltLength = 40
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'salt_length' is configured as '40' but must be less than or equal to '16'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidPBKDF2Variant() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+ suite.config.File.Password.Algorithm = "pbkdf2"
+ suite.config.File.Password.PBKDF2.Variant = testInvalid
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'variant' is configured as 'invalid' but must be one of the following values: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCryptVariant() {
+ suite.config.File.Password = schema.Password{}
+ suite.Assert().Equal("", suite.config.File.Password.Algorithm)
+ suite.config.File.Password.Algorithm = "bcrypt"
+ suite.config.File.Password.BCrypt.Variant = testInvalid
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'variant' is configured as 'invalid' but must be one of the following values: 'standard', 'sha256'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSHA2CryptOptionsTooLow() {
+ suite.config.File.Password.SHA2Crypt.Iterations = -1
+ suite.config.File.Password.SHA2Crypt.SaltLength = -1
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 2)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'iterations' is configured as '-1' but must be greater than or equal to '1000'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: file: password: sha2crypt: option 'salt_length' is configured as '-1' but must be greater than or equal to '1'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSHA2CryptOptionsTooHigh() {
+ suite.config.File.Password.SHA2Crypt.Iterations = 999999999999
+ suite.config.File.Password.SHA2Crypt.SaltLength = 99
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 2)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'iterations' is configured as '999999999999' but must be less than or equal to '999999999'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: file: password: sha2crypt: option 'salt_length' is configured as '99' but must be less than or equal to '16'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenPBKDF2OptionsTooLow() {
+ suite.config.File.Password.PBKDF2.Iterations = -1
+ suite.config.File.Password.PBKDF2.SaltLength = -1
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 2)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'iterations' is configured as '-1' but must be greater than or equal to '100000'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: file: password: pbkdf2: option 'salt_length' is configured as '-1' but must be greater than or equal to '8'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenPBKDF2OptionsTooHigh() {
+ suite.config.File.Password.PBKDF2.Iterations = 2147483649
+ suite.config.File.Password.PBKDF2.SaltLength = 2147483650
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 2)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'iterations' is configured as '2147483649' but must be less than or equal to '2147483647'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: file: password: pbkdf2: option 'salt_length' is configured as '2147483650' but must be less than or equal to '2147483647'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBCryptOptionsTooLow() {
+ suite.config.File.Password.BCrypt.Cost = -1
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'cost' is configured as '-1' but must be greater than or equal to '10'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBCryptOptionsTooHigh() {
+ suite.config.File.Password.BCrypt.Cost = 900
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'cost' is configured as '900' but must be less than or equal to '31'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSCryptOptionsTooLow() {
+ suite.config.File.Password.SCrypt.Iterations = -1
+ suite.config.File.Password.SCrypt.BlockSize = -21
+ suite.config.File.Password.SCrypt.Parallelism = -11
+ suite.config.File.Password.SCrypt.KeyLength = -77
+ suite.config.File.Password.SCrypt.SaltLength = 7
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 5)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: scrypt: option 'iterations' is configured as '-1' but must be greater than or equal to '1'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: file: password: scrypt: option 'block_size' is configured as '-21' but must be greater than or equal to '1'")
+ suite.Assert().EqualError(suite.validator.Errors()[2], "authentication_backend: file: password: scrypt: option 'parallelism' is configured as '-11' but must be greater than or equal to '1'")
+ suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: file: password: scrypt: option 'key_length' is configured as '-77' but must be greater than or equal to '1'")
+ suite.Assert().EqualError(suite.validator.Errors()[4], "authentication_backend: file: password: scrypt: option 'salt_length' is configured as '7' but must be greater than or equal to '8'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSCryptOptionsTooHigh() {
+ suite.config.File.Password.SCrypt.BlockSize = 360287970189639672
+ suite.config.File.Password.SCrypt.KeyLength = 1374389534409
+ suite.config.File.Password.SCrypt.SaltLength = 2147483647
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 3)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: scrypt: option 'block_size' is configured as '360287970189639672' but must be less than or equal to '36028797018963967'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: file: password: scrypt: option 'key_length' is configured as '1374389534409' but must be less than or equal to '137438953440'")
+ suite.Assert().EqualError(suite.validator.Errors()[2], "authentication_backend: file: password: scrypt: option 'salt_length' is configured as '2147483647' but must be less than or equal to '1024'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenArgon2OptionsTooLow() {
+ suite.config.File.Password.Argon2.Iterations = -1
+ suite.config.File.Password.Argon2.Memory = -1
+ suite.config.File.Password.Argon2.Parallelism = -1
+ suite.config.File.Password.Argon2.KeyLength = 1
+ suite.config.File.Password.Argon2.SaltLength = -1
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 5)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'iterations' is configured as '-1' but must be greater than or equal to '1'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: file: password: argon2: option 'parallelism' is configured as '-1' but must be greater than or equal to '1'")
+ suite.Assert().EqualError(suite.validator.Errors()[2], "authentication_backend: file: password: argon2: option 'memory' is configured as '-1' but must be greater than or equal to '1'")
+ suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: file: password: argon2: option 'key_length' is configured as '1' but must be greater than or equal to '4'")
+ suite.Assert().EqualError(suite.validator.Errors()[4], "authentication_backend: file: password: argon2: option 'salt_length' is configured as '-1' but must be greater than or equal to '1'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenArgon2OptionsTooHigh() {
+ suite.config.File.Password.Argon2.Iterations = 9999999999
+ suite.config.File.Password.Argon2.Parallelism = 16777216
+ suite.config.File.Password.Argon2.KeyLength = 9999999998
+ suite.config.File.Password.Argon2.SaltLength = 9999999997
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 5)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'iterations' is configured as '9999999999' but must be less than or equal to '2147483647'")
+ suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: file: password: argon2: option 'parallelism' is configured as '16777216' but must be less than or equal to '16777215'")
+ suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: file: password: argon2: option 'key_length' is configured as '9999999998' but must be less than or equal to '2147483647'")
+ suite.Assert().EqualError(suite.validator.Errors()[4], "authentication_backend: file: password: argon2: option 'salt_length' is configured as '9999999997' but must be less than or equal to '2147483647'")
+}
+
+func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenArgon2MemoryTooLow() {
+ suite.config.File.Password.Argon2.Memory = 4
+ suite.config.File.Password.Argon2.Parallelism = 4
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'memory' is configured as '4' but must be greater than or equal to '32' or '4' (the value of 'parallelism) multiplied by '8'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorithmDefined() {
@@ -156,29 +479,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorith
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' must be either 'argon2id' or 'sha512' but it is configured as 'bogus'")
-}
-
-func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenIterationsTooLow() {
- suite.config.File.Password.Iterations = -1
-
- ValidateAuthenticationBackend(&suite.config, suite.validator)
-
- suite.Assert().Len(suite.validator.Warnings(), 0)
- suite.Require().Len(suite.validator.Errors(), 1)
-
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'iterations' must be 1 or more but it is configured as '-1'")
-}
-
-func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenParallelismTooLow() {
- suite.config.File.Password.Parallelism = -1
-
- ValidateAuthenticationBackend(&suite.config, suite.validator)
-
- suite.Assert().Len(suite.validator.Warnings(), 0)
- suite.Require().Len(suite.validator.Errors(), 1)
-
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'parallelism' must be 1 or more when using algorithm 'argon2id' but it is configured as '-1'")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' is configured as 'bogus' but must be one of the following values: 'sha2crypt', 'pbkdf2', 'scrypt', 'bcrypt', 'argon2'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() {
@@ -193,42 +494,11 @@ func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.config.File.Password.Algorithm)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.config.File.Password.Iterations)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.config.File.Password.SaltLength)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.config.File.Password.Memory)
- suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.config.File.Password.Parallelism)
-}
-
-func TestFileBasedAuthenticationBackend(t *testing.T) {
- suite.Run(t, new(FileBasedAuthenticationBackend))
-}
-
-type LDAPAuthenticationBackendSuite struct {
- suite.Suite
- config schema.AuthenticationBackendConfiguration
- validator *schema.StructValidator
-}
-
-func (suite *LDAPAuthenticationBackendSuite) SetupTest() {
- suite.validator = schema.NewStructValidator()
- suite.config = schema.AuthenticationBackendConfiguration{}
- suite.config.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
- suite.config.LDAP.Implementation = schema.LDAPImplementationCustom
- suite.config.LDAP.URL = testLDAPURL
- suite.config.LDAP.User = testLDAPUser
- suite.config.LDAP.Password = testLDAPPassword
- suite.config.LDAP.BaseDN = testLDAPBaseDN
- suite.config.LDAP.UsernameAttribute = "uid"
- suite.config.LDAP.UsersFilter = "({username_attribute}={input})"
- suite.config.LDAP.GroupsFilter = "(cn={input})"
-}
-
-func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfiguration() {
- ValidateAuthenticationBackend(&suite.config, suite.validator)
-
- suite.Assert().Len(suite.validator.Warnings(), 0)
- suite.Assert().Len(suite.validator.Errors(), 0)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.Algorithm, suite.config.File.Password.Algorithm)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.Iterations, suite.config.File.Password.Iterations)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.SaltLength, suite.config.File.Password.SaltLength)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.Memory, suite.config.File.Password.Memory)
+ suite.Assert().Equal(schema.DefaultPasswordConfig.Parallelism, suite.config.File.Password.Parallelism)
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenResetURLIsInvalid() {
@@ -270,6 +540,37 @@ func (suite *FileBasedAuthenticationBackend) TestShouldConfigureDisableResetPass
suite.Assert().False(suite.config.PasswordReset.Disable)
}
+func TestFileBasedAuthenticationBackend(t *testing.T) {
+ suite.Run(t, new(FileBasedAuthenticationBackend))
+}
+
+type LDAPAuthenticationBackendSuite struct {
+ suite.Suite
+ config schema.AuthenticationBackend
+ validator *schema.StructValidator
+}
+
+func (suite *LDAPAuthenticationBackendSuite) SetupTest() {
+ suite.validator = schema.NewStructValidator()
+ suite.config = schema.AuthenticationBackend{}
+ suite.config.LDAP = &schema.LDAPAuthenticationBackend{}
+ suite.config.LDAP.Implementation = schema.LDAPImplementationCustom
+ suite.config.LDAP.URL = testLDAPURL
+ suite.config.LDAP.User = testLDAPUser
+ suite.config.LDAP.Password = testLDAPPassword
+ suite.config.LDAP.BaseDN = testLDAPBaseDN
+ suite.config.LDAP.UsernameAttribute = "uid"
+ suite.config.LDAP.UsersFilter = "({username_attribute}={input})"
+ suite.config.LDAP.GroupsFilter = "(cn={input})"
+}
+
+func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfiguration() {
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+}
+
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
suite.config.LDAP.Implementation = ""
suite.config.LDAP.UsernameAttribute = ""
@@ -277,7 +578,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementa
suite.Assert().Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation)
- suite.Assert().Equal(suite.config.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute)
+ suite.Assert().Equal(suite.config.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.UsernameAttribute)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
}
@@ -325,6 +626,42 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenPasswordNot
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'password' is required")
}
+func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseErrorWhenPasswordNotProvidedWithPermitUnauthenticatedBind() {
+ suite.config.LDAP.Password = ""
+ suite.config.LDAP.PermitUnauthenticatedBind = true
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when password reset is enabled")
+}
+
+func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenPasswordProvidedWithPermitUnauthenticatedBind() {
+ suite.config.LDAP.Password = "test"
+ suite.config.LDAP.PermitUnauthenticatedBind = true
+ suite.config.PasswordReset.Disable = true
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when a password is specified")
+}
+
+func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseErrorWhenPermitUnauthenticatedBindConfiguredCorrectly() {
+ suite.config.LDAP.Password = ""
+ suite.config.LDAP.PermitUnauthenticatedBind = true
+ suite.config.PasswordReset.Disable = true
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 0)
+}
+
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenBaseDNNotProvided() {
suite.config.LDAP.BaseDN = ""
@@ -483,19 +820,19 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlacehol
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultTLSMinimumVersion() {
- suite.config.LDAP.TLS = &schema.TLSConfig{MinimumVersion: ""}
+ suite.config.LDAP.TLS = &schema.TLSConfig{MinimumVersion: schema.TLSVersion{}}
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
- suite.Assert().Equal(schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion, suite.config.LDAP.TLS.MinimumVersion)
+ suite.Assert().Equal(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS.MinimumVersion.Value, suite.config.LDAP.TLS.MinimumVersion.MinVersion())
}
-func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowInvalidTLSValue() {
+func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowSSL30() {
suite.config.LDAP.TLS = &schema.TLSConfig{
- MinimumVersion: "SSL2.0",
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck
}
ValidateAuthenticationBackend(&suite.config, suite.validator)
@@ -503,7 +840,21 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowInvalidTLSValue()
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
- suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_tls_version' is invalid: SSL2.0: supplied tls version isn't supported")
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
+}
+
+func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowTLSVerMinGreaterThanVerMax() {
+ suite.config.LDAP.TLS = &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
+ MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS12},
+ }
+
+ ValidateAuthenticationBackend(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.2")
}
func TestLdapAuthenticationBackend(t *testing.T) {
@@ -512,20 +863,20 @@ func TestLdapAuthenticationBackend(t *testing.T) {
type ActiveDirectoryAuthenticationBackendSuite struct {
suite.Suite
- config schema.AuthenticationBackendConfiguration
+ config schema.AuthenticationBackend
validator *schema.StructValidator
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
suite.validator = schema.NewStructValidator()
- suite.config = schema.AuthenticationBackendConfiguration{}
- suite.config.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
+ suite.config = schema.AuthenticationBackend{}
+ suite.config.LDAP = &schema.LDAPAuthenticationBackend{}
suite.config.LDAP.Implementation = schema.LDAPImplementationActiveDirectory
suite.config.LDAP.URL = testLDAPURL
suite.config.LDAP.User = testLDAPUser
suite.config.LDAP.Password = testLDAPPassword
suite.config.LDAP.BaseDN = testLDAPBaseDN
- suite.config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
+ suite.config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirectoryDefaults() {
@@ -535,25 +886,25 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirec
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal(
- schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Timeout,
suite.config.LDAP.Timeout)
suite.Assert().Equal(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Assert().Equal(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Assert().Equal(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Assert().Equal(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Assert().Equal(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Assert().Equal(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
}
@@ -569,25 +920,25 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefault
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().NotEqual(
- schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Timeout,
suite.config.LDAP.Timeout)
suite.Assert().NotEqual(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Assert().NotEqual(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Assert().NotEqual(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Assert().NotEqual(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Assert().NotEqual(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Assert().NotEqual(
- schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
+ schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
}
diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go
index 4021c1f33..18ff2ae53 100644
--- a/internal/configuration/validator/configuration_test.go
+++ b/internal/configuration/validator/configuration_test.go
@@ -18,14 +18,14 @@ func newDefaultConfig() schema.Configuration {
config.Log.Level = "info"
config.Log.Format = "text"
config.JWTSecret = testJWTSecret
- config.AuthenticationBackend.File = &schema.FileAuthenticationBackendConfiguration{
+ config.AuthenticationBackend.File = &schema.FileAuthenticationBackend{
Path: "/a/path",
}
config.AccessControl = schema.AccessControlConfiguration{
DefaultPolicy: "two_factor",
}
config.Session = schema.SessionConfiguration{
- Domain: "example.com",
+ Domain: examplecom,
Name: "authelia_session",
Secret: "secret",
}
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index 78797a283..a8b102ac7 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -21,10 +21,24 @@ const (
policyDeny = "deny"
)
+const (
+ digestSHA1 = "sha1"
+ digestSHA224 = "sha224"
+ digestSHA256 = "sha256"
+ digestSHA384 = "sha384"
+ digestSHA512 = "sha512"
+)
+
// Hashing constants.
const (
- hashArgon2id = "argon2id"
- hashSHA512 = "sha512"
+ hashLegacyArgon2id = "argon2id"
+ hashLegacySHA512 = digestSHA512
+
+ hashArgon2 = "argon2"
+ hashSHA2Crypt = "sha2crypt"
+ hashPBKDF2 = "pbkdf2"
+ hashSCrypt = "scrypt"
+ hashBCrypt = "bcrypt"
)
// Scheme constants.
@@ -35,18 +49,6 @@ const (
schemeHTTPS = "https"
)
-// Test constants.
-const (
- testInvalidPolicy = "invalid"
- testJWTSecret = "a_secret"
- testLDAPBaseDN = "base_dn"
- testLDAPPassword = "password"
- testLDAPURL = "ldap://ldap"
- testLDAPUser = "user"
- testModeDisabled = "disable"
- testEncryptionKey = "a_not_so_secure_encryption_key"
-)
-
// Notifier Error constants.
const (
errFmtNotifierMultipleConfigured = "notifier: please ensure only one of the 'smtp' or 'filesystem' notifier is configured"
@@ -56,6 +58,15 @@ const (
errFmtNotifierTemplatePathUnknownError = "notifier: option 'template_path' refers to location '%s' which couldn't be opened: %w"
errFmtNotifierFileSystemFileNameNotConfigured = "notifier: filesystem: option 'filename' is required"
errFmtNotifierSMTPNotConfigured = "notifier: smtp: option '%s' is required"
+ errFmtNotifierSMTPTLSConfigInvalid = "notifier: smtp: tls: %w"
+ errFmtNotifierStartTlsDisabled = "notifier: smtp: option 'disable_starttls' is enabled: " +
+ "opportunistic STARTTLS is explicitly disabled which means all emails will be sent insecurely over plaintext " +
+ "and this setting is only necessary for non-compliant SMTP servers which advertise they support STARTTLS " +
+ "when they actually don't support STARTTLS"
+)
+
+const (
+ errSuffixMustBeOneOf = "is configured as '%s' but must be one of the following values: '%s'"
)
// Authentication Backend Error constants.
@@ -70,28 +81,24 @@ const (
" configured to '%s' which has the scheme '%s' but the scheme must be either 'http' or 'https'"
errFmtFileAuthBackendPathNotConfigured = "authentication_backend: file: option 'path' is required"
- errFmtFileAuthBackendPasswordSaltLength = "authentication_backend: file: password: option 'salt_length' " +
- "must be 2 or more but it is configured a '%d'"
errFmtFileAuthBackendPasswordUnknownAlg = "authentication_backend: file: password: option 'algorithm' " +
- "must be either 'argon2id' or 'sha512' but it is configured as '%s'"
- errFmtFileAuthBackendPasswordInvalidIterations = "authentication_backend: file: password: option " +
- "'iterations' must be 1 or more but it is configured as '%d'"
- errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength = "authentication_backend: file: password: option " +
- "'key_length' must be 16 or more when using algorithm 'argon2id' but it is configured as '%d'"
- errFmtFileAuthBackendPasswordArgon2idInvalidParallelism = "authentication_backend: file: password: option " +
- "'parallelism' must be 1 or more when using algorithm 'argon2id' but it is configured as '%d'"
- errFmtFileAuthBackendPasswordArgon2idInvalidMemory = "authentication_backend: file: password: option 'memory' " +
- "must at least be parallelism multiplied by 8 when using algorithm 'argon2id' " +
- "with parallelism %d it should be at least %d but it is configured as '%d'"
+ errSuffixMustBeOneOf
+ errFmtFileAuthBackendPasswordInvalidVariant = "authentication_backend: file: password: %s: " +
+ "option 'variant' " + errSuffixMustBeOneOf
+ errFmtFileAuthBackendPasswordOptionTooLarge = "authentication_backend: file: password: %s: " +
+ "option '%s' is configured as '%d' but must be less than or equal to '%d'"
+ errFmtFileAuthBackendPasswordOptionTooSmall = "authentication_backend: file: password: %s: " +
+ "option '%s' is configured as '%d' but must be greater than or equal to '%d'"
+ errFmtFileAuthBackendPasswordArgon2MemoryTooLow = "authentication_backend: file: password: argon2: " +
+ "option 'memory' is configured as '%d' but must be greater than or equal to '%d' or '%d' (the value of 'parallelism) multiplied by '%d'"
errFmtLDAPAuthBackendUnauthenticatedBindWithPassword = "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when a password is specified"
errFmtLDAPAuthBackendUnauthenticatedBindWithResetEnabled = "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when password reset is enabled"
- errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
- errFmtLDAPAuthBackendTLSMinVersion = "authentication_backend: ldap: tls: option " +
- "'minimum_tls_version' is invalid: %s: %w"
- errFmtLDAPAuthBackendImplementation = "authentication_backend: ldap: option 'implementation' " +
- "is configured as '%s' but must be one of the following values: '%s'"
+ errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
+ errFmtLDAPAuthBackendTLSConfigInvalid = "authentication_backend: ldap: tls: %w"
+ errFmtLDAPAuthBackendImplementation = "authentication_backend: ldap: option 'implementation' " +
+ errSuffixMustBeOneOf
errFmtLDAPAuthBackendFilterReplacedPlaceholders = "authentication_backend: ldap: option " +
"'%s' has an invalid placeholder: '%s' has been removed, please use '%s' instead"
errFmtLDAPAuthBackendURLNotParsable = "authentication_backend: ldap: option " +
@@ -114,12 +121,15 @@ const (
// Storage Error constants.
const (
- errStrStorage = "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided"
- errStrStorageEncryptionKeyMustBeProvided = "storage: option 'encryption_key' is required"
- errStrStorageEncryptionKeyTooShort = "storage: option 'encryption_key' must be 20 characters or longer"
- errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint:gosec
- errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required"
- errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of '%s' but it is configured as '%s'"
+ errStrStorage = "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided"
+ errStrStorageEncryptionKeyMustBeProvided = "storage: option 'encryption_key' is required"
+ errStrStorageEncryptionKeyTooShort = "storage: option 'encryption_key' must be 20 characters or longer"
+ errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint:gosec
+ errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required"
+ errFmtStorageTLSConfigInvalid = "storage: %s: tls: %w"
+ errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of '%s' but it is configured as '%s'"
+ errFmtStoragePostgreSQLInvalidSSLAndTLSConfig = "storage: postgres: can't define both 'tls' and 'ssl' configuration options"
+ warnFmtStoragePostgreSQLInvalidSSLDeprecated = "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead"
)
// Telemetry Error constants.
@@ -131,8 +141,11 @@ const (
const (
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
"more clients configured"
- errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required"
- errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
+ errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required"
+ errFmtOIDCInvalidPrivateKeyBitSize = "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with %d bits or more but it only has %d bits"
+ errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'"
+ errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w"
+ errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
"'public_clients_only' or 'always', but it is configured as '%s'"
errFmtOIDCCORSInvalidOrigin = "identity_providers: oidc: cors: option 'allowed_origins' contains an invalid value '%s' as it has a %s: origins must only be scheme, hostname, and an optional port"
@@ -159,6 +172,8 @@ const (
"invalid value: redirect uri '%s' must have the scheme 'http' or 'https' but it has no scheme"
errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " +
"or 'two_factor' 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 " +
"'%s' but one option is configured as '%s'"
errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " +
@@ -198,13 +213,25 @@ const (
"https://www.authelia.com/c/acl#bypass"
errAccessControlRuleBypassPolicyInvalidWithSubjectsWithGroupDomainRegex = "access control: rule %s: 'policy' option 'bypass' is " +
"not supported when 'domain_regex' option contains the user or group named matches. For more information see: " +
- "https://www.authelia.com/c/acl#bypass-and-user-identity"
+ "https://www.authelia.com/c/acl-match-concept-2"
errFmtAccessControlRuleNetworksInvalid = "access control: rule %s: the network '%s' is not a " +
"valid Group Name, IP, or CIDR notation"
errFmtAccessControlRuleSubjectInvalid = "access control: rule %s: 'subject' option '%s' is " +
"invalid: must start with 'user:' or 'group:'"
errFmtAccessControlRuleMethodInvalid = "access control: rule %s: 'methods' option '%s' is " +
"invalid: must be one of '%s'"
+ errFmtAccessControlRuleQueryInvalid = "access control: rule %s: 'query' option 'operator' with value '%s' is " +
+ "invalid: must be one of '%s'"
+ errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: 'query' option '%s' is " +
+ "invalid: must have a value"
+ errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: 'query' option '%s' is " +
+ "invalid: must have a value when the operator is '%s'"
+ errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: 'query' option '%s' is " +
+ "invalid: must not have a value when the operator is '%s'"
+ errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: 'query' option '%s' is " +
+ "invalid: %w"
+ errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: 'query' option 'value' is " +
+ "invalid: expected type was string but got %T"
)
// Theme Error constants.
@@ -226,6 +253,7 @@ const (
errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but is configured as '%d'"
errFmtSessionRedisHostRequired = "session: redis: option 'host' is required"
errFmtSessionRedisHostOrNodesRequired = "session: redis: option 'host' or the 'high_availability' option 'nodes' is required"
+ errFmtSessionRedisTLSConfigInvalid = "session: redis: tls: %w"
errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required"
errFmtSessionRedisSentinelNodeHostMissing = "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this"
@@ -285,7 +313,17 @@ const (
errFilePOptions = "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password"
)
-var validStoragePostgreSQLSSLModes = []string{testModeDisabled, "require", "verify-ca", "verify-full"}
+var validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"}
+
+var validSHA2CryptVariants = []string{digestSHA256, digestSHA512}
+
+var validPBKDF2Variants = []string{digestSHA1, digestSHA224, digestSHA256, digestSHA384, digestSHA512}
+
+var validBCryptVariants = []string{"standard", digestSHA256}
+
+var validHashAlgorithms = []string{hashSHA2Crypt, hashPBKDF2, hashSCrypt, hashBCrypt, hashArgon2}
+
+var validStoragePostgreSQLSSLModes = []string{"disable", "require", "verify-ca", "verify-full"}
var validThemeNames = []string{"light", "dark", "grey", "auto"}
@@ -294,22 +332,38 @@ var validSessionSameSiteValues = []string{"none", "lax", "strict"}
var validLoLevels = []string{"trace", "debug", "info", "warn", "error"}
var validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
+
var validWebauthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
var validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
+
var validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"}
-var validACLHTTPMethodVerbs = append(validRFC7231HTTPMethodVerbs, validRFC4918HTTPMethodVerbs...)
+const (
+ operatorPresent = "present"
+ operatorAbsent = "absent"
+ operatorEqual = "equal"
+ operatorNotEqual = "not equal"
+ operatorPattern = "pattern"
+ operatorNotPattern = "not pattern"
+)
-var validACLRulePolicies = []string{policyBypass, policyOneFactor, policyTwoFactor, policyDeny}
+var (
+ validACLHTTPMethodVerbs = append(validRFC7231HTTPMethodVerbs, validRFC4918HTTPMethodVerbs...)
+ validACLRulePolicies = []string{policyBypass, policyOneFactor, policyTwoFactor, policyDeny}
+ validACLRuleOperators = []string{operatorPresent, operatorAbsent, operatorEqual, operatorNotEqual, operatorPattern, operatorNotPattern}
+)
var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"}
-var validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
-var validOIDCGrantTypes = []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"}
-var validOIDCResponseModes = []string{"form_post", "query", "fragment"}
-var validOIDCUserinfoAlgorithms = []string{"none", "RS256"}
-var validOIDCCORSEndpoints = []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint}
+var (
+ validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
+ validOIDCGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode, oidc.GrantTypePassword, oidc.GrantTypeClientCredentials}
+ validOIDCResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
+ validOIDCUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256}
+ validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
+ validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
+)
var reKeyReplacer = regexp.MustCompile(`\[\d+]`)
diff --git a/internal/configuration/validator/const_test.go b/internal/configuration/validator/const_test.go
new file mode 100644
index 000000000..ab398a187
--- /dev/null
+++ b/internal/configuration/validator/const_test.go
@@ -0,0 +1,16 @@
+package validator
+
+// Test constants.
+const (
+ testInvalid = "invalid"
+ testJWTSecret = "a_secret"
+ testLDAPBaseDN = "base_dn"
+ testLDAPPassword = "password"
+ testLDAPURL = "ldap://ldap"
+ testLDAPUser = "user"
+ testEncryptionKey = "a_not_so_secure_encryption_key"
+)
+
+const (
+ examplecom = "example.com"
+)
diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go
index 4cdfa3800..a2ee5a549 100644
--- a/internal/configuration/validator/identity_providers.go
+++ b/internal/configuration/validator/identity_providers.go
@@ -7,6 +7,7 @@ import (
"time"
"github.com/authelia/authelia/v4/internal/configuration/schema"
+ "github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/utils"
)
@@ -16,45 +17,67 @@ func ValidateIdentityProviders(config *schema.IdentityProvidersConfiguration, va
}
func validateOIDC(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
- if config != nil {
- if config.IssuerPrivateKey == "" {
- validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
+ if config == nil {
+ return
+ }
+
+ setOIDCDefaults(config)
+
+ switch {
+ case config.IssuerPrivateKey == nil:
+ validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
+ default:
+ if config.IssuerCertificateChain.HasCertificates() {
+ if !config.IssuerCertificateChain.EqualKey(config.IssuerPrivateKey) {
+ validator.Push(fmt.Errorf(errFmtOIDCCertificateMismatch))
+ }
+
+ if err := config.IssuerCertificateChain.Validate(); err != nil {
+ validator.Push(fmt.Errorf(errFmtOIDCCertificateChain, err))
+ }
}
- if config.AccessTokenLifespan == time.Duration(0) {
- config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
+ if config.IssuerPrivateKey.Size()*8 < 2048 {
+ validator.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyBitSize, 2048, config.IssuerPrivateKey.Size()*8))
}
+ }
- if config.AuthorizeCodeLifespan == time.Duration(0) {
- config.AuthorizeCodeLifespan = schema.DefaultOpenIDConnectConfiguration.AuthorizeCodeLifespan
- }
+ if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
+ validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
+ }
- if config.IDTokenLifespan == time.Duration(0) {
- config.IDTokenLifespan = schema.DefaultOpenIDConnectConfiguration.IDTokenLifespan
- }
+ if config.EnforcePKCE != "never" && config.EnforcePKCE != "public_clients_only" && config.EnforcePKCE != "always" {
+ validator.Push(fmt.Errorf(errFmtOIDCEnforcePKCEInvalidValue, config.EnforcePKCE))
+ }
- if config.RefreshTokenLifespan == time.Duration(0) {
- config.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan
- }
+ validateOIDCOptionsCORS(config, validator)
- if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
- validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
- }
-
- if config.EnforcePKCE == "" {
- config.EnforcePKCE = schema.DefaultOpenIDConnectConfiguration.EnforcePKCE
- }
-
- if config.EnforcePKCE != "never" && config.EnforcePKCE != "public_clients_only" && config.EnforcePKCE != "always" {
- validator.Push(fmt.Errorf(errFmtOIDCEnforcePKCEInvalidValue, config.EnforcePKCE))
- }
-
- validateOIDCOptionsCORS(config, validator)
+ if len(config.Clients) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured))
+ } else {
validateOIDCClients(config, validator)
+ }
+}
- if len(config.Clients) == 0 {
- validator.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured))
- }
+func setOIDCDefaults(config *schema.OpenIDConnectConfiguration) {
+ if config.AccessTokenLifespan == time.Duration(0) {
+ config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
+ }
+
+ if config.AuthorizeCodeLifespan == time.Duration(0) {
+ config.AuthorizeCodeLifespan = schema.DefaultOpenIDConnectConfiguration.AuthorizeCodeLifespan
+ }
+
+ if config.IDTokenLifespan == time.Duration(0) {
+ config.IDTokenLifespan = schema.DefaultOpenIDConnectConfiguration.IDTokenLifespan
+ }
+
+ if config.RefreshTokenLifespan == time.Duration(0) {
+ config.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan
+ }
+
+ if config.EnforcePKCE == "" {
+ config.EnforcePKCE = schema.DefaultOpenIDConnectConfiguration.EnforcePKCE
}
}
@@ -117,6 +140,7 @@ func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration,
}
}
+//nolint:gocyclo // TODO: Refactor.
func validateOIDCClients(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
invalidID, duplicateIDs := false, false
@@ -137,11 +161,11 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, validator *s
}
if client.Public {
- if client.Secret != "" {
+ if client.Secret != nil {
validator.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, client.ID))
}
} else {
- if client.Secret == "" {
+ if client.Secret == nil {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, client.ID))
}
}
@@ -152,6 +176,23 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, validator *s
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
}
+ switch {
+ case utils.IsStringInSlice(client.ConsentMode, []string{"", "auto"}):
+ if client.ConsentPreConfiguredDuration != nil {
+ config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String()
+ } else {
+ config.Clients[c].ConsentMode = oidc.ClientConsentModeExplicit.String()
+ }
+ case utils.IsStringInSlice(client.ConsentMode, validOIDCClientConsentModes):
+ break
+ default:
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, client.ID, strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), client.ConsentMode))
+ }
+
+ if client.ConsentPreConfiguredDuration == nil {
+ config.Clients[c].ConsentPreConfiguredDuration = schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration
+ }
+
validateOIDCClientSectorIdentifier(client, validator)
validateOIDCClientScopes(c, config, validator)
validateOIDCClientGrantTypes(c, config, validator)
@@ -212,8 +253,8 @@ func validateOIDCClientScopes(c int, configuration *schema.OpenIDConnectConfigur
return
}
- if !utils.IsStringInSlice("openid", configuration.Clients[c].Scopes) {
- configuration.Clients[c].Scopes = append(configuration.Clients[c].Scopes, "openid")
+ if !utils.IsStringInSlice(oidc.ScopeOpenID, configuration.Clients[c].Scopes) {
+ configuration.Clients[c].Scopes = append(configuration.Clients[c].Scopes, oidc.ScopeOpenID)
}
for _, scope := range configuration.Clients[c].Scopes {
diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go
index 13d138bba..727676bb4 100644
--- a/internal/configuration/validator/identity_providers_test.go
+++ b/internal/configuration/validator/identity_providers_test.go
@@ -1,6 +1,9 @@
package validator
import (
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
"errors"
"fmt"
"net/url"
@@ -19,8 +22,7 @@ func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
- HMACSecret: "abc",
- IssuerPrivateKey: "",
+ HMACSecret: "abc",
},
}
@@ -37,14 +39,14 @@ func TestShouldNotRaiseErrorWhenCORSEndpointsValid(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
CORS: schema.OpenIDConnectCORSConfiguration{
- Endpoints: []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint},
+ Endpoints: []string{oidc.EndpointAuthorization, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo},
},
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "example",
- Secret: "example",
+ Secret: MustDecodeSecret("$plaintext$example"),
},
},
},
@@ -60,14 +62,14 @@ func TestShouldRaiseErrorWhenCORSEndpointsNotValid(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
CORS: schema.OpenIDConnectCORSConfiguration{
- Endpoints: []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint, "invalid_endpoint"},
+ Endpoints: []string{oidc.EndpointAuthorization, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo, "invalid_endpoint"},
},
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "example",
- Secret: "example",
+ Secret: MustDecodeSecret("$plaintext$example"),
},
},
},
@@ -85,8 +87,8 @@ func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
- EnforcePKCE: "invalid",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
+ EnforcePKCE: testInvalid,
},
}
@@ -104,7 +106,7 @@ func TestShouldRaiseErrorWhenOIDCCORSOriginsHasInvalidValues(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
CORS: schema.OpenIDConnectCORSConfiguration{
AllowedOrigins: utils.URLsFromStringSlice([]string{"https://example.com/", "https://site.example.com/subpath", "https://site.example.com?example=true", "*"}),
AllowedOriginsFromClientRedirectURIs: true,
@@ -112,7 +114,7 @@ func TestShouldRaiseErrorWhenOIDCCORSOriginsHasInvalidValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "myclient",
- Secret: "jk12nb3klqwmnelqkwenm",
+ Secret: MustDecodeSecret("$plaintext$jk12nb3klqwmnelqkwenm"),
Policy: "two_factor",
RedirectURIs: []string{"https://example.com/oauth2_callback", "https://localhost:566/callback", "http://an.example.com/callback", "file://a/file"},
},
@@ -140,7 +142,7 @@ func TestShouldRaiseErrorWhenOIDCServerNoClients(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
},
}
@@ -171,7 +173,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "",
- Secret: "",
+ Secret: nil,
Policy: "",
RedirectURIs: []string{},
},
@@ -186,7 +188,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-1",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: "a-policy",
RedirectURIs: []string{
"https://google.com",
@@ -200,13 +202,13 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-x",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{},
},
{
ID: "client-x",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{},
},
@@ -218,7 +220,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-check-uri-parse",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{
"http://abc@%two",
@@ -234,7 +236,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-check-uri-abs",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{
"google.com",
@@ -250,12 +252,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-valid-sector",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{
"https://google.com",
},
- SectorIdentifier: mustParseURL("example.com"),
+ SectorIdentifier: mustParseURL(examplecom),
},
},
},
@@ -264,7 +266,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-valid-sector",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{
"https://google.com",
@@ -278,7 +280,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-invalid-sector",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{
"https://google.com",
@@ -287,12 +289,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
Errors: []string{
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "scheme", "https"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "path", "/path"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "query", "query=abc"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "fragment", "fragment"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "username", "user"),
- fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "password"),
+ fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "scheme", "https"),
+ fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "path", "/path"),
+ fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "query", "query=abc"),
+ fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "fragment", "fragment"),
+ fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "username", "user"),
+ fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "password"),
},
},
{
@@ -300,7 +302,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-invalid-sector",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{
"https://google.com",
@@ -320,7 +322,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: tc.Clients,
},
}
@@ -344,11 +346,11 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
- Secret: "good_secret",
+ Secret: MustDecodeSecret("$plaintext$good_secret"),
Policy: "two_factor",
Scopes: []string{"openid", "bad_scope"},
RedirectURIs: []string{
@@ -370,11 +372,11 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T)
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
- Secret: "good_secret",
+ Secret: MustDecodeSecret("$plaintext$good_secret"),
Policy: "two_factor",
GrantTypes: []string{"bad_grant_type"},
RedirectURIs: []string{
@@ -391,16 +393,97 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T)
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', 'authorization_code', 'password', 'client_credentials' but one option is configured as 'bad_grant_type'")
}
+func TestShouldNotErrorOnCertificateValid(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := &schema.IdentityProvidersConfiguration{
+ OIDC: &schema.OpenIDConnectConfiguration{
+ HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
+ IssuerCertificateChain: MustParseX509CertificateChain(testCert1),
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
+ Clients: []schema.OpenIDConnectClientConfiguration{
+ {
+ ID: "good_id",
+ Secret: MustDecodeSecret("$plaintext$good_secret"),
+ Policy: "two_factor",
+ RedirectURIs: []string{
+ "https://google.com/callback",
+ },
+ },
+ },
+ },
+ }
+
+ ValidateIdentityProviders(config, validator)
+
+ assert.Len(t, validator.Warnings(), 0)
+ assert.Len(t, validator.Errors(), 0)
+}
+
+func TestShouldRaiseErrorOnCertificateNotValid(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := &schema.IdentityProvidersConfiguration{
+ OIDC: &schema.OpenIDConnectConfiguration{
+ HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
+ IssuerCertificateChain: MustParseX509CertificateChain(testCert1),
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey2),
+ Clients: []schema.OpenIDConnectClientConfiguration{
+ {
+ ID: "good_id",
+ Secret: MustDecodeSecret("$plaintext$good_secret"),
+ Policy: "two_factor",
+ RedirectURIs: []string{
+ "https://google.com/callback",
+ },
+ },
+ },
+ },
+ }
+
+ ValidateIdentityProviders(config, validator)
+
+ assert.Len(t, validator.Warnings(), 0)
+ require.Len(t, validator.Errors(), 1)
+
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'")
+}
+
+func TestShouldRaiseErrorOnKeySizeTooSmall(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := &schema.IdentityProvidersConfiguration{
+ OIDC: &schema.OpenIDConnectConfiguration{
+ HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey3),
+ Clients: []schema.OpenIDConnectClientConfiguration{
+ {
+ ID: "good_id",
+ Secret: MustDecodeSecret("$plaintext$good_secret"),
+ Policy: "two_factor",
+ RedirectURIs: []string{
+ "https://google.com/callback",
+ },
+ },
+ },
+ },
+ }
+
+ ValidateIdentityProviders(config, validator)
+
+ assert.Len(t, validator.Warnings(), 0)
+ require.Len(t, validator.Errors(), 1)
+
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with 2048 bits or more but it only has 1024 bits")
+}
+
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
- Secret: "good_secret",
+ Secret: MustDecodeSecret("$plaintext$good_secret"),
Policy: "two_factor",
ResponseModes: []string{"bad_responsemode"},
RedirectURIs: []string{
@@ -422,11 +505,11 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "key-material",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
- Secret: "good_secret",
+ Secret: MustDecodeSecret("$plaintext$good_secret"),
Policy: "two_factor",
UserinfoSigningAlgorithm: "rs256",
RedirectURIs: []string{
@@ -448,12 +531,12 @@ func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "abc",
- IssuerPrivateKey: "abc",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
MinimumParameterEntropy: 1,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
- Secret: "good_secret",
+ Secret: MustDecodeSecret("$plaintext$good_secret"),
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com/callback",
@@ -476,11 +559,11 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "hmac1",
- IssuerPrivateKey: "key2",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-with-invalid-secret",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Public: true,
Policy: "two_factor",
RedirectURIs: []string{
@@ -489,7 +572,7 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi
},
{
ID: "client-with-bad-redirect-uri",
- Secret: "a-secret",
+ Secret: MustDecodeSecret("$plaintext$a-secret"),
Public: false,
Policy: "two_factor",
RedirectURIs: []string{
@@ -514,7 +597,7 @@ func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *te
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "hmac1",
- IssuerPrivateKey: "key2",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "installed-app-client",
@@ -555,11 +638,11 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: "../../../README.md",
+ IssuerPrivateKey: MustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
- Secret: "a-client-secret",
+ Secret: MustDecodeSecret("$plaintext$a-client-secret"),
RedirectURIs: []string{
"https://google.com",
},
@@ -567,7 +650,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
{
ID: "b-client",
Description: "Normal Description",
- Secret: "b-client-secret",
+ Secret: MustDecodeSecret("$plaintext$b-client-secret"),
Policy: policyOneFactor,
UserinfoSigningAlgorithm: "RS256",
RedirectURIs: []string{
@@ -691,3 +774,135 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing
})
})
}
+
+func MustDecodeSecret(value string) *schema.PasswordDigest {
+ if secret, err := schema.NewPasswordDigest(value, true); err != nil {
+ panic(err)
+ } else {
+ return secret
+ }
+}
+
+func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
+ block, _ := pem.Decode([]byte(data))
+ if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
+ panic("not pem encoded")
+ }
+
+ if block.Type != "RSA PRIVATE KEY" {
+ panic("not private key")
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ return key
+}
+
+func MustParseX509CertificateChain(data string) schema.X509CertificateChain {
+ chain, err := schema.NewX509CertificateChain(data)
+
+ if err != nil {
+ panic(err)
+ }
+
+ return *chain
+}
+
+var (
+ testCert1 = `
+-----BEGIN CERTIFICATE-----
+MIIC5jCCAc6gAwIBAgIRAJZ+6KrHw95zIDgm2arCTCgwDQYJKoZIhvcNAQELBQAw
+EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNMjIwOTA4MDIyNDQyWhcNMjMwOTA4MDIy
+NDQyWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMAE7muDAJtLsV3WgOpjrZ1JD1RlhuSOa3V+4zo2NYFQSdZW18SZ
+fYYgUwLOleEy3VQ3N9MEFh/rWNHYHdsBjDvz/Q1EzAlXqthGd0Sic/UDYtrahrko
+jCSkZCQ5YVO9ivMRth6XdUlu7RHVYY3aSOWPx2wiw9cdN+e4p73W6KwyzT7ezbUD
+0Nng0Z7CNQTLHv3LBsLUODc4aVOvp2B4aAaw6cn990buKMvUuo2ge9gh0c5gIOM5
+dU7xOGAt7RzwCIHnG4CGAWPFuuS215ZeelgQr/9/fhtzDqSuBZw5f10vXnAyBwei
+vN6Kffj2RXB+koFwBguT84A6cfmxWllGNF0CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
+AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
+AQELBQADggEBAFvORjj7RGoIc3q0fv6QjuncZ0Mu1/24O0smCr6tq5d6RQBRpb1M
+jEsbTMLZErrHbyw/DWC75eJhW6T+6HiVTo6brBXkmDL+QGkLgRNOkZla6cnmIpmL
+bf9iPmmcThscQERgYZzNg19zqK8JAQU/6PgU/N6OXTL/mQQoB972ET9dUl7lGx1Q
+2l8XBe8t4QTp4t1xd3c4azxWvFNpzWBjC5eBWiVHLJmFXr4xpcnPFYFETOkvEqwt
+pMQ2x895BoLrep6b+g0xeF4pmmIQwA9KrUVr++gpYaRzytaOIYwcIPMzt9iLWKQe
+6ZSOrTVi8pPugYXp+LhVk/WI7r8EWtyADu0=
+-----END CERTIFICATE-----`
+
+ testKey1 = `
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwATua4MAm0uxXdaA6mOtnUkPVGWG5I5rdX7jOjY1gVBJ1lbX
+xJl9hiBTAs6V4TLdVDc30wQWH+tY0dgd2wGMO/P9DUTMCVeq2EZ3RKJz9QNi2tqG
+uSiMJKRkJDlhU72K8xG2Hpd1SW7tEdVhjdpI5Y/HbCLD1x0357invdborDLNPt7N
+tQPQ2eDRnsI1BMse/csGwtQ4NzhpU6+nYHhoBrDpyf33Ru4oy9S6jaB72CHRzmAg
+4zl1TvE4YC3tHPAIgecbgIYBY8W65LbXll56WBCv/39+G3MOpK4FnDl/XS9ecDIH
+B6K83op9+PZFcH6SgXAGC5PzgDpx+bFaWUY0XQIDAQABAoIBAQClcdpHcglMxOwe
+kRpkWdwWAAQgUJXoSbnW86wu1NRHBfmInyyrrSBVN3aunXbQITZIQIdt3kB94haW
+P6KBt5Svd2saSqOOjSWb0SMkVOCaQ/+h19VqpcASNj4+Y94y+8ZD5ofHVfJtghDr
+Y7H5OhHDEZ3e0xlwODGaCyUkUY4KBv/oIlILoh4phbDYHkZH8AzDnEiyVE1JAWlN
+voAQysgSU7eEnNCi1S07jl5bY+MD3XpJkAfQsJYhqYT/qetStZ12PuXjpbIr3y53
+qjCrKeWTyDN+gOznyIGuiR6nvXeQAw/o9hZiah4RuHXTPs/3GAcRXcuMR0pbgJ+B
+yfX6eLK1AoGBAPKkJKPYJD2NHukAelNbT2OgeDIgJmfOIvLa73/x2hXvWwa4VwIC
+POuKXtT/a02J4pYMGlaKXfHgLmaW2HPObOIjpxqgRIswsiKS1AbaDtkWnhaS1/SJ
+oZ7Fk8DdX+1QT4J/fj/2uxRT0GhXdMxDpK7ekpmRE+APPCGhmOMgmWszAoGBAMqX
+Ts1RdGWgDxLi15rDqdqRBARJG7Om/xC2voXVmbAb4Q+QoNrNeiIAM2usuhrVuj5V
+c16m9fxswRNYqQBYyShDi5wp5a8UjfqDpzJdku2bmrBaL+XVq8PY+oTK6KS3ss8U
+CGQ8P6Phz5JGavn/nDMRZ4EwEWqbEMUqJAJlpmIvAoGAQ9Wj8LJyn0qew6FAkaFL
+dpzcPZdDZW35009l+a0RvWQnXJ+Yo5UglvEeRgoKY6kS0cQccOlKDl8QWdn+NZIW
+WrqA8y6vOwKoKoZGBIxd7k8mb0UqXtFDf/HYtuis8tmrAN7H2vYNo0czUphwrNKU
+bdcHwSsQFWns87IL3iO1AIUCgYBzmBX8jOePPN6c9hXzVoVKEshp8ZT+0uBilwLq
+tk/07lNiYDGH5woy8E5mt62QtjaIbpVfgoCEwUEBWutDKWXNtYypVDabyWyhbhEu
+abn2HX0L9smxqFNTcjCvKF/J7I74HQQUvVPKnIOlgMx1TOXBNcMLMXQekc/lz/+v
+5nQjPQKBgQDjdJABeiy9tU0tzLWUVc5QoQKnlfSJoFLis46REb1yHwU9OjTc05Wx
+5lAXdTjDmnelDdGWNWHjWOiKSkTxhvQD3jXriI5y8Sdxe3zS3ikYvbMbi97GJz0O
+5oyNJo6/froW1dLkJJWR8hg2PQbtoOo6l9HHSd91BnJJ4qFbq9ZrXQ==
+-----END RSA PRIVATE KEY-----`
+
+ testKey2 = `
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
+cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
+ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
+Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
+rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
+EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
+L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
+Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
+9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
+8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
+I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
+CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
+hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
+jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
+E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
+CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
+jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
+Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
+Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
+bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
+otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
+HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
+tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
+USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
+1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
+-----END RSA PRIVATE KEY-----`
+
+ testKey3 = `-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDBi7fdmUmlpWklpgAvNUdhDrpsDVqAHuEzVApK6f6ohYAi0/q2
++YmOwyPKDSrOc6Sy1myJtV3FbZGvYaQhnokc4bnkS9DH0lY+6Hk2vKps5PrhRY/q
+1EjnfwXvzhAzb25rGFwKcSvfvndMTVvxgqXVob+3pRt9maD6HFHAh2/NCQIDAQAB
+AoGACT2bfLgJ3R/FomeHkLlxe//RBMGqdX2D8QhtKWB8qR0engsS6FOHrspAVjBE
+v/Cjh2pXake/f2KY1w/JX1WLZEFXja2RFPeeDiiC/4S7pKCySUVeHO9rQ4SY5Frg
+/s/QWWtmq7+1iu2DXhdGJA6fIurzSoDgUXo3NGFCYqIFaAECQQDUi9AAgEljmc2q
+dAUQD0KNTcJFkpTafhfPiYc2GT1vS/bArtXRmvJmbIiRfVuGbM8z5ES7JGd5FyYL
+i2WCCzUBAkEA6R14GVhN8NIPWEUrzjgOvjKlc2ZHskT3dYb3djpm69TK7GjLtHyq
+qO7l4VJowsXI+o/6YucagF6+rH0O0VrwCQJBAM8twYDbi63knA8MrGqtFUg7haTf
+bu1Tf84y1nOrQrEcMNg9E/sOuD2SicSXlwF/SrHgTgbFQ39LSzBxnm6WkgECQQCh
+AQmB98tdGLggbyXiODV2h+Rd37aFGb0QHzerIIsVNtMwlPCcp733D4kWJqTUYWZ+
+KBL3XEahgs6Os5EYZ4aBAkEAjKE+2/nBYUdHVusjMXeNsE5rqwJND5zvYzmToG7+
+xhv4RUAe4dHL4IDQoQRjhr3Nw+JYvtzBx0Iq/178xMnGKg==
+-----END RSA PRIVATE KEY-----`
+)
diff --git a/internal/configuration/validator/notifier.go b/internal/configuration/validator/notifier.go
index 61676848d..b3515188f 100644
--- a/internal/configuration/validator/notifier.go
+++ b/internal/configuration/validator/notifier.go
@@ -83,10 +83,20 @@ func validateSMTPNotifier(config *schema.SMTPNotifierConfiguration, validator *s
}
if config.TLS == nil {
- config.TLS = schema.DefaultSMTPNotifierConfiguration.TLS
+ config.TLS = &schema.TLSConfig{}
}
- if config.TLS.ServerName == "" {
- config.TLS.ServerName = config.Host
+ configDefaultTLS := &schema.TLSConfig{
+ ServerName: config.Host,
+ MinimumVersion: schema.DefaultSMTPNotifierConfiguration.TLS.MinimumVersion,
+ MaximumVersion: schema.DefaultSMTPNotifierConfiguration.TLS.MaximumVersion,
+ }
+
+ if err := ValidateTLSConfig(config.TLS, configDefaultTLS); err != nil {
+ validator.Push(fmt.Errorf(errFmtNotifierSMTPTLSConfigInvalid, err))
+ }
+
+ if config.DisableStartTLS {
+ validator.PushWarning(fmt.Errorf(errFmtNotifierStartTlsDisabled))
}
}
diff --git a/internal/configuration/validator/notifier_test.go b/internal/configuration/validator/notifier_test.go
index c22b2494f..7ba756711 100644
--- a/internal/configuration/validator/notifier_test.go
+++ b/internal/configuration/validator/notifier_test.go
@@ -1,6 +1,7 @@
package validator
import (
+ "crypto/tls"
"fmt"
"net/mail"
"testing"
@@ -22,7 +23,7 @@ func (suite *NotifierSuite) SetupTest() {
Username: "john",
Password: "password",
Sender: mail.Address{Name: "Authelia", Address: "authelia@example.com"},
- Host: "example.com",
+ Host: examplecom,
Port: 25,
}
suite.config.FileSystem = nil
@@ -77,8 +78,8 @@ func (suite *NotifierSuite) TestSMTPShouldSetTLSDefaults() {
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
- suite.Assert().Equal("example.com", suite.config.SMTP.TLS.ServerName)
- suite.Assert().Equal("TLS1.2", suite.config.SMTP.TLS.MinimumVersion)
+ suite.Assert().Equal(examplecom, suite.config.SMTP.TLS.ServerName)
+ suite.Assert().Equal(uint16(tls.VersionTLS12), suite.config.SMTP.TLS.MinimumVersion.Value)
suite.Assert().False(suite.config.SMTP.TLS.SkipVerify)
}
@@ -96,7 +97,7 @@ func (suite *NotifierSuite) TestSMTPShouldDefaultStartupCheckAddress() {
func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() {
suite.config.SMTP.Host = "google.com"
suite.config.SMTP.TLS = &schema.TLSConfig{
- MinimumVersion: "TLS1.1",
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS11},
}
ValidateNotifier(&suite.config, suite.validator)
@@ -105,10 +106,51 @@ func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() {
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal("google.com", suite.config.SMTP.TLS.ServerName)
- suite.Assert().Equal("TLS1.1", suite.config.SMTP.TLS.MinimumVersion)
+ suite.Assert().Equal(uint16(tls.VersionTLS11), suite.config.SMTP.TLS.MinimumVersion.MinVersion())
suite.Assert().False(suite.config.SMTP.TLS.SkipVerify)
}
+func (suite *NotifierSuite) TestSMTPShouldErrorOnSSL30() {
+ suite.config.SMTP.Host = examplecom
+ suite.config.SMTP.TLS = &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck
+ }
+
+ ValidateNotifier(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "notifier: smtp: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
+}
+
+func (suite *NotifierSuite) TestSMTPShouldErrorOnTLSMinVerGreaterThanMaxVer() {
+ suite.config.SMTP.Host = examplecom
+ suite.config.SMTP.TLS = &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
+ MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS10},
+ }
+
+ ValidateNotifier(&suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "notifier: smtp: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.0")
+}
+
+func (suite *NotifierSuite) TestSMTPShouldWarnOnDisabledSTARTTLS() {
+ suite.config.SMTP.Host = examplecom
+ suite.config.SMTP.DisableStartTLS = true
+
+ ValidateNotifier(&suite.config, suite.validator)
+
+ suite.Require().Len(suite.validator.Warnings(), 1)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().EqualError(suite.validator.Warnings()[0], "notifier: smtp: option 'disable_starttls' is enabled: opportunistic STARTTLS is explicitly disabled which means all emails will be sent insecurely over plaintext and this setting is only necessary for non-compliant SMTP servers which advertise they support STARTTLS when they actually don't support STARTTLS")
+}
+
func (suite *NotifierSuite) TestSMTPShouldEnsureHostAndPortAreProvided() {
suite.config.FileSystem = nil
ValidateNotifier(&suite.config, suite.validator)
diff --git a/internal/configuration/validator/session.go b/internal/configuration/validator/session.go
index 67517089f..7cf7a84b0 100644
--- a/internal/configuration/validator/session.go
+++ b/internal/configuration/validator/session.go
@@ -1,8 +1,8 @@
package validator
import (
- "errors"
"fmt"
+ "path"
"strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
@@ -58,6 +58,18 @@ func validateRedisCommon(config *schema.SessionConfiguration, validator *schema.
if config.Secret == "" {
validator.Push(fmt.Errorf(errFmtSessionSecretRequired, "redis"))
}
+
+ if config.Redis.TLS != nil {
+ configDefaultTLS := &schema.TLSConfig{
+ ServerName: config.Redis.Host,
+ MinimumVersion: schema.DefaultRedisConfiguration.TLS.MinimumVersion,
+ MaximumVersion: schema.DefaultRedisConfiguration.TLS.MaximumVersion,
+ }
+
+ if err := ValidateTLSConfig(config.Redis.TLS, configDefaultTLS); err != nil {
+ validator.Push(fmt.Errorf(errFmtSessionRedisTLSConfigInvalid, err))
+ }
+ }
}
func validateRedis(config *schema.SessionConfiguration, validator *schema.StructValidator) {
@@ -67,9 +79,7 @@ func validateRedis(config *schema.SessionConfiguration, validator *schema.Struct
validateRedisCommon(config, validator)
- if !strings.HasPrefix(config.Redis.Host, "/") && config.Redis.Port == 0 {
- validator.Push(errors.New("A redis port different than 0 must be provided"))
- } else if config.Redis.Port < 0 || config.Redis.Port > 65535 {
+ if !path.IsAbs(config.Redis.Host) && (config.Redis.Port < 1 || config.Redis.Port > 65535) {
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, config.Redis.Port))
}
diff --git a/internal/configuration/validator/session_test.go b/internal/configuration/validator/session_test.go
index 605a65fa9..29ef3d91f 100644
--- a/internal/configuration/validator/session_test.go
+++ b/internal/configuration/validator/session_test.go
@@ -1,6 +1,7 @@
package validator
import (
+ "crypto/tls"
"fmt"
"testing"
@@ -13,7 +14,7 @@ import (
func newDefaultSessionConfig() schema.SessionConfiguration {
config := schema.SessionConfiguration{}
config.Secret = testJWTSecret
- config.Domain = "example.com"
+ config.Domain = examplecom
return config
}
@@ -148,7 +149,7 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "A redis port different than 0 must be provided")
+ assert.EqualError(t, validator.Errors()[0], "session: redis: option 'port' must be between 1 and 65535 but is configured as '0'")
}
func TestShouldRaiseOneErrorWhenRedisHighAvailabilityHasNodesWithNoHost(t *testing.T) {
@@ -354,6 +355,67 @@ func TestShouldRaiseErrorsWhenRedisHostNotSet(t *testing.T) {
assert.EqualError(t, errors[0], errFmtSessionRedisHostRequired)
}
+func TestShouldSetDefaultRedisTLSOptions(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := newDefaultSessionConfig()
+
+ config.Redis = &schema.RedisSessionConfiguration{
+ Host: "redis.local",
+ Port: 6379,
+ TLS: &schema.TLSConfig{},
+ }
+
+ ValidateSession(&config, validator)
+
+ assert.Len(t, validator.Warnings(), 0)
+ assert.Len(t, validator.Errors(), 0)
+
+ assert.Equal(t, uint16(tls.VersionTLS12), config.Redis.TLS.MinimumVersion.Value)
+ assert.Equal(t, uint16(0), config.Redis.TLS.MaximumVersion.Value)
+ assert.Equal(t, "redis.local", config.Redis.TLS.ServerName)
+}
+
+func TestShouldRaiseErrorOnBadRedisTLSOptionsSSL30(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := newDefaultSessionConfig()
+
+ config.Redis = &schema.RedisSessionConfiguration{
+ Host: "redis.local",
+ Port: 6379,
+ TLS: &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck
+ },
+ }
+
+ ValidateSession(&config, validator)
+
+ assert.Len(t, validator.Warnings(), 0)
+ require.Len(t, validator.Errors(), 1)
+
+ assert.EqualError(t, validator.Errors()[0], "session: redis: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
+}
+
+func TestShouldRaiseErrorOnBadRedisTLSOptionsMinVerGreaterThanMax(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := newDefaultSessionConfig()
+
+ config.Redis = &schema.RedisSessionConfiguration{
+ Host: "redis.local",
+ Port: 6379,
+ TLS: &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
+ MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS10},
+ },
+ }
+
+ ValidateSession(&config, validator)
+
+ assert.Len(t, validator.Warnings(), 0)
+ require.Len(t, validator.Errors(), 1)
+
+ assert.EqualError(t, validator.Errors()[0], "session: redis: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.0")
+}
+
func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
diff --git a/internal/configuration/validator/shared.go b/internal/configuration/validator/shared.go
new file mode 100644
index 000000000..804945553
--- /dev/null
+++ b/internal/configuration/validator/shared.go
@@ -0,0 +1,42 @@
+package validator
+
+import (
+ "crypto/tls"
+ "errors"
+ "fmt"
+
+ "github.com/authelia/authelia/v4/internal/configuration/schema"
+)
+
+// ValidateTLSConfig sets the default values and validates a schema.TLSConfig.
+func ValidateTLSConfig(config *schema.TLSConfig, configDefault *schema.TLSConfig) (err error) {
+ if config == nil {
+ return
+ }
+
+ if config.ServerName == "" {
+ config.ServerName = configDefault.ServerName
+ }
+
+ if config.MinimumVersion.Value == 0 {
+ config.MinimumVersion.Value = configDefault.MinimumVersion.Value
+ }
+
+ if config.MaximumVersion.Value == 0 {
+ config.MaximumVersion.Value = configDefault.MaximumVersion.Value
+ }
+
+ if config.MinimumVersion.MinVersion() < tls.VersionTLS10 {
+ return errors.New("option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
+ }
+
+ if config.MinimumVersion.MinVersion() > config.MaximumVersion.MaxVersion() {
+ return fmt.Errorf("option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version %s is greater than the maximum version %s", config.MinimumVersion.String(), config.MaximumVersion.String())
+ }
+
+ if (config.CertificateChain.HasCertificates() || config.PrivateKey != nil) && !config.CertificateChain.EqualKey(config.PrivateKey) {
+ return errors.New("option 'certificates' is invalid: provided certificate is not the public key for the private key provided")
+ }
+
+ return nil
+}
diff --git a/internal/configuration/validator/storage.go b/internal/configuration/validator/storage.go
index a545113bf..7172383a7 100644
--- a/internal/configuration/validator/storage.go
+++ b/internal/configuration/validator/storage.go
@@ -17,7 +17,7 @@ func ValidateStorage(config schema.StorageConfiguration, validator *schema.Struc
switch {
case config.MySQL != nil:
- validateSQLConfiguration(&config.MySQL.SQLStorageConfiguration, validator, "mysql")
+ validateMySQLConfiguration(config.MySQL, validator)
case config.PostgreSQL != nil:
validatePostgreSQLConfiguration(config.PostgreSQL, validator)
case config.Local != nil:
@@ -49,6 +49,22 @@ func validateSQLConfiguration(config *schema.SQLStorageConfiguration, validator
}
}
+func validateMySQLConfiguration(config *schema.MySQLStorageConfiguration, validator *schema.StructValidator) {
+ validateSQLConfiguration(&config.SQLStorageConfiguration, validator, "mysql")
+
+ if config.TLS != nil {
+ configDefaultTLS := &schema.TLSConfig{
+ ServerName: config.Host,
+ MinimumVersion: schema.DefaultMySQLStorageConfiguration.TLS.MinimumVersion,
+ MaximumVersion: schema.DefaultMySQLStorageConfiguration.TLS.MaximumVersion,
+ }
+
+ if err := ValidateTLSConfig(config.TLS, configDefaultTLS); err != nil {
+ validator.Push(fmt.Errorf(errFmtStorageTLSConfigInvalid, "mysql", err))
+ }
+ }
+}
+
func validatePostgreSQLConfiguration(config *schema.PostgreSQLStorageConfiguration, validator *schema.StructValidator) {
validateSQLConfiguration(&config.SQLStorageConfiguration, validator, "postgres")
@@ -56,10 +72,28 @@ func validatePostgreSQLConfiguration(config *schema.PostgreSQLStorageConfigurati
config.Schema = schema.DefaultPostgreSQLStorageConfiguration.Schema
}
- if config.SSL.Mode == "" {
- config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
- } else if !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes) {
- validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strings.Join(validStoragePostgreSQLSSLModes, "', '"), config.SSL.Mode))
+ switch {
+ case config.TLS != nil && config.SSL != nil:
+ validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLAndTLSConfig))
+ case config.TLS != nil:
+ configDefaultTLS := &schema.TLSConfig{
+ ServerName: config.Host,
+ MinimumVersion: schema.DefaultPostgreSQLStorageConfiguration.TLS.MinimumVersion,
+ MaximumVersion: schema.DefaultPostgreSQLStorageConfiguration.TLS.MaximumVersion,
+ }
+
+ if err := ValidateTLSConfig(config.TLS, configDefaultTLS); err != nil {
+ validator.Push(fmt.Errorf(errFmtStorageTLSConfigInvalid, "postgres", err))
+ }
+ case config.SSL != nil:
+ validator.PushWarning(fmt.Errorf(warnFmtStoragePostgreSQLInvalidSSLDeprecated))
+
+ switch {
+ case config.SSL.Mode == "":
+ config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
+ case !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes):
+ validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strings.Join(validStoragePostgreSQLSSLModes, "', '"), config.SSL.Mode))
+ }
}
}
diff --git a/internal/configuration/validator/storage_test.go b/internal/configuration/validator/storage_test.go
index bf0d0a67c..8ac7d9dbb 100644
--- a/internal/configuration/validator/storage_test.go
+++ b/internal/configuration/validator/storage_test.go
@@ -1,6 +1,7 @@
package validator
import (
+ "crypto/tls"
"testing"
"github.com/stretchr/testify/suite"
@@ -79,6 +80,70 @@ func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabas
suite.Require().Len(suite.validator.Errors(), 0)
}
+func (suite *StorageSuite) TestShouldSetDefaultMySQLTLSServerName() {
+ suite.config.MySQL = &schema.MySQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "mysql1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ TLS: &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12},
+ },
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Equal(suite.config.MySQL.Host, suite.config.MySQL.TLS.ServerName)
+}
+
+func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSVersion() {
+ suite.config.MySQL = &schema.MySQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "db1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ TLS: &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck
+ },
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "storage: mysql: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
+}
+
+func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSMinVersionGreaterThanMaximum() {
+ suite.config.MySQL = &schema.MySQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "db1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ TLS: &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
+ MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS11},
+ },
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "storage: mysql: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.1")
+}
+
func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDatabaseAreProvided() {
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{}
suite.config.MySQL = nil
@@ -104,7 +169,7 @@ func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDa
suite.Assert().Len(suite.validator.Errors(), 0)
}
-func (suite *StorageSuite) TestShouldValidatePostgresSSLModeAndSchemaDefaults() {
+func (suite *StorageSuite) TestShouldValidatePostgresSchemaDefault() {
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "db1",
@@ -119,10 +184,140 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeAndSchemaDefaults()
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
- suite.Assert().Equal("disable", suite.config.PostgreSQL.SSL.Mode)
+ suite.Assert().Nil(suite.config.PostgreSQL.SSL)
+ suite.Assert().Nil(suite.config.PostgreSQL.TLS)
+
suite.Assert().Equal("public", suite.config.PostgreSQL.Schema)
}
+func (suite *StorageSuite) TestShouldValidatePostgresTLSDefaults() {
+ suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "db1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ TLS: &schema.TLSConfig{},
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Nil(suite.config.PostgreSQL.SSL)
+ suite.Require().NotNil(suite.config.PostgreSQL.TLS)
+
+ suite.Assert().Equal(uint16(tls.VersionTLS12), suite.config.PostgreSQL.TLS.MinimumVersion.Value)
+}
+
+func (suite *StorageSuite) TestShouldSetDefaultPostgreSQLTLSServerName() {
+ suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "mysql1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ TLS: &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12},
+ },
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().Equal(suite.config.PostgreSQL.Host, suite.config.PostgreSQL.TLS.ServerName)
+}
+
+func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLTLSVersion() {
+ suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "db1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ TLS: &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck
+ },
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
+}
+
+func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLMinVersionGreaterThanMaximum() {
+ suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "db1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ TLS: &schema.TLSConfig{
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
+ MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS11},
+ },
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.1")
+}
+
+func (suite *StorageSuite) TestShouldValidatePostgresSSLDefaults() {
+ suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "db1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ SSL: &schema.PostgreSQLSSLStorageConfiguration{},
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 1)
+ suite.Assert().Len(suite.validator.Errors(), 0)
+
+ suite.Assert().NotNil(suite.config.PostgreSQL.SSL)
+ suite.Require().Nil(suite.config.PostgreSQL.TLS)
+
+ suite.Assert().Equal(schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode, suite.config.PostgreSQL.SSL.Mode)
+}
+
+func (suite *StorageSuite) TestShouldRaiseErrorOnTLSAndLegacySSL() {
+ suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
+ SQLStorageConfiguration: schema.SQLStorageConfiguration{
+ Host: "db1",
+ Username: "myuser",
+ Password: "pass",
+ Database: "database",
+ },
+ SSL: &schema.PostgreSQLSSLStorageConfiguration{},
+ TLS: &schema.TLSConfig{},
+ }
+
+ ValidateStorage(suite.config, suite.validator)
+
+ suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Errors(), 1)
+
+ suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: can't define both 'tls' and 'ssl' configuration options")
+}
+
func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfiguration() {
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{
@@ -132,18 +327,20 @@ func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfigu
Database: "database",
},
Schema: "authelia",
- SSL: schema.PostgreSQLSSLStorageConfiguration{
+ SSL: &schema.PostgreSQLSSLStorageConfiguration{
Mode: "require",
},
}
ValidateStorage(suite.config, suite.validator)
- suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Require().Len(suite.validator.Warnings(), 1)
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal("require", suite.config.PostgreSQL.SSL.Mode)
suite.Assert().Equal("authelia", suite.config.PostgreSQL.Schema)
+
+ suite.Assert().EqualError(suite.validator.Warnings()[0], "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead")
}
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
@@ -154,14 +351,14 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
Password: "pass",
Database: "database",
},
- SSL: schema.PostgreSQLSSLStorageConfiguration{
+ SSL: &schema.PostgreSQLSSLStorageConfiguration{
Mode: "unknown",
},
}
ValidateStorage(suite.config, suite.validator)
- suite.Assert().Len(suite.validator.Warnings(), 0)
+ suite.Assert().Len(suite.validator.Warnings(), 1)
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', 'verify-full' but it is configured as 'unknown'")
}
diff --git a/internal/configuration/validator/theme_test.go b/internal/configuration/validator/theme_test.go
index 1b29d0e38..abe796611 100644
--- a/internal/configuration/validator/theme_test.go
+++ b/internal/configuration/validator/theme_test.go
@@ -29,7 +29,7 @@ func (suite *Theme) TestShouldValidateCompleteConfiguration() {
}
func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() {
- suite.config.Theme = "invalid"
+ suite.config.Theme = testInvalid
ValidateTheme(suite.config, suite.validator)
diff --git a/internal/configuration/validator/totp_test.go b/internal/configuration/validator/totp_test.go
index 85cd7471f..956f074c4 100644
--- a/internal/configuration/validator/totp_test.go
+++ b/internal/configuration/validator/totp_test.go
@@ -30,7 +30,7 @@ func TestValidateTOTP(t *testing.T) {
{
desc: "ShouldNormalizeTOTPAlgorithm",
have: schema.TOTPConfiguration{
- Algorithm: "sha1",
+ Algorithm: digestSHA1,
Digits: 6,
Period: 30,
SecretSize: 32,
diff --git a/internal/handlers/const.go b/internal/handlers/const.go
index f2514a080..ae9e4d603 100644
--- a/internal/handlers/const.go
+++ b/internal/handlers/const.go
@@ -28,6 +28,19 @@ var (
headerRemoteEmail = []byte("Remote-Email")
)
+const (
+ queryArgRD = "rd"
+ queryArgID = "id"
+ queryArgConsentID = "consent_id"
+ queryArgWorkflow = "workflow"
+ queryArgWorkflowID = "workflow_id"
+)
+
+var (
+ qryArgID = []byte(queryArgID)
+ qryArgConsentID = []byte(queryArgConsentID)
+)
+
const (
// Forbidden means the user is forbidden the access to a resource.
Forbidden authorizationMatching = iota
@@ -62,6 +75,39 @@ const (
logFmtTraceProfileDetails = "Profile details for user '%s' => groups: %s, emails %s"
)
+const (
+ logFmtAuthorizationPrefix = "Authorization Request with id '%s' on client with id '%s' "
+
+ logFmtErrConsentCantDetermineConsentMode = logFmtAuthorizationPrefix + "could not be processed: error occurred generating consent: client consent mode could not be reliably determined"
+
+ logFmtConsentPrefix = logFmtAuthorizationPrefix + "using consent mode '%s' "
+
+ logFmtErrConsentParseChallengeID = logFmtConsentPrefix + "could not be processed: error occurred parsing the consent id (challenge) '%s': %+v"
+ logFmtErrConsentPreConfLookup = logFmtConsentPrefix + "had error looking up pre-configured consent sessions: %+v"
+ logFmtErrConsentPreConfRowsClose = logFmtConsentPrefix + "had error closing rows while looking up pre-configured consent sessions: %+v"
+ logFmtErrConsentZeroID = logFmtConsentPrefix + "could not be processed: the consent id had a zero value"
+ logFmtErrConsentCantGetSubject = logFmtConsentPrefix + "could not be processed: error occurred retrieving subject identifier for user '%s' and sector identifier '%s': %+v"
+ logFmtErrConsentGenerateError = logFmtConsentPrefix + "could not be processed: error occurred %s consent: %+v"
+
+ logFmtDbgConsentGenerate = logFmtConsentPrefix + "proceeding to generate a new consent session"
+ logFmtDbgConsentAuthenticationSufficiency = logFmtConsentPrefix + "authentication level '%s' is %s for client level '%s'"
+ logFmtDbgConsentRedirect = logFmtConsentPrefix + "is being redirected to '%s'"
+ logFmtDbgConsentPreConfSuccessfulLookup = logFmtConsentPrefix + "successfully looked up pre-configured consent with signature of client id '%s' and subject '%s' and scopes '%s' with id '%d'"
+ logFmtDbgConsentPreConfUnsuccessfulLookup = logFmtConsentPrefix + "unsuccessfully looked up pre-configured consent with signature of client id '%s' and subject '%s' and scopes '%s'"
+ logFmtDbgConsentPreConfTryingLookup = logFmtConsentPrefix + "attempting to discover pre-configurations with signature of client id '%s' and subject '%s' and scopes '%s'"
+
+ logFmtErrConsentWithIDCouldNotBeProcessed = logFmtConsentPrefix + "could not be processed: error occurred performing consent for consent session with id '%s': "
+
+ logFmtErrConsentLookupLoadingSession = logFmtErrConsentWithIDCouldNotBeProcessed + "error occurred while loading session: %+v"
+ logFmtErrConsentSessionSubjectNotAuthorized = logFmtErrConsentWithIDCouldNotBeProcessed + "user '%s' with subject '%s' is not authorized to consent for subject '%s'"
+ logFmtErrConsentCantGrant = logFmtErrConsentWithIDCouldNotBeProcessed + "the session does not appear to be valid for %s consent: either the subject is null, the consent has already been granted, or the consent session is a pre-configured session"
+ logFmtErrConsentCantGrantPreConf = logFmtErrConsentWithIDCouldNotBeProcessed + "the session does not appear to be valid for pre-configured consent: either the subject is null, the consent has been granted and is either not pre-configured, or the pre-configuration is expired"
+ logFmtErrConsentCantGrantRejected = logFmtErrConsentWithIDCouldNotBeProcessed + "the user explicitly rejected this consent session"
+ logFmtErrConsentSaveSessionResponse = logFmtErrConsentWithIDCouldNotBeProcessed + "error occurred saving consent session response: %+v"
+ logFmtErrConsentSaveSession = logFmtErrConsentWithIDCouldNotBeProcessed + "error occurred saving consent session: %+v"
+ logFmtErrConsentGenerate = logFmtConsentPrefix + "could not be processed: error occurred generating consent: %+v"
+)
+
const (
testInactivity = time.Second * 10
testRedirectionURL = "http://redirection.local"
diff --git a/internal/handlers/handler_checks_safe_redirection.go b/internal/handlers/handler_checks_safe_redirection.go
index d53055965..96b66114e 100644
--- a/internal/handlers/handler_checks_safe_redirection.go
+++ b/internal/handlers/handler_checks_safe_redirection.go
@@ -3,7 +3,6 @@ package handlers
import (
"fmt"
- "github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/utils"
)
@@ -12,7 +11,7 @@ import (
func CheckSafeRedirectionPOST(ctx *middlewares.AutheliaCtx) {
userSession := ctx.GetSession()
- if userSession.AuthenticationLevel == authentication.NotAuthenticated {
+ if userSession.IsAnonymous() {
ctx.ReplyUnauthorized()
return
}
diff --git a/internal/handlers/handler_firstfactor.go b/internal/handlers/handler_firstfactor.go
index 967a9953f..f52119576 100644
--- a/internal/handlers/handler_firstfactor.go
+++ b/internal/handlers/handler_firstfactor.go
@@ -23,7 +23,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
defer delayFunc(ctx, requestTime, &successful)
}
- bodyJSON := firstFactorRequestBody{}
+ bodyJSON := bodyFirstFactorRequest{}
if err := ctx.ParseBody(&bodyJSON); err != nil {
ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthType1FA, err)
@@ -136,7 +136,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
successful = true
if bodyJSON.Workflow == workflowOpenIDConnect {
- handleOIDCWorkflowResponse(ctx, bodyJSON.TargetURL)
+ handleOIDCWorkflowResponse(ctx, bodyJSON.TargetURL, bodyJSON.WorkflowID)
} else {
Handle1FAResponse(ctx, bodyJSON.TargetURL, bodyJSON.RequestMethod, userSession.Username, userSession.Groups)
}
diff --git a/internal/handlers/handler_oauth_introspection.go b/internal/handlers/handler_oauth_introspection.go
index f53e5a195..a131f7398 100644
--- a/internal/handlers/handler_oauth_introspection.go
+++ b/internal/handlers/handler_oauth_introspection.go
@@ -20,12 +20,12 @@ func OAuthIntrospectionPOST(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter
oidcSession := oidc.NewSession()
- if responder, err = ctx.Providers.OpenIDConnect.Fosite.NewIntrospectionRequest(ctx, req, oidcSession); err != nil {
+ if responder, err = ctx.Providers.OpenIDConnect.NewIntrospectionRequest(ctx, req, oidcSession); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Introspection Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription())
- ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionError(rw, err)
+ ctx.Providers.OpenIDConnect.WriteIntrospectionError(rw, err)
return
}
@@ -34,5 +34,5 @@ func OAuthIntrospectionPOST(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter
ctx.Logger.Tracef("Introspection Request yeilded a %s (active: %t) requested at %s created with request id '%s' on client with id '%s'", responder.GetTokenUse(), responder.IsActive(), requester.GetRequestedAt().String(), requester.GetID(), requester.GetClient().GetID())
- ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionResponse(rw, responder)
+ ctx.Providers.OpenIDConnect.WriteIntrospectionResponse(rw, responder)
}
diff --git a/internal/handlers/handler_oauth_revocation.go b/internal/handlers/handler_oauth_revocation.go
index 95af94b9e..89d423311 100644
--- a/internal/handlers/handler_oauth_revocation.go
+++ b/internal/handlers/handler_oauth_revocation.go
@@ -14,11 +14,11 @@ import (
func OAuthRevocationPOST(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) {
var err error
- if err = ctx.Providers.OpenIDConnect.Fosite.NewRevocationRequest(ctx, req); err != nil {
+ if err = ctx.Providers.OpenIDConnect.NewRevocationRequest(ctx, req); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Revocation Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription())
}
- ctx.Providers.OpenIDConnect.Fosite.WriteRevocationResponse(rw, err)
+ ctx.Providers.OpenIDConnect.WriteRevocationResponse(rw, err)
}
diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go
index 8d16146a5..df919e074 100644
--- a/internal/handlers/handler_oidc_authorization.go
+++ b/internal/handlers/handler_oidc_authorization.go
@@ -3,6 +3,7 @@ package handlers
import (
"errors"
"net/http"
+ "net/url"
"time"
"github.com/ory/fosite"
@@ -12,25 +13,25 @@ import (
"github.com/authelia/authelia/v4/internal/oidc"
)
-// OpenIDConnectAuthorizationGET handles GET requests to the OpenID Connect 1.0 Authorization endpoint.
+// OpenIDConnectAuthorization handles GET/POST requests to the OpenID Connect 1.0 Authorization endpoint.
//
// https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
-func OpenIDConnectAuthorizationGET(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) {
+func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) {
var (
requester fosite.AuthorizeRequester
responder fosite.AuthorizeResponder
client *oidc.Client
authTime time.Time
- issuer string
+ issuer *url.URL
err error
)
- if requester, err = ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeRequest(ctx, r); err != nil {
+ if requester, err = ctx.Providers.OpenIDConnect.NewAuthorizeRequest(ctx, r); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Authorization Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription())
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, err)
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, err)
return
}
@@ -39,22 +40,22 @@ func OpenIDConnectAuthorizationGET(ctx *middlewares.AutheliaCtx, rw http.Respons
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' is being processed", requester.GetID(), clientID)
- if client, err = ctx.Providers.OpenIDConnect.Store.GetFullClient(clientID); err != nil {
+ if client, err = ctx.Providers.OpenIDConnect.GetFullClient(clientID); err != nil {
if errors.Is(err, fosite.ErrNotFound) {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: client was not found", requester.GetID(), clientID)
} else {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: failed to find client: %+v", requester.GetID(), clientID, err)
}
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, err)
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, err)
return
}
- if issuer, err = ctx.ExternalRootURL(); err != nil {
+ if issuer, err = ctx.IssuerURL(); err != nil {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred determining issuer: %+v", requester.GetID(), clientID, err)
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not determine issuer."))
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrIssuerCouldNotDerive)
return
}
@@ -75,7 +76,7 @@ func OpenIDConnectAuthorizationGET(ctx *middlewares.AutheliaCtx, rw http.Respons
if authTime, err = userSession.AuthenticatedTime(client.Policy); err != nil {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred checking authentication time: %+v", requester.GetID(), client.GetID(), err)
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time."))
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time."))
return
}
@@ -88,12 +89,12 @@ func OpenIDConnectAuthorizationGET(ctx *middlewares.AutheliaCtx, rw http.Respons
ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v",
requester.GetID(), oidcSession.ClientID, oidcSession.Subject, oidcSession.Username, oidcSession.Claims)
- if responder, err = ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeResponse(ctx, requester, oidcSession); err != nil {
+ if responder, err = ctx.Providers.OpenIDConnect.NewAuthorizeResponse(ctx, requester, oidcSession); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Authorization Response for Request with id '%s' on client with id '%s' could not be created: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription())
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, err)
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, err)
return
}
@@ -101,10 +102,10 @@ func OpenIDConnectAuthorizationGET(ctx *middlewares.AutheliaCtx, rw http.Respons
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionGranted(ctx, consent.ID); err != nil {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred saving consent session: %+v", requester.GetID(), client.GetID(), err)
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not save the session."))
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
return
}
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeResponse(rw, requester, responder)
+ ctx.Providers.OpenIDConnect.WriteAuthorizeResponse(rw, requester, responder)
}
diff --git a/internal/handlers/handler_oidc_authorization_consent.go b/internal/handlers/handler_oidc_authorization_consent.go
index 3fdfb25d1..04dfacbb1 100644
--- a/internal/handlers/handler_oidc_authorization_consent.go
+++ b/internal/handlers/handler_oidc_authorization_consent.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"fmt"
"net/http"
"net/url"
@@ -16,115 +17,68 @@ import (
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/session"
- "github.com/authelia/authelia/v4/internal/storage"
"github.com/authelia/authelia/v4/internal/utils"
)
-func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, rootURI string, client *oidc.Client,
+func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
userSession session.UserSession,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
var (
- issuer *url.URL
subject uuid.UUID
err error
)
- if issuer, err = url.ParseRequestURI(rootURI); err != nil {
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not safely determine the issuer."))
+ var handler handlerAuthorizationConsent
- return nil, true
- }
+ switch {
+ case userSession.IsAnonymous():
+ handler = handleOIDCAuthorizationConsentNotAuthenticated
+ case client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel):
+ if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.Consent, userSession.Username, client.GetSectorIdentifier(), err)
- if !strings.HasSuffix(issuer.Path, "/") {
- issuer.Path += "/"
- }
-
- // This prevents the consent request from being generated until the authentication level is sufficient.
- if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) || userSession.Username == "" {
- redirectURL := getOIDCAuthorizationRedirectURL(issuer, requester)
-
- ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' is being redirected due to insufficient authentication", requester.GetID(), client.GetID())
-
- http.Redirect(rw, r, redirectURL.String(), http.StatusFound)
-
- return nil, true
- }
-
- if subject, err = ctx.Providers.OpenIDConnect.Store.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
- ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred retrieving subject identifier for user '%s' and sector identifier '%s': %+v", requester.GetID(), client.GetID(), userSession.Username, client.GetSectorIdentifier(), err)
-
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not retrieve the subject."))
-
- return nil, true
- }
-
- var consentIDBytes []byte
-
- if consentIDBytes = ctx.QueryArgs().Peek("consent_id"); len(consentIDBytes) != 0 {
- var consentID uuid.UUID
-
- if consentID, err = uuid.Parse(string(consentIDBytes)); err != nil {
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Consent Session ID was Malformed."))
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrSubjectCouldNotLookup)
return nil, true
}
- ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' proceeding to lookup consent by challenge id '%s'", requester.GetID(), client.GetID(), consentID)
+ switch client.Consent.Mode {
+ case oidc.ClientConsentModeExplicit:
+ handler = handleOIDCAuthorizationConsentModeExplicit
+ case oidc.ClientConsentModeImplicit:
+ handler = handleOIDCAuthorizationConsentModeImplicit
+ case oidc.ClientConsentModePreConfigured:
+ handler = handleOIDCAuthorizationConsentModePreConfigured
+ default:
+ ctx.Logger.Errorf(logFmtErrConsentCantDetermineConsentMode, requester.GetID(), client.GetID())
- return handleOIDCAuthorizationConsentWithChallengeID(ctx, issuer, client, userSession, subject, consentID, rw, r, requester)
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not determine the client consent mode."))
+
+ return nil, true
+ }
+ default:
+ if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.Consent, userSession.Username, client.GetSectorIdentifier(), err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrSubjectCouldNotLookup)
+
+ return nil, true
+ }
+
+ handler = handleOIDCAuthorizationConsentGenerate
}
- return handleOIDCAuthorizationConsentGenerate(ctx, issuer, client, userSession, subject, rw, r, requester)
+ return handler(ctx, issuer, client, userSession, subject, rw, r, requester)
}
-func handleOIDCAuthorizationConsentWithChallengeID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
- userSession session.UserSession, subject, challengeID uuid.UUID,
+func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, issuer *url.URL, _ *oidc.Client,
+ _ session.UserSession, _ uuid.UUID,
rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
- var (
- err error
- )
+ redirectionURL := handleOIDCAuthorizationConsentGetRedirectionURL(issuer, nil, requester)
- if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, challengeID); err != nil {
- ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred during consent session lookup: %+v", requester.GetID(), requester.GetClient().GetID(), err)
+ http.Redirect(rw, r, redirectionURL.String(), http.StatusFound)
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Failed to lookup consent session."))
-
- return nil, true
- }
-
- if err = verifyOIDCUserAuthorizedForConsent(ctx, client, userSession, consent, subject); err != nil {
- ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not process consent session with challenge id '%s': could not authorize the user user '%s' for this consent session: %v", requester.GetID(), client.GetID(), consent.ChallengeID, userSession.Username, err)
-
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("The user is not authorized to perform consent."))
-
- return nil, true
- }
-
- if consent.Responded() {
- if consent.Granted {
- ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: this consent session with challenge id '%s' was already granted", requester.GetID(), client.GetID(), consent.ChallengeID)
-
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Authorization already granted."))
-
- return nil, true
- }
-
- ctx.Logger.Debugf("Authorization Request with id '%s' loaded consent session with id '%d' and challenge id '%s' for client id '%s' and subject '%s' and scopes '%s'", requester.GetID(), consent.ID, consent.ChallengeID, client.GetID(), consent.Subject.UUID, strings.Join(requester.GetRequestedScopes(), " "))
-
- if consent.IsDenied() {
- ctx.Logger.Warnf("Authorization Request with id '%s' and challenge id '%s' for client id '%s' and subject '%s' and scopes '%s' was not denied by the user durng the consent session", requester.GetID(), consent.ChallengeID, client.GetID(), consent.Subject.UUID, strings.Join(requester.GetRequestedScopes(), " "))
-
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrAccessDenied)
-
- return nil, true
- }
-
- return consent, false
- }
-
- handleOIDCAuthorizationConsentRedirect(ctx, issuer, consent, client, userSession, rw, r, requester)
-
- return consent, true
+ return nil, true
}
func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
@@ -134,36 +88,28 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer
err error
)
- scopes, audience := getOIDCExpectedScopesAndAudienceFromRequest(requester)
+ ctx.Logger.Debugf(logFmtDbgConsentGenerate, requester.GetID(), client.GetID(), client.Consent)
- if consent, err = getOIDCPreConfiguredConsent(ctx, client.GetID(), subject, scopes, audience); err != nil {
- ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' had error looking up pre-configured consent sessions: %+v", requester.GetID(), requester.GetClient().GetID(), err)
+ if len(ctx.QueryArgs().PeekBytes(qryArgConsentID)) != 0 {
+ ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "generating", errors.New("consent id value was present when it should be absent"))
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not lookup the consent session."))
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotGenerate)
return nil, true
}
- if consent != nil {
- ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' successfully looked up pre-configured consent with challenge id '%s'", requester.GetID(), client.GetID(), consent.ChallengeID)
-
- return consent, false
- }
-
- ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' proceeding to generate a new consent due to unsuccessful lookup of pre-configured consent", requester.GetID(), client.GetID())
-
if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil {
- ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred generating consent: %+v", requester.GetID(), requester.GetClient().GetID(), err)
+ ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "generating", err)
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not generate the consent session."))
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotGenerate)
return nil, true
}
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil {
- ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred saving consent session: %+v", requester.GetID(), client.GetID(), err)
+ ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "saving", err)
- ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not save the consent session."))
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
return nil, true
}
@@ -179,72 +125,89 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer
if client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
location, _ = url.ParseRequestURI(issuer.String())
- location.Path = path.Join(location.Path, "/consent")
+ location.Path = path.Join(location.Path, oidc.EndpointPathConsent)
query := location.Query()
- query.Set("consent_id", consent.ChallengeID.String())
+ query.Set(queryArgID, consent.ChallengeID.String())
location.RawQuery = query.Encode()
- ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' authentication level '%s' is sufficient for client level '%s'", requester.GetID(), client.GetID(), authentication.LevelToString(userSession.AuthenticationLevel), authorization.LevelToString(client.Policy))
+ ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.Consent, authentication.LevelToString(userSession.AuthenticationLevel), "sufficient", authorization.LevelToString(client.Policy))
} else {
- location = getOIDCAuthorizationRedirectURL(issuer, requester)
+ location = handleOIDCAuthorizationConsentGetRedirectionURL(issuer, consent, requester)
- ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' authentication level '%s' is insufficient for client level '%s'", requester.GetID(), client.GetID(), authentication.LevelToString(userSession.AuthenticationLevel), authorization.LevelToString(client.Policy))
+ ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.Consent, authentication.LevelToString(userSession.AuthenticationLevel), "insufficient", authorization.LevelToString(client.Policy))
}
- ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' is being redirected to '%s'", requester.GetID(), client.GetID(), location)
+ ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.Consent, location)
http.Redirect(rw, r, location.String(), http.StatusFound)
}
-func verifyOIDCUserAuthorizedForConsent(ctx *middlewares.AutheliaCtx, client *oidc.Client, userSession session.UserSession, consent *model.OAuth2ConsentSession, subject uuid.UUID) (err error) {
- var sid, csid uint32
+func handleOIDCAuthorizationConsentGetRedirectionURL(issuer *url.URL, consent *model.OAuth2ConsentSession, requester fosite.AuthorizeRequester) (redirectURL *url.URL) {
+ iss := issuer.String()
- csid = consent.Subject.UUID.ID()
-
- if !consent.Subject.Valid || csid == 0 {
- return fmt.Errorf("the consent subject is null for consent session with id '%d'", consent.ID)
+ if !strings.HasSuffix(iss, "/") {
+ iss += "/"
}
+ redirectURL, _ = url.ParseRequestURI(iss)
+
+ query := redirectURL.Query()
+ query.Set(queryArgWorkflow, workflowOpenIDConnect)
+
+ switch {
+ case consent != nil:
+ query.Set(queryArgWorkflowID, consent.ChallengeID.String())
+ case requester != nil:
+ rd, _ := url.ParseRequestURI(iss)
+ rd.Path = path.Join(rd.Path, oidc.EndpointPathAuthorization)
+ rd.RawQuery = requester.GetRequestForm().Encode()
+
+ query.Set(queryArgRD, rd.String())
+ }
+
+ redirectURL.RawQuery = query.Encode()
+
+ return redirectURL
+}
+
+func verifyOIDCUserAuthorizedForConsent(ctx *middlewares.AutheliaCtx, client *oidc.Client, userSession session.UserSession, consent *model.OAuth2ConsentSession, subject uuid.UUID) (err error) {
+ var sid uint32
+
if client == nil {
- if client, err = ctx.Providers.OpenIDConnect.Store.GetFullClient(consent.ClientID); err != nil {
+ if client, err = ctx.Providers.OpenIDConnect.GetFullClient(consent.ClientID); err != nil {
return fmt.Errorf("failed to retrieve client: %w", err)
}
}
if sid = subject.ID(); sid == 0 {
- if subject, err = ctx.Providers.OpenIDConnect.Store.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
+ if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
return fmt.Errorf("failed to lookup subject: %w", err)
}
sid = subject.ID()
}
- if csid != sid {
+ if !consent.Subject.Valid {
+ if sid == 0 {
+ return fmt.Errorf("the consent subject is null for consent session with id '%d' for anonymous user", consent.ID)
+ }
+
+ consent.Subject = uuid.NullUUID{UUID: subject, Valid: true}
+
+ if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionSubject(ctx, *consent); err != nil {
+ return fmt.Errorf("failed to update the consent subject: %w", err)
+ }
+ }
+
+ if consent.Subject.UUID.ID() != sid {
return fmt.Errorf("the consent subject identifier '%s' isn't owned by user '%s' who has a subject identifier of '%s' with sector identifier '%s'", consent.Subject.UUID, userSession.Username, subject, client.GetSectorIdentifier())
}
return nil
}
-func getOIDCAuthorizationRedirectURL(issuer *url.URL, requester fosite.AuthorizeRequester) (redirectURL *url.URL) {
- redirectURL, _ = url.ParseRequestURI(issuer.String())
-
- authorizationURL, _ := url.ParseRequestURI(issuer.String())
-
- authorizationURL.Path = path.Join(authorizationURL.Path, oidc.AuthorizationPath)
- authorizationURL.RawQuery = requester.GetRequestForm().Encode()
-
- query := redirectURL.Query()
- query.Set("rd", authorizationURL.String())
- query.Set("workflow", workflowOpenIDConnect)
-
- redirectURL.RawQuery = query.Encode()
-
- return redirectURL
-}
-
func getOIDCExpectedScopesAndAudienceFromRequest(requester fosite.Requester) (scopes, audience []string) {
return getOIDCExpectedScopesAndAudience(requester.GetClient().GetID(), requester.GetRequestedScopes(), requester.GetRequestedAudience())
}
@@ -256,45 +219,3 @@ func getOIDCExpectedScopesAndAudience(clientID string, scopes, audience []string
return scopes, audience
}
-
-func getOIDCPreConfiguredConsent(ctx *middlewares.AutheliaCtx, clientID string, subject uuid.UUID, scopes, audience []string) (consent *model.OAuth2ConsentSession, err error) {
- var (
- rows *storage.ConsentSessionRows
- )
-
- ctx.Logger.Debugf("Consent Session is being checked for pre-configuration with signature of client id '%s' and subject '%s'", clientID, subject)
-
- if rows, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionsPreConfigured(ctx, clientID, subject); err != nil {
- ctx.Logger.Debugf("Consent Session checked for pre-configuration with signature of client id '%s' and subject '%s' failed with error during load: %+v", clientID, subject, err)
-
- return nil, err
- }
-
- defer func() {
- if err := rows.Close(); err != nil {
- ctx.Logger.Errorf("Consent Session checked for pre-configuration with signature of client id '%s' and subject '%s' failed to close rows with error: %+v", clientID, subject, err)
- }
- }()
-
- for rows.Next() {
- if consent, err = rows.Get(); err != nil {
- ctx.Logger.Debugf("Consent Session checked for pre-configuration with signature of client id '%s' and subject '%s' failed with error during iteration: %+v", clientID, subject, err)
-
- return nil, err
- }
-
- if consent.HasExactGrants(scopes, audience) && consent.CanGrant() {
- break
- }
- }
-
- if consent != nil && consent.HasExactGrants(scopes, audience) && consent.CanGrant() {
- ctx.Logger.Debugf("Consent Session checked for pre-configuration with signature of client id '%s' and subject '%s' found a result with challenge id '%s'", clientID, subject, consent.ChallengeID)
-
- return consent, nil
- }
-
- ctx.Logger.Debugf("Consent Session checked for pre-configuration with signature of client id '%s' and subject '%s' did not find any results", clientID, subject)
-
- return nil, nil
-}
diff --git a/internal/handlers/handler_oidc_authorization_consent_explicit.go b/internal/handlers/handler_oidc_authorization_consent_explicit.go
new file mode 100644
index 000000000..c6aed268c
--- /dev/null
+++ b/internal/handlers/handler_oidc_authorization_consent_explicit.go
@@ -0,0 +1,96 @@
+package handlers
+
+import (
+ "net/http"
+ "net/url"
+
+ "github.com/google/uuid"
+ "github.com/ory/fosite"
+
+ "github.com/authelia/authelia/v4/internal/middlewares"
+ "github.com/authelia/authelia/v4/internal/model"
+ "github.com/authelia/authelia/v4/internal/oidc"
+ "github.com/authelia/authelia/v4/internal/session"
+)
+
+func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+ userSession session.UserSession, subject uuid.UUID,
+ rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
+ var (
+ consentID uuid.UUID
+ err error
+ )
+
+ bytesConsentID := ctx.QueryArgs().PeekBytes(qryArgConsentID)
+
+ switch len(bytesConsentID) {
+ case 0:
+ return handleOIDCAuthorizationConsentGenerate(ctx, issuer, client, userSession, subject, rw, r, requester)
+ default:
+ if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentMalformedChallengeID)
+
+ return nil, true
+ }
+
+ return handleOIDCAuthorizationConsentModeExplicitWithID(ctx, issuer, client, userSession, subject, consentID, rw, r, requester)
+ }
+}
+
+func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+ userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
+ rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
+ var (
+ err error
+ )
+
+ if consentID.ID() == 0 {
+ ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if subject.ID() != consent.Subject.UUID.ID() {
+ ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if !consent.CanGrant() {
+ ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, "explicit")
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotPerform)
+
+ return nil, true
+ }
+
+ if !consent.IsAuthorized() {
+ if consent.Responded() {
+ ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, fosite.ErrAccessDenied)
+
+ return nil, true
+ }
+
+ handleOIDCAuthorizationConsentRedirect(ctx, issuer, consent, client, userSession, rw, r, requester)
+
+ return nil, true
+ }
+
+ return consent, false
+}
diff --git a/internal/handlers/handler_oidc_authorization_consent_implicit.go b/internal/handlers/handler_oidc_authorization_consent_implicit.go
new file mode 100644
index 000000000..b9017f9b8
--- /dev/null
+++ b/internal/handlers/handler_oidc_authorization_consent_implicit.go
@@ -0,0 +1,134 @@
+package handlers
+
+import (
+ "net/http"
+ "net/url"
+
+ "github.com/google/uuid"
+ "github.com/ory/fosite"
+
+ "github.com/authelia/authelia/v4/internal/middlewares"
+ "github.com/authelia/authelia/v4/internal/model"
+ "github.com/authelia/authelia/v4/internal/oidc"
+ "github.com/authelia/authelia/v4/internal/session"
+)
+
+func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+ userSession session.UserSession, subject uuid.UUID,
+ rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
+ var (
+ consentID uuid.UUID
+ err error
+ )
+
+ switch bytesConsentID := ctx.QueryArgs().PeekBytes(qryArgConsentID); len(bytesConsentID) {
+ case 0:
+ return handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester)
+ default:
+ if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentMalformedChallengeID)
+
+ return nil, true
+ }
+
+ return handleOIDCAuthorizationConsentModeImplicitWithID(ctx, issuer, client, userSession, subject, consentID, rw, r, requester)
+ }
+}
+
+func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaCtx, _ *url.URL, client *oidc.Client,
+ userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
+ rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
+ var (
+ err error
+ )
+
+ if consentID.ID() == 0 {
+ ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if subject.ID() != consent.Subject.UUID.ID() {
+ ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if !consent.CanGrant() {
+ ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, "implicit")
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotPerform)
+
+ return nil, true
+ }
+
+ consent.Grant()
+
+ if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
+
+ return nil, true
+ }
+
+ return consent, false
+}
+
+func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.AutheliaCtx, _ *url.URL, client *oidc.Client,
+ _ session.UserSession, subject uuid.UUID,
+ rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
+ var (
+ err error
+ )
+
+ if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.Consent, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotGenerate)
+
+ return nil, true
+ }
+
+ if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
+
+ return nil, true
+ }
+
+ if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consent.ChallengeID); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
+
+ return nil, true
+ }
+
+ consent.Grant()
+
+ if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
+
+ return nil, true
+ }
+
+ return consent, false
+}
diff --git a/internal/handlers/handler_oidc_authorization_consent_pre_configured.go b/internal/handlers/handler_oidc_authorization_consent_pre_configured.go
new file mode 100644
index 000000000..e8dbdbc0d
--- /dev/null
+++ b/internal/handlers/handler_oidc_authorization_consent_pre_configured.go
@@ -0,0 +1,220 @@
+package handlers
+
+import (
+ "database/sql"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/google/uuid"
+ "github.com/ory/fosite"
+
+ "github.com/authelia/authelia/v4/internal/middlewares"
+ "github.com/authelia/authelia/v4/internal/model"
+ "github.com/authelia/authelia/v4/internal/oidc"
+ "github.com/authelia/authelia/v4/internal/session"
+ "github.com/authelia/authelia/v4/internal/storage"
+)
+
+func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+ userSession session.UserSession, subject uuid.UUID,
+ rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
+ var (
+ consentID uuid.UUID
+ err error
+ )
+
+ bytesConsentID := ctx.QueryArgs().PeekBytes(qryArgConsentID)
+
+ switch len(bytesConsentID) {
+ case 0:
+ return handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester)
+ default:
+ if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentMalformedChallengeID)
+
+ return nil, true
+ }
+
+ return handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx, issuer, client, userSession, subject, consentID, rw, r, requester)
+ }
+}
+
+func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+ userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID,
+ rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
+ var (
+ config *model.OAuth2ConsentPreConfig
+ err error
+ )
+
+ if consentID.ID() == 0 {
+ ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if subject.ID() != consent.Subject.UUID.ID() {
+ ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if !consent.CanGrant() {
+ ctx.Logger.Errorf(logFmtErrConsentCantGrantPreConf, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotPerform)
+
+ return nil, true
+ }
+
+ if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.Consent, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if config != nil {
+ consent.Grant()
+
+ consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true}
+
+ if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
+
+ return nil, true
+ }
+
+ return consent, false
+ }
+
+ if !consent.IsAuthorized() {
+ if consent.Responded() {
+ ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, fosite.ErrAccessDenied)
+
+ return nil, true
+ }
+
+ handleOIDCAuthorizationConsentRedirect(ctx, issuer, consent, client, userSession, rw, r, requester)
+
+ return nil, true
+ }
+
+ return consent, false
+}
+
+func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+ userSession session.UserSession, subject uuid.UUID,
+ rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) {
+ var (
+ config *model.OAuth2ConsentPreConfig
+ err error
+ )
+
+ if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.Consent, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotLookup)
+
+ return nil, true
+ }
+
+ if config == nil {
+ return handleOIDCAuthorizationConsentGenerate(ctx, issuer, client, userSession, subject, rw, r, requester)
+ }
+
+ if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.Consent, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotGenerate)
+
+ return nil, true
+ }
+
+ if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
+
+ return nil, true
+ }
+
+ if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consent.ChallengeID); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
+
+ return nil, true
+ }
+
+ consent.Grant()
+
+ consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true}
+
+ if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err)
+
+ ctx.Providers.OpenIDConnect.WriteAuthorizeError(rw, requester, oidc.ErrConsentCouldNotSave)
+
+ return nil, true
+ }
+
+ return consent, false
+}
+
+func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middlewares.AutheliaCtx, client *oidc.Client, subject uuid.UUID, requester fosite.Requester) (config *model.OAuth2ConsentPreConfig, err error) {
+ var (
+ rows *storage.ConsentPreConfigRows
+ )
+
+ ctx.Logger.Debugf(logFmtDbgConsentPreConfTryingLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "))
+
+ if rows, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentPreConfigurations(ctx, client.GetID(), subject); err != nil {
+ return nil, fmt.Errorf("error loading rows: %w", err)
+ }
+
+ defer func() {
+ if err := rows.Close(); err != nil {
+ ctx.Logger.Errorf(logFmtErrConsentPreConfRowsClose, requester.GetID(), client.GetID(), client.Consent, err)
+ }
+ }()
+
+ scopes, audience := getOIDCExpectedScopesAndAudienceFromRequest(requester)
+
+ for rows.Next() {
+ if config, err = rows.Get(); err != nil {
+ return nil, fmt.Errorf("error iterating rows: %w", err)
+ }
+
+ if config.HasExactGrants(scopes, audience) && config.CanConsent() {
+ ctx.Logger.Debugf(logFmtDbgConsentPreConfSuccessfulLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "), config.ID)
+
+ return config, nil
+ }
+ }
+
+ ctx.Logger.Debugf(logFmtDbgConsentPreConfUnsuccessfulLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "))
+
+ return nil, nil
+}
diff --git a/internal/handlers/handler_oidc_consent.go b/internal/handlers/handler_oidc_consent.go
index 5b34ffea8..bc5a81d62 100644
--- a/internal/handlers/handler_oidc_consent.go
+++ b/internal/handlers/handler_oidc_consent.go
@@ -1,11 +1,11 @@
package handlers
import (
+ "database/sql"
"encoding/json"
"fmt"
"net/url"
"path"
- "strings"
"time"
"github.com/google/uuid"
@@ -14,7 +14,6 @@ import (
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/session"
- "github.com/authelia/authelia/v4/internal/utils"
)
// OpenIDConnectConsentGET handles requests to provide consent for OpenID Connect.
@@ -24,22 +23,20 @@ func OpenIDConnectConsentGET(ctx *middlewares.AutheliaCtx) {
err error
)
- if consentID, err = uuid.Parse(string(ctx.RequestCtx.QueryArgs().Peek("consent_id"))); err != nil {
- ctx.Logger.Errorf("Unable to convert '%s' into a UUID: %+v", ctx.RequestCtx.QueryArgs().Peek("consent_id"), err)
+ if consentID, err = uuid.ParseBytes(ctx.RequestCtx.QueryArgs().PeekBytes(qryArgID)); err != nil {
+ ctx.Logger.Errorf("Unable to convert '%s' into a UUID: %+v", ctx.RequestCtx.QueryArgs().PeekBytes(qryArgID), err)
ctx.ReplyForbidden()
return
}
- userSession, consent, client, handled := oidcConsentGetSessionsAndClient(ctx, consentID)
- if handled {
- return
- }
-
- if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
- ctx.Logger.Errorf("Unable to perform consent without sufficient authentication for user '%s' and client id '%s'", userSession.Username, consent.ClientID)
- ctx.ReplyForbidden()
+ var (
+ consent *model.OAuth2ConsentSession
+ client *oidc.Client
+ handled bool
+ )
+ if _, consent, client, handled = oidcConsentGetSessionsAndClient(ctx, consentID); handled {
return
}
@@ -49,8 +46,6 @@ func OpenIDConnectConsentGET(ctx *middlewares.AutheliaCtx) {
}
// OpenIDConnectConsentPOST handles consent responses for OpenID Connect.
-//
-//nolint:gocyclo // TODO: Consider refactoring time permitting.
func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
var (
consentID uuid.UUID
@@ -66,21 +61,20 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
}
if consentID, err = uuid.Parse(bodyJSON.ConsentID); err != nil {
- ctx.Logger.Errorf("Unable to convert '%s' into a UUID: %+v", ctx.RequestCtx.QueryArgs().Peek("consent_id"), err)
+ ctx.Logger.Errorf("Unable to convert '%s' into a UUID: %+v", bodyJSON.ConsentID, err)
ctx.ReplyForbidden()
return
}
- userSession, consent, client, handled := oidcConsentGetSessionsAndClient(ctx, consentID)
- if handled {
- return
- }
-
- if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
- ctx.Logger.Debugf("Insufficient permissions to give consent during POST current level: %d, require 2FA: %d", userSession.AuthenticationLevel, client.Policy)
- ctx.ReplyForbidden()
+ var (
+ userSession session.UserSession
+ consent *model.OAuth2ConsentSession
+ client *oidc.Client
+ handled bool
+ )
+ if userSession, consent, client, handled = oidcConsentGetSessionsAndClient(ctx, consentID); handled {
return
}
@@ -93,32 +87,35 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
}
if bodyJSON.Consent {
- if bodyJSON.PreConfigure {
- if client.PreConfiguredConsentDuration == nil {
- ctx.Logger.Warnf("Consent session with id '%s' for user '%s': consent pre-configuration was requested and was ignored because it is not permitted on this client", consent.ChallengeID, userSession.Username)
- } else {
- expiresAt := time.Now().Add(*client.PreConfiguredConsentDuration)
- consent.ExpiresAt = &expiresAt
+ consent.Grant()
- ctx.Logger.Debugf("Consent session with id '%s' for user '%s': pre-configured and set to expire at %v", consent.ChallengeID, userSession.Username, consent.ExpiresAt)
+ if bodyJSON.PreConfigure {
+ if client.Consent.Mode == oidc.ClientConsentModePreConfigured {
+ config := model.OAuth2ConsentPreConfig{
+ ClientID: consent.ClientID,
+ Subject: consent.Subject.UUID,
+ CreatedAt: time.Now(),
+ ExpiresAt: sql.NullTime{Time: time.Now().Add(client.Consent.Duration), Valid: true},
+ Scopes: consent.GrantedScopes,
+ Audience: consent.GrantedAudience,
+ }
+
+ var id int64
+
+ if id, err = ctx.Providers.StorageProvider.SaveOAuth2ConsentPreConfiguration(ctx, config); err != nil {
+ ctx.Logger.Errorf("Failed to save the consent pre-configuration to the database: %+v", err)
+ ctx.SetJSONError(messageOperationFailed)
+
+ return
+ }
+
+ consent.PreConfiguration = sql.NullInt64{Int64: id, Valid: true}
+
+ ctx.Logger.Debugf("Consent session with id '%s' for user '%s': pre-configured and set to expire at %v", consent.ChallengeID, userSession.Username, config.ExpiresAt.Time)
+ } else {
+ ctx.Logger.Warnf("Consent session with id '%s' for user '%s': consent pre-configuration was requested and was ignored because it is not permitted on this client", consent.ChallengeID, userSession.Username)
}
}
-
- consent.GrantedScopes = consent.RequestedScopes
- consent.GrantedAudience = consent.RequestedAudience
-
- if !utils.IsStringInSlice(consent.ClientID, consent.GrantedAudience) {
- consent.GrantedAudience = append(consent.GrantedAudience, consent.ClientID)
- }
- }
-
- var externalRootURL string
-
- if externalRootURL, err = ctx.ExternalRootURL(); err != nil {
- ctx.Logger.Errorf("Could not determine the external URL during consent session processing with id '%s' for user '%s': %v", consent.ChallengeID, userSession.Username, err)
- ctx.SetJSONError(messageOperationFailed)
-
- return
}
if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, bodyJSON.Consent); err != nil {
@@ -133,17 +130,13 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
query url.Values
)
- if redirectURI, err = url.ParseRequestURI(externalRootURL); err != nil {
+ if redirectURI, err = ctx.IssuerURL(); err != nil {
ctx.Logger.Errorf("Failed to parse the consent redirect URL: %+v", err)
ctx.SetJSONError(messageOperationFailed)
return
}
- if !strings.HasSuffix(redirectURI.Path, "/") {
- redirectURI.Path += "/"
- }
-
if query, err = url.ParseQuery(consent.Form); err != nil {
ctx.Logger.Errorf("Failed to parse the consent form values: %+v", err)
ctx.SetJSONError(messageOperationFailed)
@@ -151,9 +144,9 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
return
}
- query.Set("consent_id", consent.ChallengeID.String())
+ query.Set(queryArgConsentID, consent.ChallengeID.String())
- redirectURI.Path = path.Join(redirectURI.Path, oidc.AuthorizationPath)
+ redirectURI.Path = path.Join(redirectURI.Path, oidc.EndpointPathAuthorization)
redirectURI.RawQuery = query.Encode()
response := oidc.ConsentPostResponseBody{RedirectURI: redirectURI.String()}
@@ -177,7 +170,7 @@ func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uui
return userSession, nil, nil, true
}
- if client, err = ctx.Providers.OpenIDConnect.Store.GetFullClient(consent.ClientID); err != nil {
+ if client, err = ctx.Providers.OpenIDConnect.GetFullClient(consent.ClientID); err != nil {
ctx.Logger.Errorf("Unable to find related client configuration with name '%s': %v", consent.ClientID, err)
ctx.ReplyForbidden()
@@ -192,5 +185,33 @@ func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uui
return userSession, nil, nil, true
}
+ switch client.Consent.Mode {
+ case oidc.ClientConsentModeImplicit:
+ ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the client is using the implicit consent mode", userSession.Username, consent.ClientID)
+ ctx.ReplyForbidden()
+
+ return
+ default:
+ switch {
+ case consent.Responded():
+ ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the client is using the explicit consent mode and this consent session has already been responded to", userSession.Username, consent.ClientID)
+ ctx.ReplyForbidden()
+
+ return userSession, nil, nil, true
+ case !consent.CanGrant():
+ ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the specified consent session cannot be granted", userSession.Username, consent.ClientID)
+ ctx.ReplyForbidden()
+
+ return userSession, nil, nil, true
+ }
+ }
+
+ if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
+ ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the user is not sufficiently authenticated", userSession.Username, consent.ClientID)
+ ctx.ReplyForbidden()
+
+ return userSession, nil, nil, true
+ }
+
return userSession, consent, client, false
}
diff --git a/internal/handlers/handler_oidc_token.go b/internal/handlers/handler_oidc_token.go
index d8efdae23..b7fe94efe 100644
--- a/internal/handlers/handler_oidc_token.go
+++ b/internal/handlers/handler_oidc_token.go
@@ -21,12 +21,12 @@ func OpenIDConnectTokenPOST(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter
oidcSession := oidc.NewSession()
- if requester, err = ctx.Providers.OpenIDConnect.Fosite.NewAccessRequest(ctx, req, oidcSession); err != nil {
+ if requester, err = ctx.Providers.OpenIDConnect.NewAccessRequest(ctx, req, oidcSession); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Access Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription())
- ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, requester, err)
+ ctx.Providers.OpenIDConnect.WriteAccessError(rw, requester, err)
return
}
@@ -46,12 +46,12 @@ func OpenIDConnectTokenPOST(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter
ctx.Logger.Tracef("Access Request with id '%s' on client with id '%s' response is being generated for session with type '%T'", requester.GetID(), client.GetID(), requester.GetSession())
- if responder, err = ctx.Providers.OpenIDConnect.Fosite.NewAccessResponse(ctx, requester); err != nil {
+ if responder, err = ctx.Providers.OpenIDConnect.NewAccessResponse(ctx, requester); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("Access Response for Request with id '%s' failed to be created with error: %s", requester.GetID(), rfc.WithExposeDebug(true).GetDescription())
- ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, requester, err)
+ ctx.Providers.OpenIDConnect.WriteAccessError(rw, requester, err)
return
}
@@ -60,5 +60,5 @@ func OpenIDConnectTokenPOST(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter
ctx.Logger.Tracef("Access Request with id '%s' on client with id '%s' produced the following claims: %+v", requester.GetID(), client.GetID(), responder.ToMap())
- ctx.Providers.OpenIDConnect.Fosite.WriteAccessResponse(rw, requester, responder)
+ ctx.Providers.OpenIDConnect.WriteAccessResponse(rw, requester, responder)
}
diff --git a/internal/handlers/handler_oidc_userinfo.go b/internal/handlers/handler_oidc_userinfo.go
index 0e4c59143..ddb61cb35 100644
--- a/internal/handlers/handler_oidc_userinfo.go
+++ b/internal/handlers/handler_oidc_userinfo.go
@@ -9,6 +9,7 @@ import (
"github.com/ory/fosite"
"github.com/ory/fosite/token/jwt"
"github.com/pkg/errors"
+ "github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model"
@@ -28,14 +29,14 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
oidcSession := oidc.NewSession()
- if tokenType, requester, err = ctx.Providers.OpenIDConnect.Fosite.IntrospectToken(
+ if tokenType, requester, err = ctx.Providers.OpenIDConnect.IntrospectToken(
req.Context(), fosite.AccessTokenFromRequest(req), fosite.AccessToken, oidcSession); err != nil {
rfc := fosite.ErrorToRFC6749Error(err)
ctx.Logger.Errorf("UserInfo Request failed with error: %+v", rfc)
if rfc.StatusCode() == http.StatusUnauthorized {
- rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription()))
+ rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription()))
}
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
@@ -49,27 +50,27 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed with error: bearer authorization failed as the token is not an access_token", requester.GetID(), client.GetID())
errStr := "Only access tokens are allowed in the authorization header."
- rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errStr))
+ rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errStr))
ctx.Providers.OpenIDConnect.WriteErrorCode(rw, req, http.StatusUnauthorized, errors.New(errStr))
return
}
- if client, err = ctx.Providers.OpenIDConnect.Store.GetFullClient(clientID); err != nil {
+ if client, err = ctx.Providers.OpenIDConnect.GetFullClient(clientID); err != nil {
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHint("Unable to assert type of client")))
return
}
claims := requester.GetSession().(*model.OpenIDSession).IDTokenClaims().ToMap()
- delete(claims, "jti")
- delete(claims, "sid")
- delete(claims, "at_hash")
- delete(claims, "c_hash")
- delete(claims, "exp")
- delete(claims, "nonce")
+ delete(claims, oidc.ClaimJWTID)
+ delete(claims, oidc.ClaimSessionID)
+ delete(claims, oidc.ClaimAccessTokenHash)
+ delete(claims, oidc.ClaimCodeHash)
+ delete(claims, oidc.ClaimExpirationTime)
+ delete(claims, oidc.ClaimNonce)
- audience, ok := claims["aud"].([]string)
+ audience, ok := claims[oidc.ClaimAudience].([]string)
if !ok || len(audience) == 0 {
audience = []string{client.GetID()}
@@ -88,16 +89,14 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
}
}
- claims["aud"] = audience
+ claims[oidc.ClaimAudience] = audience
- var (
- keyID, token string
- )
+ var token string
ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims)
switch client.UserinfoSigningAlgorithm {
- case "RS256":
+ case oidc.SigningAlgorithmRSAWithSHA256:
var jti uuid.UUID
if jti, err = uuid.NewRandom(); err != nil {
@@ -106,17 +105,13 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
return
}
- claims["jti"] = jti.String()
- claims["iat"] = time.Now().Unix()
-
- if keyID, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().GetPublicKeyID(req.Context()); err != nil {
- ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHintf("Could not find the active JWK."))
-
- return
- }
+ claims[oidc.ClaimJWTID] = jti.String()
+ claims[oidc.ClaimIssuedAt] = time.Now().Unix()
headers := &jwt.Headers{
- Extra: map[string]interface{}{"kid": keyID},
+ Extra: map[string]any{
+ oidc.JWTHeaderKeyIdentifier: ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
+ },
}
if token, _, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, headers); err != nil {
diff --git a/internal/handlers/handler_oidc_wellknown.go b/internal/handlers/handler_oidc_wellknown.go
index 89d346e02..1ac819817 100644
--- a/internal/handlers/handler_oidc_wellknown.go
+++ b/internal/handlers/handler_oidc_wellknown.go
@@ -1,6 +1,8 @@
package handlers
import (
+ "net/url"
+
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/middlewares"
@@ -13,8 +15,12 @@ import (
//
// https://openid.net/specs/openid-connect-discovery-1_0.html
func OpenIDConnectConfigurationWellKnownGET(ctx *middlewares.AutheliaCtx) {
- issuer, err := ctx.ExternalRootURL()
- if err != nil {
+ var (
+ issuer *url.URL
+ err error
+ )
+
+ if issuer, err = ctx.IssuerURL(); err != nil {
ctx.Logger.Errorf("Error occurred determining OpenID Connect issuer details: %+v", err)
ctx.ReplyStatusCode(fasthttp.StatusBadRequest)
@@ -22,7 +28,7 @@ func OpenIDConnectConfigurationWellKnownGET(ctx *middlewares.AutheliaCtx) {
return
}
- wellKnown := ctx.Providers.OpenIDConnect.GetOpenIDConnectWellKnownConfiguration(issuer)
+ wellKnown := ctx.Providers.OpenIDConnect.GetOpenIDConnectWellKnownConfiguration(issuer.String())
if err = ctx.ReplyJSON(wellKnown, fasthttp.StatusOK); err != nil {
ctx.Logger.Errorf("Error occurred in JSON encode: %+v", err)
@@ -41,8 +47,12 @@ func OpenIDConnectConfigurationWellKnownGET(ctx *middlewares.AutheliaCtx) {
//
// https://datatracker.ietf.org/doc/html/rfc8414
func OAuthAuthorizationServerWellKnownGET(ctx *middlewares.AutheliaCtx) {
- issuer, err := ctx.ExternalRootURL()
- if err != nil {
+ var (
+ issuer *url.URL
+ err error
+ )
+
+ if issuer, err = ctx.IssuerURL(); err != nil {
ctx.Logger.Errorf("Error occurred determining OpenID Connect issuer details: %+v", err)
ctx.ReplyStatusCode(fasthttp.StatusBadRequest)
@@ -50,7 +60,7 @@ func OAuthAuthorizationServerWellKnownGET(ctx *middlewares.AutheliaCtx) {
return
}
- wellKnown := ctx.Providers.OpenIDConnect.GetOAuth2WellKnownConfiguration(issuer)
+ wellKnown := ctx.Providers.OpenIDConnect.GetOAuth2WellKnownConfiguration(issuer.String())
if err = ctx.ReplyJSON(wellKnown, fasthttp.StatusOK); err != nil {
ctx.Logger.Errorf("Error occurred in JSON encode: %+v", err)
diff --git a/internal/handlers/handler_sign_duo.go b/internal/handlers/handler_sign_duo.go
index 704ce4320..0ebe285b0 100644
--- a/internal/handlers/handler_sign_duo.go
+++ b/internal/handlers/handler_sign_duo.go
@@ -16,7 +16,7 @@ import (
func DuoPOST(duoAPI duo.API) middlewares.RequestHandler {
return func(ctx *middlewares.AutheliaCtx) {
var (
- bodyJSON = &signDuoRequestBody{}
+ bodyJSON = &bodySignDuoRequest{}
device, method string
)
@@ -90,7 +90,7 @@ func DuoPOST(duoAPI duo.API) middlewares.RequestHandler {
}
// HandleInitialDeviceSelection handler for retrieving all available devices.
-func HandleInitialDeviceSelection(ctx *middlewares.AutheliaCtx, userSession *session.UserSession, duoAPI duo.API, bodyJSON *signDuoRequestBody) (device string, method string, err error) {
+func HandleInitialDeviceSelection(ctx *middlewares.AutheliaCtx, userSession *session.UserSession, duoAPI duo.API, bodyJSON *bodySignDuoRequest) (device string, method string, err error) {
result, message, devices, enrollURL, err := DuoPreAuth(ctx, duoAPI)
if err != nil {
ctx.Logger.Errorf("Failed to perform Duo PreAuth for user '%s': %+v", userSession.Username, err)
@@ -135,7 +135,7 @@ func HandleInitialDeviceSelection(ctx *middlewares.AutheliaCtx, userSession *ses
}
// HandlePreferredDeviceCheck handler to check if the saved device and method is still valid.
-func HandlePreferredDeviceCheck(ctx *middlewares.AutheliaCtx, userSession *session.UserSession, duoAPI duo.API, device string, method string, bodyJSON *signDuoRequestBody) (string, string, error) {
+func HandlePreferredDeviceCheck(ctx *middlewares.AutheliaCtx, userSession *session.UserSession, duoAPI duo.API, device string, method string, bodyJSON *bodySignDuoRequest) (string, string, error) {
result, message, devices, enrollURL, err := DuoPreAuth(ctx, duoAPI)
if err != nil {
ctx.Logger.Errorf("Failed to perform Duo PreAuth for user '%s': %+v", userSession.Username, err)
@@ -243,7 +243,7 @@ func HandleAutoSelection(ctx *middlewares.AutheliaCtx, devices []DuoDevice, user
}
// HandleAllow handler for successful logins.
-func HandleAllow(ctx *middlewares.AutheliaCtx, bodyJSON *signDuoRequestBody) {
+func HandleAllow(ctx *middlewares.AutheliaCtx, bodyJSON *bodySignDuoRequest) {
userSession := ctx.GetSession()
err := ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
@@ -267,7 +267,7 @@ func HandleAllow(ctx *middlewares.AutheliaCtx, bodyJSON *signDuoRequestBody) {
}
if bodyJSON.Workflow == workflowOpenIDConnect {
- handleOIDCWorkflowResponse(ctx, bodyJSON.TargetURL)
+ handleOIDCWorkflowResponse(ctx, bodyJSON.TargetURL, bodyJSON.WorkflowID)
} else {
Handle2FAResponse(ctx, bodyJSON.TargetURL)
}
diff --git a/internal/handlers/handler_sign_duo_test.go b/internal/handlers/handler_sign_duo_test.go
index 201bb521f..c08c8f1ee 100644
--- a/internal/handlers/handler_sign_duo_test.go
+++ b/internal/handlers/handler_sign_duo_test.go
@@ -54,7 +54,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldEnroll() {
duoMock.EXPECT().PreAuthCall(s.mock.Ctx, gomock.Eq(values)).Return(&preAuthResponse, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -113,7 +113,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldAutoSelect() {
duoMock.EXPECT().AuthCall(s.mock.Ctx, gomock.Eq(values)).Return(&authResponse, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{TargetURL: "https://target.example.com"})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{TargetURL: "https://target.example.com"})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -142,7 +142,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldDenyAutoSelect() {
values.Set("factor", "push")
values.Set("device", "12345ABCDEFGHIJ67890")
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -162,7 +162,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldFailAutoSelect() {
duoMock.EXPECT().PreAuthCall(s.mock.Ctx, gomock.Any()).Return(nil, fmt.Errorf("Connnection error"))
- bodyBytes, err := json.Marshal(signDuoRequestBody{TargetURL: "https://target.example.com"})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{TargetURL: "https://target.example.com"})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -191,7 +191,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldDeleteOldDeviceAndEnroll() {
s.mock.StorageMock.EXPECT().DeletePreferredDuoDevice(s.mock.Ctx, "john").Return(nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -225,7 +225,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldDeleteOldDeviceAndCallPreauthAPIWit
s.mock.StorageMock.EXPECT().DeletePreferredDuoDevice(s.mock.Ctx, "john").Return(nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -263,7 +263,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldUseOldDeviceAndSelect() {
duoMock.EXPECT().PreAuthCall(s.mock.Ctx, gomock.Eq(values)).Return(&preAuthResponse, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -319,7 +319,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldUseInvalidMethodAndAutoSelect() {
duoMock.EXPECT().AuthCall(s.mock.Ctx, gomock.Eq(values)).Return(&authResponse, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{TargetURL: "https://target.example.com"})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{TargetURL: "https://target.example.com"})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -342,7 +342,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldCallDuoPreauthAPIAndAllowAccess() {
duoMock.EXPECT().PreAuthCall(s.mock.Ctx, gomock.Eq(values)).Return(&preAuthResponse, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{TargetURL: "https://target.example.com"})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{TargetURL: "https://target.example.com"})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -372,7 +372,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldCallDuoPreauthAPIAndDenyAccess() {
values.Set("factor", "push")
values.Set("device", "12345ABCDEFGHIJ67890")
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -390,7 +390,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldCallDuoPreauthAPIAndFail() {
duoMock.EXPECT().PreAuthCall(s.mock.Ctx, gomock.Any()).Return(nil, fmt.Errorf("Connnection error"))
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -442,7 +442,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldCallDuoAPIAndDenyAccess() {
duoMock.EXPECT().AuthCall(s.mock.Ctx, gomock.Eq(values)).Return(&response, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -473,7 +473,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldCallDuoAPIAndFail() {
duoMock.EXPECT().AuthCall(s.mock.Ctx, gomock.Any()).Return(nil, fmt.Errorf("Connnection error"))
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -521,7 +521,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToDefaultURL() {
s.mock.Ctx.Configuration.DefaultRedirectionURL = testRedirectionURL
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -568,7 +568,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldNotReturnRedirectURL() {
duoMock.EXPECT().AuthCall(s.mock.Ctx, gomock.Any()).Return(&response, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{})
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{})
s.Require().NoError(err)
s.mock.Ctx.Request.SetBody(bodyBytes)
@@ -613,7 +613,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToSafeTargetURL() {
duoMock.EXPECT().AuthCall(s.mock.Ctx, gomock.Any()).Return(&response, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{
TargetURL: "https://example.com",
})
s.Require().NoError(err)
@@ -662,7 +662,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldNotRedirectToUnsafeURL() {
duoMock.EXPECT().AuthCall(s.mock.Ctx, gomock.Any()).Return(&response, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{
TargetURL: "http://example.com",
})
s.Require().NoError(err)
@@ -709,7 +709,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldRegenerateSessionForPreventingSessi
duoMock.EXPECT().AuthCall(s.mock.Ctx, gomock.Any()).Return(&response, nil)
- bodyBytes, err := json.Marshal(signDuoRequestBody{
+ bodyBytes, err := json.Marshal(bodySignDuoRequest{
TargetURL: "http://example.com",
})
s.Require().NoError(err)
diff --git a/internal/handlers/handler_sign_totp.go b/internal/handlers/handler_sign_totp.go
index 98e04561e..3d584fddd 100644
--- a/internal/handlers/handler_sign_totp.go
+++ b/internal/handlers/handler_sign_totp.go
@@ -7,7 +7,7 @@ import (
// TimeBasedOneTimePasswordPOST validate the TOTP passcode provided by the user.
func TimeBasedOneTimePasswordPOST(ctx *middlewares.AutheliaCtx) {
- bodyJSON := signTOTPRequestBody{}
+ bodyJSON := bodySignTOTPRequest{}
if err := ctx.ParseBody(&bodyJSON); err != nil {
ctx.Logger.Errorf(logFmtErrParseRequestBody, regulation.AuthTypeTOTP, err)
@@ -79,7 +79,7 @@ func TimeBasedOneTimePasswordPOST(ctx *middlewares.AutheliaCtx) {
}
if bodyJSON.Workflow == workflowOpenIDConnect {
- handleOIDCWorkflowResponse(ctx, bodyJSON.TargetURL)
+ handleOIDCWorkflowResponse(ctx, bodyJSON.TargetURL, bodyJSON.WorkflowID)
} else {
Handle2FAResponse(ctx, bodyJSON.TargetURL)
}
diff --git a/internal/handlers/handler_sign_totp_test.go b/internal/handlers/handler_sign_totp_test.go
index e5e38e9f3..6ade2e0dc 100644
--- a/internal/handlers/handler_sign_totp_test.go
+++ b/internal/handlers/handler_sign_totp_test.go
@@ -59,7 +59,7 @@ func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToDefaultURL() {
s.mock.Ctx.Configuration.DefaultRedirectionURL = testRedirectionURL
- bodyBytes, err := json.Marshal(signTOTPRequestBody{
+ bodyBytes, err := json.Marshal(bodySignTOTPRequest{
Token: "abc",
})
s.Require().NoError(err)
@@ -97,7 +97,7 @@ func (s *HandlerSignTOTPSuite) TestShouldFailWhenTOTPSignInInfoFailsToUpdate() {
s.mock.Ctx.Configuration.DefaultRedirectionURL = testRedirectionURL
- bodyBytes, err := json.Marshal(signTOTPRequestBody{
+ bodyBytes, err := json.Marshal(bodySignTOTPRequest{
Token: "abc",
})
s.Require().NoError(err)
@@ -131,7 +131,7 @@ func (s *HandlerSignTOTPSuite) TestShouldNotReturnRedirectURL() {
EXPECT().
UpdateTOTPConfigurationSignIn(s.mock.Ctx, gomock.Any(), gomock.Any())
- bodyBytes, err := json.Marshal(signTOTPRequestBody{
+ bodyBytes, err := json.Marshal(bodySignTOTPRequest{
Token: "abc",
})
s.Require().NoError(err)
@@ -165,9 +165,9 @@ func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToSafeTargetURL() {
EXPECT().
UpdateTOTPConfigurationSignIn(s.mock.Ctx, gomock.Any(), gomock.Any())
- bodyBytes, err := json.Marshal(signTOTPRequestBody{
+ bodyBytes, err := json.Marshal(bodySignTOTPRequest{
Token: "abc",
- TargetURL: "https://example.com",
+ TargetURL: "https://mydomain.example.com",
})
s.Require().NoError(err)
@@ -175,7 +175,7 @@ func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToSafeTargetURL() {
TimeBasedOneTimePasswordPOST(s.mock.Ctx)
s.mock.Assert200OK(s.T(), redirectResponse{
- Redirect: "https://example.com",
+ Redirect: "https://mydomain.example.com",
})
}
@@ -203,9 +203,9 @@ func (s *HandlerSignTOTPSuite) TestShouldNotRedirectToUnsafeURL() {
Validate(gomock.Eq("abc"), gomock.Eq(&model.TOTPConfiguration{Secret: []byte("secret")})).
Return(true, nil)
- bodyBytes, err := json.Marshal(signTOTPRequestBody{
+ bodyBytes, err := json.Marshal(bodySignTOTPRequest{
Token: "abc",
- TargetURL: "http://example.com",
+ TargetURL: "http://mydomain.example.com",
})
s.Require().NoError(err)
@@ -241,7 +241,7 @@ func (s *HandlerSignTOTPSuite) TestShouldRegenerateSessionForPreventingSessionFi
EXPECT().
UpdateTOTPConfigurationSignIn(s.mock.Ctx, gomock.Any(), gomock.Any())
- bodyBytes, err := json.Marshal(signTOTPRequestBody{
+ bodyBytes, err := json.Marshal(bodySignTOTPRequest{
Token: "abc",
})
s.Require().NoError(err)
diff --git a/internal/handlers/handler_sign_webauthn.go b/internal/handlers/handler_sign_webauthn.go
index d4007e4e2..89a980626 100644
--- a/internal/handlers/handler_sign_webauthn.go
+++ b/internal/handlers/handler_sign_webauthn.go
@@ -41,7 +41,7 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
webauthn.WithAllowedCredentials(user.WebAuthnCredentialDescriptors()),
}
- extensions := make(map[string]interface{})
+ extensions := map[string]any{}
if user.HasFIDOU2F() {
extensions["appid"] = w.Config.RPOrigin
@@ -84,7 +84,7 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
err error
w *webauthn.WebAuthn
- bodyJSON signWebauthnRequestBody
+ bodyJSON bodySignWebauthnRequest
)
if err = ctx.ParseBody(&bodyJSON); err != nil {
@@ -198,7 +198,7 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
}
if bodyJSON.Workflow == workflowOpenIDConnect {
- handleOIDCWorkflowResponse(ctx, bodyJSON.TargetURL)
+ handleOIDCWorkflowResponse(ctx, bodyJSON.TargetURL, bodyJSON.WorkflowID)
} else {
Handle2FAResponse(ctx, bodyJSON.TargetURL)
}
diff --git a/internal/handlers/handler_user_info.go b/internal/handlers/handler_user_info.go
index 28489b6e7..6e4f5dc0d 100644
--- a/internal/handlers/handler_user_info.go
+++ b/internal/handlers/handler_user_info.go
@@ -74,7 +74,7 @@ func UserInfoGET(ctx *middlewares.AutheliaCtx) {
// MethodPreferencePOST update the user preferences regarding 2FA method.
func MethodPreferencePOST(ctx *middlewares.AutheliaCtx) {
- bodyJSON := preferred2FAMethodBody{}
+ bodyJSON := bodyPreferred2FAMethod{}
err := ctx.ParseBody(&bodyJSON)
if err != nil {
diff --git a/internal/handlers/handler_verify.go b/internal/handlers/handler_verify.go
index a78d699ca..258aec61d 100644
--- a/internal/handlers/handler_verify.go
+++ b/internal/handlers/handler_verify.go
@@ -143,7 +143,7 @@ func isSessionInactiveTooLong(ctx *middlewares.AutheliaCtx, userSession *session
func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userSession *session.UserSession, refreshProfile bool,
refreshProfileInterval time.Duration) (username, name string, groups, emails []string, authLevel authentication.Level, err error) {
// No username in the session means the user is anonymous.
- isUserAnonymous := userSession.Username == ""
+ isUserAnonymous := userSession.IsAnonymous()
if isUserAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated {
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("an anonymous user cannot be authenticated (this might be the sign of a security compromise)")
@@ -180,7 +180,6 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS
func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, isBasicAuth bool, username string, method []byte) {
var (
statusCode int
- redirectionURL string
friendlyUsername string
friendlyRequestMethod string
)
@@ -200,10 +199,6 @@ func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, is
return
}
- // Kubernetes ingress controller and Traefik use the rd parameter of the verify
- // endpoint to provide the URL of the login portal. The target URL of the user
- // is computed from X-Forwarded-* headers or X-Original-URL.
- rd := string(ctx.QueryArgs().Peek("rd"))
rm := string(method)
switch rm {
@@ -213,17 +208,30 @@ func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, is
friendlyRequestMethod = rm
}
- if rd != "" {
- switch rm {
- case "":
- redirectionURL = fmt.Sprintf("%s?rd=%s", rd, url.QueryEscape(targetURL.String()))
- default:
- redirectionURL = fmt.Sprintf("%s?rd=%s&rm=%s", rd, url.QueryEscape(targetURL.String()), rm)
+ redirectionURL := ctxGetPortalURL(ctx)
+
+ if redirectionURL != nil {
+ if !utils.IsURISafeRedirection(redirectionURL, ctx.Configuration.Session.Domain) {
+ ctx.Logger.Errorf("Configured Portal URL '%s' does not appear to be able to write cookies for the '%s' domain", redirectionURL, ctx.Configuration.Session.Domain)
+
+ ctx.ReplyUnauthorized()
+
+ return
}
+
+ qry := redirectionURL.Query()
+
+ qry.Set(queryArgRD, targetURL.String())
+
+ if rm != "" {
+ qry.Set("rm", rm)
+ }
+
+ redirectionURL.RawQuery = qry.Encode()
}
switch {
- case ctx.IsXHR() || !ctx.AcceptsMIME("text/html") || rd == "":
+ case ctx.IsXHR() || !ctx.AcceptsMIME("text/html") || redirectionURL == nil:
statusCode = fasthttp.StatusUnauthorized
default:
switch rm {
@@ -234,9 +242,9 @@ func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, is
}
}
- if redirectionURL != "" {
+ if redirectionURL != nil {
ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s", targetURL.String(), friendlyRequestMethod, friendlyUsername, statusCode, redirectionURL)
- ctx.SpecialRedirect(redirectionURL, statusCode)
+ ctx.SpecialRedirect(redirectionURL.String(), statusCode)
} else {
ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d", targetURL.String(), friendlyRequestMethod, friendlyUsername, statusCode)
ctx.ReplyUnauthorized()
@@ -314,7 +322,7 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur
// See https://www.authelia.com/o/threatmodel#potential-future-guarantees
ctx.Logger.Tracef("Checking if we need check the authentication backend for an updated profile for %s.", userSession.Username)
- if !refreshProfile || userSession.Username == "" || targetURL == nil {
+ if !refreshProfile || userSession.IsAnonymous() || targetURL == nil {
return nil
}
@@ -365,7 +373,7 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur
return nil
}
-func getProfileRefreshSettings(cfg schema.AuthenticationBackendConfiguration) (refresh bool, refreshInterval time.Duration) {
+func getProfileRefreshSettings(cfg schema.AuthenticationBackend) (refresh bool, refreshInterval time.Duration) {
if cfg.LDAP != nil {
if cfg.RefreshInterval == schema.ProfileRefreshDisabled {
refresh = false
@@ -425,7 +433,7 @@ func verifyAuth(ctx *middlewares.AutheliaCtx, targetURL *url.URL, refreshProfile
}
// VerifyGET returns the handler verifying if a request is allowed to go through.
-func VerifyGET(cfg schema.AuthenticationBackendConfiguration) middlewares.RequestHandler {
+func VerifyGET(cfg schema.AuthenticationBackend) middlewares.RequestHandler {
refreshProfile, refreshProfileInterval := getProfileRefreshSettings(cfg)
return func(ctx *middlewares.AutheliaCtx) {
diff --git a/internal/handlers/handler_verify_test.go b/internal/handlers/handler_verify_test.go
index 6ae6e2932..a0ef56858 100644
--- a/internal/handlers/handler_verify_test.go
+++ b/internal/handlers/handler_verify_test.go
@@ -22,9 +22,9 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
-var verifyGetCfg = schema.AuthenticationBackendConfiguration{
+var verifyGetCfg = schema.AuthenticationBackend{
RefreshInterval: schema.RefreshIntervalDefault,
- LDAP: &schema.LDAPAuthenticationBackendConfiguration{},
+ LDAP: &schema.LDAPAuthenticationBackend{},
}
func TestShouldRaiseWhenTargetUrlIsMalformed(t *testing.T) {
@@ -429,6 +429,37 @@ func TestShouldVerifyWrongCredentialsInBasicAuth(t *testing.T) {
"https://test.example.com", actualStatus, expStatus)
}
+func TestShouldRedirectWithGroups(t *testing.T) {
+ mock := mocks.NewMockAutheliaCtx(t)
+ defer mock.Close()
+
+ mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&schema.Configuration{
+ AccessControl: schema.AccessControlConfiguration{
+ DefaultPolicy: "deny",
+ Rules: []schema.ACLRule{
+ {
+ Domains: []string{"app.example.com"},
+ Policy: "one_factor",
+ Resources: []regexp.Regexp{
+ *regexp.MustCompile(`^/code-(?P\w+)([/?].*)?$`),
+ },
+ },
+ },
+ },
+ })
+
+ mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
+ mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https")
+ mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "app.example.com")
+ mock.Ctx.Request.Header.Set("X-Forwarded-Uri", "/code-test/login")
+
+ mock.Ctx.Request.SetRequestURI("/api/verify/?rd=https://auth.example.com")
+
+ VerifyGET(verifyGetCfg)(mock.Ctx)
+
+ assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode())
+}
+
func TestShouldVerifyFailingPasswordCheckingInBasicAuth(t *testing.T) {
mock := mocks.NewMockAutheliaCtx(t)
defer mock.Close()
@@ -506,6 +537,110 @@ func (p Pair) String() string {
p.URL, p.Username, p.AuthenticationLevel, p.ExpectedStatusCode)
}
+//nolint:gocyclo // This is a test.
+func TestShouldRedirectAuthorizations(t *testing.T) {
+ testCases := []struct {
+ name string
+
+ method, originalURL, autheliaURL string
+
+ expected int
+ }{
+ {"ShouldReturnFoundMethodNone", "", "https://one-factor.example.com/", "https://auth.example.com/", fasthttp.StatusFound},
+ {"ShouldReturnFoundMethodGET", "GET", "https://one-factor.example.com/", "https://auth.example.com/", fasthttp.StatusFound},
+ {"ShouldReturnFoundMethodOPTIONS", "OPTIONS", "https://one-factor.example.com/", "https://auth.example.com/", fasthttp.StatusFound},
+ {"ShouldReturnSeeOtherMethodPOST", "POST", "https://one-factor.example.com/", "https://auth.example.com/", fasthttp.StatusSeeOther},
+ {"ShouldReturnSeeOtherMethodPATCH", "PATCH", "https://one-factor.example.com/", "https://auth.example.com/", fasthttp.StatusSeeOther},
+ {"ShouldReturnSeeOtherMethodPUT", "PUT", "https://one-factor.example.com/", "https://auth.example.com/", fasthttp.StatusSeeOther},
+ {"ShouldReturnSeeOtherMethodDELETE", "DELETE", "https://one-factor.example.com/", "https://auth.example.com/", fasthttp.StatusSeeOther},
+ {"ShouldReturnUnauthorizedBadDomain", "GET", "https://one-factor.example.com/", "https://auth.notexample.com/", fasthttp.StatusUnauthorized},
+ }
+
+ handler := VerifyGET(verifyGetCfg)
+
+ for _, tc := range testCases {
+ var (
+ suffix string
+ xhr bool
+ )
+
+ for i := 0; i < 2; i++ {
+ switch i {
+ case 0:
+ suffix += "QueryParameter"
+ default:
+ suffix += "RequestHeader"
+ }
+
+ for j := 0; j < 2; j++ {
+ switch j {
+ case 0:
+ xhr = false
+ case 1:
+ xhr = true
+ suffix += "XHR"
+ }
+
+ t.Run(tc.name+suffix, func(t *testing.T) {
+ mock := mocks.NewMockAutheliaCtx(t)
+ defer mock.Close()
+
+ mock.Clock.Set(time.Now())
+
+ autheliaURL, err := url.ParseRequestURI(tc.autheliaURL)
+
+ require.NoError(t, err)
+
+ originalURL, err := url.ParseRequestURI(tc.originalURL)
+
+ require.NoError(t, err)
+
+ if xhr {
+ mock.Ctx.Request.Header.Set(fasthttp.HeaderXRequestedWith, "XMLHttpRequest")
+ }
+
+ var rm string
+
+ if tc.method != "" {
+ rm = fmt.Sprintf("&rm=%s", tc.method)
+ mock.Ctx.Request.Header.Set("X-Forwarded-Method", tc.method)
+ }
+
+ mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
+ mock.Ctx.Request.Header.Set("X-Original-URL", originalURL.String())
+
+ if i == 0 {
+ mock.Ctx.Request.SetRequestURI(fmt.Sprintf("/?rd=%s", url.QueryEscape(autheliaURL.String())))
+ } else {
+ mock.Ctx.Request.Header.Set("X-Authelia-URL", autheliaURL.String())
+ }
+
+ handler(mock.Ctx)
+
+ if xhr && tc.expected != fasthttp.StatusUnauthorized {
+ assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode())
+ } else {
+ assert.Equal(t, tc.expected, mock.Ctx.Response.StatusCode())
+ }
+
+ switch {
+ case xhr && tc.expected != fasthttp.StatusUnauthorized:
+ href := utils.StringHTMLEscape(fmt.Sprintf("%s?rd=%s%s", autheliaURL.String(), url.QueryEscape(originalURL.String()), rm))
+ assert.Equal(t, fmt.Sprintf("%d %s ", href, fasthttp.StatusUnauthorized, fasthttp.StatusMessage(fasthttp.StatusUnauthorized)), string(mock.Ctx.Response.Body()))
+ case tc.expected >= fasthttp.StatusMultipleChoices && tc.expected < fasthttp.StatusBadRequest:
+ href := utils.StringHTMLEscape(fmt.Sprintf("%s?rd=%s%s", autheliaURL.String(), url.QueryEscape(originalURL.String()), rm))
+ assert.Equal(t, fmt.Sprintf("%d %s ", href, tc.expected, fasthttp.StatusMessage(tc.expected)), string(mock.Ctx.Response.Body()))
+ case tc.expected < fasthttp.StatusMultipleChoices:
+ assert.Equal(t, utils.StringHTMLEscape(fmt.Sprintf("%d %s", tc.expected, fasthttp.StatusMessage(tc.expected))), string(mock.Ctx.Response.Body()))
+ default:
+ assert.Equal(t, utils.StringHTMLEscape(fmt.Sprintf("%d %s", tc.expected, fasthttp.StatusMessage(tc.expected))), string(mock.Ctx.Response.Body()))
+ }
+ })
+ }
+ }
+ }
+}
+
func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
testCases := []Pair{
// should apply default policy.
@@ -716,7 +851,7 @@ func TestShouldRedirectWhenSessionInactiveForTooLongAndRDParamProvided(t *testin
err := mock.Ctx.SaveSession(userSession)
require.NoError(t, err)
- mock.Ctx.QueryArgs().Add("rd", "https://login.example.com")
+ mock.Ctx.QueryArgs().Add(queryArgRD, "https://login.example.com")
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
mock.Ctx.Request.Header.Set("X-Forwarded-Method", "GET")
mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
@@ -735,7 +870,7 @@ func TestShouldRedirectWithCorrectStatusCodeBasedOnRequestMethod(t *testing.T) {
mock := mocks.NewMockAutheliaCtx(t)
defer mock.Close()
- mock.Ctx.QueryArgs().Add("rd", "https://login.example.com")
+ mock.Ctx.QueryArgs().Add(queryArgRD, "https://login.example.com")
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
mock.Ctx.Request.Header.Set("X-Forwarded-Method", "GET")
mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
@@ -746,7 +881,7 @@ func TestShouldRedirectWithCorrectStatusCodeBasedOnRequestMethod(t *testing.T) {
string(mock.Ctx.Response.Body()))
assert.Equal(t, 302, mock.Ctx.Response.StatusCode())
- mock.Ctx.QueryArgs().Add("rd", "https://login.example.com")
+ mock.Ctx.QueryArgs().Add(queryArgRD, "https://login.example.com")
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
mock.Ctx.Request.Header.Set("X-Forwarded-Method", "POST")
mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
@@ -806,11 +941,36 @@ func TestShouldURLEncodeRedirectionURLParameter(t *testing.T) {
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
mock.Ctx.Request.SetHost("mydomain.com")
- mock.Ctx.Request.SetRequestURI("/?rd=https://auth.mydomain.com")
+ mock.Ctx.Request.SetRequestURI("/?rd=https://auth.example.com")
VerifyGET(verifyGetCfg)(mock.Ctx)
- assert.Equal(t, "302 Found ",
+ assert.Equal(t, "302 Found ",
+ string(mock.Ctx.Response.Body()))
+}
+
+func TestShouldURLEncodeRedirectionHeader(t *testing.T) {
+ mock := mocks.NewMockAutheliaCtx(t)
+ defer mock.Close()
+
+ mock.Clock.Set(time.Now())
+
+ userSession := mock.Ctx.GetSession()
+ userSession.Username = testUsername
+ userSession.AuthenticationLevel = authentication.NotAuthenticated
+ userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
+
+ err := mock.Ctx.SaveSession(userSession)
+ require.NoError(t, err)
+
+ mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
+ mock.Ctx.Request.Header.Set("X-Authelia-URL", "https://auth.example.com")
+ mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
+ mock.Ctx.Request.SetHost("mydomain.com")
+
+ VerifyGET(verifyGetCfg)(mock.Ctx)
+
+ assert.Equal(t, "302 Found ",
string(mock.Ctx.Response.Body()))
}
@@ -1289,7 +1449,7 @@ func TestShouldNotRedirectRequestsForBypassACLWhenInactiveForTooLong(t *testing.
require.NoError(t, err)
// Should respond 200 OK.
- mock.Ctx.QueryArgs().Add("rd", "https://login.example.com")
+ mock.Ctx.QueryArgs().Add(queryArgRD, "https://login.example.com")
mock.Ctx.Request.Header.Set("X-Forwarded-Method", "GET")
mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
mock.Ctx.Request.Header.Set("X-Original-URL", "https://bypass.example.com")
@@ -1298,7 +1458,7 @@ func TestShouldNotRedirectRequestsForBypassACLWhenInactiveForTooLong(t *testing.
assert.Nil(t, mock.Ctx.Response.Header.Peek("Location"))
// Should respond 302 Found.
- mock.Ctx.QueryArgs().Add("rd", "https://login.example.com")
+ mock.Ctx.QueryArgs().Add(queryArgRD, "https://login.example.com")
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
mock.Ctx.Request.Header.Set("X-Forwarded-Method", "GET")
mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
@@ -1307,7 +1467,7 @@ func TestShouldNotRedirectRequestsForBypassACLWhenInactiveForTooLong(t *testing.
assert.Equal(t, "https://login.example.com/?rd=https%3A%2F%2Ftwo-factor.example.com&rm=GET", string(mock.Ctx.Response.Header.Peek("Location")))
// Should respond 401 Unauthorized.
- mock.Ctx.QueryArgs().Del("rd")
+ mock.Ctx.QueryArgs().Del(queryArgRD)
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
mock.Ctx.Request.Header.Set("X-Forwarded-Method", "GET")
mock.Ctx.Request.Header.Set("Accept", "text/html; charset=utf-8")
diff --git a/internal/handlers/oidc.go b/internal/handlers/oidc.go
index 20c51d2c2..9febd5fe5 100644
--- a/internal/handlers/oidc.go
+++ b/internal/handlers/oidc.go
@@ -8,8 +8,8 @@ import (
"github.com/authelia/authelia/v4/internal/session"
)
-func oidcGrantRequests(ar fosite.AuthorizeRequester, consent *model.OAuth2ConsentSession, userSession *session.UserSession) (extraClaims map[string]interface{}) {
- extraClaims = map[string]interface{}{}
+func oidcGrantRequests(ar fosite.AuthorizeRequester, consent *model.OAuth2ConsentSession, userSession *session.UserSession) (extraClaims map[string]any) {
+ extraClaims = map[string]any{}
for _, scope := range consent.GrantedScopes {
if ar != nil {
@@ -21,13 +21,14 @@ func oidcGrantRequests(ar fosite.AuthorizeRequester, consent *model.OAuth2Consen
extraClaims[oidc.ClaimGroups] = userSession.Groups
case oidc.ScopeProfile:
extraClaims[oidc.ClaimPreferredUsername] = userSession.Username
- extraClaims[oidc.ClaimDisplayName] = userSession.DisplayName
+ extraClaims[oidc.ClaimFullName] = userSession.DisplayName
case oidc.ScopeEmail:
if len(userSession.Emails) != 0 {
- extraClaims[oidc.ClaimEmail] = userSession.Emails[0]
+ extraClaims[oidc.ClaimPreferredEmail] = userSession.Emails[0]
if len(userSession.Emails) > 1 {
extraClaims[oidc.ClaimEmailAlts] = userSession.Emails[1:]
}
+
// TODO (james-d-elliott): actually verify emails and record that information.
extraClaims[oidc.ClaimEmailVerified] = true
}
diff --git a/internal/handlers/oidc_test.go b/internal/handlers/oidc_test.go
index 95ff6e34c..648fdefa2 100644
--- a/internal/handlers/oidc_test.go
+++ b/internal/handlers/oidc_test.go
@@ -23,8 +23,8 @@ func TestShouldGrantAppropriateClaimsForScopeProfile(t *testing.T) {
require.Contains(t, extraClaims, oidc.ClaimPreferredUsername)
assert.Equal(t, "john", extraClaims[oidc.ClaimPreferredUsername])
- require.Contains(t, extraClaims, oidc.ClaimDisplayName)
- assert.Equal(t, "John Smith", extraClaims[oidc.ClaimDisplayName])
+ require.Contains(t, extraClaims, oidc.ClaimFullName)
+ assert.Equal(t, "John Smith", extraClaims[oidc.ClaimFullName])
}
func TestShouldGrantAppropriateClaimsForScopeGroups(t *testing.T) {
@@ -59,8 +59,8 @@ func TestShouldGrantAppropriateClaimsForScopeEmail(t *testing.T) {
assert.Len(t, extraClaims, 3)
- require.Contains(t, extraClaims, oidc.ClaimEmail)
- assert.Equal(t, "j.smith@authelia.com", extraClaims[oidc.ClaimEmail])
+ require.Contains(t, extraClaims, oidc.ClaimPreferredEmail)
+ assert.Equal(t, "j.smith@authelia.com", extraClaims[oidc.ClaimPreferredEmail])
require.Contains(t, extraClaims, oidc.ClaimEmailAlts)
assert.Len(t, extraClaims[oidc.ClaimEmailAlts], 1)
@@ -73,8 +73,8 @@ func TestShouldGrantAppropriateClaimsForScopeEmail(t *testing.T) {
assert.Len(t, extraClaims, 2)
- require.Contains(t, extraClaims, oidc.ClaimEmail)
- assert.Equal(t, "f.smith@authelia.com", extraClaims[oidc.ClaimEmail])
+ require.Contains(t, extraClaims, oidc.ClaimPreferredEmail)
+ assert.Equal(t, "f.smith@authelia.com", extraClaims[oidc.ClaimPreferredEmail])
require.Contains(t, extraClaims, oidc.ClaimEmailVerified)
assert.Equal(t, true, extraClaims[oidc.ClaimEmailVerified])
@@ -92,8 +92,8 @@ func TestShouldGrantAppropriateClaimsForScopeOpenIDAndProfile(t *testing.T) {
require.Contains(t, extraClaims, oidc.ClaimPreferredUsername)
assert.Equal(t, "john", extraClaims[oidc.ClaimPreferredUsername])
- require.Contains(t, extraClaims, oidc.ClaimDisplayName)
- assert.Equal(t, "John Smith", extraClaims[oidc.ClaimDisplayName])
+ require.Contains(t, extraClaims, oidc.ClaimFullName)
+ assert.Equal(t, "John Smith", extraClaims[oidc.ClaimFullName])
extraClaims = oidcGrantRequests(nil, consent, &oidcUserSessionFred)
@@ -102,8 +102,8 @@ func TestShouldGrantAppropriateClaimsForScopeOpenIDAndProfile(t *testing.T) {
require.Contains(t, extraClaims, oidc.ClaimPreferredUsername)
assert.Equal(t, "fred", extraClaims[oidc.ClaimPreferredUsername])
- require.Contains(t, extraClaims, oidc.ClaimDisplayName)
- assert.Equal(t, extraClaims[oidc.ClaimDisplayName], "Fred Smith")
+ require.Contains(t, extraClaims, oidc.ClaimFullName)
+ assert.Equal(t, extraClaims[oidc.ClaimFullName], "Fred Smith")
}
var (
diff --git a/internal/handlers/response.go b/internal/handlers/response.go
index 83f5de59d..33599ea8e 100644
--- a/internal/handlers/response.go
+++ b/internal/handlers/response.go
@@ -3,66 +3,19 @@ package handlers
import (
"fmt"
"net/url"
+ "path"
"time"
+ "github.com/google/uuid"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/middlewares"
+ "github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/utils"
)
-// handleOIDCWorkflowResponse handle the redirection upon authentication in the OIDC workflow.
-func handleOIDCWorkflowResponse(ctx *middlewares.AutheliaCtx, targetURI string) {
- if len(targetURI) == 0 {
- ctx.Error(fmt.Errorf("unable to parse target URL %s: empty value", targetURI), messageAuthenticationFailed)
-
- return
- }
-
- var (
- targetURL *url.URL
- err error
- )
-
- if targetURL, err = url.ParseRequestURI(targetURI); err != nil {
- ctx.Error(fmt.Errorf("unable to parse target URL %s: %w", targetURI, err), messageAuthenticationFailed)
-
- return
- }
-
- var (
- id string
- client *oidc.Client
- )
-
- if id = targetURL.Query().Get("client_id"); len(id) == 0 {
- ctx.Error(fmt.Errorf("unable to get client id from from URL '%s'", targetURL), messageAuthenticationFailed)
-
- return
- }
-
- if client, err = ctx.Providers.OpenIDConnect.Store.GetFullClient(id); err != nil {
- ctx.Error(fmt.Errorf("unable to get client for client with id '%s' from URL '%s': %w", id, targetURL, err), messageAuthenticationFailed)
-
- return
- }
-
- userSession := ctx.GetSession()
-
- if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
- ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.ID)
- ctx.ReplyOK()
-
- return
- }
-
- if err = ctx.SetJSONBody(redirectResponse{Redirect: targetURL.String()}); err != nil {
- ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err)
- }
-}
-
// Handle1FAResponse handle the redirection upon 1FA authentication.
func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI, requestMethod string, username string, groups []string) {
var err error
@@ -166,6 +119,130 @@ func Handle2FAResponse(ctx *middlewares.AutheliaCtx, targetURI string) {
ctx.ReplyOK()
}
+// handleOIDCWorkflowResponse handle the redirection upon authentication in the OIDC workflow.
+func handleOIDCWorkflowResponse(ctx *middlewares.AutheliaCtx, targetURI, workflowID string) {
+ switch {
+ case len(workflowID) != 0:
+ handleOIDCWorkflowResponseWithID(ctx, workflowID)
+ case len(targetURI) != 0:
+ handleOIDCWorkflowResponseWithTargetURL(ctx, targetURI)
+ default:
+ ctx.Error(fmt.Errorf("invalid post data: must contain either a target url or a workflow id"), messageAuthenticationFailed)
+ }
+}
+
+func handleOIDCWorkflowResponseWithTargetURL(ctx *middlewares.AutheliaCtx, targetURI string) {
+ var (
+ issuerURL *url.URL
+ targetURL *url.URL
+ err error
+ )
+
+ if targetURL, err = url.ParseRequestURI(targetURI); err != nil {
+ ctx.Error(fmt.Errorf("unable to parse target URL '%s': %w", targetURI, err), messageAuthenticationFailed)
+
+ return
+ }
+
+ if issuerURL, err = ctx.IssuerURL(); err != nil {
+ ctx.Error(fmt.Errorf("unable to get issuer for redirection: %w", err), messageAuthenticationFailed)
+
+ return
+ }
+
+ if targetURL.Host != issuerURL.Host {
+ ctx.Error(fmt.Errorf("unable to redirect to '%s': target host '%s' does not match expected issuer host '%s'", targetURL, targetURL.Host, issuerURL.Host), messageAuthenticationFailed)
+
+ return
+ }
+
+ userSession := ctx.GetSession()
+
+ if userSession.IsAnonymous() {
+ ctx.Error(fmt.Errorf("unable to redirect to '%s': user is anonymous", targetURL), messageAuthenticationFailed)
+
+ return
+ }
+
+ if err = ctx.SetJSONBody(redirectResponse{Redirect: targetURL.String()}); err != nil {
+ ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err)
+ }
+}
+
+func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
+ var (
+ workflowID uuid.UUID
+ client *oidc.Client
+ consent *model.OAuth2ConsentSession
+ err error
+ )
+
+ if workflowID, err = uuid.Parse(id); err != nil {
+ ctx.Error(fmt.Errorf("unable to parse consent session challenge id '%s': %w", id, err), messageAuthenticationFailed)
+
+ return
+ }
+
+ if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, workflowID); err != nil {
+ ctx.Error(fmt.Errorf("unable to load consent session by challenge id '%s': %w", id, err), messageAuthenticationFailed)
+
+ return
+ }
+
+ if consent.Responded() {
+ ctx.Error(fmt.Errorf("consent has already been responded to '%s': %w", id, err), messageAuthenticationFailed)
+
+ return
+ }
+
+ if client, err = ctx.Providers.OpenIDConnect.GetFullClient(consent.ClientID); err != nil {
+ ctx.Error(fmt.Errorf("unable to get client for client with id '%s' with consent challenge id '%s': %w", id, consent.ChallengeID, err), messageAuthenticationFailed)
+
+ return
+ }
+
+ userSession := ctx.GetSession()
+
+ if userSession.IsAnonymous() {
+ ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': user is anonymous", client.ID, consent.ChallengeID), messageAuthenticationFailed)
+
+ return
+ }
+
+ if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
+ ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.ID)
+ ctx.ReplyOK()
+
+ return
+ }
+
+ var (
+ targetURL *url.URL
+ form url.Values
+ )
+
+ if targetURL, err = ctx.IssuerURL(); err != nil {
+ ctx.Error(fmt.Errorf("unable to get issuer for redirection: %w", err), messageAuthenticationFailed)
+
+ return
+ }
+
+ if form, err = consent.GetForm(); err != nil {
+ ctx.Error(fmt.Errorf("unable to get authorization form values from consent session with challenge id '%s': %w", consent.ChallengeID, err), messageAuthenticationFailed)
+
+ return
+ }
+
+ form.Set(queryArgConsentID, workflowID.String())
+
+ targetURL.Path = path.Join(targetURL.Path, oidc.EndpointPathAuthorization)
+ targetURL.RawQuery = form.Encode()
+
+ if err = ctx.SetJSONBody(redirectResponse{Redirect: targetURL.String()}); err != nil {
+ ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err)
+ }
+}
+
func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, bannedUntil *time.Time, username string, authType string, errAuth error) (err error) {
// We only Mark if there was no underlying error.
ctx.Logger.Debugf("Mark %s authentication attempt made by user '%s'", authType, username)
@@ -178,7 +255,7 @@ func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, ba
if referer != nil {
refererURL, err := url.ParseRequestURI(string(referer))
if err == nil {
- requestURI = refererURL.Query().Get("rd")
+ requestURI = refererURL.Query().Get(queryArgRD)
requestMethod = refererURL.Query().Get("rm")
}
}
diff --git a/internal/handlers/types.go b/internal/handlers/types.go
index b2be6e512..517d297a6 100644
--- a/internal/handlers/types.go
+++ b/internal/handlers/types.go
@@ -1,7 +1,17 @@
package handlers
import (
+ "net/http"
+ "net/url"
+
+ "github.com/google/uuid"
+ "github.com/ory/fosite"
+
"github.com/authelia/authelia/v4/internal/authentication"
+ "github.com/authelia/authelia/v4/internal/middlewares"
+ "github.com/authelia/authelia/v4/internal/model"
+ "github.com/authelia/authelia/v4/internal/oidc"
+ "github.com/authelia/authelia/v4/internal/session"
)
// MethodList is the list of available methods.
@@ -14,36 +24,41 @@ type configurationBody struct {
AvailableMethods MethodList `json:"available_methods"`
}
-// signTOTPRequestBody model of the request body received by TOTP authentication endpoint.
-type signTOTPRequestBody struct {
- Token string `json:"token" valid:"required"`
- TargetURL string `json:"targetURL"`
- Workflow string `json:"workflow"`
+// bodySignTOTPRequest is the model of the request body of TOTP 2FA authentication endpoint.
+type bodySignTOTPRequest struct {
+ Token string `json:"token" valid:"required"`
+ TargetURL string `json:"targetURL"`
+ Workflow string `json:"workflow"`
+ WorkflowID string `json:"workflowID"`
}
-// signWebauthnRequestBody model of the request body of Webauthn authentication endpoint.
-type signWebauthnRequestBody struct {
- TargetURL string `json:"targetURL"`
- Workflow string `json:"workflow"`
+// bodySignWebauthnRequest is the model of the request body of WebAuthn 2FA authentication endpoint.
+type bodySignWebauthnRequest struct {
+ TargetURL string `json:"targetURL"`
+ Workflow string `json:"workflow"`
+ WorkflowID string `json:"workflowID"`
}
-type signDuoRequestBody struct {
- TargetURL string `json:"targetURL"`
- Passcode string `json:"passcode"`
- Workflow string `json:"workflow"`
+// bodySignDuoRequest is the model of the request body of Duo 2FA authentication endpoint.
+type bodySignDuoRequest struct {
+ TargetURL string `json:"targetURL"`
+ Passcode string `json:"passcode"`
+ Workflow string `json:"workflow"`
+ WorkflowID string `json:"workflowID"`
}
-// preferred2FAMethodBody the selected 2FA method.
-type preferred2FAMethodBody struct {
+// bodyPreferred2FAMethod the selected 2FA method.
+type bodyPreferred2FAMethod struct {
Method string `json:"method" valid:"required"`
}
-// firstFactorRequestBody represents the JSON body received by the endpoint.
-type firstFactorRequestBody struct {
+// bodyFirstFactorRequest represents the JSON body received by the endpoint.
+type bodyFirstFactorRequest struct {
Username string `json:"username" valid:"required"`
Password string `json:"password" valid:"required"`
TargetURL string `json:"targetURL"`
Workflow string `json:"workflow"`
+ WorkflowID string `json:"workflowID"`
RequestMethod string `json:"requestMethod"`
KeepMeLoggedIn *bool `json:"keepMeLoggedIn"`
// KeepMeLoggedIn: Cannot require this field because of https://github.com/asaskevich/govalidator/pull/329
@@ -128,3 +143,9 @@ type PasswordPolicyBody struct {
RequireNumber bool `json:"require_number"`
RequireSpecial bool `json:"require_special"`
}
+
+type handlerAuthorizationConsent func(
+ ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client,
+ userSession session.UserSession, subject uuid.UUID,
+ rw http.ResponseWriter, r *http.Request,
+ requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool)
diff --git a/internal/handlers/util.go b/internal/handlers/util.go
new file mode 100644
index 000000000..3117b74bf
--- /dev/null
+++ b/internal/handlers/util.go
@@ -0,0 +1,26 @@
+package handlers
+
+import (
+ "bytes"
+ "net/url"
+
+ "github.com/authelia/authelia/v4/internal/middlewares"
+)
+
+var bytesEmpty = []byte("")
+
+func ctxGetPortalURL(ctx *middlewares.AutheliaCtx) (portalURL *url.URL) {
+ var rawURL []byte
+
+ if rawURL = ctx.QueryArgRedirect(); rawURL != nil && !bytes.Equal(rawURL, bytesEmpty) {
+ portalURL, _ = url.ParseRequestURI(string(rawURL))
+
+ return portalURL
+ } else if rawURL = ctx.XAutheliaURL(); rawURL != nil && !bytes.Equal(rawURL, bytesEmpty) {
+ portalURL, _ = url.ParseRequestURI(string(rawURL))
+
+ return portalURL
+ }
+
+ return nil
+}
diff --git a/internal/logging/logger.go b/internal/logging/logger.go
index 761c8e646..3c5a549a4 100644
--- a/internal/logging/logger.go
+++ b/internal/logging/logger.go
@@ -37,8 +37,10 @@ func LoggerCtxPrintf(level logrus.Level) (logger *CtxPrintfLogger) {
func InitializeLogger(config schema.LogConfiguration, log bool) error {
setLevelStr(config.Level, log)
- callerLevels := []logrus.Level{}
+ var callerLevels []logrus.Level
+
stackLevels := []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}
+
logrus.AddHook(logrus_stack.NewHook(callerLevels, stackLevels))
if config.Format == logFormatJSON {
diff --git a/internal/middlewares/asset_override.go b/internal/middlewares/asset_override.go
index e25444aba..c8ed111c4 100644
--- a/internal/middlewares/asset_override.go
+++ b/internal/middlewares/asset_override.go
@@ -9,20 +9,22 @@ import (
// AssetOverride allows overriding and serving of specific embedded assets from disk.
func AssetOverride(root string, strip int, next fasthttp.RequestHandler) fasthttp.RequestHandler {
+ if root == "" {
+ return next
+ }
+
+ handler := fasthttp.FSHandler(root, strip)
+ stripper := fasthttp.NewPathSlashesStripper(strip)
+
return func(ctx *fasthttp.RequestCtx) {
- if root == "" {
+ asset := filepath.Join(root, string(stripper(ctx)))
+
+ if _, err := os.Stat(asset); err != nil {
next(ctx)
return
}
- _, err := os.Stat(filepath.Join(root, string(fasthttp.NewPathSlashesStripper(strip)(ctx))))
- if err != nil {
- next(ctx)
-
- return
- }
-
- fasthttp.FSHandler(root, strip)(ctx)
+ handler(ctx)
}
}
diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go
index 5189e1435..862f16d4d 100644
--- a/internal/middlewares/authelia_context.go
+++ b/internal/middlewares/authelia_context.go
@@ -95,7 +95,7 @@ func (ctx *AutheliaCtx) ReplyStatusCode(statusCode int) {
}
// ReplyJSON writes a JSON response.
-func (ctx *AutheliaCtx) ReplyJSON(data interface{}, statusCode int) (err error) {
+func (ctx *AutheliaCtx) ReplyJSON(data any, statusCode int) (err error) {
var (
body []byte
)
@@ -171,6 +171,16 @@ func (ctx *AutheliaCtx) XForwardedURI() (uri []byte) {
return uri
}
+// XAutheliaURL return the content of the X-Authelia-URL header.
+func (ctx *AutheliaCtx) XAutheliaURL() (autheliaURL []byte) {
+ return ctx.RequestCtx.Request.Header.PeekBytes(headerXAutheliaURL)
+}
+
+// QueryArgRedirect return the content of the rd query argument.
+func (ctx *AutheliaCtx) QueryArgRedirect() (val []byte) {
+ return ctx.RequestCtx.QueryArgs().PeekBytes(queryArgRedirect)
+}
+
// BasePath returns the base_url as per the path visited by the client.
func (ctx *AutheliaCtx) BasePath() (base string) {
if baseURL := ctx.UserValueBytes(UserValueKeyBaseURL); baseURL != nil {
@@ -208,6 +218,29 @@ func (ctx *AutheliaCtx) ExternalRootURL() (string, error) {
return externalRootURL, nil
}
+// IssuerURL returns the expected Issuer.
+func (ctx *AutheliaCtx) IssuerURL() (issuerURL *url.URL, err error) {
+ issuerURL = &url.URL{
+ Scheme: "https",
+ }
+
+ if scheme := ctx.XForwardedProto(); scheme != nil {
+ issuerURL.Scheme = string(scheme)
+ }
+
+ if host := ctx.XForwardedHost(); len(host) != 0 {
+ issuerURL.Host = string(host)
+ } else {
+ return nil, errMissingXForwardedHost
+ }
+
+ if base := ctx.BasePath(); base != "" {
+ issuerURL.Path = path.Join(issuerURL.Path, base)
+ }
+
+ return issuerURL, nil
+}
+
// XOriginalURL return the content of the X-Original-URL header.
func (ctx *AutheliaCtx) XOriginalURL() []byte {
return ctx.RequestCtx.Request.Header.PeekBytes(headerXOriginalURL)
@@ -236,7 +269,7 @@ func (ctx *AutheliaCtx) ReplyOK() {
}
// ParseBody parse the request body into the type of value.
-func (ctx *AutheliaCtx) ParseBody(value interface{}) error {
+func (ctx *AutheliaCtx) ParseBody(value any) error {
err := json.Unmarshal(ctx.PostBody(), &value)
if err != nil {
@@ -257,7 +290,7 @@ func (ctx *AutheliaCtx) ParseBody(value interface{}) error {
}
// SetJSONBody Set json body.
-func (ctx *AutheliaCtx) SetJSONBody(value interface{}) error {
+func (ctx *AutheliaCtx) SetJSONBody(value any) error {
return ctx.ReplyJSON(OKResponse{Status: "OK", Data: value}, 0)
}
diff --git a/internal/middlewares/authelia_context_test.go b/internal/middlewares/authelia_context_test.go
index b15b5eb77..ab8882f03 100644
--- a/internal/middlewares/authelia_context_test.go
+++ b/internal/middlewares/authelia_context_test.go
@@ -6,6 +6,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/configuration/schema"
@@ -15,6 +16,61 @@ import (
"github.com/authelia/authelia/v4/internal/session"
)
+func TestIssuerURL(t *testing.T) {
+ testCases := []struct {
+ name string
+ proto, host, base string
+ expected string
+ err string
+ }{
+ {
+ name: "Standard",
+ proto: "https", host: "auth.example.com", base: "",
+ expected: "https://auth.example.com",
+ },
+ {
+ name: "Base",
+ proto: "https", host: "example.com", base: "auth",
+ expected: "https://example.com/auth",
+ },
+ {
+ name: "NoHost",
+ proto: "https", host: "", base: "",
+ err: "Missing header X-Forwarded-Host",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(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)
+
+ if tc.base != "" {
+ mock.Ctx.SetUserValue("base_url", tc.base)
+ }
+
+ actual, err := mock.Ctx.IssuerURL()
+
+ switch tc.err {
+ case "":
+ assert.NoError(t, err)
+ require.NotNil(t, actual)
+
+ assert.Equal(t, tc.expected, actual.String())
+ assert.Equal(t, tc.proto, actual.Scheme)
+ assert.Equal(t, tc.host, actual.Host)
+ assert.Equal(t, tc.base, actual.Path)
+ default:
+ assert.EqualError(t, err, tc.err)
+ assert.Nil(t, actual)
+ }
+ })
+ }
+}
+
func TestShouldCallNextWithAutheliaCtx(t *testing.T) {
ctrl := gomock.NewController(t)
ctx := &fasthttp.RequestCtx{}
diff --git a/internal/middlewares/const.go b/internal/middlewares/const.go
index bbbbb8235..88ef4472b 100644
--- a/internal/middlewares/const.go
+++ b/internal/middlewares/const.go
@@ -7,6 +7,8 @@ import (
)
var (
+ headerXAutheliaURL = []byte("X-Authelia-URL")
+
headerAccept = []byte(fasthttp.HeaderAccept)
headerContentLength = []byte(fasthttp.HeaderContentLength)
headerLocation = []byte(fasthttp.HeaderLocation)
@@ -71,6 +73,8 @@ var (
protoHTTPS = []byte(strProtoHTTPS)
protoHTTP = []byte(strProtoHTTP)
+ queryArgRedirect = []byte("rd")
+
// UserValueKeyBaseURL is the User Value key where we store the Base URL.
UserValueKeyBaseURL = []byte("base_url")
diff --git a/internal/middlewares/cors.go b/internal/middlewares/cors.go
index d5a7234a4..8de1aaf84 100644
--- a/internal/middlewares/cors.go
+++ b/internal/middlewares/cors.go
@@ -197,19 +197,19 @@ type CORSPolicy struct {
// HandleOPTIONS is an OPTIONS handler that just adds CORS headers, the Allow header, and sets the status code to 204
// without a body. This handler should generally not be used without using WithAllowedMethods.
-func (p CORSPolicy) HandleOPTIONS(ctx *fasthttp.RequestCtx) {
+func (p *CORSPolicy) HandleOPTIONS(ctx *fasthttp.RequestCtx) {
p.handleOPTIONS(ctx)
p.handle(ctx)
}
// HandleOnlyOPTIONS is an OPTIONS handler that just handles the Allow header, and sets the status code to 204
// without a body. This handler should generally not be used without using WithAllowedMethods.
-func (p CORSPolicy) HandleOnlyOPTIONS(ctx *fasthttp.RequestCtx) {
+func (p *CORSPolicy) HandleOnlyOPTIONS(ctx *fasthttp.RequestCtx) {
p.handleOPTIONS(ctx)
}
// Middleware provides a middleware that adds the appropriate CORS headers for this CORSPolicyBuilder.
-func (p CORSPolicy) Middleware(next fasthttp.RequestHandler) (handler fasthttp.RequestHandler) {
+func (p *CORSPolicy) Middleware(next fasthttp.RequestHandler) (handler fasthttp.RequestHandler) {
return func(ctx *fasthttp.RequestCtx) {
p.handle(ctx)
@@ -217,7 +217,7 @@ func (p CORSPolicy) Middleware(next fasthttp.RequestHandler) (handler fasthttp.R
}
}
-func (p CORSPolicy) handle(ctx *fasthttp.RequestCtx) {
+func (p *CORSPolicy) handle(ctx *fasthttp.RequestCtx) {
if !p.enabled {
return
}
@@ -229,7 +229,7 @@ func (p CORSPolicy) handle(ctx *fasthttp.RequestCtx) {
}
}
-func (p CORSPolicy) handleOPTIONS(ctx *fasthttp.RequestCtx) {
+func (p *CORSPolicy) handleOPTIONS(ctx *fasthttp.RequestCtx) {
ctx.Response.ResetBody()
/* The OPTIONS method should not return a 204 as per the following specifications when read together:
@@ -250,13 +250,13 @@ func (p CORSPolicy) handleOPTIONS(ctx *fasthttp.RequestCtx) {
}
}
-func (p CORSPolicy) handleVary(ctx *fasthttp.RequestCtx) {
+func (p *CORSPolicy) handleVary(ctx *fasthttp.RequestCtx) {
if len(p.vary) != 0 {
ctx.Response.Header.SetBytesKV(headerVary, p.vary)
}
}
-func (p CORSPolicy) handleCORS(ctx *fasthttp.RequestCtx) {
+func (p *CORSPolicy) handleCORS(ctx *fasthttp.RequestCtx) {
var (
originURL *url.URL
err error
@@ -302,7 +302,7 @@ func (p CORSPolicy) handleCORS(ctx *fasthttp.RequestCtx) {
p.handleAllowedMethods(ctx)
}
-func (p CORSPolicy) handleAllowedMethods(ctx *fasthttp.RequestCtx) {
+func (p *CORSPolicy) handleAllowedMethods(ctx *fasthttp.RequestCtx) {
switch len(p.methods) {
case 0:
// TODO: It may be beneficial to be able to control this automatic behaviour.
@@ -314,7 +314,7 @@ func (p CORSPolicy) handleAllowedMethods(ctx *fasthttp.RequestCtx) {
}
}
-func (p CORSPolicy) handleAllowedHeaders(ctx *fasthttp.RequestCtx) {
+func (p *CORSPolicy) handleAllowedHeaders(ctx *fasthttp.RequestCtx) {
switch len(p.headers) {
case 0:
// TODO: It may be beneficial to be able to control this automatic behaviour.
diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go
index 6189331e4..e4cbabce9 100644
--- a/internal/middlewares/identity_verification.go
+++ b/internal/middlewares/identity_verification.go
@@ -132,7 +132,7 @@ func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(c
}
token, err := jwt.ParseWithClaims(finishBody.Token, &model.IdentityVerificationClaim{},
- func(token *jwt.Token) (interface{}, error) {
+ func(token *jwt.Token) (any, error) {
return []byte(ctx.Configuration.JWTSecret), nil
})
diff --git a/internal/middlewares/types.go b/internal/middlewares/types.go
index 59cc95f77..f26e57fc7 100644
--- a/internal/middlewares/types.go
+++ b/internal/middlewares/types.go
@@ -35,7 +35,7 @@ type Providers struct {
Authorizer *authorization.Authorizer
SessionProvider *session.Provider
Regulator *regulation.Regulator
- OpenIDConnect oidc.OpenIDConnectProvider
+ OpenIDConnect *oidc.OpenIDConnectProvider
Metrics metrics.Provider
NTP *ntp.Provider
UserProvider authentication.UserProvider
@@ -108,8 +108,8 @@ type IdentityVerificationFinishBody struct {
// OKResponse model of a status OK response.
type OKResponse struct {
- Status string `json:"status"`
- Data interface{} `json:"data,omitempty"`
+ Status string `json:"status"`
+ Data any `json:"data,omitempty"`
}
// ErrorResponse model of an error response.
diff --git a/internal/mocks/storage.go b/internal/mocks/storage.go
index 2f6160d76..2a20f8acc 100644
--- a/internal/mocks/storage.go
+++ b/internal/mocks/storage.go
@@ -6,6 +6,7 @@ package mocks
import (
context "context"
+ sql "database/sql"
reflect "reflect"
time "time"
@@ -166,6 +167,34 @@ func (mr *MockStorageMockRecorder) DeleteTOTPConfiguration(arg0, arg1 interface{
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTOTPConfiguration", reflect.TypeOf((*MockStorage)(nil).DeleteTOTPConfiguration), arg0, arg1)
}
+// DeleteWebauthnDevice mocks base method.
+func (m *MockStorage) DeleteWebauthnDevice(arg0 context.Context, arg1 string) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteWebauthnDevice", arg0, arg1)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// DeleteWebauthnDevice indicates an expected call of DeleteWebauthnDevice.
+func (mr *MockStorageMockRecorder) DeleteWebauthnDevice(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWebauthnDevice", reflect.TypeOf((*MockStorage)(nil).DeleteWebauthnDevice), arg0, arg1)
+}
+
+// DeleteWebauthnDeviceByUsername mocks base method.
+func (m *MockStorage) DeleteWebauthnDeviceByUsername(arg0 context.Context, arg1, arg2 string) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteWebauthnDeviceByUsername", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// DeleteWebauthnDeviceByUsername indicates an expected call of DeleteWebauthnDeviceByUsername.
+func (mr *MockStorageMockRecorder) DeleteWebauthnDeviceByUsername(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWebauthnDeviceByUsername", reflect.TypeOf((*MockStorage)(nil).DeleteWebauthnDeviceByUsername), arg0, arg1, arg2)
+}
+
// FindIdentityVerification mocks base method.
func (m *MockStorage) FindIdentityVerification(arg0 context.Context, arg1 string) (bool, error) {
m.ctrl.T.Helper()
@@ -211,6 +240,21 @@ func (mr *MockStorageMockRecorder) LoadOAuth2BlacklistedJTI(arg0, arg1 interface
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadOAuth2BlacklistedJTI", reflect.TypeOf((*MockStorage)(nil).LoadOAuth2BlacklistedJTI), arg0, arg1)
}
+// LoadOAuth2ConsentPreConfigurations mocks base method.
+func (m *MockStorage) LoadOAuth2ConsentPreConfigurations(arg0 context.Context, arg1 string, arg2 uuid.UUID) (*storage.ConsentPreConfigRows, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "LoadOAuth2ConsentPreConfigurations", arg0, arg1, arg2)
+ ret0, _ := ret[0].(*storage.ConsentPreConfigRows)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// LoadOAuth2ConsentPreConfigurations indicates an expected call of LoadOAuth2ConsentPreConfigurations.
+func (mr *MockStorageMockRecorder) LoadOAuth2ConsentPreConfigurations(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadOAuth2ConsentPreConfigurations", reflect.TypeOf((*MockStorage)(nil).LoadOAuth2ConsentPreConfigurations), arg0, arg1, arg2)
+}
+
// LoadOAuth2ConsentSessionByChallengeID mocks base method.
func (m *MockStorage) LoadOAuth2ConsentSessionByChallengeID(arg0 context.Context, arg1 uuid.UUID) (*model.OAuth2ConsentSession, error) {
m.ctrl.T.Helper()
@@ -226,21 +270,6 @@ func (mr *MockStorageMockRecorder) LoadOAuth2ConsentSessionByChallengeID(arg0, a
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadOAuth2ConsentSessionByChallengeID", reflect.TypeOf((*MockStorage)(nil).LoadOAuth2ConsentSessionByChallengeID), arg0, arg1)
}
-// LoadOAuth2ConsentSessionsPreConfigured mocks base method.
-func (m *MockStorage) LoadOAuth2ConsentSessionsPreConfigured(arg0 context.Context, arg1 string, arg2 uuid.UUID) (*storage.ConsentSessionRows, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "LoadOAuth2ConsentSessionsPreConfigured", arg0, arg1, arg2)
- ret0, _ := ret[0].(*storage.ConsentSessionRows)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// LoadOAuth2ConsentSessionsPreConfigured indicates an expected call of LoadOAuth2ConsentSessionsPreConfigured.
-func (mr *MockStorageMockRecorder) LoadOAuth2ConsentSessionsPreConfigured(arg0, arg1, arg2 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadOAuth2ConsentSessionsPreConfigured", reflect.TypeOf((*MockStorage)(nil).LoadOAuth2ConsentSessionsPreConfigured), arg0, arg1, arg2)
-}
-
// LoadOAuth2Session mocks base method.
func (m *MockStorage) LoadOAuth2Session(arg0 context.Context, arg1 storage.OAuth2SessionType, arg2 string) (*model.OAuth2Session, error) {
m.ctrl.T.Helper()
@@ -476,6 +505,21 @@ func (mr *MockStorageMockRecorder) SaveOAuth2BlacklistedJTI(arg0, arg1 interface
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveOAuth2BlacklistedJTI", reflect.TypeOf((*MockStorage)(nil).SaveOAuth2BlacklistedJTI), arg0, arg1)
}
+// SaveOAuth2ConsentPreConfiguration mocks base method.
+func (m *MockStorage) SaveOAuth2ConsentPreConfiguration(arg0 context.Context, arg1 model.OAuth2ConsentPreConfig) (int64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SaveOAuth2ConsentPreConfiguration", arg0, arg1)
+ ret0, _ := ret[0].(int64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SaveOAuth2ConsentPreConfiguration indicates an expected call of SaveOAuth2ConsentPreConfiguration.
+func (mr *MockStorageMockRecorder) SaveOAuth2ConsentPreConfiguration(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveOAuth2ConsentPreConfiguration", reflect.TypeOf((*MockStorage)(nil).SaveOAuth2ConsentPreConfiguration), arg0, arg1)
+}
+
// SaveOAuth2ConsentSession mocks base method.
func (m *MockStorage) SaveOAuth2ConsentSession(arg0 context.Context, arg1 model.OAuth2ConsentSession) error {
m.ctrl.T.Helper()
@@ -763,7 +807,7 @@ func (mr *MockStorageMockRecorder) StartupCheck() *gomock.Call {
}
// UpdateTOTPConfigurationSignIn mocks base method.
-func (m *MockStorage) UpdateTOTPConfigurationSignIn(arg0 context.Context, arg1 int, arg2 *time.Time) error {
+func (m *MockStorage) UpdateTOTPConfigurationSignIn(arg0 context.Context, arg1 int, arg2 sql.NullTime) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateTOTPConfigurationSignIn", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
@@ -777,7 +821,7 @@ func (mr *MockStorageMockRecorder) UpdateTOTPConfigurationSignIn(arg0, arg1, arg
}
// UpdateWebauthnDeviceSignIn mocks base method.
-func (m *MockStorage) UpdateWebauthnDeviceSignIn(arg0 context.Context, arg1 int, arg2 string, arg3 *time.Time, arg4 uint32, arg5 bool) error {
+func (m *MockStorage) UpdateWebauthnDeviceSignIn(arg0 context.Context, arg1 int, arg2 string, arg3 sql.NullTime, arg4 uint32, arg5 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateWebauthnDeviceSignIn", arg0, arg1, arg2, arg3, arg4, arg5)
ret0, _ := ret[0].(error)
diff --git a/internal/model/identity_verification.go b/internal/model/identity_verification.go
index f0f58bc45..f6d4491ad 100644
--- a/internal/model/identity_verification.go
+++ b/internal/model/identity_verification.go
@@ -1,6 +1,7 @@
package model
import (
+ "database/sql"
"net"
"time"
@@ -22,15 +23,15 @@ func NewIdentityVerification(jti uuid.UUID, username, action string, ip net.IP)
// IdentityVerification represents an identity verification row in the database.
type IdentityVerification struct {
- ID int `db:"id"`
- JTI uuid.UUID `db:"jti"`
- IssuedAt time.Time `db:"iat"`
- IssuedIP IP `db:"issued_ip"`
- ExpiresAt time.Time `db:"exp"`
- Action string `db:"action"`
- Username string `db:"username"`
- Consumed *time.Time `db:"consumed"`
- ConsumedIP NullIP `db:"consumed_ip"`
+ ID int `db:"id"`
+ JTI uuid.UUID `db:"jti"`
+ IssuedAt time.Time `db:"iat"`
+ IssuedIP IP `db:"issued_ip"`
+ ExpiresAt time.Time `db:"exp"`
+ Action string `db:"action"`
+ Username string `db:"username"`
+ Consumed sql.NullTime `db:"consumed"`
+ ConsumedIP NullIP `db:"consumed_ip"`
}
// ToIdentityVerificationClaim converts the IdentityVerification into a IdentityVerificationClaim.
diff --git a/internal/model/oidc.go b/internal/model/oidc.go
index c25e46eb4..1d742543d 100644
--- a/internal/model/oidc.go
+++ b/internal/model/oidc.go
@@ -3,6 +3,7 @@ package model
import (
"context"
"crypto/sha256"
+ "database/sql"
"encoding/json"
"fmt"
"net/url"
@@ -84,6 +85,42 @@ func NewOAuth2BlacklistedJTI(jti string, exp time.Time) (jtiBlacklist OAuth2Blac
}
}
+// OAuth2ConsentPreConfig stores information about an OAuth2.0 Pre-Configured Consent.
+type OAuth2ConsentPreConfig struct {
+ ID int64 `db:"id"`
+ ClientID string `db:"client_id"`
+ Subject uuid.UUID `db:"subject"`
+
+ CreatedAt time.Time `db:"created_at"`
+ ExpiresAt sql.NullTime `db:"expires_at"`
+
+ Revoked bool `db:"revoked"`
+
+ Scopes StringSlicePipeDelimited `db:"scopes"`
+ Audience StringSlicePipeDelimited `db:"audience"`
+}
+
+// HasExactGrants returns true if the granted audience and scopes of this consent pre-configuration matches exactly with
+// another audience and set of scopes.
+func (s *OAuth2ConsentPreConfig) HasExactGrants(scopes, audience []string) (has bool) {
+ return s.HasExactGrantedScopes(scopes) && s.HasExactGrantedAudience(audience)
+}
+
+// HasExactGrantedAudience returns true if the granted audience of this consent matches exactly with another audience.
+func (s *OAuth2ConsentPreConfig) HasExactGrantedAudience(audience []string) (has bool) {
+ return !utils.IsStringSlicesDifferent(s.Audience, audience)
+}
+
+// HasExactGrantedScopes returns true if the granted scopes of this consent matches exactly with another set of scopes.
+func (s *OAuth2ConsentPreConfig) HasExactGrantedScopes(scopes []string) (has bool) {
+ return !utils.IsStringSlicesDifferent(s.Scopes, scopes)
+}
+
+// CanConsent returns true if this pre-configuration can still provide consent.
+func (s *OAuth2ConsentPreConfig) CanConsent() bool {
+ return !s.Revoked && (!s.ExpiresAt.Valid || s.ExpiresAt.Time.After(time.Now()))
+}
+
// OAuth2ConsentSession stores information about an OAuth2.0 Consent.
type OAuth2ConsentSession struct {
ID int `db:"id"`
@@ -94,9 +131,8 @@ type OAuth2ConsentSession struct {
Authorized bool `db:"authorized"`
Granted bool `db:"granted"`
- RequestedAt time.Time `db:"requested_at"`
- RespondedAt *time.Time `db:"responded_at"`
- ExpiresAt *time.Time `db:"expires_at"`
+ RequestedAt time.Time `db:"requested_at"`
+ RespondedAt sql.NullTime `db:"responded_at"`
Form string `db:"form_data"`
@@ -104,55 +140,63 @@ type OAuth2ConsentSession struct {
GrantedScopes StringSlicePipeDelimited `db:"granted_scopes"`
RequestedAudience StringSlicePipeDelimited `db:"requested_audience"`
GrantedAudience StringSlicePipeDelimited `db:"granted_audience"`
+
+ PreConfiguration sql.NullInt64
+}
+
+// Grant grants the requested scopes and audience.
+func (s *OAuth2ConsentSession) Grant() {
+ s.GrantedScopes = s.RequestedScopes
+ s.GrantedAudience = s.RequestedAudience
+
+ if !utils.IsStringInSlice(s.ClientID, s.GrantedAudience) {
+ s.GrantedAudience = append(s.GrantedAudience, s.ClientID)
+ }
}
// HasExactGrants returns true if the granted audience and scopes of this consent matches exactly with another
// audience and set of scopes.
-func (s OAuth2ConsentSession) HasExactGrants(scopes, audience []string) (has bool) {
+func (s *OAuth2ConsentSession) HasExactGrants(scopes, audience []string) (has bool) {
return s.HasExactGrantedScopes(scopes) && s.HasExactGrantedAudience(audience)
}
// HasExactGrantedAudience returns true if the granted audience of this consent matches exactly with another audience.
-func (s OAuth2ConsentSession) HasExactGrantedAudience(audience []string) (has bool) {
+func (s *OAuth2ConsentSession) HasExactGrantedAudience(audience []string) (has bool) {
return !utils.IsStringSlicesDifferent(s.GrantedAudience, audience)
}
// HasExactGrantedScopes returns true if the granted scopes of this consent matches exactly with another set of scopes.
-func (s OAuth2ConsentSession) HasExactGrantedScopes(scopes []string) (has bool) {
+func (s *OAuth2ConsentSession) HasExactGrantedScopes(scopes []string) (has bool) {
return !utils.IsStringSlicesDifferent(s.GrantedScopes, scopes)
}
+// Responded returns true if the user has responded to the consent session.
+func (s *OAuth2ConsentSession) Responded() bool {
+ return s.RespondedAt.Valid
+}
+
// IsAuthorized returns true if the user has responded to the consent session and it was authorized.
-func (s OAuth2ConsentSession) IsAuthorized() bool {
+func (s *OAuth2ConsentSession) IsAuthorized() bool {
return s.Responded() && s.Authorized
}
-// CanGrant returns true if the user has responded to the consent session, it was authorized, and it either hast not
-// previously been granted or the ability to grant has not expired.
-func (s OAuth2ConsentSession) CanGrant() bool {
- if !s.Responded() {
- return false
- }
+// IsDenied returns true if the user has responded to the consent session and it was not authorized.
+func (s *OAuth2ConsentSession) IsDenied() bool {
+ return s.Responded() && !s.Authorized
+}
- if s.Granted && (s.ExpiresAt == nil || s.ExpiresAt.Before(time.Now())) {
+// CanGrant returns true if the session can still grant a token. This is NOT indicative of if there is a user response
+// to this consent request or if the user rejected the consent request.
+func (s *OAuth2ConsentSession) CanGrant() bool {
+ if !s.Subject.Valid || s.Granted {
return false
}
return true
}
-// IsDenied returns true if the user has responded to the consent session and it was not authorized.
-func (s OAuth2ConsentSession) IsDenied() bool {
- return s.Responded() && !s.Authorized
-}
-
-// Responded returns true if the user has responded to the consent session.
-func (s OAuth2ConsentSession) Responded() bool {
- return s.RespondedAt != nil
-}
-
// GetForm returns the form.
-func (s OAuth2ConsentSession) GetForm() (form url.Values, err error) {
+func (s *OAuth2ConsentSession) GetForm() (form url.Values, err error) {
return url.ParseQuery(s.Form)
}
@@ -227,7 +271,7 @@ type OpenIDSession struct {
ChallengeID uuid.UUID `db:"challenge_id"`
ClientID string
- Extra map[string]interface{} `json:"extra"`
+ Extra map[string]any `json:"extra"`
}
// Clone copies the OpenIDSession to a new fosite.Session.
diff --git a/internal/model/totp_configuration.go b/internal/model/totp_configuration.go
index 74777e4a2..f8f23d41b 100644
--- a/internal/model/totp_configuration.go
+++ b/internal/model/totp_configuration.go
@@ -1,6 +1,7 @@
package model
import (
+ "database/sql"
"image"
"net/url"
"strconv"
@@ -11,15 +12,23 @@ import (
// TOTPConfiguration represents a users TOTP configuration row in the database.
type TOTPConfiguration struct {
- ID int `db:"id" json:"-"`
- CreatedAt time.Time `db:"created_at" json:"-"`
- LastUsedAt *time.Time `db:"last_used_at" json:"-"`
- Username string `db:"username" json:"-"`
- Issuer string `db:"issuer" json:"-"`
- Algorithm string `db:"algorithm" json:"-"`
- Digits uint `db:"digits" json:"digits"`
- Period uint `db:"period" json:"period"`
- Secret []byte `db:"secret" json:"-"`
+ ID int `db:"id" json:"-"`
+ CreatedAt time.Time `db:"created_at" json:"-"`
+ LastUsedAt sql.NullTime `db:"last_used_at" json:"-"`
+ Username string `db:"username" json:"-"`
+ Issuer string `db:"issuer" json:"-"`
+ Algorithm string `db:"algorithm" json:"-"`
+ Digits uint `db:"digits" json:"digits"`
+ Period uint `db:"period" json:"period"`
+ Secret []byte `db:"secret" json:"-"`
+}
+
+func (c *TOTPConfiguration) LastUsed() *time.Time {
+ if c.LastUsedAt.Valid {
+ return &c.LastUsedAt.Time
+ }
+
+ return nil
}
// URI shows the configuration in the URI representation.
@@ -43,7 +52,7 @@ func (c *TOTPConfiguration) URI() (uri string) {
// UpdateSignInInfo adjusts the values of the TOTPConfiguration after a sign in.
func (c *TOTPConfiguration) UpdateSignInInfo(now time.Time) {
- c.LastUsedAt = &now
+ c.LastUsedAt = sql.NullTime{Time: now, Valid: true}
}
// Key returns the *otp.Key using TOTPConfiguration.URI with otp.NewKeyFromURL.
diff --git a/internal/model/types.go b/internal/model/types.go
index 674e6aa43..325e9485c 100644
--- a/internal/model/types.go
+++ b/internal/model/types.go
@@ -49,7 +49,7 @@ func (ip IP) Value() (value driver.Value, err error) {
}
// Scan is the IP implementation of the sql.Scanner.
-func (ip *IP) Scan(src interface{}) (err error) {
+func (ip *IP) Scan(src any) (err error) {
if src == nil {
return fmt.Errorf(errFmtScanNil, ip)
}
@@ -85,7 +85,7 @@ func (ip NullIP) Value() (value driver.Value, err error) {
}
// Scan is the NullIP implementation of the sql.Scanner.
-func (ip *NullIP) Scan(src interface{}) (err error) {
+func (ip *NullIP) Scan(src any) (err error) {
if src == nil {
ip.IP = nil
return nil
@@ -128,7 +128,7 @@ func (b Base64) Value() (value driver.Value, err error) {
}
// Scan is the Base64 implementation of the sql.Scanner.
-func (b *Base64) Scan(src interface{}) (err error) {
+func (b *Base64) Scan(src any) (err error) {
if src == nil {
return fmt.Errorf(errFmtScanNil, b)
}
@@ -158,7 +158,7 @@ type StartupCheck interface {
type StringSlicePipeDelimited []string
// Scan is the StringSlicePipeDelimited implementation of the sql.Scanner.
-func (s *StringSlicePipeDelimited) Scan(value interface{}) (err error) {
+func (s *StringSlicePipeDelimited) Scan(value any) (err error) {
var nullStr sql.NullString
if err = nullStr.Scan(value); err != nil {
diff --git a/internal/model/webauthn.go b/internal/model/webauthn.go
index 5a1c39a0b..c7c02a053 100644
--- a/internal/model/webauthn.go
+++ b/internal/model/webauthn.go
@@ -1,6 +1,7 @@
package model
import (
+ "database/sql"
"encoding/hex"
"strings"
"time"
@@ -133,24 +134,24 @@ func NewWebauthnDeviceFromCredential(rpid, username, description string, credent
// WebauthnDevice represents a Webauthn Device in the database storage.
type WebauthnDevice struct {
- ID int `db:"id" json:"id"`
- CreatedAt time.Time `db:"created_at" json:"created_at"`
- LastUsedAt *time.Time `db:"last_used_at" json:"last_used_at"`
- RPID string `db:"rpid" json:"rpid"`
- Username string `db:"username" json:"username"`
- Description string `db:"description" json:"description"`
- KID Base64 `db:"kid" json:"kid"`
- PublicKey []byte `db:"public_key" json:"public_key"`
- AttestationType string `db:"attestation_type" json:"attestation_type"`
- Transport string `db:"transport" json:"transport"`
- AAGUID uuid.UUID `db:"aaguid" json:"aaguid"`
- SignCount uint32 `db:"sign_count" json:"sign_count"`
- CloneWarning bool `db:"clone_warning" json:"clone_warning"`
+ ID int `db:"id" json:"id"`
+ CreatedAt time.Time `db:"created_at" json:"created_at"`
+ LastUsedAt sql.NullTime `db:"last_used_at" json:"last_used_at"`
+ RPID string `db:"rpid" json:"rpid"`
+ Username string `db:"username" json:"username"`
+ Description string `db:"description" json:"description"`
+ KID Base64 `db:"kid" json:"kid"`
+ PublicKey []byte `db:"public_key" json:"public_key"`
+ AttestationType string `db:"attestation_type" json:"attestation_type"`
+ Transport string `db:"transport" json:"transport"`
+ AAGUID uuid.UUID `db:"aaguid" json:"aaguid"`
+ SignCount uint32 `db:"sign_count" json:"sign_count"`
+ CloneWarning bool `db:"clone_warning" json:"clone_warning"`
}
// UpdateSignInInfo adjusts the values of the WebauthnDevice after a sign in.
func (w *WebauthnDevice) UpdateSignInInfo(config *webauthn.Config, now time.Time, signCount uint32) {
- w.LastUsedAt = &now
+ w.LastUsedAt = sql.NullTime{Time: now, Valid: true}
w.SignCount = signCount
diff --git a/internal/notification/const.go b/internal/notification/const.go
index aee69c680..3534ee359 100644
--- a/internal/notification/const.go
+++ b/internal/notification/const.go
@@ -40,6 +40,10 @@ const (
fmtSMTPDialError = "error dialing the SMTP server: %w"
)
-var (
- rfc2822DoubleNewLine = []byte("\r\n\r\n")
+const (
+ rfc2822NewLine = "\r\n"
+)
+
+var (
+ rfc2822DoubleNewLine = []byte(rfc2822NewLine + rfc2822NewLine)
)
diff --git a/internal/notification/smtp_notifier.go b/internal/notification/smtp_notifier.go
index de58dcba9..0b4d70786 100644
--- a/internal/notification/smtp_notifier.go
+++ b/internal/notification/smtp_notifier.go
@@ -27,7 +27,7 @@ import (
func NewSMTPNotifier(config *schema.SMTPNotifierConfiguration, certPool *x509.CertPool, templateProvider *templates.Provider) *SMTPNotifier {
notifier := &SMTPNotifier{
config: config,
- tlsConfig: utils.NewTLSConfig(config.TLS, tls.VersionTLS12, certPool),
+ tlsConfig: utils.NewTLSConfig(config.TLS, certPool),
log: logging.Logger(),
templates: templateProvider,
}
@@ -156,6 +156,12 @@ func (n *SMTPNotifier) dial() (err error) {
// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
func (n *SMTPNotifier) startTLS() error {
+ // Skips STARTTLS if is disabled in configuration.
+ if n.config.DisableStartTLS {
+ n.log.Warn("Notifier SMTP connection has opportunistic STARTTLS explicitly disabled which means all emails will be sent insecurely over plain text and this setting is only necessary for non-compliant SMTP servers which advertise they support STARTTLS when they actually don't support STARTTLS")
+ return nil
+ }
+
// Only start if not already encrypted.
if _, ok := n.client.TLSConnectionState(); ok {
n.log.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
diff --git a/internal/notification/smtp_notifier_test.go b/internal/notification/smtp_notifier_test.go
index efa7f8c09..0ecc3f36d 100644
--- a/internal/notification/smtp_notifier_test.go
+++ b/internal/notification/smtp_notifier_test.go
@@ -18,7 +18,7 @@ func TestShouldConfigureSMTPNotifierWithTLS11(t *testing.T) {
Port: 25,
TLS: &schema.TLSConfig{
ServerName: "smtp.example.com",
- MinimumVersion: "TLS1.1",
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS11},
},
},
}
diff --git a/internal/notification/smtp_util_test.go b/internal/notification/smtp_util_test.go
new file mode 100644
index 000000000..39c2d6617
--- /dev/null
+++ b/internal/notification/smtp_util_test.go
@@ -0,0 +1,97 @@
+package notification
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/authelia/authelia/v4/internal/utils"
+)
+
+func TestNewMIMECharacteristics(t *testing.T) {
+ testCases := []struct {
+ name string
+ expected MIMECharacteristics
+ have []byte
+ }{
+ {
+ "ShouldDetectMessageCharacteristics7Bit",
+ MIMECharacteristics{},
+ createMIMEBytes(false, true, 5, 150),
+ },
+ {
+ "ShouldDetectMessageCharacteristicsLongLine",
+ MIMECharacteristics{LongLines: true},
+ createMIMEBytes(false, true, 3, 1200),
+ },
+ {
+ "ShouldDetectMessageCharacteristicsLF",
+ MIMECharacteristics{LineFeeds: true},
+ createMIMEBytes(false, false, 5, 150),
+ },
+ {
+ "ShouldDetectMessageCharacteristics8Bit",
+ MIMECharacteristics{Characters8BIT: true},
+ createMIMEBytes(true, true, 3, 150),
+ },
+ {
+ "ShouldDetectMessageCharacteristicsLongLineAndLF",
+ MIMECharacteristics{true, true, false},
+ createMIMEBytes(false, false, 3, 1200),
+ },
+ {
+ "ShouldDetectMessageCharacteristicsLongLineAnd8Bit",
+ MIMECharacteristics{true, false, true},
+ createMIMEBytes(true, true, 3, 1200),
+ },
+ {
+ "ShouldDetectMessageCharacteristics8BitAndLF",
+ MIMECharacteristics{false, true, true},
+ createMIMEBytes(true, false, 3, 150),
+ },
+ {
+ "ShouldDetectMessageCharacteristicsAll",
+ MIMECharacteristics{true, true, true},
+ createMIMEBytes(true, false, 3, 1200),
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ actual := NewMIMECharacteristics(tc.have)
+
+ assert.Equal(t, tc.expected, actual)
+ })
+ }
+}
+
+func createMIMEBytes(include8bit, crlf bool, lines, length int) []byte {
+ buf := &bytes.Buffer{}
+
+ for i := 0; i < lines; i++ {
+ for j := 0; j < length/100; j++ {
+ switch {
+ case include8bit:
+ buf.Write(utils.RandomBytes(50, utils.CharSetAlphaNumeric, false))
+ buf.Write([]byte("£"))
+ buf.Write(utils.RandomBytes(49, utils.CharSetAlphabetic, false))
+ default:
+ buf.Write(utils.RandomBytes(100, utils.CharSetAlphaNumeric, false))
+ }
+ }
+
+ if n := length % 100; n != 0 {
+ buf.Write(utils.RandomBytes(n, utils.CharSetAlphaNumeric, false))
+ }
+
+ switch {
+ case crlf:
+ buf.Write([]byte(rfc2822NewLine))
+ default:
+ buf.Write([]byte("\n"))
+ }
+ }
+
+ return buf.Bytes()
+}
diff --git a/internal/oidc/client.go b/internal/oidc/client.go
index c5b7cb975..e256a9380 100644
--- a/internal/oidc/client.go
+++ b/internal/oidc/client.go
@@ -14,7 +14,7 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
client = &Client{
ID: config.ID,
Description: config.Description,
- Secret: []byte(config.Secret),
+ Secret: config.Secret,
SectorIdentifier: config.SectorIdentifier.String(),
Public: config.Public,
@@ -29,7 +29,7 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
Policy: authorization.StringToLevel(config.Policy),
- PreConfiguredConsentDuration: config.PreConfiguredConsentDuration,
+ Consent: NewClientConsent(config.ConsentMode, config.ConsentPreConfiguredDuration),
}
for _, mode := range config.ResponseModes {
@@ -40,26 +40,30 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
}
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
-func (c Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
+func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
+ if level == authentication.NotAuthenticated {
+ return false
+ }
+
return authorization.IsAuthLevelSufficient(level, c.Policy)
}
// GetID returns the ID.
-func (c Client) GetID() string {
+func (c *Client) GetID() string {
return c.ID
}
// GetSectorIdentifier returns the SectorIdentifier for this client.
-func (c Client) GetSectorIdentifier() string {
+func (c *Client) GetSectorIdentifier() string {
return c.SectorIdentifier
}
// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession.
-func (c Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody {
+func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody {
body := ConsentGetResponseBody{
ClientID: c.ID,
ClientDescription: c.Description,
- PreConfiguration: c.PreConfiguredConsentDuration != nil,
+ PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured,
}
if consent != nil {
@@ -71,17 +75,21 @@ func (c Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) Cons
}
// GetHashedSecret returns the Secret.
-func (c Client) GetHashedSecret() []byte {
- return c.Secret
+func (c *Client) GetHashedSecret() []byte {
+ if c.Secret == nil {
+ return []byte(nil)
+ }
+
+ return []byte(c.Secret.Encode())
}
// GetRedirectURIs returns the RedirectURIs.
-func (c Client) GetRedirectURIs() []string {
+func (c *Client) GetRedirectURIs() []string {
return c.RedirectURIs
}
// GetGrantTypes returns the GrantTypes.
-func (c Client) GetGrantTypes() fosite.Arguments {
+func (c *Client) GetGrantTypes() fosite.Arguments {
if len(c.GrantTypes) == 0 {
return fosite.Arguments{"authorization_code"}
}
@@ -90,7 +98,7 @@ func (c Client) GetGrantTypes() fosite.Arguments {
}
// GetResponseTypes returns the ResponseTypes.
-func (c Client) GetResponseTypes() fosite.Arguments {
+func (c *Client) GetResponseTypes() fosite.Arguments {
if len(c.ResponseTypes) == 0 {
return fosite.Arguments{"code"}
}
@@ -99,23 +107,23 @@ func (c Client) GetResponseTypes() fosite.Arguments {
}
// GetScopes returns the Scopes.
-func (c Client) GetScopes() fosite.Arguments {
+func (c *Client) GetScopes() fosite.Arguments {
return c.Scopes
}
// IsPublic returns the value of the Public property.
-func (c Client) IsPublic() bool {
+func (c *Client) IsPublic() bool {
return c.Public
}
// GetAudience returns the Audience.
-func (c Client) GetAudience() fosite.Arguments {
+func (c *Client) GetAudience() fosite.Arguments {
return c.Audience
}
// GetResponseModes returns the valid response modes for this client.
//
// Implements the fosite.ResponseModeClient.
-func (c Client) GetResponseModes() []fosite.ResponseModeType {
+func (c *Client) GetResponseModes() []fosite.ResponseModeType {
return c.ResponseModes
}
diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go
index aaacd5cdf..01722d21a 100644
--- a/internal/oidc/client_test.go
+++ b/internal/oidc/client_test.go
@@ -26,7 +26,7 @@ func TestNewClient(t *testing.T) {
ID: "myapp",
Description: "My App",
Policy: "two_factor",
- Secret: "abcdef",
+ Secret: MustDecodeSecret("$plaintext$abcdef"),
RedirectURIs: []string{"https://google.com/callback"},
Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes,
ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes,
@@ -48,7 +48,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) {
c := Client{}
c.Policy = authorization.Bypass
- assert.True(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
+ assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
@@ -68,7 +68,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) {
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
}
-func TestInternalClient_GetConsentResponseBody(t *testing.T) {
+func TestClient_GetConsentResponseBody(t *testing.T) {
c := Client{}
consentRequestBody := c.GetConsentResponseBody(nil)
@@ -95,7 +95,7 @@ func TestInternalClient_GetConsentResponseBody(t *testing.T) {
assert.Equal(t, expectedAudiences, consentRequestBody.Audience)
}
-func TestInternalClient_GetAudience(t *testing.T) {
+func TestClient_GetAudience(t *testing.T) {
c := Client{}
audience := c.GetAudience()
@@ -108,7 +108,7 @@ func TestInternalClient_GetAudience(t *testing.T) {
assert.Equal(t, "https://example.com", audience[0])
}
-func TestInternalClient_GetScopes(t *testing.T) {
+func TestClient_GetScopes(t *testing.T) {
c := Client{}
scopes := c.GetScopes()
@@ -121,7 +121,7 @@ func TestInternalClient_GetScopes(t *testing.T) {
assert.Equal(t, "openid", scopes[0])
}
-func TestInternalClient_GetGrantTypes(t *testing.T) {
+func TestClient_GetGrantTypes(t *testing.T) {
c := Client{}
grantTypes := c.GetGrantTypes()
@@ -135,19 +135,30 @@ func TestInternalClient_GetGrantTypes(t *testing.T) {
assert.Equal(t, "device_code", grantTypes[0])
}
-func TestInternalClient_GetHashedSecret(t *testing.T) {
+func TestClient_Hashing(t *testing.T) {
c := Client{}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
- c.Secret = []byte("a_bad_secret")
+ c.Secret = MustDecodeSecret("$plaintext$a_bad_secret")
- hashedSecret = c.GetHashedSecret()
- assert.Equal(t, []byte("a_bad_secret"), hashedSecret)
+ assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret")))
}
-func TestInternalClient_GetID(t *testing.T) {
+func TestClient_GetHashedSecret(t *testing.T) {
+ c := Client{}
+
+ hashedSecret := c.GetHashedSecret()
+ assert.Equal(t, []byte(nil), hashedSecret)
+
+ c.Secret = MustDecodeSecret("$plaintext$a_bad_secret")
+
+ hashedSecret = c.GetHashedSecret()
+ assert.Equal(t, []byte("$plaintext$a_bad_secret"), hashedSecret)
+}
+
+func TestClient_GetID(t *testing.T) {
c := Client{}
id := c.GetID()
@@ -159,7 +170,7 @@ func TestInternalClient_GetID(t *testing.T) {
assert.Equal(t, "myid", id)
}
-func TestInternalClient_GetRedirectURIs(t *testing.T) {
+func TestClient_GetRedirectURIs(t *testing.T) {
c := Client{}
redirectURIs := c.GetRedirectURIs()
@@ -172,7 +183,7 @@ func TestInternalClient_GetRedirectURIs(t *testing.T) {
assert.Equal(t, "https://example.com/oauth2/callback", redirectURIs[0])
}
-func TestInternalClient_GetResponseModes(t *testing.T) {
+func TestClient_GetResponseModes(t *testing.T) {
c := Client{}
responseModes := c.GetResponseModes()
@@ -191,7 +202,7 @@ func TestInternalClient_GetResponseModes(t *testing.T) {
assert.Equal(t, fosite.ResponseModeFragment, responseModes[3])
}
-func TestInternalClient_GetResponseTypes(t *testing.T) {
+func TestClient_GetResponseTypes(t *testing.T) {
c := Client{}
responseTypes := c.GetResponseTypes()
@@ -206,7 +217,7 @@ func TestInternalClient_GetResponseTypes(t *testing.T) {
assert.Equal(t, "id_token", responseTypes[1])
}
-func TestInternalClient_IsPublic(t *testing.T) {
+func TestClient_IsPublic(t *testing.T) {
c := Client{}
assert.False(t, c.IsPublic())
@@ -214,3 +225,11 @@ func TestInternalClient_IsPublic(t *testing.T) {
c.Public = true
assert.True(t, c.IsPublic())
}
+
+func MustDecodeSecret(value string) *schema.PasswordDigest {
+ if secret, err := schema.NewPasswordDigest(value, true); err != nil {
+ panic(err)
+ } else {
+ return secret
+ }
+}
diff --git a/internal/oidc/const.go b/internal/oidc/const.go
index 72d395dd1..c61396d4a 100644
--- a/internal/oidc/const.go
+++ b/internal/oidc/const.go
@@ -9,38 +9,101 @@ const (
ScopeGroups = "groups"
)
-// Claim strings.
+// Registered Claim strings. See https://www.iana.org/assignments/jwt/jwt.xhtml.
const (
- ClaimGroups = "groups"
- ClaimDisplayName = "name"
- ClaimPreferredUsername = "preferred_username"
- ClaimEmail = "email"
- ClaimEmailVerified = "email_verified"
- ClaimEmailAlts = "alt_emails"
+ ClaimJWTID = "jti"
+ ClaimSessionID = "sid"
+ ClaimAccessTokenHash = "at_hash"
+ ClaimCodeHash = "c_hash"
+ ClaimIssuedAt = "iat"
+ ClaimNotBefore = "nbf"
+ ClaimRequestedAt = "rat"
+ ClaimExpirationTime = "exp"
+ ClaimAuthenticationTime = "auth_time"
+ ClaimIssuer = "iss"
+ ClaimSubject = "sub"
+ ClaimNonce = "nonce"
+ ClaimAudience = "aud"
+ ClaimGroups = "groups"
+ ClaimFullName = "name"
+ ClaimPreferredUsername = "preferred_username"
+ ClaimPreferredEmail = "email"
+ ClaimEmailVerified = "email_verified"
+ ClaimAuthorizedParty = "azp"
+ ClaimAuthenticationContextClassReference = "acr"
+ ClaimAuthenticationMethodsReference = "amr"
+ ClaimClientIdentifier = "client_id"
+)
+
+const (
+ // ClaimEmailAlts is an unregistered/custom claim.
+ // It represents the emails which are not considered primary.
+ ClaimEmailAlts = "alt_emails"
+)
+
+// Response Mode strings.
+const (
+ ResponseModeQuery = "query"
+ ResponseModeFormPost = "form_post"
+ ResponseModeFragment = "fragment"
+)
+
+// Grant Type strings.
+const (
+ GrantTypeImplicit = implicit
+ GrantTypeRefreshToken = "refresh_token"
+ GrantTypeAuthorizationCode = "authorization_code"
+ GrantTypePassword = "password"
+ GrantTypeClientCredentials = "client_credentials"
+)
+
+// Signing Algorithm strings.
+const (
+ SigningAlgorithmNone = none
+ SigningAlgorithmRSAWithSHA256 = "RS256"
+)
+
+// Subject Type strings.
+const (
+ SubjectTypePublic = "public"
+ SubjectTypePairwise = "pairwise"
+)
+
+// Proof Key Code Exchange Challenge Method strings.
+const (
+ PKCEChallengeMethodPlain = "plain"
+ PKCEChallengeMethodSHA256 = "S256"
)
// Endpoints.
const (
- AuthorizationEndpoint = "authorization"
- TokenEndpoint = "token"
- UserinfoEndpoint = "userinfo"
- IntrospectionEndpoint = "introspection"
- RevocationEndpoint = "revocation"
+ EndpointAuthorization = "authorization"
+ EndpointToken = "token"
+ EndpointUserinfo = "userinfo"
+ EndpointIntrospection = "introspection"
+ EndpointRevocation = "revocation"
+)
+
+// JWT Headers.
+const (
+ // JWTHeaderKeyIdentifier is the JWT Header referencing the JWS Key Identifier used to sign a token.
+ JWTHeaderKeyIdentifier = "kid"
)
// Paths.
const (
- WellKnownOpenIDConfigurationPath = "/.well-known/openid-configuration"
- WellKnownOAuthAuthorizationServerPath = "/.well-known/oauth-authorization-server"
- JWKsPath = "/jwks.json"
+ EndpointPathConsent = "/consent"
+ EndpointPathWellKnownOpenIDConfiguration = "/.well-known/openid-configuration"
+ EndpointPathWellKnownOAuthAuthorizationServer = "/.well-known/oauth-authorization-server"
+ EndpointPathJWKs = "/jwks.json"
- RootPath = "/api/oidc"
+ EndpointPathRoot = "/api/oidc"
- AuthorizationPath = RootPath + "/" + AuthorizationEndpoint
- TokenPath = RootPath + "/" + TokenEndpoint
- UserinfoPath = RootPath + "/" + UserinfoEndpoint
- IntrospectionPath = RootPath + "/" + IntrospectionEndpoint
- RevocationPath = RootPath + "/" + RevocationEndpoint
+ EndpointPathAuthorization = EndpointPathRoot + "/" + EndpointAuthorization
+ EndpointPathToken = EndpointPathRoot + "/" + EndpointToken
+ EndpointPathUserinfo = EndpointPathRoot + "/" + EndpointUserinfo
+ EndpointPathIntrospection = EndpointPathRoot + "/" + EndpointIntrospection
+ EndpointPathRevocation = EndpointPathRoot + "/" + EndpointRevocation
)
// Authentication Method Reference Values https://datatracker.ietf.org/doc/html/rfc8176
@@ -136,3 +199,10 @@ const (
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
AMRShortMessageService = "sms"
)
+
+const (
+ implicit = "implicit"
+ explicit = "explicit"
+ preconfigured = "pre-configured"
+ none = "none"
+)
diff --git a/internal/oidc/discovery.go b/internal/oidc/discovery.go
index 145c0da8b..5f311c030 100644
--- a/internal/oidc/discovery.go
+++ b/internal/oidc/discovery.go
@@ -1,11 +1,11 @@
package oidc
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
-func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge, pairwise bool) (config OpenIDConnectWellKnownConfiguration) {
+func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) {
config = OpenIDConnectWellKnownConfiguration{
CommonDiscoveryOptions: CommonDiscoveryOptions{
SubjectTypesSupported: []string{
- "public",
+ SubjectTypePublic,
},
ResponseTypesSupported: []string{
"code",
@@ -18,9 +18,9 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge, pairwise b
"none",
},
ResponseModesSupported: []string{
- "form_post",
- "query",
- "fragment",
+ ResponseModeFormPost,
+ ResponseModeQuery,
+ ResponseModeFragment,
},
ScopesSupported: []string{
ScopeOfflineAccess,
@@ -30,52 +30,64 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge, pairwise b
ScopeEmail,
},
ClaimsSupported: []string{
- "amr",
- "aud",
- "azp",
- "client_id",
- "exp",
- "iat",
- "iss",
- "jti",
- "rat",
- "sub",
- "auth_time",
- "nonce",
- ClaimEmail,
+ ClaimAuthenticationMethodsReference,
+ ClaimAudience,
+ ClaimAuthorizedParty,
+ ClaimClientIdentifier,
+ ClaimExpirationTime,
+ ClaimIssuedAt,
+ ClaimIssuer,
+ ClaimJWTID,
+ ClaimRequestedAt,
+ ClaimSubject,
+ ClaimAuthenticationTime,
+ ClaimNonce,
+ ClaimPreferredEmail,
ClaimEmailVerified,
ClaimEmailAlts,
ClaimGroups,
ClaimPreferredUsername,
- ClaimDisplayName,
+ ClaimFullName,
},
},
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
CodeChallengeMethodsSupported: []string{
- "S256",
+ PKCEChallengeMethodSHA256,
},
},
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
IDTokenSigningAlgValuesSupported: []string{
- "RS256",
+ SigningAlgorithmRSAWithSHA256,
},
UserinfoSigningAlgValuesSupported: []string{
- "none",
- "RS256",
+ SigningAlgorithmNone,
+ SigningAlgorithmRSAWithSHA256,
},
RequestObjectSigningAlgValuesSupported: []string{
- "none",
- "RS256",
+ SigningAlgorithmNone,
+ SigningAlgorithmRSAWithSHA256,
},
},
}
+ var pairwise, public bool
+
+ for _, client := range clients {
+ if pairwise && public {
+ break
+ }
+
+ if client.SectorIdentifier != "" {
+ pairwise = true
+ }
+ }
+
if pairwise {
- config.SubjectTypesSupported = append(config.SubjectTypesSupported, "pairwise")
+ config.SubjectTypesSupported = append(config.SubjectTypesSupported, SubjectTypePairwise)
}
if enablePKCEPlainChallenge {
- config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, "plain")
+ config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
}
return config
diff --git a/internal/oidc/discovery_test.go b/internal/oidc/discovery_test.go
index 256f5f70b..7f99df15c 100644
--- a/internal/oidc/discovery_test.go
+++ b/internal/oidc/discovery_test.go
@@ -8,43 +8,62 @@ import (
func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
testCases := []struct {
- desc string
- pkcePlainChallenge, pairwise bool
+ desc string
+ pkcePlainChallenge bool
+ clients map[string]*Client
+
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
}{
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
pkcePlainChallenge: false,
- pairwise: false,
- expectCodeChallengeMethodsSupported: []string{"S256"},
- expectSubjectTypesSupported: []string{"public"},
+ clients: map[string]*Client{"a": {}},
+ expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
+ expectSubjectTypesSupported: []string{SubjectTypePublic},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
pkcePlainChallenge: true,
- pairwise: false,
- expectCodeChallengeMethodsSupported: []string{"S256", "plain"},
- expectSubjectTypesSupported: []string{"public"},
+ clients: map[string]*Client{"a": {}},
+ expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
+ expectSubjectTypesSupported: []string{SubjectTypePublic},
},
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: false,
- pairwise: true,
- expectCodeChallengeMethodsSupported: []string{"S256"},
- expectSubjectTypesSupported: []string{"public", "pairwise"},
+ clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
+ expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: true,
- pairwise: true,
- expectCodeChallengeMethodsSupported: []string{"S256", "plain"},
- expectSubjectTypesSupported: []string{"public", "pairwise"},
+ clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
+ expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
+ },
+ {
+ desc: "ShouldHaveTokenAuthMethodsNone",
+ pkcePlainChallenge: true,
+ clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
+ expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
+ },
+ {
+ desc: "ShouldHaveTokenAuthMethodsNone",
+ pkcePlainChallenge: true,
+ clients: map[string]*Client{
+ "a": {SectorIdentifier: "yes"},
+ "b": {SectorIdentifier: "yes"},
+ },
+ expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
+ expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
- actual := NewOpenIDConnectWellKnownConfiguration(tc.pkcePlainChallenge, tc.pairwise)
+ actual := NewOpenIDConnectWellKnownConfiguration(tc.pkcePlainChallenge, tc.clients)
for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported {
assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod)
}
diff --git a/internal/oidc/errors.go b/internal/oidc/errors.go
index 239b3e549..465689257 100644
--- a/internal/oidc/errors.go
+++ b/internal/oidc/errors.go
@@ -1,5 +1,19 @@
package oidc
-import "errors"
+import (
+ "errors"
+
+ "github.com/ory/fosite"
+)
var errPasswordsDoNotMatch = errors.New("the passwords don't match")
+
+var (
+ ErrIssuerCouldNotDerive = fosite.ErrServerError.WithHint("Could not safely derive the issuer.")
+ ErrSubjectCouldNotLookup = fosite.ErrServerError.WithHint("Could not lookup user subject.")
+ ErrConsentCouldNotPerform = fosite.ErrServerError.WithHint("Could not perform consent.")
+ ErrConsentCouldNotGenerate = fosite.ErrServerError.WithHint("Could not generate the consent session.")
+ ErrConsentCouldNotSave = fosite.ErrServerError.WithHint("Could not save the consent session.")
+ ErrConsentCouldNotLookup = fosite.ErrServerError.WithHint("Failed to lookup the consent session.")
+ ErrConsentMalformedChallengeID = fosite.ErrServerError.WithHint("Malformed consent session challenge ID.")
+)
diff --git a/internal/oidc/hasher.go b/internal/oidc/hasher.go
index 0bacbe98f..7aad32b25 100644
--- a/internal/oidc/hasher.go
+++ b/internal/oidc/hasher.go
@@ -2,19 +2,26 @@ package oidc
import (
"context"
- "crypto/subtle"
+
+ "github.com/go-crypt/crypt"
)
// Compare compares the hash with the data and returns an error if they don't match.
-func (h PlainTextHasher) Compare(_ context.Context, hash, data []byte) (err error) {
- if subtle.ConstantTimeCompare(hash, data) == 0 {
- return errPasswordsDoNotMatch
+func (h AdaptiveHasher) Compare(_ context.Context, hash, data []byte) (err error) {
+ var digest crypt.Digest
+
+ if digest, err = crypt.DecodeWithPlainText(string(hash)); err != nil {
+ return err
}
- return nil
+ if digest.MatchBytes(data) {
+ return nil
+ }
+
+ return errPasswordsDoNotMatch
}
// Hash creates a new hash from data.
-func (h PlainTextHasher) Hash(_ context.Context, data []byte) (hash []byte, err error) {
+func (h AdaptiveHasher) Hash(_ context.Context, data []byte) (hash []byte, err error) {
return data, nil
}
diff --git a/internal/oidc/hasher_test.go b/internal/oidc/hasher_test.go
index a86123bf4..bc3dfac1b 100644
--- a/internal/oidc/hasher_test.go
+++ b/internal/oidc/hasher_test.go
@@ -8,9 +8,9 @@ import (
)
func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) {
- hasher := PlainTextHasher{}
+ hasher := AdaptiveHasher{}
- a := []byte("abc")
+ a := []byte("$plaintext$abc")
b := []byte("abc")
ctx := context.Background()
@@ -21,9 +21,9 @@ func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) {
}
func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) {
- hasher := PlainTextHasher{}
+ hasher := AdaptiveHasher{}
- a := []byte("abc")
+ a := []byte("$plaintext$abc")
b := []byte("abcd")
ctx := context.Background()
@@ -34,7 +34,7 @@ func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) {
}
func TestShouldHashPassword(t *testing.T) {
- hasher := PlainTextHasher{}
+ hasher := AdaptiveHasher{}
data := []byte("abc")
diff --git a/internal/oidc/keys.go b/internal/oidc/keys.go
index d5351061d..7f5e3d86a 100644
--- a/internal/oidc/keys.go
+++ b/internal/oidc/keys.go
@@ -9,19 +9,17 @@ import (
"strings"
"github.com/ory/fosite/token/jwt"
- jose "gopkg.in/square/go-jose.v2"
+ "gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/configuration/schema"
- "github.com/authelia/authelia/v4/internal/utils"
)
// NewKeyManagerWithConfiguration when provided a schema.OpenIDConnectConfiguration creates a new KeyManager and adds an
// initial key to the manager.
-func NewKeyManagerWithConfiguration(configuration *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) {
+func NewKeyManagerWithConfiguration(config *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) {
manager = NewKeyManager()
- _, _, err = manager.AddActivePrivateKeyData(configuration.IssuerPrivateKey)
- if err != nil {
+ if _, err = manager.AddActiveJWK(config.IssuerCertificateChain, config.IssuerPrivateKey); err != nil {
return nil, err
}
@@ -30,174 +28,152 @@ func NewKeyManagerWithConfiguration(configuration *schema.OpenIDConnectConfigura
// NewKeyManager creates a new empty KeyManager.
func NewKeyManager() (manager *KeyManager) {
- manager = new(KeyManager)
- manager.keys = map[string]*rsa.PrivateKey{}
- manager.keySet = new(jose.JSONWebKeySet)
-
- return manager
+ return &KeyManager{
+ jwks: &jose.JSONWebKeySet{},
+ }
}
-// Strategy returns the RS256JWTStrategy.
-func (m *KeyManager) Strategy() (strategy *RS256JWTStrategy) {
- return m.strategy
+// Strategy returns the fosite jwt.JWTStrategy.
+func (m *KeyManager) Strategy() (strategy jwt.JWTStrategy) {
+ if m.jwk == nil {
+ return nil
+ }
+
+ return m.jwk.Strategy()
}
// GetKeySet returns the joseJSONWebKeySet containing the rsa.PublicKey types.
-func (m *KeyManager) GetKeySet() (keySet *jose.JSONWebKeySet) {
- return m.keySet
+func (m *KeyManager) GetKeySet() (jwks *jose.JSONWebKeySet) {
+ return m.jwks
}
-// GetActiveWebKey obtains the currently active jose.JSONWebKey.
-func (m *KeyManager) GetActiveWebKey() (webKey *jose.JSONWebKey, err error) {
- webKeys := m.keySet.Key(m.activeKeyID)
- if len(webKeys) == 1 {
- return &webKeys[0], nil
+// GetActiveJWK obtains the currently active jose.JSONWebKey.
+func (m *KeyManager) GetActiveJWK() (jwk *jose.JSONWebKey, err error) {
+ if m.jwks == nil || m.jwk == nil {
+ return nil, errors.New("could not obtain the active JWK from an improperly configured key manager")
}
- if len(webKeys) == 0 {
+ jwks := m.jwks.Key(m.jwk.id)
+
+ if len(jwks) == 1 {
+ return &jwks[0], nil
+ }
+
+ if len(jwks) == 0 {
return nil, errors.New("could not find a key with the active key id")
}
- return &webKeys[0], errors.New("multiple keys with the same key id")
+ return nil, errors.New("multiple keys with the same key id")
}
// GetActiveKeyID returns the key id of the currently active key.
func (m *KeyManager) GetActiveKeyID() (keyID string) {
- return m.activeKeyID
-}
-
-// GetActiveKey returns the rsa.PublicKey of the currently active key.
-func (m *KeyManager) GetActiveKey() (key *rsa.PublicKey, err error) {
- if key, ok := m.keys[m.activeKeyID]; ok {
- return &key.PublicKey, nil
+ if m.jwk == nil {
+ return ""
}
- return nil, errors.New("failed to retrieve active public key")
+ return m.jwk.id
}
// GetActivePrivateKey returns the rsa.PrivateKey of the currently active key.
func (m *KeyManager) GetActivePrivateKey() (key *rsa.PrivateKey, err error) {
- if key, ok := m.keys[m.activeKeyID]; ok {
- return key, nil
+ if m.jwk == nil {
+ return nil, errors.New("failed to retrieve active private key")
}
- return nil, errors.New("failed to retrieve active private key")
+ return m.jwk.key, nil
}
-// AddActivePrivateKeyData adds a rsa.PublicKey given the key in the PEM string format, then sets it to the active key.
-func (m *KeyManager) AddActivePrivateKeyData(data string) (key *rsa.PrivateKey, webKey *jose.JSONWebKey, err error) {
- ikey, err := utils.ParseX509FromPEM([]byte(data))
- if err != nil {
- return nil, nil, err
- }
-
- var ok bool
-
- if key, ok = ikey.(*rsa.PrivateKey); !ok {
- return nil, nil, errors.New("key must be an RSA private key")
- }
-
- webKey, err = m.AddActivePrivateKey(key)
-
- return key, webKey, err
-}
-
-// AddActivePrivateKey adds a rsa.PublicKey, then sets it to the active key.
-func (m *KeyManager) AddActivePrivateKey(key *rsa.PrivateKey) (webKey *jose.JSONWebKey, err error) {
- wk := jose.JSONWebKey{
- Key: &key.PublicKey,
- Algorithm: "RS256",
- Use: "sig",
- }
-
- keyID, err := wk.Thumbprint(crypto.SHA1)
- if err != nil {
+// AddActiveJWK is used to add a cert and key pair.
+func (m *KeyManager) AddActiveJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (jwk *JWK, err error) {
+ // TODO: Add a mutex when implementing key rotation to be utilized here and in methods which retrieve the JWK or JWKS.
+ if m.jwk, err = NewJWK(chain, key); err != nil {
return nil, err
}
- strKeyID := strings.ToLower(fmt.Sprintf("%x", keyID))
- if len(strKeyID) >= 7 {
- // Shorten the key if it's greater than 7 to a length of exactly 7.
- strKeyID = strKeyID[0:6]
- }
+ m.jwks.Keys = append(m.jwks.Keys, *m.jwk.JSONWebKey())
- if _, ok := m.keys[strKeyID]; ok {
- return nil, fmt.Errorf("key id %s already exists", strKeyID)
- }
-
- // TODO: Add Mutex here when implementing key rotation.
- wk.KeyID = strKeyID
- m.keySet.Keys = append(m.keySet.Keys, wk)
- m.keys[strKeyID] = key
- m.activeKeyID = strKeyID
-
- m.strategy, err = NewRS256JWTStrategy(wk.KeyID, key)
- if err != nil {
- return &wk, err
- }
-
- return &wk, nil
+ return m.jwk, nil
}
-// NewRS256JWTStrategy returns a new RS256JWTStrategy.
-func NewRS256JWTStrategy(id string, key *rsa.PrivateKey) (strategy *RS256JWTStrategy, err error) {
- strategy = new(RS256JWTStrategy)
- strategy.JWTStrategy = new(jwt.RS256JWTStrategy)
+// JWTStrategy is a decorator struct for the fosite jwt.JWTStrategy.
+type JWTStrategy struct {
+ jwt.JWTStrategy
- strategy.SetKey(id, key)
-
- return strategy, nil
-}
-
-// RS256JWTStrategy is a decorator struct for the fosite RS256JWTStrategy.
-type RS256JWTStrategy struct {
- JWTStrategy *jwt.RS256JWTStrategy
-
- keyID string
+ id string
}
// KeyID returns the key id.
-func (s RS256JWTStrategy) KeyID() (id string) {
- return s.keyID
-}
-
-// SetKey sets the provided key id and key as the active key (this is what triggers fosite to use it).
-func (s *RS256JWTStrategy) SetKey(id string, key *rsa.PrivateKey) {
- s.keyID = id
- s.JWTStrategy.PrivateKey = key
-}
-
-// Hash is a decorator func for the underlying fosite RS256JWTStrategy.
-func (s *RS256JWTStrategy) Hash(ctx context.Context, in []byte) ([]byte, error) {
- return s.JWTStrategy.Hash(ctx, in)
-}
-
-// GetSigningMethodLength is a decorator func for the underlying fosite RS256JWTStrategy.
-func (s *RS256JWTStrategy) GetSigningMethodLength() int {
- return s.JWTStrategy.GetSigningMethodLength()
-}
-
-// GetSignature is a decorator func for the underlying fosite RS256JWTStrategy.
-func (s *RS256JWTStrategy) GetSignature(ctx context.Context, token string) (string, error) {
- return s.JWTStrategy.GetSignature(ctx, token)
-}
-
-// Generate is a decorator func for the underlying fosite RS256JWTStrategy.
-func (s *RS256JWTStrategy) Generate(ctx context.Context, claims jwt.MapClaims, header jwt.Mapper) (string, string, error) {
- return s.JWTStrategy.Generate(ctx, claims, header)
-}
-
-// Validate is a decorator func for the underlying fosite RS256JWTStrategy.
-func (s *RS256JWTStrategy) Validate(ctx context.Context, token string) (string, error) {
- return s.JWTStrategy.Validate(ctx, token)
-}
-
-// Decode is a decorator func for the underlying fosite RS256JWTStrategy.
-func (s *RS256JWTStrategy) Decode(ctx context.Context, token string) (*jwt.Token, error) {
- return s.JWTStrategy.Decode(ctx, token)
+func (s *JWTStrategy) KeyID() (id string) {
+ return s.id
}
// GetPublicKeyID is a decorator func for the underlying fosite RS256JWTStrategy.
-func (s *RS256JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) {
- return s.keyID, nil
+func (s *JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) {
+ return s.id, nil
+}
+
+// NewJWK creates a new JWK.
+func NewJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (j *JWK, err error) {
+ if key == nil {
+ return nil, fmt.Errorf("JWK is not properly initialized: missing key")
+ }
+
+ j = &JWK{
+ key: key,
+ chain: chain,
+ }
+
+ jwk := &jose.JSONWebKey{
+ Algorithm: SigningAlgorithmRSAWithSHA256,
+ Use: "sig",
+ Key: &key.PublicKey,
+ }
+
+ var thumbprint []byte
+
+ if thumbprint, err = jwk.Thumbprint(crypto.SHA1); err != nil {
+ return nil, fmt.Errorf("failed to calculate SHA1 thumbprint for certificate: %w", err)
+ }
+
+ j.id = strings.ToLower(fmt.Sprintf("%x", thumbprint))
+
+ if len(j.id) >= 7 {
+ j.id = j.id[:6]
+ }
+
+ if len(j.id) >= 7 {
+ j.id = j.id[:6]
+ }
+
+ return j, nil
+}
+
+// JWK is a utility wrapper for JSON Web Key's.
+type JWK struct {
+ id string
+ key *rsa.PrivateKey
+ chain schema.X509CertificateChain
+}
+
+// Strategy returns the relevant jwt.JWTStrategy for this JWT.
+func (j *JWK) Strategy() (strategy jwt.JWTStrategy) {
+ return &JWTStrategy{id: j.id, JWTStrategy: &jwt.RS256JWTStrategy{PrivateKey: j.key}}
+}
+
+// JSONWebKey returns the relevant *jose.JSONWebKey for this JWT.
+func (j *JWK) JSONWebKey() (jwk *jose.JSONWebKey) {
+ jwk = &jose.JSONWebKey{
+ Key: &j.key.PublicKey,
+ KeyID: j.id,
+ Algorithm: "RS256",
+ Use: "sig",
+ Certificates: j.chain.Certificates(),
+ }
+
+ if len(jwk.Certificates) != 0 {
+ jwk.CertificateThumbprintSHA1, jwk.CertificateThumbprintSHA256 = j.chain.Thumbprint(crypto.SHA1), j.chain.Thumbprint(crypto.SHA256)
+ }
+
+ return jwk
}
diff --git a/internal/oidc/keys_test.go b/internal/oidc/keys_test.go
index 8ddfd71f8..1e15973ea 100644
--- a/internal/oidc/keys_test.go
+++ b/internal/oidc/keys_test.go
@@ -8,42 +8,37 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+
+ "github.com/authelia/authelia/v4/internal/configuration/schema"
)
-func TestKeyManager_AddActiveKeyData(t *testing.T) {
+func TestKeyManager_AddActiveJWK(t *testing.T) {
manager := NewKeyManager()
- assert.Nil(t, manager.strategy)
+ assert.Nil(t, manager.jwk)
assert.Nil(t, manager.Strategy())
- key, wk, err := manager.AddActivePrivateKeyData(exampleIssuerPrivateKey)
+ j, err := manager.AddActiveJWK(schema.X509CertificateChain{}, mustParseRSAPrivateKey(exampleIssuerPrivateKey))
require.NoError(t, err)
- require.NotNil(t, key)
- require.NotNil(t, wk)
- require.NotNil(t, manager.strategy)
+ require.NotNil(t, j)
+ require.NotNil(t, manager.jwk)
require.NotNil(t, manager.Strategy())
- thumbprint, err := wk.Thumbprint(crypto.SHA1)
+ thumbprint, err := j.JSONWebKey().Thumbprint(crypto.SHA1)
assert.NoError(t, err)
- kid := strings.ToLower(fmt.Sprintf("%x", thumbprint)[0:6])
- assert.Equal(t, manager.activeKeyID, kid)
- assert.Equal(t, kid, wk.KeyID)
- assert.Len(t, manager.keys, 1)
- assert.Len(t, manager.keySet.Keys, 1)
- assert.Contains(t, manager.keys, kid)
+ kid := strings.ToLower(fmt.Sprintf("%x", thumbprint)[:6])
+ assert.Equal(t, manager.jwk.id, kid)
+ assert.Equal(t, kid, j.JSONWebKey().KeyID)
+ assert.Len(t, manager.jwks.Keys, 1)
- keys := manager.keySet.Key(kid)
+ keys := manager.jwks.Key(kid)
assert.Equal(t, keys[0].KeyID, kid)
privKey, err := manager.GetActivePrivateKey()
assert.NoError(t, err)
assert.NotNil(t, privKey)
- pubKey, err := manager.GetActiveKey()
- assert.NoError(t, err)
- assert.NotNil(t, pubKey)
-
- webKey, err := manager.GetActiveWebKey()
+ webKey, err := manager.GetActiveJWK()
assert.NoError(t, err)
assert.NotNil(t, webKey)
diff --git a/internal/oidc/provider.go b/internal/oidc/provider.go
index 670d1e42c..9273df318 100644
--- a/internal/oidc/provider.go
+++ b/internal/oidc/provider.go
@@ -1,10 +1,13 @@
package oidc
import (
+ "crypto/sha512"
"fmt"
- "net/http"
"github.com/ory/fosite/compose"
+ "github.com/ory/fosite/handler/oauth2"
+ "github.com/ory/fosite/handler/openid"
+ "github.com/ory/fosite/token/hmac"
"github.com/ory/herodot"
"github.com/authelia/authelia/v4/internal/configuration/schema"
@@ -13,18 +16,17 @@ import (
)
// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider.
-func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, storageProvider storage.Provider) (provider OpenIDConnectProvider, err error) {
- provider = OpenIDConnectProvider{
- Fosite: nil,
- }
-
+func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store storage.Provider) (provider *OpenIDConnectProvider, err error) {
if config == nil {
- return provider, nil
+ return nil, nil
}
- provider.Store = NewOpenIDConnectStore(config, storageProvider)
+ provider = &OpenIDConnectProvider{
+ JSONWriter: herodot.NewJSONWriter(nil),
+ Store: NewOpenIDConnectStore(config, store),
+ }
- composeConfiguration := &compose.Config{
+ cconfig := &compose.Config{
AccessTokenLifespan: config.AccessTokenLifespan,
AuthorizeCodeLifespan: config.AuthorizeCodeLifespan,
IDTokenLifespan: config.IDTokenLifespan,
@@ -36,36 +38,38 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, storage
EnablePKCEPlainChallengeMethod: config.EnablePKCEPlainChallenge,
}
- keyManager, err := NewKeyManagerWithConfiguration(config)
- if err != nil {
- return provider, err
+ if provider.KeyManager, err = NewKeyManagerWithConfiguration(config); err != nil {
+ return nil, err
}
- provider.KeyManager = keyManager
-
- key, err := provider.KeyManager.GetActivePrivateKey()
- if err != nil {
- return provider, err
- }
+ jwtStrategy := provider.KeyManager.Strategy()
strategy := &compose.CommonStrategy{
- CoreStrategy: compose.NewOAuth2HMACStrategy(
- composeConfiguration,
- []byte(utils.HashSHA256FromString(config.HMACSecret)),
- nil,
- ),
- OpenIDConnectTokenStrategy: compose.NewOpenIDConnectStrategy(
- composeConfiguration,
- key,
- ),
- JWTStrategy: provider.KeyManager.Strategy(),
+ CoreStrategy: &oauth2.HMACSHAStrategy{
+ Enigma: &hmac.HMACStrategy{
+ GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
+ RotatedGlobalSecrets: nil,
+ TokenEntropy: cconfig.GetTokenEntropy(),
+ Hash: sha512.New512_256,
+ },
+ AccessTokenLifespan: cconfig.GetAccessTokenLifespan(),
+ AuthorizeCodeLifespan: cconfig.GetAuthorizeCodeLifespan(),
+ RefreshTokenLifespan: cconfig.GetRefreshTokenLifespan(),
+ },
+ OpenIDConnectTokenStrategy: &openid.DefaultStrategy{
+ JWTStrategy: jwtStrategy,
+ Expiry: cconfig.GetIDTokenLifespan(),
+ Issuer: cconfig.IDTokenIssuer,
+ MinParameterEntropy: cconfig.GetMinParameterEntropy(),
+ },
+ JWTStrategy: jwtStrategy,
}
- provider.Fosite = compose.Compose(
- composeConfiguration,
+ provider.OAuth2Provider = compose.Compose(
+ cconfig,
provider.Store,
strategy,
- PlainTextHasher{},
+ AdaptiveHasher{},
/*
These are the OAuth2 and OpenIDConnect factories. Order is important (the OAuth2 factories at the top must
@@ -90,60 +94,32 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, storage
compose.OAuth2PKCEFactory,
)
- provider.discovery = NewOpenIDConnectWellKnownConfiguration(config.EnablePKCEPlainChallenge, provider.Pairwise())
-
- provider.herodot = herodot.NewJSONWriter(nil)
+ provider.discovery = NewOpenIDConnectWellKnownConfiguration(config.EnablePKCEPlainChallenge, provider.Store.clients)
return provider, nil
}
-// Pairwise returns true if this provider is configured with clients that require pairwise.
-func (p OpenIDConnectProvider) Pairwise() bool {
- for _, c := range p.Store.clients {
- if c.SectorIdentifier != "" {
- return true
- }
- }
-
- return false
-}
-
-// Write writes data with herodot.JSONWriter.
-func (p OpenIDConnectProvider) Write(w http.ResponseWriter, r *http.Request, e interface{}, opts ...herodot.EncoderOptions) {
- p.herodot.Write(w, r, e, opts...)
-}
-
-// WriteError writes an error with herodot.JSONWriter.
-func (p OpenIDConnectProvider) WriteError(w http.ResponseWriter, r *http.Request, err error, opts ...herodot.Option) {
- p.herodot.WriteError(w, r, err, opts...)
-}
-
-// WriteErrorCode writes an error with an error code with herodot.JSONWriter.
-func (p OpenIDConnectProvider) WriteErrorCode(w http.ResponseWriter, r *http.Request, code int, err error, opts ...herodot.Option) {
- p.herodot.WriteErrorCode(w, r, code, err, opts...)
-}
-
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.
-func (p OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) OAuth2WellKnownConfiguration {
+func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) OAuth2WellKnownConfiguration {
options := OAuth2WellKnownConfiguration{
CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions,
OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions,
}
options.Issuer = issuer
- options.JWKSURI = fmt.Sprintf("%s%s", issuer, JWKsPath)
+ options.JWKSURI = fmt.Sprintf("%s%s", issuer, EndpointPathJWKs)
- options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, IntrospectionPath)
- options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, TokenPath)
+ options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection)
+ options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken)
- options.AuthorizationEndpoint = fmt.Sprintf("%s%s", issuer, AuthorizationPath)
- options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, RevocationPath)
+ options.AuthorizationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathAuthorization)
+ options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathRevocation)
return options
}
// GetOpenIDConnectWellKnownConfiguration returns the discovery document for the OpenID Configuration.
-func (p OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer string) OpenIDConnectWellKnownConfiguration {
+func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer string) OpenIDConnectWellKnownConfiguration {
options := OpenIDConnectWellKnownConfiguration{
CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions,
OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions,
@@ -153,14 +129,14 @@ func (p OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer str
}
options.Issuer = issuer
- options.JWKSURI = fmt.Sprintf("%s%s", issuer, JWKsPath)
+ options.JWKSURI = fmt.Sprintf("%s%s", issuer, EndpointPathJWKs)
- options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, IntrospectionPath)
- options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, TokenPath)
+ options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection)
+ options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken)
- options.AuthorizationEndpoint = fmt.Sprintf("%s%s", issuer, AuthorizationPath)
- options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, RevocationPath)
- options.UserinfoEndpoint = fmt.Sprintf("%s%s", issuer, UserinfoPath)
+ options.AuthorizationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathAuthorization)
+ options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathRevocation)
+ options.UserinfoEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathUserinfo)
return options
}
diff --git a/internal/oidc/provider_test.go b/internal/oidc/provider_test.go
index f67a73797..e4a486eb5 100644
--- a/internal/oidc/provider_test.go
+++ b/internal/oidc/provider_test.go
@@ -1,6 +1,9 @@
package oidc
import (
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
"net/url"
"testing"
@@ -16,27 +19,19 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing
provider, err := NewOpenIDConnectProvider(nil, nil)
assert.NoError(t, err)
- assert.Nil(t, provider.Fosite)
- assert.Nil(t, provider.Store)
-}
-
-func TestOpenIDConnectProvider_NewOpenIDConnectProvider_BadIssuerKey(t *testing.T) {
- _, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: "BAD KEY",
- }, nil)
-
- assert.Error(t, err, "abc")
+ assert.Nil(t, provider)
}
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
EnablePKCEPlainChallenge: true,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
- Secret: "a-client-secret",
+ Secret: MustDecodeSecret("$plaintext$a-client-secret"),
SectorIdentifier: url.URL{Host: "google.com"},
Policy: "one_factor",
RedirectURIs: []string{
@@ -48,27 +43,26 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
assert.NoError(t, err)
- assert.True(t, provider.Pairwise())
-
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
assert.Len(t, disco.SubjectTypesSupported, 2)
- assert.Contains(t, disco.SubjectTypesSupported, "public")
- assert.Contains(t, disco.SubjectTypesSupported, "pairwise")
+ assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
+ assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
assert.Len(t, disco.CodeChallengeMethodsSupported, 2)
- assert.Contains(t, disco.CodeChallengeMethodsSupported, "S256")
- assert.Contains(t, disco.CodeChallengeMethodsSupported, "S256")
+ assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256)
+ assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
- HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
+ HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
- Secret: "a-client-secret",
+ Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: "one_factor",
RedirectURIs: []string{
"https://google.com",
@@ -77,16 +71,16 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
{
ID: "b-client",
Description: "Normal Description",
- Secret: "b-client-secret",
+ Secret: MustDecodeSecret("$plaintext$b-client-secret"),
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com",
},
Scopes: []string{
- "groups",
+ ScopeGroups,
},
GrantTypes: []string{
- "refresh_token",
+ GrantTypeRefreshToken,
},
ResponseTypes: []string{
"token",
@@ -102,12 +96,13 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
- HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
+ HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
- Secret: "a-client-secret",
+ Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: "one_factor",
RedirectURIs: []string{
"https://google.com",
@@ -118,8 +113,6 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.NoError(t, err)
- assert.False(t, provider.Pairwise())
-
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
assert.Equal(t, "https://example.com", disco.Issuer)
@@ -132,7 +125,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Equal(t, "", disco.RegistrationEndpoint)
assert.Len(t, disco.CodeChallengeMethodsSupported, 1)
- assert.Contains(t, disco.CodeChallengeMethodsSupported, "S256")
+ assert.Contains(t, disco.CodeChallengeMethodsSupported, PKCEChallengeMethodSHA256)
assert.Len(t, disco.ScopesSupported, 5)
assert.Contains(t, disco.ScopesSupported, ScopeOpenID)
@@ -142,12 +135,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.ScopesSupported, ScopeEmail)
assert.Len(t, disco.ResponseModesSupported, 3)
- assert.Contains(t, disco.ResponseModesSupported, "form_post")
- assert.Contains(t, disco.ResponseModesSupported, "query")
- assert.Contains(t, disco.ResponseModesSupported, "fragment")
+ assert.Contains(t, disco.ResponseModesSupported, ResponseModeFormPost)
+ assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
+ assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
assert.Len(t, disco.SubjectTypesSupported, 1)
- assert.Contains(t, disco.SubjectTypesSupported, "public")
+ assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Len(t, disco.ResponseTypesSupported, 8)
assert.Contains(t, disco.ResponseTypesSupported, "code")
@@ -160,45 +153,46 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.ResponseTypesSupported, "none")
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
- assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, "RS256")
+ assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
- assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, "RS256")
- assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, "none")
+ assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
+ assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone)
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
- assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, "RS256")
- assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, "none")
+ assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
+ assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmNone)
assert.Len(t, disco.ClaimsSupported, 18)
- assert.Contains(t, disco.ClaimsSupported, "amr")
- assert.Contains(t, disco.ClaimsSupported, "aud")
- assert.Contains(t, disco.ClaimsSupported, "azp")
- assert.Contains(t, disco.ClaimsSupported, "client_id")
- assert.Contains(t, disco.ClaimsSupported, "exp")
- assert.Contains(t, disco.ClaimsSupported, "iat")
- assert.Contains(t, disco.ClaimsSupported, "iss")
- assert.Contains(t, disco.ClaimsSupported, "jti")
- assert.Contains(t, disco.ClaimsSupported, "rat")
- assert.Contains(t, disco.ClaimsSupported, "sub")
- assert.Contains(t, disco.ClaimsSupported, "auth_time")
- assert.Contains(t, disco.ClaimsSupported, "nonce")
- assert.Contains(t, disco.ClaimsSupported, ClaimEmail)
+ assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
+ assert.Contains(t, disco.ClaimsSupported, ClaimAudience)
+ assert.Contains(t, disco.ClaimsSupported, ClaimAuthorizedParty)
+ assert.Contains(t, disco.ClaimsSupported, ClaimClientIdentifier)
+ assert.Contains(t, disco.ClaimsSupported, ClaimExpirationTime)
+ assert.Contains(t, disco.ClaimsSupported, ClaimIssuedAt)
+ assert.Contains(t, disco.ClaimsSupported, ClaimIssuer)
+ assert.Contains(t, disco.ClaimsSupported, ClaimJWTID)
+ assert.Contains(t, disco.ClaimsSupported, ClaimRequestedAt)
+ assert.Contains(t, disco.ClaimsSupported, ClaimSubject)
+ assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationTime)
+ assert.Contains(t, disco.ClaimsSupported, ClaimNonce)
+ assert.Contains(t, disco.ClaimsSupported, ClaimPreferredEmail)
assert.Contains(t, disco.ClaimsSupported, ClaimEmailVerified)
assert.Contains(t, disco.ClaimsSupported, ClaimEmailAlts)
assert.Contains(t, disco.ClaimsSupported, ClaimGroups)
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername)
- assert.Contains(t, disco.ClaimsSupported, ClaimDisplayName)
+ assert.Contains(t, disco.ClaimsSupported, ClaimFullName)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
- HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
+ HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
- Secret: "a-client-secret",
+ Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: "one_factor",
RedirectURIs: []string{
"https://google.com",
@@ -230,12 +224,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.Contains(t, disco.ScopesSupported, ScopeEmail)
assert.Len(t, disco.ResponseModesSupported, 3)
- assert.Contains(t, disco.ResponseModesSupported, "form_post")
- assert.Contains(t, disco.ResponseModesSupported, "query")
- assert.Contains(t, disco.ResponseModesSupported, "fragment")
+ assert.Contains(t, disco.ResponseModesSupported, ResponseModeFormPost)
+ assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
+ assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
assert.Len(t, disco.SubjectTypesSupported, 1)
- assert.Contains(t, disco.SubjectTypesSupported, "public")
+ assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
assert.Len(t, disco.ResponseTypesSupported, 8)
assert.Contains(t, disco.ResponseTypesSupported, "code")
@@ -248,35 +242,36 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.Contains(t, disco.ResponseTypesSupported, "none")
assert.Len(t, disco.ClaimsSupported, 18)
- assert.Contains(t, disco.ClaimsSupported, "amr")
- assert.Contains(t, disco.ClaimsSupported, "aud")
- assert.Contains(t, disco.ClaimsSupported, "azp")
- assert.Contains(t, disco.ClaimsSupported, "client_id")
- assert.Contains(t, disco.ClaimsSupported, "exp")
- assert.Contains(t, disco.ClaimsSupported, "iat")
- assert.Contains(t, disco.ClaimsSupported, "iss")
- assert.Contains(t, disco.ClaimsSupported, "jti")
- assert.Contains(t, disco.ClaimsSupported, "rat")
- assert.Contains(t, disco.ClaimsSupported, "sub")
- assert.Contains(t, disco.ClaimsSupported, "auth_time")
- assert.Contains(t, disco.ClaimsSupported, "nonce")
- assert.Contains(t, disco.ClaimsSupported, ClaimEmail)
+ assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
+ assert.Contains(t, disco.ClaimsSupported, ClaimAudience)
+ assert.Contains(t, disco.ClaimsSupported, ClaimAuthorizedParty)
+ assert.Contains(t, disco.ClaimsSupported, ClaimClientIdentifier)
+ assert.Contains(t, disco.ClaimsSupported, ClaimExpirationTime)
+ assert.Contains(t, disco.ClaimsSupported, ClaimIssuedAt)
+ assert.Contains(t, disco.ClaimsSupported, ClaimIssuer)
+ assert.Contains(t, disco.ClaimsSupported, ClaimJWTID)
+ assert.Contains(t, disco.ClaimsSupported, ClaimRequestedAt)
+ assert.Contains(t, disco.ClaimsSupported, ClaimSubject)
+ assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationTime)
+ assert.Contains(t, disco.ClaimsSupported, ClaimNonce)
+ assert.Contains(t, disco.ClaimsSupported, ClaimPreferredEmail)
assert.Contains(t, disco.ClaimsSupported, ClaimEmailVerified)
assert.Contains(t, disco.ClaimsSupported, ClaimEmailAlts)
assert.Contains(t, disco.ClaimsSupported, ClaimGroups)
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername)
- assert.Contains(t, disco.ClaimsSupported, ClaimDisplayName)
+ assert.Contains(t, disco.ClaimsSupported, ClaimFullName)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
EnablePKCEPlainChallenge: true,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
- Secret: "a-client-secret",
+ Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: "one_factor",
RedirectURIs: []string{
"https://google.com",
@@ -290,6 +285,24 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
require.Len(t, disco.CodeChallengeMethodsSupported, 2)
- assert.Equal(t, "S256", disco.CodeChallengeMethodsSupported[0])
- assert.Equal(t, "plain", disco.CodeChallengeMethodsSupported[1])
+ assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
+ assert.Equal(t, PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1])
+}
+
+func mustParseRSAPrivateKey(data string) *rsa.PrivateKey {
+ block, _ := pem.Decode([]byte(data))
+ if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
+ panic("not pem encoded")
+ }
+
+ if block.Type != "RSA PRIVATE KEY" {
+ panic("not private key")
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ return key
}
diff --git a/internal/oidc/store.go b/internal/oidc/store.go
index af3fff7d0..1974e057f 100644
--- a/internal/oidc/store.go
+++ b/internal/oidc/store.go
@@ -18,11 +18,11 @@ import (
"github.com/authelia/authelia/v4/internal/storage"
)
-// NewOpenIDConnectStore returns a OpenIDConnectStore when provided with a schema.OpenIDConnectConfiguration and storage.Provider.
-func NewOpenIDConnectStore(config *schema.OpenIDConnectConfiguration, provider storage.Provider) (store *OpenIDConnectStore) {
+// NewOpenIDConnectStore returns a Store when provided with a schema.OpenIDConnectConfiguration and storage.Provider.
+func NewOpenIDConnectStore(config *schema.OpenIDConnectConfiguration, provider storage.Provider) (store *Store) {
logger := logging.Logger()
- store = &OpenIDConnectStore{
+ store = &Store{
provider: provider,
clients: map[string]*Client{},
}
@@ -38,7 +38,7 @@ func NewOpenIDConnectStore(config *schema.OpenIDConnectConfiguration, provider s
}
// GenerateOpaqueUserID either retrieves or creates an opaque user id from a sectorID and username.
-func (s *OpenIDConnectStore) GenerateOpaqueUserID(ctx context.Context, sectorID, username string) (opaqueID *model.UserOpaqueIdentifier, err error) {
+func (s *Store) GenerateOpaqueUserID(ctx context.Context, sectorID, username string) (opaqueID *model.UserOpaqueIdentifier, err error) {
if opaqueID, err = s.provider.LoadUserOpaqueIdentifierBySignature(ctx, "openid", sectorID, username); err != nil {
return nil, err
} else if opaqueID == nil {
@@ -55,7 +55,7 @@ func (s *OpenIDConnectStore) GenerateOpaqueUserID(ctx context.Context, sectorID,
}
// GetSubject returns a subject UUID for a username. If it exists, it returns the existing one, otherwise it creates and saves it.
-func (s *OpenIDConnectStore) GetSubject(ctx context.Context, sectorID, username string) (subject uuid.UUID, err error) {
+func (s *Store) GetSubject(ctx context.Context, sectorID, username string) (subject uuid.UUID, err error) {
var opaqueID *model.UserOpaqueIdentifier
if opaqueID, err = s.GenerateOpaqueUserID(ctx, sectorID, username); err != nil {
@@ -66,7 +66,7 @@ func (s *OpenIDConnectStore) GetSubject(ctx context.Context, sectorID, username
}
// GetClientPolicy retrieves the policy from the client with the matching provided id.
-func (s *OpenIDConnectStore) GetClientPolicy(id string) (level authorization.Level) {
+func (s *Store) GetClientPolicy(id string) (level authorization.Level) {
client, err := s.GetFullClient(id)
if err != nil {
return authorization.TwoFactor
@@ -76,7 +76,7 @@ func (s *OpenIDConnectStore) GetClientPolicy(id string) (level authorization.Lev
}
// GetFullClient returns a fosite.Client asserted as an Client matching the provided id.
-func (s *OpenIDConnectStore) GetFullClient(id string) (client *Client, err error) {
+func (s *Store) GetFullClient(id string) (client *Client, err error) {
client, ok := s.clients[id]
if !ok {
return nil, fosite.ErrNotFound
@@ -86,7 +86,7 @@ func (s *OpenIDConnectStore) GetFullClient(id string) (client *Client, err error
}
// IsValidClientID returns true if the provided id exists in the OpenIDConnectProvider.Clients map.
-func (s *OpenIDConnectStore) IsValidClientID(id string) (valid bool) {
+func (s *Store) IsValidClientID(id string) (valid bool) {
_, err := s.GetFullClient(id)
return err == nil
@@ -94,31 +94,31 @@ func (s *OpenIDConnectStore) IsValidClientID(id string) (valid bool) {
// BeginTX starts a transaction.
// This implements a portion of fosite storage.Transactional interface.
-func (s *OpenIDConnectStore) BeginTX(ctx context.Context) (c context.Context, err error) {
+func (s *Store) BeginTX(ctx context.Context) (c context.Context, err error) {
return s.provider.BeginTX(ctx)
}
// Commit completes a transaction.
// This implements a portion of fosite storage.Transactional interface.
-func (s *OpenIDConnectStore) Commit(ctx context.Context) (err error) {
+func (s *Store) Commit(ctx context.Context) (err error) {
return s.provider.Commit(ctx)
}
// Rollback rolls a transaction back.
// This implements a portion of fosite storage.Transactional interface.
-func (s *OpenIDConnectStore) Rollback(ctx context.Context) (err error) {
+func (s *Store) Rollback(ctx context.Context) (err error) {
return s.provider.Rollback(ctx)
}
// GetClient loads the client by its ID or returns an error if the client does not exist or another error occurred.
// This implements a portion of fosite.ClientManager.
-func (s *OpenIDConnectStore) GetClient(_ context.Context, id string) (client fosite.Client, err error) {
+func (s *Store) GetClient(_ context.Context, id string) (client fosite.Client, err error) {
return s.GetFullClient(id)
}
// ClientAssertionJWTValid returns an error if the JTI is known or the DB check failed and nil if the JTI is not known.
// This implements a portion of fosite.ClientManager.
-func (s *OpenIDConnectStore) ClientAssertionJWTValid(ctx context.Context, jti string) (err error) {
+func (s *Store) ClientAssertionJWTValid(ctx context.Context, jti string) (err error) {
signature := fmt.Sprintf("%x", sha256.Sum256([]byte(jti)))
blacklistedJTI, err := s.provider.LoadOAuth2BlacklistedJTI(ctx, signature)
@@ -138,7 +138,7 @@ func (s *OpenIDConnectStore) ClientAssertionJWTValid(ctx context.Context, jti st
// SetClientAssertionJWT marks a JTI as known for the given expiry time. Before inserting the new JTI, it will clean
// up any existing JTIs that have expired as those tokens can not be replayed due to the expiry.
// This implements a portion of fosite.ClientManager.
-func (s *OpenIDConnectStore) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) (err error) {
+func (s *Store) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) (err error) {
blacklistedJTI := model.NewOAuth2BlacklistedJTI(jti, exp)
return s.provider.SaveOAuth2BlacklistedJTI(ctx, blacklistedJTI)
@@ -146,7 +146,7 @@ func (s *OpenIDConnectStore) SetClientAssertionJWT(ctx context.Context, jti stri
// CreateAuthorizeCodeSession stores the authorization request for a given authorization code.
// This implements a portion of oauth2.AuthorizeCodeStorage.
-func (s *OpenIDConnectStore) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) (err error) {
+func (s *Store) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) (err error) {
return s.saveSession(ctx, storage.OAuth2SessionTypeAuthorizeCode, code, request)
}
@@ -154,7 +154,7 @@ func (s *OpenIDConnectStore) CreateAuthorizeCodeSession(ctx context.Context, cod
// code should be set to invalid and consecutive requests to GetAuthorizeCodeSession should return the
// ErrInvalidatedAuthorizeCode error.
// This implements a portion of oauth2.AuthorizeCodeStorage.
-func (s *OpenIDConnectStore) InvalidateAuthorizeCodeSession(ctx context.Context, code string) (err error) {
+func (s *Store) InvalidateAuthorizeCodeSession(ctx context.Context, code string) (err error) {
return s.provider.DeactivateOAuth2Session(ctx, storage.OAuth2SessionTypeAuthorizeCode, code)
}
@@ -163,45 +163,45 @@ func (s *OpenIDConnectStore) InvalidateAuthorizeCodeSession(ctx context.Context,
// method should return the ErrInvalidatedAuthorizeCode error.
// Make sure to also return the fosite.Requester value when returning the fosite.ErrInvalidatedAuthorizeCode error!
// This implements a portion of oauth2.AuthorizeCodeStorage.
-func (s *OpenIDConnectStore) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (request fosite.Requester, err error) {
+func (s *Store) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (request fosite.Requester, err error) {
// TODO: Implement the fosite.ErrInvalidatedAuthorizeCode error above. This requires splitting the invalidated sessions and deleted sessions.
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypeAuthorizeCode, code, session)
}
// CreateAccessTokenSession stores the authorization request for a given access token.
// This implements a portion of oauth2.AccessTokenStorage.
-func (s *OpenIDConnectStore) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) {
+func (s *Store) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) {
return s.saveSession(ctx, storage.OAuth2SessionTypeAccessToken, signature, request)
}
// DeleteAccessTokenSession marks an access token session as deleted.
// This implements a portion of oauth2.AccessTokenStorage.
-func (s *OpenIDConnectStore) DeleteAccessTokenSession(ctx context.Context, signature string) (err error) {
+func (s *Store) DeleteAccessTokenSession(ctx context.Context, signature string) (err error) {
return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, signature)
}
// RevokeAccessToken revokes an access token as specified in: https://tools.ietf.org/html/rfc7009#section-2.1
// If the token passed to the request is an access token, the server MAY revoke the respective refresh token as well.
// This implements a portion of oauth2.TokenRevocationStorage.
-func (s *OpenIDConnectStore) RevokeAccessToken(ctx context.Context, requestID string) (err error) {
+func (s *Store) RevokeAccessToken(ctx context.Context, requestID string) (err error) {
return s.revokeSessionByRequestID(ctx, storage.OAuth2SessionTypeAccessToken, requestID)
}
// GetAccessTokenSession gets the authorization request for a given access token.
// This implements a portion of oauth2.AccessTokenStorage.
-func (s *OpenIDConnectStore) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) {
+func (s *Store) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) {
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, signature, session)
}
// CreateRefreshTokenSession stores the authorization request for a given refresh token.
// This implements a portion of oauth2.RefreshTokenStorage.
-func (s *OpenIDConnectStore) CreateRefreshTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) {
+func (s *Store) CreateRefreshTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) {
return s.saveSession(ctx, storage.OAuth2SessionTypeRefreshToken, signature, request)
}
// DeleteRefreshTokenSession marks the authorization request for a given refresh token as deleted.
// This implements a portion of oauth2.RefreshTokenStorage.
-func (s *OpenIDConnectStore) DeleteRefreshTokenSession(ctx context.Context, signature string) (err error) {
+func (s *Store) DeleteRefreshTokenSession(ctx context.Context, signature string) (err error) {
return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeRefreshToken, signature)
}
@@ -209,51 +209,51 @@ func (s *OpenIDConnectStore) DeleteRefreshTokenSession(ctx context.Context, sign
// If the particular token is a refresh token and the authorization server supports the revocation of access tokens,
// then the authorization server SHOULD also invalidate all access tokens based on the same authorization grant (see Implementation Note).
// This implements a portion of oauth2.TokenRevocationStorage.
-func (s *OpenIDConnectStore) RevokeRefreshToken(ctx context.Context, requestID string) (err error) {
+func (s *Store) RevokeRefreshToken(ctx context.Context, requestID string) (err error) {
return s.provider.DeactivateOAuth2SessionByRequestID(ctx, storage.OAuth2SessionTypeRefreshToken, requestID)
}
// RevokeRefreshTokenMaybeGracePeriod revokes an access token as specified in: https://tools.ietf.org/html/rfc7009#section-2.1
// If the token passed to the request is an access token, the server MAY revoke the respective refresh token as well.
// This implements a portion of oauth2.TokenRevocationStorage.
-func (s *OpenIDConnectStore) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, signature string) (err error) {
+func (s *Store) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, signature string) (err error) {
return s.RevokeRefreshToken(ctx, requestID)
}
// GetRefreshTokenSession gets the authorization request for a given refresh token.
// This implements a portion of oauth2.RefreshTokenStorage.
-func (s *OpenIDConnectStore) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) {
+func (s *Store) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) {
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypeRefreshToken, signature, session)
}
// CreatePKCERequestSession stores the authorization request for a given PKCE request.
// This implements a portion of pkce.PKCERequestStorage.
-func (s *OpenIDConnectStore) CreatePKCERequestSession(ctx context.Context, signature string, request fosite.Requester) (err error) {
+func (s *Store) CreatePKCERequestSession(ctx context.Context, signature string, request fosite.Requester) (err error) {
return s.saveSession(ctx, storage.OAuth2SessionTypePKCEChallenge, signature, request)
}
// DeletePKCERequestSession marks the authorization request for a given PKCE request as deleted.
// This implements a portion of pkce.PKCERequestStorage.
-func (s *OpenIDConnectStore) DeletePKCERequestSession(ctx context.Context, signature string) (err error) {
+func (s *Store) DeletePKCERequestSession(ctx context.Context, signature string) (err error) {
return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, signature)
}
// GetPKCERequestSession gets the authorization request for a given PKCE request.
// This implements a portion of pkce.PKCERequestStorage.
-func (s *OpenIDConnectStore) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (requester fosite.Requester, err error) {
+func (s *Store) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (requester fosite.Requester, err error) {
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypePKCEChallenge, signature, session)
}
// CreateOpenIDConnectSession creates an open id connect session for a given authorize code.
// This is relevant for explicit open id connect flow.
// This implements a portion of openid.OpenIDConnectRequestStorage.
-func (s *OpenIDConnectStore) CreateOpenIDConnectSession(ctx context.Context, authorizeCode string, request fosite.Requester) (err error) {
+func (s *Store) CreateOpenIDConnectSession(ctx context.Context, authorizeCode string, request fosite.Requester) (err error) {
return s.saveSession(ctx, storage.OAuth2SessionTypeOpenIDConnect, authorizeCode, request)
}
// DeleteOpenIDConnectSession just implements the method required by fosite even though it's unused.
// This implements a portion of openid.OpenIDConnectRequestStorage.
-func (s *OpenIDConnectStore) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode string) (err error) {
+func (s *Store) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode string) (err error) {
return s.revokeSessionBySignature(ctx, storage.OAuth2SessionTypeAccessToken, authorizeCode)
}
@@ -262,12 +262,12 @@ func (s *OpenIDConnectStore) DeleteOpenIDConnectSession(ctx context.Context, aut
// - ErrNoSessionFound if no session was found
// - or an arbitrary error if an error occurred.
// This implements a portion of openid.OpenIDConnectRequestStorage.
-func (s *OpenIDConnectStore) GetOpenIDConnectSession(ctx context.Context, authorizeCode string, request fosite.Requester) (r fosite.Requester, err error) {
+func (s *Store) GetOpenIDConnectSession(ctx context.Context, authorizeCode string, request fosite.Requester) (r fosite.Requester, err error) {
return s.loadSessionBySignature(ctx, storage.OAuth2SessionTypeOpenIDConnect, authorizeCode, request.GetSession())
}
// IsJWTUsed implements an interface required for RFC7523.
-func (s *OpenIDConnectStore) IsJWTUsed(ctx context.Context, jti string) (used bool, err error) {
+func (s *Store) IsJWTUsed(ctx context.Context, jti string) (used bool, err error) {
if err = s.ClientAssertionJWTValid(ctx, jti); err != nil {
return true, err
}
@@ -276,11 +276,11 @@ func (s *OpenIDConnectStore) IsJWTUsed(ctx context.Context, jti string) (used bo
}
// MarkJWTUsedForTime implements an interface required for rfc7523.RFC7523KeyStorage.
-func (s *OpenIDConnectStore) MarkJWTUsedForTime(ctx context.Context, jti string, exp time.Time) (err error) {
+func (s *Store) MarkJWTUsedForTime(ctx context.Context, jti string, exp time.Time) (err error) {
return s.SetClientAssertionJWT(ctx, jti, exp)
}
-func (s *OpenIDConnectStore) loadSessionBySignature(ctx context.Context, sessionType storage.OAuth2SessionType, signature string, session fosite.Session) (r fosite.Requester, err error) {
+func (s *Store) loadSessionBySignature(ctx context.Context, sessionType storage.OAuth2SessionType, signature string, session fosite.Session) (r fosite.Requester, err error) {
var (
sessionModel *model.OAuth2Session
)
@@ -306,7 +306,7 @@ func (s *OpenIDConnectStore) loadSessionBySignature(ctx context.Context, session
return r, nil
}
-func (s *OpenIDConnectStore) saveSession(ctx context.Context, sessionType storage.OAuth2SessionType, signature string, r fosite.Requester) (err error) {
+func (s *Store) saveSession(ctx context.Context, sessionType storage.OAuth2SessionType, signature string, r fosite.Requester) (err error) {
var session *model.OAuth2Session
if session, err = model.NewOAuth2SessionFromRequest(signature, r); err != nil {
@@ -316,11 +316,11 @@ func (s *OpenIDConnectStore) saveSession(ctx context.Context, sessionType storag
return s.provider.SaveOAuth2Session(ctx, sessionType, *session)
}
-func (s *OpenIDConnectStore) revokeSessionBySignature(ctx context.Context, sessionType storage.OAuth2SessionType, signature string) (err error) {
+func (s *Store) revokeSessionBySignature(ctx context.Context, sessionType storage.OAuth2SessionType, signature string) (err error) {
return s.provider.RevokeOAuth2Session(ctx, sessionType, signature)
}
-func (s *OpenIDConnectStore) revokeSessionByRequestID(ctx context.Context, sessionType storage.OAuth2SessionType, requestID string) (err error) {
+func (s *Store) revokeSessionByRequestID(ctx context.Context, sessionType storage.OAuth2SessionType, requestID string) (err error) {
if err = s.provider.RevokeOAuth2SessionByRequestID(ctx, sessionType, requestID); err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
diff --git a/internal/oidc/store_test.go b/internal/oidc/store_test.go
index 412b81551..adba95453 100644
--- a/internal/oidc/store_test.go
+++ b/internal/oidc/store_test.go
@@ -13,21 +13,22 @@ import (
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "myclient",
Description: "myclient desc",
Policy: "one_factor",
- Scopes: []string{"openid", "profile"},
- Secret: "mysecret",
+ Scopes: []string{ScopeOpenID, ScopeProfile},
+ Secret: MustDecodeSecret("$plaintext$mysecret"),
},
{
ID: "myotherclient",
Description: "myclient desc",
Policy: "two_factor",
- Scopes: []string{"openid", "profile"},
- Secret: "mysecret",
+ Scopes: []string{ScopeOpenID, ScopeProfile},
+ Secret: MustDecodeSecret("$plaintext$mysecret"),
},
},
}, nil)
@@ -44,14 +45,15 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "myclient",
Description: "myclient desc",
Policy: "one_factor",
- Scopes: []string{"openid", "profile"},
- Secret: "mysecret",
+ Scopes: []string{ScopeOpenID, ScopeProfile},
+ Secret: MustDecodeSecret("$plaintext$mysecret"),
},
},
}, nil)
@@ -71,13 +73,14 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
ID: "myclient",
Description: "myclient desc",
Policy: "one_factor",
- Scopes: []string{"openid", "profile"},
- Secret: "mysecret",
+ Scopes: []string{ScopeOpenID, ScopeProfile},
+ Secret: MustDecodeSecret("$plaintext$mysecret"),
}
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
- Clients: []schema.OpenIDConnectClientConfiguration{c1},
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
+ Clients: []schema.OpenIDConnectClientConfiguration{c1},
}, nil)
client, err := s.GetFullClient(c1.ID)
@@ -90,7 +93,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
assert.Equal(t, client.ResponseTypes, c1.ResponseTypes)
assert.Equal(t, client.RedirectURIs, c1.RedirectURIs)
assert.Equal(t, client.Policy, authorization.OneFactor)
- assert.Equal(t, client.Secret, []byte(c1.Secret))
+ assert.Equal(t, client.Secret.Encode(), "$plaintext$mysecret")
}
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
@@ -98,13 +101,14 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
ID: "myclient",
Description: "myclient desc",
Policy: "one_factor",
- Scopes: []string{"openid", "profile"},
- Secret: "mysecret",
+ Scopes: []string{ScopeOpenID, ScopeProfile},
+ Secret: MustDecodeSecret("$plaintext$mysecret"),
}
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
- Clients: []schema.OpenIDConnectClientConfiguration{c1},
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
+ Clients: []schema.OpenIDConnectClientConfiguration{c1},
}, nil)
client, err := s.GetFullClient("another-client")
@@ -114,14 +118,15 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
- IssuerPrivateKey: exampleIssuerPrivateKey,
+ IssuerCertificateChain: schema.X509CertificateChain{},
+ IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "myclient",
Description: "myclient desc",
Policy: "one_factor",
- Scopes: []string{"openid", "profile"},
- Secret: "mysecret",
+ Scopes: []string{ScopeOpenID, ScopeProfile},
+ Secret: MustDecodeSecret("$plaintext$mysecret"),
},
},
}, nil)
diff --git a/internal/oidc/types.go b/internal/oidc/types.go
index 02078b914..97d84542a 100644
--- a/internal/oidc/types.go
+++ b/internal/oidc/types.go
@@ -1,14 +1,15 @@
package oidc
import (
- "crypto/rsa"
+ "net/url"
"time"
+ "github.com/go-crypt/crypt"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
"github.com/ory/herodot"
- "gopkg.in/square/go-jose.v2"
+ jose "gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/model"
@@ -21,46 +22,46 @@ func NewSession() (session *model.OpenIDSession) {
return &model.OpenIDSession{
DefaultSession: &openid.DefaultSession{
Claims: &jwt.IDTokenClaims{
- Extra: map[string]interface{}{},
+ Extra: map[string]any{},
},
Headers: &jwt.Headers{
- Extra: map[string]interface{}{},
+ Extra: map[string]any{},
},
},
- Extra: map[string]interface{}{},
+ Extra: map[string]any{},
}
}
// NewSessionWithAuthorizeRequest uses details from an AuthorizeRequester to generate an OpenIDSession.
-func NewSessionWithAuthorizeRequest(issuer, kid, username string, amr []string, extra map[string]interface{},
+func NewSessionWithAuthorizeRequest(issuer *url.URL, kid, username string, amr []string, extra map[string]any,
authTime time.Time, consent *model.OAuth2ConsentSession, requester fosite.AuthorizeRequester) (session *model.OpenIDSession) {
if extra == nil {
- extra = make(map[string]interface{})
+ extra = map[string]any{}
}
session = &model.OpenIDSession{
DefaultSession: &openid.DefaultSession{
Claims: &jwt.IDTokenClaims{
Subject: consent.Subject.UUID.String(),
- Issuer: issuer,
+ Issuer: issuer.String(),
AuthTime: authTime,
RequestedAt: consent.RequestedAt,
IssuedAt: time.Now(),
- Nonce: requester.GetRequestForm().Get("nonce"),
+ Nonce: requester.GetRequestForm().Get(ClaimNonce),
Audience: requester.GetGrantedAudience(),
Extra: extra,
AuthenticationMethodsReferences: amr,
},
Headers: &jwt.Headers{
- Extra: map[string]interface{}{
- "kid": kid,
+ Extra: map[string]any{
+ JWTHeaderKeyIdentifier: kid,
},
},
Subject: consent.Subject.UUID.String(),
Username: username,
},
- Extra: map[string]interface{}{},
+ Extra: map[string]any{},
ClientID: requester.GetClient().GetID(),
ChallengeID: consent.ChallengeID,
}
@@ -70,29 +71,29 @@ func NewSessionWithAuthorizeRequest(issuer, kid, username string, amr []string,
session.Claims.Audience = append(session.Claims.Audience, requester.GetClient().GetID())
}
- session.Claims.Add("azp", session.ClientID)
- session.Claims.Add("client_id", session.ClientID)
+ session.Claims.Add(ClaimAuthorizedParty, session.ClientID)
+ session.Claims.Add(ClaimClientIdentifier, session.ClientID)
return session
}
// OpenIDConnectProvider for OpenID Connect.
type OpenIDConnectProvider struct {
- Fosite fosite.OAuth2Provider
- Store *OpenIDConnectStore
- KeyManager *KeyManager
+ fosite.OAuth2Provider
+ *herodot.JSONWriter
+ *Store
- herodot *herodot.JSONWriter
+ KeyManager *KeyManager
discovery OpenIDConnectWellKnownConfiguration
}
-// OpenIDConnectStore is Authelia's internal representation of the fosite.Storage interface. It maps the following
+// Store is Authelia's internal representation of the fosite.Storage interface. It maps the following
// interfaces to the storage.Provider interface:
// fosite.Storage, fosite.ClientManager, storage.Transactional, oauth2.AuthorizeCodeStorage, oauth2.AccessTokenStorage,
// oauth2.RefreshTokenStorage, oauth2.TokenRevocationStorage, pkce.PKCERequestStorage,
// openid.OpenIDConnectRequestStorage, and partially implements rfc7523.RFC7523KeyStorage.
-type OpenIDConnectStore struct {
+type Store struct {
provider storage.Provider
clients map[string]*Client
}
@@ -101,7 +102,7 @@ type OpenIDConnectStore struct {
type Client struct {
ID string
Description string
- Secret []byte
+ Secret crypt.Digest
SectorIdentifier string
Public bool
@@ -116,20 +117,74 @@ type Client struct {
Policy authorization.Level
- PreConfiguredConsentDuration *time.Duration
+ Consent ClientConsent
+}
+
+// NewClientConsent converts the schema.OpenIDConnectClientConsentConfig into a oidc.ClientConsent.
+func NewClientConsent(mode string, duration *time.Duration) ClientConsent {
+ switch mode {
+ case ClientConsentModeImplicit.String():
+ return ClientConsent{Mode: ClientConsentModeImplicit}
+ case ClientConsentModePreConfigured.String():
+ return ClientConsent{Mode: ClientConsentModePreConfigured, Duration: *duration}
+ case ClientConsentModeExplicit.String():
+ return ClientConsent{Mode: ClientConsentModeExplicit}
+ default:
+ return ClientConsent{Mode: ClientConsentModeExplicit}
+ }
+}
+
+// ClientConsent is the consent configuration for a client.
+type ClientConsent struct {
+ Mode ClientConsentMode
+ Duration time.Duration
+}
+
+// String returns the string representation of the ClientConsentMode.
+func (c ClientConsent) String() string {
+ return c.Mode.String()
+}
+
+// ClientConsentMode represents the consent mode for a client.
+type ClientConsentMode int
+
+const (
+ // ClientConsentModeExplicit means the client does not implicitly assume consent, and does not allow pre-configured
+ // consent sessions.
+ ClientConsentModeExplicit ClientConsentMode = iota
+
+ // ClientConsentModePreConfigured means the client does not implicitly assume consent, but does allow pre-configured
+ // consent sessions.
+ ClientConsentModePreConfigured
+
+ // ClientConsentModeImplicit means the client does implicitly assume consent, and does not allow pre-configured
+ // consent sessions.
+ ClientConsentModeImplicit
+)
+
+// String returns the string representation of the ClientConsentMode.
+func (c ClientConsentMode) String() string {
+ switch c {
+ case ClientConsentModeExplicit:
+ return explicit
+ case ClientConsentModeImplicit:
+ return implicit
+ case ClientConsentModePreConfigured:
+ return preconfigured
+ default:
+ return ""
+ }
}
// KeyManager keeps track of all of the active/inactive rsa keys and provides them to services requiring them.
// It additionally allows us to add keys for the purpose of key rotation in the future.
type KeyManager struct {
- activeKeyID string
- keys map[string]*rsa.PrivateKey
- keySet *jose.JSONWebKeySet
- strategy *RS256JWTStrategy
+ jwk *JWK
+ jwks *jose.JSONWebKeySet
}
-// PlainTextHasher implements the fosite.Hasher interface without an actual hashing algo.
-type PlainTextHasher struct{}
+// AdaptiveHasher implements the fosite.Hasher interface without an actual hashing algo.
+type AdaptiveHasher struct{}
// ConsentGetResponseBody schema of the response body of the consent GET endpoint.
type ConsentGetResponseBody struct {
@@ -142,8 +197,8 @@ type ConsentGetResponseBody struct {
// ConsentPostRequestBody schema of the request body of the consent POST endpoint.
type ConsentPostRequestBody struct {
+ ConsentID string `json:"id"`
ClientID string `json:"client_id"`
- ConsentID string `json:"consent_id"`
Consent bool `json:"consent"`
PreConfigure bool `json:"pre_configure"`
}
diff --git a/internal/oidc/types_test.go b/internal/oidc/types_test.go
index 936804f83..b84461a07 100644
--- a/internal/oidc/types_test.go
+++ b/internal/oidc/types_test.go
@@ -34,7 +34,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
formValues := url.Values{}
- formValues.Set("nonce", "abc123xyzauthelia")
+ formValues.Set(ClaimNonce, "abc123xyzauthelia")
request := &fosite.AuthorizeRequest{
Request: fosite.Request{
@@ -44,8 +44,8 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
},
}
- extra := map[string]interface{}{
- "preferred_username": "john",
+ extra := map[string]any{
+ ClaimPreferredUsername: "john",
}
requested := time.Unix(1647332518, 0)
@@ -59,7 +59,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
Subject: uuid.NullUUID{UUID: subject, Valid: true},
}
- session := NewSessionWithAuthorizeRequest(issuer, "primary", "john", amr, extra, authAt, consent, request)
+ session := NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", amr, extra, authAt, consent, request)
require.NotNil(t, session)
require.NotNil(t, session.Extra)
@@ -80,21 +80,27 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
assert.Equal(t, authAt, session.Claims.AuthTime)
assert.Equal(t, requested, session.Claims.RequestedAt)
assert.Equal(t, issuer, session.Claims.Issuer)
- assert.Equal(t, "john", session.Claims.Extra["preferred_username"])
+ assert.Equal(t, "john", session.Claims.Extra[ClaimPreferredUsername])
- assert.Equal(t, "primary", session.Headers.Get("kid"))
-
- require.Contains(t, session.Claims.Extra, "preferred_username")
+ assert.Equal(t, "primary", session.Headers.Get(JWTHeaderKeyIdentifier))
consent = &model.OAuth2ConsentSession{
ChallengeID: uuid.New(),
RequestedAt: requested,
}
- session = NewSessionWithAuthorizeRequest(issuer, "primary", "john", nil, nil, authAt, consent, request)
+ session = NewSessionWithAuthorizeRequest(MustParseRequestURI(issuer), "primary", "john", nil, nil, authAt, consent, request)
require.NotNil(t, session)
require.NotNil(t, session.Claims)
assert.NotNil(t, session.Claims.Extra)
assert.Nil(t, session.Claims.AuthenticationMethodsReferences)
}
+
+func MustParseRequestURI(input string) *url.URL {
+ if requestURI, err := url.ParseRequestURI(input); err != nil {
+ panic(err)
+ } else {
+ return requestURI
+ }
+}
diff --git a/internal/server/asset.go b/internal/server/asset.go
index 5c4559a9b..6b6454e8f 100644
--- a/internal/server/asset.go
+++ b/internal/server/asset.go
@@ -20,19 +20,21 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
-//go:embed locales
-var locales embed.FS
+var (
+ //go:embed public_html
+ assets embed.FS
-//go:embed public_html
-var assets embed.FS
+ //go:embed locales
+ locales embed.FS
+)
func newPublicHTMLEmbeddedHandler() fasthttp.RequestHandler {
etags := map[string][]byte{}
- getEmbedETags(assets, "public_html", etags)
+ getEmbedETags(assets, assetsRoot, etags)
return func(ctx *fasthttp.RequestCtx) {
- p := path.Join("public_html", string(ctx.Path()))
+ p := path.Join(assetsRoot, string(ctx.Path()))
if etag, ok := etags[p]; ok {
ctx.Response.Header.SetBytesKV(headerETag, etag)
@@ -66,8 +68,10 @@ func newPublicHTMLEmbeddedHandler() fasthttp.RequestHandler {
}
}
-func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
- var languages []string
+func newLocalesPathResolver() func(ctx *fasthttp.RequestCtx) (supported bool, asset string) {
+ var (
+ languages, dirs []string
+ )
entries, err := locales.ReadDir("locales")
if err == nil {
@@ -84,6 +88,10 @@ func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
lng = strings.SplitN(entry.Name(), "-", 2)[0]
}
+ if !utils.IsStringInSlice(entry.Name(), dirs) {
+ dirs = append(dirs, entry.Name())
+ }
+
if utils.IsStringInSlice(lng, languages) {
continue
}
@@ -93,34 +101,85 @@ func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
}
}
- return func(ctx *fasthttp.RequestCtx) {
- var (
- language, variant, locale, namespace string
- )
+ aliases := map[string]string{
+ "cs": "cs-CZ",
+ "da": "da-DK",
+ "el": "el-GR",
+ "ja": "ja-JP",
+ "nb": "nb-NO",
+ "sv": "sv-SE",
+ "uk": "uk-UA",
+ "zh": "zh-CN",
+ }
- language = ctx.UserValue("language").(string)
- namespace = ctx.UserValue("namespace").(string)
- locale = language
+ return func(ctx *fasthttp.RequestCtx) (supported bool, asset string) {
+ var language, namespace, variant, locale string
+
+ language, namespace = ctx.UserValue("language").(string), ctx.UserValue("namespace").(string)
+
+ if !utils.IsStringInSlice(language, languages) {
+ return false, ""
+ }
if v := ctx.UserValue("variant"); v != nil {
variant = v.(string)
locale = fmt.Sprintf("%s-%s", language, variant)
+ } else {
+ locale = language
}
- var data []byte
+ ll := language + "-" + strings.ToUpper(language)
+ alias, ok := aliases[locale]
- if data, err = locales.ReadFile(fmt.Sprintf("locales/%s/%s.json", locale, namespace)); err != nil {
- if utils.IsStringInSliceFold(language, languages) {
- data = []byte("{}")
- }
+ switch {
+ case ok:
+ return true, fmt.Sprintf("locales/%s/%s.json", alias, namespace)
+ case utils.IsStringInSlice(locale, dirs):
+ return true, fmt.Sprintf("locales/%s/%s.json", locale, namespace)
+ case utils.IsStringInSlice(ll, dirs):
+ return true, fmt.Sprintf("locales/%s-%s/%s.json", language, strings.ToUpper(language), namespace)
+ default:
+ return true, fmt.Sprintf("locales/%s/%s.json", locale, namespace)
+ }
+ }
+}
- if len(data) == 0 {
- hfsHandleErr(ctx, err)
+func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
+ etags := map[string][]byte{}
+
+ getEmbedETags(locales, "locales", etags)
+
+ getAssetName := newLocalesPathResolver()
+
+ return func(ctx *fasthttp.RequestCtx) {
+ supported, asset := getAssetName(ctx)
+
+ if !supported {
+ handlers.SetStatusCodeResponse(ctx, fasthttp.StatusNotFound)
+
+ return
+ }
+
+ if etag, ok := etags[asset]; ok {
+ ctx.Response.Header.SetBytesKV(headerETag, etag)
+ ctx.Response.Header.SetBytesKV(headerCacheControl, headerValueCacheControlETaggedAssets)
+
+ if bytes.Equal(etag, ctx.Request.Header.PeekBytes(headerIfNoneMatch)) {
+ ctx.SetStatusCode(fasthttp.StatusNotModified)
return
}
}
+ var (
+ data []byte
+ err error
+ )
+
+ if data, err = locales.ReadFile(asset); err != nil {
+ data = []byte("{}")
+ }
+
middlewares.SetContentTypeApplicationJSON(ctx)
ctx.SetBody(data)
diff --git a/internal/server/const.go b/internal/server/const.go
index 9c9a34098..f4a774674 100644
--- a/internal/server/const.go
+++ b/internal/server/const.go
@@ -5,16 +5,17 @@ import (
)
const (
- embeddedAssets = "public_html/"
- swaggerAssets = embeddedAssets + "api/"
- apiFile = "openapi.yml"
- indexFile = "index.html"
- logoFile = "logo.png"
+ assetsRoot = "public_html"
+ assetsSwagger = assetsRoot + "/api"
+
+ fileOpenAPI = "openapi.yml"
+ fileIndexHTML = "index.html"
+ fileLogo = "logo.png"
)
var (
- rootFiles = []string{"manifest.json", "robots.txt"}
- swaggerFiles = []string{
+ filesRoot = []string{"manifest.json", "robots.txt"}
+ filesSwagger = []string{
"favicon-16x16.png",
"favicon-32x32.png",
"index.css",
@@ -35,7 +36,7 @@ var (
}
// Directories excluded from the not found handler proceeding to the next() handler.
- httpServerDirs = []struct {
+ dirsHTTPServer = []struct {
name, prefix string
}{
{name: "/api", prefix: "/api/"},
@@ -71,8 +72,7 @@ X_AUTHELIA_HEALTHCHECK_PATH=%s
`
const (
- cspDefaultTemplate = "default-src 'self'%s; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-%s'; frame-ancestors 'none'; base-uri 'self'"
- cspNoncePlaceholder = "${NONCE}"
+ 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'"
)
const (
diff --git a/internal/server/gen.go b/internal/server/gen.go
new file mode 100644
index 000000000..d80dcf10d
--- /dev/null
+++ b/internal/server/gen.go
@@ -0,0 +1,13 @@
+// Code generated by go generate. DO NOT EDIT.
+//
+// Run the following command to generate this file:
+// go run ./cmd/authelia-gen code server
+//
+
+package server
+
+const (
+ placeholderCSPNonce = "${NONCE}"
+ tmplCSPDefault = "default-src 'self'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-%s'; frame-ancestors 'none'; base-uri 'self'"
+ tmplCSPDevelopment = "default-src 'self' 'unsafe-eval'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-%s'; frame-ancestors 'none'; base-uri 'self'"
+)
diff --git a/internal/server/handlers.go b/internal/server/handlers.go
index d2808027c..cdf8f6ba5 100644
--- a/internal/server/handlers.go
+++ b/internal/server/handlers.go
@@ -79,8 +79,8 @@ func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
path := strings.ToLower(string(ctx.Path()))
- for i := 0; i < len(httpServerDirs); i++ {
- if path == httpServerDirs[i].name || strings.HasPrefix(path, httpServerDirs[i].prefix) {
+ for i := 0; i < len(dirsHTTPServer); i++ {
+ if path == dirsHTTPServer[i].name || strings.HasPrefix(path, dirsHTTPServer[i].prefix) {
handlers.SetStatusCodeResponse(ctx, fasthttp.StatusNotFound)
return
@@ -104,9 +104,9 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
https := config.Server.TLS.Key != "" && config.Server.TLS.Certificate != ""
- serveIndexHandler := ServeTemplatedFile(embeddedAssets, indexFile, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
- serveSwaggerHandler := ServeTemplatedFile(swaggerAssets, indexFile, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
- serveSwaggerAPIHandler := ServeTemplatedFile(swaggerAssets, apiFile, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
+ serveIndexHandler := ServeTemplatedFile(assetsRoot, fileIndexHTML, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
+ serveSwaggerHandler := ServeTemplatedFile(assetsSwagger, fileIndexHTML, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
+ serveSwaggerAPIHandler := ServeTemplatedFile(assetsSwagger, fileOpenAPI, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
handlerPublicHTML := newPublicHTMLEmbeddedHandler()
handlerLocales := newLocalesEmbeddedHandler()
@@ -124,7 +124,7 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
// Static Assets.
r.GET("/", middleware(serveIndexHandler))
- for _, f := range rootFiles {
+ for _, f := range filesRoot {
r.GET("/"+f, handlerPublicHTML)
}
@@ -139,10 +139,10 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
// Swagger.
r.GET("/api/", middleware(serveSwaggerHandler))
r.OPTIONS("/api/", policyCORSPublicGET.HandleOPTIONS)
- r.GET("/api/"+apiFile, policyCORSPublicGET.Middleware(middleware(serveSwaggerAPIHandler)))
- r.OPTIONS("/api/"+apiFile, policyCORSPublicGET.HandleOPTIONS)
+ r.GET("/api/"+fileOpenAPI, policyCORSPublicGET.Middleware(middleware(serveSwaggerAPIHandler)))
+ r.OPTIONS("/api/"+fileOpenAPI, policyCORSPublicGET.HandleOPTIONS)
- for _, file := range swaggerFiles {
+ for _, file := range filesSwagger {
r.GET("/api/"+file, handlerPublicHTML)
}
@@ -167,6 +167,9 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
r.GET("/api/verify", middlewares.Wrap(metricsVRMW, middleware(handlers.VerifyGET(config.AuthenticationBackend))))
r.HEAD("/api/verify", middlewares.Wrap(metricsVRMW, middleware(handlers.VerifyGET(config.AuthenticationBackend))))
+ r.GET("/api/verify/{path:*}", middlewares.Wrap(metricsVRMW, middleware(handlers.VerifyGET(config.AuthenticationBackend))))
+ r.HEAD("/api/verify/{path:*}", middlewares.Wrap(metricsVRMW, middleware(handlers.VerifyGET(config.AuthenticationBackend))))
+
r.POST("/api/checks/safe-redirection", middlewareAPI(handlers.CheckSafeRedirectionPOST))
delayFunc := middlewares.TimingAttackDelay(10, 250, 85, time.Second, true)
@@ -237,7 +240,7 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
r.GET("/debug/vars", expvarhandler.ExpvarHandler)
}
- if providers.OpenIDConnect.Fosite != nil {
+ if providers.OpenIDConnect != nil {
middlewareOIDC := middlewares.NewBridgeBuilder(config, providers).WithPreMiddlewares(
middlewares.SecurityHeaders, middlewares.SecurityHeadersCSPNone, middlewares.SecurityHeadersNoStore,
).Build()
@@ -247,62 +250,64 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
allowedOrigins := utils.StringSliceFromURLs(config.IdentityProviders.OIDC.CORS.AllowedOrigins)
- r.OPTIONS(oidc.WellKnownOpenIDConfigurationPath, policyCORSPublicGET.HandleOPTIONS)
- r.GET(oidc.WellKnownOpenIDConfigurationPath, policyCORSPublicGET.Middleware(middlewareOIDC(handlers.OpenIDConnectConfigurationWellKnownGET)))
+ r.OPTIONS(oidc.EndpointPathWellKnownOpenIDConfiguration, policyCORSPublicGET.HandleOPTIONS)
+ r.GET(oidc.EndpointPathWellKnownOpenIDConfiguration, policyCORSPublicGET.Middleware(middlewareOIDC(handlers.OpenIDConnectConfigurationWellKnownGET)))
- r.OPTIONS(oidc.WellKnownOAuthAuthorizationServerPath, policyCORSPublicGET.HandleOPTIONS)
- r.GET(oidc.WellKnownOAuthAuthorizationServerPath, policyCORSPublicGET.Middleware(middlewareOIDC(handlers.OAuthAuthorizationServerWellKnownGET)))
+ r.OPTIONS(oidc.EndpointPathWellKnownOAuthAuthorizationServer, policyCORSPublicGET.HandleOPTIONS)
+ r.GET(oidc.EndpointPathWellKnownOAuthAuthorizationServer, policyCORSPublicGET.Middleware(middlewareOIDC(handlers.OAuthAuthorizationServerWellKnownGET)))
- r.OPTIONS(oidc.JWKsPath, policyCORSPublicGET.HandleOPTIONS)
- r.GET(oidc.JWKsPath, policyCORSPublicGET.Middleware(middlewareAPI(handlers.JSONWebKeySetGET)))
+ r.OPTIONS(oidc.EndpointPathJWKs, policyCORSPublicGET.HandleOPTIONS)
+ r.GET(oidc.EndpointPathJWKs, policyCORSPublicGET.Middleware(middlewareAPI(handlers.JSONWebKeySetGET)))
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
r.OPTIONS("/api/oidc/jwks", policyCORSPublicGET.HandleOPTIONS)
r.GET("/api/oidc/jwks", policyCORSPublicGET.Middleware(middlewareOIDC(handlers.JSONWebKeySetGET)))
policyCORSAuthorization := middlewares.NewCORSPolicyBuilder().
- WithAllowedMethods("OPTIONS", "GET").
+ WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodGet, fasthttp.MethodPost).
WithAllowedOrigins(allowedOrigins...).
- WithEnabled(utils.IsStringInSlice(oidc.AuthorizationEndpoint, config.IdentityProviders.OIDC.CORS.Endpoints)).
+ WithEnabled(utils.IsStringInSlice(oidc.EndpointAuthorization, config.IdentityProviders.OIDC.CORS.Endpoints)).
Build()
- r.OPTIONS(oidc.AuthorizationPath, policyCORSAuthorization.HandleOnlyOPTIONS)
- r.GET(oidc.AuthorizationPath, middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorizationGET)))
+ r.OPTIONS(oidc.EndpointPathAuthorization, policyCORSAuthorization.HandleOnlyOPTIONS)
+ r.GET(oidc.EndpointPathAuthorization, policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
+ r.POST(oidc.EndpointPathAuthorization, policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
// TODO (james-d-elliott): Remove in GA. This is a legacy endpoint.
r.OPTIONS("/api/oidc/authorize", policyCORSAuthorization.HandleOnlyOPTIONS)
- r.GET("/api/oidc/authorize", middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorizationGET)))
+ r.GET("/api/oidc/authorize", policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
+ r.POST("/api/oidc/authorize", policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
policyCORSToken := middlewares.NewCORSPolicyBuilder().
WithAllowCredentials(true).
- WithAllowedMethods("OPTIONS", "POST").
+ WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodPost).
WithAllowedOrigins(allowedOrigins...).
- WithEnabled(utils.IsStringInSlice(oidc.TokenEndpoint, config.IdentityProviders.OIDC.CORS.Endpoints)).
+ WithEnabled(utils.IsStringInSlice(oidc.EndpointToken, config.IdentityProviders.OIDC.CORS.Endpoints)).
Build()
- r.OPTIONS(oidc.TokenPath, policyCORSToken.HandleOPTIONS)
- r.POST(oidc.TokenPath, policyCORSToken.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectTokenPOST))))
+ r.OPTIONS(oidc.EndpointPathToken, policyCORSToken.HandleOPTIONS)
+ r.POST(oidc.EndpointPathToken, policyCORSToken.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectTokenPOST))))
policyCORSUserinfo := middlewares.NewCORSPolicyBuilder().
WithAllowCredentials(true).
- WithAllowedMethods("OPTIONS", "GET", "POST").
+ WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodGet, fasthttp.MethodPost).
WithAllowedOrigins(allowedOrigins...).
- WithEnabled(utils.IsStringInSlice(oidc.UserinfoEndpoint, config.IdentityProviders.OIDC.CORS.Endpoints)).
+ WithEnabled(utils.IsStringInSlice(oidc.EndpointUserinfo, config.IdentityProviders.OIDC.CORS.Endpoints)).
Build()
- r.OPTIONS(oidc.UserinfoPath, policyCORSUserinfo.HandleOPTIONS)
- r.GET(oidc.UserinfoPath, policyCORSUserinfo.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
- r.POST(oidc.UserinfoPath, policyCORSUserinfo.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
+ r.OPTIONS(oidc.EndpointPathUserinfo, policyCORSUserinfo.HandleOPTIONS)
+ r.GET(oidc.EndpointPathUserinfo, policyCORSUserinfo.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
+ r.POST(oidc.EndpointPathUserinfo, policyCORSUserinfo.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
policyCORSIntrospection := middlewares.NewCORSPolicyBuilder().
WithAllowCredentials(true).
- WithAllowedMethods("OPTIONS", "POST").
+ WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodPost).
WithAllowedOrigins(allowedOrigins...).
- WithEnabled(utils.IsStringInSlice(oidc.IntrospectionEndpoint, config.IdentityProviders.OIDC.CORS.Endpoints)).
+ WithEnabled(utils.IsStringInSlice(oidc.EndpointIntrospection, config.IdentityProviders.OIDC.CORS.Endpoints)).
Build()
- r.OPTIONS(oidc.IntrospectionPath, policyCORSIntrospection.HandleOPTIONS)
- r.POST(oidc.IntrospectionPath, policyCORSIntrospection.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthIntrospectionPOST))))
+ r.OPTIONS(oidc.EndpointPathIntrospection, policyCORSIntrospection.HandleOPTIONS)
+ r.POST(oidc.EndpointPathIntrospection, policyCORSIntrospection.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthIntrospectionPOST))))
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
r.OPTIONS("/api/oidc/introspect", policyCORSIntrospection.HandleOPTIONS)
@@ -310,13 +315,13 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
policyCORSRevocation := middlewares.NewCORSPolicyBuilder().
WithAllowCredentials(true).
- WithAllowedMethods("OPTIONS", "POST").
+ WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodPost).
WithAllowedOrigins(allowedOrigins...).
- WithEnabled(utils.IsStringInSlice(oidc.RevocationEndpoint, config.IdentityProviders.OIDC.CORS.Endpoints)).
+ WithEnabled(utils.IsStringInSlice(oidc.EndpointRevocation, config.IdentityProviders.OIDC.CORS.Endpoints)).
Build()
- r.OPTIONS(oidc.RevocationPath, policyCORSRevocation.HandleOPTIONS)
- r.POST(oidc.RevocationPath, policyCORSRevocation.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthRevocationPOST))))
+ r.OPTIONS(oidc.EndpointPathRevocation, policyCORSRevocation.HandleOPTIONS)
+ r.POST(oidc.EndpointPathRevocation, policyCORSRevocation.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthRevocationPOST))))
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
r.OPTIONS("/api/oidc/revoke", policyCORSRevocation.HandleOPTIONS)
diff --git a/internal/server/locales/ar-SA/portal.json b/internal/server/locales/ar-SA/portal.json
new file mode 100644
index 000000000..eee796828
--- /dev/null
+++ b/internal/server/locales/ar-SA/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "قبول",
+ "Access your email addresses": "الوصول إلى عناوين بريدك الإلكتروني",
+ "Access your group membership": "الوصول إلى عضوية المجموعة الخاصة بك",
+ "Access your profile information": "الوصول إلى معلومات ملفك الشخصي",
+ "An email has been sent to your address to complete the process": "تم إرسال بريد إلكتروني إلى العنوان الخاص بك لإكمال العملية.",
+ "Authenticated": "مصادق",
+ "Automatically refresh these permissions without user interaction": "تحديث هذه الأذونات تلقائياً بدون تفاعل المستخدم",
+ "Cancel": "إلغاء",
+ "Client ID": "معرف العميل: {{client_id}}",
+ "Consent Request": "طلب الموافقة",
+ "Contact your administrator to register a device": "اتصل بالمسؤول لتسجيل الجهاز.",
+ "Could not obtain user settings": "تعذر الحصول على إعدادات المستخدم",
+ "Deny": "رفض",
+ "Done": "تم",
+ "Enter new password": "أدخل كلمة مرور جديدة",
+ "Enter one-time password": "أدخل كلمة المرور لمرة واحدة",
+ "Failed to register device, the provided link is expired or has already been used": "فشل في تسجيل الجهاز، انتهت صلاحية الرابط المتوفر أو تم استخدامه بالفعل",
+ "Hi": "Hi",
+ "Incorrect username or password": "اسم المستخدم أو كلمة المرور غير صحيحة.",
+ "Loading": "جاري التحميل",
+ "Login": "تسجيل الدخول",
+ "Logout": "تسجيل الخروج",
+ "Lost your device?": "فقدت جهازك؟",
+ "Methods": "الأساليب",
+ "Must be at least {{len}} characters in length": "يجب أن يكون على الأقل {{len}} حرف في الطول",
+ "Must have at least one UPPERCASE letter": "يجب أن يكون لديك حرف واحد على الأقل",
+ "Must have at least one lowercase letter": "يجب أن يحتوي على حرف صغير واحد على الأقل",
+ "Must have at least one number": "يجب أن يكون لديك رقم واحد على الأقل",
+ "Must have at least one special character": "يجب أن يكون لديك حرف خاص واحد على الأقل",
+ "Must not be more than {{len}} characters in length": "يجب ألا يكون أكثر من {{len}} حرف في الطول",
+ "Need Google Authenticator?": "هل تحتاج إلى مؤلف جوجل؟",
+ "New password": "كلمة مرور جديدة",
+ "No verification token provided": "لم يتم تقديم رمز التحقق",
+ "OTP Secret copied to clipboard": "تم نسخ OTP السرية إلى الحافظة.",
+ "OTP URL copied to clipboard": "تم نسخ رابط OTP إلى الحافظة.",
+ "One-Time Password": "كلمة المرور لمرة واحدة",
+ "Password has been reset": "تم إعادة تعيين كلمة المرور.",
+ "Password": "كلمة المرور",
+ "Passwords do not match": "كلمة المرور غير متطابقة.",
+ "Powered by": "مدعوم من قبل",
+ "Push Notification": "دفع الإشعار",
+ "Register device": "تسجيل الجهاز",
+ "Register your first device by clicking on the link below": "قم بتسجيل جهازك الأول بالنقر على الرابط أدناه.",
+ "Remember Consent": "تذكر الموافقة",
+ "Remember me": "تذكر لي",
+ "Repeat new password": "تكرار كلمة المرور الجديدة",
+ "Reset password": "إعادة تعيين كلمة المرور",
+ "Reset password?": "إعادة تعيين كلمة المرور؟",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "سرية",
+ "Security Key - WebAuthN": "مفتاح الأمان - WebAuthN",
+ "Select a Device": "حدد جهاز",
+ "Sign in": "تسجيل الدخول",
+ "Sign out": "تسجيل الخروج",
+ "The above application is requesting the following permissions": "طلب التطبيق أعلاه الأذونات التالية",
+ "The password does not meet the password policy": "كلمة المرور لا تفي بسياسة كلمة المرور",
+ "The resource you're attempting to access requires two-factor authentication": "يتطلب المورد الذي تحاول الوصول إليه مصادقة ذات عاملين.",
+ "There was a problem initiating the registration process": "كانت هناك مشكلة في بدء عملية التسجيل",
+ "There was an issue completing the process. The verification token might have expired": "كانت هناك مشكلة في إكمال العملية، ربما تكون علامة التحقق قد انتهت.",
+ "There was an issue initiating the password reset process": "حدثت مشكلة أثناء بدء عملية إعادة تعيين كلمة المرور.",
+ "There was an issue resetting the password": "حدثت مشكلة أثناء إعادة تعيين كلمة المرور",
+ "There was an issue signing out": "حدثت مشكلة أثناء تسجيل الدخول",
+ "This saves this consent as a pre-configured consent for future use": "هذا يحفظ هذه الموافقة كموافقة مهيأة مسبقا للاستخدام في المستقبل",
+ "Time-based One-Time Password": "كلمة المرور لمرة واحدة حسب الوقت",
+ "Use OpenID to verify your identity": "استخدم OpenID للتحقق من هويتك",
+ "Username": "اسم المستخدم",
+ "You must open the link from the same device and browser that initiated the registration process": "يجب فتح الرابط من نفس الجهاز والمتصفح الذي بدأ عملية التسجيل",
+ "You're being signed out and redirected": "يتم تسجيل الخروج وإعادة التوجيه",
+ "Your supplied password does not meet the password policy requirements": "كلمة المرور المقدمة الخاصة بك لا تفي بمتطلبات سياسة كلمة المرور."
+}
diff --git a/internal/server/locales/cs-CZ/portal.json b/internal/server/locales/cs-CZ/portal.json
new file mode 100644
index 000000000..f5dd71945
--- /dev/null
+++ b/internal/server/locales/cs-CZ/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Přijmout",
+ "Access your email addresses": "Přístup k vašim e-mailovým adresám",
+ "Access your group membership": "Přístup k Vašemu členství ve skupině",
+ "Access your profile information": "Přístup k informacím o vašem profilu",
+ "An email has been sent to your address to complete the process": "Na vaši adresu byl odeslán e-mail pro dokončení procesu.",
+ "Authenticated": "Ověřeno",
+ "Automatically refresh these permissions without user interaction": "Automaticky obnovit tato oprávnění bez interakce uživatele",
+ "Cancel": "Zrušit",
+ "Client ID": "ID klienta: {{client_id}}",
+ "Consent Request": "Žádost o souhlas",
+ "Contact your administrator to register a device": "Obraťte se na správce pro registraci zařízení.",
+ "Could not obtain user settings": "Nelze získat uživatelské nastavení",
+ "Deny": "Zamítnout",
+ "Done": "Hotovo",
+ "Enter new password": "Zadejte nové heslo",
+ "Enter one-time password": "Zadejte jednorázové heslo",
+ "Failed to register device, the provided link is expired or has already been used": "Registrace zařízení se nezdařila, odkaz vypršel nebo byl již použit",
+ "Hi": "Hi",
+ "Incorrect username or password": "Nesprávné uživatelské jméno nebo heslo.",
+ "Loading": "Načítání",
+ "Login": "Přihlásit se",
+ "Logout": "Odhlásit se",
+ "Lost your device?": "Zapomněli jste své zařízení?",
+ "Methods": "Metody",
+ "Must be at least {{len}} characters in length": "Musí mít alespoň {{len}} znaků",
+ "Must have at least one UPPERCASE letter": "Musí mít alespoň jedno písmeno UPPERCASE",
+ "Must have at least one lowercase letter": "Musí mít alespoň jedno malé písmeno",
+ "Must have at least one number": "Musí mít alespoň jedno číslo",
+ "Must have at least one special character": "Musí mít alespoň jeden speciální znak",
+ "Must not be more than {{len}} characters in length": "Nesmí být delší než {{len}} znaků",
+ "Need Google Authenticator?": "Potřebujete Google Authenticator?",
+ "New password": "Nové heslo",
+ "No verification token provided": "Nebyl zadán ověřovací token",
+ "OTP Secret copied to clipboard": "OTP tajný klíč zkopírován do schránky.",
+ "OTP URL copied to clipboard": "OTP URL zkopírováno do schránky.",
+ "One-Time Password": "Jednorázové heslo",
+ "Password has been reset": "Heslo bylo obnoveno.",
+ "Password": "Heslo",
+ "Passwords do not match": "Hesla se neshodují.",
+ "Powered by": "Běží na",
+ "Push Notification": "Push oznámení",
+ "Register device": "Registrovat zařízení",
+ "Register your first device by clicking on the link below": "Zaregistrujte své první zařízení kliknutím na odkaz níže.",
+ "Remember Consent": "Zapamatovat souhlas",
+ "Remember me": "Zapamatovat si mě",
+ "Repeat new password": "Opakovat nové heslo",
+ "Reset password": "Obnovit heslo",
+ "Reset password?": "Obnovit heslo?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Tajný klíč",
+ "Security Key - WebAuthN": "Bezpečnostní klíč - WebAuthN",
+ "Select a Device": "Vybrat zařízení",
+ "Sign in": "Přihlásit se",
+ "Sign out": "Odhlásit se",
+ "The above application is requesting the following permissions": "Výše uvedená aplikace požaduje následující oprávnění",
+ "The password does not meet the password policy": "Heslo nesplňuje zásady hesla",
+ "The resource you're attempting to access requires two-factor authentication": "Dokument, ke kterému se snažíte přistupovat, vyžaduje dvoufaktorové ověření.",
+ "There was a problem initiating the registration process": "Vyskytl se problém při zahájení procesu registrace",
+ "There was an issue completing the process. The verification token might have expired": "Vyskytl se problém při dokončování procesu. Ověřovací token možná vypršel.",
+ "There was an issue initiating the password reset process": "Při zahájení procesu obnovení hesla došlo k chybě.",
+ "There was an issue resetting the password": "Při obnovování hesla došlo k problému",
+ "There was an issue signing out": "Při odhlášení došlo k problému",
+ "This saves this consent as a pre-configured consent for future use": "Toto uloží tento souhlas jako přednastavený souhlas pro budoucí použití",
+ "Time-based One-Time Password": "Časově založené jednočasové heslo",
+ "Use OpenID to verify your identity": "Použijte OpenID k ověření Vaší identity",
+ "Username": "Uživatelské jméno",
+ "You must open the link from the same device and browser that initiated the registration process": "Musíte otevřít odkaz ze stejného zařízení a prohlížeče, který inicioval proces registrace",
+ "You're being signed out and redirected": "Probíhá odhlášení a přesměrování",
+ "Your supplied password does not meet the password policy requirements": "Zadané heslo nesplňuje požadavky zásad hesla."
+}
diff --git a/internal/server/locales/da-DK/portal.json b/internal/server/locales/da-DK/portal.json
new file mode 100644
index 000000000..b28663849
--- /dev/null
+++ b/internal/server/locales/da-DK/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Accepter",
+ "Access your email addresses": "Få adgang til dine e-mailadresser",
+ "Access your group membership": "Få adgang til dit gruppemedlemskab",
+ "Access your profile information": "Få adgang til dine profiloplysninger",
+ "An email has been sent to your address to complete the process": "En e-mail er blevet sendt til din adresse for at fuldføre processen.",
+ "Authenticated": "Godkendt",
+ "Automatically refresh these permissions without user interaction": "Opdater automatisk disse tilladelser uden brugerinteraktion",
+ "Cancel": "Annuller",
+ "Client ID": "Klient ID: {{client_id}}",
+ "Consent Request": "Samtykke Anmodning",
+ "Contact your administrator to register a device": "Kontakt din administrator for at registrere en enhed.",
+ "Could not obtain user settings": "Kunne ikke få brugerindstillinger",
+ "Deny": "Afvis",
+ "Done": "Udført",
+ "Enter new password": "Indtast ny adgangskode",
+ "Enter one-time password": "Indtast engangs adgangskode",
+ "Failed to register device, the provided link is expired or has already been used": "Kunne ikke registrere enheden, det medfølgende link er udløbet eller er allerede blevet brugt",
+ "Hi": "Hi",
+ "Incorrect username or password": "Forkert brugernavn eller adgangskode.",
+ "Loading": "Indlæser",
+ "Login": "Login",
+ "Logout": "Log Ud",
+ "Lost your device?": "Har du mistet din enhed?",
+ "Methods": "Metoder",
+ "Must be at least {{len}} characters in length": "Skal være mindst {{len}} tegn i længden",
+ "Must have at least one UPPERCASE letter": "Skal have mindst et UPPERCASE bogstav",
+ "Must have at least one lowercase letter": "Skal have mindst et lille bogstav",
+ "Must have at least one number": "Skal have mindst et tal",
+ "Must have at least one special character": "Skal have mindst et specialtegn",
+ "Must not be more than {{len}} characters in length": "Må ikke være mere end {{len}} tegn i længden",
+ "Need Google Authenticator?": "Har du brug for Google Authenticator?",
+ "New password": "Ny adgangskode",
+ "No verification token provided": "Ingen bekræftelsestoken angivet",
+ "OTP Secret copied to clipboard": "OTP Secret kopieret til udklipsholder.",
+ "OTP URL copied to clipboard": "OTP URL kopieret til udklipsholder.",
+ "One-Time Password": "Engangs Adgangskode",
+ "Password has been reset": "Adgangskode er blevet nulstillet.",
+ "Password": "Adgangskode",
+ "Passwords do not match": "Adgangskoder stemmer ikke overens.",
+ "Powered by": "Drevet af",
+ "Push Notification": "Push Notification",
+ "Register device": "Registrer enhed",
+ "Register your first device by clicking on the link below": "Registrer din første enhed ved at klikke på linket nedenfor.",
+ "Remember Consent": "Husk Samtykke",
+ "Remember me": "Husk mig",
+ "Repeat new password": "Gentag ny adgangskode",
+ "Reset password": "Nulstil adgangskode",
+ "Reset password?": "Nulstil adgangskode?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Hemmelighed",
+ "Security Key - WebAuthN": "Sikkerhedsnøgle - WebAuthN",
+ "Select a Device": "Vælg en enhed",
+ "Sign in": "Log ind",
+ "Sign out": "Log ud",
+ "The above application is requesting the following permissions": "Ovenstående program anmoder om følgende tilladelser",
+ "The password does not meet the password policy": "Adgangskoden opfylder ikke adgangskodepolitikken",
+ "The resource you're attempting to access requires two-factor authentication": "Den ressource, du forsøger at få adgang til, kræver to-faktor godkendelse.",
+ "There was a problem initiating the registration process": "Der var et problem med at indlede registreringsprocessen",
+ "There was an issue completing the process. The verification token might have expired": "Der opstod et problem ved at fuldføre processen. Bekræftelsestoken kan være udløbet.",
+ "There was an issue initiating the password reset process": "Der opstod et problem ved at starte nulstilling af adgangskode.",
+ "There was an issue resetting the password": "Der opstod et problem ved nulstilling af adgangskoden",
+ "There was an issue signing out": "Der opstod et problem ved at logge ud",
+ "This saves this consent as a pre-configured consent for future use": "Dette gemmer dette samtykke som et prækonfigureret samtykke til fremtidig brug",
+ "Time-based One-Time Password": "Tidsbaseret Engangskodeord",
+ "Use OpenID to verify your identity": "Brug OpenID til at bekræfte din identitet",
+ "Username": "Brugernavn",
+ "You must open the link from the same device and browser that initiated the registration process": "Du skal åbne linket fra den samme enhed og browser, der startede registreringsprocessen",
+ "You're being signed out and redirected": "Du bliver logget ud og omdirigeret",
+ "Your supplied password does not meet the password policy requirements": "Din indtastede adgangskode opfylder ikke kravene til adgangskodepolitik."
+}
diff --git a/internal/server/locales/de/portal.json b/internal/server/locales/de-DE/portal.json
similarity index 100%
rename from internal/server/locales/de/portal.json
rename to internal/server/locales/de-DE/portal.json
diff --git a/internal/server/locales/el-GR/portal.json b/internal/server/locales/el-GR/portal.json
new file mode 100644
index 000000000..a7d24fe75
--- /dev/null
+++ b/internal/server/locales/el-GR/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Αποδοχή",
+ "Access your email addresses": "Πρόσβαση στις διευθύνσεις email σας",
+ "Access your group membership": "Πρόσβαση στην ομάδα σας",
+ "Access your profile information": "Πρόσβαση στις πληροφορίες του προφίλ σας",
+ "An email has been sent to your address to complete the process": "Ένα μήνυμα ηλεκτρονικού ταχυδρομείου στάλθηκε στη διεύθυνσή σας για να ολοκληρωθεί η διαδικασία.",
+ "Authenticated": "Επικυρώθηκε",
+ "Automatically refresh these permissions without user interaction": "Αυτόματη ανανέωση αυτών των δικαιωμάτων χωρίς αλληλεπίδραση χρήστη",
+ "Cancel": "Ακύρωση",
+ "Client ID": "ID πελάτη: {{client_id}}",
+ "Consent Request": "Αίτημα Συναίνεσης",
+ "Contact your administrator to register a device": "Επικοινωνήστε με το διαχειριστή σας για να καταχωρήσετε μια συσκευή.",
+ "Could not obtain user settings": "Αδυναμία λήψης ρυθμίσεων χρήστη",
+ "Deny": "Άρνηση",
+ "Done": "Ολοκληρώθηκε",
+ "Enter new password": "Εισάγετε νέο κωδικό πρόσβασης",
+ "Enter one-time password": "Εισάγετε κωδικό μιας χρήσης",
+ "Failed to register device, the provided link is expired or has already been used": "Αποτυχία εγγραφής συσκευής, ο παρεχόμενος σύνδεσμος έχει λήξει ή έχει ήδη χρησιμοποιηθεί",
+ "Hi": "Hi",
+ "Incorrect username or password": "Λάθος όνομα χρήστη ή κωδικός πρόσβασης.",
+ "Loading": "Φόρτωση",
+ "Login": "Είσοδος",
+ "Logout": "Αποσύνδεση",
+ "Lost your device?": "Χάσατε τη συσκευή σας?",
+ "Methods": "Μέθοδοι",
+ "Must be at least {{len}} characters in length": "Πρέπει να είναι τουλάχιστον {{len}} χαρακτήρες σε μήκος",
+ "Must have at least one UPPERCASE letter": "Πρέπει να έχει τουλάχιστον ένα γράμμα UPPERCASE",
+ "Must have at least one lowercase letter": "Πρέπει να έχει τουλάχιστον ένα πεζό γράμμα",
+ "Must have at least one number": "Πρέπει να έχει τουλάχιστον έναν αριθμό",
+ "Must have at least one special character": "Πρέπει να έχει τουλάχιστον έναν ειδικό χαρακτήρα",
+ "Must not be more than {{len}} characters in length": "Πρέπει να μην υπερβαίνει τους {{len}} χαρακτήρες σε μήκος",
+ "Need Google Authenticator?": "Χρειάζεστε Google Authenticator?",
+ "New password": "Νέος κωδικός πρόσβασης",
+ "No verification token provided": "Δεν παρέχεται διακριτικό επαλήθευσης",
+ "OTP Secret copied to clipboard": "Το OTP Secret αντιγράφηκε στο πρόχειρο.",
+ "OTP URL copied to clipboard": "Η διεύθυνση URL OTP αντιγράφηκε στο πρόχειρο.",
+ "One-Time Password": "Κωδικός Μίας Χρήσης",
+ "Password has been reset": "Έχει γίνει επαναφορά του κωδικού πρόσβασης.",
+ "Password": "Κωδικός",
+ "Passwords do not match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν.",
+ "Powered by": "Τροφοδοτείται από",
+ "Push Notification": "Ειδοποίηση Push",
+ "Register device": "Εγγραφή συσκευής",
+ "Register your first device by clicking on the link below": "Καταχωρήστε την πρώτη σας συσκευή κάνοντας κλικ στον παρακάτω σύνδεσμο.",
+ "Remember Consent": "Απομνημόνευση Συγκατάθεσης",
+ "Remember me": "Να με θυμάσαι",
+ "Repeat new password": "Επανάληψη νέου κωδικού πρόσβασης",
+ "Reset password": "Επαναφορά κωδικού πρόσβασης",
+ "Reset password?": "Επαναφορά κωδικού πρόσβασης?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Μυστικό",
+ "Security Key - WebAuthN": "Κλειδί Ασφαλείας - WebAuthn",
+ "Select a Device": "Επιλέξτε μια συσκευή",
+ "Sign in": "Σύνδεση",
+ "Sign out": "Αποσύνδεση",
+ "The above application is requesting the following permissions": "Η παραπάνω εφαρμογή ζητά τα ακόλουθα δικαιώματα",
+ "The password does not meet the password policy": "Ο κωδικός πρόσβασης δεν ανταποκρίνεται στην πολιτική κωδικού πρόσβασης",
+ "The resource you're attempting to access requires two-factor authentication": "Ο πόρος που προσπαθείτε να αποκτήσετε πρόσβαση απαιτεί έλεγχο ταυτότητας δύο παραγόντων.",
+ "There was a problem initiating the registration process": "Υπήρξε ένα πρόβλημα κατά την έναρξη της διαδικασίας εγγραφής",
+ "There was an issue completing the process. The verification token might have expired": "Υπήρξε ένα ζήτημα ολοκλήρωσης της διαδικασίας. Το διακριτικό επαλήθευσης μπορεί να έχει λήξει.",
+ "There was an issue initiating the password reset process": "Υπήρξε ένα ζήτημα κατά την έναρξη της διαδικασίας επαναφοράς κωδικού πρόσβασης.",
+ "There was an issue resetting the password": "Υπήρξε ένα ζήτημα κατά την επαναφορά του κωδικού πρόσβασης",
+ "There was an issue signing out": "Υπήρξε ένα πρόβλημα αποσύνδεσης",
+ "This saves this consent as a pre-configured consent for future use": "Αυτό αποθηκεύει αυτή τη συγκατάθεση ως προδιαμορφωμένη συγκατάθεση για μελλοντική χρήση",
+ "Time-based One-Time Password": "Κωδικός Μιας Χρήσης Με Χρόνο",
+ "Use OpenID to verify your identity": "Χρησιμοποιήστε OpenID για να επαληθεύσετε την ταυτότητά σας",
+ "Username": "Όνομα Χρήστη",
+ "You must open the link from the same device and browser that initiated the registration process": "Πρέπει να ανοίξετε το σύνδεσμο από την ίδια συσκευή και το πρόγραμμα περιήγησης που ξεκίνησε τη διαδικασία εγγραφής",
+ "You're being signed out and redirected": "Είστε αποσυνδεδεμένοι και ανακατευθύνονται",
+ "Your supplied password does not meet the password policy requirements": "Ο παρεχόμενος κωδικός πρόσβασης δεν πληροί τις απαιτήσεις πολιτικής κωδικού πρόσβασης."
+}
diff --git a/internal/server/locales/es/portal.json b/internal/server/locales/es-ES/portal.json
similarity index 94%
rename from internal/server/locales/es/portal.json
rename to internal/server/locales/es-ES/portal.json
index 782ba166e..77fa1e619 100644
--- a/internal/server/locales/es/portal.json
+++ b/internal/server/locales/es-ES/portal.json
@@ -5,6 +5,7 @@
"Access your profile information": "Acceder a tu información de perfil",
"An email has been sent to your address to complete the process": "Un correo ha sido enviado a su cuenta para completar el proceso.",
"Authenticated": "Autenticado",
+ "Automatically refresh these permissions without user interaction": "Actualizar automáticamente estos permisos sin interacción del usuario",
"Cancel": "Cancelar",
"Client ID": "ID de cliente: {{client_id}}",
"Consent Request": "Solicitud de consentimiento",
@@ -18,7 +19,7 @@
"Hi": "Hola",
"Incorrect username or password": "Nombre de usuario o contraseña incorrectos.",
"Loading": "Cargando",
- "Login":"Iniciar Sesión",
+ "Login": "Ingresar",
"Logout": "Cerrar Sesión",
"Lost your device?": "¿Ha perdido su dispositivo?",
"Methods": "Métodos",
@@ -61,7 +62,7 @@
"There was an issue initiating the password reset process": "Hubo un problema al iniciar el proceso de restablecimiento de contraseña.",
"There was an issue resetting the password": "Ocurrió un error al intentar restablecer la contraseña",
"There was an issue signing out": "Ocurrió un error al intentar cerrar sesión",
- "This saves this consent as a pre-configured consent for future use": "Esto guarda este consentimiento como consentimiento preconfigurado para uso futuro",
+ "This saves this consent as a pre-configured consent for future use": "Esto guarda este consentimiento como consentimiento pre-configurado para uso futuro",
"Time-based One-Time Password": "Contraseña de uso único - OTP",
"Use OpenID to verify your identity": "Utilizar OpenID para verificar su identidad",
"Username": "Usuario",
diff --git a/internal/server/locales/fi-FI/portal.json b/internal/server/locales/fi-FI/portal.json
new file mode 100644
index 000000000..88510732d
--- /dev/null
+++ b/internal/server/locales/fi-FI/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Hyväksy",
+ "Access your email addresses": "Käytä sähköpostiosoitteitasi",
+ "Access your group membership": "Käytä ryhmän jäsenyyttä",
+ "Access your profile information": "Käytä profiilitietojasi",
+ "An email has been sent to your address to complete the process": "Prosessin loppuun saattamiseksi on lähetetty sähköpostiosoite. @ info",
+ "Authenticated": "Todennettu",
+ "Automatically refresh these permissions without user interaction": "Päivitä nämä oikeudet automaattisesti ilman käyttäjän vuorovaikutusta",
+ "Cancel": "Peruuta",
+ "Client ID": "Asiakkaan tunnus: {{client_id}}",
+ "Consent Request": "Hyväksyntä Pyyntö",
+ "Contact your administrator to register a device": "Ota yhteyttä järjestelmänvalvojaan rekisteröidäksesi laitteen.",
+ "Could not obtain user settings": "Käyttäjän asetuksia ei saatu",
+ "Deny": "Estä",
+ "Done": "Valmis",
+ "Enter new password": "Syötä uusi salasana",
+ "Enter one-time password": "Syötä kertakäyttösalasana",
+ "Failed to register device, the provided link is expired or has already been used": "Laitteen rekisteröinti epäonnistui, annettu linkki on vanhentunut tai sitä on jo käytetty",
+ "Hi": "Hi",
+ "Incorrect username or password": "Virheellinen käyttäjätunnus tai salasana.",
+ "Loading": "Ladataan",
+ "Login": "Kirjaudu",
+ "Logout": "Kirjaudu Ulos",
+ "Lost your device?": "Unohtuiko laitteesi?",
+ "Methods": "Menetelmät",
+ "Must be at least {{len}} characters in length": "Täytyy olla vähintään {{len}} merkkiä pitkä",
+ "Must have at least one UPPERCASE letter": "Täytyy olla vähintään yksi UPPERCASE kirjain",
+ "Must have at least one lowercase letter": "Täytyy olla vähintään yksi pieni kirjain",
+ "Must have at least one number": "Täytyy olla vähintään yksi numero",
+ "Must have at least one special character": "Täytyy olla vähintään yksi erikoismerkki",
+ "Must not be more than {{len}} characters in length": "Ei saa olla enempää kuin {{len}} merkkiä",
+ "Need Google Authenticator?": "Tarvitsetko Googlen Todennus?",
+ "New password": "Uusi salasana",
+ "No verification token provided": "Vahvistusmerkkiä ei ole annettu",
+ "OTP Secret copied to clipboard": "OTP salainen kopioitu leikepöydälle.",
+ "OTP URL copied to clipboard": "OTP URL kopioitu leikepöydälle.",
+ "One-Time Password": "Kertakäyttöinen Salasana",
+ "Password has been reset": "Salasana on nollattu.",
+ "Password": "Salasana",
+ "Passwords do not match": "Salasanat eivät täsmää.",
+ "Powered by": "Palvelun tarjoaa",
+ "Push Notification": "Push-Ilmoitus",
+ "Register device": "Rekisteröi laite",
+ "Register your first device by clicking on the link below": "Rekisteröi ensimmäinen laite klikkaamalla alla olevaa linkkiä.",
+ "Remember Consent": "Muista Hyväksyntä",
+ "Remember me": "Muista minut",
+ "Repeat new password": "Toista uusi salasana",
+ "Reset password": "Nollaa salasana",
+ "Reset password?": "Palauta salasana?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Salainen",
+ "Security Key - WebAuthN": "Suojausavain - WebAuthN",
+ "Select a Device": "Valitse laite",
+ "Sign in": "Kirjaudu sisään",
+ "Sign out": "Kirjaudu ulos",
+ "The above application is requesting the following permissions": "Edellä mainittu hakemus pyytää seuraavia käyttöoikeuksia",
+ "The password does not meet the password policy": "Salasana ei vastaa salasanakäytäntöä",
+ "The resource you're attempting to access requires two-factor authentication": "Resurssi, jota yrität käyttää vaatii kaksivaiheisen todennuksen.",
+ "There was a problem initiating the registration process": "Rekisteröintiprosessin käynnistämisessä tapahtui ongelma",
+ "There was an issue completing the process. The verification token might have expired": "Prosessin loppuun saattamisessa tapahtui virhe. Vahvistusmerkki saattaa olla vanhentunut.",
+ "There was an issue initiating the password reset process": "Salasanan nollausprosessin käynnistämisessä tapahtui virhe.",
+ "There was an issue resetting the password": "Salasanan palauttamisessa tapahtui virhe",
+ "There was an issue signing out": "Sisäänkirjautuminen ulos tapahtui virhe",
+ "This saves this consent as a pre-configured consent for future use": "Tämä tallentaa tämän suostumuksen ennalta määritettynä suostumuksena tulevaa käyttöä varten",
+ "Time-based One-Time Password": "Aikaperusteinen Kertasalasana",
+ "Use OpenID to verify your identity": "Käytä OpenID:tä tunnistaaksesi henkilöllisyytesi",
+ "Username": "Käyttäjätunnus",
+ "You must open the link from the same device and browser that initiated the registration process": "Sinun on avattava linkki samasta laitteesta ja selaimesta, joka käynnisti rekisteröintiprosessin",
+ "You're being signed out and redirected": "Sinut kirjaudutaan ulos ja ohjataan uudelleen",
+ "Your supplied password does not meet the password policy requirements": "Syötetty salasana ei täytä salasanakäytännön vaatimuksia."
+}
diff --git a/internal/server/locales/fr/portal.json b/internal/server/locales/fr-FR/portal.json
similarity index 100%
rename from internal/server/locales/fr/portal.json
rename to internal/server/locales/fr-FR/portal.json
diff --git a/internal/server/locales/it-IT/portal.json b/internal/server/locales/it-IT/portal.json
new file mode 100644
index 000000000..5a7c4afa9
--- /dev/null
+++ b/internal/server/locales/it-IT/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Accetta",
+ "Access your email addresses": "Accedi ai tuoi indirizzi email",
+ "Access your group membership": "Accedi all'iscrizione al tuo gruppo",
+ "Access your profile information": "Accedi alle informazioni del tuo profilo",
+ "An email has been sent to your address to complete the process": "Una email è stata inviata al tuo indirizzo per completare il processo.",
+ "Authenticated": "Autenticato",
+ "Automatically refresh these permissions without user interaction": "Aggiorna automaticamente queste autorizzazioni senza interazione utente",
+ "Cancel": "Annulla",
+ "Client ID": "ID cliente: {{client_id}}",
+ "Consent Request": "Richiesta Di Consenso",
+ "Contact your administrator to register a device": "Contatta l'amministratore per registrare un dispositivo.",
+ "Could not obtain user settings": "Impossibile ottenere le impostazioni utente",
+ "Deny": "Nega",
+ "Done": "Fatto",
+ "Enter new password": "Inserisci una nuova password",
+ "Enter one-time password": "Inserisci password monouso",
+ "Failed to register device, the provided link is expired or has already been used": "Impossibile registrare il dispositivo, il link fornito è scaduto o è già stato utilizzato",
+ "Hi": "Hi",
+ "Incorrect username or password": "Nome utente o password errati.",
+ "Loading": "Caricamento",
+ "Login": "Accedi",
+ "Logout": "Esci",
+ "Lost your device?": "Hai perso il dispositivo?",
+ "Methods": "Metodi",
+ "Must be at least {{len}} characters in length": "Deve essere di almeno {{len}} caratteri di lunghezza",
+ "Must have at least one UPPERCASE letter": "Deve avere almeno una lettera UPPERCASE",
+ "Must have at least one lowercase letter": "Deve avere almeno una lettera minuscola",
+ "Must have at least one number": "Deve avere almeno un numero",
+ "Must have at least one special character": "Deve avere almeno un carattere speciale",
+ "Must not be more than {{len}} characters in length": "Non deve contenere più di {{len}} caratteri di lunghezza",
+ "Need Google Authenticator?": "Hai Bisogno Di Google Authenticator?",
+ "New password": "Nuova password",
+ "No verification token provided": "Nessun token di verifica fornito",
+ "OTP Secret copied to clipboard": "OTP Secret copiato negli appunti.",
+ "OTP URL copied to clipboard": "URL OTP copiato negli appunti.",
+ "One-Time Password": "Password Unica",
+ "Password has been reset": "La password è stata reimpostata.",
+ "Password": "Password",
+ "Passwords do not match": "Le password non corrispondono.",
+ "Powered by": "Alimentato da",
+ "Push Notification": "Notifica Push",
+ "Register device": "Registra dispositivo",
+ "Register your first device by clicking on the link below": "Registra il tuo primo dispositivo cliccando sul link qui sotto.",
+ "Remember Consent": "Ricorda Consenso",
+ "Remember me": "Ricordati di me",
+ "Repeat new password": "Ripeti la nuova password",
+ "Reset password": "Reimposta password",
+ "Reset password?": "Reimposta la password?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Segreto",
+ "Security Key - WebAuthN": "Chiave Di Sicurezza - WebAuthN",
+ "Select a Device": "Seleziona un dispositivo",
+ "Sign in": "Accedi",
+ "Sign out": "Esci",
+ "The above application is requesting the following permissions": "L'applicazione di cui sopra richiede i seguenti permessi",
+ "The password does not meet the password policy": "La password non soddisfa il criterio della password",
+ "The resource you're attempting to access requires two-factor authentication": "La risorsa che stai tentando di accedere richiede un'autenticazione a due fattori.",
+ "There was a problem initiating the registration process": "Si è verificato un problema durante l'avvio del processo di registrazione",
+ "There was an issue completing the process. The verification token might have expired": "Si è verificato un problema nel completamento del processo. Il token di verifica potrebbe essere scaduto.",
+ "There was an issue initiating the password reset process": "Si è verificato un problema iniziando il processo di reimpostazione della password.",
+ "There was an issue resetting the password": "Si è verificato un problema nel reimpostare la password",
+ "There was an issue signing out": "C'è stato un problema che si è concluso",
+ "This saves this consent as a pre-configured consent for future use": "Questo salva questo consenso come consenso preconfigurato per un uso futuro",
+ "Time-based One-Time Password": "Password Una Volta Basata",
+ "Use OpenID to verify your identity": "Usa OpenID per verificare la tua identità",
+ "Username": "Username",
+ "You must open the link from the same device and browser that initiated the registration process": "È necessario aprire il link dallo stesso dispositivo e browser che ha avviato il processo di registrazione",
+ "You're being signed out and redirected": "Sei stato disconnesso e reindirizzato",
+ "Your supplied password does not meet the password policy requirements": "La password fornita non soddisfa i requisiti di politica di password."
+}
diff --git a/internal/server/locales/ja-JP/portal.json b/internal/server/locales/ja-JP/portal.json
new file mode 100644
index 000000000..11d85e036
--- /dev/null
+++ b/internal/server/locales/ja-JP/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "同意する",
+ "Access your email addresses": "メールアドレスにアクセスする",
+ "Access your group membership": "グループメンバーシップにアクセス",
+ "Access your profile information": "プロフィール情報にアクセスする",
+ "An email has been sent to your address to complete the process": "プロセスを完了するためにあなたのアドレスにメールが送信されました。",
+ "Authenticated": "認証済み",
+ "Automatically refresh these permissions without user interaction": "ユーザーの操作なしでこれらの権限を自動的に更新する",
+ "Cancel": "キャンセル",
+ "Client ID": "クライアント ID: {{client_id}}",
+ "Consent Request": "同意リクエスト",
+ "Contact your administrator to register a device": "デバイスを登録するには管理者に問い合わせてください。",
+ "Could not obtain user settings": "ユーザー設定を取得できませんでした",
+ "Deny": "拒否",
+ "Done": "完了",
+ "Enter new password": "新しいパスワードを入力",
+ "Enter one-time password": "ワンタイムパスワードを入力してください",
+ "Failed to register device, the provided link is expired or has already been used": "デバイスの登録に失敗しました。指定されたリンクは期限切れか既に使用されています。",
+ "Hi": "Hi",
+ "Incorrect username or password": "ユーザー名またはパスワードが違います。",
+ "Loading": "読み込み中",
+ "Login": "ログイン",
+ "Logout": "ログアウト",
+ "Lost your device?": "デバイスを紛失しましたか?",
+ "Methods": "メソッド",
+ "Must be at least {{len}} characters in length": "少なくとも {{len}} 文字以上でなければなりません",
+ "Must have at least one UPPERCASE letter": "少なくとも1つのUPPERCASE文字が必要です",
+ "Must have at least one lowercase letter": "少なくとも1つの小文字が必要です",
+ "Must have at least one number": "少なくとも1つの数字が必要です",
+ "Must have at least one special character": "少なくとも1つの特殊文字が必要です",
+ "Must not be more than {{len}} characters in length": "長さは {{len}} 文字以上にしてください",
+ "Need Google Authenticator?": "Google 認証システムが必要ですか?",
+ "New password": "新しいパスワード",
+ "No verification token provided": "認証トークンが提供されていません",
+ "OTP Secret copied to clipboard": "OTPシークレットをクリップボードにコピーしました。",
+ "OTP URL copied to clipboard": "OTP URL をクリップボードにコピーしました。",
+ "One-Time Password": "ワンタイムパスワード",
+ "Password has been reset": "パスワードがリセットされました。",
+ "Password": "パスワード",
+ "Passwords do not match": "パスワードが一致しません。",
+ "Powered by": "Powered by",
+ "Push Notification": "プッシュ通知",
+ "Register device": "デバイスを登録する",
+ "Register your first device by clicking on the link below": "下のリンクをクリックして、最初のデバイスを登録します。",
+ "Remember Consent": "同意を保存",
+ "Remember me": "ログイン状態を維持する",
+ "Repeat new password": "新しいパスワードを再入力",
+ "Reset password": "パスワードのリセット",
+ "Reset password?": "パスワードのリセット?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "シークレット",
+ "Security Key - WebAuthN": "セキュリティキー - WebAuthN",
+ "Select a Device": "デバイスを選択",
+ "Sign in": "サインイン",
+ "Sign out": "サインアウト",
+ "The above application is requesting the following permissions": "上記のアプリケーションは、次の権限を要求しています",
+ "The password does not meet the password policy": "パスワードがパスワードポリシーを満たしていません",
+ "The resource you're attempting to access requires two-factor authentication": "アクセスしようとしているリソースには2要素認証が必要です。",
+ "There was a problem initiating the registration process": "登録プロセスの開始時に問題が発生しました",
+ "There was an issue completing the process. The verification token might have expired": "プロセスの完了に問題がありました。検証トークンの有効期限が切れている可能性があります。",
+ "There was an issue initiating the password reset process": "パスワードのリセット処理中に問題が発生しました。",
+ "There was an issue resetting the password": "パスワードをリセットする際に問題が発生しました",
+ "There was an issue signing out": "サインアウト中に問題が発生しました",
+ "This saves this consent as a pre-configured consent for future use": "これにより、この同意を将来の使用のための事前設定された同意として保存します。",
+ "Time-based One-Time Password": "時間ベースのワンタイムパスワード",
+ "Use OpenID to verify your identity": "OpenIDを使用して身元を確認してください",
+ "Username": "ユーザー名",
+ "You must open the link from the same device and browser that initiated the registration process": "登録プロセスを開始した同じデバイスとブラウザからリンクを開く必要があります",
+ "You're being signed out and redirected": "サインアウトしてリダイレクトしています",
+ "Your supplied password does not meet the password policy requirements": "入力されたパスワードは、パスワードポリシーの要件を満たしていません。"
+}
diff --git a/internal/server/locales/nb-NO/portal.json b/internal/server/locales/nb-NO/portal.json
new file mode 100644
index 000000000..4e8737478
--- /dev/null
+++ b/internal/server/locales/nb-NO/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Godta",
+ "Access your email addresses": "Få tilgang til e-postadressene",
+ "Access your group membership": "Tilgang til ditt gruppemedlemskap",
+ "Access your profile information": "Få tilgang til profilinformasjonen din",
+ "An email has been sent to your address to complete the process": "En e-post har blitt sendt til din adresse for å fullføre prosessen.",
+ "Authenticated": "Verifisert",
+ "Automatically refresh these permissions without user interaction": "Oppdater disse tillatelsene automatisk uten brukerinteraksjon",
+ "Cancel": "Avbryt",
+ "Client ID": "Klient-ID: {{client_id}}",
+ "Consent Request": "Samtaleforespørsel",
+ "Contact your administrator to register a device": "Kontakt systemansvarlig for å registrere en enhet.",
+ "Could not obtain user settings": "Kan ikke hente brukerinnstillinger",
+ "Deny": "Avslå",
+ "Done": "Ferdig",
+ "Enter new password": "Skriv inn nytt passord",
+ "Enter one-time password": "Angi engangspassord",
+ "Failed to register device, the provided link is expired or has already been used": "Kan ikke registrere enheten, den angitte lenken er utløpt eller allerede er brukt",
+ "Hi": "Hi",
+ "Incorrect username or password": "Ugyldig brukernavn eller passord.",
+ "Loading": "Laster",
+ "Login": "Innlogging",
+ "Logout": "Logg",
+ "Lost your device?": "Har du mistet enheten?",
+ "Methods": "Metoder",
+ "Must be at least {{len}} characters in length": "Må være minst {{len}} tegn i lengde",
+ "Must have at least one UPPERCASE letter": "Må ha minst én STORE bokstav",
+ "Must have at least one lowercase letter": "Må ha minst én liten bokstav",
+ "Must have at least one number": "Må ha minst ett tall",
+ "Must have at least one special character": "Må ha minst ett spesialtegn",
+ "Must not be more than {{len}} characters in length": "Må ikke være mer enn {{len}} tegn i lengde",
+ "Need Google Authenticator?": "Trenger du Google Authenticator?",
+ "New password": "Nytt passord",
+ "No verification token provided": "Ingen verifikasjonstoken angitt",
+ "OTP Secret copied to clipboard": "Hemmelig engangspassord kopiert til utklippstavlen.",
+ "OTP URL copied to clipboard": "OTP-URL kopiert til utklippstavlen.",
+ "One-Time Password": "Engangs passord",
+ "Password has been reset": "Passordet er tilbakestilt.",
+ "Password": "Passord",
+ "Passwords do not match": "Passordene samsvarer ikke.",
+ "Powered by": "Drives av",
+ "Push Notification": "Push varsling",
+ "Register device": "Registrer enheten",
+ "Register your first device by clicking on the link below": "Registrer din første enhet ved å klikke på lenken nedenfor.",
+ "Remember Consent": "Husk samtykke",
+ "Remember me": "Husk meg",
+ "Repeat new password": "Gjenta nytt passord",
+ "Reset password": "Tilbakestill passord",
+ "Reset password?": "Tilbakestill passord?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Hemmelig",
+ "Security Key - WebAuthN": "Sikkerhetsnøkkel - WebAuthN",
+ "Select a Device": "Velg en enhet",
+ "Sign in": "Logg inn",
+ "Sign out": "Logg ut",
+ "The above application is requesting the following permissions": "Programmet ovenfor ber om følgende tillatelser",
+ "The password does not meet the password policy": "Passordet oppfyller ikke minimumskravene for passord",
+ "The resource you're attempting to access requires two-factor authentication": "Ressursen du prøver å få tilgang krever 2-trinnsinnlogging.",
+ "There was a problem initiating the registration process": "Det oppsto et problem med å starte registreringsprosessen",
+ "There was an issue completing the process. The verification token might have expired": "Det oppstod et problem under fullførelse av prosessen. Bekreftelsestokenet kan ha utløpt.",
+ "There was an issue initiating the password reset process": "Det oppstod et problem under oppstart av tilbakestilling av passord.",
+ "There was an issue resetting the password": "Det oppstod et problem under tilbakestilling av passordet",
+ "There was an issue signing out": "Et problem oppstod under utlogging",
+ "This saves this consent as a pre-configured consent for future use": "Dette lagrer dette samtykket som et forhåndskonfigurert samtykke til fremtidig bruk",
+ "Time-based One-Time Password": "Tidsbasert engangspassord",
+ "Use OpenID to verify your identity": "Bruk OpenID for å bekrefte identiteten din",
+ "Username": "Brukernavn",
+ "You must open the link from the same device and browser that initiated the registration process": "Du må åpne linken fra samme enhet og nettleser som startet registreringsprosessen",
+ "You're being signed out and redirected": "Du blir logget ut og omdirigert",
+ "Your supplied password does not meet the password policy requirements": "Ditt angitte passord oppfyller ikke kravene for passord."
+}
diff --git a/internal/server/locales/nl/portal.json b/internal/server/locales/nl-NL/portal.json
similarity index 97%
rename from internal/server/locales/nl/portal.json
rename to internal/server/locales/nl-NL/portal.json
index f31e38c7e..96b7759bd 100644
--- a/internal/server/locales/nl/portal.json
+++ b/internal/server/locales/nl-NL/portal.json
@@ -5,6 +5,7 @@
"Access your profile information": "Toegang tot je profielgegevens",
"An email has been sent to your address to complete the process": "Er is een e-mail verzonden naar uw adres om het proces te voltooien.",
"Authenticated": "Geverifieerd",
+ "Automatically refresh these permissions without user interaction": "Vernieuw deze machtigingen automatisch zonder gebruikersinteractie",
"Cancel": "annuleren",
"Client ID": "Client-ID: {{client_id}}",
"Consent Request": "Toestemming Verzoek",
diff --git a/internal/server/locales/no-NO/portal.json b/internal/server/locales/no-NO/portal.json
new file mode 100644
index 000000000..4e8737478
--- /dev/null
+++ b/internal/server/locales/no-NO/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Godta",
+ "Access your email addresses": "Få tilgang til e-postadressene",
+ "Access your group membership": "Tilgang til ditt gruppemedlemskap",
+ "Access your profile information": "Få tilgang til profilinformasjonen din",
+ "An email has been sent to your address to complete the process": "En e-post har blitt sendt til din adresse for å fullføre prosessen.",
+ "Authenticated": "Verifisert",
+ "Automatically refresh these permissions without user interaction": "Oppdater disse tillatelsene automatisk uten brukerinteraksjon",
+ "Cancel": "Avbryt",
+ "Client ID": "Klient-ID: {{client_id}}",
+ "Consent Request": "Samtaleforespørsel",
+ "Contact your administrator to register a device": "Kontakt systemansvarlig for å registrere en enhet.",
+ "Could not obtain user settings": "Kan ikke hente brukerinnstillinger",
+ "Deny": "Avslå",
+ "Done": "Ferdig",
+ "Enter new password": "Skriv inn nytt passord",
+ "Enter one-time password": "Angi engangspassord",
+ "Failed to register device, the provided link is expired or has already been used": "Kan ikke registrere enheten, den angitte lenken er utløpt eller allerede er brukt",
+ "Hi": "Hi",
+ "Incorrect username or password": "Ugyldig brukernavn eller passord.",
+ "Loading": "Laster",
+ "Login": "Innlogging",
+ "Logout": "Logg",
+ "Lost your device?": "Har du mistet enheten?",
+ "Methods": "Metoder",
+ "Must be at least {{len}} characters in length": "Må være minst {{len}} tegn i lengde",
+ "Must have at least one UPPERCASE letter": "Må ha minst én STORE bokstav",
+ "Must have at least one lowercase letter": "Må ha minst én liten bokstav",
+ "Must have at least one number": "Må ha minst ett tall",
+ "Must have at least one special character": "Må ha minst ett spesialtegn",
+ "Must not be more than {{len}} characters in length": "Må ikke være mer enn {{len}} tegn i lengde",
+ "Need Google Authenticator?": "Trenger du Google Authenticator?",
+ "New password": "Nytt passord",
+ "No verification token provided": "Ingen verifikasjonstoken angitt",
+ "OTP Secret copied to clipboard": "Hemmelig engangspassord kopiert til utklippstavlen.",
+ "OTP URL copied to clipboard": "OTP-URL kopiert til utklippstavlen.",
+ "One-Time Password": "Engangs passord",
+ "Password has been reset": "Passordet er tilbakestilt.",
+ "Password": "Passord",
+ "Passwords do not match": "Passordene samsvarer ikke.",
+ "Powered by": "Drives av",
+ "Push Notification": "Push varsling",
+ "Register device": "Registrer enheten",
+ "Register your first device by clicking on the link below": "Registrer din første enhet ved å klikke på lenken nedenfor.",
+ "Remember Consent": "Husk samtykke",
+ "Remember me": "Husk meg",
+ "Repeat new password": "Gjenta nytt passord",
+ "Reset password": "Tilbakestill passord",
+ "Reset password?": "Tilbakestill passord?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Hemmelig",
+ "Security Key - WebAuthN": "Sikkerhetsnøkkel - WebAuthN",
+ "Select a Device": "Velg en enhet",
+ "Sign in": "Logg inn",
+ "Sign out": "Logg ut",
+ "The above application is requesting the following permissions": "Programmet ovenfor ber om følgende tillatelser",
+ "The password does not meet the password policy": "Passordet oppfyller ikke minimumskravene for passord",
+ "The resource you're attempting to access requires two-factor authentication": "Ressursen du prøver å få tilgang krever 2-trinnsinnlogging.",
+ "There was a problem initiating the registration process": "Det oppsto et problem med å starte registreringsprosessen",
+ "There was an issue completing the process. The verification token might have expired": "Det oppstod et problem under fullførelse av prosessen. Bekreftelsestokenet kan ha utløpt.",
+ "There was an issue initiating the password reset process": "Det oppstod et problem under oppstart av tilbakestilling av passord.",
+ "There was an issue resetting the password": "Det oppstod et problem under tilbakestilling av passordet",
+ "There was an issue signing out": "Et problem oppstod under utlogging",
+ "This saves this consent as a pre-configured consent for future use": "Dette lagrer dette samtykket som et forhåndskonfigurert samtykke til fremtidig bruk",
+ "Time-based One-Time Password": "Tidsbasert engangspassord",
+ "Use OpenID to verify your identity": "Bruk OpenID for å bekrefte identiteten din",
+ "Username": "Brukernavn",
+ "You must open the link from the same device and browser that initiated the registration process": "Du må åpne linken fra samme enhet og nettleser som startet registreringsprosessen",
+ "You're being signed out and redirected": "Du blir logget ut og omdirigert",
+ "Your supplied password does not meet the password policy requirements": "Ditt angitte passord oppfyller ikke kravene for passord."
+}
diff --git a/internal/server/locales/pl-PL/portal.json b/internal/server/locales/pl-PL/portal.json
new file mode 100644
index 000000000..0f5dd5593
--- /dev/null
+++ b/internal/server/locales/pl-PL/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Zaakceptuj",
+ "Access your email addresses": "Dostęp do swoich adresów e-mail",
+ "Access your group membership": "Uzyskaj dostęp do członkostwa w grupie",
+ "Access your profile information": "Dostęp do informacji o profilu",
+ "An email has been sent to your address to complete the process": "E-mail został wysłany na Twój adres w celu zakończenia procesu.",
+ "Authenticated": "Uwierzytelniony",
+ "Automatically refresh these permissions without user interaction": "Automatycznie odśwież te uprawnienia bez interakcji użytkownika",
+ "Cancel": "Anuluj",
+ "Client ID": "ID klienta: {{client_id}}",
+ "Consent Request": "Prośba o zgodę",
+ "Contact your administrator to register a device": "Skontaktuj się z administratorem, aby zarejestrować urządzenie.",
+ "Could not obtain user settings": "Nie można uzyskać ustawień użytkownika",
+ "Deny": "Odmowa",
+ "Done": "Gotowe",
+ "Enter new password": "Wprowadź nowe hasło",
+ "Enter one-time password": "Wprowadź jednorazowe hasło",
+ "Failed to register device, the provided link is expired or has already been used": "Nie udało się zarejestrować urządzenia, link wygasł lub został już użyty",
+ "Hi": "Hi",
+ "Incorrect username or password": "Nieprawidłowa nazwa użytkownika lub hasło.",
+ "Loading": "Ładowanie",
+ "Login": "Logowanie",
+ "Logout": "Wyloguj się",
+ "Lost your device?": "Utracono urządzenie?",
+ "Methods": "Metody",
+ "Must be at least {{len}} characters in length": "Musi mieć co najmniej {{len}} znaków",
+ "Must have at least one UPPERCASE letter": "Musi mieć co najmniej jedną literę UPPERCASE",
+ "Must have at least one lowercase letter": "Musi mieć co najmniej jedną małą literę",
+ "Must have at least one number": "Musi mieć co najmniej jedną liczbę",
+ "Must have at least one special character": "Musi mieć co najmniej jeden znak specjalny",
+ "Must not be more than {{len}} characters in length": "Nie może być więcej niż {{len}} znaków",
+ "Need Google Authenticator?": "Potrzebujesz Google Authenticator?",
+ "New password": "Nowe hasło",
+ "No verification token provided": "Nie podano tokenu weryfikacyjnego",
+ "OTP Secret copied to clipboard": "Hasło OTP skopiowane do schowka.",
+ "OTP URL copied to clipboard": "Adres OTP został skopiowany do schowka.",
+ "One-Time Password": "Jednorazowe hasło",
+ "Password has been reset": "Hasło zostało zresetowane",
+ "Password": "Hasło",
+ "Passwords do not match": "Hasła nie są identyczne.",
+ "Powered by": "Wspierane przez",
+ "Push Notification": "Powiadomienie push",
+ "Register device": "Zarejestruj urządzenie",
+ "Register your first device by clicking on the link below": "Zarejestruj swoje pierwsze urządzenie, klikając na poniższy link.",
+ "Remember Consent": "Zapamiętaj zgodę",
+ "Remember me": "Zapamiętaj",
+ "Repeat new password": "Powtórz nowe hasło",
+ "Reset password": "Zresetuj hasło",
+ "Reset password?": "Zresetować hasło?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Sekretny",
+ "Security Key - WebAuthN": "Klucz bezpieczeństwa - WebAuthN",
+ "Select a Device": "Wybierz urządzenie",
+ "Sign in": "Zaloguj się",
+ "Sign out": "Wyloguj się",
+ "The above application is requesting the following permissions": "Powyższa aplikacja żąda następujących uprawnień",
+ "The password does not meet the password policy": "Hasło nie jest zgodne z zasadami haseł",
+ "The resource you're attempting to access requires two-factor authentication": "Zasób, do którego próbujesz uzyskać dostęp, wymaga uwierzytelniania dwuskładnikowego.",
+ "There was a problem initiating the registration process": "Wystąpił błąd podczas inicjowania procesu rejestracji",
+ "There was an issue completing the process. The verification token might have expired": "Wystąpił problem z ukończeniem procesu. Token weryfikacyjny mógł wygasnąć.",
+ "There was an issue initiating the password reset process": "Wystąpił błąd podczas inicjowania procesu resetowania hasła.",
+ "There was an issue resetting the password": "Wystąpił błąd podczas resetowania hasła",
+ "There was an issue signing out": "Wystąpił problem podczas wylogowania",
+ "This saves this consent as a pre-configured consent for future use": "Zapisuje tę zgodę jako wstępnie skonfigurowaną zgodę do przyszłego użytku",
+ "Time-based One-Time Password": "Hasło jednorazowe oparte na czasie",
+ "Use OpenID to verify your identity": "Użyj OpenID aby zweryfikować swoją tożsamość",
+ "Username": "Nazwa użytkownika",
+ "You must open the link from the same device and browser that initiated the registration process": "Musisz otworzyć link z tego samego urządzenia i przeglądarki, która zainicjowała proces rejestracji",
+ "You're being signed out and redirected": "Jesteś wylogowany i przekierowany",
+ "Your supplied password does not meet the password policy requirements": "Podane hasło nie spełnia wymogów polityki haseł."
+}
diff --git a/internal/server/locales/pt-BR/portal.json b/internal/server/locales/pt-BR/portal.json
new file mode 100644
index 000000000..8c576bc6a
--- /dev/null
+++ b/internal/server/locales/pt-BR/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Aceitar",
+ "Access your email addresses": "Acesse seus endereços de e-mail",
+ "Access your group membership": "Acessar sua associação ao grupo",
+ "Access your profile information": "Acessar informações do seu perfil",
+ "An email has been sent to your address to complete the process": "Foi enviado um email para seu endereço para completar o processo.",
+ "Authenticated": "Autenticado",
+ "Automatically refresh these permissions without user interaction": "Atualizar estas permissões automaticamente sem interação do usuário",
+ "Cancel": "cancelar",
+ "Client ID": "ID do cliente: {{client_id}}",
+ "Consent Request": "Solicitação de consentimento",
+ "Contact your administrator to register a device": "Contate seu administrador para registrar o dispositivo.",
+ "Could not obtain user settings": "Não foi possível obter as configurações do usuário",
+ "Deny": "Recusar",
+ "Done": "Concluído",
+ "Enter new password": "Digite a nova senha",
+ "Enter one-time password": "Digite senha de uso único",
+ "Failed to register device, the provided link is expired or has already been used": "Falha ao registrar o dispositivo, o link fornecido expirou ou já foi usado",
+ "Hi": "Hi",
+ "Incorrect username or password": "Nome de usuário ou senha incorretos.",
+ "Loading": "Carregando...",
+ "Login": "Conectar-se",
+ "Logout": "Desconectar",
+ "Lost your device?": "Perdeu seu dispositivo?",
+ "Methods": "Métodos",
+ "Must be at least {{len}} characters in length": "Deve ter pelo menos {{len}} caracteres de comprimento",
+ "Must have at least one UPPERCASE letter": "Deve ter pelo menos uma letra UPPERCASE",
+ "Must have at least one lowercase letter": "Deve ter pelo menos uma letra minúscula",
+ "Must have at least one number": "Deve ter pelo menos um número",
+ "Must have at least one special character": "Deve ter pelo menos um caractere especial",
+ "Must not be more than {{len}} characters in length": "Não deve ter mais de {{len}} caracteres de comprimento",
+ "Need Google Authenticator?": "Precisa do Google Authenticator?",
+ "New password": "Nova senha",
+ "No verification token provided": "Nenhum token de verificação fornecido",
+ "OTP Secret copied to clipboard": "OTP Secret copiado para a área de transferência.",
+ "OTP URL copied to clipboard": "URL OTP copiado para a área de transferência.",
+ "One-Time Password": "Senha Única",
+ "Password has been reset": "A senha foi redefinida.",
+ "Password": "Palavra-passe",
+ "Passwords do not match": "As senhas não conferem.",
+ "Powered by": "Desenvolvido por",
+ "Push Notification": "Notificação enviada automaticamente",
+ "Register device": "Registrar dispositivo",
+ "Register your first device by clicking on the link below": "Registre seu primeiro dispositivo clicando no link abaixo.",
+ "Remember Consent": "Lembrar Consentimento",
+ "Remember me": "Lembrar de mim",
+ "Repeat new password": "Repita a nova senha",
+ "Reset password": "Redefinir senha",
+ "Reset password?": "Redefinir senha?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Segredo",
+ "Security Key - WebAuthN": "Chave de segurança - WebAuthN",
+ "Select a Device": "Selecione um dispositivo",
+ "Sign in": "Iniciar sessão",
+ "Sign out": "Encerrar sessão",
+ "The above application is requesting the following permissions": "A aplicação acima está solicitando as seguintes permissões",
+ "The password does not meet the password policy": "A senha não cumpre a política de senha",
+ "The resource you're attempting to access requires two-factor authentication": "O recurso que você está tentando acessar requer autenticação de dois fatores.",
+ "There was a problem initiating the registration process": "Houve um problema ao iniciar o processo de registro",
+ "There was an issue completing the process. The verification token might have expired": "Houve um problema ao concluir o processo. O token de verificação pode ter expirado.",
+ "There was an issue initiating the password reset process": "Ocorreu um erro ao iniciar o processo de redefinição de senha.",
+ "There was an issue resetting the password": "Houve um problema ao redefinir a senha",
+ "There was an issue signing out": "Houve um problema ao fazer logout",
+ "This saves this consent as a pre-configured consent for future use": "Isto salva este consentimento como um consentimento pré-configurado para uso futuro",
+ "Time-based One-Time Password": "Senha baseada em tempo único",
+ "Use OpenID to verify your identity": "Use OpenID para verificar sua identidade",
+ "Username": "Usuário:",
+ "You must open the link from the same device and browser that initiated the registration process": "Você deve abrir o link do mesmo dispositivo e navegador que iniciou o processo de registro",
+ "You're being signed out and redirected": "Você está sendo desconectado e redirecionado",
+ "Your supplied password does not meet the password policy requirements": "A sua senha fornecida não atende aos requisitos da política de senha."
+}
diff --git a/internal/server/locales/pt/portal.json b/internal/server/locales/pt-PT/portal.json
similarity index 100%
rename from internal/server/locales/pt/portal.json
rename to internal/server/locales/pt-PT/portal.json
diff --git a/internal/server/locales/ro-RO/portal.json b/internal/server/locales/ro-RO/portal.json
new file mode 100644
index 000000000..b5f62cd03
--- /dev/null
+++ b/internal/server/locales/ro-RO/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Acceptă",
+ "Access your email addresses": "Accesează-ți adresele de e-mail",
+ "Access your group membership": "Accesează apartenența la grup",
+ "Access your profile information": "Accesează-ți informațiile de profil",
+ "An email has been sent to your address to complete the process": "Un e-mail a fost trimis la adresa dvs. pentru a finaliza procesul.",
+ "Authenticated": "Autentificat",
+ "Automatically refresh these permissions without user interaction": "Împrospătează automat aceste permisiuni fără interacțiunea utilizatorului",
+ "Cancel": "Anulează",
+ "Client ID": "ID client: {{client_id}}",
+ "Consent Request": "Cerere de consimțământ",
+ "Contact your administrator to register a device": "Contactați administratorul pentru a înregistra un dispozitiv.",
+ "Could not obtain user settings": "Nu s-au putut obține setările utilizatorului",
+ "Deny": "Refuză",
+ "Done": "Terminat",
+ "Enter new password": "Introduceți parola nouă",
+ "Enter one-time password": "Introduceți parola unică",
+ "Failed to register device, the provided link is expired or has already been used": "Eșuare înregistrare dispozitiv, link-ul furnizat este expirat sau a fost deja utilizat",
+ "Hi": "Hi",
+ "Incorrect username or password": "Nume de utilizator sau parolă incorecte.",
+ "Loading": "Încărcare",
+ "Login": "Autentificare",
+ "Logout": "Deconectare",
+ "Lost your device?": "Aţi pierdut dispozitivul?",
+ "Methods": "Metode",
+ "Must be at least {{len}} characters in length": "Trebuie să aibă cel puțin {{len}} caractere în lungime",
+ "Must have at least one UPPERCASE letter": "Trebuie să aibă cel puţin o literă UPPERCASE",
+ "Must have at least one lowercase letter": "Trebuie să aibă cel puțin o literă mică",
+ "Must have at least one number": "Trebuie să aibă cel puțin un număr",
+ "Must have at least one special character": "Trebuie să aibă cel puțin un caracter special",
+ "Must not be more than {{len}} characters in length": "Nu trebuie să aibă mai mult de {{len}} caractere lungime",
+ "Need Google Authenticator?": "Ai nevoie de autentificatorul Google?",
+ "New password": "Parola nouă",
+ "No verification token provided": "Niciun token de verificare furnizat",
+ "OTP Secret copied to clipboard": "Secret OTP copiat în clipboard.",
+ "OTP URL copied to clipboard": "URL OTP copiat în clipboard.",
+ "One-Time Password": "Parolă unică",
+ "Password has been reset": "Parola a fost resetată.",
+ "Password": "Parolă",
+ "Passwords do not match": "Parolele nu se potrivesc.",
+ "Powered by": "Oferit de",
+ "Push Notification": "Notificare push",
+ "Register device": "Înregistrează dispozitivul",
+ "Register your first device by clicking on the link below": "Înregistrează primul tău dispozitiv făcând clic pe link-ul de mai jos.",
+ "Remember Consent": "Ține minte consimțământul",
+ "Remember me": "Ține-mă minte",
+ "Repeat new password": "Repetă parola nouă",
+ "Reset password": "Resetare parolă",
+ "Reset password?": "Resetați parola?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Secret",
+ "Security Key - WebAuthN": "Cheie de securitate - WebAuthN",
+ "Select a Device": "Selectați un dispozitiv",
+ "Sign in": "Autentificare",
+ "Sign out": "Deconectare",
+ "The above application is requesting the following permissions": "Aplicația de mai sus solicită următoarele permisiuni",
+ "The password does not meet the password policy": "Parola nu îndeplinește politica de parolă",
+ "The resource you're attempting to access requires two-factor authentication": "Resursa pe care încercați să o accesați necesită autentificarea cu doi factori.",
+ "There was a problem initiating the registration process": "A existat o problemă la inițierea procesului de înregistrare",
+ "There was an issue completing the process. The verification token might have expired": "A apărut o problemă la finalizarea procesului. Este posibil ca simbolul de verificare să fi expirat.",
+ "There was an issue initiating the password reset process": "A apărut o problemă la inițierea procesului de resetare a parolei.",
+ "There was an issue resetting the password": "A apărut o problemă la resetarea parolei",
+ "There was an issue signing out": "A existat o problemă de deconectare",
+ "This saves this consent as a pre-configured consent for future use": "Acest lucru salvează consimțământul ca un consimțământ pre-configurat pentru utilizare viitoare",
+ "Time-based One-Time Password": "Parolă unică bazată pe timp",
+ "Use OpenID to verify your identity": "Folosiți OpenID pentru a vă verifica identitatea",
+ "Username": "Nume",
+ "You must open the link from the same device and browser that initiated the registration process": "Trebuie să deschideți link-ul de pe același dispozitiv și browser care a inițiat procesul de înregistrare",
+ "You're being signed out and redirected": "Ești deconectat și redirecționat",
+ "Your supplied password does not meet the password policy requirements": "Parola furnizată de dvs. nu îndeplinește cerințele politicii de parolă."
+}
diff --git a/internal/server/locales/ru/portal.json b/internal/server/locales/ru-RU/portal.json
similarity index 96%
rename from internal/server/locales/ru/portal.json
rename to internal/server/locales/ru-RU/portal.json
index e7586c4c6..092def962 100644
--- a/internal/server/locales/ru/portal.json
+++ b/internal/server/locales/ru-RU/portal.json
@@ -5,6 +5,7 @@
"Access your profile information": "Доступ к информации вашего профиля",
"An email has been sent to your address to complete the process": "На вашу почту было отправлено электронное письмо для завершения процесса.",
"Authenticated": "Авторизован",
+ "Automatically refresh these permissions without user interaction": "Автоматически обновлять эти разрешения без взаимодействия пользователя",
"Cancel": "Отменить",
"Client ID": "Идентификатор пользователя: {{client_id}}",
"Consent Request": "Запрос согласия",
@@ -18,6 +19,7 @@
"Hi": "Здравствуйте",
"Incorrect username or password": "Неправильное имя пользователя или пароль.",
"Loading": "Загрузка",
+ "Login": "Логин",
"Logout": "Выход",
"Lost your device?": "Потеряли свое устройство?",
"Methods": "Методы",
@@ -36,6 +38,7 @@
"Password has been reset": "Пароль был сброшен.",
"Password": "Пароль",
"Passwords do not match": "Пароли не совпадают.",
+ "Powered by": "При поддержке",
"Push Notification": "Push-уведомление",
"Register device": "Регистрация устройства",
"Register your first device by clicking on the link below": "Зарегистрируйте свое первое устройство, перейдя по ссылке ниже.",
diff --git a/internal/server/locales/sv-SE/portal.json b/internal/server/locales/sv-SE/portal.json
index 20f8e1948..abf8a6cad 100644
--- a/internal/server/locales/sv-SE/portal.json
+++ b/internal/server/locales/sv-SE/portal.json
@@ -3,8 +3,9 @@
"Access your email addresses": "Hantera din e-postadress",
"Access your group membership": "Hantera dina gruppmedlemskap",
"Access your profile information": "Hantera din profilinformation",
- "An email has been sent to your address to complete the process": "Ett mejl har skickats till din e-postadress för att slutföra processen.",
- "Authenticated": "Autentiserad",
+ "An email has been sent to your address to complete the process": "Ett mejl har skickas till din e-postadress för att slutföra processen.",
+ "Authenticated": "Autentiserat",
+ "Automatically refresh these permissions without user interaction": "Uppdatera automatiskt dessa behörigheter utan användarinteraktion",
"Cancel": "Avbryt",
"Client ID": "Klient-ID: {{client_id}}",
"Consent Request": "Begäran om medgivande",
@@ -15,17 +16,18 @@
"Enter new password": "Skriv ditt nya lösenord",
"Enter one-time password": "Skriv ditt engångslösenord",
"Failed to register device, the provided link is expired or has already been used": "Enhetsregistreringen misslyckades, den angivna länken har utgått eller redan blivit använd.",
- "Hi": "Hej",
+ "Hi": "Hej!",
"Incorrect username or password": "Fel användarnamn eller lösenord",
"Loading": "Läser in",
+ "Login": "Inloggning",
"Logout": "Logga ut",
"Lost your device?": "Har du tappat bort din enhet?",
"Methods": "Metoder",
"Must be at least {{len}} characters in length": "Måste vara minst {{len}} tecken långt",
- "Must have at least one UPPERCASE letter": "Måste innehålla minst en stor bokstav",
- "Must have at least one lowercase letter": "Måste innehålla minst en liten bokstav",
- "Must have at least one number": "Måste innehålla minst ett nummer",
- "Must have at least one special character": "Måste innehålla minst ett specialtecken",
+ "Must have at least one UPPERCASE letter": "Måste ha minst en UPPERCASE bokstav",
+ "Must have at least one lowercase letter": "Måste ha minst en liten bokstav",
+ "Must have at least one number": "Måste ha minst ett nummer",
+ "Must have at least one special character": "Måste ha minst ett specialtecken",
"Must not be more than {{len}} characters in length": "Får inte vara längre än {{len}} tecken",
"Need Google Authenticator?": "Behöver du Google Authenticator?",
"New password": "Nytt lösenord",
@@ -36,12 +38,13 @@
"Password has been reset": "Lösenordet har blivit återställt",
"Password": "Lösenord",
"Passwords do not match": "Lösenorden matchar inte",
+ "Powered by": "Drivs av",
"Push Notification": "Push-avisering",
"Register device": "Registrera enhet",
"Register your first device by clicking on the link below": "Registrera din första enhet genom att klicka på länken nedan",
- "Remember Consent": "Kom ihåg samtycke",
+ "Remember Consent": "Kom ihåg Samtycke",
"Remember me": "Kom ihåg mig",
- "Repeat new password": "Upprepa nya lösenordet",
+ "Repeat new password": "Uprepa nya lösenordet",
"Reset password": "Återställ lösenord",
"Reset password?": "Återställ lösenord?",
"Reset": "Återställ",
@@ -59,7 +62,7 @@
"There was an issue initiating the password reset process": "Ett problem uppstod när processen för lösenordsåterställning startade",
"There was an issue resetting the password": "Ett problem uppstod med att återställa lösenordet",
"There was an issue signing out": "Ett problem uppstod med att logga ut",
- "This saves this consent as a pre-configured consent for future use": "Spara detta samtycke som ett förkonfigurerat samtycke för framtida användning",
+ "This saves this consent as a pre-configured consent for future use": "Detta sparar detta samtycke som ett förkonfigurerat samtycke för framtida användning",
"Time-based One-Time Password": "Tidsbaserat engångslösenord",
"Use OpenID to verify your identity": "Använd OpenID till att verifiera din identitet",
"Username": "Användarnamn",
diff --git a/internal/server/locales/sv/portal.json b/internal/server/locales/sv/portal.json
deleted file mode 100644
index 20f8e1948..000000000
--- a/internal/server/locales/sv/portal.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
- "Accept": "Acceptera",
- "Access your email addresses": "Hantera din e-postadress",
- "Access your group membership": "Hantera dina gruppmedlemskap",
- "Access your profile information": "Hantera din profilinformation",
- "An email has been sent to your address to complete the process": "Ett mejl har skickats till din e-postadress för att slutföra processen.",
- "Authenticated": "Autentiserad",
- "Cancel": "Avbryt",
- "Client ID": "Klient-ID: {{client_id}}",
- "Consent Request": "Begäran om medgivande",
- "Contact your administrator to register a device": "Kontakta din administratör för att registrera en enhet.",
- "Could not obtain user settings": "Misslyckades med att hämta användarinställningarna.",
- "Deny": "Avböj",
- "Done": "Klar",
- "Enter new password": "Skriv ditt nya lösenord",
- "Enter one-time password": "Skriv ditt engångslösenord",
- "Failed to register device, the provided link is expired or has already been used": "Enhetsregistreringen misslyckades, den angivna länken har utgått eller redan blivit använd.",
- "Hi": "Hej",
- "Incorrect username or password": "Fel användarnamn eller lösenord",
- "Loading": "Läser in",
- "Logout": "Logga ut",
- "Lost your device?": "Har du tappat bort din enhet?",
- "Methods": "Metoder",
- "Must be at least {{len}} characters in length": "Måste vara minst {{len}} tecken långt",
- "Must have at least one UPPERCASE letter": "Måste innehålla minst en stor bokstav",
- "Must have at least one lowercase letter": "Måste innehålla minst en liten bokstav",
- "Must have at least one number": "Måste innehålla minst ett nummer",
- "Must have at least one special character": "Måste innehålla minst ett specialtecken",
- "Must not be more than {{len}} characters in length": "Får inte vara längre än {{len}} tecken",
- "Need Google Authenticator?": "Behöver du Google Authenticator?",
- "New password": "Nytt lösenord",
- "No verification token provided": "Ingen verifieringskod tillhandahålls",
- "OTP Secret copied to clipboard": "OTP koden har kopierats till urklipp",
- "OTP URL copied to clipboard": "OTP länken har kopierats till urklipp",
- "One-Time Password": "Engångslösenord",
- "Password has been reset": "Lösenordet har blivit återställt",
- "Password": "Lösenord",
- "Passwords do not match": "Lösenorden matchar inte",
- "Push Notification": "Push-avisering",
- "Register device": "Registrera enhet",
- "Register your first device by clicking on the link below": "Registrera din första enhet genom att klicka på länken nedan",
- "Remember Consent": "Kom ihåg samtycke",
- "Remember me": "Kom ihåg mig",
- "Repeat new password": "Upprepa nya lösenordet",
- "Reset password": "Återställ lösenord",
- "Reset password?": "Återställ lösenord?",
- "Reset": "Återställ",
- "Scan QR Code": "Skanna QR koden",
- "Secret": "Kod",
- "Security Key - WebAuthN": "Säkerhetsnyckel - WebAuthN",
- "Select a Device": "Välj en enhet",
- "Sign in": "Logga in",
- "Sign out": "Logga ut",
- "The above application is requesting the following permissions": "Ovanstående program begär följande behörigheter",
- "The password does not meet the password policy": "Lösenordet uppfyller inte lösenordspolicyn",
- "The resource you're attempting to access requires two-factor authentication": "Resursen du vill komma åt kräver tvåstegsverifiering",
- "There was a problem initiating the registration process": "Ett problem uppstod när registreringsprocessen skulle starta",
- "There was an issue completing the process. The verification token might have expired": "Det uppstod ett problem med att slutföra processen. Verifieringskoden kan ha gått ut",
- "There was an issue initiating the password reset process": "Ett problem uppstod när processen för lösenordsåterställning startade",
- "There was an issue resetting the password": "Ett problem uppstod med att återställa lösenordet",
- "There was an issue signing out": "Ett problem uppstod med att logga ut",
- "This saves this consent as a pre-configured consent for future use": "Spara detta samtycke som ett förkonfigurerat samtycke för framtida användning",
- "Time-based One-Time Password": "Tidsbaserat engångslösenord",
- "Use OpenID to verify your identity": "Använd OpenID till att verifiera din identitet",
- "Username": "Användarnamn",
- "You must open the link from the same device and browser that initiated the registration process": "Du måste öppna länken från samma enhet och webbläsare som startade registreringsprocessen",
- "You're being signed out and redirected": "Du blir utloggad och omdirigerad",
- "Your supplied password does not meet the password policy requirements": "Det angivna lösenordet möter inte lösenordskraven"
-}
diff --git a/internal/server/locales/uk-UA/portal.json b/internal/server/locales/uk-UA/portal.json
new file mode 100644
index 000000000..857130458
--- /dev/null
+++ b/internal/server/locales/uk-UA/portal.json
@@ -0,0 +1,72 @@
+{
+ "Accept": "Прийняти",
+ "Access your email addresses": "Доступ до ваших адрес електронної пошти",
+ "Access your group membership": "Доступ до членства в групі",
+ "Access your profile information": "Доступ до інформації вашого профілю",
+ "An email has been sent to your address to complete the process": "Лист надіслано на вашу адресу для завершення процесу.",
+ "Authenticated": "Автентифіковано",
+ "Automatically refresh these permissions without user interaction": "Автоматично оновлювати ці дозволи без взаємодії з користувачем",
+ "Cancel": "Скасувати",
+ "Client ID": "ID клієнта: {{client_id}}",
+ "Consent Request": "Запит згоди",
+ "Contact your administrator to register a device": "Зверніться до вашого адміністратора для реєстрації пристрою.",
+ "Could not obtain user settings": "Не вдалося отримати налаштування користувача",
+ "Deny": "Відмовити",
+ "Done": "Виконано",
+ "Enter new password": "Введіть новий пароль",
+ "Enter one-time password": "Введіть одноразовий пароль",
+ "Failed to register device, the provided link is expired or has already been used": "Не вдалося зареєструвати пристрій, надане посилання прострочено або вже використовується",
+ "Hi": "Hi",
+ "Incorrect username or password": "Ім'я користувача або пароль помилкові.",
+ "Loading": "Завантаження",
+ "Login": "Логін",
+ "Logout": "Вихід із системи",
+ "Lost your device?": "Прогубили ваш пристрій?",
+ "Methods": "Методи",
+ "Must be at least {{len}} characters in length": "Повинно мати якнайменше {{len}} символів",
+ "Must have at least one UPPERCASE letter": "Має мати принаймні один символ UPPERCASE",
+ "Must have at least one lowercase letter": "Повинна мати принаймні одну малу літеру",
+ "Must have at least one number": "Повинно мати хоча б одне число",
+ "Must have at least one special character": "Повинно мати принаймні один спеціальний символ",
+ "Must not be more than {{len}} characters in length": "Не має бути більше {{len}} символів за довжину",
+ "Need Google Authenticator?": "Потрібен Google Authenticator?",
+ "New password": "Новий пароль",
+ "No verification token provided": "Не вказано перевірковий токен",
+ "OTP Secret copied to clipboard": "Секрет одноразових паролів скопійовано до буфера обміну.",
+ "OTP URL copied to clipboard": "URL одноразових паролів скопійовано до буфера обміну.",
+ "One-Time Password": "Одноразовий пароль",
+ "Password has been reset": "Пароль було скинуто.",
+ "Password": "Пароль",
+ "Passwords do not match": "Паролі не співпадають.",
+ "Powered by": "Працює на",
+ "Push Notification": "Push-сповіщення",
+ "Register device": "Зареєструвати пристрій",
+ "Register your first device by clicking on the link below": "Зареєструйте свій перший пристрій, натиснувши на посилання нижче.",
+ "Remember Consent": "Запам'ятати згоду",
+ "Remember me": "Запам'ятати мене",
+ "Repeat new password": "Повторіть новий пароль ще раз",
+ "Reset password": "Скинути пароль",
+ "Reset password?": "Скинути пароль?",
+ "Reset": "Reset",
+ "Scan QR Code": "Scan QR Code",
+ "Secret": "Секрет",
+ "Security Key - WebAuthN": "Ключ безпеки - WebAuthN",
+ "Select a Device": "Оберіть пристрій",
+ "Sign in": "Увійти",
+ "Sign out": "Вийти",
+ "The above application is requesting the following permissions": "Додаток запитує такі дозволи",
+ "The password does not meet the password policy": "Пароль не відповідає політиці",
+ "The resource you're attempting to access requires two-factor authentication": "Ресурс, до якого ви намагаєтеся отримати доступ, потребує двофакторної автентифікації.",
+ "There was a problem initiating the registration process": "Сталася помилка під час ініціювання процесу реєстрації",
+ "There was an issue completing the process. The verification token might have expired": "Виникла проблема із завершенням процесу. Можливо, термін дії перевірочного токена минув.",
+ "There was an issue initiating the password reset process": "Виникла проблема під час ініціювання процесу скидання пароля.",
+ "There was an issue resetting the password": "При скиданні пароля виникла проблема",
+ "There was an issue signing out": "Виникла проблема при виході",
+ "This saves this consent as a pre-configured consent for future use": "Це заощаджує цю згоду як попередньо налаштовану згоду на майбутнє",
+ "Time-based One-Time Password": "Одноразовий пароль",
+ "Use OpenID to verify your identity": "Використовуйте OpenID для підтвердження вашої особи",
+ "Username": "Ім'я користувача",
+ "You must open the link from the same device and browser that initiated the registration process": "Ви повинні відкрити посилання з того ж пристрою і браузера, який ініціював процес реєстрації",
+ "You're being signed out and redirected": "Ви виходите і перенаправлені",
+ "Your supplied password does not meet the password policy requirements": "Ваш поставлений пароль не відповідає вимогам політики пароля."
+}
diff --git a/internal/server/locales/zh/portal.json b/internal/server/locales/zh-CN/portal.json
similarity index 92%
rename from internal/server/locales/zh/portal.json
rename to internal/server/locales/zh-CN/portal.json
index 1f570dddf..70a6d6b13 100644
--- a/internal/server/locales/zh/portal.json
+++ b/internal/server/locales/zh-CN/portal.json
@@ -1,7 +1,7 @@
{
"Accept": "接受",
"Access your email addresses": "访问您的电子邮件地址",
- "Access your group membership": "访问您的群组成员",
+ "Access your group membership": "访问您的所属用户组",
"Access your profile information": "访问您的个人资料信息",
"An email has been sent to your address to complete the process": "一封电子邮件已经发送到您的地址来完成该进程。",
"Authenticated": "已认证",
@@ -22,7 +22,7 @@
"Login": "登录",
"Logout": "注销",
"Lost your device?": "丢失了您的设备?",
- "Methods": "方法",
+ "Methods": "验证方法",
"Must be at least {{len}} characters in length": "长度必须至少为 {{len}} 个字符",
"Must have at least one UPPERCASE letter": "必须至少有一个大写字母",
"Must have at least one lowercase letter": "必须至少有一个小写字母",
@@ -42,7 +42,7 @@
"Push Notification": "推送通知",
"Register device": "注册设备",
"Register your first device by clicking on the link below": "点击下面的链接来注册您的第一台设备。",
- "Remember Consent": "记住协议",
+ "Remember Consent": "记住我的选择",
"Remember me": "记住我",
"Repeat new password": "再次输入新密码",
"Reset password": "重置密码",
@@ -54,7 +54,7 @@
"Select a Device": "选择一个设备",
"Sign in": "登录",
"Sign out": "登出",
- "The above application is requesting the following permissions": "上述应用程序正在请求以下权限",
+ "The above application is requesting the following permissions": "此应用正在请求以下权限",
"The password does not meet the password policy": "密码不符合密码策略",
"The resource you're attempting to access requires two-factor authentication": "您要访问的资源需要双重身份验证。",
"There was a problem initiating the registration process": "启动注册过程时出现了问题",
@@ -62,11 +62,11 @@
"There was an issue initiating the password reset process": "启动密码重置过程时出现问题。",
"There was an issue resetting the password": "重置密码时出现问题",
"There was an issue signing out": "登出时出现问题",
- "This saves this consent as a pre-configured consent for future use": "这将此同意保存为预配置的同意以供未来使用",
+ "This saves this consent as a pre-configured consent for future use": "以后为此应用自动授权以上权限",
"Time-based One-Time Password": "基于时间的一次性密码",
"Use OpenID to verify your identity": "使用 OpenID 验证您的身份",
"Username": "用户名",
- "You must open the link from the same device and browser that initiated the registration process": "您必须从启动注册过程的同一设备和浏览器打开链接",
+ "You must open the link from the same device and browser that initiated the registration process": "您必须使用与注册时相同的设备和浏览器打开链接",
"You're being signed out and redirected": "您正在登出并重定向",
"Your supplied password does not meet the password policy requirements": "您提供的密码不符合密码政策要求。"
}
diff --git a/internal/server/server.go b/internal/server/server.go
index 37e6f6a79..760cbe50e 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -28,7 +28,7 @@ func CreateDefaultServer(config schema.Configuration, providers middlewares.Prov
ReadTimeout: config.Server.Timeouts.Read,
WriteTimeout: config.Server.Timeouts.Write,
IdleTimeout: config.Server.Timeouts.Idle,
- Logger: logging.LoggerPrintf(logrus.WarnLevel),
+ Logger: logging.LoggerPrintf(logrus.DebugLevel),
}
address := net.JoinHostPort(config.Server.Host, strconv.Itoa(config.Server.Port))
diff --git a/internal/server/template.go b/internal/server/template.go
index 9c5bb523d..0042f8856 100644
--- a/internal/server/template.go
+++ b/internal/server/template.go
@@ -4,10 +4,13 @@ import (
"fmt"
"io"
"os"
+ "path"
"path/filepath"
"strings"
"text/template"
+ "github.com/valyala/fasthttp"
+
"github.com/authelia/authelia/v4/internal/logging"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/utils"
@@ -19,7 +22,7 @@ import (
func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, session, theme string, https bool) middlewares.RequestHandler {
logger := logging.Logger()
- a, err := assets.Open(publicDir + file)
+ a, err := assets.Open(path.Join(publicDir, file))
if err != nil {
logger.Fatalf("Unable to open %s: %s", file, err)
}
@@ -43,7 +46,7 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
logoOverride := f
if assetPath != "" {
- if _, err := os.Stat(filepath.Join(assetPath, logoFile)); err == nil {
+ if _, err := os.Stat(filepath.Join(assetPath, fileLogo)); err == nil {
logoOverride = t
}
}
@@ -61,7 +64,7 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
}
baseURL := scheme + "://" + string(ctx.XForwardedHost()) + base + "/"
- nonce := utils.RandomString(32, utils.AlphaNumericCharacters, true)
+ nonce := utils.RandomString(32, utils.CharSetAlphaNumeric, true)
switch extension := filepath.Ext(file); extension {
case ".html":
@@ -71,14 +74,14 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
}
switch {
- case publicDir == swaggerAssets:
- ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("base-uri 'self'; default-src 'self'; img-src 'self' https://validator.swagger.io data:; object-src 'none'; script-src 'self' 'unsafe-inline' 'nonce-%s'; style-src 'self' 'nonce-%s'", nonce, nonce))
+ case publicDir == assetsSwagger:
+ ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPSwagger, nonce, nonce))
case ctx.Configuration.Server.Headers.CSPTemplate != "":
- ctx.Response.Header.Add("Content-Security-Policy", strings.ReplaceAll(ctx.Configuration.Server.Headers.CSPTemplate, cspNoncePlaceholder, nonce))
+ ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, strings.ReplaceAll(ctx.Configuration.Server.Headers.CSPTemplate, placeholderCSPNonce, nonce))
case os.Getenv("ENVIRONMENT") == dev:
- ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf(cspDefaultTemplate, " 'unsafe-eval'", nonce))
+ ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPDevelopment, nonce))
default:
- ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf(cspDefaultTemplate, "", nonce))
+ ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPDefault, nonce))
}
err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, BaseURL, CSPNonce, DuoSelfEnrollment, LogoOverride, RememberMe, ResetPassword, ResetPasswordCustomURL, Session, Theme string }{Base: base, BaseURL: baseURL, CSPNonce: nonce, DuoSelfEnrollment: duoSelfEnrollment, LogoOverride: logoOverride, RememberMe: rememberMe, ResetPassword: resetPassword, ResetPasswordCustomURL: resetPasswordCustomURL, Session: session, Theme: theme})
diff --git a/internal/session/provider_config.go b/internal/session/provider_config.go
index 0a1ba6e11..c9e694a28 100644
--- a/internal/session/provider_config.go
+++ b/internal/session/provider_config.go
@@ -75,7 +75,7 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
var tlsConfig *tls.Config
if config.Redis.TLS != nil {
- tlsConfig = utils.NewTLSConfig(config.Redis.TLS, tls.VersionTLS12, certPool)
+ tlsConfig = utils.NewTLSConfig(config.Redis.TLS, certPool)
}
if config.Redis.HighAvailability != nil && config.Redis.HighAvailability.SentinelName != "" {
diff --git a/internal/session/provider_config_test.go b/internal/session/provider_config_test.go
index 3f7b79dac..23b131c71 100644
--- a/internal/session/provider_config_test.go
+++ b/internal/session/provider_config_test.go
@@ -42,7 +42,7 @@ func TestShouldCreateRedisSessionProviderTLS(t *testing.T) {
Password: "pass",
TLS: &schema.TLSConfig{
ServerName: "redis.fqdn.example.com",
- MinimumVersion: "TLS1.3",
+ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
},
}
providerConfig := NewProviderConfig(configuration, nil)
diff --git a/internal/session/user_session.go b/internal/session/user_session.go
index d691ec695..88511eb5d 100644
--- a/internal/session/user_session.go
+++ b/internal/session/user_session.go
@@ -17,6 +17,11 @@ func NewDefaultUserSession() UserSession {
}
}
+// IsAnonymous returns true if the username is empty or the AuthenticationLevel is authentication.NotAuthenticated.
+func (s *UserSession) IsAnonymous() bool {
+ return s.Username == "" || s.AuthenticationLevel == authentication.NotAuthenticated
+}
+
// SetOneFactor sets the 1FA AMR's and expected property values for one factor authentication.
func (s *UserSession) SetOneFactor(now time.Time, details *authentication.UserDetails, keepMeLoggedIn bool) {
s.FirstFactorAuthnTimestamp = now.Unix()
diff --git a/internal/storage/const.go b/internal/storage/const.go
index 4f451d8e3..39645979d 100644
--- a/internal/storage/const.go
+++ b/internal/storage/const.go
@@ -13,13 +13,14 @@ const (
tableUserPreferences = "user_preferences"
tableWebauthnDevices = "webauthn_devices"
- tableOAuth2ConsentSession = "oauth2_consent_session"
- tableOAuth2AuthorizeCodeSession = "oauth2_authorization_code_session"
- tableOAuth2AccessTokenSession = "oauth2_access_token_session" //nolint:gosec // This is not a hardcoded credential.
- tableOAuth2RefreshTokenSession = "oauth2_refresh_token_session" //nolint:gosec // This is not a hardcoded credential.
- tableOAuth2PKCERequestSession = "oauth2_pkce_request_session"
- tableOAuth2OpenIDConnectSession = "oauth2_openid_connect_session"
- tableOAuth2BlacklistedJTI = "oauth2_blacklisted_jti"
+ tableOAuth2ConsentSession = "oauth2_consent_session"
+ tableOAuth2ConsentPreConfiguration = "oauth2_consent_preconfiguration"
+ tableOAuth2AuthorizeCodeSession = "oauth2_authorization_code_session"
+ tableOAuth2AccessTokenSession = "oauth2_access_token_session" //nolint:gosec // This is not a hardcoded credential.
+ tableOAuth2RefreshTokenSession = "oauth2_refresh_token_session" //nolint:gosec // This is not a hardcoded credential.
+ tableOAuth2PKCERequestSession = "oauth2_pkce_request_session"
+ tableOAuth2OpenIDConnectSession = "oauth2_openid_connect_session"
+ tableOAuth2BlacklistedJTI = "oauth2_blacklisted_jti"
tableMigrations = "migrations"
tableEncryption = "encryption"
@@ -39,6 +40,11 @@ const (
OAuth2SessionTypeOpenIDConnect OAuth2SessionType = "openid connect"
)
+const (
+ sqlNetworkTypeTCP = "tcp"
+ sqlNetworkTypeUnixSocket = "unix"
+)
+
const (
encryptionNameCheck = "check"
)
@@ -76,11 +82,6 @@ const (
providerSQLite = "sqlite"
)
-const (
- // This is the latest schema version for the purpose of tests.
- testLatestVersion = 5
-)
-
const (
// SchemaLatest represents the value expected for a "migrate to latest" migration. It's the maximum 32bit signed integer.
SchemaLatest = 2147483647
diff --git a/internal/storage/migrations/V0004.OpenIDConenct.mysql.up.sql b/internal/storage/migrations/V0004.OpenIDConnect.mysql.up.sql
similarity index 100%
rename from internal/storage/migrations/V0004.OpenIDConenct.mysql.up.sql
rename to internal/storage/migrations/V0004.OpenIDConnect.mysql.up.sql
diff --git a/internal/storage/migrations/V0004.OpenIDConenct.postgres.up.sql b/internal/storage/migrations/V0004.OpenIDConnect.postgres.up.sql
similarity index 100%
rename from internal/storage/migrations/V0004.OpenIDConenct.postgres.up.sql
rename to internal/storage/migrations/V0004.OpenIDConnect.postgres.up.sql
diff --git a/internal/storage/migrations/V0004.OpenIDConenct.sqlite.up.sql b/internal/storage/migrations/V0004.OpenIDConnect.sqlite.up.sql
similarity index 100%
rename from internal/storage/migrations/V0004.OpenIDConenct.sqlite.up.sql
rename to internal/storage/migrations/V0004.OpenIDConnect.sqlite.up.sql
diff --git a/internal/storage/migrations/V0006.ConsentPreConfiguration.mysql.down.sql b/internal/storage/migrations/V0006.ConsentPreConfiguration.mysql.down.sql
new file mode 100644
index 000000000..e06f19d81
--- /dev/null
+++ b/internal/storage/migrations/V0006.ConsentPreConfiguration.mysql.down.sql
@@ -0,0 +1,175 @@
+DROP TABLE oauth2_authorization_code_session;
+DROP TABLE oauth2_access_token_session;
+DROP TABLE oauth2_refresh_token_session;
+DROP TABLE oauth2_pkce_request_session;
+DROP TABLE oauth2_openid_connect_session;
+DROP TABLE oauth2_consent_session;
+DROP TABLE oauth2_consent_preconfiguration;
+
+CREATE TABLE oauth2_consent_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NULL DEFAULT NULL,
+ authorized BOOLEAN NOT NULL DEFAULT FALSE,
+ granted BOOLEAN NOT NULL DEFAULT FALSE,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ responded_at TIMESTAMP NULL DEFAULT NULL,
+ expires_at TIMESTAMP NULL DEFAULT NULL,
+ form_data TEXT NOT NULL,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_session_subject_fkey
+ FOREIGN KEY (subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE UNIQUE INDEX oauth2_consent_session_challenge_id_key ON oauth2_consent_session (challenge_id);
+
+CREATE TABLE oauth2_authorization_code_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_authorization_code_session_challenge_id_fkey
+ FOREIGN KEY (challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_authorization_code_session_subject_fkey
+ FOREIGN KEY (subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_authorization_code_session_request_id_idx ON oauth2_authorization_code_session (request_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_idx ON oauth2_authorization_code_session (client_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_subject_idx ON oauth2_authorization_code_session (client_id, subject);
+
+CREATE TABLE oauth2_access_token_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_access_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_access_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_access_token_session_request_id_idx ON oauth2_access_token_session (request_id);
+CREATE INDEX oauth2_access_token_session_client_id_idx ON oauth2_access_token_session (client_id);
+CREATE INDEX oauth2_access_token_session_client_id_subject_idx ON oauth2_access_token_session (client_id, subject);
+
+CREATE TABLE oauth2_refresh_token_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_refresh_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_refresh_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_refresh_token_session_request_id_idx ON oauth2_refresh_token_session (request_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_idx ON oauth2_refresh_token_session (client_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_subject_idx ON oauth2_refresh_token_session (client_id, subject);
+
+CREATE TABLE oauth2_pkce_request_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_pkce_request_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_pkce_request_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_pkce_request_session_request_id_idx ON oauth2_pkce_request_session (request_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_idx ON oauth2_pkce_request_session (client_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_subject_idx ON oauth2_pkce_request_session (client_id, subject);
+
+CREATE TABLE oauth2_openid_connect_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_openid_connect_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_openid_connect_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_openid_connect_session_request_id_idx ON oauth2_openid_connect_session (request_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_idx ON oauth2_openid_connect_session (client_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_subject_idx ON oauth2_openid_connect_session (client_id, subject);
diff --git a/internal/storage/migrations/V0006.ConsentPreConfiguration.mysql.up.sql b/internal/storage/migrations/V0006.ConsentPreConfiguration.mysql.up.sql
new file mode 100644
index 000000000..a74a42fb9
--- /dev/null
+++ b/internal/storage/migrations/V0006.ConsentPreConfiguration.mysql.up.sql
@@ -0,0 +1,198 @@
+CREATE TABLE oauth2_consent_preconfiguration (
+ id INTEGER AUTO_INCREMENT,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ expires_at TIMESTAMP NULL DEFAULT NULL,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ scopes TEXT NOT NULL,
+ audience TEXT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_preconfiguration_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+INSERT INTO oauth2_consent_preconfiguration (client_id, subject, created_at, expires_at, scopes, audience)
+SELECT client_id, subject, responded_at, expires_at, granted_scopes, granted_audience
+FROM oauth2_consent_session
+WHERE expires_at IS NOT NULL AND responded_at IS NOT NULL
+ORDER BY responded_at;
+
+DROP TABLE oauth2_authorization_code_session;
+DROP TABLE oauth2_access_token_session;
+DROP TABLE oauth2_refresh_token_session;
+DROP TABLE oauth2_pkce_request_session;
+DROP TABLE oauth2_openid_connect_session;
+DROP TABLE oauth2_consent_session;
+
+CREATE TABLE oauth2_consent_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ authorized BOOLEAN NOT NULL DEFAULT FALSE,
+ granted BOOLEAN NOT NULL DEFAULT FALSE,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ responded_at TIMESTAMP NULL DEFAULT NULL,
+ form_data TEXT NOT NULL,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ preconfiguration INTEGER NULL DEFAULT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_session_subject_fkey
+ FOREIGN KEY (subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT,
+ CONSTRAINT oauth2_consent_session_preconfiguration_fkey
+ FOREIGN KEY (preconfiguration)
+ REFERENCES oauth2_consent_preconfiguration(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+CREATE UNIQUE INDEX oauth2_consent_session_challenge_id_key ON oauth2_consent_session (challenge_id);
+
+CREATE TABLE oauth2_authorization_code_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_authorization_code_session_challenge_id_fkey
+ FOREIGN KEY (challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_authorization_code_session_subject_fkey
+ FOREIGN KEY (subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_authorization_code_session_request_id_idx ON oauth2_authorization_code_session (request_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_idx ON oauth2_authorization_code_session (client_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_subject_idx ON oauth2_authorization_code_session (client_id, subject);
+
+CREATE TABLE oauth2_access_token_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_access_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_access_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_access_token_session_request_id_idx ON oauth2_access_token_session (request_id);
+CREATE INDEX oauth2_access_token_session_client_id_idx ON oauth2_access_token_session (client_id);
+CREATE INDEX oauth2_access_token_session_client_id_subject_idx ON oauth2_access_token_session (client_id, subject);
+
+CREATE TABLE oauth2_refresh_token_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_refresh_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_refresh_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_refresh_token_session_request_id_idx ON oauth2_refresh_token_session (request_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_idx ON oauth2_refresh_token_session (client_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_subject_idx ON oauth2_refresh_token_session (client_id, subject);
+
+CREATE TABLE oauth2_pkce_request_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_pkce_request_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_pkce_request_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_pkce_request_session_request_id_idx ON oauth2_pkce_request_session (request_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_idx ON oauth2_pkce_request_session (client_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_subject_idx ON oauth2_pkce_request_session (client_id, subject);
+
+CREATE TABLE oauth2_openid_connect_session (
+ id INTEGER AUTO_INCREMENT,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_openid_connect_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_openid_connect_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_openid_connect_session_request_id_idx ON oauth2_openid_connect_session (request_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_idx ON oauth2_openid_connect_session (client_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_subject_idx ON oauth2_openid_connect_session (client_id, subject);
diff --git a/internal/storage/migrations/V0006.ConsentPreConfiguration.postgres.down.sql b/internal/storage/migrations/V0006.ConsentPreConfiguration.postgres.down.sql
new file mode 100644
index 000000000..a180af297
--- /dev/null
+++ b/internal/storage/migrations/V0006.ConsentPreConfiguration.postgres.down.sql
@@ -0,0 +1,175 @@
+DROP TABLE oauth2_authorization_code_session;
+DROP TABLE oauth2_access_token_session;
+DROP TABLE oauth2_refresh_token_session;
+DROP TABLE oauth2_pkce_request_session;
+DROP TABLE oauth2_openid_connect_session;
+DROP TABLE oauth2_consent_session;
+DROP TABLE oauth2_consent_preconfiguration;
+
+CREATE TABLE oauth2_consent_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NULL DEFAULT NULL,
+ authorized BOOLEAN NOT NULL DEFAULT FALSE,
+ granted BOOLEAN NOT NULL DEFAULT FALSE,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ responded_at TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+ expires_at TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+ form_data TEXT NOT NULL,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE UNIQUE INDEX oauth2_consent_session_challenge_id_key ON oauth2_consent_session (challenge_id);
+
+CREATE TABLE oauth2_authorization_code_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36),
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_authorization_code_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_authorization_code_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_authorization_code_session_request_id_idx ON oauth2_authorization_code_session (request_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_idx ON oauth2_authorization_code_session (client_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_subject_idx ON oauth2_authorization_code_session (client_id, subject);
+
+CREATE TABLE oauth2_access_token_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_access_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_access_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_access_token_session_request_id_idx ON oauth2_access_token_session (request_id);
+CREATE INDEX oauth2_access_token_session_client_id_idx ON oauth2_access_token_session (client_id);
+CREATE INDEX oauth2_access_token_session_client_id_subject_idx ON oauth2_access_token_session (client_id, subject);
+
+CREATE TABLE oauth2_refresh_token_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_refresh_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_refresh_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_refresh_token_session_request_id_idx ON oauth2_refresh_token_session (request_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_idx ON oauth2_refresh_token_session (client_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_subject_idx ON oauth2_refresh_token_session (client_id, subject);
+
+CREATE TABLE oauth2_pkce_request_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_pkce_request_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_pkce_request_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_pkce_request_session_request_id_idx ON oauth2_pkce_request_session (request_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_idx ON oauth2_pkce_request_session (client_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_subject_idx ON oauth2_pkce_request_session (client_id, subject);
+
+CREATE TABLE oauth2_openid_connect_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_openid_connect_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_openid_connect_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_openid_connect_session_request_id_idx ON oauth2_openid_connect_session (request_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_idx ON oauth2_openid_connect_session (client_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_subject_idx ON oauth2_openid_connect_session (client_id, subject);
diff --git a/internal/storage/migrations/V0006.ConsentPreConfiguration.postgres.up.sql b/internal/storage/migrations/V0006.ConsentPreConfiguration.postgres.up.sql
new file mode 100644
index 000000000..964ddd3d9
--- /dev/null
+++ b/internal/storage/migrations/V0006.ConsentPreConfiguration.postgres.up.sql
@@ -0,0 +1,198 @@
+CREATE TABLE oauth2_consent_preconfiguration (
+ id SERIAL,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ expires_at TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ scopes TEXT NOT NULL,
+ audience TEXT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_preconfiguration_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+INSERT INTO oauth2_consent_preconfiguration (client_id, subject, created_at, expires_at, scopes, audience)
+SELECT client_id, subject, responded_at, expires_at, granted_scopes, granted_audience
+FROM oauth2_consent_session
+WHERE expires_at IS NOT NULL AND responded_at IS NOT NULL
+ORDER BY responded_at;
+
+DROP TABLE oauth2_authorization_code_session;
+DROP TABLE oauth2_access_token_session;
+DROP TABLE oauth2_refresh_token_session;
+DROP TABLE oauth2_pkce_request_session;
+DROP TABLE oauth2_openid_connect_session;
+DROP TABLE oauth2_consent_session;
+
+CREATE TABLE oauth2_consent_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ authorized BOOLEAN NOT NULL DEFAULT FALSE,
+ granted BOOLEAN NOT NULL DEFAULT FALSE,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ responded_at TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+ form_data TEXT NOT NULL,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL,
+ granted_audience TEXT NULL,
+ preconfiguration INTEGER NULL DEFAULT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT,
+ CONSTRAINT oauth2_consent_session_preconfiguration_fkey
+ FOREIGN KEY(preconfiguration)
+ REFERENCES oauth2_consent_preconfiguration(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+CREATE UNIQUE INDEX oauth2_consent_session_challenge_id_key ON oauth2_consent_session (challenge_id);
+
+CREATE TABLE oauth2_authorization_code_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_authorization_code_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_authorization_code_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_authorization_code_session_request_id_idx ON oauth2_authorization_code_session (request_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_idx ON oauth2_authorization_code_session (client_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_subject_idx ON oauth2_authorization_code_session (client_id, subject);
+
+CREATE TABLE oauth2_access_token_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_access_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_access_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_access_token_session_request_id_idx ON oauth2_access_token_session (request_id);
+CREATE INDEX oauth2_access_token_session_client_id_idx ON oauth2_access_token_session (client_id);
+CREATE INDEX oauth2_access_token_session_client_id_subject_idx ON oauth2_access_token_session (client_id, subject);
+
+CREATE TABLE oauth2_refresh_token_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_refresh_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_refresh_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_refresh_token_session_request_id_idx ON oauth2_refresh_token_session (request_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_idx ON oauth2_refresh_token_session (client_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_subject_idx ON oauth2_refresh_token_session (client_id, subject);
+
+CREATE TABLE oauth2_pkce_request_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_pkce_request_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_pkce_request_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_pkce_request_session_request_id_idx ON oauth2_pkce_request_session (request_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_idx ON oauth2_pkce_request_session (client_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_subject_idx ON oauth2_pkce_request_session (client_id, subject);
+
+CREATE TABLE oauth2_openid_connect_session (
+ id SERIAL,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BYTEA NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_openid_connect_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_openid_connect_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_openid_connect_session_request_id_idx ON oauth2_openid_connect_session (request_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_idx ON oauth2_openid_connect_session (client_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_subject_idx ON oauth2_openid_connect_session (client_id, subject);
diff --git a/internal/storage/migrations/V0006.ConsentPreConfiguration.sqlite.down.sql b/internal/storage/migrations/V0006.ConsentPreConfiguration.sqlite.down.sql
new file mode 100644
index 000000000..e4bfaefeb
--- /dev/null
+++ b/internal/storage/migrations/V0006.ConsentPreConfiguration.sqlite.down.sql
@@ -0,0 +1,175 @@
+DROP TABLE oauth2_authorization_code_session;
+DROP TABLE oauth2_access_token_session;
+DROP TABLE oauth2_refresh_token_session;
+DROP TABLE oauth2_pkce_request_session;
+DROP TABLE oauth2_openid_connect_session;
+DROP TABLE oauth2_consent_session;
+DROP TABLE oauth2_consent_preconfiguration;
+
+CREATE TABLE oauth2_consent_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NULL DEFAULT NULL,
+ authorized BOOLEAN NOT NULL DEFAULT FALSE,
+ granted BOOLEAN NOT NULL DEFAULT FALSE,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ responded_at TIMESTAMP NULL DEFAULT NULL,
+ expires_at TIMESTAMP NULL DEFAULT NULL,
+ form_data TEXT NOT NULL,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE UNIQUE INDEX oauth2_consent_session_challenge_id_key ON oauth2_consent_session (challenge_id);
+
+CREATE TABLE oauth2_authorization_code_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_authorization_code_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_authorization_code_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_authorization_code_session_request_id_idx ON oauth2_authorization_code_session (request_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_idx ON oauth2_authorization_code_session (client_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_subject_idx ON oauth2_authorization_code_session (client_id, subject);
+
+CREATE TABLE IF NOT EXISTS oauth2_access_token_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_access_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_access_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_access_token_session_request_id_idx ON oauth2_access_token_session (request_id);
+CREATE INDEX oauth2_access_token_session_client_id_idx ON oauth2_access_token_session (client_id);
+CREATE INDEX oauth2_access_token_session_client_id_subject_idx ON oauth2_access_token_session (client_id, subject);
+
+CREATE TABLE IF NOT EXISTS oauth2_refresh_token_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_refresh_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_refresh_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_refresh_token_session_request_id_idx ON oauth2_refresh_token_session (request_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_idx ON oauth2_refresh_token_session (client_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_subject_idx ON oauth2_refresh_token_session (client_id, subject);
+
+CREATE TABLE oauth2_pkce_request_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_pkce_request_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_pkce_request_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_pkce_request_session_request_id_idx ON oauth2_pkce_request_session (request_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_idx ON oauth2_pkce_request_session (client_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_subject_idx ON oauth2_pkce_request_session (client_id, subject);
+
+CREATE TABLE oauth2_openid_connect_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_openid_connect_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_openid_connect_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_openid_connect_session_request_id_idx ON oauth2_openid_connect_session (request_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_idx ON oauth2_openid_connect_session (client_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_subject_idx ON oauth2_openid_connect_session (client_id, subject);
diff --git a/internal/storage/migrations/V0006.ConsentPreConfiguration.sqlite.up.sql b/internal/storage/migrations/V0006.ConsentPreConfiguration.sqlite.up.sql
new file mode 100644
index 000000000..3f1bccef3
--- /dev/null
+++ b/internal/storage/migrations/V0006.ConsentPreConfiguration.sqlite.up.sql
@@ -0,0 +1,198 @@
+CREATE TABLE oauth2_consent_preconfiguration (
+ id INTEGER,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ expires_at TIMESTAMP NULL DEFAULT NULL,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ scopes TEXT NOT NULL,
+ audience TEXT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_preconfiguration_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+INSERT INTO oauth2_consent_preconfiguration (client_id, subject, created_at, expires_at, scopes, audience)
+SELECT client_id, subject, responded_at, expires_at, granted_scopes, granted_audience
+FROM oauth2_consent_session
+WHERE expires_at IS NOT NULL AND responded_at IS NOT NULL
+ORDER BY responded_at;
+
+DROP TABLE oauth2_authorization_code_session;
+DROP TABLE oauth2_access_token_session;
+DROP TABLE oauth2_refresh_token_session;
+DROP TABLE oauth2_pkce_request_session;
+DROP TABLE oauth2_openid_connect_session;
+DROP TABLE oauth2_consent_session;
+
+CREATE TABLE oauth2_consent_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ authorized BOOLEAN NOT NULL DEFAULT FALSE,
+ granted BOOLEAN NOT NULL DEFAULT FALSE,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ responded_at TIMESTAMP NULL DEFAULT NULL,
+ form_data TEXT NOT NULL,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ preconfiguration INTEGER NULL DEFAULT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_consent_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT,
+ CONSTRAINT oauth2_consent_session_preconfiguration_fkey
+ FOREIGN KEY(preconfiguration)
+ REFERENCES oauth2_consent_preconfiguration(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+CREATE UNIQUE INDEX oauth2_consent_session_challenge_id_key ON oauth2_consent_session (challenge_id);
+
+CREATE TABLE oauth2_authorization_code_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_authorization_code_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_authorization_code_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_authorization_code_session_request_id_idx ON oauth2_authorization_code_session (request_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_idx ON oauth2_authorization_code_session (client_id);
+CREATE INDEX oauth2_authorization_code_session_client_id_subject_idx ON oauth2_authorization_code_session (client_id, subject);
+
+CREATE TABLE oauth2_access_token_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_access_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_access_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_access_token_session_request_id_idx ON oauth2_access_token_session (request_id);
+CREATE INDEX oauth2_access_token_session_client_id_idx ON oauth2_access_token_session (client_id);
+CREATE INDEX oauth2_access_token_session_client_id_subject_idx ON oauth2_access_token_session (client_id, subject);
+
+CREATE TABLE oauth2_refresh_token_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_refresh_token_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_refresh_token_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_refresh_token_session_request_id_idx ON oauth2_refresh_token_session (request_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_idx ON oauth2_refresh_token_session (client_id);
+CREATE INDEX oauth2_refresh_token_session_client_id_subject_idx ON oauth2_refresh_token_session (client_id, subject);
+
+CREATE TABLE oauth2_pkce_request_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_pkce_request_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_pkce_request_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_pkce_request_session_request_id_idx ON oauth2_pkce_request_session (request_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_idx ON oauth2_pkce_request_session (client_id);
+CREATE INDEX oauth2_pkce_request_session_client_id_subject_idx ON oauth2_pkce_request_session (client_id, subject);
+
+CREATE TABLE oauth2_openid_connect_session (
+ id INTEGER,
+ challenge_id CHAR(36) NOT NULL,
+ request_id VARCHAR(40) NOT NULL,
+ client_id VARCHAR(255) NOT NULL,
+ signature VARCHAR(255) NOT NULL,
+ subject CHAR(36) NOT NULL,
+ requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ requested_scopes TEXT NOT NULL,
+ granted_scopes TEXT NOT NULL,
+ requested_audience TEXT NULL DEFAULT '',
+ granted_audience TEXT NULL DEFAULT '',
+ active BOOLEAN NOT NULL DEFAULT FALSE,
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
+ form_data TEXT NOT NULL,
+ session_data BLOB NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT oauth2_openid_connect_session_challenge_id_fkey
+ FOREIGN KEY(challenge_id)
+ REFERENCES oauth2_consent_session(challenge_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT oauth2_openid_connect_session_subject_fkey
+ FOREIGN KEY(subject)
+ REFERENCES user_opaque_identifier(identifier) ON UPDATE RESTRICT ON DELETE RESTRICT
+);
+
+CREATE INDEX oauth2_openid_connect_session_request_id_idx ON oauth2_openid_connect_session (request_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_idx ON oauth2_openid_connect_session (client_id);
+CREATE INDEX oauth2_openid_connect_session_client_id_subject_idx ON oauth2_openid_connect_session (client_id, subject);
diff --git a/internal/storage/migrations_test.go b/internal/storage/migrations_test.go
index 38fa84f42..f7c9df73e 100644
--- a/internal/storage/migrations_test.go
+++ b/internal/storage/migrations_test.go
@@ -7,11 +7,16 @@ import (
"github.com/stretchr/testify/require"
)
+const (
+ // This is the latest schema version for the purpose of tests.
+ LatestVersion = 6
+)
+
func TestShouldObtainCorrectUpMigrations(t *testing.T) {
ver, err := latestMigrationVersion(providerSQLite)
require.NoError(t, err)
- assert.Equal(t, testLatestVersion, ver)
+ assert.Equal(t, LatestVersion, ver)
migrations, err := loadMigrations(providerSQLite, 0, ver)
require.NoError(t, err)
@@ -27,7 +32,7 @@ func TestShouldObtainCorrectDownMigrations(t *testing.T) {
ver, err := latestMigrationVersion(providerSQLite)
require.NoError(t, err)
- assert.Equal(t, testLatestVersion, ver)
+ assert.Equal(t, LatestVersion, ver)
migrations, err := loadMigrations(providerSQLite, ver, 0)
require.NoError(t, err)
diff --git a/internal/storage/provider.go b/internal/storage/provider.go
index 7696966a5..ecfe104b0 100644
--- a/internal/storage/provider.go
+++ b/internal/storage/provider.go
@@ -2,6 +2,7 @@ package storage
import (
"context"
+ "database/sql"
"time"
"github.com/google/uuid"
@@ -32,13 +33,15 @@ type Provider interface {
FindIdentityVerification(ctx context.Context, jti string) (found bool, err error)
SaveTOTPConfiguration(ctx context.Context, config model.TOTPConfiguration) (err error)
- UpdateTOTPConfigurationSignIn(ctx context.Context, id int, lastUsedAt *time.Time) (err error)
+ UpdateTOTPConfigurationSignIn(ctx context.Context, id int, lastUsedAt sql.NullTime) (err error)
DeleteTOTPConfiguration(ctx context.Context, username string) (err error)
LoadTOTPConfiguration(ctx context.Context, username string) (config *model.TOTPConfiguration, err error)
LoadTOTPConfigurations(ctx context.Context, limit, page int) (configs []model.TOTPConfiguration, err error)
SaveWebauthnDevice(ctx context.Context, device model.WebauthnDevice) (err error)
- UpdateWebauthnDeviceSignIn(ctx context.Context, id int, rpid string, lastUsedAt *time.Time, signCount uint32, cloneWarning bool) (err error)
+ UpdateWebauthnDeviceSignIn(ctx context.Context, id int, rpid string, lastUsedAt sql.NullTime, signCount uint32, cloneWarning bool) (err error)
+ DeleteWebauthnDevice(ctx context.Context, kid string) (err error)
+ DeleteWebauthnDeviceByUsername(ctx context.Context, username, description string) (err error)
LoadWebauthnDevices(ctx context.Context, limit, page int) (devices []model.WebauthnDevice, err error)
LoadWebauthnDevicesByUsername(ctx context.Context, username string) (devices []model.WebauthnDevice, err error)
@@ -46,12 +49,14 @@ type Provider interface {
DeletePreferredDuoDevice(ctx context.Context, username string) (err error)
LoadPreferredDuoDevice(ctx context.Context, username string) (device *model.DuoDevice, err error)
+ SaveOAuth2ConsentPreConfiguration(ctx context.Context, config model.OAuth2ConsentPreConfig) (insertedID int64, err error)
+ LoadOAuth2ConsentPreConfigurations(ctx context.Context, clientID string, subject uuid.UUID) (rows *ConsentPreConfigRows, err error)
+
SaveOAuth2ConsentSession(ctx context.Context, consent model.OAuth2ConsentSession) (err error)
SaveOAuth2ConsentSessionSubject(ctx context.Context, consent model.OAuth2ConsentSession) (err error)
SaveOAuth2ConsentSessionResponse(ctx context.Context, consent model.OAuth2ConsentSession, rejection bool) (err error)
SaveOAuth2ConsentSessionGranted(ctx context.Context, id int) (err error)
LoadOAuth2ConsentSessionByChallengeID(ctx context.Context, challengeID uuid.UUID) (consent *model.OAuth2ConsentSession, err error)
- LoadOAuth2ConsentSessionsPreConfigured(ctx context.Context, clientID string, subject uuid.UUID) (rows *ConsentSessionRows, err error)
SaveOAuth2Session(ctx context.Context, sessionType OAuth2SessionType, session model.OAuth2Session) (err error)
RevokeOAuth2Session(ctx context.Context, sessionType OAuth2SessionType, signature string) (err error)
diff --git a/internal/storage/sql_provider.go b/internal/storage/sql_provider.go
index bb5ac9895..7ee60ab5c 100644
--- a/internal/storage/sql_provider.go
+++ b/internal/storage/sql_provider.go
@@ -6,6 +6,7 @@ import (
"database/sql"
"errors"
"fmt"
+ "strings"
"time"
"github.com/google/uuid"
@@ -56,6 +57,10 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlUpdateWebauthnDeviceRecordSignIn: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignIn, tableWebauthnDevices),
sqlUpdateWebauthnDeviceRecordSignInByUsername: fmt.Sprintf(queryFmtUpdateWebauthnDeviceRecordSignInByUsername, tableWebauthnDevices),
+ sqlDeleteWebauthnDevice: fmt.Sprintf(queryFmtDeleteWebauthnDevice, tableWebauthnDevices),
+ sqlDeleteWebauthnDeviceByUsername: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsername, tableWebauthnDevices),
+ sqlDeleteWebauthnDeviceByUsernameAndDescription: fmt.Sprintf(queryFmtDeleteWebauthnDeviceByUsernameAndDescription, tableWebauthnDevices),
+
sqlUpsertDuoDevice: fmt.Sprintf(queryFmtUpsertDuoDevice, tableDuoDevices),
sqlDeleteDuoDevice: fmt.Sprintf(queryFmtDeleteDuoDevice, tableDuoDevices),
sqlSelectDuoDevice: fmt.Sprintf(queryFmtSelectDuoDevice, tableDuoDevices),
@@ -69,6 +74,15 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlSelectUserOpaqueIdentifiers: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifiers, tableUserOpaqueIdentifier),
sqlSelectUserOpaqueIdentifierBySignature: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifierBySignature, tableUserOpaqueIdentifier),
+ sqlInsertOAuth2ConsentPreConfiguration: fmt.Sprintf(queryFmtInsertOAuth2ConsentPreConfiguration, tableOAuth2ConsentPreConfiguration),
+ sqlSelectOAuth2ConsentPreConfigurations: fmt.Sprintf(queryFmtSelectOAuth2ConsentPreConfigurations, tableOAuth2ConsentPreConfiguration),
+
+ sqlInsertOAuth2ConsentSession: fmt.Sprintf(queryFmtInsertOAuth2ConsentSession, tableOAuth2ConsentSession),
+ sqlUpdateOAuth2ConsentSessionSubject: fmt.Sprintf(queryFmtUpdateOAuth2ConsentSessionSubject, tableOAuth2ConsentSession),
+ sqlUpdateOAuth2ConsentSessionResponse: fmt.Sprintf(queryFmtUpdateOAuth2ConsentSessionResponse, tableOAuth2ConsentSession),
+ sqlUpdateOAuth2ConsentSessionGranted: fmt.Sprintf(queryFmtUpdateOAuth2ConsentSessionGranted, tableOAuth2ConsentSession),
+ sqlSelectOAuth2ConsentSessionByChallengeID: fmt.Sprintf(queryFmtSelectOAuth2ConsentSessionByChallengeID, tableOAuth2ConsentSession),
+
sqlInsertOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlSelectOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2AuthorizeCodeSession),
sqlRevokeOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2AuthorizeCodeSession),
@@ -104,13 +118,6 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlDeactivateOAuth2OpenIDConnectSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2OpenIDConnectSession),
sqlDeactivateOAuth2OpenIDConnectSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2OpenIDConnectSession),
- sqlInsertOAuth2ConsentSession: fmt.Sprintf(queryFmtInsertOAuth2ConsentSession, tableOAuth2ConsentSession),
- sqlUpdateOAuth2ConsentSessionSubject: fmt.Sprintf(queryFmtUpdateOAuth2ConsentSessionSubject, tableOAuth2ConsentSession),
- sqlUpdateOAuth2ConsentSessionResponse: fmt.Sprintf(queryFmtUpdateOAuth2ConsentSessionResponse, tableOAuth2ConsentSession),
- sqlUpdateOAuth2ConsentSessionGranted: fmt.Sprintf(queryFmtUpdateOAuth2ConsentSessionGranted, tableOAuth2ConsentSession),
- sqlSelectOAuth2ConsentSessionByChallengeID: fmt.Sprintf(queryFmtSelectOAuth2ConsentSessionByChallengeID, tableOAuth2ConsentSession),
- sqlSelectOAuth2ConsentSessionsPreConfigured: fmt.Sprintf(queryFmtSelectOAuth2ConsentSessionsPreConfigured, tableOAuth2ConsentSession),
-
sqlUpsertOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtUpsertOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI),
sqlSelectOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtSelectOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI),
@@ -169,6 +176,10 @@ type SQLProvider struct {
sqlUpdateWebauthnDeviceRecordSignIn string
sqlUpdateWebauthnDeviceRecordSignInByUsername string
+ sqlDeleteWebauthnDevice string
+ sqlDeleteWebauthnDeviceByUsername string
+ sqlDeleteWebauthnDeviceByUsernameAndDescription string
+
// Table: duo_devices.
sqlUpsertDuoDevice string
sqlDeleteDuoDevice string
@@ -194,6 +205,17 @@ type SQLProvider struct {
sqlUpsertEncryptionValue string
sqlSelectEncryptionValue string
+ // Table: oauth2_consent_preconfiguration.
+ sqlInsertOAuth2ConsentPreConfiguration string
+ sqlSelectOAuth2ConsentPreConfigurations string
+
+ // Table: oauth2_consent_session.
+ sqlInsertOAuth2ConsentSession string
+ sqlUpdateOAuth2ConsentSessionSubject string
+ sqlUpdateOAuth2ConsentSessionResponse string
+ sqlUpdateOAuth2ConsentSessionGranted string
+ sqlSelectOAuth2ConsentSessionByChallengeID string
+
// Table: oauth2_authorization_code_session.
sqlInsertOAuth2AuthorizeCodeSession string
sqlSelectOAuth2AuthorizeCodeSession string
@@ -234,14 +256,6 @@ type SQLProvider struct {
sqlDeactivateOAuth2OpenIDConnectSession string
sqlDeactivateOAuth2OpenIDConnectSessionByRequestID string
- // Table: oauth2_consent_session.
- sqlInsertOAuth2ConsentSession string
- sqlUpdateOAuth2ConsentSessionSubject string
- sqlUpdateOAuth2ConsentSessionResponse string
- sqlUpdateOAuth2ConsentSessionGranted string
- sqlSelectOAuth2ConsentSessionByChallengeID string
- sqlSelectOAuth2ConsentSessionsPreConfigured string
-
sqlUpsertOAuth2BlacklistedJTI string
sqlSelectOAuth2BlacklistedJTI string
@@ -396,8 +410,8 @@ func (p *SQLProvider) LoadUserOpaqueIdentifierBySignature(ctx context.Context, s
func (p *SQLProvider) SaveOAuth2ConsentSession(ctx context.Context, consent model.OAuth2ConsentSession) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlInsertOAuth2ConsentSession,
consent.ChallengeID, consent.ClientID, consent.Subject, consent.Authorized, consent.Granted,
- consent.RequestedAt, consent.RespondedAt, consent.ExpiresAt, consent.Form,
- consent.RequestedScopes, consent.GrantedScopes, consent.RequestedAudience, consent.GrantedAudience); err != nil {
+ consent.RequestedAt, consent.RespondedAt, consent.Form,
+ consent.RequestedScopes, consent.GrantedScopes, consent.RequestedAudience, consent.GrantedAudience, consent.PreConfiguration); err != nil {
return fmt.Errorf("error inserting oauth2 consent session with challenge id '%s' for subject '%s': %w", consent.ChallengeID.String(), consent.Subject.UUID.String(), err)
}
@@ -415,7 +429,7 @@ func (p *SQLProvider) SaveOAuth2ConsentSessionSubject(ctx context.Context, conse
// SaveOAuth2ConsentSessionResponse updates an OAuth2.0 consent session with the response.
func (p *SQLProvider) SaveOAuth2ConsentSessionResponse(ctx context.Context, consent model.OAuth2ConsentSession, authorized bool) (err error) {
- if _, err = p.db.ExecContext(ctx, p.sqlUpdateOAuth2ConsentSessionResponse, authorized, consent.ExpiresAt, consent.GrantedScopes, consent.GrantedAudience, consent.ID); err != nil {
+ if _, err = p.db.ExecContext(ctx, p.sqlUpdateOAuth2ConsentSessionResponse, authorized, consent.GrantedScopes, consent.GrantedAudience, consent.PreConfiguration, consent.ID); err != nil {
return fmt.Errorf("error updating oauth2 consent session (authorized '%t') with id '%d' and challenge id '%s' for subject '%s': %w", authorized, consent.ID, consent.ChallengeID, consent.Subject.UUID, err)
}
@@ -442,19 +456,43 @@ func (p *SQLProvider) LoadOAuth2ConsentSessionByChallengeID(ctx context.Context,
return consent, nil
}
-// LoadOAuth2ConsentSessionsPreConfigured returns an OAuth2.0 consents that are pre-configured given the consent signature.
-func (p *SQLProvider) LoadOAuth2ConsentSessionsPreConfigured(ctx context.Context, clientID string, subject uuid.UUID) (rows *ConsentSessionRows, err error) {
- var r *sqlx.Rows
-
- if r, err = p.db.QueryxContext(ctx, p.sqlSelectOAuth2ConsentSessionsPreConfigured, clientID, subject); err != nil {
- if errors.Is(err, sql.ErrNoRows) {
- return &ConsentSessionRows{}, nil
+// SaveOAuth2ConsentPreConfiguration inserts an OAuth2.0 consent pre-configuration.
+func (p *SQLProvider) SaveOAuth2ConsentPreConfiguration(ctx context.Context, config model.OAuth2ConsentPreConfig) (insertedID int64, err error) {
+ switch p.name {
+ case providerPostgres:
+ if err = p.db.GetContext(ctx, &insertedID, p.sqlInsertOAuth2ConsentPreConfiguration,
+ config.ClientID, config.Subject, config.CreatedAt, config.ExpiresAt,
+ config.Revoked, config.Scopes, config.Audience); err != nil {
+ return -1, fmt.Errorf("error inserting oauth2 consent pre-configuration for subject '%s' with client id '%s' and scopes '%s': %w", config.Subject.String(), config.ClientID, strings.Join(config.Scopes, " "), err)
}
- return &ConsentSessionRows{}, fmt.Errorf("error selecting oauth2 consent session by signature with client id '%s' and subject '%s': %w", clientID, subject.String(), err)
+ return insertedID, nil
+ default:
+ var result sql.Result
+
+ if result, err = p.db.ExecContext(ctx, p.sqlInsertOAuth2ConsentPreConfiguration,
+ config.ClientID, config.Subject, config.CreatedAt, config.ExpiresAt,
+ config.Revoked, config.Scopes, config.Audience); err != nil {
+ return -1, fmt.Errorf("error inserting oauth2 consent pre-configuration for subject '%s' with client id '%s' and scopes '%s': %w", config.Subject.String(), config.ClientID, strings.Join(config.Scopes, " "), err)
+ }
+
+ return result.LastInsertId()
+ }
+}
+
+// LoadOAuth2ConsentPreConfigurations returns an OAuth2.0 consents pre-configurations given the consent signature.
+func (p *SQLProvider) LoadOAuth2ConsentPreConfigurations(ctx context.Context, clientID string, subject uuid.UUID) (rows *ConsentPreConfigRows, err error) {
+ var r *sqlx.Rows
+
+ if r, err = p.db.QueryxContext(ctx, p.sqlSelectOAuth2ConsentPreConfigurations, clientID, subject); err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return &ConsentPreConfigRows{}, nil
+ }
+
+ return &ConsentPreConfigRows{}, fmt.Errorf("error selecting oauth2 consent pre-configurations by signature with client id '%s' and subject '%s': %w", clientID, subject.String(), err)
}
- return &ConsentSessionRows{rows: r}, nil
+ return &ConsentPreConfigRows{rows: r}, nil
}
// SaveOAuth2Session saves a OAuth2Session to the database.
@@ -716,7 +754,7 @@ func (p *SQLProvider) FindIdentityVerification(ctx context.Context, jti string)
}
switch {
- case verification.Consumed != nil:
+ case verification.Consumed.Valid:
return false, fmt.Errorf("the token has already been consumed")
case verification.ExpiresAt.Before(time.Now()):
return false, fmt.Errorf("the token expired %s ago", time.Since(verification.ExpiresAt))
@@ -742,7 +780,7 @@ func (p *SQLProvider) SaveTOTPConfiguration(ctx context.Context, config model.TO
}
// UpdateTOTPConfigurationSignIn updates a registered Webauthn devices sign in information.
-func (p *SQLProvider) UpdateTOTPConfigurationSignIn(ctx context.Context, id int, lastUsedAt *time.Time) (err error) {
+func (p *SQLProvider) UpdateTOTPConfigurationSignIn(ctx context.Context, id int, lastUsedAt sql.NullTime) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlUpdateTOTPConfigRecordSignIn, lastUsedAt, id); err != nil {
return fmt.Errorf("error updating TOTP configuration id %d: %w", id, err)
}
@@ -833,7 +871,7 @@ func (p *SQLProvider) SaveWebauthnDevice(ctx context.Context, device model.Webau
}
// UpdateWebauthnDeviceSignIn updates a registered Webauthn devices sign in information.
-func (p *SQLProvider) UpdateWebauthnDeviceSignIn(ctx context.Context, id int, rpid string, lastUsedAt *time.Time, signCount uint32, cloneWarning bool) (err error) {
+func (p *SQLProvider) UpdateWebauthnDeviceSignIn(ctx context.Context, id int, rpid string, lastUsedAt sql.NullTime, signCount uint32, cloneWarning bool) (err error) {
if _, err = p.db.ExecContext(ctx, p.sqlUpdateWebauthnDeviceRecordSignIn, rpid, lastUsedAt, signCount, cloneWarning, id); err != nil {
return fmt.Errorf("error updating Webauthn signin metadata for id '%x': %w", id, err)
}
@@ -841,6 +879,34 @@ func (p *SQLProvider) UpdateWebauthnDeviceSignIn(ctx context.Context, id int, rp
return nil
}
+// DeleteWebauthnDevice deletes a registered Webauthn device.
+func (p *SQLProvider) DeleteWebauthnDevice(ctx context.Context, kid string) (err error) {
+ if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDevice, kid); err != nil {
+ return fmt.Errorf("error deleting webauthn device with kid '%s': %w", kid, err)
+ }
+
+ return nil
+}
+
+// DeleteWebauthnDeviceByUsername deletes registered Webauthn devices by username or username and description.
+func (p *SQLProvider) DeleteWebauthnDeviceByUsername(ctx context.Context, username, description string) (err error) {
+ if len(username) == 0 {
+ return fmt.Errorf("error deleting webauthn device with username '%s' and description '%s': username must not be empty", username, description)
+ }
+
+ if len(description) == 0 {
+ if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDeviceByUsername, username); err != nil {
+ return fmt.Errorf("error deleting webauthn devices for username '%s': %w", username, err)
+ }
+ } else {
+ if _, err = p.db.ExecContext(ctx, p.sqlDeleteWebauthnDeviceByUsernameAndDescription, username, description); err != nil {
+ return fmt.Errorf("error deleting webauthn device with username '%s' and description '%s': %w", username, description, err)
+ }
+ }
+
+ return nil
+}
+
// LoadWebauthnDevices loads Webauthn device registrations.
func (p *SQLProvider) LoadWebauthnDevices(ctx context.Context, limit, page int) (devices []model.WebauthnDevice, err error) {
devices = make([]model.WebauthnDevice, 0, limit)
diff --git a/internal/storage/sql_provider_backend_mysql.go b/internal/storage/sql_provider_backend_mysql.go
index 6c313ee57..32cc7de16 100644
--- a/internal/storage/sql_provider_backend_mysql.go
+++ b/internal/storage/sql_provider_backend_mysql.go
@@ -1,12 +1,15 @@
package storage
import (
+ "crypto/x509"
"fmt"
+ "path"
"time"
- _ "github.com/go-sql-driver/mysql" // Load the MySQL Driver used in the connection string.
+ "github.com/go-sql-driver/mysql"
"github.com/authelia/authelia/v4/internal/configuration/schema"
+ "github.com/authelia/authelia/v4/internal/utils"
)
// MySQLProvider is a MySQL provider.
@@ -15,9 +18,9 @@ type MySQLProvider struct {
}
// NewMySQLProvider a MySQL provider.
-func NewMySQLProvider(config *schema.Configuration) (provider *MySQLProvider) {
+func NewMySQLProvider(config *schema.Configuration, caCertPool *x509.CertPool) (provider *MySQLProvider) {
provider = &MySQLProvider{
- SQLProvider: NewSQLProvider(config, providerMySQL, providerMySQL, dataSourceNameMySQL(*config.Storage.MySQL)),
+ SQLProvider: NewSQLProvider(config, providerMySQL, providerMySQL, dsnMySQL(config.Storage.MySQL, caCertPool)),
}
// All providers have differing SELECT existing table statements.
@@ -29,22 +32,41 @@ func NewMySQLProvider(config *schema.Configuration) (provider *MySQLProvider) {
return provider
}
-func dataSourceNameMySQL(config schema.MySQLStorageConfiguration) (dataSourceName string) {
- dataSourceName = fmt.Sprintf("%s:%s", config.Username, config.Password)
+func dsnMySQL(config *schema.MySQLStorageConfiguration, caCertPool *x509.CertPool) (dataSourceName string) {
+ dsnConfig := mysql.NewConfig()
- if dataSourceName != "" {
- dataSourceName += "@"
+ switch {
+ case path.IsAbs(config.Host):
+ dsnConfig.Net = sqlNetworkTypeUnixSocket
+ dsnConfig.Addr = config.Host
+ case config.Port == 0:
+ dsnConfig.Net = sqlNetworkTypeTCP
+ dsnConfig.Addr = fmt.Sprintf("%s:%d", config.Host, 3306)
+ default:
+ dsnConfig.Net = sqlNetworkTypeTCP
+ dsnConfig.Addr = fmt.Sprintf("%s:%d", config.Host, config.Port)
}
- address := config.Host
- if config.Port > 0 {
- address += fmt.Sprintf(":%d", config.Port)
+ if config.TLS != nil {
+ _ = mysql.RegisterTLSConfig("storage", utils.NewTLSConfig(config.TLS, caCertPool))
+
+ dsnConfig.TLSConfig = "storage"
}
- dataSourceName += fmt.Sprintf("tcp(%s)/%s", address, config.Database)
+ switch config.Port {
+ case 0:
+ dsnConfig.Addr = config.Host
+ default:
+ dsnConfig.Addr = fmt.Sprintf("%s:%d", config.Host, config.Port)
+ }
- dataSourceName += "?"
- dataSourceName += fmt.Sprintf("timeout=%ds&multiStatements=true&parseTime=true", int32(config.Timeout/time.Second))
+ dsnConfig.DBName = config.Database
+ dsnConfig.User = config.Username
+ dsnConfig.Passwd = config.Password
+ dsnConfig.Timeout = config.Timeout
+ dsnConfig.MultiStatements = true
+ dsnConfig.ParseTime = true
+ dsnConfig.Loc = time.Local
- return dataSourceName
+ return dsnConfig.FormatDSN()
}
diff --git a/internal/storage/sql_provider_backend_postgres.go b/internal/storage/sql_provider_backend_postgres.go
index 3b98e64e4..e8834db44 100644
--- a/internal/storage/sql_provider_backend_postgres.go
+++ b/internal/storage/sql_provider_backend_postgres.go
@@ -1,13 +1,18 @@
package storage
import (
+ "crypto/tls"
+ "crypto/x509"
+ "errors"
"fmt"
- "strings"
- "time"
+ "os"
+ "path"
- _ "github.com/jackc/pgx/v4/stdlib" // Load the PostgreSQL Driver used in the connection string.
+ "github.com/jackc/pgx/v5"
+ "github.com/jackc/pgx/v5/stdlib"
"github.com/authelia/authelia/v4/internal/configuration/schema"
+ "github.com/authelia/authelia/v4/internal/utils"
)
// PostgreSQLProvider is a PostgreSQL provider.
@@ -16,9 +21,9 @@ type PostgreSQLProvider struct {
}
// NewPostgreSQLProvider a PostgreSQL provider.
-func NewPostgreSQLProvider(config *schema.Configuration) (provider *PostgreSQLProvider) {
+func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPool) (provider *PostgreSQLProvider) {
provider = &PostgreSQLProvider{
- SQLProvider: NewSQLProvider(config, providerPostgres, "pgx", dataSourceNamePostgreSQL(*config.Storage.PostgreSQL)),
+ SQLProvider: NewSQLProvider(config, providerPostgres, "pgx", dsnPostgreSQL(config.Storage.PostgreSQL, caCertPool)),
}
// All providers have differing SELECT existing table statements.
@@ -32,6 +37,7 @@ func NewPostgreSQLProvider(config *schema.Configuration) (provider *PostgreSQLPr
provider.sqlUpsertPreferred2FAMethod = fmt.Sprintf(queryFmtUpsertPreferred2FAMethodPostgreSQL, tableUserPreferences)
provider.sqlUpsertEncryptionValue = fmt.Sprintf(queryFmtUpsertEncryptionValuePostgreSQL, tableEncryption)
provider.sqlUpsertOAuth2BlacklistedJTI = fmt.Sprintf(queryFmtUpsertOAuth2BlacklistedJTIPostgreSQL, tableOAuth2BlacklistedJTI)
+ provider.sqlInsertOAuth2ConsentPreConfiguration = fmt.Sprintf(queryFmtInsertOAuth2ConsentPreConfigurationPostgreSQL, tableOAuth2ConsentPreConfiguration)
// PostgreSQL requires rebinding of any query that contains a '?' placeholder to use the '$#' notation placeholders.
provider.sqlFmtRenameTable = provider.db.Rebind(provider.sqlFmtRenameTable)
@@ -61,6 +67,9 @@ func NewPostgreSQLProvider(config *schema.Configuration) (provider *PostgreSQLPr
provider.sqlUpdateWebauthnDevicePublicKeyByUsername = provider.db.Rebind(provider.sqlUpdateWebauthnDevicePublicKeyByUsername)
provider.sqlUpdateWebauthnDeviceRecordSignIn = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceRecordSignIn)
provider.sqlUpdateWebauthnDeviceRecordSignInByUsername = provider.db.Rebind(provider.sqlUpdateWebauthnDeviceRecordSignInByUsername)
+ provider.sqlDeleteWebauthnDevice = provider.db.Rebind(provider.sqlDeleteWebauthnDevice)
+ provider.sqlDeleteWebauthnDeviceByUsername = provider.db.Rebind(provider.sqlDeleteWebauthnDeviceByUsername)
+ provider.sqlDeleteWebauthnDeviceByUsernameAndDescription = provider.db.Rebind(provider.sqlDeleteWebauthnDeviceByUsernameAndDescription)
provider.sqlSelectDuoDevice = provider.db.Rebind(provider.sqlSelectDuoDevice)
provider.sqlDeleteDuoDevice = provider.db.Rebind(provider.sqlDeleteDuoDevice)
@@ -74,12 +83,13 @@ func NewPostgreSQLProvider(config *schema.Configuration) (provider *PostgreSQLPr
provider.sqlSelectEncryptionValue = provider.db.Rebind(provider.sqlSelectEncryptionValue)
+ provider.sqlSelectOAuth2ConsentPreConfigurations = provider.db.Rebind(provider.sqlSelectOAuth2ConsentPreConfigurations)
+
provider.sqlInsertOAuth2ConsentSession = provider.db.Rebind(provider.sqlInsertOAuth2ConsentSession)
provider.sqlUpdateOAuth2ConsentSessionSubject = provider.db.Rebind(provider.sqlUpdateOAuth2ConsentSessionSubject)
provider.sqlUpdateOAuth2ConsentSessionResponse = provider.db.Rebind(provider.sqlUpdateOAuth2ConsentSessionResponse)
provider.sqlUpdateOAuth2ConsentSessionGranted = provider.db.Rebind(provider.sqlUpdateOAuth2ConsentSessionGranted)
provider.sqlSelectOAuth2ConsentSessionByChallengeID = provider.db.Rebind(provider.sqlSelectOAuth2ConsentSessionByChallengeID)
- provider.sqlSelectOAuth2ConsentSessionsPreConfigured = provider.db.Rebind(provider.sqlSelectOAuth2ConsentSessionsPreConfigured)
provider.sqlInsertOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlInsertOAuth2AuthorizeCodeSession)
provider.sqlRevokeOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSession)
@@ -123,33 +133,141 @@ func NewPostgreSQLProvider(config *schema.Configuration) (provider *PostgreSQLPr
return provider
}
-func dataSourceNamePostgreSQL(config schema.PostgreSQLStorageConfiguration) (dataSourceName string) {
- args := []string{
- fmt.Sprintf("host=%s", config.Host),
- fmt.Sprintf("user='%s'", config.Username),
- fmt.Sprintf("password='%s'", config.Password),
- fmt.Sprintf("dbname=%s", config.Database),
- fmt.Sprintf("search_path=%s", config.Schema),
- fmt.Sprintf("sslmode=%s", config.SSL.Mode),
+func dsnPostgreSQL(config *schema.PostgreSQLStorageConfiguration, globalCACertPool *x509.CertPool) (dsn string) {
+ dsnConfig, _ := pgx.ParseConfig("")
+
+ dsnConfig.Host = config.Host
+ dsnConfig.Port = uint16(config.Port)
+ dsnConfig.Database = config.Database
+ dsnConfig.User = config.Username
+ dsnConfig.Password = config.Password
+ dsnConfig.TLSConfig = loadPostgreSQLTLSConfig(config, globalCACertPool)
+ dsnConfig.ConnectTimeout = config.Timeout
+ dsnConfig.RuntimeParams = map[string]string{
+ "search_path": config.Schema,
}
- if config.Port > 0 {
- args = append(args, fmt.Sprintf("port=%d", config.Port))
+ if dsnConfig.Port == 0 && !path.IsAbs(dsnConfig.Host) {
+ dsnConfig.Port = 5432
}
+ return stdlib.RegisterConnConfig(dsnConfig)
+}
+
+func loadPostgreSQLTLSConfig(config *schema.PostgreSQLStorageConfiguration, globalCACertPool *x509.CertPool) (tlsConfig *tls.Config) {
+ if config.TLS == nil && config.SSL == nil {
+ return nil
+ }
+
+ if config.TLS != nil {
+ return utils.NewTLSConfig(config.TLS, globalCACertPool)
+ }
+
+ ca, certs := loadPostgreSQLLegacyTLSConfig(config)
+
+ switch config.SSL.Mode {
+ case "disable":
+ return nil
+ default:
+ var caCertPool *x509.CertPool
+
+ switch ca {
+ case nil:
+ caCertPool = globalCACertPool
+ default:
+ caCertPool = globalCACertPool.Clone()
+ caCertPool.AddCert(ca)
+ }
+
+ tlsConfig = &tls.Config{
+ Certificates: certs,
+ RootCAs: caCertPool,
+ InsecureSkipVerify: true, //nolint:gosec
+ }
+
+ switch {
+ case config.SSL.Mode == "require" && config.SSL.RootCertificate != "" || config.SSL.Mode == "verify-ca":
+ tlsConfig.VerifyPeerCertificate = newPostgreSQLVerifyCAFunc(tlsConfig)
+ case config.SSL.Mode == "verify-full":
+ tlsConfig.InsecureSkipVerify = false
+ tlsConfig.ServerName = config.Host
+ }
+ }
+
+ return tlsConfig
+}
+
+func loadPostgreSQLLegacyTLSConfig(config *schema.PostgreSQLStorageConfiguration) (ca *x509.Certificate, certs []tls.Certificate) {
+ var (
+ err error
+ )
+
if config.SSL.RootCertificate != "" {
- args = append(args, fmt.Sprintf("sslrootcert=%s", config.SSL.RootCertificate))
+ var caPEMBlock []byte
+
+ if caPEMBlock, err = os.ReadFile(config.SSL.RootCertificate); err != nil {
+ return nil, nil
+ }
+
+ if ca, err = x509.ParseCertificate(caPEMBlock); err != nil {
+ return nil, nil
+ }
}
- if config.SSL.Certificate != "" {
- args = append(args, fmt.Sprintf("sslcert=%s", config.SSL.Certificate))
+ if config.SSL.Certificate != "" && config.SSL.Key != "" {
+ var (
+ keyPEMBlock []byte
+ certPEMBlock []byte
+ )
+
+ if keyPEMBlock, err = os.ReadFile(config.SSL.Key); err != nil {
+ return nil, nil
+ }
+
+ if certPEMBlock, err = os.ReadFile(config.SSL.Certificate); err != nil {
+ return nil, nil
+ }
+
+ var cert tls.Certificate
+
+ if cert, err = tls.X509KeyPair(certPEMBlock, keyPEMBlock); err != nil {
+ return nil, nil
+ }
+
+ certs = []tls.Certificate{cert}
}
- if config.SSL.Key != "" {
- args = append(args, fmt.Sprintf("sslkey=%s", config.SSL.Key))
- }
-
- args = append(args, fmt.Sprintf("connect_timeout=%d", int32(config.Timeout/time.Second)))
-
- return strings.Join(args, " ")
+ return ca, certs
+}
+
+func newPostgreSQLVerifyCAFunc(config *tls.Config) func(certificates [][]byte, _ [][]*x509.Certificate) (err error) {
+ return func(certificates [][]byte, _ [][]*x509.Certificate) (err error) {
+ certs := make([]*x509.Certificate, len(certificates))
+
+ var cert *x509.Certificate
+
+ for i, asn1Data := range certificates {
+ if cert, err = x509.ParseCertificate(asn1Data); err != nil {
+ return errors.New("failed to parse certificate from server: " + err.Error())
+ }
+
+ certs[i] = cert
+ }
+
+ // Leave DNSName empty to skip hostname verification.
+ opts := x509.VerifyOptions{
+ Roots: config.RootCAs,
+ Intermediates: x509.NewCertPool(),
+ }
+
+ // Skip the first cert because it's the leaf. All others
+ // are intermediates.
+ for _, cert = range certs[1:] {
+ opts.Intermediates.AddCert(cert)
+ }
+
+ _, err = certs[0].Verify(opts)
+
+ return err
+ }
}
diff --git a/internal/storage/sql_provider_queries.go b/internal/storage/sql_provider_queries.go
index 7dc44e156..327ab546f 100644
--- a/internal/storage/sql_provider_queries.go
+++ b/internal/storage/sql_provider_queries.go
@@ -122,13 +122,13 @@ const (
const (
queryFmtSelectWebauthnDevices = `
- SELECT id, created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning
+ SELECT id, created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning
FROM %s
LIMIT ?
OFFSET ?;`
queryFmtSelectWebauthnDevicesByUsername = `
- SELECT id, created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning
+ SELECT id, created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning
FROM %s
WHERE username = ?;`
@@ -144,14 +144,14 @@ const (
queryFmtUpdateWebauthnDeviceRecordSignIn = `
UPDATE %s
- SET
+ SET
rpid = ?, last_used_at = ?, sign_count = ?,
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
WHERE id = ?;`
queryFmtUpdateWebauthnDeviceRecordSignInByUsername = `
UPDATE %s
- SET
+ SET
rpid = ?, last_used_at = ?, sign_count = ?,
clone_warning = CASE clone_warning WHEN TRUE THEN TRUE ELSE ? END
WHERE username = ? AND kid = ?;`
@@ -165,6 +165,18 @@ const (
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
ON CONFLICT (username, description)
DO UPDATE SET created_at = $1, last_used_at = $2, rpid = $3, kid = $6, public_key = $7, attestation_type = $8, transport = $9, aaguid = $10, sign_count = $11, clone_warning = $12;`
+
+ queryFmtDeleteWebauthnDevice = `
+ DELETE FROM %s
+ WHERE kid = ?;`
+
+ queryFmtDeleteWebauthnDeviceByUsername = `
+ DELETE FROM %s
+ WHERE username = ?;`
+
+ queryFmtDeleteWebauthnDeviceByUsernameAndDescription = `
+ DELETE FROM %s
+ WHERE username = ? AND description = ?;`
)
const (
@@ -222,22 +234,30 @@ const (
)
const (
+ queryFmtSelectOAuth2ConsentPreConfigurations = `
+ SELECT id, client_id, subject, created_at, expires_at, revoked, scopes, audience
+ FROM %s
+ WHERE client_id = ? AND subject = ? AND
+ revoked = FALSE AND (expires_at IS NULL OR expires_at >= CURRENT_TIMESTAMP);`
+
+ queryFmtInsertOAuth2ConsentPreConfiguration = `
+ INSERT INTO %s (client_id, subject, created_at, expires_at, revoked, scopes, audience)
+ VALUES(?, ?, ?, ?, ?, ?, ?);`
+
+ queryFmtInsertOAuth2ConsentPreConfigurationPostgreSQL = `
+ INSERT INTO %s (client_id, subject, created_at, expires_at, revoked, scopes, audience)
+ VALUES($1, $2, $3, $4, $5, $6, $7)
+ RETURNING id;`
+
queryFmtSelectOAuth2ConsentSessionByChallengeID = `
- SELECT id, challenge_id, client_id, subject, authorized, granted, requested_at, responded_at, expires_at,
- form_data, requested_scopes, granted_scopes, requested_audience, granted_audience
+ SELECT id, challenge_id, client_id, subject, authorized, granted, requested_at, responded_at,
+ form_data, requested_scopes, granted_scopes, requested_audience, granted_audience, preconfiguration
FROM %s
WHERE challenge_id = ?;`
- queryFmtSelectOAuth2ConsentSessionsPreConfigured = `
- SELECT id, challenge_id, client_id, subject, authorized, granted, requested_at, responded_at, expires_at,
- form_data, requested_scopes, granted_scopes, requested_audience, granted_audience
- FROM %s
- WHERE client_id = ? AND subject = ? AND
- authorized = TRUE AND granted = TRUE AND expires_at IS NOT NULL AND expires_at >= CURRENT_TIMESTAMP;`
-
queryFmtInsertOAuth2ConsentSession = `
- INSERT INTO %s (challenge_id, client_id, subject, authorized, granted, requested_at, responded_at, expires_at,
- form_data, requested_scopes, granted_scopes, requested_audience, granted_audience)
+ INSERT INTO %s (challenge_id, client_id, subject, authorized, granted, requested_at, responded_at,
+ form_data, requested_scopes, granted_scopes, requested_audience, granted_audience, preconfiguration)
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
queryFmtUpdateOAuth2ConsentSessionSubject = `
@@ -247,7 +267,7 @@ const (
queryFmtUpdateOAuth2ConsentSessionResponse = `
UPDATE %s
- SET authorized = ?, responded_at = CURRENT_TIMESTAMP, expires_at = ?, granted_scopes = ?, granted_audience = ?
+ SET authorized = ?, responded_at = CURRENT_TIMESTAMP, granted_scopes = ?, granted_audience = ?, preconfiguration = ?
WHERE id = ? AND responded_at IS NULL;`
queryFmtUpdateOAuth2ConsentSessionGranted = `
@@ -263,8 +283,8 @@ const (
WHERE signature = ? AND revoked = FALSE;`
queryFmtInsertOAuth2Session = `
- INSERT INTO %s (challenge_id, request_id, client_id, signature, subject, requested_at,
- requested_scopes, granted_scopes, requested_audience, granted_audience,
+ INSERT INTO %s (challenge_id, request_id, client_id, signature, subject, requested_at,
+ requested_scopes, granted_scopes, requested_audience, granted_audience,
active, revoked, form_data, session_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
diff --git a/internal/storage/sql_provider_schema_test.go b/internal/storage/sql_provider_schema_test.go
index 8769e52bf..16352e451 100644
--- a/internal/storage/sql_provider_schema_test.go
+++ b/internal/storage/sql_provider_schema_test.go
@@ -31,98 +31,98 @@ func TestShouldReturnErrOnTargetSameAsCurrent(t *testing.T) {
func TestShouldReturnErrOnUpMigrationTargetVersionLessTHanCurrent(t *testing.T) {
assert.EqualError(t,
- schemaMigrateChecks(providerPostgres, true, 0, testLatestVersion),
- fmt.Sprintf(ErrFmtMigrateUpTargetLessThanCurrent, 0, testLatestVersion))
+ schemaMigrateChecks(providerPostgres, true, 0, LatestVersion),
+ fmt.Sprintf(ErrFmtMigrateUpTargetLessThanCurrent, 0, LatestVersion))
assert.NoError(t,
- schemaMigrateChecks(providerPostgres, true, testLatestVersion, 0))
+ schemaMigrateChecks(providerPostgres, true, LatestVersion, 0))
assert.EqualError(t,
- schemaMigrateChecks(providerSQLite, true, 0, testLatestVersion),
- fmt.Sprintf(ErrFmtMigrateUpTargetLessThanCurrent, 0, testLatestVersion))
+ schemaMigrateChecks(providerSQLite, true, 0, LatestVersion),
+ fmt.Sprintf(ErrFmtMigrateUpTargetLessThanCurrent, 0, LatestVersion))
assert.NoError(t,
- schemaMigrateChecks(providerSQLite, true, testLatestVersion, 0))
+ schemaMigrateChecks(providerSQLite, true, LatestVersion, 0))
assert.EqualError(t,
- schemaMigrateChecks(providerMySQL, true, 0, testLatestVersion),
- fmt.Sprintf(ErrFmtMigrateUpTargetLessThanCurrent, 0, testLatestVersion))
+ schemaMigrateChecks(providerMySQL, true, 0, LatestVersion),
+ fmt.Sprintf(ErrFmtMigrateUpTargetLessThanCurrent, 0, LatestVersion))
assert.NoError(t,
- schemaMigrateChecks(providerMySQL, true, testLatestVersion, 0))
+ schemaMigrateChecks(providerMySQL, true, LatestVersion, 0))
}
func TestMigrationUpShouldReturnErrOnAlreadyLatest(t *testing.T) {
assert.Equal(t,
ErrSchemaAlreadyUpToDate,
- schemaMigrateChecks(providerPostgres, true, SchemaLatest, testLatestVersion))
+ schemaMigrateChecks(providerPostgres, true, SchemaLatest, LatestVersion))
assert.Equal(t,
ErrSchemaAlreadyUpToDate,
- schemaMigrateChecks(providerMySQL, true, SchemaLatest, testLatestVersion))
+ schemaMigrateChecks(providerMySQL, true, SchemaLatest, LatestVersion))
assert.Equal(t,
ErrSchemaAlreadyUpToDate,
- schemaMigrateChecks(providerSQLite, true, SchemaLatest, testLatestVersion))
+ schemaMigrateChecks(providerSQLite, true, SchemaLatest, LatestVersion))
}
func TestShouldReturnErrOnVersionDoesntExits(t *testing.T) {
assert.EqualError(t,
- schemaMigrateChecks(providerPostgres, true, SchemaLatest-1, testLatestVersion),
- fmt.Sprintf(ErrFmtMigrateUpTargetGreaterThanLatest, SchemaLatest-1, testLatestVersion))
+ schemaMigrateChecks(providerPostgres, true, SchemaLatest-1, LatestVersion),
+ fmt.Sprintf(ErrFmtMigrateUpTargetGreaterThanLatest, SchemaLatest-1, LatestVersion))
assert.EqualError(t,
- schemaMigrateChecks(providerMySQL, true, SchemaLatest-1, testLatestVersion),
- fmt.Sprintf(ErrFmtMigrateUpTargetGreaterThanLatest, SchemaLatest-1, testLatestVersion))
+ schemaMigrateChecks(providerMySQL, true, SchemaLatest-1, LatestVersion),
+ fmt.Sprintf(ErrFmtMigrateUpTargetGreaterThanLatest, SchemaLatest-1, LatestVersion))
assert.EqualError(t,
- schemaMigrateChecks(providerSQLite, true, SchemaLatest-1, testLatestVersion),
- fmt.Sprintf(ErrFmtMigrateUpTargetGreaterThanLatest, SchemaLatest-1, testLatestVersion))
+ schemaMigrateChecks(providerSQLite, true, SchemaLatest-1, LatestVersion),
+ fmt.Sprintf(ErrFmtMigrateUpTargetGreaterThanLatest, SchemaLatest-1, LatestVersion))
}
func TestMigrationDownShouldReturnErrOnTargetLessThanPre1(t *testing.T) {
assert.EqualError(t,
- schemaMigrateChecks(providerSQLite, false, -4, testLatestVersion),
+ schemaMigrateChecks(providerSQLite, false, -4, LatestVersion),
fmt.Sprintf(ErrFmtMigrateDownTargetLessThanMinimum, -4))
assert.EqualError(t,
- schemaMigrateChecks(providerMySQL, false, -2, testLatestVersion),
+ schemaMigrateChecks(providerMySQL, false, -2, LatestVersion),
fmt.Sprintf(ErrFmtMigrateDownTargetLessThanMinimum, -2))
assert.EqualError(t,
- schemaMigrateChecks(providerPostgres, false, -2, testLatestVersion),
+ schemaMigrateChecks(providerPostgres, false, -2, LatestVersion),
fmt.Sprintf(ErrFmtMigrateDownTargetLessThanMinimum, -2))
assert.NoError(t,
- schemaMigrateChecks(providerPostgres, false, -1, testLatestVersion))
+ schemaMigrateChecks(providerPostgres, false, -1, LatestVersion))
}
func TestMigrationDownShouldReturnErrOnTargetVersionGreaterThanCurrent(t *testing.T) {
assert.EqualError(t,
- schemaMigrateChecks(providerSQLite, false, testLatestVersion, 0),
- fmt.Sprintf(ErrFmtMigrateDownTargetGreaterThanCurrent, testLatestVersion, 0))
+ schemaMigrateChecks(providerSQLite, false, LatestVersion, 0),
+ fmt.Sprintf(ErrFmtMigrateDownTargetGreaterThanCurrent, LatestVersion, 0))
assert.EqualError(t,
- schemaMigrateChecks(providerMySQL, false, testLatestVersion, 0),
- fmt.Sprintf(ErrFmtMigrateDownTargetGreaterThanCurrent, testLatestVersion, 0))
+ schemaMigrateChecks(providerMySQL, false, LatestVersion, 0),
+ fmt.Sprintf(ErrFmtMigrateDownTargetGreaterThanCurrent, LatestVersion, 0))
assert.EqualError(t,
- schemaMigrateChecks(providerPostgres, false, testLatestVersion, 0),
- fmt.Sprintf(ErrFmtMigrateDownTargetGreaterThanCurrent, testLatestVersion, 0))
+ schemaMigrateChecks(providerPostgres, false, LatestVersion, 0),
+ fmt.Sprintf(ErrFmtMigrateDownTargetGreaterThanCurrent, LatestVersion, 0))
}
func TestShouldReturnErrWhenCurrentIsGreaterThanLatest(t *testing.T) {
assert.EqualError(t,
schemaMigrateChecks(providerPostgres, true, SchemaLatest-4, SchemaLatest-5),
- fmt.Sprintf(errFmtSchemaCurrentGreaterThanLatestKnown, testLatestVersion))
+ fmt.Sprintf(errFmtSchemaCurrentGreaterThanLatestKnown, LatestVersion))
assert.EqualError(t,
schemaMigrateChecks(providerMySQL, true, SchemaLatest-4, SchemaLatest-5),
- fmt.Sprintf(errFmtSchemaCurrentGreaterThanLatestKnown, testLatestVersion))
+ fmt.Sprintf(errFmtSchemaCurrentGreaterThanLatestKnown, LatestVersion))
assert.EqualError(t,
schemaMigrateChecks(providerSQLite, true, SchemaLatest-4, SchemaLatest-5),
- fmt.Sprintf(errFmtSchemaCurrentGreaterThanLatestKnown, testLatestVersion))
+ fmt.Sprintf(errFmtSchemaCurrentGreaterThanLatestKnown, LatestVersion))
}
func TestSchemaVersionToString(t *testing.T) {
diff --git a/internal/storage/sql_rows.go b/internal/storage/sql_rows.go
index 148dbf38d..f89c19121 100644
--- a/internal/storage/sql_rows.go
+++ b/internal/storage/sql_rows.go
@@ -8,13 +8,13 @@ import (
"github.com/authelia/authelia/v4/internal/model"
)
-// ConsentSessionRows holds and assists with retrieving multiple model.OAuth2ConsentSession rows.
-type ConsentSessionRows struct {
+// ConsentPreConfigRows holds and assists with retrieving multiple model.OAuth2ConsentSession rows.
+type ConsentPreConfigRows struct {
rows *sqlx.Rows
}
// Next is the row iterator.
-func (r *ConsentSessionRows) Next() bool {
+func (r *ConsentPreConfigRows) Next() bool {
if r.rows == nil {
return false
}
@@ -23,7 +23,7 @@ func (r *ConsentSessionRows) Next() bool {
}
// Close the rows.
-func (r *ConsentSessionRows) Close() (err error) {
+func (r *ConsentPreConfigRows) Close() (err error) {
if r.rows == nil {
return nil
}
@@ -32,16 +32,16 @@ func (r *ConsentSessionRows) Close() (err error) {
}
// Get returns the *model.OAuth2ConsentSession or scan error.
-func (r *ConsentSessionRows) Get() (consent *model.OAuth2ConsentSession, err error) {
+func (r *ConsentPreConfigRows) Get() (config *model.OAuth2ConsentPreConfig, err error) {
if r.rows == nil {
return nil, sql.ErrNoRows
}
- consent = &model.OAuth2ConsentSession{}
+ config = &model.OAuth2ConsentPreConfig{}
- if err = r.rows.StructScan(consent); err != nil {
+ if err = r.rows.StructScan(config); err != nil {
return nil, err
}
- return consent, nil
+ return config, nil
}
diff --git a/internal/suites/Envoy/configuration.yml b/internal/suites/Envoy/configuration.yml
new file mode 100644
index 000000000..7a5f55448
--- /dev/null
+++ b/internal/suites/Envoy/configuration.yml
@@ -0,0 +1,54 @@
+---
+###############################################################
+# Authelia minimal configuration #
+###############################################################
+
+jwt_secret: unsecure_secret
+
+server:
+ port: 9091
+ asset_path: /config/assets/
+ tls:
+ certificate: /config/ssl/cert.pem
+ key: /config/ssl/key.pem
+
+log:
+ level: debug
+
+authentication_backend:
+ file:
+ path: /config/users.yml
+
+session:
+ secret: unsecure_session_secret
+ domain: example.com
+ expiration: 3600 # 1 hour
+ inactivity: 300 # 5 minutes
+ remember_me_duration: 1y
+
+storage:
+ encryption_key: a_not_so_secure_encryption_key
+ local:
+ path: /config/db.sqlite
+
+access_control:
+ default_policy: bypass
+ rules:
+ - domain: "login.example.com"
+ policy: bypass
+ - domain: "public.example.com"
+ policy: bypass
+ - domain: "admin.example.com"
+ policy: two_factor
+ - domain: "secure.example.com"
+ policy: two_factor
+ - domain: "singlefactor.example.com"
+ policy: one_factor
+
+notifier:
+ smtp:
+ host: smtp
+ port: 1025
+ sender: admin@example.com
+ disable_require_tls: true
+...
diff --git a/internal/suites/Envoy/docker-compose.yml b/internal/suites/Envoy/docker-compose.yml
new file mode 100644
index 000000000..194d32ab4
--- /dev/null
+++ b/internal/suites/Envoy/docker-compose.yml
@@ -0,0 +1,9 @@
+---
+version: '3'
+services:
+ authelia-backend:
+ volumes:
+ - './Envoy/configuration.yml:/config/configuration.yml:ro'
+ - './Envoy/users.yml:/config/users.yml'
+ - './common/ssl:/config/ssl:ro'
+...
diff --git a/internal/suites/Envoy/users.yml b/internal/suites/Envoy/users.yml
new file mode 100644
index 000000000..a52978b20
--- /dev/null
+++ b/internal/suites/Envoy/users.yml
@@ -0,0 +1,35 @@
+---
+###############################################################
+# Users Database #
+###############################################################
+
+# This file can be used if you do not have an LDAP set up.
+
+# List of users
+users:
+ john:
+ displayname: "John Doe"
+ password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length
+ email: john.doe@authelia.com
+ groups:
+ - admins
+ - dev
+
+ harry:
+ displayname: "Harry Potter"
+ password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length
+ email: harry.potter@authelia.com
+ groups: []
+
+ bob:
+ displayname: "Bob Dylan"
+ password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length
+ email: bob.dylan@authelia.com
+ groups:
+ - dev
+
+ james:
+ displayname: "James Dean"
+ password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length
+ email: james.dean@authelia.com
+...
diff --git a/internal/suites/OIDC/configuration.yml b/internal/suites/OIDC/configuration.yml
index b4d137b17..9e0c5f383 100644
--- a/internal/suites/OIDC/configuration.yml
+++ b/internal/suites/OIDC/configuration.yml
@@ -60,33 +60,71 @@ identity_providers:
oidc:
enable_client_debug_messages: true
hmac_secret: IVPWBkAdJHje3uz7LtFTDU2pFUfh39Xm
+ issuer_certificate_chain: |
+ -----BEGIN CERTIFICATE-----
+ MIIC6DCCAdCgAwIBAgIRAIxvm0gFgsbh3D22rSZLuFQwDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwIBcNMjIxMDAyMDAzMDQyWhgPMjEyMjA5MDgw
+ MDMwNDJaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOC
+ AQ8AMIIBCgKCAQEAy71EOkV3jOpVQtVTH5HYcI4PryUCiAEyxAIuO+66gaAa4aCd
+ UCRr8iO/pt5nOwPxjPo+hMHhkcKpX7evj+wgYXAccpIQFSCYWTJkaXFL0jL7yFuE
+ 5xpjgRM/x6FfK0IbN5WmVWO9EjesbyMCyDoYpjwzIrxnB70F9Y0nrXst1SnW/Sy0
+ 01BQZNzD1tky1KDvEkw7L5mMPZFZMr5wV+ELvbo1LLvvrGYhhzbXWk7pPbxT0gAa
+ 7yVvQbDKuCDqssAUyQa2JdlDaQocpldtK6l+dc3IsSWKd2UMouta75ngr9E1igy3
+ t7owMRqH8NjwKHt6KQeDVSdBnWNjG572vaRimQIDAQABozUwMzAOBgNVHQ8BAf8E
+ BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG
+ 9w0BAQsFAAOCAQEAaZJ09yGa+DGr/iTqshGdPKNCtcf/CXCkL52xiI7DzLxDt30P
+ 8vCuXXrrTGBY7eWYupcNy/MyqaUrz1ED+map3nQzZQBJ9vWIfr01B9phkg/WSaNJ
+ 1DlYtbPYzr86BlGP1V5d3Wv6JqF3tkWHI0kI38CT68fWdDKrfa5j3JdZGIVJW+51
+ U0IE3Nqhfc76YzwQ3sNX5FT2Fr55RowH+l5OBPk0Bcztq58XmyPR/bvPfDASt8iS
+ DBT+0iiDiwk6LvOkasL8p7nuh5Grc9LMEYXY/QMUbkIWhIVRFlqyJA9s8vGHx1D4
+ 96iYKudj+yvO17Szzr/NNmcwETbCs4j6P6QeiA==
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ MIIDBTCCAe2gAwIBAgIQAK/NIAl3Bdg4Xk0y/ZGL7jANBgkqhkiG9w0BAQsFADAT
+ MREwDwYDVQQKEwhBdXRoZWxpYTAgFw0yMjEwMDIwMDMwMjFaGA8yMTIyMDkwODAw
+ MzAyMVowEzERMA8GA1UEChMIQXV0aGVsaWEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+ DwAwggEKAoIBAQCg7jdO1HmydfkPzTtz57pvAS3YOdBT0hlNjJ4N2lrKhNnixrzK
+ +4R1dWQDP2SHbZQ0TskF8eQ8HhTr7AsApotTthJFkUgV2g+bv7wVroz0Hok5xtd4
+ bnpOvG3YUCP13Nk3ZVxdQXqR3/G3MrbyiXVPcgU+0giJ8EBykbtMu8L79/1iyk+m
+ w4fZfzTOeorRgspO3z3+pTAib2MCTA7bby1dX9qI/ysFPLdbJYfNQDxij8SzNLyJ
+ EkQ4kh3jKXf1VcZjbQTtYTZ3JJDqM08OxGMKuXUxPHd72Xlb+Fzql8LjYdEy/YKA
+ 3r8FMf14lzcjvxtLnFXh//hiXh4+xgXMkrLZAgMBAAGjUzBRMA4GA1UdDwEB/wQE
+ AwICpDAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+ FGKpXiZA+8VQyMBqTTep+dVTthSbMA0GCSqGSIb3DQEBCwUAA4IBAQAE4DJg+Rb4
+ iiocvxxQ85lhh94ql++E8MKuzIdN7ORs+ybUnsDD1WFDebubroTQuTSBkFrNuGNJ
+ 8B7NZsHiWWLvNsrnxxeC5CicqfhSDti0rKWsbGyeoq7Kqok5E4pwOzeRsxL2e/Hm
+ G6LsUQuQMUG2vxKNynqmJS4VpgSVkiGhUfURFuRRDuRpVQ/XTl7jDIGf/ls7TAZq
+ 1AnmnSi4Cqy4hrTnwYUYkFCcH69onUKAoaVNl1eAH7ogxakz32WyWObY98NBrjzA
+ I6VQlaQNSHtdFqDpu7NWJZZZSgN4BknbMYQEPNYCm701cPB4ahJbpg5C3pVPFSql
+ Bc9iI6nN3PCr
+ -----END CERTIFICATE-----
issuer_private_key: |
-----BEGIN RSA PRIVATE KEY-----
- MIIEogIBAAKCAQEAvOFmoEJFt1JkfdlwM3vJFg5rrY9d6LyyqezjZkBZDQ4qdEEU
- dCrbW8ISFTtg9sfbrS3qingUzVP9VOfYPMC3r0ugjJXjhvJdBSaoLlzL3saeyrXk
- frOOvkcWKzeOynqUNPhKy9dchmuLALFfd/Jy7Wzq0y7XxGeNidEmFjMAf9dwf6/+
- PjQjbG7zBFu/XSajITPHlDXPVDd0j2qw2wu5Z9iqn4LRXnAFnC438hZZKZU/+JxU
- 2ezr6Sefiy8XTC2kDiq3cgLeEjSywlJOs+4TLjVS/3h75sh2Wk0xVaSwjPEjCOgm
- a+2E3GJrGdQBiAjMSu101VBVwHUHaLDCn1T4NwIDAQABAoIBADWkupXnXI99Ogc4
- GxK0JF88Rz6qyhwQg5mZKthejCwWCt6roRiBF33O933KOHa+OljMAqHDCv1pzjgw
- BIz0mvaRPw7OfylTajHNUdShDFHADVc7I6MMcgz+eYBarhY5jCAjKHMOPjv7DSZs
- OdYCKLvfxC2oTyV714n9uZhyccDcvQpkgZuBDL0oxPom1GOI8TGhPjxvFOovEHWA
- Q8q9XY4cUVNDikZmvpgeUkJHWYHYb+11vKeSupnYD03yJ3sDy+F6+m+3/XmzFbXb
- 1p43ermHQsMfDlxPyulUUI0viSo2UhlMC/moAb9FusOv+dTl2lt0gGqzDJ9gg1z1
- XpHRnwkCgYEA5x48dyxd4lydtVYef9sBmbLJEYozsYyOwLcnrLSNaZxeCza1exyR
- QIRogswoLDacxrYvO8FY6LtAEMkisv732M29zthBPm5wyoSZiM1X2YfQXKsmyh2h
- x1/yCWv/BQjj68A8IAxToaXxSG4WAr/X00RGUkXgkgw122FxcmGuFyUCgYEA0TcR
- dnt/oRMK4aCZHcBgTknzDfxKlJh4S0C9WjxKgr8IlW4LTeVSBuuqOObOQYImEhtw
- TRTKZIViL0roDF79cioQSp1Tk5h6uy8wr6VyhWRnWfTz2/azoTHnmQ780rtAuEI/
- NvE6FiqwikJLjma1YJoRfr/bfmgMdxcYbJI1MSsCgYAEZ5Yda1IKu1siFpcUNrdM
- F5UvaWPc0WHzGEqARxye06UTL6K7yuqVwTBAteVaGlxYiSZTTDcGkHMDHuIzaRqO
- HjWs2IA90VsC8Q4ABnHTKnx1F6nwlin8I774IP/GN8ooNwyuS63YWdJEYBy5RrC1
- TQrODJjgD62DFdNUq7nmpQKBgFMJEzI+Q+KPJ0NztTG8t7x61y/W0Vb2yM+9Syn0
- QfJwlZyRR4VMHelHQZFB8dzIJgoLv9+n/8gztEtm5IB8dwUHst2aYaBz5UpDqYQd
- Gz3cIrTuZpcH7DVvFCeIbknJLh+zk1lgFpjTqqvFMi27kANeQtFWnmwmKcRec0As
- K1ZvAoGAV/3YB44/zIoB590+yhpx2HTmDPVHH+J+5O71Pi1D9W13ClBFLrE69wo+
- IQLIstBI5tGOGeuQNjXhDKJ1U30xppZXcnebrkA+oOo+6dy20zghFR2maAGXfWFU
- pM4GsSnSTm0bXPebVouQFqhj7LqcQQzCqRDThmw/Lp1tJUmu40g=
+ MIIEowIBAAKCAQEAy71EOkV3jOpVQtVTH5HYcI4PryUCiAEyxAIuO+66gaAa4aCd
+ UCRr8iO/pt5nOwPxjPo+hMHhkcKpX7evj+wgYXAccpIQFSCYWTJkaXFL0jL7yFuE
+ 5xpjgRM/x6FfK0IbN5WmVWO9EjesbyMCyDoYpjwzIrxnB70F9Y0nrXst1SnW/Sy0
+ 01BQZNzD1tky1KDvEkw7L5mMPZFZMr5wV+ELvbo1LLvvrGYhhzbXWk7pPbxT0gAa
+ 7yVvQbDKuCDqssAUyQa2JdlDaQocpldtK6l+dc3IsSWKd2UMouta75ngr9E1igy3
+ t7owMRqH8NjwKHt6KQeDVSdBnWNjG572vaRimQIDAQABAoIBAA/EhhM8bRQqzo5t
+ lBFNaELNu8kCRD/iV9tzj8BzqVt+2JW9qG8bYn9K5Po1HCglFfyjIVOE7cAqIJGX
+ 1a59x8PCuXDkfPolm6TLkZnXeta5u2K2MoLwN+M1aio5AvSGGTUkD8tr/KX8SQwQ
+ 2ZZFaML0xcBadF7U8jEey4NRlSp5/voiIAB+FrJHepZBz2XJYCX5s2vYLPMn+51R
+ 1HyO0n2aQ9H1Na8aBjTfAp9GDKJWBV3bSM7cVaLGlMFj/HNXUNVnSsVsJj0tdWKz
+ K6r9zPskLnS+eNjCgqrOtZSqJ7M3PL0/PoTFPrr1Fevr+soKWCaPF94Ib94O9SEq
+ scvP3kECgYEA0HBdGab0HjcZgFtsIaMm+eBcDhUmUrvMPUw6FmspKnc8wplscONW
+ wrDGhR0dpT8+aAMD5jFC2pvyHjI5AWkW+53LB15j6SVzUlUMfS3VTwE2prLtDHDs
+ nCDW2+fXY2kjv45efZGpMGbLJVePx2RCPzUlAlc14lzxnHgpo7eho1cCgYEA+jpi
+ Eo/Jqa5CNd4hrXqFxZTFtU2Mn38ZKI3QK/l47/347yHLebjsYIIwJRoHenxWxNlz
+ Y+BZ38vkP+f9BGAVGiRcyMmIJU0X305wKwl26Y2Q/tEm2OpwmDboD2pL9byi9vfY
+ bz7pQGK/l9j86KofRwVJJRLsofPI1SsjnC8c448CgYAkpg0IjJ1RjriSJADwLSKW
+ PseQxlE1rMVtZbC07mSPjeWGBbnWY3KGytQs5YCn5GXRne4alEC/9Tlt68CwKc0b
+ spPXGNaSUL5lFIUcoWlm+bylNMKPNG+1x+RfR/VMCll5vcuJYooP85L2Xt3t3gfz
+ 2yFFtxXHVjY5H7uaiJgIAwKBgQDvkGXEj5TqtsL8/6YOiHb6Kuz+Hzi6mtxjTyI2
+ d6mpWuWxTBGaf8kOvJWLb9gpFFGeNPGcdXaWJIZqCJjcT4Dkflu2f/uwepaYXGhX
+ S8Bk6fwfee5PTmRt1mNmHsaKhgcfmznDh9+YnPIBVuULe5RmUlEtBWk3xEZKj/qP
+ 1Ss7UQKBgAwZQz+h5Z/XOJH3Qs5nJBKAZUiYkj3ux7G6tjx0cz7XcUYd/6enBpkY
+ JeqVHB6G+bMRLwb+Hc5Vgpbd5GdaUWo8udaghHgSGPUVcn0lK38XhYek6ACGz7Lo
+ xEfgtKoBlUq+uPb8H05HY0t9KybA3LA5wkRYYnJ17/nkZtrrJAmX
-----END RSA PRIVATE KEY-----
clients:
- id: oidc-tester-app
diff --git a/internal/suites/OIDCTraefik/configuration.yml b/internal/suites/OIDCTraefik/configuration.yml
index b26f0c36d..1766da293 100644
--- a/internal/suites/OIDCTraefik/configuration.yml
+++ b/internal/suites/OIDCTraefik/configuration.yml
@@ -62,33 +62,71 @@ identity_providers:
oidc:
enable_client_debug_messages: true
hmac_secret: IVPWBkAdJHje3uz7LtFTDU2pFUfh39Xm
+ issuer_certificate_chain: |
+ -----BEGIN CERTIFICATE-----
+ MIIC6DCCAdCgAwIBAgIRAIxvm0gFgsbh3D22rSZLuFQwDQYJKoZIhvcNAQELBQAw
+ EzERMA8GA1UEChMIQXV0aGVsaWEwIBcNMjIxMDAyMDAzMDQyWhgPMjEyMjA5MDgw
+ MDMwNDJaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOC
+ AQ8AMIIBCgKCAQEAy71EOkV3jOpVQtVTH5HYcI4PryUCiAEyxAIuO+66gaAa4aCd
+ UCRr8iO/pt5nOwPxjPo+hMHhkcKpX7evj+wgYXAccpIQFSCYWTJkaXFL0jL7yFuE
+ 5xpjgRM/x6FfK0IbN5WmVWO9EjesbyMCyDoYpjwzIrxnB70F9Y0nrXst1SnW/Sy0
+ 01BQZNzD1tky1KDvEkw7L5mMPZFZMr5wV+ELvbo1LLvvrGYhhzbXWk7pPbxT0gAa
+ 7yVvQbDKuCDqssAUyQa2JdlDaQocpldtK6l+dc3IsSWKd2UMouta75ngr9E1igy3
+ t7owMRqH8NjwKHt6KQeDVSdBnWNjG572vaRimQIDAQABozUwMzAOBgNVHQ8BAf8E
+ BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG
+ 9w0BAQsFAAOCAQEAaZJ09yGa+DGr/iTqshGdPKNCtcf/CXCkL52xiI7DzLxDt30P
+ 8vCuXXrrTGBY7eWYupcNy/MyqaUrz1ED+map3nQzZQBJ9vWIfr01B9phkg/WSaNJ
+ 1DlYtbPYzr86BlGP1V5d3Wv6JqF3tkWHI0kI38CT68fWdDKrfa5j3JdZGIVJW+51
+ U0IE3Nqhfc76YzwQ3sNX5FT2Fr55RowH+l5OBPk0Bcztq58XmyPR/bvPfDASt8iS
+ DBT+0iiDiwk6LvOkasL8p7nuh5Grc9LMEYXY/QMUbkIWhIVRFlqyJA9s8vGHx1D4
+ 96iYKudj+yvO17Szzr/NNmcwETbCs4j6P6QeiA==
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ MIIDBTCCAe2gAwIBAgIQAK/NIAl3Bdg4Xk0y/ZGL7jANBgkqhkiG9w0BAQsFADAT
+ MREwDwYDVQQKEwhBdXRoZWxpYTAgFw0yMjEwMDIwMDMwMjFaGA8yMTIyMDkwODAw
+ MzAyMVowEzERMA8GA1UEChMIQXV0aGVsaWEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+ DwAwggEKAoIBAQCg7jdO1HmydfkPzTtz57pvAS3YOdBT0hlNjJ4N2lrKhNnixrzK
+ +4R1dWQDP2SHbZQ0TskF8eQ8HhTr7AsApotTthJFkUgV2g+bv7wVroz0Hok5xtd4
+ bnpOvG3YUCP13Nk3ZVxdQXqR3/G3MrbyiXVPcgU+0giJ8EBykbtMu8L79/1iyk+m
+ w4fZfzTOeorRgspO3z3+pTAib2MCTA7bby1dX9qI/ysFPLdbJYfNQDxij8SzNLyJ
+ EkQ4kh3jKXf1VcZjbQTtYTZ3JJDqM08OxGMKuXUxPHd72Xlb+Fzql8LjYdEy/YKA
+ 3r8FMf14lzcjvxtLnFXh//hiXh4+xgXMkrLZAgMBAAGjUzBRMA4GA1UdDwEB/wQE
+ AwICpDAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+ FGKpXiZA+8VQyMBqTTep+dVTthSbMA0GCSqGSIb3DQEBCwUAA4IBAQAE4DJg+Rb4
+ iiocvxxQ85lhh94ql++E8MKuzIdN7ORs+ybUnsDD1WFDebubroTQuTSBkFrNuGNJ
+ 8B7NZsHiWWLvNsrnxxeC5CicqfhSDti0rKWsbGyeoq7Kqok5E4pwOzeRsxL2e/Hm
+ G6LsUQuQMUG2vxKNynqmJS4VpgSVkiGhUfURFuRRDuRpVQ/XTl7jDIGf/ls7TAZq
+ 1AnmnSi4Cqy4hrTnwYUYkFCcH69onUKAoaVNl1eAH7ogxakz32WyWObY98NBrjzA
+ I6VQlaQNSHtdFqDpu7NWJZZZSgN4BknbMYQEPNYCm701cPB4ahJbpg5C3pVPFSql
+ Bc9iI6nN3PCr
+ -----END CERTIFICATE-----
issuer_private_key: |
-----BEGIN RSA PRIVATE KEY-----
- MIIEogIBAAKCAQEAvOFmoEJFt1JkfdlwM3vJFg5rrY9d6LyyqezjZkBZDQ4qdEEU
- dCrbW8ISFTtg9sfbrS3qingUzVP9VOfYPMC3r0ugjJXjhvJdBSaoLlzL3saeyrXk
- frOOvkcWKzeOynqUNPhKy9dchmuLALFfd/Jy7Wzq0y7XxGeNidEmFjMAf9dwf6/+
- PjQjbG7zBFu/XSajITPHlDXPVDd0j2qw2wu5Z9iqn4LRXnAFnC438hZZKZU/+JxU
- 2ezr6Sefiy8XTC2kDiq3cgLeEjSywlJOs+4TLjVS/3h75sh2Wk0xVaSwjPEjCOgm
- a+2E3GJrGdQBiAjMSu101VBVwHUHaLDCn1T4NwIDAQABAoIBADWkupXnXI99Ogc4
- GxK0JF88Rz6qyhwQg5mZKthejCwWCt6roRiBF33O933KOHa+OljMAqHDCv1pzjgw
- BIz0mvaRPw7OfylTajHNUdShDFHADVc7I6MMcgz+eYBarhY5jCAjKHMOPjv7DSZs
- OdYCKLvfxC2oTyV714n9uZhyccDcvQpkgZuBDL0oxPom1GOI8TGhPjxvFOovEHWA
- Q8q9XY4cUVNDikZmvpgeUkJHWYHYb+11vKeSupnYD03yJ3sDy+F6+m+3/XmzFbXb
- 1p43ermHQsMfDlxPyulUUI0viSo2UhlMC/moAb9FusOv+dTl2lt0gGqzDJ9gg1z1
- XpHRnwkCgYEA5x48dyxd4lydtVYef9sBmbLJEYozsYyOwLcnrLSNaZxeCza1exyR
- QIRogswoLDacxrYvO8FY6LtAEMkisv732M29zthBPm5wyoSZiM1X2YfQXKsmyh2h
- x1/yCWv/BQjj68A8IAxToaXxSG4WAr/X00RGUkXgkgw122FxcmGuFyUCgYEA0TcR
- dnt/oRMK4aCZHcBgTknzDfxKlJh4S0C9WjxKgr8IlW4LTeVSBuuqOObOQYImEhtw
- TRTKZIViL0roDF79cioQSp1Tk5h6uy8wr6VyhWRnWfTz2/azoTHnmQ780rtAuEI/
- NvE6FiqwikJLjma1YJoRfr/bfmgMdxcYbJI1MSsCgYAEZ5Yda1IKu1siFpcUNrdM
- F5UvaWPc0WHzGEqARxye06UTL6K7yuqVwTBAteVaGlxYiSZTTDcGkHMDHuIzaRqO
- HjWs2IA90VsC8Q4ABnHTKnx1F6nwlin8I774IP/GN8ooNwyuS63YWdJEYBy5RrC1
- TQrODJjgD62DFdNUq7nmpQKBgFMJEzI+Q+KPJ0NztTG8t7x61y/W0Vb2yM+9Syn0
- QfJwlZyRR4VMHelHQZFB8dzIJgoLv9+n/8gztEtm5IB8dwUHst2aYaBz5UpDqYQd
- Gz3cIrTuZpcH7DVvFCeIbknJLh+zk1lgFpjTqqvFMi27kANeQtFWnmwmKcRec0As
- K1ZvAoGAV/3YB44/zIoB590+yhpx2HTmDPVHH+J+5O71Pi1D9W13ClBFLrE69wo+
- IQLIstBI5tGOGeuQNjXhDKJ1U30xppZXcnebrkA+oOo+6dy20zghFR2maAGXfWFU
- pM4GsSnSTm0bXPebVouQFqhj7LqcQQzCqRDThmw/Lp1tJUmu40g=
+ MIIEowIBAAKCAQEAy71EOkV3jOpVQtVTH5HYcI4PryUCiAEyxAIuO+66gaAa4aCd
+ UCRr8iO/pt5nOwPxjPo+hMHhkcKpX7evj+wgYXAccpIQFSCYWTJkaXFL0jL7yFuE
+ 5xpjgRM/x6FfK0IbN5WmVWO9EjesbyMCyDoYpjwzIrxnB70F9Y0nrXst1SnW/Sy0
+ 01BQZNzD1tky1KDvEkw7L5mMPZFZMr5wV+ELvbo1LLvvrGYhhzbXWk7pPbxT0gAa
+ 7yVvQbDKuCDqssAUyQa2JdlDaQocpldtK6l+dc3IsSWKd2UMouta75ngr9E1igy3
+ t7owMRqH8NjwKHt6KQeDVSdBnWNjG572vaRimQIDAQABAoIBAA/EhhM8bRQqzo5t
+ lBFNaELNu8kCRD/iV9tzj8BzqVt+2JW9qG8bYn9K5Po1HCglFfyjIVOE7cAqIJGX
+ 1a59x8PCuXDkfPolm6TLkZnXeta5u2K2MoLwN+M1aio5AvSGGTUkD8tr/KX8SQwQ
+ 2ZZFaML0xcBadF7U8jEey4NRlSp5/voiIAB+FrJHepZBz2XJYCX5s2vYLPMn+51R
+ 1HyO0n2aQ9H1Na8aBjTfAp9GDKJWBV3bSM7cVaLGlMFj/HNXUNVnSsVsJj0tdWKz
+ K6r9zPskLnS+eNjCgqrOtZSqJ7M3PL0/PoTFPrr1Fevr+soKWCaPF94Ib94O9SEq
+ scvP3kECgYEA0HBdGab0HjcZgFtsIaMm+eBcDhUmUrvMPUw6FmspKnc8wplscONW
+ wrDGhR0dpT8+aAMD5jFC2pvyHjI5AWkW+53LB15j6SVzUlUMfS3VTwE2prLtDHDs
+ nCDW2+fXY2kjv45efZGpMGbLJVePx2RCPzUlAlc14lzxnHgpo7eho1cCgYEA+jpi
+ Eo/Jqa5CNd4hrXqFxZTFtU2Mn38ZKI3QK/l47/347yHLebjsYIIwJRoHenxWxNlz
+ Y+BZ38vkP+f9BGAVGiRcyMmIJU0X305wKwl26Y2Q/tEm2OpwmDboD2pL9byi9vfY
+ bz7pQGK/l9j86KofRwVJJRLsofPI1SsjnC8c448CgYAkpg0IjJ1RjriSJADwLSKW
+ PseQxlE1rMVtZbC07mSPjeWGBbnWY3KGytQs5YCn5GXRne4alEC/9Tlt68CwKc0b
+ spPXGNaSUL5lFIUcoWlm+bylNMKPNG+1x+RfR/VMCll5vcuJYooP85L2Xt3t3gfz
+ 2yFFtxXHVjY5H7uaiJgIAwKBgQDvkGXEj5TqtsL8/6YOiHb6Kuz+Hzi6mtxjTyI2
+ d6mpWuWxTBGaf8kOvJWLb9gpFFGeNPGcdXaWJIZqCJjcT4Dkflu2f/uwepaYXGhX
+ S8Bk6fwfee5PTmRt1mNmHsaKhgcfmznDh9+YnPIBVuULe5RmUlEtBWk3xEZKj/qP
+ 1Ss7UQKBgAwZQz+h5Z/XOJH3Qs5nJBKAZUiYkj3ux7G6tjx0cz7XcUYd/6enBpkY
+ JeqVHB6G+bMRLwb+Hc5Vgpbd5GdaUWo8udaghHgSGPUVcn0lK38XhYek6ACGz7Lo
+ xEfgtKoBlUq+uPb8H05HY0t9KybA3LA5wkRYYnJ17/nkZtrrJAmX
-----END RSA PRIVATE KEY-----
clients:
- id: oidc-tester-app
diff --git a/internal/suites/action_2fa_methods.go b/internal/suites/action_2fa_methods.go
index b012c1107..5e8990a95 100644
--- a/internal/suites/action_2fa_methods.go
+++ b/internal/suites/action_2fa_methods.go
@@ -9,26 +9,26 @@ import (
)
func (rs *RodSession) doChangeMethod(t *testing.T, page *rod.Page, method string) {
- err := rs.WaitElementLocatedByID(t, page, "methods-button").Click("left")
+ err := rs.WaitElementLocatedByID(t, page, "methods-button").Click("left", 1)
require.NoError(t, err)
rs.WaitElementLocatedByID(t, page, "methods-dialog")
- err = rs.WaitElementLocatedByID(t, page, fmt.Sprintf("%s-option", method)).Click("left")
+ err = rs.WaitElementLocatedByID(t, page, fmt.Sprintf("%s-option", method)).Click("left", 1)
require.NoError(t, err)
}
func (rs *RodSession) doChangeDevice(t *testing.T, page *rod.Page, deviceID string) {
- err := rs.WaitElementLocatedByID(t, page, "selection-link").Click("left")
+ err := rs.WaitElementLocatedByID(t, page, "selection-link").Click("left", 1)
require.NoError(t, err)
rs.doSelectDevice(t, page, deviceID)
}
func (rs *RodSession) doSelectDevice(t *testing.T, page *rod.Page, deviceID string) {
rs.WaitElementLocatedByID(t, page, "device-selection")
- err := rs.WaitElementLocatedByID(t, page, fmt.Sprintf("device-%s", deviceID)).Click("left")
+ err := rs.WaitElementLocatedByID(t, page, fmt.Sprintf("device-%s", deviceID)).Click("left", 1)
require.NoError(t, err)
}
func (rs *RodSession) doClickButton(t *testing.T, page *rod.Page, buttonID string) {
- err := rs.WaitElementLocatedByID(t, page, buttonID).Click("left")
+ err := rs.WaitElementLocatedByID(t, page, buttonID).Click("left", 1)
require.NoError(t, err)
}
diff --git a/internal/suites/action_login.go b/internal/suites/action_login.go
index 73e7b8073..cb327b2d4 100644
--- a/internal/suites/action_login.go
+++ b/internal/suites/action_login.go
@@ -19,12 +19,12 @@ func (rs *RodSession) doFillLoginPageAndClick(t *testing.T, page *rod.Page, user
if keepMeLoggedIn {
keepMeLoggedInElement := rs.WaitElementLocatedByID(t, page, "remember-checkbox")
- err = keepMeLoggedInElement.Click("left")
+ err = keepMeLoggedInElement.Click("left", 1)
require.NoError(t, err)
}
buttonElement := rs.WaitElementLocatedByID(t, page, "sign-in-button")
- err = buttonElement.Click("left")
+ err = buttonElement.Click("left", 1)
require.NoError(t, err)
}
diff --git a/internal/suites/action_reset_password.go b/internal/suites/action_reset_password.go
index 5a3ca5d5b..112771ed4 100644
--- a/internal/suites/action_reset_password.go
+++ b/internal/suites/action_reset_password.go
@@ -9,13 +9,13 @@ import (
)
func (rs *RodSession) doInitiatePasswordReset(t *testing.T, page *rod.Page, username string) {
- err := rs.WaitElementLocatedByID(t, page, "reset-password-button").Click("left")
+ err := rs.WaitElementLocatedByID(t, page, "reset-password-button").Click("left", 1)
require.NoError(t, err)
// Fill in username.
err = rs.WaitElementLocatedByID(t, page, "username-textfield").Input(username)
require.NoError(t, err)
// And click on the reset button.
- err = rs.WaitElementLocatedByID(t, page, "reset-button").Click("left")
+ err = rs.WaitElementLocatedByID(t, page, "reset-button").Click("left", 1)
require.NoError(t, err)
}
@@ -33,7 +33,7 @@ func (rs *RodSession) doCompletePasswordReset(t *testing.T, page *rod.Page, newP
err = rs.WaitElementLocatedByID(t, page, "password2-textfield").Input(newPassword2)
require.NoError(t, err)
- err = rs.WaitElementLocatedByID(t, page, "reset-button").Click("left")
+ err = rs.WaitElementLocatedByID(t, page, "reset-button").Click("left", 1)
require.NoError(t, err)
}
diff --git a/internal/suites/action_totp.go b/internal/suites/action_totp.go
index c4cf63a31..78636eafb 100644
--- a/internal/suites/action_totp.go
+++ b/internal/suites/action_totp.go
@@ -12,7 +12,7 @@ import (
)
func (rs *RodSession) doRegisterTOTP(t *testing.T, page *rod.Page) string {
- err := rs.WaitElementLocatedByID(t, page, "register-link").Click("left")
+ err := rs.WaitElementLocatedByID(t, page, "register-link").Click("left", 1)
require.NoError(t, err)
rs.verifyMailNotificationDisplayed(t, page)
link := doGetLinkFromLastMail(t)
diff --git a/internal/suites/example/compose/authelia/Dockerfile.backend b/internal/suites/example/compose/authelia/Dockerfile.backend
index 2df4f2bcd..e221a9f52 100644
--- a/internal/suites/example/compose/authelia/Dockerfile.backend
+++ b/internal/suites/example/compose/authelia/Dockerfile.backend
@@ -1,4 +1,4 @@
-FROM golang:1.19.1-alpine
+FROM golang:1.19.3-alpine
ARG USER_ID
ARG GROUP_ID
diff --git a/internal/suites/example/compose/authelia/Dockerfile.frontend b/internal/suites/example/compose/authelia/Dockerfile.frontend
index 45cc41c5e..ef85b9b38 100644
--- a/internal/suites/example/compose/authelia/Dockerfile.frontend
+++ b/internal/suites/example/compose/authelia/Dockerfile.frontend
@@ -1,4 +1,4 @@
-FROM node:18-alpine
+FROM node:19-alpine
ARG USER_ID
ARG GROUP_ID
diff --git a/internal/suites/example/compose/caddy/Dockerfile b/internal/suites/example/compose/caddy/Dockerfile
index 2a3115835..3b5cb28e4 100644
--- a/internal/suites/example/compose/caddy/Dockerfile
+++ b/internal/suites/example/compose/caddy/Dockerfile
@@ -1,7 +1,7 @@
-FROM caddy:2.5.2-builder AS builder
+FROM caddy:2.6.2-builder AS builder
RUN xcaddy build fix-empty-copy-headers
-FROM caddy:2.5.2
+FROM caddy:2.6.2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
diff --git a/internal/suites/example/compose/caddy/docker-compose.yml b/internal/suites/example/compose/caddy/docker-compose.yml
index 515d70a42..fefa0b7f3 100644
--- a/internal/suites/example/compose/caddy/docker-compose.yml
+++ b/internal/suites/example/compose/caddy/docker-compose.yml
@@ -3,7 +3,7 @@ version: '3'
services:
caddy:
# build: ./example/compose/caddy/ # used for debugging
- image: caddy:2.5.2-alpine
+ image: caddy:2.6.2-alpine
volumes:
- ./example/compose/caddy/Caddyfile:/etc/caddy/Caddyfile
networks:
diff --git a/internal/suites/example/compose/duo-api/Dockerfile b/internal/suites/example/compose/duo-api/Dockerfile
index 8f6237e49..c46a28a78 100644
--- a/internal/suites/example/compose/duo-api/Dockerfile
+++ b/internal/suites/example/compose/duo-api/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:18-alpine
+FROM node:19-alpine
WORKDIR /usr/app/src
diff --git a/internal/suites/example/compose/envoy/docker-compose.yml b/internal/suites/example/compose/envoy/docker-compose.yml
new file mode 100644
index 000000000..61d80a37c
--- /dev/null
+++ b/internal/suites/example/compose/envoy/docker-compose.yml
@@ -0,0 +1,12 @@
+---
+version: '3'
+services:
+ envoy:
+ image: envoyproxy/envoy:v1.24.0
+ volumes:
+ - ./example/compose/envoy/envoy.yaml:/etc/envoy/envoy.yaml
+ - ./example/compose/nginx/portal/ssl:/etc/ssl
+ networks:
+ authelianet:
+ ipv4_address: 192.168.240.100
+...
diff --git a/internal/suites/example/compose/envoy/envoy.yaml b/internal/suites/example/compose/envoy/envoy.yaml
new file mode 100644
index 000000000..fc5039ebe
--- /dev/null
+++ b/internal/suites/example/compose/envoy/envoy.yaml
@@ -0,0 +1,236 @@
+---
+static_resources:
+ listeners:
+ - name: listener_0
+ address:
+ socket_address:
+ address: 0.0.0.0
+ port_value: 8080
+ filter_chains:
+ - filters:
+ - name: envoy.filters.network.http_connection_manager
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager # yamllint disable-line rule:line-length
+ stat_prefix: ingress_http
+ use_remote_address: true
+ skip_xff_append: false
+ access_log:
+ - name: envoy.access_loggers.stdout
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
+ route_config:
+ name: local_route
+ virtual_hosts:
+ - name: login_service
+ domains: ["login.example.com:8080"]
+ typed_per_filter_config:
+ envoy.filters.http.ext_authz:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
+ disabled: true
+ routes:
+ - match:
+ prefix: "/.well-known/"
+ route:
+ cluster: authelia-backend
+ - match:
+ prefix: "/api/"
+ route:
+ cluster: authelia-backend
+ - match:
+ prefix: "/locales/"
+ route:
+ cluster: authelia-backend
+ - match:
+ path: "/jwks.json"
+ route:
+ cluster: authelia-backend
+ - match:
+ prefix: "/"
+ route:
+ cluster: authelia-frontend
+ - name: mail_service
+ domains: ["mail.example.com:8080"]
+ typed_per_filter_config:
+ envoy.filters.http.ext_authz:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
+ disabled: true
+ routes:
+ - match:
+ prefix: "/"
+ route:
+ cluster: smtp
+ - name: http_service
+ domains: ["*.example.com:8080"]
+ routes:
+ - match:
+ prefix: "/headers"
+ route:
+ cluster: httpbin
+ - match:
+ prefix: "/"
+ route:
+ cluster: nginx-backend
+ http_filters:
+ - name: envoy.filters.http.ext_authz
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
+ http_service:
+ path_prefix: /api/verify/
+ server_uri:
+ uri: authelia-backend:9091
+ cluster: authelia-backend
+ timeout: 0.25s
+ authorization_request:
+ allowed_headers:
+ patterns:
+ - exact: accept
+ - exact: cookie
+ - exact: proxy-authorization
+ headers_to_add:
+ - key: X-Authelia-URL
+ value: 'https://login.example.com:8080/'
+ - key: X-Forwarded-Method
+ value: '%REQ(:METHOD)%'
+ - key: X-Forwarded-Proto
+ value: '%REQ(:SCHEME)%'
+ - key: X-Forwarded-Host
+ value: '%REQ(:AUTHORITY)%'
+ - key: X-Forwarded-URI
+ value: '%REQ(:PATH)%'
+ - key: X-Forwarded-For
+ value: '%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%'
+ authorization_response:
+ allowed_upstream_headers:
+ patterns:
+ - prefix: remote-
+ allowed_client_headers:
+ patterns:
+ - exact: set-cookie
+ allowed_client_headers_on_success:
+ patterns:
+ - exact: set-cookie
+ failure_mode_allow: false
+ - name: envoy.filters.http.router
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+ transport_socket:
+ name: envoy.transport_sockets.tls
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+ common_tls_context:
+ tls_certificates:
+ - certificate_chain:
+ filename: /etc/ssl/server.cert
+ private_key:
+ filename: /etc/ssl/server.key
+ clusters:
+ - name: authelia-frontend
+ transport_socket_matches:
+ - name: "enableTLS"
+ match:
+ enableTLS: true
+ transport_socket:
+ name: envoy.transport_sockets.tls
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+ common_tls_context: {}
+ - name: "defaultTLSDisabled"
+ match: {}
+ transport_socket:
+ name: envoy.transport_sockets.raw_buffer
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer
+ connect_timeout: 0.25s
+ type: STRICT_DNS
+ dns_lookup_family: V4_ONLY
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: authelia-frontend
+ endpoints:
+ - locality:
+ region: dev
+ priority: 0
+ lb_endpoints:
+ - endpoint:
+ health_check_config:
+ hostname: authelia-frontend
+ port_value: 3000
+ address:
+ socket_address:
+ address: authelia-frontend
+ port_value: 3000
+ - locality:
+ region: ci
+ priority: 1
+ lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: authelia-backend
+ port_value: 9091
+ metadata:
+ filter_metadata:
+ envoy.transport_socket_match:
+ enableTLS: true
+ - name: authelia-backend
+ connect_timeout: 0.25s
+ type: LOGICAL_DNS
+ dns_lookup_family: V4_ONLY
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: authelia-backend
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: authelia-backend
+ port_value: 9091
+ transport_socket:
+ name: envoy.transport_sockets.tls
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+ common_tls_context: {}
+ - name: smtp
+ connect_timeout: 0.25s
+ type: LOGICAL_DNS
+ dns_lookup_family: V4_ONLY
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: smtp
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: smtp
+ port_value: 1080
+ - name: httpbin
+ connect_timeout: 0.25s
+ type: LOGICAL_DNS
+ dns_lookup_family: V4_ONLY
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: httpbin
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: httpbin
+ port_value: 8000
+ - name: nginx-backend
+ connect_timeout: 0.25s
+ type: LOGICAL_DNS
+ dns_lookup_family: V4_ONLY
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: nginx-backend
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: nginx-backend
+ port_value: 80
+...
diff --git a/internal/suites/example/compose/haproxy/Dockerfile b/internal/suites/example/compose/haproxy/Dockerfile
index c72470737..c42df2bc3 100644
--- a/internal/suites/example/compose/haproxy/Dockerfile
+++ b/internal/suites/example/compose/haproxy/Dockerfile
@@ -1,4 +1,4 @@
-FROM haproxy:2.6.5-alpine
+FROM haproxy:2.6.6-alpine
USER root
RUN \
diff --git a/internal/suites/example/compose/mariadb/docker-compose.yml b/internal/suites/example/compose/mariadb/docker-compose.yml
index 951f72596..72e1661d2 100644
--- a/internal/suites/example/compose/mariadb/docker-compose.yml
+++ b/internal/suites/example/compose/mariadb/docker-compose.yml
@@ -2,7 +2,7 @@
version: '3'
services:
mariadb:
- image: mariadb:10.9.2
+ image: mariadb:10.9.4
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- MYSQL_USER=admin
diff --git a/internal/suites/example/compose/oidc-client/docker-compose.yml b/internal/suites/example/compose/oidc-client/docker-compose.yml
index 3a9ecc808..c9645d8a9 100644
--- a/internal/suites/example/compose/oidc-client/docker-compose.yml
+++ b/internal/suites/example/compose/oidc-client/docker-compose.yml
@@ -2,7 +2,7 @@
version: '3'
services:
oidc-client:
- image: ghcr.io/authelia/oidc-tester-app:master-01ff268
+ image: ghcr.io/authelia/oidc-tester-app:master-aeac7f4
command: /entrypoint.sh
depends_on:
- authelia-backend
diff --git a/internal/suites/example/compose/postgres/docker-compose.yml b/internal/suites/example/compose/postgres/docker-compose.yml
index 715e1f841..1e19a6f66 100644
--- a/internal/suites/example/compose/postgres/docker-compose.yml
+++ b/internal/suites/example/compose/postgres/docker-compose.yml
@@ -2,7 +2,7 @@
version: "3"
services:
postgres:
- image: postgres:14
+ image: postgres:15
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_USER=admin
diff --git a/internal/suites/example/compose/traefik2/docker-compose.yml b/internal/suites/example/compose/traefik2/docker-compose.yml
index 68e4f5334..a99aea134 100644
--- a/internal/suites/example/compose/traefik2/docker-compose.yml
+++ b/internal/suites/example/compose/traefik2/docker-compose.yml
@@ -2,7 +2,7 @@
version: '3'
services:
traefik:
- image: traefik:v2.8.4
+ image: traefik:v2.9.4
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
labels:
diff --git a/internal/suites/scenario_available_methods_test.go b/internal/suites/scenario_available_methods_test.go
index 7135f7afa..4fcaf35bf 100644
--- a/internal/suites/scenario_available_methods_test.go
+++ b/internal/suites/scenario_available_methods_test.go
@@ -59,7 +59,7 @@ func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() {
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
methodsButton := s.WaitElementLocatedByID(s.T(), s.Context(ctx), "methods-button")
- err := methodsButton.Click("left")
+ err := methodsButton.Click("left", 1)
s.Assert().NoError(err)
methodsDialog := s.WaitElementLocatedByID(s.T(), s.Context(ctx), "methods-dialog")
diff --git a/internal/suites/scenario_oidc_test.go b/internal/suites/scenario_oidc_test.go
index 7add41b48..65b867471 100644
--- a/internal/suites/scenario_oidc_test.go
+++ b/internal/suites/scenario_oidc_test.go
@@ -10,6 +10,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
+
+ "github.com/authelia/authelia/v4/internal/oidc"
)
type OIDCScenario struct {
@@ -86,11 +88,11 @@ func (s *OIDCScenario) TestShouldAuthorizeAccessToOIDCApp() {
s.waitBodyContains(s.T(), s.Context(ctx), "Not logged yet...")
// Search for the 'login' link.
- err := s.Page.MustSearch("Log in").Click("left")
+ err := s.Page.MustSearch("Log in").Click("left", 1)
assert.NoError(s.T(), err)
s.verifyIsConsentPage(s.T(), s.Context(ctx))
- err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "accept-button").Click("left")
+ err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "accept-button").Click("left", 1)
assert.NoError(s.T(), err)
// Verify that the app is showing the info related to the user stored in the JWT token.
@@ -101,36 +103,44 @@ func (s *OIDCScenario) TestShouldAuthorizeAccessToOIDCApp() {
rBase64 := regexp.MustCompile(`^[-_A-Za-z0-9+\\/]+([=]{0,3})$`)
testCases := []struct {
- desc, elementID, elementText string
- pattern *regexp.Regexp
+ desc, elementID string
+ expected any
}{
- {"welcome", "welcome", "Logged in as john!", nil},
- {"at_hash", "claim-at_hash", "", rBase64},
- {"jti", "claim-jti", "", rUUID},
- {"iat", "claim-iat", "", rInteger},
- {"nbf", "claim-nbf", "", rInteger},
- {"rat", "claim-rat", "", rInteger},
- {"expires", "claim-exp", "", rInteger},
- {"amr", "claim-amr", "pwd, otp, mfa", nil},
- {"acr", "claim-acr", "", nil},
- {"issuer", "claim-iss", "https://login.example.com:8080", nil},
- {"name", "claim-name", "John Doe", nil},
- {"preferred_username", "claim-preferred_username", "john", nil},
- {"groups", "claim-groups", "admins, dev", nil},
- {"email", "claim-email", "john.doe@authelia.com", nil},
- {"email_verified", "claim-email_verified", "", rBoolean},
+ {"welcome", "welcome", "Logged in as john!"},
+ {oidc.ClaimAccessTokenHash, "", rBase64},
+ {oidc.ClaimJWTID, "", rUUID},
+ {oidc.ClaimIssuedAt, "", rInteger},
+ {oidc.ClaimSubject, "", rUUID},
+ {oidc.ClaimNotBefore, "", rInteger},
+ {oidc.ClaimRequestedAt, "", rInteger},
+ {oidc.ClaimExpirationTime, "", rInteger},
+ {oidc.ClaimAuthenticationMethodsReference, "", "pwd, otp, mfa"},
+ {oidc.ClaimAuthenticationContextClassReference, "", ""},
+ {oidc.ClaimIssuer, "", "https://login.example.com:8080"},
+ {oidc.ClaimFullName, "", "John Doe"},
+ {oidc.ClaimPreferredUsername, "", "john"},
+ {oidc.ClaimGroups, "", "admins, dev"},
+ {oidc.ClaimPreferredEmail, "", "john.doe@authelia.com"},
+ {oidc.ClaimEmailVerified, "", rBoolean},
}
- var text string
+ var actual string
for _, tc := range testCases {
s.T().Run(fmt.Sprintf("check_claims/%s", tc.desc), func(t *testing.T) {
- text, err = s.WaitElementLocatedByID(t, s.Context(ctx), tc.elementID).Text()
+ switch tc.elementID {
+ case "":
+ actual, err = s.WaitElementLocatedByID(t, s.Context(ctx), "claim-"+tc.desc).Text()
+ default:
+ actual, err = s.WaitElementLocatedByID(t, s.Context(ctx), tc.elementID).Text()
+ }
+
assert.NoError(t, err)
- if tc.pattern == nil {
- assert.Equal(t, tc.elementText, text)
- } else {
- assert.Regexp(t, tc.pattern, text)
+ switch expected := tc.expected.(type) {
+ case *regexp.Regexp:
+ assert.Regexp(t, expected, actual)
+ default:
+ assert.Equal(t, expected, actual)
}
})
}
@@ -152,12 +162,12 @@ func (s *OIDCScenario) TestShouldDenyConsent() {
s.waitBodyContains(s.T(), s.Context(ctx), "Not logged yet...")
// Search for the 'login' link.
- err := s.Page.MustSearch("Log in").Click("left")
+ err := s.Page.MustSearch("Log in").Click("left", 1)
assert.NoError(s.T(), err)
s.verifyIsConsentPage(s.T(), s.Context(ctx))
- err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "deny-button").Click("left")
+ err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "deny-button").Click("left", 1)
assert.NoError(s.T(), err)
s.verifyIsOIDC(s.T(), s.Context(ctx), "access_denied", "https://oidc.example.com:8080/error?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.+Make+sure+that+the+request+you+are+making+is+valid.+Maybe+the+credential+or+request+parameters+you+are+using+are+limited+in+scope+or+otherwise+restricted.&state=random-string-here")
diff --git a/internal/suites/scenario_regulation_test.go b/internal/suites/scenario_regulation_test.go
index 0eba4661d..92404fdd2 100644
--- a/internal/suites/scenario_regulation_test.go
+++ b/internal/suites/scenario_regulation_test.go
@@ -62,14 +62,14 @@ func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
for i := 0; i < 3; i++ {
err := s.WaitElementLocatedByID(s.T(), s.Context(ctx), "password-textfield").Input("bad-password")
require.NoError(s.T(), err)
- err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "sign-in-button").Click("left")
+ err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "sign-in-button").Click("left", 1)
require.NoError(s.T(), err)
}
// Enter the correct password and test the regulation lock out.
err := s.WaitElementLocatedByID(s.T(), s.Context(ctx), "password-textfield").Input("password")
require.NoError(s.T(), err)
- err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "sign-in-button").Click("left")
+ err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "sign-in-button").Click("left", 1)
require.NoError(s.T(), err)
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.")
@@ -79,7 +79,7 @@ func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
// Enter the correct password and test a successful login.
err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "password-textfield").Input("password")
require.NoError(s.T(), err)
- err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "sign-in-button").Click("left")
+ err = s.WaitElementLocatedByID(s.T(), s.Context(ctx), "sign-in-button").Click("left", 1)
require.NoError(s.T(), err)
s.verifyIsSecondFactorPage(s.T(), s.Context(ctx))
}
diff --git a/internal/suites/suite_cli.go b/internal/suites/suite_cli.go
index 873a57f8e..7a81f088b 100644
--- a/internal/suites/suite_cli.go
+++ b/internal/suites/suite_cli.go
@@ -49,7 +49,7 @@ func init() {
SetUp: setup,
SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: displayAutheliaLogs,
- TestTimeout: 2 * time.Minute,
+ TestTimeout: 3 * time.Minute,
TearDown: teardown,
TearDownTimeout: 2 * time.Minute,
})
diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go
index 0fd51c2cc..0357b89b1 100644
--- a/internal/suites/suite_cli_test.go
+++ b/internal/suites/suite_cli_test.go
@@ -10,7 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/suite"
- "gopkg.in/yaml.v3"
+ yaml "gopkg.in/yaml.v3"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/storage"
@@ -84,16 +84,180 @@ func (s *CLISuite) TestShouldFailValidateConfig() {
s.Assert().Contains(output, "failed to load configuration from yaml file(/config/invalid.yml) source: open /config/invalid.yml: no such file or directory")
}
-func (s *CLISuite) TestShouldHashPasswordArgon2id() {
- output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "hash-password", "test", "-m", "32", "-s", "test1234"})
+func (s *CLISuite) TestShouldHashPasswordArgon2idLegacy() {
+ output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "hash-password", "test", "-m", "32"})
s.Assert().NoError(err)
- s.Assert().Contains(output, "Password hash: $argon2id$v=19$m=32768,t=3,p=4$")
+ s.Assert().Contains(output, "Digest: $argon2id$v=19$m=32768,t=3,p=4$")
}
-func (s *CLISuite) TestShouldHashPasswordSHA512() {
+func (s *CLISuite) TestShouldHashPasswordSHA512Legacy() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "hash-password", "test", "-z"})
s.Assert().NoError(err)
- s.Assert().Contains(output, "Password hash: $6$rounds=50000")
+ s.Assert().Contains(output, "Digest: $6$rounds=50000")
+}
+
+func (s *CLISuite) TestShouldHashPasswordArgon2() {
+ var (
+ output string
+ err error
+ )
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "argon2", "--password=apple123", "-m=32768"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $argon2id$v=19$m=32768,t=3,p=4$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "argon2", "--password=apple123", "-m", "32768", "-v=argon2i"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $argon2i$v=19$m=32768,t=3,p=4$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "argon2", "--password=apple123", "-m=32768", "-v=argon2d"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $argon2d$v=19$m=32768,t=3,p=4$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "argon2", "--random", "-m=32"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Random Password: ")
+ s.Assert().Contains(output, "Digest: $argon2id$v=19$m=32,t=3,p=4$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "argon2", "--password=apple123", "-p=1"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $argon2id$v=19$m=65536,t=3,p=1$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "argon2", "--password=apple123", "-i=1"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $argon2id$v=19$m=65536,t=1,p=4$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "argon2", "--password=apple123", "-s=64"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $argon2id$v=19$m=65536,t=3,p=4$")
+ s.Assert().GreaterOrEqual(len(output), 169)
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "argon2", "--password=apple123", "-k=128"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $argon2id$v=19$m=65536,t=3,p=4$")
+ s.Assert().GreaterOrEqual(len(output), 233)
+}
+
+func (s *CLISuite) TestShouldHashPasswordSHA2Crypt() {
+ var (
+ output string
+ err error
+ )
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "sha2crypt", "--password=apple123", "-v=sha256"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $5$rounds=50000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "sha2crypt", "--password=apple123", "-v=sha512"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $6$rounds=50000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "sha2crypt", "--random", "-s=8"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $6$rounds=50000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "sha2crypt", "--password=apple123", "-i=10000"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $6$rounds=10000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "sha2crypt", "--password=apple123", "-s=20"})
+ s.Assert().NotNil(err)
+ s.Assert().Contains(output, "Error: errors occurred validating the password configuration: authentication_backend: file: password: sha2crypt: option 'salt_length' is configured as '20' but must be less than or equal to '16'")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "sha2crypt", "--password=apple123", "-i=20"})
+ s.Assert().NotNil(err)
+ s.Assert().Contains(output, "Error: errors occurred validating the password configuration: authentication_backend: file: password: sha2crypt: option 'iterations' is configured as '20' but must be greater than or equal to '1000'")
+}
+
+func (s *CLISuite) TestShouldHashPasswordSHA2CryptSHA512() {
+ output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "sha2crypt", "--password=apple123", "-v=sha512"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $6$rounds=50000$")
+}
+
+func (s *CLISuite) TestShouldHashPasswordPBKDF2() {
+ var (
+ output string
+ err error
+ )
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "pbkdf2", "--password=apple123", "-v=sha1"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $pbkdf2$310000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "pbkdf2", "--random", "-v=sha256", "-i=100000"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Random Password: ")
+ s.Assert().Contains(output, "Digest: $pbkdf2-sha256$100000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "pbkdf2", "--password=apple123", "-v=sha512", "-i=100000"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $pbkdf2-sha512$100000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "pbkdf2", "--password=apple123", "-v=sha224", "-i=100000"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $pbkdf2-sha224$100000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "pbkdf2", "--password=apple123", "-v=sha384", "-i=100000"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $pbkdf2-sha384$100000$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "pbkdf2", "--password=apple123", "-s=32", "-i=100000"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $pbkdf2-sha512$100000$")
+}
+
+func (s *CLISuite) TestShouldHashPasswordBCrypt() {
+ var (
+ output string
+ err error
+ )
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "bcrypt", "--password=apple123"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $2b$12$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "bcrypt", "--random", "-i=10"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Random Password: ")
+ s.Assert().Contains(output, "Digest: $2b$10$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "bcrypt", "--password=apple123", "-v=sha256"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $bcrypt-sha256$v=2,t=2b,r=12$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "bcrypt", "--random", "-v=sha256", "-i=10"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Random Password: ")
+ s.Assert().Contains(output, "Digest: $bcrypt-sha256$v=2,t=2b,r=10$")
+}
+
+func (s *CLISuite) TestShouldHashPasswordSCrypt() {
+ var (
+ output string
+ err error
+ )
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "scrypt", "--password=apple123"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $scrypt$ln=16,r=8,p=1$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "scrypt", "--random"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Random Password: ")
+ s.Assert().Contains(output, "Digest: $scrypt$ln=16,r=8,p=1$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "scrypt", "--password=apple123", "-i=1"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $scrypt$ln=1,r=8,p=1$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "scrypt", "--password=apple123", "-i=1", "-p=2"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $scrypt$ln=1,r=8,p=2$")
+
+ output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "crypto", "hash", "generate", "scrypt", "--password=apple123", "-i=1", "-r=2"})
+ s.Assert().NoError(err)
+ s.Assert().Contains(output, "Digest: $scrypt$ln=1,r=2,p=1$")
}
func (s *CLISuite) TestShouldGenerateRSACertificateRequest() {
diff --git a/internal/suites/suite_duo_push.go b/internal/suites/suite_duo_push.go
index 586655844..2505ed24c 100644
--- a/internal/suites/suite_duo_push.go
+++ b/internal/suites/suite_duo_push.go
@@ -64,7 +64,7 @@ func init() {
SetUpTimeout: 5 * time.Minute,
OnSetupTimeout: displayAutheliaLogs,
OnError: displayAutheliaLogs,
- TestTimeout: 3 * time.Minute,
+ TestTimeout: 4 * time.Minute,
TearDown: teardown,
TearDownTimeout: 2 * time.Minute,
diff --git a/internal/suites/suite_duo_push_test.go b/internal/suites/suite_duo_push_test.go
index 4573c5b06..56a2c4400 100644
--- a/internal/suites/suite_duo_push_test.go
+++ b/internal/suites/suite_duo_push_test.go
@@ -303,7 +303,7 @@ func (s *DuoPushWebDriverSuite) TestShouldFailSelectionBecauseOfSelectionDenied(
s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "")
s.doChangeMethod(s.T(), s.Context(ctx), "push-notification")
- err := s.WaitElementLocatedByID(s.T(), s.Context(ctx), "selection-link").Click("left")
+ err := s.WaitElementLocatedByID(s.T(), s.Context(ctx), "selection-link").Click("left", 1)
require.NoError(s.T(), err)
s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Device selection was denied by Duo policy")
}
diff --git a/internal/suites/suite_envoy.go b/internal/suites/suite_envoy.go
new file mode 100644
index 000000000..9b9ff3916
--- /dev/null
+++ b/internal/suites/suite_envoy.go
@@ -0,0 +1,84 @@
+package suites
+
+import (
+ "fmt"
+ "os"
+ "time"
+)
+
+var envoySuiteName = "Envoy"
+
+func init() {
+ dockerEnvironment := NewDockerEnvironment([]string{
+ "internal/suites/docker-compose.yml",
+ "internal/suites/Envoy/docker-compose.yml",
+ "internal/suites/example/compose/authelia/docker-compose.backend.{}.yml",
+ "internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml",
+ "internal/suites/example/compose/nginx/backend/docker-compose.yml",
+ "internal/suites/example/compose/envoy/docker-compose.yml",
+ "internal/suites/example/compose/smtp/docker-compose.yml",
+ "internal/suites/example/compose/httpbin/docker-compose.yml",
+ })
+
+ if os.Getenv("CI") == t {
+ dockerEnvironment = NewDockerEnvironment([]string{
+ "internal/suites/docker-compose.yml",
+ "internal/suites/Envoy/docker-compose.yml",
+ "internal/suites/example/compose/authelia/docker-compose.backend.{}.yml",
+ "internal/suites/example/compose/nginx/backend/docker-compose.yml",
+ "internal/suites/example/compose/envoy/docker-compose.yml",
+ "internal/suites/example/compose/smtp/docker-compose.yml",
+ "internal/suites/example/compose/httpbin/docker-compose.yml",
+ })
+ }
+
+ setup := func(suitePath string) error {
+ if err := dockerEnvironment.Up(); err != nil {
+ return err
+ }
+
+ return waitUntilAutheliaIsReady(dockerEnvironment, envoySuiteName)
+ }
+
+ displayAutheliaLogs := func() error {
+ backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(backendLogs)
+
+ if os.Getenv("CI") != t {
+ frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(frontendLogs)
+ }
+
+ envoyLogs, err := dockerEnvironment.Logs("envoy", nil)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(envoyLogs)
+
+ return nil
+ }
+
+ teardown := func(suitePath string) error {
+ err := dockerEnvironment.Down()
+ return err
+ }
+
+ GlobalRegistry.Register(envoySuiteName, Suite{
+ SetUp: setup,
+ SetUpTimeout: 5 * time.Minute,
+ OnSetupTimeout: displayAutheliaLogs,
+ OnError: displayAutheliaLogs,
+ TestTimeout: 2 * time.Minute,
+ TearDown: teardown,
+ TearDownTimeout: 2 * time.Minute,
+ })
+}
diff --git a/internal/suites/suite_envoy_test.go b/internal/suites/suite_envoy_test.go
new file mode 100644
index 000000000..ca7cb1149
--- /dev/null
+++ b/internal/suites/suite_envoy_test.go
@@ -0,0 +1,39 @@
+package suites
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+)
+
+type EnvoySuite struct {
+ *RodSuite
+}
+
+func NewEnvoySuite() *EnvoySuite {
+ return &EnvoySuite{RodSuite: new(RodSuite)}
+}
+
+func (s *EnvoySuite) Test1FAScenario() {
+ suite.Run(s.T(), New1FAScenario())
+}
+
+func (s *EnvoySuite) Test2FAScenario() {
+ suite.Run(s.T(), New2FAScenario())
+}
+
+func (s *EnvoySuite) TestCustomHeaders() {
+ suite.Run(s.T(), NewCustomHeadersScenario())
+}
+
+func (s *EnvoySuite) TestResetPasswordScenario() {
+ suite.Run(s.T(), NewResetPasswordScenario())
+}
+
+func TestEnvoySuite(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping suite test in short mode")
+ }
+
+ suite.Run(t, NewEnvoySuite())
+}
diff --git a/internal/utils/const.go b/internal/utils/const.go
index d72aa5cd7..1188b5f9c 100644
--- a/internal/utils/const.go
+++ b/internal/utils/const.go
@@ -110,9 +110,38 @@ const (
HoursInYear = HoursInDay * 365
)
-var (
- // AlphaNumericCharacters are literally just valid alphanumeric chars.
- AlphaNumericCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+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(
diff --git a/internal/utils/crypto.go b/internal/utils/crypto.go
index e2aa80ee7..2e2dbab2d 100644
--- a/internal/utils/crypto.go
+++ b/internal/utils/crypto.go
@@ -122,7 +122,7 @@ func ConvertDERToPEM(der []byte, blockType PEMBlockType) ([]byte, error) {
return buf.Bytes(), nil
}
-func publicKey(privateKey interface{}) interface{} {
+func publicKey(privateKey any) any {
switch k := privateKey.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
@@ -137,7 +137,7 @@ func publicKey(privateKey interface{}) interface{} {
// PrivateKeyBuilder interface for a private key builder.
type PrivateKeyBuilder interface {
- Build() (interface{}, error)
+ Build() (any, error)
}
// RSAKeyBuilder builder of RSA private key.
@@ -152,7 +152,7 @@ func (rkb RSAKeyBuilder) WithKeySize(bits int) RSAKeyBuilder {
}
// Build a RSA private key.
-func (rkb RSAKeyBuilder) Build() (interface{}, error) {
+func (rkb RSAKeyBuilder) Build() (any, error) {
return rsa.GenerateKey(rand.Reader, rkb.keySizeInBits)
}
@@ -160,7 +160,7 @@ func (rkb RSAKeyBuilder) Build() (interface{}, error) {
type Ed25519KeyBuilder struct{}
// Build an Ed25519 private key.
-func (ekb Ed25519KeyBuilder) Build() (interface{}, error) {
+func (ekb Ed25519KeyBuilder) Build() (any, error) {
_, priv, err := ed25519.GenerateKey(rand.Reader)
return priv, err
}
@@ -177,12 +177,12 @@ func (ekb ECDSAKeyBuilder) WithCurve(curve elliptic.Curve) ECDSAKeyBuilder {
}
// Build an ECDSA private key.
-func (ekb ECDSAKeyBuilder) Build() (interface{}, error) {
+func (ekb ECDSAKeyBuilder) Build() (any, error) {
return ecdsa.GenerateKey(ekb.curve, rand.Reader)
}
// ParseX509FromPEM parses PEM bytes and returns a PKCS key.
-func ParseX509FromPEM(data []byte) (key interface{}, err error) {
+func ParseX509FromPEM(data []byte) (key any, err error) {
block, _ := pem.Decode(data)
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
@@ -213,7 +213,7 @@ func ParseX509FromPEM(data []byte) (key interface{}, err error) {
}
// CastX509AsCertificate converts an interface to an *x509.Certificate.
-func CastX509AsCertificate(c interface{}) (certificate *x509.Certificate, ok bool) {
+func CastX509AsCertificate(c any) (certificate *x509.Certificate, ok bool) {
switch t := c.(type) {
case x509.Certificate:
return &t, true
@@ -225,7 +225,7 @@ func CastX509AsCertificate(c interface{}) (certificate *x509.Certificate, ok boo
}
// IsX509PrivateKey returns true if the provided interface is an rsa.PrivateKey, ecdsa.PrivateKey, or ed25519.PrivateKey.
-func IsX509PrivateKey(i interface{}) bool {
+func IsX509PrivateKey(i any) bool {
switch i.(type) {
case rsa.PrivateKey, *rsa.PrivateKey, ecdsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *ed25519.PrivateKey:
return true
@@ -235,17 +235,26 @@ func IsX509PrivateKey(i interface{}) bool {
}
// NewTLSConfig generates a tls.Config from a schema.TLSConfig and a x509.CertPool.
-func NewTLSConfig(config *schema.TLSConfig, defaultMinVersion uint16, certPool *x509.CertPool) (tlsConfig *tls.Config) {
- minVersion, err := TLSStringToTLSConfigVersion(config.MinimumVersion)
- if err != nil {
- minVersion = defaultMinVersion
+func NewTLSConfig(config *schema.TLSConfig, caCertPool *x509.CertPool) (tlsConfig *tls.Config) {
+ var certificates []tls.Certificate
+
+ if config.CertificateChain.HasCertificates() && config.PrivateKey != nil {
+ certificates = []tls.Certificate{
+ {
+ Certificate: config.CertificateChain.CertificatesRaw(),
+ Leaf: config.CertificateChain.Leaf(),
+ PrivateKey: config.PrivateKey,
+ },
+ }
}
return &tls.Config{
ServerName: config.ServerName,
InsecureSkipVerify: config.SkipVerify, //nolint:gosec // Informed choice by user. Off by default.
- MinVersion: minVersion,
- RootCAs: certPool,
+ MinVersion: config.MinimumVersion.MinVersion(),
+ MaxVersion: config.MinimumVersion.MaxVersion(),
+ RootCAs: caCertPool,
+ Certificates: certificates,
}
}
@@ -290,22 +299,6 @@ func NewX509CertPool(directory string) (certPool *x509.CertPool, warnings []erro
return certPool, warnings, errors
}
-// TLSStringToTLSConfigVersion returns a go crypto/tls version for a tls.Config based on string input.
-func TLSStringToTLSConfigVersion(input string) (version uint16, err error) {
- switch strings.ToUpper(input) {
- case "TLS1.3", TLS13:
- return tls.VersionTLS13, nil
- case "TLS1.2", TLS12:
- return tls.VersionTLS12, nil
- case "TLS1.1", TLS11:
- return tls.VersionTLS11, nil
- case "TLS1.0", TLS10:
- return tls.VersionTLS10, nil
- }
-
- return 0, ErrTLSVersionNotSupported
-}
-
// WriteCertificateBytesToPEM writes a certificate/csr to a file in the PEM format.
func WriteCertificateBytesToPEM(cert []byte, path string, csr bool) (err error) {
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
@@ -328,7 +321,7 @@ func WriteCertificateBytesToPEM(cert []byte, path string, csr bool) (err error)
}
// WriteKeyToPEM writes a key that can be encoded as a PEM to a file in the PEM format.
-func WriteKeyToPEM(key interface{}, path string, pkcs8 bool) (err error) {
+func WriteKeyToPEM(key any, path string, pkcs8 bool) (err error) {
pemBlock, err := PEMBlockFromX509Key(key, pkcs8)
if err != nil {
return err
@@ -349,7 +342,7 @@ func WriteKeyToPEM(key interface{}, path string, pkcs8 bool) (err error) {
}
// PEMBlockFromX509Key turns a PublicKey or PrivateKey into a pem.Block.
-func PEMBlockFromX509Key(key interface{}, pkcs8 bool) (pemBlock *pem.Block, err error) {
+func PEMBlockFromX509Key(key any, pkcs8 bool) (pemBlock *pem.Block, err error) {
var (
data []byte
blockType string
@@ -491,7 +484,7 @@ func EllipticCurveFromString(curveString string) (curve elliptic.Curve) {
}
// PublicKeyFromPrivateKey returns a PublicKey when provided with a PrivateKey.
-func PublicKeyFromPrivateKey(privateKey interface{}) (publicKey interface{}) {
+func PublicKeyFromPrivateKey(privateKey any) (publicKey any) {
switch k := privateKey.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
diff --git a/internal/utils/crypto_test.go b/internal/utils/crypto_test.go
index 9d4adb9f5..dcc065721 100644
--- a/internal/utils/crypto_test.go
+++ b/internal/utils/crypto_test.go
@@ -5,7 +5,6 @@ import (
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
- "crypto/tls"
"crypto/x509"
"runtime"
"strings"
@@ -14,75 +13,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
-
- "github.com/authelia/authelia/v4/internal/configuration/schema"
)
-func TestShouldSetupDefaultTLSMinVersionOnErr(t *testing.T) {
- schemaTLSConfig := &schema.TLSConfig{
- MinimumVersion: "NotAVersion",
- ServerName: "golang.org",
- SkipVerify: true,
- }
-
- tlsConfig := NewTLSConfig(schemaTLSConfig, tls.VersionTLS12, nil)
-
- assert.Equal(t, uint16(tls.VersionTLS12), tlsConfig.MinVersion)
- assert.Equal(t, "golang.org", tlsConfig.ServerName)
- assert.True(t, tlsConfig.InsecureSkipVerify)
-}
-
-func TestShouldReturnCorrectTLSVersions(t *testing.T) {
- tls13 := uint16(tls.VersionTLS13)
- tls12 := uint16(tls.VersionTLS12)
- tls11 := uint16(tls.VersionTLS11)
- tls10 := uint16(tls.VersionTLS10)
-
- version, err := TLSStringToTLSConfigVersion(TLS13)
- assert.Equal(t, tls13, version)
- assert.NoError(t, err)
-
- version, err = TLSStringToTLSConfigVersion("TLS" + TLS13)
- assert.Equal(t, tls13, version)
- assert.NoError(t, err)
-
- version, err = TLSStringToTLSConfigVersion(TLS12)
- assert.Equal(t, tls12, version)
- assert.NoError(t, err)
-
- version, err = TLSStringToTLSConfigVersion("TLS" + TLS12)
- assert.Equal(t, tls12, version)
- assert.NoError(t, err)
-
- version, err = TLSStringToTLSConfigVersion(TLS11)
- assert.Equal(t, tls11, version)
- assert.NoError(t, err)
-
- version, err = TLSStringToTLSConfigVersion("TLS" + TLS11)
- assert.Equal(t, tls11, version)
- assert.NoError(t, err)
-
- version, err = TLSStringToTLSConfigVersion(TLS10)
- assert.Equal(t, tls10, version)
- assert.NoError(t, err)
-
- version, err = TLSStringToTLSConfigVersion("TLS" + TLS10)
- assert.Equal(t, tls10, version)
- assert.NoError(t, err)
-}
-
-func TestShouldReturnZeroAndErrorOnInvalidTLSVersions(t *testing.T) {
- version, err := TLSStringToTLSConfigVersion("TLS1.4")
- assert.Error(t, err)
- assert.Equal(t, uint16(0), version)
- assert.EqualError(t, err, "supplied tls version isn't supported")
-
- version, err = TLSStringToTLSConfigVersion("SSL3.0")
- assert.Error(t, err)
- assert.Equal(t, uint16(0), version)
- assert.EqualError(t, err, "supplied tls version isn't supported")
-}
-
func TestShouldReturnErrWhenX509DirectoryNotExist(t *testing.T) {
pool, warnings, errors := NewX509CertPool("/tmp/asdfzyxabc123/not/a/real/dir")
assert.NotNil(t, pool)
diff --git a/internal/utils/hashing_test.go b/internal/utils/hashing_test.go
index 81ed04299..5a86c83be 100644
--- a/internal/utils/hashing_test.go
+++ b/internal/utils/hashing_test.go
@@ -22,7 +22,7 @@ func TestShouldHashString(t *testing.T) {
assert.Equal(t, "ae448ac86c4e8e4dec645729708ef41873ae79c6dff84eff73360989487f08e5", anotherSum)
assert.NotEqual(t, sum, anotherSum)
- randomInput := RandomString(40, AlphaNumericCharacters, false)
+ randomInput := RandomString(40, CharSetAlphaNumeric, false)
randomSum := HashSHA256FromString(randomInput)
assert.NotEqual(t, randomSum, sum)
@@ -39,7 +39,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, AlphaNumericCharacters, true)+"\n"), 0600)
+ err = os.WriteFile(filepath.Join(dir, "randomfile"), []byte(RandomString(40, CharSetAlphaNumeric, true)+"\n"), 0600)
assert.NoError(t, err)
sum, err := HashSHA256FromPath(filepath.Join(dir, "myfile"))
diff --git a/internal/utils/strings_test.go b/internal/utils/strings_test.go
index 0e2b92651..c086dd0ae 100644
--- a/internal/utils/strings_test.go
+++ b/internal/utils/strings_test.go
@@ -54,11 +54,11 @@ func TestStringJoinDelimitedEscaped(t *testing.T) {
}
func TestShouldNotGenerateSameRandomString(t *testing.T) {
- randomStringOne := RandomString(10, AlphaNumericCharacters, false)
- randomStringTwo := RandomString(10, AlphaNumericCharacters, false)
+ randomStringOne := RandomString(10, CharSetAlphaNumeric, false)
+ randomStringTwo := RandomString(10, CharSetAlphaNumeric, false)
- randomCryptoStringOne := RandomString(10, AlphaNumericCharacters, true)
- randomCryptoStringTwo := RandomString(10, AlphaNumericCharacters, true)
+ randomCryptoStringOne := RandomString(10, CharSetAlphaNumeric, true)
+ randomCryptoStringTwo := RandomString(10, CharSetAlphaNumeric, true)
assert.NotEqual(t, randomStringOne, randomStringTwo)
assert.NotEqual(t, randomCryptoStringOne, randomCryptoStringTwo)
diff --git a/internal/utils/version.go b/internal/utils/version.go
index 3e051d708..24fa855a5 100644
--- a/internal/utils/version.go
+++ b/internal/utils/version.go
@@ -35,10 +35,11 @@ var BuildNumber = "0"
// BuildTag i.e. v1.0.0. If dirty and tagged are present it returns -dirty. Otherwise, the following is the
// format: untagged--dirty- (, ).
func Version() (versionString string) {
- return version(BuildTag, BuildState, BuildCommit, BuildBranch, BuildExtra)
+ return VersionAdv(BuildTag, BuildState, BuildCommit, BuildBranch, BuildExtra)
}
-func version(tag, state, commit, branch, extra string) (version string) {
+// VersionAdv takes inputs to generate the version.
+func VersionAdv(tag, state, commit, branch, extra string) (version string) {
b := strings.Builder{}
states := strings.Split(state, " ")
diff --git a/internal/utils/version_test.go b/internal/utils/version_test.go
index 3b8aed08a..68aa9f522 100644
--- a/internal/utils/version_test.go
+++ b/internal/utils/version_test.go
@@ -15,27 +15,27 @@ func TestVersionDefault(t *testing.T) {
func TestVersion(t *testing.T) {
var v string
- v = version("v4.90.0", "tagged clean", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
+ v = VersionAdv("v4.90.0", "tagged clean", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
assert.Equal(t, "v4.90.0", v)
- v = version("v4.90.0", "tagged clean", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "freshports")
+ v = VersionAdv("v4.90.0", "tagged clean", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "freshports")
assert.Equal(t, "v4.90.0-freshports", v)
- v = version("v4.90.0", "tagged dirty", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
+ v = VersionAdv("v4.90.0", "tagged dirty", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
assert.Equal(t, "v4.90.0-dirty", v)
- v = version("v4.90.0", "untagged dirty", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
+ v = VersionAdv("v4.90.0", "untagged dirty", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
assert.Equal(t, "untagged-v4.90.0-dirty (master, 50d8b4a)", v)
- v = version("v4.90.0", "untagged clean", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
+ v = VersionAdv("v4.90.0", "untagged clean", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
assert.Equal(t, "untagged-v4.90.0 (master, 50d8b4a)", v)
- v = version("v4.90.0", "untagged clean", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "freshports")
+ v = VersionAdv("v4.90.0", "untagged clean", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "freshports")
assert.Equal(t, "untagged-v4.90.0-freshports (master, 50d8b4a)", v)
- v = version("v4.90.0", "untagged clean", "", "master", "")
+ v = VersionAdv("v4.90.0", "untagged clean", "", "master", "")
assert.Equal(t, "untagged-v4.90.0 (master, unknown)", v)
- v = version("v4.90.0", "", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
+ v = VersionAdv("v4.90.0", "", "50d8b4a941c26b89482c94ab324b5a274f9ced66", "master", "")
assert.Equal(t, "untagged-v4.90.0-dirty (master, 50d8b4a)", v)
}
diff --git a/web/.commitlintrc.js b/web/.commitlintrc.js
index a97f14f2f..02297d21c 100644
--- a/web/.commitlintrc.js
+++ b/web/.commitlintrc.js
@@ -54,5 +54,5 @@ module.exports = {
],
},
defaultIgnores: true,
- helpUrl: "https://www.authelia.com/contributing/development/guidelines-commit-message/",
+ helpUrl: "https://www.authelia.com/contributing/guidelines/commit-message/",
};
diff --git a/web/package.json b/web/package.json
index a4ddc1adb..e3d9f6820 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,6 +1,6 @@
{
"name": "authelia",
- "version": "4.36.7",
+ "version": "4.37.2",
"private": true,
"pnpm": {
"peerDependencyRules": {
@@ -18,29 +18,30 @@
}
},
"dependencies": {
- "@emotion/cache": "11.10.3",
- "@emotion/react": "11.10.4",
- "@emotion/styled": "11.10.4",
+ "@emotion/cache": "11.10.5",
+ "@emotion/react": "11.10.5",
+ "@emotion/styled": "11.10.5",
"@fortawesome/fontawesome-svg-core": "6.2.0",
"@fortawesome/free-regular-svg-icons": "6.2.0",
"@fortawesome/free-solid-svg-icons": "6.2.0",
"@fortawesome/react-fontawesome": "0.2.0",
- "@mui/icons-material": "5.10.3",
- "@mui/material": "5.10.4",
- "@mui/styles": "5.10.3",
- "axios": "0.27.2",
- "classnames": "2.3.1",
- "i18next": "21.9.1",
- "i18next-browser-languagedetector": "6.1.5",
- "i18next-http-backend": "1.4.1",
+ "@mui/icons-material": "5.10.9",
+ "@mui/material": "5.10.13",
+ "@mui/styles": "5.10.10",
+ "axios": "1.1.3",
+ "broadcast-channel": "4.18.1",
+ "classnames": "2.3.2",
+ "i18next": "22.0.5",
+ "i18next-browser-languagedetector": "7.0.1",
+ "i18next-http-backend": "2.0.1",
"qrcode.react": "3.1.0",
"query-string": "7.1.1",
"react": "18.2.0",
"react-dom": "18.2.0",
- "react-i18next": "11.18.6",
+ "react-i18next": "12.0.0",
"react-loading": "2.0.3",
"react-otp-input": "2.4.0",
- "react-router-dom": "6.3.0",
+ "react-router-dom": "6.4.3",
"zxcvbn": "4.4.2"
},
"scripts": {
@@ -142,43 +143,43 @@
]
},
"devDependencies": {
- "@commitlint/cli": "17.1.2",
- "@commitlint/config-conventional": "17.1.0",
+ "@commitlint/cli": "17.2.0",
+ "@commitlint/config-conventional": "17.2.0",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0",
- "@types/jest": "29.0.0",
- "@types/node": "16.11.58",
+ "@types/jest": "29.2.2",
+ "@types/node": "18.11.9",
"@types/qrcode.react": "1.0.2",
- "@types/react": "18.0.19",
- "@types/react-dom": "18.0.6",
+ "@types/react": "18.0.25",
+ "@types/react-dom": "18.0.8",
"@types/zxcvbn": "4.4.1",
- "@typescript-eslint/eslint-plugin": "5.36.2",
- "@typescript-eslint/parser": "5.36.2",
- "@vitejs/plugin-react": "2.1.0",
- "esbuild": "0.15.7",
+ "@typescript-eslint/eslint-plugin": "5.42.1",
+ "@typescript-eslint/parser": "5.42.1",
+ "@vitejs/plugin-react": "2.2.0",
+ "esbuild": "0.15.13",
"esbuild-jest": "0.5.0",
- "eslint": "8.23.0",
+ "eslint": "8.27.0",
"eslint-config-prettier": "8.5.0",
"eslint-config-react-app": "7.0.1",
"eslint-formatter-rdjson": "1.0.5",
- "eslint-import-resolver-typescript": "3.5.1",
+ "eslint-import-resolver-typescript": "3.5.2",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-prettier": "4.2.1",
- "eslint-plugin-react": "7.31.8",
+ "eslint-plugin-react": "7.31.10",
"eslint-plugin-react-hooks": "4.6.0",
- "husky": "8.0.1",
- "jest": "29.0.2",
- "jest-environment-jsdom": "29.0.2",
+ "husky": "8.0.2",
+ "jest": "29.3.1",
+ "jest-environment-jsdom": "29.3.1",
"jest-transform-stub": "2.0.0",
- "jest-watch-typeahead": "2.1.1",
+ "jest-watch-typeahead": "2.2.0",
"prettier": "2.7.1",
"react-test-renderer": "18.2.0",
- "typescript": "4.8.3",
- "vite": "3.1.0",
+ "typescript": "4.8.4",
+ "vite": "3.2.3",
"vite-plugin-eslint": "1.8.1",
- "vite-plugin-istanbul": "3.0.1",
- "vite-plugin-svgr": "2.2.1",
- "vite-tsconfig-paths": "3.5.0"
+ "vite-plugin-istanbul": "3.0.2",
+ "vite-plugin-svgr": "2.2.2",
+ "vite-tsconfig-paths": "3.5.2"
}
}
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index bb0905c45..3034b4346 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -1,134 +1,136 @@
lockfileVersion: 5.4
specifiers:
- '@commitlint/cli': 17.1.2
- '@commitlint/config-conventional': 17.1.0
- '@emotion/cache': 11.10.3
- '@emotion/react': 11.10.4
- '@emotion/styled': 11.10.4
+ '@commitlint/cli': 17.2.0
+ '@commitlint/config-conventional': 17.2.0
+ '@emotion/cache': 11.10.5
+ '@emotion/react': 11.10.5
+ '@emotion/styled': 11.10.5
'@fortawesome/fontawesome-svg-core': 6.2.0
'@fortawesome/free-regular-svg-icons': 6.2.0
'@fortawesome/free-solid-svg-icons': 6.2.0
'@fortawesome/react-fontawesome': 0.2.0
- '@mui/icons-material': 5.10.3
- '@mui/material': 5.10.4
- '@mui/styles': 5.10.3
+ '@mui/icons-material': 5.10.9
+ '@mui/material': 5.10.13
+ '@mui/styles': 5.10.10
'@testing-library/jest-dom': 5.16.5
'@testing-library/react': 13.4.0
- '@types/jest': 29.0.0
- '@types/node': 16.11.58
+ '@types/jest': 29.2.2
+ '@types/node': 18.11.9
'@types/qrcode.react': 1.0.2
- '@types/react': 18.0.19
- '@types/react-dom': 18.0.6
+ '@types/react': 18.0.25
+ '@types/react-dom': 18.0.8
'@types/zxcvbn': 4.4.1
- '@typescript-eslint/eslint-plugin': 5.36.2
- '@typescript-eslint/parser': 5.36.2
- '@vitejs/plugin-react': 2.1.0
- axios: 0.27.2
- classnames: 2.3.1
- esbuild: 0.15.7
+ '@typescript-eslint/eslint-plugin': 5.42.1
+ '@typescript-eslint/parser': 5.42.1
+ '@vitejs/plugin-react': 2.2.0
+ axios: 1.1.3
+ broadcast-channel: 4.18.1
+ classnames: 2.3.2
+ esbuild: 0.15.13
esbuild-jest: 0.5.0
- eslint: 8.23.0
+ eslint: 8.27.0
eslint-config-prettier: 8.5.0
eslint-config-react-app: 7.0.1
eslint-formatter-rdjson: 1.0.5
- eslint-import-resolver-typescript: 3.5.1
+ eslint-import-resolver-typescript: 3.5.2
eslint-plugin-import: 2.26.0
eslint-plugin-jsx-a11y: 6.6.1
eslint-plugin-prettier: 4.2.1
- eslint-plugin-react: 7.31.8
+ eslint-plugin-react: 7.31.10
eslint-plugin-react-hooks: 4.6.0
- husky: 8.0.1
- i18next: 21.9.1
- i18next-browser-languagedetector: 6.1.5
- i18next-http-backend: 1.4.1
- jest: 29.0.2
- jest-environment-jsdom: 29.0.2
+ husky: 8.0.2
+ i18next: 22.0.5
+ i18next-browser-languagedetector: 7.0.1
+ i18next-http-backend: 2.0.1
+ jest: 29.3.1
+ jest-environment-jsdom: 29.3.1
jest-transform-stub: 2.0.0
- jest-watch-typeahead: 2.1.1
+ jest-watch-typeahead: 2.2.0
prettier: 2.7.1
qrcode.react: 3.1.0
query-string: 7.1.1
react: 18.2.0
react-dom: 18.2.0
- react-i18next: 11.18.6
+ react-i18next: 12.0.0
react-loading: 2.0.3
react-otp-input: 2.4.0
- react-router-dom: 6.3.0
+ react-router-dom: 6.4.3
react-test-renderer: 18.2.0
- typescript: 4.8.3
- vite: 3.1.0
+ typescript: 4.8.4
+ vite: 3.2.3
vite-plugin-eslint: 1.8.1
- vite-plugin-istanbul: 3.0.1
- vite-plugin-svgr: 2.2.1
- vite-tsconfig-paths: 3.5.0
+ vite-plugin-istanbul: 3.0.2
+ vite-plugin-svgr: 2.2.2
+ vite-tsconfig-paths: 3.5.2
zxcvbn: 4.4.2
dependencies:
- '@emotion/cache': 11.10.3
- '@emotion/react': 11.10.4_fn6mmk7tilns4p7ajkklhuudvi
- '@emotion/styled': 11.10.4_v6omebeixt23oq3ljl3rjllla4
+ '@emotion/cache': 11.10.5
+ '@emotion/react': 11.10.5_fan5qbzahqtxlm5dzefqlqx5ia
+ '@emotion/styled': 11.10.5_otcjdfkheatawhug6fwk7ldzni
'@fortawesome/fontawesome-svg-core': 6.2.0
'@fortawesome/free-regular-svg-icons': 6.2.0
'@fortawesome/free-solid-svg-icons': 6.2.0
'@fortawesome/react-fontawesome': 0.2.0_6j2wdqxompc6mcokhxkpep2gqu
- '@mui/icons-material': 5.10.3_a2hj5atstaulbk2sc24hz7fpli
- '@mui/material': 5.10.4_g6p6knjyjrjltwufjb7cltiueu
- '@mui/styles': 5.10.3_fn6mmk7tilns4p7ajkklhuudvi
- axios: 0.27.2
- classnames: 2.3.1
- i18next: 21.9.1
- i18next-browser-languagedetector: 6.1.5
- i18next-http-backend: 1.4.1
+ '@mui/icons-material': 5.10.9_4tcfjhohj57yvze6zkot3vymie
+ '@mui/material': 5.10.13_thp4wrxo45nyllusrnt46mid4u
+ '@mui/styles': 5.10.10_fan5qbzahqtxlm5dzefqlqx5ia
+ axios: 1.1.3
+ broadcast-channel: 4.18.1
+ classnames: 2.3.2
+ i18next: 22.0.5
+ i18next-browser-languagedetector: 7.0.1
+ i18next-http-backend: 2.0.1
qrcode.react: 3.1.0_react@18.2.0
query-string: 7.1.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
- react-i18next: 11.18.6_4sidbwfhen5r7txudrvpua6nty
+ react-i18next: 12.0.0_e52s5leb62x5eudkmfoztpifde
react-loading: 2.0.3_react@18.2.0
react-otp-input: 2.4.0_biqbaboplfbrettd7655fr4n2y
- react-router-dom: 6.3.0_biqbaboplfbrettd7655fr4n2y
+ react-router-dom: 6.4.3_biqbaboplfbrettd7655fr4n2y
zxcvbn: 4.4.2
devDependencies:
- '@commitlint/cli': 17.1.2
- '@commitlint/config-conventional': 17.1.0
+ '@commitlint/cli': 17.2.0
+ '@commitlint/config-conventional': 17.2.0
'@testing-library/jest-dom': 5.16.5
'@testing-library/react': 13.4.0_biqbaboplfbrettd7655fr4n2y
- '@types/jest': 29.0.0
- '@types/node': 16.11.58
+ '@types/jest': 29.2.2
+ '@types/node': 18.11.9
'@types/qrcode.react': 1.0.2
- '@types/react': 18.0.19
- '@types/react-dom': 18.0.6
+ '@types/react': 18.0.25
+ '@types/react-dom': 18.0.8
'@types/zxcvbn': 4.4.1
- '@typescript-eslint/eslint-plugin': 5.36.2_2l2r3i3lm6jysqd4ac3ql4n2mm
- '@typescript-eslint/parser': 5.36.2_itqs5654cmlnjraw6gjzqacppi
- '@vitejs/plugin-react': 2.1.0_vite@3.1.0
- esbuild: 0.15.7
- esbuild-jest: 0.5.0_esbuild@0.15.7
- eslint: 8.23.0
- eslint-config-prettier: 8.5.0_eslint@8.23.0
- eslint-config-react-app: 7.0.1_4fdwrjms7sui3pmirbrnmoo7cy
+ '@typescript-eslint/eslint-plugin': 5.42.1_2udltptbznfmezdozpdoa2aemq
+ '@typescript-eslint/parser': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
+ '@vitejs/plugin-react': 2.2.0_vite@3.2.3
+ esbuild: 0.15.13
+ esbuild-jest: 0.5.0_esbuild@0.15.13
+ eslint: 8.27.0
+ eslint-config-prettier: 8.5.0_eslint@8.27.0
+ eslint-config-react-app: 7.0.1_ar3kwoulcryci6535wcfvjpsqy
eslint-formatter-rdjson: 1.0.5
- eslint-import-resolver-typescript: 3.5.1_faomjyrlgqmwswvqymymzkxcqi
- eslint-plugin-import: 2.26.0_ikxfxf6lcdzwf3sfhs76tj4x7m
- eslint-plugin-jsx-a11y: 6.6.1_eslint@8.23.0
- eslint-plugin-prettier: 4.2.1_tgumt6uwl2md3n6uqnggd6wvce
- eslint-plugin-react: 7.31.8_eslint@8.23.0
- eslint-plugin-react-hooks: 4.6.0_eslint@8.23.0
- husky: 8.0.1
- jest: 29.0.2_@types+node@16.11.58
- jest-environment-jsdom: 29.0.2
+ eslint-import-resolver-typescript: 3.5.2_dcpv4nbdr5ks2h5677xdltrk6e
+ eslint-plugin-import: 2.26.0_gbipkkcbnjmysmpjttq6vkmfqq
+ eslint-plugin-jsx-a11y: 6.6.1_eslint@8.27.0
+ eslint-plugin-prettier: 4.2.1_v7o5sx5x3wbs57ifz6wc4f76we
+ eslint-plugin-react: 7.31.10_eslint@8.27.0
+ eslint-plugin-react-hooks: 4.6.0_eslint@8.27.0
+ husky: 8.0.2
+ jest: 29.3.1_@types+node@18.11.9
+ jest-environment-jsdom: 29.3.1
jest-transform-stub: 2.0.0
- jest-watch-typeahead: 2.1.1_jest@29.0.2
+ jest-watch-typeahead: 2.2.0_jest@29.3.1
prettier: 2.7.1
react-test-renderer: 18.2.0_react@18.2.0
- typescript: 4.8.3
- vite: 3.1.0
- vite-plugin-eslint: 1.8.1_eslint@8.23.0+vite@3.1.0
- vite-plugin-istanbul: 3.0.1
- vite-plugin-svgr: 2.2.1_vite@3.1.0
- vite-tsconfig-paths: 3.5.0_vite@3.1.0
+ typescript: 4.8.4
+ vite: 3.2.3_@types+node@18.11.9
+ vite-plugin-eslint: 1.8.1_eslint@8.27.0+vite@3.2.3
+ vite-plugin-istanbul: 3.0.2
+ vite-plugin-svgr: 2.2.2_vite@3.2.3
+ vite-tsconfig-paths: 3.5.2_vite@3.2.3
packages:
@@ -155,6 +157,11 @@ packages:
engines: {node: '>=6.9.0'}
dev: true
+ /@babel/compat-data/7.19.4:
+ resolution: {integrity: sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
/@babel/core/7.18.6:
resolution: {integrity: sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==}
engines: {node: '>=6.9.0'}
@@ -178,20 +185,20 @@ packages:
- supports-color
dev: true
- /@babel/core/7.19.0:
- resolution: {integrity: sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==}
+ /@babel/core/7.19.6:
+ resolution: {integrity: sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.2.0
'@babel/code-frame': 7.18.6
- '@babel/generator': 7.19.0
- '@babel/helper-compilation-targets': 7.19.0_@babel+core@7.19.0
- '@babel/helper-module-transforms': 7.19.0
- '@babel/helpers': 7.19.0
- '@babel/parser': 7.19.0
+ '@babel/generator': 7.19.6
+ '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.19.6
+ '@babel/helper-module-transforms': 7.19.6
+ '@babel/helpers': 7.19.4
+ '@babel/parser': 7.19.6
'@babel/template': 7.18.10
- '@babel/traverse': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/traverse': 7.19.6
+ '@babel/types': 7.19.4
convert-source-map: 1.8.0
debug: 4.3.4
gensync: 1.0.0-beta.2
@@ -201,7 +208,7 @@ packages:
- supports-color
dev: true
- /@babel/eslint-parser/7.18.2_oj2zia55h5aiwaxxlc5vhqdsce:
+ /@babel/eslint-parser/7.18.2_chvycicpad5erj2yemazgxq6vy:
resolution: {integrity: sha512-oFQYkE8SuH14+uR51JVAmdqwKYXGRjEXx7s+WiagVjqQ+HPE+nnwyF2qlVG8evUsUHmPcA+6YXMEDbIhEyQc5A==}
engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0}
peerDependencies:
@@ -212,7 +219,7 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- eslint: 8.23.0
+ eslint: 8.27.0
eslint-scope: 5.1.1
eslint-visitor-keys: 2.1.0
semver: 6.3.0
@@ -222,16 +229,16 @@ packages:
resolution: {integrity: sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
'@jridgewell/gen-mapping': 0.3.2
jsesc: 2.5.2
dev: true
- /@babel/generator/7.19.0:
- resolution: {integrity: sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==}
+ /@babel/generator/7.19.6:
+ resolution: {integrity: sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
'@jridgewell/gen-mapping': 0.3.2
jsesc: 2.5.2
dev: true
@@ -240,7 +247,7 @@ packages:
resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-builder-binary-assignment-operator-visitor/7.18.6:
@@ -248,7 +255,7 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-explode-assignable-expression': 7.18.6
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-compilation-targets/7.18.9_@babel+core@7.18.6:
@@ -267,8 +274,8 @@ packages:
semver: 6.3.0
dev: true
- /@babel/helper-compilation-targets/7.19.0_@babel+core@7.19.0:
- resolution: {integrity: sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==}
+ /@babel/helper-compilation-targets/7.19.3_@babel+core@7.19.6:
+ resolution: {integrity: sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -276,14 +283,14 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/compat-data': 7.19.0
- '@babel/core': 7.19.0
+ '@babel/compat-data': 7.19.4
+ '@babel/core': 7.19.6
'@babel/helper-validator-option': 7.18.6
- browserslist: 4.21.2
+ browserslist: 4.21.4
semver: 6.3.0
dev: true
- /@babel/helper-create-class-features-plugin/7.18.6_@babel+core@7.19.0:
+ /@babel/helper-create-class-features-plugin/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -292,7 +299,7 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-function-name': 7.19.0
@@ -304,7 +311,7 @@ packages:
- supports-color
dev: true
- /@babel/helper-create-regexp-features-plugin/7.18.6_@babel+core@7.19.0:
+ /@babel/helper-create-regexp-features-plugin/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -313,12 +320,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-annotate-as-pure': 7.18.6
regexpu-core: 5.1.0
dev: true
- /@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.19.0:
+ /@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==}
peerDependencies:
'@babel/core': ^7.4.0-0
@@ -326,11 +333,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-compilation-targets': 7.19.0_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.19.6
'@babel/helper-module-imports': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/traverse': 7.19.0
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/traverse': 7.19.6
debug: 4.3.4
lodash.debounce: 4.0.8
resolve: 1.22.1
@@ -348,7 +355,7 @@ packages:
resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-function-name/7.19.0:
@@ -356,21 +363,21 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.10
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-hoist-variables/7.18.6:
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-member-expression-to-functions/7.18.6:
resolution: {integrity: sha512-CeHxqwwipekotzPDUuJOfIMtcIHBuc7WAzLmTYWctVigqS5RktNMQ5bEwQSuGewzYnCtTWa3BARXeiLxDTv+Ng==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-module-imports/7.18.6:
@@ -389,24 +396,24 @@ packages:
'@babel/helper-split-export-declaration': 7.18.6
'@babel/helper-validator-identifier': 7.18.6
'@babel/template': 7.18.10
- '@babel/traverse': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/traverse': 7.19.6
+ '@babel/types': 7.19.4
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/helper-module-transforms/7.19.0:
- resolution: {integrity: sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==}
+ /@babel/helper-module-transforms/7.19.6:
+ resolution: {integrity: sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-module-imports': 7.18.6
- '@babel/helper-simple-access': 7.18.6
+ '@babel/helper-simple-access': 7.19.4
'@babel/helper-split-export-declaration': 7.18.6
- '@babel/helper-validator-identifier': 7.18.6
+ '@babel/helper-validator-identifier': 7.19.1
'@babel/template': 7.18.10
- '@babel/traverse': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/traverse': 7.19.6
+ '@babel/types': 7.19.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -415,14 +422,19 @@ packages:
resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-plugin-utils/7.18.9:
resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==}
engines: {node: '>=6.9.0'}
- /@babel/helper-remap-async-to-generator/7.18.6_@babel+core@7.19.0:
+ /@babel/helper-plugin-utils/7.19.0:
+ resolution: {integrity: sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-remap-async-to-generator/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-z5wbmV55TveUPZlCLZvxWHtrjuJd+8inFhk7DG0WW87/oJuGDcjDiu7HIvGcpf5464L6xKCg3vNkmlVVz9hwyQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -431,11 +443,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-wrap-function': 7.18.6
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -447,8 +459,8 @@ packages:
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-member-expression-to-functions': 7.18.6
'@babel/helper-optimise-call-expression': 7.18.6
- '@babel/traverse': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/traverse': 7.19.6
+ '@babel/types': 7.19.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -457,31 +469,47 @@ packages:
resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.18.10
+ '@babel/types': 7.19.4
+ dev: true
+
+ /@babel/helper-simple-access/7.19.4:
+ resolution: {integrity: sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-skip-transparent-expression-wrappers/7.18.6:
resolution: {integrity: sha512-4KoLhwGS9vGethZpAhYnMejWkX64wsnHPDwvOsKWU6Fg4+AlK2Jz3TyjQLMEPvz+1zemi/WBdkYxCD0bAfIkiw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-split-export-declaration/7.18.6:
resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/helper-string-parser/7.18.10:
resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==}
engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-string-parser/7.19.4:
+ resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
+ engines: {node: '>=6.9.0'}
/@babel/helper-validator-identifier/7.18.6:
resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==}
engines: {node: '>=6.9.0'}
+ /@babel/helper-validator-identifier/7.19.1:
+ resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
+ engines: {node: '>=6.9.0'}
+
/@babel/helper-validator-option/7.18.6:
resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==}
engines: {node: '>=6.9.0'}
@@ -493,8 +521,8 @@ packages:
dependencies:
'@babel/helper-function-name': 7.19.0
'@babel/template': 7.18.10
- '@babel/traverse': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/traverse': 7.19.6
+ '@babel/types': 7.19.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -504,19 +532,19 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.10
- '@babel/traverse': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/traverse': 7.19.6
+ '@babel/types': 7.19.4
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/helpers/7.19.0:
- resolution: {integrity: sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==}
+ /@babel/helpers/7.19.4:
+ resolution: {integrity: sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.10
- '@babel/traverse': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/traverse': 7.19.6
+ '@babel/types': 7.19.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -534,7 +562,7 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/parser/7.19.0:
@@ -542,10 +570,18 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
- /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.19.0:
+ /@babel/parser/7.19.6:
+ resolution: {integrity: sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.19.4
+ dev: true
+
+ /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -554,11 +590,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-Udgu8ZRgrBrttVz6A0EVL0SJ1z+RLbIeqsu632SA1hf0awEppD6TvdznoH+orIF8wtFFAV/Enmw9Y+9oV8TQcw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -567,13 +603,13 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-skip-transparent-expression-wrappers': 7.18.6
- '@babel/plugin-proposal-optional-chaining': 7.18.6_@babel+core@7.19.0
+ '@babel/plugin-proposal-optional-chaining': 7.18.6_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-async-generator-functions/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-async-generator-functions/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -582,16 +618,16 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-environment-visitor': 7.18.9
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-remap-async-to-generator': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.0
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/helper-remap-async-to-generator': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.6
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-class-properties/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-class-properties/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -600,14 +636,14 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-class-static-block/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-class-static-block/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -616,15 +652,15 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.19.6
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-decorators/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-decorators/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-gAdhsjaYmiZVxx5vTMiRfj31nB7LhwBJFMSLzeDxc7X4tKLixup0+k9ughn0RcpBrv9E3PBaXJW7jF5TCihAOg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -633,17 +669,17 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-replace-supers': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
- '@babel/plugin-syntax-decorators': 7.18.6_@babel+core@7.19.0
+ '@babel/plugin-syntax-decorators': 7.18.6_@babel+core@7.19.6
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-dynamic-import/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-dynamic-import/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -652,12 +688,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-export-namespace-from/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-export-namespace-from/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-zr/QcUlUo7GPo6+X1wC98NJADqmy5QTFWWhqeQWiki4XHafJtLl/YMGkmRB2szDD2IYJCCdBTd4ElwhId9T7Xw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -666,12 +702,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-json-strings/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-json-strings/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -680,12 +716,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-logical-assignment-operators/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-logical-assignment-operators/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-zMo66azZth/0tVd7gmkxOkOjs2rpHyhpcFo565PUP37hSp6hSd9uUKIfTDFMz58BwqgQKhJ9YxtM5XddjXVn+Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -694,12 +730,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-nullish-coalescing-operator/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-nullish-coalescing-operator/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -708,12 +744,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-numeric-separator/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-numeric-separator/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -722,12 +758,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-object-rest-spread/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-object-rest-spread/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-9yuM6wr4rIsKa1wlUAbZEazkCrgw2sMPEXCr4Rnwetu7cEW1NydkCWytLuYletbf8vFxdJxFhwEZqMpOx2eZyw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -736,15 +772,15 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/compat-data': 7.19.0
- '@babel/core': 7.19.0
- '@babel/helper-compilation-targets': 7.19.0_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.19.0
+ '@babel/compat-data': 7.19.4
+ '@babel/core': 7.19.6
+ '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-optional-catch-binding/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-optional-catch-binding/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -753,12 +789,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-optional-chaining/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-optional-chaining/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-PatI6elL5eMzoypFAiYDpYQyMtXTn+iMhuxxQt5mAXD4fEmKorpSI3PHd+i3JXBJN3xyA6MvJv7at23HffFHwA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -767,13 +803,13 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-skip-transparent-expression-wrappers': 7.18.6
- '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.0
+ '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.6
dev: true
- /@babel/plugin-proposal-private-methods/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-private-methods/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -782,14 +818,14 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-private-property-in-object/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-private-property-in-object/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -798,16 +834,16 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-annotate-as-pure': 7.18.6
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.19.0
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.19.6
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-proposal-unicode-property-regex/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-proposal-unicode-property-regex/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==}
engines: {node: '>=4'}
peerDependencies:
@@ -816,9 +852,9 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.18.6:
@@ -830,10 +866,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.19.0:
+ /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.19.6:
resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -841,8 +877,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.18.6:
@@ -854,10 +890,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.19.0:
+ /@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.19.6:
resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -865,8 +901,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.18.6:
@@ -878,10 +914,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.19.0:
+ /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.19.6:
resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -889,11 +925,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.19.0:
+ /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.19.6:
resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -902,11 +938,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-decorators/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-syntax-decorators/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-fqyLgjcxf/1yhyZ6A+yo1u9gJ7eleFQod2lkaUsF9DQ7sbbY3Ligym3L0+I2c0WmqNKDpoD9UTb1AKP3qRMOAQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -915,11 +951,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.19.0:
+ /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.19.6:
resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -927,11 +963,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.19.0:
+ /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.19.6:
resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -939,11 +975,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-flow/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-syntax-flow/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -952,11 +988,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-import-assertions/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-syntax-import-assertions/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -965,8 +1001,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.18.6:
@@ -978,10 +1014,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.19.0:
+ /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.19.6:
resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -989,8 +1025,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.18.6:
@@ -1002,10 +1038,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.19.0:
+ /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.19.6:
resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1013,8 +1049,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-jsx/7.18.6:
@@ -1029,7 +1065,7 @@ packages:
'@babel/helper-plugin-utils': 7.18.9
dev: false
- /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1038,7 +1074,7 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-plugin-utils': 7.18.9
dev: true
@@ -1051,10 +1087,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.19.0:
+ /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.19.6:
resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1062,8 +1098,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.18.6:
@@ -1075,10 +1111,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.19.0:
+ /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.19.6:
resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1086,8 +1122,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.18.6:
@@ -1099,10 +1135,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.19.0:
+ /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.19.6:
resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1110,8 +1146,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.18.6:
@@ -1123,10 +1159,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.19.0:
+ /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.19.6:
resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1134,8 +1170,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.18.6:
@@ -1147,10 +1183,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.19.0:
+ /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.19.6:
resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1158,8 +1194,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.18.6:
@@ -1171,10 +1207,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.19.0:
+ /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.19.6:
resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1182,11 +1218,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.19.0:
+ /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.19.6:
resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1195,8 +1231,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
/@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.18.6:
@@ -1209,10 +1245,10 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.19.0:
+ /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.19.6:
resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1221,11 +1257,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-syntax-typescript/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-syntax-typescript/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1234,11 +1270,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-arrow-functions/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-arrow-functions/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1247,11 +1283,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-async-to-generator/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-async-to-generator/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1260,15 +1296,15 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-module-imports': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-remap-async-to-generator': 7.18.6_@babel+core@7.19.0
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/helper-remap-async-to-generator': 7.18.6_@babel+core@7.19.6
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-block-scoped-functions/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-block-scoped-functions/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1277,11 +1313,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-block-scoping/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-block-scoping/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-pRqwb91C42vs1ahSAWJkxOxU1RHWDn16XAa6ggQ72wjLlWyYeAcLvTtE0aM8ph3KNydy9CQF2nLYcjq1WysgxQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1290,11 +1326,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-classes/7.18.8_@babel+core@7.19.0:
+ /@babel/plugin-transform-classes/7.18.8_@babel+core@7.19.6:
resolution: {integrity: sha512-RySDoXdF6hgHSHuAW4aLGyVQdmvEX/iJtjVre52k0pxRq4hzqze+rAVP++NmNv596brBpYmaiKgTZby7ziBnVg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1303,12 +1339,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-function-name': 7.19.0
'@babel/helper-optimise-call-expression': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-replace-supers': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
globals: 11.12.0
@@ -1316,7 +1352,7 @@ packages:
- supports-color
dev: true
- /@babel/plugin-transform-computed-properties/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-computed-properties/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-9repI4BhNrR0KenoR9vm3/cIc1tSBIo+u1WVjKCAynahj25O8zfbiE6JtAtHPGQSs4yZ+bA8mRasRP+qc+2R5A==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1325,11 +1361,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-destructuring/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-destructuring/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-tgy3u6lRp17ilY8r1kP4i2+HDUwxlVqq3RTc943eAWSzGgpU1qhiKpqZ5CMyHReIYPHdo3Kg8v8edKtDqSVEyQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1338,11 +1374,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-dotall-regex/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-dotall-regex/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1351,12 +1387,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-duplicate-keys/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-duplicate-keys/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-NJU26U/208+sxYszf82nmGYqVF9QN8py2HFTblPT9hbawi8+1C5a9JubODLTGFuT0qlkqVinmkwOD13s0sZktg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1365,11 +1401,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-exponentiation-operator/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-exponentiation-operator/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1378,12 +1414,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-builder-binary-assignment-operator-visitor': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-flow-strip-types/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-flow-strip-types/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-wE0xtA7csz+hw4fKPwxmu5jnzAsXPIO57XnRwzXP3T19jWh1BODnPGoG9xKYwvAwusP7iUktHayRFbMPGtODaQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1392,12 +1428,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-flow': 7.18.6_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-flow': 7.18.6_@babel+core@7.19.6
dev: true
- /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.19.0:
+ /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.19.6:
resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1406,11 +1442,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-function-name/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-function-name/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-kJha/Gbs5RjzIu0CxZwf5e3aTTSlhZnHMT8zPWnJMjNpLOUgqevg+PN5oMH68nMCXnfiMo4Bhgxqj59KHTlAnA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1419,13 +1455,13 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-compilation-targets': 7.19.0_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.19.6
'@babel/helper-function-name': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-literals/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-literals/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-x3HEw0cJZVDoENXOp20HlypIHfl0zMIhMVZEBVTfmqbObIpsMxMbmU5nOEO8R7LYT+z5RORKPlTI5Hj4OsO9/Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1434,11 +1470,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-member-expression-literals/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-member-expression-literals/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1447,11 +1483,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-modules-amd/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-modules-amd/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1460,9 +1496,9 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-module-transforms': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-module-transforms': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
babel-plugin-dynamic-import-node: 2.3.3
transitivePeerDependencies:
- supports-color
@@ -1486,7 +1522,7 @@ packages:
- supports-color
dev: true
- /@babel/plugin-transform-modules-commonjs/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-modules-commonjs/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1495,7 +1531,7 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-module-transforms': 7.18.9
'@babel/helper-plugin-utils': 7.18.9
'@babel/helper-simple-access': 7.18.6
@@ -1504,7 +1540,7 @@ packages:
- supports-color
dev: true
- /@babel/plugin-transform-modules-systemjs/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-modules-systemjs/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-UbPYpXxLjTw6w6yXX2BYNxF3p6QY225wcTkfQCy3OMnSlS/C3xGtwUjEzGkldb/sy6PWLiCQ3NbYfjWUTI3t4g==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1513,17 +1549,17 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-hoist-variables': 7.18.6
- '@babel/helper-module-transforms': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/helper-validator-identifier': 7.18.6
+ '@babel/helper-module-transforms': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/helper-validator-identifier': 7.19.1
babel-plugin-dynamic-import-node: 2.3.3
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-modules-umd/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-modules-umd/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1532,14 +1568,14 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-module-transforms': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-module-transforms': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-named-capturing-groups-regex/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-named-capturing-groups-regex/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1548,12 +1584,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-new-target/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-new-target/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1562,11 +1598,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-object-super/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-object-super/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1575,14 +1611,14 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-replace-supers': 7.18.6
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-parameters/7.18.8_@babel+core@7.19.0:
+ /@babel/plugin-transform-parameters/7.18.8_@babel+core@7.19.6:
resolution: {integrity: sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1591,11 +1627,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-property-literals/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-property-literals/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1604,11 +1640,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-react-display-name/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-react-display-name/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1617,11 +1653,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-react-jsx-development/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-react-jsx-development/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1630,11 +1666,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/plugin-transform-react-jsx': 7.18.10_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.6
dev: true
- /@babel/plugin-transform-react-jsx-self/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-react-jsx-self/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1643,12 +1679,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-plugin-utils': 7.18.9
dev: true
- /@babel/plugin-transform-react-jsx-source/7.18.6_@babel+core@7.19.0:
- resolution: {integrity: sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw==}
+ /@babel/plugin-transform-react-jsx-source/7.19.6_@babel+core@7.19.6:
+ resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1656,12 +1692,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-react-jsx/7.18.10_@babel+core@7.19.0:
- resolution: {integrity: sha512-gCy7Iikrpu3IZjYZolFE4M1Sm+nrh1/6za2Ewj77Z+XirT4TsbJcvOFOyF+fRPwU6AKKK136CZxx6L8AbSFG6A==}
+ /@babel/plugin-transform-react-jsx/7.19.0_@babel+core@7.19.6:
+ resolution: {integrity: sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1669,15 +1705,15 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-module-imports': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.0
- '@babel/types': 7.19.0
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.6
+ '@babel/types': 7.19.4
dev: true
- /@babel/plugin-transform-react-pure-annotations/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-react-pure-annotations/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1686,12 +1722,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-annotate-as-pure': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-regenerator/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-regenerator/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1700,12 +1736,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
regenerator-transform: 0.15.0
dev: true
- /@babel/plugin-transform-reserved-words/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-reserved-words/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1714,11 +1750,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-runtime/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-runtime/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-8uRHk9ZmRSnWqUgyae249EJZ94b0yAGLBIqzZzl+0iEdbno55Pmlt/32JZsHwXD9k/uZj18Aqqk35wBX4CBTXA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1727,18 +1763,18 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@babel/helper-module-imports': 7.18.6
- '@babel/helper-plugin-utils': 7.18.9
- babel-plugin-polyfill-corejs2: 0.3.1_@babel+core@7.19.0
- babel-plugin-polyfill-corejs3: 0.5.2_@babel+core@7.19.0
- babel-plugin-polyfill-regenerator: 0.3.1_@babel+core@7.19.0
+ '@babel/helper-plugin-utils': 7.19.0
+ babel-plugin-polyfill-corejs2: 0.3.1_@babel+core@7.19.6
+ babel-plugin-polyfill-corejs3: 0.5.2_@babel+core@7.19.6
+ babel-plugin-polyfill-regenerator: 0.3.1_@babel+core@7.19.6
semver: 6.3.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-shorthand-properties/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-shorthand-properties/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1747,11 +1783,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-spread/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-spread/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-ayT53rT/ENF8WWexIRg9AiV9h0aIteyWn5ptfZTZQrjk/+f3WdrJGCY4c9wcgl2+MKkKPhzbYp97FTsquZpDCw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1760,12 +1796,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-skip-transparent-expression-wrappers': 7.18.6
dev: true
- /@babel/plugin-transform-sticky-regex/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-sticky-regex/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1774,11 +1810,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-template-literals/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-template-literals/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-UuqlRrQmT2SWRvahW46cGSany0uTlcj8NYOS5sRGYi8FxPYPoLd5DDmMd32ZXEj2Jq+06uGVQKHxa/hJx2EzKw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1787,11 +1823,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-typeof-symbol/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-typeof-symbol/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-7m71iS/QhsPk85xSjFPovHPcH3H9qeyzsujhTc+vcdnsXavoWYJ74zx0lP5RhpC5+iDnVLO+PPMHzC11qels1g==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1800,11 +1836,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-typescript/7.18.8_@babel+core@7.19.0:
+ /@babel/plugin-transform-typescript/7.18.8_@babel+core@7.19.6:
resolution: {integrity: sha512-p2xM8HI83UObjsZGofMV/EdYjamsDm6MoN3hXPYIT0+gxIoopE+B7rPYKAxfrz9K9PK7JafTTjqYC6qipLExYA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1813,15 +1849,15 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-create-class-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.19.6
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/plugin-transform-unicode-escapes/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-unicode-escapes/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-XNRwQUXYMP7VLuy54cr/KS/WeL3AZeORhrmeZ7iewgu+X2eBqmpaLI/hzqr9ZxCeUoq0ASK4GUzSM0BDhZkLFw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1830,11 +1866,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/plugin-transform-unicode-regex/7.18.6_@babel+core@7.19.0:
+ /@babel/plugin-transform-unicode-regex/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1843,12 +1879,12 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-create-regexp-features-plugin': 7.18.6_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
dev: true
- /@babel/preset-env/7.18.6_@babel+core@7.19.0:
+ /@babel/preset-env/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1857,87 +1893,87 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/compat-data': 7.19.0
- '@babel/core': 7.19.0
- '@babel/helper-compilation-targets': 7.19.0_@babel+core@7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/compat-data': 7.19.4
+ '@babel/core': 7.19.6
+ '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-validator-option': 7.18.6
- '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-async-generator-functions': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-class-static-block': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-dynamic-import': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-export-namespace-from': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-json-strings': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-logical-assignment-operators': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-object-rest-spread': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-optional-catch-binding': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-optional-chaining': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-private-property-in-object': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.0
- '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.0
- '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.19.0
- '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-import-assertions': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.0
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.0
- '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.19.0
- '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.0
- '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-async-to-generator': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-block-scoping': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-classes': 7.18.8_@babel+core@7.19.0
- '@babel/plugin-transform-computed-properties': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-destructuring': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-duplicate-keys': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-exponentiation-operator': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.19.0
- '@babel/plugin-transform-function-name': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-literals': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-modules-amd': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-modules-commonjs': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-modules-systemjs': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-modules-umd': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-named-capturing-groups-regex': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-new-target': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.19.0
- '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-regenerator': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-reserved-words': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-spread': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-sticky-regex': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-template-literals': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-typeof-symbol': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-unicode-escapes': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.19.0
- '@babel/preset-modules': 0.1.5_@babel+core@7.19.0
- '@babel/types': 7.19.0
- babel-plugin-polyfill-corejs2: 0.3.1_@babel+core@7.19.0
- babel-plugin-polyfill-corejs3: 0.5.2_@babel+core@7.19.0
- babel-plugin-polyfill-regenerator: 0.3.1_@babel+core@7.19.0
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-async-generator-functions': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-class-static-block': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-dynamic-import': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-export-namespace-from': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-json-strings': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-logical-assignment-operators': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-object-rest-spread': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-optional-catch-binding': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-optional-chaining': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-private-property-in-object': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.6
+ '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.6
+ '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.19.6
+ '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-import-assertions': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.6
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.6
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.19.6
+ '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.6
+ '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-async-to-generator': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-block-scoping': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-classes': 7.18.8_@babel+core@7.19.6
+ '@babel/plugin-transform-computed-properties': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-destructuring': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-duplicate-keys': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-exponentiation-operator': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.19.6
+ '@babel/plugin-transform-function-name': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-literals': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-modules-amd': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-modules-commonjs': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-modules-systemjs': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-modules-umd': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-named-capturing-groups-regex': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-new-target': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.19.6
+ '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-regenerator': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-reserved-words': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-spread': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-sticky-regex': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-template-literals': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-typeof-symbol': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-unicode-escapes': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.19.6
+ '@babel/preset-modules': 0.1.5_@babel+core@7.19.6
+ '@babel/types': 7.19.4
+ babel-plugin-polyfill-corejs2: 0.3.1_@babel+core@7.19.6
+ babel-plugin-polyfill-corejs3: 0.5.2_@babel+core@7.19.6
+ babel-plugin-polyfill-regenerator: 0.3.1_@babel+core@7.19.6
core-js-compat: 3.23.4
semver: 6.3.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/preset-modules/0.1.5_@babel+core@7.19.0:
+ /@babel/preset-modules/0.1.5_@babel+core@7.19.6:
resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1945,15 +1981,15 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
- '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.19.0
- '@babel/types': 7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
+ '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.19.6
+ '@babel/types': 7.19.4
esutils: 2.0.3
dev: true
- /@babel/preset-react/7.18.6_@babel+core@7.19.0:
+ /@babel/preset-react/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1962,16 +1998,16 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-validator-option': 7.18.6
- '@babel/plugin-transform-react-display-name': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-react-jsx': 7.18.10_@babel+core@7.19.0
- '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-react-pure-annotations': 7.18.6_@babel+core@7.19.0
+ '@babel/plugin-transform-react-display-name': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.6
+ '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-react-pure-annotations': 7.18.6_@babel+core@7.19.6
dev: true
- /@babel/preset-typescript/7.18.6_@babel+core@7.19.0:
+ /@babel/preset-typescript/7.18.6_@babel+core@7.19.6:
resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -1980,10 +2016,10 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/helper-plugin-utils': 7.19.0
'@babel/helper-validator-option': 7.18.6
- '@babel/plugin-transform-typescript': 7.18.8_@babel+core@7.19.0
+ '@babel/plugin-transform-typescript': 7.18.8_@babel+core@7.19.6
transitivePeerDependencies:
- supports-color
dev: true
@@ -2001,6 +2037,19 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.9
+ dev: true
+
+ /@babel/runtime/7.19.0:
+ resolution: {integrity: sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ regenerator-runtime: 0.13.9
+
+ /@babel/runtime/7.19.4:
+ resolution: {integrity: sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ regenerator-runtime: 0.13.9
/@babel/template/7.18.10:
resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==}
@@ -2008,7 +2057,7 @@ packages:
dependencies:
'@babel/code-frame': 7.18.6
'@babel/parser': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@babel/traverse/7.18.11:
@@ -2016,31 +2065,31 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
- '@babel/generator': 7.19.0
+ '@babel/generator': 7.19.6
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-function-name': 7.19.0
'@babel/helper-hoist-variables': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
'@babel/parser': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: true
- /@babel/traverse/7.19.0:
- resolution: {integrity: sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==}
+ /@babel/traverse/7.19.6:
+ resolution: {integrity: sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
- '@babel/generator': 7.19.0
+ '@babel/generator': 7.19.6
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-function-name': 7.19.0
'@babel/helper-hoist-variables': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
- '@babel/parser': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/parser': 7.19.6
+ '@babel/types': 7.19.4
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
@@ -2060,10 +2109,19 @@ packages:
resolution: {integrity: sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/helper-string-parser': 7.18.10
- '@babel/helper-validator-identifier': 7.18.6
+ '@babel/helper-string-parser': 7.19.4
+ '@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
+ /@babel/types/7.19.4:
+ resolution: {integrity: sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.19.4
+ '@babel/helper-validator-identifier': 7.19.1
+ to-fast-properties: 2.0.0
+ dev: true
+
/@bcoe/v8-coverage/0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true
@@ -2077,15 +2135,15 @@ packages:
minimist: 1.2.6
dev: true
- /@commitlint/cli/17.1.2:
- resolution: {integrity: sha512-h/4Hlka3bvCLbnxf0Er2ri5A44VMlbMSkdTRp8Adv2tRiklSTRIoPGs7OEXDv3EoDs2AAzILiPookgM4Gi7LOw==}
+ /@commitlint/cli/17.2.0:
+ resolution: {integrity: sha512-kd1zykcrjIKyDRftWW1E1TJqkgzeosEkv1BiYPCdzkb/g/3BrfgwZUHR1vg+HO3qKUb/0dN+jNXArhGGAHpmaQ==}
engines: {node: '>=v14'}
hasBin: true
dependencies:
'@commitlint/format': 17.0.0
- '@commitlint/lint': 17.1.0
- '@commitlint/load': 17.1.2
- '@commitlint/read': 17.1.0
+ '@commitlint/lint': 17.2.0
+ '@commitlint/load': 17.2.0
+ '@commitlint/read': 17.2.0
'@commitlint/types': 17.0.0
execa: 5.1.1
lodash: 4.17.21
@@ -2097,8 +2155,8 @@ packages:
- '@swc/wasm'
dev: true
- /@commitlint/config-conventional/17.1.0:
- resolution: {integrity: sha512-WU2p0c9/jLi8k2q2YrDV96Y8XVswQOceIQ/wyJvQxawJSCasLdRB3kUIYdNjOCJsxkpoUlV/b90ZPxp1MYZDiA==}
+ /@commitlint/config-conventional/17.2.0:
+ resolution: {integrity: sha512-g5hQqRa80f++SYS233dbDSg16YdyounMTAhVcmqtInNeY/GF3aA4st9SVtJxpeGrGmueMrU4L+BBb+6Vs5wrcg==}
engines: {node: '>=v14'}
dependencies:
conventional-changelog-conventionalcommits: 5.0.0
@@ -2133,26 +2191,26 @@ packages:
chalk: 4.1.2
dev: true
- /@commitlint/is-ignored/17.1.0:
- resolution: {integrity: sha512-JITWKDMHhIh8IpdIbcbuH9rEQJty1ZWelgjleTFrVRAcEwN/sPzk1aVUXRIZNXMJWbZj8vtXRJnFihrml8uECQ==}
+ /@commitlint/is-ignored/17.2.0:
+ resolution: {integrity: sha512-rgUPUQraHxoMLxiE8GK430HA7/R2vXyLcOT4fQooNrZq9ERutNrP6dw3gdKLkq22Nede3+gEHQYUzL4Wu75ndg==}
engines: {node: '>=v14'}
dependencies:
'@commitlint/types': 17.0.0
semver: 7.3.7
dev: true
- /@commitlint/lint/17.1.0:
- resolution: {integrity: sha512-ltpqM2ogt/+SDhUaScFo0MdscncEF96lvQTPMM/VTTWlw7sTGLLWkOOppsee2MN/uLNNWjQ7kqkd4h6JqoM9AQ==}
+ /@commitlint/lint/17.2.0:
+ resolution: {integrity: sha512-N2oLn4Dj672wKH5qJ4LGO+73UkYXGHO+NTVUusGw83SjEv7GjpqPGKU6KALW2kFQ/GsDefSvOjpSi3CzWHQBDg==}
engines: {node: '>=v14'}
dependencies:
- '@commitlint/is-ignored': 17.1.0
- '@commitlint/parse': 17.0.0
- '@commitlint/rules': 17.0.0
+ '@commitlint/is-ignored': 17.2.0
+ '@commitlint/parse': 17.2.0
+ '@commitlint/rules': 17.2.0
'@commitlint/types': 17.0.0
dev: true
- /@commitlint/load/17.1.2:
- resolution: {integrity: sha512-sk2p/jFYAWLChIfOIp/MGSIn/WzZ0vkc3afw+l4X8hGEYkvDe4gQUUAVxjl/6xMRn0HgnSLMZ04xXh5pkTsmgg==}
+ /@commitlint/load/17.2.0:
+ resolution: {integrity: sha512-HDD57qSqNrk399R4TIjw31AWBG8dBjNj1MrDKZKmC/wvimtnIFlqzcu1+sxfXIOHj/+M6tcMWDtvknGUd7SU+g==}
engines: {node: '>=v14'}
dependencies:
'@commitlint/config-validator': 17.1.0
@@ -2162,23 +2220,23 @@ packages:
'@types/node': 14.18.26
chalk: 4.1.2
cosmiconfig: 7.0.1
- cosmiconfig-typescript-loader: 4.0.0_phiamshn6hdmqb4hbjl227ychy
+ cosmiconfig-typescript-loader: 4.0.0_6y7t74n3veizl5rsceyhsjfzxy
lodash: 4.17.21
resolve-from: 5.0.0
- ts-node: 10.9.0_xwfh4scogix3vdbcy4yenbcgby
- typescript: 4.8.3
+ ts-node: 10.9.0_kvex4k72pyqcsdotmwd3oetkri
+ typescript: 4.8.4
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
dev: true
- /@commitlint/message/17.0.0:
- resolution: {integrity: sha512-LpcwYtN+lBlfZijHUdVr8aNFTVpHjuHI52BnfoV01TF7iSLnia0jttzpLkrLmI8HNQz6Vhr9UrxDWtKZiMGsBw==}
+ /@commitlint/message/17.2.0:
+ resolution: {integrity: sha512-/4l2KFKxBOuoEn1YAuuNNlAU05Zt7sNsC9H0mPdPm3chOrT4rcX0pOqrQcLtdMrMkJz0gC7b3SF80q2+LtdL9Q==}
engines: {node: '>=v14'}
dev: true
- /@commitlint/parse/17.0.0:
- resolution: {integrity: sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==}
+ /@commitlint/parse/17.2.0:
+ resolution: {integrity: sha512-vLzLznK9Y21zQ6F9hf8D6kcIJRb2haAK5T/Vt1uW2CbHYOIfNsR/hJs0XnF/J9ctM20Tfsqv4zBitbYvVw7F6Q==}
engines: {node: '>=v14'}
dependencies:
'@commitlint/types': 17.0.0
@@ -2186,8 +2244,8 @@ packages:
conventional-commits-parser: 3.2.4
dev: true
- /@commitlint/read/17.1.0:
- resolution: {integrity: sha512-73BoFNBA/3Ozo2JQvGsE0J8SdrJAWGfZQRSHqvKaqgmY042Su4gXQLqvAzgr55S9DI1l9TiU/5WDuh8IE86d/g==}
+ /@commitlint/read/17.2.0:
+ resolution: {integrity: sha512-bbblBhrHkjxra3ptJNm0abxu7yeAaxumQ8ZtD6GIVqzURCETCP7Dm0tlVvGRDyXBuqX6lIJxh3W7oyKqllDsHQ==}
engines: {node: '>=v14'}
dependencies:
'@commitlint/top-level': 17.0.0
@@ -2209,12 +2267,12 @@ packages:
resolve-global: 1.0.0
dev: true
- /@commitlint/rules/17.0.0:
- resolution: {integrity: sha512-45nIy3dERKXWpnwX9HeBzK5SepHwlDxdGBfmedXhL30fmFCkJOdxHyOJsh0+B0RaVsLGT01NELpfzJUmtpDwdQ==}
+ /@commitlint/rules/17.2.0:
+ resolution: {integrity: sha512-1YynwD4Eh7HXZNpqG8mtUlL2pSX2jBy61EejYJv4ooZPcg50Ak7LPOyD3a9UZnsE76AXWFBz+yo9Hv4MIpAa0Q==}
engines: {node: '>=v14'}
dependencies:
'@commitlint/ensure': 17.0.0
- '@commitlint/message': 17.0.0
+ '@commitlint/message': 17.2.0
'@commitlint/to-lines': 17.0.0
'@commitlint/types': 17.0.0
execa: 5.1.1
@@ -2250,8 +2308,8 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
- /@emotion/babel-plugin/11.10.0:
- resolution: {integrity: sha512-xVnpDAAbtxL1dsuSelU5A7BnY/lftws0wUexNJZTPsvX/1tM4GZJbclgODhvW4E+NH7E5VFcH0bBn30NvniPJA==}
+ /@emotion/babel-plugin/11.10.5:
+ resolution: {integrity: sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==}
peerDependencies:
'@babel/core': ^7.0.0
peerDependenciesMeta:
@@ -2260,26 +2318,26 @@ packages:
dependencies:
'@babel/helper-module-imports': 7.18.6
'@babel/plugin-syntax-jsx': 7.18.6
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
'@emotion/hash': 0.9.0
'@emotion/memoize': 0.8.0
- '@emotion/serialize': 1.1.0
+ '@emotion/serialize': 1.1.1
babel-plugin-macros: 3.1.0
convert-source-map: 1.8.0
escape-string-regexp: 4.0.0
find-root: 1.1.0
source-map: 0.5.7
- stylis: 4.0.13
+ stylis: 4.1.3
dev: false
- /@emotion/cache/11.10.3:
- resolution: {integrity: sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==}
+ /@emotion/cache/11.10.5:
+ resolution: {integrity: sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==}
dependencies:
'@emotion/memoize': 0.8.0
- '@emotion/sheet': 1.2.0
+ '@emotion/sheet': 1.2.1
'@emotion/utils': 1.2.0
'@emotion/weak-memoize': 0.3.0
- stylis: 4.0.13
+ stylis: 4.1.3
dev: false
/@emotion/hash/0.9.0:
@@ -2296,8 +2354,8 @@ packages:
resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==}
dev: false
- /@emotion/react/11.10.4_fn6mmk7tilns4p7ajkklhuudvi:
- resolution: {integrity: sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==}
+ /@emotion/react/11.10.5_fan5qbzahqtxlm5dzefqlqx5ia:
+ resolution: {integrity: sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==}
peerDependencies:
'@babel/core': ^7.0.0
'@types/react': '*'
@@ -2308,34 +2366,34 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
- '@emotion/babel-plugin': 11.10.0
- '@emotion/cache': 11.10.3
- '@emotion/serialize': 1.1.0
+ '@babel/runtime': 7.19.4
+ '@emotion/babel-plugin': 11.10.5
+ '@emotion/cache': 11.10.5
+ '@emotion/serialize': 1.1.1
'@emotion/use-insertion-effect-with-fallbacks': 1.0.0_react@18.2.0
'@emotion/utils': 1.2.0
'@emotion/weak-memoize': 0.3.0
- '@types/react': 18.0.19
+ '@types/react': 18.0.25
hoist-non-react-statics: 3.3.2
react: 18.2.0
dev: false
- /@emotion/serialize/1.1.0:
- resolution: {integrity: sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==}
+ /@emotion/serialize/1.1.1:
+ resolution: {integrity: sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==}
dependencies:
'@emotion/hash': 0.9.0
'@emotion/memoize': 0.8.0
'@emotion/unitless': 0.8.0
'@emotion/utils': 1.2.0
- csstype: 3.1.0
+ csstype: 3.1.1
dev: false
- /@emotion/sheet/1.2.0:
- resolution: {integrity: sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w==}
+ /@emotion/sheet/1.2.1:
+ resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==}
dev: false
- /@emotion/styled/11.10.4_v6omebeixt23oq3ljl3rjllla4:
- resolution: {integrity: sha512-pRl4R8Ez3UXvOPfc2bzIoV8u9P97UedgHS4FPX594ntwEuAMA114wlaHvOK24HB48uqfXiGlYIZYCxVJ1R1ttQ==}
+ /@emotion/styled/11.10.5_otcjdfkheatawhug6fwk7ldzni:
+ resolution: {integrity: sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==}
peerDependencies:
'@babel/core': ^7.0.0
'@emotion/react': ^11.0.0-rc.0
@@ -2347,14 +2405,14 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
- '@emotion/babel-plugin': 11.10.0
+ '@babel/runtime': 7.19.4
+ '@emotion/babel-plugin': 11.10.5
'@emotion/is-prop-valid': 1.2.0
- '@emotion/react': 11.10.4_fn6mmk7tilns4p7ajkklhuudvi
- '@emotion/serialize': 1.1.0
+ '@emotion/react': 11.10.5_fan5qbzahqtxlm5dzefqlqx5ia
+ '@emotion/serialize': 1.1.1
'@emotion/use-insertion-effect-with-fallbacks': 1.0.0_react@18.2.0
'@emotion/utils': 1.2.0
- '@types/react': 18.0.19
+ '@types/react': 18.0.25
react: 18.2.0
dev: false
@@ -2378,8 +2436,17 @@ packages:
resolution: {integrity: sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==}
dev: false
- /@esbuild/linux-loong64/0.15.7:
- resolution: {integrity: sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==}
+ /@esbuild/android-arm/0.15.13:
+ resolution: {integrity: sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-loong64/0.15.13:
+ resolution: {integrity: sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
@@ -2387,8 +2454,8 @@ packages:
dev: true
optional: true
- /@eslint/eslintrc/1.3.1:
- resolution: {integrity: sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==}
+ /@eslint/eslintrc/1.3.3:
+ resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
ajv: 6.12.6
@@ -2445,8 +2512,8 @@ packages:
react: 18.2.0
dev: false
- /@humanwhocodes/config-array/0.10.4:
- resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==}
+ /@humanwhocodes/config-array/0.11.6:
+ resolution: {integrity: sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==}
engines: {node: '>=10.10.0'}
dependencies:
'@humanwhocodes/object-schema': 1.2.1
@@ -2456,10 +2523,6 @@ packages:
- supports-color
dev: true
- /@humanwhocodes/gitignore-to-minimatch/1.0.2:
- resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==}
- dev: true
-
/@humanwhocodes/module-importer/1.0.1:
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
engines: {node: '>=12.22'}
@@ -2485,32 +2548,20 @@ packages:
engines: {node: '>=8'}
dev: true
- /@jest/console/28.1.3:
- resolution: {integrity: sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
- dependencies:
- '@jest/types': 28.1.3
- '@types/node': 16.11.58
- chalk: 4.1.2
- jest-message-util: 28.1.3
- jest-util: 28.1.3
- slash: 3.0.0
- dev: true
-
- /@jest/console/29.0.2:
- resolution: {integrity: sha512-Fv02ijyhF4D/Wb3DvZO3iBJQz5DnzpJEIDBDbvje8Em099N889tNMUnBw7SalmSuOI+NflNG40RA1iK71kImPw==}
+ /@jest/console/29.3.1:
+ resolution: {integrity: sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
chalk: 4.1.2
- jest-message-util: 29.0.2
- jest-util: 29.0.2
+ jest-message-util: 29.3.1
+ jest-util: 29.3.1
slash: 3.0.0
dev: true
- /@jest/core/29.0.2:
- resolution: {integrity: sha512-imP5M6cdpHEOkmcuFYZuM5cTG1DAF7ZlVNCq1+F7kbqme2Jcl+Kh4M78hihM76DJHNkurbv4UVOnejGxBKEmww==}
+ /@jest/core/29.3.1:
+ resolution: {integrity: sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
@@ -2518,32 +2569,32 @@ packages:
node-notifier:
optional: true
dependencies:
- '@jest/console': 29.0.2
- '@jest/reporters': 29.0.2
- '@jest/test-result': 29.0.2
- '@jest/transform': 29.0.2
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
+ '@jest/console': 29.3.1
+ '@jest/reporters': 29.3.1
+ '@jest/test-result': 29.3.1
+ '@jest/transform': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.3.2
exit: 0.1.2
graceful-fs: 4.2.10
- jest-changed-files: 29.0.0
- jest-config: 29.0.2_@types+node@16.11.58
- jest-haste-map: 29.0.2
- jest-message-util: 29.0.2
- jest-regex-util: 29.0.0
- jest-resolve: 29.0.2
- jest-resolve-dependencies: 29.0.2
- jest-runner: 29.0.2
- jest-runtime: 29.0.2
- jest-snapshot: 29.0.2
- jest-util: 29.0.2
- jest-validate: 29.0.2
- jest-watcher: 29.0.2
+ jest-changed-files: 29.2.0
+ jest-config: 29.3.1_@types+node@18.11.9
+ jest-haste-map: 29.3.1
+ jest-message-util: 29.3.1
+ jest-regex-util: 29.2.0
+ jest-resolve: 29.3.1
+ jest-resolve-dependencies: 29.3.1
+ jest-runner: 29.3.1
+ jest-runtime: 29.3.1
+ jest-snapshot: 29.3.1
+ jest-util: 29.3.1
+ jest-validate: 29.3.1
+ jest-watcher: 29.3.1
micromatch: 4.0.5
- pretty-format: 29.0.2
+ pretty-format: 29.3.1
slash: 3.0.0
strip-ansi: 6.0.1
transitivePeerDependencies:
@@ -2551,66 +2602,66 @@ packages:
- ts-node
dev: true
- /@jest/environment/29.0.2:
- resolution: {integrity: sha512-Yf+EYaLOrVCgts/aTS5nGznU4prZUPa5k9S63Yct8YSOKj2jkdS17hHSUKhk5jxDFMyCy1PXknypDw7vfgc/mA==}
+ /@jest/environment/29.3.1:
+ resolution: {integrity: sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/fake-timers': 29.0.2
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
- jest-mock: 29.0.2
+ '@jest/fake-timers': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
+ jest-mock: 29.3.1
dev: true
- /@jest/expect-utils/29.0.1:
- resolution: {integrity: sha512-Tw5kUUOKmXGQDmQ9TSgTraFFS7HMC1HG/B7y0AN2G2UzjdAXz9BzK2rmNpCSDl7g7y0Gf/VLBm//blonvhtOTQ==}
+ /@jest/expect-utils/29.2.2:
+ resolution: {integrity: sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- jest-get-type: 29.0.0
+ jest-get-type: 29.2.0
dev: true
- /@jest/expect-utils/29.0.2:
- resolution: {integrity: sha512-+wcQF9khXKvAEi8VwROnCWWmHfsJYCZAs5dmuMlJBKk57S6ZN2/FQMIlo01F29fJyT8kV/xblE7g3vkIdTLOjw==}
+ /@jest/expect-utils/29.3.1:
+ resolution: {integrity: sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- jest-get-type: 29.0.0
+ jest-get-type: 29.2.0
dev: true
- /@jest/expect/29.0.2:
- resolution: {integrity: sha512-y/3geZ92p2/zovBm/F+ZjXUJ3thvT9IRzD6igqaWskFE2aR0idD+N/p5Lj/ZautEox/9RwEc6nqergebeh72uQ==}
+ /@jest/expect/29.3.1:
+ resolution: {integrity: sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- expect: 29.0.2
- jest-snapshot: 29.0.2
+ expect: 29.3.1
+ jest-snapshot: 29.3.1
transitivePeerDependencies:
- supports-color
dev: true
- /@jest/fake-timers/29.0.2:
- resolution: {integrity: sha512-2JhQeWU28fvmM5r33lxg6BxxkTKaVXs6KMaJ6eXSM8ml/MaWkt2BvbIO8G9KWAJFMdBXWbn+2h9OK1/s5urKZA==}
+ /@jest/fake-timers/29.3.1:
+ resolution: {integrity: sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/types': 29.0.2
+ '@jest/types': 29.3.1
'@sinonjs/fake-timers': 9.1.2
- '@types/node': 16.11.58
- jest-message-util: 29.0.2
- jest-mock: 29.0.2
- jest-util: 29.0.2
+ '@types/node': 18.11.9
+ jest-message-util: 29.3.1
+ jest-mock: 29.3.1
+ jest-util: 29.3.1
dev: true
- /@jest/globals/29.0.2:
- resolution: {integrity: sha512-4hcooSNJCVXuTu07/VJwCWW6HTnjLtQdqlcGisK6JST7z2ixa8emw4SkYsOk7j36WRc2ZUEydlUePnOIOTCNXg==}
+ /@jest/globals/29.3.1:
+ resolution: {integrity: sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/environment': 29.0.2
- '@jest/expect': 29.0.2
- '@jest/types': 29.0.2
- jest-mock: 29.0.2
+ '@jest/environment': 29.3.1
+ '@jest/expect': 29.3.1
+ '@jest/types': 29.3.1
+ jest-mock: 29.3.1
transitivePeerDependencies:
- supports-color
dev: true
- /@jest/reporters/29.0.2:
- resolution: {integrity: sha512-Kr41qejRQHHkCgWHC9YwSe7D5xivqP4XML+PvgwsnRFaykKdNflDUb4+xLXySOU+O/bPkVdFpGzUpVNSJChCrw==}
+ /@jest/reporters/29.3.1:
+ resolution: {integrity: sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
@@ -2619,12 +2670,12 @@ packages:
optional: true
dependencies:
'@bcoe/v8-coverage': 0.2.3
- '@jest/console': 29.0.2
- '@jest/test-result': 29.0.2
- '@jest/transform': 29.0.2
- '@jest/types': 29.0.2
+ '@jest/console': 29.3.1
+ '@jest/test-result': 29.3.1
+ '@jest/transform': 29.3.1
+ '@jest/types': 29.3.1
'@jridgewell/trace-mapping': 0.3.15
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
chalk: 4.1.2
collect-v8-coverage: 1.0.1
exit: 0.1.2
@@ -2635,25 +2686,17 @@ packages:
istanbul-lib-report: 3.0.0
istanbul-lib-source-maps: 4.0.1
istanbul-reports: 3.1.5
- jest-message-util: 29.0.2
- jest-util: 29.0.2
- jest-worker: 29.0.2
+ jest-message-util: 29.3.1
+ jest-util: 29.3.1
+ jest-worker: 29.3.1
slash: 3.0.0
string-length: 4.0.2
strip-ansi: 6.0.1
- terminal-link: 2.1.1
v8-to-istanbul: 9.0.1
transitivePeerDependencies:
- supports-color
dev: true
- /@jest/schemas/28.1.3:
- resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
- dependencies:
- '@sinclair/typebox': 0.24.19
- dev: true
-
/@jest/schemas/29.0.0:
resolution: {integrity: sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -2661,8 +2704,8 @@ packages:
'@sinclair/typebox': 0.24.19
dev: true
- /@jest/source-map/29.0.0:
- resolution: {integrity: sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ==}
+ /@jest/source-map/29.2.0:
+ resolution: {integrity: sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jridgewell/trace-mapping': 0.3.15
@@ -2670,33 +2713,23 @@ packages:
graceful-fs: 4.2.10
dev: true
- /@jest/test-result/28.1.3:
- resolution: {integrity: sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+ /@jest/test-result/29.3.1:
+ resolution: {integrity: sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/console': 28.1.3
- '@jest/types': 28.1.3
+ '@jest/console': 29.3.1
+ '@jest/types': 29.3.1
'@types/istanbul-lib-coverage': 2.0.4
collect-v8-coverage: 1.0.1
dev: true
- /@jest/test-result/29.0.2:
- resolution: {integrity: sha512-b5rDc0lLL6Kx73LyCx6370k9uZ8o5UKdCpMS6Za3ke7H9y8PtAU305y6TeghpBmf2In8p/qqi3GpftgzijSsNw==}
+ /@jest/test-sequencer/29.3.1:
+ resolution: {integrity: sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/console': 29.0.2
- '@jest/types': 29.0.2
- '@types/istanbul-lib-coverage': 2.0.4
- collect-v8-coverage: 1.0.1
- dev: true
-
- /@jest/test-sequencer/29.0.2:
- resolution: {integrity: sha512-fsyZqHBlXNMv5ZqjQwCuYa2pskXCO0DVxh5aaVCuAtwzHuYEGrhordyEncBLQNuCGQSYgElrEEmS+7wwFnnMKw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/test-result': 29.0.2
+ '@jest/test-result': 29.3.1
graceful-fs: 4.2.10
- jest-haste-map: 29.0.2
+ jest-haste-map: 29.3.1
slash: 3.0.0
dev: true
@@ -2704,7 +2737,7 @@ packages:
resolution: {integrity: sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==}
engines: {node: '>= 10.14.2'}
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
'@jest/types': 26.6.2
babel-plugin-istanbul: 6.1.1
chalk: 4.1.2
@@ -2723,21 +2756,21 @@ packages:
- supports-color
dev: true
- /@jest/transform/29.0.2:
- resolution: {integrity: sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==}
+ /@jest/transform/29.3.1:
+ resolution: {integrity: sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@babel/core': 7.19.0
- '@jest/types': 29.0.2
+ '@babel/core': 7.19.6
+ '@jest/types': 29.3.1
'@jridgewell/trace-mapping': 0.3.15
babel-plugin-istanbul: 6.1.1
chalk: 4.1.2
- convert-source-map: 1.8.0
+ convert-source-map: 2.0.0
fast-json-stable-stringify: 2.1.0
graceful-fs: 4.2.10
- jest-haste-map: 29.0.2
- jest-regex-util: 29.0.0
- jest-util: 29.0.2
+ jest-haste-map: 29.3.1
+ jest-regex-util: 29.2.0
+ jest-util: 29.3.1
micromatch: 4.0.5
pirates: 4.0.5
slash: 3.0.0
@@ -2752,31 +2785,31 @@ packages:
dependencies:
'@types/istanbul-lib-coverage': 2.0.4
'@types/istanbul-reports': 3.0.1
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
'@types/yargs': 15.0.14
chalk: 4.1.2
dev: true
- /@jest/types/28.1.3:
- resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
- dependencies:
- '@jest/schemas': 28.1.3
- '@types/istanbul-lib-coverage': 2.0.4
- '@types/istanbul-reports': 3.0.1
- '@types/node': 16.11.58
- '@types/yargs': 17.0.10
- chalk: 4.1.2
- dev: true
-
- /@jest/types/29.0.2:
- resolution: {integrity: sha512-5WNMesBLmlkt1+fVkoCjHa0X3i3q8zc4QLTDkdHgCa2gyPZc7rdlZBWgVLqwS1860ZW5xJuCDwAzqbGaXIr/ew==}
+ /@jest/types/29.2.1:
+ resolution: {integrity: sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/schemas': 29.0.0
'@types/istanbul-lib-coverage': 2.0.4
'@types/istanbul-reports': 3.0.1
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
+ '@types/yargs': 17.0.10
+ chalk: 4.1.2
+ dev: true
+
+ /@jest/types/29.3.1:
+ resolution: {integrity: sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dependencies:
+ '@jest/schemas': 29.0.0
+ '@types/istanbul-lib-coverage': 2.0.4
+ '@types/istanbul-reports': 3.0.1
+ '@types/node': 18.11.9
'@types/yargs': 17.0.10
chalk: 4.1.2
dev: true
@@ -2826,8 +2859,8 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
- /@mui/base/5.0.0-alpha.96_nylzxt5ale4dsv666zkb25cgtm:
- resolution: {integrity: sha512-GZf2YguepLFMWGG8vQNLRl7FlXPDeyxQBYgrkHxwNJYeRw55rrGrJxHzL1iCbk71VZ2IIAIs5IAxNpyHK9iqPQ==}
+ /@mui/base/5.0.0-alpha.105_2zx2umvpluuhvlq44va5bta2da:
+ resolution: {integrity: sha512-4IPBcJQIgVVXQvN6DQMoCHed52GBtwSqYs0jD0dDcMR3o76AodQtpEeWFz3p7mJoc6f/IHBl9U6jEfL1r/kM4g==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || 18
@@ -2837,12 +2870,12 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
'@emotion/is-prop-valid': 1.2.0
- '@mui/types': 7.2.0_@types+react@18.0.19
- '@mui/utils': 5.10.3_react@18.2.0
+ '@mui/types': 7.2.0_@types+react@18.0.25
+ '@mui/utils': 5.10.9_react@18.2.0
'@popperjs/core': 2.11.6
- '@types/react': 18.0.19
+ '@types/react': 18.0.25
clsx: 1.2.1
prop-types: 15.8.1
react: 18.2.0
@@ -2850,12 +2883,12 @@ packages:
react-is: 18.2.0
dev: false
- /@mui/core-downloads-tracker/5.10.4:
- resolution: {integrity: sha512-VGekVa9dleJ+ii47+gXvKUV5a11T0nsjXN8bk5NqiJRQWRCAhbTHgsfZuctNcMeLW9FSf2gu6U0k2U6rhABKcA==}
+ /@mui/core-downloads-tracker/5.10.13:
+ resolution: {integrity: sha512-zWkWPV/SaNdsIdxAWiuVGZ+Ue3BkfSIlU/BFIrJmuUcwiIa7gQsbI/DOpj1KzLvqZhdEe2wC1aG4nCHfzgc1Hg==}
dev: false
- /@mui/icons-material/5.10.3_a2hj5atstaulbk2sc24hz7fpli:
- resolution: {integrity: sha512-o0kbUlsWCBtCE0wP33cGKbyryCh7kpm2EECYMPDmWrLhbA+HUODXIdhiTFS26szp2xXo9HY1lEx0ufeJ+tddYw==}
+ /@mui/icons-material/5.10.9_4tcfjhohj57yvze6zkot3vymie:
+ resolution: {integrity: sha512-sqClXdEM39WKQJOQ0ZCPTptaZgqwibhj2EFV9N0v7BU1PO8y4OcX/a2wIQHn4fNuDjIZktJIBrmU23h7aqlGgg==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@mui/material': ^5.0.0
@@ -2865,14 +2898,14 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
- '@mui/material': 5.10.4_g6p6knjyjrjltwufjb7cltiueu
- '@types/react': 18.0.19
+ '@babel/runtime': 7.19.0
+ '@mui/material': 5.10.13_thp4wrxo45nyllusrnt46mid4u
+ '@types/react': 18.0.25
react: 18.2.0
dev: false
- /@mui/material/5.10.4_g6p6knjyjrjltwufjb7cltiueu:
- resolution: {integrity: sha512-uwupjunU3p8XxZU4f2zF1GYlwB9GDxW1H05BMDPSh0u+37yLxuQDMMxwQQPUo9jSA5Y22/eR4Y71ScxKU89QHg==}
+ /@mui/material/5.10.13_thp4wrxo45nyllusrnt46mid4u:
+ resolution: {integrity: sha512-TkkT1rNc0/hhL4/+zv4gYcA6egNWBH/1Tz+azoTnQIUdZ32fgwFI2pFX2KVJNTt30xnLznxDWtTv7ilmJQ52xw==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -2888,18 +2921,18 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
- '@emotion/react': 11.10.4_fn6mmk7tilns4p7ajkklhuudvi
- '@emotion/styled': 11.10.4_v6omebeixt23oq3ljl3rjllla4
- '@mui/base': 5.0.0-alpha.96_nylzxt5ale4dsv666zkb25cgtm
- '@mui/core-downloads-tracker': 5.10.4
- '@mui/system': 5.10.4_wc57cflkzchz4hcfrsslybhb6e
- '@mui/types': 7.2.0_@types+react@18.0.19
- '@mui/utils': 5.10.3_react@18.2.0
- '@types/react': 18.0.19
+ '@babel/runtime': 7.19.4
+ '@emotion/react': 11.10.5_fan5qbzahqtxlm5dzefqlqx5ia
+ '@emotion/styled': 11.10.5_otcjdfkheatawhug6fwk7ldzni
+ '@mui/base': 5.0.0-alpha.105_2zx2umvpluuhvlq44va5bta2da
+ '@mui/core-downloads-tracker': 5.10.13
+ '@mui/system': 5.10.13_v64nlmo6pkqfmpmxge6otzlg54
+ '@mui/types': 7.2.0_@types+react@18.0.25
+ '@mui/utils': 5.10.9_react@18.2.0
+ '@types/react': 18.0.25
'@types/react-transition-group': 4.4.5
clsx: 1.2.1
- csstype: 3.1.0
+ csstype: 3.1.1
prop-types: 15.8.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
@@ -2907,8 +2940,8 @@ packages:
react-transition-group: 4.4.5_biqbaboplfbrettd7655fr4n2y
dev: false
- /@mui/private-theming/5.10.3_fn6mmk7tilns4p7ajkklhuudvi:
- resolution: {integrity: sha512-LCYIKlkGz2BTSng2BFzzwSJBRZbChIUri2x2Nh8ryk2B1Ho7zpvE7ex6y39LlStG2Frf92NFC/V4YQbmMAjD5A==}
+ /@mui/private-theming/5.10.9_fan5qbzahqtxlm5dzefqlqx5ia:
+ resolution: {integrity: sha512-BN7/CnsVPVyBaQpDTij4uV2xGYHHHhOgpdxeYLlIu+TqnsVM7wUeF+37kXvHovxM6xmL5qoaVUD98gDC0IZnHg==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || 18
@@ -2917,15 +2950,15 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
- '@mui/utils': 5.10.3_react@18.2.0
- '@types/react': 18.0.19
+ '@babel/runtime': 7.19.4
+ '@mui/utils': 5.10.9_react@18.2.0
+ '@types/react': 18.0.25
prop-types: 15.8.1
react: 18.2.0
dev: false
- /@mui/styled-engine/5.10.4_hfzxdiydbrbhhfpkwuv3jhvwmq:
- resolution: {integrity: sha512-y29qlonPj4wndTjbePk9O6uWp/FBRNXjiMwI64oiYQ+v3E0EkLnADfDn49GAqOJAfN/r7nHbVZBlvLNIcMbgDg==}
+ /@mui/styled-engine/5.10.8_dovxhg2tvkkxkdnqyoum6wzcxm:
+ resolution: {integrity: sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.4.1
@@ -2937,17 +2970,17 @@ packages:
'@emotion/styled':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
- '@emotion/cache': 11.10.3
- '@emotion/react': 11.10.4_fn6mmk7tilns4p7ajkklhuudvi
- '@emotion/styled': 11.10.4_v6omebeixt23oq3ljl3rjllla4
- csstype: 3.1.0
+ '@babel/runtime': 7.19.4
+ '@emotion/cache': 11.10.5
+ '@emotion/react': 11.10.5_fan5qbzahqtxlm5dzefqlqx5ia
+ '@emotion/styled': 11.10.5_otcjdfkheatawhug6fwk7ldzni
+ csstype: 3.1.1
prop-types: 15.8.1
react: 18.2.0
dev: false
- /@mui/styles/5.10.3_fn6mmk7tilns4p7ajkklhuudvi:
- resolution: {integrity: sha512-I9BYAKENJ6Ot+UEqsU+D2o5jVSeHAme+ZcPzBataTNmpjd7yrmdPeJgAyzju21KtSucBb283gvggcFqxLH1lOQ==}
+ /@mui/styles/5.10.10_fan5qbzahqtxlm5dzefqlqx5ia:
+ resolution: {integrity: sha512-utr87q/euocRdc2ekFX7DL1gqTVfogSVeu74Nspr8rtK/afC9QwF3ScP/XThVXWPcQKjBWHMKWtAO9BSVE4KDg==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || 18
@@ -2956,14 +2989,14 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.0
'@emotion/hash': 0.9.0
- '@mui/private-theming': 5.10.3_fn6mmk7tilns4p7ajkklhuudvi
- '@mui/types': 7.2.0_@types+react@18.0.19
- '@mui/utils': 5.10.3_react@18.2.0
- '@types/react': 18.0.19
+ '@mui/private-theming': 5.10.9_fan5qbzahqtxlm5dzefqlqx5ia
+ '@mui/types': 7.2.0_@types+react@18.0.25
+ '@mui/utils': 5.10.9_react@18.2.0
+ '@types/react': 18.0.25
clsx: 1.2.1
- csstype: 3.1.0
+ csstype: 3.1.1
hoist-non-react-statics: 3.3.2
jss: 10.9.2
jss-plugin-camel-case: 10.9.2
@@ -2977,8 +3010,8 @@ packages:
react: 18.2.0
dev: false
- /@mui/system/5.10.4_wc57cflkzchz4hcfrsslybhb6e:
- resolution: {integrity: sha512-fE7LtYStPHPfq7EJXEVLQGvz23pS5YwSI7A4mWEFyyW21hHUh0c1opZXb7caHJsUTiBoC22W7R8GGfbVdSOnZA==}
+ /@mui/system/5.10.13_v64nlmo6pkqfmpmxge6otzlg54:
+ resolution: {integrity: sha512-Xzx26Asu5fVlm0ucm+gnJmeX4Y1isrpVDvqxX4yJaOT7Fzmd8Lfq9ih3QMfZajns5LMtUiOuCQlVFRtUG5IY7A==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -2993,21 +3026,21 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.18.9
- '@emotion/react': 11.10.4_fn6mmk7tilns4p7ajkklhuudvi
- '@emotion/styled': 11.10.4_v6omebeixt23oq3ljl3rjllla4
- '@mui/private-theming': 5.10.3_fn6mmk7tilns4p7ajkklhuudvi
- '@mui/styled-engine': 5.10.4_hfzxdiydbrbhhfpkwuv3jhvwmq
- '@mui/types': 7.2.0_@types+react@18.0.19
- '@mui/utils': 5.10.3_react@18.2.0
- '@types/react': 18.0.19
+ '@babel/runtime': 7.19.4
+ '@emotion/react': 11.10.5_fan5qbzahqtxlm5dzefqlqx5ia
+ '@emotion/styled': 11.10.5_otcjdfkheatawhug6fwk7ldzni
+ '@mui/private-theming': 5.10.9_fan5qbzahqtxlm5dzefqlqx5ia
+ '@mui/styled-engine': 5.10.8_dovxhg2tvkkxkdnqyoum6wzcxm
+ '@mui/types': 7.2.0_@types+react@18.0.25
+ '@mui/utils': 5.10.9_react@18.2.0
+ '@types/react': 18.0.25
clsx: 1.2.1
- csstype: 3.1.0
+ csstype: 3.1.1
prop-types: 15.8.1
react: 18.2.0
dev: false
- /@mui/types/7.2.0_@types+react@18.0.19:
+ /@mui/types/7.2.0_@types+react@18.0.25:
resolution: {integrity: sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA==}
peerDependencies:
'@types/react': '*'
@@ -3015,16 +3048,16 @@ packages:
'@types/react':
optional: true
dependencies:
- '@types/react': 18.0.19
+ '@types/react': 18.0.25
dev: false
- /@mui/utils/5.10.3_react@18.2.0:
- resolution: {integrity: sha512-4jXMDPfx6bpMVuheLaOpKTjpzw39ogAZLeaLj5+RJec3E37/hAZMYjURfblLfTWMMoGoqkY03mNsZaEwNobBow==}
+ /@mui/utils/5.10.9_react@18.2.0:
+ resolution: {integrity: sha512-2tdHWrq3+WCy+G6TIIaFx3cg7PorXZ71P375ExuX61od1NOAJP1mK90VxQ8N4aqnj2vmO3AQDkV4oV2Ktvt4bA==}
engines: {node: '>=12.0.0'}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || 18
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
'@types/prop-types': 15.7.5
'@types/react-is': 17.0.3
prop-types: 15.8.1
@@ -3069,6 +3102,11 @@ packages:
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
dev: false
+ /@remix-run/router/1.0.3:
+ resolution: {integrity: sha512-ceuyTSs7PZ/tQqi19YZNBc5X7kj1f8p+4DIyrcIYFY9h+hd1OKm4RqtiWldR9eGEvIiJfsqwM4BsuCtRIuEw6Q==}
+ engines: {node: '>=14'}
+ dev: false
+
/@rollup/pluginutils/4.2.1:
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
engines: {node: '>= 8.0.0'}
@@ -3077,6 +3115,20 @@ packages:
picomatch: 2.3.1
dev: true
+ /@rollup/pluginutils/5.0.1:
+ resolution: {integrity: sha512-4HaCVEXXuObvcPUaUlLt4faHYHCeQOOWNj8NKFGaRSrw3ZLD0TWeAFZicV9vXjnE2nkNuaVTfTuwAnjR+6uc9A==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ dependencies:
+ '@types/estree': 1.0.0
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ dev: true
+
/@rushstack/eslint-patch/1.1.4:
resolution: {integrity: sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==}
dev: true
@@ -3097,7 +3149,17 @@ packages:
'@sinonjs/commons': 1.8.3
dev: true
- /@svgr/babel-plugin-add-jsx-attribute/6.3.1_@babel+core@7.19.0:
+ /@svgr/babel-plugin-add-jsx-attribute/6.3.1:
+ resolution: {integrity: sha512-jDBKArXYO1u0B1dmd2Nf8Oy6aTF5vLDfLoO9Oon/GLkqZ/NiggYWZA+a2HpUMH4ITwNqS3z43k8LWApB8S583w==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dev: true
+
+ /@svgr/babel-plugin-add-jsx-attribute/6.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-jDBKArXYO1u0B1dmd2Nf8Oy6aTF5vLDfLoO9Oon/GLkqZ/NiggYWZA+a2HpUMH4ITwNqS3z43k8LWApB8S583w==}
engines: {node: '>=10'}
peerDependencies:
@@ -3106,10 +3168,20 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
dev: true
- /@svgr/babel-plugin-remove-jsx-attribute/6.3.1_@babel+core@7.19.0:
+ /@svgr/babel-plugin-remove-jsx-attribute/6.3.1:
+ resolution: {integrity: sha512-dQzyJ4prwjcFd929T43Z8vSYiTlTu8eafV40Z2gO7zy/SV5GT+ogxRJRBIKWomPBOiaVXFg3jY4S5hyEN3IBjQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dev: true
+
+ /@svgr/babel-plugin-remove-jsx-attribute/6.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-dQzyJ4prwjcFd929T43Z8vSYiTlTu8eafV40Z2gO7zy/SV5GT+ogxRJRBIKWomPBOiaVXFg3jY4S5hyEN3IBjQ==}
engines: {node: '>=10'}
peerDependencies:
@@ -3118,10 +3190,20 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
dev: true
- /@svgr/babel-plugin-remove-jsx-empty-expression/6.3.1_@babel+core@7.19.0:
+ /@svgr/babel-plugin-remove-jsx-empty-expression/6.3.1:
+ resolution: {integrity: sha512-HBOUc1XwSU67fU26V5Sfb8MQsT0HvUyxru7d0oBJ4rA2s4HW3PhyAPC7fV/mdsSGpAvOdd8Wpvkjsr0fWPUO7A==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dev: true
+
+ /@svgr/babel-plugin-remove-jsx-empty-expression/6.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-HBOUc1XwSU67fU26V5Sfb8MQsT0HvUyxru7d0oBJ4rA2s4HW3PhyAPC7fV/mdsSGpAvOdd8Wpvkjsr0fWPUO7A==}
engines: {node: '>=10'}
peerDependencies:
@@ -3130,10 +3212,20 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
dev: true
- /@svgr/babel-plugin-replace-jsx-attribute-value/6.3.1_@babel+core@7.19.0:
+ /@svgr/babel-plugin-replace-jsx-attribute-value/6.3.1:
+ resolution: {integrity: sha512-C12e6aN4BXAolRrI601gPn5MDFCRHO7C4TM8Kks+rDtl8eEq+NN1sak0eAzJu363x3TmHXdZn7+Efd2nr9I5dA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dev: true
+
+ /@svgr/babel-plugin-replace-jsx-attribute-value/6.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-C12e6aN4BXAolRrI601gPn5MDFCRHO7C4TM8Kks+rDtl8eEq+NN1sak0eAzJu363x3TmHXdZn7+Efd2nr9I5dA==}
engines: {node: '>=10'}
peerDependencies:
@@ -3142,10 +3234,20 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
dev: true
- /@svgr/babel-plugin-svg-dynamic-title/6.3.1_@babel+core@7.19.0:
+ /@svgr/babel-plugin-svg-dynamic-title/6.3.1:
+ resolution: {integrity: sha512-6NU55Mmh3M5u2CfCCt6TX29/pPneutrkJnnDCHbKZnjukZmmgUAZLtZ2g6ZoSPdarowaQmAiBRgAHqHmG0vuqA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dev: true
+
+ /@svgr/babel-plugin-svg-dynamic-title/6.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-6NU55Mmh3M5u2CfCCt6TX29/pPneutrkJnnDCHbKZnjukZmmgUAZLtZ2g6ZoSPdarowaQmAiBRgAHqHmG0vuqA==}
engines: {node: '>=10'}
peerDependencies:
@@ -3154,10 +3256,20 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
dev: true
- /@svgr/babel-plugin-svg-em-dimensions/6.3.1_@babel+core@7.19.0:
+ /@svgr/babel-plugin-svg-em-dimensions/6.3.1:
+ resolution: {integrity: sha512-HV1NGHYTTe1vCNKlBgq/gKuCSfaRlKcHIADn7P8w8U3Zvujdw1rmusutghJ1pZJV7pDt3Gt8ws+SVrqHnBO/Qw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dev: true
+
+ /@svgr/babel-plugin-svg-em-dimensions/6.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-HV1NGHYTTe1vCNKlBgq/gKuCSfaRlKcHIADn7P8w8U3Zvujdw1rmusutghJ1pZJV7pDt3Gt8ws+SVrqHnBO/Qw==}
engines: {node: '>=10'}
peerDependencies:
@@ -3166,10 +3278,20 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
dev: true
- /@svgr/babel-plugin-transform-react-native-svg/6.3.1_@babel+core@7.19.0:
+ /@svgr/babel-plugin-transform-react-native-svg/6.3.1:
+ resolution: {integrity: sha512-2wZhSHvTolFNeKDAN/ZmIeSz2O9JSw72XD+o2bNp2QAaWqa8KGpn5Yk5WHso6xqfSAiRzAE+GXlsrBO4UP9LLw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dev: true
+
+ /@svgr/babel-plugin-transform-react-native-svg/6.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-2wZhSHvTolFNeKDAN/ZmIeSz2O9JSw72XD+o2bNp2QAaWqa8KGpn5Yk5WHso6xqfSAiRzAE+GXlsrBO4UP9LLw==}
engines: {node: '>=10'}
peerDependencies:
@@ -3178,10 +3300,20 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
dev: true
- /@svgr/babel-plugin-transform-svg-component/6.3.1_@babel+core@7.19.0:
+ /@svgr/babel-plugin-transform-svg-component/6.3.1:
+ resolution: {integrity: sha512-cZ8Tr6ZAWNUFfDeCKn/pGi976iWSkS8ijmEYKosP+6ktdZ7lW9HVLHojyusPw3w0j8PI4VBeWAXAmi/2G7owxw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dev: true
+
+ /@svgr/babel-plugin-transform-svg-component/6.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-cZ8Tr6ZAWNUFfDeCKn/pGi976iWSkS8ijmEYKosP+6ktdZ7lW9HVLHojyusPw3w0j8PI4VBeWAXAmi/2G7owxw==}
engines: {node: '>=12'}
peerDependencies:
@@ -3190,11 +3322,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
+ '@babel/core': 7.19.6
dev: true
- /@svgr/babel-preset/6.3.1_@babel+core@7.19.0:
- resolution: {integrity: sha512-tQtWtzuMMQ3opH7je+MpwfuRA1Hf3cKdSgTtAYwOBDfmhabP7rcTfBi3E7V3MuwJNy/Y02/7/RutvwS1W4Qv9g==}
+ /@svgr/babel-preset/6.4.0:
+ resolution: {integrity: sha512-Ytuh7N282fv2Cy1JePf6HZ29/G5Hb8mQAjx4iykPjvfFl9NK6o5lZavmewgjOGT8kNPtwgvheuOQn4CifHRUhQ==}
engines: {node: '>=10'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -3202,46 +3334,67 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@svgr/babel-plugin-add-jsx-attribute': 6.3.1_@babel+core@7.19.0
- '@svgr/babel-plugin-remove-jsx-attribute': 6.3.1_@babel+core@7.19.0
- '@svgr/babel-plugin-remove-jsx-empty-expression': 6.3.1_@babel+core@7.19.0
- '@svgr/babel-plugin-replace-jsx-attribute-value': 6.3.1_@babel+core@7.19.0
- '@svgr/babel-plugin-svg-dynamic-title': 6.3.1_@babel+core@7.19.0
- '@svgr/babel-plugin-svg-em-dimensions': 6.3.1_@babel+core@7.19.0
- '@svgr/babel-plugin-transform-react-native-svg': 6.3.1_@babel+core@7.19.0
- '@svgr/babel-plugin-transform-svg-component': 6.3.1_@babel+core@7.19.0
+ '@svgr/babel-plugin-add-jsx-attribute': 6.3.1
+ '@svgr/babel-plugin-remove-jsx-attribute': 6.3.1
+ '@svgr/babel-plugin-remove-jsx-empty-expression': 6.3.1
+ '@svgr/babel-plugin-replace-jsx-attribute-value': 6.3.1
+ '@svgr/babel-plugin-svg-dynamic-title': 6.3.1
+ '@svgr/babel-plugin-svg-em-dimensions': 6.3.1
+ '@svgr/babel-plugin-transform-react-native-svg': 6.3.1
+ '@svgr/babel-plugin-transform-svg-component': 6.3.1
dev: true
- /@svgr/core/6.3.1:
- resolution: {integrity: sha512-Sm3/7OdXbQreemf9aO25keerZSbnKMpGEfmH90EyYpj1e8wMD4TuwJIb3THDSgRMWk1kYJfSRulELBy4gVgZUA==}
+ /@svgr/babel-preset/6.4.0_@babel+core@7.19.6:
+ resolution: {integrity: sha512-Ytuh7N282fv2Cy1JePf6HZ29/G5Hb8mQAjx4iykPjvfFl9NK6o5lZavmewgjOGT8kNPtwgvheuOQn4CifHRUhQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ dependencies:
+ '@babel/core': 7.19.6
+ '@svgr/babel-plugin-add-jsx-attribute': 6.3.1_@babel+core@7.19.6
+ '@svgr/babel-plugin-remove-jsx-attribute': 6.3.1_@babel+core@7.19.6
+ '@svgr/babel-plugin-remove-jsx-empty-expression': 6.3.1_@babel+core@7.19.6
+ '@svgr/babel-plugin-replace-jsx-attribute-value': 6.3.1_@babel+core@7.19.6
+ '@svgr/babel-plugin-svg-dynamic-title': 6.3.1_@babel+core@7.19.6
+ '@svgr/babel-plugin-svg-em-dimensions': 6.3.1_@babel+core@7.19.6
+ '@svgr/babel-plugin-transform-react-native-svg': 6.3.1_@babel+core@7.19.6
+ '@svgr/babel-plugin-transform-svg-component': 6.3.1_@babel+core@7.19.6
+ dev: true
+
+ /@svgr/core/6.4.0:
+ resolution: {integrity: sha512-wU9uyF6BUnwAqG7fDOowmDQzmbvovj1uq/iETfMK9xwQNaT+e7yN7SmDDcETXC72dnOrMcRuEWw0JEvpJha+yg==}
engines: {node: '>=10'}
dependencies:
- '@svgr/plugin-jsx': 6.3.1_@svgr+core@6.3.1
+ '@svgr/babel-preset': 6.4.0
+ '@svgr/plugin-jsx': 6.4.0_@svgr+core@6.4.0
camelcase: 6.3.0
cosmiconfig: 7.0.1
transitivePeerDependencies:
+ - '@babel/core'
- supports-color
dev: true
- /@svgr/hast-util-to-babel-ast/6.3.1:
- resolution: {integrity: sha512-NgyCbiTQIwe3wHe/VWOUjyxmpUmsrBjdoIxKpXt3Nqc3TN30BpJG22OxBvVzsAh9jqep0w0/h8Ywvdk3D9niNQ==}
+ /@svgr/hast-util-to-babel-ast/6.4.0:
+ resolution: {integrity: sha512-PjcU8jCneKXJnrREycsgfgQ/bzR1ogSKC5MBeUu2wmEoJIjzXX7X14DDktUjU9bkBy26yMDiVHn46Nl82P3WEg==}
engines: {node: '>=10'}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
entities: 4.3.1
dev: true
- /@svgr/plugin-jsx/6.3.1_@svgr+core@6.3.1:
- resolution: {integrity: sha512-r9+0mYG3hD4nNtUgsTXWGYJomv/bNd7kC16zvsM70I/bGeoCi/3lhTmYqeN6ChWX317OtQCSZZbH4wq9WwoXbw==}
+ /@svgr/plugin-jsx/6.4.0_@svgr+core@6.4.0:
+ resolution: {integrity: sha512-gu6E7v8qRAtCxbymI1et3G7athogvKvzsJkSWiHVZsuVZbyx5O2b7+DIkKvAGh2RvEzgSvfGf8QD1BaHal2vBw==}
engines: {node: '>=10'}
peerDependencies:
'@svgr/core': ^6.0.0
dependencies:
- '@babel/core': 7.19.0
- '@svgr/babel-preset': 6.3.1_@babel+core@7.19.0
- '@svgr/core': 6.3.1
- '@svgr/hast-util-to-babel-ast': 6.3.1
+ '@babel/core': 7.19.6
+ '@svgr/babel-preset': 6.4.0_@babel+core@7.19.6
+ '@svgr/core': 6.4.0
+ '@svgr/hast-util-to-babel-ast': 6.4.0
svg-parser: 2.0.4
transitivePeerDependencies:
- supports-color
@@ -3252,7 +3405,7 @@ packages:
engines: {node: '>=12'}
dependencies:
'@babel/code-frame': 7.18.6
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.0
'@types/aria-query': 4.2.2
aria-query: 5.0.0
chalk: 4.1.2
@@ -3285,7 +3438,7 @@ packages:
dependencies:
'@babel/runtime': 7.18.9
'@testing-library/dom': 8.16.0
- '@types/react-dom': 18.0.6
+ '@types/react-dom': 18.0.8
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: true
@@ -3318,8 +3471,8 @@ packages:
/@types/babel__core/7.1.19:
resolution: {integrity: sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==}
dependencies:
- '@babel/parser': 7.18.11
- '@babel/types': 7.18.10
+ '@babel/parser': 7.19.6
+ '@babel/types': 7.19.4
'@types/babel__generator': 7.6.4
'@types/babel__template': 7.4.1
'@types/babel__traverse': 7.17.1
@@ -3328,20 +3481,20 @@ packages:
/@types/babel__generator/7.6.4:
resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@types/babel__template/7.4.1:
resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==}
dependencies:
- '@babel/parser': 7.19.0
- '@babel/types': 7.19.0
+ '@babel/parser': 7.19.6
+ '@babel/types': 7.19.4
dev: true
/@types/babel__traverse/7.17.1:
resolution: {integrity: sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==}
dependencies:
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
dev: true
/@types/eslint/8.4.5:
@@ -3358,7 +3511,7 @@ packages:
/@types/graceful-fs/4.1.5:
resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==}
dependencies:
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
dev: true
/@types/istanbul-lib-coverage/2.0.4:
@@ -3377,17 +3530,17 @@ packages:
'@types/istanbul-lib-report': 3.0.0
dev: true
- /@types/jest/29.0.0:
- resolution: {integrity: sha512-X6Zjz3WO4cT39Gkl0lZ2baFRaEMqJl5NC1OjElkwtNzAlbkr2K/WJXkBkH5VP0zx4Hgsd2TZYdOEfvp2Dxia+Q==}
+ /@types/jest/29.2.2:
+ resolution: {integrity: sha512-og1wAmdxKoS71K2ZwSVqWPX6OVn3ihZ6ZT2qvZvZQm90lJVDyXIjYcu4Khx2CNIeaFv12rOU/YObOsI3VOkzog==}
dependencies:
- expect: 29.0.1
- pretty-format: 29.0.1
+ expect: 29.2.2
+ pretty-format: 29.2.1
dev: true
/@types/jsdom/20.0.0:
resolution: {integrity: sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==}
dependencies:
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
'@types/tough-cookie': 4.0.2
parse5: 7.0.0
dev: true
@@ -3408,8 +3561,8 @@ packages:
resolution: {integrity: sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==}
dev: true
- /@types/node/16.11.58:
- resolution: {integrity: sha512-uMVxJ111wpHzkx/vshZFb6Qni3BOMnlWLq7q9jrwej7Yw/KvjsEbpxCCxw+hLKxexFMc8YmpG8J9tnEe/rKsIg==}
+ /@types/node/18.11.9:
+ resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==}
dev: true
/@types/normalize-package-data/2.4.1:
@@ -3429,37 +3582,41 @@ packages:
/@types/qrcode.react/1.0.2:
resolution: {integrity: sha512-I9Oq5Cjlkgy3Tw7krCnCXLw2/zMhizkTere49OOcta23tkvH0xBTP0yInimTh0gstLRtb8Ki9NZVujE5UI6ffQ==}
dependencies:
- '@types/react': 18.0.19
+ '@types/react': 18.0.25
dev: true
- /@types/react-dom/18.0.6:
- resolution: {integrity: sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==}
+ /@types/react-dom/18.0.8:
+ resolution: {integrity: sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==}
dependencies:
- '@types/react': 18.0.19
+ '@types/react': 18.0.25
dev: true
/@types/react-is/17.0.3:
resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==}
dependencies:
- '@types/react': 18.0.19
+ '@types/react': 18.0.25
dev: false
/@types/react-transition-group/4.4.5:
resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==}
dependencies:
- '@types/react': 18.0.19
+ '@types/react': 18.0.25
dev: false
- /@types/react/18.0.19:
- resolution: {integrity: sha512-BDc3Q+4Q3zsn7k9xZrKfjWyJsSlEDMs38gD1qp2eDazLCdcPqAT+vq1ND+Z8AGel/UiwzNUk8ptpywgNQcJ1MQ==}
+ /@types/react/18.0.25:
+ resolution: {integrity: sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==}
dependencies:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.2
- csstype: 3.1.0
+ csstype: 3.1.1
/@types/scheduler/0.16.2:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
+ /@types/semver/7.3.12:
+ resolution: {integrity: sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==}
+ dev: true
+
/@types/stack-utils/2.0.1:
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
dev: true
@@ -3467,7 +3624,7 @@ packages:
/@types/testing-library__jest-dom/5.14.5:
resolution: {integrity: sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==}
dependencies:
- '@types/jest': 29.0.0
+ '@types/jest': 29.2.2
dev: true
/@types/tough-cookie/4.0.2:
@@ -3494,8 +3651,8 @@ packages:
resolution: {integrity: sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w==}
dev: true
- /@typescript-eslint/eslint-plugin/5.36.2_2l2r3i3lm6jysqd4ac3ql4n2mm:
- resolution: {integrity: sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw==}
+ /@typescript-eslint/eslint-plugin/5.42.1_2udltptbznfmezdozpdoa2aemq:
+ resolution: {integrity: sha512-LyR6x784JCiJ1j6sH5Y0K6cdExqCCm8DJUTcwG5ThNXJj/G8o5E56u5EdG4SLy+bZAwZBswC+GYn3eGdttBVCg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
'@typescript-eslint/parser': ^5.0.0
@@ -3505,37 +3662,37 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/parser': 5.36.2_itqs5654cmlnjraw6gjzqacppi
- '@typescript-eslint/scope-manager': 5.36.2
- '@typescript-eslint/type-utils': 5.36.2_itqs5654cmlnjraw6gjzqacppi
- '@typescript-eslint/utils': 5.36.2_itqs5654cmlnjraw6gjzqacppi
+ '@typescript-eslint/parser': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
+ '@typescript-eslint/scope-manager': 5.42.1
+ '@typescript-eslint/type-utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
+ '@typescript-eslint/utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
debug: 4.3.4
- eslint: 8.23.0
- functional-red-black-tree: 1.0.1
+ eslint: 8.27.0
ignore: 5.2.0
+ natural-compare-lite: 1.4.0
regexpp: 3.2.0
semver: 7.3.7
- tsutils: 3.21.0_typescript@4.8.3
- typescript: 4.8.3
+ tsutils: 3.21.0_typescript@4.8.4
+ typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
- /@typescript-eslint/experimental-utils/5.30.6_itqs5654cmlnjraw6gjzqacppi:
+ /@typescript-eslint/experimental-utils/5.30.6_rmayb2veg2btbq6mbmnyivgasy:
resolution: {integrity: sha512-bqvT+0L8IjtW7MCrMgm9oVNxs4g7mESro1mm5c1/SNfTnHuFTf9OUX1WzVkTz75M9cp//UrTrSmGvK48NEKshQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
- '@typescript-eslint/utils': 5.30.6_itqs5654cmlnjraw6gjzqacppi
- eslint: 8.23.0
+ '@typescript-eslint/utils': 5.30.6_rmayb2veg2btbq6mbmnyivgasy
+ eslint: 8.27.0
transitivePeerDependencies:
- supports-color
- typescript
dev: true
- /@typescript-eslint/parser/5.36.2_itqs5654cmlnjraw6gjzqacppi:
- resolution: {integrity: sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA==}
+ /@typescript-eslint/parser/5.42.1_rmayb2veg2btbq6mbmnyivgasy:
+ resolution: {integrity: sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -3544,12 +3701,12 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/scope-manager': 5.36.2
- '@typescript-eslint/types': 5.36.2
- '@typescript-eslint/typescript-estree': 5.36.2_typescript@4.8.3
+ '@typescript-eslint/scope-manager': 5.42.1
+ '@typescript-eslint/types': 5.42.1
+ '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4
debug: 4.3.4
- eslint: 8.23.0
- typescript: 4.8.3
+ eslint: 8.27.0
+ typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -3562,16 +3719,16 @@ packages:
'@typescript-eslint/visitor-keys': 5.30.6
dev: true
- /@typescript-eslint/scope-manager/5.36.2:
- resolution: {integrity: sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==}
+ /@typescript-eslint/scope-manager/5.42.1:
+ resolution: {integrity: sha512-QAZY/CBP1Emx4rzxurgqj3rUinfsh/6mvuKbLNMfJMMKYLRBfweus8brgXF8f64ABkIZ3zdj2/rYYtF8eiuksQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
- '@typescript-eslint/types': 5.36.2
- '@typescript-eslint/visitor-keys': 5.36.2
+ '@typescript-eslint/types': 5.42.1
+ '@typescript-eslint/visitor-keys': 5.42.1
dev: true
- /@typescript-eslint/type-utils/5.36.2_itqs5654cmlnjraw6gjzqacppi:
- resolution: {integrity: sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==}
+ /@typescript-eslint/type-utils/5.42.1_rmayb2veg2btbq6mbmnyivgasy:
+ resolution: {integrity: sha512-WWiMChneex5w4xPIX56SSnQQo0tEOy5ZV2dqmj8Z371LJ0E+aymWD25JQ/l4FOuuX+Q49A7pzh/CGIQflxMVXg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: '*'
@@ -3580,12 +3737,12 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/typescript-estree': 5.36.2_typescript@4.8.3
- '@typescript-eslint/utils': 5.36.2_itqs5654cmlnjraw6gjzqacppi
+ '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4
+ '@typescript-eslint/utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
debug: 4.3.4
- eslint: 8.23.0
- tsutils: 3.21.0_typescript@4.8.3
- typescript: 4.8.3
+ eslint: 8.27.0
+ tsutils: 3.21.0_typescript@4.8.4
+ typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -3595,12 +3752,12 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
- /@typescript-eslint/types/5.36.2:
- resolution: {integrity: sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==}
+ /@typescript-eslint/types/5.42.1:
+ resolution: {integrity: sha512-Qrco9dsFF5lhalz+lLFtxs3ui1/YfC6NdXu+RAGBa8uSfn01cjO7ssCsjIsUs484vny9Xm699FSKwpkCcqwWwA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
- /@typescript-eslint/typescript-estree/5.30.6_typescript@4.8.3:
+ /@typescript-eslint/typescript-estree/5.30.6_typescript@4.8.4:
resolution: {integrity: sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -3615,14 +3772,14 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
semver: 7.3.7
- tsutils: 3.21.0_typescript@4.8.3
- typescript: 4.8.3
+ tsutils: 3.21.0_typescript@4.8.4
+ typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
- /@typescript-eslint/typescript-estree/5.36.2_typescript@4.8.3:
- resolution: {integrity: sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==}
+ /@typescript-eslint/typescript-estree/5.42.1_typescript@4.8.4:
+ resolution: {integrity: sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
typescript: '*'
@@ -3630,19 +3787,19 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/types': 5.36.2
- '@typescript-eslint/visitor-keys': 5.36.2
+ '@typescript-eslint/types': 5.42.1
+ '@typescript-eslint/visitor-keys': 5.42.1
debug: 4.3.4
globby: 11.1.0
is-glob: 4.0.3
semver: 7.3.7
- tsutils: 3.21.0_typescript@4.8.3
- typescript: 4.8.3
+ tsutils: 3.21.0_typescript@4.8.4
+ typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
- /@typescript-eslint/utils/5.30.6_itqs5654cmlnjraw6gjzqacppi:
+ /@typescript-eslint/utils/5.30.6_rmayb2veg2btbq6mbmnyivgasy:
resolution: {integrity: sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -3651,28 +3808,30 @@ packages:
'@types/json-schema': 7.0.11
'@typescript-eslint/scope-manager': 5.30.6
'@typescript-eslint/types': 5.30.6
- '@typescript-eslint/typescript-estree': 5.30.6_typescript@4.8.3
- eslint: 8.23.0
+ '@typescript-eslint/typescript-estree': 5.30.6_typescript@4.8.4
+ eslint: 8.27.0
eslint-scope: 5.1.1
- eslint-utils: 3.0.0_eslint@8.23.0
+ eslint-utils: 3.0.0_eslint@8.27.0
transitivePeerDependencies:
- supports-color
- typescript
dev: true
- /@typescript-eslint/utils/5.36.2_itqs5654cmlnjraw6gjzqacppi:
- resolution: {integrity: sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==}
+ /@typescript-eslint/utils/5.42.1_rmayb2veg2btbq6mbmnyivgasy:
+ resolution: {integrity: sha512-Gxvf12xSp3iYZd/fLqiQRD4uKZjDNR01bQ+j8zvhPjpsZ4HmvEFL/tC4amGNyxN9Rq+iqvpHLhlqx6KTxz9ZyQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
'@types/json-schema': 7.0.11
- '@typescript-eslint/scope-manager': 5.36.2
- '@typescript-eslint/types': 5.36.2
- '@typescript-eslint/typescript-estree': 5.36.2_typescript@4.8.3
- eslint: 8.23.0
+ '@types/semver': 7.3.12
+ '@typescript-eslint/scope-manager': 5.42.1
+ '@typescript-eslint/types': 5.42.1
+ '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4
+ eslint: 8.27.0
eslint-scope: 5.1.1
- eslint-utils: 3.0.0_eslint@8.23.0
+ eslint-utils: 3.0.0_eslint@8.27.0
+ semver: 7.3.7
transitivePeerDependencies:
- supports-color
- typescript
@@ -3686,28 +3845,28 @@ packages:
eslint-visitor-keys: 3.3.0
dev: true
- /@typescript-eslint/visitor-keys/5.36.2:
- resolution: {integrity: sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==}
+ /@typescript-eslint/visitor-keys/5.42.1:
+ resolution: {integrity: sha512-LOQtSF4z+hejmpUvitPlc4hA7ERGoj2BVkesOcG91HCn8edLGUXbTrErmutmPbl8Bo9HjAvOO/zBKQHExXNA2A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
- '@typescript-eslint/types': 5.36.2
+ '@typescript-eslint/types': 5.42.1
eslint-visitor-keys: 3.3.0
dev: true
- /@vitejs/plugin-react/2.1.0_vite@3.1.0:
- resolution: {integrity: sha512-am6rPyyU3LzUYne3Gd9oj9c4Rzbq5hQnuGXSMT6Gujq45Il/+bunwq3lrB7wghLkiF45ygMwft37vgJ/NE8IAA==}
+ /@vitejs/plugin-react/2.2.0_vite@3.2.3:
+ resolution: {integrity: sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: ^3.0.0
dependencies:
- '@babel/core': 7.19.0
- '@babel/plugin-transform-react-jsx': 7.18.10_@babel+core@7.19.0
- '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-react-jsx-self': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-react-jsx-source': 7.18.6_@babel+core@7.19.0
- magic-string: 0.26.2
+ '@babel/core': 7.19.6
+ '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.6
+ '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-react-jsx-self': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.19.6
+ magic-string: 0.26.7
react-refresh: 0.14.0
- vite: 3.1.0
+ vite: 3.2.3_@types+node@18.11.9
transitivePeerDependencies:
- supports-color
dev: true
@@ -3865,7 +4024,7 @@ packages:
resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==}
engines: {node: '>=6.0'}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
'@babel/runtime-corejs3': 7.18.6
dev: true
@@ -3962,11 +4121,12 @@ packages:
engines: {node: '>=4'}
dev: true
- /axios/0.27.2:
- resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
+ /axios/1.1.3:
+ resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==}
dependencies:
follow-redirects: 1.15.1
form-data: 4.0.0
+ proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
@@ -3997,8 +4157,8 @@ packages:
- supports-color
dev: true
- /babel-jest/29.0.2_@babel+core@7.19.0:
- resolution: {integrity: sha512-yTu4/WSi/HzarjQtrJSwV+/0maoNt+iP0DmpvFJdv9yY+5BuNle8TbheHzzcSWj5gIHfuhpbLYHWRDYhWKyeKQ==}
+ /babel-jest/29.3.1_@babel+core@7.19.6:
+ resolution: {integrity: sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
'@babel/core': ^7.8.0
@@ -4006,11 +4166,11 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@jest/transform': 29.0.2
+ '@babel/core': 7.19.6
+ '@jest/transform': 29.3.1
'@types/babel__core': 7.1.19
babel-plugin-istanbul: 6.1.1
- babel-preset-jest: 29.0.2_@babel+core@7.19.0
+ babel-preset-jest: 29.2.0_@babel+core@7.19.6
chalk: 4.1.2
graceful-fs: 4.2.10
slash: 3.0.0
@@ -4028,7 +4188,7 @@ packages:
resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==}
engines: {node: '>=8'}
dependencies:
- '@babel/helper-plugin-utils': 7.18.9
+ '@babel/helper-plugin-utils': 7.19.0
'@istanbuljs/load-nyc-config': 1.1.0
'@istanbuljs/schema': 0.1.3
istanbul-lib-instrument: 5.2.0
@@ -4042,17 +4202,17 @@ packages:
engines: {node: '>= 10.14.2'}
dependencies:
'@babel/template': 7.18.10
- '@babel/types': 7.18.10
+ '@babel/types': 7.19.4
'@types/babel__core': 7.1.19
'@types/babel__traverse': 7.17.1
dev: true
- /babel-plugin-jest-hoist/29.0.2:
- resolution: {integrity: sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg==}
+ /babel-plugin-jest-hoist/29.2.0:
+ resolution: {integrity: sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@babel/template': 7.18.10
- '@babel/types': 7.19.0
+ '@babel/types': 7.19.4
'@types/babel__core': 7.1.19
'@types/babel__traverse': 7.17.1
dev: true
@@ -4061,11 +4221,11 @@ packages:
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
engines: {node: '>=10', npm: '>=6'}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
cosmiconfig: 7.0.1
resolve: 1.22.1
- /babel-plugin-polyfill-corejs2/0.3.1_@babel+core@7.19.0:
+ /babel-plugin-polyfill-corejs2/0.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -4073,15 +4233,15 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/compat-data': 7.19.0
- '@babel/core': 7.19.0
- '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.19.0
+ '@babel/compat-data': 7.19.4
+ '@babel/core': 7.19.6
+ '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.19.6
semver: 6.3.0
transitivePeerDependencies:
- supports-color
dev: true
- /babel-plugin-polyfill-corejs3/0.5.2_@babel+core@7.19.0:
+ /babel-plugin-polyfill-corejs3/0.5.2_@babel+core@7.19.6:
resolution: {integrity: sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -4089,14 +4249,14 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.19.6
core-js-compat: 3.23.4
transitivePeerDependencies:
- supports-color
dev: true
- /babel-plugin-polyfill-regenerator/0.3.1_@babel+core@7.19.0:
+ /babel-plugin-polyfill-regenerator/0.3.1_@babel+core@7.19.6:
resolution: {integrity: sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -4104,8 +4264,8 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.19.6
transitivePeerDependencies:
- supports-color
dev: true
@@ -4137,7 +4297,7 @@ packages:
'@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.18.6
dev: true
- /babel-preset-current-node-syntax/1.0.1_@babel+core@7.19.0:
+ /babel-preset-current-node-syntax/1.0.1_@babel+core@7.19.6:
resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==}
peerDependencies:
'@babel/core': ^7.0.0
@@ -4145,19 +4305,19 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.0
- '@babel/plugin-syntax-bigint': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.0
- '@babel/plugin-syntax-import-meta': 7.10.4_@babel+core@7.19.0
- '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.0
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.0
- '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.0
- '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.6
+ '@babel/plugin-syntax-bigint': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.6
+ '@babel/plugin-syntax-import-meta': 7.10.4_@babel+core@7.19.6
+ '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.6
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.6
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.6
+ '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.6
dev: true
/babel-preset-jest/26.6.2_@babel+core@7.18.6:
@@ -4174,8 +4334,8 @@ packages:
babel-preset-current-node-syntax: 1.0.1_@babel+core@7.18.6
dev: true
- /babel-preset-jest/29.0.2_@babel+core@7.19.0:
- resolution: {integrity: sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==}
+ /babel-preset-jest/29.2.0_@babel+core@7.19.6:
+ resolution: {integrity: sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
'@babel/core': ^7.0.0
@@ -4183,29 +4343,29 @@ packages:
'@babel/core':
optional: true
dependencies:
- '@babel/core': 7.19.0
- babel-plugin-jest-hoist: 29.0.2
- babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ babel-plugin-jest-hoist: 29.2.0
+ babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.6
dev: true
/babel-preset-react-app/10.0.1:
resolution: {integrity: sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==}
dependencies:
- '@babel/core': 7.19.0
- '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-decorators': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-optional-chaining': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-proposal-private-property-in-object': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-flow-strip-types': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-react-display-name': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-transform-runtime': 7.18.6_@babel+core@7.19.0
- '@babel/preset-env': 7.18.6_@babel+core@7.19.0
- '@babel/preset-react': 7.18.6_@babel+core@7.19.0
- '@babel/preset-typescript': 7.18.6_@babel+core@7.19.0
- '@babel/runtime': 7.18.9
+ '@babel/core': 7.19.6
+ '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-decorators': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-optional-chaining': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-proposal-private-property-in-object': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-flow-strip-types': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-react-display-name': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-transform-runtime': 7.18.6_@babel+core@7.19.6
+ '@babel/preset-env': 7.18.6_@babel+core@7.19.6
+ '@babel/preset-react': 7.18.6_@babel+core@7.19.6
+ '@babel/preset-typescript': 7.18.6_@babel+core@7.19.6
+ '@babel/runtime': 7.19.0
babel-plugin-macros: 3.1.0
babel-plugin-transform-react-remove-prop-types: 0.4.24
transitivePeerDependencies:
@@ -4214,7 +4374,6 @@ packages:
/balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
- dev: true
/base/0.11.2:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
@@ -4234,7 +4393,6 @@ packages:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
- dev: true
/braces/2.3.2:
resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
@@ -4261,6 +4419,16 @@ packages:
fill-range: 7.0.1
dev: true
+ /broadcast-channel/4.18.1:
+ resolution: {integrity: sha512-eV1srWgt6H4hbtGqD7THn60me66WA5l0LogpssuX9jK6NK26HzIZr+VsrlD7Obe0BtYnwoo/a4v4z5gfty04DA==}
+ dependencies:
+ '@babel/runtime': 7.19.4
+ oblivious-set: 1.1.1
+ p-queue: 6.6.2
+ rimraf: 3.0.2
+ unload: 2.3.1
+ dev: false
+
/browser-process-hrtime/1.0.0:
resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==}
dev: true
@@ -4276,6 +4444,17 @@ packages:
update-browserslist-db: 1.0.4_browserslist@4.21.2
dev: true
+ /browserslist/4.21.4:
+ resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+ dependencies:
+ caniuse-lite: 1.0.30001425
+ electron-to-chromium: 1.4.284
+ node-releases: 2.0.6
+ update-browserslist-db: 1.0.10_browserslist@4.21.4
+ dev: true
+
/bser/2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
dependencies:
@@ -4335,6 +4514,10 @@ packages:
resolution: {integrity: sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==}
dev: true
+ /caniuse-lite/1.0.30001425:
+ resolution: {integrity: sha512-/pzFv0OmNG6W0ym80P3NtapU0QEiDS3VuYAZMGoLLqiC7f6FJFe1MjpQDREGApeenD9wloeytmVDj+JLXPC6qw==}
+ dev: true
+
/capture-exit/2.0.0:
resolution: {integrity: sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==}
engines: {node: 6.* || 8.* || >= 10.*}
@@ -4398,8 +4581,8 @@ packages:
static-extend: 0.1.2
dev: true
- /classnames/2.3.1:
- resolution: {integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==}
+ /classnames/2.3.2:
+ resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
dev: false
/cliui/7.0.4:
@@ -4469,8 +4652,7 @@ packages:
dev: true
/concat-map/0.0.1:
- resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
- dev: true
+ resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
/confusing-browser-globals/1.0.11:
resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==}
@@ -4511,6 +4693,10 @@ packages:
dependencies:
safe-buffer: 5.1.2
+ /convert-source-map/2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ dev: true
+
/copy-descriptor/0.1.1:
resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
engines: {node: '>=0.10.0'}
@@ -4519,7 +4705,7 @@ packages:
/core-js-compat/3.23.4:
resolution: {integrity: sha512-RkSRPe+JYEoflcsuxJWaiMPhnZoFS51FcIxm53k4KzhISCBTmaGlto9dTIrYuk0hnJc3G6pKufAKepHnBq6B6Q==}
dependencies:
- browserslist: 4.21.2
+ browserslist: 4.21.4
semver: 7.0.0
dev: true
@@ -4528,7 +4714,7 @@ packages:
requiresBuild: true
dev: true
- /cosmiconfig-typescript-loader/4.0.0_phiamshn6hdmqb4hbjl227ychy:
+ /cosmiconfig-typescript-loader/4.0.0_6y7t74n3veizl5rsceyhsjfzxy:
resolution: {integrity: sha512-cVpucSc2Tf+VPwCCR7SZzmQTQkPbkk4O01yXsYqXBIbjE1bhwqSyAgYQkRK1un4i0OPziTleqFhdkmOc4RQ/9g==}
engines: {node: '>=12', npm: '>=6'}
peerDependencies:
@@ -4539,8 +4725,8 @@ packages:
dependencies:
'@types/node': 14.18.26
cosmiconfig: 7.0.1
- ts-node: 10.9.0_xwfh4scogix3vdbcy4yenbcgby
- typescript: 4.8.3
+ ts-node: 10.9.0_kvex4k72pyqcsdotmwd3oetkri
+ typescript: 4.8.4
dev: true
/cosmiconfig/7.0.1:
@@ -4588,7 +4774,7 @@ packages:
/css-vendor/2.0.8:
resolution: {integrity: sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
is-in-browser: 1.1.3
dev: false
@@ -4611,8 +4797,8 @@ packages:
cssom: 0.3.8
dev: true
- /csstype/3.1.0:
- resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==}
+ /csstype/3.1.1:
+ resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
/damerau-levenshtein/1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -4744,8 +4930,17 @@ packages:
engines: {node: '>=8'}
dev: true
- /diff-sequences/29.0.0:
- resolution: {integrity: sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==}
+ /detect-node/2.1.0:
+ resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
+ dev: false
+
+ /diff-sequences/29.2.0:
+ resolution: {integrity: sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dev: true
+
+ /diff-sequences/29.3.1:
+ resolution: {integrity: sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dev: true
@@ -4782,8 +4977,8 @@ packages:
/dom-helpers/5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
dependencies:
- '@babel/runtime': 7.18.9
- csstype: 3.1.0
+ '@babel/runtime': 7.19.4
+ csstype: 3.1.1
dev: false
/domexception/4.0.0:
@@ -4804,11 +4999,20 @@ packages:
resolution: {integrity: sha512-dQ6Zn4ll2NofGtxPXaDfY2laIa6NyCQdqXYHdwH90GJQW0LpJJib0ZU/ERtbb0XkBEmUD2eJtagbOie3pdMiPg==}
dev: true
+ /electron-to-chromium/1.4.284:
+ resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
+ dev: true
+
/emittery/0.10.2:
resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==}
engines: {node: '>=12'}
dev: true
+ /emittery/0.13.1:
+ resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
+ engines: {node: '>=12'}
+ dev: true
+
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
@@ -4885,8 +5089,8 @@ packages:
is-symbol: 1.0.4
dev: true
- /esbuild-android-64/0.15.7:
- resolution: {integrity: sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==}
+ /esbuild-android-64/0.15.13:
+ resolution: {integrity: sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
@@ -4894,8 +5098,8 @@ packages:
dev: true
optional: true
- /esbuild-android-arm64/0.15.7:
- resolution: {integrity: sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==}
+ /esbuild-android-arm64/0.15.13:
+ resolution: {integrity: sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
@@ -4903,8 +5107,8 @@ packages:
dev: true
optional: true
- /esbuild-darwin-64/0.15.7:
- resolution: {integrity: sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==}
+ /esbuild-darwin-64/0.15.13:
+ resolution: {integrity: sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
@@ -4912,8 +5116,8 @@ packages:
dev: true
optional: true
- /esbuild-darwin-arm64/0.15.7:
- resolution: {integrity: sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==}
+ /esbuild-darwin-arm64/0.15.13:
+ resolution: {integrity: sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
@@ -4921,8 +5125,8 @@ packages:
dev: true
optional: true
- /esbuild-freebsd-64/0.15.7:
- resolution: {integrity: sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==}
+ /esbuild-freebsd-64/0.15.13:
+ resolution: {integrity: sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
@@ -4930,8 +5134,8 @@ packages:
dev: true
optional: true
- /esbuild-freebsd-arm64/0.15.7:
- resolution: {integrity: sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==}
+ /esbuild-freebsd-arm64/0.15.13:
+ resolution: {integrity: sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
@@ -4939,7 +5143,7 @@ packages:
dev: true
optional: true
- /esbuild-jest/0.5.0_esbuild@0.15.7:
+ /esbuild-jest/0.5.0_esbuild@0.15.13:
resolution: {integrity: sha512-AMZZCdEpXfNVOIDvURlqYyHwC8qC1/BFjgsrOiSL1eyiIArVtHL8YAC83Shhn16cYYoAWEW17yZn0W/RJKJKHQ==}
peerDependencies:
esbuild: '>=0.8.50'
@@ -4947,13 +5151,13 @@ packages:
'@babel/core': 7.18.6
'@babel/plugin-transform-modules-commonjs': 7.18.6_@babel+core@7.18.6
babel-jest: 26.6.3_@babel+core@7.18.6
- esbuild: 0.15.7
+ esbuild: 0.15.13
transitivePeerDependencies:
- supports-color
dev: true
- /esbuild-linux-32/0.15.7:
- resolution: {integrity: sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==}
+ /esbuild-linux-32/0.15.13:
+ resolution: {integrity: sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
@@ -4961,8 +5165,8 @@ packages:
dev: true
optional: true
- /esbuild-linux-64/0.15.7:
- resolution: {integrity: sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==}
+ /esbuild-linux-64/0.15.13:
+ resolution: {integrity: sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
@@ -4970,8 +5174,8 @@ packages:
dev: true
optional: true
- /esbuild-linux-arm/0.15.7:
- resolution: {integrity: sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==}
+ /esbuild-linux-arm/0.15.13:
+ resolution: {integrity: sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
@@ -4979,8 +5183,8 @@ packages:
dev: true
optional: true
- /esbuild-linux-arm64/0.15.7:
- resolution: {integrity: sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==}
+ /esbuild-linux-arm64/0.15.13:
+ resolution: {integrity: sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
@@ -4988,8 +5192,8 @@ packages:
dev: true
optional: true
- /esbuild-linux-mips64le/0.15.7:
- resolution: {integrity: sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==}
+ /esbuild-linux-mips64le/0.15.13:
+ resolution: {integrity: sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
@@ -4997,8 +5201,8 @@ packages:
dev: true
optional: true
- /esbuild-linux-ppc64le/0.15.7:
- resolution: {integrity: sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==}
+ /esbuild-linux-ppc64le/0.15.13:
+ resolution: {integrity: sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
@@ -5006,8 +5210,8 @@ packages:
dev: true
optional: true
- /esbuild-linux-riscv64/0.15.7:
- resolution: {integrity: sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==}
+ /esbuild-linux-riscv64/0.15.13:
+ resolution: {integrity: sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
@@ -5015,8 +5219,8 @@ packages:
dev: true
optional: true
- /esbuild-linux-s390x/0.15.7:
- resolution: {integrity: sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==}
+ /esbuild-linux-s390x/0.15.13:
+ resolution: {integrity: sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
@@ -5024,8 +5228,8 @@ packages:
dev: true
optional: true
- /esbuild-netbsd-64/0.15.7:
- resolution: {integrity: sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==}
+ /esbuild-netbsd-64/0.15.13:
+ resolution: {integrity: sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
@@ -5033,8 +5237,8 @@ packages:
dev: true
optional: true
- /esbuild-openbsd-64/0.15.7:
- resolution: {integrity: sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==}
+ /esbuild-openbsd-64/0.15.13:
+ resolution: {integrity: sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
@@ -5042,8 +5246,8 @@ packages:
dev: true
optional: true
- /esbuild-sunos-64/0.15.7:
- resolution: {integrity: sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==}
+ /esbuild-sunos-64/0.15.13:
+ resolution: {integrity: sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
@@ -5051,8 +5255,8 @@ packages:
dev: true
optional: true
- /esbuild-windows-32/0.15.7:
- resolution: {integrity: sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==}
+ /esbuild-windows-32/0.15.13:
+ resolution: {integrity: sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
@@ -5060,8 +5264,8 @@ packages:
dev: true
optional: true
- /esbuild-windows-64/0.15.7:
- resolution: {integrity: sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==}
+ /esbuild-windows-64/0.15.13:
+ resolution: {integrity: sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
@@ -5069,8 +5273,8 @@ packages:
dev: true
optional: true
- /esbuild-windows-arm64/0.15.7:
- resolution: {integrity: sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==}
+ /esbuild-windows-arm64/0.15.13:
+ resolution: {integrity: sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
@@ -5078,33 +5282,34 @@ packages:
dev: true
optional: true
- /esbuild/0.15.7:
- resolution: {integrity: sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw==}
+ /esbuild/0.15.13:
+ resolution: {integrity: sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
- '@esbuild/linux-loong64': 0.15.7
- esbuild-android-64: 0.15.7
- esbuild-android-arm64: 0.15.7
- esbuild-darwin-64: 0.15.7
- esbuild-darwin-arm64: 0.15.7
- esbuild-freebsd-64: 0.15.7
- esbuild-freebsd-arm64: 0.15.7
- esbuild-linux-32: 0.15.7
- esbuild-linux-64: 0.15.7
- esbuild-linux-arm: 0.15.7
- esbuild-linux-arm64: 0.15.7
- esbuild-linux-mips64le: 0.15.7
- esbuild-linux-ppc64le: 0.15.7
- esbuild-linux-riscv64: 0.15.7
- esbuild-linux-s390x: 0.15.7
- esbuild-netbsd-64: 0.15.7
- esbuild-openbsd-64: 0.15.7
- esbuild-sunos-64: 0.15.7
- esbuild-windows-32: 0.15.7
- esbuild-windows-64: 0.15.7
- esbuild-windows-arm64: 0.15.7
+ '@esbuild/android-arm': 0.15.13
+ '@esbuild/linux-loong64': 0.15.13
+ esbuild-android-64: 0.15.13
+ esbuild-android-arm64: 0.15.13
+ esbuild-darwin-64: 0.15.13
+ esbuild-darwin-arm64: 0.15.13
+ esbuild-freebsd-64: 0.15.13
+ esbuild-freebsd-arm64: 0.15.13
+ esbuild-linux-32: 0.15.13
+ esbuild-linux-64: 0.15.13
+ esbuild-linux-arm: 0.15.13
+ esbuild-linux-arm64: 0.15.13
+ esbuild-linux-mips64le: 0.15.13
+ esbuild-linux-ppc64le: 0.15.13
+ esbuild-linux-riscv64: 0.15.13
+ esbuild-linux-s390x: 0.15.13
+ esbuild-netbsd-64: 0.15.13
+ esbuild-openbsd-64: 0.15.13
+ esbuild-sunos-64: 0.15.13
+ esbuild-windows-32: 0.15.13
+ esbuild-windows-64: 0.15.13
+ esbuild-windows-arm64: 0.15.13
dev: true
/escalade/3.1.1:
@@ -5138,16 +5343,16 @@ packages:
source-map: 0.6.1
dev: true
- /eslint-config-prettier/8.5.0_eslint@8.23.0:
+ /eslint-config-prettier/8.5.0_eslint@8.27.0:
resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
- eslint: 8.23.0
+ eslint: 8.27.0
dev: true
- /eslint-config-react-app/7.0.1_4fdwrjms7sui3pmirbrnmoo7cy:
+ /eslint-config-react-app/7.0.1_ar3kwoulcryci6535wcfvjpsqy:
resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==}
engines: {node: '>=14.0.0'}
peerDependencies:
@@ -5158,21 +5363,21 @@ packages:
optional: true
dependencies:
'@babel/core': 7.18.6
- '@babel/eslint-parser': 7.18.2_oj2zia55h5aiwaxxlc5vhqdsce
+ '@babel/eslint-parser': 7.18.2_chvycicpad5erj2yemazgxq6vy
'@rushstack/eslint-patch': 1.1.4
- '@typescript-eslint/eslint-plugin': 5.36.2_2l2r3i3lm6jysqd4ac3ql4n2mm
- '@typescript-eslint/parser': 5.36.2_itqs5654cmlnjraw6gjzqacppi
+ '@typescript-eslint/eslint-plugin': 5.42.1_2udltptbznfmezdozpdoa2aemq
+ '@typescript-eslint/parser': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
babel-preset-react-app: 10.0.1
confusing-browser-globals: 1.0.11
- eslint: 8.23.0
- eslint-plugin-flowtype: 8.0.3_eslint@8.23.0
- eslint-plugin-import: 2.26.0_ikxfxf6lcdzwf3sfhs76tj4x7m
- eslint-plugin-jest: 25.7.0_62vgemiqelkrsjmvvizzfhdaya
- eslint-plugin-jsx-a11y: 6.6.1_eslint@8.23.0
- eslint-plugin-react: 7.31.8_eslint@8.23.0
- eslint-plugin-react-hooks: 4.6.0_eslint@8.23.0
- eslint-plugin-testing-library: 5.5.1_itqs5654cmlnjraw6gjzqacppi
- typescript: 4.8.3
+ eslint: 8.27.0
+ eslint-plugin-flowtype: 8.0.3_eslint@8.27.0
+ eslint-plugin-import: 2.26.0_gbipkkcbnjmysmpjttq6vkmfqq
+ eslint-plugin-jest: 25.7.0_kdswgjmqcx7mthqz7ow2zlfevy
+ eslint-plugin-jsx-a11y: 6.6.1_eslint@8.27.0
+ eslint-plugin-react: 7.31.10_eslint@8.27.0
+ eslint-plugin-react-hooks: 4.6.0_eslint@8.27.0
+ eslint-plugin-testing-library: 5.5.1_rmayb2veg2btbq6mbmnyivgasy
+ typescript: 4.8.4
transitivePeerDependencies:
- '@babel/plugin-syntax-flow'
- '@babel/plugin-transform-react-jsx'
@@ -5195,17 +5400,17 @@ packages:
- supports-color
dev: true
- /eslint-import-resolver-typescript/3.5.1_faomjyrlgqmwswvqymymzkxcqi:
- resolution: {integrity: sha512-U7LUjNJPYjNsHvAUAkt/RU3fcTSpbllA0//35B4eLYTX74frmOepbt7F7J3D1IGtj9k21buOpaqtDd4ZlS/BYQ==}
- engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ /eslint-import-resolver-typescript/3.5.2_dcpv4nbdr5ks2h5677xdltrk6e:
+ resolution: {integrity: sha512-zX4ebnnyXiykjhcBvKIf5TNvt8K7yX6bllTRZ14MiurKPjDpCAZujlszTdB8pcNXhZcOf+god4s9SjQa5GnytQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
eslint: '*'
eslint-plugin-import: '*'
dependencies:
debug: 4.3.4
enhanced-resolve: 5.10.0
- eslint: 8.23.0
- eslint-plugin-import: 2.26.0_ikxfxf6lcdzwf3sfhs76tj4x7m
+ eslint: 8.27.0
+ eslint-plugin-import: 2.26.0_gbipkkcbnjmysmpjttq6vkmfqq
get-tsconfig: 4.2.0
globby: 13.1.2
is-core-module: 2.10.0
@@ -5215,7 +5420,7 @@ packages:
- supports-color
dev: true
- /eslint-module-utils/2.7.3_7liths4w263er5tix7tpkwznyq:
+ /eslint-module-utils/2.7.3_go6mpysrr6ruih3ko533i3mldm:
resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
engines: {node: '>=4'}
peerDependencies:
@@ -5233,16 +5438,16 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
- '@typescript-eslint/parser': 5.36.2_itqs5654cmlnjraw6gjzqacppi
+ '@typescript-eslint/parser': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
debug: 3.2.7
eslint-import-resolver-node: 0.3.6
- eslint-import-resolver-typescript: 3.5.1_faomjyrlgqmwswvqymymzkxcqi
+ eslint-import-resolver-typescript: 3.5.2_dcpv4nbdr5ks2h5677xdltrk6e
find-up: 2.1.0
transitivePeerDependencies:
- supports-color
dev: true
- /eslint-plugin-flowtype/8.0.3_eslint@8.23.0:
+ /eslint-plugin-flowtype/8.0.3_eslint@8.27.0:
resolution: {integrity: sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
@@ -5255,12 +5460,12 @@ packages:
'@babel/plugin-transform-react-jsx':
optional: true
dependencies:
- eslint: 8.23.0
+ eslint: 8.27.0
lodash: 4.17.21
string-natural-compare: 3.0.1
dev: true
- /eslint-plugin-import/2.26.0_ikxfxf6lcdzwf3sfhs76tj4x7m:
+ /eslint-plugin-import/2.26.0_gbipkkcbnjmysmpjttq6vkmfqq:
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
engines: {node: '>=4'}
peerDependencies:
@@ -5270,14 +5475,14 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
- '@typescript-eslint/parser': 5.36.2_itqs5654cmlnjraw6gjzqacppi
+ '@typescript-eslint/parser': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
array-includes: 3.1.5
array.prototype.flat: 1.3.0
debug: 2.6.9
doctrine: 2.1.0
- eslint: 8.23.0
+ eslint: 8.27.0
eslint-import-resolver-node: 0.3.6
- eslint-module-utils: 2.7.3_7liths4w263er5tix7tpkwznyq
+ eslint-module-utils: 2.7.3_go6mpysrr6ruih3ko533i3mldm
has: 1.0.3
is-core-module: 2.9.0
is-glob: 4.0.3
@@ -5291,7 +5496,7 @@ packages:
- supports-color
dev: true
- /eslint-plugin-jest/25.7.0_62vgemiqelkrsjmvvizzfhdaya:
+ /eslint-plugin-jest/25.7.0_kdswgjmqcx7mthqz7ow2zlfevy:
resolution: {integrity: sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
peerDependencies:
@@ -5304,16 +5509,16 @@ packages:
jest:
optional: true
dependencies:
- '@typescript-eslint/eslint-plugin': 5.36.2_2l2r3i3lm6jysqd4ac3ql4n2mm
- '@typescript-eslint/experimental-utils': 5.30.6_itqs5654cmlnjraw6gjzqacppi
- eslint: 8.23.0
- jest: 29.0.2_@types+node@16.11.58
+ '@typescript-eslint/eslint-plugin': 5.42.1_2udltptbznfmezdozpdoa2aemq
+ '@typescript-eslint/experimental-utils': 5.30.6_rmayb2veg2btbq6mbmnyivgasy
+ eslint: 8.27.0
+ jest: 29.3.1_@types+node@18.11.9
transitivePeerDependencies:
- supports-color
- typescript
dev: true
- /eslint-plugin-jsx-a11y/6.6.1_eslint@8.23.0:
+ /eslint-plugin-jsx-a11y/6.6.1_eslint@8.27.0:
resolution: {integrity: sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==}
engines: {node: '>=4.0'}
peerDependencies:
@@ -5327,7 +5532,7 @@ packages:
axobject-query: 2.2.0
damerau-levenshtein: 1.0.8
emoji-regex: 9.2.2
- eslint: 8.23.0
+ eslint: 8.27.0
has: 1.0.3
jsx-ast-utils: 3.3.2
language-tags: 1.0.5
@@ -5335,7 +5540,7 @@ packages:
semver: 6.3.0
dev: true
- /eslint-plugin-prettier/4.2.1_tgumt6uwl2md3n6uqnggd6wvce:
+ /eslint-plugin-prettier/4.2.1_v7o5sx5x3wbs57ifz6wc4f76we:
resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
@@ -5346,23 +5551,23 @@ packages:
eslint-config-prettier:
optional: true
dependencies:
- eslint: 8.23.0
- eslint-config-prettier: 8.5.0_eslint@8.23.0
+ eslint: 8.27.0
+ eslint-config-prettier: 8.5.0_eslint@8.27.0
prettier: 2.7.1
prettier-linter-helpers: 1.0.0
dev: true
- /eslint-plugin-react-hooks/4.6.0_eslint@8.23.0:
+ /eslint-plugin-react-hooks/4.6.0_eslint@8.27.0:
resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
engines: {node: '>=10'}
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
dependencies:
- eslint: 8.23.0
+ eslint: 8.27.0
dev: true
- /eslint-plugin-react/7.31.8_eslint@8.23.0:
- resolution: {integrity: sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==}
+ /eslint-plugin-react/7.31.10_eslint@8.27.0:
+ resolution: {integrity: sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==}
engines: {node: '>=4'}
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
@@ -5370,7 +5575,7 @@ packages:
array-includes: 3.1.5
array.prototype.flatmap: 1.3.0
doctrine: 2.1.0
- eslint: 8.23.0
+ eslint: 8.27.0
estraverse: 5.3.0
jsx-ast-utils: 3.3.2
minimatch: 3.1.2
@@ -5384,14 +5589,14 @@ packages:
string.prototype.matchall: 4.0.7
dev: true
- /eslint-plugin-testing-library/5.5.1_itqs5654cmlnjraw6gjzqacppi:
+ /eslint-plugin-testing-library/5.5.1_rmayb2veg2btbq6mbmnyivgasy:
resolution: {integrity: sha512-plLEkkbAKBjPxsLj7x4jNapcHAg2ernkQlKKrN2I8NrQwPISZHyCUNvg5Hv3EDqOQReToQb5bnqXYbkijJPE/g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'}
peerDependencies:
eslint: ^7.5.0 || ^8.0.0
dependencies:
- '@typescript-eslint/utils': 5.36.2_itqs5654cmlnjraw6gjzqacppi
- eslint: 8.23.0
+ '@typescript-eslint/utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy
+ eslint: 8.27.0
transitivePeerDependencies:
- supports-color
- typescript
@@ -5413,13 +5618,13 @@ packages:
estraverse: 5.3.0
dev: true
- /eslint-utils/3.0.0_eslint@8.23.0:
+ /eslint-utils/3.0.0_eslint@8.27.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5'
dependencies:
- eslint: 8.23.0
+ eslint: 8.27.0
eslint-visitor-keys: 2.1.0
dev: true
@@ -5433,15 +5638,15 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
- /eslint/8.23.0:
- resolution: {integrity: sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==}
+ /eslint/8.27.0:
+ resolution: {integrity: sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
- '@eslint/eslintrc': 1.3.1
- '@humanwhocodes/config-array': 0.10.4
- '@humanwhocodes/gitignore-to-minimatch': 1.0.2
+ '@eslint/eslintrc': 1.3.3
+ '@humanwhocodes/config-array': 0.11.6
'@humanwhocodes/module-importer': 1.0.1
+ '@nodelib/fs.walk': 1.2.8
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
@@ -5449,7 +5654,7 @@ packages:
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.1.1
- eslint-utils: 3.0.0_eslint@8.23.0
+ eslint-utils: 3.0.0_eslint@8.27.0
eslint-visitor-keys: 3.3.0
espree: 9.4.0
esquery: 1.4.0
@@ -5457,15 +5662,15 @@ packages:
fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1
find-up: 5.0.0
- functional-red-black-tree: 1.0.1
glob-parent: 6.0.2
globals: 13.16.0
- globby: 11.1.0
grapheme-splitter: 1.0.4
ignore: 5.2.0
import-fresh: 3.3.0
imurmurhash: 0.1.4
is-glob: 4.0.3
+ is-path-inside: 3.0.3
+ js-sdsl: 4.1.4
js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
@@ -5529,6 +5734,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /eventemitter3/4.0.7:
+ resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+ dev: false
+
/exec-sh/0.3.6:
resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==}
dev: true
@@ -5581,26 +5790,26 @@ packages:
- supports-color
dev: true
- /expect/29.0.1:
- resolution: {integrity: sha512-yQgemsjLU+1S8t2A7pXT3Sn/v5/37LY8J+tocWtKEA0iEYYc6gfKbbJJX2fxHZmd7K9WpdbQqXUpmYkq1aewYg==}
+ /expect/29.2.2:
+ resolution: {integrity: sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/expect-utils': 29.0.1
- jest-get-type: 29.0.0
- jest-matcher-utils: 29.0.1
- jest-message-util: 29.0.1
- jest-util: 29.0.1
+ '@jest/expect-utils': 29.2.2
+ jest-get-type: 29.2.0
+ jest-matcher-utils: 29.2.2
+ jest-message-util: 29.2.1
+ jest-util: 29.2.1
dev: true
- /expect/29.0.2:
- resolution: {integrity: sha512-JeJlAiLKn4aApT4pzUXBVxl3NaZidWIOdg//smaIlP9ZMBDkHZGFd9ubphUZP9pUyDEo7bC6M0IIZR51o75qQw==}
+ /expect/29.3.1:
+ resolution: {integrity: sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/expect-utils': 29.0.2
- jest-get-type: 29.0.0
- jest-matcher-utils: 29.0.2
- jest-message-util: 29.0.2
- jest-util: 29.0.2
+ '@jest/expect-utils': 29.3.1
+ jest-get-type: 29.2.0
+ jest-matcher-utils: 29.3.1
+ jest-message-util: 29.3.1
+ jest-util: 29.3.1
dev: true
/extend-shallow/2.0.1:
@@ -5782,7 +5991,6 @@ packages:
/fs.realpath/1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
- dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
@@ -5805,10 +6013,6 @@ packages:
functions-have-names: 1.2.3
dev: true
- /functional-red-black-tree/1.0.1:
- resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==}
- dev: true
-
/functions-have-names/1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
dev: true
@@ -5904,7 +6108,6 @@ packages:
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
- dev: true
/global-dirs/0.1.1:
resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
@@ -6037,12 +6240,6 @@ packages:
dependencies:
function-bind: 1.1.1
- /history/5.3.0:
- resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
- dependencies:
- '@babel/runtime': 7.18.9
- dev: false
-
/hoist-non-react-statics/3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:
@@ -6103,8 +6300,8 @@ packages:
engines: {node: '>=10.17.0'}
dev: true
- /husky/8.0.1:
- resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==}
+ /husky/8.0.2:
+ resolution: {integrity: sha512-Tkv80jtvbnkK3mYWxPZePGFpQ/tT3HNSs/sasF9P2YfkMezDl3ON37YN6jUUI4eTg5LcyVynlb6r4eyvOmspvg==}
engines: {node: '>=14'}
hasBin: true
dev: true
@@ -6113,24 +6310,24 @@ packages:
resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==}
dev: false
- /i18next-browser-languagedetector/6.1.5:
- resolution: {integrity: sha512-11t7b39oKeZe4uyMxLSPnfw28BCPNLZgUk7zyufex0zKXZ+Bv+JnmJgoB+IfQLZwDt1d71PM8vwBX1NCgliY3g==}
+ /i18next-browser-languagedetector/7.0.1:
+ resolution: {integrity: sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
dev: false
- /i18next-http-backend/1.4.1:
- resolution: {integrity: sha512-s4Q9hK2jS29iyhniMP82z+yYY8riGTrWbnyvsSzi5TaF7Le4E7b5deTmtuaRuab9fdDcYXtcwdBgawZG+JCEjA==}
+ /i18next-http-backend/2.0.1:
+ resolution: {integrity: sha512-kzvSkOT3yhVijumDlp8/TgD1v07lYdFXsf5YYbB7Yu+K2S6PO0lKgZ4c/fyFcAKWiAiTjt9uVecBAbnJjKzhOw==}
dependencies:
cross-fetch: 3.1.5
transitivePeerDependencies:
- encoding
dev: false
- /i18next/21.9.1:
- resolution: {integrity: sha512-ITbDrAjbRR73spZAiu6+ex5WNlHRr1mY+acDi2ioTHuUiviJqSz269Le1xHAf0QaQ6GgIHResUhQNcxGwa/PhA==}
+ /i18next/22.0.5:
+ resolution: {integrity: sha512-N2AXKCLHzv1ClfLqiwbAPJHClovfRv9iplySiZ/S3LfQ7PNjkVEkVpFbhCXDsPhQd6kLp6DKjbzpowniE8Mdiw==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
dev: false
/iconv-lite/0.6.3:
@@ -6176,11 +6373,9 @@ packages:
dependencies:
once: 1.4.0
wrappy: 1.0.2
- dev: true
/inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
- dev: true
/ini/1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
@@ -6365,6 +6560,11 @@ packages:
engines: {node: '>=8'}
dev: true
+ /is-path-inside/3.0.3:
+ resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+ engines: {node: '>=8'}
+ dev: true
+
/is-plain-obj/1.1.0:
resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
engines: {node: '>=0.10.0'}
@@ -6477,8 +6677,8 @@ packages:
resolution: {integrity: sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==}
engines: {node: '>=8'}
dependencies:
- '@babel/core': 7.19.0
- '@babel/parser': 7.18.11
+ '@babel/core': 7.19.6
+ '@babel/parser': 7.19.6
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.0
semver: 6.3.0
@@ -6514,43 +6714,43 @@ packages:
istanbul-lib-report: 3.0.0
dev: true
- /jest-changed-files/29.0.0:
- resolution: {integrity: sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==}
+ /jest-changed-files/29.2.0:
+ resolution: {integrity: sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
execa: 5.1.1
p-limit: 3.1.0
dev: true
- /jest-circus/29.0.2:
- resolution: {integrity: sha512-YTPEsoE1P1X0bcyDQi3QIkpt2Wl9om9k2DQRuLFdS5x8VvAKSdYAVJufgvudhnKgM8WHvvAzhBE+1DRQB8x1CQ==}
+ /jest-circus/29.3.1:
+ resolution: {integrity: sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/environment': 29.0.2
- '@jest/expect': 29.0.2
- '@jest/test-result': 29.0.2
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
+ '@jest/environment': 29.3.1
+ '@jest/expect': 29.3.1
+ '@jest/test-result': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
chalk: 4.1.2
co: 4.6.0
dedent: 0.7.0
is-generator-fn: 2.1.0
- jest-each: 29.0.2
- jest-matcher-utils: 29.0.2
- jest-message-util: 29.0.2
- jest-runtime: 29.0.2
- jest-snapshot: 29.0.2
- jest-util: 29.0.2
+ jest-each: 29.3.1
+ jest-matcher-utils: 29.3.1
+ jest-message-util: 29.3.1
+ jest-runtime: 29.3.1
+ jest-snapshot: 29.3.1
+ jest-util: 29.3.1
p-limit: 3.1.0
- pretty-format: 29.0.2
+ pretty-format: 29.3.1
slash: 3.0.0
stack-utils: 2.0.5
transitivePeerDependencies:
- supports-color
dev: true
- /jest-cli/29.0.2_@types+node@16.11.58:
- resolution: {integrity: sha512-tlf8b+4KcUbBGr25cywIi3+rbZ4+G+SiG8SvY552m9sRZbXPafdmQRyeVE/C/R8K+TiBAMrTIUmV2SlStRJ40g==}
+ /jest-cli/29.3.1_@types+node@18.11.9:
+ resolution: {integrity: sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
@@ -6559,16 +6759,16 @@ packages:
node-notifier:
optional: true
dependencies:
- '@jest/core': 29.0.2
- '@jest/test-result': 29.0.2
- '@jest/types': 29.0.2
+ '@jest/core': 29.3.1
+ '@jest/test-result': 29.3.1
+ '@jest/types': 29.3.1
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.10
import-local: 3.1.0
- jest-config: 29.0.2_@types+node@16.11.58
- jest-util: 29.0.2
- jest-validate: 29.0.2
+ jest-config: 29.3.1_@types+node@18.11.9
+ jest-util: 29.3.1
+ jest-validate: 29.3.1
prompts: 2.4.2
yargs: 17.5.1
transitivePeerDependencies:
@@ -6577,8 +6777,8 @@ packages:
- ts-node
dev: true
- /jest-config/29.0.2_@types+node@16.11.58:
- resolution: {integrity: sha512-RU4gzeUNZAFktYVzDGimDxeYoaiTnH100jkYYZgldqFamaZukF0IqmFx8+QrzVeEWccYg10EEJT3ox1Dq5b74w==}
+ /jest-config/29.3.1_@types+node@18.11.9:
+ resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
'@types/node': '*'
@@ -6589,94 +6789,108 @@ packages:
ts-node:
optional: true
dependencies:
- '@babel/core': 7.19.0
- '@jest/test-sequencer': 29.0.2
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
- babel-jest: 29.0.2_@babel+core@7.19.0
+ '@babel/core': 7.19.6
+ '@jest/test-sequencer': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
+ babel-jest: 29.3.1_@babel+core@7.19.6
chalk: 4.1.2
ci-info: 3.3.2
deepmerge: 4.2.2
glob: 7.2.3
graceful-fs: 4.2.10
- jest-circus: 29.0.2
- jest-environment-node: 29.0.2
- jest-get-type: 29.0.0
- jest-regex-util: 29.0.0
- jest-resolve: 29.0.2
- jest-runner: 29.0.2
- jest-util: 29.0.2
- jest-validate: 29.0.2
+ jest-circus: 29.3.1
+ jest-environment-node: 29.3.1
+ jest-get-type: 29.2.0
+ jest-regex-util: 29.2.0
+ jest-resolve: 29.3.1
+ jest-runner: 29.3.1
+ jest-util: 29.3.1
+ jest-validate: 29.3.1
micromatch: 4.0.5
parse-json: 5.2.0
- pretty-format: 29.0.2
+ pretty-format: 29.3.1
slash: 3.0.0
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
dev: true
- /jest-diff/29.0.2:
- resolution: {integrity: sha512-b9l9970sa1rMXH1owp2Woprmy42qIwwll/htsw4Gf7+WuSp5bZxNhkKHDuCGKL+HoHn1KhcC+tNEeAPYBkD2Jg==}
+ /jest-diff/29.2.1:
+ resolution: {integrity: sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
chalk: 4.1.2
- diff-sequences: 29.0.0
- jest-get-type: 29.0.0
- pretty-format: 29.0.2
+ diff-sequences: 29.2.0
+ jest-get-type: 29.2.0
+ pretty-format: 29.2.1
dev: true
- /jest-docblock/29.0.0:
- resolution: {integrity: sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw==}
+ /jest-diff/29.3.1:
+ resolution: {integrity: sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dependencies:
+ chalk: 4.1.2
+ diff-sequences: 29.3.1
+ jest-get-type: 29.2.0
+ pretty-format: 29.3.1
+ dev: true
+
+ /jest-docblock/29.2.0:
+ resolution: {integrity: sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
detect-newline: 3.1.0
dev: true
- /jest-each/29.0.2:
- resolution: {integrity: sha512-+sA9YjrJl35iCg0W0VCrgCVj+wGhDrrKQ+YAqJ/DHBC4gcDFAeePtRRhpJnX9gvOZ63G7gt52pwp2PesuSEx0Q==}
+ /jest-each/29.3.1:
+ resolution: {integrity: sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/types': 29.0.2
+ '@jest/types': 29.3.1
chalk: 4.1.2
- jest-get-type: 29.0.0
- jest-util: 29.0.2
- pretty-format: 29.0.2
+ jest-get-type: 29.2.0
+ jest-util: 29.3.1
+ pretty-format: 29.3.1
dev: true
- /jest-environment-jsdom/29.0.2:
- resolution: {integrity: sha512-hWqC9FQI5yT04lTd4VJnzT5QObxq0xrSrqpGkqsYfxPeJYjyhriI7W2oJC5HZ1UbhnvA+8GS1nzgPsstvRpdVw==}
+ /jest-environment-jsdom/29.3.1:
+ resolution: {integrity: sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ canvas: ^2.5.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
dependencies:
- '@jest/environment': 29.0.2
- '@jest/fake-timers': 29.0.2
- '@jest/types': 29.0.2
+ '@jest/environment': 29.3.1
+ '@jest/fake-timers': 29.3.1
+ '@jest/types': 29.3.1
'@types/jsdom': 20.0.0
- '@types/node': 16.11.58
- jest-mock: 29.0.2
- jest-util: 29.0.2
+ '@types/node': 18.11.9
+ jest-mock: 29.3.1
+ jest-util: 29.3.1
jsdom: 20.0.0
transitivePeerDependencies:
- bufferutil
- - canvas
- supports-color
- utf-8-validate
dev: true
- /jest-environment-node/29.0.2:
- resolution: {integrity: sha512-4Fv8GXVCToRlMzDO94gvA8iOzKxQ7rhAbs8L+j8GPyTxGuUiYkV+63LecGeVdVhsL2KXih1sKnoqmH6tp89J7Q==}
+ /jest-environment-node/29.3.1:
+ resolution: {integrity: sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/environment': 29.0.2
- '@jest/fake-timers': 29.0.2
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
- jest-mock: 29.0.2
- jest-util: 29.0.2
+ '@jest/environment': 29.3.1
+ '@jest/fake-timers': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
+ jest-mock: 29.3.1
+ jest-util: 29.3.1
dev: true
- /jest-get-type/29.0.0:
- resolution: {integrity: sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==}
+ /jest-get-type/29.2.0:
+ resolution: {integrity: sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dev: true
@@ -6686,7 +6900,7 @@ packages:
dependencies:
'@jest/types': 26.6.2
'@types/graceful-fs': 4.1.5
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
anymatch: 3.1.2
fb-watchman: 2.0.1
graceful-fs: 4.2.10
@@ -6703,107 +6917,93 @@ packages:
- supports-color
dev: true
- /jest-haste-map/29.0.2:
- resolution: {integrity: sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==}
+ /jest-haste-map/29.3.1:
+ resolution: {integrity: sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/types': 29.0.2
+ '@jest/types': 29.3.1
'@types/graceful-fs': 4.1.5
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
anymatch: 3.1.2
fb-watchman: 2.0.1
graceful-fs: 4.2.10
- jest-regex-util: 29.0.0
- jest-util: 29.0.2
- jest-worker: 29.0.2
+ jest-regex-util: 29.2.0
+ jest-util: 29.3.1
+ jest-worker: 29.3.1
micromatch: 4.0.5
walker: 1.0.8
optionalDependencies:
fsevents: 2.3.2
dev: true
- /jest-leak-detector/29.0.2:
- resolution: {integrity: sha512-5f0493qDeAxjUldkBSQg5D1cLadRgZVyWpTQvfJeQwQUpHQInE21AyVHVv64M7P2Ue8Z5EZ4BAcoDS/dSPPgMw==}
+ /jest-leak-detector/29.3.1:
+ resolution: {integrity: sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- jest-get-type: 29.0.0
- pretty-format: 29.0.2
+ jest-get-type: 29.2.0
+ pretty-format: 29.3.1
dev: true
- /jest-matcher-utils/29.0.1:
- resolution: {integrity: sha512-/e6UbCDmprRQFnl7+uBKqn4G22c/OmwriE5KCMVqxhElKCQUDcFnq5XM9iJeKtzy4DUjxT27y9VHmKPD8BQPaw==}
+ /jest-matcher-utils/29.2.2:
+ resolution: {integrity: sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
chalk: 4.1.2
- jest-diff: 29.0.2
- jest-get-type: 29.0.0
- pretty-format: 29.0.2
+ jest-diff: 29.2.1
+ jest-get-type: 29.2.0
+ pretty-format: 29.2.1
dev: true
- /jest-matcher-utils/29.0.2:
- resolution: {integrity: sha512-s62YkHFBfAx0JLA2QX1BlnCRFwHRobwAv2KP1+YhjzF6ZCbCVrf1sG8UJyn62ZUsDaQKpoo86XMTjkUyO5aWmQ==}
+ /jest-matcher-utils/29.3.1:
+ resolution: {integrity: sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
chalk: 4.1.2
- jest-diff: 29.0.2
- jest-get-type: 29.0.0
- pretty-format: 29.0.2
+ jest-diff: 29.3.1
+ jest-get-type: 29.2.0
+ pretty-format: 29.3.1
dev: true
- /jest-message-util/28.1.3:
- resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+ /jest-message-util/29.2.1:
+ resolution: {integrity: sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@babel/code-frame': 7.18.6
- '@jest/types': 28.1.3
+ '@jest/types': 29.2.1
'@types/stack-utils': 2.0.1
chalk: 4.1.2
graceful-fs: 4.2.10
micromatch: 4.0.5
- pretty-format: 28.1.3
+ pretty-format: 29.2.1
slash: 3.0.0
stack-utils: 2.0.5
dev: true
- /jest-message-util/29.0.1:
- resolution: {integrity: sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==}
+ /jest-message-util/29.3.1:
+ resolution: {integrity: sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@babel/code-frame': 7.18.6
- '@jest/types': 29.0.2
+ '@jest/types': 29.3.1
'@types/stack-utils': 2.0.1
chalk: 4.1.2
graceful-fs: 4.2.10
micromatch: 4.0.5
- pretty-format: 29.0.2
+ pretty-format: 29.3.1
slash: 3.0.0
stack-utils: 2.0.5
dev: true
- /jest-message-util/29.0.2:
- resolution: {integrity: sha512-kcJAgms3ckJV0wUoLsAM40xAhY+pb9FVSZwicjFU9PFkaTNmqh9xd99/CzKse48wPM1ANUQKmp03/DpkY+lGrA==}
+ /jest-mock/29.3.1:
+ resolution: {integrity: sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@babel/code-frame': 7.18.6
- '@jest/types': 29.0.2
- '@types/stack-utils': 2.0.1
- chalk: 4.1.2
- graceful-fs: 4.2.10
- micromatch: 4.0.5
- pretty-format: 29.0.2
- slash: 3.0.0
- stack-utils: 2.0.5
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
+ jest-util: 29.3.1
dev: true
- /jest-mock/29.0.2:
- resolution: {integrity: sha512-giWXOIT23UCxHCN2VUfUJ0Q7SmiqQwfSFXlCaIhW5anITpNQ+3vuLPQdKt5wkuwM37GrbFyHIClce8AAK9ft9g==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
- dev: true
-
- /jest-pnp-resolver/1.2.2_jest-resolve@29.0.2:
+ /jest-pnp-resolver/1.2.2_jest-resolve@29.3.1:
resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==}
engines: {node: '>=6'}
peerDependencies:
@@ -6812,7 +7012,7 @@ packages:
jest-resolve:
optional: true
dependencies:
- jest-resolve: 29.0.2
+ jest-resolve: 29.3.1
dev: true
/jest-regex-util/26.0.0:
@@ -6820,94 +7020,94 @@ packages:
engines: {node: '>= 10.14.2'}
dev: true
- /jest-regex-util/28.0.2:
- resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
- dev: true
-
/jest-regex-util/29.0.0:
resolution: {integrity: sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dev: true
- /jest-resolve-dependencies/29.0.2:
- resolution: {integrity: sha512-fSAu6eIG7wtGdnPJUkVVdILGzYAP9Dj/4+zvC8BrGe8msaUMJ9JeygU0Hf9+Uor6/icbuuzQn5See1uajLnAqg==}
+ /jest-regex-util/29.2.0:
+ resolution: {integrity: sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dev: true
+
+ /jest-resolve-dependencies/29.3.1:
+ resolution: {integrity: sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- jest-regex-util: 29.0.0
- jest-snapshot: 29.0.2
+ jest-regex-util: 29.2.0
+ jest-snapshot: 29.3.1
transitivePeerDependencies:
- supports-color
dev: true
- /jest-resolve/29.0.2:
- resolution: {integrity: sha512-V3uLjSA+EHxLtjIDKTBXnY71hyx+8lusCqPXvqzkFO1uCGvVpjBfuOyp+KOLBNSuY61kM2jhepiMwt4eiJS+Vw==}
+ /jest-resolve/29.3.1:
+ resolution: {integrity: sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
chalk: 4.1.2
graceful-fs: 4.2.10
- jest-haste-map: 29.0.2
- jest-pnp-resolver: 1.2.2_jest-resolve@29.0.2
- jest-util: 29.0.2
- jest-validate: 29.0.2
+ jest-haste-map: 29.3.1
+ jest-pnp-resolver: 1.2.2_jest-resolve@29.3.1
+ jest-util: 29.3.1
+ jest-validate: 29.3.1
resolve: 1.22.1
resolve.exports: 1.1.0
slash: 3.0.0
dev: true
- /jest-runner/29.0.2:
- resolution: {integrity: sha512-+D82iPZejI8t+SfduOO1deahC/QgLFf8aJBO++Znz3l2ETtOMdM7K4ATsGWzCFnTGio5yHaRifg1Su5Ybza5Nw==}
+ /jest-runner/29.3.1:
+ resolution: {integrity: sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/console': 29.0.2
- '@jest/environment': 29.0.2
- '@jest/test-result': 29.0.2
- '@jest/transform': 29.0.2
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
+ '@jest/console': 29.3.1
+ '@jest/environment': 29.3.1
+ '@jest/test-result': 29.3.1
+ '@jest/transform': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
chalk: 4.1.2
- emittery: 0.10.2
+ emittery: 0.13.1
graceful-fs: 4.2.10
- jest-docblock: 29.0.0
- jest-environment-node: 29.0.2
- jest-haste-map: 29.0.2
- jest-leak-detector: 29.0.2
- jest-message-util: 29.0.2
- jest-resolve: 29.0.2
- jest-runtime: 29.0.2
- jest-util: 29.0.2
- jest-watcher: 29.0.2
- jest-worker: 29.0.2
+ jest-docblock: 29.2.0
+ jest-environment-node: 29.3.1
+ jest-haste-map: 29.3.1
+ jest-leak-detector: 29.3.1
+ jest-message-util: 29.3.1
+ jest-resolve: 29.3.1
+ jest-runtime: 29.3.1
+ jest-util: 29.3.1
+ jest-watcher: 29.3.1
+ jest-worker: 29.3.1
p-limit: 3.1.0
source-map-support: 0.5.13
transitivePeerDependencies:
- supports-color
dev: true
- /jest-runtime/29.0.2:
- resolution: {integrity: sha512-DO6F81LX4okOgjJLkLySv10E5YcV5NHUbY1ZqAUtofxdQE+q4hjH0P2gNsY8x3z3sqgw7O/+919SU4r18Fcuig==}
+ /jest-runtime/29.3.1:
+ resolution: {integrity: sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/environment': 29.0.2
- '@jest/fake-timers': 29.0.2
- '@jest/globals': 29.0.2
- '@jest/source-map': 29.0.0
- '@jest/test-result': 29.0.2
- '@jest/transform': 29.0.2
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
+ '@jest/environment': 29.3.1
+ '@jest/fake-timers': 29.3.1
+ '@jest/globals': 29.3.1
+ '@jest/source-map': 29.2.0
+ '@jest/test-result': 29.3.1
+ '@jest/transform': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
chalk: 4.1.2
cjs-module-lexer: 1.2.2
collect-v8-coverage: 1.0.1
glob: 7.2.3
graceful-fs: 4.2.10
- jest-haste-map: 29.0.2
- jest-message-util: 29.0.2
- jest-mock: 29.0.2
- jest-regex-util: 29.0.0
- jest-resolve: 29.0.2
- jest-snapshot: 29.0.2
- jest-util: 29.0.2
+ jest-haste-map: 29.3.1
+ jest-message-util: 29.3.1
+ jest-mock: 29.3.1
+ jest-regex-util: 29.2.0
+ jest-resolve: 29.3.1
+ jest-snapshot: 29.3.1
+ jest-util: 29.3.1
slash: 3.0.0
strip-bom: 4.0.0
transitivePeerDependencies:
@@ -6918,37 +7118,37 @@ packages:
resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==}
engines: {node: '>= 10.14.2'}
dependencies:
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
graceful-fs: 4.2.10
dev: true
- /jest-snapshot/29.0.2:
- resolution: {integrity: sha512-26C4PzGKaX5gkoKg8UzYGVy2HPVcTaROSkf0gwnHu3lGeTB7bAIJBovvVPZoiJ20IximJELQs/r8WSDRCuGX2A==}
+ /jest-snapshot/29.3.1:
+ resolution: {integrity: sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@babel/core': 7.19.0
- '@babel/generator': 7.19.0
- '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.0
- '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.19.0
- '@babel/traverse': 7.19.0
- '@babel/types': 7.19.0
- '@jest/expect-utils': 29.0.2
- '@jest/transform': 29.0.2
- '@jest/types': 29.0.2
+ '@babel/core': 7.19.6
+ '@babel/generator': 7.19.6
+ '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.6
+ '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.19.6
+ '@babel/traverse': 7.19.6
+ '@babel/types': 7.19.4
+ '@jest/expect-utils': 29.3.1
+ '@jest/transform': 29.3.1
+ '@jest/types': 29.3.1
'@types/babel__traverse': 7.17.1
'@types/prettier': 2.6.3
- babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.0
+ babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.6
chalk: 4.1.2
- expect: 29.0.2
+ expect: 29.3.1
graceful-fs: 4.2.10
- jest-diff: 29.0.2
- jest-get-type: 29.0.0
- jest-haste-map: 29.0.2
- jest-matcher-utils: 29.0.2
- jest-message-util: 29.0.2
- jest-util: 29.0.2
+ jest-diff: 29.3.1
+ jest-get-type: 29.2.0
+ jest-haste-map: 29.3.1
+ jest-matcher-utils: 29.3.1
+ jest-message-util: 29.3.1
+ jest-util: 29.3.1
natural-compare: 1.4.0
- pretty-format: 29.0.2
+ pretty-format: 29.3.1
semver: 7.3.7
transitivePeerDependencies:
- supports-color
@@ -6963,102 +7163,90 @@ packages:
engines: {node: '>= 10.14.2'}
dependencies:
'@jest/types': 26.6.2
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
chalk: 4.1.2
graceful-fs: 4.2.10
is-ci: 2.0.0
micromatch: 4.0.5
dev: true
- /jest-util/28.1.3:
- resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+ /jest-util/29.2.1:
+ resolution: {integrity: sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/types': 28.1.3
- '@types/node': 16.11.58
+ '@jest/types': 29.2.1
+ '@types/node': 18.11.9
chalk: 4.1.2
ci-info: 3.3.2
graceful-fs: 4.2.10
picomatch: 2.3.1
dev: true
- /jest-util/29.0.1:
- resolution: {integrity: sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==}
+ /jest-util/29.3.1:
+ resolution: {integrity: sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
chalk: 4.1.2
ci-info: 3.3.2
graceful-fs: 4.2.10
picomatch: 2.3.1
dev: true
- /jest-util/29.0.2:
- resolution: {integrity: sha512-ozk8ruEEEACxqpz0hN9UOgtPZS0aN+NffwQduR5dVlhN+eN47vxurtvgZkYZYMpYrsmlAEx1XabkB3BnN0GfKQ==}
+ /jest-validate/29.3.1:
+ resolution: {integrity: sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
- chalk: 4.1.2
- ci-info: 3.3.2
- graceful-fs: 4.2.10
- picomatch: 2.3.1
- dev: true
-
- /jest-validate/29.0.2:
- resolution: {integrity: sha512-AeRKm7cEucSy7tr54r3LhiGIXYvOILUwBM1S7jQkKs6YelwAlWKsmZGVrQR7uwsd31rBTnR5NQkODi1Z+6TKIQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- dependencies:
- '@jest/types': 29.0.2
+ '@jest/types': 29.3.1
camelcase: 6.3.0
chalk: 4.1.2
- jest-get-type: 29.0.0
+ jest-get-type: 29.2.0
leven: 3.1.0
- pretty-format: 29.0.2
+ pretty-format: 29.3.1
dev: true
- /jest-watch-typeahead/2.1.1_jest@29.0.2:
- resolution: {integrity: sha512-yKrj2VBDJoMLlD6lpfWfIQpSSC1GFGvPHaLnJWRhL/PiLlBDRWFwCYe21h0o+peqcVlilQhIa2g2vncaBXS73g==}
+ /jest-watch-typeahead/2.2.0_jest@29.3.1:
+ resolution: {integrity: sha512-cM3Qbw9P+jUYxqUSt53KdDDFRVBG96XA6bsIAG0zffl/gUkNK/kjWcCX7R559BgPWs2/UDrsJHPIw2f6b0qZCw==}
engines: {node: ^14.17.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
jest: ^27.0.0 || ^28.0.0 || ^29.0.0
dependencies:
ansi-escapes: 5.0.0
chalk: 4.1.2
- jest: 29.0.2_@types+node@16.11.58
- jest-regex-util: 28.0.2
- jest-watcher: 28.1.3
+ jest: 29.3.1_@types+node@18.11.9
+ jest-regex-util: 29.0.0
+ jest-watcher: 29.0.3
slash: 4.0.0
string-length: 5.0.1
strip-ansi: 7.0.1
dev: true
- /jest-watcher/28.1.3:
- resolution: {integrity: sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+ /jest-watcher/29.0.3:
+ resolution: {integrity: sha512-tQX9lU91A+9tyUQKUMp0Ns8xAcdhC9fo73eqA3LFxP2bSgiF49TNcc+vf3qgGYYK9qRjFpXW9+4RgF/mbxyOOw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/test-result': 28.1.3
- '@jest/types': 28.1.3
- '@types/node': 16.11.58
+ '@jest/test-result': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.10.2
- jest-util: 28.1.3
+ jest-util: 29.3.1
string-length: 4.0.2
dev: true
- /jest-watcher/29.0.2:
- resolution: {integrity: sha512-ds2bV0oyUdYoyrUTv4Ga5uptz4cEvmmP/JzqDyzZZanvrIn8ipxg5l3SDOAIiyuAx1VdHd2FBzeXPFO5KPH8vQ==}
+ /jest-watcher/29.3.1:
+ resolution: {integrity: sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@jest/test-result': 29.0.2
- '@jest/types': 29.0.2
- '@types/node': 16.11.58
+ '@jest/test-result': 29.3.1
+ '@jest/types': 29.3.1
+ '@types/node': 18.11.9
ansi-escapes: 4.3.2
chalk: 4.1.2
- emittery: 0.10.2
- jest-util: 29.0.2
+ emittery: 0.13.1
+ jest-util: 29.3.1
string-length: 4.0.2
dev: true
@@ -7066,22 +7254,23 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
merge-stream: 2.0.0
supports-color: 7.2.0
dev: true
- /jest-worker/29.0.2:
- resolution: {integrity: sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==}
+ /jest-worker/29.3.1:
+ resolution: {integrity: sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@types/node': 16.11.58
+ '@types/node': 18.11.9
+ jest-util: 29.3.1
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
- /jest/29.0.2_@types+node@16.11.58:
- resolution: {integrity: sha512-enziNbNUmXTcTaTP/Uq5rV91r0Yqy2UKzLUIabxMpGm9YHz8qpbJhiRnNVNvm6vzWfzt/0o97NEHH8/3udoClA==}
+ /jest/29.3.1_@types+node@18.11.9:
+ resolution: {integrity: sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
@@ -7090,16 +7279,20 @@ packages:
node-notifier:
optional: true
dependencies:
- '@jest/core': 29.0.2
- '@jest/types': 29.0.2
+ '@jest/core': 29.3.1
+ '@jest/types': 29.3.1
import-local: 3.1.0
- jest-cli: 29.0.2_@types+node@16.11.58
+ jest-cli: 29.3.1_@types+node@18.11.9
transitivePeerDependencies:
- '@types/node'
- supports-color
- ts-node
dev: true
+ /js-sdsl/4.1.4:
+ resolution: {integrity: sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==}
+ dev: true
+
/js-tokens/4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -7215,7 +7408,7 @@ packages:
/jss-plugin-camel-case/10.9.2:
resolution: {integrity: sha512-wgBPlL3WS0WDJ1lPJcgjux/SHnDuu7opmgQKSraKs4z8dCCyYMx9IDPFKBXQ8Q5dVYij1FFV0WdxyhuOOAXuTg==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
hyphenate-style-name: 1.0.4
jss: 10.9.2
dev: false
@@ -7223,21 +7416,21 @@ packages:
/jss-plugin-default-unit/10.9.2:
resolution: {integrity: sha512-pYg0QX3bBEFtTnmeSI3l7ad1vtHU42YEEpgW7pmIh+9pkWNWb5dwS/4onSfAaI0kq+dOZHzz4dWe+8vWnanoSg==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
jss: 10.9.2
dev: false
/jss-plugin-global/10.9.2:
resolution: {integrity: sha512-GcX0aE8Ef6AtlasVrafg1DItlL/tWHoC4cGir4r3gegbWwF5ZOBYhx04gurPvWHC8F873aEGqge7C17xpwmp2g==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
jss: 10.9.2
dev: false
/jss-plugin-nested/10.9.2:
resolution: {integrity: sha512-VgiOWIC6bvgDaAL97XCxGD0BxOKM0K0zeB/ECyNaVF6FqvdGB9KBBWRdy2STYAss4VVA7i5TbxFZN+WSX1kfQA==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
jss: 10.9.2
tiny-warning: 1.0.3
dev: false
@@ -7245,14 +7438,14 @@ packages:
/jss-plugin-props-sort/10.9.2:
resolution: {integrity: sha512-AP1AyUTbi2szylgr+O0OB7gkIxEGzySLITZ2GpsaoX72YMCGI2jYAc+WUhPfvUnZYiauF4zTnN4V4TGuvFjJlw==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
jss: 10.9.2
dev: false
/jss-plugin-rule-value-function/10.9.2:
resolution: {integrity: sha512-vf5ms8zvLFMub6swbNxvzsurHfUZ5Shy5aJB2gIpY6WNA3uLinEcxYyraQXItRHi5ivXGqYciFDRM2ZoVoRZ4Q==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
jss: 10.9.2
tiny-warning: 1.0.3
dev: false
@@ -7260,7 +7453,7 @@ packages:
/jss-plugin-vendor-prefixer/10.9.2:
resolution: {integrity: sha512-SxcEoH+Rttf9fEv6KkiPzLdXRmI6waOTcMkbbEFgdZLDYNIP9UKNHFy6thhbRKqv0XMQZdrEsbDyV464zE/dUA==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
css-vendor: 2.0.8
jss: 10.9.2
dev: false
@@ -7268,8 +7461,8 @@ packages:
/jss/10.9.2:
resolution: {integrity: sha512-b8G6rWpYLR4teTUbGd4I4EsnWjg7MN0Q5bSsjKhVkJVjhQDy2KzkbD2AW3TuT0RYZVmZZHKIrXDn6kjU14qkUg==}
dependencies:
- '@babel/runtime': 7.18.9
- csstype: 3.1.0
+ '@babel/runtime': 7.19.4
+ csstype: 3.1.1
is-in-browser: 1.1.3
tiny-warning: 1.0.3
dev: false
@@ -7397,8 +7590,8 @@ packages:
hasBin: true
dev: true
- /magic-string/0.26.2:
- resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==}
+ /magic-string/0.26.7:
+ resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==}
engines: {node: '>=12'}
dependencies:
sourcemap-codec: 1.4.8
@@ -7522,7 +7715,6 @@ packages:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
- dev: true
/minimist-options/4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
@@ -7582,6 +7774,10 @@ packages:
- supports-color
dev: true
+ /natural-compare-lite/1.4.0:
+ resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
+ dev: true
+
/natural-compare/1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
@@ -7739,11 +7935,14 @@ packages:
es-abstract: 1.20.1
dev: true
+ /oblivious-set/1.1.1:
+ resolution: {integrity: sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w==}
+ dev: false
+
/once/1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
- dev: true
/onetime/5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
@@ -7788,7 +7987,6 @@ packages:
/p-finally/1.0.0:
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
engines: {node: '>=4'}
- dev: true
/p-limit/1.3.0:
resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==}
@@ -7832,6 +8030,21 @@ packages:
p-limit: 3.1.0
dev: true
+ /p-queue/6.6.2:
+ resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ eventemitter3: 4.0.7
+ p-timeout: 3.2.0
+ dev: false
+
+ /p-timeout/3.2.0:
+ resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
+ engines: {node: '>=8'}
+ dependencies:
+ p-finally: 1.0.0
+ dev: false
+
/p-try/1.0.0:
resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==}
engines: {node: '>=4'}
@@ -7881,7 +8094,6 @@ packages:
/path-is-absolute/1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
- dev: true
/path-key/2.0.1:
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
@@ -7926,8 +8138,8 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /postcss/8.4.16:
- resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
+ /postcss/8.4.18:
+ resolution: {integrity: sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.4
@@ -7967,18 +8179,8 @@ packages:
react-is: 17.0.2
dev: true
- /pretty-format/28.1.3:
- resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==}
- engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
- dependencies:
- '@jest/schemas': 28.1.3
- ansi-regex: 5.0.1
- ansi-styles: 5.2.0
- react-is: 18.2.0
- dev: true
-
- /pretty-format/29.0.1:
- resolution: {integrity: sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==}
+ /pretty-format/29.2.1:
+ resolution: {integrity: sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/schemas': 29.0.0
@@ -7986,8 +8188,8 @@ packages:
react-is: 18.2.0
dev: true
- /pretty-format/29.0.2:
- resolution: {integrity: sha512-wp3CdtUa3cSJVFn3Miu5a1+pxc1iPIQTenOAn+x5erXeN1+ryTcLesV5pbK/rlW5EKwp27x38MoYfNGaNXDDhg==}
+ /pretty-format/29.3.1:
+ resolution: {integrity: sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/schemas': 29.0.0
@@ -8010,6 +8212,10 @@ packages:
object-assign: 4.1.1
react-is: 16.13.1
+ /proxy-from-env/1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+ dev: false
+
/psl/1.9.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
dev: true
@@ -8067,8 +8273,8 @@ packages:
react: 18.2.0
scheduler: 0.23.0
- /react-i18next/11.18.6_4sidbwfhen5r7txudrvpua6nty:
- resolution: {integrity: sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==}
+ /react-i18next/12.0.0_e52s5leb62x5eudkmfoztpifde:
+ resolution: {integrity: sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg==}
peerDependencies:
i18next: '>= 19.0.0'
react: '>= 16.8.0 || 18'
@@ -8080,9 +8286,9 @@ packages:
react-native:
optional: true
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.0
html-parse-stringify: 3.0.1
- i18next: 21.9.1
+ i18next: 22.0.5
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
@@ -8124,24 +8330,26 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /react-router-dom/6.3.0_biqbaboplfbrettd7655fr4n2y:
- resolution: {integrity: sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==}
+ /react-router-dom/6.4.3_biqbaboplfbrettd7655fr4n2y:
+ resolution: {integrity: sha512-MiaYQU8CwVCaOfJdYvt84KQNjT78VF0TJrA17SIQgNHRvLnXDJO6qsFqq8F/zzB1BWZjCFIrQpu4QxcshitziQ==}
+ engines: {node: '>=14'}
peerDependencies:
react: '>=16.8 || 18'
react-dom: '>=16.8 || 18'
dependencies:
- history: 5.3.0
+ '@remix-run/router': 1.0.3
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
- react-router: 6.3.0_react@18.2.0
+ react-router: 6.4.3_react@18.2.0
dev: false
- /react-router/6.3.0_react@18.2.0:
- resolution: {integrity: sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==}
+ /react-router/6.4.3_react@18.2.0:
+ resolution: {integrity: sha512-BT6DoGn6aV1FVP5yfODMOiieakp3z46P1Fk0RNzJMACzE7C339sFuHebfvWtnB4pzBvXXkHP2vscJzWRuUjTtA==}
+ engines: {node: '>=14'}
peerDependencies:
react: '>=16.8 || 18'
dependencies:
- history: 5.3.0
+ '@remix-run/router': 1.0.3
react: 18.2.0
dev: false
@@ -8172,7 +8380,7 @@ packages:
react: '>=16.6.0 || 18'
react-dom: '>=16.6.0 || 18'
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1
@@ -8248,7 +8456,7 @@ packages:
/regenerator-transform/0.15.0:
resolution: {integrity: sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==}
dependencies:
- '@babel/runtime': 7.18.9
+ '@babel/runtime': 7.19.4
dev: true
/regex-not/1.0.2:
@@ -8385,7 +8593,6 @@ packages:
hasBin: true
dependencies:
glob: 7.2.3
- dev: true
/rollup/2.78.0:
resolution: {integrity: sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg==}
@@ -8395,6 +8602,14 @@ packages:
fsevents: 2.3.2
dev: true
+ /rollup/2.79.1:
+ resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
/rsvp/4.8.5:
resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==}
engines: {node: 6.* || >= 7.*}
@@ -8784,8 +8999,8 @@ packages:
engines: {node: '>=8'}
dev: true
- /stylis/4.0.13:
- resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==}
+ /stylis/4.1.3:
+ resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==}
dev: false
/supports-color/5.5.0:
@@ -8808,14 +9023,6 @@ packages:
has-flag: 4.0.0
dev: true
- /supports-hyperlinks/2.2.0:
- resolution: {integrity: sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==}
- engines: {node: '>=8'}
- dependencies:
- has-flag: 4.0.0
- supports-color: 7.2.0
- dev: true
-
/supports-preserve-symlinks-flag/1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
@@ -8841,14 +9048,6 @@ packages:
engines: {node: '>=6'}
dev: true
- /terminal-link/2.1.1:
- resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==}
- engines: {node: '>=8'}
- dependencies:
- ansi-escapes: 4.3.2
- supports-hyperlinks: 2.2.0
- dev: true
-
/test-exclude/6.0.0:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
engines: {node: '>=8'}
@@ -8953,7 +9152,7 @@ packages:
engines: {node: '>=8'}
dev: true
- /ts-node/10.9.0_xwfh4scogix3vdbcy4yenbcgby:
+ /ts-node/10.9.0_kvex4k72pyqcsdotmwd3oetkri:
resolution: {integrity: sha512-bunW18GUyaCSYRev4DPf4SQpom3pWH29wKl0sDk5zE7ze19RImEVhCW7K4v3hHKkUyfWotU08ToE2RS+Y49aug==}
hasBin: true
peerDependencies:
@@ -8979,7 +9178,7 @@ packages:
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
- typescript: 4.8.3
+ typescript: 4.8.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
@@ -9009,14 +9208,14 @@ packages:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
dev: true
- /tsutils/3.21.0_typescript@4.8.3:
+ /tsutils/3.21.0_typescript@4.8.4:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
peerDependencies:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
dependencies:
tslib: 1.14.1
- typescript: 4.8.3
+ typescript: 4.8.4
dev: true
/type-check/0.3.2:
@@ -9074,8 +9273,8 @@ packages:
is-typedarray: 1.0.0
dev: true
- /typescript/4.8.3:
- resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==}
+ /typescript/4.8.4:
+ resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
@@ -9132,6 +9331,13 @@ packages:
engines: {node: '>= 10.0.0'}
dev: true
+ /unload/2.3.1:
+ resolution: {integrity: sha512-MUZEiDqvAN9AIDRbbBnVYVvfcR6DrjCqeU2YQMmliFZl9uaBUjTkhuDQkBiyAy8ad5bx1TXVbqZ3gg7namsWjA==}
+ dependencies:
+ '@babel/runtime': 7.19.4
+ detect-node: 2.1.0
+ dev: false
+
/unset-value/1.0.0:
resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
engines: {node: '>=0.10.0'}
@@ -9140,6 +9346,17 @@ packages:
isobject: 3.0.1
dev: true
+ /update-browserslist-db/1.0.10_browserslist@4.21.4:
+ resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+ dependencies:
+ browserslist: 4.21.4
+ escalade: 3.1.1
+ picocolors: 1.0.0
+ dev: true
+
/update-browserslist-db/1.0.4_browserslist@4.21.2:
resolution: {integrity: sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==}
hasBin: true
@@ -9191,7 +9408,7 @@ packages:
spdx-expression-parse: 3.0.1
dev: true
- /vite-plugin-eslint/1.8.1_eslint@8.23.0+vite@3.1.0:
+ /vite-plugin-eslint/1.8.1_eslint@8.27.0+vite@3.2.3:
resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==}
peerDependencies:
eslint: '>=7'
@@ -9199,13 +9416,13 @@ packages:
dependencies:
'@rollup/pluginutils': 4.2.1
'@types/eslint': 8.4.5
- eslint: 8.23.0
+ eslint: 8.27.0
rollup: 2.78.0
- vite: 3.1.0
+ vite: 3.2.3_@types+node@18.11.9
dev: true
- /vite-plugin-istanbul/3.0.1:
- resolution: {integrity: sha512-b6yBg7WHXHTaULcyb/d17/YXFcYrV6nHTiqYxOIhhbIFjgp8CgVkuP3iGQU1vRxeutrIwTa4pFNmS4uZSAKhGA==}
+ /vite-plugin-istanbul/3.0.2:
+ resolution: {integrity: sha512-eOKedaeciqJTLEAUo7mkMqXjjeAXGhHUYuiLLBUaBwj8AdO31uVOsZvKeVViRqHKyhi5YlarmGh8r7jJVlX0VQ==}
dependencies:
'@istanbuljs/load-nyc-config': 1.1.0
istanbul-lib-instrument: 5.2.0
@@ -9215,20 +9432,22 @@ packages:
- supports-color
dev: true
- /vite-plugin-svgr/2.2.1_vite@3.1.0:
- resolution: {integrity: sha512-+EqwahbwjETJH/ssA/66dNYyKN1cO0AStq96MuXmq5maU7AePBMf2lDKfQna49tJZAjtRz+R899BWCsUUP45Fg==}
+ /vite-plugin-svgr/2.2.2_vite@3.2.3:
+ resolution: {integrity: sha512-u8Ac27uZmDHTVGawpAhvLMJMuzbGeZGhe61TGeHoRQLxVhmQfIYCefa0iLbjC0ui1zFo6XZnS8EkzPITCYp85g==}
peerDependencies:
vite: ^2.6.0 || 3
dependencies:
- '@rollup/pluginutils': 4.2.1
- '@svgr/core': 6.3.1
- vite: 3.1.0
+ '@rollup/pluginutils': 5.0.1
+ '@svgr/core': 6.4.0
+ vite: 3.2.3_@types+node@18.11.9
transitivePeerDependencies:
+ - '@babel/core'
+ - rollup
- supports-color
dev: true
- /vite-tsconfig-paths/3.5.0_vite@3.1.0:
- resolution: {integrity: sha512-NKIubr7gXgh/3uniQaOytSg+aKWPrjquP6anAy+zCWEn6h9fB8z2/qdlfQrTgZWaXJ2pHVlllrSdRZltHn9P4g==}
+ /vite-tsconfig-paths/3.5.2_vite@3.2.3:
+ resolution: {integrity: sha512-xJMgHA2oJ28QCG2f+hXrcqzo7IttrSRK4A//Tp94CfuX5eetOx33qiwXHUdi3FwkHP2ocpxHuvE45Ix67gwEmQ==}
peerDependencies:
vite: '>2.0.0-0'
dependencies:
@@ -9236,45 +9455,53 @@ packages:
globrex: 0.1.2
recrawl-sync: 2.2.2
tsconfig-paths: 4.0.0
- vite: 3.1.0
+ vite: 3.2.3_@types+node@18.11.9
transitivePeerDependencies:
- supports-color
dev: true
- /vite/3.1.0:
- resolution: {integrity: sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==}
+ /vite/3.2.3_@types+node@18.11.9:
+ resolution: {integrity: sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
+ '@types/node': '>= 14'
less: '*'
sass: '*'
stylus: '*'
+ sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
+ '@types/node':
+ optional: true
less:
optional: true
sass:
optional: true
stylus:
optional: true
+ sugarss:
+ optional: true
terser:
optional: true
dependencies:
- esbuild: 0.15.7
- postcss: 8.4.16
+ '@types/node': 18.11.9
+ esbuild: 0.15.13
+ postcss: 8.4.18
resolve: 1.22.1
- rollup: 2.78.0
+ rollup: 2.79.1
optionalDependencies:
fsevents: 2.3.2
dev: true
/void-elements/3.1.0:
- resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+ resolution: {integrity: sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=}
engines: {node: '>=0.10.0'}
dev: false
/w3c-hr-time/1.0.2:
resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
+ deprecated: Use your platform's native performance.now() and performance.timeOrigin.
dependencies:
browser-process-hrtime: 1.0.0
dev: true
@@ -9369,7 +9596,6 @@ packages:
/wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
- dev: true
/write-file-atomic/3.0.3:
resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
diff --git a/web/src/components/PasswordMeter.tsx b/web/src/components/PasswordMeter.tsx
index 52c6551c3..c274eeaac 100644
--- a/web/src/components/PasswordMeter.tsx
+++ b/web/src/components/PasswordMeter.tsx
@@ -77,7 +77,7 @@ const PasswordMeter = function (props: Props) {
if (props.policy.require_special) {
required++;
- const hasSpecial = /[^0-9\w]/i.test(password);
+ const hasSpecial = /[^a-z0-9]/i.test(password);
if (hasSpecial) {
hits++;
} else {
diff --git a/web/src/constants/SearchParams.ts b/web/src/constants/SearchParams.ts
new file mode 100644
index 000000000..c23f1a2f6
--- /dev/null
+++ b/web/src/constants/SearchParams.ts
@@ -0,0 +1 @@
+export const Identifier = "id";
diff --git a/web/src/hooks/Consent.ts b/web/src/hooks/Consent.ts
deleted file mode 100644
index 08300c6e6..000000000
--- a/web/src/hooks/Consent.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { useRemoteCall } from "@hooks/RemoteCall";
-import { getConsentResponse } from "@services/Consent";
-
-export function useConsentResponse() {
- return useRemoteCall(getConsentResponse, []);
-}
diff --git a/web/src/hooks/ConsentID.ts b/web/src/hooks/ConsentID.ts
deleted file mode 100644
index b780fc601..000000000
--- a/web/src/hooks/ConsentID.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import queryString from "query-string";
-import { useLocation } from "react-router-dom";
-
-export function useConsentID() {
- const location = useLocation();
- const queryParams = queryString.parse(location.search);
- return queryParams && "consent_id" in queryParams ? (queryParams["consent_id"] as string) : undefined;
-}
diff --git a/web/src/hooks/Workflow.ts b/web/src/hooks/Workflow.ts
index f4a56a4cf..659b5546d 100644
--- a/web/src/hooks/Workflow.ts
+++ b/web/src/hooks/Workflow.ts
@@ -1,8 +1,10 @@
-import queryString from "query-string";
-import { useLocation } from "react-router-dom";
+import { useSearchParams } from "react-router-dom";
-export function useWorkflow() {
- const location = useLocation();
- const queryParams = queryString.parse(location.search);
- return queryParams && "workflow" in queryParams ? (queryParams["workflow"] as string) : undefined;
+export function useWorkflow(): [string | undefined, string | undefined] {
+ const [searchParams] = useSearchParams();
+
+ const workflow = searchParams.get("workflow");
+ const id = searchParams.get("workflow_id");
+
+ return [workflow === null ? undefined : workflow, id === null ? undefined : id];
}
diff --git a/web/src/i18n/detectors/localStorageCustom.ts b/web/src/i18n/detectors/localStorageCustom.ts
new file mode 100644
index 000000000..6c48de244
--- /dev/null
+++ b/web/src/i18n/detectors/localStorageCustom.ts
@@ -0,0 +1,41 @@
+import { CustomDetector, DetectorOptions } from "i18next-browser-languagedetector";
+
+let hasLocalStorageSupport: null | boolean = null;
+const testKey = "authelia.test";
+const testValue = "foo";
+
+const localStorageAvailable = () => {
+ if (hasLocalStorageSupport !== null) return hasLocalStorageSupport;
+
+ if (typeof window !== "undefined" && window.localStorage !== null) {
+ hasLocalStorageSupport = true;
+
+ try {
+ window.localStorage.setItem(testKey, testValue);
+ window.localStorage.removeItem(testKey);
+ } catch (e) {
+ hasLocalStorageSupport = false;
+ }
+ }
+
+ return hasLocalStorageSupport;
+};
+
+const LocalStorageCustomDetector: CustomDetector = {
+ name: "localStorageCustom",
+
+ lookup(options: DetectorOptions): string | undefined {
+ let found;
+
+ if (options.lookupLocalStorage && localStorageAvailable()) {
+ const lng = window.localStorage.getItem(options.lookupLocalStorage);
+ if (lng && lng !== "") {
+ found = lng;
+ }
+ }
+
+ return found;
+ },
+};
+
+export default LocalStorageCustomDetector;
diff --git a/web/src/i18n/index.ts b/web/src/i18n/index.ts
index 53231ccd1..63cfd5edf 100644
--- a/web/src/i18n/index.ts
+++ b/web/src/i18n/index.ts
@@ -1,41 +1,106 @@
+// Code generated by go generate. DO NOT EDIT.
+//
+// Run the following command to generate this file:
+// go run ./cmd/authelia-gen locales
+//
+
import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";
+import LocalStorageCustomDetector from "@i18n/detectors/localStorageCustom";
import { getBasePath } from "@utils/BasePath";
const basePath = getBasePath();
+const CustomLanguageDetector = new LanguageDetector();
+
+CustomLanguageDetector.addDetector(LocalStorageCustomDetector);
+
i18n.use(Backend)
- .use(LanguageDetector)
+ .use(CustomLanguageDetector)
.use(initReactI18next)
.init({
detection: {
- order: ["querystring", "navigator"],
+ order: ["querystring", "localStorageCustom", "navigator"],
lookupQuerystring: "lng",
+ lookupLocalStorage: "lng",
},
backend: {
loadPath: basePath + "/locales/{{lng}}/{{ns}}.json",
},
+ load: "all",
ns: ["portal"],
defaultNS: "portal",
- load: "all",
fallbackLng: {
default: ["en"],
+ ar: ["en"],
+ "ar-SA": ["ar", "en"],
+ cs: ["en"],
+ "cs-CZ": ["cs", "en"],
+ da: ["en"],
+ "da-DK": ["da", "en"],
de: ["en"],
+ el: ["en"],
+ "el-GR": ["el", "en"],
es: ["en"],
+ fi: ["en"],
fr: ["en"],
+ it: ["en"],
+ ja: ["en"],
+ "ja-JP": ["ja", "en"],
+ nb: ["en"],
+ "nb-NO": ["nb", "en"],
nl: ["en"],
+ no: ["en"],
+ pl: ["en"],
pt: ["en"],
+ "pt-BR": ["en"],
+ ro: ["en"],
ru: ["en"],
sv: ["en"],
"sv-SE": ["sv", "en"],
+ uk: ["en"],
+ "uk-UA": ["uk", "en"],
zh: ["en"],
"zh-CN": ["zh", "en"],
- "zh-TW": ["zh", "en"],
+ "zh-TW": ["en"],
},
- supportedLngs: ["en", "de", "es", "fr", "nl", "pt", "ru", "sv", "sv-SE", "zh", "zh-CN", "zh-TW"],
+ supportedLngs: [
+ "en",
+ "ar",
+ "ar-SA",
+ "cs",
+ "cs-CZ",
+ "da",
+ "da-DK",
+ "de",
+ "el",
+ "el-GR",
+ "es",
+ "fi",
+ "fr",
+ "it",
+ "ja",
+ "ja-JP",
+ "nb",
+ "nb-NO",
+ "nl",
+ "no",
+ "pl",
+ "pt",
+ "pt-BR",
+ "ro",
+ "ru",
+ "sv",
+ "sv-SE",
+ "uk",
+ "uk-UA",
+ "zh",
+ "zh-CN",
+ "zh-TW",
+ ],
lowerCaseLng: false,
nonExplicitSupportedLngs: true,
interpolation: {
diff --git a/web/src/models/Webauthn.ts b/web/src/models/Webauthn.ts
index 048bd65f0..24dc3b6b7 100644
--- a/web/src/models/Webauthn.ts
+++ b/web/src/models/Webauthn.ts
@@ -76,6 +76,8 @@ export interface PublicKeyCredentialJSON
clientExtensionResults: AuthenticationExtensionsClientOutputs;
response: AuthenticatorAssertionResponseJSON;
targetURL?: string;
+ workflow?: string;
+ workflowID?: string;
}
export enum AttestationResult {
diff --git a/web/src/services/Consent.ts b/web/src/services/Consent.ts
index a3a26380e..ed535f7d0 100644
--- a/web/src/services/Consent.ts
+++ b/web/src/services/Consent.ts
@@ -2,8 +2,8 @@ import { ConsentPath } from "@services/Api";
import { Get, Post } from "@services/Client";
interface ConsentPostRequestBody {
+ id?: string;
client_id: string;
- consent_id?: string;
consent: boolean;
pre_configure: boolean;
}
@@ -21,23 +21,23 @@ export interface ConsentGetResponseBody {
}
export function getConsentResponse(consentID: string) {
- return Get(ConsentPath + "?consent_id=" + consentID);
+ return Get(ConsentPath + "?id=" + consentID);
}
-export function acceptConsent(preConfigure: boolean, clientID: string, consentID?: string) {
+export function acceptConsent(preConfigure: boolean, clientID: string, consentID: string | null) {
const body: ConsentPostRequestBody = {
+ id: consentID === null ? undefined : consentID,
client_id: clientID,
- consent_id: consentID,
consent: true,
pre_configure: preConfigure,
};
return Post(ConsentPath, body);
}
-export function rejectConsent(clientID: string, consentID?: string) {
+export function rejectConsent(clientID: string, consentID: string | null) {
const body: ConsentPostRequestBody = {
+ id: consentID === null ? undefined : consentID,
client_id: clientID,
- consent_id: consentID,
consent: false,
pre_configure: false,
};
diff --git a/web/src/services/OneTimePassword.ts b/web/src/services/OneTimePassword.ts
index 51e9f3164..ec66e9e83 100644
--- a/web/src/services/OneTimePassword.ts
+++ b/web/src/services/OneTimePassword.ts
@@ -2,21 +2,20 @@ import { CompleteTOTPSignInPath } from "@services/Api";
import { PostWithOptionalResponse } from "@services/Client";
import { SignInResponse } from "@services/SignIn";
-interface CompleteTOTPSigninBody {
+interface CompleteTOTPSignInBody {
token: string;
targetURL?: string;
workflow?: string;
+ workflowID?: string;
}
-export function completeTOTPSignIn(passcode: string, targetURL?: string, workflow?: string) {
- const body: CompleteTOTPSigninBody = { token: `${passcode}` };
- if (targetURL) {
- body.targetURL = targetURL;
- }
-
- if (workflow) {
- body.workflow = workflow;
- }
+export function completeTOTPSignIn(passcode: string, targetURL?: string, workflow?: string, workflowID?: string) {
+ const body: CompleteTOTPSignInBody = {
+ token: `${passcode}`,
+ targetURL: targetURL,
+ workflow: workflow,
+ workflowID: workflowID,
+ };
return PostWithOptionalResponse(CompleteTOTPSignInPath, body);
}
diff --git a/web/src/services/PushNotification.ts b/web/src/services/PushNotification.ts
index 2a7e40872..c24b08a50 100644
--- a/web/src/services/PushNotification.ts
+++ b/web/src/services/PushNotification.ts
@@ -5,20 +5,18 @@ import {
} from "@services/Api";
import { Get, PostWithOptionalResponse } from "@services/Client";
-interface CompletePushSigninBody {
+interface CompletePushSignInBody {
targetURL?: string;
workflow?: string;
+ workflowID?: string;
}
-export function completePushNotificationSignIn(targetURL?: string, workflow?: string) {
- const body: CompletePushSigninBody = {};
- if (targetURL) {
- body.targetURL = targetURL;
- }
-
- if (workflow) {
- body.workflow = workflow;
- }
+export function completePushNotificationSignIn(targetURL?: string, workflow?: string, workflowID?: string) {
+ const body: CompletePushSignInBody = {
+ targetURL: targetURL,
+ workflow: workflow,
+ workflowID: workflowID,
+ };
return PostWithOptionalResponse(CompletePushNotificationSignInPath, body);
}
diff --git a/web/src/services/Webauthn.ts b/web/src/services/Webauthn.ts
index 176fd840d..7dc2de6ce 100644
--- a/web/src/services/Webauthn.ts
+++ b/web/src/services/Webauthn.ts
@@ -131,6 +131,8 @@ function encodeAttestationPublicKeyCredential(
function encodeAssertionPublicKeyCredential(
credential: PublicKeyCredential,
targetURL: string | undefined,
+ workflow: string | undefined,
+ workflowID: string | undefined,
): PublicKeyCredentialJSON {
const response = credential.response as AuthenticatorAssertionResponse;
@@ -154,6 +156,8 @@ function encodeAssertionPublicKeyCredential(
userHandle: userHandle,
},
targetURL: targetURL,
+ workflow: workflow,
+ workflowID: workflowID,
};
}
@@ -319,8 +323,10 @@ async function postAttestationPublicKeyCredentialResult(
export async function postAssertionPublicKeyCredentialResult(
credential: PublicKeyCredential,
targetURL: string | undefined,
+ workflow?: string,
+ workflowID?: string,
): Promise>> {
- const credentialJSON = encodeAssertionPublicKeyCredential(credential, targetURL);
+ const credentialJSON = encodeAssertionPublicKeyCredential(credential, targetURL, workflow, workflowID);
return axios.post>(WebauthnAssertionPath, credentialJSON);
}
@@ -353,7 +359,11 @@ export async function performAttestationCeremony(token: string): Promise {
+export async function performAssertionCeremony(
+ targetURL?: string,
+ workflow?: string,
+ workflowID?: string,
+): Promise {
const assertionRequestOpts = await getAssertionRequestOptions();
if (assertionRequestOpts.status !== 200 || assertionRequestOpts.options == null) {
@@ -368,7 +378,12 @@ export async function performAssertionCeremony(targetURL: string | undefined): P
return AssertionResult.Failure;
}
- const response = await postAssertionPublicKeyCredentialResult(assertionResult.credential, targetURL);
+ const response = await postAssertionPublicKeyCredentialResult(
+ assertionResult.credential,
+ targetURL,
+ workflow,
+ workflowID,
+ );
if (response.data.status === "OK" && response.status === 200) {
return AssertionResult.Success;
diff --git a/web/src/views/LoginPortal/ConsentView/ConsentView.tsx b/web/src/views/LoginPortal/ConsentView/ConsentView.tsx
index 1eb3f6625..642d14fce 100644
--- a/web/src/views/LoginPortal/ConsentView/ConsentView.tsx
+++ b/web/src/views/LoginPortal/ConsentView/ConsentView.tsx
@@ -16,10 +16,10 @@ import {
} from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
import { useTranslation } from "react-i18next";
-import { useNavigate } from "react-router-dom";
+import { useNavigate, useSearchParams } from "react-router-dom";
import { IndexRoute } from "@constants/Routes";
-import { useConsentID } from "@hooks/ConsentID";
+import { Identifier } from "@constants/SearchParams";
import { useNotifications } from "@hooks/NotificationsContext";
import { useRedirector } from "@hooks/Redirector";
import { useUserInfoGET } from "@hooks/UserInfo";
@@ -50,8 +50,9 @@ const ConsentView = function (props: Props) {
const styles = useStyles();
const { t: translate } = useTranslation();
const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
const redirect = useRedirector();
- const consentID = useConsentID();
+ const consentID = searchParams.get(Identifier);
const { createErrorNotification, resetNotification } = useNotifications();
const [response, setResponse] = useState(undefined);
const [error, setError] = useState(undefined);
@@ -68,7 +69,7 @@ const ConsentView = function (props: Props) {
}, [fetchUserInfo]);
useEffect(() => {
- if (consentID) {
+ if (consentID !== null) {
getConsentResponse(consentID)
.then((r) => {
setResponse(r);
diff --git a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx
index a9cc4fd2f..f6ed2a055 100644
--- a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx
+++ b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx
@@ -2,6 +2,7 @@ import React, { MutableRefObject, useEffect, useMemo, useRef, useState } from "r
import { Button, Checkbox, FormControlLabel, Grid, Link, Theme } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
+import { BroadcastChannel } from "broadcast-channel";
import classnames from "classnames";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
@@ -25,6 +26,7 @@ export interface Props {
onAuthenticationStart: () => void;
onAuthenticationFailure: () => void;
onAuthenticationSuccess: (redirectURL: string | undefined) => void;
+ onChannelStateChange: () => void;
}
const FirstFactorForm = function (props: Props) {
@@ -32,9 +34,9 @@ const FirstFactorForm = function (props: Props) {
const navigate = useNavigate();
const redirectionURL = useRedirectionURL();
const requestMethod = useRequestMethod();
- const workflow = useWorkflow();
+ const [workflow] = useWorkflow();
- const loginChannel = useMemo(() => new BroadcastChannel("login"), []);
+ const loginChannel = useMemo(() => new BroadcastChannel("login"), []);
const [rememberMe, setRememberMe] = useState(false);
const [username, setUsername] = useState("");
const [usernameError, setUsernameError] = useState(false);
@@ -52,9 +54,9 @@ const FirstFactorForm = function (props: Props) {
}, [usernameRef]);
useEffect(() => {
- loginChannel.addEventListener("message", (ev) => {
- if (ev.data) {
- props.onAuthenticationSuccess(redirectionURL);
+ loginChannel.addEventListener("message", (authenticated) => {
+ if (authenticated) {
+ props.onChannelStateChange();
}
});
}, [loginChannel, redirectionURL, props]);
@@ -80,7 +82,7 @@ const FirstFactorForm = function (props: Props) {
props.onAuthenticationStart();
try {
const res = await postFirstFactor(username, password, rememberMe, redirectionURL, requestMethod, workflow);
- loginChannel.postMessage(true);
+ await loginChannel.postMessage(true);
props.onAuthenticationSuccess(res ? res.redirect : undefined);
} catch (err) {
console.error(err);
diff --git a/web/src/views/LoginPortal/LoginPortal.tsx b/web/src/views/LoginPortal/LoginPortal.tsx
index ac85220dd..bcb66c86b 100644
--- a/web/src/views/LoginPortal/LoginPortal.tsx
+++ b/web/src/views/LoginPortal/LoginPortal.tsx
@@ -1,6 +1,6 @@
import React, { Fragment, ReactNode, useCallback, useEffect, useState } from "react";
-import { Route, Routes, useLocation, useNavigate } from "react-router-dom";
+import { Route, Routes, useLocation, useNavigate, useSearchParams } from "react-router-dom";
import {
AuthenticatedRoute,
@@ -14,10 +14,8 @@ import { useConfiguration } from "@hooks/Configuration";
import { useNotifications } from "@hooks/NotificationsContext";
import { useRedirectionURL } from "@hooks/RedirectionURL";
import { useRedirector } from "@hooks/Redirector";
-import { useRequestMethod } from "@hooks/RequestMethod";
import { useAutheliaState } from "@hooks/State";
import { useUserInfoPOST } from "@hooks/UserInfo";
-import { useWorkflow } from "@hooks/Workflow";
import { SecondFactorMethod } from "@models/Methods";
import { checkSafeRedirection } from "@services/SafeRedirection";
import { AuthenticationLevel } from "@services/State";
@@ -41,25 +39,31 @@ const LoginPortal = function (props: Props) {
const navigate = useNavigate();
const location = useLocation();
const redirectionURL = useRedirectionURL();
- const requestMethod = useRequestMethod();
- const workflow = useWorkflow();
const { createErrorNotification } = useNotifications();
const [firstFactorDisabled, setFirstFactorDisabled] = useState(true);
+ const [broadcastRedirect, setBroadcastRedirect] = useState(false);
const redirector = useRedirector();
const [state, fetchState, , fetchStateError] = useAutheliaState();
const [userInfo, fetchUserInfo, , fetchUserInfoError] = useUserInfoPOST();
const [configuration, fetchConfiguration, , fetchConfigurationError] = useConfiguration();
+ const [searchParams] = useSearchParams();
const redirect = useCallback(
- (pathname: string, search?: string) => {
- if (search) {
- navigate({ pathname: pathname, search: search });
+ (
+ pathname: string,
+ preserveSearchParams: boolean = true,
+ searchParamsOverride: URLSearchParams | undefined = undefined,
+ ) => {
+ if (searchParamsOverride && URLSearchParamsHasValues(searchParamsOverride)) {
+ navigate({ pathname: pathname, search: `?${searchParamsOverride.toString()}` });
+ } else if (preserveSearchParams && URLSearchParamsHasValues(searchParams)) {
+ navigate({ pathname: pathname, search: `?${searchParams.toString()}` });
} else {
navigate({ pathname: pathname });
}
},
- [navigate],
+ [navigate, searchParams],
);
// Fetch the state when portal is mounted.
@@ -115,7 +119,8 @@ const LoginPortal = function (props: Props) {
((configuration &&
configuration.available_methods.size === 0 &&
state.authentication_level >= AuthenticationLevel.OneFactor) ||
- state.authentication_level === AuthenticationLevel.TwoFactor)
+ state.authentication_level === AuthenticationLevel.TwoFactor ||
+ broadcastRedirect)
) {
try {
const res = await checkSafeRedirection(redirectionURL);
@@ -130,25 +135,19 @@ const LoginPortal = function (props: Props) {
return;
}
- const search = redirectionURL
- ? `?rd=${encodeURIComponent(redirectionURL)}${requestMethod ? `&rm=${requestMethod}` : ""}${
- workflow ? `&workflow=${workflow}` : ""
- }`
- : undefined;
-
if (state.authentication_level === AuthenticationLevel.Unauthenticated) {
setFirstFactorDisabled(false);
- redirect(IndexRoute, search);
+ redirect(IndexRoute);
} else if (state.authentication_level >= AuthenticationLevel.OneFactor && userInfo && configuration) {
if (configuration.available_methods.size === 0) {
- redirect(AuthenticatedRoute);
+ redirect(AuthenticatedRoute, false);
} else {
if (userInfo.method === SecondFactorMethod.Webauthn) {
- redirect(`${SecondFactorRoute}${SecondFactorWebauthnSubRoute}`, search);
+ redirect(`${SecondFactorRoute}${SecondFactorWebauthnSubRoute}`);
} else if (userInfo.method === SecondFactorMethod.MobilePush) {
- redirect(`${SecondFactorRoute}${SecondFactorPushSubRoute}`, search);
+ redirect(`${SecondFactorRoute}${SecondFactorPushSubRoute}`);
} else {
- redirect(`${SecondFactorRoute}${SecondFactorTOTPSubRoute}`, search);
+ redirect(`${SecondFactorRoute}${SecondFactorTOTPSubRoute}`);
}
}
}
@@ -156,16 +155,20 @@ const LoginPortal = function (props: Props) {
}, [
state,
redirectionURL,
- requestMethod,
- workflow,
redirect,
userInfo,
setFirstFactorDisabled,
configuration,
createErrorNotification,
redirector,
+ broadcastRedirect,
]);
+ const handleChannelStateChange = async () => {
+ setBroadcastRedirect(true);
+ fetchState();
+ };
+
const handleAuthSuccess = async (redirectionURL: string | undefined) => {
if (redirectionURL) {
// Do an external redirection pushed by the server.
@@ -195,6 +198,7 @@ const LoginPortal = function (props: Props) {
onAuthenticationStart={() => setFirstFactorDisabled(true)}
onAuthenticationFailure={() => setFirstFactorDisabled(false)}
onAuthenticationSuccess={handleAuthSuccess}
+ onChannelStateChange={handleChannelStateChange}
/>
}
@@ -240,3 +244,7 @@ function ComponentOrLoading(props: ComponentOrLoadingProps) {
);
}
+
+function URLSearchParamsHasValues(params?: URLSearchParams) {
+ return params ? !params.entries().next().done : false;
+}
diff --git a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx
index 8f9309603..5cd60d08f 100644
--- a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx
+++ b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx
@@ -34,7 +34,7 @@ const OneTimePasswordMethod = function (props: Props) {
props.authenticationLevel === AuthenticationLevel.TwoFactor ? State.Success : State.Idle,
);
const redirectionURL = useRedirectionURL();
- const workflow = useWorkflow();
+ const [workflow, workflowID] = useWorkflow();
const { t: translate } = useTranslation();
const { onSignInSuccess, onSignInError } = props;
@@ -69,7 +69,7 @@ const OneTimePasswordMethod = function (props: Props) {
try {
setState(State.InProgress);
- const res = await completeTOTPSignIn(passcodeStr, redirectionURL, workflow);
+ const res = await completeTOTPSignIn(passcodeStr, redirectionURL, workflow, workflowID);
setState(State.Success);
onSignInSuccessCallback(res ? res.redirect : undefined);
} catch (err) {
@@ -84,6 +84,7 @@ const OneTimePasswordMethod = function (props: Props) {
passcode,
redirectionURL,
workflow,
+ workflowID,
resp,
props.authenticationLevel,
props.registered,
diff --git a/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx b/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx
index ecccb345e..ea4b93890 100644
--- a/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx
+++ b/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx
@@ -45,7 +45,7 @@ const PushNotificationMethod = function (props: Props) {
const styles = useStyles();
const [state, setState] = useState(State.SignInInProgress);
const redirectionURL = useRedirectionURL();
- const workflow = useWorkflow();
+ const [workflow, workflowID] = useWorkflow();
const mounted = useIsMountedRef();
const [enroll_url, setEnrollUrl] = useState("");
const [devices, setDevices] = useState([] as SelectableDevice[]);
@@ -95,7 +95,7 @@ const PushNotificationMethod = function (props: Props) {
try {
setState(State.SignInInProgress);
- const res = await completePushNotificationSignIn(redirectionURL, workflow);
+ const res = await completePushNotificationSignIn(redirectionURL, workflow, workflowID);
// If the request was initiated and the user changed 2FA method in the meantime,
// the process is interrupted to avoid updating state of unmounted component.
if (!mounted.current) return;
@@ -139,6 +139,7 @@ const PushNotificationMethod = function (props: Props) {
props.duoSelfEnrollment,
redirectionURL,
workflow,
+ workflowID,
mounted,
onSignInErrorCallback,
onSignInSuccessCallback,
diff --git a/web/src/views/LoginPortal/SecondFactor/WebauthnMethod.tsx b/web/src/views/LoginPortal/SecondFactor/WebauthnMethod.tsx
index 6b42dd1c1..96fa00fcf 100644
--- a/web/src/views/LoginPortal/SecondFactor/WebauthnMethod.tsx
+++ b/web/src/views/LoginPortal/SecondFactor/WebauthnMethod.tsx
@@ -9,6 +9,7 @@ import LinearProgressBar from "@components/LinearProgressBar";
import { useIsMountedRef } from "@hooks/Mounted";
import { useRedirectionURL } from "@hooks/RedirectionURL";
import { useTimer } from "@hooks/Timer";
+import { useWorkflow } from "@hooks/Workflow";
import { AssertionResult } from "@models/Webauthn";
import { AuthenticationLevel } from "@services/State";
import {
@@ -40,6 +41,7 @@ const WebauthnMethod = function (props: Props) {
const [state, setState] = useState(State.WaitTouch);
const styles = useStyles();
const redirectionURL = useRedirectionURL();
+ const [workflow, workflowID] = useWorkflow();
const mounted = useIsMountedRef();
const [timerPercent, triggerTimer] = useTimer(signInTimeout * 1000 - 500);
@@ -112,7 +114,12 @@ const WebauthnMethod = function (props: Props) {
setState(State.InProgress);
- const response = await postAssertionPublicKeyCredentialResult(result.credential, redirectionURL);
+ const response = await postAssertionPublicKeyCredentialResult(
+ result.credential,
+ redirectionURL,
+ workflow,
+ workflowID,
+ );
if (response.data.status === "OK" && response.status === 200) {
onSignInSuccessCallback(response.data.data ? response.data.data.redirect : undefined);
@@ -135,6 +142,8 @@ const WebauthnMethod = function (props: Props) {
onSignInErrorCallback,
onSignInSuccessCallback,
redirectionURL,
+ workflow,
+ workflowID,
mounted,
triggerTimer,
props.authenticationLevel,