diff --git a/api/openapi.yml b/api/openapi.yml index 9501bfd64..77071a8ab 100644 --- a/api/openapi.yml +++ b/api/openapi.yml @@ -6,7 +6,7 @@ info: Authelia is an open-source authentication and authorization server providing 2-factor authentication and single sign-on (SSO) for your applications via a web portal. contact: - name: Authelia Support + name: Support url: https://www.authelia.com/contact/ email: team@authelia.com license: @@ -2996,9 +2996,9 @@ components: - "address" - "phone" openid.spec.IntrospectionRequest: - type: object required: - "token" + type: object properties: token: description: > @@ -3008,8 +3008,8 @@ components: this is the "refresh_token" value returned from the token endpoint as defined in OAuth 2.0 [RFC6749], Section 5.1. Other token types are outside the scope of this specification. - type: string example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn" + type: string token_type_hint: description: > A hint about the type of the token submitted for @@ -3021,27 +3021,61 @@ components: is able to detect the token type automatically. Values for this field are defined in the "OAuth Token Type Hints" registry defined in OAuth Token Revocation [RFC7009]. - type: string - example: "access_token" enum: - "access_token" - "refresh_token" + example: "access_token" + type: string openid.spec.AccessRequest.ClientAuth: + oneOf: + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Base' + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Secret' + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.JWT' + openid.spec.AccessRequest.ClientAuth.Base: + required: + - "client_id" type: object properties: client_id: description: > - REQUIRED if the client is not authenticating with the - authorization server as described in Section 3.2.1. of [RFC6749]. - The client identifier as described in Section 2.2 of [RFC6749]. + REQUIRED if the client is not authenticating with the authorization server as described in + Section 3.2.1. of [RFC6749]. The client identifier as described in Section 2.2 of [RFC6749]. + example: "my_client" type: string - example: "authelia_dc_mn123kjn12kj3123njk" + openid.spec.AccessRequest.ClientAuth.Secret: + required: + - "client_secret" + type: object + properties: client_secret: description: > REQUIRED. The client secret. The client MAY omit the parameter if the client secret is an empty string. - type: string format: password + type: string + openid.spec.AccessRequest.ClientAuth.JWT: + allOf: + - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth.Base' + - type: object + required: + - "client_assertion" + - "client_assertion_type" + properties: + client_assertion: + description: > + The value of the client_assertion_type parameter MUST be + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + enum: + - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + example: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + type: string + client_assertion_type: + description: > + A JWT signed with HS256 using the client secret value or RS256 using a registered public key. + Theoretically a properly formed JWT signed using HS256 with the client secret as the HMAC key should + work but this has not been tested. + format: password + type: string openid.spec.AccessRequest.AuthorizationCodeFlow: allOf: - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth' @@ -3051,22 +3085,22 @@ components: - "grant_type" properties: grant_type: - description: Value MUST be set to "urn:ietf:params:oauth:grant-type:device_code". - type: string + description: Value MUST be set to "code". enum: - "authorization_code" + type: string code: description: The Authorization Code. - type: string example: "authelia_ac_1j2kn3knj12n3kj12n" + type: string code_verifier: description: The Authorization Code Verifier (PKCE). - type: string example: "88a25754f7c0b3b3b88cf6cd4e29e8356b160524fdc1cb329a94471825628fd3" + type: string redirect_uri: description: The original Redirect URI used in the Authorization Request. - type: string example: "https://app.example.com/oidc/callback" + type: string openid.spec.AccessRequest.DeviceCodeFlow: allOf: - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth' @@ -3077,13 +3111,13 @@ components: properties: grant_type: description: Value MUST be set to "urn:ietf:params:oauth:grant-type:device_code". - type: string enum: - "urn:ietf:params:oauth:grant-type:device_code" + type: string device_code: description: The Device Authorization Code. - type: string example: "authelia_dc_mn123kjn12kj3123njk" + type: string openid.spec.AccessRequest.RefreshTokenFlow: allOf: - $ref: '#/components/schemas/openid.spec.AccessRequest.ClientAuth' @@ -3094,12 +3128,13 @@ components: properties: grant_type: description: Value MUST be set to "refresh_token". - type: string enum: - "refresh_token" + type: string refresh_token: description: The Refresh Token. example: "authelia_rt_1n2j3kihn12kj3n12k" + type: string scope: description: > The scope of the access request as described by @@ -3107,20 +3142,30 @@ components: not originally granted by the resource owner, and if omitted is treated as equal to the scope originally granted by the resource owner. + example: "openid profile groups" + type: string openid.spec.AccessResponse: type: object + required: + - "access_token" + - "token_type" + - "expires_in" properties: access_token: description: The access token issued by the authorization server. - type: string example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn" - refresh_token: type: string + id_token: + description: The id token issued by the authorization server. + example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + type: string + refresh_token: description: > The refresh token, which can be used to obtain new access tokens using the same authorization grant as described in Section 6. - token_type: + example: "authelia_rt_kGBoSMbfVGP2RR6Kvujv3Xg7uXV2i" type: string + token_type: description: > The access token type provides the client with the information required to successfully utilize the access token to make a protected @@ -3129,21 +3174,26 @@ components: type. enum: - "bearer" + example: "bearer" + type: string expires_in: - type: integer description: > The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated. If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value. + example: 3600 + type: integer state: - type: string description: Exactly the state value passed in the authorization request if present. - scope: + example: "5dVZhNfri5XZS6wadskuzUk4MHYCvEcUgidjMeBjsktAhY7EKB" type: string + scope: description: > The scope of the access token as described by Section 3.3 if it differs from the requested scope. + example: "openid profile groups" + type: string openid.spec.AuthorizeRequest: type: object required: @@ -3154,14 +3204,14 @@ components: properties: scope: description: The requested scope. - type: string example: "openid profile groups" + type: string response_type: $ref: '#/components/schemas/openid.spec.ResponseType' client_id: description: The OAuth 2.0 client identifier. - type: string example: "app" + type: string redirect_uri: description: > Redirection URI to which the response will be sent. This URI MUST exactly match one of the @@ -3171,15 +3221,15 @@ components: that the Client Type is confidential, as defined in Section 2.1 of OAuth 2.0, and provided the OP allows the use of http Redirection URIs in this case. The Redirection URI MAY use an alternate scheme, such as one that is intended to identify a callback into a native application. - type: string example: "https://app.example.com" + type: string state: description: > Opaque value used to maintain state between the request and the callback. Typically, Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this parameter with a browser cookie. - type: string example: "oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f" + type: string response_mode: $ref: '#/components/schemas/openid.spec.ResponseMode' nonce: @@ -3188,14 +3238,23 @@ components: The value is passed through unmodified from the Authentication Request to the ID Token. Sufficient entropy MUST be present in the nonce values used to prevent attackers from guessing values. For implementation notes, see Section 15.5.2. - type: string example: "TRMLqchoKGQNcooXvBvUy9PtmLdJGf" + type: string display: $ref: '#/components/schemas/openid.spec.DisplayType' prompt: description: > Not Supported: Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for reauthentication and consent. + enum: + - "none" + - "login" + - "consent" + - "select_account" + - "login consent" + - "login select_account" + - "consent select_account" + example: "consent" type: string max_age: description: > @@ -3273,34 +3332,32 @@ components: description: > A Subject Identifier is a locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client. - type: string enum: - "public" - "pairwise" + type: string openid.spec.ClientAuthMethod: description: The OAuth 2.0 / OpenID Connect 1.0 Client Authentication Method. - type: string enum: - "client_secret_basic" - "client_secret_post" - "client_secret_jwt" - "private_key_jwt" - "none" + type: string openid.spec.DisplayType: description: > ASCII string value that specifies how the Authorization Server displays the authentication and consent user interface pages to the End-User. - type: string - example: "page" enum: - "page" - "popup" - "touch" - "wap" + example: "page" + type: string openid.spec.ResponseType: description: The OAuth 2.0 / OpenID Connect 1.0 Response Type. - type: string - example: "code" enum: - "code" - "id_token" @@ -3310,21 +3367,21 @@ components: - "token id_token" - "code id_token token" - "none" + example: "code" + type: string openid.spec.ResponseMode: description: > Informs the Authorization Server of the mechanism to be used for returning parameters from the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED when the Response Mode that would be requested is the default mode specified for the Response Type. - type: string - example: "query" enum: - "query" - "fragment" - "form_post" + example: "query" + type: string openid.spec.GrantType: description: The OAuth 2.0 / OpenID Connect 1.0 Grant Type. - type: string - example: "authorization_code" enum: - "authorization_code" - "refresh_token" @@ -3332,21 +3389,23 @@ components: - "password" - "client_credentials" - "urn:ietf:params:oauth:grant-type:device_code" + example: "authorization_code" + type: string openid.spec.CodeChallengeMethod: description: The RFC7636 Code Challenge Verifier Method. - type: string - example: "S256" enum: - "plain" - "S256" + example: "S256" + type: string openid.spec.ClaimType: description: The representation of claims. - type: string - example: "normal" enum: - "normal" - "aggregated" - "distributed" + example: "normal" + type: string jose.spec.None: description: The JSON Web Signature Algorithm type: string @@ -3354,13 +3413,12 @@ components: - "none" jose.spec.JWS.None: description: The JSON Web Signature Algorithm - type: string oneOf: - $ref: '#/components/schemas/jose.spec.None' - $ref: '#/components/schemas/jose.spec.jws' + type: string jose.spec.jws: description: The JSON Web Signature Algorithm - type: string enum: - "HS256" - "HS384" @@ -3374,9 +3432,9 @@ components: - "PS256" - "PS384" - "PS512" + type: string jose.spec.JWE.alg: description: The JSON Web Encryption Algorithm (CEK) - type: string enum: - "RSA1_5" - "RSA-OAEP" @@ -3395,9 +3453,9 @@ components: - "PBES2-HS256+A128KW" - "PBES2-HS384+A192KW" - "PBES2-HS512+A256KW" + type: string jose.spec.JWE.enc: description: The JSON Web Encryption Algorithm (Claims) - type: string enum: - "A128CBC-HS256" - "A192CBC-HS384" @@ -3406,6 +3464,7 @@ components: - "A256CBC" - "A128GCM" - "A256GCM" + type: string jose.spec.JWK.base: type: object properties: @@ -3415,21 +3474,20 @@ components: the public key. The "use" parameter is employed to indicate whether a public key is used for encrypting data or verifying the signature on data. - type: string - example: "sig" enum: - "sig" - "enc" + example: "sig" + type: string key_ops: description: > The "key_ops" (key operations) parameter identifies the operation(s) for which the key is intended to be used. The "key_ops" parameter is intended for use cases in which public, private, or symmetric keys may be present. - type: array example: ["sign"] + type: array items: - type: string enum: - "sign" - "verify" @@ -3439,6 +3497,7 @@ components: - "unwrapKey" - "deriveKey" - "deriveBits" + type: string kid: description: > The "kid" (key ID) parameter is used to match a specific key. This @@ -3483,8 +3542,8 @@ components: OPTIONAL. type: array items: - type: string format: byte + type: string x5t: description: > The "x5t" (X.509 certificate SHA-1 thumbprint) parameter is a @@ -3493,8 +3552,8 @@ components: thumbprints are also sometimes known as certificate fingerprints. The key in the certificate MUST match the public key represented by other members of the JWK. Use of this member is OPTIONAL. - type: string format: byte + type: string x5t#S256: description: > The "x5t#S256" (X.509 certificate SHA-256 thumbprint) parameter is a @@ -3503,17 +3562,17 @@ components: thumbprints are also sometimes known as certificate fingerprints. The key in the certificate MUST match the public key represented by other members of the JWK. Use of this member is OPTIONAL. - type: string format: byte + type: string jose.spec.JWK.RSA: description: RSA Public Key in JSON Web Key format as defined by RFC7517 and RFC7518. allOf: - $ref: '#/components/schemas/jose.spec.JWK.base' - - type: object - required: + - required: - "kty" - "n" - "e" + type: object properties: kty: description: > diff --git a/cmd/authelia-scripts/cmd/gen.go b/cmd/authelia-scripts/cmd/gen.go index e51ab8ad3..57ce058e6 100644 --- a/cmd/authelia-scripts/cmd/gen.go +++ b/cmd/authelia-scripts/cmd/gen.go @@ -7,5 +7,5 @@ package cmd const ( - versionSwaggerUI = "4.16.1" + versionSwaggerUI = "4.17.0" ) 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 57e897394..9c5f1b44d 100644 --- a/docs/content/en/configuration/identity-providers/open-id-connect.md +++ b/docs/content/en/configuration/identity-providers/open-id-connect.md @@ -272,6 +272,23 @@ Allows [PKCE] `plain` challenges when set to `true`. *__Security Notice:__* Changing this value is generally discouraged. Applications should use the `S256` [PKCE] challenge method instead. +### pushed_authorizations + +Controls the behaviour of [Pushed Authorization Requests]. + +#### enforce + +{{< confkey type="boolean" default="false" required="no" >}} + +When enabled all authorization requests must use the [Pushed Authorization Requests] flow. + +#### context_lifespan + +{{< confkey type="duration" default="5m" required="no" >}} + +The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated +`request_uri` being utilized by a client. + ### cors Some [OpenID Connect 1.0] Endpoints need to allow cross-origin resource sharing, however some are optional. This section allows @@ -285,6 +302,7 @@ A list of endpoints to configure with cross-origin resource sharing headers. It option is at least in this list. The potential endpoints which this can be enabled on are as follows: * authorization +* pushed-authorization-request * token * revocation * introspection @@ -472,6 +490,12 @@ See the [Response Modes](../../integration/openid-connect/introduction.md#respon The authorization policy for this client: either `one_factor` or `two_factor`. +#### enforce_par + +{{< confkey type="boolean" default="false" required="no" >}} + +Enforces the use of a [Pushed Authorization Requests] flow for this client. + #### enforce_pkce {{< confkey type="bool" default="false" required="no" >}} @@ -550,3 +574,4 @@ To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party [Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth [Subject Identifier Type]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes [Pairwise Identifier Algorithm]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg +[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 diff --git a/docs/content/en/configuration/storage/migrations.md b/docs/content/en/configuration/storage/migrations.md index 4d987ce92..263f659de 100644 --- a/docs/content/en/configuration/storage/migrations.md +++ b/docs/content/en/configuration/storage/migrations.md @@ -36,3 +36,4 @@ this instance if you wanted to downgrade to pre1 you would need to use an Authel | 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 | | 7 | 4.37.3 | Fixed some schema inconsistencies most notably the MySQL/MariaDB Engine and Collation | +| 8 | 4.38.0 | OpenID Connect 1.0 Pushed Authorization Requests | diff --git a/docs/content/en/contributing/guidelines/accessibiliy.md b/docs/content/en/contributing/guidelines/accessibiliy.md new file mode 100644 index 000000000..b41d928a2 --- /dev/null +++ b/docs/content/en/contributing/guidelines/accessibiliy.md @@ -0,0 +1,36 @@ +--- +title: "Accessibility" +description: "Authelia Development Accessibility Guidelines" +lead: "This section covers the accessibility guidelines we aim to respect during development." +date: 2023-03-06T11:42:13+11:00 +draft: false +images: [] +menu: + contributing: + parent: "guidelines" +weight: 350 +toc: true +--- + +## Backend + +There are no specific guidelines for backend accessibility other than ensuring there are reasonable logging and this is +extremely subjective. + + +## Frontend + +### Translations + +We aim to ensure as much of the web frontend information displayed to users is translated by default. This allows for +both automatic and manual translations by the community to be contributed to the code base. In addition it allows for +admins to locally override these values. + +### Responsive Design + +We aim to make the web frontend responsive to multiple screen resolutions. There are a few guidelines which we aim to +abide by: + +- The available space is utilized efficiently in order to avoid scrolling where possible. +- The user only has to scroll in one direction to view available information. This direction should always be + vertically. diff --git a/docs/content/en/contributing/guidelines/style.md b/docs/content/en/contributing/guidelines/style.md index 9d559985b..e99e5bb8f 100644 --- a/docs/content/en/contributing/guidelines/style.md +++ b/docs/content/en/contributing/guidelines/style.md @@ -8,7 +8,7 @@ images: [] menu: contributing: parent: "guidelines" -weight: 320 +weight: 350 toc: true aliases: - /docs/contributing/style-guide.html diff --git a/docs/content/en/integration/openid-connect/introduction.md b/docs/content/en/integration/openid-connect/introduction.md index 4b06ae438..6ab7ba46c 100644 --- a/docs/content/en/integration/openid-connect/introduction.md +++ b/docs/content/en/integration/openid-connect/introduction.md @@ -209,14 +209,71 @@ These endpoints can be utilized to discover other endpoints and metadata about t These endpoints implement OpenID Connect elements. -| Endpoint | Path | Discovery Attribute | -|:-------------------:|:-----------------------------------------------:|:----------------------:| -| [JSON Web Key Sets] | https://auth.example.com/jwks.json | jwks_uri | -| [Authorization] | https://auth.example.com/api/oidc/authorization | authorization_endpoint | -| [Token] | https://auth.example.com/api/oidc/token | token_endpoint | -| [UserInfo] | https://auth.example.com/api/oidc/userinfo | userinfo_endpoint | -| [Introspection] | https://auth.example.com/api/oidc/introspection | introspection_endpoint | -| [Revocation] | https://auth.example.com/api/oidc/revocation | revocation_endpoint | +| Endpoint | Path | Discovery Attribute | +|:-------------------------------:|:--------------------------------------------------------------:|:-------------------------------------:| +| [JSON Web Key Set] | https://auth.example.com/jwks.json | jwks_uri | +| [Authorization] | https://auth.example.com/api/oidc/authorization | authorization_endpoint | +| [Pushed Authorization Requests] | https://auth.example.com/api/oidc/pushed-authorization-request | pushed_authorization_request_endpoint | +| [Token] | https://auth.example.com/api/oidc/token | token_endpoint | +| [UserInfo] | https://auth.example.com/api/oidc/userinfo | userinfo_endpoint | +| [Introspection] | https://auth.example.com/api/oidc/introspection | introspection_endpoint | +| [Revocation] | https://auth.example.com/api/oidc/revocation | revocation_endpoint | + +## Security + +The following information covers some security topics some users may wish to be familiar with. + +#### Pushed Authorization Requests Endpoint + +The [Pushed Authorization Requests] endpoint is discussed in depth in [RFC9126] as well as in the +[OAuth 2.0 Pushed Authorization Requests](https://oauth.net/2/pushed-authorization-requests/) documentation. + +Essentially it's a special endpoint that takes the same parameters as the [Authorization] endpoint (including +[Proof Key Code Exchange](#proof-key-code-exchange)) with a few caveats: + +1. The same [Client Authentication] mechanism required by the [Token] endpoint **MUST** be used. +2. The request **MUST** use the [HTTP POST method]. +3. The request **MUST** use the `application/x-www-form-urlencoded` content type (i.e. the parameters **MUST** be in the + body, not the URI). +4. The request **MUST** occur over the back-channel. + +The response of this endpoint is a JSON Object with two key-value pairs: +- `request_uri` +- `expires_in` + +The `expires_in` indicates how long the `request_uri` is valid for. The `request_uri` is used as a parameter to the +[Authorization] endpoint instead of the standard parameters (as the `request_uri` parameter). + +The advantages of this approach are as follows: + +1. [Pushed Authorization Requests] cannot be created or influenced by any party other than the Relying Party (client). +2. Since you can force all [Authorization] requests to be initiated via [Pushed Authorization Requests] you drastically + improve the authorization flows resistance to phishing attacks (this can be done globally or on a per-client basis). +3. Since the [Pushed Authorization Requests] endpoint requires all of the same [Client Authentication] mechanisms as the + [Token] endpoint: + 1. Clients using the confidential [Client Type] can't have [Pushed Authorization Requests] generated by parties who do not + have the credentials. + 2. Clients using the public [Client Type] and utilizing [Proof Key Code Exchange](#proof-key-code-exchange) never + transmit the verifier over any front-channel making even the `plain` challenge method relatively secure. + +#### Proof Key Code Exchange + +The [Proof Key Code Exchange] mechanism is discussed in depth in [RFC7636] as well as in the +[OAuth 2.0 Proof Key Code Exchange](https://oauth.net/2/pkce/) documentation. + +Essentially a random opaque value is generated by the Relying Party and optionally (but recommended) passed through a +SHA256 hash. The original value is saved by the Relying Party, and the hashed value is sent in the [Authorization] +request in the `code_verifier` parameter with the `code_challenge_method` set to `S256` (or `plain` using a bad practice +of not hashing the opaque value). + +When the Relying Party requests the token from the [Token] endpoint, they must include the `code_verifier` parameter +again (in the body), but this time they send the value without it being hashed. + +The advantages of this approach are as follows: + +1. Provided the value was hashed it's certain that the Relying Party which generated the authorization request is the + same party as the one requesting the token or is permitted by the Relying Party to make this request. +2. Even when using the public [Client Type] there is a form of authentication on the [Token] endpoint. [ID Token]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken [Access Token]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.4 @@ -230,14 +287,23 @@ These endpoints implement OpenID Connect elements. [OpenID Connect Discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html [OAuth 2.0 Authorization Server Metadata]: https://datatracker.ietf.org/doc/html/rfc8414 -[JSON Web Key Sets]: https://datatracker.ietf.org/doc/html/rfc7517#section-5 +[JSON Web Key Set]: https://datatracker.ietf.org/doc/html/rfc7517#section-5 [Authorization]: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint +[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 [Token]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint [UserInfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo [Introspection]: https://datatracker.ietf.org/doc/html/rfc7662 [Revocation]: https://datatracker.ietf.org/doc/html/rfc7009 +[Proof Key Code Exchange]: https://www.rfc-editor.org/rfc/rfc7636.html -[RFC8176]: https://datatracker.ietf.org/doc/html/rfc8176 -[RFC4122]: https://datatracker.ietf.org/doc/html/rfc4122 [Subject Identifier Types]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes +[Client Authentication]: https://datatracker.ietf.org/doc/html/rfc6749#section-2.3 +[Client Type]: https://oauth.net/2/client-types/ +[HTTP POST method]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST +[Proof Key Code Exchange]: #proof-key-code-exchange + +[RFC4122]: https://datatracker.ietf.org/doc/html/rfc4122 +[RFC7636]: https://datatracker.ietf.org/doc/html/rfc7636 +[RFC8176]: https://datatracker.ietf.org/doc/html/rfc8176 +[RFC9126]: https://datatracker.ietf.org/doc/html/rfc9126 diff --git a/docs/content/en/integration/openid-connect/specific-information.md b/docs/content/en/integration/openid-connect/specific-information.md index 81d35a99f..b13069cb2 100644 --- a/docs/content/en/integration/openid-connect/specific-information.md +++ b/docs/content/en/integration/openid-connect/specific-information.md @@ -53,3 +53,20 @@ deprecated__* and we instead recommended that users remove this from their confi 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 as is the `$plaintext$` prefix. + +## Frequently Asked Questions + +### Why isn't my application able to retrieve the token even though I've consented? + +The most common cause for this issue is when the affected application can not make requests to the Token [Endpoint]. +This becomes obvious when the log level is set to `debug` or `trace` and a presence of requests to the Authorization +[Endpoint] without errors but an absence of requests made to the Token [Endpoint]. + +These requests can be identified by looking at the `path` field in the logs, or by messages prefixed with +`Authorization Request` indicating a request to the Authorization [Endpoint] and `Access Request` indicating a request +to the Token [Endpoint]. + +All causes should be clearly logged by the client application, and all errors that do not match this scenario are +clearly logged by Authelia. It's not possible for us to log requests that never occur however. + +[Endpoint]: ./introduction.md#discoverable-endpoints diff --git a/docs/content/en/integration/proxies/nginx.md b/docs/content/en/integration/proxies/nginx.md index 373834264..b53de1df4 100644 --- a/docs/content/en/integration/proxies/nginx.md +++ b/docs/content/en/integration/proxies/nginx.md @@ -383,7 +383,7 @@ proxy_set_header X-Forwarded-For $remote_addr; set $upstream_authelia http://authelia:9091/api/authz/auth-request; ## Virtual endpoint created by nginx to forward auth requests. -location /authelia { +location /internal/authelia/authz { ## Essential Proxy Configuration internal; proxy_pass $upstream_authelia; @@ -423,7 +423,7 @@ and is paired with [authelia-location.conf](#authelia-locationconf).* {{< details "/config/nginx/snippets/authelia-authrequest.conf" >}} ```nginx ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. -auth_request /authelia; +auth_request /internal/authelia/authz; ## Set the $target_url variable based on the original request. @@ -478,7 +478,7 @@ implementation `AuthRequest` which contains the `HeaderAuthorization` and `Heade set $upstream_authelia http://authelia:9091/api/authz/auth-request/basic; # Virtual endpoint created by nginx to forward auth requests. -location /authelia-basic { +location /internal/authelia/authz/basic { ## Essential Proxy Configuration internal; proxy_pass $upstream_authelia; @@ -526,7 +526,7 @@ endpoint. It's recommended to use [authelia-authrequest.conf](#authelia-authrequ {{< details "/config/nginx/snippets/authelia-authrequest-basic.conf" >}} ```nginx ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. -auth_request /authelia-basic; +auth_request /internal/authelia/authz/basic; ## Comment this line if you're using nginx without the http_set_misc module. set_escape_uri $target_url $scheme://$http_host$request_uri; @@ -570,7 +570,7 @@ if ($request_uri = "/force-basic") { } ## A new virtual endpoint to used if the auth_request failed -location /authelia-detect { +location /internal/authelia/authz/detect { internal; if ($is_basic_auth) { @@ -598,7 +598,7 @@ endpoint. It's recommended to use [authelia-authrequest.conf](#authelia-authrequ {{< details "/config/nginx/snippets/authelia-authrequest-detect.conf" >}} ```nginx ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. -auth_request /authelia; +auth_request /internal/authelia/authz; ## Comment this line if you're using nginx without the http_set_misc module. set_escape_uri $target_url $scheme://$http_host$request_uri; @@ -619,7 +619,7 @@ proxy_set_header Remote-Name $name; proxy_set_header Remote-Email $email; ## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal. -error_page 401 =302 /authelia-detect?rd=$target_url; +error_page 401 =302 /internal/authelia/authz/detect?rd=$target_url; ``` {{< /details >}} diff --git a/docs/content/en/integration/proxies/support.md b/docs/content/en/integration/proxies/support.md index 01711ce27..6364fde3a 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 | [Implementation] | [Standard](#standard) | [Kubernetes](#kubernetes) | [XHR Redirect](#xhr-redirect) | [Request Method](#request-method) | -|:---------------------:|:----------------:|:------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------:|:---------------------------------:| -| [Traefik] | [ForwardAuth] | {{% support support="full" link="traefik.md" %}} | {{% support support="full" link="../../integration/kubernetes/traefik-ingress.md" %}} | {{% support support="full" %}} | {{% support support="full" %}} | -| [Caddy] | [ForwardAuth] | {{% support support="full" link="caddy.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} | -| [Envoy] | [ExtAuthz] | {{% support support="full" link="envoy.md" %}} | {{% support support="full" link="../../integration/kubernetes/istio.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | -| [NGINX] | [AuthRequest] | {{% support support="full" link="nginx.md" %}} | {{% support support="full" link="../../integration/kubernetes/nginx-ingress.md" %}} | {{% support %}} | {{% support support="full" %}} | -| [NGINX Proxy Manager] | [AuthRequest] | {{% support support="full" link="nginx-proxy-manager/index.md" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} | -| [SWAG] | [AuthRequest] | {{% support support="full" link="swag.md" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} | -| [HAProxy] | [AuthRequest] | {{% support support="full" link="haproxy.md" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | -| [Skipper] | [ForwardAuth] | {{% support support="full" link="skipper.md" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | -| [Traefik] 1.x | [ForwardAuth] | {{% support support="full" link="traefikv1.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} | -| [Apache] | N/A | {{% support link="#apache" %}} | {{% support %}} | {{% support %}} | {{% support %}} | -| [IIS] | N/A | {{% support link="#iis" %}} | {{% support %}} | {{% support %}} | {{% support %}} | +| Proxy | [Implementation] | [Standard](#standard) | [Kubernetes](#kubernetes) | [XHR Redirect](#xhr-redirect) | [Request Method](#request-method) | +|:---------------------------------------:|:----------------:|:---------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------:|:---------------------------------:| +| [Traefik] ([guide](/i/traefik)) | [ForwardAuth] | {{% support support="full" link="/i/traefik" %}} | {{% support support="full" link="../../integration/kubernetes/traefik-ingress.md" %}} | {{% support support="full" %}} | {{% support support="full" %}} | +| [Caddy] ([guide](/i/caddy)) | [ForwardAuth] | {{% support support="full" link="/i/caddy" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} | +| [Envoy] ([guide](/i/envoy)) | [ExtAuthz] | {{% support support="full" link="/i/envoy" %}} | {{% support support="full" link="../../integration/kubernetes/istio.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | +| [NGINX] ([guide](/i/nginx)) | [AuthRequest] | {{% support support="full" link="/i/nginx" %}} | {{% support support="full" link="../../integration/kubernetes/nginx-ingress.md" %}} | {{% support %}} | {{% support support="full" %}} | +| [NGINX Proxy Manager] ([guide](/i/npm)) | [AuthRequest] | {{% support support="full" link="/i/npm" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} | +| [SWAG] ([guide](/i/swag)) | [AuthRequest] | {{% support support="full" link="/i/swag" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} | +| [HAProxy] ([guide](/i/haproxy)) | [AuthRequest] | {{% support support="full" link="/i/haproxy" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | +| [Skipper] ([guide](/i/skipper)) | [ForwardAuth] | {{% support support="full" link="/i/skipper" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | +| [Traefik] 1.x ([guide](/i/traefik/v1)) | [ForwardAuth] | {{% support support="full" link="/i/traefik/v1" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} | +| [Apache] | N/A | {{% support link="#apache" %}} | {{% support %}} | {{% support %}} | {{% support %}} | +| [IIS] | N/A | {{% support link="#iis" %}} | {{% support %}} | {{% support %}} | {{% support %}} | [ForwardAuth]: ../../reference/guides/proxy-authorization.md#forwardauth [AuthRequest]: ../../reference/guides/proxy-authorization.md#authrequest diff --git a/docs/content/en/integration/proxies/swag.md b/docs/content/en/integration/proxies/swag.md index 27bf138f8..242b3b6e1 100644 --- a/docs/content/en/integration/proxies/swag.md +++ b/docs/content/en/integration/proxies/swag.md @@ -57,7 +57,7 @@ In addition this represents a bad user experience in some instances such as: - Users sometimes visit the `https://app.example.com/authelia` 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). + redirected back to their original URL) - Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/open-id-connect.md) in which case it also doesn't represent a good user experience as the `issuer` will be `https://app.example.com/authelia` for example @@ -147,8 +147,8 @@ services: - '443:443' volumes: - ${PWD}/data/swag:/config + ## Uncomment the line below if you want to use the Authelia configuration snippets. #- ${PWD}/data/nginx/snippets:/snippets:ro - ## Uncomment the above line if you want to use the Authelia configuration snippets. environment: PUID: '1000' PGID: '1000' diff --git a/docs/content/en/integration/proxies/traefikv1.md b/docs/content/en/integration/proxies/traefikv1.md index 7f0e0f598..41bd52201 100644 --- a/docs/content/en/integration/proxies/traefikv1.md +++ b/docs/content/en/integration/proxies/traefikv1.md @@ -11,6 +11,7 @@ menu: weight: 371 toc: true aliases: + - /i/traefik/v1 - /docs/deployment/supported-proxies/traefik1.x.html --- diff --git a/docs/content/en/overview/prologue/supported-proxies.md b/docs/content/en/overview/prologue/supported-proxies.md index d834d6973..dfb960f2c 100644 --- a/docs/content/en/overview/prologue/supported-proxies.md +++ b/docs/content/en/overview/prologue/supported-proxies.md @@ -14,19 +14,19 @@ toc: false The following table is a support matrix for Authelia features and specific reverse proxies. -| Proxy | Standard | Kubernetes | XHR Redirect | Request Method | -|:---------------------:|:--------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------:|:---------------------------------:| -| [Traefik] | {{% support support="full" link="../../integration/proxies/traefik.md" %}} | {{% support support="full" link="../../integration/kubernetes/traefik-ingress.md" %}} | {{% support support="full" %}} | {{% support support="full" %}} | -| [Caddy] | {{% support support="full" link="../../integration/proxies/caddy.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} | -| [Envoy] | {{% support support="full" link="../../integration/proxies/envoy.md" %}} | {{% support support="full" link="../../integration/kubernetes/istio.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | -| [NGINX] | {{% support support="full" link="../../integration/proxies/nginx.md" %}} | {{% support support="full" link="../../integration/kubernetes/nginx-ingress.md" %}} | {{% support %}} | {{% support support="full" %}} | -| [NGINX Proxy Manager] | {{% support support="full" link="../../integration/proxies/nginx-proxy-manager/index.md" %}} | {{% support %}} | {{% support %}} | {{% support support="full" %}} | -| [SWAG] | {{% support support="full" link="../../integration/proxies/swag.md" %}} | {{% support %}} | {{% support %}} | {{% support support="full" %}} | -| [HAProxy] | {{% support support="full" link="../../integration/proxies/haproxy.md" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | -| [Traefik] 1.x | {{% support support="full" link="../../integration/proxies/traefikv1.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} | -| [Skipper] | {{% support support="full" link="../../integration/proxies/skipper.md" %}} | {{% support %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | -| [Apache] | {{% support %}} | {{% support %}} | {{% support %}} | {{% support %}} | -| [IIS] | {{% support %}} | {{% support %}} | {{% support %}} | {{% support %}} | +| Proxy | Standard | Kubernetes | XHR Redirect | Request Method | +|:---------------------------------------:|:-------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------:|:---------------------------------:|:---------------------------------:| +| [Traefik] ([guide](/i/traefik)) | {{% support support="full" link="/i/traefik" %}} | {{% support support="full" link="../../integration/kubernetes/traefik-ingress.md" %}} | {{% support support="full" %}} | {{% support support="full" %}} | +| [Caddy] ([guide](/i/caddy)) | {{% support support="full" link="/i/caddy" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} | +| [Envoy] ([guide](/i/envoy)) | {{% support support="full" link="/i/envoy" %}} | {{% support support="full" link="../../integration/kubernetes/istio.md" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | +| [NGINX] ([guide](/i/nginx)) | {{% support support="full" link="/i/nginx" %}} | {{% support support="full" link="../../integration/kubernetes/nginx-ingress.md" %}} | {{% support %}} | {{% support support="full" %}} | +| [NGINX Proxy Manager] ([guide](/i/npm)) | {{% support support="full" link="/i/npm" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} | +| [SWAG] ([guide](/i/swag)) | {{% support support="full" link="/i/swag" %}} | {{% support support="unknown" %}} | {{% support %}} | {{% support support="full" %}} | +| [HAProxy] ([guide](/i/haproxy)) | {{% support support="full" link="/i/haproxy" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | +| [Skipper] ([guide](/i/skipper)) | {{% support support="full" link="/i/skipper" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | {{% support support="unknown" %}} | +| [Traefik] 1.x ([guide](/i/traefik/v1)) | {{% support support="full" link="/i/traefik/v1" %}} | {{% support support="unknown" %}} | {{% support support="full" %}} | {{% support support="full" %}} | +| [Apache] | {{% support %}} | {{% support %}} | {{% support %}} | {{% support %}} | +| [IIS] | {{% support %}} | {{% support %}} | {{% support %}} | {{% support %}} | Legend: diff --git a/docs/content/en/reference/guides/frequently-asked-questions.md b/docs/content/en/reference/guides/frequently-asked-questions.md new file mode 100644 index 000000000..128a04536 --- /dev/null +++ b/docs/content/en/reference/guides/frequently-asked-questions.md @@ -0,0 +1,17 @@ +--- +title: "Frequently Asked Questions" +description: "This guide shows a list of other frequently asked question documents as well as some general ones" +lead: "This guide shows a list of other frequently asked question documents as well as some general ones." +date: 2023-03-06T11:04:10+11:00 +draft: false +images: [] +menu: + reference: + parent: "guides" +weight: 220 +toc: true +--- + +## Identity Providers + +- [OpenID Connect 1.0 Integration](../../integration/openid-connect/specific-information.md#frequently-asked-questions) diff --git a/docs/data/configkeys.json b/docs/data/configkeys.json index 0f6c42911..bf06efb15 100644 --- a/docs/data/configkeys.json +++ b/docs/data/configkeys.json @@ -1 +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.allowed_origins_from_client_redirect_uris","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ALLOWED_ORIGINS_FROM_CLIENT_REDIRECT_URIS"},{"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.secret","secret":true,"env":"AUTHELIA_SESSION_SECRET_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.expiration","secret":false,"env":"AUTHELIA_SESSION_EXPIRATION"},{"path":"session.inactivity","secret":false,"env":"AUTHELIA_SESSION_INACTIVITY"},{"path":"session.remember_me","secret":false,"env":"AUTHELIA_SESSION_REMEMBER_ME"},{"path":"session","secret":false,"env":"AUTHELIA_SESSION"},{"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.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":"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.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.headers.csp_template","secret":false,"env":"AUTHELIA_SERVER_HEADERS_CSP_TEMPLATE"},{"path":"server.endpoints.enable_pprof","secret":false,"env":"AUTHELIA_SERVER_ENDPOINTS_ENABLE_PPROF"},{"path":"server.endpoints.enable_expvars","secret":false,"env":"AUTHELIA_SERVER_ENDPOINTS_ENABLE_EXPVARS"},{"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"},{"path":"privacy_policy.enabled","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_ENABLED"},{"path":"privacy_policy.require_user_acceptance","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_REQUIRE_USER_ACCEPTANCE"},{"path":"privacy_policy.policy_url","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_POLICY_URL"}] \ No newline at end of file +[{"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.allowed_origins_from_client_redirect_uris","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_CORS_ALLOWED_ORIGINS_FROM_CLIENT_REDIRECT_URIS"},{"path":"identity_providers.oidc.pushed_authorizations.enforce","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_PUSHED_AUTHORIZATIONS_ENFORCE"},{"path":"identity_providers.oidc.pushed_authorizations.context_lifespan","secret":false,"env":"AUTHELIA_IDENTITY_PROVIDERS_OIDC_PUSHED_AUTHORIZATIONS_CONTEXT_LIFESPAN"},{"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.secret","secret":true,"env":"AUTHELIA_SESSION_SECRET_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.expiration","secret":false,"env":"AUTHELIA_SESSION_EXPIRATION"},{"path":"session.inactivity","secret":false,"env":"AUTHELIA_SESSION_INACTIVITY"},{"path":"session.remember_me","secret":false,"env":"AUTHELIA_SESSION_REMEMBER_ME"},{"path":"session","secret":false,"env":"AUTHELIA_SESSION"},{"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.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":"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.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.headers.csp_template","secret":false,"env":"AUTHELIA_SERVER_HEADERS_CSP_TEMPLATE"},{"path":"server.endpoints.enable_pprof","secret":false,"env":"AUTHELIA_SERVER_ENDPOINTS_ENABLE_PPROF"},{"path":"server.endpoints.enable_expvars","secret":false,"env":"AUTHELIA_SERVER_ENDPOINTS_ENABLE_EXPVARS"},{"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"},{"path":"privacy_policy.enabled","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_ENABLED"},{"path":"privacy_policy.require_user_acceptance","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_REQUIRE_USER_ACCEPTANCE"},{"path":"privacy_policy.policy_url","secret":false,"env":"AUTHELIA_PRIVACY_POLICY_POLICY_URL"}] \ No newline at end of file diff --git a/go.mod b/go.mod index 0b8263ba6..7191df1c9 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.20 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/v2 v2.1.0 + github.com/deckarep/golang-set/v2 v2.2.0 github.com/duosecurity/duo_api_golang v0.0.0-20230203160531-b221c950c2b0 - github.com/fasthttp/router v1.4.16 + github.com/fasthttp/router v1.4.17 github.com/fasthttp/session/v2 v2.4.16 github.com/fsnotify/fsnotify v1.6.0 github.com/go-asn1-ber/asn1-ber v1.5.4 @@ -33,7 +33,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/ory/fosite v0.44.0 github.com/ory/herodot v0.9.13 - github.com/ory/x v0.0.541 + github.com/ory/x v0.0.543 github.com/otiai10/copy v1.9.0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 @@ -45,10 +45,10 @@ require ( github.com/trustelem/zxcvbn v1.0.1 github.com/valyala/fasthttp v1.44.0 github.com/wneessen/go-mail v0.3.8 - golang.org/x/net v0.7.0 + golang.org/x/net v0.8.0 golang.org/x/sync v0.1.0 - golang.org/x/term v0.5.0 - golang.org/x/text v0.7.0 + golang.org/x/term v0.6.0 + golang.org/x/text v0.8.0 gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -101,7 +101,7 @@ require ( 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 - github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -115,10 +115,10 @@ require ( github.com/ysmood/gson v0.7.3 // indirect github.com/ysmood/leakless v0.8.0 // indirect golang.org/x/crypto v0.6.0 // indirect - golang.org/x/mod v0.7.0 // indirect + golang.org/x/mod v0.8.0 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/tools v0.4.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect google.golang.org/grpc v1.50.1 // indirect diff --git a/go.sum b/go.sum index bf11c7000..5f70808bd 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+Egvsz github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= -github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/deckarep/golang-set/v2 v2.2.0 h1:2pMQd3Soi6qfw7E5MMKaEh5W5ES18bW3AbFFnGl6LgQ= +github.com/deckarep/golang-set/v2 v2.2.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= @@ -126,8 +126,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.16 h1:faWJ9OtaHvAtodreyQLps58M80YFNzphMJtOJzeESXs= -github.com/fasthttp/router v1.4.16/go.mod h1:NFNlTCilbRVkeLc+E5JDkcxUdkpiJGKDL8Zy7Ey2JTI= +github.com/fasthttp/router v1.4.17 h1:Z8fndZotdwcPoYTt8BWwnRBts2UQPnKmOxbb94n0GUc= +github.com/fasthttp/router v1.4.17/go.mod h1:EOMfK/dT1IMzbyPhzw6E2j90owHvY+/BY60bLxOye/8= github.com/fasthttp/session/v2 v2.4.16 h1:JRvuEqr/+/cNMBkhGZN118FurLh6paUGscwJr26TxAQ= github.com/fasthttp/session/v2 v2.4.16/go.mod h1:nv8SD6pAx3n3KjJsEt4k1p0vstqclbNcrCwjc1OjuCI= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= @@ -387,8 +387,8 @@ github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8 github.com/ory/herodot v0.9.13 h1:cN/Z4eOkErl/9W7hDIDLb79IO/bfsH+8yscBjRpB4IU= github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo= github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= -github.com/ory/x v0.0.541 h1:rp8AD7X5/WiZIJws5kBwFgXoSeunxNMD54QV58+pcew= -github.com/ory/x v0.0.541/go.mod h1:ktXUvx51Ok1gMGr3ysvktanqr+eiB4FXglt4nF4w2Uo= +github.com/ory/x v0.0.543 h1:I6bl6IV2Ok07io6M2dnaRaJHP5oRU096T9FYoe8m48U= +github.com/ory/x v0.0.543/go.mod h1:ktXUvx51Ok1gMGr3ysvktanqr+eiB4FXglt4nF4w2Uo= 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= @@ -457,8 +457,9 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -607,8 +608,9 @@ golang.org/x/mod v0.4.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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -656,8 +658,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -750,13 +752,13 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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= @@ -767,8 +769,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -826,8 +828,9 @@ 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.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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= diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go index d56d79f57..57376dc87 100644 --- a/internal/configuration/schema/identity_providers.go +++ b/internal/configuration/schema/identity_providers.go @@ -29,10 +29,17 @@ type OpenIDConnectConfiguration struct { EnablePKCEPlainChallenge bool `koanf:"enable_pkce_plain_challenge"` CORS OpenIDConnectCORSConfiguration `koanf:"cors"` + PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"` Clients []OpenIDConnectClientConfiguration `koanf:"clients"` } +// OpenIDConnectPARConfiguration represents an OpenID Connect PAR config. +type OpenIDConnectPARConfiguration struct { + Enforce bool `koanf:"enforce"` + ContextLifespan time.Duration `koanf:"context_lifespan"` +} + // OpenIDConnectCORSConfiguration represents an OpenID Connect CORS config. type OpenIDConnectCORSConfiguration struct { Endpoints []string `koanf:"endpoints"` @@ -59,6 +66,7 @@ type OpenIDConnectClientConfiguration struct { Policy string `koanf:"authorization_policy"` + EnforcePAR bool `koanf:"enforce_par"` EnforcePKCE bool `koanf:"enforce_pkce"` PKCEChallengeMethod string `koanf:"pkce_challenge_method"` diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index a5122ee82..526913918 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -31,6 +31,8 @@ var Keys = []string{ "identity_providers.oidc.cors.endpoints", "identity_providers.oidc.cors.allowed_origins", "identity_providers.oidc.cors.allowed_origins_from_client_redirect_uris", + "identity_providers.oidc.pushed_authorizations.enforce", + "identity_providers.oidc.pushed_authorizations.context_lifespan", "identity_providers.oidc.clients", "identity_providers.oidc.clients[].id", "identity_providers.oidc.clients[].description", @@ -44,6 +46,7 @@ var Keys = []string{ "identity_providers.oidc.clients[].response_types", "identity_providers.oidc.clients[].response_modes", "identity_providers.oidc.clients[].authorization_policy", + "identity_providers.oidc.clients[].enforce_par", "identity_providers.oidc.clients[].enforce_pkce", "identity_providers.oidc.clients[].pkce_challenge_method", "identity_providers.oidc.clients[].userinfo_signing_algorithm", diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 8730518de..0f4b26a0c 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -392,7 +392,7 @@ var ( 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} + validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo} validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()} ) diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go index fe6bdc628..549b3ff19 100644 --- a/internal/configuration/validator/identity_providers_test.go +++ b/internal/configuration/validator/identity_providers_test.go @@ -80,7 +80,7 @@ func TestShouldRaiseErrorWhenCORSEndpointsNotValid(t *testing.T) { require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'token', 'introspection', 'revocation', 'userinfo'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'pushed-authorization-request', 'token', 'introspection', 'revocation', 'userinfo'") } func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) { diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index 57f686068..29147a0de 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -53,10 +53,20 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr return } - if err = client.ValidateAuthorizationPolicy(requester); err != nil { + if err = client.ValidatePARPolicy(requester, ctx.Providers.OpenIDConnect.GetPushedAuthorizeRequestURIPrefix(ctx)); err != nil { rfc := fosite.ErrorToRFC6749Error(err) - ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the authorization policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription()) + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the PAR policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription()) + + ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err) + + return + } + + if err = client.ValidatePKCEPolicy(requester); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the PKCE policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription()) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err) @@ -95,13 +105,13 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID) - oidcSession := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(), + session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(), userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, consent, requester) 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) + requester.GetID(), session.ClientID, session.Subject, session.Username, session.Claims) - if responder, err = ctx.Providers.OpenIDConnect.NewAuthorizeResponse(ctx, requester, oidcSession); err != nil { + if responder, err = ctx.Providers.OpenIDConnect.NewAuthorizeResponse(ctx, requester, session); 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()) @@ -125,3 +135,62 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr ctx.Providers.OpenIDConnect.WriteAuthorizeResponse(ctx, rw, requester, responder) } + +// OpenIDConnectPushedAuthorizationRequest handles POST requests to the OAuth 2.0 Pushed Authorization Requests endpoint. +// +// RFC9126 https://www.rfc-editor.org/rfc/rfc9126.html +func OpenIDConnectPushedAuthorizationRequest(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) { + var ( + requester fosite.AuthorizeRequester + responder fosite.PushedAuthorizeResponder + err error + ) + + if requester, err = ctx.Providers.OpenIDConnect.NewPushedAuthorizeRequest(ctx, r); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Pushed Authorization Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription()) + + ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err) + + return + } + + var client *oidc.Client + + clientID := requester.GetClient().GetID() + + if client, err = ctx.Providers.OpenIDConnect.GetFullClient(clientID); err != nil { + if errors.Is(err, fosite.ErrNotFound) { + ctx.Logger.Errorf("Pushed 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("Pushed 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.WritePushedAuthorizeError(ctx, rw, requester, err) + + return + } + + if err = client.ValidatePKCEPolicy(requester); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Pushed Authorization Request with id '%s' on client with id '%s' failed to validate the PKCE policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription()) + + ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err) + + return + } + + if responder, err = ctx.Providers.OpenIDConnect.NewPushedAuthorizeResponse(ctx, requester, oidc.NewSession()); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Pushed Authorization Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription()) + + ctx.Providers.OpenIDConnect.WritePushedAuthorizeError(ctx, rw, requester, err) + + return + } + + ctx.Providers.OpenIDConnect.WritePushedAuthorizeResponse(ctx, rw, requester, responder) +} diff --git a/internal/mocks/notifier.go b/internal/mocks/notifier.go index 36ef6f94c..b568e808c 100644 --- a/internal/mocks/notifier.go +++ b/internal/mocks/notifier.go @@ -9,9 +9,8 @@ import ( mail "net/mail" reflect "reflect" - gomock "github.com/golang/mock/gomock" - templates "github.com/authelia/authelia/v4/internal/templates" + gomock "github.com/golang/mock/gomock" ) // MockNotifier is a mock of Notifier interface. diff --git a/internal/mocks/storage.go b/internal/mocks/storage.go index e27abb260..2107ce9ce 100644 --- a/internal/mocks/storage.go +++ b/internal/mocks/storage.go @@ -39,6 +39,7 @@ func (m *MockStorage) EXPECT() *MockStorageMockRecorder { return m.recorder } + // AppendAuthenticationLog mocks base method. func (m *MockStorage) AppendAuthenticationLog(arg0 context.Context, arg1 model.AuthenticationAttempt) error { m.ctrl.T.Helper() @@ -269,6 +270,21 @@ func (mr *MockStorageMockRecorder) LoadOAuth2ConsentSessionByChallengeID(arg0, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadOAuth2ConsentSessionByChallengeID", reflect.TypeOf((*MockStorage)(nil).LoadOAuth2ConsentSessionByChallengeID), arg0, arg1) } +// LoadOAuth2PARContext mocks base method. +func (m *MockStorage) LoadOAuth2PARContext(arg0 context.Context, arg1 string) (*model.OAuth2PARContext, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadOAuth2PARContext", arg0, arg1) + ret0, _ := ret[0].(*model.OAuth2PARContext) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadOAuth2PARContext indicates an expected call of LoadOAuth2PARContext. +func (mr *MockStorageMockRecorder) LoadOAuth2PARContext(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadOAuth2PARContext", reflect.TypeOf((*MockStorage)(nil).LoadOAuth2PARContext), arg0, arg1) +} + // LoadOAuth2Session mocks base method. func (m *MockStorage) LoadOAuth2Session(arg0 context.Context, arg1 storage.OAuth2SessionType, arg2 string) (*model.OAuth2Session, error) { m.ctrl.T.Helper() @@ -464,6 +480,20 @@ func (mr *MockStorageMockRecorder) LoadWebauthnUser(arg0, arg1, arg2 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadWebauthnUser", reflect.TypeOf((*MockStorage)(nil).LoadWebauthnUser), arg0, arg1, arg2) } +// RevokeOAuth2PARContext mocks base method. +func (m *MockStorage) RevokeOAuth2PARContext(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RevokeOAuth2PARContext", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// RevokeOAuth2PARContext indicates an expected call of RevokeOAuth2PARContext. +func (mr *MockStorageMockRecorder) RevokeOAuth2PARContext(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeOAuth2PARContext", reflect.TypeOf((*MockStorage)(nil).RevokeOAuth2PARContext), arg0, arg1) +} + // RevokeOAuth2Session mocks base method. func (m *MockStorage) RevokeOAuth2Session(arg0 context.Context, arg1 storage.OAuth2SessionType, arg2 string) error { m.ctrl.T.Helper() @@ -605,6 +635,20 @@ func (mr *MockStorageMockRecorder) SaveOAuth2ConsentSessionSubject(arg0, arg1 in return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveOAuth2ConsentSessionSubject", reflect.TypeOf((*MockStorage)(nil).SaveOAuth2ConsentSessionSubject), arg0, arg1) } +// SaveOAuth2PARContext mocks base method. +func (m *MockStorage) SaveOAuth2PARContext(arg0 context.Context, arg1 model.OAuth2PARContext) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveOAuth2PARContext", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveOAuth2PARContext indicates an expected call of SaveOAuth2PARContext. +func (mr *MockStorageMockRecorder) SaveOAuth2PARContext(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveOAuth2PARContext", reflect.TypeOf((*MockStorage)(nil).SaveOAuth2PARContext), arg0, arg1) +} + // SaveOAuth2Session mocks base method. func (m *MockStorage) SaveOAuth2Session(arg0 context.Context, arg1 storage.OAuth2SessionType, arg2 model.OAuth2Session) error { m.ctrl.T.Helper() diff --git a/internal/mocks/totp.go b/internal/mocks/totp.go index 673cdb8c0..8b029b6ea 100644 --- a/internal/mocks/totp.go +++ b/internal/mocks/totp.go @@ -7,9 +7,8 @@ package mocks import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" - model "github.com/authelia/authelia/v4/internal/model" + gomock "github.com/golang/mock/gomock" ) // MockTOTP is a mock of Provider interface. diff --git a/internal/mocks/user_provider.go b/internal/mocks/user_provider.go index 25d0e2aec..4cc592dc9 100644 --- a/internal/mocks/user_provider.go +++ b/internal/mocks/user_provider.go @@ -7,9 +7,8 @@ package mocks import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" - authentication "github.com/authelia/authelia/v4/internal/authentication" + gomock "github.com/golang/mock/gomock" ) // MockUserProvider is a mock of UserProvider interface. diff --git a/internal/model/oidc.go b/internal/model/oidc.go index 1d742543d..c0f2b51fb 100644 --- a/internal/model/oidc.go +++ b/internal/model/oidc.go @@ -39,6 +39,14 @@ func NewOAuth2ConsentSession(subject uuid.UUID, r fosite.Requester) (consent *OA return consent, nil } +// NewOAuth2BlacklistedJTI creates a new OAuth2BlacklistedJTI. +func NewOAuth2BlacklistedJTI(jti string, exp time.Time) (jtiBlacklist OAuth2BlacklistedJTI) { + return OAuth2BlacklistedJTI{ + Signature: fmt.Sprintf("%x", sha256.Sum256([]byte(jti))), + ExpiresAt: exp, + } +} + // NewOAuth2SessionFromRequest creates a new OAuth2Session from a signature and fosite.Requester. func NewOAuth2SessionFromRequest(signature string, r fosite.Requester) (session *OAuth2Session, err error) { var ( @@ -77,12 +85,43 @@ func NewOAuth2SessionFromRequest(signature string, r fosite.Requester) (session }, nil } -// NewOAuth2BlacklistedJTI creates a new OAuth2BlacklistedJTI. -func NewOAuth2BlacklistedJTI(jti string, exp time.Time) (jtiBlacklist OAuth2BlacklistedJTI) { - return OAuth2BlacklistedJTI{ - Signature: fmt.Sprintf("%x", sha256.Sum256([]byte(jti))), - ExpiresAt: exp, +// NewOAuth2PARContext creates a new Pushed Authorization Request Context as a OAuth2PARContext. +func NewOAuth2PARContext(contextID string, r fosite.AuthorizeRequester) (context *OAuth2PARContext, err error) { + var ( + s *OpenIDSession + ok bool + req *fosite.AuthorizeRequest + session []byte + ) + + if s, ok = r.GetSession().(*OpenIDSession); !ok { + return nil, fmt.Errorf("can't convert type '%T' to an *OAuth2Session", r.GetSession()) } + + if session, err = json.Marshal(s); err != nil { + return nil, err + } + + var handled StringSlicePipeDelimited + + if req, ok = r.(*fosite.AuthorizeRequest); ok { + handled = StringSlicePipeDelimited(req.HandledResponseTypes) + } + + return &OAuth2PARContext{ + Signature: contextID, + RequestID: r.GetID(), + ClientID: r.GetClient().GetID(), + RequestedAt: r.GetRequestedAt(), + Scopes: StringSlicePipeDelimited(r.GetRequestedScopes()), + Audience: StringSlicePipeDelimited(r.GetRequestedAudience()), + HandledResponseTypes: handled, + ResponseMode: string(r.GetResponseMode()), + DefaultResponseMode: string(r.GetDefaultResponseMode()), + Revoked: false, + Form: r.GetRequestForm().Encode(), + Session: session, + }, nil } // OAuth2ConsentPreConfig stores information about an OAuth2.0 Pre-Configured Consent. @@ -264,6 +303,70 @@ func (s *OAuth2Session) ToRequest(ctx context.Context, session fosite.Session, s }, nil } +// OAuth2PARContext holds relevant information about a Pushed Authorization Request in order to process the authorization. +type OAuth2PARContext struct { + ID int `db:"id"` + Signature string `db:"signature"` + RequestID string `db:"request_id"` + ClientID string `db:"client_id"` + RequestedAt time.Time `db:"requested_at"` + Scopes StringSlicePipeDelimited `db:"scopes"` + Audience StringSlicePipeDelimited `db:"audience"` + HandledResponseTypes StringSlicePipeDelimited `db:"handled_response_types"` + ResponseMode string `db:"response_mode"` + DefaultResponseMode string `db:"response_mode_default"` + Revoked bool `db:"revoked"` + Form string `db:"form_data"` + Session []byte `db:"session_data"` +} + +func (par *OAuth2PARContext) ToAuthorizeRequest(ctx context.Context, session fosite.Session, store fosite.Storage) (request *fosite.AuthorizeRequest, err error) { + if session != nil { + if err = json.Unmarshal(par.Session, session); err != nil { + return nil, err + } + } + + var ( + client fosite.Client + form url.Values + ) + + if client, err = store.GetClient(ctx, par.ClientID); err != nil { + return nil, err + } + + if form, err = url.ParseQuery(par.Form); err != nil { + return nil, err + } + + request = fosite.NewAuthorizeRequest() + + request.Request = fosite.Request{ + ID: par.RequestID, + RequestedAt: par.RequestedAt, + Client: client, + RequestedScope: fosite.Arguments(par.Scopes), + RequestedAudience: fosite.Arguments(par.Audience), + Form: form, + Session: session, + } + + if par.ResponseMode != "" { + request.ResponseMode = fosite.ResponseModeType(par.ResponseMode) + } + + if par.DefaultResponseMode != "" { + request.DefaultResponseMode = fosite.ResponseModeType(par.DefaultResponseMode) + } + + if len(par.HandledResponseTypes) != 0 { + request.HandledResponseTypes = fosite.Arguments(par.HandledResponseTypes) + } + + return request, nil +} + // OpenIDSession holds OIDC Session information. type OpenIDSession struct { *openid.DefaultSession `json:"id_token"` diff --git a/internal/oidc/client.go b/internal/oidc/client.go index 633161bc6..8388db9e7 100644 --- a/internal/oidc/client.go +++ b/internal/oidc/client.go @@ -1,7 +1,7 @@ package oidc import ( - "fmt" + "strings" "github.com/ory/fosite" "github.com/ory/x/errorsx" @@ -32,6 +32,8 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) ResponseTypes: config.ResponseTypes, ResponseModes: []fosite.ResponseModeType{fosite.ResponseModeDefault}, + EnforcePAR: config.EnforcePAR, + UserinfoSigningAlgorithm: config.UserinfoSigningAlgorithm, Policy: authorization.NewLevel(config.Policy), @@ -46,22 +48,22 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) return client } -// ValidateAuthorizationPolicy is a helper function to validate additional policy constraints on a per-client basis. -func (c *Client) ValidateAuthorizationPolicy(r fosite.Requester) (err error) { +// ValidatePKCEPolicy is a helper function to validate PKCE policy constraints on a per-client basis. +func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) { form := r.GetRequestForm() if c.EnforcePKCE { - if form.Get("code_challenge") == "" { + if form.Get(FormParameterCodeChallenge) == "" { return errorsx.WithStack(fosite.ErrInvalidRequest. WithHint("Clients must include a code_challenge when performing the authorize code flow, but it is missing."). WithDebug("The server is configured in a way that enforces PKCE for this client.")) } if c.EnforcePKCEChallengeMethod { - if method := form.Get("code_challenge_method"); method != c.PKCEChallengeMethod { + if method := form.Get(FormParameterCodeChallengeMethod); method != c.PKCEChallengeMethod { return errorsx.WithStack(fosite.ErrInvalidRequest. - WithHint(fmt.Sprintf("Client must use code_challenge_method=%s, %s is not allowed.", c.PKCEChallengeMethod, method)). - WithDebug(fmt.Sprintf("The server is configured in a way that enforces PKCE %s as challenge method for this client.", c.PKCEChallengeMethod))) + WithHintf("Client must use code_challenge_method=%s, %s is not allowed.", c.PKCEChallengeMethod, method). + WithDebugf("The server is configured in a way that enforces PKCE %s as challenge method for this client.", c.PKCEChallengeMethod)) } } } @@ -69,6 +71,23 @@ func (c *Client) ValidateAuthorizationPolicy(r fosite.Requester) (err error) { return nil } +// ValidatePARPolicy is a helper function to validate additional policy constraints on a per-client basis. +func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) { + form := r.GetRequestForm() + + if c.EnforcePAR { + if requestURI := form.Get(FormParameterRequestURI); !strings.HasPrefix(requestURI, prefix) { + if requestURI == "" { + return errorsx.WithStack(ErrPAREnforcedClientMissingPAR.WithDebug("The request_uri parameter was empty.")) + } + + return errorsx.WithStack(ErrPAREnforcedClientMissingPAR.WithDebugf("The request_uri parameter '%s' is malformed.", requestURI)) + } + } + + return nil +} + // IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient. func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool { if level == authentication.NotAuthenticated { @@ -105,7 +124,7 @@ func (c *Client) GetID() string { } // GetHashedSecret returns the Secret. -func (c *Client) GetHashedSecret() []byte { +func (c *Client) GetHashedSecret() (secret []byte) { if c.Secret == nil { return []byte(nil) } @@ -114,7 +133,7 @@ func (c *Client) GetHashedSecret() []byte { } // GetRedirectURIs returns the RedirectURIs. -func (c *Client) GetRedirectURIs() []string { +func (c *Client) GetRedirectURIs() (redirectURIs []string) { return c.RedirectURIs } diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go index 23b53aaec..747f1e8c2 100644 --- a/internal/oidc/client_test.go +++ b/internal/oidc/client_test.go @@ -224,7 +224,7 @@ func TestNewClientPKCE(t *testing.T) { expectedEnforcePKCE bool expectedEnforcePKCEChallengeMethod bool expected string - req *fosite.Request + r *fosite.Request err string }{ { @@ -288,8 +288,8 @@ func TestNewClientPKCE(t *testing.T) { assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod) assert.Equal(t, tc.expected, client.PKCEChallengeMethod) - if tc.req != nil { - err := client.ValidateAuthorizationPolicy(tc.req) + if tc.r != nil { + err := client.ValidatePKCEPolicy(tc.r) if tc.err != "" { assert.EqualError(t, err, tc.err) diff --git a/internal/oidc/config.go b/internal/oidc/config.go index 7a5110b09..1cc7bf098 100644 --- a/internal/oidc/config.go +++ b/internal/oidc/config.go @@ -6,6 +6,7 @@ import ( "hash" "html/template" "net/url" + "path" "time" "github.com/hashicorp/go-retryablehttp" @@ -23,8 +24,8 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.Provider) *Config { - c := &Config{ +func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.Provider) (c *Config) { + c = &Config{ GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)), SendDebugMessagesToClients: config.EnableClientDebugMessages, MinParameterEntropy: config.MinimumParameterEntropy, @@ -39,18 +40,23 @@ func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.P EnforcePublicClients: config.EnforcePKCE != "never", AllowPlainChallengeMethod: config.EnablePKCEPlainChallenge, }, + PAR: PARConfig{ + Enforced: config.PAR.Enforce, + ContextLifespan: config.PAR.ContextLifespan, + URIPrefix: urnPARPrefix, + }, Templates: templates, } c.Strategy.Core = &HMACCoreStrategy{ Enigma: &hmac.HMACStrategy{Config: c}, Config: c, - prefix: tokenPrefixFmt, } return c } +// Config is an implementation of the fosite.Configurator. type Config struct { // GlobalSecret is the global secret used to sign and verify signatures. GlobalSecret []byte @@ -67,7 +73,7 @@ type Config struct { JWTScopeField jwt.JWTScopeFieldEnum JWTMaxDuration time.Duration - Hasher *AdaptiveHasher + Hasher *Hasher Hash HashConfig Strategy StrategyConfig PAR PARConfig @@ -91,11 +97,13 @@ type Config struct { Templates *templates.Provider } +// HashConfig holds specific fosite.Configurator information for hashing. type HashConfig struct { ClientSecrets fosite.Hasher HMAC func() (h hash.Hash) } +// StrategyConfig holds specific fosite.Configurator information for various strategies. type StrategyConfig struct { Core oauth2.CoreStrategy OpenID openid.OpenIDConnectTokenStrategy @@ -105,17 +113,20 @@ type StrategyConfig struct { ClientAuthentication fosite.ClientAuthenticationStrategy } +// PARConfig holds specific fosite.Configurator information for Pushed Authorization Requests. type PARConfig struct { Enforced bool URIPrefix string ContextLifespan time.Duration } +// IssuersConfig holds specific fosite.Configurator information for the issuer. type IssuersConfig struct { IDToken string AccessToken string } +// HandlersConfig holds specific fosite.Configurator handlers configuration information. type HandlersConfig struct { // ResponseMode provides an extension handler for custom response modes. ResponseMode fosite.ResponseModeHandler @@ -136,18 +147,21 @@ type HandlersConfig struct { PushedAuthorizeEndpoint fosite.PushedAuthorizeEndpointHandlers } +// GrantTypeJWTBearerConfig holds specific fosite.Configurator information for the JWT Bearer Grant Type. type GrantTypeJWTBearerConfig struct { OptionalClientAuth bool OptionalJTIClaim bool OptionalIssuedDate bool } +// ProofKeyCodeExchangeConfig holds specific fosite.Configurator information for PKCE. type ProofKeyCodeExchangeConfig struct { Enforce bool EnforcePublicClients bool AllowPlainChallengeMethod bool } +// LifespanConfig holds specific fosite.Configurator information for various lifespans. type LifespanConfig struct { AccessToken time.Duration AuthorizeCode time.Duration @@ -161,6 +175,7 @@ const ( PromptConsent = "consent" ) +// LoadHandlers reloads the handlers based on the current configuration. func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) { validator := openid.NewOpenIDConnectRequestValidator(strategy, c) @@ -277,6 +292,10 @@ func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) { if h, ok := handler.(fosite.RevocationHandler); ok { x.Revocation.Append(h) } + + if h, ok := handler.(fosite.PushedAuthorizeEndpointHandler); ok { + x.PushedAuthorizeEndpoint.Append(h) + } } c.Handlers = x @@ -515,13 +534,24 @@ func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) (tmpl *template.Te // GetTokenURL returns the token URL. func (c *Config) GetTokenURL(ctx context.Context) (tokenURL string) { + if ctx, ok := ctx.(OpenIDConnectContext); ok { + tokenURI, err := ctx.IssuerURL() + if err != nil { + return c.TokenURL + } + + tokenURI.Path = path.Join(tokenURI.Path, EndpointPathToken) + + return tokenURI.String() + } + return c.TokenURL } // GetSecretsHasher returns the client secrets hashing function. func (c *Config) GetSecretsHasher(ctx context.Context) (hasher fosite.Hasher) { if c.Hash.ClientSecrets == nil { - c.Hash.ClientSecrets, _ = NewAdaptiveHasher() + c.Hash.ClientSecrets, _ = NewHasher() } return c.Hash.ClientSecrets @@ -583,7 +613,7 @@ func (c *Config) EnforcePushedAuthorize(ctx context.Context) bool { // GetPushedAuthorizeContextLifespan is the lifespan of the short-lived PAR context. func (c *Config) GetPushedAuthorizeContextLifespan(ctx context.Context) (lifespan time.Duration) { - if c.PAR.ContextLifespan == 0 { + if c.PAR.ContextLifespan.Seconds() == 0 { c.PAR.ContextLifespan = lifespanPARContextDefault } diff --git a/internal/oidc/const.go b/internal/oidc/const.go index 9c8fa6942..1629ef7c7 100644 --- a/internal/oidc/const.go +++ b/internal/oidc/const.go @@ -73,6 +73,25 @@ const ( GrantTypeClientCredentials = "client_credentials" ) +// Client Auth Method strings. +const ( + ClientAuthMethodClientSecretBasic = "client_secret_basic" + ClientAuthMethodClientSecretPost = "client_secret_post" + ClientAuthMethodClientSecretJWT = "client_secret_jwt" + ClientAuthMethodNone = "none" +) + +// Response Type strings. +const ( + ResponseTypeAuthorizationCodeFlow = "code" + ResponseTypeImplicitFlowIDToken = "id_token" + ResponseTypeImplicitFlowToken = "token" + ResponseTypeImplicitFlowBoth = "id_token token" + ResponseTypeHybridFlowIDToken = "code id_token" + ResponseTypeHybridFlowToken = "code token" + ResponseTypeHybridFlowBoth = "code id_token token" +) + // Signing Algorithm strings. const ( SigningAlgorithmNone = none @@ -91,13 +110,20 @@ const ( PKCEChallengeMethodSHA256 = "S256" ) +const ( + FormParameterRequestURI = "request_uri" + FormParameterCodeChallenge = "code_challenge" + FormParameterCodeChallengeMethod = "code_challenge_method" +) + // Endpoints. const ( - EndpointAuthorization = "authorization" - EndpointToken = "token" - EndpointUserinfo = "userinfo" - EndpointIntrospection = "introspection" - EndpointRevocation = "revocation" + EndpointAuthorization = "authorization" + EndpointToken = "token" + EndpointUserinfo = "userinfo" + EndpointIntrospection = "introspection" + EndpointRevocation = "revocation" + EndpointPushedAuthorizationRequest = "pushed-authorization-request" ) // JWT Headers. @@ -107,7 +133,9 @@ const ( ) const ( - tokenPrefixFmt = "authelia_%s_" //nolint:gosec + tokenPrefixOrgAutheliaFmt = "authelia_%s_" //nolint:gosec + tokenPrefixOrgOryFmt = "ory_%s_" //nolint:gosec + tokenPrefixPartAccessToken = "at" tokenPrefixPartRefreshToken = "rt" tokenPrefixPartAuthorizeCode = "ac" @@ -127,6 +155,8 @@ const ( EndpointPathUserinfo = EndpointPathRoot + "/" + EndpointUserinfo EndpointPathIntrospection = EndpointPathRoot + "/" + EndpointIntrospection EndpointPathRevocation = EndpointPathRoot + "/" + EndpointRevocation + + EndpointPathPushedAuthorizationRequest = EndpointPathRoot + "/" + EndpointPushedAuthorizationRequest ) // Authentication Method Reference Values https://datatracker.ietf.org/doc/html/rfc8176 diff --git a/internal/oidc/hmac.go b/internal/oidc/core_strategy_hmac.go similarity index 92% rename from internal/oidc/hmac.go rename to internal/oidc/core_strategy_hmac.go index 979a98afc..47d52da7a 100644 --- a/internal/oidc/hmac.go +++ b/internal/oidc/core_strategy_hmac.go @@ -19,7 +19,6 @@ type HMACCoreStrategy struct { fosite.RefreshTokenLifespanProvider fosite.AuthorizeCodeLifespanProvider } - prefix string } // AccessTokenSignature implements oauth2.AccessTokenStrategy. @@ -112,11 +111,11 @@ func (h *HMACCoreStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.R } func (h *HMACCoreStrategy) getPrefix(part string) string { - if len(h.prefix) == 0 { - return "" - } + return h.getCustomPrefix(tokenPrefixOrgAutheliaFmt, part) +} - return fmt.Sprintf(h.prefix, part) +func (h *HMACCoreStrategy) getCustomPrefix(tokenPrefixFmt, part string) string { + return fmt.Sprintf(tokenPrefixFmt, part) } func (h *HMACCoreStrategy) setPrefix(token, part string) string { @@ -124,5 +123,9 @@ func (h *HMACCoreStrategy) setPrefix(token, part string) string { } func (h *HMACCoreStrategy) trimPrefix(token, part string) string { + if strings.HasPrefix(token, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) { + return strings.TrimPrefix(token, h.getCustomPrefix(tokenPrefixOrgOryFmt, part)) + } + return strings.TrimPrefix(token, h.getPrefix(part)) } diff --git a/internal/oidc/core_strategy_hmac_test.go b/internal/oidc/core_strategy_hmac_test.go new file mode 100644 index 000000000..d390d4bdb --- /dev/null +++ b/internal/oidc/core_strategy_hmac_test.go @@ -0,0 +1,56 @@ +package oidc + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHMACCoreStrategy_TrimPrefix(t *testing.T) { + testCases := []struct { + name string + have string + part string + expected string + }{ + {"ShouldTrimAutheliaPrefix", "authelia_at_example", tokenPrefixPartAccessToken, "example"}, + {"ShouldTrimOryPrefix", "ory_at_example", tokenPrefixPartAccessToken, "example"}, + {"ShouldTrimOnlyAutheliaPrefix", "authelia_at_ory_at_example", tokenPrefixPartAccessToken, "ory_at_example"}, + {"ShouldTrimOnlyOryPrefix", "ory_at_authelia_at_example", tokenPrefixPartAccessToken, "authelia_at_example"}, + {"ShouldNotTrimGitHubPrefix", "gh_at_example", tokenPrefixPartAccessToken, "gh_at_example"}, + } + + strategy := &HMACCoreStrategy{} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, strategy.trimPrefix(tc.have, tc.part)) + }) + } +} + +func TestHMACCoreStrategy_GetSetPrefix(t *testing.T) { + testCases := []struct { + name string + have string + expectedSet string + expectedGet string + }{ + {"ShouldAddPrefix", "example", "authelia_%s_example", "authelia_%s_"}, + } + + strategy := &HMACCoreStrategy{} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + for _, part := range []string{tokenPrefixPartAccessToken, tokenPrefixPartAuthorizeCode, tokenPrefixPartRefreshToken} { + t.Run(strings.ToUpper(part), func(t *testing.T) { + assert.Equal(t, fmt.Sprintf(tc.expectedSet, part), strategy.setPrefix(tc.have, part)) + assert.Equal(t, fmt.Sprintf(tc.expectedGet, part), strategy.getPrefix(part)) + }) + } + }) + } +} diff --git a/internal/oidc/discovery.go b/internal/oidc/discovery.go index 5f311c030..996e5e3f5 100644 --- a/internal/oidc/discovery.go +++ b/internal/oidc/discovery.go @@ -1,21 +1,29 @@ package oidc +import ( + "github.com/authelia/authelia/v4/internal/configuration/schema" +) + // NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration. -func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) { +func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) { config = OpenIDConnectWellKnownConfiguration{ CommonDiscoveryOptions: CommonDiscoveryOptions{ SubjectTypesSupported: []string{ SubjectTypePublic, }, ResponseTypesSupported: []string{ - "code", - "token", - "id_token", - "code token", - "code id_token", - "token id_token", - "code token id_token", - "none", + ResponseTypeAuthorizationCodeFlow, + ResponseTypeImplicitFlowIDToken, + ResponseTypeImplicitFlowToken, + ResponseTypeImplicitFlowBoth, + ResponseTypeHybridFlowIDToken, + ResponseTypeHybridFlowToken, + ResponseTypeHybridFlowBoth, + }, + GrantTypesSupported: []string{ + GrantTypeAuthorizationCode, + GrantTypeImplicit, + GrantTypeRefreshToken, }, ResponseModesSupported: []string{ ResponseModeFormPost, @@ -49,6 +57,12 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clien ClaimPreferredUsername, ClaimFullName, }, + TokenEndpointAuthMethodsSupported: []string{ + ClientAuthMethodClientSecretBasic, + ClientAuthMethodClientSecretPost, + ClientAuthMethodClientSecretJWT, + ClientAuthMethodNone, + }, }, OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{ CodeChallengeMethodsSupported: []string{ @@ -68,6 +82,9 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clien SigningAlgorithmRSAWithSHA256, }, }, + PushedAuthorizationDiscoveryOptions: PushedAuthorizationDiscoveryOptions{ + RequirePushedAuthorizationRequests: c.PAR.Enforce, + }, } var pairwise, public bool @@ -86,7 +103,7 @@ func NewOpenIDConnectWellKnownConfiguration(enablePKCEPlainChallenge bool, clien config.SubjectTypesSupported = append(config.SubjectTypesSupported, SubjectTypePairwise) } - if enablePKCEPlainChallenge { + if c.EnablePKCEPlainChallenge { config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain) } diff --git a/internal/oidc/discovery_test.go b/internal/oidc/discovery_test.go index 7f99df15c..63ad18b65 100644 --- a/internal/oidc/discovery_test.go +++ b/internal/oidc/discovery_test.go @@ -4,12 +4,15 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/authelia/authelia/v4/internal/configuration/schema" ) func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { testCases := []struct { desc string pkcePlainChallenge bool + enforcePAR bool clients map[string]*Client expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string @@ -63,7 +66,14 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - actual := NewOpenIDConnectWellKnownConfiguration(tc.pkcePlainChallenge, tc.clients) + c := schema.OpenIDConnectConfiguration{ + EnablePKCEPlainChallenge: tc.pkcePlainChallenge, + PAR: schema.OpenIDConnectPARConfiguration{ + Enforce: tc.enforcePAR, + }, + } + + actual := NewOpenIDConnectWellKnownConfiguration(&c, 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 465689257..58464568c 100644 --- a/internal/oidc/errors.go +++ b/internal/oidc/errors.go @@ -9,11 +9,27 @@ import ( 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.") + // ErrIssuerCouldNotDerive is sent when the issuer couldn't be determined from the headers. + ErrIssuerCouldNotDerive = fosite.ErrServerError.WithHint("Could not safely derive the issuer.") + + // ErrSubjectCouldNotLookup is sent when the Subject Identifier for a user couldn't be generated or obtained from the database. + ErrSubjectCouldNotLookup = fosite.ErrServerError.WithHint("Could not lookup user subject.") + + // ErrConsentCouldNotPerform is sent when the Consent Session couldn't be performed for varying reasons. + ErrConsentCouldNotPerform = fosite.ErrServerError.WithHint("Could not perform consent.") + + // ErrConsentCouldNotGenerate is sent when the Consent Session failed to be generated for some reason, usually a failed UUIDv4 generation. + ErrConsentCouldNotGenerate = fosite.ErrServerError.WithHint("Could not generate the consent session.") + + // ErrConsentCouldNotSave is sent when the Consent Session couldn't be saved to the database. + ErrConsentCouldNotSave = fosite.ErrServerError.WithHint("Could not save the consent session.") + + // ErrConsentCouldNotLookup is sent when the Consent ID is not a known UUID. + ErrConsentCouldNotLookup = fosite.ErrServerError.WithHint("Failed to lookup the consent session.") + + // ErrConsentMalformedChallengeID is sent when the Consent ID is not a UUID. ErrConsentMalformedChallengeID = fosite.ErrServerError.WithHint("Malformed consent session challenge ID.") + + // ErrPAREnforcedClientMissingPAR is sent when a client has EnforcePAR configured but the Authorization Request was not Pushed. + ErrPAREnforcedClientMissingPAR = fosite.ErrInvalidRequest.WithHint("Pushed Authorization Requests are enforced for this client but no such request was sent.") ) diff --git a/internal/oidc/hasher.go b/internal/oidc/hasher.go index 746f92ced..1714ec688 100644 --- a/internal/oidc/hasher.go +++ b/internal/oidc/hasher.go @@ -8,8 +8,9 @@ import ( "github.com/go-crypt/crypt/algorithm/plaintext" ) -func NewAdaptiveHasher() (hasher *AdaptiveHasher, err error) { - hasher = &AdaptiveHasher{} +// NewHasher returns a new Hasher. +func NewHasher() (hasher *Hasher, err error) { + hasher = &Hasher{} if hasher.decoder, err = crypt.NewDefaultDecoder(); err != nil { return nil, err @@ -22,13 +23,13 @@ func NewAdaptiveHasher() (hasher *AdaptiveHasher, err error) { return hasher, nil } -// AdaptiveHasher implements the fosite.Hasher interface without an actual hashing algo. -type AdaptiveHasher struct { +// Hasher implements the fosite.Hasher interface and adaptively compares hashes. +type Hasher struct { decoder algorithm.DecoderRegister } // Compare compares the hash with the data and returns an error if they don't match. -func (h *AdaptiveHasher) Compare(_ context.Context, hash, data []byte) (err error) { +func (h Hasher) Compare(_ context.Context, hash, data []byte) (err error) { var digest algorithm.Digest if digest, err = h.decoder.Decode(string(hash)); err != nil { @@ -43,6 +44,6 @@ func (h *AdaptiveHasher) Compare(_ context.Context, hash, data []byte) (err erro } // Hash creates a new hash from data. -func (h *AdaptiveHasher) Hash(_ context.Context, data []byte) (hash []byte, err error) { +func (h Hasher) 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 04f1b0f41..7f757460b 100644 --- a/internal/oidc/hasher_test.go +++ b/internal/oidc/hasher_test.go @@ -9,7 +9,7 @@ import ( ) func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) { - hasher, err := NewAdaptiveHasher() + hasher, err := NewHasher() require.NoError(t, err) @@ -22,7 +22,7 @@ func TestShouldNotRaiseErrorOnEqualPasswordsPlainText(t *testing.T) { } func TestShouldNotRaiseErrorOnEqualPasswordsPlainTextWithSeparator(t *testing.T) { - hasher, err := NewAdaptiveHasher() + hasher, err := NewHasher() require.NoError(t, err) @@ -35,7 +35,7 @@ func TestShouldNotRaiseErrorOnEqualPasswordsPlainTextWithSeparator(t *testing.T) } func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) { - hasher, err := NewAdaptiveHasher() + hasher, err := NewHasher() require.NoError(t, err) @@ -48,7 +48,7 @@ func TestShouldRaiseErrorOnNonEqualPasswordsPlainText(t *testing.T) { } func TestShouldHashPassword(t *testing.T) { - hasher := AdaptiveHasher{} + hasher := Hasher{} data := []byte("abc") diff --git a/internal/oidc/provider.go b/internal/oidc/provider.go index fd2581661..54bd1595d 100644 --- a/internal/oidc/provider.go +++ b/internal/oidc/provider.go @@ -37,7 +37,7 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy()) - provider.discovery = NewOpenIDConnectWellKnownConfiguration(config.EnablePKCEPlainChallenge, provider.Store.clients) + provider.discovery = NewOpenIDConnectWellKnownConfiguration(config, provider.Store.clients) return provider, nil } @@ -50,12 +50,12 @@ func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) O } options.Issuer = issuer + options.JWKSURI = fmt.Sprintf("%s%s", issuer, EndpointPathJWKs) - - options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection) - options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken) - options.AuthorizationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathAuthorization) + options.PushedAuthorizationRequestEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathPushedAuthorizationRequest) + options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken) + options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection) options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathRevocation) return options @@ -72,14 +72,14 @@ func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer st } options.Issuer = issuer + options.JWKSURI = fmt.Sprintf("%s%s", issuer, EndpointPathJWKs) - - options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection) - options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken) - options.AuthorizationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathAuthorization) - options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathRevocation) + options.PushedAuthorizationRequestEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathPushedAuthorizationRequest) + options.TokenEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathToken) options.UserinfoEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathUserinfo) + options.IntrospectionEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathIntrospection) + options.RevocationEndpoint = fmt.Sprintf("%s%s", issuer, EndpointPathRevocation) return options } diff --git a/internal/oidc/provider_test.go b/internal/oidc/provider_test.go index 4b533084e..709bab0b0 100644 --- a/internal/oidc/provider_test.go +++ b/internal/oidc/provider_test.go @@ -142,15 +142,25 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.Len(t, disco.SubjectTypesSupported, 1) assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) - assert.Len(t, disco.ResponseTypesSupported, 8) - assert.Contains(t, disco.ResponseTypesSupported, "code") - assert.Contains(t, disco.ResponseTypesSupported, "token") - assert.Contains(t, disco.ResponseTypesSupported, "id_token") - assert.Contains(t, disco.ResponseTypesSupported, "code token") - assert.Contains(t, disco.ResponseTypesSupported, "code id_token") - assert.Contains(t, disco.ResponseTypesSupported, "token id_token") - assert.Contains(t, disco.ResponseTypesSupported, "code token id_token") - assert.Contains(t, disco.ResponseTypesSupported, "none") + assert.Len(t, disco.ResponseTypesSupported, 7) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth) + + assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone) + + assert.Len(t, disco.GrantTypesSupported, 3) + assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode) + assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken) + assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit) assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1) assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256) @@ -231,15 +241,25 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig assert.Len(t, disco.SubjectTypesSupported, 1) assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) - assert.Len(t, disco.ResponseTypesSupported, 8) - assert.Contains(t, disco.ResponseTypesSupported, "code") - assert.Contains(t, disco.ResponseTypesSupported, "token") - assert.Contains(t, disco.ResponseTypesSupported, "id_token") - assert.Contains(t, disco.ResponseTypesSupported, "code token") - assert.Contains(t, disco.ResponseTypesSupported, "code id_token") - assert.Contains(t, disco.ResponseTypesSupported, "token id_token") - assert.Contains(t, disco.ResponseTypesSupported, "code token id_token") - assert.Contains(t, disco.ResponseTypesSupported, "none") + assert.Len(t, disco.ResponseTypesSupported, 7) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowIDToken) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowToken) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeImplicitFlowBoth) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowIDToken) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken) + assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth) + + assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) + assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone) + + assert.Len(t, disco.GrantTypesSupported, 3) + assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode) + assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken) + assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit) assert.Len(t, disco.ClaimsSupported, 18) assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference) diff --git a/internal/oidc/store.go b/internal/oidc/store.go index d192ed1b8..9089137e1 100644 --- a/internal/oidc/store.go +++ b/internal/oidc/store.go @@ -165,7 +165,7 @@ func (s *Store) InvalidateAuthorizeCodeSession(ctx context.Context, code string) // This implements a portion of oauth2.AuthorizeCodeStorage. 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) + return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypeAuthorizeCode, code, session) } // CreateAccessTokenSession stores the authorization request for a given access token. @@ -190,7 +190,7 @@ func (s *Store) RevokeAccessToken(ctx context.Context, requestID string) (err er // GetAccessTokenSession gets the authorization request for a given access token. // This implements a portion of oauth2.AccessTokenStorage. 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) + return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypeAccessToken, signature, session) } // CreateRefreshTokenSession stores the authorization request for a given refresh token. @@ -223,7 +223,7 @@ func (s *Store) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestI // GetRefreshTokenSession gets the authorization request for a given refresh token. // This implements a portion of oauth2.RefreshTokenStorage. 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) + return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypeRefreshToken, signature, session) } // CreatePKCERequestSession stores the authorization request for a given PKCE request. @@ -241,7 +241,7 @@ func (s *Store) DeletePKCERequestSession(ctx context.Context, signature string) // GetPKCERequestSession gets the authorization request for a given PKCE request. // This implements a portion of pkce.PKCERequestStorage. 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) + return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypePKCEChallenge, signature, session) } // CreateOpenIDConnectSession creates an open id connect session for a given authorize code. @@ -263,7 +263,37 @@ func (s *Store) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode st // - or an arbitrary error if an error occurred. // This implements a portion of openid.OpenIDConnectRequestStorage. 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()) + return s.loadRequesterBySignature(ctx, storage.OAuth2SessionTypeOpenIDConnect, authorizeCode, request.GetSession()) +} + +// CreatePARSession stores the pushed authorization request context. The requestURI is used to derive the key. +// This implements a portion of fosite.PARStorage. +func (s *Store) CreatePARSession(ctx context.Context, requestURI string, request fosite.AuthorizeRequester) (err error) { + var par *model.OAuth2PARContext + + if par, err = model.NewOAuth2PARContext(requestURI, request); err != nil { + return err + } + + return s.provider.SaveOAuth2PARContext(ctx, *par) +} + +// GetPARSession gets the push authorization request context. The caller is expected to merge the AuthorizeRequest. +// This implements a portion of fosite.PARStorage. +func (s *Store) GetPARSession(ctx context.Context, requestURI string) (request fosite.AuthorizeRequester, err error) { + var par *model.OAuth2PARContext + + if par, err = s.provider.LoadOAuth2PARContext(ctx, requestURI); err != nil { + return nil, err + } + + return par.ToAuthorizeRequest(ctx, NewSession(), s) +} + +// DeletePARSession deletes the context. +// This implements a portion of fosite.PARStorage. +func (s *Store) DeletePARSession(ctx context.Context, requestURI string) (err error) { + return s.provider.RevokeOAuth2PARContext(ctx, requestURI) } // IsJWTUsed implements an interface required for RFC7523. @@ -280,7 +310,7 @@ func (s *Store) MarkJWTUsedForTime(ctx context.Context, jti string, exp time.Tim return s.SetClientAssertionJWT(ctx, jti, exp) } -func (s *Store) loadSessionBySignature(ctx context.Context, sessionType storage.OAuth2SessionType, signature string, session fosite.Session) (r fosite.Requester, err error) { +func (s *Store) loadRequesterBySignature(ctx context.Context, sessionType storage.OAuth2SessionType, signature string, session fosite.Session) (r fosite.Requester, err error) { var ( sessionModel *model.OAuth2Session ) diff --git a/internal/oidc/types.go b/internal/oidc/types.go index d2782220c..7403f2fed 100644 --- a/internal/oidc/types.go +++ b/internal/oidc/types.go @@ -1,6 +1,7 @@ package oidc import ( + "context" "net/url" "time" @@ -118,6 +119,8 @@ type Client struct { ResponseTypes []string ResponseModes []fosite.ResponseModeType + EnforcePAR bool + UserinfoSigningAlgorithm string Policy authorization.Level @@ -643,3 +646,10 @@ type OpenIDConnectWellKnownConfiguration struct { OpenIDConnectFrontChannelLogoutDiscoveryOptions OpenIDConnectBackChannelLogoutDiscoveryOptions } + +// OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations. +type OpenIDConnectContext interface { + context.Context + + IssuerURL() (issuerURL *url.URL, err error) +} diff --git a/internal/server/handlers.go b/internal/server/handlers.go index fbd6e1611..657c84278 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -338,6 +338,15 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers) r.GET("/api/oidc/authorize", policyCORSAuthorization.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization)))) r.POST("/api/oidc/authorize", policyCORSAuthorization.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization)))) + policyCORSPAR := middlewares.NewCORSPolicyBuilder(). + WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodPost). + WithAllowedOrigins(allowedOrigins...). + WithEnabled(utils.IsStringInSliceFold(oidc.EndpointPushedAuthorizationRequest, config.IdentityProviders.OIDC.CORS.Endpoints)). + Build() + + r.OPTIONS(oidc.EndpointPathPushedAuthorizationRequest, policyCORSPAR.HandleOnlyOPTIONS) + r.POST(oidc.EndpointPathPushedAuthorizationRequest, policyCORSPAR.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectPushedAuthorizationRequest)))) + policyCORSToken := middlewares.NewCORSPolicyBuilder(). WithAllowCredentials(true). WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodPost). diff --git a/internal/storage/const.go b/internal/storage/const.go index e710d0338..b2ae72ca2 100644 --- a/internal/storage/const.go +++ b/internal/storage/const.go @@ -14,15 +14,16 @@ const ( tableWebauthnDevices = "webauthn_devices" tableWebauthnUsers = "webauthn_users" + tableOAuth2BlacklistedJTI = "oauth2_blacklisted_jti" tableOAuth2ConsentSession = "oauth2_consent_session" tableOAuth2ConsentPreConfiguration = "oauth2_consent_preconfiguration" + tableOAuth2AccessTokenSession = "oauth2_access_token_session" //nolint:gosec // This is not a hardcoded credential. 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" + tableOAuth2PARContext = "oauth2_par_context" + tableOAuth2PKCERequestSession = "oauth2_pkce_request_session" + tableOAuth2RefreshTokenSession = "oauth2_refresh_token_session" //nolint:gosec // This is not a hardcoded credential. tableMigrations = "migrations" tableEncryption = "encryption" @@ -33,26 +34,29 @@ type OAuth2SessionType int // Representation of specific OAuth 2.0 session types. const ( - OAuth2SessionTypeAuthorizeCode OAuth2SessionType = iota - OAuth2SessionTypeAccessToken - OAuth2SessionTypeRefreshToken - OAuth2SessionTypePKCEChallenge + OAuth2SessionTypeAccessToken OAuth2SessionType = iota + OAuth2SessionTypeAuthorizeCode OAuth2SessionTypeOpenIDConnect + OAuth2SessionTypePAR + OAuth2SessionTypePKCEChallenge + OAuth2SessionTypeRefreshToken ) // String returns a string representation of this OAuth2SessionType. func (s OAuth2SessionType) String() string { switch s { - case OAuth2SessionTypeAuthorizeCode: - return "authorization code" case OAuth2SessionTypeAccessToken: return "access token" - case OAuth2SessionTypeRefreshToken: - return "refresh token" - case OAuth2SessionTypePKCEChallenge: - return "pkce challenge" + case OAuth2SessionTypeAuthorizeCode: + return "authorization code" case OAuth2SessionTypeOpenIDConnect: return "openid connect" + case OAuth2SessionTypePAR: + return "pushed authorization request context" + case OAuth2SessionTypePKCEChallenge: + return "pkce challenge" + case OAuth2SessionTypeRefreshToken: + return "refresh token" default: return "invalid" } @@ -61,16 +65,18 @@ func (s OAuth2SessionType) String() string { // Table returns the table name for this session type. func (s OAuth2SessionType) Table() string { switch s { - case OAuth2SessionTypeAuthorizeCode: - return tableOAuth2AuthorizeCodeSession case OAuth2SessionTypeAccessToken: return tableOAuth2AccessTokenSession - case OAuth2SessionTypeRefreshToken: - return tableOAuth2RefreshTokenSession - case OAuth2SessionTypePKCEChallenge: - return tableOAuth2PKCERequestSession + case OAuth2SessionTypeAuthorizeCode: + return tableOAuth2AuthorizeCodeSession case OAuth2SessionTypeOpenIDConnect: return tableOAuth2OpenIDConnectSession + case OAuth2SessionTypePAR: + return tableOAuth2PARContext + case OAuth2SessionTypePKCEChallenge: + return tableOAuth2PKCERequestSession + case OAuth2SessionTypeRefreshToken: + return tableOAuth2RefreshTokenSession default: return "" } @@ -120,7 +126,7 @@ const ( ) var ( - reMigration = regexp.MustCompile(`^V(\d{4})\.([^.]+)\.(all|sqlite|postgres|mysql)\.(up|down)\.sql$`) + reMigration = regexp.MustCompile(`^V(?P\d{4})\.(?P[^.]+)\.(?P(all|sqlite|postgres|mysql))\.(?P(up|down))\.sql$`) ) const ( diff --git a/internal/storage/migrations.go b/internal/storage/migrations.go index f634bccdd..18d79aa25 100644 --- a/internal/storage/migrations.go +++ b/internal/storage/migrations.go @@ -130,15 +130,15 @@ func skipMigration(providerName string, up bool, target, prior int, migration *m } func scanMigration(m string) (migration model.SchemaMigration, err error) { - result := reMigration.FindStringSubmatch(m) - - if result == nil || len(result) != 5 { + if !reMigration.MatchString(m) { return model.SchemaMigration{}, errors.New("invalid migration: could not parse the format") } + result := reMigration.FindStringSubmatch(m) + migration = model.SchemaMigration{ - Name: strings.ReplaceAll(result[2], "_", " "), - Provider: result[3], + Name: strings.ReplaceAll(result[reMigration.SubexpIndex("Name")], "_", " "), + Provider: result[reMigration.SubexpIndex("Provider")], } data, err := migrationsFS.ReadFile(fmt.Sprintf("migrations/%s", m)) @@ -148,22 +148,22 @@ func scanMigration(m string) (migration model.SchemaMigration, err error) { migration.Query = string(data) - switch result[4] { + switch direction := result[reMigration.SubexpIndex("Direction")]; direction { case "up": migration.Up = true case "down": migration.Up = false default: - return model.SchemaMigration{}, fmt.Errorf("invalid migration: value in position 4 '%s' must be up or down", result[4]) + return model.SchemaMigration{}, fmt.Errorf("invalid migration: value in Direction group '%s' must be up or down", direction) } - migration.Version, _ = strconv.Atoi(result[1]) + migration.Version, _ = strconv.Atoi(result[reMigration.SubexpIndex("Version")]) switch migration.Provider { case providerAll, providerSQLite, providerMySQL, providerPostgres: break default: - return model.SchemaMigration{}, fmt.Errorf("invalid migration: value in position 3 '%s' must be all, sqlite, postgres, or mysql", result[3]) + return model.SchemaMigration{}, fmt.Errorf("invalid migration: value in Provider group '%s' must be all, sqlite, postgres, or mysql", migration.Provider) } return migration, nil diff --git a/internal/storage/migrations/V0008.OpenIDConnectPAR.all.down.sql b/internal/storage/migrations/V0008.OpenIDConnectPAR.all.down.sql new file mode 100644 index 000000000..1cf22a47d --- /dev/null +++ b/internal/storage/migrations/V0008.OpenIDConnectPAR.all.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS oauth2_par_context; diff --git a/internal/storage/migrations/V0008.OpenIDConnectPAR.mysql.up.sql b/internal/storage/migrations/V0008.OpenIDConnectPAR.mysql.up.sql new file mode 100644 index 000000000..53829ced0 --- /dev/null +++ b/internal/storage/migrations/V0008.OpenIDConnectPAR.mysql.up.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS oauth2_par_context ( + id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + request_id VARCHAR(40) NOT NULL, + client_id VARCHAR(255) NOT NULL, + signature VARCHAR(255) NOT NULL, + requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + scopes TEXT NOT NULL, + audience TEXT NOT NULL, + handled_response_types TEXT NOT NULL, + response_mode TEXT NOT NULL, + response_mode_default TEXT NOT NULL, + revoked BOOLEAN NOT NULL DEFAULT FALSE, + form_data TEXT NOT NULL, + session_data BLOB NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +CREATE UNIQUE INDEX oauth2_par_context_signature_key ON oauth2_par_context (signature); diff --git a/internal/storage/migrations/V0008.OpenIDConnectPAR.postgres.up.sql b/internal/storage/migrations/V0008.OpenIDConnectPAR.postgres.up.sql new file mode 100644 index 000000000..7926146e9 --- /dev/null +++ b/internal/storage/migrations/V0008.OpenIDConnectPAR.postgres.up.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS oauth2_par_context ( + id SERIAL CONSTRAINT oauth2_par_context_pkey PRIMARY KEY, + request_id VARCHAR(40) NOT NULL, + client_id VARCHAR(255) NOT NULL, + signature VARCHAR(255) NOT NULL, + requested_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + scopes TEXT NOT NULL, + audience TEXT NULL DEFAULT '', + handled_response_types TEXT NOT NULL DEFAULT '', + response_mode TEXT NOT NULL DEFAULT '', + response_mode_default TEXT NOT NULL DEFAULT '', + revoked BOOLEAN NOT NULL DEFAULT FALSE, + form_data TEXT NOT NULL, + session_data BYTEA NOT NULL +); + +CREATE UNIQUE INDEX oauth2_par_context_signature_key ON oauth2_par_context (signature); diff --git a/internal/storage/migrations/V0008.OpenIDConnectPAR.sqlite.up.sql b/internal/storage/migrations/V0008.OpenIDConnectPAR.sqlite.up.sql new file mode 100644 index 000000000..9ef735998 --- /dev/null +++ b/internal/storage/migrations/V0008.OpenIDConnectPAR.sqlite.up.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS oauth2_par_context ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + signature VARCHAR(255) NOT NULL, + request_id VARCHAR(40) NOT NULL, + client_id VARCHAR(255) NOT NULL, + requested_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + scopes TEXT NOT NULL, + audience TEXT NOT NULL, + handled_response_types TEXT NOT NULL, + response_mode TEXT NOT NULL, + response_mode_default TEXT NOT NULL, + revoked BOOLEAN NOT NULL DEFAULT FALSE, + form_data TEXT NOT NULL, + session_data BLOB NOT NULL +); + +CREATE UNIQUE INDEX oauth2_par_context_signature_key ON oauth2_par_context (signature); diff --git a/internal/storage/provider.go b/internal/storage/provider.go index 69cd7a8d6..bdc526e30 100644 --- a/internal/storage/provider.go +++ b/internal/storage/provider.go @@ -24,8 +24,8 @@ type Provider interface { LoadUserInfo(ctx context.Context, username string) (info model.UserInfo, err error) SaveUserOpaqueIdentifier(ctx context.Context, subject model.UserOpaqueIdentifier) (err error) - LoadUserOpaqueIdentifier(ctx context.Context, opaqueUUID uuid.UUID) (subject *model.UserOpaqueIdentifier, err error) - LoadUserOpaqueIdentifiers(ctx context.Context) (opaqueIDs []model.UserOpaqueIdentifier, err error) + LoadUserOpaqueIdentifier(ctx context.Context, identifier uuid.UUID) (subject *model.UserOpaqueIdentifier, err error) + LoadUserOpaqueIdentifiers(ctx context.Context) (identifiers []model.UserOpaqueIdentifier, err error) LoadUserOpaqueIdentifierBySignature(ctx context.Context, service, sectorID, username string) (subject *model.UserOpaqueIdentifier, err error) SaveIdentityVerification(ctx context.Context, verification model.IdentityVerification) (err error) @@ -70,6 +70,10 @@ type Provider interface { DeactivateOAuth2SessionByRequestID(ctx context.Context, sessionType OAuth2SessionType, requestID string) (err error) LoadOAuth2Session(ctx context.Context, sessionType OAuth2SessionType, signature string) (session *model.OAuth2Session, err error) + SaveOAuth2PARContext(ctx context.Context, par model.OAuth2PARContext) (err error) + LoadOAuth2PARContext(ctx context.Context, signature string) (par *model.OAuth2PARContext, err error) + RevokeOAuth2PARContext(ctx context.Context, signature string) (err error) + SaveOAuth2BlacklistedJTI(ctx context.Context, blacklistedJTI model.OAuth2BlacklistedJTI) (err error) LoadOAuth2BlacklistedJTI(ctx context.Context, signature string) (blacklistedJTI *model.OAuth2BlacklistedJTI, err error) diff --git a/internal/storage/sql_provider.go b/internal/storage/sql_provider.go index 0ebb2898d..2285c499f 100644 --- a/internal/storage/sql_provider.go +++ b/internal/storage/sql_provider.go @@ -73,6 +73,13 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa sqlSelectUserOpaqueIdentifiers: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifiers, tableUserOpaqueIdentifier), sqlSelectUserOpaqueIdentifierBySignature: fmt.Sprintf(queryFmtSelectUserOpaqueIdentifierBySignature, tableUserOpaqueIdentifier), + sqlUpsertOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtUpsertOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI), + sqlSelectOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtSelectOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI), + + sqlInsertOAuth2PARContext: fmt.Sprintf(queryFmtInsertOAuth2PARContext, tableOAuth2PARContext), + sqlSelectOAuth2PARContext: fmt.Sprintf(queryFmtSelectOAuth2PARContext, tableOAuth2PARContext), + sqlRevokeOAuth2PARContext: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2PARContext), + sqlInsertOAuth2ConsentPreConfiguration: fmt.Sprintf(queryFmtInsertOAuth2ConsentPreConfiguration, tableOAuth2ConsentPreConfiguration), sqlSelectOAuth2ConsentPreConfigurations: fmt.Sprintf(queryFmtSelectOAuth2ConsentPreConfigurations, tableOAuth2ConsentPreConfiguration), @@ -82,13 +89,6 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa 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), - sqlRevokeOAuth2AuthorizeCodeSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2AuthorizeCodeSession), - sqlDeactivateOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2AuthorizeCodeSession), - sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2AuthorizeCodeSession), - sqlInsertOAuth2AccessTokenSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2AccessTokenSession), sqlSelectOAuth2AccessTokenSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2AccessTokenSession), sqlRevokeOAuth2AccessTokenSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2AccessTokenSession), @@ -96,19 +96,12 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa sqlDeactivateOAuth2AccessTokenSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2AccessTokenSession), sqlDeactivateOAuth2AccessTokenSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2AccessTokenSession), - sqlInsertOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2RefreshTokenSession), - sqlSelectOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2RefreshTokenSession), - sqlRevokeOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2RefreshTokenSession), - sqlRevokeOAuth2RefreshTokenSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2RefreshTokenSession), - sqlDeactivateOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2RefreshTokenSession), - sqlDeactivateOAuth2RefreshTokenSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2RefreshTokenSession), - - sqlInsertOAuth2PKCERequestSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2PKCERequestSession), - sqlSelectOAuth2PKCERequestSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2PKCERequestSession), - sqlRevokeOAuth2PKCERequestSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2PKCERequestSession), - sqlRevokeOAuth2PKCERequestSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2PKCERequestSession), - sqlDeactivateOAuth2PKCERequestSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2PKCERequestSession), - sqlDeactivateOAuth2PKCERequestSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2PKCERequestSession), + sqlInsertOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2AuthorizeCodeSession), + sqlSelectOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2AuthorizeCodeSession), + sqlRevokeOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2AuthorizeCodeSession), + sqlRevokeOAuth2AuthorizeCodeSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2AuthorizeCodeSession), + sqlDeactivateOAuth2AuthorizeCodeSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2AuthorizeCodeSession), + sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2AuthorizeCodeSession), sqlInsertOAuth2OpenIDConnectSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2OpenIDConnectSession), sqlSelectOAuth2OpenIDConnectSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2OpenIDConnectSession), @@ -117,8 +110,19 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa sqlDeactivateOAuth2OpenIDConnectSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2OpenIDConnectSession), sqlDeactivateOAuth2OpenIDConnectSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2OpenIDConnectSession), - sqlUpsertOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtUpsertOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI), - sqlSelectOAuth2BlacklistedJTI: fmt.Sprintf(queryFmtSelectOAuth2BlacklistedJTI, tableOAuth2BlacklistedJTI), + sqlInsertOAuth2PKCERequestSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2PKCERequestSession), + sqlSelectOAuth2PKCERequestSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2PKCERequestSession), + sqlRevokeOAuth2PKCERequestSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2PKCERequestSession), + sqlRevokeOAuth2PKCERequestSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2PKCERequestSession), + sqlDeactivateOAuth2PKCERequestSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2PKCERequestSession), + sqlDeactivateOAuth2PKCERequestSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2PKCERequestSession), + + sqlInsertOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtInsertOAuth2Session, tableOAuth2RefreshTokenSession), + sqlSelectOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtSelectOAuth2Session, tableOAuth2RefreshTokenSession), + sqlRevokeOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtRevokeOAuth2Session, tableOAuth2RefreshTokenSession), + sqlRevokeOAuth2RefreshTokenSessionByRequestID: fmt.Sprintf(queryFmtRevokeOAuth2SessionByRequestID, tableOAuth2RefreshTokenSession), + sqlDeactivateOAuth2RefreshTokenSession: fmt.Sprintf(queryFmtDeactivateOAuth2Session, tableOAuth2RefreshTokenSession), + sqlDeactivateOAuth2RefreshTokenSessionByRequestID: fmt.Sprintf(queryFmtDeactivateOAuth2SessionByRequestID, tableOAuth2RefreshTokenSession), sqlInsertMigration: fmt.Sprintf(queryFmtInsertMigration, tableMigrations), sqlSelectMigrations: fmt.Sprintf(queryFmtSelectMigrations, tableMigrations), @@ -233,13 +237,18 @@ type SQLProvider struct { sqlDeactivateOAuth2AccessTokenSession string sqlDeactivateOAuth2AccessTokenSessionByRequestID string - // Table: oauth2_refresh_token_session. - sqlInsertOAuth2RefreshTokenSession string - sqlSelectOAuth2RefreshTokenSession string - sqlRevokeOAuth2RefreshTokenSession string - sqlRevokeOAuth2RefreshTokenSessionByRequestID string - sqlDeactivateOAuth2RefreshTokenSession string - sqlDeactivateOAuth2RefreshTokenSessionByRequestID string + // Table: oauth2_openid_connect_session. + sqlInsertOAuth2OpenIDConnectSession string + sqlSelectOAuth2OpenIDConnectSession string + sqlRevokeOAuth2OpenIDConnectSession string + sqlRevokeOAuth2OpenIDConnectSessionByRequestID string + sqlDeactivateOAuth2OpenIDConnectSession string + sqlDeactivateOAuth2OpenIDConnectSessionByRequestID string + + // Table: oauth2_par_context. + sqlInsertOAuth2PARContext string + sqlSelectOAuth2PARContext string + sqlRevokeOAuth2PARContext string // Table: oauth2_pkce_request_session. sqlInsertOAuth2PKCERequestSession string @@ -249,13 +258,13 @@ type SQLProvider struct { sqlDeactivateOAuth2PKCERequestSession string sqlDeactivateOAuth2PKCERequestSessionByRequestID string - // Table: oauth2_openid_connect_session. - sqlInsertOAuth2OpenIDConnectSession string - sqlSelectOAuth2OpenIDConnectSession string - sqlRevokeOAuth2OpenIDConnectSession string - sqlRevokeOAuth2OpenIDConnectSessionByRequestID string - sqlDeactivateOAuth2OpenIDConnectSession string - sqlDeactivateOAuth2OpenIDConnectSessionByRequestID string + // Table: oauth2_refresh_token_session. + sqlInsertOAuth2RefreshTokenSession string + sqlSelectOAuth2RefreshTokenSession string + sqlRevokeOAuth2RefreshTokenSession string + sqlRevokeOAuth2RefreshTokenSessionByRequestID string + sqlDeactivateOAuth2RefreshTokenSession string + sqlDeactivateOAuth2RefreshTokenSessionByRequestID string sqlUpsertOAuth2BlacklistedJTI string sqlSelectOAuth2BlacklistedJTI string @@ -348,32 +357,32 @@ func (p *SQLProvider) Rollback(ctx context.Context) (err error) { } // SaveUserOpaqueIdentifier saves a new opaque user identifier to the database. -func (p *SQLProvider) SaveUserOpaqueIdentifier(ctx context.Context, opaqueID model.UserOpaqueIdentifier) (err error) { - if _, err = p.db.ExecContext(ctx, p.sqlInsertUserOpaqueIdentifier, opaqueID.Service, opaqueID.SectorID, opaqueID.Username, opaqueID.Identifier); err != nil { - return fmt.Errorf("error inserting user opaque id for user '%s' with opaque id '%s': %w", opaqueID.Username, opaqueID.Identifier.String(), err) +func (p *SQLProvider) SaveUserOpaqueIdentifier(ctx context.Context, subject model.UserOpaqueIdentifier) (err error) { + if _, err = p.db.ExecContext(ctx, p.sqlInsertUserOpaqueIdentifier, subject.Service, subject.SectorID, subject.Username, subject.Identifier); err != nil { + return fmt.Errorf("error inserting user opaque id for user '%s' with opaque id '%s': %w", subject.Username, subject.Identifier.String(), err) } return nil } // LoadUserOpaqueIdentifier selects an opaque user identifier from the database. -func (p *SQLProvider) LoadUserOpaqueIdentifier(ctx context.Context, opaqueUUID uuid.UUID) (opaqueID *model.UserOpaqueIdentifier, err error) { - opaqueID = &model.UserOpaqueIdentifier{} +func (p *SQLProvider) LoadUserOpaqueIdentifier(ctx context.Context, identifier uuid.UUID) (subject *model.UserOpaqueIdentifier, err error) { + subject = &model.UserOpaqueIdentifier{} - if err = p.db.GetContext(ctx, opaqueID, p.sqlSelectUserOpaqueIdentifier, opaqueUUID); err != nil { + if err = p.db.GetContext(ctx, subject, p.sqlSelectUserOpaqueIdentifier, identifier); err != nil { switch { case errors.Is(err, sql.ErrNoRows): return nil, nil default: - return nil, fmt.Errorf("error selecting user opaque id with value '%s': %w", opaqueUUID.String(), err) + return nil, fmt.Errorf("error selecting user opaque id with value '%s': %w", identifier.String(), err) } } - return opaqueID, nil + return subject, nil } // LoadUserOpaqueIdentifiers selects an opaque user identifiers from the database. -func (p *SQLProvider) LoadUserOpaqueIdentifiers(ctx context.Context) (opaqueIDs []model.UserOpaqueIdentifier, err error) { +func (p *SQLProvider) LoadUserOpaqueIdentifiers(ctx context.Context) (identifiers []model.UserOpaqueIdentifier, err error) { var rows *sqlx.Rows if rows, err = p.db.QueryxContext(ctx, p.sqlSelectUserOpaqueIdentifiers); err != nil { @@ -389,17 +398,17 @@ func (p *SQLProvider) LoadUserOpaqueIdentifiers(ctx context.Context) (opaqueIDs return nil, fmt.Errorf("error selecting user opaque identifiers: error scanning row: %w", err) } - opaqueIDs = append(opaqueIDs, *opaqueID) + identifiers = append(identifiers, *opaqueID) } - return opaqueIDs, nil + return identifiers, nil } -// LoadUserOpaqueIdentifierBySignature selects an opaque user identifier from the database given a service name, sector id, and username. -func (p *SQLProvider) LoadUserOpaqueIdentifierBySignature(ctx context.Context, service, sectorID, username string) (opaqueID *model.UserOpaqueIdentifier, err error) { - opaqueID = &model.UserOpaqueIdentifier{} +// LoadUserOpaqueIdentifierBySignature selects an opaque user identifier from the database given a service name, sector id, and username. +func (p *SQLProvider) LoadUserOpaqueIdentifierBySignature(ctx context.Context, service, sectorID, username string) (subject *model.UserOpaqueIdentifier, err error) { + subject = &model.UserOpaqueIdentifier{} - if err = p.db.GetContext(ctx, opaqueID, p.sqlSelectUserOpaqueIdentifierBySignature, service, sectorID, username); err != nil { + if err = p.db.GetContext(ctx, subject, p.sqlSelectUserOpaqueIdentifierBySignature, service, sectorID, username); err != nil { switch { case errors.Is(err, sql.ErrNoRows): return nil, nil @@ -408,7 +417,7 @@ func (p *SQLProvider) LoadUserOpaqueIdentifierBySignature(ctx context.Context, s } } - return opaqueID, nil + return subject, nil } // SaveOAuth2ConsentSession inserts an OAuth2.0 consent session. @@ -505,22 +514,22 @@ func (p *SQLProvider) SaveOAuth2Session(ctx context.Context, sessionType OAuth2S var query string switch sessionType { - case OAuth2SessionTypeAuthorizeCode: - query = p.sqlInsertOAuth2AuthorizeCodeSession case OAuth2SessionTypeAccessToken: query = p.sqlInsertOAuth2AccessTokenSession - case OAuth2SessionTypeRefreshToken: - query = p.sqlInsertOAuth2RefreshTokenSession - case OAuth2SessionTypePKCEChallenge: - query = p.sqlInsertOAuth2PKCERequestSession + case OAuth2SessionTypeAuthorizeCode: + query = p.sqlInsertOAuth2AuthorizeCodeSession case OAuth2SessionTypeOpenIDConnect: query = p.sqlInsertOAuth2OpenIDConnectSession + case OAuth2SessionTypePKCEChallenge: + query = p.sqlInsertOAuth2PKCERequestSession + case OAuth2SessionTypeRefreshToken: + query = p.sqlInsertOAuth2RefreshTokenSession default: return fmt.Errorf("error inserting oauth2 session for subject '%s' and request id '%s': unknown oauth2 session type '%s'", session.Subject, session.RequestID, sessionType) } if session.Session, err = p.encrypt(session.Session); err != nil { - return fmt.Errorf("error encrypting the oauth2 %s session data for subject '%s' and request id '%s' and challenge id '%s': %w", sessionType, session.Subject, session.RequestID, session.ChallengeID.String(), err) + return fmt.Errorf("error encrypting oauth2 %s session data for subject '%s' and request id '%s' and challenge id '%s': %w", sessionType, session.Subject, session.RequestID, session.ChallengeID.String(), err) } _, err = p.db.ExecContext(ctx, query, @@ -541,16 +550,16 @@ func (p *SQLProvider) RevokeOAuth2Session(ctx context.Context, sessionType OAuth var query string switch sessionType { - case OAuth2SessionTypeAuthorizeCode: - query = p.sqlRevokeOAuth2AuthorizeCodeSession case OAuth2SessionTypeAccessToken: query = p.sqlRevokeOAuth2AccessTokenSession - case OAuth2SessionTypeRefreshToken: - query = p.sqlRevokeOAuth2RefreshTokenSession - case OAuth2SessionTypePKCEChallenge: - query = p.sqlRevokeOAuth2PKCERequestSession + case OAuth2SessionTypeAuthorizeCode: + query = p.sqlRevokeOAuth2AuthorizeCodeSession case OAuth2SessionTypeOpenIDConnect: query = p.sqlRevokeOAuth2OpenIDConnectSession + case OAuth2SessionTypePKCEChallenge: + query = p.sqlRevokeOAuth2PKCERequestSession + case OAuth2SessionTypeRefreshToken: + query = p.sqlRevokeOAuth2RefreshTokenSession default: return fmt.Errorf("error revoking oauth2 session with signature '%s': unknown oauth2 session type '%s'", signature, sessionType.String()) } @@ -567,16 +576,16 @@ func (p *SQLProvider) RevokeOAuth2SessionByRequestID(ctx context.Context, sessio var query string switch sessionType { - case OAuth2SessionTypeAuthorizeCode: - query = p.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID case OAuth2SessionTypeAccessToken: query = p.sqlRevokeOAuth2AccessTokenSessionByRequestID - case OAuth2SessionTypeRefreshToken: - query = p.sqlRevokeOAuth2RefreshTokenSessionByRequestID - case OAuth2SessionTypePKCEChallenge: - query = p.sqlRevokeOAuth2PKCERequestSessionByRequestID + case OAuth2SessionTypeAuthorizeCode: + query = p.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID case OAuth2SessionTypeOpenIDConnect: query = p.sqlRevokeOAuth2OpenIDConnectSessionByRequestID + case OAuth2SessionTypePKCEChallenge: + query = p.sqlRevokeOAuth2PKCERequestSessionByRequestID + case OAuth2SessionTypeRefreshToken: + query = p.sqlRevokeOAuth2RefreshTokenSessionByRequestID default: return fmt.Errorf("error revoking oauth2 session with request id '%s': unknown oauth2 session type '%s'", requestID, sessionType.String()) } @@ -593,16 +602,16 @@ func (p *SQLProvider) DeactivateOAuth2Session(ctx context.Context, sessionType O var query string switch sessionType { - case OAuth2SessionTypeAuthorizeCode: - query = p.sqlDeactivateOAuth2AuthorizeCodeSession case OAuth2SessionTypeAccessToken: query = p.sqlDeactivateOAuth2AccessTokenSession - case OAuth2SessionTypeRefreshToken: - query = p.sqlDeactivateOAuth2RefreshTokenSession - case OAuth2SessionTypePKCEChallenge: - query = p.sqlDeactivateOAuth2PKCERequestSession + case OAuth2SessionTypeAuthorizeCode: + query = p.sqlDeactivateOAuth2AuthorizeCodeSession case OAuth2SessionTypeOpenIDConnect: query = p.sqlDeactivateOAuth2OpenIDConnectSession + case OAuth2SessionTypePKCEChallenge: + query = p.sqlDeactivateOAuth2PKCERequestSession + case OAuth2SessionTypeRefreshToken: + query = p.sqlDeactivateOAuth2RefreshTokenSession default: return fmt.Errorf("error deactivating oauth2 session with signature '%s': unknown oauth2 session type '%s'", signature, sessionType.String()) } @@ -619,16 +628,16 @@ func (p *SQLProvider) DeactivateOAuth2SessionByRequestID(ctx context.Context, se var query string switch sessionType { - case OAuth2SessionTypeAuthorizeCode: - query = p.sqlDeactivateOAuth2AuthorizeCodeSession case OAuth2SessionTypeAccessToken: query = p.sqlDeactivateOAuth2AccessTokenSessionByRequestID - case OAuth2SessionTypeRefreshToken: - query = p.sqlDeactivateOAuth2RefreshTokenSessionByRequestID - case OAuth2SessionTypePKCEChallenge: - query = p.sqlDeactivateOAuth2PKCERequestSessionByRequestID + case OAuth2SessionTypeAuthorizeCode: + query = p.sqlDeactivateOAuth2AuthorizeCodeSession case OAuth2SessionTypeOpenIDConnect: query = p.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID + case OAuth2SessionTypePKCEChallenge: + query = p.sqlDeactivateOAuth2PKCERequestSessionByRequestID + case OAuth2SessionTypeRefreshToken: + query = p.sqlDeactivateOAuth2RefreshTokenSessionByRequestID default: return fmt.Errorf("error deactivating oauth2 session with request id '%s': unknown oauth2 session type '%s'", requestID, sessionType.String()) } @@ -645,16 +654,16 @@ func (p *SQLProvider) LoadOAuth2Session(ctx context.Context, sessionType OAuth2S var query string switch sessionType { - case OAuth2SessionTypeAuthorizeCode: - query = p.sqlSelectOAuth2AuthorizeCodeSession case OAuth2SessionTypeAccessToken: query = p.sqlSelectOAuth2AccessTokenSession - case OAuth2SessionTypeRefreshToken: - query = p.sqlSelectOAuth2RefreshTokenSession - case OAuth2SessionTypePKCEChallenge: - query = p.sqlSelectOAuth2PKCERequestSession + case OAuth2SessionTypeAuthorizeCode: + query = p.sqlSelectOAuth2AuthorizeCodeSession case OAuth2SessionTypeOpenIDConnect: query = p.sqlSelectOAuth2OpenIDConnectSession + case OAuth2SessionTypePKCEChallenge: + query = p.sqlSelectOAuth2PKCERequestSession + case OAuth2SessionTypeRefreshToken: + query = p.sqlSelectOAuth2RefreshTokenSession default: return nil, fmt.Errorf("error selecting oauth2 session: unknown oauth2 session type '%s'", sessionType.String()) } @@ -672,6 +681,45 @@ func (p *SQLProvider) LoadOAuth2Session(ctx context.Context, sessionType OAuth2S return session, nil } +// SaveOAuth2PARContext save a OAuth2PARContext to the database. +func (p *SQLProvider) SaveOAuth2PARContext(ctx context.Context, par model.OAuth2PARContext) (err error) { + if par.Session, err = p.encrypt(par.Session); err != nil { + return fmt.Errorf("error encrypting oauth2 pushed authorization request context data for with signature '%s' and request id '%s': %w", par.Signature, par.RequestID, err) + } + + if _, err = p.db.ExecContext(ctx, p.sqlInsertOAuth2PARContext, + par.Signature, par.RequestID, par.ClientID, par.RequestedAt, par.Scopes, par.Audience, par.HandledResponseTypes, + par.ResponseMode, par.DefaultResponseMode, par.Revoked, par.Form, par.Session); err != nil { + return fmt.Errorf("error inserting oauth2 pushed authorization request context data for with signature '%s' and request id '%s': %w", par.Signature, par.RequestID, err) + } + + return nil +} + +// LoadOAuth2PARContext loads a OAuth2PARContext from the database. +func (p *SQLProvider) LoadOAuth2PARContext(ctx context.Context, signature string) (par *model.OAuth2PARContext, err error) { + par = &model.OAuth2PARContext{} + + if err = p.db.GetContext(ctx, par, p.sqlSelectOAuth2PARContext, signature); err != nil { + return nil, fmt.Errorf("error selecting oauth2 pushed authorization request context with signature '%s': %w", signature, err) + } + + if par.Session, err = p.decrypt(par.Session); err != nil { + return nil, fmt.Errorf("error decrypting oauth2 oauth2 pushed authorization request context data with signature '%s' and request id '%s': %w", signature, par.RequestID, err) + } + + return par, nil +} + +// RevokeOAuth2PARContext marks a OAuth2PARContext as revoked in the database. +func (p *SQLProvider) RevokeOAuth2PARContext(ctx context.Context, signature string) (err error) { + if _, err = p.db.ExecContext(ctx, p.sqlRevokeOAuth2PARContext, signature); err != nil { + return fmt.Errorf("error revoking oauth2 pushed authorization request context with signature '%s': %w", signature, err) + } + + return nil +} + // SaveOAuth2BlacklistedJTI saves a OAuth2BlacklistedJTI to the database. func (p *SQLProvider) SaveOAuth2BlacklistedJTI(ctx context.Context, blacklistedJTI model.OAuth2BlacklistedJTI) (err error) { if _, err = p.db.ExecContext(ctx, p.sqlUpsertOAuth2BlacklistedJTI, blacklistedJTI.Signature, blacklistedJTI.ExpiresAt); err != nil { @@ -771,7 +819,7 @@ func (p *SQLProvider) FindIdentityVerification(ctx context.Context, jti string) // SaveTOTPConfiguration save a TOTP configuration of a given user in the database. func (p *SQLProvider) SaveTOTPConfiguration(ctx context.Context, config model.TOTPConfiguration) (err error) { if config.Secret, err = p.encrypt(config.Secret); err != nil { - return fmt.Errorf("error encrypting the TOTP configuration secret for user '%s': %w", config.Username, err) + return fmt.Errorf("error encrypting TOTP configuration secret for user '%s': %w", config.Username, err) } if _, err = p.db.ExecContext(ctx, p.sqlUpsertTOTPConfig, @@ -815,7 +863,7 @@ func (p *SQLProvider) LoadTOTPConfiguration(ctx context.Context, username string } if config.Secret, err = p.decrypt(config.Secret); err != nil { - return nil, fmt.Errorf("error decrypting the TOTP secret for user '%s': %w", username, err) + return nil, fmt.Errorf("error decrypting TOTP secret for user '%s': %w", username, err) } return config, nil @@ -870,7 +918,7 @@ func (p *SQLProvider) LoadWebauthnUser(ctx context.Context, rpid, username strin // SaveWebauthnDevice saves a registered Webauthn device. func (p *SQLProvider) SaveWebauthnDevice(ctx context.Context, device model.WebauthnDevice) (err error) { if device.PublicKey, err = p.encrypt(device.PublicKey); err != nil { - return fmt.Errorf("error encrypting the Webauthn device public key for user '%s' kid '%x': %w", device.Username, device.KID, err) + return fmt.Errorf("error encrypting Webauthn device public key for user '%s' kid '%x': %w", device.Username, device.KID, err) } if _, err = p.db.ExecContext(ctx, p.sqlInsertWebauthnDevice, diff --git a/internal/storage/sql_provider_backend_postgres.go b/internal/storage/sql_provider_backend_postgres.go index 00989feef..723f4b989 100644 --- a/internal/storage/sql_provider_backend_postgres.go +++ b/internal/storage/sql_provider_backend_postgres.go @@ -92,13 +92,6 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo provider.sqlUpdateOAuth2ConsentSessionGranted = provider.db.Rebind(provider.sqlUpdateOAuth2ConsentSessionGranted) provider.sqlSelectOAuth2ConsentSessionByChallengeID = provider.db.Rebind(provider.sqlSelectOAuth2ConsentSessionByChallengeID) - provider.sqlInsertOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlInsertOAuth2AuthorizeCodeSession) - provider.sqlRevokeOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSession) - provider.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID) - provider.sqlDeactivateOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlDeactivateOAuth2AuthorizeCodeSession) - provider.sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID) - provider.sqlSelectOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlSelectOAuth2AuthorizeCodeSession) - provider.sqlInsertOAuth2AccessTokenSession = provider.db.Rebind(provider.sqlInsertOAuth2AccessTokenSession) provider.sqlRevokeOAuth2AccessTokenSession = provider.db.Rebind(provider.sqlRevokeOAuth2AccessTokenSession) provider.sqlRevokeOAuth2AccessTokenSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2AccessTokenSessionByRequestID) @@ -106,12 +99,23 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo provider.sqlDeactivateOAuth2AccessTokenSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2AccessTokenSessionByRequestID) provider.sqlSelectOAuth2AccessTokenSession = provider.db.Rebind(provider.sqlSelectOAuth2AccessTokenSession) - provider.sqlInsertOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlInsertOAuth2RefreshTokenSession) - provider.sqlRevokeOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlRevokeOAuth2RefreshTokenSession) - provider.sqlRevokeOAuth2RefreshTokenSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2RefreshTokenSessionByRequestID) - provider.sqlDeactivateOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlDeactivateOAuth2RefreshTokenSession) - provider.sqlDeactivateOAuth2RefreshTokenSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2RefreshTokenSessionByRequestID) - provider.sqlSelectOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlSelectOAuth2RefreshTokenSession) + provider.sqlInsertOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlInsertOAuth2AuthorizeCodeSession) + provider.sqlRevokeOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSession) + provider.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2AuthorizeCodeSessionByRequestID) + provider.sqlDeactivateOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlDeactivateOAuth2AuthorizeCodeSession) + provider.sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2AuthorizeCodeSessionByRequestID) + provider.sqlSelectOAuth2AuthorizeCodeSession = provider.db.Rebind(provider.sqlSelectOAuth2AuthorizeCodeSession) + + provider.sqlInsertOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlInsertOAuth2OpenIDConnectSession) + provider.sqlRevokeOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlRevokeOAuth2OpenIDConnectSession) + provider.sqlRevokeOAuth2OpenIDConnectSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2OpenIDConnectSessionByRequestID) + provider.sqlDeactivateOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlDeactivateOAuth2OpenIDConnectSession) + provider.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID) + provider.sqlSelectOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlSelectOAuth2OpenIDConnectSession) + + provider.sqlInsertOAuth2PARContext = provider.db.Rebind(provider.sqlInsertOAuth2PARContext) + provider.sqlRevokeOAuth2PARContext = provider.db.Rebind(provider.sqlRevokeOAuth2PARContext) + provider.sqlSelectOAuth2PARContext = provider.db.Rebind(provider.sqlSelectOAuth2PARContext) provider.sqlInsertOAuth2PKCERequestSession = provider.db.Rebind(provider.sqlInsertOAuth2PKCERequestSession) provider.sqlRevokeOAuth2PKCERequestSession = provider.db.Rebind(provider.sqlRevokeOAuth2PKCERequestSession) @@ -120,12 +124,12 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo provider.sqlDeactivateOAuth2PKCERequestSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2PKCERequestSessionByRequestID) provider.sqlSelectOAuth2PKCERequestSession = provider.db.Rebind(provider.sqlSelectOAuth2PKCERequestSession) - provider.sqlInsertOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlInsertOAuth2OpenIDConnectSession) - provider.sqlRevokeOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlRevokeOAuth2OpenIDConnectSession) - provider.sqlRevokeOAuth2OpenIDConnectSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2OpenIDConnectSessionByRequestID) - provider.sqlDeactivateOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlDeactivateOAuth2OpenIDConnectSession) - provider.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2OpenIDConnectSessionByRequestID) - provider.sqlSelectOAuth2OpenIDConnectSession = provider.db.Rebind(provider.sqlSelectOAuth2OpenIDConnectSession) + provider.sqlInsertOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlInsertOAuth2RefreshTokenSession) + provider.sqlRevokeOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlRevokeOAuth2RefreshTokenSession) + provider.sqlRevokeOAuth2RefreshTokenSessionByRequestID = provider.db.Rebind(provider.sqlRevokeOAuth2RefreshTokenSessionByRequestID) + provider.sqlDeactivateOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlDeactivateOAuth2RefreshTokenSession) + provider.sqlDeactivateOAuth2RefreshTokenSessionByRequestID = provider.db.Rebind(provider.sqlDeactivateOAuth2RefreshTokenSessionByRequestID) + provider.sqlSelectOAuth2RefreshTokenSession = provider.db.Rebind(provider.sqlSelectOAuth2RefreshTokenSession) provider.sqlSelectOAuth2BlacklistedJTI = provider.db.Rebind(provider.sqlSelectOAuth2BlacklistedJTI) diff --git a/internal/storage/sql_provider_queries.go b/internal/storage/sql_provider_queries.go index 6e16eab02..6bd1ec54d 100644 --- a/internal/storage/sql_provider_queries.go +++ b/internal/storage/sql_provider_queries.go @@ -327,6 +327,19 @@ const ( SET active = FALSE WHERE request_id = ?;` + queryFmtSelectOAuth2PARContext = ` + SELECT id, signature, request_id, client_id, requested_at, scopes, audience, + handled_response_types, response_mode, response_mode_default, revoked, + form_data, session_data + FROM %s + WHERE signature = ? AND revoked = FALSE;` + + queryFmtInsertOAuth2PARContext = ` + INSERT INTO %s (signature, request_id, client_id, requested_at, scopes, audience, + handled_response_types, response_mode, response_mode_default, revoked, + form_data, session_data) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);` + queryFmtSelectOAuth2BlacklistedJTI = ` SELECT id, signature, expires_at FROM %s diff --git a/internal/suites/example/compose/envoy/docker-compose.yml b/internal/suites/example/compose/envoy/docker-compose.yml index 4941537df..1ad99ab5b 100644 --- a/internal/suites/example/compose/envoy/docker-compose.yml +++ b/internal/suites/example/compose/envoy/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: envoy: - image: envoyproxy/envoy:v1.25.1 + image: envoyproxy/envoy:v1.25.2 volumes: - ./example/compose/envoy/envoy.yaml:/etc/envoy/envoy.yaml - ./common/pki:/pki diff --git a/internal/suites/example/compose/k3d/docker-compose.yml b/internal/suites/example/compose/k3d/docker-compose.yml index db82d3ba4..ebf03209e 100644 --- a/internal/suites/example/compose/k3d/docker-compose.yml +++ b/internal/suites/example/compose/k3d/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: k3d: - image: ghcr.io/k3d-io/k3d:5.4.7-dind + image: ghcr.io/k3d-io/k3d:5.4.8-dind volumes: - './example/kube:/authelia' - './example/kube/authelia/configs/configuration.yml:/configmaps/authelia/configuration.yml' diff --git a/internal/suites/example/compose/nginx/portal/nginx.conf b/internal/suites/example/compose/nginx/portal/nginx.conf index a6986fe06..d7260426c 100644 --- a/internal/suites/example/compose/nginx/portal/nginx.conf +++ b/internal/suites/example/compose/nginx/portal/nginx.conf @@ -164,7 +164,7 @@ http { # to the virtual endpoint introduced by nginx and declared in the next block. location / { ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. - auth_request /authelia; + auth_request /internal/authelia/authz; ## Set the $target_url variable based on the original request. set $target_url $scheme://$http_host$request_uri; @@ -209,7 +209,7 @@ http { } # Virtual endpoint forwarding requests to Authelia server. - location /authelia { + location /internal/authelia/authz { ## Essential Proxy Configuration internal; proxy_pass $upstream_authelia; @@ -250,7 +250,7 @@ http { # Used by suites to test the forwarded users and groups headers produced by Authelia. location /headers { ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. - auth_request /authelia; + auth_request /internal/authelia/authz; ## Set the $target_url variable based on the original request. set $target_url $scheme://$http_host$request_uri; @@ -307,7 +307,7 @@ http { # to the virtual endpoint introduced by nginx and declared in the next block. location / { ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. - auth_request /authelia; + auth_request /internal/authelia/authz; ## Set the $target_url variable based on the original request. set $target_url $scheme://$http_host$request_uri; @@ -346,7 +346,7 @@ http { } # Virtual endpoint forwarding requests to Authelia server. - location /authelia { + location /internal/authelia/authz { ## Essential Proxy Configuration internal; proxy_pass $upstream_authelia; @@ -356,7 +356,6 @@ http { # Those headers will be used by Authelia to deduce the target url of the user. # # X-Forwarded-Proto is mandatory since Authelia uses the "trust proxy" option. - # See https://expressjs.com/en/guide/behind-proxies.html proxy_set_header X-Original-Method $request_method; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go index 6176dfbd6..a77507efe 100644 --- a/internal/suites/suite_cli_test.go +++ b/internal/suites/suite_cli_test.go @@ -1132,6 +1132,7 @@ func (s *CLISuite) TestStorage05ShouldChangeEncryptionKey() { s.Assert().Contains(output, "\n\n\tTable (oauth2_openid_connect_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") s.Assert().Contains(output, "\n\n\tTable (oauth2_pkce_request_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") s.Assert().Contains(output, "\n\n\tTable (oauth2_refresh_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") + s.Assert().Contains(output, "\n\n\tTable (oauth2_par_context): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") s.Assert().Contains(output, "\n\n\tTable (totp_configurations): FAILURE\n\t\tInvalid Rows: 4\n\t\tTotal Rows: 4\n") s.Assert().Contains(output, "\n\n\tTable (webauthn_devices): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") @@ -1149,6 +1150,7 @@ func (s *CLISuite) TestStorage05ShouldChangeEncryptionKey() { s.Assert().Contains(output, "\n\n\tTable (oauth2_openid_connect_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") s.Assert().Contains(output, "\n\n\tTable (oauth2_pkce_request_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") s.Assert().Contains(output, "\n\n\tTable (oauth2_refresh_token_session): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") + s.Assert().Contains(output, "\n\n\tTable (oauth2_par_context): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") s.Assert().Contains(output, "\n\n\tTable (totp_configurations): SUCCESS\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 4\n") s.Assert().Contains(output, "\n\n\tTable (webauthn_devices): N/A\n\t\tInvalid Rows: 0\n\t\tTotal Rows: 0\n") diff --git a/internal/utils/strings.go b/internal/utils/strings.go index b56aed5eb..3e9dc12cd 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -104,8 +104,13 @@ func IsStringSliceContainsAll(needles []string, haystack []string) (inSlice bool // IsStringSliceContainsAny checks if the haystack contains any of the strings in the needles. func IsStringSliceContainsAny(needles []string, haystack []string) (inSlice bool) { + return IsStringSliceContainsAnyF(needles, haystack, IsStringInSlice) +} + +// IsStringSliceContainsAnyF checks if the haystack contains any of the strings in the needles using the isInSlice func. +func IsStringSliceContainsAnyF(needles []string, haystack []string, isInSlice func(needle string, haystack []string) bool) (inSlice bool) { for _, n := range needles { - if IsStringInSlice(n, haystack) { + if isInSlice(n, haystack) { return true } } diff --git a/web/package.json b/web/package.json index df816aea0..2da1fbe37 100644 --- a/web/package.json +++ b/web/package.json @@ -29,9 +29,9 @@ "@fortawesome/free-regular-svg-icons": "6.3.0", "@fortawesome/free-solid-svg-icons": "6.3.0", "@fortawesome/react-fontawesome": "0.2.0", - "@mui/icons-material": "5.11.9", - "@mui/material": "5.11.10", - "@mui/styles": "5.11.9", + "@mui/icons-material": "5.11.11", + "@mui/material": "5.11.11", + "@mui/styles": "5.11.11", "@simplewebauthn/browser": "7.1.0", "@simplewebauthn/typescript-types": "7.0.0", "axios": "1.3.4", @@ -154,7 +154,7 @@ "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "14.0.0", "@types/jest": "29.4.0", - "@types/node": "18.14.2", + "@types/node": "18.14.6", "@types/qrcode.react": "1.0.2", "@types/react": "18.0.28", "@types/react-dom": "18.0.11", @@ -162,10 +162,10 @@ "@typescript-eslint/eslint-plugin": "5.54.0", "@typescript-eslint/parser": "5.54.0", "@vitejs/plugin-react": "3.1.0", - "esbuild": "0.17.10", + "esbuild": "0.17.11", "esbuild-jest": "0.5.0", "eslint": "8.35.0", - "eslint-config-prettier": "8.6.0", + "eslint-config-prettier": "8.7.0", "eslint-config-react-app": "7.0.1", "eslint-formatter-rdjson": "1.0.5", "eslint-import-resolver-typescript": "3.5.3", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 3c2f4cd21..b3af63f3d 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -11,15 +11,15 @@ specifiers: '@fortawesome/free-solid-svg-icons': 6.3.0 '@fortawesome/react-fontawesome': 0.2.0 '@limegrass/eslint-plugin-import-alias': 1.0.6 - '@mui/icons-material': 5.11.9 - '@mui/material': 5.11.10 - '@mui/styles': 5.11.9 + '@mui/icons-material': 5.11.11 + '@mui/material': 5.11.11 + '@mui/styles': 5.11.11 '@simplewebauthn/browser': 7.1.0 '@simplewebauthn/typescript-types': 7.0.0 '@testing-library/jest-dom': 5.16.5 '@testing-library/react': 14.0.0 '@types/jest': 29.4.0 - '@types/node': 18.14.2 + '@types/node': 18.14.6 '@types/qrcode.react': 1.0.2 '@types/react': 18.0.28 '@types/react-dom': 18.0.11 @@ -30,10 +30,10 @@ specifiers: axios: 1.3.4 broadcast-channel: 4.20.2 classnames: 2.3.2 - esbuild: 0.17.10 + esbuild: 0.17.11 esbuild-jest: 0.5.0 eslint: 8.35.0 - eslint-config-prettier: 8.6.0 + eslint-config-prettier: 8.7.0 eslint-config-react-app: 7.0.1 eslint-formatter-rdjson: 1.0.5 eslint-import-resolver-typescript: 3.5.3 @@ -75,9 +75,9 @@ dependencies: '@fortawesome/free-regular-svg-icons': 6.3.0 '@fortawesome/free-solid-svg-icons': 6.3.0 '@fortawesome/react-fontawesome': 0.2.0_d5rbrisxfyemehbvmdbryvgjte - '@mui/icons-material': 5.11.9_ylmaxqh5wvme7ymgn4ys5vax6u - '@mui/material': 5.11.10_xqeqsl5kvjjtyxwyi3jhw3yuli - '@mui/styles': 5.11.9_pmekkgnqduwlme35zpnqhenc34 + '@mui/icons-material': 5.11.11_h5fh5ntwxtyr677wxvzgewjsma + '@mui/material': 5.11.11_xqeqsl5kvjjtyxwyi3jhw3yuli + '@mui/styles': 5.11.11_pmekkgnqduwlme35zpnqhenc34 '@simplewebauthn/browser': 7.1.0 '@simplewebauthn/typescript-types': 7.0.0 axios: 1.3.4 @@ -102,7 +102,7 @@ devDependencies: '@testing-library/jest-dom': 5.16.5 '@testing-library/react': 14.0.0_biqbaboplfbrettd7655fr4n2y '@types/jest': 29.4.0 - '@types/node': 18.14.2 + '@types/node': 18.14.6 '@types/qrcode.react': 1.0.2 '@types/react': 18.0.28 '@types/react-dom': 18.0.11 @@ -110,27 +110,27 @@ devDependencies: '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu '@vitejs/plugin-react': 3.1.0_vite@4.1.4 - esbuild: 0.17.10 - esbuild-jest: 0.5.0_esbuild@0.17.10 + esbuild: 0.17.11 + esbuild-jest: 0.5.0_esbuild@0.17.11 eslint: 8.35.0 - eslint-config-prettier: 8.6.0_eslint@8.35.0 + eslint-config-prettier: 8.7.0_eslint@8.35.0 eslint-config-react-app: 7.0.1_rujdaanoqbgar7y6lyhesjm6ei eslint-formatter-rdjson: 1.0.5 eslint-import-resolver-typescript: 3.5.3_yckic57kx266ph64dhq6ozvb54 eslint-plugin-import: 2.27.5_tqrcrxlenpngfto46ddarus52y eslint-plugin-jsx-a11y: 6.7.1_eslint@8.35.0 - eslint-plugin-prettier: 4.2.1_u2zha4kiojzs42thzpgwygphmy + eslint-plugin-prettier: 4.2.1_xprnzp4ul2bcpmfe73av4voica eslint-plugin-react: 7.32.2_eslint@8.35.0 eslint-plugin-react-hooks: 4.6.0_eslint@8.35.0 husky: 8.0.3 - jest: 29.4.3_@types+node@18.14.2 + jest: 29.4.3_@types+node@18.14.6 jest-environment-jsdom: 29.4.3 jest-transform-stub: 2.0.0 jest-watch-typeahead: 2.2.2_jest@29.4.3 prettier: 2.8.4 react-test-renderer: 18.2.0_react@18.2.0 typescript: 4.9.5 - vite: 4.1.4_@types+node@18.14.2 + vite: 4.1.4_@types+node@18.14.6 vite-plugin-eslint: 1.8.1_eslint@8.35.0+vite@4.1.4 vite-plugin-istanbul: 4.0.1_vite@4.1.4 vite-plugin-svgr: 2.4.0_vite@4.1.4 @@ -1966,6 +1966,12 @@ packages: dependencies: regenerator-runtime: 0.13.11 + /@babel/runtime/7.21.0: + resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + /@babel/template/7.20.7: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} @@ -2098,15 +2104,15 @@ packages: '@commitlint/execute-rule': 17.4.0 '@commitlint/resolve-extends': 17.4.4 '@commitlint/types': 17.4.4 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 cosmiconfig: 8.0.0 - cosmiconfig-typescript-loader: 4.0.0_pqkpmik5v4qfgv7siq75hqx7eu + cosmiconfig-typescript-loader: 4.0.0_hxwe5hvsmo6pskygql6awmmuoa lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.0_ellgaeuoqnti3hful2ny2iugba + ts-node: 10.9.0_alpjt73dvgv6kni625hu7f2l4m typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' @@ -2312,8 +2318,8 @@ packages: dev: true optional: true - /@esbuild/android-arm/0.17.10: - resolution: {integrity: sha512-7YEBfZ5lSem9Tqpsz+tjbdsEshlO9j/REJrfv4DXgKTt1+/MHqGwbtlyxQuaSlMeUZLxUKBaX8wdzlTfHkmnLw==} + /@esbuild/android-arm/0.17.11: + resolution: {integrity: sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -2330,8 +2336,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64/0.17.10: - resolution: {integrity: sha512-ht1P9CmvrPF5yKDtyC+z43RczVs4rrHpRqrmIuoSvSdn44Fs1n6DGlpZKdK6rM83pFLbVaSUwle8IN+TPmkv7g==} + /@esbuild/android-arm64/0.17.11: + resolution: {integrity: sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -2348,8 +2354,8 @@ packages: dev: true optional: true - /@esbuild/android-x64/0.17.10: - resolution: {integrity: sha512-CYzrm+hTiY5QICji64aJ/xKdN70IK8XZ6iiyq0tZkd3tfnwwSWTYH1t3m6zyaaBxkuj40kxgMyj1km/NqdjQZA==} + /@esbuild/android-x64/0.17.11: + resolution: {integrity: sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -2366,8 +2372,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64/0.17.10: - resolution: {integrity: sha512-3HaGIowI+nMZlopqyW6+jxYr01KvNaLB5znXfbyyjuo4lE0VZfvFGcguIJapQeQMS4cX/NEispwOekJt3gr5Dg==} + /@esbuild/darwin-arm64/0.17.11: + resolution: {integrity: sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -2384,8 +2390,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64/0.17.10: - resolution: {integrity: sha512-J4MJzGchuCRG5n+B4EHpAMoJmBeAE1L3wGYDIN5oWNqX0tEr7VKOzw0ymSwpoeSpdCa030lagGUfnfhS7OvzrQ==} + /@esbuild/darwin-x64/0.17.11: + resolution: {integrity: sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -2402,8 +2408,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64/0.17.10: - resolution: {integrity: sha512-ZkX40Z7qCbugeK4U5/gbzna/UQkM9d9LNV+Fro8r7HA7sRof5Rwxc46SsqeMvB5ZaR0b1/ITQ/8Y1NmV2F0fXQ==} + /@esbuild/freebsd-arm64/0.17.11: + resolution: {integrity: sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -2420,8 +2426,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64/0.17.10: - resolution: {integrity: sha512-0m0YX1IWSLG9hWh7tZa3kdAugFbZFFx9XrvfpaCMMvrswSTvUZypp0NFKriUurHpBA3xsHVE9Qb/0u2Bbi/otg==} + /@esbuild/freebsd-x64/0.17.11: + resolution: {integrity: sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -2438,8 +2444,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm/0.17.10: - resolution: {integrity: sha512-whRdrrl0X+9D6o5f0sTZtDM9s86Xt4wk1bf7ltx6iQqrIIOH+sre1yjpcCdrVXntQPCNw/G+XqsD4HuxeS+2QA==} + /@esbuild/linux-arm/0.17.11: + resolution: {integrity: sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -2456,8 +2462,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64/0.17.10: - resolution: {integrity: sha512-g1EZJR1/c+MmCgVwpdZdKi4QAJ8DCLP5uTgLWSAVd9wlqk9GMscaNMEViG3aE1wS+cNMzXXgdWiW/VX4J+5nTA==} + /@esbuild/linux-arm64/0.17.11: + resolution: {integrity: sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -2474,8 +2480,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32/0.17.10: - resolution: {integrity: sha512-1vKYCjfv/bEwxngHERp7huYfJ4jJzldfxyfaF7hc3216xiDA62xbXJfRlradiMhGZbdNLj2WA1YwYFzs9IWNPw==} + /@esbuild/linux-ia32/0.17.11: + resolution: {integrity: sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -2492,8 +2498,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64/0.17.10: - resolution: {integrity: sha512-mvwAr75q3Fgc/qz3K6sya3gBmJIYZCgcJ0s7XshpoqIAIBszzfXsqhpRrRdVFAyV1G9VUjj7VopL2HnAS8aHFA==} + /@esbuild/linux-loong64/0.17.11: + resolution: {integrity: sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -2510,8 +2516,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el/0.17.10: - resolution: {integrity: sha512-XilKPgM2u1zR1YuvCsFQWl9Fc35BqSqktooumOY2zj7CSn5czJn279j9TE1JEqSqz88izJo7yE4x3LSf7oxHzg==} + /@esbuild/linux-mips64el/0.17.11: + resolution: {integrity: sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -2528,8 +2534,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64/0.17.10: - resolution: {integrity: sha512-kM4Rmh9l670SwjlGkIe7pYWezk8uxKHX4Lnn5jBZYBNlWpKMBCVfpAgAJqp5doLobhzF3l64VZVrmGeZ8+uKmQ==} + /@esbuild/linux-ppc64/0.17.11: + resolution: {integrity: sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -2546,8 +2552,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64/0.17.10: - resolution: {integrity: sha512-r1m9ZMNJBtOvYYGQVXKy+WvWd0BPvSxMsVq8Hp4GzdMBQvfZRvRr5TtX/1RdN6Va8JMVQGpxqde3O+e8+khNJQ==} + /@esbuild/linux-riscv64/0.17.11: + resolution: {integrity: sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -2564,8 +2570,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x/0.17.10: - resolution: {integrity: sha512-LsY7QvOLPw9WRJ+fU5pNB3qrSfA00u32ND5JVDrn/xG5hIQo3kvTxSlWFRP0NJ0+n6HmhPGG0Q4jtQsb6PFoyg==} + /@esbuild/linux-s390x/0.17.11: + resolution: {integrity: sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -2582,8 +2588,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64/0.17.10: - resolution: {integrity: sha512-zJUfJLebCYzBdIz/Z9vqwFjIA7iSlLCFvVi7glMgnu2MK7XYigwsonXshy9wP9S7szF+nmwrelNaP3WGanstEg==} + /@esbuild/linux-x64/0.17.11: + resolution: {integrity: sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -2600,8 +2606,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64/0.17.10: - resolution: {integrity: sha512-lOMkailn4Ok9Vbp/q7uJfgicpDTbZFlXlnKT2DqC8uBijmm5oGtXAJy2ZZVo5hX7IOVXikV9LpCMj2U8cTguWA==} + /@esbuild/netbsd-x64/0.17.11: + resolution: {integrity: sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -2618,8 +2624,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64/0.17.10: - resolution: {integrity: sha512-/VE0Kx6y7eekqZ+ZLU4AjMlB80ov9tEz4H067Y0STwnGOYL8CsNg4J+cCmBznk1tMpxMoUOf0AbWlb1d2Pkbig==} + /@esbuild/openbsd-x64/0.17.11: + resolution: {integrity: sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -2636,8 +2642,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64/0.17.10: - resolution: {integrity: sha512-ERNO0838OUm8HfUjjsEs71cLjLMu/xt6bhOlxcJ0/1MG3hNqCmbWaS+w/8nFLa0DDjbwZQuGKVtCUJliLmbVgg==} + /@esbuild/sunos-x64/0.17.11: + resolution: {integrity: sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -2654,8 +2660,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64/0.17.10: - resolution: {integrity: sha512-fXv+L+Bw2AeK+XJHwDAQ9m3NRlNemG6Z6ijLwJAAVdu4cyoFbBWbEtyZzDeL+rpG2lWI51cXeMt70HA8g2MqIg==} + /@esbuild/win32-arm64/0.17.11: + resolution: {integrity: sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -2672,8 +2678,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32/0.17.10: - resolution: {integrity: sha512-3s+HADrOdCdGOi5lnh5DMQEzgbsFsd4w57L/eLKKjMnN0CN4AIEP0DCP3F3N14xnxh3ruNc32A0Na9zYe1Z/AQ==} + /@esbuild/win32-ia32/0.17.11: + resolution: {integrity: sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -2690,8 +2696,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64/0.17.10: - resolution: {integrity: sha512-oP+zFUjYNaMNmjTwlFtWep85hvwUu19cZklB3QsBOcZSs6y7hmH4LNCJ7075bsqzYaNvZFXJlAVaQ2ApITDXtw==} + /@esbuild/win32-x64/0.17.11: + resolution: {integrity: sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -2803,7 +2809,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 jest-message-util: 29.4.3 jest-util: 29.4.3 @@ -2824,14 +2830,14 @@ packages: '@jest/test-result': 29.4.3 '@jest/transform': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 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.4.3 - jest-config: 29.4.3_@types+node@18.14.2 + jest-config: 29.4.3_@types+node@18.14.6 jest-haste-map: 29.4.3 jest-message-util: 29.4.3 jest-regex-util: 29.4.3 @@ -2858,7 +2864,7 @@ packages: dependencies: '@jest/fake-timers': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 jest-mock: 29.4.3 dev: true @@ -2892,7 +2898,7 @@ packages: dependencies: '@jest/types': 29.4.3 '@sinonjs/fake-timers': 10.0.2 - '@types/node': 18.14.2 + '@types/node': 18.14.6 jest-message-util: 29.4.3 jest-mock: 29.4.3 jest-util: 29.4.3 @@ -2925,7 +2931,7 @@ packages: '@jest/transform': 29.4.3 '@jest/types': 29.4.3 '@jridgewell/trace-mapping': 0.3.15 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -3042,7 +3048,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.2 + '@types/node': 18.14.6 '@types/yargs': 15.0.14 chalk: 4.1.2 dev: true @@ -3054,7 +3060,7 @@ packages: '@jest/schemas': 29.4.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.2 + '@types/node': 18.14.6 '@types/yargs': 17.0.10 chalk: 4.1.2 dev: true @@ -3117,8 +3123,8 @@ packages: tsconfig-paths: 3.14.1 dev: true - /@mui/base/5.0.0-alpha.118_zula6vjvt3wdocc4mwcxqa6nzi: - resolution: {integrity: sha512-GAEpqhnuHjRaAZLdxFNuOf2GDTp9sUawM46oHZV4VnYPFjXJDkIYFWfIQLONb0nga92OiqS5DD/scGzVKCL0Mw==} + /@mui/base/5.0.0-alpha.119_zula6vjvt3wdocc4mwcxqa6nzi: + resolution: {integrity: sha512-XA5zhlYfXi67u613eIF0xRmktkatx6ERy3h+PwrMN5IcWFbgiL1guz8VpdXON+GWb8+G7B8t5oqTFIaCqaSAeA==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || 18 @@ -3128,10 +3134,10 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 '@emotion/is-prop-valid': 1.2.0 '@mui/types': 7.2.3_@types+react@18.0.28 - '@mui/utils': 5.11.9_react@18.2.0 + '@mui/utils': 5.11.11_react@18.2.0 '@popperjs/core': 2.11.6 '@types/react': 18.0.28 clsx: 1.2.1 @@ -3141,12 +3147,12 @@ packages: react-is: 18.2.0 dev: false - /@mui/core-downloads-tracker/5.11.9: - resolution: {integrity: sha512-YGEtucQ/Nl91VZkzYaLad47Cdui51n/hW+OQm4210g4N3/nZzBxmGeKfubEalf+ShKH4aYDS86XTO6q/TpZnjQ==} + /@mui/core-downloads-tracker/5.11.11: + resolution: {integrity: sha512-0YK0K9GfW1ysw9z4ztWAjLW+bktf+nExMyn2+EQe1Ijb0kF2kz1kIOmb4+di0/PsXG70uCuw4DhEIdNd+JQkRA==} dev: false - /@mui/icons-material/5.11.9_ylmaxqh5wvme7ymgn4ys5vax6u: - resolution: {integrity: sha512-SPANMk6K757Q1x48nCwPGdSNb8B71d+2hPMJ0V12VWerpSsbjZtvAPi5FAn13l2O5mwWkvI0Kne+0tCgnNxMNw==} + /@mui/icons-material/5.11.11_h5fh5ntwxtyr677wxvzgewjsma: + resolution: {integrity: sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==} engines: {node: '>=12.0.0'} peerDependencies: '@mui/material': ^5.0.0 @@ -3156,14 +3162,14 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.20.13 - '@mui/material': 5.11.10_xqeqsl5kvjjtyxwyi3jhw3yuli + '@babel/runtime': 7.21.0 + '@mui/material': 5.11.11_xqeqsl5kvjjtyxwyi3jhw3yuli '@types/react': 18.0.28 react: 18.2.0 dev: false - /@mui/material/5.11.10_xqeqsl5kvjjtyxwyi3jhw3yuli: - resolution: {integrity: sha512-hs1WErbiedqlJIZsljgoil908x4NMp8Lfk8di+5c7o809roqKcFTg2+k3z5ucKvs29AXcsdXrDB/kn2K6dGYIw==} + /@mui/material/5.11.11_xqeqsl5kvjjtyxwyi3jhw3yuli: + resolution: {integrity: sha512-sSe0dmKjB1IGOYt32Pcha+cXV3IIrX5L5mFAF9LDRssp/x53bluhgLLbkc8eTiJvueVvo6HAyze6EkFEYLQRXQ==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -3179,14 +3185,14 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 '@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34 '@emotion/styled': 11.10.6_oouaibmszuch5k64ms7uxp2aia - '@mui/base': 5.0.0-alpha.118_zula6vjvt3wdocc4mwcxqa6nzi - '@mui/core-downloads-tracker': 5.11.9 - '@mui/system': 5.11.9_d2lgyfpecxdc2bsiwyag5wf7ti + '@mui/base': 5.0.0-alpha.119_zula6vjvt3wdocc4mwcxqa6nzi + '@mui/core-downloads-tracker': 5.11.11 + '@mui/system': 5.11.11_d2lgyfpecxdc2bsiwyag5wf7ti '@mui/types': 7.2.3_@types+react@18.0.28 - '@mui/utils': 5.11.9_react@18.2.0 + '@mui/utils': 5.11.11_react@18.2.0 '@types/react': 18.0.28 '@types/react-transition-group': 4.4.5 clsx: 1.2.1 @@ -3198,8 +3204,8 @@ packages: react-transition-group: 4.4.5_biqbaboplfbrettd7655fr4n2y dev: false - /@mui/private-theming/5.11.9_pmekkgnqduwlme35zpnqhenc34: - resolution: {integrity: sha512-XMyVIFGomVCmCm92EvYlgq3zrC9K+J6r7IKl/rBJT2/xVYoRY6uM7jeB+Wxh7kXxnW9Dbqsr2yL3cx6wSD1sAg==} + /@mui/private-theming/5.11.11_pmekkgnqduwlme35zpnqhenc34: + resolution: {integrity: sha512-yLgTkjNC1mpye2SOUkc+zQQczUpg8NvQAETvxwXTMzNgJK1pv4htL7IvBM5vmCKG7IHAB3hX26W2u6i7bxwF3A==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || 18 @@ -3208,15 +3214,15 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.20.13 - '@mui/utils': 5.11.9_react@18.2.0 + '@babel/runtime': 7.21.0 + '@mui/utils': 5.11.11_react@18.2.0 '@types/react': 18.0.28 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/styled-engine/5.11.9_xqp3pgpqjlfxxa3zxu4zoc4fba: - resolution: {integrity: sha512-bkh2CjHKOMy98HyOc8wQXEZvhOmDa/bhxMUekFX5IG0/w4f5HJ8R6+K6nakUUYNEgjOWPYzNPrvGB8EcGbhahQ==} + /@mui/styled-engine/5.11.11_xqp3pgpqjlfxxa3zxu4zoc4fba: + resolution: {integrity: sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.4.1 @@ -3228,7 +3234,7 @@ packages: '@emotion/styled': optional: true dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 '@emotion/cache': 11.10.5 '@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34 '@emotion/styled': 11.10.6_oouaibmszuch5k64ms7uxp2aia @@ -3237,8 +3243,8 @@ packages: react: 18.2.0 dev: false - /@mui/styles/5.11.9_pmekkgnqduwlme35zpnqhenc34: - resolution: {integrity: sha512-AWur9Cx5IQ/FWHEpsHU78pNRelGiJLr4jHu+M3PT0rC9w5n7tjMT8oEdaZKPt1bYUiRvkLC/vpNH+E8ov8gXxA==} + /@mui/styles/5.11.11_pmekkgnqduwlme35zpnqhenc34: + resolution: {integrity: sha512-aek4SAkCnEPnpSqP+jEwNtOA4UczeEi5XqYspisce9sD7ld1IEewvASj5R0SvSn5yjkQikLkcb+INYPNkiFwdA==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || 18 @@ -3247,11 +3253,11 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 '@emotion/hash': 0.9.0 - '@mui/private-theming': 5.11.9_pmekkgnqduwlme35zpnqhenc34 + '@mui/private-theming': 5.11.11_pmekkgnqduwlme35zpnqhenc34 '@mui/types': 7.2.3_@types+react@18.0.28 - '@mui/utils': 5.11.9_react@18.2.0 + '@mui/utils': 5.11.11_react@18.2.0 '@types/react': 18.0.28 clsx: 1.2.1 csstype: 3.1.1 @@ -3268,8 +3274,8 @@ packages: react: 18.2.0 dev: false - /@mui/system/5.11.9_d2lgyfpecxdc2bsiwyag5wf7ti: - resolution: {integrity: sha512-h6uarf+l3FO6l75Nf7yO+qDGrIoa1DM9nAMCUFZQsNCDKOInRzcptnm8M1w/Z3gVetfeeGoIGAYuYKbft6KZZA==} + /@mui/system/5.11.11_d2lgyfpecxdc2bsiwyag5wf7ti: + resolution: {integrity: sha512-a9gaOAJBjpzypDfhbGZQ8HzdcxdxsKkFvbp1aAWZhFHBPdehEkARNh7mj851VfEhD/GdffYt85PFKFKdUta5Eg==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -3284,13 +3290,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 '@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34 '@emotion/styled': 11.10.6_oouaibmszuch5k64ms7uxp2aia - '@mui/private-theming': 5.11.9_pmekkgnqduwlme35zpnqhenc34 - '@mui/styled-engine': 5.11.9_xqp3pgpqjlfxxa3zxu4zoc4fba + '@mui/private-theming': 5.11.11_pmekkgnqduwlme35zpnqhenc34 + '@mui/styled-engine': 5.11.11_xqp3pgpqjlfxxa3zxu4zoc4fba '@mui/types': 7.2.3_@types+react@18.0.28 - '@mui/utils': 5.11.9_react@18.2.0 + '@mui/utils': 5.11.11_react@18.2.0 '@types/react': 18.0.28 clsx: 1.2.1 csstype: 3.1.1 @@ -3309,13 +3315,13 @@ packages: '@types/react': 18.0.28 dev: false - /@mui/utils/5.11.9_react@18.2.0: - resolution: {integrity: sha512-eOJaqzcEs4qEwolcvFAmXGpln+uvouvOS9FUX6Wkrte+4I8rZbjODOBDVNlK+V6/ziTfD4iNKC0G+KfOTApbqg==} + /@mui/utils/5.11.11_react@18.2.0: + resolution: {integrity: sha512-neMM5rrEXYQrOrlxUfns/TGgX4viS8K2zb9pbQh11/oUUYFlGI32Tn+PHePQx7n6Fy/0zq6WxdBFC9VpnJ5JrQ==} engines: {node: '>=12.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 || 18 dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 '@types/prop-types': 15.7.5 '@types/react-is': 17.0.3 prop-types: 15.8.1 @@ -3678,7 +3684,7 @@ packages: /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: - '@types/node': 18.14.2 + '@types/node': 18.14.6 dev: true /@types/istanbul-lib-coverage/2.0.4: @@ -3707,7 +3713,7 @@ packages: /@types/jsdom/20.0.0: resolution: {integrity: sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==} dependencies: - '@types/node': 18.14.2 + '@types/node': 18.14.6 '@types/tough-cookie': 4.0.2 parse5: 7.0.0 dev: true @@ -3724,8 +3730,8 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node/18.14.2: - resolution: {integrity: sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==} + /@types/node/18.14.6: + resolution: {integrity: sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==} dev: true /@types/normalize-package-data/2.4.1: @@ -4028,7 +4034,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.20.12 magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 4.1.4_@types+node@18.14.2 + vite: 4.1.4_@types+node@18.14.6 transitivePeerDependencies: - supports-color dev: true @@ -4394,7 +4400,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 cosmiconfig: 7.0.1 resolve: 1.22.1 @@ -4870,7 +4876,7 @@ packages: semver: 7.0.0 dev: true - /cosmiconfig-typescript-loader/4.0.0_pqkpmik5v4qfgv7siq75hqx7eu: + /cosmiconfig-typescript-loader/4.0.0_hxwe5hvsmo6pskygql6awmmuoa: resolution: {integrity: sha512-cVpucSc2Tf+VPwCCR7SZzmQTQkPbkk4O01yXsYqXBIbjE1bhwqSyAgYQkRK1un4i0OPziTleqFhdkmOc4RQ/9g==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -4879,9 +4885,9 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 18.14.2 + '@types/node': 18.14.6 cosmiconfig: 8.0.0 - ts-node: 10.9.0_ellgaeuoqnti3hful2ny2iugba + ts-node: 10.9.0_alpjt73dvgv6kni625hu7f2l4m typescript: 4.9.5 dev: true @@ -4940,7 +4946,7 @@ packages: /css-vendor/2.0.8: resolution: {integrity: sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 is-in-browser: 1.1.3 dev: false @@ -5157,7 +5163,7 @@ packages: /dom-helpers/5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 csstype: 3.1.1 dev: false @@ -5274,7 +5280,7 @@ packages: is-symbol: 1.0.4 dev: true - /esbuild-jest/0.5.0_esbuild@0.17.10: + /esbuild-jest/0.5.0_esbuild@0.17.11: resolution: {integrity: sha512-AMZZCdEpXfNVOIDvURlqYyHwC8qC1/BFjgsrOiSL1eyiIArVtHL8YAC83Shhn16cYYoAWEW17yZn0W/RJKJKHQ==} peerDependencies: esbuild: '>=0.8.50' @@ -5282,7 +5288,7 @@ 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.17.10 + esbuild: 0.17.11 transitivePeerDependencies: - supports-color dev: true @@ -5316,33 +5322,33 @@ packages: '@esbuild/win32-x64': 0.16.17 dev: true - /esbuild/0.17.10: - resolution: {integrity: sha512-n7V3v29IuZy5qgxx25TKJrEm0FHghAlS6QweUcyIgh/U0zYmQcvogWROitrTyZId1mHSkuhhuyEXtI9OXioq7A==} + /esbuild/0.17.11: + resolution: {integrity: sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==} engines: {node: '>=12'} requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.17.10 - '@esbuild/android-arm64': 0.17.10 - '@esbuild/android-x64': 0.17.10 - '@esbuild/darwin-arm64': 0.17.10 - '@esbuild/darwin-x64': 0.17.10 - '@esbuild/freebsd-arm64': 0.17.10 - '@esbuild/freebsd-x64': 0.17.10 - '@esbuild/linux-arm': 0.17.10 - '@esbuild/linux-arm64': 0.17.10 - '@esbuild/linux-ia32': 0.17.10 - '@esbuild/linux-loong64': 0.17.10 - '@esbuild/linux-mips64el': 0.17.10 - '@esbuild/linux-ppc64': 0.17.10 - '@esbuild/linux-riscv64': 0.17.10 - '@esbuild/linux-s390x': 0.17.10 - '@esbuild/linux-x64': 0.17.10 - '@esbuild/netbsd-x64': 0.17.10 - '@esbuild/openbsd-x64': 0.17.10 - '@esbuild/sunos-x64': 0.17.10 - '@esbuild/win32-arm64': 0.17.10 - '@esbuild/win32-ia32': 0.17.10 - '@esbuild/win32-x64': 0.17.10 + '@esbuild/android-arm': 0.17.11 + '@esbuild/android-arm64': 0.17.11 + '@esbuild/android-x64': 0.17.11 + '@esbuild/darwin-arm64': 0.17.11 + '@esbuild/darwin-x64': 0.17.11 + '@esbuild/freebsd-arm64': 0.17.11 + '@esbuild/freebsd-x64': 0.17.11 + '@esbuild/linux-arm': 0.17.11 + '@esbuild/linux-arm64': 0.17.11 + '@esbuild/linux-ia32': 0.17.11 + '@esbuild/linux-loong64': 0.17.11 + '@esbuild/linux-mips64el': 0.17.11 + '@esbuild/linux-ppc64': 0.17.11 + '@esbuild/linux-riscv64': 0.17.11 + '@esbuild/linux-s390x': 0.17.11 + '@esbuild/linux-x64': 0.17.11 + '@esbuild/netbsd-x64': 0.17.11 + '@esbuild/openbsd-x64': 0.17.11 + '@esbuild/sunos-x64': 0.17.11 + '@esbuild/win32-arm64': 0.17.11 + '@esbuild/win32-ia32': 0.17.11 + '@esbuild/win32-x64': 0.17.11 dev: true /escalade/3.1.1: @@ -5375,8 +5381,9 @@ packages: source-map: 0.6.1 dev: true - /eslint-config-prettier/8.6.0_eslint@8.35.0: - resolution: {integrity: sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==} + /eslint-config-prettier/8.7.0_eslint@8.35.0: + resolution: {integrity: sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==} + hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: @@ -5549,7 +5556,7 @@ packages: '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi '@typescript-eslint/experimental-utils': 5.30.6_ycpbpc6yetojsgtrx3mwntkhsu eslint: 8.35.0 - jest: 29.4.3_@types+node@18.14.2 + jest: 29.4.3_@types+node@18.14.6 transitivePeerDependencies: - supports-color - typescript @@ -5580,7 +5587,7 @@ packages: semver: 6.3.0 dev: true - /eslint-plugin-prettier/4.2.1_u2zha4kiojzs42thzpgwygphmy: + /eslint-plugin-prettier/4.2.1_xprnzp4ul2bcpmfe73av4voica: resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} engines: {node: '>=12.0.0'} peerDependencies: @@ -5592,7 +5599,7 @@ packages: optional: true dependencies: eslint: 8.35.0 - eslint-config-prettier: 8.6.0_eslint@8.35.0 + eslint-config-prettier: 8.7.0_eslint@8.35.0 prettier: 2.8.4 prettier-linter-helpers: 1.0.0 dev: true @@ -6820,7 +6827,7 @@ packages: '@jest/expect': 29.4.3 '@jest/test-result': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -6839,7 +6846,7 @@ packages: - supports-color dev: true - /jest-cli/29.4.3_@types+node@18.14.2: + /jest-cli/29.4.3_@types+node@18.14.6: resolution: {integrity: sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -6855,7 +6862,7 @@ packages: exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 - jest-config: 29.4.3_@types+node@18.14.2 + jest-config: 29.4.3_@types+node@18.14.6 jest-util: 29.4.3 jest-validate: 29.4.3 prompts: 2.4.2 @@ -6866,7 +6873,7 @@ packages: - ts-node dev: true - /jest-config/29.4.3_@types+node@18.14.2: + /jest-config/29.4.3_@types+node@18.14.6: resolution: {integrity: sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -6881,7 +6888,7 @@ packages: '@babel/core': 7.20.12 '@jest/test-sequencer': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 babel-jest: 29.4.3_@babel+core@7.20.12 chalk: 4.1.2 ci-info: 3.3.2 @@ -6946,7 +6953,7 @@ packages: '@jest/fake-timers': 29.4.3 '@jest/types': 29.4.3 '@types/jsdom': 20.0.0 - '@types/node': 18.14.2 + '@types/node': 18.14.6 jest-mock: 29.4.3 jest-util: 29.4.3 jsdom: 20.0.0 @@ -6963,7 +6970,7 @@ packages: '@jest/environment': 29.4.3 '@jest/fake-timers': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 jest-mock: 29.4.3 jest-util: 29.4.3 dev: true @@ -6984,7 +6991,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@types/graceful-fs': 4.1.5 - '@types/node': 18.14.2 + '@types/node': 18.14.6 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -7007,7 +7014,7 @@ packages: dependencies: '@jest/types': 29.4.3 '@types/graceful-fs': 4.1.5 - '@types/node': 18.14.2 + '@types/node': 18.14.6 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -7083,7 +7090,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 jest-util: 29.4.3 dev: true @@ -7148,7 +7155,7 @@ packages: '@jest/test-result': 29.4.3 '@jest/transform': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.10 @@ -7179,7 +7186,7 @@ packages: '@jest/test-result': 29.4.3 '@jest/transform': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -7202,7 +7209,7 @@ packages: resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==} engines: {node: '>= 10.14.2'} dependencies: - '@types/node': 18.14.2 + '@types/node': 18.14.6 graceful-fs: 4.2.10 dev: true @@ -7247,7 +7254,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 graceful-fs: 4.2.10 is-ci: 2.0.0 @@ -7259,7 +7266,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -7271,7 +7278,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -7298,7 +7305,7 @@ packages: dependencies: ansi-escapes: 6.0.0 chalk: 5.2.0 - jest: 29.4.3_@types+node@18.14.2 + jest: 29.4.3_@types+node@18.14.6 jest-regex-util: 29.2.0 jest-watcher: 29.4.0 slash: 5.0.0 @@ -7312,7 +7319,7 @@ packages: dependencies: '@jest/test-result': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -7326,7 +7333,7 @@ packages: dependencies: '@jest/test-result': 29.4.3 '@jest/types': 29.4.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -7338,7 +7345,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.14.2 + '@types/node': 18.14.6 merge-stream: 2.0.0 supports-color: 7.2.0 dev: true @@ -7347,13 +7354,13 @@ packages: resolution: {integrity: sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 18.14.2 + '@types/node': 18.14.6 jest-util: 29.4.3 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest/29.4.3_@types+node@18.14.2: + /jest/29.4.3_@types+node@18.14.6: resolution: {integrity: sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -7365,7 +7372,7 @@ packages: '@jest/core': 29.4.3 '@jest/types': 29.4.3 import-local: 3.1.0 - jest-cli: 29.4.3_@types+node@18.14.2 + jest-cli: 29.4.3_@types+node@18.14.6 transitivePeerDependencies: - '@types/node' - supports-color @@ -7485,7 +7492,7 @@ packages: /jss-plugin-camel-case/10.10.0: resolution: {integrity: sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 hyphenate-style-name: 1.0.4 jss: 10.10.0 dev: false @@ -7493,21 +7500,21 @@ packages: /jss-plugin-default-unit/10.10.0: resolution: {integrity: sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 jss: 10.10.0 dev: false /jss-plugin-global/10.10.0: resolution: {integrity: sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 jss: 10.10.0 dev: false /jss-plugin-nested/10.10.0: resolution: {integrity: sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 jss: 10.10.0 tiny-warning: 1.0.3 dev: false @@ -7515,14 +7522,14 @@ packages: /jss-plugin-props-sort/10.10.0: resolution: {integrity: sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 jss: 10.10.0 dev: false /jss-plugin-rule-value-function/10.10.0: resolution: {integrity: sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 jss: 10.10.0 tiny-warning: 1.0.3 dev: false @@ -7530,7 +7537,7 @@ packages: /jss-plugin-vendor-prefixer/10.10.0: resolution: {integrity: sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 css-vendor: 2.0.8 jss: 10.10.0 dev: false @@ -7538,7 +7545,7 @@ packages: /jss/10.10.0: resolution: {integrity: sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 csstype: 3.1.1 is-in-browser: 1.1.3 tiny-warning: 1.0.3 @@ -8451,7 +8458,7 @@ packages: react: '>=16.6.0 || 18' react-dom: '>=16.6.0 || 18' dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -8528,7 +8535,7 @@ packages: /regenerator-transform/0.15.0: resolution: {integrity: sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 dev: true /regex-not/1.0.2: @@ -9208,7 +9215,7 @@ packages: engines: {node: '>=8'} dev: true - /ts-node/10.9.0_ellgaeuoqnti3hful2ny2iugba: + /ts-node/10.9.0_alpjt73dvgv6kni625hu7f2l4m: resolution: {integrity: sha512-bunW18GUyaCSYRev4DPf4SQpom3pWH29wKl0sDk5zE7ze19RImEVhCW7K4v3hHKkUyfWotU08ToE2RS+Y49aug==} peerDependencies: '@swc/core': '>=1.2.50' @@ -9226,7 +9233,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 18.14.2 + '@types/node': 18.14.6 acorn: 8.8.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -9460,7 +9467,7 @@ packages: '@types/eslint': 8.4.5 eslint: 8.35.0 rollup: 2.78.0 - vite: 4.1.4_@types+node@18.14.2 + vite: 4.1.4_@types+node@18.14.6 dev: true /vite-plugin-istanbul/4.0.1_vite@4.1.4: @@ -9472,7 +9479,7 @@ packages: istanbul-lib-instrument: 5.2.0 picocolors: 1.0.0 test-exclude: 6.0.0 - vite: 4.1.4_@types+node@18.14.2 + vite: 4.1.4_@types+node@18.14.6 transitivePeerDependencies: - supports-color dev: true @@ -9484,7 +9491,7 @@ packages: dependencies: '@rollup/pluginutils': 5.0.2 '@svgr/core': 6.5.1 - vite: 4.1.4_@types+node@18.14.2 + vite: 4.1.4_@types+node@18.14.6 transitivePeerDependencies: - rollup - supports-color @@ -9501,7 +9508,7 @@ packages: - typescript dev: true - /vite/4.1.4_@types+node@18.14.2: + /vite/4.1.4_@types+node@18.14.6: resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -9525,7 +9532,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.14.2 + '@types/node': 18.14.6 esbuild: 0.16.17 postcss: 8.4.21 resolve: 1.22.1