From eba6a2ce9034d0e6459420ef7ffbe49d838c2191 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 21:05:04 +0300 Subject: [PATCH 01/27] chore(deps): bump github.com/stretchr/testify from 1.12.0 to 1.12.1 in the minor-patch group (#1086) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 ++- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index f80701d8..e17a239d 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/pquerna/otp v1.5.0 github.com/rs/zerolog v1.35.1 github.com/steveiliop56/ding v0.2.0 - github.com/stretchr/testify v1.12.0 + github.com/stretchr/testify v1.12.1 github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298 github.com/weppos/publicsuffix-go v0.50.3 go.uber.org/dig v1.19.0 @@ -132,6 +132,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/arch v0.22.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/mod v0.39.0 // indirect diff --git a/go.sum b/go.sum index ec2ed358..aaa5b18a 100644 --- a/go.sum +++ b/go.sum @@ -291,8 +291,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= -github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298 h1:EYSb5jv8ZL/0/NVFZtY7Ejplk0QG5+3lrdL3mSrjFZQ= github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298/go.mod h1:TlUDoCF66hMqFZqoBym9bUdJ0bKAWYMir6hLJeYN5z0= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -333,8 +333,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= 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= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From d7f35615c17b86a41e7f6e4eda466c295e0b90d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 21:11:24 +0300 Subject: [PATCH 02/27] chore(deps): bump @hookform/resolvers from 5.8.0 to 5.9.0 in /frontend in the minor-patch group (#1084) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 62414e63..83d72dd3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,7 +12,7 @@ }, "packageManager": "pnpm@11.1.2", "dependencies": { - "@hookform/resolvers": "^5.8.0", + "@hookform/resolvers": "^5.9.0", "@radix-ui/react-dropdown-menu": "^2.1.24", "@radix-ui/react-label": "^2.1.15", "@radix-ui/react-select": "^2.3.7", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index c8e2b8d4..b04f31be 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@hookform/resolvers': - specifier: ^5.8.0 - version: 5.8.0(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3) + specifier: ^5.9.0 + version: 5.9.0(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3) '@radix-ui/react-dropdown-menu': specifier: ^2.1.24 version: 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -267,8 +267,8 @@ packages: '@floating-ui/utils@0.2.12': resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} - '@hookform/resolvers@5.8.0': - resolution: {integrity: sha512-2m6GvRLmYYK1Fwt093lGMf7db9l/+8pNuAtwoNkpBntJT4xcA5lNthYGWKViOc3z2SuaPD0HjE81pyXmqc1JyA==} + '@hookform/resolvers@5.9.0': + resolution: {integrity: sha512-8sNo1IklGaONrsKzXjwZJNsPbccAXLXGX//G+VFEcQOviMtbvR8/fM6HWyA0qrjgpOYauwoodda53CQIWOZ7Ag==} peerDependencies: '@sinclair/typebox': '>=0.25.24' '@standard-schema/spec': ^1.0.0 @@ -286,7 +286,7 @@ packages: fluentvalidation-ts: ^3.0.0 fp-ts: ^2.7.0 io-ts: ^2.0.0 - joi: ^17.0.0 + joi: ^17.0.0 || ^18.0.0 nope-validator: '>=0.12.0' react-hook-form: ^7.55.0 superstruct: '>=0.12.0' @@ -2810,7 +2810,7 @@ snapshots: '@floating-ui/utils@0.2.12': {} - '@hookform/resolvers@5.8.0(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3)': + '@hookform/resolvers@5.9.0(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3)': dependencies: '@standard-schema/utils': 0.3.0 react-hook-form: 7.85.0(react@19.2.8) From b541b612c5dc9183ebf20d14aaadc09dbdea2549 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 21:11:58 +0300 Subject: [PATCH 03/27] chore(deps): bump docker/setup-buildx-action from 4.2.0 to 4.3.0 (#1085) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stavros --- .github/workflows/nightly.yml | 12 ++++++------ .github/workflows/release.yml | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 09ce71b9..00de3063 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -174,7 +174,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build and push uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -233,7 +233,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build and push uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -292,7 +292,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build and push uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -351,7 +351,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build and push uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -406,7 +406,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Docker meta id: meta @@ -445,7 +445,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Docker meta id: meta diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8515a612..ef77f8e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build and push uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -203,7 +203,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build and push uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -260,7 +260,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build and push uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -317,7 +317,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build and push uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -373,7 +373,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Docker meta id: meta @@ -414,7 +414,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Docker meta id: meta From be48d712ee4ccbc01cf9b8f2b8d1ee518eba524f Mon Sep 17 00:00:00 2001 From: Stavros Date: Sat, 22 Aug 2026 19:58:05 +0300 Subject: [PATCH 04/27] feat: support for custom claims in oauth (#1087) --- .env.example | 8 +++++++ internal/model/config.go | 32 +++++++++++++++++----------- internal/service/oauth_extractors.go | 10 ++++++--- internal/service/oauth_service.go | 28 ++++++++++++++++++++++-- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index 770a7e97..baf02c49 100644 --- a/.env.example +++ b/.env.example @@ -171,6 +171,14 @@ TINYAUTH_OAUTH_PROVIDERS_name_USERINFOURL= TINYAUTH_OAUTH_PROVIDERS_name_INSECURE=false # Provider name in UI. TINYAUTH_OAUTH_PROVIDERS_name_NAME= +# Username claim. +TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_USERNAME= +# Email claim. +TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_EMAIL= +# Name claim. +TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_NAME= +# Groups claim. +TINYAUTH_OAUTH_PROVIDERS_name_CLAIMS_GROUPS= # oidc config diff --git a/internal/model/config.go b/internal/model/config.go index 5b077fc5..642c00a9 100644 --- a/internal/model/config.go +++ b/internal/model/config.go @@ -253,18 +253,26 @@ type TailscaleConfig struct { // OAuth/OIDC config type OAuthServiceConfig struct { - ClientID string `description:"OAuth client ID." yaml:"clientId,omitempty"` - ClientSecret string `description:"OAuth client secret." yaml:"clientSecret,omitempty"` - ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile,omitempty"` - Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist,omitempty"` - WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile,omitempty"` - Scopes []string `description:"OAuth scopes." yaml:"scopes,omitempty"` - RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl,omitempty"` - AuthURL string `description:"OAuth authorization URL." yaml:"authUrl,omitempty"` - TokenURL string `description:"OAuth token URL." yaml:"tokenUrl,omitempty"` - UserinfoURL string `description:"OAuth userinfo URL." yaml:"userinfoUrl,omitempty"` - Insecure bool `description:"Allow insecure OAuth connections." yaml:"insecure,omitempty"` - Name string `description:"Provider name in UI." yaml:"name,omitempty"` + ClientID string `description:"OAuth client ID." yaml:"clientId,omitempty"` + ClientSecret string `description:"OAuth client secret." yaml:"clientSecret,omitempty"` + ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile,omitempty"` + Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist,omitempty"` + WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile,omitempty"` + Scopes []string `description:"OAuth scopes." yaml:"scopes,omitempty"` + RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl,omitempty"` + AuthURL string `description:"OAuth authorization URL." yaml:"authUrl,omitempty"` + TokenURL string `description:"OAuth token URL." yaml:"tokenUrl,omitempty"` + UserinfoURL string `description:"OAuth userinfo URL." yaml:"userinfoUrl,omitempty"` + Insecure bool `description:"Allow insecure OAuth connections." yaml:"insecure,omitempty"` + Name string `description:"Provider name in UI." yaml:"name,omitempty"` + Claims OAuthServiceClaimsMap `description:"Map of claims to extract from the userinfo response." yaml:"claims,omitempty"` +} + +type OAuthServiceClaimsMap struct { + Username string `description:"Username claim." yaml:"username,omitempty"` + Email string `description:"Email claim." yaml:"email,omitempty"` + Name string `description:"Name claim." yaml:"name,omitempty"` + Groups string `description:"Groups claim." yaml:"groups,omitempty"` } type OIDCClientConfig struct { diff --git a/internal/service/oauth_extractors.go b/internal/service/oauth_extractors.go index 7ce37fd3..52758fd2 100644 --- a/internal/service/oauth_extractors.go +++ b/internal/service/oauth_extractors.go @@ -21,11 +21,15 @@ type GithubUserinfoResponse struct { ID int `json:"id"` } -func defaultExtractor(client *http.Client, ctx context.Context, url string) (*model.Claims, error) { - return simpleReq[model.Claims](client, ctx, url, nil) +func defaultExtractor(client *http.Client, ctx context.Context, url string, mapClaims MapClaims) (*model.Claims, error) { + claims, err := simpleReq[map[string]any](client, ctx, url, nil) + if err != nil { + return nil, err + } + return new(mapClaims(*claims)), nil } -func githubExtractor(client *http.Client, ctx context.Context, _ string) (*model.Claims, error) { +func githubExtractor(client *http.Client, ctx context.Context, _ string, _ MapClaims) (*model.Claims, error) { var user model.Claims userInfo, err := simpleReq[GithubUserinfoResponse](client, ctx, "https://api.github.com/user", map[string]string{ diff --git a/internal/service/oauth_service.go b/internal/service/oauth_service.go index 9667e513..5a372baf 100644 --- a/internal/service/oauth_service.go +++ b/internal/service/oauth_service.go @@ -10,7 +10,8 @@ import ( "golang.org/x/oauth2" ) -type OAuthUserinfoExtractor func(client *http.Client, ctx context.Context, url string) (*model.Claims, error) +type MapClaims func(claims map[string]any) model.Claims +type OAuthUserinfoExtractor func(client *http.Client, ctx context.Context, url string, mapClaims MapClaims) (*model.Claims, error) type OAuthService struct { serviceCfg model.OAuthServiceConfig @@ -81,7 +82,7 @@ func (s *OAuthService) GetToken(code string, verifier string) (*oauth2.Token, er func (s *OAuthService) GetUserinfo(token *oauth2.Token) (*model.Claims, error) { client := oauth2.NewClient(s.ctx, oauth2.StaticTokenSource(token)) - return s.userinfoExtractor(client, s.ctx, s.serviceCfg.UserinfoURL) + return s.userinfoExtractor(client, s.ctx, s.serviceCfg.UserinfoURL, s.mapClaims) } func (s *OAuthService) GetConfig() model.OAuthServiceConfig { @@ -97,3 +98,26 @@ func (s *OAuthService) UpdateConfig(config model.OAuthServiceConfig) { s.config.Endpoint.TokenURL = config.TokenURL s.config.RedirectURL = config.RedirectURL } + +func (s *OAuthService) mapClaims(claims map[string]any) model.Claims { + return model.Claims{ + Sub: mapClaim[string]("sub", "", claims), + Name: mapClaim[string]("name", s.serviceCfg.Claims.Name, claims), + PreferredUsername: mapClaim[string]("preferred_username", s.serviceCfg.Claims.Username, claims), + Email: mapClaim[string]("email", s.serviceCfg.Claims.Email, claims), + Groups: mapClaim[any]("groups", s.serviceCfg.Claims.Groups, claims), + } +} + +func mapClaim[T any](fallback, override string, kv map[string]any) T { + key := fallback + if override != "" { + key = override + } + v, ok := kv[key].(T) + if !ok { + var zero T + return zero + } + return v +} From 847d8325c70b64775fdeaf61343c75fdac855009 Mon Sep 17 00:00:00 2001 From: Stavros Date: Tue, 25 Aug 2026 17:07:56 +0300 Subject: [PATCH 05/27] fix: auth module selection (#1089) --- .env.example | 2 + internal/controller/oauth_controller.go | 4 +- internal/controller/proxy_controller.go | 88 ++++++++++++--- internal/controller/proxy_controller_test.go | 32 +++++- internal/model/config.go | 3 +- internal/service/access_controls_service.go | 48 ++++++-- .../service/access_controls_service_test.go | 106 ++++++++++++++---- internal/utils/app_utils.go | 11 +- internal/utils/app_utils_test.go | 9 +- pkg/validators/domain_validator.go | 8 +- pkg/validators/domain_validator_test.go | 38 ------- 11 files changed, 250 insertions(+), 99 deletions(-) diff --git a/.env.example b/.env.example index baf02c49..bd9843bf 100644 --- a/.env.example +++ b/.env.example @@ -235,6 +235,8 @@ TINYAUTH_LDAP_GROUPCACHETTL=900 # Enable the OAuth bridge, uses a new way to format OAuth user information. TINYAUTH_EXPERIMENTAL_OAUTHBRIDGEENABLED=false +# Disable the fallback to forward_auth modules when auth_request or ext_authz fail. +TINYAUTH_EXPERIMENTAL_DISABLEAUTHMODULEFALLBACK=false # tailscale config diff --git a/internal/controller/oauth_controller.go b/internal/controller/oauth_controller.go index fd6c2658..f48c3fde 100644 --- a/internal/controller/oauth_controller.go +++ b/internal/controller/oauth_controller.go @@ -294,7 +294,9 @@ func (controller *OAuthController) getCookieDomain() string { func (controller *OAuthController) isRedirectSafe(redirectURI string) bool { v := validators.NewDomainValidator(validators.DomainValidatorOptions{ - WithPort: true, + WithPort: true, + WithScheme: true, + AllowedSchemes: []string{"https", "http"}, }) _, err := v.SafeHostname(controller.runtime.AppURL) diff --git a/internal/controller/proxy_controller.go b/internal/controller/proxy_controller.go index c239e29f..7349a2ca 100644 --- a/internal/controller/proxy_controller.go +++ b/internal/controller/proxy_controller.go @@ -57,6 +57,7 @@ type ProxyContext struct { type ProxyController struct { log *logger.Logger runtime *model.RuntimeConfig + config *model.Config acls *service.AccessControlsService auth *service.AuthService policyEngine *service.PolicyEngine @@ -67,6 +68,7 @@ type ProxyControllerInput struct { Log *logger.Logger RuntimeConfig *model.RuntimeConfig + Config *model.Config RouterGroup *gin.RouterGroup `name:"apiRouterGroup"` ACLsService *service.AccessControlsService AuthService *service.AuthService @@ -77,6 +79,7 @@ func NewProxyController(i ProxyControllerInput) *ProxyController { controller := &ProxyController{ log: i.Log, runtime: i.RuntimeConfig, + config: i.Config, acls: i.ACLsService, auth: i.AuthService, policyEngine: i.PolicyEngine, @@ -465,6 +468,10 @@ func (controller *ProxyController) getExtAuthzContext(c *gin.Context) (ProxyCont // We get the path from the query string path := c.Query("path") + if strings.TrimSpace(path) == "" { + return ProxyContext{}, errors.New("path not found") + } + // For envoy we need to support every method method := c.Request.Method @@ -477,14 +484,22 @@ func (controller *ProxyController) getExtAuthzContext(c *gin.Context) (ProxyCont }, nil } -func (controller *ProxyController) determineAuthModules(proxy ProxyType) []AuthModuleType { +func (controller *ProxyController) determineAuthModules(proxy ProxyType, fallbacks bool) []AuthModuleType { switch proxy { case Traefik, Caddy: return []AuthModuleType{ForwardAuth} case Envoy: - return []AuthModuleType{ExtAuthz, ForwardAuth} + authModules := []AuthModuleType{ExtAuthz} + if fallbacks { + authModules = append(authModules, ForwardAuth) + } + return authModules case Nginx: - return []AuthModuleType{AuthRequest, ForwardAuth} + authModules := []AuthModuleType{AuthRequest} + if fallbacks { + authModules = append(authModules, ForwardAuth) + } + return authModules default: return []AuthModuleType{} } @@ -514,6 +529,39 @@ func (controller *ProxyController) getContextFromAuthModule(c *gin.Context, modu return ProxyContext{}, fmt.Errorf("unsupported auth module: %v", module) } +func (controller *ProxyController) authModuleIdentifiersPresent(c *gin.Context, module AuthModuleType) bool { + switch module { + case ForwardAuth: + _, host := controller.getHeader(c, "x-forwarded-host") + _, uri := controller.getHeader(c, "x-forwarded-uri") + return host || uri + case AuthRequest: + _, ok := controller.getHeader(c, "x-original-url") + return ok + case ExtAuthz: + return strings.TrimSpace(c.Query("path")) != "" + default: + return false + } +} + +func (controller *ProxyController) ensureNoMultipleAuthModules(c *gin.Context, authModules []AuthModuleType) error { + present := 0 + + for _, module := range authModules { + if controller.authModuleIdentifiersPresent(c, module) { + present++ + } + } + + if present > 1 { + controller.log.App.Warn().Msg("Request carries headers for multiple auth modules, possible spoofing attempt, denying") + return fmt.Errorf("conflicting auth module headers") + } + + return nil +} + func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext, error) { var req Proxy @@ -530,28 +578,36 @@ func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext controller.log.App.Debug().Msgf("Determined proxy type: %v", proxy) - authModules := controller.determineAuthModules(proxy) + authModules := controller.determineAuthModules(proxy, !controller.config.Experimental.DisableAuthModuleFallback) if len(authModules) == 0 { return ProxyContext{}, fmt.Errorf("no auth modules supported for proxy: %v", req.Proxy) } - var ctx ProxyContext - - for _, module := range authModules { - controller.log.App.Debug().Msgf("Trying to get context from auth module %v", module) - ctx, err = controller.getContextFromAuthModule(c, module) - if err == nil { - controller.log.App.Debug().Msgf("Successfully got context from auth module %v", module) - break - } - controller.log.App.Debug().Msgf("Failed to get context from auth module %v: %v", module, err) - } + err = controller.ensureNoMultipleAuthModules(c, controller.determineAuthModules(proxy, true)) if err != nil { return ProxyContext{}, err } + var ctx *ProxyContext + + for _, module := range authModules { + controller.log.App.Debug().Msgf("Trying to get context from auth module %v", module) + authModuleCtx, err := controller.getContextFromAuthModule(c, module) + if err != nil { + controller.log.App.Debug().Msgf("Failed to get context from auth module %v: %v", module, err) + continue + } + controller.log.App.Debug().Msgf("Successfully got context from auth module %v", module) + ctx = &authModuleCtx + break + } + + if ctx == nil { + return ProxyContext{}, fmt.Errorf("failed to get context from any auth module") + } + // Parse the raw path to populate the cleaned path used for ACLs upath, err := url.Parse(ctx.PathRaw) @@ -577,5 +633,5 @@ func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext ctx.IsBrowser = isBrowser ctx.ProxyType = proxy - return ctx, nil + return *ctx, nil } diff --git a/internal/controller/proxy_controller_test.go b/internal/controller/proxy_controller_test.go index 4d2e23a3..fd06ae39 100644 --- a/internal/controller/proxy_controller_test.go +++ b/internal/controller/proxy_controller_test.go @@ -213,7 +213,7 @@ func TestProxyController(t *testing.T) { description: "Ensure forward auth fallback for envoy", middlewares: []gin.HandlerFunc{}, run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { - req := httptest.NewRequest("HEAD", "/api/auth/envoy?path=/hello", nil) + req := httptest.NewRequest("HEAD", "/api/auth/envoy", nil) req.Host = "" req.Header.Set("x-forwarded-host", "test.example.com") req.Header.Set("x-forwarded-proto", "https") @@ -261,7 +261,7 @@ func TestProxyController(t *testing.T) { description: "Ensure extauthz with envoy non browser returns json", middlewares: []gin.HandlerFunc{}, run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { - req := httptest.NewRequest("HEAD", "/api/auth/envoy?path=/hello", nil) + req := httptest.NewRequest("HEAD", "/api/auth/envoy", nil) req.Header.Set("x-forwarded-host", "test.example.com") req.Header.Set("x-forwarded-proto", "https") req.Header.Set("x-forwarded-uri", "/hello") @@ -877,6 +877,32 @@ func TestProxyController(t *testing.T) { assert.Equal(t, "bar", recorder.Header().Get("x-foo")) }, }, + { + description: "Forward auth and auth request headers should fail for nginx", + run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { + req := httptest.NewRequest("GET", "/api/auth/nginx", nil) + req.Header.Set("x-forwarded-host", "foo.example.com") + req.Header.Set("x-forwarded-proto", "https") + req.Header.Set("x-forwarded-uri", "/foo?bar=foo") + req.Header.Set("x-original-url", "https://foo.example.com/foo?bar=foo") + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusBadRequest, recorder.Code) + }, + }, + { + description: "Forward auth and ext authz headers should fail for envoy", + run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) { + req := httptest.NewRequest("HEAD", "/api/auth/envoy?path=/hello", nil) + req.Host = "foo.example.com" + req.Header.Set("x-forwarded-host", "foo.example.com") + req.Header.Set("x-forwarded-proto", "https") + req.Header.Set("x-forwarded-uri", "/foo?bar=foo") + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusBadRequest, recorder.Code) + }, + }, } store := memory.New() @@ -892,6 +918,7 @@ func TestProxyController(t *testing.T) { aclsService := service.NewAccessControlsService(service.AccessControlServiceInput{ Log: log, Config: &cfg, + Runtime: &runtime, LabelProvider: nil, }) @@ -952,6 +979,7 @@ func TestProxyController(t *testing.T) { NewProxyController(ProxyControllerInput{ Log: log, RuntimeConfig: &runtime, + Config: &cfg, RouterGroup: group, ACLsService: aclsService, AuthService: authService, diff --git a/internal/model/config.go b/internal/model/config.go index 642c00a9..80b986f0 100644 --- a/internal/model/config.go +++ b/internal/model/config.go @@ -239,7 +239,8 @@ type LogStreamConfig struct { } type ExperimentalConfig struct { - OAuthBridgeEnabled bool `description:"Enable the OAuth bridge, uses a new way to format OAuth user information." yaml:"oauthBridgeEnabled,omitempty"` + OAuthBridgeEnabled bool `description:"Enable the OAuth bridge, uses a new way to format OAuth user information." yaml:"oauthBridgeEnabled,omitempty"` + DisableAuthModuleFallback bool `description:"Disable the fallback to forward_auth modules when auth_request or ext_authz fail." yaml:"disableAuthModuleFallback,omitempty"` } type TailscaleConfig struct { diff --git a/internal/service/access_controls_service.go b/internal/service/access_controls_service.go index f8816a1f..922926bd 100644 --- a/internal/service/access_controls_service.go +++ b/internal/service/access_controls_service.go @@ -2,11 +2,13 @@ package service import ( "errors" + "fmt" + "net" "strings" + "unicode" "github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/utils/logger" - "github.com/tinyauthapp/tinyauth/pkg/validators" "go.uber.org/dig" ) @@ -17,6 +19,7 @@ type LabelProvider interface { type AccessControlsService struct { log *logger.Logger config *model.Config + runtime *model.RuntimeConfig labelProvider LabelProvider } @@ -25,6 +28,7 @@ type AccessControlServiceInput struct { Log *logger.Logger Config *model.Config + Runtime *model.RuntimeConfig LabelProvider LabelProvider `optional:"true"` } @@ -33,12 +37,38 @@ func NewAccessControlsService(i AccessControlServiceInput) *AccessControlsServic return &AccessControlsService{ log: i.Log, config: i.Config, + runtime: i.Runtime, labelProvider: i.LabelProvider, } } +func (service *AccessControlsService) ensureAscii(str string) bool { + for i := 0; i < len(str); i++ { + if str[i] > unicode.MaxASCII { + return false + } + } + return true +} + +func (service *AccessControlsService) normalizeDomain(domain string) string { + if host, _, err := net.SplitHostPort(domain); err == nil { + domain = host + } + domain = strings.TrimRight(domain, ".") + return strings.ToLower(domain) +} + func (service *AccessControlsService) getACLs(domain string, lookup func(locator func(name string, app *model.App) bool) error) (*model.App, error) { - v := validators.NewDomainValidator(validators.DomainValidatorOptions{}) + if !service.ensureAscii(domain) { + return nil, errors.New("domain contains non-ascii characters") + } + + normalizedDomain := service.normalizeDomain(domain) + + if !strings.HasSuffix(normalizedDomain, "."+service.runtime.CookieDomain) && normalizedDomain != service.runtime.CookieDomain { + return nil, fmt.Errorf("domain does not match cookie domain, expected %s (or a subdomain), got %s", service.runtime.CookieDomain, domain) + } var domainMatch *model.App var nameMatch *model.App @@ -46,16 +76,18 @@ func (service *AccessControlsService) getACLs(domain string, lookup func(locator locatorFunc := func(name string, app *model.App) bool { if app.Config.Domain != "" { - err := v.Validate(app.Config.Domain, domain) - if err == nil { + if !service.ensureAscii(app.Config.Domain) { + service.log.App.Warn().Str("name", name).Str("domain", app.Config.Domain).Msg("Domain contains non-ascii characters, skipping") + return false + } + if normalizedDomain == service.normalizeDomain(app.Config.Domain) { service.log.App.Debug().Str("name", name).Msg("Found matching container by domain") domainMatch = app return true - } else if !errors.Is(err, validators.ErrHostnameMismatch) { - service.log.App.Debug().Str("name", name).Err(err).Msg("Domain validation failed") } + return false } - if strings.HasPrefix(strings.ToLower(domain), strings.ToLower(name+".")) { + if strings.HasPrefix(normalizedDomain, strings.ToLower(name+".")) { service.log.App.Debug().Str("name", name).Msg("Found matching container by app name") nameMatch = app nameMatchedApps = append(nameMatchedApps, name) @@ -79,7 +111,7 @@ func (service *AccessControlsService) getACLs(domain string, lookup func(locator } if len(nameMatchedApps) > 1 { - service.log.App.Warn().Str("domain", domain).Strs("apps", nameMatchedApps).Msg("Multiple apps matched domain by name, app names must be unique, using last match") + return nil, fmt.Errorf("domain matched multiple apps by name prefix, use explicit domain config") } service.log.App.Debug().Str("domain", domain).Msg("Found matching app by app name") diff --git a/internal/service/access_controls_service_test.go b/internal/service/access_controls_service_test.go index 30415933..c5d00b7c 100644 --- a/internal/service/access_controls_service_test.go +++ b/internal/service/access_controls_service_test.go @@ -4,8 +4,10 @@ import ( "errors" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tinyauthapp/tinyauth/internal/model" + "github.com/tinyauthapp/tinyauth/internal/test" "github.com/tinyauthapp/tinyauth/internal/utils/logger" ) @@ -34,14 +36,25 @@ func TestAccessControlsService(t *testing.T) { log := logger.NewLogger().WithTestConfig() log.Init() + _, runtime := test.CreateTestConfigs(t) + tests := []struct { - name string - domain string - acls map[string]model.App - want *model.App + name string + domain string + acls map[string]model.App + want *model.App + errorFunc func(t *testing.T, e error) }{ { name: "returns ACLs for domain", + domain: "app.example.com", + acls: map[string]model.App{ + "foo": {Config: model.AppConfig{Domain: "app.example.com"}}, + }, + want: &model.App{Config: model.AppConfig{Domain: "app.example.com"}}, + }, + { + name: "returns ACLs for root domain", domain: "example.com", acls: map[string]model.App{ "foo": {Config: model.AppConfig{Domain: "example.com"}}, @@ -65,20 +78,11 @@ func TestAccessControlsService(t *testing.T) { want: &model.App{Config: model.AppConfig{Domain: "example.com"}}, }, { - name: "returns ACLs for non-ascii domain", + name: "returns error for non-ascii domain", domain: "bücher.example.com", - acls: map[string]model.App{ - "foo": {Config: model.AppConfig{Domain: "bücher.example.com"}}, + errorFunc: func(t *testing.T, e error) { + assert.ErrorContains(t, e, "domain contains non-ascii characters") }, - want: &model.App{Config: model.AppConfig{Domain: "bücher.example.com"}}, - }, - { - name: "returns ACLs for punycode domain and non-ascii config", - domain: "bücher.example.com", - acls: map[string]model.App{ - "foo": {Config: model.AppConfig{Domain: "xn--bcher-kva.example.com"}}, - }, - want: &model.App{Config: model.AppConfig{Domain: "xn--bcher-kva.example.com"}}, }, { name: "returns ACLs with case-insensitive matching", @@ -110,6 +114,33 @@ func TestAccessControlsService(t *testing.T) { acls: map[string]model.App{}, want: nil, }, + { + name: "App in domain not matching with the cookie domain should return nothing with name matching", + domain: "foo.bad_example.com", + acls: map[string]model.App{ + "foo": { + Path: model.AppPath{Allow: "/foo"}, + }, + }, + want: nil, + errorFunc: func(t *testing.T, e error) { + assert.ErrorContains(t, e, "domain does not match cookie domain") + }, + }, + { + name: "App in domain not matching with the cookie domain should return nothing with domain matching", + domain: "foo.bad_example.com", + acls: map[string]model.App{ + "foo": { + Path: model.AppPath{Allow: "/foo"}, + Config: model.AppConfig{Domain: "foo.bad_example.com"}, + }, + }, + want: nil, + errorFunc: func(t *testing.T, e error) { + assert.ErrorContains(t, e, "domain does not match cookie domain") + }, + }, } // run once for a mock provider @@ -118,10 +149,15 @@ func TestAccessControlsService(t *testing.T) { mock := newMockProvider(test.acls, false) acls := NewAccessControlsService(AccessControlServiceInput{ Log: log, + Runtime: &runtime, Config: &model.Config{}, LabelProvider: mock, }) app, err := acls.getACLs(test.domain, mock.Lookup) + if test.errorFunc != nil { + test.errorFunc(t, err) + return + } require.NoError(t, err) require.Equal(t, test.want, app) }) @@ -131,12 +167,17 @@ func TestAccessControlsService(t *testing.T) { for _, test := range tests { t.Run(test.name+"(staticACLs)", func(t *testing.T) { acls := NewAccessControlsService(AccessControlServiceInput{ - Log: log, + Log: log, + Runtime: &runtime, Config: &model.Config{ Apps: test.acls, }, }) app, err := acls.lookupStaticACLs(test.domain) + if test.errorFunc != nil { + test.errorFunc(t, err) + return + } require.NoError(t, err) require.Equal(t, test.want, app) }) @@ -145,16 +186,32 @@ func TestAccessControlsService(t *testing.T) { // get acls should return an error when the provider fails mock := newMockProvider(map[string]model.App{}, true) acls := NewAccessControlsService(AccessControlServiceInput{ - Log: log, - Config: &model.Config{}, + Log: log, + Runtime: &runtime, + Config: &model.Config{}, }) _, err := acls.getACLs("example.com", mock.Lookup) - require.Error(t, err) + assert.Error(t, err) + + // get acls should return an error when multiple apps with the same domain exist + acls = NewAccessControlsService(AccessControlServiceInput{ + Log: log, + Runtime: &runtime, + Config: &model.Config{ + Apps: map[string]model.App{ + "foo": {Path: model.AppPath{Allow: "/foo"}}, + "foo.bar": {Path: model.AppPath{Allow: "/bar"}}, + }, + }, + }) + _, err = acls.GetAccessControls("foo.bar.example.com") + assert.ErrorContains(t, err, "domain matched multiple apps by name prefix, use explicit domain config") // get access controls should get acls from // static when static acls are configured acls = NewAccessControlsService(AccessControlServiceInput{ - Log: log, + Log: log, + Runtime: &runtime, Config: &model.Config{ Apps: map[string]model.App{ "foo": {Config: model.AppConfig{Domain: "foo.example.com"}}, @@ -163,12 +220,12 @@ func TestAccessControlsService(t *testing.T) { }) app, err := acls.GetAccessControls("foo.example.com") require.NoError(t, err) - require.Equal(t, &model.App{Config: model.AppConfig{Domain: "foo.example.com"}}, app) + assert.Equal(t, &model.App{Config: model.AppConfig{Domain: "foo.example.com"}}, app) // should return nil for no apps app, err = acls.GetAccessControls("bar.example.com") require.NoError(t, err) - require.Nil(t, app) + assert.Nil(t, app) // Should use label provider if available mock = newMockProvider(map[string]model.App{ @@ -178,10 +235,11 @@ func TestAccessControlsService(t *testing.T) { }, false) acls = NewAccessControlsService(AccessControlServiceInput{ Log: log, + Runtime: &runtime, Config: &model.Config{}, LabelProvider: mock, }) app, err = acls.GetAccessControls("bar.example.com") require.NoError(t, err) - require.Equal(t, &model.App{Config: model.AppConfig{Domain: "bar.example.com"}}, app) + assert.Equal(t, &model.App{Config: model.AppConfig{Domain: "bar.example.com"}}, app) } diff --git a/internal/utils/app_utils.go b/internal/utils/app_utils.go index 3bc3546a..7c168423 100644 --- a/internal/utils/app_utils.go +++ b/internal/utils/app_utils.go @@ -38,7 +38,16 @@ func SafeParseAppURL(str string) (string, error) { return "", fmt.Errorf("ip addresses not allowed") } - hostname, err = idna.Lookup.ToASCII(hostname) + i := idna.New( + idna.MapForLookup(), + idna.Transitional(false), + idna.BidiRule(), + idna.StrictDomainName(false), + idna.CheckHyphens(true), + idna.CheckJoiners(false), + ) + + hostname, err = i.ToASCII(hostname) if err != nil { return "", fmt.Errorf("failed to convert hostname to ascii: %w", err) diff --git a/internal/utils/app_utils_test.go b/internal/utils/app_utils_test.go index 8c9e9bc5..6dbe4492 100644 --- a/internal/utils/app_utils_test.go +++ b/internal/utils/app_utils_test.go @@ -43,6 +43,13 @@ func TestSafeParseAPPURL(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expected, result) + // Underscores + appURL = "http://sub_tinyauth.app" + expected = "http://sub_tinyauth.app" + result, err = utils.SafeParseAppURL(appURL) + assert.NoError(t, err) + assert.Equal(t, expected, result) + // Lowercase appURL = "HTTP://SUb.tinyAUth.aPP" expected = "http://sub.tinyauth.app" @@ -66,7 +73,7 @@ func TestSafeParseAPPURL(t *testing.T) { assert.ErrorContains(t, err, "invalid url") // Invalid punycode - appURL = "http://ab--cd.example.com" + appURL = "http://xn--h-kva.example.com" _, err = utils.SafeParseAppURL(appURL) assert.ErrorContains(t, err, "failed to convert hostname to ascii") diff --git a/pkg/validators/domain_validator.go b/pkg/validators/domain_validator.go index d41d82b3..9d0503f8 100644 --- a/pkg/validators/domain_validator.go +++ b/pkg/validators/domain_validator.go @@ -11,8 +11,6 @@ import ( "net" "net/url" "strings" - - "golang.org/x/net/idna" ) // Errors @@ -115,14 +113,10 @@ func (v *DomainValidator) getURL(i string) (*url.URL, error) { func (v *DomainValidator) getHostname(hostname string) (string, error) { hostname = strings.ToLower(hostname) - hostname = strings.TrimSuffix(hostname, ".") + hostname = strings.TrimRight(hostname, ".") if net.ParseIP(hostname) != nil { return "", fmt.Errorf("ip addresses are not supported") } - hostname, err := idna.Lookup.ToASCII(hostname) - if err != nil { - return "", fmt.Errorf("failed to convert hostname to ascii: %w", err) - } return hostname, nil } diff --git a/pkg/validators/domain_validator_test.go b/pkg/validators/domain_validator_test.go index aa7587f1..b47c52ec 100644 --- a/pkg/validators/domain_validator_test.go +++ b/pkg/validators/domain_validator_test.go @@ -101,18 +101,6 @@ func TestDomainValidator_SafeHostname(t *testing.T) { assert.ErrorContains(t, e, "ip addresses are not supported") }, }, - { - description: "Domains with unicode characters should be allowed", - input: "bücher.example.com", - expected: "xn--bcher-kva.example.com", - }, - { - description: "Invalid IDNA domain should fail", - input: "ab--cd.example.com", - errorFunc: func(t *testing.T, e error) { - assert.ErrorContains(t, e, "invalid label") - }, - }, { description: "With port enabled without any port should work", options: DomainValidatorOptions{WithPort: true}, @@ -194,22 +182,6 @@ func TestDomainValidator_Validate(t *testing.T) { expected: "https://example.com:443", actual: "https://example.com:443", }, - { - description: "Failure to format expected domain should fail", - expected: "ab--cd.example.com", - actual: "example.com", - errorFunc: func(t *testing.T, e error) { - assert.ErrorContains(t, e, "idna: invalid label") - }, - }, - { - description: "Failure to format check domain should fail", - expected: "example.com", - actual: "ab--cd.example.com", - errorFunc: func(t *testing.T, e error) { - assert.ErrorContains(t, e, "idna: invalid label") - }, - }, { description: "Valid domains with matching schemes and ports should pass", options: DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https", "http"}, WithPort: true}, @@ -236,16 +208,6 @@ func TestDomainValidator_Validate(t *testing.T) { actual: "example.com", expected: "example.com", }, - { - description: "Unicode valid domains should pass", - expected: "xn--bcher-kva.example.com", - actual: "bücher.example.com", - }, - { - description: "Unicode valid domains should pass (reverse)", - expected: "bücher.example.com", - actual: "xn--bcher-kva.example.com", - }, { description: "Non matching hostnames should fail", expected: "example.com", From 640354fedd6fcefdcac07c062cecc29a07edbc05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:10:27 +0300 Subject: [PATCH 06/27] chore(deps): bump github/codeql-action/upload-sarif from 4.37.7 to 4.37.8 (#1092) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index d08ae275..556a1328 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -38,6 +38,6 @@ jobs: retention-days: 5 - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: sarif_file: results.sarif From bd5a727492650ff1f7242a7c63862f3576f6ed9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:10:57 +0300 Subject: [PATCH 07/27] chore(deps): bump the minor-patch group with 3 updates (#1091) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index e17a239d..b8d46e02 100644 --- a/go.mod +++ b/go.mod @@ -28,9 +28,9 @@ require ( golang.org/x/oauth2 v0.36.0 golang.org/x/tools v0.49.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apimachinery v0.36.3 - k8s.io/client-go v0.36.3 - modernc.org/sqlite v1.56.0 + k8s.io/apimachinery v0.36.4 + k8s.io/client-go v0.36.4 + modernc.org/sqlite v1.57.0 ) require ( diff --git a/go.sum b/go.sum index aaa5b18a..c6abb3f2 100644 --- a/go.sum +++ b/go.sum @@ -380,12 +380,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= -k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= -k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= -k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= -k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= -k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= +k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo= +k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw= +k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4= +k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk= +k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0= +k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= @@ -414,8 +414,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0= -modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ= +modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg= +modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From 306d91f00ae313d1368463e20b62b454833adfc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:11:38 +0300 Subject: [PATCH 08/27] chore(deps): bump the minor-patch group across 1 directory with 8 updates (#1095) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package.json | 16 +- frontend/pnpm-lock.yaml | 317 +++++++++++++++++++++------------------- 2 files changed, 173 insertions(+), 160 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 83d72dd3..b9dd68f7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,7 +12,7 @@ }, "packageManager": "pnpm@11.1.2", "dependencies": { - "@hookform/resolvers": "^5.9.0", + "@hookform/resolvers": "^5.9.1", "@radix-ui/react-dropdown-menu": "^2.1.24", "@radix-ui/react-label": "^2.1.15", "@radix-ui/react-select": "^2.3.7", @@ -23,16 +23,16 @@ "axios": "^1.19.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "i18next": "^26.3.6", + "i18next": "^26.4.0", "i18next-browser-languagedetector": "^8.2.1", "i18next-resources-to-backend": "^1.2.3", - "lucide-react": "^1.31.0", + "lucide-react": "^1.33.0", "next-themes": "^0.4.6", "radix-ui": "^1.6.7", "react": "^19.2.8", "react-dom": "^19.2.8", - "react-hook-form": "^7.85.0", - "react-i18next": "^17.0.11", + "react-hook-form": "^7.86.0", + "react-i18next": "^17.0.12", "react-markdown": "^10.1.0", "react-router": "^8.3.0", "sonner": "^2.0.8", @@ -46,8 +46,8 @@ "@types/node": "^26.2.0", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", - "@vitejs/plugin-react": "^6.0.5", - "eslint": "^10.8.1", + "@vitejs/plugin-react": "^6.1.0", + "eslint": "^10.9.0", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.4", "globals": "^17.11.0", @@ -55,6 +55,6 @@ "tw-animate-css": "^1.4.0", "typescript": "~6.0.2", "typescript-eslint": "^8.67.0", - "vite": "^8.2.1" + "vite": "^8.2.2" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index b04f31be..c4b4a916 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@hookform/resolvers': - specifier: ^5.9.0 - version: 5.9.0(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3) + specifier: ^5.9.1 + version: 5.9.1(react-hook-form@7.86.0(react@19.2.8))(zod@4.4.3) '@radix-ui/react-dropdown-menu': specifier: ^2.1.24 version: 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -28,7 +28,7 @@ importers: version: 1.3.3(@types/react@19.2.18)(react@19.2.8) '@tailwindcss/vite': specifier: ^4.3.3 - version: 4.3.3(vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0)) + version: 4.3.3(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)) '@tanstack/react-query': specifier: ^5.101.4 version: 5.101.4(react@19.2.8) @@ -42,8 +42,8 @@ importers: specifier: ^2.1.1 version: 2.1.1 i18next: - specifier: ^26.3.6 - version: 26.3.6(typescript@6.0.3) + specifier: ^26.4.0 + version: 26.4.0(typescript@6.0.3) i18next-browser-languagedetector: specifier: ^8.2.1 version: 8.2.1 @@ -51,8 +51,8 @@ importers: specifier: ^1.2.3 version: 1.2.3 lucide-react: - specifier: ^1.31.0 - version: 1.31.0(react@19.2.8) + specifier: ^1.33.0 + version: 1.33.0(react@19.2.8) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -66,11 +66,11 @@ importers: specifier: ^19.2.8 version: 19.2.8(react@19.2.8) react-hook-form: - specifier: ^7.85.0 - version: 7.85.0(react@19.2.8) + specifier: ^7.86.0 + version: 7.86.0(react@19.2.8) react-i18next: - specifier: ^17.0.11 - version: 17.0.11(i18next@26.3.6(typescript@6.0.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@6.0.3) + specifier: ^17.0.12 + version: 17.0.12(i18next@26.4.0(typescript@6.0.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@6.0.3) react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.2.18)(react@19.2.8) @@ -92,10 +92,10 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.8.1(jiti@2.7.0)) + version: 10.0.1(eslint@10.9.0(jiti@2.7.0)) '@tanstack/eslint-plugin-query': specifier: ^5.101.4 - version: 5.101.4(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) + version: 5.101.4(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) '@types/node': specifier: ^26.2.0 version: 26.2.0 @@ -106,23 +106,23 @@ importers: specifier: ^19.2.4 version: 19.2.4(@types/react@19.2.18) '@vitejs/plugin-react': - specifier: ^6.0.5 - version: 6.0.5(vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0)) + specifier: ^6.1.0 + version: 6.1.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)) eslint: - specifier: ^10.8.1 - version: 10.8.1(jiti@2.7.0) + specifier: ^10.9.0 + version: 10.9.0(jiti@2.7.0) eslint-plugin-react-hooks: specifier: ^7.0.1 - version: 7.1.1(eslint@10.8.1(jiti@2.7.0)) + version: 7.1.1(eslint@10.9.0(jiti@2.7.0)) eslint-plugin-react-refresh: specifier: ^0.5.4 - version: 0.5.4(eslint@10.8.1(jiti@2.7.0)) + version: 0.5.4(eslint@10.9.0(jiti@2.7.0)) globals: specifier: ^17.11.0 version: 17.11.0 rollup-plugin-visualizer: specifier: ^7.1.1 - version: 7.1.1(rolldown@1.2.3) + version: 7.1.1(rolldown@1.2.5) tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -131,10 +131,10 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.67.0 - version: 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) + version: 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) vite: - specifier: ^8.2.1 - version: 8.2.1(@types/node@26.2.0)(jiti@2.7.0) + specifier: ^8.2.2 + version: 8.2.2(@types/node@26.2.0)(jiti@2.7.0) packages: @@ -267,8 +267,8 @@ packages: '@floating-ui/utils@0.2.12': resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} - '@hookform/resolvers@5.9.0': - resolution: {integrity: sha512-8sNo1IklGaONrsKzXjwZJNsPbccAXLXGX//G+VFEcQOviMtbvR8/fM6HWyA0qrjgpOYauwoodda53CQIWOZ7Ag==} + '@hookform/resolvers@5.9.1': + resolution: {integrity: sha512-7b7vsbraJxKgjVSA1Nur9tLwj539WGJUBLA7QNvXnFoT2pM5Z7G+6rlukk4B2/QrTZy6huRtH6wKeESPKuIr6w==} peerDependencies: '@sinclair/typebox': '>=0.25.24' '@standard-schema/spec': ^1.0.0 @@ -381,8 +381,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@oxc-project/types@0.143.0': - resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} + '@oxc-project/types@0.146.0': + resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==} '@radix-ui/number@1.1.3': resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} @@ -1074,92 +1074,98 @@ packages: '@radix-ui/rect@1.1.3': resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} - '@rolldown/binding-android-arm64@1.2.3': - resolution: {integrity: sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==} + '@rolldown/binding-android-arm-eabi@1.2.5': + resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.5': + resolution: {integrity: sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.3': - resolution: {integrity: sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==} + '@rolldown/binding-darwin-arm64@1.2.5': + resolution: {integrity: sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.3': - resolution: {integrity: sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==} + '@rolldown/binding-darwin-x64@1.2.5': + resolution: {integrity: sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.3': - resolution: {integrity: sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==} + '@rolldown/binding-freebsd-x64@1.2.5': + resolution: {integrity: sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.3': - resolution: {integrity: sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.5': + resolution: {integrity: sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.3': - resolution: {integrity: sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==} + '@rolldown/binding-linux-arm64-gnu@1.2.5': + resolution: {integrity: sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.3': - resolution: {integrity: sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==} + '@rolldown/binding-linux-arm64-musl@1.2.5': + resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.3': - resolution: {integrity: sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==} + '@rolldown/binding-linux-ppc64-gnu@1.2.5': + resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.3': - resolution: {integrity: sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==} + '@rolldown/binding-linux-s390x-gnu@1.2.5': + resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.3': - resolution: {integrity: sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==} + '@rolldown/binding-linux-x64-gnu@1.2.5': + resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.3': - resolution: {integrity: sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==} + '@rolldown/binding-linux-x64-musl@1.2.5': + resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.3': - resolution: {integrity: sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==} + '@rolldown/binding-openharmony-arm64@1.2.5': + resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.3': - resolution: {integrity: sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==} + '@rolldown/binding-win32-arm64-msvc@1.2.5': + resolution: {integrity: sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.3': - resolution: {integrity: sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==} + '@rolldown/binding-win32-x64-msvc@1.2.5': + resolution: {integrity: sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1421,18 +1427,21 @@ packages: '@ungap/structured-clone@1.3.1': resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} - '@vitejs/plugin-react@6.0.5': - resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==} + '@vitejs/plugin-react@6.1.0': + resolution: {integrity: sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.145.0 vite: ^8.0.0 peerDependenciesMeta: '@rolldown/plugin-babel': optional: true babel-plugin-react-compiler: optional: true + oxc-transform-react: + optional: true acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -1653,8 +1662,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.8.1: - resolution: {integrity: sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==} + eslint@10.9.0: + resolution: {integrity: sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1822,8 +1831,8 @@ packages: i18next-resources-to-backend@1.2.3: resolution: {integrity: sha512-8Y/LLAm5fqZc2ckQxtTWbu75ndjNLzF7mcYl6rhc4g+WBopIsG3YhdYqF5qhJy64tlYhlU2KSSnEgW3gGHLqwg==} - i18next@26.3.6: - resolution: {integrity: sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==} + i18next@26.4.0: + resolution: {integrity: sha512-rsmK5bFqsD1AetSFSIa43wtNR4WpvvH4p0tLEsTxkC7QTrfdFm06nbQ95bh8Og4wwaCnUEcm9DVYL2cgxitiQg==} peerDependencies: typescript: ^5 || ^6 || ^7 peerDependenciesMeta: @@ -2081,8 +2090,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@1.31.0: - resolution: {integrity: sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg==} + lucide-react@1.33.0: + resolution: {integrity: sha512-MTRwMy0ZlL8Ur/vOAiJ9XGHE+kFPC7brq6MxAm0GiGXEBj0qy0jA/pG4N675oSzciO/UCdX8T+5yUQdmDeTLxg==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2291,14 +2300,14 @@ packages: peerDependencies: react: ^19.2.8 - react-hook-form@7.85.0: - resolution: {integrity: sha512-U2MTriFXnclmV4rOE20p2DcRFv5WEg3FIcBFOKcOLFHDVvGIMPvLTkTWefUsonmlaVy23khVDxDWym6uJVGOzw==} + react-hook-form@7.86.0: + resolution: {integrity: sha512-4kbWJrh5jPZt1+YqVcXcGKffGcXV/XVbozknLh0Yjh0KhpoAkus21TAQhzRYqNwFkkObmnSvRlZZ3GT+ehoIrA==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-i18next@17.0.11: - resolution: {integrity: sha512-cDtkXgxjuFTWUH6V+aQn1Ve5vDiUztCNPWW5GtSHDccsgRXO1nE6QFWCEmc1KAutrb3OUv87wFShJL5RhUwPXg==} + react-i18next@17.0.12: + resolution: {integrity: sha512-lFWPEGkxQ6RhusdUkysFBD58VHfSSzvHBzqMgN0SvfVpdQGfwtNkStTqdy08/sJd7s807qqutgx93fRpD0DJ3Q==} peerDependencies: i18next: '>= 26.2.0' react: '>= 16.8.0' @@ -2369,8 +2378,8 @@ packages: remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} - rolldown@1.2.3: - resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} + rolldown@1.2.5: + resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -2562,13 +2571,13 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite@8.2.1: - resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} + vite@8.2.2: + resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 + '@vitejs/devtools': ^0.4.0 || ^0.5.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -2759,9 +2768,9 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils@4.10.1(eslint@10.8.1(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.9.0(jiti@2.7.0))': dependencies: - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2782,9 +2791,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.8.1(jiti@2.7.0))': + '@eslint/js@10.0.1(eslint@10.9.0(jiti@2.7.0))': optionalDependencies: - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) '@eslint/object-schema@3.0.5': {} @@ -2810,10 +2819,10 @@ snapshots: '@floating-ui/utils@0.2.12': {} - '@hookform/resolvers@5.9.0(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3)': + '@hookform/resolvers@5.9.1(react-hook-form@7.86.0(react@19.2.8))(zod@4.4.3)': dependencies: '@standard-schema/utils': 0.3.0 - react-hook-form: 7.85.0(react@19.2.8) + react-hook-form: 7.86.0(react@19.2.8) optionalDependencies: zod: 4.4.3 @@ -2852,7 +2861,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@oxc-project/types@0.143.0': {} + '@oxc-project/types@0.146.0': {} '@radix-ui/number@1.1.3': {} @@ -3601,46 +3610,49 @@ snapshots: '@radix-ui/rect@1.1.3': {} - '@rolldown/binding-android-arm64@1.2.3': + '@rolldown/binding-android-arm-eabi@1.2.5': optional: true - '@rolldown/binding-darwin-arm64@1.2.3': + '@rolldown/binding-android-arm64@1.2.5': optional: true - '@rolldown/binding-darwin-x64@1.2.3': + '@rolldown/binding-darwin-arm64@1.2.5': optional: true - '@rolldown/binding-freebsd-x64@1.2.3': + '@rolldown/binding-darwin-x64@1.2.5': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.3': + '@rolldown/binding-freebsd-x64@1.2.5': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.3': + '@rolldown/binding-linux-arm-gnueabihf@1.2.5': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.3': + '@rolldown/binding-linux-arm64-gnu@1.2.5': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.3': + '@rolldown/binding-linux-arm64-musl@1.2.5': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.3': + '@rolldown/binding-linux-ppc64-gnu@1.2.5': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.3': + '@rolldown/binding-linux-s390x-gnu@1.2.5': optional: true - '@rolldown/binding-linux-x64-musl@1.2.3': + '@rolldown/binding-linux-x64-gnu@1.2.5': optional: true - '@rolldown/binding-openharmony-arm64@1.2.3': + '@rolldown/binding-linux-x64-musl@1.2.5': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.3': + '@rolldown/binding-openharmony-arm64@1.2.5': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.3': + '@rolldown/binding-win32-arm64-msvc@1.2.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.5': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -3708,17 +3720,17 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.3(vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0))': + '@tailwindcss/vite@4.3.3(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: 8.2.1(@types/node@26.2.0)(jiti@2.7.0) + vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0) - '@tanstack/eslint-plugin-query@5.101.4(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)': + '@tanstack/eslint-plugin-query@5.101.4(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/utils': 8.65.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.8.1(jiti@2.7.0) + '@typescript-eslint/utils': 8.65.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.9.0(jiti@2.7.0) optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: @@ -3771,15 +3783,15 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/type-utils': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.67.0 - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -3787,14 +3799,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3 - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3835,13 +3847,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.67.0 '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -3881,24 +3893,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.65.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.65.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3915,10 +3927,10 @@ snapshots: '@ungap/structured-clone@1.3.1': {} - '@vitejs/plugin-react@6.0.5(vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0))': + '@vitejs/plugin-react@6.1.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.2.1(@types/node@26.2.0)(jiti@2.7.0) + vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0) acorn-jsx@5.3.2(acorn@8.18.0): dependencies: @@ -4093,20 +4105,20 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@7.1.1(eslint@10.8.1(jiti@2.7.0)): + eslint-plugin-react-hooks@7.1.1(eslint@10.9.0(jiti@2.7.0)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.3 - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.4.3 zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.5.4(eslint@10.8.1(jiti@2.7.0)): + eslint-plugin-react-refresh@0.5.4(eslint@10.9.0(jiti@2.7.0)): dependencies: - eslint: 10.8.1(jiti@2.7.0) + eslint: 10.9.0(jiti@2.7.0) eslint-scope@9.1.2: dependencies: @@ -4119,9 +4131,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.8.1(jiti@2.7.0): + eslint@10.9.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.7.0 @@ -4314,7 +4326,7 @@ snapshots: dependencies: '@babel/runtime': 7.29.7 - i18next@26.3.6(typescript@6.0.3): + i18next@26.4.0(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 @@ -4490,7 +4502,7 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@1.31.0(react@19.2.8): + lucide-react@1.33.0(react@19.2.8): dependencies: react: 19.2.8 @@ -4875,15 +4887,15 @@ snapshots: react: 19.2.8 scheduler: 0.27.0 - react-hook-form@7.85.0(react@19.2.8): + react-hook-form@7.86.0(react@19.2.8): dependencies: react: 19.2.8 - react-i18next@17.0.11(i18next@26.3.6(typescript@6.0.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@6.0.3): + react-i18next@17.0.12(i18next@26.4.0(typescript@6.0.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@6.0.3): dependencies: '@babel/runtime': 7.29.7 html-parse-stringify: 4.0.1 - i18next: 26.3.6(typescript@6.0.3) + i18next: 26.4.0(typescript@6.0.3) react: 19.2.8 use-sync-external-store: 1.6.0(react@19.2.8) optionalDependencies: @@ -4961,34 +4973,35 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 - rolldown@1.2.3: + rolldown@1.2.5: dependencies: - '@oxc-project/types': 0.143.0 + '@oxc-project/types': 0.146.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.2.3 - '@rolldown/binding-darwin-arm64': 1.2.3 - '@rolldown/binding-darwin-x64': 1.2.3 - '@rolldown/binding-freebsd-x64': 1.2.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.3 - '@rolldown/binding-linux-arm64-gnu': 1.2.3 - '@rolldown/binding-linux-arm64-musl': 1.2.3 - '@rolldown/binding-linux-ppc64-gnu': 1.2.3 - '@rolldown/binding-linux-s390x-gnu': 1.2.3 - '@rolldown/binding-linux-x64-gnu': 1.2.3 - '@rolldown/binding-linux-x64-musl': 1.2.3 - '@rolldown/binding-openharmony-arm64': 1.2.3 - '@rolldown/binding-win32-arm64-msvc': 1.2.3 - '@rolldown/binding-win32-x64-msvc': 1.2.3 + '@rolldown/binding-android-arm-eabi': 1.2.5 + '@rolldown/binding-android-arm64': 1.2.5 + '@rolldown/binding-darwin-arm64': 1.2.5 + '@rolldown/binding-darwin-x64': 1.2.5 + '@rolldown/binding-freebsd-x64': 1.2.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.5 + '@rolldown/binding-linux-arm64-gnu': 1.2.5 + '@rolldown/binding-linux-arm64-musl': 1.2.5 + '@rolldown/binding-linux-ppc64-gnu': 1.2.5 + '@rolldown/binding-linux-s390x-gnu': 1.2.5 + '@rolldown/binding-linux-x64-gnu': 1.2.5 + '@rolldown/binding-linux-x64-musl': 1.2.5 + '@rolldown/binding-openharmony-arm64': 1.2.5 + '@rolldown/binding-win32-arm64-msvc': 1.2.5 + '@rolldown/binding-win32-x64-msvc': 1.2.5 - rollup-plugin-visualizer@7.1.1(rolldown@1.2.3): + rollup-plugin-visualizer@7.1.1(rolldown@1.2.5): dependencies: open: 11.0.1 picomatch: 4.0.5 source-map: 0.8.0 yargs: 18.1.0 optionalDependencies: - rolldown: 1.2.3 + rolldown: 1.2.5 run-applescript@7.1.0: {} @@ -5072,13 +5085,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3): + typescript-eslint@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.8.1(jiti@2.7.0) + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.9.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -5159,12 +5172,12 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0): + vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 postcss: 8.5.26 - rolldown: 1.2.3 + rolldown: 1.2.5 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.2.0 From 5de4e8e23e8719bd9130620bf4fb3ae2b978e60a Mon Sep 17 00:00:00 2001 From: Stavros Date: Tue, 25 Aug 2026 17:12:24 +0300 Subject: [PATCH 09/27] chore: disable e2e tests for now --- .github/workflows/e2e.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 881ff760..1d4e29ae 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,9 +1,9 @@ name: Run e2e tests on: workflow_dispatch: - pull_request: - branches: - - main + # pull_request: + # branches: + # - main jobs: test: From 4bb766997b234ec729abbb621c3f3af9f3f33568 Mon Sep 17 00:00:00 2001 From: Stavros Date: Tue, 25 Aug 2026 17:16:50 +0300 Subject: [PATCH 10/27] chore: remove dosu from sponsors --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4d5aeb31..065df5c7 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,6 @@ A huge thank you to [selfh.st](https://selfh.st) for their generous donation to - [JetBrains for Open-Source](https://jb.gg/OpenSource) - [CodeRabbit AI](https://www.coderabbit.ai) -- [Dosu](https://dosu.dev/) - [InstaPods](https://instapods.com) - [Deploy Tinyauth from 3$/month](https://app.instapods.com/dashboard/pods/create?app=tinyauth&ref=tinyauth) ## Acknowledgements From 88901d342f4a848eeef5c001cdd1a66372112609 Mon Sep 17 00:00:00 2001 From: Stavros Date: Sun, 30 Aug 2026 20:09:41 +0300 Subject: [PATCH 11/27] fix: consent screen skip status should get checked after auth (#1099) --- frontend/src/pages/authorize-page.tsx | 55 ++- frontend/vite.config.ts | 2 +- internal/controller/controller.go | 5 + internal/controller/oidc_controller.go | 113 ++++++- internal/controller/oidc_controller_test.go | 356 ++++++++++++++++---- internal/service/oidc_service.go | 11 + 6 files changed, 436 insertions(+), 106 deletions(-) diff --git a/frontend/src/pages/authorize-page.tsx b/frontend/src/pages/authorize-page.tsx index 730e5f27..261a7da3 100644 --- a/frontend/src/pages/authorize-page.tsx +++ b/frontend/src/pages/authorize-page.tsx @@ -1,5 +1,5 @@ import { useUserContext } from "@/context/user-context"; -import { useMutation } from "@tanstack/react-query"; +import {useMutation} from "@tanstack/react-query"; import { Navigate, useNavigate } from "react-router"; import { useLocation } from "react-router"; import { @@ -25,7 +25,8 @@ import { searchParamsFromObject, useScreenParams, } from "@/lib/hooks/screen-params"; -import { useEffect } from "react"; +import {useEffect, useState} from "react"; +import { z } from "zod"; type Scope = { id: string; @@ -34,6 +35,10 @@ type Scope = { icon: React.ReactNode; }; +const skipConsentResponseSchema = z.object({ + skipConsent: z.boolean(), +}) + const scopeMapIconProps = { className: "stroke-muted-foreground stroke-[1.75] h-4", }; @@ -96,14 +101,8 @@ export const AuthorizePage = () => { } return ""; })(); - - // TODO: maybe a better way to do this - const shouldAutoAuthorize = - auth.authenticated && - isOidc && - screenParams.oidc_ticket !== undefined && - screenParams.oidc_scope !== undefined && - screenParams.oidc_prompt === "none"; + const [autoAuthorize, setAutoAuthorize] = useState(false); + const [skipConsentChecked, setSkipConsentChecked] = useState(false); const { mutate: authorizeMutate, isPending: authorizePending } = useMutation({ mutationFn: () => { @@ -126,10 +125,34 @@ export const AuthorizePage = () => { }); useEffect(() => { - if (shouldAutoAuthorize) { - authorizeMutate(); - } - }, [shouldAutoAuthorize, authorizeMutate]); + let active = true; + const controller = new AbortController(); + + const checkSkipConsent = async () => { + try { + const res = await fetch( + `/api/oidc/skip-consent?oidc_ticket=${encodeURIComponent( screenParams.oidc_ticket ?? "")}`, + { signal: controller.signal }, + ); + if (!res.ok) return; + const parsed = skipConsentResponseSchema.safeParse(await res.json()); + if (!active || !parsed.success || !parsed.data.skipConsent) return; + setAutoAuthorize(true); + authorizeMutate(); + } catch { + // Fall back to manual consent on any failure (including abort). + } finally { + if (active) setSkipConsentChecked(true); + } + }; + + checkSkipConsent(); + + return () => { + active = false; + controller.abort(); + }; + }, [authorizeMutate, screenParams.oidc_ticket]); if (!isOidc || !screenParams.oidc_ticket || !screenParams.oidc_scope) { return ( @@ -190,13 +213,13 @@ export const AuthorizePage = () => {