diff --git a/Dockerfile.coverage b/Dockerfile.coverage index b92f0b8b1..a14317cd5 100644 --- a/Dockerfile.coverage +++ b/Dockerfile.coverage @@ -15,7 +15,7 @@ RUN yarn global add pnpm && \ # ======================================= # ===== Build image for the backend ===== # ======================================= -FROM golang:1.20.2-alpine AS builder-backend +FROM golang:1.20.3-alpine AS builder-backend WORKDIR /go/src/app diff --git a/Dockerfile.dev b/Dockerfile.dev index a09fd4232..decb022e4 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -13,7 +13,7 @@ RUN yarn install --frozen-lockfile && yarn build # ======================================= # ===== Build image for the backend ===== # ======================================= -FROM golang:1.20.2-alpine AS builder-backend +FROM golang:1.20.3-alpine AS builder-backend WORKDIR /go/src/app diff --git a/api/openapi.yml b/api/openapi.yml index 77071a8ab..95490d1a3 100644 --- a/api/openapi.yml +++ b/api/openapi.yml @@ -111,6 +111,8 @@ paths: application/json: schema: $ref: '#/components/schemas/handlers.StateResponse' + {{- $app := "" }}{{ if .Domain }}{{ $app = printf "https://%s/" .Domain }}{{ else if .BaseURL }}{{ $app = .BaseURL }}{{ else }}{{ $app = "https://app.example.com" }}{{ end }} + {{- $redir := printf "%s?rd=%s&rm=GET" (.BaseURL | default "https://auth.example.com/") (urlquery $app) }} {{- range $name, $config := .EndpointsAuthz }} {{- $uri := printf "/api/authz/%s" $name }} {{- if (eq $name "legacy") }}{{ $uri = "/api/verify" }}{{ end }} @@ -141,7 +143,7 @@ paths: required: false style: simple explode: true - example: "https" + example: 'https' schema: type: string - name: X-Forwarded-Host @@ -150,7 +152,7 @@ paths: required: false style: simple explode: true - example: "example.com" + example: '{{ $.Domain | default "example.com" }}' schema: type: string - name: X-Forwarded-Uri @@ -159,7 +161,7 @@ paths: required: false style: simple explode: true - example: "/path/example" + example: '/path/example' schema: type: string - $ref: '#/components/parameters/forwardedForParam' @@ -188,8 +190,37 @@ paths: schema: type: string example: admin,devs + set-cookie: + description: Sets a new cookie value + schema: + type: string + "302": + description: Found + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "303": + description: See Other + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string "401": description: Unauthorized + headers: + set-cookie: + description: Sets a new cookie value + schema: + type: string security: - authelia_auth: [] {{- end }} @@ -232,6 +263,32 @@ paths: schema: type: string example: admin,devs + set-cookie: + description: Sets a new cookie value + schema: + type: string + "302": + description: Found + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "303": + description: See Other + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "400": + description: Bad Request "401": description: Unauthorized security: @@ -275,6 +332,32 @@ paths: schema: type: string example: admin,devs + set-cookie: + description: Sets a new cookie value + schema: + type: string + "302": + description: Found + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "303": + description: See Other + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string + "400": + description: Bad Request "401": description: Unauthorized security: @@ -316,8 +399,22 @@ paths: schema: type: string example: admin,devs + set-cookie: + description: Sets a new cookie value + schema: + type: string + "400": + description: Bad Request "401": description: Unauthorized + headers: + location: + description: Redirect Location for user authorization + example: '{{ $redir }}' + set-cookie: + description: Sets a new cookie value + schema: + type: string security: - authelia_auth: [] {{- end }} @@ -906,14 +1003,14 @@ paths: type: string format: uuid pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' - example: "713ef767-81bc-4a27-9b83-5fe2e101b2b4" + example: '713ef767-81bc-4a27-9b83-5fe2e101b2b4' - in: query name: scope description: The requested scope. required: true schema: type: string - example: "openid profile groups" + example: 'openid profile groups' - in: query name: response_type description: The OAuth 2.0 response type. @@ -926,7 +1023,7 @@ paths: required: true schema: type: string - example: "app" + example: 'app' - in: query name: redirect_uri description: > @@ -940,7 +1037,7 @@ paths: required: true schema: type: string - example: "https://app.example.com" + example: 'https://app.{{ .Domain | default "example.com" }}' - in: query name: state description: > @@ -950,7 +1047,7 @@ paths: required: false schema: type: string - example: "oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f" + example: 'oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f' - in: query name: response_mode description: > @@ -970,7 +1067,7 @@ paths: required: false schema: type: string - example: "TRMLqchoKGQNcooXvBvUy9PtmLdJGf" + example: 'TRMLqchoKGQNcooXvBvUy9PtmLdJGf' - in: query name: display description: > @@ -1010,7 +1107,7 @@ paths: required: false schema: type: string - example: "en-US" + example: 'en-US' - in: query name: claims_locales description: > @@ -1020,7 +1117,7 @@ paths: required: false schema: type: string - example: "en-US" + example: 'en-US' - in: query name: id_token_hint required: false @@ -1258,7 +1355,7 @@ paths: description: The OAuth 2.0 Access Token issued by this OpenID Connect 1.0 Provider. schema: type: string - example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn" + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' responses: "200": description: OK @@ -1287,7 +1384,7 @@ paths: description: The OAuth 2.0 Access Token issued by this OpenID Connect 1.0 Provider. schema: type: string - example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn" + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' requestBody: content: application/x-www-form-urlencoded: @@ -1297,7 +1394,7 @@ paths: access_token: description: The OAuth 2.0 Access Token issued by this OpenID Connect 1.0 Provider. type: string - example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn" + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' responses: "200": description: OK @@ -1429,7 +1526,7 @@ components: required: true style: simple explode: true - example: "https" + example: 'https' schema: type: string forwardedHostParam: @@ -1439,7 +1536,7 @@ components: required: true style: simple explode: true - example: "example.com" + example: '{{ .Domain | default "example.com" }}' schema: type: string forwardedURIParam: @@ -1449,7 +1546,7 @@ components: required: true style: simple explode: true - example: "/path/example" + example: '/path/example' schema: type: string forwardedForParam: @@ -1459,7 +1556,7 @@ components: required: false style: simple explode: true - example: "192.168.0.55,192.168.0.20" + example: '192.168.0.55,192.168.0.20' schema: type: string autheliaURLParam: @@ -1469,7 +1566,7 @@ components: required: false style: simple explode: true - example: "https://auth.example.com" + example: '{{ .BaseURL | default "https://auth.example.com" }}' schema: type: string authParam: @@ -1493,7 +1590,7 @@ components: properties: uri: type: string - example: https://secure.example.com + example: 'https://secure.{{ .Domain | default "example.com" }}' handlers.checkURIWithinDomainResponseBody: type: object properties: @@ -1610,7 +1707,7 @@ components: example: password targetURL: type: string - example: https://home.example.com + example: 'https://home.{{ .Domain | default "example.com" }}' workflow: type: string example: openid_connect @@ -1618,7 +1715,7 @@ components: type: string format: uuid pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' - example: "3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c" + example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' requestMethod: type: string example: GET @@ -1630,7 +1727,7 @@ components: properties: targetURL: type: string - example: https://redirect.example.com + example: 'https://redirect.{{ .Domain | default "example.com" }}' handlers.logoutResponseBody: type: object properties: @@ -1654,7 +1751,7 @@ components: properties: redirect: type: string - example: https://home.example.com + example: 'https://home.{{ .Domain | default "example.com" }}' {{- if .PasswordReset }} handlers.PasswordResetStep1RequestBody: required: @@ -1679,7 +1776,7 @@ components: properties: targetURL: type: string - example: https://secure.example.com + example: 'https://secure.{{ .Domain | default "example.com" }}' passcode: type: string workflow: @@ -1689,7 +1786,7 @@ components: type: string format: uuid pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' - example: "3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c" + example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' {{- end }} handlers.StateResponse: type: object @@ -1708,7 +1805,7 @@ components: example: 1 default_redirection_url: type: string - example: https://home.example.com + example: 'https://home.{{ .Domain | default "example.com" }}' middlewares.ErrorResponse: type: object properties: @@ -1799,10 +1896,10 @@ components: properties: token: type: string - example: "123456" + example: '123456' targetURL: type: string - example: https://secure.example.com + example: 'https://secure.{{ .Domain | default "example.com" }}' workflow: type: string example: openid_connect @@ -1810,7 +1907,7 @@ components: type: string format: uuid pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' - example: "3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c" + example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' handlers.TOTPKeyResponse: type: object properties: @@ -1825,7 +1922,7 @@ components: example: 5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q otpauth_url: type: string - example: otpauth://totp/auth.example.com:john?algorithm=SHA1&digits=6&issuer=auth.example.com&period=30&secret=5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q + example: 'otpauth://totp/{{ .Domain | default "example.com" }}:john?algorithm=SHA1&digits=6&issuer=auth.{{ .Domain | default "example.com" }}&period=30&secret=5ZH7Y5CTFWOXN7EOLGBMMXADRNQFHVUDZSYKCN5HMFAIRSLAWY3Q' {{- end }} {{- if .Webauthn }} webauthn.PublicKeyCredential: @@ -1903,7 +2000,7 @@ components: type: string format: uuid pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' - example: "3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c" + example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' webauthn.DeviceUpdateRequest: type: object properties: @@ -1960,7 +2057,7 @@ components: properties: appidExclude: type: string - example: https://auth.example.com + example: '{{ .BaseURL }}' webauthn.PublicKeyCredentialRequestOptions: type: object properties: @@ -1984,7 +2081,7 @@ components: example: 60000 rpId: type: string - example: auth.example.com + example: 'auth.{{ .Domain | default "example.com" }}' allowCredentials: type: array items: @@ -1995,7 +2092,7 @@ components: properties: appid: type: string - example: https://auth.example.com + example: '{{ .BaseURL }}' webauthn.Transports: type: object properties: @@ -2150,11 +2247,11 @@ components: client_id: type: string description: The identifier of the client for the user to provide consent for. - example: "app" + example: 'app' client_description: description: The descriptive name of the client for the user to provide consent for. type: string - example: "App Platform" + example: 'App Platform' scopes: description: The list of the requested scopes for the user to provide consent for. type: array @@ -2189,11 +2286,11 @@ components: type: string format: uuid pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' - example: "713ef767-81bc-4a27-9b83-5fe2e101b2b4" + example: '713ef767-81bc-4a27-9b83-5fe2e101b2b4' client_id: description: The identifier of the client for the user to provide consent for. type: string - example: "app" + example: 'app' consent: description: Indicates if the user consented to the consent request. type: boolean @@ -2216,7 +2313,7 @@ components: URL of the OP''s OAuth 2.0 Authorization Endpoint [OpenID.Core]. See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html type: string - example: "{{ .BaseURL }}api/oidc/authorization" + example: '{{ .BaseURL }}api/oidc/authorization' claims_supported: description: > JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply @@ -2268,7 +2365,7 @@ components: URL of the authorization server''s OAuth 2.0 introspection endpoint [RFC7662]. See Also: OAuth 2.0 Token Introspection: https://datatracker.ietf.org/doc/html/rfc7662 type: string - example: "{{ .BaseURL }}api/oidc/introspection" + example: '{{ .BaseURL }}api/oidc/introspection' introspection_endpoint_auth_methods_supported: description: > JSON array containing a list of client authentication methods supported by this introspection endpoint. The @@ -2301,7 +2398,7 @@ components: If Issuer discovery is supported (see Section 2), this value MUST be identical to the issuer value returned by WebFinger. This also MUST be identical to the iss Claim value in ID Tokens issued from this Issuer. type: string - example: "{{ .BaseURL }}" + example: '{{ .BaseURL }}' jwks_uri: description: > URL of the OP's JSON Web Key Set [JWK] document. This contains the signing key(s) the RP uses to validate @@ -2312,7 +2409,7 @@ components: RECOMMENDED, as it is less secure. The JWK x5c parameter MAY be used to provide X.509 representations of keys provided. When used, the bare key values MUST still be present and MUST match those in the certificate. type: string - example: "{{ .BaseURL }}jwks.json" + example: '{{ .BaseURL }}jwks.json' op_policy_uri: description: URL that the OpenID Provider provides to the person registering the Client to read about the OP's @@ -2330,13 +2427,13 @@ components: The URL of the pushed authorization request endpoint at which a client can post an authorization request to exchange for a "request_uri" value usable at the authorization server. type: string - example: "{{ .BaseURL }}api/oidc/par" + example: '{{ .BaseURL }}api/oidc/par' registration_endpoint: description: > URL of the authorization server''s OAuth 2.0 Dynamic Client Registration endpoint [RFC7591]. See Also: OAuth 2.0 Dynamic Client Registration Protocol: https://datatracker.ietf.org/doc/html/rfc7591 type: string - example: "{{ .BaseURL }}api/oidc/registration" + example: '{{ .BaseURL }}api/oidc/registration' require_pushed_authorization_requests: description: > Boolean parameter indicating whether the authorization server accepts authorization request data only via @@ -2365,7 +2462,7 @@ components: URL of the authorization server''s OAuth 2.0 revocation endpoint [RFC7009]. See Also: OAuth 2.0 Token Revocation: https://datatracker.ietf.org/doc/html/rfc7009 type: string - example: "{{ .BaseURL }}api/oidc/revocation" + example: '{{ .BaseURL }}api/oidc/revocation' revocation_endpoint_auth_methods_supported: description: > JSON array containing a list of client authentication methods supported by this revocation endpoint. The @@ -2411,7 +2508,7 @@ components: the OpenID Provider. In particular, if the OpenID Provider does not support Dynamic Client Registration, then information on how to register Clients needs to be provided in this documentation. type: string - example: "https://authelia.com" + example: 'https://authelia.com' subject_types_supported: description: > JSON array containing a list of the Subject Identifier types that this OP supports. @@ -2425,7 +2522,7 @@ components: URL of the OP''s OAuth 2.0 Token Endpoint [OpenID.Core]. This is REQUIRED unless only the Implicit Flow is used. See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html type: string - example: "{{ .BaseURL }}api/oidc/token" + example: '{{ .BaseURL }}api/oidc/token' token_endpoint_auth_methods_supported: description: > JSON array containing a list of Client Authentication methods supported by this Token Endpoint. The options @@ -2483,7 +2580,7 @@ components: URL of the OP''s OAuth 2.0 Authorization Endpoint [OpenID.Core]. See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html type: string - example: "{{ .BaseURL }}api/oidc/authorization" + example: '{{ .BaseURL }}api/oidc/authorization' backchannel_logout_session_supported: description: > Boolean value specifying whether the OP can pass a sid (session ID) Claim in the Logout Token to identify @@ -2625,7 +2722,7 @@ components: URL of the authorization server''s OAuth 2.0 introspection endpoint [RFC7662]. See Also: OAuth 2.0 Token Introspection: https://datatracker.ietf.org/doc/html/rfc7662' type: string - example: "{{ .BaseURL }}api/oidc/introspection" + example: '{{ .BaseURL }}api/oidc/introspection' introspection_endpoint_auth_methods_supported: description: > JSON array containing a list of client authentication methods supported by this introspection endpoint. The @@ -2658,7 +2755,7 @@ components: If Issuer discovery is supported (see Section 2), this value MUST be identical to the issuer value returned by WebFinger. This also MUST be identical to the iss Claim value in ID Tokens issued from this Issuer. type: string - example: "{{ .BaseURL }}" + example: '{{ .BaseURL }}' jwks_uri: description: > URL of the OP's JSON Web Key Set [JWK] document. This contains the signing key(s) the RP uses to validate @@ -2669,7 +2766,7 @@ components: RECOMMENDED, as it is less secure. The JWK x5c parameter MAY be used to provide X.509 representations of keys provided. When used, the bare key values MUST still be present and MUST match those in the certificate. type: string - example: "{{ .BaseURL }}jwks.json" + example: '{{ .BaseURL }}jwks.json' op_policy_uri: description: > URL that the OpenID Provider provides to the person registering the Client to read about the OP's @@ -2687,13 +2784,13 @@ components: The URL of the pushed authorization request endpoint at which a client can post an authorization request to exchange for a "request_uri" value usable at the authorization server. type: string - example: "{{ .BaseURL }}api/oidc/par" + example: '{{ .BaseURL }}api/oidc/par' registration_endpoint: description: > URL of the authorization server''s OAuth 2.0 Dynamic Client Registration endpoint [RFC7591]. See Also: OAuth 2.0 Dynamic Client Registration Protocol: https://datatracker.ietf.org/doc/html/rfc7591 type: string - example: "{{ .BaseURL }}api/oidc/registration" + example: '{{ .BaseURL }}api/oidc/registration' request_object_encryption_alg_values_supported: description: > JSON array containing a list of the JWE encryption algorithms (alg values) supported by the OP for Request @@ -2764,7 +2861,7 @@ components: URL of the authorization server''s OAuth 2.0 revocation endpoint [RFC7009]. See Also: OAuth 2.0 Token Revocation: https://datatracker.ietf.org/doc/html/rfc7009 type: string - example: "{{ .BaseURL }}api/oidc/revocation" + example: '{{ .BaseURL }}api/oidc/revocation' revocation_endpoint_auth_methods_supported: description: > JSON array containing a list of client authentication methods supported by this revocation endpoint. The @@ -2811,7 +2908,7 @@ components: the OpenID Provider. In particular, if the OpenID Provider does not support Dynamic Client Registration, then information on how to register Clients needs to be provided in this documentation. type: string - example: "https://www.authelia.com" + example: 'https://www.authelia.com' subject_types_supported: description: > JSON array containing a list of the Subject Identifier types that this OP supports. Valid types include @@ -2825,7 +2922,7 @@ components: URL of the OP''s OAuth 2.0 Token Endpoint [OpenID.Core]. This is REQUIRED unless only the Implicit Flow is used. See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html type: string - example: "{{ .BaseURL }}api/oidc/token" + example: '{{ .BaseURL }}api/oidc/token' token_endpoint_auth_methods_supported: description: > JSON array containing a list of Client Authentication methods supported by this Token Endpoint. The options @@ -2881,7 +2978,7 @@ components: path, and query parameter components. See Also: OpenID.Core: https://openid.net/specs/openid-connect-core-1_0.html type: string - example: "{{ .BaseURL }}api/oidc/userinfo" + example: '{{ .BaseURL }}api/oidc/userinfo' userinfo_signing_alg_values_supported: description: > JSON array containing a list of the JWS [JWS] signing algorithms (alg values) [JWA] supported by the @@ -3008,7 +3105,7 @@ 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. - example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn" + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' type: string token_type_hint: description: > @@ -3024,7 +3121,7 @@ components: enum: - "access_token" - "refresh_token" - example: "access_token" + example: 'access_token' type: string openid.spec.AccessRequest.ClientAuth: oneOf: @@ -3040,7 +3137,7 @@ components: 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]. - example: "my_client" + example: 'my_client' type: string openid.spec.AccessRequest.ClientAuth.Secret: required: @@ -3067,7 +3164,7 @@ components: "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" + example: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' type: string client_assertion_type: description: > @@ -3091,15 +3188,15 @@ components: type: string code: description: The Authorization Code. - example: "authelia_ac_1j2kn3knj12n3kj12n" + example: 'authelia_ac_1j2kn3knj12n3kj12n' type: string code_verifier: description: The Authorization Code Verifier (PKCE). - example: "88a25754f7c0b3b3b88cf6cd4e29e8356b160524fdc1cb329a94471825628fd3" + example: '88a25754f7c0b3b3b88cf6cd4e29e8356b160524fdc1cb329a94471825628fd3' type: string redirect_uri: description: The original Redirect URI used in the Authorization Request. - example: "https://app.example.com/oidc/callback" + example: 'https://app.{{ .Domain | default "example.com" }}/oidc/callback' type: string openid.spec.AccessRequest.DeviceCodeFlow: allOf: @@ -3116,7 +3213,7 @@ components: type: string device_code: description: The Device Authorization Code. - example: "authelia_dc_mn123kjn12kj3123njk" + example: 'authelia_dc_mn123kjn12kj3123njk' type: string openid.spec.AccessRequest.RefreshTokenFlow: allOf: @@ -3133,7 +3230,7 @@ components: type: string refresh_token: description: The Refresh Token. - example: "authelia_rt_1n2j3kihn12kj3n12k" + example: 'authelia_rt_1n2j3kihn12kj3n12k' type: string scope: description: > @@ -3142,7 +3239,7 @@ 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" + example: 'openid profile groups' type: string openid.spec.AccessResponse: type: object @@ -3153,17 +3250,17 @@ components: properties: access_token: description: The access token issued by the authorization server. - example: "authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn" + example: 'authelia_at_cr4i4EtTn2F4k6mX4XzxbsBewkxCGn' type: string id_token: description: The id token issued by the authorization server. - example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + 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. - example: "authelia_rt_kGBoSMbfVGP2RR6Kvujv3Xg7uXV2i" + example: 'authelia_rt_kGBoSMbfVGP2RR6Kvujv3Xg7uXV2i' type: string token_type: description: > @@ -3174,7 +3271,7 @@ components: type. enum: - "bearer" - example: "bearer" + example: 'bearer' type: string expires_in: description: > @@ -3187,12 +3284,12 @@ components: type: integer state: description: Exactly the state value passed in the authorization request if present. - example: "5dVZhNfri5XZS6wadskuzUk4MHYCvEcUgidjMeBjsktAhY7EKB" + 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" + example: 'openid profile groups' type: string openid.spec.AuthorizeRequest: type: object @@ -3204,13 +3301,13 @@ components: properties: scope: description: The requested scope. - example: "openid profile groups" + example: 'openid profile groups' type: string response_type: $ref: '#/components/schemas/openid.spec.ResponseType' client_id: description: The OAuth 2.0 client identifier. - example: "app" + example: 'app' type: string redirect_uri: description: > @@ -3221,14 +3318,14 @@ 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. - example: "https://app.example.com" + example: 'https://app.{{ .Domain | default "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. - example: "oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f" + example: 'oV84Vsy7wyCgRk2h4aZBmXZq4q3g2f' type: string response_mode: $ref: '#/components/schemas/openid.spec.ResponseMode' @@ -3238,7 +3335,7 @@ 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. - example: "TRMLqchoKGQNcooXvBvUy9PtmLdJGf" + example: 'TRMLqchoKGQNcooXvBvUy9PtmLdJGf' type: string display: $ref: '#/components/schemas/openid.spec.DisplayType' @@ -3254,7 +3351,7 @@ components: - "login consent" - "login select_account" - "consent select_account" - example: "consent" + example: 'consent' type: string max_age: description: > @@ -3354,7 +3451,7 @@ components: - "popup" - "touch" - "wap" - example: "page" + example: 'page' type: string openid.spec.ResponseType: description: The OAuth 2.0 / OpenID Connect 1.0 Response Type. @@ -3367,7 +3464,7 @@ components: - "token id_token" - "code id_token token" - "none" - example: "code" + example: 'code' type: string openid.spec.ResponseMode: description: > @@ -3378,7 +3475,7 @@ components: - "query" - "fragment" - "form_post" - example: "query" + example: 'query' type: string openid.spec.GrantType: description: The OAuth 2.0 / OpenID Connect 1.0 Grant Type. @@ -3389,14 +3486,14 @@ components: - "password" - "client_credentials" - "urn:ietf:params:oauth:grant-type:device_code" - example: "authorization_code" + example: 'authorization_code' type: string openid.spec.CodeChallengeMethod: description: The RFC7636 Code Challenge Verifier Method. enum: - "plain" - "S256" - example: "S256" + example: 'S256' type: string openid.spec.ClaimType: description: The representation of claims. @@ -3404,7 +3501,7 @@ components: - "normal" - "aggregated" - "distributed" - example: "normal" + example: 'normal' type: string jose.spec.None: description: The JSON Web Signature Algorithm @@ -3477,7 +3574,7 @@ components: enum: - "sig" - "enc" - example: "sig" + example: 'sig' type: string key_ops: description: > @@ -3579,13 +3676,13 @@ components: The "kty" (key type) parameter identifies the cryptographic algorithm family used with the key. type: string - example: "RSA" + example: 'RSA' enum: - "RSA" alg: description: The JSON Web Signature Algorithm type: string - example: "RS256" + example: 'RS256' enum: - "RS256" - "RS384" @@ -3696,13 +3793,13 @@ components: The "kty" (key type) parameter identifies the cryptographic algorithm family used with the key. type: string - example: "EC" + example: 'EC' enum: - "EC" alg: description: The JSON Web Signature Algorithm type: string - example: "ES256" + example: 'ES256' enum: - "ES256" - "ES384" @@ -3726,7 +3823,7 @@ components: The curve parameter identifies the cryptographic curve used with the key. Curve values from [DSS] used by this specification. type: string - example: "P-521" + example: 'P-521' enum: - "P-256" - "P-384" @@ -3766,7 +3863,7 @@ components: The "kty" (key type) parameter identifies the cryptographic algorithm family used with the key. type: string - example: "oct" + example: 'oct' enum: - "oct" k: diff --git a/docs/content/en/integration/prologue/get-started.md b/docs/content/en/integration/prologue/get-started.md index e9202ef8d..78e9d754a 100644 --- a/docs/content/en/integration/prologue/get-started.md +++ b/docs/content/en/integration/prologue/get-started.md @@ -17,6 +17,12 @@ obviously choose a different path if you are so inclined. ## Prerequisites +The most important prerequisite that users understand that there is no single way to deploy software similar to +Authelia. We provide as much information as possible for users to configure the critical parts usually in the most +common scenarios however those using more advanced architectures are likely going to have to adapt. We can generally +help with answering less specific questions about this and it may be possible if provided adequate information more +specific questions may be answered. + ### Forwarded Authentication Forwarded Authentication is a simple per-request authorization flow that checks the metadata of a request and a session diff --git a/docs/content/en/integration/proxies/caddy.md b/docs/content/en/integration/proxies/caddy.md index 20b5b104f..51b00303c 100644 --- a/docs/content/en/integration/proxies/caddy.md +++ b/docs/content/en/integration/proxies/caddy.md @@ -63,6 +63,47 @@ to the trusted proxy list in [Caddy]: * 192.168.0.0/16 * fc00::/7 +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + +## Implementation + +[Caddy] utilizes the [ForwardAuth](../../reference/guides/proxy-authorization.md#forwardauth) Authz implementation. The +associated [Metadata](../../reference/guides/proxy-authorization.md#forwardauth-metadata) should be considered required. + +The examples below assume you are using the default +[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the +following minimal configuration: + +```yaml +server: + endpoints: + authz: + forward-auth: + implementation: ForwardAuth +``` + ## Configuration Below you will find commented examples of the following configuration: diff --git a/docs/content/en/integration/proxies/envoy.md b/docs/content/en/integration/proxies/envoy.md index 76ce52e12..0d45b7cea 100644 --- a/docs/content/en/integration/proxies/envoy.md +++ b/docs/content/en/integration/proxies/envoy.md @@ -37,6 +37,47 @@ how you can configure multiple IP ranges. You should customize this example to f You should only include the specific IP address ranges of the trusted proxies within your architecture and should not trust entire subnets unless that subnet only has trusted proxies and no other services.* +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + +## Implementation + +[Envoy] utilizes the [ExtAuthz](../../reference/guides/proxy-authorization.md#extauthz) Authz implementation. The +associated [Metadata](../../reference/guides/proxy-authorization.md#extauthz-metadata) should be considered required. + +The examples below assume you are using the default +[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the +following minimal configuration: + +```yaml +server: + endpoints: + authz: + ext-authz: + implementation: ExtAuthz +``` + ## Configuration Below you will find commented examples of the following configuration: diff --git a/docs/content/en/integration/proxies/haproxy.md b/docs/content/en/integration/proxies/haproxy.md index 353ebb9db..12b9f98a1 100644 --- a/docs/content/en/integration/proxies/haproxy.md +++ b/docs/content/en/integration/proxies/haproxy.md @@ -66,22 +66,61 @@ the following networks to the trusted proxy list in [HAProxy]: * 192.168.0.0/16 * fc00::/7 +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + +## Implementation + +[HAProxy] utilizes the [ForwardAuth](../../reference/guides/proxy-authorization.md#forwardauth) Authz implementation. The +associated [Metadata](../../reference/guides/proxy-authorization.md#forwardauth-metadata) should be considered required. + +The examples below assume you are using the default +[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the +following minimal configuration: + +```yaml +server: + endpoints: + authz: + forward-auth: + implementation: ForwardAuth +``` + ## Configuration Below you will find commented examples of the following configuration: * Authelia Portal -* Protected Endpoint (Nextcloud) -* Protected Endpoint with `Authorization` header for basic authentication (Heimdall) +* Protected Endpoints (Nextcloud) With this configuration you can protect your virtual hosts with Authelia, by following the steps below: -1. Add host(s) to the `protected-frontends` or `protected-frontends-basic` ACLs to support protection with Authelia. -You can separate each subdomain with a `|` in the regex, for example: +1. Add host(s) to the `protected-frontends` ACLs to support protection with Authelia. You can separate each subdomain + with a `|` in the regex, for example: ```text acl protected-frontends hdr(host) -m reg -i ^(?i)(jenkins|nextcloud|phpmyadmin)\.example\.com - acl protected-frontends-basic hdr(host) -m reg -i ^(?i)(heimdall)\.example\.com ``` 2. Add host ACL(s) in the form of `host-service`, this will be utilised to route to the correct @@ -166,46 +205,24 @@ frontend fe_http option forwardfor # Host ACLs - acl protected-frontends hdr(host) -m reg -i ^(?i)(nextcloud)\.example\.com - acl protected-frontends-basic hdr(host) -m reg -i ^(?i)(heimdall)\.example\.com - acl host-authelia hdr(host) -i auth.example.com - acl host-nextcloud hdr(host) -i nextcloud.example.com - acl host-heimdall hdr(host) -i heimdall.example.com - - # This is required if utilising basic auth with /api/verify?auth=basic - http-request set-var(txn.host) hdr(Host) + acl protected-frontends hdr(Host) -m reg -i ^(?i)(nextcloud|heimdall)\.example\.com + acl host-authelia hdr(Host) -i auth.example.com + acl host-nextcloud hdr(Host) -i nextcloud.example.com + acl host-heimdall hdr(Host) -i heimdall.example.com http-request set-var(req.scheme) str(https) if { ssl_fc } http-request set-var(req.scheme) str(http) if !{ ssl_fc } http-request set-var(req.questionmark) str(?) if { query -m found } - # These are optional if you wish to use the Methods rule in the access_control section. - #http-request set-var(req.method) str(CONNECT) if { method CONNECT } - #http-request set-var(req.method) str(GET) if { method GET } - #http-request set-var(req.method) str(HEAD) if { method HEAD } - #http-request set-var(req.method) str(OPTIONS) if { method OPTIONS } - #http-request set-var(req.method) str(POST) if { method POST } - #http-request set-var(req.method) str(TRACE) if { method TRACE } - #http-request set-var(req.method) str(PUT) if { method PUT } - #http-request set-var(req.method) str(PATCH) if { method PATCH } - #http-request set-var(req.method) str(DELETE) if { method DELETE } - #http-request set-header X-Forwarded-Method %[var(req.method)] - - # Required headers - http-request set-header X-Real-IP %[src] - http-request set-header X-Original-Method %[var(req.method)] - http-request set-header X-Original-URL %[var(req.scheme)]://%[req.hdr(Host)]%[path]%[var(req.questionmark)]%[query] + # Required Headers + http-request set-header X-Forwarded-Method %[method] + http-request set-header X-Forwarded-Proto %[var(req.scheme)] + http-request set-header X-Forwarded-Host %[req.hdr(Host)] + http-request set-header X-Forwarded-URI %[path]%[var(req.questionmark)]%[query] # Protect endpoints with haproxy-auth-request and Authelia - http-request lua.auth-request be_authelia /api/authz/auth-request if protected-frontends - # Force `Authorization` header via query arg to /api/verify - http-request lua.auth-request be_authelia /api/verify?auth=basic if protected-frontends-basic - - # Redirect protected-frontends to Authelia if not authenticated - http-request redirect location https://auth.example.com/?rd=%[var(req.scheme)]://%[base]%[var(req.questionmark)]%[query] if protected-frontends !{ var(txn.auth_response_successful) -m bool } - # Send 401 and pass `WWW-Authenticate` header on protected-frontend-basic if not pre-authenticated - http-request set-var(txn.auth) var(req.auth_response_header.www_authenticate) if protected-frontends-basic !{ var(txn.auth_response_successful) -m bool } - http-response deny deny_status 401 hdr WWW-Authenticate %[var(txn.auth)] if { var(txn.host) -m reg -i ^(?i)(heimdall)\.example\.com } !{ var(txn.auth_response_successful) -m bool } + http-request lua.auth-intercept be_authelia /api/authz/forward-auth HEAD * authorization,proxy-authorization,remote_user,remote-user,remote-groups,remote-name,remote-email - if protected-frontends + http-request redirect location %[var(txn.auth_response_location)] if protected-frontends !{ var(txn.auth_response_successful) -m bool } # Authelia backend route use_backend be_authelia if host-authelia @@ -218,24 +235,6 @@ backend be_authelia server authelia authelia:9091 backend be_nextcloud - ## Pass the special authorization response headers to the protected application. - acl authorization_exist var(req.auth_response_header.authorization) -m found - acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found - - http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist - http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist - - ## Pass the special metadata response headers to the protected application. - acl remote_user_exist var(req.auth_response_header.remote_user) -m found - acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found - acl remote_name_exist var(req.auth_response_header.remote_name) -m found - acl remote_email_exist var(req.auth_response_header.remote_email) -m found - - http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist - http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist - http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist - http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist - ## Pass the Set-Cookie response headers to the user. acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist @@ -243,24 +242,6 @@ backend be_nextcloud server nextcloud nextcloud:443 ssl verify none backend be_heimdall - ## Pass the special authorization response headers to the protected application. - acl authorization_exist var(req.auth_response_header.authorization) -m found - acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found - - http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist - http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist - - ## Pass the special metadata response headers to the protected application. - acl remote_user_exist var(req.auth_response_header.remote_user) -m found - acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found - acl remote_name_exist var(req.auth_response_header.remote_name) -m found - acl remote_email_exist var(req.auth_response_header.remote_email) -m found - - http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist - http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist - http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist - http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist - ## Pass the Set-Cookie response headers to the user. acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist @@ -287,47 +268,37 @@ defaults frontend fe_http bind *:443 ssl crt /usr/local/etc/haproxy/haproxy.pem - # Host ACLs - acl protected-frontends hdr(host) -m reg -i ^(?i)(nextcloud)\.example\.com - acl protected-frontends-basic hdr(host) -m reg -i ^(?i)(heimdall)\.example\.com - acl host-authelia hdr(host) -i auth.example.com - acl host-nextcloud hdr(host) -i nextcloud.example.com - acl host-heimdall hdr(host) -i heimdall.example.com + ## Trusted Proxies. + http-request del-header X-Forwarded-For - # This is required if utilising basic auth with /api/verify?auth=basic - http-request set-var(txn.host) hdr(Host) + ## Comment the above directive and the two directives below to enable the trusted proxies ACL. + # acl src-trusted_proxies src -f trusted_proxies.src.acl + # http-request del-header X-Forwarded-For if !src-trusted_proxies + + ## Ensure X-Forwarded-For is set for the auth request. + acl hdr-xff_exists req.hdr(X-Forwarded-For) -m found + http-request set-header X-Forwarded-For %[src] if !hdr-xff_exists + option forwardfor + + # Host ACLs + acl protected-frontends hdr(Host) -m reg -i ^(?i)(nextcloud|heimdall)\.example\.com + acl host-authelia hdr(Host) -i auth.example.com + acl host-nextcloud hdr(Host) -i nextcloud.example.com + acl host-heimdall hdr(Host) -i heimdall.example.com http-request set-var(req.scheme) str(https) if { ssl_fc } http-request set-var(req.scheme) str(http) if !{ ssl_fc } http-request set-var(req.questionmark) str(?) if { query -m found } - # These are optional if you wish to use the Methods rule in the access_control section. - #http-request set-var(req.method) str(CONNECT) if { method CONNECT } - #http-request set-var(req.method) str(GET) if { method GET } - #http-request set-var(req.method) str(HEAD) if { method HEAD } - #http-request set-var(req.method) str(OPTIONS) if { method OPTIONS } - #http-request set-var(req.method) str(POST) if { method POST } - #http-request set-var(req.method) str(TRACE) if { method TRACE } - #http-request set-var(req.method) str(PUT) if { method PUT } - #http-request set-var(req.method) str(PATCH) if { method PATCH } - #http-request set-var(req.method) str(DELETE) if { method DELETE } - #http-request set-header X-Forwarded-Method %[var(req.method)] - - # Required headers - http-request set-header X-Real-IP %[src] - http-request set-header X-Original-Method %[var(req.method)] - http-request set-header X-Original-URL %[var(req.scheme)]://%[req.hdr(Host)]%[path]%[var(req.questionmark)]%[query] + # Required Headers + http-request set-header X-Forwarded-Method %[method] + http-request set-header X-Forwarded-Proto %[var(req.scheme)] + http-request set-header X-Forwarded-Host %[req.hdr(Host)] + http-request set-header X-Forwarded-URI %[path]%[var(req.questionmark)]%[query] # Protect endpoints with haproxy-auth-request and Authelia - http-request lua.auth-request be_authelia_proxy /api/authz/auth-request if protected-frontends - # Force `Authorization` header via query arg to /api/verify - http-request lua.auth-request be_authelia_proxy /api/verify?auth=basic if protected-frontends-basic - - # Redirect protected-frontends to Authelia if not authenticated - http-request redirect location https://auth.example.com/?rd=%[var(req.scheme)]://%[base]%[var(req.questionmark)]%[query] if protected-frontends !{ var(txn.auth_response_successful) -m bool } - # Send 401 and pass `WWW-Authenticate` header on protected-frontend-basic if not pre-authenticated - http-request set-var(txn.auth) var(req.auth_response_header.www_authenticate) if protected-frontends-basic !{ var(txn.auth_response_successful) -m bool } - http-response deny deny_status 401 hdr WWW-Authenticate %[var(txn.auth)] if { var(txn.host) -m reg -i ^(?i)(heimdall)\.example\.com } !{ var(txn.auth_response_successful) -m bool } + http-request lua.auth-intercept be_authelia_proxy /api/authz/forward-auth HEAD * authorization,proxy-authorization,remote_user,remote-user,remote-groups,remote-name,remote-email - if protected-frontends + http-request redirect location %[var(txn.auth_response_location)] if protected-frontends !{ var(txn.auth_response_successful) -m bool } # Authelia backend route use_backend be_authelia if host-authelia @@ -349,24 +320,6 @@ listen authelia_proxy server authelia authelia:9091 ssl verify none backend be_nextcloud - ## Pass the special authorization response headers to the protected application. - acl authorization_exist var(req.auth_response_header.authorization) -m found - acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found - - http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist - http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist - - ## Pass the special metadata response headers to the protected application. - acl remote_user_exist var(req.auth_response_header.remote_user) -m found - acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found - acl remote_name_exist var(req.auth_response_header.remote_name) -m found - acl remote_email_exist var(req.auth_response_header.remote_email) -m found - - http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist - http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist - http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist - http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist - ## Pass the Set-Cookie response headers to the user. acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist @@ -374,24 +327,6 @@ backend be_nextcloud server nextcloud nextcloud:443 ssl verify none backend be_heimdall - ## Pass the special authorization response headers to the protected application. - acl authorization_exist var(req.auth_response_header.authorization) -m found - acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found - - http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist - http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist - - ## Pass the special metadata response headers to the protected application. - acl remote_user_exist var(req.auth_response_header.remote_user) -m found - acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found - acl remote_name_exist var(req.auth_response_header.remote_name) -m found - acl remote_email_exist var(req.auth_response_header.remote_email) -m found - - http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist - http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist - http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist - http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist - ## Pass the Set-Cookie response headers to the user. acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist diff --git a/docs/content/en/integration/proxies/nginx-proxy-manager/index.md b/docs/content/en/integration/proxies/nginx-proxy-manager/index.md index e6d52270d..b260455dd 100644 --- a/docs/content/en/integration/proxies/nginx-proxy-manager/index.md +++ b/docs/content/en/integration/proxies/nginx-proxy-manager/index.md @@ -40,6 +40,30 @@ To configure trusted proxies for [NGINX Proxy Manager] see the [NGINX] section o [Trusted Proxies](../nginx.md#trusted-proxies). Adapting this to [NGINX Proxy Manager] is beyond the scope of this documentation. +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + ## Docker Compose The following docker compose example has various applications suitable for setting up an example environment. diff --git a/docs/content/en/integration/proxies/nginx.md b/docs/content/en/integration/proxies/nginx.md index b53de1df4..4c9e9f95a 100644 --- a/docs/content/en/integration/proxies/nginx.md +++ b/docs/content/en/integration/proxies/nginx.md @@ -34,8 +34,8 @@ You need the following to run __Authelia__ with [NGINX]: * [NGINX] must be built with the `http_auth_request` module which is relatively common * [NGINX] must be built with the `http_realip` module which is relatively common -* [NGINX] must be built with the `http_set_misc` module or the `nginx-mod-http-set-misc` package if you want to preserve - more than one query parameter when redirected to the portal due to a limitation in [NGINX] +* [NGINX] must be built with the `http_set_misc` module or the `nginx-mod-http-set-misc` package if you want to use the + legacy method and preserve more than one query parameter when redirected to the portal due to a limitation in [NGINX] ## Trusted Proxies @@ -52,6 +52,47 @@ configured in the `proxy.conf` file. Each `set_realip_from` directive adds a tru proxies list. Any request that comes from a source IP not in one of the configured ranges results in the header being replaced with the source IP of the client. +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + +## Implementation + +[NGINX] utilizes the [AuthRequest](../../reference/guides/proxy-authorization.md#authrequest) Authz implementation. The +associated [Metadata](../../reference/guides/proxy-authorization.md#authrequest-metadata) should be considered required. + +The examples below assume you are using the default +[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the +following minimal configuration: + +```yaml +server: + endpoints: + authz: + auth-request: + implementation: AuthRequest +``` + ## Docker Compose The following docker compose example has various applications suitable for setting up an example environment. @@ -425,14 +466,6 @@ and is paired with [authelia-location.conf](#authelia-locationconf).* ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. auth_request /internal/authelia/authz; -## Set the $target_url variable based on the original request. - -## Comment this line if you're using nginx without the http_set_misc module. -set_escape_uri $target_url $scheme://$http_host$request_uri; - -## Uncomment this line if you're using NGINX without the http_set_misc module. -# set $target_url $scheme://$http_host$request_uri; - ## Save the upstream authorization response headers from Authelia to variables. auth_request_set $authorization $upstream_http_authorization; auth_request_set $proxy_authorization $upstream_http_proxy_authorization; @@ -457,8 +490,23 @@ proxy_set_header Remote-Name $name; auth_request_set $cookie $upstream_http_set_cookie; add_header Set-Cookie $cookie; -## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal. -error_page 401 =302 https://auth.example.com/?rd=$target_url; +## Configure the redirection when the authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method' +## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url +## value to determine the redirection URL here. It's much simpler and compatible with the mutli-cookie domain easily. + +## Modern Method: Set the $redirection_url to the Location header of the response to the Authz endpoint. +auth_request_set $redirection_url $upstream_http_location; + +## Modern Method: When there is a 401 response code from the authz endpoint redirect to the $redirection_url. +error_page 401 =302 $redirection_url; + +## Legacy Method: Set $target_url to the original requested URL. +## This requires http_set_misc module, replace 'set_escape_uri' with 'set' if you don't have this module. +# set_escape_uri $target_url $scheme://$http_host$request_uri; + +## Legacy Method: When there is a 401 response code from the authz endpoint redirect to the portal with the 'rd' +## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL. +# error_page 401 =302 https://auth.example.com/?rd=$target_url; ``` {{< /details >}} @@ -528,12 +576,6 @@ endpoint. It's recommended to use [authelia-authrequest.conf](#authelia-authrequ ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. 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; - -## Uncomment this line if you're using NGINX without the http_set_misc module. -# set $target_url $scheme://$http_host$request_uri; - ## Save the upstream response headers from Authelia to variables. auth_request_set $user $upstream_http_remote_user; auth_request_set $groups $upstream_http_remote_groups; @@ -580,6 +622,9 @@ location /internal/authelia/authz/detect { return 401; } + ## IMPORTANT: The below URL `https://auth.example.com/` MUST be replaced with the externally accessible URL of the + ## Authelia Portal/Site. + ## ## The original request didn't target /force-basic, redirect to the pretty login page ## This is what `error_page 401 =302 https://auth.example.com/?rd=$target_url;` did. return 302 https://auth.example.com/$is_args$args; diff --git a/docs/content/en/integration/proxies/skipper.md b/docs/content/en/integration/proxies/skipper.md index fa56e3c5a..97292d7a7 100644 --- a/docs/content/en/integration/proxies/skipper.md +++ b/docs/content/en/integration/proxies/skipper.md @@ -44,6 +44,30 @@ how you can configure multiple IP ranges. You should customize this example to f You should only include the specific IP address ranges of the trusted proxies within your architecture and should not trust entire subnets unless that subnet only has trusted proxies and no other services.* +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + ## Potential Support for [Skipper] should be possible via [Skipper]'s diff --git a/docs/content/en/integration/proxies/swag.md b/docs/content/en/integration/proxies/swag.md index 242b3b6e1..e43d458e0 100644 --- a/docs/content/en/integration/proxies/swag.md +++ b/docs/content/en/integration/proxies/swag.md @@ -55,13 +55,13 @@ possible that due to web standards this will never change. 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 +* 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) -- Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/open-id-connect.md) in +* 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 -- Using the [SWAG] default configurations are more difficult to support as our specific familiarity is with our own +* Using the [SWAG] default configurations are more difficult to support as our specific familiarity is with our own example snippets #### Option 1: Adjusting the Default Configuration @@ -112,6 +112,30 @@ Especially if you have never read it before.* To configure trusted proxies for [SWAG] see the [NGINX] section on [Trusted Proxies](nginx.md#trusted-proxies). Adapting this to [SWAG] is beyond the scope of this documentation. +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + ## Docker Compose The following docker compose example has various applications suitable for setting up an example environment. diff --git a/docs/content/en/integration/proxies/traefik.md b/docs/content/en/integration/proxies/traefik.md index 0d1ce5303..d5553e95d 100644 --- a/docs/content/en/integration/proxies/traefik.md +++ b/docs/content/en/integration/proxies/traefik.md @@ -61,6 +61,23 @@ networks to the trusted proxy list in [Traefik]: See the [Entry Points](https://doc.traefik.io/traefik/routing/entrypoints) documentation for more information. +## Implementation + +[Traefik] utilizes the [ForwardAuth](../../reference/guides/proxy-authorization.md#forwardauth) Authz implementation. The +associated [Metadata](../../reference/guides/proxy-authorization.md#forwardauth-metadata) should be considered required. + +The examples below assume you are using the default +[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the +following minimal configuration: + +```yaml +server: + endpoints: + authz: + forward-auth: + implementation: ForwardAuth +``` + ## Configuration Below you will find commented examples of the following docker deployment: @@ -76,6 +93,30 @@ The below configuration looks to provide examples of running [Traefik] 2.x with Please ensure that you also setup the respective [ACME configuration](https://docs.traefik.io/https/acme/) for your [Traefik] setup as this is not covered in the example below. +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + ### Docker Compose This is an example configuration using [docker compose] labels: diff --git a/docs/content/en/integration/proxies/traefikv1.md b/docs/content/en/integration/proxies/traefikv1.md index 41bd52201..0519782ca 100644 --- a/docs/content/en/integration/proxies/traefikv1.md +++ b/docs/content/en/integration/proxies/traefikv1.md @@ -50,6 +50,47 @@ networks to the trusted proxy list in [Traefik]: * 192.168.0.0/16 * fc00::/7 +## Assumptions and Adaptation + +This guide makes a few assumptions. These assumptions may require adaptation in more advanced and complex scenarios. We +can not reasonably have examples for every advanced configuration option that exists. The +following are the assumptions we make: + +* Deployment Scenario: + * Single Host + * Authelia is deployed as a Container with the container name `authelia` on port `9091` + * Proxy is deployed as a Container on a network shared with Authelia +* The above assumption means that AUthelia should be accesible to the proxy on `http://authelia:9091` and as such: + * You will have to adapt all instances of the above URL to be `https://` if Authelia configuration has a TLS key and + certificate defined + * You will have to adapt all instances of `authelia` in the URL if: + * you're using a different container name + * you deployed the proxy to a different location + * You will have to adapt all instances of `9091` in the URL if: + * you have adjusted the default port in the configuration + * You will have to adapt the entire URL if: + * Authelia is on a different host to the proxy +* All services are part of the `example.com` domain: + * This domain and the subdomains will have to be adapted in all examples to match your specific domains unless you're + just testing or you want ot use that specific domain + +## Implementation + +[Traefik] utilizes the [ForwardAuth](../../reference/guides/proxy-authorization.md#forwardauth) Authz implementation. The +associated [Metadata](../../reference/guides/proxy-authorization.md#forwardauth-metadata) should be considered required. + +The examples below assume you are using the default +[Authz Endpoints Configuration](../../configuration/miscellaneous/server-endpoints-authz.md) or one similar to the +following minimal configuration: + +```yaml +server: + endpoints: + authz: + forward-auth: + implementation: ForwardAuth +``` + ## Configuration Below you will find commented examples of the following docker deployment: diff --git a/docs/content/en/reference/guides/proxy-authorization.md b/docs/content/en/reference/guides/proxy-authorization.md index 175655eb4..8a7a72f4c 100644 --- a/docs/content/en/reference/guides/proxy-authorization.md +++ b/docs/content/en/reference/guides/proxy-authorization.md @@ -64,7 +64,7 @@ completely unset. ### ForwardAuth This is the implementation which supports [Traefik] via the [ForwardAuth Middleware], [Caddy] via the -[forward_auth directive], and [Skipper] via the [webhook auth filter]. +[forward_auth directive], [HAProxy] via the [auth-request lua plugin], and [Skipper] via the [webhook auth filter]. #### ForwardAuth Metadata @@ -87,7 +87,7 @@ This is the implementation which supports [Traefik] via the [ForwardAuth Middlew ### ExtAuthz -This is the implementation which supports [Envoy] via the [ExtAuthz Extension Filter]. +This is the implementation which supports [Envoy] via the [HTTP ExtAuthz Filter]. #### ExtAuthz Metadata @@ -110,26 +110,31 @@ This is the implementation which supports [Envoy] via the [ExtAuthz Extension Fi ### AuthRequest -This is the implementation which supports [NGINX] via the [auth_request HTTP module] and [HAProxy] via the -[auth-request lua plugin]. +This is the implementation which supports [NGINX] via the [auth_request HTTP module], and can technically support +[HAProxy] via the [auth-request lua plugin]. -| Metadata | Source | Key | -|:------------:|:--------:|:-------------------:| -| Method | [Header] | `X-Original-Method` | -| Scheme | [Header] | `X-Original-URL` | -| Hostname | [Header] | `X-Original-URL` | -| Path | [Header] | `X-Original-URL` | -| IP | [Header] | [X-Forwarded-For] | -| Authelia URL | _N/A_ | _N/A_ | +#### AuthRequest Metadata -_**Note:** This endpoint does not support automatic redirection. This is because there is no support on NGINX's side to -achieve this with `ngx_http_auth_request_module` and the redirection must be performed within the NGINX configuration._ +| Metadata | Source | Key | +|:------------:|:----------------------------:|:-------------------:| +| Method | [Header] | `X-Original-Method` | +| Scheme | [Header] | `X-Original-URL` | +| Hostname | [Header] | `X-Original-URL` | +| Path | [Header] | `X-Original-URL` | +| IP | [Header] | [X-Forwarded-For] | +| Authelia URL | Session Cookie Configuration | `authelia_url` | + +_**Note:** This endpoint does not support automatic redirection. This is because there is no support on [NGINX]'s side +to achieve this with `ngx_http_auth_request_module` and the redirection must be performed within the [NGINX] +configuration. However we return the appropriate URL to redirect users to with the `Location` header which +simplifies this process especially for multi-cookie domain deployments._ #### AuthRequest Metadata Alternatives -| Metadata | Alternative Type | Source | Key | -|:--------:|:----------------:|:----------:|:---------:| -| IP | Fallback | TCP Packet | Source IP | +| Metadata | Alternative Type | Source | Key | +|:------------:|:----------------:|:--------------:|:--------------:| +| IP | Fallback | TCP Packet | Source IP | +| Authelia URL | Override | Query Argument | `authelia_url` | ### Legacy @@ -213,7 +218,7 @@ or the header is malformed it will respond with the [WWW-Authenticate] header. [Skipper]: https://opensource.zalando.com/skipper/ [HAProxy]: http://www.haproxy.org/ -[ExtAuthz Extension Filter]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto#envoy-v3-api-msg-extensions-filters-http-ext-authz-v3-extauthz +[HTTP ExtAuthz Filter]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto#envoy-v3-api-msg-extensions-filters-http-ext-authz-v3-extauthz [auth_request HTTP module]: https://nginx.org/en/docs/http/ngx_http_auth_request_module.html [auth-request lua plugin]: https://github.com/TimWolla/haproxy-auth-request [ForwardAuth Middleware]: https://doc.traefik.io/traefik/middlewares/http/forwardauth/ diff --git a/docs/content/en/reference/guides/templating.md b/docs/content/en/reference/guides/templating.md index e0d3b7d44..31ab3a96c 100644 --- a/docs/content/en/reference/guides/templating.md +++ b/docs/content/en/reference/guides/templating.md @@ -81,6 +81,8 @@ The following functions which mimic the behaviour of helm exist in most templati - indent - nindent - uuidv4 +- urlquery +- urlunquery (opposite of urlquery) See the [Helm Documentation](https://helm.sh/docs/chart_template_guide/function_list/) for more information. Please note that only the functions listed above are supported and the functions don't necessarily behave exactly the same. diff --git a/examples/compose/lite/docker-compose.yml b/examples/compose/lite/docker-compose.yml index 6c1e4c150..774b961d1 100644 --- a/examples/compose/lite/docker-compose.yml +++ b/examples/compose/lite/docker-compose.yml @@ -45,7 +45,7 @@ services: - TZ=Australia/Melbourne traefik: - image: traefik:v2.9.9 + image: traefik:v2.9.10 container_name: traefik volumes: - ./traefik:/etc/traefik diff --git a/examples/compose/local/docker-compose.yml b/examples/compose/local/docker-compose.yml index bcab9e395..86e2410d3 100644 --- a/examples/compose/local/docker-compose.yml +++ b/examples/compose/local/docker-compose.yml @@ -32,7 +32,7 @@ services: - TZ=Australia/Melbourne traefik: - image: traefik:v2.9.9 + image: traefik:v2.9.10 container_name: traefik volumes: - ./traefik:/etc/traefik diff --git a/go.mod b/go.mod index e3d8f6ae0..8894d2ce0 100644 --- a/go.mod +++ b/go.mod @@ -32,23 +32,23 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/ory/fosite v0.44.0 - github.com/ory/herodot v0.9.13 - github.com/ory/x v0.0.545 - github.com/otiai10/copy v1.9.0 + github.com/ory/herodot v0.10.0 + github.com/ory/x v0.0.549 + github.com/otiai10/copy v1.10.0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 github.com/prometheus/client_golang v1.14.0 github.com/sirupsen/logrus v1.9.0 - github.com/spf13/cobra v1.6.1 + github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.2 github.com/trustelem/zxcvbn v1.0.1 github.com/valyala/fasthttp v1.45.0 github.com/wneessen/go-mail v0.3.9 - golang.org/x/net v0.8.0 + golang.org/x/net v0.9.0 golang.org/x/sync v0.1.0 - golang.org/x/term v0.6.0 - golang.org/x/text v0.8.0 + golang.org/x/term v0.7.0 + golang.org/x/text v0.9.0 gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -74,7 +74,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-webauthn/revoke v0.1.9 // indirect github.com/golang/glog v1.0.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-tpm v0.3.3 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -102,11 +102,11 @@ require ( github.com/prometheus/procfs v0.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect - github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.14.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/test-go/testify v1.1.4 // indirect github.com/tinylib/msgp v1.1.8 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect @@ -115,14 +115,14 @@ require ( github.com/ysmood/gson v0.7.3 // indirect github.com/ysmood/leakless v0.8.0 // indirect golang.org/x/crypto 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.6.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/tools v0.7.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 - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index f87c8f4ff..505b49658 100644 --- a/go.sum +++ b/go.sum @@ -50,7 +50,6 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= @@ -73,11 +72,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -98,8 +92,6 @@ 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.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g= github.com/deckarep/golang-set/v2 v2.3.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= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -121,8 +113,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 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= @@ -133,7 +123,6 @@ github.com/fasthttp/session/v2 v2.4.17/go.mod h1:+pr8HLEQp6h9X70KLBY/Y4NrdJR2ts7 github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= @@ -205,8 +194,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -219,7 +209,6 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw= @@ -243,21 +232,18 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= @@ -271,7 +257,6 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -292,7 +277,6 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -318,7 +302,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -326,7 +309,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -343,7 +325,6 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -364,34 +345,25 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/ory/fosite v0.44.0 h1:Z3UjyO11/wlIoa3BotOqcTkfm7kUNA8F7dd8mOMfx0o= github.com/ory/fosite v0.44.0/go.mod h1:o/G4kAeNn65l6MCod2+KmFfU6JQBSojS7eXys6lKGzM= -github.com/ory/go-acc v0.2.6/go.mod h1:4Kb/UnPcT8qRAk3IAxta+hvVapdxTLWtrr7bFLlEgpw= github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe h1:rvu4obdvqR0fkSIJ8IfgzKOWwZ5kOT2UNfLq81Qk7rc= github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe/go.mod h1:z4n3u6as84LbV4YmgjHhnwtccQqzf4cZlSk9f1FhygI= github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= -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.545 h1:B2zw7LrQwtdzbaRo0nz4EvDukH7A2UK+IdeYQF2iXBw= -github.com/ory/x v0.0.545/go.mod h1:x0n1bElGPQeONaKO++izk4CIOhiDhan+i1MUygjrlfM= -github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4= -github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4= -github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/ory/herodot v0.10.0 h1:j4wDWezsHtZNTSWyXt0sVeQS3QUDCzpVWJMQx1A5Kmg= +github.com/ory/herodot v0.10.0/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= +github.com/ory/x v0.0.549 h1:/ngQEYmHMEQAsYxK4uasAR9/WALxRLfHiDUPFQrD6/I= +github.com/ory/x v0.0.549/go.mod h1:00UrEq/wEgXxpagcfjn5w2PsJPpfxAVnb94M+eg1bC0= +github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ= +github.com/otiai10/copy v1.10.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -435,7 +407,6 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -450,22 +421,18 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -491,9 +458,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= @@ -528,7 +494,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -537,7 +502,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -547,12 +511,11 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -591,8 +554,8 @@ 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/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/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -630,14 +593,13 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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= @@ -649,8 +611,8 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -680,13 +642,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -697,7 +657,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -716,7 +675,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -725,25 +683,24 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.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= @@ -754,7 +711,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -798,11 +754,10 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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/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/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= 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= @@ -857,13 +812,11 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -872,9 +825,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 h1:GEgb2jF5zxsFJpJfg9RoDDWm7tiwc/DDSTE2BtLUkXU= -google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -889,17 +841,12 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99 h1:qA8rMbz1wQ4DOFfM2ouD29DG9aHWBm6ZOy9BGxiUMmY= -google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -912,17 +859,14 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -932,10 +876,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/configuration/const.go b/internal/configuration/const.go index 7b6321784..cc9b5a6fb 100644 --- a/internal/configuration/const.go +++ b/internal/configuration/const.go @@ -28,7 +28,9 @@ var ( const ( errFmtSecretAlreadyDefined = "secrets: error loading secret into key '%s': it's already defined in other " + "configuration sources" - errFmtSecretIOIssue = "secrets: error loading secret path %s into key '%s': %v" + errFmtSecretOSError = "secrets: error loading secret path %s into key '%s': %w" + errFmtSecretOSPermission = "secrets: error loading secret path %s into key '%s': file permission error occurred: %w" + errFmtSecretOSNotExist = "secrets: error loading secret path %s into key '%s': file does not exist error occurred: %w" errFmtGenerateConfiguration = "error occurred generating configuration: %+v" errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s%s: %w" diff --git a/internal/configuration/deprecation.go b/internal/configuration/deprecation.go index b49711898..d8eae81fb 100644 --- a/internal/configuration/deprecation.go +++ b/internal/configuration/deprecation.go @@ -59,7 +59,7 @@ var deprecations = map[string]Deprecation{ }, "host": { Version: model.SemanticVersion{Major: 4, Minor: 30}, - Key: "logs_file", + Key: "host", NewKey: "server.host", AutoMap: true, MapFunc: nil, diff --git a/internal/configuration/koanf_callbacks.go b/internal/configuration/koanf_callbacks.go index d62984eb1..b7218a639 100644 --- a/internal/configuration/koanf_callbacks.go +++ b/internal/configuration/koanf_callbacks.go @@ -2,6 +2,7 @@ package configuration import ( "fmt" + "os" "strings" "github.com/spf13/pflag" @@ -40,13 +41,25 @@ func koanfEnvironmentSecretsCallback(keyMap map[string]string, validator *schema return "", nil } - v, err := loadSecret(value) - if err != nil { - validator.Push(fmt.Errorf(errFmtSecretIOIssue, value, k, err)) - return k, "" - } + switch v, err := loadSecret(value); err { + case nil: + return k, v + default: + switch { + case os.IsNotExist(err): + validator.Push(fmt.Errorf(errFmtSecretOSNotExist, value, k, err)) - return k, v + return "", nil + case os.IsPermission(err): + validator.Push(fmt.Errorf(errFmtSecretOSPermission, value, k, err)) + + return "", nil + default: + validator.Push(fmt.Errorf(errFmtSecretOSError, value, k, err)) + + return "", nil + } + } } } diff --git a/internal/configuration/koanf_callbacks_test.go b/internal/configuration/koanf_callbacks_test.go index 561038efd..039f18637 100644 --- a/internal/configuration/koanf_callbacks_test.go +++ b/internal/configuration/koanf_callbacks_test.go @@ -117,10 +117,10 @@ func TestKoanfSecretCallbackShouldErrorOnFSError(t *testing.T) { callback := koanfEnvironmentSecretsCallback(keyMap, val) key, value := callback("AUTHELIA_THEME", secret) - assert.Equal(t, "theme", key) - assert.Equal(t, "", value) + assert.Equal(t, "", key) + assert.Equal(t, nil, value) require.Len(t, val.Errors(), 1) assert.Len(t, val.Warnings(), 0) - assert.EqualError(t, val.Errors()[0], fmt.Sprintf(errFmtSecretIOIssue, secret, "theme", fmt.Sprintf("open %s: permission denied", secret))) + assert.EqualError(t, val.Errors()[0], fmt.Sprintf("secrets: error loading secret path %s into key 'theme': file permission error occurred: open %s: permission denied", secret, secret)) } diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go index f60d68651..f91b9629b 100644 --- a/internal/configuration/provider_test.go +++ b/internal/configuration/provider_test.go @@ -22,7 +22,7 @@ func TestShouldErrorSecretNotExist(t *testing.T) { testSetEnv(t, "JWT_SECRET_FILE", filepath.Join(dir, "jwt")) testSetEnv(t, "DUO_API_SECRET_KEY_FILE", filepath.Join(dir, "duo")) testSetEnv(t, "SESSION_SECRET_FILE", filepath.Join(dir, "session")) - testSetEnv(t, "AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE", filepath.Join(dir, "authentication")) + testSetEnv(t, "AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE", dir) testSetEnv(t, "NOTIFIER_SMTP_PASSWORD_FILE", filepath.Join(dir, "notifier")) testSetEnv(t, "SESSION_REDIS_PASSWORD_FILE", filepath.Join(dir, "redis")) testSetEnv(t, "SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE", filepath.Join(dir, "redis-sentinel")) @@ -44,20 +44,21 @@ func TestShouldErrorSecretNotExist(t *testing.T) { sort.Sort(utils.ErrSliceSortAlphabetical(errs)) errFmt := utils.GetExpectedErrTxt("filenotfound") + errFmtDir := utils.GetExpectedErrTxt("isdir") // ignore the errors before this as they are checked by the validator. - assert.EqualError(t, errs[0], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "authentication"), "authentication_backend.ldap.password", fmt.Sprintf(errFmt, filepath.Join(dir, "authentication")))) - assert.EqualError(t, errs[1], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "duo"), "duo_api.secret_key", fmt.Sprintf(errFmt, filepath.Join(dir, "duo")))) - assert.EqualError(t, errs[2], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "jwt"), "jwt_secret", fmt.Sprintf(errFmt, filepath.Join(dir, "jwt")))) - assert.EqualError(t, errs[3], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "mysql"), "storage.mysql.password", fmt.Sprintf(errFmt, filepath.Join(dir, "mysql")))) - assert.EqualError(t, errs[4], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "notifier"), "notifier.smtp.password", fmt.Sprintf(errFmt, filepath.Join(dir, "notifier")))) - assert.EqualError(t, errs[5], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "oidc-hmac"), "identity_providers.oidc.hmac_secret", fmt.Sprintf(errFmt, filepath.Join(dir, "oidc-hmac")))) - assert.EqualError(t, errs[6], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "oidc-key"), "identity_providers.oidc.issuer_private_key", fmt.Sprintf(errFmt, filepath.Join(dir, "oidc-key")))) - assert.EqualError(t, errs[7], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "postgres"), "storage.postgres.password", fmt.Sprintf(errFmt, filepath.Join(dir, "postgres")))) - assert.EqualError(t, errs[8], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "redis"), "session.redis.password", fmt.Sprintf(errFmt, filepath.Join(dir, "redis")))) - assert.EqualError(t, errs[9], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "redis-sentinel"), "session.redis.high_availability.sentinel_password", fmt.Sprintf(errFmt, filepath.Join(dir, "redis-sentinel")))) - assert.EqualError(t, errs[10], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "session"), "session.secret", fmt.Sprintf(errFmt, filepath.Join(dir, "session")))) - assert.EqualError(t, errs[11], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "tls"), "server.tls.key", fmt.Sprintf(errFmt, filepath.Join(dir, "tls")))) + assert.EqualError(t, errs[0], fmt.Sprintf("secrets: error loading secret path %s into key 'authentication_backend.ldap.password': %s", dir, fmt.Sprintf(errFmtDir, dir))) + assert.EqualError(t, errs[1], fmt.Sprintf("secrets: error loading secret path %s into key 'duo_api.secret_key': file does not exist error occurred: %s", filepath.Join(dir, "duo"), fmt.Sprintf(errFmt, filepath.Join(dir, "duo")))) + assert.EqualError(t, errs[2], fmt.Sprintf("secrets: error loading secret path %s into key 'jwt_secret': file does not exist error occurred: %s", filepath.Join(dir, "jwt"), fmt.Sprintf(errFmt, filepath.Join(dir, "jwt")))) + assert.EqualError(t, errs[3], fmt.Sprintf("secrets: error loading secret path %s into key 'storage.mysql.password': file does not exist error occurred: %s", filepath.Join(dir, "mysql"), fmt.Sprintf(errFmt, filepath.Join(dir, "mysql")))) + assert.EqualError(t, errs[4], fmt.Sprintf("secrets: error loading secret path %s into key 'notifier.smtp.password': file does not exist error occurred: %s", filepath.Join(dir, "notifier"), fmt.Sprintf(errFmt, filepath.Join(dir, "notifier")))) + assert.EqualError(t, errs[5], fmt.Sprintf("secrets: error loading secret path %s into key 'identity_providers.oidc.hmac_secret': file does not exist error occurred: %s", filepath.Join(dir, "oidc-hmac"), fmt.Sprintf(errFmt, filepath.Join(dir, "oidc-hmac")))) + assert.EqualError(t, errs[6], fmt.Sprintf("secrets: error loading secret path %s into key 'identity_providers.oidc.issuer_private_key': file does not exist error occurred: %s", filepath.Join(dir, "oidc-key"), fmt.Sprintf(errFmt, filepath.Join(dir, "oidc-key")))) + assert.EqualError(t, errs[7], fmt.Sprintf("secrets: error loading secret path %s into key 'storage.postgres.password': file does not exist error occurred: %s", filepath.Join(dir, "postgres"), fmt.Sprintf(errFmt, filepath.Join(dir, "postgres")))) + assert.EqualError(t, errs[8], fmt.Sprintf("secrets: error loading secret path %s into key 'session.redis.password': file does not exist error occurred: %s", filepath.Join(dir, "redis"), fmt.Sprintf(errFmt, filepath.Join(dir, "redis")))) + assert.EqualError(t, errs[9], fmt.Sprintf("secrets: error loading secret path %s into key 'session.redis.high_availability.sentinel_password': file does not exist error occurred: %s", filepath.Join(dir, "redis-sentinel"), fmt.Sprintf(errFmt, filepath.Join(dir, "redis-sentinel")))) + assert.EqualError(t, errs[10], fmt.Sprintf("secrets: error loading secret path %s into key 'session.secret': file does not exist error occurred: %s", filepath.Join(dir, "session"), fmt.Sprintf(errFmt, filepath.Join(dir, "session")))) + assert.EqualError(t, errs[11], fmt.Sprintf("secrets: error loading secret path %s into key 'server.tls.key': file does not exist error occurred: %s", filepath.Join(dir, "tls"), fmt.Sprintf(errFmt, filepath.Join(dir, "tls")))) } func TestLoadShouldReturnErrWithoutValidator(t *testing.T) { diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 0a91a138f..44ac2622b 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -142,11 +142,12 @@ const ( const ( errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " + "more clients configured" - errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required" - errFmtOIDCInvalidPrivateKeyBitSize = "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with %d bits or more but it only has %d bits" - errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'" - errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w" - errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " + + errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required" + errFmtOIDCInvalidPrivateKeyBitSize = "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with %d bits or more but it only has %d bits" + errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey = "identity_providers: oidc: option 'issuer_private_key' must be a valid RSA private key but the provided data is missing the public key bits" + errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'" + errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w" + errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " + "'public_clients_only' or 'always', but it is configured as '%s'" errFmtOIDCCORSInvalidOrigin = "identity_providers: oidc: cors: option 'allowed_origins' contains an invalid value '%s' as it has a %s: origins must only be scheme, hostname, and an optional port" diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go index 95f7c2707..b9ea7e9b9 100644 --- a/internal/configuration/validator/identity_providers.go +++ b/internal/configuration/validator/identity_providers.go @@ -37,7 +37,9 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV } } - if config.IssuerPrivateKey.Size()*8 < 2048 { + if config.IssuerPrivateKey.PublicKey.N == nil { + val.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey)) + } else if config.IssuerPrivateKey.Size()*8 < 2048 { val.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyBitSize, 2048, config.IssuerPrivateKey.Size()*8)) } } diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go index 00c3d5636..81a354faa 100644 --- a/internal/configuration/validator/identity_providers_test.go +++ b/internal/configuration/validator/identity_providers_test.go @@ -525,6 +525,35 @@ func TestShouldRaiseErrorOnKeySizeTooSmall(t *testing.T) { assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with 2048 bits or more but it only has 1024 bits") } +func TestShouldRaiseErrorOnKeyInvalidPublicKey(t *testing.T) { + validator := schema.NewStructValidator() + config := &schema.IdentityProvidersConfiguration{ + OIDC: &schema.OpenIDConnectConfiguration{ + HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", + IssuerPrivateKey: MustParseRSAPrivateKey(testKey3), + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "good_id", + Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), + Policy: "two_factor", + RedirectURIs: []string{ + "https://google.com/callback", + }, + }, + }, + }, + } + + config.OIDC.IssuerPrivateKey.PublicKey.N = nil + + ValidateIdentityProviders(config, validator) + + assert.Len(t, validator.Warnings(), 0) + require.Len(t, validator.Errors(), 1) + + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' must be a valid RSA private key but the provided data is missing the public key bits") +} + func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing.T) { validator := schema.NewStructValidator() config := &schema.IdentityProvidersConfiguration{ diff --git a/internal/handlers/const.go b/internal/handlers/const.go index 8eb0c7bb6..5e9bc2b25 100644 --- a/internal/handlers/const.go +++ b/internal/handlers/const.go @@ -15,12 +15,22 @@ const ( ActionResetPassword = "ResetPassword" ) +const ( + anonymous = "" +) + var ( headerAuthorization = []byte(fasthttp.HeaderAuthorization) headerWWWAuthenticate = []byte(fasthttp.HeaderWWWAuthenticate) headerProxyAuthorization = []byte(fasthttp.HeaderProxyAuthorization) headerProxyAuthenticate = []byte(fasthttp.HeaderProxyAuthenticate) + + headerSessionUsername = []byte("Session-Username") + headerRemoteUser = []byte("Remote-User") + headerRemoteGroups = []byte("Remote-Groups") + headerRemoteName = []byte("Remote-Name") + headerRemoteEmail = []byte("Remote-Email") ) const ( @@ -31,14 +41,6 @@ var ( headerValueAuthenticateBasic = []byte(`Basic realm="Authorization Required"`) ) -var ( - headerSessionUsername = []byte("Session-Username") - headerRemoteUser = []byte("Remote-User") - headerRemoteGroups = []byte("Remote-Groups") - headerRemoteName = []byte("Remote-Name") - headerRemoteEmail = []byte("Remote-Email") -) - const ( queryArgRD = "rd" queryArgRM = "rm" @@ -88,6 +90,8 @@ const ( ) const ( + logFmtAuthzRedirect = "Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s" + logFmtAuthorizationPrefix = "Authorization Request with id '%s' on client with id '%s' " logFmtErrConsentCantDetermineConsentMode = logFmtAuthorizationPrefix + "could not be processed: error occurred generating consent: client consent mode could not be reliably determined" diff --git a/internal/handlers/handler_authz.go b/internal/handlers/handler_authz.go index 88596552c..f8adb6bba 100644 --- a/internal/handlers/handler_authz.go +++ b/internal/handlers/handler_authz.go @@ -21,9 +21,9 @@ func (authz *Authz) Handler(ctx *middlewares.AutheliaCtx) { ) if object, err = authz.handleGetObject(ctx); err != nil { - ctx.Logger.Errorf("Error getting original request object: %v", err) + ctx.Logger.WithError(err).Error("Error getting Target URL and Request Method") - ctx.ReplyUnauthorized() + ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest) return } @@ -31,23 +31,23 @@ func (authz *Authz) Handler(ctx *middlewares.AutheliaCtx) { if !utils.IsURISecure(object.URL) { ctx.Logger.Errorf("Target URL '%s' has an insecure scheme '%s', only the 'https' and 'wss' schemes are supported so session cookies can be transmitted securely", object.URL.String(), object.URL.Scheme) - ctx.ReplyUnauthorized() + ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest) return } if provider, err = ctx.GetSessionProviderByTargetURL(object.URL); err != nil { - ctx.Logger.WithError(err).Errorf("Target URL '%s' does not appear to be configured as a session domain", object.URL.String()) + ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Target URL does not appear to have a relevant session cookies configuration") - ctx.ReplyUnauthorized() + ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest) return } if autheliaURL, err = authz.getAutheliaURL(ctx, provider); err != nil { - ctx.Logger.WithError(err).Error("Error occurred trying to determine the URL of the portal") + ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Error occurred trying to determine the external Authelia URL for Target URL") - ctx.ReplyUnauthorized() + ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest) return } @@ -104,23 +104,23 @@ func (authz *Authz) Handler(ctx *middlewares.AutheliaCtx) { } func (authz *Authz) getAutheliaURL(ctx *middlewares.AutheliaCtx, provider *session.Session) (autheliaURL *url.URL, err error) { - if authz.handleGetAutheliaURL == nil { - return nil, nil - } - if autheliaURL, err = authz.handleGetAutheliaURL(ctx); err != nil { return nil, err } - if autheliaURL != nil || authz.legacy { + switch { + case authz.implementation == AuthzImplLegacy: return autheliaURL, nil + case autheliaURL != nil: + switch { + case utils.HasURIDomainSuffix(autheliaURL, provider.Config.Domain): + return autheliaURL, nil + default: + return nil, fmt.Errorf("authelia url '%s' is not valid for detected domain '%s' as the url does not have the domain as a suffix", autheliaURL.String(), provider.Config.Domain) + } } if provider.Config.AutheliaURL != nil { - if authz.legacy { - return nil, nil - } - return provider.Config.AutheliaURL, nil } @@ -134,6 +134,10 @@ func (authz *Authz) getRedirectionURL(object *authorization.Object, autheliaURL redirectionURL, _ = url.ParseRequestURI(autheliaURL.String()) + if redirectionURL.Path == "" { + redirectionURL.Path = "/" + } + qry := redirectionURL.Query() qry.Set(queryArgRD, object.URL.String()) @@ -151,10 +155,10 @@ func (authz *Authz) authn(ctx *middlewares.AutheliaCtx, provider *session.Sessio for _, strategy = range authz.strategies { if authn, err = strategy.Get(ctx, provider); err != nil { if strategy.CanHandleUnauthorized() { - return Authn{Type: authn.Type, Level: authentication.NotAuthenticated}, strategy, err + return Authn{Type: authn.Type, Level: authentication.NotAuthenticated, Username: anonymous}, strategy, err } - return Authn{Type: authn.Type, Level: authentication.NotAuthenticated}, nil, err + return Authn{Type: authn.Type, Level: authentication.NotAuthenticated, Username: anonymous}, nil, err } if authn.Level != authentication.NotAuthenticated { diff --git a/internal/handlers/handler_authz_authn.go b/internal/handlers/handler_authz_authn.go index 10188e19e..ef13c9bd6 100644 --- a/internal/handlers/handler_authz_authn.go +++ b/internal/handlers/handler_authz_authn.go @@ -81,13 +81,14 @@ type CookieSessionAuthnStrategy struct { // Get returns the Authn information for this AuthnStrategy. func (s *CookieSessionAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, provider *session.Session) (authn Authn, err error) { - authn = Authn{ - Type: AuthnTypeCookie, - Level: authentication.NotAuthenticated, - } - var userSession session.UserSession + authn = Authn{ + Type: AuthnTypeCookie, + Level: authentication.NotAuthenticated, + Username: anonymous, + } + if userSession, err = provider.GetSession(ctx.RequestCtx); err != nil { return authn, fmt.Errorf("failed to retrieve user session: %w", err) } @@ -108,21 +109,21 @@ func (s *CookieSessionAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, provider if invalid := handleVerifyGETAuthnCookieValidate(ctx, provider, &userSession, s.refreshEnabled, s.refreshInterval); invalid { if err = ctx.DestroySession(); err != nil { - ctx.Logger.Errorf("Unable to destroy user session: %+v", err) + ctx.Logger.WithError(err).Errorf("Unable to destroy user session") } userSession = provider.NewDefaultUserSession() userSession.LastActivity = ctx.Clock.Now().Unix() if err = provider.SaveSession(ctx.RequestCtx, userSession); err != nil { - ctx.Logger.Errorf("Unable to save updated user session: %+v", err) + ctx.Logger.WithError(err).Error("Unable to save updated user session") } return authn, nil } if err = provider.SaveSession(ctx.RequestCtx, userSession); err != nil { - ctx.Logger.Errorf("Unable to save updated user session: %+v", err) + ctx.Logger.WithError(err).Error("Unable to save updated user session") } return Authn{ @@ -164,8 +165,9 @@ func (s *HeaderAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session.Sessi ) authn = Authn{ - Type: s.authn, - Level: authentication.NotAuthenticated, + Type: s.authn, + Level: authentication.NotAuthenticated, + Username: anonymous, } if value = ctx.Request.Header.PeekBytes(s.headerAuthorize); value == nil { @@ -195,7 +197,7 @@ func (s *HeaderAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session.Sessi if details, err = ctx.Providers.UserProvider.GetDetails(username); err != nil { if errors.Is(err, authentication.ErrUserNotFound) { - ctx.Logger.Errorf("Error occurred while attempting to get user details for user '%s': the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login", username) + ctx.Logger.WithField("username", username).Error("Error occurred while attempting to get user details for user: the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login") return authn, err } @@ -237,7 +239,8 @@ func (s *HeaderLegacyAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session ) authn = Authn{ - Level: authentication.NotAuthenticated, + Level: authentication.NotAuthenticated, + Username: anonymous, } if qryValueAuth := ctx.QueryArgs().PeekBytes(qryArgAuth); bytes.Equal(qryValueAuth, qryValueBasic) { @@ -280,7 +283,7 @@ func (s *HeaderLegacyAuthnStrategy) Get(ctx *middlewares.AutheliaCtx, _ *session if details, err = ctx.Providers.UserProvider.GetDetails(username); err != nil { if errors.Is(err, authentication.ErrUserNotFound) { - ctx.Logger.Errorf("Error occurred while attempting to get user details for user '%s': the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login", username) + ctx.Logger.WithField("username", username).Error("Error occurred while attempting to get user details for user: the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login") return authn, err } @@ -309,13 +312,13 @@ func handleVerifyGETAuthnCookieValidate(ctx *middlewares.AutheliaCtx, provider * isAnonymous := userSession.Username == "" if isAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated { - ctx.Logger.Errorf("Session for anonymous user has an authentication level of '%s': this may be a sign of a compromise", userSession.AuthenticationLevel) + ctx.Logger.WithFields(map[string]any{"username": anonymous, "level": userSession.AuthenticationLevel.String()}).Errorf("Session for user has an invalid authentication level: this may be a sign of a compromise") return true } if invalid = handleVerifyGETAuthnCookieValidateInactivity(ctx, provider, userSession, isAnonymous); invalid { - ctx.Logger.Infof("Session for user '%s' not marked as remembereded has exceeded configured session inactivity", userSession.Username) + ctx.Logger.WithField("username", userSession.Username).Info("Session for user not marked as remembered has exceeded configured session inactivity") return true } @@ -325,7 +328,7 @@ func handleVerifyGETAuthnCookieValidate(ctx *middlewares.AutheliaCtx, provider * } if username := ctx.Request.Header.PeekBytes(headerSessionUsername); username != nil && !strings.EqualFold(string(username), userSession.Username) { - ctx.Logger.Warnf("Session for user '%s' does not match the Session-Username header with value '%s' which could be a sign of a cookie hijack", userSession.Username, username) + ctx.Logger.WithField("username", userSession.Username).Warnf("Session for user does not match the Session-Username header with value '%s' which could be a sign of a cookie hijack", username) return true } @@ -342,7 +345,7 @@ func handleVerifyGETAuthnCookieValidateInactivity(ctx *middlewares.AutheliaCtx, return false } - ctx.Logger.Tracef("Inactivity report for user '%s'. Current Time: %d, Last Activity: %d, Maximum Inactivity: %d.", userSession.Username, ctx.Clock.Now().Unix(), userSession.LastActivity, int(provider.Config.Inactivity.Seconds())) + ctx.Logger.WithField("username", userSession.Username).Tracef("Inactivity report for user. Current Time: %d, Last Activity: %d, Maximum Inactivity: %d.", ctx.Clock.Now().Unix(), userSession.LastActivity, int(provider.Config.Inactivity.Seconds())) return time.Unix(userSession.LastActivity, 0).Add(provider.Config.Inactivity).Before(ctx.Clock.Now()) } @@ -352,13 +355,13 @@ func handleVerifyGETAuthnCookieValidateUpdate(ctx *middlewares.AutheliaCtx, user return false } - ctx.Logger.Tracef("Checking if we need check the authentication backend for an updated profile for user '%s'", userSession.Username) + ctx.Logger.WithField("username", userSession.Username).Trace("Checking if we need check the authentication backend for an updated profile for user") if interval != schema.RefreshIntervalAlways && userSession.RefreshTTL.After(ctx.Clock.Now()) { return false } - ctx.Logger.Debugf("Checking the authentication backend for an updated profile for user '%s'", userSession.Username) + ctx.Logger.WithField("username", userSession.Username).Debug("Checking the authentication backend for an updated profile for user") var ( details *authentication.UserDetails @@ -367,12 +370,12 @@ func handleVerifyGETAuthnCookieValidateUpdate(ctx *middlewares.AutheliaCtx, user if details, err = ctx.Providers.UserProvider.GetDetails(userSession.Username); err != nil { if errors.Is(err, authentication.ErrUserNotFound) { - ctx.Logger.Errorf("Error occurred while attempting to update user details for user '%s': the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login", userSession.Username) + ctx.Logger.WithField("username", userSession.Username).Error("Error occurred while attempting to update user details for user: the user was not found indicating they were deleted, disabled, or otherwise no longer authorized to login") return true } - ctx.Logger.Errorf("Error occurred while attempting to update user details for user '%s': %v", userSession.Username, err) + ctx.Logger.WithError(err).WithField("username", userSession.Username).Error("Error occurred while attempting to update user details for user") return false } @@ -389,12 +392,12 @@ func handleVerifyGETAuthnCookieValidateUpdate(ctx *middlewares.AutheliaCtx, user } if !diffEmails && !diffGroups && !diffDisplayName { - ctx.Logger.Tracef("Updated profile not detected for user '%s'", userSession.Username) + ctx.Logger.WithField("username", userSession.Username).Trace("Updated profile not detected for user") return false } - ctx.Logger.Debugf("Updated profile detected for user '%s'", userSession.Username) + ctx.Logger.WithField("username", userSession.Username).Debug("Updated profile detected for user") if ctx.Logger.Level >= logrus.TraceLevel { generateVerifySessionHasUpToDateProfileTraceLogs(ctx, userSession, details) diff --git a/internal/handlers/handler_authz_builder.go b/internal/handlers/handler_authz_builder.go index 98aa39215..dffa91b34 100644 --- a/internal/handlers/handler_authz_builder.go +++ b/internal/handlers/handler_authz_builder.go @@ -1,9 +1,10 @@ package handlers import ( - "fmt" "time" + "github.com/valyala/fasthttp" + "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" ) @@ -22,31 +23,10 @@ func (b *AuthzBuilder) WithStrategies(strategies ...AuthnStrategy) *AuthzBuilder return b } -// WithStrategyCookie adds the Cookie header strategy to the strategies in this builder. -func (b *AuthzBuilder) WithStrategyCookie(refreshInterval time.Duration) *AuthzBuilder { - b.strategies = append(b.strategies, NewCookieSessionAuthnStrategy(refreshInterval)) - - return b -} - -// WithStrategyAuthorization adds the Authorization header strategy to the strategies in this builder. -func (b *AuthzBuilder) WithStrategyAuthorization() *AuthzBuilder { - b.strategies = append(b.strategies, NewHeaderAuthorizationAuthnStrategy()) - - return b -} - -// WithStrategyProxyAuthorization adds the Proxy-Authorization header strategy to the strategies in this builder. -func (b *AuthzBuilder) WithStrategyProxyAuthorization() *AuthzBuilder { - b.strategies = append(b.strategies, NewHeaderProxyAuthorizationAuthnStrategy()) - - return b -} - // WithImplementationLegacy configures this builder to output an Authz which is used with the Legacy // implementation which is a mix of the other implementations and usually works with most proxies. func (b *AuthzBuilder) WithImplementationLegacy() *AuthzBuilder { - b.impl = AuthzImplLegacy + b.implementation = AuthzImplLegacy return b } @@ -54,7 +34,7 @@ func (b *AuthzBuilder) WithImplementationLegacy() *AuthzBuilder { // WithImplementationForwardAuth configures this builder to output an Authz which is used with the ForwardAuth // implementation traditionally used by Traefik, Caddy, and Skipper. func (b *AuthzBuilder) WithImplementationForwardAuth() *AuthzBuilder { - b.impl = AuthzImplForwardAuth + b.implementation = AuthzImplForwardAuth return b } @@ -62,7 +42,7 @@ func (b *AuthzBuilder) WithImplementationForwardAuth() *AuthzBuilder { // WithImplementationAuthRequest configures this builder to output an Authz which is used with the AuthRequest // implementation traditionally used by NGINX. func (b *AuthzBuilder) WithImplementationAuthRequest() *AuthzBuilder { - b.impl = AuthzImplAuthRequest + b.implementation = AuthzImplAuthRequest return b } @@ -70,7 +50,7 @@ func (b *AuthzBuilder) WithImplementationAuthRequest() *AuthzBuilder { // WithImplementationExtAuthz configures this builder to output an Authz which is used with the ExtAuthz // implementation traditionally used by Envoy. func (b *AuthzBuilder) WithImplementationExtAuthz() *AuthzBuilder { - b.impl = AuthzImplExtAuthz + b.implementation = AuthzImplExtAuthz return b } @@ -95,12 +75,6 @@ func (b *AuthzBuilder) WithConfig(config *schema.Configuration) *AuthzBuilder { b.config = AuthzConfig{ RefreshInterval: refreshInterval, - Domains: []AuthzDomain{ - { - Name: fmt.Sprintf(".%s", config.Session.Domain), - PortalURL: nil, - }, - }, } return b @@ -140,24 +114,19 @@ func (b *AuthzBuilder) WithEndpointConfig(config schema.ServerAuthzEndpoint) *Au return b } -// WithAuthzConfig allows configuring the Authz config by providing a AuthzConfig directly. Recommended this is only -// used in testing and WithConfig is used instead. -func (b *AuthzBuilder) WithAuthzConfig(config AuthzConfig) *AuthzBuilder { - b.config = config - - return b -} - // Build returns a new Authz from the currently configured options in this builder. func (b *AuthzBuilder) Build() (authz *Authz) { authz = &Authz{ config: b.config, strategies: b.strategies, handleAuthorized: handleAuthzAuthorizedStandard, + implementation: b.implementation, } + authz.config.StatusCodeBadRequest = fasthttp.StatusBadRequest + if len(authz.strategies) == 0 { - switch b.impl { + switch b.implementation { case AuthzImplLegacy: authz.strategies = []AuthnStrategy{NewHeaderLegacyAuthnStrategy(), NewCookieSessionAuthnStrategy(b.config.RefreshInterval)} case AuthzImplAuthRequest: @@ -167,9 +136,9 @@ func (b *AuthzBuilder) Build() (authz *Authz) { } } - switch b.impl { + switch b.implementation { case AuthzImplLegacy: - authz.legacy = true + authz.config.StatusCodeBadRequest = fasthttp.StatusUnauthorized authz.handleGetObject = handleAuthzGetObjectLegacy authz.handleUnauthorized = handleAuthzUnauthorizedLegacy authz.handleGetAutheliaURL = handleAuthzPortalURLLegacy @@ -180,6 +149,7 @@ func (b *AuthzBuilder) Build() (authz *Authz) { case AuthzImplAuthRequest: authz.handleGetObject = handleAuthzGetObjectAuthRequest authz.handleUnauthorized = handleAuthzUnauthorizedAuthRequest + authz.handleGetAutheliaURL = handleAuthzPortalURLFromQuery case AuthzImplExtAuthz: authz.handleGetObject = handleAuthzGetObjectExtAuthz authz.handleUnauthorized = handleAuthzUnauthorizedExtAuthz diff --git a/internal/handlers/handler_authz_impl_authrequest.go b/internal/handlers/handler_authz_impl_authrequest.go index 19292201f..11b3e5371 100644 --- a/internal/handlers/handler_authz_impl_authrequest.go +++ b/internal/handlers/handler_authz_impl_authrequest.go @@ -36,7 +36,13 @@ func handleAuthzGetObjectAuthRequest(ctx *middlewares.AutheliaCtx) (object autho return authorization.NewObjectRaw(targetURL, method), nil } -func handleAuthzUnauthorizedAuthRequest(ctx *middlewares.AutheliaCtx, authn *Authn, _ *url.URL) { - ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d", authn.Object.URL.String(), authn.Method, authn.Username, fasthttp.StatusUnauthorized) - ctx.ReplyUnauthorized() +func handleAuthzUnauthorizedAuthRequest(ctx *middlewares.AutheliaCtx, authn *Authn, redirectionURL *url.URL) { + ctx.Logger.Infof(logFmtAuthzRedirect, authn.Object.URL.String(), authn.Method, authn.Username, fasthttp.StatusUnauthorized, redirectionURL) + + switch authn.Object.Method { + case fasthttp.MethodHead: + ctx.SpecialRedirectNoBody(redirectionURL.String(), fasthttp.StatusUnauthorized) + default: + ctx.SpecialRedirect(redirectionURL.String(), fasthttp.StatusUnauthorized) + } } diff --git a/internal/handlers/handler_authz_impl_authrequest_test.go b/internal/handlers/handler_authz_impl_authrequest_test.go index 6f4a1d8b4..f360bf1dc 100644 --- a/internal/handlers/handler_authz_impl_authrequest_test.go +++ b/internal/handlers/handler_authz_impl_authrequest_test.go @@ -13,7 +13,7 @@ import ( "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/mocks" - "github.com/authelia/authelia/v4/internal/session" + "github.com/authelia/authelia/v4/internal/utils" ) func TestRunAuthRequestAuthzSuite(t *testing.T) { @@ -35,26 +35,36 @@ type AuthRequestAuthzSuite struct { func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsDeny() { for _, method := range testRequestMethods { - s.T().Run(fmt.Sprintf("OriginalMethod%s", method), func(t *testing.T) { - for _, targetURI := range []*url.URL{ - s.RequireParseRequestURI("https://one-factor.example.com"), - s.RequireParseRequestURI("https://one-factor.example.com/subpath"), - s.RequireParseRequestURI("https://one-factor.example2.com"), - s.RequireParseRequestURI("https://one-factor.example2.com/subpath"), + s.T().Run(fmt.Sprintf("Method%s", method), func(t *testing.T) { + for _, pairURI := range []urlpair{ + {s.RequireParseRequestURI("https://one-factor.example.com"), s.RequireParseRequestURI("https://auth.example.com/")}, + {s.RequireParseRequestURI("https://one-factor.example.com/subpath"), s.RequireParseRequestURI("https://auth.example.com/")}, + {s.RequireParseRequestURI("https://one-factor.example2.com"), s.RequireParseRequestURI("https://auth.example2.com/")}, + {s.RequireParseRequestURI("https://one-factor.example2.com/subpath"), s.RequireParseRequestURI("https://auth.example2.com/")}, } { - t.Run(targetURI.String(), func(t *testing.T) { + t.Run(pairURI.TargetURI.String(), func(t *testing.T) { + expected := s.RequireParseRequestURI(pairURI.AutheliaURI.String()) + authz := s.Builder().Build() mock := mocks.NewMockAutheliaCtx(t) defer mock.Close() - s.setRequest(mock.Ctx, method, targetURI, true, false) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) + + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) + + s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false) authz.Handler(mock.Ctx) + query := expected.Query() + query.Set(queryArgRD, pairURI.TargetURI.String()) + query.Set(queryArgRM, method) + expected.RawQuery = query.Encode() assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) - assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) + assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))) }) } }) @@ -83,7 +93,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleInvalidMethodCharsDeny() { authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -109,7 +119,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleMissingXOriginalMethodDeny() { authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -128,7 +138,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleMissingXOriginalURLDeny() { authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -150,6 +160,8 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsAllow() { defer mock.Close() + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) + s.setRequest(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) @@ -174,11 +186,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, true, false) @@ -188,8 +196,21 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() { assert.Equal(t, fasthttp.StatusOK, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) } else { - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) - assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) + expected := s.RequireParseRequestURI("https://auth.example.com/") + + query := expected.Query() + query.Set(queryArgRD, targetURI.String()) + query.Set(queryArgRM, method) + expected.RawQuery = query.Encode() + + switch method { + case fasthttp.MethodHead: + assert.Nil(t, mock.Ctx.Response.Body()) + default: + assert.Equal(t, fmt.Sprintf(`%d %s`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusUnauthorized, fasthttp.StatusMessage(fasthttp.StatusUnauthorized)), string(mock.Ctx.Response.Body())) + } + + assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))) } }) } @@ -205,7 +226,7 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() { }{ {"Should401UnauthorizedWithNullByte", []byte{104, 116, 116, 112, 115, 58, 47, 47, 0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, - fasthttp.StatusUnauthorized, + fasthttp.StatusBadRequest, }, {"Should200OkWithoutNullByte", []byte{104, 116, 116, 112, 115, 58, 47, 47, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, @@ -226,6 +247,8 @@ func (s *AuthRequestAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() { mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) + mock.Ctx.Request.Header.Set(testXOriginalMethod, method) mock.Ctx.Request.Header.SetBytesKV([]byte(testXOriginalUrl), tc.uri) @@ -255,17 +278,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsAllow() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestExtAuthz(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -291,17 +310,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsAllowXHR() defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestExtAuthz(mock.Ctx, method, targetURI, x, x) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -323,17 +338,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsWithMethods defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestExtAuthz(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -357,17 +368,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsAllow() defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestForwardAuth(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -393,17 +400,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsAllowXHR defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestForwardAuth(mock.Ctx, method, targetURI, x, x) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -425,17 +428,13 @@ func (s *AuthRequestAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsWithMeth defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestForwardAuth(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } diff --git a/internal/handlers/handler_authz_impl_extauthz.go b/internal/handlers/handler_authz_impl_extauthz.go index ccae832d9..67786a33d 100644 --- a/internal/handlers/handler_authz_impl_extauthz.go +++ b/internal/handlers/handler_authz_impl_extauthz.go @@ -50,6 +50,12 @@ func handleAuthzUnauthorizedExtAuthz(ctx *middlewares.AutheliaCtx, authn *Authn, } } - ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s", authn.Object.String(), authn.Method, authn.Username, statusCode, redirectionURL) - ctx.SpecialRedirect(redirectionURL.String(), statusCode) + ctx.Logger.Infof(logFmtAuthzRedirect, authn.Object.String(), authn.Method, authn.Username, statusCode, redirectionURL) + + switch authn.Object.Method { + case fasthttp.MethodHead: + ctx.SpecialRedirectNoBody(redirectionURL.String(), statusCode) + default: + ctx.SpecialRedirect(redirectionURL.String(), statusCode) + } } diff --git a/internal/handlers/handler_authz_impl_extauthz_test.go b/internal/handlers/handler_authz_impl_extauthz_test.go index 820cb66d1..642df3ffc 100644 --- a/internal/handlers/handler_authz_impl_extauthz_test.go +++ b/internal/handlers/handler_authz_impl_extauthz_test.go @@ -13,7 +13,7 @@ import ( "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/mocks" - "github.com/authelia/authelia/v4/internal/session" + "github.com/authelia/authelia/v4/internal/utils" ) func TestRunExtAuthzAuthzSuite(t *testing.T) { @@ -51,11 +51,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false) @@ -98,11 +94,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsOverrideAutheliaURLDeny() defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.Header.Set("X-Authelia-Url", pairURI.AutheliaURI.String()) s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false) @@ -148,8 +140,10 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsMissingAutheliaURLDeny() authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fmt.Sprintf("%d %s", fasthttp.StatusBadRequest, fasthttp.StatusMessage(fasthttp.StatusBadRequest)), string(mock.Ctx.Response.Body())) assert.Equal(t, "", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))) + assert.Equal(t, "text/plain; charset=utf-8", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderContentType))) }) } }) @@ -176,11 +170,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsXHRDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, pairURI.TargetURI, x, x) @@ -222,17 +212,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleInvalidMethodCharsDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -249,17 +235,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleMissingHostDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, nil, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -281,11 +263,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsAllow() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, true, false) @@ -317,11 +295,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsAllowXHR() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, x, x) @@ -349,11 +323,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, true, false) @@ -365,18 +335,23 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() { } else { expected := s.RequireParseRequestURI("https://auth.example.com/") - switch method { - case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead: - assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) - default: - assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode()) - } - query := expected.Query() query.Set(queryArgRD, targetURI.String()) query.Set(queryArgRM, method) expected.RawQuery = query.Encode() + switch method { + case fasthttp.MethodHead: + assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) + assert.Nil(t, mock.Ctx.Response.Body()) + case fasthttp.MethodGet, fasthttp.MethodOptions: + assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fmt.Sprintf(`%d %s`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusFound, fasthttp.StatusMessage(fasthttp.StatusFound)), string(mock.Ctx.Response.Body())) + default: + assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fmt.Sprintf(`%d %s`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusSeeOther, fasthttp.StatusMessage(fasthttp.StatusSeeOther)), string(mock.Ctx.Response.Body())) + } + assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))) } }) @@ -394,7 +369,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() { }{ {"Should401UnauthorizedWithNullByte", []byte("https"), []byte{0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example", - fasthttp.StatusUnauthorized, + fasthttp.StatusBadRequest, }, {"Should200OkWithoutNullByte", []byte("https"), []byte{110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example", @@ -415,11 +390,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() { mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration) - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.SetHostBytes(tc.host) mock.Ctx.Request.Header.SetMethodBytes([]byte(method)) @@ -458,7 +429,7 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleAuthRequestAllMethodsAllow() { authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -478,17 +449,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleAuthRequestAllMethodsWithMethods defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestAuthRequest(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -512,17 +479,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsAllow() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestForwardAuth(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -548,17 +511,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsAllowXHR() defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestForwardAuth(mock.Ctx, method, targetURI, x, x) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -580,17 +539,13 @@ func (s *ExtAuthzAuthzSuite) TestShouldNotHandleForwardAuthAllMethodsWithMethods defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestForwardAuth(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } diff --git a/internal/handlers/handler_authz_impl_forwardauth.go b/internal/handlers/handler_authz_impl_forwardauth.go index a042c13bb..d385cf971 100644 --- a/internal/handlers/handler_authz_impl_forwardauth.go +++ b/internal/handlers/handler_authz_impl_forwardauth.go @@ -50,6 +50,12 @@ func handleAuthzUnauthorizedForwardAuth(ctx *middlewares.AutheliaCtx, authn *Aut } } - ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s", authn.Object.String(), authn.Method, authn.Username, statusCode, redirectionURL) - ctx.SpecialRedirect(redirectionURL.String(), statusCode) + ctx.Logger.Infof(logFmtAuthzRedirect, authn.Object.String(), authn.Method, authn.Username, statusCode, redirectionURL) + + switch authn.Object.Method { + case fasthttp.MethodHead: + ctx.SpecialRedirectNoBody(redirectionURL.String(), statusCode) + default: + ctx.SpecialRedirect(redirectionURL.String(), statusCode) + } } diff --git a/internal/handlers/handler_authz_impl_forwardauth_test.go b/internal/handlers/handler_authz_impl_forwardauth_test.go index d7ea3baab..de8be0ba5 100644 --- a/internal/handlers/handler_authz_impl_forwardauth_test.go +++ b/internal/handlers/handler_authz_impl_forwardauth_test.go @@ -13,7 +13,7 @@ import ( "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/mocks" - "github.com/authelia/authelia/v4/internal/session" + "github.com/authelia/authelia/v4/internal/utils" ) func TestRunForwardAuthAuthzSuite(t *testing.T) { @@ -51,11 +51,9 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false) @@ -98,11 +96,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsOverrideAutheliaURLDen defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.RequestCtx.QueryArgs().Set("authelia_url", pairURI.AutheliaURI.String()) s.setRequest(mock.Ctx, method, pairURI.TargetURI, true, false) @@ -148,8 +142,10 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsMissingAutheliaURLDeny authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fmt.Sprintf("%d %s", fasthttp.StatusBadRequest, fasthttp.StatusMessage(fasthttp.StatusBadRequest)), string(mock.Ctx.Response.Body())) assert.Equal(t, "", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))) + assert.Equal(t, "text/plain; charset=utf-8", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderContentType))) }) } }) @@ -176,11 +172,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsXHRDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, pairURI.TargetURI, x, x) @@ -220,17 +212,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleInvalidMethodCharsDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -247,11 +235,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleMissingHostDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https") @@ -261,7 +245,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleMissingHostDeny() { authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -283,11 +267,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsAllow() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, true, false) @@ -313,11 +293,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, true, false) @@ -329,18 +305,23 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() { } else { expected := s.RequireParseRequestURI("https://auth.example.com/") - switch method { - case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead: - assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) - default: - assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode()) - } - query := expected.Query() query.Set(queryArgRD, targetURI.String()) query.Set(queryArgRM, method) expected.RawQuery = query.Encode() + switch method { + case fasthttp.MethodHead: + assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) + assert.Nil(t, mock.Ctx.Response.Body()) + case fasthttp.MethodGet, fasthttp.MethodOptions: + assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fmt.Sprintf(`%d %s`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusFound, fasthttp.StatusMessage(fasthttp.StatusFound)), string(mock.Ctx.Response.Body())) + default: + assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fmt.Sprintf(`%d %s`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusSeeOther, fasthttp.StatusMessage(fasthttp.StatusSeeOther)), string(mock.Ctx.Response.Body())) + } + assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))) } }) @@ -365,11 +346,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleAllMethodsAllowXHR() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) s.setRequest(mock.Ctx, method, targetURI, true, true) @@ -392,7 +369,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() { }{ {"Should401UnauthorizedWithNullByte", []byte("https"), []byte{0, 110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example", - fasthttp.StatusUnauthorized, + fasthttp.StatusBadRequest, }, {"Should200OkWithoutNullByte", []byte("https"), []byte{110, 111, 116, 45, 111, 110, 101, 45, 102, 97, 99, 116, 111, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}, "/path-example", @@ -413,11 +390,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() { mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration) - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) mock.Ctx.Request.Header.SetBytesKV([]byte(fasthttp.HeaderXForwardedProto), tc.scheme) @@ -455,7 +428,7 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleAuthRequestAllMethodsAllow() authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -475,17 +448,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleAuthRequestAllMethodsWithMeth defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestAuthRequest(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -509,17 +478,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsAllow() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestExtAuthz(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -545,17 +510,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsAllowXHR() defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestExtAuthz(mock.Ctx, method, targetURI, x, x) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } @@ -577,17 +538,13 @@ func (s *ForwardAuthAuthzSuite) TestShouldNotHandleExtAuthzAllMethodsWithMethods defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) setRequestExtAuthz(mock.Ctx, method, targetURI, true, false) authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) }) } diff --git a/internal/handlers/handler_authz_impl_legacy.go b/internal/handlers/handler_authz_impl_legacy.go index 33af89616..25a851152 100644 --- a/internal/handlers/handler_authz_impl_legacy.go +++ b/internal/handlers/handler_authz_impl_legacy.go @@ -47,7 +47,7 @@ func handleAuthzUnauthorizedLegacy(ctx *middlewares.AutheliaCtx, authn *Authn, r statusCode = fasthttp.StatusUnauthorized default: switch authn.Object.Method { - case fasthttp.MethodGet, fasthttp.MethodOptions, "": + case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead, "": statusCode = fasthttp.StatusFound default: statusCode = fasthttp.StatusSeeOther @@ -55,8 +55,14 @@ func handleAuthzUnauthorizedLegacy(ctx *middlewares.AutheliaCtx, authn *Authn, r } if redirectionURL != nil { - ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d with location redirect to %s", authn.Object.URL.String(), authn.Method, authn.Username, statusCode, redirectionURL.String()) - ctx.SpecialRedirect(redirectionURL.String(), statusCode) + ctx.Logger.Infof(logFmtAuthzRedirect, authn.Object.URL.String(), authn.Method, authn.Username, statusCode, redirectionURL) + + switch authn.Object.Method { + case fasthttp.MethodHead: + ctx.SpecialRedirectNoBody(redirectionURL.String(), statusCode) + default: + ctx.SpecialRedirect(redirectionURL.String(), statusCode) + } } else { ctx.Logger.Infof("Access to %s (method %s) is not authorized to user %s, responding with status code %d", authn.Object.URL.String(), authn.Method, authn.Username, statusCode) ctx.ReplyUnauthorized() diff --git a/internal/handlers/handler_authz_impl_legacy_test.go b/internal/handlers/handler_authz_impl_legacy_test.go index 541920b6d..82d798d84 100644 --- a/internal/handlers/handler_authz_impl_legacy_test.go +++ b/internal/handlers/handler_authz_impl_legacy_test.go @@ -4,6 +4,7 @@ import ( "fmt" "net/url" "regexp" + "strings" "testing" "github.com/golang/mock/gomock" @@ -15,7 +16,7 @@ import ( "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/mocks" - "github.com/authelia/authelia/v4/internal/session" + "github.com/authelia/authelia/v4/internal/utils" ) func TestRunLegacyAuthzSuite(t *testing.T) { @@ -53,11 +54,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String()) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) @@ -69,7 +66,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsDeny() { authz.Handler(mock.Ctx) switch method { - case fasthttp.MethodGet, fasthttp.MethodOptions: + case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead: assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) default: assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode()) @@ -105,11 +102,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsOverrideAutheliaURLDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String()) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) @@ -121,7 +114,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsOverrideAutheliaURLDeny() { authz.Handler(mock.Ctx) switch method { - case fasthttp.MethodGet, fasthttp.MethodOptions: + case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead: assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) default: assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode()) @@ -227,7 +220,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsRDAutheliaURLOneFactorStatu authz.Handler(mock.Ctx) switch method { - case fasthttp.MethodGet, fasthttp.MethodOptions: + case fasthttp.MethodGet, fasthttp.MethodOptions, fasthttp.MethodHead: assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) default: assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode()) @@ -264,11 +257,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsXHRDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, pairURI.AutheliaURI.String()) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) @@ -317,11 +306,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleInvalidMethodCharsDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme) @@ -348,11 +333,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleMissingHostDeny() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https") @@ -384,11 +365,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllow() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme) @@ -406,6 +383,56 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllow() { } } +func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsWithMethodsACL() { + for _, method := range testRequestMethods { + s.T().Run(fmt.Sprintf("Method%s", method), func(t *testing.T) { + for _, methodACL := range testRequestMethods { + targetURI := s.RequireParseRequestURI(fmt.Sprintf("https://bypass-%s.example.com", strings.ToLower(methodACL))) + t.Run(targetURI.String(), func(t *testing.T) { + authz := s.Builder().Build() + + mock := mocks.NewMockAutheliaCtx(t) + + defer mock.Close() + + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) + + s.setRequest(mock.Ctx, method, targetURI, true, false) + mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, "https://auth.example.com") + + authz.Handler(mock.Ctx) + + if method == methodACL { + assert.Equal(t, fasthttp.StatusOK, mock.Ctx.Response.StatusCode()) + assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation)) + } else { + expected := s.RequireParseRequestURI("https://auth.example.com/") + + query := expected.Query() + query.Set(queryArgRD, targetURI.String()) + query.Set(queryArgRM, method) + expected.RawQuery = query.Encode() + + switch method { + case fasthttp.MethodHead: + assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) + assert.Nil(t, mock.Ctx.Response.Body()) + case fasthttp.MethodGet, fasthttp.MethodOptions: + assert.Equal(t, fasthttp.StatusFound, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fmt.Sprintf(`%d %s`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusFound, fasthttp.StatusMessage(fasthttp.StatusFound)), string(mock.Ctx.Response.Body())) + default: + assert.Equal(t, fasthttp.StatusSeeOther, mock.Ctx.Response.StatusCode()) + assert.Equal(t, fmt.Sprintf(`%d %s`, utils.StringHTMLEscape(expected.String()), fasthttp.StatusSeeOther, fasthttp.StatusMessage(fasthttp.StatusSeeOther)), string(mock.Ctx.Response.Body())) + } + + assert.Equal(t, expected.String(), string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))) + } + }) + } + }) + } +} + func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllowXHR() { for _, method := range testRequestMethods { s.T().Run(fmt.Sprintf("Method%s", method), func(t *testing.T) { @@ -422,11 +449,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleAllMethodsAllowXHR() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, targetURI.Scheme) @@ -451,11 +474,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleLegacyBasicAuth() { // TestShouldVeri defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.QueryArgs().Add("auth", "basic") mock.Ctx.Request.Header.Set("Authorization", "Basic am9objpwYXNzd29yZA==") @@ -540,11 +559,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleLegacyBasicAuthFailures() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.QueryArgs().Add("auth", "basic") mock.Ctx.Request.Header.Set("X-Original-URL", "https://one-factor.example.com") @@ -593,11 +608,7 @@ func (s *LegacyAuthzSuite) TestShouldHandleInvalidURLForCVE202132637() { mock.Ctx.Configuration.AccessControl.DefaultPolicy = testBypass mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&mock.Ctx.Configuration) - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) mock.Ctx.Request.Header.Set("X-Forwarded-Method", method) mock.Ctx.Request.Header.SetBytesKV([]byte(fasthttp.HeaderXForwardedProto), tc.scheme) diff --git a/internal/handlers/handler_authz_test.go b/internal/handlers/handler_authz_test.go index 432ee7bbd..bd32a0228 100644 --- a/internal/handlers/handler_authz_test.go +++ b/internal/handlers/handler_authz_test.go @@ -50,9 +50,12 @@ func (s *AuthzSuite) RequireParseRequestURI(rawURL string) *url.URL { return u } -type urlpair struct { - TargetURI *url.URL - AutheliaURI *url.URL +func (s *AuthzSuite) ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock *mocks.MockAutheliaCtx) { + for i, cookie := range mock.Ctx.Configuration.Session.Cookies { + mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) + } + + mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) } func (s *AuthzSuite) Builder() (builder *AuthzBuilder) { @@ -87,11 +90,7 @@ func (s *AuthzSuite) TestShouldNotBeAbleToParseBasicAuth() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://test.example.com") @@ -124,11 +123,7 @@ func (s *AuthzSuite) TestShouldApplyDefaultPolicy() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://test.example.com") @@ -181,11 +176,7 @@ func (s *AuthzSuite) TestShouldDenyObject() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI(tc.value) @@ -193,7 +184,12 @@ func (s *AuthzSuite) TestShouldDenyObject() { authz.Handler(mock.Ctx) - assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + switch s.implementation { + case AuthzImplLegacy: + assert.Equal(t, fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + default: + assert.Equal(t, fasthttp.StatusBadRequest, mock.Ctx.Response.StatusCode()) + } }) } } @@ -209,11 +205,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfBypassDomain() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://bypass.example.com") @@ -250,11 +242,7 @@ func (s *AuthzSuite) TestShouldVerifyFailureToGetDetailsUsingBasicScheme() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://bypass.example.com") @@ -299,11 +287,7 @@ func (s *AuthzSuite) TestShouldNotFailOnMissingEmail() { mock.Clock.Set(time.Now()) - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://bypass.example.com") @@ -340,11 +324,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomain() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -392,11 +372,7 @@ func (s *AuthzSuite) TestShouldHandleAnyCaseSchemeParameter() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -435,11 +411,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfTwoFactorDomain() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://two-factor.example.com") @@ -483,11 +455,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfDenyDomain() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://deny.example.com") @@ -534,11 +502,7 @@ func (s *AuthzSuite) TestShouldApplyPolicyOfOneFactorDomainWithAuthorizationHead defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -584,11 +548,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithoutHeaderNoCookie() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -621,11 +581,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithEmptyAuthorizationHeader() { defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -660,11 +616,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithAuthorizationHeaderInvalidPassword defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -700,11 +652,7 @@ func (s *AuthzSuite) TestShouldHandleAuthzWithIncorrectAuthHeader() { // TestSho defer mock.Close() - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -744,11 +692,7 @@ func (s *AuthzSuite) TestShouldDestroySessionWhenInactiveForTooLong() { mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://two-factor.example.com") @@ -796,11 +740,7 @@ func (s *AuthzSuite) TestShouldNotDestroySessionWhenInactiveForTooLongRememberMe mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://two-factor.example.com") @@ -850,11 +790,7 @@ func (s *AuthzSuite) TestShouldNotDestroySessionWhenNotInactiveForTooLong() { mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://two-factor.example.com") @@ -905,11 +841,7 @@ func (s *AuthzSuite) TestShouldUpdateInactivityTimestampEvenWhenHittingForbidden mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://deny.example.com") @@ -971,11 +903,7 @@ func (s *AuthzSuite) TestShouldNotRefreshUserDetailsFromBackendWhenRefreshDisabl mock.Ctx.Configuration.AuthenticationBackend.RefreshInterval = schema.ProfileRefreshDisabled mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://two-factor.example.com") @@ -1057,11 +985,7 @@ func (s *AuthzSuite) TestShouldDestroySessionWhenUserDoesNotExist() { mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://two-factor.example.com") @@ -1149,11 +1073,7 @@ func (s *AuthzSuite) TestShouldUpdateRemovedUserGroupsFromBackendAndDeny() { mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://admin.example.com") @@ -1239,11 +1159,7 @@ func (s *AuthzSuite) TestShouldUpdateAddedUserGroupsFromBackendAndDeny() { mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://admin.example.com") @@ -1328,11 +1244,7 @@ func (s *AuthzSuite) TestShouldCheckValidSessionUsernameHeaderAndReturn200() { mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -1385,11 +1297,7 @@ func (s *AuthzSuite) TestShouldCheckInvalidSessionUsernameHeaderAndReturn401AndD mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://one-factor.example.com") @@ -1462,11 +1370,7 @@ func (s *AuthzSuite) TestShouldNotRedirectRequestsForBypassACLWhenInactiveForToo mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://bypass.example.com") @@ -1520,7 +1424,7 @@ func (s *AuthzSuite) TestShouldNotRedirectRequestsForBypassACLWhenInactiveForToo } func (s *AuthzSuite) TestShouldFailToParsePortalURL() { - if s.setRequest == nil || s.implementation == AuthzImplAuthRequest { + if s.setRequest == nil { s.T().Skip() } @@ -1538,20 +1442,20 @@ func (s *AuthzSuite) TestShouldFailToParsePortalURL() { mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity - for i, cookie := range mock.Ctx.Configuration.Session.Cookies { - mock.Ctx.Configuration.Session.Cookies[i].AutheliaURL = s.RequireParseRequestURI(fmt.Sprintf("https://auth.%s", cookie.Domain)) - } - - mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + s.ConfigureMockSessionProviderWithAutomaticAutheliaURLs(mock) targetURI := s.RequireParseRequestURI("https://bypass.example.com") s.setRequest(mock.Ctx, fasthttp.MethodGet, targetURI, true, false) + expected := fasthttp.StatusBadRequest + switch s.implementation { case AuthzImplLegacy: + expected = fasthttp.StatusUnauthorized + mock.Ctx.RequestCtx.QueryArgs().Set(queryArgRD, "JKL$#N%KJ#@$N") - case AuthzImplForwardAuth: + case AuthzImplForwardAuth, AuthzImplAuthRequest: mock.Ctx.RequestCtx.QueryArgs().Set("authelia_url", "JKL$#N%KJ#@$N") case AuthzImplExtAuthz: mock.Ctx.Request.Header.Set("X-Authelia-URL", "JKL$#N%KJ#@$N") @@ -1559,7 +1463,10 @@ func (s *AuthzSuite) TestShouldFailToParsePortalURL() { authz.Handler(mock.Ctx) - s.Equal(fasthttp.StatusUnauthorized, mock.Ctx.Response.StatusCode()) + s.Equal(expected, mock.Ctx.Response.StatusCode()) + s.Equal(fmt.Sprintf("%d %s", expected, fasthttp.StatusMessage(expected)), string(mock.Ctx.Response.Body())) + s.Equal("", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderLocation))) + s.Equal("text/plain; charset=utf-8", string(mock.Ctx.Response.Header.Peek(fasthttp.HeaderContentType))) } func setRequestXHRValues(ctx *middlewares.AutheliaCtx, accept, xhr bool) { @@ -1571,3 +1478,8 @@ func setRequestXHRValues(ctx *middlewares.AutheliaCtx, accept, xhr bool) { ctx.Request.Header.Set(fasthttp.HeaderXRequestedWith, "XMLHttpRequest") } } + +type urlpair struct { + TargetURI *url.URL + AutheliaURI *url.URL +} diff --git a/internal/handlers/handler_authz_types.go b/internal/handlers/handler_authz_types.go index 549e7505f..ca2bfa7a1 100644 --- a/internal/handlers/handler_authz_types.go +++ b/internal/handlers/handler_authz_types.go @@ -10,7 +10,8 @@ import ( "github.com/authelia/authelia/v4/internal/session" ) -// Authz is a type which is a effectively is a middlewares.RequestHandler for authorization requests. +// Authz is a type which is a effectively is a middlewares.RequestHandler for authorization requests. This should NOT be +// manually used and developers should instead use NewAuthzBuilder. type Authz struct { config AuthzConfig @@ -23,7 +24,7 @@ type Authz struct { handleAuthorized HandlerAuthzAuthorized handleUnauthorized HandlerAuthzUnauthorized - legacy bool + implementation AuthzImplementation } // HandlerAuthzUnauthorized is a Authz handler func that handles unauthorized responses. @@ -75,20 +76,17 @@ type Authn struct { // AuthzConfig represents the configuration elements of the Authz type. type AuthzConfig struct { RefreshInterval time.Duration - Domains []AuthzDomain -} -// AuthzDomain represents a domain for the AuthzConfig. -type AuthzDomain struct { - Name string - PortalURL *url.URL + // StatusCodeBadRequest is sent for configuration issues prior to performing authorization checks. It's set by the + // builder. + StatusCodeBadRequest int } // AuthzBuilder is a builder pattern for the Authz type. type AuthzBuilder struct { - config AuthzConfig - impl AuthzImplementation - strategies []AuthnStrategy + config AuthzConfig + implementation AuthzImplementation + strategies []AuthnStrategy } // AuthnStrategy is a strategy used for Authz authentication. diff --git a/internal/handlers/handler_authz_util.go b/internal/handlers/handler_authz_util.go index 5fadcd953..44b034ced 100644 --- a/internal/handlers/handler_authz_util.go +++ b/internal/handlers/handler_authz_util.go @@ -1,9 +1,6 @@ package handlers import ( - "fmt" - "strings" - "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/middlewares" @@ -23,7 +20,7 @@ func friendlyMethod(m string) (fm string) { func friendlyUsername(username string) (fusername string) { switch username { case "": - return "" + return anonymous default: return username } @@ -54,39 +51,55 @@ func generateVerifySessionHasUpToDateProfileTraceLogs(ctx *middlewares.AutheliaC emailsAdded, emailsRemoved := utils.StringSlicesDelta(userSession.Emails, details.Emails) nameDelta := userSession.DisplayName != details.DisplayName - var groupsDelta []string - if len(groupsAdded) != 0 { - groupsDelta = append(groupsDelta, fmt.Sprintf("added: %s.", strings.Join(groupsAdded, ", "))) + fields := map[string]any{"username": userSession.Username} + msg := "User session groups are current" + + if len(groupsAdded) != 0 || len(groupsRemoved) != 0 { + if len(groupsAdded) != 0 { + fields["added"] = groupsAdded + } + + if len(groupsRemoved) != 0 { + fields["removed"] = groupsRemoved + } + + msg = "User session groups were updated" } - if len(groupsRemoved) != 0 { - groupsDelta = append(groupsDelta, fmt.Sprintf("removed: %s.", strings.Join(groupsRemoved, ", "))) - } + ctx.Logger.WithFields(fields).Trace(msg) - if len(groupsDelta) != 0 { - ctx.Logger.Tracef("Updated groups detected for %s. %s", userSession.Username, strings.Join(groupsDelta, " ")) + if len(emailsAdded) != 0 || len(emailsRemoved) != 0 { + if len(emailsAdded) != 0 { + fields["added"] = emailsAdded + } else { + delete(fields, "added") + } + + if len(emailsRemoved) != 0 { + fields["removed"] = emailsRemoved + } else { + delete(fields, "removed") + } + + msg = "User session emails were updated" } else { - ctx.Logger.Tracef("No updated groups detected for %s", userSession.Username) + msg = "User session emails are current" + + delete(fields, "added") + delete(fields, "removed") } - var emailsDelta []string - if len(emailsAdded) != 0 { - emailsDelta = append(emailsDelta, fmt.Sprintf("added: %s.", strings.Join(emailsAdded, ", "))) - } - - if len(emailsRemoved) != 0 { - emailsDelta = append(emailsDelta, fmt.Sprintf("removed: %s.", strings.Join(emailsRemoved, ", "))) - } - - if len(emailsDelta) != 0 { - ctx.Logger.Tracef("Updated emails detected for %s. %s", userSession.Username, strings.Join(emailsDelta, " ")) - } else { - ctx.Logger.Tracef("No updated emails detected for %s", userSession.Username) - } + ctx.Logger.WithFields(fields).Trace(msg) if nameDelta { - ctx.Logger.Tracef("Updated display name detected for %s. Added: %s. Removed: %s.", userSession.Username, details.DisplayName, userSession.DisplayName) + ctx.Logger. + WithFields(map[string]any{ + "username": userSession.Username, + "before": userSession.DisplayName, + "after": details.DisplayName, + }). + Trace("User session display name updated") } else { - ctx.Logger.Tracef("No updated display name detected for %s", userSession.Username) + ctx.Logger.Trace("User session display name is current") } } diff --git a/internal/handlers/handler_oidc_userinfo.go b/internal/handlers/handler_oidc_userinfo.go index ddb61cb35..3e1314d72 100644 --- a/internal/handlers/handler_oidc_userinfo.go +++ b/internal/handlers/handler_oidc_userinfo.go @@ -33,7 +33,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req.Context(), fosite.AccessTokenFromRequest(req), fosite.AccessToken, oidcSession); err != nil { rfc := fosite.ErrorToRFC6749Error(err) - ctx.Logger.Errorf("UserInfo Request failed with error: %+v", rfc) + ctx.Logger.Errorf("UserInfo Request failed with error: %s", rfc.WithExposeDebug(true).GetDescription()) if rfc.StatusCode() == http.StatusUnauthorized { rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription())) @@ -47,7 +47,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, clientID := requester.GetClient().GetID() if tokenType != fosite.AccessToken { - ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed with error: bearer authorization failed as the token is not an access_token", requester.GetID(), client.GetID()) + ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed with error: bearer authorization failed as the token is not an access token", requester.GetID(), client.GetID()) errStr := "Only access tokens are allowed in the authorization header." rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errStr)) @@ -57,7 +57,11 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, } if client, err = ctx.Providers.OpenIDConnect.GetFullClient(clientID); err != nil { - ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHint("Unable to assert type of client"))) + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed to retrieve client configuration with error: %s", requester.GetID(), client.GetID(), rfc.WithExposeDebug(true).GetDescription()) + + ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(rfc)) return } @@ -100,7 +104,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, var jti uuid.UUID if jti, err = uuid.NewRandom(); err != nil { - ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHintf("Could not generate JTI.")) + ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHint("Could not generate JTI.")) return } @@ -120,9 +124,9 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, return } - rw.Header().Set("Content-Type", "application/jwt") + rw.Header().Set(fasthttp.HeaderContentType, "application/jwt") _, _ = rw.Write([]byte(token)) - case "none", "": + case oidc.SigningAlgorithmNone, "": ctx.Providers.OpenIDConnect.Write(rw, req, claims) default: ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm))) diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go index ba67fe207..9b3b10ee7 100644 --- a/internal/middlewares/authelia_context.go +++ b/internal/middlewares/authelia_context.go @@ -586,13 +586,27 @@ func (ctx *AutheliaCtx) AcceptsMIME(mime string) (acceptsMime bool) { } // SpecialRedirect performs a redirect similar to fasthttp.RequestCtx except it allows statusCode 401 and includes body -// content in the form of a link to the location. +// content in the form of a link to the location if the request method was not head. func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) { + var u []byte + + u, statusCode = ctx.setSpecialRedirect(uri, statusCode) + + ctx.SetContentTypeTextHTML() + ctx.SetBodyString(fmt.Sprintf("%d %s", utils.StringHTMLEscape(string(u)), statusCode, fasthttp.StatusMessage(statusCode))) +} + +// SpecialRedirectNoBody performs a redirect similar to fasthttp.RequestCtx except it allows statusCode 401 and includes +// no body. +func (ctx *AutheliaCtx) SpecialRedirectNoBody(uri string, statusCode int) { + _, _ = ctx.setSpecialRedirect(uri, statusCode) +} + +func (ctx *AutheliaCtx) setSpecialRedirect(uri string, statusCode int) ([]byte, int) { if statusCode < fasthttp.StatusMovedPermanently || (statusCode > fasthttp.StatusSeeOther && statusCode != fasthttp.StatusTemporaryRedirect && statusCode != fasthttp.StatusPermanentRedirect && statusCode != fasthttp.StatusUnauthorized) { statusCode = fasthttp.StatusFound } - ctx.SetContentTypeTextHTML() ctx.SetStatusCode(statusCode) u := fasthttp.AcquireURI() @@ -600,11 +614,13 @@ func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) { ctx.URI().CopyTo(u) u.Update(uri) - ctx.Response.Header.SetBytesKV(headerLocation, u.FullURI()) + raw := u.FullURI() - ctx.SetBodyString(fmt.Sprintf("%d %s", utils.StringHTMLEscape(string(u.FullURI())), statusCode, fasthttp.StatusMessage(statusCode))) + ctx.Response.Header.SetBytesKV(headerLocation, raw) fasthttp.ReleaseURI(u) + + return raw, statusCode } // RecordAuthn records authentication metrics. diff --git a/internal/model/schema_migration.go b/internal/model/schema_migration.go index a18b8dc06..313b88436 100644 --- a/internal/model/schema_migration.go +++ b/internal/model/schema_migration.go @@ -1,5 +1,9 @@ package model +import ( + "strings" +) + // SchemaMigration represents an intended migration. type SchemaMigration struct { Version int @@ -9,6 +13,11 @@ type SchemaMigration struct { Query string } +// NotEmpty returns true if the SchemaMigration is not an empty string. +func (m SchemaMigration) NotEmpty() bool { + return len(strings.TrimSpace(m.Query)) != 0 +} + // Before returns the version the schema should be at Before the migration is applied. func (m SchemaMigration) Before() (before int) { if m.Up { diff --git a/internal/oidc/store.go b/internal/oidc/store.go index 9089137e1..a5a181c2b 100644 --- a/internal/oidc/store.go +++ b/internal/oidc/store.go @@ -79,7 +79,7 @@ func (s *Store) GetClientPolicy(id string) (level authorization.Level) { func (s *Store) GetFullClient(id string) (client *Client, err error) { client, ok := s.clients[id] if !ok { - return nil, fosite.ErrNotFound + return nil, fosite.ErrInvalidClient } return client, nil diff --git a/internal/oidc/store_test.go b/internal/oidc/store_test.go index c5bff5b0c..580e864e4 100644 --- a/internal/oidc/store_test.go +++ b/internal/oidc/store_test.go @@ -59,7 +59,7 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { }, nil) client, err := s.GetClient(context.Background(), "myinvalidclient") - assert.EqualError(t, err, "not_found") + assert.EqualError(t, err, "invalid_client") assert.Nil(t, client) client, err = s.GetClient(context.Background(), "myclient") @@ -113,7 +113,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) { client, err := s.GetFullClient("another-client") assert.Nil(t, client) - assert.EqualError(t, err, "not_found") + assert.EqualError(t, err, "invalid_client") } func TestOpenIDConnectStore_IsValidClientID(t *testing.T) { diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 657c84278..b3dd82da7 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -201,8 +201,8 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers) case "legacy": log. WithField("path_prefix", pathAuthzLegacy). - WithField("impl", endpoint.Implementation). - WithField("methods", []string{"*"}). + WithField("implementation", endpoint.Implementation). + WithField("methods", "*"). Trace("Registering Authz Endpoint") r.ANY(pathAuthzLegacy, handler) @@ -212,8 +212,8 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers) case handlers.AuthzImplLegacy.String(), handlers.AuthzImplExtAuthz.String(): log. WithField("path_prefix", uri). - WithField("impl", endpoint.Implementation). - WithField("methods", []string{"*"}). + WithField("implementation", endpoint.Implementation). + WithField("methods", "*"). Trace("Registering Authz Endpoint") r.ANY(uri, handler) @@ -221,7 +221,7 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers) default: log. WithField("path", uri). - WithField("impl", endpoint.Implementation). + WithField("implementation", endpoint.Implementation). WithField("methods", []string{fasthttp.MethodGet, fasthttp.MethodHead}). Trace("Registering Authz Endpoint") diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 1712ae8c8..4514f5619 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -9,7 +9,6 @@ import ( "io" "net/http" "os" - "path" "strconv" "strings" "testing" @@ -26,12 +25,6 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -func Test(t *testing.T) { - fmt.Println(path.Join("/api/authz/", "abc")) - fmt.Println(path.Join("/api/authz/", "abc/123/", "{path:*}")) - fmt.Println(path.Join("/api/authz/", "abc/123/")) -} - // TemporaryCertificate contains the FD of 2 temporary files containing the PEM format of the certificate and private key. type TemporaryCertificate struct { CertFile *os.File diff --git a/internal/server/template.go b/internal/server/template.go index be71d9874..57a120c01 100644 --- a/internal/server/template.go +++ b/internal/server/template.go @@ -61,16 +61,27 @@ func ServeTemplatedFile(t templates.Template, opts *TemplatedFileOptions) middle var ( rememberMe string + baseURL string + domain string provider *session.Session ) if provider, err = ctx.GetSessionProvider(); err == nil { + if provider.Config.AutheliaURL != nil { + baseURL = provider.Config.AutheliaURL.String() + } else { + baseURL = ctx.RootURLSlash().String() + } + + domain = provider.Config.Domain rememberMe = strconv.FormatBool(!provider.Config.DisableRememberMe) + } else { + baseURL = ctx.RootURLSlash().String() } data := &bytes.Buffer{} - if err = t.Execute(data, opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride, rememberMe)); err != nil { + if err = t.Execute(data, opts.CommonData(ctx.BasePath(), baseURL, domain, nonce, logoOverride, rememberMe)); err != nil { ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable) ctx.Logger.WithError(err).Errorf("Error occcurred rendering template") @@ -118,11 +129,28 @@ func ServeTemplatedOpenAPI(t templates.Template, opts *TemplatedFileOptions) mid ctx.SetContentTypeTextPlain() } - var err error + var ( + baseURL string + domain string + provider *session.Session + err error + ) + + if provider, err = ctx.GetSessionProvider(); err == nil { + if provider.Config.AutheliaURL != nil { + baseURL = provider.Config.AutheliaURL.String() + } else { + baseURL = ctx.RootURLSlash().String() + } + + domain = provider.Config.Domain + } else { + baseURL = ctx.RootURLSlash().String() + } data := &bytes.Buffer{} - if err = t.Execute(data, opts.OpenAPIData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce)); err != nil { + if err = t.Execute(data, opts.OpenAPIData(ctx.BasePath(), baseURL, domain, nonce)); err != nil { ctx.RequestCtx.Error("an error occurred", fasthttp.StatusServiceUnavailable) ctx.Logger.WithError(err).Errorf("Error occcurred rendering template") @@ -285,14 +313,15 @@ type TemplatedFileOptions struct { } // CommonData returns a TemplatedFileCommonData with the dynamic options. -func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverride, rememberMe string) TemplatedFileCommonData { +func (options *TemplatedFileOptions) CommonData(base, baseURL, domain, nonce, logoOverride, rememberMe string) TemplatedFileCommonData { if rememberMe != "" { - return options.commonDataWithRememberMe(base, baseURL, nonce, logoOverride, rememberMe) + return options.commonDataWithRememberMe(base, baseURL, domain, nonce, logoOverride, rememberMe) } return TemplatedFileCommonData{ Base: base, BaseURL: baseURL, + Domain: domain, CSPNonce: nonce, LogoOverride: logoOverride, DuoSelfEnrollment: options.DuoSelfEnrollment, @@ -307,10 +336,11 @@ func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverri } // CommonDataWithRememberMe returns a TemplatedFileCommonData with the dynamic options. -func (options *TemplatedFileOptions) commonDataWithRememberMe(base, baseURL, nonce, logoOverride, rememberMe string) TemplatedFileCommonData { +func (options *TemplatedFileOptions) commonDataWithRememberMe(base, baseURL, domain, nonce, logoOverride, rememberMe string) TemplatedFileCommonData { return TemplatedFileCommonData{ Base: base, BaseURL: baseURL, + Domain: domain, CSPNonce: nonce, LogoOverride: logoOverride, DuoSelfEnrollment: options.DuoSelfEnrollment, @@ -323,10 +353,11 @@ func (options *TemplatedFileOptions) commonDataWithRememberMe(base, baseURL, non } // OpenAPIData returns a TemplatedFileOpenAPIData with the dynamic options. -func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, nonce string) TemplatedFileOpenAPIData { +func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, domain, nonce string) TemplatedFileOpenAPIData { return TemplatedFileOpenAPIData{ Base: base, BaseURL: baseURL, + Domain: domain, CSPNonce: nonce, Session: options.Session, @@ -343,6 +374,7 @@ func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, nonce string) Te type TemplatedFileCommonData struct { Base string BaseURL string + Domain string CSPNonce string LogoOverride string DuoSelfEnrollment string @@ -359,6 +391,7 @@ type TemplatedFileCommonData struct { type TemplatedFileOpenAPIData struct { Base string BaseURL string + Domain string CSPNonce string Session string PasswordReset bool diff --git a/internal/server/template_test.go b/internal/server/template_test.go new file mode 100644 index 000000000..330edb851 --- /dev/null +++ b/internal/server/template_test.go @@ -0,0 +1,84 @@ +package server + +import ( + "io/fs" + "net/url" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp" + + "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/mocks" + "github.com/authelia/authelia/v4/internal/session" + "github.com/authelia/authelia/v4/internal/templates" +) + +const ( + assetsOpenAPIPath = "public_html/api/openapi.yml" + localOpenAPIPath = "../../api/openapi.yml" +) + +type ReadFileOpenAPI struct{} + +func (lfs *ReadFileOpenAPI) Open(name string) (fs.File, error) { + switch name { + case assetsOpenAPIPath: + return os.Open(localOpenAPIPath) + default: + return assets.Open(name) + } +} + +func (lfs *ReadFileOpenAPI) ReadFile(name string) ([]byte, error) { + switch name { + case assetsOpenAPIPath: + return os.ReadFile(localOpenAPIPath) + default: + return assets.ReadFile(name) + } +} + +func TestShouldTemplateOpenAPI(t *testing.T) { + provider, err := templates.New(templates.Config{}) + require.NoError(t, err) + + fs := &ReadFileOpenAPI{} + + require.NoError(t, provider.LoadTemplatedAssets(fs)) + + mock := mocks.NewMockAutheliaCtx(t) + + mock.Ctx.Configuration.Server = schema.DefaultServerConfiguration + mock.Ctx.Configuration.Session = schema.SessionConfiguration{ + Cookies: []schema.SessionCookieConfiguration{ + { + SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{ + Domain: "example.com", + }, + AutheliaURL: &url.URL{Scheme: "https", Host: "auth.example.com", Path: "/"}, + }, + }, + } + + mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil) + + opts := NewTemplatedFileOptions(&mock.Ctx.Configuration) + + handler := ServeTemplatedOpenAPI(provider.GetAssetOpenAPISpecTemplate(), opts) + + mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedProto, "https") + mock.Ctx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "example.com") + mock.Ctx.Request.Header.Set("X-Forwarded-Uri", "/api/openapi.yml") + + handler(mock.Ctx) + + assert.Equal(t, fasthttp.StatusOK, mock.Ctx.Response.StatusCode()) + + body := string(mock.Ctx.Response.Body()) + + assert.NotEqual(t, "", body) + assert.Contains(t, body, "example: 'https://auth.example.com/?rd=https%3A%2F%2Fexample.com%2F&rm=GET'") +} diff --git a/internal/storage/migrations.go b/internal/storage/migrations.go index 18d79aa25..bfc78e954 100644 --- a/internal/storage/migrations.go +++ b/internal/storage/migrations.go @@ -4,6 +4,7 @@ import ( "embed" "errors" "fmt" + "io/fs" "sort" "strconv" "strings" @@ -15,8 +16,12 @@ import ( var migrationsFS embed.FS func latestMigrationVersion(providerName string) (version int, err error) { - entries, err := migrationsFS.ReadDir("migrations") - if err != nil { + var ( + entries []fs.DirEntry + migration model.SchemaMigration + ) + + if entries, err = migrationsFS.ReadDir("migrations"); err != nil { return -1, err } @@ -25,21 +30,20 @@ func latestMigrationVersion(providerName string) (version int, err error) { continue } - m, err := scanMigration(entry.Name()) - if err != nil { + if migration, err = scanMigration(entry.Name()); err != nil { return -1, err } - if m.Provider != providerName { + if migration.Provider != providerName && migration.Provider != providerAll { continue } - if !m.Up { + if !migration.Up { continue } - if m.Version > version { - version = m.Version + if migration.Version > version { + version = migration.Version } } @@ -50,12 +54,17 @@ func latestMigrationVersion(providerName string) (version int, err error) { // target versions. If the target version is -1 this indicates the latest version. If the target version is 0 // this indicates the database zero state. func loadMigrations(providerName string, prior, target int) (migrations []model.SchemaMigration, err error) { - if prior == target && (prior != -1 || target != -1) { + if prior == target { return nil, ErrMigrateCurrentVersionSameAsTarget } - entries, err := migrationsFS.ReadDir("migrations") - if err != nil { + var ( + migrationsAll []model.SchemaMigration + migration model.SchemaMigration + entries []fs.DirEntry + ) + + if entries, err = migrationsFS.ReadDir("migrations"); err != nil { return nil, err } @@ -66,8 +75,7 @@ func loadMigrations(providerName string, prior, target int) (migrations []model. continue } - migration, err := scanMigration(entry.Name()) - if err != nil { + if migration, err = scanMigration(entry.Name()); err != nil { return nil, err } @@ -75,7 +83,28 @@ func loadMigrations(providerName string, prior, target int) (migrations []model. continue } - migrations = append(migrations, migration) + if migration.Provider == providerAll { + migrationsAll = append(migrationsAll, migration) + } else { + migrations = append(migrations, migration) + } + } + + // Add "all" migrations for versions that don't exist. + for _, am := range migrationsAll { + found := false + + for _, m := range migrations { + if m.Version == am.Version { + found = true + + break + } + } + + if !found { + migrations = append(migrations, am) + } } if up { @@ -103,7 +132,7 @@ func skipMigration(providerName string, up bool, target, prior int, migration *m return true } - if target != -1 && (migration.Version > target || migration.Version <= prior) { + if migration.Version > target || migration.Version <= prior { // Skip if the migration version is greater than the target or less than or equal to the previous version. return true } @@ -113,12 +142,6 @@ func skipMigration(providerName string, up bool, target, prior int, migration *m return true } - if migration.Version == 1 && target == -1 { - // Skip if we're targeting pre1 and the migration version is 1 as this migration will destroy all data - // preventing a successful migration. - return true - } - if migration.Version <= target || migration.Version > prior { // Skip the migration if we want to go down and the migration version is less than or equal to the target // or greater than the previous version. @@ -141,8 +164,9 @@ func scanMigration(m string) (migration model.SchemaMigration, err error) { Provider: result[reMigration.SubexpIndex("Provider")], } - data, err := migrationsFS.ReadFile(fmt.Sprintf("migrations/%s", m)) - if err != nil { + var data []byte + + if data, err = migrationsFS.ReadFile(fmt.Sprintf("migrations/%s", m)); err != nil { return model.SchemaMigration{}, err } diff --git a/internal/storage/migrations/V0007.ConsistencyFixes.postgres.up.sql b/internal/storage/migrations/V0007.ConsistencyFixes.postgres.up.sql index a0c50cc90..52ad1df16 100644 --- a/internal/storage/migrations/V0007.ConsistencyFixes.postgres.up.sql +++ b/internal/storage/migrations/V0007.ConsistencyFixes.postgres.up.sql @@ -56,30 +56,8 @@ ALTER TABLE totp_configurations DROP INDEX IF EXISTS totp_configurations_username_key1; DROP INDEX IF EXISTS totp_configurations_username_key; -ALTER TABLE totp_configurations - RENAME TO _bkp_UP_V0007_totp_configurations; - -CREATE TABLE IF NOT EXISTS totp_configurations ( - id SERIAL CONSTRAINT totp_configurations_pkey PRIMARY KEY, - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, - last_used_at TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL, - username VARCHAR(100) NOT NULL, - issuer VARCHAR(100), - algorithm VARCHAR(6) NOT NULL DEFAULT 'SHA1', - digits INTEGER NOT NULL DEFAULT 6, - period INTEGER NOT NULL DEFAULT 30, - secret BYTEA NOT NULL -); - CREATE UNIQUE INDEX totp_configurations_username_key ON totp_configurations (username); -INSERT INTO totp_configurations (created_at, last_used_at, username, issuer, algorithm, digits, period, secret) -SELECT created_at, last_used_at, username, issuer, algorithm, digits, period, secret -FROM _bkp_UP_V0007_totp_configurations -ORDER BY id; - -DROP TABLE IF EXISTS _bkp_UP_V0007_totp_configurations; - ALTER TABLE webauthn_devices DROP CONSTRAINT IF EXISTS webauthn_devices_username_description_key1, DROP CONSTRAINT IF EXISTS webauthn_devices_kid_key1, @@ -97,34 +75,9 @@ DROP INDEX IF EXISTS webauthn_devices_username_description_key; DROP INDEX IF EXISTS webauthn_devices_kid_key; DROP INDEX IF EXISTS webauthn_devices_lookup_key; -ALTER TABLE webauthn_devices - RENAME TO _bkp_UP_V0007_webauthn_devices; - -CREATE TABLE IF NOT EXISTS webauthn_devices ( - id SERIAL CONSTRAINT webauthn_devices_pkey PRIMARY KEY, - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, - last_used_at TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL, - rpid TEXT, - username VARCHAR(100) NOT NULL, - description VARCHAR(30) NOT NULL DEFAULT 'Primary', - kid VARCHAR(512) NOT NULL, - public_key BYTEA NOT NULL, - attestation_type VARCHAR(32), - transport VARCHAR(20) DEFAULT '', - aaguid CHAR(36) NOT NULL, - sign_count INTEGER DEFAULT 0, - clone_warning BOOLEAN NOT NULL DEFAULT FALSE -); - CREATE UNIQUE INDEX webauthn_devices_kid_key ON webauthn_devices (kid); CREATE UNIQUE INDEX webauthn_devices_lookup_key ON webauthn_devices (username, description); -INSERT INTO webauthn_devices (created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning) -SELECT created_at, last_used_at, rpid, username, description, kid, public_key, attestation_type, transport, aaguid, sign_count, clone_warning -FROM _bkp_UP_V0007_webauthn_devices; - -DROP TABLE IF EXISTS _bkp_UP_V0007_webauthn_devices; - ALTER TABLE oauth2_consent_session DROP CONSTRAINT oauth2_consent_session_subject_fkey, DROP CONSTRAINT oauth2_consent_session_preconfiguration_fkey; diff --git a/internal/storage/migrations/V0009.FixConstraints.all.down.sql b/internal/storage/migrations/V0009.FixConstraints.all.down.sql new file mode 100644 index 000000000..e69de29bb diff --git a/internal/storage/migrations/V0009.FixConstraints.all.up.sql b/internal/storage/migrations/V0009.FixConstraints.all.up.sql new file mode 100644 index 000000000..e69de29bb diff --git a/internal/storage/migrations/V0009.FixConstraints.postgres.up.sql b/internal/storage/migrations/V0009.FixConstraints.postgres.up.sql new file mode 100644 index 000000000..6ea9834d6 --- /dev/null +++ b/internal/storage/migrations/V0009.FixConstraints.postgres.up.sql @@ -0,0 +1,6 @@ +ALTER TABLE webauthn_devices + ALTER COLUMN aaguid DROP NOT NULL; + +UPDATE webauthn_devices +SET aaguid = NULL +WHERE aaguid = '' OR aaguid = '00000000-00000000-00000000-00000000'; diff --git a/internal/storage/migrations_test.go b/internal/storage/migrations_test.go index 3eeb155ea..ac898aa46 100644 --- a/internal/storage/migrations_test.go +++ b/internal/storage/migrations_test.go @@ -9,7 +9,7 @@ import ( const ( // This is the latest schema version for the purpose of tests. - LatestVersion = 8 + LatestVersion = 9 ) func TestShouldObtainCorrectUpMigrations(t *testing.T) { @@ -44,6 +44,47 @@ func TestShouldObtainCorrectDownMigrations(t *testing.T) { } } +func TestMigrationShouldGetSpecificMigrationIfAvaliable(t *testing.T) { + upMigrationsPostgreSQL, err := loadMigrations(providerPostgres, 8, 9) + require.NoError(t, err) + require.Len(t, upMigrationsPostgreSQL, 1) + + assert.True(t, upMigrationsPostgreSQL[0].Up) + assert.Equal(t, 9, upMigrationsPostgreSQL[0].Version) + assert.Equal(t, providerPostgres, upMigrationsPostgreSQL[0].Provider) + + upMigrationsSQLite, err := loadMigrations(providerSQLite, 8, 9) + require.NoError(t, err) + require.Len(t, upMigrationsSQLite, 1) + + assert.True(t, upMigrationsSQLite[0].Up) + assert.Equal(t, 9, upMigrationsSQLite[0].Version) + assert.Equal(t, providerAll, upMigrationsSQLite[0].Provider) + + downMigrationsPostgreSQL, err := loadMigrations(providerPostgres, 9, 8) + require.NoError(t, err) + require.Len(t, downMigrationsPostgreSQL, 1) + + assert.False(t, downMigrationsPostgreSQL[0].Up) + assert.Equal(t, 9, downMigrationsPostgreSQL[0].Version) + assert.Equal(t, providerAll, downMigrationsPostgreSQL[0].Provider) + + downMigrationsSQLite, err := loadMigrations(providerSQLite, 9, 8) + require.NoError(t, err) + require.Len(t, downMigrationsSQLite, 1) + + assert.False(t, downMigrationsSQLite[0].Up) + assert.Equal(t, 9, downMigrationsSQLite[0].Version) + assert.Equal(t, providerAll, downMigrationsSQLite[0].Provider) +} + +func TestMigrationShouldReturnErrorOnSame(t *testing.T) { + migrations, err := loadMigrations(providerPostgres, 1, 1) + + assert.EqualError(t, err, "current version is same as migration target, no action being taken") + assert.Nil(t, migrations) +} + func TestMigrationsShouldNotBeDuplicatedPostgres(t *testing.T) { migrations, err := loadMigrations(providerPostgres, 0, SchemaLatest) require.NoError(t, err) diff --git a/internal/storage/sql_provider_schema.go b/internal/storage/sql_provider_schema.go index 3747365d2..c940e6b34 100644 --- a/internal/storage/sql_provider_schema.go +++ b/internal/storage/sql_provider_schema.go @@ -244,7 +244,7 @@ func (p *SQLProvider) schemaMigrateLock(ctx context.Context, conn SQLXConnection } func (p *SQLProvider) schemaMigrateApply(ctx context.Context, conn SQLXConnection, migration model.SchemaMigration) (err error) { - if migration.Query != "" { + if migration.NotEmpty() { if _, err = conn.ExecContext(ctx, migration.Query); err != nil { return fmt.Errorf(errFmtFailedMigration, migration.Version, migration.Name, err) } diff --git a/internal/suites/ActiveDirectory/configuration.yml b/internal/suites/ActiveDirectory/configuration.yml index 26044c7e5..88c297b54 100644 --- a/internal/suites/ActiveDirectory/configuration.yml +++ b/internal/suites/ActiveDirectory/configuration.yml @@ -31,10 +31,12 @@ authentication_backend: session: secret: unsecure_session_secret - domain: example.com expiration: 3600 # 1 hour inactivity: 300 # 5 minutes remember_me: 1y + cookies: + - domain: 'example.com' + authelia_url: 'https://login.example.com:8080' storage: encryption_key: a_not_so_secure_encryption_key diff --git a/internal/suites/HighAvailability/configuration.yml b/internal/suites/HighAvailability/configuration.yml index c1d3002e5..8794cbd35 100644 --- a/internal/suites/HighAvailability/configuration.yml +++ b/internal/suites/HighAvailability/configuration.yml @@ -87,7 +87,9 @@ session: secret: unsecure_session_secret expiration: 3600 # 1 hour inactivity: 300 # 5 minutes - domain: example.com + cookies: + - domain: 'example.com' + authelia_url: 'https://login.example.com:8080' redis: username: authelia password: redis-user-password diff --git a/internal/suites/MultiCookieDomain/configuration.yml b/internal/suites/MultiCookieDomain/configuration.yml index 8102b658e..d19f591f9 100644 --- a/internal/suites/MultiCookieDomain/configuration.yml +++ b/internal/suites/MultiCookieDomain/configuration.yml @@ -32,13 +32,14 @@ session: cookies: - name: 'authelia_session' domain: 'example.com' + authelia_url: 'https://login.example.com:8080' - name: 'example2_session' domain: 'example2.com' - authelia_url: 'https://login.example2.com' + authelia_url: 'https://login.example2.com:8080' remember_me: -1 - name: 'authelia_session' domain: 'example3.com' - authelia_url: 'https://login.example3.com' + authelia_url: 'https://login.example3.com:8080' storage: encryption_key: a_not_so_secure_encryption_key diff --git a/internal/suites/example/compose/authelia/Dockerfile.backend b/internal/suites/example/compose/authelia/Dockerfile.backend index b8dd26bcd..ff3e4c755 100644 --- a/internal/suites/example/compose/authelia/Dockerfile.backend +++ b/internal/suites/example/compose/authelia/Dockerfile.backend @@ -1,4 +1,4 @@ -FROM golang:1.20.2-alpine +FROM golang:1.20.3-alpine ARG USER_ID ARG GROUP_ID diff --git a/internal/suites/example/compose/envoy/docker-compose.yml b/internal/suites/example/compose/envoy/docker-compose.yml index f32822216..c17497ace 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.3 + image: envoyproxy/envoy:v1.25.4 volumes: - ./example/compose/envoy/envoy.yaml:/etc/envoy/envoy.yaml - ./common/pki:/pki diff --git a/internal/suites/example/compose/haproxy/auth-request.lua b/internal/suites/example/compose/haproxy/auth-request.lua index 37b75f160..1504948d4 100644 --- a/internal/suites/example/compose/haproxy/auth-request.lua +++ b/internal/suites/example/compose/haproxy/auth-request.lua @@ -19,9 +19,42 @@ -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -- SOFTWARE. +-- +-- SPDX-License-Identifier: MIT local http = require("haproxy-lua-http") +core.register_action("auth-request", { "http-req" }, function(txn, be, path) + auth_request(txn, be, path, "HEAD", ".*", "-", "-") +end, 2) + +core.register_action("auth-intercept", { "http-req" }, function(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail) + hdr_req = globToLuaPattern(hdr_req) + hdr_succeed = globToLuaPattern(hdr_succeed) + hdr_fail = globToLuaPattern(hdr_fail) + auth_request(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail) +end, 6) + +function globToLuaPattern(glob) + if glob == "-" then + return "-" + end + -- magic chars: '^', '$', '(', ')', '%', '.', '[', ']', '*', '+', '-', '?' + -- https://www.lua.org/manual/5.4/manual.html#6.4.1 + -- + -- this chain is: + -- 1. escaping all the magic chars, adding a `%` in front of all of them, + -- except the chars being processed later in the chain; + -- 1.1. all the chars inside the [set] are magic chars and have special + -- meaning inside a set, so we're also escaping all of them to avoid + -- misbehavior; + -- 2. converting "match all" `*` and "match one" `?` to their Lua pattern + -- counterparts; + -- 3. adding start and finish boundaries outside the whole string and, + -- being a comma-separated list, between every single item as well. + return "^" .. glob:gsub("[%^%$%(%)%%%.%[%]%+%-]", "%%%1"):gsub("*", ".*"):gsub("?", "."):gsub(",", "$,^") .. "$" +end + function set_var_pre_2_2(txn, var, value) return txn:set_var(var, value) end @@ -44,8 +77,49 @@ function sanitize_header_for_variable(header) return header:gsub("[^a-zA-Z0-9]", "_") end +-- header_match checks whether the provided header matches the pattern. +-- pattern is a comma-separated list of Lua Patterns. +function header_match(header, pattern) + if header == "content-length" or header == "host" or pattern == "-" then + return false + end + for p in pattern:gmatch("[^,]*") do + if header:match(p) then + return true + end + end + return false +end -core.register_action("auth-request", { "http-req" }, function(txn, be, path) +-- Terminates the transaction and sends the provided response to the client. +-- hdr_fail filters header names that should be provided using Lua Patterns. +function send_response(txn, response, hdr_fail) + local reply = txn:reply() + if response then + reply:set_status(response.status_code) + for header, value in response:get_headers(false) do + if header_match(header, hdr_fail) then + reply:add_header(header, value) + end + end + if response.content then + reply:set_body(response.content) + end + else + reply:set_status(500) + end + txn:done(reply) +end + +-- auth_request makes the request to the external authentication service +-- and waits for the response. hdr_* params receive a comma-separated +-- list of Lua Patterns used to identify the headers that should be +-- copied between the requests and responses. A dash `-` in these params +-- mean that the headers shouldn't be copied at all. +-- Special values and behavior: +-- * method == "*": call the auth service using the same method used by the client. +-- * hdr_fail == "-": make the Lua script to not terminate the request. +function auth_request(txn, be, path, method, hdr_req, hdr_succeed, hdr_fail) set_var(txn, "txn.auth_response_successful", false) -- Check whether the given backend exists. @@ -75,7 +149,7 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path) -- socket.http's format. local headers = {} for header, values in pairs(txn.http:req_get_headers()) do - if header ~= 'content-length' then + if header_match(header, hdr_req) then for i, v in pairs(values) do if headers[header] == nil then headers[header] = v @@ -87,28 +161,46 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path) end -- Make request to backend. - local response, err = http.head { + if method == "*" then + method = txn.sf:method() + end + local response, err = http.send(method:upper(), { url = "http://" .. addr .. path, headers = headers, - } + }) + + -- `terminate_on_failure == true` means that the Lua script should send the response + -- and terminate the transaction in the case of a failure. This will happen when + -- hdr_fail content isn't a dash `-`. + local terminate_on_failure = hdr_fail ~= "-" -- Check whether we received a valid HTTP response. if response == nil then txn:Warning("Failure in auth-request backend '" .. be .. "': " .. err) set_var(txn, "txn.auth_response_code", 500) + if terminate_on_failure then + send_response(txn) + end return end set_var(txn, "txn.auth_response_code", response.status_code) + local response_ok = 200 <= response.status_code and response.status_code < 300 for header, value in response:get_headers(true) do set_var(txn, "req.auth_response_header." .. sanitize_header_for_variable(header), value) + if response_ok and hdr_succeed ~= "-" and header_match(header, hdr_succeed) then + txn.http:req_set_header(header, value) + end end - -- 2xx: Allow request. - if 200 <= response.status_code and response.status_code < 300 then + -- response_ok means 2xx: allow request. + if response_ok then set_var(txn, "txn.auth_response_successful", true) - -- Don't allow other codes. + -- Don't allow codes < 200 or >= 300. + -- Forward the response to the client if required. + elseif terminate_on_failure then + send_response(txn, response, hdr_fail) -- Codes with Location: Passthrough location at redirect. elseif response.status_code == 301 or response.status_code == 302 or response.status_code == 303 or response.status_code == 307 or response.status_code == 308 then set_var(txn, "txn.auth_response_location", response:get_header("location", "last")) @@ -116,4 +208,4 @@ core.register_action("auth-request", { "http-req" }, function(txn, be, path) elseif response.status_code ~= 401 and response.status_code ~= 403 then txn:Warning("Invalid status code in auth-request backend '" .. be .. "': " .. response.status_code) end -end, 2) +end diff --git a/internal/suites/example/compose/haproxy/haproxy.cfg b/internal/suites/example/compose/haproxy/haproxy.cfg index ae9529d2c..63c471284 100644 --- a/internal/suites/example/compose/haproxy/haproxy.cfg +++ b/internal/suites/example/compose/haproxy/haproxy.cfg @@ -42,25 +42,17 @@ frontend fe_http http-request set-var(req.scheme) str(https) if { ssl_fc } http-request set-var(req.scheme) str(http) if !{ ssl_fc } http-request set-var(req.questionmark) str(?) if { query -m found } - http-request set-var(req.method) str(CONNECT) if { method CONNECT } - http-request set-var(req.method) str(GET) if { method GET } - http-request set-var(req.method) str(HEAD) if { method HEAD } - http-request set-var(req.method) str(OPTIONS) if { method OPTIONS } - http-request set-var(req.method) str(POST) if { method POST } - http-request set-var(req.method) str(TRACE) if { method TRACE } - http-request set-var(req.method) str(PUT) if { method PUT } - http-request set-var(req.method) str(PATCH) if { method PATCH } - http-request set-var(req.method) str(DELETE) if { method DELETE } http-request set-header X-Real-IP %[src] - http-request set-header X-Original-Method %[var(req.method)] - http-request set-header X-Original-URL %[var(req.scheme)]://%[req.hdr(Host)]%[path]%[var(req.questionmark)]%[query] + http-request set-header X-Forwarded-Method %[method] + http-request set-header X-Forwarded-Proto %[var(req.scheme)] + http-request set-header X-Forwarded-Host %[req.hdr(Host)] + http-request set-header X-Forwarded-URI %[path]%[var(req.questionmark)]%[query] # be_auth_request is used to make HAProxy do the TLS termination since the Lua script # does not know how to handle it (see https://github.com/TimWolla/haproxy-auth-request/issues/12). - http-request lua.auth-request be_auth_request /api/authz/auth-request if protected-frontends - - http-request redirect location https://login.example.com:8080/?rd=%[var(req.scheme)]://%[base]%[var(req.questionmark)]%[query]&rm=%[var(req.method)] if protected-frontends !{ var(txn.auth_response_successful) -m bool } + http-request lua.auth-intercept be_auth_request /api/authz/forward-auth HEAD * authorization,proxy-authorization,remote_user,remote-user,remote-groups,remote-name,remote-email - if protected-frontends + http-request redirect location %[var(txn.auth_response_location)] if protected-frontends !{ var(txn.auth_response_successful) -m bool } use_backend be_authelia if host-authelia-portal api-path || devworkflow-path || jwks-path || locales-path || wellknown-path use_backend fe_authelia if host-authelia-portal !api-path @@ -86,24 +78,6 @@ backend fe_authelia server authelia-backend authelia-backend:9091 check backup resolvers docker ssl verify none backend be_httpbin - ## Pass the special authorization response headers to the protected application. - acl authorization_exist var(req.auth_response_header.authorization) -m found - acl proxy_authorization_exist var(req.auth_response_header.proxy_authorization) -m found - - http-request set-header Authorization %[var(req.auth_response_header.authorization)] if authorization_exist - http-request set-header Proxy-Authorization %[var(req.auth_response_header.proxy_authorization)] if proxy_authorization_exist - - ## Pass the special metadata response headers to the protected application. - acl remote_user_exist var(req.auth_response_header.remote_user) -m found - acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found - acl remote_name_exist var(req.auth_response_header.remote_name) -m found - acl remote_email_exist var(req.auth_response_header.remote_email) -m found - - http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist - http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist - http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist - http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist - ## Pass the Set-Cookie response headers to the user. acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist @@ -114,4 +88,8 @@ backend be_mail server smtp-backend smtp:1080 resolvers docker backend be_protected + ## Pass the Set-Cookie response headers to the user. + acl set_cookie_exist var(req.auth_response_header.set_cookie) -m found + http-response set-header Set-Cookie %[var(req.auth_response_header.set_cookie)] if set_cookie_exist + server nginx-backend nginx-backend:80 resolvers docker diff --git a/internal/suites/example/compose/nginx/portal/nginx.conf b/internal/suites/example/compose/nginx/portal/nginx.conf index d7260426c..a667ba80d 100644 --- a/internal/suites/example/compose/nginx/portal/nginx.conf +++ b/internal/suites/example/compose/nginx/portal/nginx.conf @@ -166,9 +166,6 @@ http { ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. auth_request /internal/authelia/authz; - ## Set the $target_url variable based on the original request. - set $target_url $scheme://$http_host$request_uri; - ## Save the upstream authorization response headers from Authelia to variables. auth_request_set $authorization $upstream_http_authorization; auth_request_set $proxy_authorization $upstream_http_proxy_authorization; @@ -193,8 +190,23 @@ http { auth_request_set $cookie $upstream_http_set_cookie; add_header Set-Cookie $cookie; - ## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal. - error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url; + ## Configure the redirection when the Authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method' + ## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url + ## value to determine the redirection URL here. It's much simpler and compatible with the mutli-cookie domain easily. + + ## Modern Method: Set the $redirection_url to the Location header of the response to the Authz endpoint. + auth_request_set $redirection_url $upstream_http_location; + + ## Modern Method: When there is a 401 response code from the Authz endpoint redirect to the $redirection_url. + error_page 401 =302 $redirection_url; + + ## Legacy Method: Set $target_url to the original requested URL. + ## This requires http_set_misc module, replace 'set_escape_uri' with 'set' if you don't have this module. + # set $target_url $scheme://$http_host$request_uri; + + ## Legacy Method: When there is a 401 response code from the Authz endpoint redirect to the portal with the 'rd' + ## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL. + # error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url; # Authelia relies on Proxy-Authorization header to authenticate in basic auth. # but for the sake of simplicity (because Authorization in supported in most @@ -252,9 +264,6 @@ http { ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. auth_request /internal/authelia/authz; - ## Set the $target_url variable based on the original request. - set $target_url $scheme://$http_host$request_uri; - ## Save the upstream authorization response headers from Authelia to variables. auth_request_set $authorization $upstream_http_authorization; auth_request_set $proxy_authorization $upstream_http_proxy_authorization; @@ -279,8 +288,23 @@ http { auth_request_set $cookie $upstream_http_set_cookie; add_header Set-Cookie $cookie; - ## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal. - error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url; + ## Configure the redirection when the Authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method' + ## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url + ## value to determine the redirection URL here. It's much simpler and compatible with the mutli-cookie domain easily. + + ## Modern Method: Set the $redirection_url to the Location header of the response to the Authz endpoint. + auth_request_set $redirection_url $upstream_http_location; + + ## Modern Method: When there is a 401 response code from the Authz endpoint redirect to the $redirection_url. + error_page 401 =302 $redirection_url; + + ## Legacy Method: Set $target_url to the original requested URL. + ## This requires http_set_misc module, replace 'set_escape_uri' with 'set' if you don't have this module. + # set $target_url $scheme://$http_host$request_uri; + + ## Legacy Method: When there is a 401 response code from the Authz endpoint redirect to the portal with the 'rd' + ## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL. + # error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url; proxy_pass $upstream_headers; } @@ -309,9 +333,6 @@ http { ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource. auth_request /internal/authelia/authz; - ## Set the $target_url variable based on the original request. - set $target_url $scheme://$http_host$request_uri; - ## Save the upstream authorization response headers from Authelia to variables. auth_request_set $authorization $upstream_http_authorization; auth_request_set $proxy_authorization $upstream_http_proxy_authorization; @@ -336,8 +357,23 @@ http { auth_request_set $cookie $upstream_http_set_cookie; add_header Set-Cookie $cookie; - ## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal. - error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url; + ## Configure the redirection when the Authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method' + ## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url + ## value to determine the redirection URL here. It's much simpler and compatible with the mutli-cookie domain easily. + + ## Modern Method: Set the $redirection_url to the Location header of the response to the Authz endpoint. + auth_request_set $redirection_url $upstream_http_location; + + ## Modern Method: When there is a 401 response code from the Authz endpoint redirect to the $redirection_url. + error_page 401 =302 $redirection_url; + + ## Legacy Method: Set $target_url to the original requested URL. + ## This requires http_set_misc module, replace 'set_escape_uri' with 'set' if you don't have this module. + # set $target_url $scheme://$http_host$request_uri; + + ## Legacy Method: When there is a 401 response code from the Authz endpoint redirect to the portal with the 'rd' + ## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL. + # error_page 401 =302 https://login.$basedomain:8080/?rd=$target_url; # Route the request to the correct virtual host in the backend. proxy_set_header Host $http_host; diff --git a/internal/suites/example/compose/traefik2/docker-compose.yml b/internal/suites/example/compose/traefik2/docker-compose.yml index 980e0f7aa..cfaff9ffe 100644 --- a/internal/suites/example/compose/traefik2/docker-compose.yml +++ b/internal/suites/example/compose/traefik2/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: traefik: - image: traefik:v2.9.9 + image: traefik:v2.9.10 volumes: - '/var/run/docker.sock:/var/run/docker.sock' labels: diff --git a/internal/templates/funcs.go b/internal/templates/funcs.go index f8be41e3d..d8e6e3538 100644 --- a/internal/templates/funcs.go +++ b/internal/templates/funcs.go @@ -9,6 +9,7 @@ import ( "encoding/hex" "fmt" "hash" + "net/url" "os" "path" "path/filepath" @@ -79,6 +80,8 @@ func FuncMap() map[string]any { "indent": FuncIndent, "nindent": FuncNewlineIndent, "uuidv4": FuncUUIDv4, + "urlquery": url.QueryEscape, + "urlunquery": url.QueryUnescape, } } diff --git a/internal/templates/provider.go b/internal/templates/provider.go index 10235dca6..40e737725 100644 --- a/internal/templates/provider.go +++ b/internal/templates/provider.go @@ -1,9 +1,9 @@ package templates import ( - "embed" "fmt" th "html/template" + "io/fs" "path" tt "text/template" ) @@ -28,7 +28,7 @@ type Provider struct { } // LoadTemplatedAssets takes an embed.FS and loads each templated asset document into a Template. -func (p *Provider) LoadTemplatedAssets(fs embed.FS) (err error) { +func (p *Provider) LoadTemplatedAssets(fs fs.ReadFileFS) (err error) { var ( data []byte ) diff --git a/internal/utils/const.go b/internal/utils/const.go index 3ce72025f..eb53c2d93 100644 --- a/internal/utils/const.go +++ b/internal/utils/const.go @@ -122,6 +122,15 @@ var htmlEscaper = strings.NewReplacer( var ErrTimeoutReached = errors.New("timeout reached") const ( - windows = "windows" - errFmtLinuxNotFound = "open %s: no such file or directory" + windows = "windows" + errFmtLinuxNotFound = "%s %%s: no such file or directory" + errFmtWindowsNotFound = "%s %%s: The system cannot find the %s specified." + + strStat = "stat" + strOpen = "open" + strFile = "file" + strPath = "path" + strIsDir = "isdir" + strPathNotFound = "pathnotfound" + strFileNotFound = "filenotfound" ) diff --git a/internal/utils/errs.go b/internal/utils/errs.go index 7dc0f828e..a96b9e1ea 100644 --- a/internal/utils/errs.go +++ b/internal/utils/errs.go @@ -1,6 +1,9 @@ package utils -import "runtime" +import ( + "fmt" + "runtime" +) // ErrSliceSortAlphabetical is a helper type that can be used with sort.Sort to sort a slice of errors in alphabetical // order. Usage is simple just do sort.Sort(ErrSliceSortAlphabetical([]error{})). @@ -12,28 +15,29 @@ func (s ErrSliceSortAlphabetical) Less(i, j int) bool { return s[i].Error() < s[ func (s ErrSliceSortAlphabetical) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -// GetExpectedErrTxt returns error text for expected errs. +// GetExpectedErrTxt returns error text for expected errs. THIS IS A TEST UTILITY FUNCTION. func GetExpectedErrTxt(err string) string { - switch err { - case "pathnotfound": - switch runtime.GOOS { - case windows: - return "open %s: The system cannot find the path specified." - default: - return errFmtLinuxNotFound - } - case "filenotfound": - switch runtime.GOOS { - case windows: - return "open %s: The system cannot find the file specified." - default: - return errFmtLinuxNotFound - } - case "yamlisdir": - switch runtime.GOOS { - case windows: + switch runtime.GOOS { + case windows: + switch err { + case strPathNotFound: + return fmt.Sprintf(errFmtWindowsNotFound, strOpen, strPath) + case strStat + strPathNotFound: + return fmt.Sprintf(errFmtWindowsNotFound, strStat, strPath) + case strFileNotFound: + return fmt.Sprintf(errFmtWindowsNotFound, strOpen, strFile) + case strStat + strFileNotFound: + return fmt.Sprintf(errFmtWindowsNotFound, strStat, strFile) + case strIsDir: return "read %s: The handle is invalid." - default: + } + default: + switch err { + case strPathNotFound, strFileNotFound: + return fmt.Sprintf(errFmtLinuxNotFound, strOpen) + case strStat + strPathNotFound, strStat + strFileNotFound: + return fmt.Sprintf(errFmtLinuxNotFound, strStat) + case strIsDir: return "read %s: is a directory" } } diff --git a/web/package.json b/web/package.json index 593b16b91..ba18d1f81 100644 --- a/web/package.json +++ b/web/package.json @@ -22,22 +22,22 @@ } }, "dependencies": { - "@emotion/cache": "11.10.5", + "@emotion/cache": "11.10.7", "@emotion/react": "11.10.6", "@emotion/styled": "11.10.6", "@fortawesome/fontawesome-svg-core": "6.4.0", "@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/react-fontawesome": "0.2.0", - "@mui/icons-material": "5.11.11", - "@mui/material": "5.11.15", - "@mui/styles": "5.11.13", + "@mui/icons-material": "5.11.16", + "@mui/material": "5.11.16", + "@mui/styles": "5.11.16", "@simplewebauthn/browser": "7.2.0", "@simplewebauthn/typescript-types": "7.0.0", - "axios": "1.3.4", + "axios": "1.3.5", "broadcast-channel": "5.0.3", "classnames": "2.3.2", - "i18next": "22.4.13", + "i18next": "22.4.14", "i18next-browser-languagedetector": "7.0.1", "i18next-http-backend": "2.2.0", "qrcode.react": "3.1.0", @@ -155,19 +155,19 @@ "@testing-library/react": "14.0.0", "@types/jest": "29.5.0", "@types/node": "18.15.11", - "@types/react": "18.0.31", + "@types/react": "18.0.33", "@types/react-dom": "18.0.11", "@types/zxcvbn": "4.4.1", - "@typescript-eslint/eslint-plugin": "5.57.0", - "@typescript-eslint/parser": "5.57.0", + "@typescript-eslint/eslint-plugin": "5.57.1", + "@typescript-eslint/parser": "5.57.1", "@vitejs/plugin-react": "3.1.0", "esbuild": "0.17.15", "esbuild-jest": "0.5.0", - "eslint": "8.37.0", + "eslint": "8.38.0", "eslint-config-prettier": "8.8.0", "eslint-config-react-app": "7.0.1", "eslint-formatter-rdjson": "1.0.5", - "eslint-import-resolver-typescript": "3.5.4", + "eslint-import-resolver-typescript": "3.5.5", "eslint-plugin-import": "2.27.5", "eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-prettier": "4.2.1", @@ -180,11 +180,11 @@ "jest-watch-typeahead": "2.2.2", "prettier": "2.8.7", "react-test-renderer": "18.2.0", - "typescript": "5.0.3", + "typescript": "5.0.4", "vite": "4.2.1", "vite-plugin-eslint": "1.8.1", "vite-plugin-istanbul": "4.0.1", "vite-plugin-svgr": "2.4.0", - "vite-tsconfig-paths": "4.0.7" + "vite-tsconfig-paths": "4.0.8" } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 4e6231ed1..f219a477d 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -2,14 +2,14 @@ lockfileVersion: '6.0' dependencies: '@emotion/cache': - specifier: 11.10.5 - version: 11.10.5 + specifier: 11.10.7 + version: 11.10.7 '@emotion/react': specifier: 11.10.6 - version: 11.10.6(@types/react@18.0.31)(react@18.2.0) + version: 11.10.6(@types/react@18.0.33)(react@18.2.0) '@emotion/styled': specifier: 11.10.6 - version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.31)(react@18.2.0) + version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.33)(react@18.2.0) '@fortawesome/fontawesome-svg-core': specifier: 6.4.0 version: 6.4.0 @@ -23,14 +23,14 @@ dependencies: specifier: 0.2.0 version: 0.2.0(@fortawesome/fontawesome-svg-core@6.4.0)(react@18.2.0) '@mui/icons-material': - specifier: 5.11.11 - version: 5.11.11(@mui/material@5.11.15)(@types/react@18.0.31)(react@18.2.0) + specifier: 5.11.16 + version: 5.11.16(@mui/material@5.11.16)(@types/react@18.0.33)(react@18.2.0) '@mui/material': - specifier: 5.11.15 - version: 5.11.15(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.31)(react-dom@18.2.0)(react@18.2.0) + specifier: 5.11.16 + version: 5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0) '@mui/styles': - specifier: 5.11.13 - version: 5.11.13(@types/react@18.0.31)(react@18.2.0) + specifier: 5.11.16 + version: 5.11.16(@types/react@18.0.33)(react@18.2.0) '@simplewebauthn/browser': specifier: 7.2.0 version: 7.2.0 @@ -38,8 +38,8 @@ dependencies: specifier: 7.0.0 version: 7.0.0 axios: - specifier: 1.3.4 - version: 1.3.4 + specifier: 1.3.5 + version: 1.3.5 broadcast-channel: specifier: 5.0.3 version: 5.0.3 @@ -47,8 +47,8 @@ dependencies: specifier: 2.3.2 version: 2.3.2 i18next: - specifier: 22.4.13 - version: 22.4.13 + specifier: 22.4.14 + version: 22.4.14 i18next-browser-languagedetector: specifier: 7.0.1 version: 7.0.1 @@ -66,7 +66,7 @@ dependencies: version: 18.2.0(react@18.2.0) react-i18next: specifier: 12.2.0 - version: 12.2.0(i18next@22.4.13)(react-dom@18.2.0)(react@18.2.0) + version: 12.2.0(i18next@22.4.14)(react-dom@18.2.0)(react@18.2.0) react-loading: specifier: 2.0.3 version: 2.0.3(react@18.2.0) @@ -89,7 +89,7 @@ devDependencies: version: 17.4.4 '@limegrass/eslint-plugin-import-alias': specifier: 1.0.6 - version: 1.0.6(eslint@8.37.0) + version: 1.0.6(eslint@8.38.0) '@testing-library/jest-dom': specifier: 5.16.5 version: 5.16.5 @@ -103,8 +103,8 @@ devDependencies: specifier: 18.15.11 version: 18.15.11 '@types/react': - specifier: 18.0.31 - version: 18.0.31 + specifier: 18.0.33 + version: 18.0.33 '@types/react-dom': specifier: 18.0.11 version: 18.0.11 @@ -112,11 +112,11 @@ devDependencies: specifier: 4.4.1 version: 4.4.1 '@typescript-eslint/eslint-plugin': - specifier: 5.57.0 - version: 5.57.0(@typescript-eslint/parser@5.57.0)(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.57.1 + version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.38.0)(typescript@5.0.4) '@typescript-eslint/parser': - specifier: 5.57.0 - version: 5.57.0(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.57.1 + version: 5.57.1(eslint@8.38.0)(typescript@5.0.4) '@vitejs/plugin-react': specifier: 3.1.0 version: 3.1.0(vite@4.2.1) @@ -127,35 +127,35 @@ devDependencies: specifier: 0.5.0 version: 0.5.0(esbuild@0.17.15) eslint: - specifier: 8.37.0 - version: 8.37.0 + specifier: 8.38.0 + version: 8.38.0 eslint-config-prettier: specifier: 8.8.0 - version: 8.8.0(eslint@8.37.0) + version: 8.8.0(eslint@8.38.0) eslint-config-react-app: specifier: 7.0.1 - version: 7.0.1(eslint-import-resolver-typescript@3.5.4)(eslint@8.37.0)(jest@29.5.0)(typescript@5.0.3) + version: 7.0.1(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0)(jest@29.5.0)(typescript@5.0.4) eslint-formatter-rdjson: specifier: 1.0.5 version: 1.0.5 eslint-import-resolver-typescript: - specifier: 3.5.4 - version: 3.5.4(eslint-plugin-import@2.27.5)(eslint@8.37.0) + specifier: 3.5.5 + version: 3.5.5(@typescript-eslint/parser@5.57.1)(eslint-plugin-import@2.27.5)(eslint@8.38.0) eslint-plugin-import: specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.57.0)(eslint-import-resolver-typescript@3.5.4)(eslint@8.37.0) + version: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0) eslint-plugin-jsx-a11y: specifier: 6.7.1 - version: 6.7.1(eslint@8.37.0) + version: 6.7.1(eslint@8.38.0) eslint-plugin-prettier: specifier: 4.2.1 - version: 4.2.1(eslint-config-prettier@8.8.0)(eslint@8.37.0)(prettier@2.8.7) + version: 4.2.1(eslint-config-prettier@8.8.0)(eslint@8.38.0)(prettier@2.8.7) eslint-plugin-react: specifier: 7.32.2 - version: 7.32.2(eslint@8.37.0) + version: 7.32.2(eslint@8.38.0) eslint-plugin-react-hooks: specifier: 4.6.0 - version: 4.6.0(eslint@8.37.0) + version: 4.6.0(eslint@8.38.0) husky: specifier: 8.0.3 version: 8.0.3 @@ -178,14 +178,14 @@ devDependencies: specifier: 18.2.0 version: 18.2.0(react@18.2.0) typescript: - specifier: 5.0.3 - version: 5.0.3 + specifier: 5.0.4 + version: 5.0.4 vite: specifier: 4.2.1 version: 4.2.1(@types/node@18.15.11) vite-plugin-eslint: specifier: 1.8.1 - version: 1.8.1(eslint@8.37.0)(vite@4.2.1) + version: 1.8.1(eslint@8.38.0)(vite@4.2.1) vite-plugin-istanbul: specifier: 4.0.1 version: 4.0.1(vite@4.2.1) @@ -193,8 +193,8 @@ devDependencies: specifier: 2.4.0 version: 2.4.0(vite@4.2.1) vite-tsconfig-paths: - specifier: 4.0.7 - version: 4.0.7(typescript@5.0.3)(vite@4.2.1) + specifier: 4.0.8 + version: 4.0.8(typescript@5.0.4)(vite@4.2.1) packages: @@ -267,7 +267,7 @@ packages: - supports-color dev: true - /@babel/eslint-parser@7.18.2(@babel/core@7.18.6)(eslint@8.37.0): + /@babel/eslint-parser@7.18.2(@babel/core@7.18.6)(eslint@8.38.0): resolution: {integrity: sha512-oFQYkE8SuH14+uR51JVAmdqwKYXGRjEXx7s+WiagVjqQ+HPE+nnwyF2qlVG8evUsUHmPcA+6YXMEDbIhEyQc5A==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} peerDependencies: @@ -278,7 +278,7 @@ packages: optional: true dependencies: '@babel/core': 7.18.6 - eslint: 8.37.0 + eslint: 8.38.0 eslint-scope: 5.1.1 eslint-visitor-keys: 2.1.0 semver: 6.3.0 @@ -2168,13 +2168,13 @@ packages: '@types/node': 18.15.11 chalk: 4.1.2 cosmiconfig: 8.0.0 - cosmiconfig-typescript-loader: 4.0.0(@types/node@18.15.11)(cosmiconfig@8.0.0)(ts-node@10.9.0)(typescript@5.0.3) + cosmiconfig-typescript-loader: 4.0.0(@types/node@18.15.11)(cosmiconfig@8.0.0)(ts-node@10.9.0)(typescript@5.0.4) 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(@types/node@18.15.11)(typescript@5.0.3) - typescript: 5.0.3 + ts-node: 10.9.0(@types/node@18.15.11)(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -2270,8 +2270,8 @@ packages: stylis: 4.1.3 dev: false - /@emotion/cache@11.10.5: - resolution: {integrity: sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==} + /@emotion/cache@11.10.7: + resolution: {integrity: sha512-VLl1/2D6LOjH57Y8Vem1RoZ9haWF4jesHDGiHtKozDQuBIkJm2gimVo0I02sWCuzZtVACeixTVB4jeE8qvCBoQ==} dependencies: '@emotion/memoize': 0.8.0 '@emotion/sheet': 1.2.1 @@ -2294,7 +2294,7 @@ packages: resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} dev: false - /@emotion/react@11.10.6(@types/react@18.0.31)(react@18.2.0): + /@emotion/react@11.10.6(@types/react@18.0.33)(react@18.2.0): resolution: {integrity: sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==} peerDependencies: '@types/react': '*' @@ -2305,12 +2305,12 @@ packages: dependencies: '@babel/runtime': 7.20.13 '@emotion/babel-plugin': 11.10.6 - '@emotion/cache': 11.10.5 + '@emotion/cache': 11.10.7 '@emotion/serialize': 1.1.1 '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) '@emotion/utils': 1.2.0 '@emotion/weak-memoize': 0.3.0 - '@types/react': 18.0.31 + '@types/react': 18.0.33 hoist-non-react-statics: 3.3.2 react: 18.2.0 dev: false @@ -2329,7 +2329,7 @@ packages: resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==} dev: false - /@emotion/styled@11.10.6(@emotion/react@11.10.6)(@types/react@18.0.31)(react@18.2.0): + /@emotion/styled@11.10.6(@emotion/react@11.10.6)(@types/react@18.0.33)(react@18.2.0): resolution: {integrity: sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 @@ -2342,11 +2342,11 @@ packages: '@babel/runtime': 7.20.13 '@emotion/babel-plugin': 11.10.6 '@emotion/is-prop-valid': 1.2.0 - '@emotion/react': 11.10.6(@types/react@18.0.31)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.33)(react@18.2.0) '@emotion/serialize': 1.1.1 '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) '@emotion/utils': 1.2.0 - '@types/react': 18.0.31 + '@types/react': 18.0.33 react: 18.2.0 dev: false @@ -2568,13 +2568,13 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.2.0(eslint@8.37.0): + /@eslint-community/eslint-utils@4.2.0(eslint@8.38.0): resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.37.0 + eslint: 8.38.0 eslint-visitor-keys: 3.4.0 dev: true @@ -2600,8 +2600,8 @@ packages: - supports-color dev: true - /@eslint/js@8.37.0: - resolution: {integrity: sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==} + /@eslint/js@8.38.0: + resolution: {integrity: sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -2984,12 +2984,12 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true - /@limegrass/eslint-plugin-import-alias@1.0.6(eslint@8.37.0): + /@limegrass/eslint-plugin-import-alias@1.0.6(eslint@8.38.0): resolution: {integrity: sha512-BtPmdHbL4NmkVh2wMnOboyOCrdLOpBqwwtBIsB0/giTiALw/UTHD9TyH4vVnbDOuWPZQgE6kKloJ9G77FApt7w==} peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 dependencies: - eslint: 8.37.0 + eslint: 8.38.0 find-up: 5.0.0 fs-extra: 10.1.0 micromatch: 4.0.5 @@ -2997,8 +2997,8 @@ packages: tsconfig-paths: 3.14.1 dev: true - /@mui/base@5.0.0-alpha.123(@types/react@18.0.31)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-pxzcAfET3I6jvWqS4kijiLMn1OmdMw+mGmDa0SqmDZo3bXXdvLhpCCPqCkULG3UykhvFCOcU5HclOX3JCA+Zhg==} + /@mui/base@5.0.0-alpha.124(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-I6M+FrjRCybQCr8I8JTu6L2MkUobSQFgNIpOJyDNKL5zq/73LvZIQXvsKumAzthVGvI1PYaarM9vGDrDYbumKA==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || 18 @@ -3010,10 +3010,10 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@emotion/is-prop-valid': 1.2.0 - '@mui/types': 7.2.3(@types/react@18.0.31) + '@mui/types': 7.2.3(@types/react@18.0.33) '@mui/utils': 5.11.13(react@18.2.0) '@popperjs/core': 2.11.7 - '@types/react': 18.0.31 + '@types/react': 18.0.33 clsx: 1.2.1 prop-types: 15.8.1 react: 18.2.0 @@ -3021,12 +3021,12 @@ packages: react-is: 18.2.0 dev: false - /@mui/core-downloads-tracker@5.11.15: - resolution: {integrity: sha512-Q0e2oBsjHyIWWj1wLzl14btunvBYC0yl+px7zL9R69tF87uenj6q72ieS369BJ6jxYpJwvXfR6/f+TC+ZUsKKg==} + /@mui/core-downloads-tracker@5.11.16: + resolution: {integrity: sha512-GxRfZ/HquQ/1nUc9qQVGReP6oOMS8/3QjPJ+23a7TMrxl2wjlmXrMNn7tRa30vZcGcDgEG+J0aseefUN0AoawQ==} dev: false - /@mui/icons-material@5.11.11(@mui/material@5.11.15)(@types/react@18.0.31)(react@18.2.0): - resolution: {integrity: sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==} + /@mui/icons-material@5.11.16(@mui/material@5.11.16)(@types/react@18.0.33)(react@18.2.0): + resolution: {integrity: sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==} engines: {node: '>=12.0.0'} peerDependencies: '@mui/material': ^5.0.0 @@ -3037,13 +3037,13 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@mui/material': 5.11.15(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.31)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.0.31 + '@mui/material': 5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.0.33 react: 18.2.0 dev: false - /@mui/material@5.11.15(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.31)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-E5RbLq9/OvRKmGyeZawdnmFBCvhKkI/Zqgr0xFqW27TGwKLxObq/BreJc6Uu5Sbv8Fjj34vEAbRx6otfOyxn5w==} + /@mui/material@5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-++glQqbZ3rMzOWB77yOvqRG+k8+scYTUKVWZpWff+GWsf6L10g9L2wgRhhAS8bDLuxCbXZlPNbSZowXDDw6z6Q==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -3060,17 +3060,17 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@emotion/react': 11.10.6(@types/react@18.0.31)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.31)(react@18.2.0) - '@mui/base': 5.0.0-alpha.123(@types/react@18.0.31)(react-dom@18.2.0)(react@18.2.0) - '@mui/core-downloads-tracker': 5.11.15 - '@mui/system': 5.11.15(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.31)(react@18.2.0) - '@mui/types': 7.2.3(@types/react@18.0.31) + '@emotion/react': 11.10.6(@types/react@18.0.33)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.33)(react@18.2.0) + '@mui/base': 5.0.0-alpha.124(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0) + '@mui/core-downloads-tracker': 5.11.16 + '@mui/system': 5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.33)(react@18.2.0) + '@mui/types': 7.2.3(@types/react@18.0.33) '@mui/utils': 5.11.13(react@18.2.0) - '@types/react': 18.0.31 + '@types/react': 18.0.33 '@types/react-transition-group': 4.4.5 clsx: 1.2.1 - csstype: 3.1.1 + csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -3078,7 +3078,7 @@ packages: react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) dev: false - /@mui/private-theming@5.11.13(@types/react@18.0.31)(react@18.2.0): + /@mui/private-theming@5.11.13(@types/react@18.0.33)(react@18.2.0): resolution: {integrity: sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -3090,13 +3090,13 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@mui/utils': 5.11.13(react@18.2.0) - '@types/react': 18.0.31 + '@types/react': 18.0.33 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/styled-engine@5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0): - resolution: {integrity: sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==} + /@mui/styled-engine@5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0): + resolution: {integrity: sha512-8dJRR/LqtGGaZN21p1vU9euwrKERlgtQIWyuzBKZ8/cuSlW5rIzlp46liP+Uh0+7d9NcHU0H4hBMoPt3ax64PA==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.4.1 @@ -3109,16 +3109,16 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@emotion/cache': 11.10.5 - '@emotion/react': 11.10.6(@types/react@18.0.31)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.31)(react@18.2.0) - csstype: 3.1.1 + '@emotion/cache': 11.10.7 + '@emotion/react': 11.10.6(@types/react@18.0.33)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.33)(react@18.2.0) + csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/styles@5.11.13(@types/react@18.0.31)(react@18.2.0): - resolution: {integrity: sha512-7NQTTdl8Z54qQBRPLi4cR9LG8tvXQdQE0kVEYVYc5A3e+IFH6xfGzNCNa0X4zz0f9JGaS8e6kK3YYOS2+KyJHg==} + /@mui/styles@5.11.16(@types/react@18.0.33)(react@18.2.0): + resolution: {integrity: sha512-KoJubDToD4jqslY4f2K7dzLQoEOWHWnh0qGp8ybFeQBAyffIcuBGEOYqe0YbsJKgU7/Qv+nTHtgvl/y6OS1w3w==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || 18 @@ -3129,12 +3129,12 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@emotion/hash': 0.9.0 - '@mui/private-theming': 5.11.13(@types/react@18.0.31)(react@18.2.0) - '@mui/types': 7.2.3(@types/react@18.0.31) + '@mui/private-theming': 5.11.13(@types/react@18.0.33)(react@18.2.0) + '@mui/types': 7.2.3(@types/react@18.0.33) '@mui/utils': 5.11.13(react@18.2.0) - '@types/react': 18.0.31 + '@types/react': 18.0.33 clsx: 1.2.1 - csstype: 3.1.1 + csstype: 3.1.2 hoist-non-react-statics: 3.3.2 jss: 10.10.0 jss-plugin-camel-case: 10.10.0 @@ -3148,8 +3148,8 @@ packages: react: 18.2.0 dev: false - /@mui/system@5.11.15(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.31)(react@18.2.0): - resolution: {integrity: sha512-vCatoWCTnAPquoNifHbqMCMnOElEbLosVUeW0FQDyjCq+8yMABD9E6iY0s14O7iq1wD+qqU7rFAuDIVvJ/AzzA==} + /@mui/system@5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.33)(react@18.2.0): + resolution: {integrity: sha512-JY7CNm7ik2Gr4kQpz1+C9N/f4ET3QjVBo/iaHcmlSOgjdxnOzFbv+vCdb1DMzBGew+UbqckppZpZwbgbrBE2Rw==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -3165,20 +3165,20 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@emotion/react': 11.10.6(@types/react@18.0.31)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.31)(react@18.2.0) - '@mui/private-theming': 5.11.13(@types/react@18.0.31)(react@18.2.0) - '@mui/styled-engine': 5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) - '@mui/types': 7.2.3(@types/react@18.0.31) + '@emotion/react': 11.10.6(@types/react@18.0.33)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.33)(react@18.2.0) + '@mui/private-theming': 5.11.13(@types/react@18.0.33)(react@18.2.0) + '@mui/styled-engine': 5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/types': 7.2.3(@types/react@18.0.33) '@mui/utils': 5.11.13(react@18.2.0) - '@types/react': 18.0.31 + '@types/react': 18.0.33 clsx: 1.2.1 - csstype: 3.1.1 + csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/types@7.2.3(@types/react@18.0.31): + /@mui/types@7.2.3(@types/react@18.0.33): resolution: {integrity: sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==} peerDependencies: '@types/react': '*' @@ -3186,7 +3186,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.0.31 + '@types/react': 18.0.33 dev: false /@mui/utils@5.11.13(react@18.2.0): @@ -3627,23 +3627,23 @@ packages: /@types/react-dom@18.0.11: resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} dependencies: - '@types/react': 18.0.31 + '@types/react': 18.0.33 dev: true /@types/react-is@17.0.3: resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} dependencies: - '@types/react': 18.0.31 + '@types/react': 18.0.33 dev: false /@types/react-transition-group@4.4.5: resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} dependencies: - '@types/react': 18.0.31 + '@types/react': 18.0.33 dev: false - /@types/react@18.0.31: - resolution: {integrity: sha512-EEG67of7DsvRDU6BLLI0p+k1GojDLz9+lZsnCpCRTa/lOokvyPBvp8S5x+A24hME3yyQuIipcP70KJ6H7Qupww==} + /@types/react@18.0.33: + resolution: {integrity: sha512-sHxzVxeanvQyQ1lr8NSHaj0kDzcNiGpILEVt69g9S31/7PfMvNCKLKcsHw4lYKjs3cGNJjXSP4mYzX43QlnjNA==} dependencies: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.2 @@ -3690,8 +3690,8 @@ packages: resolution: {integrity: sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w==} dev: true - /@typescript-eslint/eslint-plugin@5.57.0(@typescript-eslint/parser@5.57.0)(eslint@8.37.0)(typescript@5.0.3): - resolution: {integrity: sha512-itag0qpN6q2UMM6Xgk6xoHa0D0/P+M17THnr4SVgqn9Rgam5k/He33MA7/D7QoJcdMxHFyX7U9imaBonAX/6qA==} + /@typescript-eslint/eslint-plugin@5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.38.0)(typescript@5.0.4): + resolution: {integrity: sha512-1MeobQkQ9tztuleT3v72XmY0XuKXVXusAhryoLuU5YZ+mXoYKZP9SQ7Flulh1NX4DTjpGTc2b/eMu4u7M7dhnQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -3702,37 +3702,37 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.4.0 - '@typescript-eslint/parser': 5.57.0(eslint@8.37.0)(typescript@5.0.3) - '@typescript-eslint/scope-manager': 5.57.0 - '@typescript-eslint/type-utils': 5.57.0(eslint@8.37.0)(typescript@5.0.3) - '@typescript-eslint/utils': 5.57.0(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/parser': 5.57.1(eslint@8.38.0)(typescript@5.0.4) + '@typescript-eslint/scope-manager': 5.57.1 + '@typescript-eslint/type-utils': 5.57.1(eslint@8.38.0)(typescript@5.0.4) + '@typescript-eslint/utils': 5.57.1(eslint@8.38.0)(typescript@5.0.4) debug: 4.3.4 - eslint: 8.37.0 + eslint: 8.38.0 grapheme-splitter: 1.0.4 ignore: 5.2.0 natural-compare-lite: 1.4.0 semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.3) - typescript: 5.0.3 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/experimental-utils@5.30.6(eslint@8.37.0)(typescript@5.0.3): + /@typescript-eslint/experimental-utils@5.30.6(eslint@8.38.0)(typescript@5.0.4): resolution: {integrity: sha512-bqvT+0L8IjtW7MCrMgm9oVNxs4g7mESro1mm5c1/SNfTnHuFTf9OUX1WzVkTz75M9cp//UrTrSmGvK48NEKshQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@typescript-eslint/utils': 5.30.6(eslint@8.37.0)(typescript@5.0.3) - eslint: 8.37.0 + '@typescript-eslint/utils': 5.30.6(eslint@8.38.0)(typescript@5.0.4) + eslint: 8.38.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/parser@5.57.0(eslint@8.37.0)(typescript@5.0.3): - resolution: {integrity: sha512-orrduvpWYkgLCyAdNtR1QIWovcNZlEm6yL8nwH/eTxWLd8gsP+25pdLHYzL2QdkqrieaDwLpytHqycncv0woUQ==} + /@typescript-eslint/parser@5.57.1(eslint@8.38.0)(typescript@5.0.4): + resolution: {integrity: sha512-hlA0BLeVSA/wBPKdPGxoVr9Pp6GutGoY380FEhbVi0Ph4WNe8kLvqIRx76RSQt1lynZKfrXKs0/XeEk4zZycuA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3741,12 +3741,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.57.0 - '@typescript-eslint/types': 5.57.0 - '@typescript-eslint/typescript-estree': 5.57.0(typescript@5.0.3) + '@typescript-eslint/scope-manager': 5.57.1 + '@typescript-eslint/types': 5.57.1 + '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.4) debug: 4.3.4 - eslint: 8.37.0 - typescript: 5.0.3 + eslint: 8.38.0 + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true @@ -3759,16 +3759,16 @@ packages: '@typescript-eslint/visitor-keys': 5.30.6 dev: true - /@typescript-eslint/scope-manager@5.57.0: - resolution: {integrity: sha512-NANBNOQvllPlizl9LatX8+MHi7bx7WGIWYjPHDmQe5Si/0YEYfxSljJpoTyTWFTgRy3X8gLYSE4xQ2U+aCozSw==} + /@typescript-eslint/scope-manager@5.57.1: + resolution: {integrity: sha512-N/RrBwEUKMIYxSKl0oDK5sFVHd6VI7p9K5MyUlVYAY6dyNb/wHUqndkTd3XhpGlXgnQsBkRZuu4f9kAHghvgPw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.57.0 - '@typescript-eslint/visitor-keys': 5.57.0 + '@typescript-eslint/types': 5.57.1 + '@typescript-eslint/visitor-keys': 5.57.1 dev: true - /@typescript-eslint/type-utils@5.57.0(eslint@8.37.0)(typescript@5.0.3): - resolution: {integrity: sha512-kxXoq9zOTbvqzLbdNKy1yFrxLC6GDJFE2Yuo3KqSwTmDOFjUGeWSakgoXT864WcK5/NAJkkONCiKb1ddsqhLXQ==} + /@typescript-eslint/type-utils@5.57.1(eslint@8.38.0)(typescript@5.0.4): + resolution: {integrity: sha512-/RIPQyx60Pt6ga86hKXesXkJ2WOS4UemFrmmq/7eOyiYjYv/MUSHPlkhU6k9T9W1ytnTJueqASW+wOmW4KrViw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -3777,12 +3777,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.57.0(typescript@5.0.3) - '@typescript-eslint/utils': 5.57.0(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.4) + '@typescript-eslint/utils': 5.57.1(eslint@8.38.0)(typescript@5.0.4) debug: 4.3.4 - eslint: 8.37.0 - tsutils: 3.21.0(typescript@5.0.3) - typescript: 5.0.3 + eslint: 8.38.0 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true @@ -3792,12 +3792,12 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/types@5.57.0: - resolution: {integrity: sha512-mxsod+aZRSyLT+jiqHw1KK6xrANm19/+VFALVFP5qa/aiJnlP38qpyaTd0fEKhWvQk6YeNZ5LGwI1pDpBRBhtQ==} + /@typescript-eslint/types@5.57.1: + resolution: {integrity: sha512-bSs4LOgyV3bJ08F5RDqO2KXqg3WAdwHCu06zOqcQ6vqbTJizyBhuh1o1ImC69X4bV2g1OJxbH71PJqiO7Y1RuA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree@5.30.6(typescript@5.0.3): + /@typescript-eslint/typescript-estree@5.30.6(typescript@5.0.4): resolution: {integrity: sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -3812,14 +3812,14 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.3) - typescript: 5.0.3 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/typescript-estree@5.57.0(typescript@5.0.3): - resolution: {integrity: sha512-LTzQ23TV82KpO8HPnWuxM2V7ieXW8O142I7hQTxWIHDcCEIjtkat6H96PFkYBQqGFLW/G/eVVOB9Z8rcvdY/Vw==} + /@typescript-eslint/typescript-estree@5.57.1(typescript@5.0.4): + resolution: {integrity: sha512-A2MZqD8gNT0qHKbk2wRspg7cHbCDCk2tcqt6ScCFLr5Ru8cn+TCfM786DjPhqwseiS+PrYwcXht5ztpEQ6TFTw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -3827,19 +3827,19 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.57.0 - '@typescript-eslint/visitor-keys': 5.57.0 + '@typescript-eslint/types': 5.57.1 + '@typescript-eslint/visitor-keys': 5.57.1 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.3) - typescript: 5.0.3 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.30.6(eslint@8.37.0)(typescript@5.0.3): + /@typescript-eslint/utils@5.30.6(eslint@8.38.0)(typescript@5.0.4): resolution: {integrity: sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -3848,28 +3848,28 @@ packages: '@types/json-schema': 7.0.11 '@typescript-eslint/scope-manager': 5.30.6 '@typescript-eslint/types': 5.30.6 - '@typescript-eslint/typescript-estree': 5.30.6(typescript@5.0.3) - eslint: 8.37.0 + '@typescript-eslint/typescript-estree': 5.30.6(typescript@5.0.4) + eslint: 8.38.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0(eslint@8.37.0) + eslint-utils: 3.0.0(eslint@8.38.0) transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/utils@5.57.0(eslint@8.37.0)(typescript@5.0.3): - resolution: {integrity: sha512-ps/4WohXV7C+LTSgAL5CApxvxbMkl9B9AUZRtnEFonpIxZDIT7wC1xfvuJONMidrkB9scs4zhtRyIwHh4+18kw==} + /@typescript-eslint/utils@5.57.1(eslint@8.38.0)(typescript@5.0.4): + resolution: {integrity: sha512-kN6vzzf9NkEtawECqze6v99LtmDiUJCVpvieTFA1uL7/jDghiJGubGZ5csicYHU1Xoqb3oH/R5cN5df6W41Nfg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.2.0(eslint@8.37.0) + '@eslint-community/eslint-utils': 4.2.0(eslint@8.38.0) '@types/json-schema': 7.0.11 '@types/semver': 7.3.12 - '@typescript-eslint/scope-manager': 5.57.0 - '@typescript-eslint/types': 5.57.0 - '@typescript-eslint/typescript-estree': 5.57.0(typescript@5.0.3) - eslint: 8.37.0 + '@typescript-eslint/scope-manager': 5.57.1 + '@typescript-eslint/types': 5.57.1 + '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.4) + eslint: 8.38.0 eslint-scope: 5.1.1 semver: 7.3.8 transitivePeerDependencies: @@ -3885,11 +3885,11 @@ packages: eslint-visitor-keys: 3.4.0 dev: true - /@typescript-eslint/visitor-keys@5.57.0: - resolution: {integrity: sha512-ery2g3k0hv5BLiKpPuwYt9KBkAp2ugT6VvyShXdLOkax895EC55sP0Tx5L0fZaQueiK3fBLvHVvEl3jFS5ia+g==} + /@typescript-eslint/visitor-keys@5.57.1: + resolution: {integrity: sha512-RjQrAniDU0CEk5r7iphkm731zKlFiUjvcBS2yHAg8WWqFMCaCrD0rKEVOMUyMMcbGPZ0bPp56srkGWrgfZqLRA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.57.0 + '@typescript-eslint/types': 5.57.1 eslint-visitor-keys: 3.4.0 dev: true @@ -4168,8 +4168,8 @@ packages: engines: {node: '>=4'} dev: true - /axios@1.3.4: - resolution: {integrity: sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==} + /axios@1.3.5: + resolution: {integrity: sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==} dependencies: follow-redirects: 1.15.1 form-data: 4.0.0 @@ -4746,7 +4746,7 @@ packages: semver: 7.0.0 dev: true - /cosmiconfig-typescript-loader@4.0.0(@types/node@18.15.11)(cosmiconfig@8.0.0)(ts-node@10.9.0)(typescript@5.0.3): + /cosmiconfig-typescript-loader@4.0.0(@types/node@18.15.11)(cosmiconfig@8.0.0)(ts-node@10.9.0)(typescript@5.0.4): resolution: {integrity: sha512-cVpucSc2Tf+VPwCCR7SZzmQTQkPbkk4O01yXsYqXBIbjE1bhwqSyAgYQkRK1un4i0OPziTleqFhdkmOc4RQ/9g==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -4757,8 +4757,8 @@ packages: dependencies: '@types/node': 18.15.11 cosmiconfig: 8.0.0 - ts-node: 10.9.0(@types/node@18.15.11)(typescript@5.0.3) - typescript: 5.0.3 + ts-node: 10.9.0(@types/node@18.15.11)(typescript@5.0.4) + typescript: 5.0.4 dev: true /cosmiconfig@7.0.1: @@ -4842,6 +4842,10 @@ packages: /csstype@3.1.1: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + /csstype@3.1.2: + resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + dev: false + /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true @@ -5034,7 +5038,7 @@ packages: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: '@babel/runtime': 7.21.0 - csstype: 3.1.1 + csstype: 3.1.2 dev: false /domexception@4.0.0: @@ -5222,16 +5226,15 @@ packages: source-map: 0.6.1 dev: true - /eslint-config-prettier@8.8.0(eslint@8.37.0): + /eslint-config-prettier@8.8.0(eslint@8.38.0): resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} - hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.37.0 + eslint: 8.38.0 dev: true - /eslint-config-react-app@7.0.1(eslint-import-resolver-typescript@3.5.4)(eslint@8.37.0)(jest@29.5.0)(typescript@5.0.3): + /eslint-config-react-app@7.0.1(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0)(jest@29.5.0)(typescript@5.0.4): resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -5242,21 +5245,21 @@ packages: optional: true dependencies: '@babel/core': 7.18.6 - '@babel/eslint-parser': 7.18.2(@babel/core@7.18.6)(eslint@8.37.0) + '@babel/eslint-parser': 7.18.2(@babel/core@7.18.6)(eslint@8.38.0) '@rushstack/eslint-patch': 1.1.4 - '@typescript-eslint/eslint-plugin': 5.57.0(@typescript-eslint/parser@5.57.0)(eslint@8.37.0)(typescript@5.0.3) - '@typescript-eslint/parser': 5.57.0(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/eslint-plugin': 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.38.0)(typescript@5.0.4) + '@typescript-eslint/parser': 5.57.1(eslint@8.38.0)(typescript@5.0.4) babel-preset-react-app: 10.0.1 confusing-browser-globals: 1.0.11 - eslint: 8.37.0 - eslint-plugin-flowtype: 8.0.3(eslint@8.37.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.57.0)(eslint-import-resolver-typescript@3.5.4)(eslint@8.37.0) - eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.57.0)(eslint@8.37.0)(jest@29.5.0)(typescript@5.0.3) - eslint-plugin-jsx-a11y: 6.7.1(eslint@8.37.0) - eslint-plugin-react: 7.32.2(eslint@8.37.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.37.0) - eslint-plugin-testing-library: 5.5.1(eslint@8.37.0)(typescript@5.0.3) - typescript: 5.0.3 + eslint: 8.38.0 + eslint-plugin-flowtype: 8.0.3(eslint@8.38.0) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0) + eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.57.1)(eslint@8.38.0)(jest@29.5.0)(typescript@5.0.4) + eslint-plugin-jsx-a11y: 6.7.1(eslint@8.38.0) + eslint-plugin-react: 7.32.2(eslint@8.38.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.38.0) + eslint-plugin-testing-library: 5.5.1(eslint@8.38.0)(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - '@babel/plugin-syntax-flow' - '@babel/plugin-transform-react-jsx' @@ -5280,8 +5283,8 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript@3.5.4(eslint-plugin-import@2.27.5)(eslint@8.37.0): - resolution: {integrity: sha512-9xUpnedEmSfG57sN1UvWPiEhfJ8bPt0Wg2XysA7Mlc79iFGhmJtRUg9LxtkK81FhMUui0YuR2E8iUsVhePkh4A==} + /eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.57.1)(eslint-plugin-import@2.27.5)(eslint@8.38.0): + resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -5289,18 +5292,22 @@ packages: dependencies: debug: 4.3.4 enhanced-resolve: 5.12.0 - eslint: 8.37.0 - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.57.0)(eslint-import-resolver-typescript@3.5.4)(eslint@8.37.0) + eslint: 8.38.0 + eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0) get-tsconfig: 4.5.0 globby: 13.1.3 is-core-module: 2.11.0 is-glob: 4.0.3 synckit: 0.8.5 transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack - supports-color dev: true - /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.57.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.4)(eslint@8.37.0): + /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0): resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} peerDependencies: @@ -5321,16 +5328,16 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.57.0(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/parser': 5.57.1(eslint@8.38.0)(typescript@5.0.4) debug: 3.2.7 - eslint: 8.37.0 + eslint: 8.38.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.4(eslint-plugin-import@2.27.5)(eslint@8.37.0) + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.57.1)(eslint-plugin-import@2.27.5)(eslint@8.38.0) transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-flowtype@8.0.3(eslint@8.37.0): + /eslint-plugin-flowtype@8.0.3(eslint@8.38.0): resolution: {integrity: sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==} engines: {node: '>=12.0.0'} peerDependencies: @@ -5343,12 +5350,12 @@ packages: '@babel/plugin-transform-react-jsx': optional: true dependencies: - eslint: 8.37.0 + eslint: 8.38.0 lodash: 4.17.21 string-natural-compare: 3.0.1 dev: true - /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.57.0)(eslint-import-resolver-typescript@3.5.4)(eslint@8.37.0): + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0): resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} peerDependencies: @@ -5358,15 +5365,15 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.57.0(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/parser': 5.57.1(eslint@8.38.0)(typescript@5.0.4) array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.37.0 + eslint: 8.38.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.57.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.4)(eslint@8.37.0) + eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0) has: 1.0.3 is-core-module: 2.11.0 is-glob: 4.0.3 @@ -5381,7 +5388,7 @@ packages: - supports-color dev: true - /eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.57.0)(eslint@8.37.0)(jest@29.5.0)(typescript@5.0.3): + /eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.57.1)(eslint@8.38.0)(jest@29.5.0)(typescript@5.0.4): resolution: {integrity: sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} peerDependencies: @@ -5394,16 +5401,16 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.57.0(@typescript-eslint/parser@5.57.0)(eslint@8.37.0)(typescript@5.0.3) - '@typescript-eslint/experimental-utils': 5.30.6(eslint@8.37.0)(typescript@5.0.3) - eslint: 8.37.0 + '@typescript-eslint/eslint-plugin': 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.38.0)(typescript@5.0.4) + '@typescript-eslint/experimental-utils': 5.30.6(eslint@8.38.0)(typescript@5.0.4) + eslint: 8.38.0 jest: 29.5.0(@types/node@18.15.11)(ts-node@10.9.0) transitivePeerDependencies: - supports-color - typescript dev: true - /eslint-plugin-jsx-a11y@6.7.1(eslint@8.37.0): + /eslint-plugin-jsx-a11y@6.7.1(eslint@8.38.0): resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} engines: {node: '>=4.0'} peerDependencies: @@ -5418,7 +5425,7 @@ packages: axobject-query: 3.1.1 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 8.37.0 + eslint: 8.38.0 has: 1.0.3 jsx-ast-utils: 3.3.3 language-tags: 1.0.5 @@ -5428,7 +5435,7 @@ packages: semver: 6.3.0 dev: true - /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0)(eslint@8.37.0)(prettier@2.8.7): + /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0)(eslint@8.38.0)(prettier@2.8.7): resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} engines: {node: '>=12.0.0'} peerDependencies: @@ -5439,22 +5446,22 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.37.0 - eslint-config-prettier: 8.8.0(eslint@8.37.0) + eslint: 8.38.0 + eslint-config-prettier: 8.8.0(eslint@8.38.0) prettier: 2.8.7 prettier-linter-helpers: 1.0.0 dev: true - /eslint-plugin-react-hooks@4.6.0(eslint@8.37.0): + /eslint-plugin-react-hooks@4.6.0(eslint@8.38.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: - eslint: 8.37.0 + eslint: 8.38.0 dev: true - /eslint-plugin-react@7.32.2(eslint@8.37.0): + /eslint-plugin-react@7.32.2(eslint@8.38.0): resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} engines: {node: '>=4'} peerDependencies: @@ -5464,7 +5471,7 @@ packages: array.prototype.flatmap: 1.3.1 array.prototype.tosorted: 1.1.1 doctrine: 2.1.0 - eslint: 8.37.0 + eslint: 8.38.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.3 minimatch: 3.1.2 @@ -5478,14 +5485,14 @@ packages: string.prototype.matchall: 4.0.8 dev: true - /eslint-plugin-testing-library@5.5.1(eslint@8.37.0)(typescript@5.0.3): + /eslint-plugin-testing-library@5.5.1(eslint@8.38.0)(typescript@5.0.4): resolution: {integrity: sha512-plLEkkbAKBjPxsLj7x4jNapcHAg2ernkQlKKrN2I8NrQwPISZHyCUNvg5Hv3EDqOQReToQb5bnqXYbkijJPE/g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} peerDependencies: eslint: ^7.5.0 || ^8.0.0 dependencies: - '@typescript-eslint/utils': 5.57.0(eslint@8.37.0)(typescript@5.0.3) - eslint: 8.37.0 + '@typescript-eslint/utils': 5.57.1(eslint@8.38.0)(typescript@5.0.4) + eslint: 8.38.0 transitivePeerDependencies: - supports-color - typescript @@ -5507,13 +5514,13 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils@3.0.0(eslint@8.37.0): + /eslint-utils@3.0.0(eslint@8.38.0): resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.37.0 + eslint: 8.38.0 eslint-visitor-keys: 2.1.0 dev: true @@ -5527,14 +5534,14 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.37.0: - resolution: {integrity: sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==} + /eslint@8.38.0: + resolution: {integrity: sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@eslint-community/eslint-utils': 4.2.0(eslint@8.37.0) + '@eslint-community/eslint-utils': 4.2.0(eslint@8.38.0) '@eslint-community/regexpp': 4.4.0 '@eslint/eslintrc': 2.0.2 - '@eslint/js': 8.37.0 + '@eslint/js': 8.38.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -6204,8 +6211,8 @@ packages: - encoding dev: false - /i18next@22.4.13: - resolution: {integrity: sha512-GX7flMHRRqQA0I1yGLmaZ4Hwt1JfLqagk8QPDPZsqekbKtXsuIngSVWM/s3SLgNkrEXjA+0sMGNuOEkkmyqmWg==} + /i18next@22.4.14: + resolution: {integrity: sha512-VtLPtbdwGn0+DAeE00YkiKKXadkwg+rBUV+0v8v0ikEjwdiJ0gmYChVE4GIa9HXymY6wKapkL93vGT7xpq6aTw==} dependencies: '@babel/runtime': 7.21.0 dev: false @@ -6674,7 +6681,6 @@ packages: /jest-cli@29.5.0(@types/node@18.15.11)(ts-node@10.9.0): resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: @@ -6734,7 +6740,7 @@ packages: pretty-format: 29.5.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.0(@types/node@18.15.11)(typescript@5.0.3) + ts-node: 10.9.0(@types/node@18.15.11)(typescript@5.0.4) transitivePeerDependencies: - supports-color dev: true @@ -7147,7 +7153,6 @@ packages: /jest@29.5.0(@types/node@18.15.11)(ts-node@10.9.0): resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: @@ -7331,7 +7336,7 @@ packages: resolution: {integrity: sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==} dependencies: '@babel/runtime': 7.21.0 - csstype: 3.1.1 + csstype: 3.1.2 is-in-browser: 1.1.3 tiny-warning: 1.0.3 dev: false @@ -8141,7 +8146,7 @@ packages: react: 18.2.0 scheduler: 0.23.0 - /react-i18next@12.2.0(i18next@22.4.13)(react-dom@18.2.0)(react@18.2.0): + /react-i18next@12.2.0(i18next@22.4.14)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-5XeVgSygaGfyFmDd2WcXvINRw2WEC1XviW1LXY/xLOEMzsCFRwKqfnHN+hUjla8ZipbVJR27GCMSuTr0BhBBBQ==} peerDependencies: i18next: '>= 19.0.0' @@ -8156,7 +8161,7 @@ packages: dependencies: '@babel/runtime': 7.20.13 html-parse-stringify: 3.0.1 - i18next: 22.4.13 + i18next: 22.4.14 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -8990,9 +8995,8 @@ packages: engines: {node: '>=8'} dev: true - /ts-node@10.9.0(@types/node@18.15.11)(typescript@5.0.3): + /ts-node@10.9.0(@types/node@18.15.11)(typescript@5.0.4): resolution: {integrity: sha512-bunW18GUyaCSYRev4DPf4SQpom3pWH29wKl0sDk5zE7ze19RImEVhCW7K4v3hHKkUyfWotU08ToE2RS+Y49aug==} - hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -9016,22 +9020,21 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.0.3 + typescript: 5.0.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true - /tsconfck@2.0.1(typescript@5.0.3): - resolution: {integrity: sha512-/ipap2eecmVBmBlsQLBRbUmUNFwNJV/z2E+X0FPtHNjPwroMZQ7m39RMaCywlCulBheYXgMdUlWDd9rzxwMA0Q==} - engines: {node: ^14.13.1 || ^16 || >=18, pnpm: ^7.0.1} - hasBin: true + /tsconfck@2.1.1(typescript@5.0.4): + resolution: {integrity: sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==} + engines: {node: ^14.13.1 || ^16 || >=18} peerDependencies: - typescript: ^4.3.5 + typescript: ^4.3.5 || ^5.0.0 peerDependenciesMeta: typescript: optional: true dependencies: - typescript: 5.0.3 + typescript: 5.0.4 dev: true /tsconfig-paths@3.14.1: @@ -9051,14 +9054,14 @@ packages: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} dev: true - /tsutils@3.21.0(typescript@5.0.3): + /tsutils@3.21.0(typescript@5.0.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.0.3 + typescript: 5.0.4 dev: true /type-check@0.3.2: @@ -9116,8 +9119,8 @@ packages: is-typedarray: 1.0.0 dev: true - /typescript@5.0.3: - resolution: {integrity: sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==} + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} engines: {node: '>=12.20'} dev: true @@ -9187,7 +9190,6 @@ packages: /update-browserslist-db@1.0.10(browserslist@4.21.4): resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} - hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: @@ -9235,7 +9237,7 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite-plugin-eslint@1.8.1(eslint@8.37.0)(vite@4.2.1): + /vite-plugin-eslint@1.8.1(eslint@8.38.0)(vite@4.2.1): resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} peerDependencies: eslint: '>=7' @@ -9243,7 +9245,7 @@ packages: dependencies: '@rollup/pluginutils': 4.2.1 '@types/eslint': 8.4.5 - eslint: 8.37.0 + eslint: 8.38.0 rollup: 2.78.0 vite: 4.2.1(@types/node@18.15.11) dev: true @@ -9275,8 +9277,8 @@ packages: - supports-color dev: true - /vite-tsconfig-paths@4.0.7(typescript@5.0.3)(vite@4.2.1): - resolution: {integrity: sha512-MwIYaby6kcbQGZqMH+gAK6h0UYQGOkjsuAgw4q6bP/5vWkn8VKvnmLuCQHA2+IzHAJHnE8OFTO4lnJLFMf9+7Q==} + /vite-tsconfig-paths@4.0.8(typescript@5.0.4)(vite@4.2.1): + resolution: {integrity: sha512-p04zH+Ey+NT78571x0pdX7nVRIJSlmKVvYryFglSWOK3Hc72eDL0+JJfbyQiugaIBApJkaEqbBQvqpsFZOSVGg==} peerDependencies: vite: '*' peerDependenciesMeta: @@ -9285,7 +9287,7 @@ packages: dependencies: debug: 4.3.4 globrex: 0.1.2 - tsconfck: 2.0.1(typescript@5.0.3) + tsconfck: 2.1.1(typescript@5.0.4) vite: 4.2.1(@types/node@18.15.11) transitivePeerDependencies: - supports-color @@ -9295,7 +9297,6 @@ packages: /vite@4.2.1(@types/node@18.15.11): resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==} engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true peerDependencies: '@types/node': '>= 14' less: '*'