mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
8dac4e2b5462793367a5276041ac9e4462f7b2fb
136
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8dac4e2b54 |
docs: add instance administrator hardening guide (#12678)
# Which Problems Are Solved - Operators lack a single guide for separating instance-level principals (break-glass, login client) from tenant organizations. - Login policy and IdP behavior for platform accounts is easy to conflate with customer org settings when admins share one organization. - There is no documented procedure to inventory instance administrators or detect unexpected grants over time. # How the Problems Are Solved - Add Hardening instance administrators covering: - Dedicated platform organization layout and why login, password, and lockout policies matter for operator accounts. - Login client (`IAM_LOGIN_CLIENT`) and break-glass (`IAM_OWNER`) hygiene. - Periodic review with Internal Permission Service v2 `ListAdministrators` (instance and platform org filters). - Actions v2 webhooks for `instance.member.*` and `org.member.*` events (with payload interpretation and alert hints). - Event API polling alternative for SIEM/batch pipelines (`instance.member.*` and platform-org `org.member.*`). - Register the page in the docs sidebar under Roles & Permissions. - Cross-link from administrators, production checklists, login-client setup, and service account authentication guides. # Additional Changes - Clarify instance vs org IdP availability depends on effective login policy and linked providers (not automatic for all orgs). - Note deprecated Admin API `POST /admin/v1/members/_search` vs v2 `ListAdministrators`. # Additional Context - Operational hardening guide only (no product authz changes). --------- Co-authored-by: Cursor <[email protected]> |
||
|
|
3be21ccbf0 |
docs(perf): v4.17.1 results and harness fixes (#12651)
Publishes the v4.17.1 benchmark sweep and the harness fixes the sweep uncovered. ## Results All 11 published targets ran for 30 minutes each against a fixed 7-container deployment (vCPU 6 / 6 Gi, psql v17.4, US1 client and server), at 600 VUs except where noted. Latencies are `http_req_duration` over the test phase. | target | VUs | iter/s | p50 | p95 | p99 | failed requests | |:---|---:|---:|---:|---:|---:|:---| | machine_pat_login | 600 | 3452 | 24.53ms | 872ms | 1.00s | 29 / 6,335,633 (0.00%) | | user_info | 600 | 3217 | 29.32ms | 943ms | 1.13s | 66 / 5,800,788 (0.00%) | | introspect | 600 | 2297 | 46.49ms | 1.16s | 1.36s | 1,400 / 4,159,863 (0.03%) | | machine_jwt_profile_grant | 600 | 1076 | 85.34ms | 2.57s | 2.93s | 97 / 1,986,156 (0.00%) | | machine_client_credentials_login | 600 | 834 | 56.78ms | 1.74s | 2.42s | 1,789 / 3,073,497 (0.05%) | | add_session | 600 | 767 | 95.25ms | 3.53s | 4.15s | 1 / 1,416,546 (0.00%) | | oidc_session | 600 | 326 | 77.47ms | 3.41s | 4.43s | 3 / 1,767,162 (0.00%) | | otp_session | 600 | 84 | 47.80ms | 13.83s | 20.22s | 182 / 466,756 (0.03%) | | password_session | 600 | 68 | 408.63ms | 20.00s | 24.05s | 1 / 253,280 (0.00%) | | human_password_login | 600 | 49 | 146.67ms | 4.08s | 1m0s | 9,227 / 683,256 (1.35%) | | manipulate_user | **200** | 19 | 304.58ms | 10.23s | 38.43s | 225 / 171,987 (0.13%) | Against the goals: - **#8352 is met.** `machine_jwt_profile_grant` reached **1076/s** against the 1000/s goal, with p50 at 85ms. - **#4424 is not met.** `human_password_login` runs at **49/s** against a 1200/s goal. The sweep ran with `improvedPerformance` enabled (`PROJECT_GRANT`, `PROJECT`, `USER_GRANT`, `ORG_DOMAIN_VERIFIED`), captured from the instance before the first target rather than assumed; the full flag set is on every page. ## What the observability rows show The containers were never the constraint. ZITADEL CPU sat at 9–50% (p50), memory at 6–7% (p50, peaking at 16%) of 6 GiB, and instance count was flat at 7 with no container startups in any window. Database CPU was at **86–98% median on nine of eleven targets**. Any "ZITADEL achieves N/s" figure that omits which resource ran out is true and misleading, so the pages say. The query breakdown then shows what the CPU numbers cannot: **advisory lock wait is the single largest category of database time across the sweep (91,857 s accumulated)**, ahead of event reads (64,805 s), event push (26,411 s) and projection reads (14,625 s). The three locks are the projection handler lock (`handler/v2/handler.go`), the eventstore read barrier (`repository/sql/query.go`) and the shared lock every push takes (`eventstore/v3/push.go`). That is what sets the `flowchart outcome` on each page, and it is not uniform: - **Scale** on the seven targets where a resource genuinely ran out — database CPU at 92–98% with 0% lock share. - **Potential issues** on `human_password_login`, `password_session`, `otp_session` and `manipulate_user`, where 45–74% of the top query time is lock wait. `password_session` runs at 51% database CPU and 50% container CPU and still only manages 68 iter/s; adding vCPU there buys nothing. Autovacuum is cleared as a contributor: 93.9% median DB CPU in buckets containing a vacuum against 93.1% without, within noise. The new v4.17 eventstore autovacuum is barely triggered — `events2` is not in the top twenty vacuumed tables. ## Harness fixes Defects found during the sweep, all of which corrupted published numbers or aborted runs: 1. **Colliding check names.** `updateHuman`, `lockUser` and `deleteUser` all registered checks as `update user is status ok`, two of them asserting `status === 201` against endpoints returning 200. k6 aggregates checks by name, so this produced a permanent, fictional 28% failure rate. Each check now has its own name and accepts 2xx. 2. **User-creation read-after-write race**, in three variants, each found after the previous fix. The read-back resolved `undefined` when the projection was behind, surfacing much later as `Cannot read property '0' of undefined`; then an unguarded `res.json()` turned every request timeout into a killed iteration; then a *partial* read — a valid user object with `loginNames` not yet projected — passed an `if (user)` guard and failed identically. `readUserAfterCreate()` now guards status and body before parsing, requires the field rather than the object, and retries with backoff, rejecting with the user id and username. 3. **Metric cardinality.** Id-bearing URLs produced 800k+ time series against k6's suggested 100k, driving k6 to 6.8 GB and an OOM mid-run. All 16 such call sites now set a grouped `name` tag. 4. **`password_session` wrote its CSV to `otp_session`'s filename** in the Makefile, silently overwriting the previous target's output. The read-back race is not only a harness defect: at 600 VUs it measures real load-induced projection lag, exceeding 25 s for login-name projection and 100 s for the user projection, against instant reads on an unloaded instance. ## Stable v2 user API Four calls in `benchmark/src/user.ts` still went to `/v2beta/users` — update, lock, delete and the machine read-back. They date from the original load-test bootstrap in April 2024 (#7635), when the user service was still v2beta, and were never moved when it was promoted; they surfaced in this diff only because the read-after-write fix rewrote the surrounding functions. All four are now on stable v2. `updateHuman` is the one that changed shape: `PUT /v2beta/users/{id}` is `UpdateHumanUser`, whose stable-v2 counterpart `PUT /v2/users/human/{id}` is deprecated in the proto, so it now calls `PATCH /v2/users/{id}` (`UpdateUser`) and `manipulate_user` nests the profile under `human`. Both RPCs land on `command.ChangeUserHuman`, so the measured work is unchanged. `lockUser`, `deleteUser` and the `createMachine` read-back move over unchanged in method and payload. No published result is affected: per-endpoint latencies come from custom Trends, and `run-bench.sh` drops the `name` system tag, so no artefact ever recorded a v2beta URL. The remaining `/management/v1` and `/admin/v1` calls are left alone — migrating those changes which API surface is benchmarked and would need a re-run. ## Timestamps Pages reported `Test start` as a bare `HH:MM`, so a run could not be correlated against monitoring the next day. Pages now carry the full UTC date and time and a `Test end` row, both derived from the first and last per-second row of `output.json`. `_template.mdx` is updated to match. ## Post-mortem `benchmark/postmortems/2026-08-27-503-burst.md` records two bursts of HTTP 503 `Service error -27` — an HTML page from the Google frontend, not a ZITADEL error — on `manipulate_user` and `human_password_login`, six hours apart. **The investigation is open**, and the note is explicit about what is established (no container startups; the two bursts have opposite server-side signatures), what is provisionally ruled out (restart, panic), and what is not yet known. It is included because both affected pages report the 503s as a separate population from the ZITADEL failures beside them, and that split needs its evidence somewhere. It also records two defects found by accident in the collected logs, unrelated to the 503s: `sql: transaction has already been committed or rolled back` (232 occurrences under load) and a missing i18n key, `"unable to get user by id" not found in language "en"`. ## Notes for review - **`manipulate_user` ran at 200 VUs, not 600, and the page says so.** Throughput saturates near 100 VUs; at 600 the same target produced 4,283 failed requests for identical throughput against 225 at 200 VUs. Its failure mode is degradation over sustained single-org churn — per-operation p50 climbs 1.7x–6x between the first and last five minutes of a run — not a concurrency threshold. - **Two `Observed errors` rows are hand-corrected** and deliberately differ from the generated text, on `manipulate_user` and `human_password_login`. Both cases are one generated number covering two unrelated failure populations; each page says it was corrected and why. - Org deletion failing with a 500 (`V3-C8l3V`) after heavy user churn is already tracked as #8510 and was hit repeatedly during cleanup. - **The run/publish tooling is no longer part of this PR.** It moved to a private repository, along with the cloud collection scripts, because deployment identity does not belong here. What remains is the published pages, the post-mortem findings and the harness fixes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <[email protected]> |
||
|
|
8de625dc48 |
docs(oidc): remove stale back-channel logout unimplemented notice (#12660)
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> # Which Problems Are Solved - The OIDC logout guide still claimed Back-Channel Logout was not implemented in ZITADEL, even though the feature is supported and documented elsewhere. # How the Problems Are Solved - Removed the outdated "This is not yet implemented in ZITADEL" callout from the Back-Channel Logout section of the logout guide. - Added a short note linking to the dedicated [OIDC Back-Channel Logout](/guides/integrate/back-channel-logout) guide. - Left the Front-Channel Logout "not yet implemented" callout unchanged, since that mechanism is still unsupported. # Additional Changes - None. # Additional Context - Highlighted in https://github.com/zitadel/zitadel/issues/8287#issuecomment-5425724614 - Related epic: #8287 <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-3be6fda6-ce62-409c-96f0-f7ae9c58914d?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-3be6fda6-ce62-409c-96f0-f7ae9c58914d&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Silvan <[email protected]> |
||
|
|
8e442ed4bc |
fix(oidc): allow OrgRoleIDScope downscoping in token exchange (#12563)
# Which Problems Are Solved - Token exchange rejects `urn:zitadel:iam:org:roles🆔{orgID}` when that scope is not already present on the subject or actor token (`invalid_scope`). - A documented least-privilege pattern — exchanging a broad access token for one whose roles claims are limited to a single organization — has been broken since the v4.15.3 token-exchange hardening and was not restored by #12312. - A naive allow-any-`roles:id` exception would let a previously narrowed token widen again (switch org filter, or drop the filter and get all role orgs), including via an actor token's filter on actor-path exchanges. - A client-requested bare `urn:zitadel:iam:org:roles🆔` (empty org id) is not a meaningful filter and should be rejected on exchange. # How the Problems Are Solved - Treats `OrgRoleIDScope` as a restriction filter with downscope-only semantics via `applyOrgRoleIDScopeDownscoping(requested, ceilingScopes)`: - union / standard exchange passes the **subject** token scopes as the ceiling (actor filters ignored) - impersonation (scopeless `user_id` / `id_token` subject) passes the **actor** token scopes as the ceiling - ceiling unfiltered → any requested `roles:id` is allowed (broad → narrow) - ceiling filtered → requested filters must be ⊆ that set - ceiling filtered but request omits them → inherit the ceiling (omit must not re-widen) - Rejects a **client-requested** bare `urn:zitadel:iam:org:roles🆔` in `validateTokenExchangeScopes` after normalize (before empty-request fallback). Input tokens may still carry the bare prefix as a degenerate ceiling; empty-request / omit inherit does not re-trigger the rejection. - Keeps non-restriction scopes on the existing subject ∪ actor presence checks. - Adds unit coverage for downscope, further downscope, widen rejection, omit-inherit, mixed subject/actor filters, empty-scope access/JWT subjects, and bare-prefix request/ceiling cases. - Adds an integration test for broad → `roles:id` filtered roles, plus a second exchange that rejects widening to another org. - Documents downscope-only and omit-inherit behavior in the reserved scopes table and the token exchange guide. # Additional Changes - Updates the token-exchange guide Scope section to reflect that authorization-related reserved scopes must already be present on input tokens (aligned with the v4.15.3 hardening), rather than suggesting scope can be freely extended. # Additional Context - Closes #12413 - Follow-up for PR #12312 - Related discussion in #11869 (recommended `roles:id` workaround; this restores that path for token exchange without allowing filter widening) - Behavior note for release notes: during token exchange, an existing `urn:zitadel:iam:org:roles🆔*` filter on the subject is inherited when omitted from the request, so role claims cannot re-widen. --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Livio Spring <[email protected]> |
||
|
|
27ce10276b |
docs: create knowledge gap ID 65 (#12646)
Automatically generated PR resolving Knowledge Gap ID 65. **Thread ID:** manual-1787771291264 **Action:** CREATE **New File:** `apps/docs/content/guides/integrate/login-ui/fork-and-deploy-login-app.mdx` ### AI Summary > My recommendation is to publish a a new Deploy & Operate guide for the Login App, then link to it from the existing Login App page. The existing pages address architecture/development, login-client authorization, and Login V2 adoption separately, but none currently explains long-term fork ownership and upgrades. (Contribution guide, login-client setup, adoption guide) The article should cover: Full-repository fork as the simplest current recommendation. The customer’s history-preserving slim-fork approach as an advanced alternative. Why copying only apps/login is unsafe while the current npm packages remain unavailable, linked to #12250. Release-tag pinning and versioning such as v4.17.0-custom.1. Development, generation, linting, tests, and Docker builds. An EKS/Kubernetes Deployment and Service example. PAT handling through ZITADEL_SERVICE_USER_TOKEN_FILE. Same-origin versus separate-domain proxy requirements. Gradual rollout, break-glass access, rollback, and troubleshooting. A repeatable upstream synchronization and release process. ### Human Reviewer Instructions > Sample content: > Maintain and deploy a customized ZITADEL Login UI > > The ZITADEL Login UI is an MIT-licensed Next.js application maintained in the ZITADEL monorepo. You can customize and deploy it independently, but your fork becomes an application that your team must build, test, secure, monitor, and update. > > This guide describes the current workflow for maintaining a customized Login UI. It is intended for teams that need source-code changes beyond the branding and text customization available through ZITADEL settings. > > If you only need to change colors, logos, fonts, or translated login text, first use ZITADEL's supported branding and customization settings. A source fork creates an ongoing maintenance responsibility. > > Understand the source dependencies > > The Login UI is not currently an isolated project in the repository. Its relevant dependency chain is: > > apps/login > └── packages/zitadel-client > └── packages/zitadel-proto > └── proto > > The root pnpm workspace and Nx configuration also participate in installing, generating, testing, and building these projects. Copying only apps/login can therefore fail or silently separate the UI from the API definitions against which it was developed. > > For the current monorepo workflow, keep the Login UI and these workspace dependencies on the same upstream ZITADEL revision. > > Choose a fork strategy > > Full repository fork — recommended > > Fork the complete zitadel/zitadel repository and limit your own changes to apps/login wherever possible. This is the simplest model to understand and the least likely to miss a new build-time dependency when upstream changes. > > Use two remotes: > > git clone [email protected]:YOUR_ORGANIZATION/zitadel.git > cd zitadel > git remote add upstream https://github.com/zitadel/zitadel.git > git fetch upstream --tags > > Start customization from a released tag rather than an arbitrary commit on main: > > git checkout -b custom-login/vX.Y.Z vX.Y.Z > > Replace vX.Y.Z with the ZITADEL release you have selected. A release tag gives you a reproducible compatibility baseline and a corresponding changelog. > > History-preserving slim fork — advanced > > If repository size or internal policy makes a full fork impractical, you can maintain a filtered fork containing only: > > apps/login > > packages/zitadel-client > > packages/zitadel-proto > > proto > > the root pnpm, lockfile, Node, and Nx configuration required by those projects > > Use git filter-repo identically both when bootstrapping the fork and during every later synchronization. The list of retained paths and all filter arguments must remain stable so that freshly filtered upstream history shares commit ancestry with your fork. > > Important constraints for this model: > > Bootstrap and update with one shared filtering implementation; do not duplicate the path list in separate scripts. > > Merge the initial import and subsequent update branches with a merge commit or fast-forward. Do not squash them, because squashing removes the filtered upstream ancestry needed for future three-way merges. > > Treat any change to the retained path list as a new bootstrap. Adding or removing paths rewrites the filtered commit IDs. > > Re-run pnpm install after filtering or merging so the lockfile is reconciled with the retained workspace. > > Review the retained path list whenever upstream changes the Login build graph. > > This is a valid Git maintenance technique, but it adds machinery that ZITADEL's normal build does not require. Own and test the filtering automation as part of your fork. > > Why a standalone apps/login copy is not recommended today > > At the time of writing, current @zitadel/client and @zitadel/proto releases are not available as version-matched npm dependencies for the Login UI. The application consumes their workspace sources from the monorepo. Until version-matched packages are published, retain those sources rather than replacing the workspace dependencies with old npm versions. Progress is tracked in zitadel/zitadel#12250. > > Configure a development environment > > The current contribution guide requires Node.js 22.x, Corepack/pnpm, and Nx. From the repository root: > > corepack enable > pnpm install > pnpm nx run-many --target generate > > To develop the Login UI against an existing ZITADEL instance, create apps/login/.env.dev.local: > > ZITADEL_API_URL=https://your-zitadel-instance.example.com > ZITADEL_SERVICE_USER_TOKEN=your-login-client-pat > > Do not commit this file or its token. ZITADEL_API_URL must not have a trailing slash. > > Start the development server: > > pnpm nx run @zitadel/login:dev > > The token must belong to a machine user with the Instance Login Client role (IAM_LOGIN_CLIENT). See Connect your self-hosted Login UI to ZITADEL for the supported setup. > > Run the required quality checks > > Run the checks for all three affected projects, not only the Next.js application: > > pnpm nx run-many \ > --projects @zitadel/login @zitadel/client @zitadel/proto \ > --targets lint build test > > If your update includes .proto changes, regenerate the TypeScript clients before testing: > > pnpm nx run @zitadel/proto:generate > > For meaningful Login UI changes, also perform end-to-end tests against a staging ZITADEL instance. At minimum, cover the authentication methods and policies you enable in production: > > Authorization Code Flow with PKCE and callback to the application > > password, passkey, external identity provider, and applicable MFA/OTP flows > > registration, account selection, password reset, verification, and logout > > organization-specific policies, branding, and translated text > > expected negative cases such as invalid or expired codes > > desktop and mobile browsers supported by your application > > Build an immutable image > > The repository's documented Docker build flow is: > > pnpm nx run @zitadel/login:build > docker build -t registry.example.com/identity/zitadel-login:vX.Y.Z-custom.1 apps/login > docker push registry.example.com/identity/zitadel-login:vX.Y.Z-custom.1 > > Use an immutable tag that records both the upstream ZITADEL baseline and your customization revision. For example: > > v4.17.0-custom.1 > > Also retain the source commit and image digest in your release record. Do not deploy a mutable latest tag to production. > > Deploy outside Vercel > > Vercel is not required. The Login UI can run as the Node.js application produced by the Next.js build or as a container. In Kubernetes or EKS, run it as a separate Deployment and Service behind your ingress or load balancer. > > The following fragment shows the important application settings. Adapt registry authentication, resources, topology, and ingress to your platform: > > apiVersion: apps/v1 > kind: Deployment > metadata: > name: zitadel-login > spec: > replicas: 2 > selector: > matchLabels: > app: zitadel-login > template: > metadata: > labels: > app: zitadel-login > spec: > containers: > - name: login > image: registry.example.com/identity/zitadel-login:vX.Y.Z-custom.1 > ports: > - name: http > containerPort: 3000 > env: > - name: ZITADEL_API_URL > value: https://zitadel.example.com > - name: NEXT_PUBLIC_BASE_PATH > value: /ui/v2/login > - name: ZITADEL_SERVICE_USER_TOKEN_FILE > value: /var/run/secrets/zitadel/login-client.pat > volumeMounts: > - name: login-client > mountPath: /var/run/secrets/zitadel > readOnly: true > readinessProbe: > tcpSocket: > port: http > livenessProbe: > tcpSocket: > port: http > volumes: > - name: login-client > secret: > secretName: zitadel-login-client > --- > apiVersion: v1 > kind: Service > metadata: > name: zitadel-login > spec: > selector: > app: zitadel-login > ports: > - name: http > port: 3000 > targetPort: http > > Store the PAT in a Kubernetes Secret or external secrets manager and expose it as a read-only file. Restrict access to the Secret and plan token rotation. Do not bake the PAT into the image. > > Add CPU and memory requests and limits based on load tests from your own authentication traffic. Use multiple replicas, disruption controls, and multi-zone scheduling according to your availability requirements. > > Configure routing and domains > > The Login UI contains a server-side component and Next.js middleware that proxies OIDC-related requests to ZITADEL. Correct proxy routing is part of the authentication flow, not only static frontend delivery. > > Same public origin > > When the Login UI and ZITADEL share a public origin, route: > > /ui/v2/login to the Login UI service > > all other ZITADEL paths to the ZITADEL API service > > Set NEXT_PUBLIC_BASE_PATH=/ui/v2/login at build and runtime as required by your packaging. Keep the value consistent with the Login V2 base URI configured in ZITADEL. > > Separate Login domain > > If the Login UI uses a separate public domain such as login.example.com: > > Serve it over HTTPS. > > Add the Login domain to the instance's Trusted Domains. > > Configure the proxy in front of the Login UI as documented by ZITADEL: > > x-zitadel-public-host: the public Login UI host, for example login.example.com > > x-zitadel-instance-host: the ZITADEL instance host, for example auth.example.com > > Test OIDC and SAML callbacks and every configured external identity provider through the public route. > > Do not assume that setting only ZITADEL_API_URL replaces the required host-routing behavior for every topology. The Login App architectural and proxy guidance is the source of truth for these headers. > > Enable Login V2 safely > > For an existing self-hosted ZITADEL installation, follow Adopt Login V2 on an existing installation. > > Prefer a gradual rollout: > > Deploy the custom Login UI without changing the instance-wide setting. > > Enable the new Login UI for a non-production test application and set its custom base URL. > > Run the complete authentication test matrix. > > Expand to selected applications. > > Enable it instance-wide only after validation, if that is your intended topology. > > Keep a break-glass PAT for a machine user with the Instance Owner role (IAM_OWNER) so that you can revert the Login V2 setting if interactive login becomes unavailable. Store and audit this credential according to your incident-access policy. > > Keep the fork synchronized > > Treat upstream synchronization as a normal application dependency update, not an automatic deployment. > > For a full repository fork: > > git fetch upstream --tags > git checkout -b update-login-vX.Y.Z > git merge vX.Y.Z > > Resolve conflicts while preserving the intent of your customizations. Then install, generate, test, and build again. For a slim fork, fetch a freshly filtered copy of the same release and merge its history using the identical filter configuration used during bootstrap. > > Recommended update workflow: > > Select a released ZITADEL version and read its release notes and migration guidance. > > Merge that release into a dedicated update branch. > > Review changes to apps/login, packages/zitadel-client, packages/zitadel-proto, proto, and the root build files. > > Reconcile dependencies and generated code. > > Run lint, build, unit, and staging end-to-end tests. > > Build an immutable custom image and scan it using your normal supply-chain controls. > > Deploy to staging, then canary or gradually roll out to production. > > Retain the previous image digest and configuration for rollback. > > Do not merge upstream continuously into production without qualification. Login changes can affect cookies, redirects, external IdP callbacks, API contracts, and authentication-policy behavior. > > Recommended release policy for your fork > > ZITADEL releases are the upstream input; your custom Login UI image is your own release artifact. A useful version format is: > > <upstream-zitadel-version>-custom.<revision> > > Maintain a short release record containing: > > upstream tag and commit > > custom fork commit > > container image digest > > applicable ZITADEL backend version or tested range > > configuration changes and migrations > > test evidence and known limitations > > Subscribe to ZITADEL releases and security notices. Decide deliberately which releases to adopt; prioritize security fixes and changes that affect Login V2 or the APIs it consumes. > > Troubleshooting checklist > > If the Login UI returns an internal error or an authentication flow redirects incorrectly, verify: > > the PAT is readable and belongs to a user with IAM_LOGIN_CLIENT > > ZITADEL_API_URL is reachable from the Login pod and has no trailing slash > > the public Login domain is trusted by the ZITADEL instance > > the configured Login V2 base URI exactly matches the public route and base path > > the ingress sends Login UI traffic to the correct service > > separate-domain deployments set the public-host and instance-host headers correctly > > the selected Login source, client package, proto package, and ZITADEL API version were tested together > > external IdP callback URLs reach the expected public endpoint > > the prior known-good image and configuration can still be restored > > Related documentation > > Login App architecture and setup > > Contribute to the Login UI > > Connect your self-hosted Login UI to ZITADEL > > Adopt Login V2 on an existing installation > > ZITADEL releases ### ⚠️ Manual Action Required > **Sidebar Update Needed:** Because this PR creates a brand new document, it will not automatically appear in the site's navigation. Please review the file location and manually update `apps/docs/lib/sidebar-data.ts` to include a reference to this new page before merging. --- 🤖 **Need adjustments?** Leave a comment below and tag **@zitadel-knowledge-bot** with your requested changes, and I will automatically update the files and push a new commit! --------- Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com> Co-authored-by: Matías Racedo <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> |
||
|
|
c9d747d407 |
fix(api): prevent cross-instance caching of well-known app link files (#12644)
# Which Problems Are Solved The `/.well-known/apple-app-site-association` and `/.well-known/assetlinks.json` responses are instance specific: the body depends on the requested host. The responses did not declare this, so a shared cache whose cache key does not include the host could store one instance's file and serve it on another instance's domain. This could result in a wrong or empty file being returned intermittently, breaking native passkey verification. Dropping the `public` directive (#12634) was not sufficient: depending on its cache mode, a shared cache may store any response carrying a `max-age`. The docs page for native app links also still documented the old `Cache-Control: public` header and only the single `get_login_creds` relation (missing `handle_all_urls`, added in #12634). # How the Problems Are Solved - Send **`Vary: Host`** on both well-known responses, declaring that the body depends on the host: a spec-compliant shared cache must store per-host variants or skip caching. Clients addressing a single host (the Apple and Google verifiers, browsers) are unaffected and keep honoring the configured `max-age`. - Correct the outdated caching reasoning in the code comments. - Guard the header with unit and integration tests. - Update the native app links docs page: both Digital Asset Links relations and the current response headers. # Additional Changes None. `WellKnown.AppLinksCacheControlMaxAge` behavior is unchanged (`0` still results in `no-store`). # Additional Context - Follow-up to #12634; relates to #12497. - Verified with interleaved requests against two instances: responses are no longer reused across domains, and each domain consistently serves its own configuration. - To be cherry-picked to `v4.x` from `main` once merged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <[email protected]> |
||
|
|
6fdb74593c |
docs: remove outdated Actions v2 feature flag references (#12641)
# Which Problems Are Solved The docs ([Actions v2 concepts page](https://zitadel.com/docs/concepts/features/actions_v2) and the Actions testing guides) state that Actions v2 must be enabled via the "Actions" feature flag. That flag no longer exists — it is `reserved` in `proto/zitadel/feature/v2/instance.proto` and `system.proto` — and Actions v2 is available by default. The callout even linked to a non-existent API field. # How the Problems Are Solved - Removed the stale feature-flag callout (and the roadmap teaser callout) from `concepts/features/actions_v2.mdx`. - Removed the "Your ZITADEL instance needs to have the actions feature enabled." prerequisite bullet from the seven `guides/integrate/actions/testing-*.mdx` guides. Pure deletions, 17 lines across 8 files. # Additional Context The `SetInstanceFeatures` mentions in `hosted-login.mdx` and `webkeys.mdx` refer to flags that still exist and are untouched. |
||
|
|
7e8a3fb21d |
docs: add ZITADEL Cloud refund policy (#12577)
Adds a new public-facing Refund Policy to the documentation for ZITADEL Cloud subscriptions. This document clarifies billing expectations for customers and standardizes support operations by outlining: - **Scope:** Eligible situations for refunds (e.g., accidental overages, billing errors, accidental auto-renewals). - **Out of Scope:** Non-refundable items (e.g., Startup plan, standard usage-based fees, partial periods). - **Process:** Clear instructions on how customers can submit a refund request via the customer portal. --------- Co-authored-by: Maximilian <[email protected]> |
||
|
|
a1a4572303 |
chore(deps): update npm dependencies and clean up pnpm overrides (#12592)
# Which Problems Are Solved Several dependencies across the workspace had fallen behind their latest patch releases, and the override list in pnpm-workspace.yaml had drifted: many of the 28 entries were no longer needed because the dependency tree had already moved past them, while a few enforced outdated version floors. # How the Problems Are Solved Direct dependencies were updated where newer patch releases were available: `@angular/*` → ^21.2.19, `next` → 16.2.11 (docs and login, incl. `@next/eslint-plugin-next`), `nx` → 22.7.7, and `js-yaml` → ^4.3.1 / `postcss` → ^8.5.23 in docs. The lockfile was regenerated from scratch so all transitive dependencies resolve to the newest versions within their allowed ranges. With that in place, the override list was rebuilt down to the 8 entries that are still required because a parent package pins an older version (axios and brace-expansion via nx, postcss and sharp via next, esbuild via tsx, uuid via testcontainers, and the two OpenTelemetry packages); the other 20 overrides were removed. A verification pass confirmed the resolved versions stay at or above the floors the removed overrides used to enforce, and `pnpm audit` comes back clean. --------- Co-authored-by: Livio Spring <[email protected]> |
||
|
|
632a519680 |
docs: fix redirect to WIP page (#12594)
Fixes the path for the temporary redirect for all /reference/api/group/* routes to a new "Under Development" page while the API is being built. |
||
|
|
a0e53933a2 |
docs: redirect /api/group/* to WIP page (#12593)
Adds a temporary redirect for all /api/group/* routes to a new "Under Development" page while the API is being built. --------- Co-authored-by: Copilot Autofix powered by AI <[email protected]> |
||
|
|
5f3a0de33a |
docs: update knowledge gap ID 43 (#12590)
Automatically generated PR resolving Knowledge Gap ID 43.
**Thread ID:** mixpanel-cmvvwsc2uwvlkujw
**Action:** UPDATE
**Target Files:** 1
### AI Summary
> *Negative Docs Feedback*
*Path:* `/sdk-examples/react`
*Comment:* "ai generator went crazy it seems, with so many repeated &
unfinished sentences"
---
🤖 **Need adjustments?**
Leave a comment below and tag **@zitadel-knowledge-bot** with your
requested changes, and I will automatically update the files and push a
new commit!
Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>
|
||
|
|
6127b567e7 |
feat(oidc): provide actor information in userinfo actions (#12566)
# Which Problems Are Solved Expose impersonating actor to `preAccessToken` and `preUserinfo` actions. # How the Problems Are Solved Provide the `actor` information already present in the OIDC session model of JWT token to the relevant actions. Both goja-based actions "v1" and webhook based execution targets carry the actor information now. Actor remains null in case of non-impersonated tokens. Actor may contain nested actors to display a delegation chain of impersonators. # Additional Changes - dba6261c8ed0dfd00ffde6193910b1405b17432f: refactor `userinfoFlows` to reduce complexity, add test coverage and solve a couple of potential bugs. - `getClientId` in actions was previously undocumented. Added to documentation. - A skill that reproduces manual testing using webhook.site or a local sink. # Additional Context - Closes https://github.com/zitadel/zitadel/issues/12097 --------- Co-authored-by: Claude Sonnet 5 <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> Co-authored-by: Wim Van Laer <[email protected]> |
||
|
|
318bfc36d7 |
feat: native app links for passkeys (#12580)
# Which Problems Are Solved - Native iOS/Android passkeys need OS trust files on the ZITADEL domain; without them, Associated Domains / App Links verification fails. - Operators could not configure iOS Team ID + Bundle ID or Android package name + SHA-256 fingerprints on OIDC apps. - `/.well-known/apple-app-site-association` and `/.well-known/assetlinks.json` were not served from application config. # How the Problems Are Solved - Add iOS/Android app-link fields on OIDC app create/update (Application API v2 + Management), with validation. - Persist and project those fields; query active app-link configs instance-wide. - Serve AASA (`webcredentials`) and Digital Asset Links (`get_login_creds`) from well-known paths, with configurable `Cache-Control` and fingerprint normalization at serve time. - Console UI to edit the fields, with links to the well-known endpoints. - Operator docs for configuration, endpoints, caching, and verification. # Additional Changes - Document on API fields that well-known responses may be HTTP-cached and platform verifiers may delay propagation. - Runtime config: `WellKnown.AppLinksCacheControlMaxAge` (default `5m`; `0` → `no-store`). # Additional Context - Closes #12497 - Implemented and reviewed as stack: - #12531 API contract - #12532 storage wiring - #12536 well-known endpoints - #12537 console - #12547 docs --------- Co-authored-by: Cursor <[email protected]> |
||
|
|
2bd42e8fc4 |
fix(actions): allow adding raw metadata values via appendMetadataRaw (#12567)
# Which Problems Are Solved Since #10666 (v4.1+, backported to v4.x), metadata values set through actions v1 (`api.metadata.push` and `api.v1.user.appendMetadata`) are always JSON-encoded via `json.Marshal`. This made the write path consistent with the JSON-based read path, but removed the ability to store raw (unencoded) metadata values: - A scalar string is now always stored quoted (`"de"` instead of `de`). - The previous byte-array convention (mapping a string to an integer array in the script, handled by `mapBytesToByteArray` introduced in #5526) now stores the literal integer-array text (e.g. `[100,101]`) instead of the raw bytes. Customers migrating from v3.x whose downstream systems base64-decode metadata values from tokens and expect raw bytes have no way to produce them anymore — changing the consuming system is not always possible. Reverting the default is not an option either, as clients that adopted actions v1 on v4.x now rely on the JSON encoding. # How the Problems Are Solved - Adds a new, opt-in function `api.v1.user.appendMetadataRaw(key, value)` to the actions v1 login flows (external / internal authentication post authentication and pre creation), next to the existing `appendMetadata`. - The value is stored as raw bytes without JSON encoding: - a string is stored as its plain UTF-8 bytes (`de`, not `"de"`) - byte arrays (`Uint8Array` or a plain array of integers 0-255, the old convention) are stored as-is, so existing v3 scripts using a string-to-byte-array helper only need to switch the function name - other types (and empty values) throw an error - The existing `appendMetadata` and `api.metadata.push` behavior remains byte-for-byte unchanged. # Additional Changes - Documented `appendMetadataRaw` (and the JSON encoding behavior of `appendMetadata`) in the external and internal authentication actions docs. - Added unit tests for the new function (through a real goja runtime) and a test locking in the existing `appendMetadata` JSON-encoding behavior. # Additional Context - Regression introduced as a side effect of #10666 (which fixed #10470); the raw byte handling was originally introduced in #5526. - Reported by a customer upgrading from v3.4.x to v4.16.x, whose PostAuthentication action maps token payload claims into user metadata. - Requires backport to v4.x. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <[email protected]> |
||
|
|
9fa0cad9d9 | docs: Update production guidelines (#12552) | ||
|
|
9070b537d2 |
feat(oidc): add RFC 7592 dynamic client management (#12315)
# Which Problems Are Solved #12313 lets clients register themselves as OIDC applications (RFC 7591), but a dynamically registered client has no way to manage itself afterwards: - The registration response returns no `registration_access_token` and no `registration_client_uri`, which RFC 7591 and 7592 clients (including the MCP SDKs) expect in order to manage their own registration. - There is no way to read back, update or delete a registration without an operator acting on the client's behalf through the Console or the Management API. A client that needs to rotate a redirect URI has to ask a human. # How the Problems Are Solved Implement the [OAuth 2.0 Dynamic Client Registration Management Protocol (RFC 7592)](https://datatracker.ietf.org/doc/html/rfc7592): `GET`, `PUT` and `DELETE` on `/oauth/v2/register/{client_id}`, wired through the same `op.WithSetRouter` hook as the registration endpoint. ## Control plane: the same security settings, no new switch The management endpoints are gated on the `DynamicClientRegistrationSettings.enabled` introduced in #12313. No new setting, no feature flag: a client that may register may manage what it registered. When registration is disabled the endpoints answer `404`, exactly as `POST /oauth/v2/register` does. `allow_unauthenticated` selects the registration mode and does not apply here, because the registration access token authorizes these endpoints in both modes. ## Authorization: the registration access token, and nothing else RFC 7592 §3 defines the credential as the registration access token issued at registration and bound to a single client, not a user credential. So a client manages only itself, proving it with the token it was handed: | | Registration (`POST`) | Management (`GET` / `PUT` / `DELETE`) | | --- | --- | --- | | Credential | Access token, or none in open mode | Registration access token | | Permission | `project.app.register_dynamic` in token mode | None | Neither `project.app.register_dynamic`, which governs who may *create* clients, nor `project.app.write` applies to the management endpoints. One authorization path per endpoint is easier to reason about than two, and there is no user identity to check against in the first place. Operators keep managing dynamically registered clients through the Management API and the Console, like any other application; they cannot obtain a client's registration access token, by design. Status codes: a missing, malformed or non matching token is `401`; a client that no longer exists is `404`. The token's `client_id` is checked against the path before any lookup, so a token bound to another client is rejected without touching the database. ## The token itself - **Stored like a client secret.** Only its passwap hash is persisted, through a new `project.application.config.oidc.registration_token.changed` event; the plain token leaves the server exactly once, in the response that issues it. - **Bound to its client and organization** through authenticated encryption with the instance key, the same mechanism as refresh tokens. The opaque token handed to the client encrypts `client_id:org_id:secret`, so the endpoint recovers the binding before checking the secret against the stored hash. - **Verified in O(1)** against the hash on the projection, folded into the client lookup the handler already does, with a strongly consistent fallback to the eventstore so a token that was just rotated is accepted before it is projected. - **Rotated on update** (RFC 7592 §4.4) and invalidated implicitly when the client is deleted. A read does not rotate it. Every update rotates, including one that changes no metadata, so a client always gets a usable token back. ## Reusing the existing persistence A small refactor extracts the shared OIDC application creation and update logic (`pushOIDCApplication` gains a variadic `extraEvents`, and `oidcApplicationChangeEvent` is lifted out of `UpdateOIDCApplication`), so the dynamic commands reuse it without the permission check. The registration token event is pushed atomically with the application, so a registered client is never left unmanageable. # Additional Changes - Setup step **74** adds the `registration_token` column to the `apps7_oidc_configs` projection, with a reducer for the new event modelled on the client secret. Applications are not part of the relational storage projection, so there is no second write path to keep in sync. - The registration response (`POST`) now also carries `registration_access_token` and `registration_client_uri`. - Docs: a "Manage a registration" section in the dynamic client registration guide covering the three operations, the authorization model, token rotation and the new limitations, plus the management endpoints on the OIDC endpoints page. - Unit tests for the command layer (issue, verify, rotate, update, delete), the projection reducer, and the token binding and response mapping. An integration test covers the full lifecycle (register, read, update with rotation, delete, read again with `404`), a token bound to another client, and the endpoints not being served when registration is disabled. None of the integration calls carries a user access token, which pins down that the management endpoints require no permission of their own. # Additional Context - Part of the MCP authorization effort in #9810. Follow-up to #12313 (RFC 7591), which is now merged; this branch has been rebased on `main` and carries the settings and authorization model agreed there. - **Open question for the review**: the authorization model above (registration access token only, no operator path through these endpoints) is the one I proposed when #12313 was reviewed, but it was never explicitly confirmed. If you would rather have `project.app.write` also grant access to a client in the operator's own organization, that is a contained change in `authorizeClientManagement`. - Rotation is eventually consistent: right after an update the previous token may keep working for a brief moment until the rotation is projected, while the new token works immediately through the eventstore fallback. This matches how ZITADEL validates access tokens, where revocation is likewise eventually consistent. It is documented in the guide and asserted as such in the integration test. - A database migration is introduced for the new projection column. No proto change and no new feature flag. - Verified locally: full build, `go vet` including `-tags integration`, `golangci-lint` with 0 issues, and the unit suites for every touched package. As before, I cannot run the integration suite here, so CI remains the check on it. |
||
|
|
f34cc45eac |
feat(oidc): add RFC 7591 dynamic client registration (#12313)
# Which Problems Are Solved ZITADEL cannot currently be used as an OAuth 2.0 Authorization Server by clients that need to register themselves at runtime: - There is no [OAuth 2.0 Dynamic Client Registration (RFC 7591)](https://datatracker.ietf.org/doc/html/rfc7591) endpoint, and no `registration_endpoint` is advertised in the discovery document. - This blocks [Model Context Protocol (MCP)](https://modelcontextprotocol.io) clients (Claude Desktop, claude.ai, Cursor, the MCP SDKs), whose OAuth 2.1 authorization profile expects an Authorization Server that supports dynamic client registration. They self-register before any user context exists and only then start the authorization code + PKCE flow. # How the Problems Are Solved ## Control plane: instance security settings No feature flag and no runtime config. A `DynamicClientRegistrationSettings` message is nested under the instance `SecuritySettings` in `settings/v2`, next to `embedded_iframe` and `enable_impersonation`: | `enabled` | `allow_unauthenticated` | Behaviour | | --- | --- | --- | | `false` (default) | — | `POST /oauth/v2/register` returns `404`, `registration_endpoint` absent from discovery | | `true` | `false` (default) | Token-gated registration, client homed in the token's organization | | `true` | `true` | Open registration for MCP hosts, client homed in the instance default organization | Both values are carried on `authz.Instance` and loaded with the instance, exactly like `EnableImpersonation`, so the discovery document and the endpoint read them per request without an extra query. `allow_unauthenticated` implies `enabled` in both implementations, so a single setting closes the endpoint. Adding the two columns bumps the projection table to `projections.security_policies3`, following what the impersonation setting did for `security_policies2`. ## Authorization: a dedicated permission, not `project.app.write` - New org-scoped permission **`project.app.register_dynamic`**, granted by default to `ORG_OWNER`, `IAM_OWNER` and `IAM_ORG_MANAGER` — in both `InternalAuthZ` and `SystemAuthZ`, mirroring where `project.app.write` already lives. - New built-in role **`ORG_DYNAMIC_CLIENT_REGISTRAR`** carrying only that permission, so a service user can self-register clients without gaining write access to the organization's existing applications. - Open mode (`allow_unauthenticated = true`) requires no token and no permission: that is the MCP self-register path. - A valid token whose user lacks the permission is answered with **`403 insufficient_scope`** (RFC 6750 §3.1) rather than `401 invalid_token`, so a caller can tell "your token is bad" from "your token may not do this". `verifyAccessToken` also reports invalid tokens as permission denied, so the authorization failure is marked explicitly to keep the two apart. The check runs once, at the endpoint, right after the token is verified and before any state is created, so it also gates the auto-provisioning of the organization's DCR project. The permission itself lives in the command layer next to the others (`Commands.CheckPermissionRegisterDynamicClient`). As the OIDC endpoints are not behind the authorization interceptor, the caller identity is derived from the verified token, the way token exchange does for the impersonating actor. ## Registration itself - **Registered clients are ordinary OIDC applications.** The endpoint reuses the existing application-creation events (`NewApplicationAddedEvent` and `NewOIDCConfigAddedEvent`), so the whole token, authorization and introspection flow keeps working unchanged. They are stored in a dedicated, auto-provisioned project named `ZITADEL DCR` per organization (name lookup), so they do not pollute the `IAMProject` or other projects. - Input is validated with the existing `GetOIDCV1Compliance` rules; `private_key_jwt` and `jwks`/`jwks_uri` are rejected with `invalid_client_metadata`. - **Endpoint wiring without forking the library.** `NewServer` calls `op.RegisterServer` instead of `op.RegisterLegacyServer`, so the new route can be added through the same `op.WithSetRouter` hook as the authorize callback. `op.RegisterLegacyServer` appends a middleware after the caller options, which chi forbids once a route has been registered; `op.NewIssuerInterceptor` reproduces the issuer middleware it would otherwise add. The `/oauth/v2` routing prefix is unchanged. # Additional Changes - **`application_type` inference for custom-scheme redirects** (last commit, isolated). `application_type` is an OpenID Connect member; RFC 7591 does not define it, so clients that only implement RFC 7591 omit it. Native MCP hosts doing so register custom-scheme redirect URIs, which ZITADEL reserves for native applications, and the OIDC default of `web` rejected them with `invalid_redirect_uri`. Inference is limited to custom schemes and to an absent `application_type`, so every request accepted today keeps its current application type and auth method — an `http` loopback redirect still yields a web application. Happy to drop this commit if you would rather keep it out of this PR. - **Docs**: a Dynamic Client Registration integration guide, a `registration_endpoint` section on the OpenID Connect endpoints page, and the new role in the administrators table (plus its description in the Console translations). # Additional Context - Part of #9810. This is **Phase 1 (DCR)**; #12315 adds RFC 7592 and #12316 adds CIMD, both stacked on this branch and rebased once this lands. - Implements the control-plane and authorization model requested in the review of this PR, itself a refinement of the direction in https://github.com/zitadel/zitadel/issues/9810#issuecomment-4891176997. - **Out of scope (deliberately)**: RFC 7592, CIMD, persisting free-form metadata (`logo_uri`, `contacts`, and similar), and `private_key_jwt` / `jwks_uri` (rejected cleanly). No new event types. - Follow-ups explicitly left out, per the review: DCR client lifecycle and richer audit, consent and custom scopes, JWT / `aud` (RFC 8707), and RFC 8414 AS metadata (currently absent from both ZITADEL and `zitadel/oidc`). |
||
|
|
87538f29e1 |
docs: prefix legacy V1 API paths in generated OpenAPI (#12491)
# Which Problems Are Solved
- Generated V1 API reference docs are missing their base path prefixes
(e.g. `/admin/v1`, `/auth/v1`, `/management/v1`, `/system/v1`).
- Endpoint pages show routes like `/healthz` instead of
`/admin/v1/healthz`.
This happens because V1 protos still define `base_path` via legacy
OpenAPI v2 swagger annotations, while docs generation uses
`protoc-gen-connect-openapi`, which ignores those annotations and emits
the bare `google.api.http` paths.
# How the Problems Are Solved
- After OpenAPI generation in
`apps/docs/scripts/generate-proto-docs.mjs`, apply a small hardcoded map
of the four legacy V1 services and prepend the correct prefix to each
path in the generated specs.
- The rewrite is idempotent (`startsWith(prefix)`), so already-prefixed
paths are left alone.
- Because the prefixes are written into the generated OpenAPI files,
Fumadocs MDX generation and runtime page rendering stay in sync.
# Additional Changes
- None.
# Additional Context
- V1 APIs are legacy and frozen; hardcoding the four known prefixes is
simpler and more reliable than dynamically scraping `.proto` files.
- Changing the protos or dual-generating with `openapiv2` would be much
more invasive for a docs-only issue.
- Verified locally for Admin, Auth, Management, and System pages (e.g.
`/admin/v1/healthz`, `/management/v1/users/{id}`).
---------
Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
|
||
|
|
bb4f546b7c |
docs: add video walkthrough link to quickstart guide (#12479)
Link the YouTube walkthrough at the end of the quickstart page for users who prefer watching over reading. Co-authored-by: Rajat Singh <[email protected]> |
||
|
|
62e56e085c | docs: fixed broken docs references (#12465) | ||
|
|
2b9b9856b4 |
docs: remove changelog link from sidebar (#12459)
The changelog page on zitadel.com was removed for being unmaintained, leaving the docs sidebar link 404ing. Co-authored-by: Rajat Singh <[email protected]> |
||
|
|
437a0802bd |
docs: document initial admin password requirements in compose guide (#12452)
# Which Problems Are Solved - The **Harden secrets** section of the Docker Compose guide lists commands for the masterkey and the two database passwords, but never mentions the **initial admin user password**. - Setting `ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD` to a value that does not meet the default password complexity policy fails the initial setup with a `_password_complexity_model` error, and the requirements were not documented anywhere near the other secrets. # How the Problems Are Solved - Adds a short note directly below the secret-generation commands documenting the initial admin password: the default (`Password1!` / `[email protected]`), how to override it via `ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD`, and the default complexity requirements (min 8 chars, upper + lower + number + symbol). - Links to the [Password Complexity](/guides/manage/console/default-settings#password-complexity) policy docs and notes that the variable only applies during initial setup. # Additional Changes None. # Additional Context - Reported by a self-hosting user (TrueNAS + Ansible) who hit repeated setup errors because the initial admin password complexity requirement was not documented alongside the masterkey/DB password commands. |
||
|
|
94ac4237a9 |
docs: add user self-deletion guide (#12458)
## Summary - Add a new guide documenting how to let users delete their own account via the User v2 API's `DeleteUser` endpoint, gated by the `user.self.delete` permission (granted via `ORG_USER_SELF_MANAGER`) - Link the new guide from the self-service concepts page and sidebar - Add the `ORG_USER_SELF_MANAGER` role to the administrators reference table ## Test plan - [x] Verified the new and updated pages render correctly in local dev (`pnpm dev`) replaces #12455 and #12457 (branch got renamed from `docs/human-pat-clarification`, which auto-closed #12457). Co-authored-by: Rajat Singh <[email protected]> |
||
|
|
75bc058bce |
feat(eventstore): autovacuum tuning for events2 table (#12449)
# Which Problems Are Solved
In Zitadel's append-only event-sourced architecture, the
`eventstore.events2` table grows indefinitely. PostgreSQL's default
autovacuum uses a percentage-based scale factor, so as the table grows,
the number of changed rows required to trigger a `VACUUM` or `ANALYZE`
drifts towards infinity. Without regular vacuums, the table's Visibility
Map becomes stale, disabling fast Index-Only Scans and forcing expensive
heap reads. Without regular analyzes, query planner statistics become
stale, leading to suboptimal execution plans.
This causes eventstore operations to progressively degrade as `events2`
grows, even without CPU, memory, or I/O saturation. A manual `VACUUM
ANALYZE` immediately restores performance, confirming the root cause.
- If `Eventstore.Autovacuum` is left at its default, `events2` keeps
using PostgreSQL's default, percentage-based autovacuum/autoanalyze
scale factors, which become impractically infrequent on large tables.
- There was previously no supported way to apply static, table-level
autovacuum tuning to `events2` through Zitadel's own configuration/setup
process.
# How the Problems Are Solved
- Added an `Eventstore.Autovacuum` runtime configuration block to
`cmd/defaults.yaml` (disabled by default):
```yaml
Eventstore:
Autovacuum:
Enabled: false # ZITADEL_EVENTSTORE_AUTOVACUUM_ENABLED
VacuumThreshold: 50000 # ZITADEL_EVENTSTORE_AUTOVACUUM_VACUUMTHRESHOLD
AnalyzeThreshold: 50000 # ZITADEL_EVENTSTORE_AUTOVACUUM_ANALYZETHRESHOLD
```
- Added a repeatable `zitadel setup` migration step
(`cmd/setup/eventstore_autovacuum.go`) that:
- When `Enabled: true`, disables the percentage-based
`autovacuum_vacuum_scale_factor`, `autovacuum_analyze_scale_factor`, and
`autovacuum_vacuum_insert_scale_factor` on `eventstore.events2`, and
applies static thresholds (`autovacuum_vacuum_insert_threshold`,
`autovacuum_vacuum_threshold`, `autovacuum_analyze_threshold`) from the
config instead.
- When `Enabled: false`, resets those storage parameters on
`eventstore.events2` back to the cluster defaults.
- Implements `Repeatable.Check()` so the step only re-runs when the
configuration actually changed since the last `zitadel setup` run.
- Added documentation in the new "Performance tuning" page.
# Additional Changes
- Move the projection documentation into performance tuning page
- Expand and update the projection documentation to the latest state in
zitadel. (contained some stale information)
# Additional Context
Several other issues describe read-performance symptoms consistent with
this same root cause (stale `events2` visibility map / planner
statistics at scale, without resource saturation). Since this PR
addresses the shared root cause:
- Closes #12448
- Closes #10754
- Closes #10260
- Closes #8585
- Closes #9239
---
_Generated by [Claude
Code](https://claude.ai/code/session_01HoKvEY7niCVgLCz7CajBwW)_
---------
Co-authored-by: Claude <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Silvan <[email protected]>
|
||
|
|
6bd46c5fb6 |
feat(login): idp userAction oneof (#12433)
Closes #11369 # Which Problems Are Solved During SSO login with an external IdP, Actions v2 lets you manipulate the `RetrieveIdentityProviderIntent` response to shape the user that ZITADEL creates or updates. There was an inconsistency between the two flows: - **First login (user does not exist):** the response carried an `addHumanUser` object (mirroring the deprecated `AddHumanUser` API), which **does** allow setting user `metadata`. - **Subsequent logins (user exists):** the response carried an `updateHumanUser` object (mirroring the deprecated `UpdateHumanUser` API), which does **not** support metadata. As a result, actions could set metadata when creating a user but not when updating one. Customers doing SSO attribute mapping had to make a separate `SetUserMetadata` call on every subsequent login — extra latency and a non-atomic update. The proto/backend already gained a non-deprecated `user_action` oneof (`create_user` → `CreateUserRequest`, `update_user` → `UpdateUserRequest`, both supporting metadata), but the login app was still reading the deprecated flat fields, so the new capability was unreachable from the frontend. # How the Problems Are Solved Migrate the login app's IDP intent handler to consume the new `user_action` oneof, with a fallback to the deprecated fields so older API responses keep working during the transition. - **`zitadel.ts`** — added `createUser` / `updateUser` client wrappers calling the non-deprecated `UserService.CreateUser` / `UserService.UpdateUser` endpoints. - **`idp-intent.ts`** — added three helpers, each preferring `user_action` and falling back to `add_human_user` / `update_human_user`: - `resolveCreateUser` — flat read view for org resolution, required-field checks, and registration-form pre-fill. - `buildCreateUserRequest` — passes the action's `CreateUserRequest` through and injects the resolved `organizationId`; maps the deprecated flat payload into the nested shape on fallback. - `buildUpdateUserRequest` — builds an `UpdateUserRequest` **including metadata** (the fix); deliberately syncs only profile/email/phone/metadata (not username) to preserve existing auto-update behavior and avoid invalidating sessions. - Rewired all handlers (`handleUserExists`, `handleAutoLinking`, `handleAutoCreation`, `handleManualCreation`, `resolveOrganizationForUser`) to use these, and switched auto-create to read `CreateUserResponse.id`. - **Tests** — updated mocks/assertions to the new request shapes and added two cases exercising the `user_action` oneof with metadata (create + update). 817/817 login unit tests pass; no new type errors. # Additional Changes Updated the Actions v2 guide `guides/integrate/actions/testing-response-manipulation.mdx` (the unreleased/`latest` docs) to reflect the new response shape: - Go handler example now manipulates `resp.GetCreateUser()` / `resp.GetUpdateUser()` and appends `user.Metadata`, demonstrating metadata on both flows. - Both JSON payloads switched from `addHumanUser` to the nested `createUser` shape (`human.profile`, `human.email`, `human.idpLinks`, top-level `metadata`). - Added a Callout explaining first-login → `createUser` vs. existing-user → `updateUser`, that both support metadata, and that `addHumanUser`/`updateHumanUser` are deprecated. - Updated the claim-mapping debugging section to the new `createUser.human.profile.givenName` path. Versioned snapshots (`v4.12`/`v4.13`/`v4.14`) were intentionally left unchanged, as they document releases where the old API was correct. --------- Co-authored-by: gayathri <[email protected]> |
||
|
|
8395d4326e |
docs: add v3 to v4 upgrade and Login V2 adoption guides (#12443)
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved - Operators upgrading from ZITADEL v3 to v4 lacked a clear, docs-backed upgrade path covering web keys (A-10017), Login V1 vs Login V2 expectations, and post-upgrade options. - There was no standalone guide for adopting Login V2 after already running on v4. - Related ops/docs pages did not consistently point readers to the upgrade and Login V2 adoption material. - Kubernetes/Helm operators upgrading to a chart that ships ZITADEL v4 had no ops-page section linking the advisory, upgrade guide, optional Login V2 deferral (`login.enabled: false`), and chart README upgrade notes. # How the Problems Are Solved - Adds technical advisory **A-10017** documenting OIDC web key staging requirements before upgrading to v4. - Adds an **upgrade-v3-to-v4** guide with the recommended upgrade path (Login V1 remains supported; Login V2 is optional at upgrade time). - Adds a **v4-only adopt-login-v2** guide for teams adopting Login V2 later, with steps and caveats separate from the version upgrade. - Adds an **Upgrading to ZITADEL v4** subsection under `self-hosting/deploy/kubernetes/operations.mdx` that links to A-10017, the upgrade guide, Adopt Login V2, notes `login.enabled: false` when not adopting Login V2 yet, and points to the [Helm chart README](https://github.com/zitadel/zitadel-charts/blob/main/charts/zitadel/README.md) for chart-specific breaking changes. # Additional Changes - Registers new docs in the sidebar and wires Related / cross-links from upgrade and ops pages so the advisory and guides are discoverable together. - Aligns language so upgrade vs. Login V2 adoption are clearly separated concerns. - Light cross-link updates on troubleshooting / updating-scaling / login-client pages where related. # Additional Context - Related: A-10017 (web keys advisory) - Docs paths introduced/updated: - `apps/docs/content/support/advisory/a10017.mdx` - `apps/docs/content/self-hosting/manage/upgrade-v3-to-v4.mdx` - `apps/docs/content/self-hosting/manage/adopt-login-v2.mdx` - `apps/docs/content/self-hosting/deploy/kubernetes/operations.mdx` (new "Upgrading to ZITADEL v4" section) - Sidebar and Related / cross-links - Companion Helm chart README PR: https://github.com/zitadel/zitadel-charts/pull/608 --------- Co-authored-by: Cursor <[email protected]> |
||
|
|
305579dd07 |
docs: update V2 end-of-support copy in roadmap (#12435)
## Summary - V2 has already reached end of support; the roadmap page previously implied end-of-support timelines for V2 and V3 were both still forthcoming. - Updated copy to state V2's end-of-support status plainly and note that V3's timeline and migration guidance will be published soon. ## Test plan - [x] Verified the updated sentence renders correctly in `apps/docs/content/product/roadmap.mdx` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Elina Sokolovska <[email protected]> Co-authored-by: Claude Sonnet 5 <[email protected]> |
||
|
|
f0a999bccd |
docs: clarify human PAT limitation and link issue #10915 (#12412)
## Which Problems Are Solved There is currently a knowledge gap in the documentation where users are unclear on if or why human accounts cannot have Personal Access Tokens (PATs), which has led to confusion in community channels like Discord and GitHub. ## How the Problems Are Solved Clarified that human PATs are currently not supported in the Personal Access Token documentation and added a direct link to GitHub Issue #10915 so users can track and upvote this capability. ## Additional Changes None ## Additional Context None --------- Signed-off-by: Rajat Singh <[email protected]> Co-authored-by: Rajat Singh <[email protected]> |
||
|
|
b429ec240a |
docs: remove GitHub discussion link from TOTP issuer callout (#12428)
## Which Problems Are Solved The callout added in #12335 included a link to a GitHub discussion which will become outdated and difficult to maintain. ## How the Problems Are Solved Removes the discussion link from the ZITADEL Cloud bullet point in the TOTP issuer callout, keeping the rest of the callout intact. Co-authored-by: Rajat Singh <[email protected]> |
||
|
|
6bcdb27e17 |
docs: document TOTP issuer name configuration for self-hosted and cloud instances (#12335)
# Which Problems Are Solved - No documentation explained that the TOTP issuer name defaults to "ZITADEL" and is not derived from the domain - No documentation clarified how to change it on self-hosted (env var only, Helm values don't work) - No documentation communicated that it is not configurable on ZITADEL Cloud # How the Problems Are Solved - Adds a callout in the MFA section of the default settings page covering both self-hosted (ZITADEL_SYSTEMDEFAULTS_MULTIFACTORS_OTP_ISSUER) and cloud (not configurable, discussion link) # Additional Changes None # Additional Context - https://github.com/zitadel/zitadel/discussions/5453 Signed-off-by: Rajat Singh <[email protected]> Co-authored-by: Rajat Singh <[email protected]> |
||
|
|
a97999e5ca |
docs: update roadmap page content (#12402)
## Summary - Update the description frontmatter and reword "next generation" to "next iteration" throughout - Add a "What this means for existing customers" subsection after the disclaimer, before the Strategic Roadmap section - Convert `### **Strategic Investments**` / `### **Customer Outcomes**` subheadings to bold text so they no longer appear in the TOC - Rename the closing section to "Migration and Adoption" with updated content ## Test plan - [x] Diffed against source content to confirm all requested sections match - [x] Verified no remaining "next generation" occurrences --------- Co-authored-by: Elina Sokolovska <[email protected]> Co-authored-by: Florian Forster <[email protected]> |
||
|
|
d15b4e2da2 |
docs: create knowledge gap ID 34 (#12388)
Automatically generated PR resolving Knowledge Gap ID 34. **Thread ID:** manual-1783339162398 **Action:** CREATE **New File:** `content/docs/drafts/gap-manual-1783339162398.mdx` ### AI Summary > Add a user migration guide for firebase to Zitadel ### Human Reviewer Instructions > Firebase to Zitadel Migration Summary > The Blocker: Incompatible Password Hashes > > Firebase uses a proprietary, modified scrypt algorithm requiring project-specific keys. > > Zitadel supports standard algorithms but lacks a verifier for Firebase's custom format. > > Result: Passwords cannot be directly imported. Firebase hashes must be discarded. > > Strategy 1: Bulk Import & Password Reset (Standard) > Requires users to set a new password on their first login. > > Export: Run firebase auth:export users.json --format=json. > > Map: Convert Firebase fields (e.g., localId) to Zitadel's schema. > > Import to Zitadel: Call the Zitadel import API. > > Action: Omit the hashedPassword object entirely. > > Action: Include "passwordChangeRequired": true in the JSON payload to trigger a reset flow via email or login prompt. > > Strategy 2: Just-In-Time (JIT) Migration (Seamless) > Migrates users transparently behind the scenes during an active grace period. > > Intercept Login: Your backend captures the plain-text password during login. > > Verify: Backend POSTs credentials to Firebase Auth REST API: > > https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=[WEB_API_KEY] > > Migrate to Zitadel: > > If Valid (200 OK): Create the user in Zitadel immediately using the plain-text password (Zitadel will natively hash it). > > If Invalid (400): Reject login or check if the user is already in Zitadel. > > Sunset: After the grace period ends, migrate remaining inactive users using Strategy 1. > (Note: Do not log plain-text passwords and strictly enforce HTTPS during this phase). --- 🤖 **Need adjustments?** Leave a comment below and tag **@zitadel-knowledge-bot** with your requested changes, and I will automatically update the files and push a new commit! --------- Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <[email protected]> |
||
|
|
bc9333aef9 |
chore: update npm dependencies (#12375)
# Which Problems Are Solved - npm dependencies across the monorepo are behind current patch/minor releases. - Transitive dependencies are pinned to older versions by parent packages (karma, nx, @changesets/cli, etc.). - Console build fails after the Angular toolchain update because `angular.json` references assets outside the workspace root. # How the Problems Are Solved - Bumps `@angular/*` to `^21.2.17` in console. - Bumps `js-yaml` to `^4.2.0` in docs. - Bumps `concurrently` to `^10.0.3` in login. - Adds pnpm overrides for transitive deps that cannot be bumped directly (ws, undici, minimatch, esbuild, dompurify, qs, and others). # Additional Changes - Removes 10 overrides that are no longer needed after parent packages resolve to newer versions. - Updates 4 existing overrides (`tar`, `js-yaml`, `dompurify`, `brace-expansion`) to match current upstream ranges. - 2 low-severity findings remain via the abandoned `raw-loader` package in docs (peer dep resolution; no upstream fix without replacing `raw-loader`). - Fixes console build: replaced the `angular.json` asset glob `../apps/docs/public/img/tech` with a `prebuild` script that copies tech images into `src/assets/docs/img/tech`. **Verify at runtime that tech images on project grant / integration pages still load.** # Additional Context - Overrides remain where parent packages still pin older transitive versions. - The `angular.json` asset path issue predates this PR (not introduced by the Angular bump). --------- Co-authored-by: Livio Spring <[email protected]> |
||
|
|
6b629c2290 |
docs: update roadmap and remove release cycle page (#12373)
Removes the release cycle page and sidebar entry. Replaces the roadmap page with updated strategic roadmap content and renames its sidebar label to Roadmap. --------- Co-authored-by: Elina Sokolovska <[email protected]> Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Fabienne Bühler <[email protected]> |
||
|
|
b4e205c601 |
docs: add IdP claim mapping troubleshooting to Actions V2 response manipulation guide (#12339)
# Which Problems Are Solved - The documentation had no guidance for debugging auto-user-creation failures caused by unexpected or missing claims from an external IdP, leaving users without a path forward when they see a `SetHumanProfile.GivenName` validation error during OIDC login. # How the Problems Are Solved - Adds a troubleshooting section to the Actions V2 response manipulation guide explaining how to use the `RetrieveIdentityProviderIntent` webhook to inspect `rawInformation` and identify claim key mismatches from the external IdP. # Additional Changes - None Signed-off-by: Rajat Singh <[email protected]> Co-authored-by: Rajat Singh <[email protected]> |
||
|
|
1e0b810dca |
feat: allow managing invite code in secret generators (#12109)
# Which Problems Are Solved Zitadel exposes the secrets generator configuration through its admin api. This allows instance admins to manage them on their own and they can create overwrite the system / runtime defaults (incl. expiration). This very much needed in multi-instance scenarios such as zitadel.cloud. Currently the invite code configuration was not manageable through the API, but only runtime config. # How the Problems Are Solved - added the `invite_code` type to the API allowing it to be set and retrieved. - added the type to console's management list - added the type to be stored on instance setup - change the `GetSecretGenerator` endpoint to fall back to the runtime config if no config is stored on the instance itself - ensure the `length` and at least one charset is enabled, return an error otherwise - expiry is not enforced, so 0 allows codes with no expiry (current state) # Additional Changes None # Additional Context - closes https://github.com/zitadel/zitadel/issues/10474 |
||
|
|
561da54ff7 |
docs: update knowledge gap from thread 1517531035175354430 (#12352)
Automatically generated PR targeting 1 files. **Thread ID:** 1517531035175354430 **Action:** UPDATE **AI Summary:** > Documentation does not explain what happens when users bookmark the login page or access ZITADEL without an OIDC flow, particularly regarding redirect behavior and the purpose of organization Default Redirect URI settings. Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com> |
||
|
|
f96080a4c1 | docs: update applications image (#12331) | ||
|
|
9ccafca12b |
docs: update knowledge gap from thread 1512031509387673610 (#12328)
Automatically generated PR targeting 1 files. **Thread ID:** 1512031509387673610 **Action:** UPDATE **AI Summary:** > The documentation lacks clear explanation of how the 'Use new login UI' checkbox and 'Custom base URL for the new Login UI' field work together, including step-by-step configuration and troubleshooting guidance. --------- Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com> Co-authored-by: fcoppede <[email protected]> |
||
|
|
64b1a7d42b |
docs: update knowledge gap from thread manual-1782237446658 (#12323)
Automatically generated PR targeting 1 files. **Thread ID:** manual-1782237446658 **Action:** UPDATE **AI Summary:** > Need to add a note on this page to encourage users with active subscriptions to link their github and Discord account because that will help github issues get higher priority and discord threads as well Co-authored-by: zitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com> |
||
|
|
d184e976fc |
Merge commit from fork
* feat(jwt idp): manage and validate audience * translations * fix tests * address comments * update migration version * fix merge |
||
|
|
385c5ae54b |
docs: Update restrict console guide (#12262)
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved The projects setting texts and sceenshot where outdated. # How the Problems Are Solved Update the text and screenshot. # Additional Changes * Rewording * Lockout warning component --------- Co-authored-by: Copilot Autofix powered by AI <[email protected]> |
||
|
|
cdc6c43d09 |
fix: update dependencies (#12216)
# Which Problems Are Solved Fixes open vulnerabilities # How the Problems Are Solved Updated npm/js dependencies across console, new login v2, and docs Based on https://github.com/zitadel/zitadel/pull/12164 --------- Co-authored-by: conblem <[email protected]> Co-authored-by: Livio Spring <[email protected]> |
||
|
|
f4f43f5248 |
docs: fix broken GitHub code-reference blocks after fumadocs migration (#12213)
# Which Problems Are Solved Pages in `apps/docs` that embed source from GitHub via the Docusaurus convention ```` ```js reference https://github.com/zitadel/actions/blob/main/examples/org_metadata_claim.js ``` ```` stopped working after the migration from Docusaurus to fumadocs. The old `docusaurus-theme-github-codeblock` plugin used to fetch the file and render it; fumadocs has no support for that meta, so the page rendered the raw URL as plain code-block text. Visible at `/docs/apis/actions/code-examples` and 16 other pages. # How the Problems Are Solved - Converted every ```` ```<lang> reference\n<URL>\n``` ```` block (46 total across 17 `.mdx` files) to the native fumadocs JSX form: `<GithubCodeBlock url="<URL>" />`. The existing `<details>`/`<summary>` collapsibles around blocks are kept — they're an authoring choice, not part of the rendering bug. - Updated `apps/docs/components/github-code-block.tsx` to render via `DynamicCodeBlock` from `fumadocs-ui/components/dynamic-codeblock` (proper shiki highlighting) instead of raw `CodeBlock` + `Pre` (which produced unhighlighted output). Also fixed language detection so a URL hash like `#L10-L20` no longer pollutes the language token. - Registered `GithubCodeBlock` globally in `apps/docs/mdx-components.tsx`, matching how every other shared component (`APIPage`, `Callout`, `Tab/Tabs`, `Step/Steps`, `Admonition`, `TerminologyUpdate`) is exposed. MDX files no longer need a local `import`. # Additional Changes - Normalized the two MDX files that were already using the JSX form (`examples/secure-api/python-django.mdx`, `examples/secure-api/java-spring.mdx`): removed their now-redundant local `import { GithubCodeBlock }` and rewrote 9 long-form `<GithubCodeBlock url="..."></GithubCodeBlock>` tags to self-closing for consistency. # Additional Context Verified locally with `pnpm --filter @zitadel/docs dev`: - `/docs/apis/actions/code-examples` — 20 shiki-highlighted code blocks rendered inside the `<details>` collapsibles (was 0). - `/docs/apis/openidoauth/claims` — line-range hashes (`#L9-L11`) honored. - `/docs/examples/login/flutter` — mixed languages (xml/dart/html) detected and highlighted. - `/docs/guides/integrate/external-audit-log` — edge case of fenced reference indented inside a numbered list also converted and rendered. Greps: - `^[ \t]*\`\`\`[a-zA-Z0-9]+ reference` in `apps/docs/content/**/*.mdx` → 0 matches. - `<GithubCodeBlock url="` in `apps/docs/content/**/*.mdx` → 55 matches. - `from '@/components/github-code-block'` in `apps/docs/content/**/*.mdx` → 0 matches. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]> |
||
|
|
b4f2da2171 |
docs: Add Gateway API routing to Kubernetes deployment guide (#12168)
## Description This PR updates the Kubernetes deployment documentation to address user feedback regarding our routing instructions. A user noted that standard `Ingress` is becoming outdated and requested configuration examples for the modern Gateway API. To support both existing and modern clusters, I have updated the guide to provide two clear pathways for exposing ZITADEL. ## Changes Included * **Prerequisites updated:** Mentioned Gateway API controllers alongside standard Ingress controllers. * **Refactored Stage 2 (Production):** Split the routing configuration into "Option A: Standard Ingress" and "Option B: Gateway API". * **Added YAML example:** Provided a sample `HTTPRoute` resource to route traffic to the `zitadel` and `zitadel-login` backend services. * **Terminology updates:** Broadened terms like "Ingress" to "Routing" or "Routing controller" where applicable. |
||
|
|
ecfe72b509 |
docs: update vue repo url (#12160)
fix broken vue repo url |
||
|
|
1b8b0ac410 |
docs: move customer portal docs to knowledge base (#12147)
Moved the Customer Portal Documentation to the knowledge base (help.zitadel.com) |
||
|
|
64737bfedf |
chore: deps update (#12114)
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved Outdated depdencies # How the Problems Are Solved This PR mostly just updates our NPM depdencies to the newest feature releases. The UUID package was update to version 14, the changelog only includes changes to the supported node version. # Additional Changes Replace tsx for some scripts in the docs. --------- Co-authored-by: Livio Spring <[email protected]> Co-authored-by: Max Peintner <[email protected]> |
||
|
|
24d0f34f39 |
perf: optimize docs static generation cost (#12108)
## Summary - Switch the docs catch-all route to full static generation and prebuild both latest and versioned docs paths. - Prebuild OG images for all docs pages, make sitemap and LLM export static, and remove nondeterministic sitemap timestamps. - Reduce build-time overhead by memoizing docs sidebar trees and skipping processed markdown generation for versioned docs. ## Testing - `pnpm nx run @zitadel/docs:build` - `pnpm nx run @zitadel/docs:lint` - `pnpm nx run @zitadel/docs:check-types` - Verified the prerender manifest contains 8,816 prerendered routes, 3,293 versioned docs routes, 4,406 OG routes, and zero revalidating docs routes. |