Compare commits

..

13 Commits

Author SHA1 Message Date
Jonas Letzbor dd673e0e82
Change from basic auth to header authentication 2023-06-24 14:08:18 +02:00
Jonas Letzbor e9a383be0c
Add option to ban user by ip instead of username 2023-06-23 22:07:23 +02:00
Jonas Letzbor c13e0e12ea
Implement gRPC endpoint for envoy 2023-06-23 21:21:40 +02:00
renovate[bot] 2128969afc
build(deps): update dependency @mui/material to v5.13.6 (#5598)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-24 02:07:13 +10:00
renovate[bot] bb1e853b9f
build(deps): update module github.com/ory/x to v0.0.563 (#5596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-22 23:17:19 +10:00
renovate[bot] 0a92f13f15
build(deps): update dependency i18next to v23.2.3 (#5594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-22 21:41:41 +10:00
renovate[bot] 6af53a66dd
build(deps): update dependency react-i18next to v13.0.1 (#5595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-22 16:38:41 +10:00
renovate[bot] fb68177b23
build(deps): update traefik docker tag to v2.10.3 (#5591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-22 00:00:54 +10:00
renovate[bot] 035070414b
build(deps): update dependency i18next to v23.2.2 (#5593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-21 20:54:45 +10:00
renovate[bot] 2671151cae
build(deps): update dependency esbuild to v0.18.6 (#5590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-21 10:02:07 +10:00
renovate[bot] 41e9c51245
build(deps): update dependency i18next to v23.2.1 (#5572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-20 16:34:06 +00:00
renovate[bot] 360b672387
build(deps): update module github.com/valyala/fasthttp to v1.48.0 (#5589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-20 21:18:05 +10:00
renovate[bot] 0f9a8994c2
build(deps): update dependency esbuild to v0.18.5 (#5588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-20 15:48:56 +10:00
44 changed files with 1219 additions and 444 deletions

View File

@ -7,3 +7,4 @@
!entrypoint.sh
!healthcheck.sh
!.healthcheck.env
!dist/public_html/

95
MyNotes.md 100644
View File

@ -0,0 +1,95 @@
# Ausführen
Um die Anwendung lokal auszuführen, können die folgenden Befehle verwendet werden.
```
export GOPATH=/tmp
source bootstrap.sh
authelia-scripts suites setup Standalone
```
Nun sollte der "Haupt-Enpunkt" unter `https://home.example.com:8080` und die API unter `https://authelia.example.com:9091` erreichbar sein. Achtung: es wird ein selbstsigniertes Zertifikat verwendet!
Mithilfe der Hot-Reload kann jetzt gecoded werden.
---
Nach der Entwicklung kann die Testumgebung durch den folgenden Befehl wieder zurückgesetzt werden.
```
go run ./cmd/authelia-scripts/ suites teardown Standalone
```
## Benutzerdefinierte Zertifikate
Um ein benutzerdefiniertes Zertifikat für die Ausführung zu verwenden, muss die Datai `public.backend.crt` und `private.bakend.pem` unter [diesem](/internal/suites/common/pki/) Verzeichnis abgeändert werden.
Um die Gültigkeit zu testen, kann der folgendende Befehl ausgeführt werden.
```
curl https://auth.rpjosh.de:9091 --connect-to 'auth.rpjosh.de:9091:authelia.example.com:9091'
```
## Externe erreichbarkeit
Im aktuellen Zustand sind die Endpunkte nur unter den Docker internen IP-Adressen erreichbar. Daher muss noch ein NAT Regel angelegt werden.
```
ip=$(ping -c 1 authelia.example.com | gawk -F'[()]' '/PING/{print $2}')
sudo iptables -t nat -A PREROUTING -p tcp --dport 9091 -d 192.168.0.15 -j DNAT --to-destination 192.168.240.50:9091 -m comment --comment "Authelia-Test"
sudo iptables -t nat -A PREROUTING -p tcp --dport 9092 -d 192.168.0.15 -j DNAT --to-destination 192.168.240.50:9092 -m comment --comment "Authelia-Test"
sudo iptables -t nat -I OUTPUT -p tcp -o lo --dport 9091 -j DNAT --to-destination 192.168.240.50:9091
```
# Customizations
Für das Starten des *gRPC* Servers müssen die folgenden Abhängigkeiten installiert werden.
```
go get github.com/envoyproxy/go-control-plane
go get github.com/envoyproxy/go-control-plane/envoy/config/core/v3
go get github.com/gogo/googleapis/google/rpc
go get google.golang.org/grpc
```
## Konfiguration ändern
Wenn die Konfiguration geändert wurde, müssen die Keys zur Validierung wieder erneut gebaut werden.
```
go run ./cmd/authelia-gen code keys
```
## Mocks abgeändert
Wenn interfaces von den Mocks geändert werden, muss folgendes wieder ausgeführt werden:
```
export PATH=$PATH:$(go env GOPATH)/bin
go generate ./...
```
## Bauen
Um ein Docker Image für authelia zu bauen, müssen die folgenden Befehle ausgeführt werden.
```sh
# Dieser Befehle funktionieren aktuell nicht
authelia-scripts docker build
authelia-scripts build
# => Manuell bauen
export CC=musl-gcc
authelia-scripts build
cp -r dist/public_html internal/server/
go build -buildmode=pie -ldflags "-linkmode=external -s -w" -trimpath -buildmode=pie -o authelia ./cmd/authelia
mv authelia authelia-linux-amd64-musl
# Build docker image
docker build --tag git.rpjosh.de/rpjosh/authelia/authelia:4.38.0-dev .
docker push git.rpjosh.de/rpjosh/authelia/authelia:4.38.0-dev
# Cleanup
rm -rf internal/server/public_html/ ./authelia-linux-amd64-musl
```
# gRCP
Um einen gRCP Endpunkt nutzen zu können, brauch mein eine *.proto* Datei. Für Envoy sieht diese wie in [dieser Datei](/ext-auth.proto) folgendermaßen aus.

View File

@ -62,6 +62,10 @@ server:
## This is disabled by default if either /app/.healthcheck.env or /app/healthcheck.sh do not exist.
disable_healthcheck: false
## If a request over the insecure http protocol is received from authelias gRPC endpoint (only for envoy),
## the request is by default redirected to the matching https URL (301)
disable_autho_https_redirect: false
## Authelia by default doesn't accept TLS communication on the server port. This section overrides this behaviour.
tls:
## The path to the DER base64/PEM format private key.
@ -73,6 +77,17 @@ server:
## The list of certificates for client authentication.
client_certificates: []
## Enable the support for gRPC ext authentication for envoy. If TLS is enabled in the above section,
## the defined certificates will also be used for the gRPC endpoint
grpc:
address: 'tcp://:9092'
# Even if TLS is configured in the server setting (under server.tls), the grcp server won't use TLS
disableTLS: false
# By default the ban is issued for the user. With this options the IP instead of the user will be banned
use_ip_for_ban: true
## Server headers configuration/customization.
headers:

View File

@ -0,0 +1,144 @@
syntax = "proto3";
package envoy.service.auth.v3;
import "envoy/config/core/v3/base.proto";
import "envoy/service/auth/v3/attribute_context.proto";
import "envoy/type/v3/http_status.proto";
import "google/protobuf/struct.proto";
import "google/rpc/status.proto";
import "envoy/annotations/deprecation.proto";
import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
option java_package = "io.envoyproxy.envoy.service.auth.v3";
option java_outer_classname = "ExternalAuthProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3;authv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;
// [#protodoc-title: Authorization service]
// The authorization service request messages used by external authorization :ref:`network filter
// <config_network_filters_ext_authz>` and :ref:`HTTP filter <config_http_filters_ext_authz>`.
// A generic interface for performing authorization check on incoming
// requests to a networked service.
service Authorization {
// Performs authorization check based on the attributes associated with the
// incoming request, and returns status `OK` or not `OK`.
rpc Check(CheckRequest) returns (CheckResponse) {
}
}
message CheckRequest {
option (udpa.annotations.versioning).previous_message_type = "envoy.service.auth.v2.CheckRequest";
// The request attributes.
AttributeContext attributes = 1;
}
// HTTP attributes for a denied response.
message DeniedHttpResponse {
option (udpa.annotations.versioning).previous_message_type =
"envoy.service.auth.v2.DeniedHttpResponse";
// This field allows the authorization service to send an HTTP response status code to the
// downstream client. If not set, Envoy sends ``403 Forbidden`` HTTP status code by default.
type.v3.HttpStatus status = 1;
// This field allows the authorization service to send HTTP response headers
// to the downstream client. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>` defaults to
// false when used in this message.
repeated config.core.v3.HeaderValueOption headers = 2;
// This field allows the authorization service to send a response body data
// to the downstream client.
string body = 3;
}
// HTTP attributes for an OK response.
// [#next-free-field: 9]
message OkHttpResponse {
option (udpa.annotations.versioning).previous_message_type =
"envoy.service.auth.v2.OkHttpResponse";
// HTTP entity headers in addition to the original request headers. This allows the authorization
// service to append, to add or to override headers from the original request before
// dispatching it to the upstream. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>` defaults to
// false when used in this message. By setting the ``append`` field to ``true``,
// the filter will append the correspondent header value to the matched request header.
// By leaving ``append`` as false, the filter will either add a new header, or override an existing
// one if there is a match.
repeated config.core.v3.HeaderValueOption headers = 2;
// HTTP entity headers to remove from the original request before dispatching
// it to the upstream. This allows the authorization service to act on auth
// related headers (like ``Authorization``), process them, and consume them.
// Under this model, the upstream will either receive the request (if it's
// authorized) or not receive it (if it's not), but will not see headers
// containing authorization credentials.
//
// Pseudo headers (such as ``:authority``, ``:method``, ``:path`` etc), as well as
// the header ``Host``, may not be removed as that would make the request
// malformed. If mentioned in ``headers_to_remove`` these special headers will
// be ignored.
//
// When using the HTTP service this must instead be set by the HTTP
// authorization service as a comma separated list like so:
// ``x-envoy-auth-headers-to-remove: one-auth-header, another-auth-header``.
repeated string headers_to_remove = 5;
// This field has been deprecated in favor of :ref:`CheckResponse.dynamic_metadata
// <envoy_v3_api_field_service.auth.v3.CheckResponse.dynamic_metadata>`. Until it is removed,
// setting this field overrides :ref:`CheckResponse.dynamic_metadata
// <envoy_v3_api_field_service.auth.v3.CheckResponse.dynamic_metadata>`.
google.protobuf.Struct dynamic_metadata = 3
[deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];
// This field allows the authorization service to send HTTP response headers
// to the downstream client on success. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>`
// defaults to false when used in this message.
repeated config.core.v3.HeaderValueOption response_headers_to_add = 6;
// This field allows the authorization service to set (and overwrite) query
// string parameters on the original request before it is sent upstream.
repeated config.core.v3.QueryParameter query_parameters_to_set = 7;
// This field allows the authorization service to specify which query parameters
// should be removed from the original request before it is sent upstream. Each
// element in this list is a case-sensitive query parameter name to be removed.
repeated string query_parameters_to_remove = 8;
}
// Intended for gRPC and Network Authorization servers ``only``.
message CheckResponse {
option (udpa.annotations.versioning).previous_message_type =
"envoy.service.auth.v2.CheckResponse";
// Status ``OK`` allows the request. Any other status indicates the request should be denied, and
// for HTTP filter, if not overridden by :ref:`denied HTTP response status <envoy_v3_api_field_service.auth.v3.DeniedHttpResponse.status>`
// Envoy sends ``403 Forbidden`` HTTP status code by default.
google.rpc.Status status = 1;
// An message that contains HTTP response attributes. This message is
// used when the authorization service needs to send custom responses to the
// downstream client or, to modify/add request headers being dispatched to the upstream.
oneof http_response {
// Supplies http attributes for a denied response.
DeniedHttpResponse denied_response = 2;
// Supplies http attributes for an ok response.
OkHttpResponse ok_response = 3;
}
// Optional response metadata that will be emitted as dynamic metadata to be consumed by the next
// filter. This metadata lives in a namespace specified by the canonical name of extension filter
// that requires it:
//
// - :ref:`envoy.filters.http.ext_authz <config_http_filters_ext_authz_dynamic_metadata>` for HTTP filter.
// - :ref:`envoy.filters.network.ext_authz <config_network_filters_ext_authz_dynamic_metadata>` for network filter.
google.protobuf.Struct dynamic_metadata = 4;
}

View File

@ -45,7 +45,7 @@ services:
- TZ=Australia/Melbourne
traefik:
image: traefik:v2.10.1
image: traefik:v2.10.3
container_name: traefik
volumes:
- ./traefik:/etc/traefik

View File

@ -32,7 +32,7 @@ services:
- TZ=Australia/Melbourne
traefik:
image: traefik:v2.10.1
image: traefik:v2.10.3
container_name: traefik
volumes:
- ./traefik:/etc/traefik

22
go.mod
View File

@ -33,7 +33,7 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/ory/fosite v0.44.0
github.com/ory/herodot v0.10.2
github.com/ory/x v0.0.561
github.com/ory/x v0.0.563
github.com/otiai10/copy v1.12.0
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.4.0
@ -43,7 +43,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
github.com/trustelem/zxcvbn v1.0.1
github.com/valyala/fasthttp v1.47.0
github.com/valyala/fasthttp v1.48.0
github.com/wneessen/go-mail v0.3.9
golang.org/x/net v0.11.0
golang.org/x/sync v0.3.0
@ -59,6 +59,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cristalhq/jwt/v4 v4.0.2 // indirect
github.com/dave/jennifer v1.6.0 // indirect
@ -68,12 +69,16 @@ require (
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/ecordell/optgen v0.0.6 // indirect
github.com/envoyproxy/go-control-plane v0.11.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.1 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/go-crypt/x v0.2.1 // indirect
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/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.0 // 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
@ -97,7 +102,7 @@ require (
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/redis/go-redis/v9 v9.0.4 // indirect
@ -119,12 +124,13 @@ require (
github.com/ysmood/leakless v0.8.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/tools v0.8.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect
google.golang.org/grpc v1.56.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

38
go.sum
View File

@ -72,6 +72,8 @@ 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/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/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=
@ -113,7 +115,11 @@ 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.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM=
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.1 h1:kt9FtLiooDc0vbwTLhdg3dyNX1K9Qwa1EK9LcD4jVUQ=
github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
@ -154,13 +160,19 @@ github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8Rkn
github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w=
github.com/go-webauthn/webauthn v0.5.0 h1:Tbmp37AGIhYbQmcy2hEffo3U3cgPClqvxJ7cLUnF7Rc=
github.com/go-webauthn/webauthn v0.5.0/go.mod h1:0CBq/jNfPS9l033j4AxMk8K8MluiMsde9uGNSPFLEVE=
github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -267,6 +279,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
@ -331,8 +344,8 @@ github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTs
github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs=
github.com/ory/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E=
github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc=
github.com/ory/x v0.0.561 h1:SvNDGd6OhvAFl4XiPnYJuLCtR6iLxZJcF1Vzlo1IFTM=
github.com/ory/x v0.0.561/go.mod h1:kup4ebSC4SzwU6KPZJ4G60UR3EEsHxJ0apQVflVw5yQ=
github.com/ory/x v0.0.563 h1:T77Bjt6ALMZmUJIsQ5UEkzDBCD+8vxfQlBCU1Y39uDk=
github.com/ory/x v0.0.563/go.mod h1:kup4ebSC4SzwU6KPZJ4G60UR3EEsHxJ0apQVflVw5yQ=
github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY=
github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
@ -363,6 +376,8 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
@ -436,8 +451,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc=
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/wneessen/go-mail v0.3.9 h1:Q4DbCk3htT5DtDWKeMgNXCiHc4bBY/vv/XQPT6XDXzc=
github.com/wneessen/go-mail v0.3.9/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@ -579,6 +594,10 @@ 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.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
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=
@ -707,6 +726,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@ -716,6 +736,7 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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=
@ -724,6 +745,7 @@ golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
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/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
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=
@ -793,6 +815,10 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
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/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw=
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
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=
@ -812,6 +838,10 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE=
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99 h1:qA8rMbz1wQ4DOFfM2ouD29DG9aHWBm6ZOy9BGxiUMmY=
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=

View File

@ -208,10 +208,6 @@ func domainToPrefixSuffix(domain string) (prefix, suffix string) {
return parts[0], strings.Join(parts[1:], ".")
}
func NewSubjects(subjectRules [][]string) (subjects []AccessControlSubjects) {
return schemaSubjectsToACL(subjectRules)
}
// IsAuthLevelSufficient returns true if the current authenticationLevel is above the authorizationLevel.
func IsAuthLevelSufficient(authenticationLevel authentication.Level, authorizationLevel Level) bool {
switch authorizationLevel {

View File

@ -16,6 +16,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/server"
@ -33,6 +34,17 @@ func NewServerService(name string, server *fasthttp.Server, listener net.Listene
}
}
// NewGRCPServerService creates a new ServerService with the appropriate logger etc.
func NewGRCPServerService(name string, server *grpc.Server, listener net.Listener, isTLS bool, log *logrus.Logger) (service *GRCPServerService) {
return &GRCPServerService{
name: name,
server: server,
listener: listener,
isTLS: isTLS,
log: log.WithFields(map[string]any{logFieldService: serviceTypeServer, serviceTypeServer: name}),
}
}
// NewFileWatcherService creates a new FileWatcherService with the appropriate logger etc.
func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus.Logger) (service *FileWatcherService, err error) {
if path == "" {
@ -161,6 +173,54 @@ func (service *ServerService) Log() *logrus.Entry {
return service.log
}
// GRCPServerService is a Service which runs a gRCP server.
type GRCPServerService struct {
name string
server *grpc.Server
isTLS bool
listener net.Listener
log *logrus.Entry
}
// ServiceType returns the service type for this service, which is always 'server'.
func (service *GRCPServerService) ServiceType() string {
return serviceTypeServer
}
// ServiceName returns the individual name for this service.
func (service *GRCPServerService) ServiceName() string {
return service.name
}
// Run the ServerService.
func (service *GRCPServerService) Run() (err error) {
defer func() {
if r := recover(); r != nil {
service.log.WithError(recoverErr(r)).Error("Critical error caught (recovered)")
}
}()
service.log.Infof(fmtLogServerListening, connectionType(service.isTLS), service.listener.Addr().String())
if err = service.server.Serve(service.listener); err != nil {
service.log.WithError(err).Error("Error returned attempting to serve requests")
return err
}
return nil
}
// Shutdown the ServerService.
func (service *GRCPServerService) Shutdown() {
service.server.Stop()
}
// Log returns the *logrus.Entry of the ServerService.
func (service *GRCPServerService) Log() *logrus.Entry {
return service.log
}
// FileWatcherService is a Service that watches files for changes.
type FileWatcherService struct {
name string
@ -272,6 +332,19 @@ func svcSvrMetricsFunc(ctx *CmdCtx) (service Service) {
return service
}
func svcSvrGRPCFunc(ctx *CmdCtx) (service Service) {
switch svr, listener, isTLS, err := server.CreateGRPCServer(ctx.config, ctx.providers); {
case err != nil:
ctx.log.WithError(err).Fatal("Create Server Service (gRPC) returned error")
case svr != nil && listener != nil:
service = NewGRCPServerService("gRCP", svr, listener, isTLS, ctx.log)
default:
ctx.log.Debug("Create Server Service (gRPC) skipped")
}
return service
}
func svcWatcherUsersFunc(ctx *CmdCtx) (service Service) {
var err error
@ -312,7 +385,7 @@ func servicesRun(ctx *CmdCtx) {
)
for _, serviceFunc := range []func(ctx *CmdCtx) Service{
svcSvrMainFunc, svcSvrMetricsFunc,
svcSvrMainFunc, svcSvrGRPCFunc, svcSvrMetricsFunc,
svcWatcherUsersFunc,
} {
if service := serviceFunc(ctx); service != nil {

View File

@ -35,25 +35,11 @@ type OpenIDConnect struct {
Clients []OpenIDConnectClient `koanf:"clients"`
Policies map[string]OpenIDConnectPolicy `koanf:"policies"`
Discovery OpenIDConnectDiscovery // MetaData value. Not configurable by users.
}
type OpenIDConnectPolicy struct {
DefaultPolicy string `koanf:"default_policy"`
Rules []OpenIDConnectPolicyRule `koanf:"rules"`
}
type OpenIDConnectPolicyRule struct {
Policy string `koanf:"policy"`
Subjects [][]string `koanf:"subject"`
}
// OpenIDConnectDiscovery is information discovered during validation reused for the discovery handlers.
type OpenIDConnectDiscovery struct {
Policies []string
DefaultKeyIDs map[string]string
DefaultKeyID string
ResponseObjectSigningKeyIDs []string

View File

@ -72,11 +72,6 @@ var Keys = []string{
"identity_providers.oidc.clients[].public_keys.values[].key",
"identity_providers.oidc.clients[].public_keys.values[].certificate_chain",
"identity_providers.oidc.clients[]",
"identity_providers.oidc.policies",
"identity_providers.oidc.policies.*.default_policy",
"identity_providers.oidc.policies.*.rules",
"identity_providers.oidc.policies.*.rules[].policy",
"identity_providers.oidc.policies.*.rules[].subject",
"identity_providers.oidc",
"authentication_backend.password_reset.disable",
"authentication_backend.password_reset.custom_url",
@ -269,6 +264,8 @@ var Keys = []string{
"server.address",
"server.asset_path",
"server.disable_healthcheck",
"server.disable_autho_https_redirect",
"server.use_ip_for_ban",
"server.tls.certificate",
"server.tls.key",
"server.tls.client_certificates",
@ -279,6 +276,8 @@ var Keys = []string{
"server.endpoints.authz.*.implementation",
"server.endpoints.authz.*.authn_strategies",
"server.endpoints.authz.*.authn_strategies[].name",
"server.grpc.address",
"server.grpc.disableTLS",
"server.buffers.read",
"server.buffers.write",
"server.timeouts.read",

View File

@ -10,10 +10,13 @@ type ServerConfiguration struct {
Address *AddressTCP `koanf:"address"`
AssetPath string `koanf:"asset_path"`
DisableHealthcheck bool `koanf:"disable_healthcheck"`
DisableAutoHttpsRedirect bool `koanf:"disable_autho_https_redirect"`
UseIPInsteadOfUserForBan bool `koanf:"use_ip_for_ban"`
TLS ServerTLS `koanf:"tls"`
Headers ServerHeaders `koanf:"headers"`
Endpoints ServerEndpoints `koanf:"endpoints"`
GRPC ServerGRPC `koanf:"grpc"`
Buffers ServerBuffers `koanf:"buffers"`
Timeouts ServerTimeouts `koanf:"timeouts"`
@ -60,6 +63,15 @@ type ServerHeaders struct {
CSPTemplate string `koanf:"csp_template"`
}
// ServerGRCP contains configuration options for the gRCP server.
type ServerGRPC struct {
// Address with port to listen on. If this field is empty, no grcp server
// will be spawned.
Address *AddressTCP `koanf:"address"`
DisableTLS bool `koanf:"disableTLS"`
}
// DefaultServerConfiguration represents the default values of the ServerConfiguration.
var DefaultServerConfiguration = ServerConfiguration{
Address: &AddressTCP{Address{true, false, -1, 9091, &url.URL{Scheme: AddressSchemeTCP, Host: ":9091", Path: "/"}}},

View File

@ -124,12 +124,6 @@ notifier:
identity_providers:
oidc:
policies:
abc:
default_policy: 'two_factor'
rules:
- subject: 'group:admin'
policy: 'one_factor'
cors:
allowed_origins:
- 'https://google.com'

View File

@ -30,7 +30,6 @@ func validateOIDC(config *schema.OpenIDConnect, val *schema.StructValidator) {
setOIDCDefaults(config)
validateOIDCIssuer(config, val)
validateOIDCPolicies(config, val)
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.ResponseObjectSigningAlgs))
@ -59,53 +58,6 @@ func validateOIDC(config *schema.OpenIDConnect, val *schema.StructValidator) {
}
}
func validateOIDCPolicies(config *schema.OpenIDConnect, val *schema.StructValidator) {
config.Discovery.Policies = []string{policyOneFactor, policyTwoFactor}
for name, policy := range config.Policies {
switch name {
case "":
val.Push(fmt.Errorf("policy must have a name"))
case policyOneFactor, policyTwoFactor, policyDeny:
val.Push(fmt.Errorf("policy names can't be named any of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny})))
default:
break
}
switch policy.DefaultPolicy {
case "":
policy.DefaultPolicy = policyTwoFactor
case policyOneFactor, policyTwoFactor, policyDeny:
break
default:
val.Push(fmt.Errorf("policy must be one of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny})))
}
if len(policy.Rules) == 0 {
val.Push(fmt.Errorf("policy must include at least one rule"))
}
for i, rule := range policy.Rules {
switch rule.Policy {
case "":
policy.Rules[i].Policy = policyTwoFactor
case policyOneFactor, policyTwoFactor, policyDeny:
break
default:
val.Push(fmt.Errorf("policy must be one of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny})))
}
if len(rule.Subjects) == 0 {
val.Push(fmt.Errorf("policy must include at least one criteria"))
}
}
config.Policies[name] = policy
config.Discovery.Policies = append(config.Discovery.Policies, name)
}
}
func validateOIDCIssuer(config *schema.OpenIDConnect, val *schema.StructValidator) {
switch {
case config.IssuerPrivateKey != nil:
@ -385,13 +337,13 @@ func validateOIDCClient(c int, config *schema.OpenIDConnect, val *schema.StructV
}
}
switch {
case config.Clients[c].Policy == "":
switch config.Clients[c].Policy {
case "":
config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
case utils.IsStringInSlice(config.Clients[c].Policy, config.Discovery.Policies):
case policyOneFactor, policyTwoFactor:
break
default:
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "authorization_policy", strJoinOr(config.Discovery.Policies), config.Clients[c].Policy))
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "authorization_policy", strJoinOr([]string{policyOneFactor, policyTwoFactor}), config.Clients[c].Policy))
}
switch config.Clients[c].PKCEChallengeMethod {

View File

@ -67,18 +67,16 @@ KEYS:
// NewKeyPattern returns patterns which are required to match key patterns.
func NewKeyPattern(key string) (pattern *regexp.Regexp, err error) {
switch {
case reIsMapKey.MatchString(key):
case strings.Contains(key, ".*."):
return NewKeyMapPattern(key)
default:
return nil, nil
}
}
var reIsMapKey = regexp.MustCompile(`\.\*(\[]|\.)`)
// NewKeyMapPattern returns a pattern required to match map keys.
func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
parts := strings.Split(key, ".*")
parts := strings.Split(key, ".*.")
buf := &strings.Builder{}
@ -87,16 +85,11 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
n := len(parts) - 1
for i, part := range parts {
if i != 0 && !strings.HasPrefix(part, "[]") {
if i != 0 {
buf.WriteString("\\.")
}
for j, r := range part {
// Skip prefixed period.
if j == 0 && r == '.' {
continue
}
for _, r := range part {
switch r {
case '[', ']', '.', '{', '}':
buf.WriteRune('\\')

View File

@ -0,0 +1,410 @@
package handlers
import (
"encoding/json"
"fmt"
"net"
"net/url"
"strings"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/session"
"github.com/authelia/authelia/v4/internal/utils"
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
autha "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
envoy_type "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/gogo/googleapis/google/rpc"
"github.com/valyala/fasthttp"
rpcstatus "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status"
"context"
)
type AuthzGRCP struct {
Config *schema.Configuration
Providers middlewares.Providers
AuthStrategies []AuthnStrategy
Authz Authz
}
func NewAuthzGRCP(config *schema.Configuration, providers middlewares.Providers) *AuthzGRCP {
// Determine the refresh interval
authBuilder := NewAuthzBuilder().WithConfig(config)
// Only the following strategies are supported. These are hardcoded at the moment and won't be taken from the configuration
strategies := []AuthnStrategy{NewHeaderProxyAuthorizationAuthnStrategy() /* NewHeaderAuthorizationAuthnStrategy(), */, NewCookieSessionAuthnStrategy(authBuilder.config.RefreshInterval)}
return &AuthzGRCP{
Config: config,
Providers: providers,
AuthStrategies: strategies,
Authz: Authz{strategies: strategies},
}
}
type GRCPRequestData struct {
RemoteHost string
Domain string
Method string
Protocol string
Path string
AutheliaURL string
}
func (data *GRCPRequestData) String() string {
return fmt.Sprintf(
`Extracted data from headers:
Remote Host = %s
Domain = %s
Method = %s
Protocol = %s
Path = %s
AutheliaURL = %s
`, data.RemoteHost, data.Domain, data.Method, data.Protocol, data.Path, data.AutheliaURL)
}
// GetRequestURI returns a request URI from the provided Data inside this struct.
// It returns an error if values are missing
func (data *GRCPRequestData) GetRequestURI() (*url.URL, error) {
if len(data.Protocol) == 0 || len(data.Domain) == 0 || len(data.Path) == 0 || len(data.Method) == 0 {
return nil, fmt.Errorf("missing required values for URI. One of 'Protocol', 'Domain', 'Path' or 'Method' was not found")
}
return url.ParseRequestURI(fmt.Sprintf("%s://%s%s", data.Protocol, data.Domain, data.Path))
}
// GetAutheliaURI returns a URI for the authelia protal from the provided Data inside this struct.
func (data *GRCPRequestData) GetAutheliaURI(provider *session.Session) (*url.URL, error) {
if len(data.AutheliaURL) == 0 {
return nil, fmt.Errorf("missing required 'AutheliaURL' for request")
}
// Parse the URL
uri, err := url.ParseRequestURI(data.AutheliaURL)
if err != nil {
return nil, err
}
// Validate URL
if !utils.HasURIDomainSuffix(uri, provider.Config.Domain) {
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", data.AutheliaURL, provider.Config.Domain)
}
return uri, nil
}
// HandleGRPC is the authentication handler for envoy proxies via the gRPC protocoll.
// It is oriented on handler_authz.go
func (authz *AuthzGRCP) Check(goCtx context.Context, req *autha.CheckRequest) (*autha.CheckResponse, error) {
request, data := authz.GetHttpCtxFromGRPC(req)
ctx := middlewares.NewAutheliaCtx(request, *authz.Config, authz.Providers)
// Log the parsed data and the inbound headers
ctx.Logger.Debug(data)
b, err := json.MarshalIndent(req.Attributes.Request.Http.Headers, "", " ")
if err == nil {
ctx.Logger.Trace("Inbound Headers: ")
ctx.Logger.Trace((string(b)))
}
// Get request URI
uri, err := data.GetRequestURI()
if err != nil {
ctx.Logger.WithError(err).Error("Error getting Target URL and Request Method")
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad authentication request"), nil
}
// Get authentication object
object := authorization.NewObject(uri, data.Method)
if !utils.IsURISecure(object.URL) {
// Check for redirect
if !ctx.Configuration.Server.DisableAutoHttpsRedirect {
// Redirect to https
return authz.AuthResponseHeader(envoy_type.StatusCode_PermanentRedirect, "", []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "Location",
Value: fmt.Sprintf("https://%s%s", data.Domain, data.Path),
},
},
}), nil
}
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)
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad protocol for authentication request"), nil
}
// Get provider
provider, err := ctx.GetSessionProviderByTargetURL(object.URL)
if err != nil {
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Target URL does not appear to have a relevant session cookies configuration")
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad domain for authentication request"), nil
}
// Get authelia url
if len(data.AutheliaURL) == 0 {
ctx.Logger.Info("Received no authelia URL from headers. Using the default of https://auth.rpjosh.de")
data.AutheliaURL = "https://auth.rpjosh.de"
}
autheliaURI, err := data.GetAutheliaURI(provider)
if err != nil {
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Error occurred trying to determine the external Authelia URL for Target URL")
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad authentication request"), nil
}
ctx.Logger.Trace("Using authelia URL " + autheliaURI.String())
var (
authn Authn
strategy AuthnStrategy
)
// Get strategie
if authn, strategy, err = authz.Authz.authn(ctx, provider); err != nil {
authn.Object = object
ctx.Logger.WithError(err).Error("Error occurred while attempting to authenticate a request")
switch strategy {
case nil:
ctx.Logger.Trace("Received no strategy to use")
return authz.ErrAuthResponse(envoy_type.StatusCode_Unauthorized, "Unauthorized"), nil
default:
// Because the response is modified directly, we have to catch this and rewrite this function
if s, ok := strategy.(*HeaderAuthnStrategy); ok {
ctx.Logger.Trace("Rewriting HeaderAuthnStrategy")
ctx.Logger.Debugf("Responding %d %s", s.authn, autheliaURI)
headers := make([]*core.HeaderValueOption, 0)
if s.headerAuthenticate != nil {
headers = append(headers, &core.HeaderValueOption{
Header: &core.HeaderValue{
Key: string(s.headerAuthenticate),
Value: string(headerValueAuthenticateBasic),
},
})
}
return authz.AuthResponseHeader(envoy_type.StatusCode(s.statusAuthenticate), "", headers), nil
} else if _, ok := strategy.(*CookieSessionAuthnStrategy); ok {
ctx.Logger.Trace("Rewriting CookieSessionAuthnStrategy")
}
ctx.Logger.Error("Received unsupported auth strategy for gRPC server")
// strategy.HandleUnauthorized(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI))
}
return authz.ErrAuthResponse(envoy_type.StatusCode_Unauthorized, "Unauthorized"), nil
}
authn.Object = object
authn.Method = friendlyMethod(authn.Object.Method)
ruleHasSubject, required := ctx.Providers.Authorizer.GetRequiredLevel(
authorization.Subject{
Username: authn.Details.Username,
Groups: authn.Details.Groups,
IP: net.ParseIP(data.RemoteHost),
},
object,
)
switch isAuthzResult(authn.Level, required, ruleHasSubject) {
case AuthzResultForbidden:
ctx.Logger.Infof("Access to '%s' is forbidden to user '%s'", object.URL.String(), authn.Username)
return authz.ErrAuthResponse(envoy_type.StatusCode_Forbidden, "Forbidden"), nil
case AuthzResultUnauthorized:
if strategy != nil {
ctx.Logger.Error("Handling not supported handler")
//strategy.HandleUnauthorized(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI))
} else {
ctx.Logger.Debugf("Redirecting user")
return authz.HandleUnauthorizedRedirect(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI)), nil
}
case AuthzResultAuthorized:
ctx.Logger.Debugf("Authorized request")
return authz.HandleAuthorized(ctx, &authn), nil
//authz.Authz.handleAuthorized(ctx, &authn)
}
ctx.Logger.Info("Handling default redirection")
return authz.HandleUnauthorizedRedirect(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI)), nil
}
// GetHttpCtxFromGRPC is an Adapter that converts common fields between a grpc request
// a "normal" http request.
// It also returns the
func (authz *AuthzGRCP) GetHttpCtxFromGRPC(req *autha.CheckRequest) (*fasthttp.RequestCtx, *GRCPRequestData) {
// Extract headers
headers := req.Attributes.Request.Http.Headers
// Parse the header data
data := &GRCPRequestData{
RemoteHost: headers["x-forwarded-for"],
Domain: headers[":authority"],
Method: headers[":method"],
Protocol: headers["x-forwarded-proto"],
Path: headers[":path"],
AutheliaURL: headers[""],
}
// Build fasthttp request with common types
rtc := &fasthttp.RequestCtx{}
// General headers
rtc.Request.Header.Set(fasthttp.HeaderXForwardedFor, data.RemoteHost)
// Needed for NewHeaderProxyAuthorizationAuthnStrategy and NewHeaderAuthorizationAuthnStrategy
authz.setHeaderIfSet(fasthttp.HeaderAuthorization, rtc, &headers)
authz.setHeaderIfSet(fasthttp.HeaderProxyAuthorization, rtc, &headers)
authz.setHeaderIfSet(fasthttp.HeaderWWWAuthenticate, rtc, &headers)
authz.setHeaderIfSet(fasthttp.HeaderProxyAuthenticate, rtc, &headers)
// Needed for CookieSesseionauthnStrategy
rtc.Request.Header.Set("cookie", headers["cookie"])
return rtc, data
}
// setHeaderIfSet sets the header in the given fastHttp request if the header from the envoy authentication
// request was also set
func (authz *AuthzGRCP) setHeaderIfSet(headerKeyFast string, rtc *fasthttp.RequestCtx, envoyHeaders *map[string]string) {
// Envoys provided header keys are always lower case
envoyHeaderKey := strings.ToLower(headerKeyFast)
if val, isSet := (*envoyHeaders)[envoyHeaderKey]; isSet {
rtc.Request.Header.Set(headerKeyFast, val)
}
}
// ErrAuthResponse returns an authentication error for envoy with the given status code
// and the given text body
func (authz *AuthzGRCP) ErrAuthResponse(statuscode envoy_type.StatusCode, body string) *autha.CheckResponse {
return &autha.CheckResponse{
Status: &rpcstatus.Status{
Code: int32(rpc.UNAUTHENTICATED),
},
HttpResponse: &autha.CheckResponse_DeniedResponse{
DeniedResponse: &autha.DeniedHttpResponse{
Status: &envoy_type.HttpStatus{
Code: statuscode,
},
Body: body,
},
},
}
}
// ErrAuthResponse returns an authentication error for envoy with the given status code
// and the given text body
func (authz *AuthzGRCP) AuthResponseHeader(statuscode envoy_type.StatusCode, body string, headers []*core.HeaderValueOption) *autha.CheckResponse {
return &autha.CheckResponse{
Status: &rpcstatus.Status{
Code: int32(rpc.UNAUTHENTICATED),
},
HttpResponse: &autha.CheckResponse_DeniedResponse{
DeniedResponse: &autha.DeniedHttpResponse{
Status: &envoy_type.HttpStatus{
Code: statuscode,
},
Body: body,
Headers: headers,
},
},
}
}
func (authz *AuthzGRCP) HandleAuthorized(ctx *middlewares.AutheliaCtx, authn *Authn) *autha.CheckResponse {
return &autha.CheckResponse{
Status: &rpcstatus.Status{
Code: int32(rpc.OK),
},
HttpResponse: &autha.CheckResponse_OkResponse{
OkResponse: &autha.OkHttpResponse{
Headers: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "Auth-Handler",
Value: getAuthType(authn.Type),
},
},
{
Header: &core.HeaderValue{
Key: "Auth-Username",
Value: authn.Username,
},
},
{
Header: &core.HeaderValue{
Key: "Auth-Username-Display",
Value: authn.Details.DisplayName,
},
},
{
Header: &core.HeaderValue{
Key: "Auth-Realm",
Value: authn.Object.Domain,
},
},
},
},
},
}
}
func (authz *AuthzGRCP) HandleUnauthorizedRedirect(ctx *middlewares.AutheliaCtx, authn *Authn, redirectionURL *url.URL) *autha.CheckResponse {
return &autha.CheckResponse{
Status: &rpcstatus.Status{
Code: int32(rpc.UNAUTHENTICATED),
},
HttpResponse: &autha.CheckResponse_DeniedResponse{
DeniedResponse: &autha.DeniedHttpResponse{
Status: &envoy_type.HttpStatus{
Code: 302,
},
Headers: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "Location",
Value: redirectionURL.String(),
},
},
},
},
},
}
}
func getAuthType(t AuthnType) string {
switch t {
case AuthnTypeNone:
return "none"
case AuthnTypeCookie:
return "cookie"
case AuthnTypeProxyAuthorization:
return "proxy"
case AuthnTypeAuthorization:
return "header"
default:
return "unknown"
}
}
// HealthEndpoint implements a health function to check if the appliction is
// still alive
type HealthEndpoint struct{}
func (s *HealthEndpoint) Check(ctx context.Context, in *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil
}
func (s *HealthEndpoint) Watch(in *healthpb.HealthCheckRequest, srv healthpb.Health_WatchServer) error {
return status.Error(codes.Unimplemented, "Watch is not implemented")
}

View File

@ -33,7 +33,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
return
}
if bannedUntil, err := ctx.Providers.Regulator.Regulate(ctx, bodyJSON.Username); err != nil {
if bannedUntil, err := ctx.Providers.Regulator.Regulate(ctx, bodyJSON.Username, ctx.RemoteIP().String()); err != nil {
if errors.Is(err, regulation.ErrUserIsBanned) {
_ = markAuthenticationAttempt(ctx, false, &bannedUntil, bodyJSON.Username, regulation.AuthType1FA, nil)

View File

@ -8,7 +8,6 @@ import (
"github.com/ory/fosite"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
@ -118,7 +117,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
extraClaims := oidcGrantRequests(requester, consent, &userSession)
if authTime, err = userSession.AuthenticatedTime(client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()})); err != nil {
if authTime, err = userSession.AuthenticatedTime(client.GetAuthorizationPolicy()); err != nil {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred checking authentication time: %+v", requester.GetID(), client.GetID(), err)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time."))

View File

@ -11,7 +11,6 @@ import (
"github.com/google/uuid"
"github.com/ory/fosite"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
@ -29,12 +28,10 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR
var handler handlerAuthorizationConsent
policy := client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()})
switch {
case userSession.IsAnonymous():
handler = handleOIDCAuthorizationConsentNotAuthenticated
case authorization.IsAuthLevelSufficient(userSession.AuthenticationLevel, policy):
case client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel):
if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err)
@ -58,14 +55,6 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR
return nil, true
}
default:
if policy == authorization.Denied {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: the user '%s' is not authorized to use this client", requester.GetID(), client.GetID(), userSession.Username)
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrClientAuthorizationUserAccessDenied)
return nil, true
}
if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil {
ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err)
@ -132,7 +121,7 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer
userSession session.UserSession, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) {
var location *url.URL
if client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel, authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) {
if client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
location, _ = url.ParseRequestURI(issuer.String())
location.Path = path.Join(location.Path, oidc.EndpointPathConsent)
@ -141,11 +130,11 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer
location.RawQuery = query.Encode()
ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "sufficient", client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}))
ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "sufficient", client.GetAuthorizationPolicy())
} else {
location = handleOIDCAuthorizationConsentGetRedirectionURL(issuer, consent, requester)
ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "insufficient", client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}))
ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "insufficient", client.GetAuthorizationPolicy())
}
ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.GetConsentPolicy(), location)

View File

@ -10,7 +10,6 @@ import (
"github.com/google/uuid"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/oidc"
@ -87,14 +86,6 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
return
}
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel, authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) {
ctx.Logger.Errorf("User '%s' can't consent to authorization request for client with id '%s' as they are not sufficiently authenticated",
userSession.Username, consent.ClientID)
ctx.SetJSONError(messageOperationFailed)
return
}
if bodyJSON.Consent {
consent.Grant()
@ -215,7 +206,7 @@ func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uui
}
}
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel, authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) {
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the user is not sufficiently authenticated", userSession.Username, consent.ClientID)
ctx.ReplyForbidden()

View File

@ -221,10 +221,13 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
return
}
policy := client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()})
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.GetID())
ctx.ReplyOK()
return
}
switch {
case authorization.IsAuthLevelSufficient(userSession.AuthenticationLevel, policy), policy == authorization.Denied:
var (
targetURL *url.URL
form url.Values
@ -246,12 +249,6 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
if err = ctx.SetJSONBody(redirectResponse{Redirect: targetURL.String()}); err != nil {
ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err)
}
default:
ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.GetID())
ctx.ReplyOK()
return
}
}
func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, bannedUntil *time.Time, username string, authType string, errAuth error) (err error) {
@ -280,13 +277,18 @@ func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, ba
if successful {
ctx.Logger.Debugf("Successful %s authentication attempt made by user '%s'", authType, username)
} else {
reasonPhrase := "by user '" + username + "'"
if ctx.Configuration.Server.UseIPInsteadOfUserForBan {
reasonPhrase = fmt.Sprintf("by ip %q (user %q)", ctx.RemoteIP().String(), username)
}
switch {
case errAuth != nil:
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s': %+v", authType, username, errAuth)
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s: %+v", authType, reasonPhrase, errAuth)
case bannedUntil != nil:
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s' and they are banned until %s", authType, username, bannedUntil)
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s and they are banned until %s", authType, reasonPhrase, bannedUntil)
default:
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s'", authType, username)
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s", authType, reasonPhrase)
}
}

View File

@ -210,18 +210,18 @@ func (mr *MockStorageMockRecorder) FindIdentityVerification(arg0, arg1 interface
}
// LoadAuthenticationLogs mocks base method.
func (m *MockStorage) LoadAuthenticationLogs(arg0 context.Context, arg1 string, arg2 time.Time, arg3, arg4 int) ([]model.AuthenticationAttempt, error) {
func (m *MockStorage) LoadAuthenticationLogs(arg0 context.Context, arg1, arg2 string, arg3 time.Time, arg4, arg5 int) ([]model.AuthenticationAttempt, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadAuthenticationLogs", arg0, arg1, arg2, arg3, arg4)
ret := m.ctrl.Call(m, "LoadAuthenticationLogs", arg0, arg1, arg2, arg3, arg4, arg5)
ret0, _ := ret[0].([]model.AuthenticationAttempt)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadAuthenticationLogs indicates an expected call of LoadAuthenticationLogs.
func (mr *MockStorageMockRecorder) LoadAuthenticationLogs(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
func (mr *MockStorageMockRecorder) LoadAuthenticationLogs(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadAuthenticationLogs", reflect.TypeOf((*MockStorage)(nil).LoadAuthenticationLogs), arg0, arg1, arg2, arg3, arg4)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadAuthenticationLogs", reflect.TypeOf((*MockStorage)(nil).LoadAuthenticationLogs), arg0, arg1, arg2, arg3, arg4, arg5)
}
// LoadOAuth2BlacklistedJTI mocks base method.

View File

@ -13,7 +13,7 @@ import (
)
// NewClient creates a new Client.
func NewClient(config schema.OpenIDConnectClient, c *schema.OpenIDConnect) (client Client) {
func NewClient(config schema.OpenIDConnectClient) (client Client) {
base := &BaseClient{
ID: config.ID,
Description: config.Description,
@ -39,7 +39,8 @@ func NewClient(config schema.OpenIDConnectClient, c *schema.OpenIDConnect) (clie
UserinfoSigningAlg: config.UserinfoSigningAlg,
UserinfoSigningKeyID: config.UserinfoSigningKeyID,
Policy: NewClientPolicy(config.Policy, c),
Policy: authorization.NewLevel(config.Policy),
Consent: NewClientConsent(config.ConsentMode, config.ConsentPreConfiguredDuration),
}
@ -191,18 +192,9 @@ func (c *BaseClient) GetPKCEChallengeMethod() string {
return c.PKCEChallengeMethod
}
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
func (c *BaseClient) IsAuthenticationLevelSufficient(level authentication.Level, subject authorization.Subject) bool {
if level == authentication.NotAuthenticated {
return false
}
return authorization.IsAuthLevelSufficient(level, c.GetAuthorizationPolicy(subject))
}
// GetAuthorizationPolicy returns Policy.
func (c *BaseClient) GetAuthorizationPolicy(subject authorization.Subject) authorization.Level {
return c.Policy.GetRequiredLevel(subject)
func (c *BaseClient) GetAuthorizationPolicy() authorization.Level {
return c.Policy
}
// GetConsentPolicy returns Consent.
@ -231,6 +223,15 @@ func (c *BaseClient) IsPublic() bool {
return c.Public
}
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
func (c *BaseClient) IsAuthenticationLevelSufficient(level authentication.Level) bool {
if level == authentication.NotAuthenticated {
return false
}
return authorization.IsAuthLevelSufficient(level, c.Policy)
}
// ValidatePKCEPolicy is a helper function to validate PKCE policy constraints on a per-client basis.
func (c *BaseClient) ValidatePKCEPolicy(r fosite.Requester) (err error) {
form := r.GetRequestForm()

View File

@ -1,73 +0,0 @@
package oidc
import (
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func NewClientPolicy(name string, config *schema.OpenIDConnect) (policy ClientPolicy) {
switch name {
case authorization.OneFactor.String(), authorization.TwoFactor.String():
return ClientPolicy{DefaultPolicy: authorization.NewLevel(name)}
default:
if p, ok := config.Policies[name]; ok {
policy.DefaultPolicy = authorization.NewLevel(p.DefaultPolicy)
for _, r := range p.Rules {
policy.Rules = append(policy.Rules, ClientPolicyRule{
Policy: authorization.NewLevel(r.Policy),
Subjects: authorization.NewSubjects(r.Subjects),
})
}
return policy
}
return ClientPolicy{DefaultPolicy: authorization.TwoFactor}
}
}
// ClientPolicy controls and represents a client policy.
type ClientPolicy struct {
DefaultPolicy authorization.Level
Rules []ClientPolicyRule
}
func (p *ClientPolicy) GetRequiredLevel(subject authorization.Subject) authorization.Level {
for _, rule := range p.Rules {
if rule.IsMatch(subject) {
return rule.Policy
}
}
return p.DefaultPolicy
}
type ClientPolicyRule struct {
Subjects []authorization.AccessControlSubjects
Policy authorization.Level
}
// MatchesSubjects returns true if the rule matches the subjects.
func (p *ClientPolicyRule) MatchesSubjects(subject authorization.Subject) (match bool) {
// If there are no subjects in this rule then the subject condition is a match.
if len(p.Subjects) == 0 {
return true
} else if subject.IsAnonymous() {
return false
}
// Iterate over the subjects until we find a match (return true) or until we exit the loop (return false).
for _, rule := range p.Subjects {
if rule.IsMatch(subject) {
return true
}
}
return false
}
// IsMatch returns true if all elements of an AccessControlRule match the object and subject.
func (p *ClientPolicyRule) IsMatch(subject authorization.Subject) (match bool) {
return p.MatchesSubjects(subject)
}

View File

@ -19,7 +19,7 @@ import (
func TestNewClient(t *testing.T) {
config := schema.OpenIDConnectClient{}
client := oidc.NewClient(config, &schema.OpenIDConnect{})
client := oidc.NewClient(config)
assert.Equal(t, "", client.GetID())
assert.Equal(t, "", client.GetDescription())
assert.Len(t, client.GetResponseModes(), 0)
@ -47,17 +47,17 @@ func TestNewClient(t *testing.T) {
ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes,
}
client = oidc.NewClient(config, &schema.OpenIDConnect{})
client = oidc.NewClient(config)
assert.Equal(t, myclient, client.GetID())
require.Len(t, client.GetResponseModes(), 1)
assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy(authorization.Subject{}))
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
config = schema.OpenIDConnectClient{
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
}
client = oidc.NewClient(config, &schema.OpenIDConnect{})
client = oidc.NewClient(config)
fclient, ok := client.(*oidc.FullClient)
@ -204,25 +204,25 @@ func TestBaseClient_ValidatePARPolicy(t *testing.T) {
func TestIsAuthenticationLevelSufficient(t *testing.T) {
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
c.Policy = oidc.ClientPolicy{DefaultPolicy: authorization.Bypass}
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated, authorization.Subject{}))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor, authorization.Subject{}))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor, authorization.Subject{}))
c.Policy = authorization.Bypass
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
c.Policy = oidc.ClientPolicy{DefaultPolicy: authorization.OneFactor}
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated, authorization.Subject{}))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor, authorization.Subject{}))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor, authorization.Subject{}))
c.Policy = authorization.OneFactor
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
c.Policy = oidc.ClientPolicy{DefaultPolicy: authorization.TwoFactor}
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated, authorization.Subject{}))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor, authorization.Subject{}))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor, authorization.Subject{}))
c.Policy = authorization.TwoFactor
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
c.Policy = oidc.ClientPolicy{DefaultPolicy: authorization.Denied}
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated, authorization.Subject{}))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor, authorization.Subject{}))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor, authorization.Subject{}))
c.Policy = authorization.Denied
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
}
func TestClient_GetConsentResponseBody(t *testing.T) {
@ -446,7 +446,7 @@ func TestNewClientPKCE(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := oidc.NewClient(tc.have, &schema.OpenIDConnect{})
client := oidc.NewClient(tc.have)
assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement())
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement())
@ -511,7 +511,7 @@ func TestNewClientPAR(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := oidc.NewClient(tc.have, &schema.OpenIDConnect{})
client := oidc.NewClient(tc.have)
assert.Equal(t, tc.expected, client.GetPAREnforcement())
@ -575,7 +575,7 @@ func TestNewClientResponseModes(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := oidc.NewClient(tc.have, &schema.OpenIDConnect{})
client := oidc.NewClient(tc.have)
assert.Equal(t, tc.expected, client.GetResponseModes())
@ -615,7 +615,7 @@ func TestNewClient_JSONWebKeySetURI(t *testing.T) {
PublicKeys: schema.OpenIDConnectClientPublicKeys{
URI: MustParseRequestURI("https://google.com"),
},
}, &schema.OpenIDConnect{})
})
require.NotNil(t, client)
@ -630,7 +630,7 @@ func TestNewClient_JSONWebKeySetURI(t *testing.T) {
PublicKeys: schema.OpenIDConnectClientPublicKeys{
URI: nil,
},
}, &schema.OpenIDConnect{})
})
require.NotNil(t, client)

