From 89c5d12d5a26798ab9a1ca6557688f6ed63e63d6 Mon Sep 17 00:00:00 2001 From: toim Date: Sun, 15 Feb 2026 17:26:57 +0200 Subject: [PATCH 1/2] Rework Session and Casbin examples --- cookbook/casbin/auth_model.conf | 14 ++ cookbook/casbin/auth_policy.csv | 8 ++ cookbook/casbin/server.go | 73 ++++++++++ cookbook/jwt/custom-claims/server.go | 4 +- cookbook/prometheus/server.go | 2 +- go.mod | 20 +-- go.sum | 49 ++++--- website/docs/middleware/casbin-auth.md | 188 ++++++++++++++++++++----- website/docs/middleware/jaeger.md | 8 +- website/docs/middleware/prometheus.md | 12 +- website/docs/middleware/session.md | 97 +++++++------ 11 files changed, 357 insertions(+), 118 deletions(-) create mode 100644 cookbook/casbin/auth_model.conf create mode 100644 cookbook/casbin/auth_policy.csv create mode 100644 cookbook/casbin/server.go diff --git a/cookbook/casbin/auth_model.conf b/cookbook/casbin/auth_model.conf new file mode 100644 index 00000000..fd2f08df --- /dev/null +++ b/cookbook/casbin/auth_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*") diff --git a/cookbook/casbin/auth_policy.csv b/cookbook/casbin/auth_policy.csv new file mode 100644 index 00000000..3666a9b6 --- /dev/null +++ b/cookbook/casbin/auth_policy.csv @@ -0,0 +1,8 @@ +p, 1234567890, /dataset1/*, GET +p, alice, /dataset1/*, GET +p, alice, /dataset1/resource1, POST +p, bob, /dataset2/resource1, * +p, bob, /dataset2/resource2, GET +p, bob, /dataset2/folder1/*, POST +p, dataset1_admin, /dataset1/*, * +g, cathy, dataset1_admin diff --git a/cookbook/casbin/server.go b/cookbook/casbin/server.go new file mode 100644 index 00000000..de19d25a --- /dev/null +++ b/cookbook/casbin/server.go @@ -0,0 +1,73 @@ +package main + +import ( + "log/slog" + "net/http" + + "github.com/casbin/casbin/v3" + "github.com/golang-jwt/jwt/v5" + echojwt "github.com/labstack/echo-jwt/v5" + "github.com/labstack/echo/v5" +) + +// NewCasbinMiddleware returns middleware for [Casbin](https://casbin.org/). +func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + username, err := userGetter(c) + if err != nil { + return echo.ErrUnauthorized.Wrap(err) + } + if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil { + return echo.ErrInternalServerError.Wrap(err) + } else if !pass { + return echo.NewHTTPError(http.StatusForbidden, "access denied") + } + return next(c) + } + } +} + +/* +Test with: +curl -v "http://localhost:8080/dataset1/any" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" +*/ +func main() { + e := echo.New() + + ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv") + if err != nil { + slog.Error("failed to initialize Casbin enforcer", "error", err) + } + + // BasicAuth middleware does authentication + // - should pass `curl -v -u "alice:password" http://localhost:8080/dataset1/any` + // - should fail `curl -v -u "alice:password" http://localhost:8080/dataset2/resource2` + //e.Use(middleware.BasicAuth(func(c *echo.Context, user string, password string) (bool, error) { + // return subtle.ConstantTimeCompare([]byte(user), []byte("alice")) == 1 && + // subtle.ConstantTimeCompare([]byte(password), []byte("password")) == 1, nil + //})) + //basicAuthUser := func(c *echo.Context) (string, error) { // basic auth user getter for Casbin authorization + // username, _, _ := c.Request().BasicAuth() // NB: authorization (PASSWORD check) must be done somewhere!!! + // return username, nil + //} + //e.Use(NewCasbinMiddleware(ce, basicAuthUser)) // Casbin does authorization + + e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication + jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization + token, err := echo.ContextGet[*jwt.Token](c, "user") + if err != nil { + return "", err + } + return token.Claims.GetSubject() + } + e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization + + e.GET("/*", func(c *echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") + }) + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } +} diff --git a/cookbook/jwt/custom-claims/server.go b/cookbook/jwt/custom-claims/server.go index 3238cffa..ab317a87 100644 --- a/cookbook/jwt/custom-claims/server.go +++ b/cookbook/jwt/custom-claims/server.go @@ -56,11 +56,11 @@ func accessible(c *echo.Context) error { } func restricted(c *echo.Context) error { - user, err := echo.ContextGet[*jwt.Token](c, "user") + token, err := echo.ContextGet[*jwt.Token](c, "user") if err != nil { return echo.ErrUnauthorized.Wrap(err) } - claims := user.Claims.(*jwtCustomClaims) + claims := token.Claims.(*jwtCustomClaims) name := claims.Name return c.String(http.StatusOK, "Welcome "+name+"!") } diff --git a/cookbook/prometheus/server.go b/cookbook/prometheus/server.go index 9e41bb4d..3f627e27 100644 --- a/cookbook/prometheus/server.go +++ b/cookbook/prometheus/server.go @@ -3,7 +3,7 @@ package main import ( "net/http" - "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo-contrib/v5/echoprometheus" "github.com/labstack/echo/v5" "github.com/prometheus/client_golang/prometheus" ) diff --git a/go.mod b/go.mod index 57ed6765..5245751a 100644 --- a/go.mod +++ b/go.mod @@ -3,29 +3,33 @@ module github.com/labstack/echox go 1.25.0 require ( - github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/gorilla/websocket v1.5.3 - github.com/labstack/echo-contrib v0.50.0 + github.com/labstack/echo-contrib/v5 v5.0.0 github.com/labstack/echo-jwt/v5 v5.0.0 - github.com/labstack/echo/v5 v5.0.0 + github.com/labstack/echo/v5 v5.0.3 github.com/lestrrat-go/jwx/v3 v3.0.13 github.com/prometheus/client_golang v1.23.2 github.com/r3labs/sse/v2 v2.10.0 github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.47.0 - golang.org/x/net v0.49.0 + golang.org/x/crypto v0.48.0 + golang.org/x/net v0.50.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect + github.com/casbin/casbin/v3 v3.10.0 // indirect + github.com/casbin/govaluate v1.10.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc/v3 v3.0.3 // indirect + github.com/lestrrat-go/httprc/v3 v3.0.4 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -34,8 +38,8 @@ require ( github.com/prometheus/procfs v0.19.2 // indirect github.com/segmentio/asm v1.2.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 06c0b7fe..e2b5d675 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,13 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/casbin/casbin/v3 v3.10.0 h1:039ORla55vCeIZWd0LfzWFt1yiEA5X4W41xBW2bQuHs= +github.com/casbin/casbin/v3 v3.10.0/go.mod h1:5rJbQr2e6AuuDDNxnPc5lQlC9nIgg6nS1zYwKXhpHC8= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0= +github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -12,8 +20,12 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -24,18 +36,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo-contrib v0.17.5-0.20260113213056-68ad9fe34c3d h1:P5nNuXYDv47aLCbf+jLWxmIgtCLPFZQPRqNP52KUoKI= -github.com/labstack/echo-contrib v0.17.5-0.20260113213056-68ad9fe34c3d/go.mod h1:tGZ39uN7bCDY4WtVWd2KiDFI2PxHB5lhLOtSNb+9tE4= -github.com/labstack/echo-contrib v0.50.0 h1:MLTQdqME3BEBczV2thYz9yPT5sBhzkoUEpwAOY9llds= -github.com/labstack/echo-contrib v0.50.0/go.mod h1:oftqJL4enNg9ao1VLpVZmisVE5/8uwHtIYE4zTpqyWU= -github.com/labstack/echo-jwt/v5 v5.0.0-20260101195926-7cdd901b4337 h1:+9keiGOPRLJvrh+hp+4hApQK/seq3iVr0hVdRweUUYI= -github.com/labstack/echo-jwt/v5 v5.0.0-20260101195926-7cdd901b4337/go.mod h1:7zV1rqeuZ57XS4nwL/ymw4tywVUzg5SEovZandLv9nI= +github.com/labstack/echo-contrib/v5 v5.0.0 h1:ZukJ7gzW/gEe9ZiqpSQGAkRR8E35eTvu1PQBjmp2tDs= +github.com/labstack/echo-contrib/v5 v5.0.0/go.mod h1:oUtPer7/M+vUJjDATlgVUhHvVUDc7Nh/4Xs+/m52OKA= github.com/labstack/echo-jwt/v5 v5.0.0 h1:uPp+FpkI/PKpMPPygtnK3RQOpg5a2wlM04UgfpWLVyI= github.com/labstack/echo-jwt/v5 v5.0.0/go.mod h1:RYF2ojWXbaY09QQ5J9vVtPUtkyI5UztS0gJotmCRz/U= -github.com/labstack/echo/v5 v5.0.0-20260106091252-d6cb58b5c24e h1:QFu8S1iZijCXEUcP8u/h+JtZYlekbt0VIndrKbT/VAo= -github.com/labstack/echo/v5 v5.0.0-20260106091252-d6cb58b5c24e/go.mod h1:5La3y+CVfH4IzVRlQA2LmK+clJEniclmzbqSyZIhsP4= -github.com/labstack/echo/v5 v5.0.0 h1:JHKGrI0cbNsNMyKvranuY0C94O4hSM7yc/HtwcV3Na4= -github.com/labstack/echo/v5 v5.0.0/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= +github.com/labstack/echo/v5 v5.0.1 h1:60L7x1KMWRIJuaFqvnEHH322g+YnsMWq5Rzaeo6lcP4= +github.com/labstack/echo/v5 v5.0.1/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= +github.com/labstack/echo/v5 v5.0.3 h1:Jql8sDtCYXrhh2Mbs6jKwjR6r7X8FSQQmch+w6QS7kc= +github.com/labstack/echo/v5 v5.0.3/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= @@ -46,8 +54,8 @@ github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZ github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc/v3 v3.0.3 h1:WjLHWkDkgWXeIUrKi/7lS/sGq2DjkSAwdTbH5RHXAKs= github.com/lestrrat-go/httprc/v3 v3.0.3/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= -github.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg= -github.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8= +github.com/lestrrat-go/httprc/v3 v3.0.4 h1:pXyH2ppK8GYYggygxJ3TvxpCZnbEUWc9qSwRTTApaLA= +github.com/lestrrat-go/httprc/v3 v3.0.4/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= github.com/lestrrat-go/jwx/v3 v3.0.13 h1:AdHKiPIYeCSnOJtvdpipPg/0SuFh9rdkN+HF3O0VdSk= github.com/lestrrat-go/jwx/v3 v3.0.13/go.mod h1:2m0PV1A9tM4b/jVLMx8rh6rBl7F6WGb3EG2hufN9OQU= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= @@ -75,31 +83,32 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= -github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= +github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/website/docs/middleware/casbin-auth.md b/website/docs/middleware/casbin-auth.md index 01b46103..4dd24ef9 100644 --- a/website/docs/middleware/casbin-auth.md +++ b/website/docs/middleware/casbin-auth.md @@ -4,11 +4,6 @@ description: Casbin auth middleware # Casbin Auth -:::note - -Echo community contribution - -::: [Casbin](https://github.com/casbin/casbin) is a powerful and efficient open-source access control library for Go. It provides support for enforcing authorization based on various models. So far, the access control models supported by Casbin are: @@ -23,65 +18,186 @@ Echo community contribution - RESTful - Deny-override: both allow and deny authorizations are supported, deny overrides the allow. -:::info -Currently, only HTTP basic authentication is supported. - -::: +See [API Overview](https://casbin.org/docs/api-overview). +For more information, see: [Casbin Documentation](https://casbin.org/docs/). ## Dependencies +```bash +go get -u github.com/casbin/casbin/v3 +``` + ```go import ( - "github.com/casbin/casbin" - casbin_mw "github.com/labstack/echo-contrib/casbin" + "github.com/casbin/casbin/v3" ) ``` +## Implementation + +```go +// NewCasbinMiddleware returns middleware for [Casbin](https://casbin.org/). +func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + username, err := userGetter(c) + if err != nil { + return echo.ErrUnauthorized.Wrap(err) + } + if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil { + return echo.ErrInternalServerError.Wrap(err) + } else if !pass { + return echo.NewHTTPError(http.StatusForbidden, "access denied") + } + return next(c) + } + } +} +``` + ## Usage +Middleware can be initialized like this: ```go e := echo.New() enforcer, err := casbin.NewEnforcer("casbin_auth_model.conf", "casbin_auth_policy.csv") -e.Use(casbin_mw.Middleware(enforcer)) +e.Use(NewCasbinMiddleware(enforcer)) ``` -For syntax, see: [Syntax for Models](https://casbin.org/docs/syntax-for-models). +## Example + +Create a Casbin policy file `auth_model.conf`: +```ini +[request_definition] +r = sub, obj, act +[policy_definition] +p = sub, obj, act -## Custom Configuration +[role_definition] +g = _, _ -### Usage +[policy_effect] +e = some(where (p.eft == allow)) +[matchers] +m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*") +``` + +Create a Casbin policy file `auth_policy.csv`: +```csv +p, 1234567890, /dataset1/*, GET +p, alice, /dataset1/*, GET +p, alice, /dataset1/resource1, POST +p, bob, /dataset2/resource1, * +p, bob, /dataset2/resource2, GET +p, bob, /dataset2/folder1/*, POST +p, dataset1_admin, /dataset1/*, * +g, cathy, dataset1_admin +``` + +Authenticating user with JWT and authorizing access to resources based on Casbin policy. ```go -e := echo.New() -ce := casbin.NewEnforcer("casbin_auth_model.conf", "") -ce.AddRoleForUser("alice", "admin") -ce.AddPolicy(...) -e.Use(casbin_mw.MiddlewareWithConfig(casbin_mw.Config{ - Enforcer: ce, -})) + e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication + jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization + token, err := echo.ContextGet[*jwt.Token](c, "user") + if err != nil { + return "", err + } + return token.Claims.GetSubject() + } + e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization ``` -## Configuration +Try with: +```bash +curl -v "http://localhost:8080/dataset1/any" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" +``` +or authenticating user with Basic Auth and authorizing access to resources based on Casbin policy. ```go -// Config defines the config for CasbinAuth middleware. -Config struct { - // Skipper defines a function to skip middleware. - Skipper middleware.Skipper - - // Enforcer CasbinAuth main rule. - // Required. - Enforcer *casbin.Enforcer -} + // BasicAuth middleware does authentication + e.Use(middleware.BasicAuth(func(c *echo.Context, user string, password string) (bool, error) { + return subtle.ConstantTimeCompare([]byte(user), []byte("alice")) == 1 && + subtle.ConstantTimeCompare([]byte(password), []byte("password")) == 1, nil + })) + basicAuthUser := func(c *echo.Context) (string, error) { // basic auth user getter for Casbin authorization + username, _, _ := c.Request().BasicAuth() // NB: authorization (PASSWORD check) must be done somewhere!!! + return username, nil + } + e.Use(newCasbinMiddleware(ce, basicAuthUser)) // Casbin does authorization ``` -### Default Configuration +Try with: +```bash +# should pass +curl -v -u "alice:password" http://localhost:8080/dataset1/any +# should fail +curl -v -u "alice:password" http://localhost:8080/dataset2/resource2 +``` + +### Full Casbin + JWT example ```go -// DefaultConfig is the default CasbinAuth middleware config. -DefaultConfig = Config{ - Skipper: middleware.DefaultSkipper, +package main + +import ( + "log/slog" + "net/http" + + "github.com/casbin/casbin/v3" + "github.com/golang-jwt/jwt/v5" + echojwt "github.com/labstack/echo-jwt/v5" + "github.com/labstack/echo/v5" +) + +// NewCasbinMiddleware returns middleware for [Casbin](https://casbin.org/). +func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + username, err := userGetter(c) + if err != nil { + return echo.ErrUnauthorized.Wrap(err) + } + if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil { + return echo.ErrInternalServerError.Wrap(err) + } else if !pass { + return echo.NewHTTPError(http.StatusForbidden, "access denied") + } + return next(c) + } + } +} + +/* +Test with: +curl -v "http://localhost:8080/dataset1/any" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" +*/ +func main() { + e := echo.New() + + ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv") + if err != nil { + slog.Error("failed to initialize Casbin enforcer", "error", err) + } + + e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication + jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization + token, err := echo.ContextGet[*jwt.Token](c, "user") + if err != nil { + return "", err + } + return token.Claims.GetSubject() + } + e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization + + e.GET("/*", func(c *echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") + }) + + if err := e.Start(":8080"); err != nil { + e.Logger.Error("failed to start server", "error", err) + } } ``` diff --git a/website/docs/middleware/jaeger.md b/website/docs/middleware/jaeger.md index 5f18a26f..fd0acd44 100644 --- a/website/docs/middleware/jaeger.md +++ b/website/docs/middleware/jaeger.md @@ -17,7 +17,7 @@ Trace requests on Echo framework with Jaeger Tracing Middleware. ```go package main import ( - "github.com/labstack/echo-contrib/jaegertracing" + "github.com/labstack/echo-contrib/v5/jaegertracing" "github.com/labstack/echo/v5" ) func main() { @@ -84,7 +84,7 @@ A middleware skipper can be passed to avoid tracing spans to certain URL(s). package main import ( "strings" - "github.com/labstack/echo-contrib/jaegertracing" + "github.com/labstack/echo-contrib/v5/jaegertracing" "github.com/labstack/echo/v5" ) @@ -119,7 +119,7 @@ the duration of the invoked function. There is no need to change function argume ```go package main import ( - "github.com/labstack/echo-contrib/jaegertracing" + "github.com/labstack/echo-contrib/v5/jaegertracing" "github.com/labstack/echo/v5" "net/http" "time" @@ -157,7 +157,7 @@ giving control on data to be appended to the span like log messages, baggages an ```go package main import ( - "github.com/labstack/echo-contrib/jaegertracing" + "github.com/labstack/echo-contrib/v5/jaegertracing" "github.com/labstack/echo/v5" ) func main() { diff --git a/website/docs/middleware/prometheus.md b/website/docs/middleware/prometheus.md index 0ba70d90..8b3b86b9 100644 --- a/website/docs/middleware/prometheus.md +++ b/website/docs/middleware/prometheus.md @@ -16,7 +16,7 @@ https://github.com/labstack/echo-contrib/blob/master/echoprometheus/prometheus.g ## Usage -- Add needed module `go get -u github.com/labstack/echo-contrib` +- Add needed module `go get -u github.com/labstack/echo-contrib/v5` - Add Prometheus middleware and metrics serving route ```go e := echo.New() @@ -34,7 +34,7 @@ package main import ( "net/http" - "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo-contrib/v5/echoprometheus" "github.com/labstack/echo/v5" ) @@ -116,7 +116,7 @@ package main import ( "log" - "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo-contrib/v5/echoprometheus" "github.com/labstack/echo/v5" "github.com/prometheus/client_golang/prometheus" ) @@ -156,7 +156,7 @@ package main import ( "log" - "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo-contrib/v5/echoprometheus" "github.com/labstack/echo/v5" "github.com/prometheus/client_golang/prometheus" ) @@ -203,7 +203,7 @@ import ( "net/http" "strings" - "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo-contrib/v5/echoprometheus" "github.com/labstack/echo/v5" ) @@ -240,7 +240,7 @@ package main import ( "net/http" - "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo-contrib/v5/echoprometheus" "github.com/labstack/echo/v5" "github.com/prometheus/client_golang/prometheus" ) diff --git a/website/docs/middleware/session.md b/website/docs/middleware/session.md index a2c49db3..607c7f57 100644 --- a/website/docs/middleware/session.md +++ b/website/docs/middleware/session.md @@ -8,22 +8,49 @@ Session middleware facilitates HTTP session management backed by [gorilla sessio filesystem based session store; however, you can take advantage of [community maintained implementation](https://github.com/gorilla/sessions#store-implementations) for various backends. -:::note - -Echo community contribution - -::: ## Dependencies +```bash +go get github.com/gorilla/sessions +``` + ```go import ( - "github.com/gorilla/sessions" - "github.com/labstack/echo-contrib/session" + "github.com/gorilla/sessions" ) ``` -## Usage +## Implementation + +Function to create session middleware and utility function to get session from context: +```go +func NewSessionMiddleware(store sessions.Store) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + c.Set("_session_store", store) + return next(c) + } + } +} + +func GetSession(c *echo.Context, name string) (*sessions.Session, error) { + store, err := echo.ContextGet[sessions.Store](c, "_session_store") + if err != nil { + return nil, err + } + return store.Get(c.Request(), name) +} +``` + +Middleware can be initialized like this: +```go + sessionStore := sessions.NewCookieStore([]byte("secret")) + e.Use(NewSessionMiddleware(sessionStore)) +``` + + +## Usage example This example exposes two endpoints: `/create-session` creates new session and `/read-session` read value from session if request contains session id. @@ -36,17 +63,34 @@ import ( "net/http" "github.com/gorilla/sessions" - "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v5" ) +func NewSessionMiddleware(store sessions.Store) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + c.Set("_session_store", store) + return next(c) + } + } +} + +func GetSession(c *echo.Context, name string) (*sessions.Session, error) { + store, err := echo.ContextGet[sessions.Store](c, "_session_store") + if err != nil { + return nil, fmt.Errorf("failed to get session store: %w", err) + } + return store.Get(c.Request(), name) +} + func main() { e := echo.New() - e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret")))) + sessionStore := sessions.NewCookieStore([]byte("secret")) + e.Use(NewSessionMiddleware(sessionStore)) e.GET("/create-session", func(c *echo.Context) error { - sess, err := session.Get("session", c) + sess, err := GetSession(c, "session") if err != nil { return err } @@ -63,7 +107,7 @@ func main() { }) e.GET("/read-session", func(c *echo.Context) error { - sess, err := session.Get("session", c) + sess, err := GetSession(c, "session") if err != nil { return err } @@ -139,32 +183,3 @@ foo=bar * Connection #0 to host localhost left intact ``` -## Custom Configuration - -### Usage - -```go -e := echo.New() -e.Use(session.MiddlewareWithConfig(session.Config{})) -``` - -## Configuration - -```go -Config struct { - // Skipper defines a function to skip middleware. - Skipper middleware.Skipper - - // Session store. - // Required. - Store sessions.Store -} -``` - -### Default Configuration - -```go -DefaultConfig = Config{ - Skipper: DefaultSkipper, -} -``` From bd9eaef0f4dde9cb158246322a20cc6b86f9661e Mon Sep 17 00:00:00 2001 From: toim Date: Sun, 15 Feb 2026 17:32:00 +0200 Subject: [PATCH 2/2] update npm dependencies to latest --- website/package-lock.json | 49 +++++++++++++++++++++++++++++++-------- website/package.json | 4 ++-- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 979a1edc..a34dccab 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -15,8 +15,8 @@ "clsx": "2.1.1", "docusaurus-theme-github-codeblock": "2.0.2", "prism-react-renderer": "2.4.1", - "react": "19.2.0", - "react-dom": "19.2.0" + "react": "19.2.4", + "react-dom": "19.2.4" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.9.2" @@ -231,6 +231,7 @@ "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.41.0.tgz", "integrity": "sha512-G9I2atg1ShtFp0t7zwleP6aPS4DcZvsV4uoQOripp16aR6VJzbEnKFPLW4OFXzX7avgZSpYeBAS+Zx4FOgmpPw==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/client-common": "5.41.0", "@algolia/requester-browser-xhr": "5.41.0", @@ -356,6 +357,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2148,6 +2150,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2170,6 +2173,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2279,6 +2283,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2700,6 +2705,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3587,6 +3593,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -4282,6 +4289,7 @@ "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", "license": "MIT", + "peer": true, "dependencies": { "@types/mdx": "^2.0.0" }, @@ -4600,6 +4608,7 @@ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -4966,6 +4975,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5317,6 +5327,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5399,6 +5410,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5444,6 +5456,7 @@ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.41.0.tgz", "integrity": "sha512-9E4b3rJmYbBkn7e3aAPt1as+VVnRhsR4qwRRgOzpeyz4PAOuwKh0HI4AN6mTrqK0S0M9fCCSTOUnuJ8gPY/tvA==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/abtesting": "1.7.0", "@algolia/client-abtesting": "5.41.0", @@ -5907,6 +5920,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -6857,6 +6871,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8239,6 +8254,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12704,6 +12720,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -13219,6 +13236,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14122,6 +14140,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -14897,24 +14916,26 @@ } }, "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.0" + "react": "^19.2.4" } }, "node_modules/react-fast-compare": { @@ -14965,6 +14986,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" }, @@ -14993,6 +15015,7 @@ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -16609,6 +16632,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -16813,7 +16837,8 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/type-fest": { "version": "2.19.0", @@ -17212,6 +17237,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -17419,6 +17445,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -17690,6 +17717,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -18052,6 +18080,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/website/package.json b/website/package.json index 45363734..bd389481 100644 --- a/website/package.json +++ b/website/package.json @@ -21,8 +21,8 @@ "clsx": "2.1.1", "docusaurus-theme-github-codeblock": "2.0.2", "prism-react-renderer": "2.4.1", - "react": "19.2.0", - "react-dom": "19.2.0" + "react": "19.2.4", + "react-dom": "19.2.4" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.9.2"