From e4f633bcb3339a6e7fb4c604876294d7098be6c8 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Mon, 28 Jul 2025 13:32:41 +0200 Subject: [PATCH 1/7] chore: cleanup scripts, v1.3.1 @zitadel/client @zitadel/proto (#10329) This PR includes scripts for cleaning up workspaces, and changes the versions of @zitadel/client and /proto to v1.3.0 --- apps/login/acceptance/package.json | 3 ++- console/package.json | 3 ++- e2e/package.json | 3 ++- load-test/package.json | 3 ++- package.json | 2 ++ packages/zitadel-client/package.json | 4 ++-- packages/zitadel-proto/.npmignore | 6 ++++++ packages/zitadel-proto/package.json | 11 ++++++++++- 8 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 packages/zitadel-proto/.npmignore diff --git a/apps/login/acceptance/package.json b/apps/login/acceptance/package.json index fc4a191373..87cbe9a7e1 100644 --- a/apps/login/acceptance/package.json +++ b/apps/login/acceptance/package.json @@ -4,7 +4,8 @@ "scripts": { "test:acceptance": "dotenv -e ../login/.env.test.local playwright", "test:acceptance:setup": "cd ../.. && make login_test_acceptance_setup_env && NODE_ENV=test turbo run test:acceptance:setup:dev", - "test:acceptance:setup:dev": "cd ../.. && make login_test_acceptance_setup_dev" + "test:acceptance:setup:dev": "cd ../.. && make login_test_acceptance_setup_dev", + "clean": "rm -rf .turbo node_modules" }, "devDependencies": { "@faker-js/faker": "^9.7.0", diff --git a/console/package.json b/console/package.json index 698ea7984e..1eafec0502 100644 --- a/console/package.json +++ b/console/package.json @@ -10,7 +10,8 @@ "lint:check:ng": "ng lint", "lint:check:prettier": "prettier --check src", "lint:fix": "prettier --write src", - "generate": "pnpm exec buf generate ../proto --include-imports --include-wkt" + "generate": "pnpm exec buf generate ../proto --include-imports --include-wkt", + "clean": "rm -rf dist .angular .turbo node_modules src/app/proto/generated" }, "private": true, "dependencies": { diff --git a/e2e/package.json b/e2e/package.json index b465d6b6d9..b10f313c57 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -13,7 +13,8 @@ "test:open:angular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 CYPRESS_WEBHOOK_HANDLER_HOST=host.docker.internal pnpm run open --", "test:e2e:angular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 CYPRESS_WEBHOOK_HANDLER_HOST=host.docker.internal pnpm run e2e --", "lint": "prettier --check cypress", - "lint:fix": "prettier --write cypress" + "lint:fix": "prettier --write cypress", + "clean": "rm -rf .turbo node_modules" }, "private": true, "dependencies": { diff --git a/load-test/package.json b/load-test/package.json index 73bf8fd449..fd47427f60 100644 --- a/load-test/package.json +++ b/load-test/package.json @@ -28,6 +28,7 @@ "scripts": { "bundle": "webpack", "lint": "prettier --check src/**", - "lint:fix": "prettier --write src" + "lint:fix": "prettier --write src", + "clean": "rm -rf dist .turbo node_modules" } } diff --git a/package.json b/package.json index 744ef66c04..eb0c881b11 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "devcontainer": "devcontainer", "devcontainer:lint-unit": "pnpm devcontainer up --config .devcontainer/turbo-lint-unit/devcontainer.json --workspace-folder . --remove-existing-container", "devcontainer:integration:login": "pnpm devcontainer up --config .devcontainer/login-integration/devcontainer.json --workspace-folder . --remove-existing-container", + "clean": "turbo run clean", + "clean:all": "pnpm run clean && rm -rf .turbo node_modules", "generate": "turbo run generate" }, "pnpm": { diff --git a/packages/zitadel-client/package.json b/packages/zitadel-client/package.json index 9dcdcc324e..cef3a02021 100644 --- a/packages/zitadel-client/package.json +++ b/packages/zitadel-client/package.json @@ -1,6 +1,6 @@ { "name": "@zitadel/client", - "version": "1.2.0", + "version": "1.3.1", "license": "MIT", "publishConfig": { "access": "public" @@ -77,7 +77,7 @@ "@connectrpc/connect": "^2.0.0", "@connectrpc/connect-node": "^2.0.0", "@connectrpc/connect-web": "^2.0.0", - "@zitadel/proto": "latest", + "@zitadel/proto": "workspace:*", "jose": "^5.3.0" }, "devDependencies": { diff --git a/packages/zitadel-proto/.npmignore b/packages/zitadel-proto/.npmignore new file mode 100644 index 0000000000..f422a6b429 --- /dev/null +++ b/packages/zitadel-proto/.npmignore @@ -0,0 +1,6 @@ +node_modules +.turbo +*.log +.DS_Store +buf.gen.yaml +turbo.json diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json index 780ae13ce3..7119edb8de 100644 --- a/packages/zitadel-proto/package.json +++ b/packages/zitadel-proto/package.json @@ -1,6 +1,6 @@ { "name": "@zitadel/proto", - "version": "1.2.0", + "version": "1.3.1", "license": "MIT", "publishConfig": { "access": "public" @@ -8,6 +8,15 @@ "type": "module", "main": "./cjs/index.js", "types": "./types/index.d.ts", + "files": [ + "cjs/**", + "es/**", + "types/**", + "zitadel/**", + "google/**", + "validate/**", + "protoc-gen-openapiv2/**" + ], "exports": { "./zitadel/*": { "types": "./types/zitadel/*.d.ts", From 5d2d1d6da6f4f8bc9dd0a062ff8c12200b636e83 Mon Sep 17 00:00:00 2001 From: Livio Spring Date: Mon, 28 Jul 2025 09:55:55 -0400 Subject: [PATCH 2/7] feat(OIDC): handle logout hint on end_session_endpoint (#10039) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Which Problems Are Solved The OIDC session endpoint allows to pass a `id_token_hint` to identify the session to terminate. In case the application is not able to pass that, e.g. Console currently allows multiple sessions to be open, but will only store the id_token of the current session, allowing to pass the `logout_hint` to identify the user adds some new possibilities. # How the Problems Are Solved In case the end_session_endpoint is called with no `id_token_hint`, but a `logout_hint` and the v2 login UI is configured, the information is passed to the login UI also as `login_hint` parameter to allow the login UI to determine the session to be terminated, resp. let the user decide. # Additional Changes Also added the `ui_locales` as parameter to handle and pass to the V2 login UI. # Dependencies ⚠️ ~These changes depend on https://github.com/zitadel/oidc/pull/774~ # Additional Context closes #9847 --------- Co-authored-by: Marco Ardizzone --- docs/docs/apis/openidoauth/endpoints.mdx | 14 +-- go.mod | 8 +- go.sum | 18 ++-- internal/api/oidc/auth_request.go | 43 +++++--- internal/api/oidc/auth_request_test.go | 98 +++++++++++++++++++ .../integration_test/auth_request_test.go | 21 +++- .../api/oidc/integration_test/oidc_test.go | 2 +- 7 files changed, 169 insertions(+), 35 deletions(-) create mode 100644 internal/api/oidc/auth_request_test.go diff --git a/docs/docs/apis/openidoauth/endpoints.mdx b/docs/docs/apis/openidoauth/endpoints.mdx index 13745eaeb1..79d533ab3a 100644 --- a/docs/docs/apis/openidoauth/endpoints.mdx +++ b/docs/docs/apis/openidoauth/endpoints.mdx @@ -656,12 +656,14 @@ The endpoint has to be opened in the user agent (browser) to terminate the user No parameters are needed apart from the user agent cookie, but you can provide the following to customize the behavior: -| Parameter | Description | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | -| id_token_hint | the id_token that was previously issued to the client | -| client_id | client_id of the application | -| post_logout_redirect_uri | Callback uri of the logout where the user (agent) will be redirected to. Must match exactly one of the preregistered in Console. | -| state | Opaque value used to maintain state between the request and the callback | +| Parameter | Description | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| id_token_hint | the id_token that was previously issued to the client | +| client_id | client_id of the application | +| post_logout_redirect_uri | Callback uri of the logout where the user (agent) will be redirected to. Must match exactly one of the preregistered in Console. | +| state | Opaque value used to maintain state between the request and the callback | +| logout_hint | A valid login name of a user. Will be used to select the user to logout. Only supported when using the login UI V2. | +| ui_locales | Spaces delimited list of preferred locales for the login UI, e.g. `de-CH de en`. If none is provided or matches the possible locales provided by the login UI, the `accept-language` header of the browser will be taken into account. | The `post_logout_redirect_uri` will be checked against the previously registered uris of the client provided by the `azp` claim of the `id_token_hint` or the `client_id` parameter. If both parameters are provided, they must be equal. diff --git a/go.mod b/go.mod index 22980acfaf..eb4856d087 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( github.com/twilio/twilio-go v1.26.1 github.com/zitadel/exifremove v0.1.0 github.com/zitadel/logging v0.6.2 - github.com/zitadel/oidc/v3 v3.39.1 + github.com/zitadel/oidc/v3 v3.42.0 github.com/zitadel/passwap v0.9.0 github.com/zitadel/saml v0.3.5 github.com/zitadel/schema v1.3.1 @@ -101,8 +101,8 @@ require ( golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 golang.org/x/net v0.40.0 golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.15.0 - golang.org/x/text v0.26.0 + golang.org/x/sync v0.16.0 + golang.org/x/text v0.27.0 google.golang.org/api v0.233.0 google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 google.golang.org/grpc v1.72.1 @@ -119,7 +119,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect - github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.9.0 // indirect github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect diff --git a/go.sum b/go.sum index 7221111a2b..48f90f954a 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce 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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= -github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.9.0 h1:DBvuZxjdKkRP/dr4GVV4w2fnmrk5Hxc90T51LZjv0JA= +github.com/bmatcuk/doublestar/v4 v4.9.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4= github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -810,8 +810,10 @@ github.com/zitadel/exifremove v0.1.0 h1:qD50ezWsfeeqfcvs79QyyjVfK+snN12v0U0deaU8 github.com/zitadel/exifremove v0.1.0/go.mod h1:rzKJ3woL/Rz2KthVBiSBKIBptNTvgmk9PLaeUKTm+ek= github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU= github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4= -github.com/zitadel/oidc/v3 v3.39.1 h1:6QwGwI3yxh4somT7fwRCeT1KOn/HOGv0PA0dFciwJjE= -github.com/zitadel/oidc/v3 v3.39.1/go.mod h1:aH8brOrzoliAybVdfq2xIdGvbtl0j/VsKRNa7WE72gI= +github.com/zitadel/oidc/v3 v3.41.1-0.20250718152526-16ebef905b40 h1:MmUhfhwIcPStWqsTW+Pw+kYa5SNY7TxwzktUDohwO78= +github.com/zitadel/oidc/v3 v3.41.1-0.20250718152526-16ebef905b40/go.mod h1:Y/rY7mHTzMGrZgf7REgQZFWxySlaSVqqFdBmNZq+9wA= +github.com/zitadel/oidc/v3 v3.42.0 h1:cqlCYIEapmDprp5a5hUl9ivkUOu1SQxOqbrKdalHqGk= +github.com/zitadel/oidc/v3 v3.42.0/go.mod h1:Y/rY7mHTzMGrZgf7REgQZFWxySlaSVqqFdBmNZq+9wA= github.com/zitadel/passwap v0.9.0 h1:QvDK8OHKdb73C0m+mwXvu87UJSBqix3oFwTVENHdv80= github.com/zitadel/passwap v0.9.0/go.mod h1:6QzwFjDkIr3FfudzSogTOx5Ydhq4046dRJtDM/kX+G8= github.com/zitadel/saml v0.3.5 h1:L1RKWS5y66cGepVxUGjx/WSBOtrtSpRA/J3nn5BJLOY= @@ -948,8 +950,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -992,8 +994,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go index b29e157fc2..152176a59c 100644 --- a/internal/api/oidc/auth_request.go +++ b/internal/api/oidc/auth_request.go @@ -13,6 +13,7 @@ import ( "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" + "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/api/authz" http_utils "github.com/zitadel/zitadel/internal/api/http" @@ -30,6 +31,8 @@ import ( const ( LoginClientHeader = "x-zitadel-login-client" LoginPostLogoutRedirectParam = "post_logout_redirect" + LoginLogoutHintParam = "logout_hint" + LoginUILocalesParam = "ui_locales" LoginPath = "/login" LogoutPath = "/logout" LogoutDonePath = "/logout/done" @@ -283,14 +286,19 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR // we'll redirect to the UI (V2) and let it decide which session to terminate // // If there's no id_token_hint and for v1 logins, we handle them separately - if endSessionRequest.IDTokenHintClaims == nil && - (authz.GetFeatures(ctx).LoginV2.Required || headers.Get(LoginClientHeader) != "") { + if endSessionRequest.IDTokenHintClaims == nil && (authz.GetFeatures(ctx).LoginV2.Required || headers.Get(LoginClientHeader) != "") { redirectURI := v2PostLogoutRedirectURI(endSessionRequest.RedirectURI) - // if no base uri is set, fallback to the default configured in the runtime config - if authz.GetFeatures(ctx).LoginV2.BaseURI == nil || authz.GetFeatures(ctx).LoginV2.BaseURI.String() == "" { - return o.defaultLogoutURLV2 + redirectURI, nil + logoutURI := authz.GetFeatures(ctx).LoginV2.BaseURI + // if no logout uri is set, fallback to the default configured in the runtime config + if logoutURI == nil || logoutURI.String() == "" { + logoutURI, err = url.Parse(o.defaultLogoutURLV2) + if err != nil { + return "", err + } + } else { + logoutURI = logoutURI.JoinPath(LogoutPath) } - return buildLoginV2LogoutURL(authz.GetFeatures(ctx).LoginV2.BaseURI, redirectURI), nil + return buildLoginV2LogoutURL(logoutURI, redirectURI, endSessionRequest.LogoutHint, endSessionRequest.UILocales), nil } // V1: @@ -367,12 +375,25 @@ func (o *OPStorage) federatedLogout(ctx context.Context, sessionID string, postL return login.ExternalLogoutPath(sessionID) } -func buildLoginV2LogoutURL(baseURI *url.URL, redirectURI string) string { - baseURI.JoinPath(LogoutPath) - q := baseURI.Query() +func buildLoginV2LogoutURL(logoutURI *url.URL, redirectURI, logoutHint string, uiLocales []language.Tag) string { + if strings.HasSuffix(logoutURI.Path, "/") && len(logoutURI.Path) > 1 { + logoutURI.Path = strings.TrimSuffix(logoutURI.Path, "/") + } + + q := logoutURI.Query() q.Set(LoginPostLogoutRedirectParam, redirectURI) - baseURI.RawQuery = q.Encode() - return baseURI.String() + if logoutHint != "" { + q.Set(LoginLogoutHintParam, logoutHint) + } + if len(uiLocales) > 0 { + locales := make([]string, len(uiLocales)) + for i, locale := range uiLocales { + locales[i] = locale.String() + } + q.Set(LoginUILocalesParam, strings.Join(locales, " ")) + } + logoutURI.RawQuery = q.Encode() + return logoutURI.String() } // v2PostLogoutRedirectURI will take care that the post_logout_redirect_uri is correctly set for v2 logins. diff --git a/internal/api/oidc/auth_request_test.go b/internal/api/oidc/auth_request_test.go new file mode 100644 index 0000000000..0210ead49e --- /dev/null +++ b/internal/api/oidc/auth_request_test.go @@ -0,0 +1,98 @@ +package oidc + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/text/language" +) + +func TestBuildLoginV2LogoutURL(t *testing.T) { + t.Parallel() + + tt := []struct { + testName string + logoutURIStr string + redirectURI string + logoutHint string + uiLocales []language.Tag + expectedParams map[string]string + }{ + { + testName: "basic with only redirectURI", + logoutURIStr: "https://example.com/logout", + redirectURI: "https://client/cb", + expectedParams: map[string]string{ + "post_logout_redirect": "https://client/cb", + }, + }, + { + testName: "with logout hint", + logoutURIStr: "https://example.com/logout", + redirectURI: "https://client/cb", + logoutHint: "user@example.com", + expectedParams: map[string]string{ + "post_logout_redirect": "https://client/cb", + "logout_hint": "user@example.com", + }, + }, + { + testName: "with ui_locales", + logoutURIStr: "https://example.com/logout", + redirectURI: "https://client/cb", + uiLocales: []language.Tag{language.English, language.Italian}, + expectedParams: map[string]string{ + "post_logout_redirect": "https://client/cb", + "ui_locales": "en it", + }, + }, + { + testName: "with all params", + logoutURIStr: "https://example.com/logout", + redirectURI: "https://client/cb", + logoutHint: "logoutme", + uiLocales: []language.Tag{language.Make("de-CH"), language.Make("fr")}, + expectedParams: map[string]string{ + "post_logout_redirect": "https://client/cb", + "logout_hint": "logoutme", + "ui_locales": "de-CH fr", + }, + }, + { + testName: "base with trailing slash", + logoutURIStr: "https://example.com/logout/", + redirectURI: "https://client/cb", + expectedParams: map[string]string{ + "post_logout_redirect": "https://client/cb", + }, + }, + } + + for _, tc := range tt { + t.Run(tc.testName, func(t *testing.T) { + // t.Parallel() + + // Given + logoutURI, err := url.Parse(tc.logoutURIStr) + require.NoError(t, err) + + // When + got := buildLoginV2LogoutURL(logoutURI, tc.redirectURI, tc.logoutHint, tc.uiLocales) + + // Then + gotURL, err := url.Parse(got) + require.NoError(t, err) + require.NotContains(t, gotURL.String(), "/logout/") + + q := gotURL.Query() + // Ensure no unexpected params + require.Len(t, q, len(tc.expectedParams)) + + for k, v := range tc.expectedParams { + assert.Equal(t, v, q.Get(k)) + } + }) + } +} diff --git a/internal/api/oidc/integration_test/auth_request_test.go b/internal/api/oidc/integration_test/auth_request_test.go index ad78184a04..77e389f7be 100644 --- a/internal/api/oidc/integration_test/auth_request_test.go +++ b/internal/api/oidc/integration_test/auth_request_test.go @@ -498,7 +498,7 @@ func TestOPStorage_TerminateSession(t *testing.T) { _, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider) require.NoError(t, err) - postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state") + postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil) require.NoError(t, err) assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String()) @@ -535,7 +535,7 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) { _, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider) require.NoError(t, err) - postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state") + postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil) require.NoError(t, err) assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String()) @@ -551,6 +551,17 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) { require.NoError(t, err) } +func buildLogoutURL(origin, logoutURLV2 string, redirectURI string, extraParams map[string]string) string { + u, _ := url.Parse(origin + logoutURLV2 + redirectURI) + q := u.Query() + for k, v := range extraParams { + q.Set(k, v) + } + u.RawQuery = q.Encode() + // Append the redirect URI as a URL-escaped string + return u.String() +} + func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) { tests := []struct { name string @@ -565,7 +576,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) { return clientID }(), authRequestID: createAuthRequest, - logoutURL: http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure) + Instance.Config.LogoutURLV2 + logoutRedirectURI + "?state=state", + logoutURL: buildLogoutURL(http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure), Instance.Config.LogoutURLV2, logoutRedirectURI+"?state=state", map[string]string{"logout_hint": "hint", "ui_locales": "it-IT en-US"}), }, { name: "login v2 config", @@ -574,7 +585,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) { return clientID }(), authRequestID: createAuthRequestNoLoginClientHeader, - logoutURL: http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure) + Instance.Config.LogoutURLV2 + logoutRedirectURI + "?state=state", + logoutURL: buildLogoutURL(http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure), Instance.Config.LogoutURLV2, logoutRedirectURI+"?state=state", map[string]string{"logout_hint": "hint", "ui_locales": "it-IT en-US"}), }, } for _, tt := range tests { @@ -601,7 +612,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) { assertTokens(t, tokens, false) assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID) - postLogoutRedirect, err := rp.EndSession(CTX, provider, "", logoutRedirectURI, "state") + postLogoutRedirect, err := rp.EndSession(CTX, provider, "", logoutRedirectURI, "state", "hint", oidc.ParseLocales([]string{"it-IT", "en-US"})) require.NoError(t, err) assert.Equal(t, tt.logoutURL, postLogoutRedirect.String()) diff --git a/internal/api/oidc/integration_test/oidc_test.go b/internal/api/oidc/integration_test/oidc_test.go index 2b43154743..d3d80b0557 100644 --- a/internal/api/oidc/integration_test/oidc_test.go +++ b/internal/api/oidc/integration_test/oidc_test.go @@ -311,7 +311,7 @@ func Test_ZITADEL_API_terminated_session(t *testing.T) { require.Equal(t, User.GetUserId(), myUserResp.GetUser().GetId()) // end session - postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state") + postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil) require.NoError(t, err) assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String()) From 416a35537f89b1c3ccd3d123289cea37b3309bba Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:08:12 +0200 Subject: [PATCH 3/7] feat: actions context information add clientID (#10339) # Which Problems Are Solved There is no information contained in the context info sent to Actions v2. # How the Problems Are Solved Add application information to the context information sent to Actions v2, to give more information about the execution. # Additional Changes None # Additional Context Closes #9377 --- .../integration_test/execution_target_test.go | 29 ++++++++-------- internal/api/oidc/introspect.go | 1 + internal/api/oidc/token.go | 6 ++-- internal/api/oidc/token_exchange.go | 2 +- internal/api/oidc/userinfo.go | 33 ++++++++++++++----- 5 files changed, 45 insertions(+), 26 deletions(-) diff --git a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go index 3353c4f0dd..a2e6131e11 100644 --- a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go +++ b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go @@ -605,7 +605,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { {Key: "added", Value: "value"}, }, } - return expectPreUserinfoExecution(ctx, t, instance, req, response) + return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -630,7 +630,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { "addedLog", }, } - return expectPreUserinfoExecution(ctx, t, instance, req, response) + return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -655,7 +655,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { {Key: "key", Value: []byte("value")}, }, } - return expectPreUserinfoExecution(ctx, t, instance, req, response) + return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -692,7 +692,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { {Key: "added3", Value: "value3"}, }, } - return expectPreUserinfoExecution(ctx, t, instance, req, response) + return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -755,7 +755,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { } } -func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *integration.Instance, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) { +func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) { userEmail := gofakeit.Email() userPhone := "+41" + gofakeit.Phone() userResp := instance.CreateHumanUserVerified(ctx, instance.DefaultOrg.Id, userEmail, userPhone) @@ -767,7 +767,7 @@ func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *int SessionToken: sessionResp.GetSessionToken(), }, } - expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", userResp, userEmail, userPhone) + expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", clientID, userResp, userEmail, userPhone) targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) @@ -845,7 +845,7 @@ func getAccessTokenClaims(ctx context.Context, t *testing.T, instance *integrati return claims } -func contextInfoForUserOIDC(instance *integration.Instance, function string, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfo { +func contextInfoForUserOIDC(instance *integration.Instance, function string, clientID string, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfo { return &oidc_api.ContextInfo{ Function: function, UserInfo: &oidc.UserInfo{ @@ -878,6 +878,9 @@ func contextInfoForUserOIDC(instance *integration.Instance, function string, use }, }, UserMetadata: nil, + Application: &oidc_api.ContextInfoApplication{ + ClientID: clientID, + }, Org: &query.UserInfoOrg{ ID: instance.DefaultOrg.GetId(), Name: instance.DefaultOrg.GetName(), @@ -918,7 +921,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { {Key: "added1", Value: "value"}, }, } - return expectPreAccessTokenExecution(ctx, t, instance, req, response) + return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -943,7 +946,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { "addedLog", }, } - return expectPreAccessTokenExecution(ctx, t, instance, req, response) + return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -968,7 +971,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { {Key: "key", Value: []byte("value")}, }, } - return expectPreAccessTokenExecution(ctx, t, instance, req, response) + return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -1005,7 +1008,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { {Key: "added3", Value: "value3"}, }, } - return expectPreAccessTokenExecution(ctx, t, instance, req, response) + return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -1060,7 +1063,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { } } -func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *integration.Instance, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) { +func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) { userEmail := gofakeit.Email() userPhone := "+41" + gofakeit.Phone() userResp := instance.CreateHumanUserVerified(ctx, instance.DefaultOrg.Id, userEmail, userPhone) @@ -1072,7 +1075,7 @@ func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance * SessionToken: sessionResp.GetSessionToken(), }, } - expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", userResp, userEmail, userPhone) + expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", clientID, userResp, userEmail, userPhone) targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) diff --git a/internal/api/oidc/introspect.go b/internal/api/oidc/introspect.go index e5479a4683..6ce5d72e24 100644 --- a/internal/api/oidc/introspect.go +++ b/internal/api/oidc/introspect.go @@ -100,6 +100,7 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR token.userID, token.scope, client.projectID, + client.clientID, client.projectRoleAssertion, true, true, diff --git a/internal/api/oidc/token.go b/internal/api/oidc/token.go index 2efc0fb583..d7a258259a 100644 --- a/internal/api/oidc/token.go +++ b/internal/api/oidc/token.go @@ -31,7 +31,7 @@ for example the v2 code exchange and refresh token. */ func (s *Server) accessTokenResponseFromSession(ctx context.Context, client op.Client, session *command.OIDCSession, state, projectID string, projectRoleAssertion, accessTokenRoleAssertion, idTokenRoleAssertion, userInfoAssertion bool) (_ *oidc.AccessTokenResponse, err error) { - getUserInfo := s.getUserInfo(session.UserID, projectID, projectRoleAssertion, userInfoAssertion, session.Scope) + getUserInfo := s.getUserInfo(session.UserID, projectID, client.GetID(), projectRoleAssertion, userInfoAssertion, session.Scope) getSigner := s.getSignerOnce() resp := &oidc.AccessTokenResponse{ @@ -113,8 +113,8 @@ type userInfoFunc func(ctx context.Context, roleAssertion bool, triggerType doma // getUserInfo returns a function which retrieves userinfo from the database once. // However, each time, role claims are asserted and also action flows will trigger. -func (s *Server) getUserInfo(userID, projectID string, projectRoleAssertion, userInfoAssertion bool, scope []string) userInfoFunc { - userInfo := s.userInfo(userID, scope, projectID, projectRoleAssertion, userInfoAssertion, false) +func (s *Server) getUserInfo(userID, projectID, clientID string, projectRoleAssertion, userInfoAssertion bool, scope []string) userInfoFunc { + userInfo := s.userInfo(userID, scope, projectID, clientID, projectRoleAssertion, userInfoAssertion, false) return func(ctx context.Context, roleAssertion bool, triggerType domain.TriggerType) (*oidc.UserInfo, error) { return userInfo(ctx, roleAssertion, triggerType) } diff --git a/internal/api/oidc/token_exchange.go b/internal/api/oidc/token_exchange.go index 030066ea1c..8cb087e760 100644 --- a/internal/api/oidc/token_exchange.go +++ b/internal/api/oidc/token_exchange.go @@ -218,7 +218,7 @@ func validateTokenExchangeAudience(requestedAudience, subjectAudience, actorAudi // Both tokens may point to the same object (subjectToken) in case of a regular Token Exchange. // When the subject and actor Tokens point to different objects, the new tokens will be for impersonation / delegation. func (s *Server) createExchangeTokens(ctx context.Context, tokenType oidc.TokenType, client *Client, subjectToken, actorToken *exchangeToken, audience, scopes []string) (_ *oidc.TokenExchangeResponse, err error) { - getUserInfo := s.getUserInfo(subjectToken.userID, client.client.ProjectID, client.client.ProjectRoleAssertion, client.IDTokenUserinfoClaimsAssertion(), scopes) + getUserInfo := s.getUserInfo(subjectToken.userID, client.client.ProjectID, client.GetID(), client.client.ProjectRoleAssertion, client.IDTokenUserinfoClaimsAssertion(), scopes) getSigner := s.getSignerOnce() resp := &oidc.TokenExchangeResponse{ diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go index 170ff49c94..833c7a6ee4 100644 --- a/internal/api/oidc/userinfo.go +++ b/internal/api/oidc/userinfo.go @@ -54,6 +54,7 @@ func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoReques token.userID, token.scope, projectID, + token.clientID, assertion, true, false, @@ -86,6 +87,7 @@ func (s *Server) userInfo( userID string, scope []string, projectID string, + clientID string, projectRoleAssertion, userInfoAssertion, currentProjectOnly bool, ) func(ctx context.Context, roleAssertion bool, triggerType domain.TriggerType) (_ *oidc.UserInfo, err error) { var ( @@ -120,7 +122,7 @@ func (s *Server) userInfo( Claims: maps.Clone(rawUserInfo.Claims), } assertRoles(projectID, qu, roleAudience, requestedRoles, roleAssertion, userInfo) - return userInfo, s.userinfoFlows(ctx, qu, userInfo, triggerType) + return userInfo, s.userinfoFlows(ctx, qu, userInfo, triggerType, clientID) } } @@ -285,7 +287,8 @@ func setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) { } } -func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo, triggerType domain.TriggerType) (err error) { +//nolint:gocognit +func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo, triggerType domain.TriggerType, clientID string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -319,6 +322,13 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user } }), ), + actions.SetFields("application", + actions.SetFields("getClientId", func(c *actions.FieldConfig) interface{} { + return func(goja.FunctionCall) goja.Value { + return c.Runtime.ToValue(clientID) + } + }), + ), ), ) @@ -427,6 +437,7 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user User: qu.User, UserMetadata: qu.Metadata, Org: qu.Org, + Application: &ContextInfoApplication{ClientID: clientID}, UserGrants: qu.UserGrants, } @@ -463,13 +474,17 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user } type ContextInfo struct { - Function string `json:"function,omitempty"` - UserInfo *oidc.UserInfo `json:"userinfo,omitempty"` - User *query.User `json:"user,omitempty"` - UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"` - Org *query.UserInfoOrg `json:"org,omitempty"` - UserGrants []query.UserGrant `json:"user_grants,omitempty"` - Response *ContextInfoResponse `json:"response,omitempty"` + Function string `json:"function,omitempty"` + UserInfo *oidc.UserInfo `json:"userinfo,omitempty"` + User *query.User `json:"user,omitempty"` + UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"` + Org *query.UserInfoOrg `json:"org,omitempty"` + UserGrants []query.UserGrant `json:"user_grants,omitempty"` + Application *ContextInfoApplication `json:"application,omitempty"` + Response *ContextInfoResponse `json:"response,omitempty"` +} +type ContextInfoApplication struct { + ClientID string `json:"client_id,omitempty"` } type ContextInfoResponse struct { From b5f97d64b04039e38871dcab41454e19cc4d798b Mon Sep 17 00:00:00 2001 From: Silvan <27845747+adlerhurst@users.noreply.github.com> Date: Tue, 29 Jul 2025 09:09:00 +0200 Subject: [PATCH 4/7] chore(queue): use schema config instead of `search_path` and `application_name` to configure the database schema (#10075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes manual schema and application name setup via raw SQL and switches to using River’s built-in schema configuration. # Which Problems Are Solved River provides a configuration flag to set the schema of the queue. Zitadel sets the schema through database statements which is not needed anymore. # How the Problems Are Solved Set the schema in the river configuration and removed old code --- internal/queue/database.go | 39 -------------------------------------- internal/queue/migrate.go | 3 +-- internal/queue/queue.go | 3 +-- 3 files changed, 2 insertions(+), 43 deletions(-) diff --git a/internal/queue/database.go b/internal/queue/database.go index c5eb0b8ca3..93bf5abb9f 100644 --- a/internal/queue/database.go +++ b/internal/queue/database.go @@ -1,45 +1,6 @@ package queue -import ( - "context" - "sync" - - "github.com/jackc/pgx/v5" - - "github.com/zitadel/zitadel/internal/database/dialect" -) - const ( schema = "queue" applicationName = "zitadel_queue" ) - -var conns = &sync.Map{} - -type queueKey struct{} - -func WithQueue(parent context.Context) context.Context { - return context.WithValue(parent, queueKey{}, struct{}{}) -} - -func init() { - dialect.RegisterBeforeAcquire(func(ctx context.Context, c *pgx.Conn) error { - if _, ok := ctx.Value(queueKey{}).(struct{}); !ok { - return nil - } - _, err := c.Exec(ctx, "SET search_path TO "+schema+"; SET application_name TO "+applicationName) - if err != nil { - return err - } - conns.Store(c, struct{}{}) - return nil - }) - dialect.RegisterAfterRelease(func(c *pgx.Conn) error { - _, ok := conns.LoadAndDelete(c) - if !ok { - return nil - } - _, err := c.Exec(context.Background(), "SET search_path TO DEFAULT; SET application_name TO "+dialect.DefaultAppName) - return err - }) -} diff --git a/internal/queue/migrate.go b/internal/queue/migrate.go index e814da3bd3..9af294cbe1 100644 --- a/internal/queue/migrate.go +++ b/internal/queue/migrate.go @@ -27,11 +27,10 @@ func (m *Migrator) Execute(ctx context.Context) error { return err } - migrator, err := rivermigrate.New(m.driver, nil) + migrator, err := rivermigrate.New(m.driver, &rivermigrate.Config{Schema: schema}) if err != nil { return err } - ctx = WithQueue(ctx) _, err = migrator.Migrate(ctx, rivermigrate.DirectionUp, nil) return err diff --git a/internal/queue/queue.go b/internal/queue/queue.go index 44e291bf4d..6db5e0ec2a 100644 --- a/internal/queue/queue.go +++ b/internal/queue/queue.go @@ -41,6 +41,7 @@ func NewQueue(config *Config) (_ *Queue, err error) { Queues: make(map[string]river.QueueConfig), JobTimeout: -1, Middleware: middleware, + Schema: schema, }, }, nil } @@ -56,7 +57,6 @@ func (q *Queue) Start(ctx context.Context) (err error) { if q == nil || !q.shouldStart { return nil } - ctx = WithQueue(ctx) q.client, err = river.NewClient(q.driver, q.config) if err != nil { @@ -112,7 +112,6 @@ func WithQueueName(name string) InsertOpt { func (q *Queue) Insert(ctx context.Context, args river.JobArgs, opts ...InsertOpt) error { options := new(river.InsertOpts) - ctx = WithQueue(ctx) for _, opt := range opts { opt(options) } From 168f9661147e1ec346c8dd9b897e92a9787c31a1 Mon Sep 17 00:00:00 2001 From: Silvan <27845747+adlerhurst@users.noreply.github.com> Date: Tue, 29 Jul 2025 09:42:02 +0200 Subject: [PATCH 5/7] chore: update golangci-lint to v2 (#10331) Updates the configuration of golangci-lint to version 2 and updates the linter in the pipeline. --- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- .golangci.yaml | 377 +++++++++++------------------------- CONTRIBUTING.md | 2 +- 4 files changed, 111 insertions(+), 272 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e501eb169b..ac1ced50c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,7 +81,7 @@ jobs: with: node_version: "18" buf_version: "latest" - go_lint_version: "v1.64.8" + go_lint_version: "latest" core_cache_key: ${{ needs.core.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b8c7486f1f..9459fe638a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -77,7 +77,7 @@ jobs: path: ${{ inputs.core_cache_path }} key: ${{ inputs.core_cache_key }} fail-on-cache-miss: true - - uses: golangci/golangci-lint-action@v6 + - uses: golangci/golangci-lint-action@v8 with: version: ${{ inputs.go_lint_version }} github-token: ${{ github.token }} diff --git a/.golangci.yaml b/.golangci.yaml index a4d5fd95d4..712df7d33d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,309 +1,148 @@ -issues: - new-from-rev: main - # Set to 0 to disable. - max-issues-per-linter: 0 - # Set to 0 to disable. - max-same-issues: 0 - exclude-dirs: - - .artifacts - - .backups - - .codecov - - .github - - .keys - - .vscode - - build - - console - - deploy - - docs - - guides - - internal/api/ui/login/static - - openapi - - proto - - tools - - login - +version: "2" run: concurrency: 4 - timeout: 10m - go: '1.22' + go: "1.24" linters: enable: - # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false] - asciicheck - # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false] - bodyclose - # check the function whether use a non-inherited context [fast: false, auto-fix: false] - contextcheck - # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false] - - gocognit - # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false] - - unused - # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false] - - errcheck - # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false] - errname - # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false] - errorlint - # check exhaustiveness of enum switch statements [fast: false, auto-fix: false] - exhaustive - # Gci controls golang package import order and makes it always deterministic. [fast: true, auto-fix: false] - - gci - # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: false] + - gocognit - gocritic - # Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false] - - gosimple - # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false] - - govet - # Detects when assignments to existing variables are not used [fast: true, auto-fix: false] - - ineffassign - # Finds commonly misspelled English words in comments [fast: true, auto-fix: true] - misspell - # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false] - nakedret - # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false] - - staticcheck - # Like the front-end of a Go compiler, parses and type-checks Go code [fast: false, auto-fix: false] - - typecheck - # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false] - nolintlint - # Checks for misuse of Sprintf to construct a host with port in a URL. - nosprintfhostport - # checks whether Err of rows is checked successfully in `sql.Rows` [fast: false, auto-fix: false] - rowserrcheck - # Checks that sql.Rows and sql.Stmt are closed. [fast: false, auto-fix: false] - sqlclosecheck - # Remove unnecessary type conversions [fast: false, auto-fix: false] - unconvert - disable: - # Checks for dangerous unicode character sequences [fast: true, auto-fix: false] - # not needed because github does that out of the box - - bidichk - # containedctx is a linter that detects struct contained context.Context field [fast: true, auto-fix: false] - # using contextcheck which looks more active - - containedctx - # checks function and package cyclomatic complexity [fast: false, auto-fix: false] - # not use because gocognit is used - - cyclop - # The owner seems to have abandoned the linter. Replaced by unused. - # deprecated, replaced by unused - - deadcode - # check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false] - # FUTURE: IMO it sometimes makes sense to declare consts or types after a func - - decorder - # Go linter that checks if package imports are in a list of acceptable packages [fast: false, auto-fix: false] - # not required because of dependabot - - depguard - # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] - # FUTURE: old code is not compatible - - dogsled - # Tool for code clone detection [fast: true, auto-fix: false] - # FUTURE: old code is not compatible - - dupl - # checks for duplicate words in the source code - # not sure if it makes sense - - dupword - # check for two durations multiplied together [fast: false, auto-fix: false] - # FUTURE: checks for accident `1 * time.Second * time.Second` - - durationcheck - # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false] - # FUTURE: use asap, because we use json alot. nice feature is possiblity to check if err check is required - - errchkjson - # execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds - # FUTURE: might find some errors in sql queries - - execinquery - # Checks if all struct's fields are initialized [fast: false, auto-fix: false] - # deprecated - - exhaustivestruct - # Checks if all structure fields are initialized - # Not all fields have to be initialized - - exhaustruct - # checks for pointers to enclosing loop variables [fast: false, auto-fix: false] - # FUTURE: finds bugs hard to find, could occur much later - - exportloopref - # Forbids identifiers [fast: true, auto-fix: false] - # see no reason. allows to define regexp which are not allowed to use - - forbidigo - # finds forced type assertions [fast: true, auto-fix: false] - # not used because we mostly use `_, _ = a.(int)` - - forcetypeassert - # Tool for detection of long functions [fast: true, auto-fix: false] - # not used because it ignores complexity - - funlen - # check that no global variables exist [fast: true, auto-fix: false] - # We use some global variables which is ok IMO - - gochecknoglobals - # Checks that no init functions are present in Go code [fast: true, auto-fix: false] - # we use inits for the database abstraction - - gochecknoinits - # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false] - # FUTURE: might be cool to check - - goconst - # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false] - # not used because cyclop also checks complexity of package - - gocyclo - # Check if comments end in a period [fast: true, auto-fix: true] - # FUTURE: checks if comments are written as specified - - godot - # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] - # FUTURE: maybe makes sense later. IMO some view todos are ok for later tasks. - - godox - # Golang linter to check the errors handling expressions [fast: false, auto-fix: false] - # Not used in favore of errorlint - - goerr113 - # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true] - # ignored in favor of goimports - - gofmt - # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true] - # ignored in favor of goimports - - gofumpt - # Checks is file header matches to pattern [fast: true, auto-fix: false] - # ignored because we don't write licenses as headers - - goheader - # In addition to fixing imports, goimports also formats your code in the same style as gofmt. [fast: true, auto-fix: true] - # ignored in favor of gci - - goimports - #deprecated]: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: false, auto-fix: false] - # ignored in favor of goimports - - golint - # An analyzer to detect magic numbers. [fast: true, auto-fix: false] - # FUTURE: not that critical at the moment - - gomnd - # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false] - # FUTURE: not a problem at the moment - - gomoddirectives - # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false] - # FUTURE: maybe interesting because of licenses - - gomodguard - # Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false] - # FUTURE: not a problem at the moment - - goprintffuncname - # Inspects source code for security problems [fast: false, auto-fix: false] - # TODO: I think it would be more interesting to integrate into gh code scanning: https://github.com/securego/gosec#integrating-with-code-scanning - - gosec - # An analyzer to analyze expression groups. [fast: true, auto-fix: false] - # I think the groups (vars, consts, imports, ...) we have atm are ok - - grouper - # Checks that your code uses short syntax for if-statements whenever possible [fast: true, auto-fix: false] - # Dont't use its deprecated - - ifshort - # Enforces consistent import aliases [fast: false, auto-fix: false] - # FUTURE: aliasing of imports is more or less consistent - - importas - # A linter that checks the number of methods inside an interface. - # No need at the moment, repository abstraction was removed - - interfacebloat - # A linter that suggests interface types - # Don't use it's archived - - interfacer - # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false] - # FUTURE: check if no interface is returned - - ireturn - # Reports long lines [fast: true, auto-fix: false] - # FUTURE: would make code more readable - - lll - # Checks key valur pairs for common logger libraries (kitlog,klog,logr,zap). - # FUTURE: useable as soon as we switch logger library - - loggercheck - # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false] - # not used because volume of halstead complexity feels strange as measurement https://en.wikipedia.org/wiki/Halstead_complexity_measures - - maintidx - # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false] - # I would prefer to use https://github.com/alexkohler/prealloc - - makezero - # Reports deeply nested if statements [fast: true, auto-fix: false] - # focus only on if's - - nestif - # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false] - # FUTURE: check if it is allowed to return nil partially in error catch - - nilerr - # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false] - # FUTURE: would reduce checks and panics + - errcheck + - govet + - ineffassign + - staticcheck + - unused + - nilnil + disable: + - bidichk + - containedctx + - cyclop + - decorder + - depguard + - dogsled + - dupl + - dupword + - durationcheck + - err113 + - errchkjson + - exhaustruct + - forbidigo + - forcetypeassert + - funlen + - gochecknoglobals + - gochecknoinits + - goconst + - gocyclo + - godot + - godox + - goheader + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - grouper + - importas + - interfacebloat + - ireturn + - lll + - loggercheck + - maintidx + - makezero + - mnd + - nestif + - nilerr - nilnil - # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false] - # DISCUSS: IMO the readability of does not always increase using more empty lines - nlreturn - # noctx finds sending http request without context.Context [fast: false, auto-fix: false] - # only interesting if using http - noctx - # Reports all names returns - # Named returns are not allowed which IMO reduces readability of code - nonamedreturns - # detects snake case of variable naming and function name. - # has not been a problem in our code and deprecated - - nosnakecase - # paralleltest detects missing usage of t.Parallel() method in your Go test [fast: true, auto-fix: false] - # FUTURE: will break all of our tests - paralleltest - # Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false] - # FUTURE: would improve performance - prealloc - # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false] - # FUTURE: checks for overwrites - predeclared - # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false] - # Not interesting at the moment - promlinter - # Checks that package variables are not reassigned - # FUTURE: checks if vars like Err's are reassigned which might break code - reassign - # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false] - # Linter aggregator, would allow to use less other linters - revive - # checks for unpinned variables in go programs - # deprecated - - scopelint - # Finds unused struct fields [fast: false, auto-fix: false] - # deprecated, replaced by unused - - structcheck - # Stylecheck is a replacement for golint [fast: false, auto-fix: false] - # we use goimports - - stylecheck - # Checks the struct tags. [fast: true, auto-fix: false] - # FUTURE: would help for new structs - tagliatelle - # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false] - # FUTURE: currently are no env vars set - - tenv - # linter checks if examples are testable (have an expected output) - # FUTURE: as soon as examples are added - testableexamples - # linter that makes you use a separate _test package [fast: true, auto-fix: false] - # don't use because we test some unexported functions - testpackage - # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false] - # FUTURE: nice to improve test quality - thelper - # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes [fast: false, auto-fix: false] - # FUTURE: nice to improve test quality - tparallel - # Reports unused function parameters [fast: false, auto-fix: false] - # DISCUSS: nice idea and would improve code quality, but how to handle false positives? - unparam - # A linter that detect the possibility to use variables/constants from the Go standard library. - # FUTURE: improves code quality - usestdlibvars - # Finds unused global variables and constants [fast: false, auto-fix: false] - # deprecated, replaced by unused - - varcheck - # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false] - # I would not use it because it more or less checks if var lenght matches - varnamelen - # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false] - # FUTURE: would improve code quality (maybe already checked by vet?) - wastedassign - # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true] - # Not sure if it improves code readability - whitespace - # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false] - # FUTURE: improves UX because all the errors will be ZITADEL errors - wrapcheck - # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false] - # FUTURE: improves code quality by allowing and blocking line breaks - wsl -linters-settings: - gci: - sections: - - standard # Standard section: captures all standard packages. - - default # Default section: contains all imports that could not be matched to another section type. - - prefix(github.com/zitadel/zitadel) # Custom section: groups all imports with the specified Prefix. - custom-order: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - .artifacts + - .backups + - .codecov + - .github + - .keys + - .vscode + - build + - console + - deploy + - docs + - guides + - internal/api/ui/login/static + - openapi + - proto + - tools + - third_party$ + - builtin$ + - examples$ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + new-from-rev: main +formatters: + enable: + - gci + settings: + gci: + sections: + - standard + - default + - prefix(github.com/zitadel/zitadel) + custom-order: true + exclusions: + generated: lax + paths: + - .artifacts + - .backups + - .codecov + - .github + - .keys + - .vscode + - build + - console + - deploy + - docs + - guides + - internal/api/ui/login/static + - openapi + - proto + - tools + - third_party$ + - builtin$ + - examples$ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c1ae53072..3d6e517a8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -200,7 +200,7 @@ With [make](https://www.gnu.org/software/make/), you build a debuggable Zitadel Then, you test your changes via the console your binary is serving at http://localhost:8080 and by verifying the database. Once you are happy with your changes, you run end-to-end tests and tear everything down. -Zitadel uses [golangci-lint](https://golangci-lint.run) for code quality checks. Please use [this configuration](.golangci.yaml) when running `golangci-lint`. We recommend to set golangci-lint as linter in your IDE. +Zitadel uses [golangci-lint v2](https://golangci-lint.run) for code quality checks. Please use [this configuration](.golangci.yaml) when running `golangci-lint`. We recommend to set golangci-lint as linter in your IDE. The commands in this section are tested against the following software versions: From 6353eb757cc59ff1fed2baa4987f6819f645b436 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Tue, 29 Jul 2025 10:07:26 +0200 Subject: [PATCH 6/7] chore: remove depot (#10343) # Which Problems Are Solved We hit a rate limit for our current plan at Depot https://github.com/zitadel/zitadel/actions/runs/16589101600/job/46920267954?pr=10342. # How the Problems Are Solved Let's remove Depot for now, make things work and then optimize with caching. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/build.yml | 4 ---- .github/workflows/compile.yml | 3 --- .github/workflows/lint.yml | 2 ++ .github/workflows/login-container.yml | 10 +++------- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac1ced50c7..b805c99060 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,8 +54,6 @@ jobs: console_cache_path: ${{ needs.console.outputs.cache_path }} version: ${{ needs.version.outputs.version }} node_version: "20" - secrets: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} core-unit-test: needs: core @@ -103,8 +101,6 @@ jobs: with: login_build_image_name: "ghcr.io/zitadel/zitadel-login-build" node_version: "20" - secrets: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} e2e: uses: ./.github/workflows/e2e.yml diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 65e7851d48..e1493cfcff 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -21,9 +21,6 @@ on: node_version: required: true type: string - secrets: - DEPOT_TOKEN: - required: true jobs: executable: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9459fe638a..ce824e94e8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -53,6 +53,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Run lint and unit tests in dev container uses: devcontainers/ci@v0.3 with: diff --git a/.github/workflows/login-container.yml b/.github/workflows/login-container.yml index 5137213cc4..538d1b505a 100644 --- a/.github/workflows/login-container.yml +++ b/.github/workflows/login-container.yml @@ -14,9 +14,6 @@ on: login_build_image: description: 'The full image tag of the standalone login image' value: '${{ inputs.login_build_image_name }}:${{ github.sha }}' - secrets: - DEPOT_TOKEN: - required: true permissions: packages: write @@ -35,7 +32,6 @@ jobs: packages: write steps: - uses: actions/checkout@v4 - - uses: depot/setup-action@v1 - name: Login meta id: login-meta uses: docker/metadata-action@v5 @@ -52,17 +48,17 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Bake login multi-arch - uses: depot/bake-action@v1 + uses: docker/bake-action@v6 env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} NODE_VERSION: ${{ inputs.node_version }} with: push: true provenance: true sbom: true targets: login-standalone - project: w47wkxzdtw files: | ./apps/login/docker-bake.hcl ./apps/login/docker-bake-release.hcl From 20e7807ee5357c3d63459accd202f390500382f4 Mon Sep 17 00:00:00 2001 From: Silvan <27845747+adlerhurst@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:58:42 +0200 Subject: [PATCH 7/7] fix(projections): pass context to statement execution method (#10328) ## Which problems are solved The execution of statements of projections did not have the context present. ## How the problems were solved Pass the context to the execute function ## Additional info This change is required to use the repositories of the relational tables in projections. --- .../eventsourcing/handler/styling.go | 2 +- internal/eventstore/handler/init.go | 2 +- internal/eventstore/handler/v2/handler.go | 4 +- internal/eventstore/handler/v2/init.go | 14 +++--- internal/eventstore/handler/v2/statement.go | 9 ++-- .../eventstore/handler/v2/statement_test.go | 23 ++++----- internal/execution/ctx.go | 4 +- internal/execution/handlers.go | 6 +-- internal/execution/handlers_test.go | 2 +- .../handlers/back_channel_logout.go | 8 ++-- internal/notification/handlers/ctx.go | 11 ++--- .../notification/handlers/quota_notifier.go | 4 +- .../notification/handlers/telemetry_pusher.go | 4 +- .../notification/handlers/user_notifier.go | 48 +++++++++---------- .../handlers/user_notifier_legacy.go | 40 ++++++++-------- .../handlers/user_notifier_legacy_test.go | 12 ++--- .../handlers/user_notifier_test.go | 18 +++---- internal/query/projection/event_test.go | 2 +- 18 files changed, 106 insertions(+), 107 deletions(-) diff --git a/internal/admin/repository/eventsourcing/handler/styling.go b/internal/admin/repository/eventsourcing/handler/styling.go index 2f8964b519..a9dfba0108 100644 --- a/internal/admin/repository/eventsourcing/handler/styling.go +++ b/internal/admin/repository/eventsourcing/handler/styling.go @@ -185,7 +185,7 @@ func (s *Styling) Reducers() []handler.AggregateReducer { } func (m *Styling) processLabelPolicy(event eventstore.Event) (_ *handler.Statement, err error) { - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { policy := new(iam_model.LabelPolicyView) switch event.Type() { case instance.LabelPolicyAddedEventType, diff --git a/internal/eventstore/handler/init.go b/internal/eventstore/handler/init.go index 0ae6d2261b..6b1f108a66 100644 --- a/internal/eventstore/handler/init.go +++ b/internal/eventstore/handler/init.go @@ -6,7 +6,7 @@ import "context" type Init func(context.Context, *Check) error type Check struct { - Executes []func(ex Executer, projectionName string) (bool, error) + Executes []func(ctx context.Context, executer Executer, projectionName string) (bool, error) } func (c *Check) IsNoop() bool { diff --git a/internal/eventstore/handler/v2/handler.go b/internal/eventstore/handler/v2/handler.go index fd8b206b38..2f59aeed62 100644 --- a/internal/eventstore/handler/v2/handler.go +++ b/internal/eventstore/handler/v2/handler.go @@ -646,7 +646,7 @@ func (h *Handler) executeStatements(ctx context.Context, tx *sql.Tx, statements for i, statement := range statements { select { case <-ctx.Done(): - break + return lastProcessedIndex, ctx.Err() default: err := h.executeStatement(ctx, tx, statement) if err != nil { @@ -669,7 +669,7 @@ func (h *Handler) executeStatement(ctx context.Context, tx *sql.Tx, statement *S return err } - if err = statement.Execute(tx, h.projection.Name()); err != nil { + if err = statement.Execute(ctx, tx, h.projection.Name()); err != nil { h.log().WithError(err).Error("statement execution failed") _, rollbackErr := tx.ExecContext(ctx, "ROLLBACK TO SAVEPOINT exec_stmt") diff --git a/internal/eventstore/handler/v2/init.go b/internal/eventstore/handler/v2/init.go index ead1c806d0..f797734644 100644 --- a/internal/eventstore/handler/v2/init.go +++ b/internal/eventstore/handler/v2/init.go @@ -200,7 +200,7 @@ func (h *Handler) Init(ctx context.Context) error { } for i, execute := range check.Init().Executes { logging.WithFields("projection", h.projection.Name(), "execute", i).Debug("executing check") - next, err := execute(tx, h.projection.Name()) + next, err := execute(ctx, tx, h.projection.Name()) if err != nil { logging.OnError(tx.Rollback()).Debug("unable to rollback") return err @@ -218,7 +218,7 @@ func NewTableCheck(table *Table, opts ...execOption) *handler.Check { create := func(config execConfig) string { return createTableStatement(table, config.tableName, "") } - executes := make([]func(handler.Executer, string) (bool, error), len(table.indices)+1) + executes := make([]func(context.Context, handler.Executer, string) (bool, error), len(table.indices)+1) executes[0] = execNextIfExists(config, create, opts, true) for i, index := range table.indices { executes[i+1] = execNextIfExists(config, createIndexCheck(index), opts, true) @@ -239,7 +239,7 @@ func NewMultiTableCheck(primaryTable *Table, secondaryTables ...*SuffixedTable) } return &handler.Check{ - Executes: []func(handler.Executer, string) (bool, error){ + Executes: []func(context.Context, handler.Executer, string) (bool, error){ execNextIfExists(config, create, nil, true), }, } @@ -257,14 +257,14 @@ func NewViewCheck(selectStmt string, secondaryTables ...*SuffixedTable) *handler } return &handler.Check{ - Executes: []func(handler.Executer, string) (bool, error){ + Executes: []func(context.Context, handler.Executer, string) (bool, error){ execNextIfExists(config, create, nil, false), }, } } -func execNextIfExists(config execConfig, q query, opts []execOption, executeNext bool) func(handler.Executer, string) (bool, error) { - return func(handler handler.Executer, name string) (shouldExecuteNext bool, err error) { +func execNextIfExists(config execConfig, q query, opts []execOption, executeNext bool) func(ctx context.Context, handler handler.Executer, name string) (bool, error) { + return func(ctx context.Context, handler handler.Executer, name string) (shouldExecuteNext bool, err error) { _, err = handler.Exec("SAVEPOINT exec_stmt") if err != nil { return false, zerrors.ThrowInternal(err, "V2-U1wlz", "create savepoint failed") @@ -280,7 +280,7 @@ func execNextIfExists(config execConfig, q query, opts []execOption, executeNext return } }() - err = exec(config, q, opts)(handler, name) + err = exec(config, q, opts)(ctx, handler, name) return false, err } } diff --git a/internal/eventstore/handler/v2/statement.go b/internal/eventstore/handler/v2/statement.go index 5024c8c945..e584160287 100644 --- a/internal/eventstore/handler/v2/statement.go +++ b/internal/eventstore/handler/v2/statement.go @@ -1,6 +1,7 @@ package handler import ( + "context" "database/sql" "encoding/json" "errors" @@ -91,7 +92,7 @@ type Statement struct { Execute Exec } -type Exec func(ex Executer, projectionName string) error +type Exec func(ctx context.Context, ex Executer, projectionName string) error func WithTableSuffix(name string) func(*execConfig) { return func(o *execConfig) { @@ -670,7 +671,7 @@ type execConfig struct { type query func(config execConfig) string func exec(config execConfig, q query, opts []execOption) Exec { - return func(ex Executer, projectionName string) (err error) { + return func(ctx context.Context, ex Executer, projectionName string) (err error) { if projectionName == "" { return ErrNoProjection } @@ -694,12 +695,12 @@ func exec(config execConfig, q query, opts []execOption) Exec { } func multiExec(execList []Exec) Exec { - return func(ex Executer, projectionName string) error { + return func(ctx context.Context, ex Executer, projectionName string) error { for _, exec := range execList { if exec == nil { continue } - if err := exec(ex, projectionName); err != nil { + if err := exec(ctx, ex, projectionName); err != nil { return err } } diff --git a/internal/eventstore/handler/v2/statement_test.go b/internal/eventstore/handler/v2/statement_test.go index 787ec105e1..8384029ec8 100644 --- a/internal/eventstore/handler/v2/statement_test.go +++ b/internal/eventstore/handler/v2/statement_test.go @@ -1,6 +1,7 @@ package handler import ( + "context" "database/sql" "errors" "reflect" @@ -197,7 +198,7 @@ func TestNewCreateStatement(t *testing.T) { tt.want.executer.t = t stmt := NewCreateStatement(tt.args.event, tt.args.values) - err := stmt.Execute(tt.want.executer, tt.args.table) + err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table) if !tt.want.isErr(err) { t.Errorf("unexpected error: %v", err) } @@ -506,7 +507,7 @@ func TestNewUpsertStatement(t *testing.T) { tt.want.executer.t = t stmt := NewUpsertStatement(tt.args.event, tt.args.conflictCols, tt.args.values) - err := stmt.Execute(tt.want.executer, tt.args.table) + err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table) if !tt.want.isErr(err) { t.Errorf("unexpected error: %v", err) } @@ -710,7 +711,7 @@ func TestNewUpdateStatement(t *testing.T) { tt.want.executer.t = t stmt := NewUpdateStatement(tt.args.event, tt.args.values, tt.args.conditions) - err := stmt.Execute(tt.want.executer, tt.args.table) + err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table) if !tt.want.isErr(err) { t.Errorf("unexpected error: %v", err) } @@ -827,7 +828,7 @@ func TestNewDeleteStatement(t *testing.T) { tt.want.executer.t = t stmt := NewDeleteStatement(tt.args.event, tt.args.conditions) - err := stmt.Execute(tt.want.executer, tt.args.table) + err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table) if !tt.want.isErr(err) { t.Errorf("unexpected error: %v", err) } @@ -878,7 +879,7 @@ func TestNewNoOpStatement(t *testing.T) { return } tt.want.executer.t = t - err := stmt.Execute(tt.want.executer, tt.args.table) + err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table) if !tt.want.isErr(err) { t.Errorf("unexpected error: %v", err) } @@ -1054,7 +1055,7 @@ func TestNewMultiStatement(t *testing.T) { return } tt.want.executer.t = t - err := stmt.Execute(tt.want.executer, tt.args.table) + err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table) if !tt.want.isErr(err) { t.Errorf("unexpected error: %v", err) } @@ -1338,7 +1339,7 @@ func TestNewCopyStatement(t *testing.T) { tt.want.executer.t = t stmt := NewCopyStatement(tt.args.event, tt.args.conflictingCols, tt.args.from, tt.args.to, tt.args.conds) - err := stmt.Execute(tt.want.executer, tt.args.table) + err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table) if !tt.want.isErr(err) { t.Errorf("unexpected error: %v", err) } @@ -1349,7 +1350,7 @@ func TestNewCopyStatement(t *testing.T) { func TestStatement_Execute(t *testing.T) { type fields struct { - execute func(ex Executer, projectionName string) error + execute func(ctx context.Context, ex Executer, projectionName string) error } type want struct { isErr func(error) bool @@ -1366,7 +1367,7 @@ func TestStatement_Execute(t *testing.T) { { name: "execute returns no error", fields: fields{ - execute: func(ex Executer, projectionName string) error { return nil }, + execute: func(ctx context.Context, ex Executer, projectionName string) error { return nil }, }, args: args{ projectionName: "my_projection", @@ -1383,7 +1384,7 @@ func TestStatement_Execute(t *testing.T) { projectionName: "my_projection", }, fields: fields{ - execute: func(ex Executer, projectionName string) error { return errTest }, + execute: func(ctx context.Context, ex Executer, projectionName string) error { return errTest }, }, want: want{ isErr: func(err error) bool { @@ -1397,7 +1398,7 @@ func TestStatement_Execute(t *testing.T) { stmt := &Statement{ Execute: tt.fields.execute, } - if err := stmt.Execute(nil, tt.args.projectionName); !tt.want.isErr(err) { + if err := stmt.Execute(t.Context(), nil, tt.args.projectionName); !tt.want.isErr(err) { t.Errorf("unexpected error: %v", err) } }) diff --git a/internal/execution/ctx.go b/internal/execution/ctx.go index 9e6bac3e30..d63fa13e5d 100644 --- a/internal/execution/ctx.go +++ b/internal/execution/ctx.go @@ -9,8 +9,8 @@ import ( const ExecutionUserID = "EXECUTION" -func HandlerContext(event *eventstore.Aggregate) context.Context { - ctx := authz.WithInstanceID(context.Background(), event.InstanceID) +func HandlerContext(parent context.Context, event *eventstore.Aggregate) context.Context { + ctx := authz.WithInstanceID(parent, event.InstanceID) return authz.SetCtxData(ctx, authz.CtxData{UserID: ExecutionUserID, OrgID: event.ResourceOwner}) } diff --git a/internal/execution/handlers.go b/internal/execution/handlers.go index 030e6d5186..1c27cb5920 100644 --- a/internal/execution/handlers.go +++ b/internal/execution/handlers.go @@ -113,7 +113,7 @@ func idsForEventType(eventType string) []string { } func (u *eventHandler) reduce(e eventstore.Event) (*handler.Statement, error) { - ctx := HandlerContext(e.Aggregate()) + ctx := HandlerContext(context.Background(), e.Aggregate()) targets, err := u.query.TargetsByExecutionID(ctx, idsForEventType(string(e.Type()))) if err != nil { @@ -125,8 +125,8 @@ func (u *eventHandler) reduce(e eventstore.Event) (*handler.Statement, error) { return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(e, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(e.Aggregate()) + return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, e.Aggregate()) req, err := NewRequest(e, targets) if err != nil { return err diff --git a/internal/execution/handlers_test.go b/internal/execution/handlers_test.go index de220abcc0..a123947160 100644 --- a/internal/execution/handlers_test.go +++ b/internal/execution/handlers_test.go @@ -440,7 +440,7 @@ func TestActionProjection_reduces(t *testing.T) { assert.Nil(t, stmt.Execute) return } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.stmtErr != nil { w.stmtErr(t, err) return diff --git a/internal/notification/handlers/back_channel_logout.go b/internal/notification/handlers/back_channel_logout.go index 983915ac28..12c2c708e9 100644 --- a/internal/notification/handlers/back_channel_logout.go +++ b/internal/notification/handlers/back_channel_logout.go @@ -95,8 +95,8 @@ func (u *backChannelLogoutNotifier) reduceUserSignedOut(event eventstore.Event) return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Gr63h", "reduce.wrong.event.type %s", user.HumanSignedOutType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx, err := u.queries.HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx, err := u.queries.HandlerContext(ctx, event.Aggregate()) if err != nil { return err } @@ -116,8 +116,8 @@ func (u *backChannelLogoutNotifier) reduceSessionTerminated(event eventstore.Eve return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-D6H2h", "reduce.wrong.event.type %s", session.TerminateType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx, err := u.queries.HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx, err := u.queries.HandlerContext(ctx, event.Aggregate()) if err != nil { return err } diff --git a/internal/notification/handlers/ctx.go b/internal/notification/handlers/ctx.go index b091a61cdd..2c2599b060 100644 --- a/internal/notification/handlers/ctx.go +++ b/internal/notification/handlers/ctx.go @@ -9,8 +9,8 @@ import ( const NotifyUserID = "NOTIFICATION" //TODO: system? -func HandlerContext(event *eventstore.Aggregate) context.Context { - ctx := authz.WithInstanceID(context.Background(), event.InstanceID) +func HandlerContext(parent context.Context, event *eventstore.Aggregate) context.Context { + ctx := authz.WithInstanceID(parent, event.InstanceID) return authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: event.ResourceOwner}) } @@ -18,12 +18,11 @@ func ContextWithNotifier(ctx context.Context, aggregate *eventstore.Aggregate) c return authz.WithInstanceID(authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: aggregate.ResourceOwner}), aggregate.InstanceID) } -func (n *NotificationQueries) HandlerContext(event *eventstore.Aggregate) (context.Context, error) { - ctx := context.Background() - instance, err := n.InstanceByID(ctx, event.InstanceID) +func (n *NotificationQueries) HandlerContext(parent context.Context, event *eventstore.Aggregate) (context.Context, error) { + instance, err := n.InstanceByID(parent, event.InstanceID) if err != nil { return nil, err } - ctx = authz.WithInstance(ctx, instance) + ctx := authz.WithInstance(parent, instance) return authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: event.ResourceOwner}), nil } diff --git a/internal/notification/handlers/quota_notifier.go b/internal/notification/handlers/quota_notifier.go index f308291243..365888959f 100644 --- a/internal/notification/handlers/quota_notifier.go +++ b/internal/notification/handlers/quota_notifier.go @@ -62,8 +62,8 @@ func (u *quotaNotifier) reduceNotificationDue(event eventstore.Event) (*handler. return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-DLxdE", "reduce.wrong.event.type %s", quota.NotificationDueEventType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, map[string]interface{}{"dueEventID": e.ID}, quota.NotifiedEventType) if err != nil { return err diff --git a/internal/notification/handlers/telemetry_pusher.go b/internal/notification/handlers/telemetry_pusher.go index 7e510a2b4c..2c32db61c1 100644 --- a/internal/notification/handlers/telemetry_pusher.go +++ b/internal/notification/handlers/telemetry_pusher.go @@ -5,7 +5,6 @@ import ( "net/http" "time" - "github.com/zitadel/zitadel/internal/api/call" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" @@ -69,12 +68,11 @@ func (t *telemetryPusher) Reducers() []handler.AggregateReducer { } func (t *telemetryPusher) pushMilestones(event eventstore.Event) (*handler.Statement, error) { - ctx := call.WithTimestamp(context.Background()) e, ok := event.(*milestone.ReachedEvent) if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-lDTs5", "reduce.wrong.event.type %s", event.Type()) } - return handler.NewStatement(event, func(handler.Executer, string) error { + return handler.NewStatement(event, func(ctx context.Context, _ handler.Executer, _ string) error { // Do not push the milestone again if this was a migration event. if e.ReachedDate != nil { return nil diff --git a/internal/notification/handlers/user_notifier.go b/internal/notification/handlers/user_notifier.go index f36f5d828c..6ca753caa9 100644 --- a/internal/notification/handlers/user_notifier.go +++ b/internal/notification/handlers/user_notifier.go @@ -203,8 +203,8 @@ func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Sta if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-EFe2f", "reduce.wrong.event.type %s", user.HumanInitialCodeAddedType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.UserV1InitialCodeAddedType, user.UserV1InitialCodeSentType, user.HumanInitialCodeAddedType, user.HumanInitialCodeSentType) @@ -253,8 +253,8 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType, user.HumanEmailCodeAddedType, user.HumanEmailCodeSentType) @@ -309,8 +309,8 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.UserV1PasswordCodeAddedType, user.UserV1PasswordCodeSentType, user.HumanPasswordCodeAddedType, user.HumanPasswordCodeSentType) @@ -362,8 +362,8 @@ func (u *userNotifier) reduceOTPSMSCodeAdded(event eventstore.Event) (*handler.S return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ASF3g", "reduce.wrong.event.type %s", user.HumanOTPSMSCodeAddedType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.HumanOTPSMSCodeAddedType, user.HumanOTPSMSCodeSentType) @@ -406,8 +406,8 @@ func (u *userNotifier) reduceSessionOTPSMSChallenged(event eventstore.Event) (*h return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, session.OTPSMSChallengedType, session.OTPSMSSentType) @@ -455,8 +455,8 @@ func (u *userNotifier) reduceOTPEmailCodeAdded(event eventstore.Event) (*handler return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-JL3hw", "reduce.wrong.event.type %s", user.HumanOTPEmailCodeAddedType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.HumanOTPEmailCodeAddedType, user.HumanOTPEmailCodeSentType) @@ -507,8 +507,8 @@ func (u *userNotifier) reduceSessionOTPEmailChallenged(event eventstore.Event) ( if e.ReturnCode { return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, session.OTPEmailChallengedType, session.OTPEmailSentType) @@ -573,8 +573,8 @@ func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Sta if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Drh5w", "reduce.wrong.event.type %s", user.UserDomainClaimedType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, user.UserDomainClaimedType, user.UserDomainClaimedSentType) if err != nil { @@ -619,8 +619,8 @@ func (u *userNotifier) reducePasswordlessCodeRequested(event eventstore.Event) ( return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, map[string]interface{}{"id": e.ID}, user.HumanPasswordlessInitCodeSentType) if err != nil { return err @@ -668,8 +668,8 @@ func (u *userNotifier) reducePasswordChanged(event eventstore.Event) (*handler.S return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Yko2z8", "reduce.wrong.event.type %s", user.HumanPasswordChangedType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, user.HumanPasswordChangeSentType) if err != nil { return err @@ -720,8 +720,8 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.UserV1PhoneCodeAddedType, user.UserV1PhoneCodeSentType, user.HumanPhoneCodeAddedType, user.HumanPhoneCodeSentType) @@ -768,8 +768,8 @@ func (u *userNotifier) reduceInviteCodeAdded(event eventstore.Event) (*handler.S return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.HumanInviteCodeAddedType, user.HumanInviteCodeSentType) if err != nil { diff --git a/internal/notification/handlers/user_notifier_legacy.go b/internal/notification/handlers/user_notifier_legacy.go index 1921510bf3..146c60e10b 100644 --- a/internal/notification/handlers/user_notifier_legacy.go +++ b/internal/notification/handlers/user_notifier_legacy.go @@ -133,8 +133,8 @@ func (u *userNotifierLegacy) reduceInitCodeAdded(event eventstore.Event) (*handl return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-EFe2f", "reduce.wrong.event.type %s", user.HumanInitialCodeAddedType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.UserV1InitialCodeAddedType, user.UserV1InitialCodeSentType, user.HumanInitialCodeAddedType, user.HumanInitialCodeSentType) @@ -194,8 +194,8 @@ func (u *userNotifierLegacy) reduceEmailCodeAdded(event eventstore.Event) (*hand return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType, user.HumanEmailCodeAddedType, user.HumanEmailCodeSentType) @@ -254,8 +254,8 @@ func (u *userNotifierLegacy) reducePasswordCodeAdded(event eventstore.Event) (*h return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.UserV1PasswordCodeAddedType, user.UserV1PasswordCodeSentType, user.HumanPasswordCodeAddedType, user.HumanPasswordCodeSentType) @@ -337,7 +337,7 @@ func (u *userNotifierLegacy) reduceSessionOTPSMSChallenged(event eventstore.Even if e.CodeReturned { return handler.NewNoOpStatement(e), nil } - ctx := HandlerContext(event.Aggregate()) + ctx := HandlerContext(context.Background(), event.Aggregate()) s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "", nil) if err != nil { return nil, err @@ -363,7 +363,7 @@ func (u *userNotifierLegacy) reduceOTPSMS( sentCommand func(ctx context.Context, userID, resourceOwner string, generatorInfo *senders.CodeGeneratorInfo) (err error), eventTypes ...eventstore.EventType, ) (*handler.Statement, error) { - ctx := HandlerContext(event.Aggregate()) + ctx := HandlerContext(context.Background(), event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, expiry, nil, eventTypes...) if err != nil { return nil, err @@ -445,7 +445,7 @@ func (u *userNotifierLegacy) reduceSessionOTPEmailChallenged(event eventstore.Ev if e.ReturnCode { return handler.NewNoOpStatement(e), nil } - ctx := HandlerContext(event.Aggregate()) + ctx := HandlerContext(context.Background(), event.Aggregate()) s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "", nil) if err != nil { return nil, err @@ -484,7 +484,7 @@ func (u *userNotifierLegacy) reduceOTPEmail( sentCommand func(ctx context.Context, userID string, resourceOwner string) (err error), eventTypes ...eventstore.EventType, ) (*handler.Statement, error) { - ctx := HandlerContext(event.Aggregate()) + ctx := HandlerContext(context.Background(), event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, expiry, nil, eventTypes...) if err != nil { return nil, err @@ -543,8 +543,8 @@ func (u *userNotifierLegacy) reduceDomainClaimed(event eventstore.Event) (*handl if !ok { return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Drh5w", "reduce.wrong.event.type %s", user.UserDomainClaimedType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, user.UserDomainClaimedType, user.UserDomainClaimedSentType) if err != nil { @@ -598,8 +598,8 @@ func (u *userNotifierLegacy) reducePasswordlessCodeRequested(event eventstore.Ev return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, map[string]interface{}{"id": e.ID}, user.HumanPasswordlessInitCodeSentType) if err != nil { return err @@ -653,8 +653,8 @@ func (u *userNotifierLegacy) reducePasswordChanged(event eventstore.Event) (*han return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Yko2z8", "reduce.wrong.event.type %s", user.HumanPasswordChangedType) } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, user.HumanPasswordChangeSentType) if err != nil { return err @@ -719,8 +719,8 @@ func (u *userNotifierLegacy) reducePhoneCodeAdded(event eventstore.Event) (*hand return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.UserV1PhoneCodeAddedType, user.UserV1PhoneCodeSentType, user.HumanPhoneCodeAddedType, user.HumanPhoneCodeSentType) @@ -777,8 +777,8 @@ func (u *userNotifierLegacy) reduceInviteCodeAdded(event eventstore.Event) (*han return handler.NewNoOpStatement(e), nil } - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - ctx := HandlerContext(event.Aggregate()) + return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error { + ctx = HandlerContext(ctx, event.Aggregate()) alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil, user.HumanInviteCodeAddedType, user.HumanInviteCodeSentType) if err != nil { diff --git a/internal/notification/handlers/user_notifier_legacy_test.go b/internal/notification/handlers/user_notifier_legacy_test.go index a4c24fd196..08461ee706 100644 --- a/internal/notification/handlers/user_notifier_legacy_test.go +++ b/internal/notification/handlers/user_notifier_legacy_test.go @@ -283,7 +283,7 @@ func Test_userNotifierLegacy_reduceInitCodeAdded(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -596,7 +596,7 @@ func Test_userNotifierLegacy_reduceEmailCodeAdded(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -949,7 +949,7 @@ func Test_userNotifierLegacy_reducePasswordCodeAdded(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -1080,7 +1080,7 @@ func Test_userNotifierLegacy_reduceDomainClaimed(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -1355,7 +1355,7 @@ func Test_userNotifierLegacy_reducePasswordlessCodeRequested(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -1495,7 +1495,7 @@ func Test_userNotifierLegacy_reducePasswordChanged(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { diff --git a/internal/notification/handlers/user_notifier_test.go b/internal/notification/handlers/user_notifier_test.go index f7090f0146..874fbdf9af 100644 --- a/internal/notification/handlers/user_notifier_test.go +++ b/internal/notification/handlers/user_notifier_test.go @@ -188,7 +188,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -366,7 +366,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) { assert.Nil(t, stmt.Execute) return } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -601,7 +601,7 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) { assert.Nil(t, stmt.Execute) return } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -731,7 +731,7 @@ func Test_userNotifier_reduceDomainClaimed(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -906,7 +906,7 @@ func Test_userNotifier_reducePasswordlessCodeRequested(t *testing.T) { assert.Nil(t, stmt.Execute) return } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -1066,7 +1066,7 @@ func Test_userNotifier_reducePasswordChanged(t *testing.T) { } else { assert.NoError(t, err) } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -1329,7 +1329,7 @@ func Test_userNotifier_reduceOTPEmailChallenged(t *testing.T) { assert.Nil(t, stmt.Execute) return } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -1590,7 +1590,7 @@ func Test_userNotifier_reduceOTPSMSChallenged(t *testing.T) { assert.Nil(t, stmt.Execute) return } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { @@ -1886,7 +1886,7 @@ func Test_userNotifier_reduceInviteCodeAdded(t *testing.T) { assert.Nil(t, stmt.Execute) return } - err = stmt.Execute(nil, "") + err = stmt.Execute(t.Context(), nil, "") if w.err != nil { w.err(t, err) } else { diff --git a/internal/query/projection/event_test.go b/internal/query/projection/event_test.go index 073f34c688..317efe817e 100644 --- a/internal/query/projection/event_test.go +++ b/internal/query/projection/event_test.go @@ -99,7 +99,7 @@ func assertReduce(t *testing.T, stmt *handler.Statement, err error, projection s want.executer.Validate(t) return } - err = stmt.Execute(want.executer, projection) + err = stmt.Execute(t.Context(), want.executer, projection) if err != nil { t.Errorf("unexpected error: %v", err) }