View File

@ -32,6 +32,4 @@ var (
// ErrPAREnforcedClientMissingPAR is sent when a client has EnforcePAR configured but the Authorization Request was not Pushed.
ErrPAREnforcedClientMissingPAR = fosite.ErrInvalidRequest.WithHint("Pushed Authorization Requests are enforced for this client but no such request was sent.")
ErrClientAuthorizationUserAccessDenied = fosite.ErrAccessDenied.WithHint("The user was denied access to this client.")
)

View File

@ -31,7 +31,7 @@ func NewStore(config *schema.OpenIDConnect, provider storage.Provider) (store *S
policy := authorization.NewLevel(client.Policy)
logger.Debugf("Registering client %s with policy %s (%v)", client.ID, client.Policy, policy)
store.clients[client.ID] = NewClient(client, config)
store.clients[client.ID] = NewClient(client)
}
return store
@ -65,6 +65,16 @@ func (s *Store) GetSubject(ctx context.Context, sectorID, username string) (subj
return opaqueID.Identifier, nil
}
// GetClientPolicy retrieves the policy from the client with the matching provided id.
func (s *Store) GetClientPolicy(id string) (level authorization.Level) {
client, err := s.GetFullClient(id)
if err != nil {
return authorization.TwoFactor
}
return client.GetAuthorizationPolicy()
}
// GetFullClient returns a fosite.Client asserted as an Client matching the provided id.
func (s *Store) GetFullClient(id string) (client Client, err error) {
client, ok := s.clients[id]

View File

@ -23,6 +23,38 @@ import (
"github.com/authelia/authelia/v4/internal/storage"
)
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
s := oidc.NewStore(&schema.OpenIDConnect{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClient{
{
ID: myclient,
Description: myclientdesc,
Policy: onefactor,
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
Secret: tOpenIDConnectPlainTextClientSecret,
},
{
ID: "myotherclient",
Description: myclientdesc,
Policy: twofactor,
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
Secret: tOpenIDConnectPlainTextClientSecret,
},
},
}, nil)
policyOne := s.GetClientPolicy(myclient)
assert.Equal(t, authorization.OneFactor, policyOne)
policyTwo := s.GetClientPolicy("myotherclient")
assert.Equal(t, authorization.TwoFactor, policyTwo)
policyInvalid := s.GetClientPolicy("invalidclient")
assert.Equal(t, authorization.TwoFactor, policyInvalid)
}
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
s := oidc.NewStore(&schema.OpenIDConnect{
IssuerCertificateChain: schema.X509CertificateChain{},
@ -74,7 +106,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
assert.Equal(t, fosite.Arguments([]string{oidc.GrantTypeAuthorizationCode}), client.GetGrantTypes())
assert.Equal(t, fosite.Arguments([]string{oidc.ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes())
assert.Equal(t, []string(nil), client.GetRedirectURIs())
assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy(authorization.Subject{}))
assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy())
assert.Equal(t, "$plaintext$client-secret", client.GetSecret().Encode())
}

View File

@ -10,7 +10,7 @@ import (
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
"github.com/ory/herodot"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
@ -127,7 +127,7 @@ type BaseClient struct {
UserinfoSigningAlg string
UserinfoSigningKeyID string
Policy ClientPolicy
Policy authorization.Level
Consent ClientConsent
}
@ -164,14 +164,14 @@ type Client interface {
GetPKCEEnforcement() bool
GetPKCEChallengeMethodEnforcement() bool
GetPKCEChallengeMethod() string
GetAuthorizationPolicy() authorization.Level
GetConsentPolicy() ClientConsent
IsAuthenticationLevelSufficient(level authentication.Level) bool
ValidatePKCEPolicy(r fosite.Requester) (err error)
ValidatePARPolicy(r fosite.Requester, prefix string) (err error)
ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error)
GetConsentPolicy() ClientConsent
IsAuthenticationLevelSufficient(level authentication.Level, subject authorization.Subject) bool
GetAuthorizationPolicy(subject authorization.Subject) authorization.Level
}
// NewClientConsent converts the schema.OpenIDConnectClientConsentConfig into a oidc.ClientConsent.

View File

@ -40,13 +40,13 @@ func (r *Regulator) Mark(ctx Context, successful, banned bool, username, request
// Regulate the authentication attempts for a given user.
// This method returns ErrUserIsBanned if the user is banned along with the time until when the user is banned.
func (r *Regulator) Regulate(ctx context.Context, username string) (time.Time, error) {
func (r *Regulator) Regulate(ctx context.Context, username string, remoteIp string) (time.Time, error) {
// If there is regulation configuration, no regulation applies.
if !r.enabled {
return time.Time{}, nil
}
attempts, err := r.store.LoadAuthenticationLogs(ctx, username, r.clock.Now().Add(-r.config.BanTime), 10, 0)
attempts, err := r.store.LoadAuthenticationLogs(ctx, username, remoteIp, r.clock.Now().Add(-r.config.BanTime), 10, 0)
if err != nil {
return time.Time{}, nil
}

View File

@ -23,6 +23,8 @@ type RegulatorSuite struct {
mock *mocks.MockAutheliaCtx
}
// @TODO
// Extend this test for IP ban :)
func (s *RegulatorSuite) SetupTest() {
s.mock = mocks.NewMockAutheliaCtx(s.T())
s.mock.Ctx.Configuration.Regulation = schema.RegulationConfiguration{
@ -58,9 +60,9 @@ func (s *RegulatorSuite) TestShouldMark() {
func (s *RegulatorSuite) TestShouldHandleRegulateError() {
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
s.mock.StorageMock.EXPECT().LoadAuthenticationLogs(s.mock.Ctx, "john", s.mock.Clock.Now().Add(-s.mock.Ctx.Configuration.Regulation.BanTime), 10, 0).Return(nil, fmt.Errorf("failed"))
s.mock.StorageMock.EXPECT().LoadAuthenticationLogs(s.mock.Ctx, "john", "127.0.0.1", s.mock.Clock.Now().Add(-s.mock.Ctx.Configuration.Regulation.BanTime), 10, 0).Return(nil, fmt.Errorf("failed"))
until, err := regulator.Regulate(s.mock.Ctx, "john")
until, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
s.NoError(err)
s.Equal(time.Time{}, until)
@ -76,12 +78,12 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenUserIsLegitimate() {
}
s.mock.StorageMock.EXPECT().
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
Return(attemptsInDB, nil)
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
_, err := regulator.Regulate(s.mock.Ctx, "john")
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.NoError(s.T(), err)
}
@ -107,12 +109,12 @@ func (s *RegulatorSuite) TestShouldNotThrowWhenFailedAuthenticationNotInFindTime
}
s.mock.StorageMock.EXPECT().
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
Return(attemptsInDB, nil)
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
_, err := regulator.Regulate(s.mock.Ctx, "john")
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.NoError(s.T(), err)
}
@ -143,12 +145,12 @@ func (s *RegulatorSuite) TestShouldBanUserIfLatestAttemptsAreWithinFinTime() {
}
s.mock.StorageMock.EXPECT().
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
Return(attemptsInDB, nil)
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
_, err := regulator.Regulate(s.mock.Ctx, "john")
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
}
@ -176,12 +178,12 @@ func (s *RegulatorSuite) TestShouldCheckUserIsStillBanned() {
}
s.mock.StorageMock.EXPECT().
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
Return(attemptsInDB, nil)
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
_, err := regulator.Regulate(s.mock.Ctx, "john")
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
}
@ -200,12 +202,12 @@ func (s *RegulatorSuite) TestShouldCheckUserIsNotYetBanned() {
}
s.mock.StorageMock.EXPECT().
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
Return(attemptsInDB, nil)
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
_, err := regulator.Regulate(s.mock.Ctx, "john")
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.NoError(s.T(), err)
}
@ -232,12 +234,12 @@ func (s *RegulatorSuite) TestShouldCheckUserWasAboutToBeBanned() {
}
s.mock.StorageMock.EXPECT().
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
Return(attemptsInDB, nil)
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
_, err := regulator.Regulate(s.mock.Ctx, "john")
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.NoError(s.T(), err)
}
@ -268,12 +270,12 @@ func (s *RegulatorSuite) TestShouldCheckRegulationHasBeenResetOnSuccessfulAttemp
}
s.mock.StorageMock.EXPECT().
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
Return(attemptsInDB, nil)
regulator := regulation.NewRegulator(s.mock.Ctx.Configuration.Regulation, s.mock.StorageMock, &s.mock.Clock)
_, err := regulator.Regulate(s.mock.Ctx, "john")
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.NoError(s.T(), err)
}
@ -303,7 +305,7 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
}
s.mock.StorageMock.EXPECT().
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
LoadAuthenticationLogs(s.mock.Ctx, gomock.Eq("john"), "127.0.0.1", gomock.Any(), gomock.Eq(10), gomock.Eq(0)).
Return(attemptsInDB, nil)
// Check Disabled Functionality.
@ -314,7 +316,7 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
}
regulator := regulation.NewRegulator(config, s.mock.StorageMock, &s.mock.Clock)
_, err := regulator.Regulate(s.mock.Ctx, "john")
_, err := regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.NoError(s.T(), err)
// Check Enabled Functionality.
@ -325,6 +327,6 @@ func (s *RegulatorSuite) TestShouldHaveRegulatorDisabled() {
}
regulator = regulation.NewRegulator(config, s.mock.StorageMock, &s.mock.Clock)
_, err = regulator.Regulate(s.mock.Ctx, "john")
_, err = regulator.Regulate(s.mock.Ctx, "john", "127.0.0.1")
assert.Equal(s.T(), regulation.ErrUserIsBanned, err)
}

View File

@ -9,6 +9,7 @@ import (
"time"
duoapi "github.com/duosecurity/duo_api_golang"
autha "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/fasthttp/router"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
@ -16,6 +17,8 @@ import (
"github.com/valyala/fasthttp/expvarhandler"
"github.com/valyala/fasthttp/fasthttpadaptor"
"github.com/valyala/fasthttp/pprofhandler"
"google.golang.org/grpc"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/duo"
@ -189,7 +192,7 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
uri := path.Join(pathAuthz, name)
authz := handlers.NewAuthzBuilder().WithConfig(config).WithEndpointConfig(endpoint).Build()
// @HERE
handler := middlewares.Wrap(metricsVRMW, bridge(authz.Handler))
switch name {
@ -404,6 +407,12 @@ func handleRouter(config *schema.Configuration, providers middlewares.Providers)
return handler
}
func handleGRCP(server *grpc.Server, config *schema.Configuration, providers middlewares.Providers) {
// Register endpoints
healthpb.RegisterHealthServer(server, &handlers.HealthEndpoint{})
autha.RegisterAuthorizationServer(server, handlers.NewAuthzGRCP(config, providers))
}
func handleMetrics(path string) fasthttp.RequestHandler {
r := router.New()

View File

@ -9,6 +9,8 @@ import (
"github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/logging"
@ -44,30 +46,13 @@ func CreateDefaultServer(config *schema.Configuration, providers middlewares.Pro
if config.Server.TLS.Certificate != "" && config.Server.TLS.Key != "" {
isTLS, connectionScheme = true, schemeHTTPS
if err = server.AppendCert(config.Server.TLS.Certificate, config.Server.TLS.Key); err != nil {
return nil, nil, nil, false, fmt.Errorf("unable to load tls server certificate '%s' or private key '%s': %w", config.Server.TLS.Certificate, config.Server.TLS.Key, err)
tlsConfig, err := loadTLSCertificates(config)
if err != nil {
return nil, nil, nil, false, fmt.Errorf("HTTP server: %w", err)
}
if len(config.Server.TLS.ClientCertificates) > 0 {
caCertPool := x509.NewCertPool()
var cert []byte
for _, path := range config.Server.TLS.ClientCertificates {
if cert, err = os.ReadFile(path); err != nil {
return nil, nil, nil, false, fmt.Errorf("unable to load tls client certificate '%s': %w", path, err)
}
caCertPool.AppendCertsFromPEM(cert)
}
// ClientCAs should never be nil, otherwise the system cert pool is used for client authentication
// but we don't want everybody on the Internet to be able to authenticate.
server.TLSConfig.ClientCAs = caCertPool
server.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
listener = tls.NewListener(listener, server.TLSConfig.Clone())
// Apply configuration
listener = tls.NewListener(listener, tlsConfig.Clone())
}
if err = writeHealthCheckEnv(config.Server.DisableHealthcheck, connectionScheme, config.Server.Address.Hostname(),
@ -108,3 +93,72 @@ func CreateMetricsServer(config *schema.Configuration, providers middlewares.Pro
return server, listener, []string{config.Telemetry.Metrics.Address.Path()}, false, nil
}
// CreateGRPCServer creates a server for handling gRPC authentication requests from an envoy proxy.
func CreateGRPCServer(config *schema.Configuration, providers middlewares.Providers) (server *grpc.Server, listener net.Listener, tls bool, err error) {
if config.Server.GRPC.Address == nil {
return nil, nil, false, nil
}
// Initialize gPRC server
lis, err := config.Server.GRPC.Address.Listener()
if err != nil {
return nil, nil, false, fmt.Errorf("error occurred while attempting to initialize grcp server listener for address '%s': %w", config.Server.GRPC.Address, err)
}
opts := []grpc.ServerOption{grpc.MaxConcurrentStreams(10)}
if config.Server.TLS.Certificate != "" && config.Server.TLS.Key != "" && !config.Server.GRPC.DisableTLS {
tlsConfig, err := loadTLSCertificates(config)
if err != nil {
return nil, nil, false, fmt.Errorf("gRPC server: %w", err)
}
opts = append(opts, grpc.Creds(credentials.NewTLS(tlsConfig)))
tls = true
}
s := grpc.NewServer(opts...)
handleGRCP(s, config, providers)
return s, lis, tls, nil
}
// loadTLSCertificates loads the server and client certificates from the files and returns a
// tls.Config object for the server
func loadTLSCertificates(config *schema.Configuration) (*tls.Config, error) {
// Load the server certificates
serverCert, err := tls.LoadX509KeyPair(config.Server.TLS.Certificate, config.Server.TLS.Key)
if err != nil {
return nil, fmt.Errorf("unable to load server certificate '%s' or key '%s': %w", config.Server.TLS.Certificate, config.Server.TLS.Key, err)
}
// Create the tls configuration
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.NoClientCert,
}
// Load client certificates
if len(config.Server.TLS.ClientCertificates) > 0 {
caCertPool := x509.NewCertPool()
var cert []byte
for _, path := range config.Server.TLS.ClientCertificates {
if cert, err = os.ReadFile(path); err != nil {
return nil, fmt.Errorf("unable to load tls client certificate '%s': %w", path, err)
}
caCertPool.AppendCertsFromPEM(cert)
}
// ClientCAs should never be nil, otherwise the system cert pool is used for client authentication,
// but we don't want everybody on the Internet to be able to authenticate.
tlsConfig.ClientCAs = caCertPool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
return tlsConfig, nil
}

View File

@ -90,5 +90,5 @@ type Provider interface {
// RegulatorProvider is an interface providing storage capabilities for persisting any kind of data related to the regulator.
type RegulatorProvider interface {
AppendAuthenticationLog(ctx context.Context, attempt model.AuthenticationAttempt) (err error)
LoadAuthenticationLogs(ctx context.Context, username string, fromDate time.Time, limit, page int) (attempts []model.AuthenticationAttempt, err error)
LoadAuthenticationLogs(ctx context.Context, username string, ip string, fromDate time.Time, limit, page int) (attempts []model.AuthenticationAttempt, err error)
}

View File

@ -33,6 +33,7 @@ func NewSQLProvider(config *schema.Configuration, name, driverName, dataSourceNa
sqlInsertAuthenticationAttempt: fmt.Sprintf(queryFmtInsertAuthenticationLogEntry, tableAuthenticationLogs),
sqlSelectAuthenticationAttemptsByUsername: fmt.Sprintf(queryFmtSelect1FAAuthenticationLogEntryByUsername, tableAuthenticationLogs),
sqlSelectAuthenticationAttemptyByIP: fmt.Sprintf(queryFmtSelect1FAAuthenticationLogEntryByIP, tableAuthenticationLogs),
sqlInsertIdentityVerification: fmt.Sprintf(queryFmtInsertIdentityVerification, tableIdentityVerification),
sqlConsumeIdentityVerification: fmt.Sprintf(queryFmtConsumeIdentityVerification, tableIdentityVerification),
@ -149,6 +150,7 @@ type SQLProvider struct {
// Table: authentication_logs.
sqlInsertAuthenticationAttempt string
sqlSelectAuthenticationAttemptsByUsername string
sqlSelectAuthenticationAttemptyByIP string
// Table: identity_verification.
sqlInsertIdentityVerification string
@ -1021,10 +1023,18 @@ func (p *SQLProvider) AppendAuthenticationLog(ctx context.Context, attempt model
}
// LoadAuthenticationLogs retrieve the latest failed authentications from the authentication log.
func (p *SQLProvider) LoadAuthenticationLogs(ctx context.Context, username string, fromDate time.Time, limit, page int) (attempts []model.AuthenticationAttempt, err error) {
func (p *SQLProvider) LoadAuthenticationLogs(ctx context.Context, username string, ip string, fromDate time.Time, limit, page int) (attempts []model.AuthenticationAttempt, err error) {
attempts = make([]model.AuthenticationAttempt, 0, limit)
if err = p.db.SelectContext(ctx, &attempts, p.sqlSelectAuthenticationAttemptsByUsername, fromDate, username, limit, limit*page); err != nil {
// Dynmaic values based on ip / username ban
query := p.sqlSelectAuthenticationAttemptsByUsername
placeholder := username
if p.config.Server.UseIPInsteadOfUserForBan {
query = p.sqlSelectAuthenticationAttemptyByIP
placeholder = ip
}
if err = p.db.SelectContext(ctx, &attempts, query, fromDate, placeholder, limit, limit*page); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNoAuthenticationLogs
}
@ -1033,4 +1043,5 @@ func (p *SQLProvider) LoadAuthenticationLogs(ctx context.Context, username strin
}
return attempts, nil
}

View File

@ -71,6 +71,7 @@ func NewPostgreSQLProvider(config *schema.Configuration, caCertPool *x509.CertPo
provider.sqlInsertAuthenticationAttempt = provider.db.Rebind(provider.sqlInsertAuthenticationAttempt)
provider.sqlSelectAuthenticationAttemptsByUsername = provider.db.Rebind(provider.sqlSelectAuthenticationAttemptsByUsername)
provider.sqlSelectAuthenticationAttemptyByIP = provider.db.Rebind(provider.sqlSelectAuthenticationAttemptyByIP)
provider.sqlInsertMigration = provider.db.Rebind(provider.sqlInsertMigration)
provider.sqlSelectMigrations = provider.db.Rebind(provider.sqlSelectMigrations)

View File

@ -211,6 +211,14 @@ const (
ORDER BY time DESC
LIMIT ?
OFFSET ?;`
queryFmtSelect1FAAuthenticationLogEntryByIP = `
SELECT time, successful, username
FROM %s
WHERE time > ? AND remote_ip = ? AND auth_type = '1FA' AND banned = FALSE
ORDER BY time DESC
LIMIT ?
OFFSET ?;`
)
const (

View File

@ -10,6 +10,9 @@ server:
tls:
certificate: /pki/public.backend.crt
key: /pki/private.backend.pem
grpc:
address: 'tcp://0.0.0.0:9092'
disableTLS: false
telemetry:
metrics:
@ -17,7 +20,7 @@ telemetry:
address: tcp://0.0.0.0:9959
log:
level: debug
level: trace
authentication_backend:
file:
@ -30,6 +33,8 @@ session:
cookies:
- domain: 'example.com'
authelia_url: 'https://login.example.com:8080'
- domain: 'rpjosh.de'
authelia_url: 'https://ubuntugui.rpjosh.de:9091'
storage:
encryption_key: a_not_so_secure_encryption_key
@ -83,6 +88,18 @@ access_control:
subject: "user:bob"
policy: two_factor
- domain: auth.rpjosh.de
policy: bypass
- domain: datenbank.rpjosh.de
resources:
- "^/$"
policy: bypass
- domain: datenbank.rpjosh.de
resources:
- "^/hi.*$"
policy: one_factor
regulation:
# Set it to 0 to disable max_retries.

View File

@ -17,3 +17,4 @@ ENV PATH="/app:${PATH}" \
VOLUME /config
EXPOSE 9091
EXPOSE 9092

View File

@ -2,4 +2,4 @@
set -x
pnpm install --frozen-lockfile && pnpm start
pnpm start

View File

@ -2,7 +2,7 @@
version: '3'
services:
traefik:
image: traefik:v2.10.1
image: traefik:v2.10.3
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
labels:

View File

@ -19,18 +19,18 @@
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@mui/icons-material": "5.11.16",
"@mui/material": "5.13.5",
"@mui/material": "5.13.6",
"@mui/styles": "5.13.2",
"axios": "1.4.0",
"broadcast-channel": "5.1.0",
"classnames": "2.3.2",
"i18next": "23.1.0",
"i18next": "23.2.3",
"i18next-browser-languagedetector": "7.0.2",
"i18next-http-backend": "2.2.1",
"qrcode.react": "3.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-i18next": "13.0.0",
"react-i18next": "13.0.1",
"react-loading": "2.0.3",
"react-router-dom": "6.13.0",
"react18-input-otp": "1.1.3",
@ -80,7 +80,7 @@
"@typescript-eslint/parser": "5.60.0",
"@vitejs/plugin-react": "4.0.1",
"@vitest/coverage-istanbul": "0.32.2",
"esbuild": "0.18.4",
"esbuild": "0.18.6",
"eslint": "8.43.0",
"eslint-config-prettier": "8.8.0",
"eslint-config-react-app": "7.0.1",

View File

@ -28,10 +28,10 @@ dependencies:
version: 0.2.0(@fortawesome/fontawesome-svg-core@6.4.0)(react@18.2.0)
'@mui/icons-material':
specifier: 5.11.16
version: 5.11.16(@mui/material@5.13.5)(@types/react@18.2.13)(react@18.2.0)
version: 5.11.16(@mui/material@5.13.6)(@types/react@18.2.13)(react@18.2.0)
'@mui/material':
specifier: 5.13.5
version: 5.13.5(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0)
specifier: 5.13.6
version: 5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0)
'@mui/styles':
specifier: 5.13.2
version: 5.13.2(@types/react@18.2.13)(react@18.2.0)
@ -45,8 +45,8 @@ dependencies:
specifier: 2.3.2
version: 2.3.2
i18next:
specifier: 23.1.0
version: 23.1.0
specifier: 23.2.3
version: 23.2.3
i18next-browser-languagedetector:
specifier: 7.0.2
version: 7.0.2
@ -63,8 +63,8 @@ dependencies:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
react-i18next:
specifier: 13.0.0
version: 13.0.0(i18next@23.1.0)(react-dom@18.2.0)(react@18.2.0)
specifier: 13.0.1
version: 13.0.1(i18next@23.2.3)(react-dom@18.2.0)(react@18.2.0)
react-loading:
specifier: 2.0.3
version: 2.0.3(prop-types@15.8.1)(react@18.2.0)
@ -122,8 +122,8 @@ devDependencies:
specifier: 0.32.2
version: 0.32.2(vitest@0.32.2)
esbuild:
specifier: 0.18.4
version: 0.18.4
specifier: 0.18.6
version: 0.18.6
eslint:
specifier: 8.43.0
version: 8.43.0
@ -1938,8 +1938,8 @@ packages:
dev: true
optional: true
/@esbuild/android-arm64@0.18.4:
resolution: {integrity: sha512-yQVgO+V307hA2XhzELQ6F91CBGX7gSnlVGAj5YIqjQOxThDpM7fOcHT2YLJbE6gNdPtgRSafQrsK8rJ9xHCaZg==}
/@esbuild/android-arm64@0.18.6:
resolution: {integrity: sha512-pL0Ci8P9q1sWbtPx8CXbc8JvPvvYdJJQ+LO09PLFsbz3aYNdFBGWJjiHU+CaObO4Ames+GOFpXRAJZS2L3ZK/A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
@ -1965,8 +1965,8 @@ packages:
dev: true
optional: true
/@esbuild/android-arm@0.18.4:
resolution: {integrity: sha512-yKmQC9IiuvHdsNEbPHSprnMHg6OhL1cSeQZLzPpgzJBJ9ppEg9GAZN8MKj1TcmB4tZZUrq5xjK7KCmhwZP8iDA==}
/@esbuild/android-arm@0.18.6:
resolution: {integrity: sha512-J3lwhDSXBBppSzm/LC1uZ8yKSIpExc+5T8MxrYD9KNVZG81FOAu2VF2gXi/6A/LwDDQQ+b6DpQbYlo3VwxFepQ==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
@ -1983,8 +1983,8 @@ packages:
dev: true
optional: true
/@esbuild/android-x64@0.18.4:
resolution: {integrity: sha512-yLKXMxQg6sk1ntftxQ5uwyVgG4/S2E7UoOCc5N4YZW7fdkfRiYEXqm7CMuIfY2Vs3FTrNyKmSfNevIuIvJnMww==}
/@esbuild/android-x64@0.18.6:
resolution: {integrity: sha512-hE2vZxOlJ05aY28lUpB0y0RokngtZtcUB+TVl9vnLEnY0z/8BicSvrkThg5/iI1rbf8TwXrbr2heEjl9fLf+EA==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
@ -2001,8 +2001,8 @@ packages:
dev: true
optional: true
/@esbuild/darwin-arm64@0.18.4:
resolution: {integrity: sha512-MVPEoZjZpk2xQ1zckZrb8eQuQib+QCzdmMs3YZAYEQPg+Rztk5pUxGyk8htZOC8Z38NMM29W+MqY9Sqo/sDGKw==}
/@esbuild/darwin-arm64@0.18.6:
resolution: {integrity: sha512-/tuyl4R+QhhoROQtuQj9E/yfJtZNdv2HKaHwYhhHGQDN1Teziem2Kh7BWQMumfiY7Lu9g5rO7scWdGE4OsQ6MQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
@ -2019,8 +2019,8 @@ packages:
dev: true
optional: true
/@esbuild/darwin-x64@0.18.4:
resolution: {integrity: sha512-uEsRtYRUDsz7i2tXg/t/SyF+5gU1cvi9B6B8i5ebJgtUUHJYWyIPIesmIOL4/+bywjxsDMA/XrNFMgMffLnh5A==}
/@esbuild/darwin-x64@0.18.6:
resolution: {integrity: sha512-L7IQga2pDT+14Ti8HZwsVfbCjuKP4U213T3tuPggOzyK/p4KaUJxQFXJgfUFHKzU0zOXx8QcYRYZf0hSQtppkw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
@ -2037,8 +2037,8 @@ packages:
dev: true
optional: true
/@esbuild/freebsd-arm64@0.18.4:
resolution: {integrity: sha512-I8EOigqWnOHRin6Zp5Y1cfH3oT54bd7Sdz/VnpUNksbOtfp8IWRTH4pgkgO5jWaRQPjCpJcOpdRjYAMjPt8wXg==}
/@esbuild/freebsd-arm64@0.18.6:
resolution: {integrity: sha512-bq10jFv42V20Kk77NvmO+WEZaLHBKuXcvEowixnBOMkaBgS7kQaqTc77ZJDbsUpXU3KKNLQFZctfaeINmeTsZA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
@ -2055,8 +2055,8 @@ packages:
dev: true
optional: true
/@esbuild/freebsd-x64@0.18.4:
resolution: {integrity: sha512-1bHfgMz/cNMjbpsYxjVgMJ1iwKq+NdDPlACBrWULD7ZdFmBQrhMicMaKb5CdmdVyvIwXmasOuF4r6Iq574kUTA==}
/@esbuild/freebsd-x64@0.18.6:
resolution: {integrity: sha512-HbDLlkDZqUMBQaiday0pJzB6/8Xx/10dI3xRebJBReOEeDSeS+7GzTtW9h8ZnfB7/wBCqvtAjGtWQLTNPbR2+g==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
@ -2073,8 +2073,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-arm64@0.18.4:
resolution: {integrity: sha512-J42vLHaYREyiBwH0eQE4/7H1DTfZx8FuxyWSictx4d7ezzuKE3XOkIvOg+SQzRz7T9HLVKzq2tvbAov4UfufBw==}
/@esbuild/linux-arm64@0.18.6:
resolution: {integrity: sha512-NMY9yg/88MskEZH2s4i6biz/3av+M8xY5ua4HE7CCz5DBz542cr7REe317+v7oKjnYBCijHpkzo5vU85bkXQmQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
@ -2091,8 +2091,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-arm@0.18.4:
resolution: {integrity: sha512-4XCGqM/Ay1LCXUBH59bL4JbSbbTK1K22dWHymWMGaEh2sQCDOUw+OQxozYV/YdBb91leK2NbuSrE2BRamwgaYw==}
/@esbuild/linux-arm@0.18.6:
resolution: {integrity: sha512-C+5kb6rgsGMmvIdUI7v1PPgC98A6BMv233e97aXZ5AE03iMdlILFD/20HlHrOi0x2CzbspXn9HOnlE4/Ijn5Kw==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
@ -2109,8 +2109,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-ia32@0.18.4:
resolution: {integrity: sha512-4ksIqFwhq7OExty7Sl1n0vqQSCqTG4sU6i99G2yuMr28CEOUZ/60N+IO9hwI8sIxBqmKmDgncE1n5CMu/3m0IA==}
/@esbuild/linux-ia32@0.18.6:
resolution: {integrity: sha512-AXazA0ljvQEp7cA9jscABNXsjodKbEcqPcAE3rDzKN82Vb3lYOq6INd+HOCA7hk8IegEyHW4T72Z7QGIhyCQEA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
@ -2136,8 +2136,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-loong64@0.18.4:
resolution: {integrity: sha512-bsWtoVHkGQgAsFXioDueXRiUIfSGrVkJjBBz4gcBJxXcD461cWFQFyu8Fxdj9TP+zEeqJ8C/O4LFFMBNi6Fscw==}
/@esbuild/linux-loong64@0.18.6:
resolution: {integrity: sha512-JjBf7TwY7ldcPgHYt9UcrjZB03+WZqg/jSwMAfzOzM5ZG+tu5umUqzy5ugH/crGI4eoDIhSOTDp1NL3Uo/05Fw==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
@ -2154,8 +2154,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-mips64el@0.18.4:
resolution: {integrity: sha512-LRD9Fu8wJQgIOOV1o3nRyzrheFYjxA0C1IVWZ93eNRRWBKgarYFejd5WBtrp43cE4y4D4t3qWWyklm73Mrsd/g==}
/@esbuild/linux-mips64el@0.18.6:
resolution: {integrity: sha512-kATNsslryVxcH1sO3KP2nnyUWtZZVkgyhAUnyTVVa0OQQ9pmDRjTpHaE+2EQHoCM5wt/uav2edrAUqbwn3tkKQ==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
@ -2172,8 +2172,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-ppc64@0.18.4:
resolution: {integrity: sha512-jtQgoZjM92gauVRxNaaG/TpL3Pr4WcL3Pwqi9QgdrBGrEXzB+twohQiWNSTycs6lUygakos4mm2h0B9/SHveng==}
/@esbuild/linux-ppc64@0.18.6:
resolution: {integrity: sha512-B+wTKz+8pi7mcWXFQV0LA79dJ+qhiut5uK9q0omoKnq8yRIwQJwfg3/vclXoqqcX89Ri5Y5538V0Se2v5qlcLA==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
@ -2190,8 +2190,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-riscv64@0.18.4:
resolution: {integrity: sha512-7WaU/kRZG0VCV09Xdlkg6LNAsfU9SAxo6XEdaZ8ffO4lh+DZoAhGTx7+vTMOXKxa+r2w1LYDGxfJa2rcgagMRA==}
/@esbuild/linux-riscv64@0.18.6:
resolution: {integrity: sha512-h44RBLVXFUSjvhOfseE+5UxQ/r9LVeqK2S8JziJKOm9W7SePYRPDyn7MhzhNCCFPkcjIy+soCxfhlJXHXXCR0A==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
@ -2208,8 +2208,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-s390x@0.18.4:
resolution: {integrity: sha512-D19ed0xreKQvC5t+ArE2njSnm18WPpE+1fhwaiJHf+Xwqsq+/SUaV8Mx0M27nszdU+Atq1HahrgCOZCNNEASUg==}
/@esbuild/linux-s390x@0.18.6:
resolution: {integrity: sha512-FlYpyr2Xc2AUePoAbc84NRV+mj7xpsISeQ36HGf9etrY5rTBEA+IU9HzWVmw5mDFtC62EQxzkLRj8h5Hq85yOQ==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
@ -2226,8 +2226,8 @@ packages:
dev: true
optional: true
/@esbuild/linux-x64@0.18.4:
resolution: {integrity: sha512-Rx3AY1sxyiO/gvCGP00nL69L60dfmWyjKWY06ugpB8Ydpdsfi3BHW58HWC24K3CAjAPSwxcajozC2PzA9JBS1g==}
/@esbuild/linux-x64@0.18.6:
resolution: {integrity: sha512-Mc4EUSYwzLci77u0Kao6ajB2WbTe5fNc7+lHwS3a+vJISC/oprwURezUYu1SdWAYoczbsyOvKAJwuNftoAdjjg==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
@ -2244,8 +2244,8 @@ packages:
dev: true
optional: true
/@esbuild/netbsd-x64@0.18.4:
resolution: {integrity: sha512-AaShPmN9c6w1mKRpliKFlaWcSkpBT4KOlk93UfFgeI3F3cbjzdDKGsbKnOZozmYbE1izZKLmNJiW0sFM+A5JPA==}
/@esbuild/netbsd-x64@0.18.6:
resolution: {integrity: sha512-3hgZlp7NqIM5lNG3fpdhBI5rUnPmdahraSmwAi+YX/bp7iZ7mpTv2NkypGs/XngdMtpzljICxnUG3uPfqLFd3w==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
@ -2262,8 +2262,8 @@ packages:
dev: true
optional: true
/@esbuild/openbsd-x64@0.18.4:
resolution: {integrity: sha512-tRGvGwou3BrvHVvF8HxTqEiC5VtPzySudS9fh2jBIKpLX7HCW8jIkW+LunkFDNwhslx4xMAgh0jAHsx/iCymaQ==}
/@esbuild/openbsd-x64@0.18.6:
resolution: {integrity: sha512-aEWTdZQHtSRROlDYn7ygB8yAqtnall/UnmoVIJVqccKitkAWVVSYocQUWrBOxLEFk8XdlRouVrLZe6WXszyviA==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
@ -2280,8 +2280,8 @@ packages:
dev: true
optional: true
/@esbuild/sunos-x64@0.18.4:
resolution: {integrity: sha512-acORFDI95GKhmAnlH8EarBeuqoy/j3yxIU+FDB91H3+ZON+8HhTadtT450YkaMzX6lEWbhi+mjVUCj00M5yyOQ==}
/@esbuild/sunos-x64@0.18.6:
resolution: {integrity: sha512-uxk/5yAGpjKZUHOECtI9W+9IcLjKj+2m0qf+RG7f7eRBHr8wP6wsr3XbNbgtOD1qSpPapd6R2ZfSeXTkCcAo5g==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
@ -2298,8 +2298,8 @@ packages:
dev: true
optional: true
/@esbuild/win32-arm64@0.18.4:
resolution: {integrity: sha512-1NxP+iOk8KSvS1L9SSxEvBAJk39U0GiGZkiiJGbuDF9G4fG7DSDw6XLxZMecAgmvQrwwx7yVKdNN3GgNh0UfKg==}
/@esbuild/win32-arm64@0.18.6:
resolution: {integrity: sha512-oXlXGS9zvNCGoAT/tLHAsFKrIKye1JaIIP0anCdpaI+Dc10ftaNZcqfLzEwyhdzFAYInXYH4V7kEdH4hPyo9GA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
@ -2316,8 +2316,8 @@ packages:
dev: true
optional: true
/@esbuild/win32-ia32@0.18.4:
resolution: {integrity: sha512-OKr8jze93vbgqZ/r23woWciTixUwLa976C9W7yNBujtnVHyvsL/ocYG61tsktUfJOpyIz5TsohkBZ6Lo2+PCcQ==}
/@esbuild/win32-ia32@0.18.6:
resolution: {integrity: sha512-qh7IcAHUvvmMBmoIG+V+BbE9ZWSR0ohF51e5g8JZvU08kZF58uDFL5tHs0eoYz31H6Finv17te3W3QB042GqVA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
@ -2334,8 +2334,8 @@ packages:
dev: true
optional: true
/@esbuild/win32-x64@0.18.4:
resolution: {integrity: sha512-qJr3wVvcLjPFcV4AMDS3iquhBfTef2zo/jlm8RMxmiRp3Vy2HY8WMxrykJlcbCnqLXZPA0YZxZGND6eug85ogg==}
/@esbuild/win32-x64@0.18.6:
resolution: {integrity: sha512-9UDwkz7Wlm4N9jnv+4NL7F8vxLhSZfEkRArz2gD33HesAFfMLGIGNVXRoIHtWNw8feKsnGly9Hq1EUuRkWl0zA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
@ -2542,8 +2542,8 @@ packages:
tsconfig-paths: 3.14.2
dev: true
/@mui/base@5.0.0-beta.4(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g==}
/@mui/base@5.0.0-beta.5(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-vy3TWLQYdGNecTaufR4wDNQFV2WEg6wRPi6BVbx6q1vP3K1mbxIn1+XOqOzfYBXjFHvMx0gZAo2TgWbaqfgvAA==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || 18
@ -2556,7 +2556,7 @@ packages:
'@babel/runtime': 7.22.5
'@emotion/is-prop-valid': 1.2.1
'@mui/types': 7.2.4(@types/react@18.2.13)
'@mui/utils': 5.13.1(react@18.2.0)
'@mui/utils': 5.13.6(react@18.2.0)
'@popperjs/core': 2.11.8
'@types/react': 18.2.13
clsx: 1.2.1
@ -2570,7 +2570,7 @@ packages:
resolution: {integrity: sha512-yFrMWcrlI0TqRN5jpb6Ma9iI7sGTHpytdzzL33oskFHNQ8UgrtPas33Y1K7sWAMwCrr1qbWDrOHLAQG4tAzuSw==}
dev: false
/@mui/icons-material@5.11.16(@mui/material@5.13.5)(@types/react@18.2.13)(react@18.2.0):
/@mui/icons-material@5.11.16(@mui/material@5.13.6)(@types/react@18.2.13)(react@18.2.0):
resolution: {integrity: sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==}
engines: {node: '>=12.0.0'}
peerDependencies:
@ -2582,13 +2582,13 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@mui/material': 5.13.5(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0)
'@mui/material': 5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.13
react: 18.2.0
dev: false
/@mui/material@5.13.5(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-eMay+Ue1OYXOFMQA5Aau7qbAa/kWHLAyi0McsbPTWssCbGehqkF6CIdPsfVGw6tlO+xPee1hUitphHJNL3xpOQ==}
/@mui/material@5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-/c2ZApeQm2sTYdQXjqEnldaBMBcUEiyu2VRS6bS39ZeNaAcCLBQbYocLR46R+f0S5dgpBzB0T4AsOABPOFYZ5Q==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@ -2604,14 +2604,14 @@ packages:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@babel/runtime': 7.22.5
'@emotion/react': 11.11.1(@types/react@18.2.13)(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.13)(react@18.2.0)
'@mui/base': 5.0.0-beta.4(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0)
'@mui/base': 5.0.0-beta.5(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0)
'@mui/core-downloads-tracker': 5.13.4
'@mui/system': 5.13.5(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react@18.2.0)
'@mui/system': 5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react@18.2.0)
'@mui/types': 7.2.4(@types/react@18.2.13)
'@mui/utils': 5.13.1(react@18.2.0)
'@mui/utils': 5.13.6(react@18.2.0)
'@types/react': 18.2.13
'@types/react-transition-group': 4.4.6
clsx: 1.2.1
@ -2634,7 +2634,7 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.22.5
'@mui/utils': 5.13.1(react@18.2.0)
'@mui/utils': 5.13.6(react@18.2.0)
'@types/react': 18.2.13
prop-types: 15.8.1
react: 18.2.0
@ -2693,8 +2693,8 @@ packages:
react: 18.2.0
dev: false
/@mui/system@5.13.5(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react@18.2.0):
resolution: {integrity: sha512-n0gzUxoZ2ZHZgnExkh2Htvo9uW2oakofgPRQrDoa/GQOWyRD0NH9MDszBwOb6AAoXZb+OV5TE7I4LeZ/dzgHYA==}
/@mui/system@5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.13)(react@18.2.0):
resolution: {integrity: sha512-G3Xr28uLqU3DyF6r2LQkHGw/ku4P0AHzlKVe7FGXOPl7X1u+hoe2xxj8Vdiq/69II/mh9OP21i38yBWgWb7WgQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@ -2715,7 +2715,7 @@ packages:
'@mui/private-theming': 5.13.1(@types/react@18.2.13)(react@18.2.0)
'@mui/styled-engine': 5.13.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4(@types/react@18.2.13)
'@mui/utils': 5.13.1(react@18.2.0)
'@mui/utils': 5.13.6(react@18.2.0)
'@types/react': 18.2.13
clsx: 1.2.1
csstype: 3.1.2
@ -2748,6 +2748,20 @@ packages:
react-is: 18.2.0
dev: false
/@mui/utils@5.13.6(react@18.2.0):
resolution: {integrity: sha512-ggNlxl5NPSbp+kNcQLmSig6WVB0Id+4gOxhx644987v4fsji+CSXc+MFYLocFB/x4oHtzCUlSzbVHlJfP/fXoQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || 18
dependencies:
'@babel/runtime': 7.22.5
'@types/prop-types': 15.7.5
'@types/react-is': 18.2.0
prop-types: 15.8.1
react: 18.2.0
react-is: 18.2.0
dev: false
/@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1:
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
dependencies:
@ -4565,6 +4579,7 @@ packages:
/esbuild@0.15.18:
resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.15.18
@ -4594,6 +4609,7 @@ packages:
/esbuild@0.17.19:
resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.17.19
@ -4620,33 +4636,34 @@ packages:
'@esbuild/win32-x64': 0.17.19
dev: true
/esbuild@0.18.4:
resolution: {integrity: sha512-9rxWV/Cb2DMUXfe9aUsYtqg0KTlw146ElFH22kYeK9KVV1qT082X4lpmiKsa12ePiCcIcB686TQJxaGAa9TFvA==}
/esbuild@0.18.6:
resolution: {integrity: sha512-5QgxWaAhU/tPBpvkxUmnFv2YINHuZzjbk0LeUUnC2i3aJHjfi5yR49lgKgF7cb98bclOp/kans8M5TGbGFfJlQ==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.18.4
'@esbuild/android-arm64': 0.18.4
'@esbuild/android-x64': 0.18.4
'@esbuild/darwin-arm64': 0.18.4
'@esbuild/darwin-x64': 0.18.4
'@esbuild/freebsd-arm64': 0.18.4
'@esbuild/freebsd-x64': 0.18.4
'@esbuild/linux-arm': 0.18.4
'@esbuild/linux-arm64': 0.18.4
'@esbuild/linux-ia32': 0.18.4
'@esbuild/linux-loong64': 0.18.4
'@esbuild/linux-mips64el': 0.18.4
'@esbuild/linux-ppc64': 0.18.4
'@esbuild/linux-riscv64': 0.18.4
'@esbuild/linux-s390x': 0.18.4
'@esbuild/linux-x64': 0.18.4
'@esbuild/netbsd-x64': 0.18.4
'@esbuild/openbsd-x64': 0.18.4
'@esbuild/sunos-x64': 0.18.4
'@esbuild/win32-arm64': 0.18.4
'@esbuild/win32-ia32': 0.18.4
'@esbuild/win32-x64': 0.18.4
'@esbuild/android-arm': 0.18.6
'@esbuild/android-arm64': 0.18.6
'@esbuild/android-x64': 0.18.6
'@esbuild/darwin-arm64': 0.18.6
'@esbuild/darwin-x64': 0.18.6
'@esbuild/freebsd-arm64': 0.18.6
'@esbuild/freebsd-x64': 0.18.6
'@esbuild/linux-arm': 0.18.6
'@esbuild/linux-arm64': 0.18.6
'@esbuild/linux-ia32': 0.18.6
'@esbuild/linux-loong64': 0.18.6
'@esbuild/linux-mips64el': 0.18.6
'@esbuild/linux-ppc64': 0.18.6
'@esbuild/linux-riscv64': 0.18.6
'@esbuild/linux-s390x': 0.18.6
'@esbuild/linux-x64': 0.18.6
'@esbuild/netbsd-x64': 0.18.6
'@esbuild/openbsd-x64': 0.18.6
'@esbuild/sunos-x64': 0.18.6
'@esbuild/win32-arm64': 0.18.6
'@esbuild/win32-ia32': 0.18.6
'@esbuild/win32-x64': 0.18.6
dev: true
/escalade@3.1.1:
@ -5587,8 +5604,8 @@ packages:
- encoding
dev: false
/i18next@23.1.0:
resolution: {integrity: sha512-CObNPofJpw7zGVGYLd58mtMZUF+NZQl9czYMihbJkStjX+Nlu9kC3PHiC6uE1niP3qxP/3ocLXIBc2zqbAb1dg==}
/i18next@23.2.3:
resolution: {integrity: sha512-5spO7L0rNmW0jFuNhz+gfirlFt1anle4mTy4+gFkgsH0+T3R5++4oncBrzeKa7v8pweRyGBoGmOpboqlxovg6A==}
dependencies:
'@babel/runtime': 7.22.5
dev: false
@ -6796,10 +6813,10 @@ packages:
react: 18.2.0
scheduler: 0.23.0
/react-i18next@13.0.0(i18next@23.1.0)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-qRFbrSgynsBSjfnSTb/Um3mw9uPjOfDi4Iq2rMCuzfsRsYGdkEdyCr0i+T0bR0bG6xwULvK4k1oRVLLd7ZDBVw==}
/react-i18next@13.0.1(i18next@23.2.3)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-gMO6N2GfSfuH7xlHSsZ/mZf+Py9bLm/+EDKIn5fNTuDTjcCcwmMU5UEuGCDk5mdfivbo7ySyYXBN7B9tbGUxiA==}
peerDependencies:
i18next: '>= 23.0.1'
i18next: '>= 23.2.3'
react: '>= 16.8.0 || 18'
react-dom: '*'
react-native: '*'
@ -6811,7 +6828,7 @@ packages:
dependencies:
'@babel/runtime': 7.22.5
html-parse-stringify: 3.0.1
i18next: 23.1.0
i18next: 23.2.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false