Commit Graph
280 Commits
Author SHA1 Message Date
6fa5c0e662 Merge commit from fork
* feat(login): add shared enrollment authorization guard

Introduce assertSessionAuthorizedForEnrollment, a single gate that decides
whether a session may enroll a new authenticator: it requires a verified
primary factor (password, passkey or IDP) on a non-expired session, or, for
onboarding, a user with no auth methods plus a prior user-verification check.

This mirrors the gate previously inlined only in registerPasskeyLink, which
is refactored to reuse the shared helper. It is the foundation for gating the
remaining credential-enrollment entry points (GHSA-45f2-5q3r-xgg6).

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(login): require authentication before WebAuthn/U2F enrollment

addU2F and verifyU2F registered a credential for any session that resolved to
a user id, including a bare identify-only session created by merely submitting
a login name. An unauthenticated attacker who knew a victim's login name could
therefore attach an attacker-controlled authenticator to the victim's account
and then complete a login as the victim.

Both actions now run assertSessionAuthorizedForEnrollment before registering or
verifying, so an unauthenticated session is rejected and no credential is
persisted.

GHSA-45f2-5q3r-xgg6

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(login): require authentication before TOTP/OTP enrollment

verifyTOTP and the otp/<method>/set page activated a TOTP/OTP factor for any
session with a user id, and the u2f/set page rendered the registration form
without an authorization check. An identify-only session could thus enroll a
second factor on a victim's account.

Gate all of them with assertSessionAuthorizedForEnrollment. The setup pages now
render the unknown-context/error state instead of enrolling when the session is
not authorized. Also guard the method value in the OTP set page, since the added
guard branch removes the control-flow narrowing the previous throw provided.

GHSA-45f2-5q3r-xgg6

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(login): require authentication before legacy (V1) passwordless enrollment

The V1 passwordless registration handlers began and verified passwordless setup
for any auth request that carried a user id, without checking that a first
factor had been verified. An unauthenticated attacker could submit a victim's
login name and then POST directly to the passwordless prompt/init endpoints to
plant an attacker-controlled passkey, then log in as the victim.

Add passwordlessSetupAllowed / CheckPasswordlessSetupAllowed, which permit
enrollment only for a legitimate onboarding user (PasswordlessInitRequired) or
an auth request whose first factor is already verified, and call it before
BeginPasswordlessSetup and VerifyPasswordlessSetup. The verify path is also
bound to authReq.UserID instead of the attacker-controllable form field, so a
credential cannot be redirected onto a different account. The init-code
(mail/onboarding) path is unchanged.

GHSA-45f2-5q3r-xgg6

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(login): remove never-active legacy (V1) authReq passwordless setup path

Supersedes the runtime gate added in the previous commit. Maintainer review
(Livio, the original author) and investigation confirmed the auth-request based
passwordless *setup* path in the legacy login was a never-active leftover:

- The passwordless prompt page (passwordless_prompt.html) has no submit button
  and no script; it only tells the user to complete setup via the emailed link
  (the PasswordlessButtonText/NextButtonText/SkipButtonText i18n keys are unused).
- Legitimate onboarding for a PasswordlessInitRequired user goes through the
  emailed init-code flow (authReq == nil, BeginPasswordlessInitCodeSetup /
  VerifyPasswordlessInitCodeSetup), which is untouched.
- The authReq branch (handlePasswordlessPrompt -> BeginPasswordlessSetup /
  VerifyPasswordlessSetup) was reachable only by a crafted direct POST — the
  GHSA-45f2-5q3r-xgg6 attack.

Rather than gate the dead path, remove it entirely, eliminating the attack
surface:
- delete handlePasswordlessPrompt and the POST /login/passwordless/prompt route
  (keep renderPasswordlessPrompt: the informational "use your emailed link" page
  for PasswordlessRegistrationPromptStep),
- make renderPasswordlessRegistration / checkPasswordlessRegistration use only
  the init-code path,
- drop the now-unused AuthRequestRepo.BeginPasswordlessSetup /
  VerifyPasswordlessSetup (and the interface decls) and the passwordlessPromptUrl
  template helper; the underlying HumanAddPasswordlessSetup /
  HumanHumanPasswordlessSetup commands remain (used by the auth-API self-service
  and the v2 passkey APIs).

A crafted POST to /login/passwordless/init carrying an authRequestID but no valid
code now falls through to the init-code path and fails.

GHSA-45f2-5q3r-xgg6

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* check for userverified true as well, cleanup return message

---------

Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Max Peintner <[email protected]>
2026-07-29 05:45:56 +02:00
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]>
2026-07-27 14:59:02 +00:00
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]>
2026-07-22 13:47:00 +02:00
Federico CoppedeandGitHub 62e56e085c docs: fixed broken docs references (#12465) 2026-07-20 15:28:34 +02:00
3e94047da7 fix(login): redirect to external IdP after domain discovery regardless of registration policy (#12369)
Closes #12021
Closes #12023

# Which Problems Are Solved

When a user entered an email on the login page and domain discovery
resolved the correct organization, the login flow failed to redirect to
the configured external IdP. Instead it returned a "user not found"
error or showed the registration page.

This happened because the "user not found" decision tree in
`sendLoginname` only attempted an IdP redirect when
`allowLocalAuthentication` was disabled. If local auth was enabled but
`allowRegister` was `false`, the IdP check was skipped entirely.

# How the Problems Are Solved

Combined the IdP redirect logic into a single block that triggers when
either local authentication is disabled **or** domain discovery resolved
an organization. The registration policy (`allowRegister`) now only
affects local account creation and no longer gates external IdP
authentication.

---------

Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Livio Spring <[email protected]>
2026-07-17 13:09:48 +00:00
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]>
2026-07-17 14:26:01 +02:00
Florian ForsterandGitHub 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.
2026-07-17 11:36:51 +00:00
Max PeintnerandGitHub 56f4798ed3 Merge commit from fork
* add mfa check to session validity check

* change session check to check for already set up mfa methods

* lint

* improve naming

* test changes

* cleanup continueWithSession fcn
2026-07-17 08:23:56 +02:00
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]>
2026-07-16 16:35:44 +00:00
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]>
2026-07-16 14:42:11 +00:00
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]>
2026-07-16 12:01:07 +02:00
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]>
2026-07-15 15:14:18 +00:00
1e09b4fc19 feat(login): hu translations + I18n consistency v2 (#12447)
# Which Problems Are Solved

Fixes i18n consistency and adds HU translations for login v2

# How the Problems Are Solved

- Updated i18n files and added hu.json
- Moved all translations to v2-default.json

---------

Co-authored-by: Dobos Zoltán <[email protected]>
Co-authored-by: Liam Neville <[email protected]>
2026-07-15 11:27:25 +00:00
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]>
2026-07-15 04:35:41 +00:00
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]>
2026-07-14 14:16:13 +00:00
051506b5bb fix(login): prevent crash on a stale session cookie (#12423)
<!--
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 `sessions` cookie lifetime is independent of Zitadel's server-side
session validity, so a cookie can reference an invalid session. In that
case `getSession` throws `not_found` (QUERY-SFeaa).

- `loadMostRecentSession` lets that error propagate instead of treating
it as "no session", crashing the server render of every page that calls
it without its own guard.
- Most visibly, the "forgot password" flow landing on `/password/set`
throws `[not_found] Session does not exist (QUERY-SFeaa)` (gRPC code 5,
HTTP 404) and fails to render.
- 12 of the 16 call sites are unguarded and share this crash.

# How the Problems Are Solved

Catch the error in `loadMostRecentSession` and return `undefined`,
unifying "cookie references a dropped session" with the existing "no
cookie" case. Every call site already handles an undefined session,
either with a graceful fallback or by throwing its own explicit error.

```ts
return getSession({ serviceConfig, sessionId: recent.id, sessionToken: recent.token })
  .then((resp: GetSessionResponse) => resp.session)
  .catch((error) => {
    console.warn("[Session] Could not load most recent session", error);
    return undefined;
  });
```

All `loadMostRecentSession` call sites — **Previous** = behaviour when
the cookie referenced an invalid session; **Change** = what this PR does
to that call site:

| # | Call site | Change in this PR | Previous behaviour (invalid
session) | New behaviour |

|---|-----------|-------------------|--------------------------------------|---------------|
| — | `lib/session.ts` — `loadMostRecentSession` | **Added `.catch` →
`undefined`** (core fix) | Threw `not_found` to caller | Resolves to
`undefined` |
| 1 | `password/set/page.tsx` | none | **Crash** (the reported bug) |
Falls back to login-name flow |
| 2 | `password/change/page.tsx` | none | **Crash** | `undefined` →
`failedLoading` alert if no user |
| 3 | `passkey/set/page.tsx` | none | **Crash** | `undefined` → `userId`
fallback / `unknownContext` |
| 4 | `otp/[method]/page.tsx` | none | **Crash** | `undefined` →
`unknownContext` alert |
| 5 | `otp/[method]/set/page.tsx` | none | **Crash** (raw
`ConnectError`) | `undefined` → own `throw "No session found"` |
| 6 | `mfa/page.tsx` | none | **Crash** | `undefined` →
`verify.noResults` alert |
| 7 | `mfa/set/page.tsx` | none | **Crash** (raw `ConnectError`) |
`undefined` → own `throw "Could not get user id from session"` |
| 8 | `authenticator/set/page.tsx` | none | **Crash** (raw
`ConnectError`) | `undefined` → own throw / `unknownContext` |
| 9 | `signedin/page.tsx` | none | **Crash** | `undefined` → renders
with `loginName` fallback |
| 10 | `u2f/set/page.tsx` | none | **Crash** | `undefined` →
`unknownContext` alert |
| 11 | `u2f/page.tsx` | none | **Crash** | `undefined` → renders on
`loginName` |
| 12 | `lib/server/verify.ts` — `verifyTOTP` | none | **Crash** (raw
`ConnectError`) | `undefined` → own `throw "No user id found in
session."` |
| 13 | `password/page.tsx` | **Removed redundant `try/catch`** | Caught
locally → `undefined` | `undefined` — unchanged |
| 14 | `verify/page.tsx` | **Removed redundant `.catch`** ¹ | Caught
locally → `undefined` | `undefined` — unchanged |
| 15 | `verify/success/page.tsx` | **Removed redundant `.catch`** |
Caught locally → `undefined` | `undefined` — unchanged |
| 16 | `passkey/page.tsx` | **Removed redundant `.catch`** (login-name
path) | Caught locally → `undefined` | `undefined` — unchanged |

Rows 1–12 were unguarded and crashed; the central change fixes them.
Rows 13–16 already handled the error locally.

# Additional Changes

- This makes the four callers that wrapped the call in their own
try/catch or `.catch` redundant, so remove that now-dead handling
(password, verify, verify/success, passkey).
- Add unit tests covering `loadMostRecentSession`: no cookie, success,
and the stale-cookie rejection path.

¹ `verify/page.tsx`'s `.catch` also eagerly fetched `loginSettings`;
that is already covered by the page's own lazy fetch, so behaviour is
unchanged (one cosmetic edge case under `ignoreUnknownUsernames` with
both `userId` and `loginName` present — no functional impact).

# Additional Context

- Session *expiry* alone does not trigger this: expired rows are not
removed from the session projection, so `getSession` still returns them.
The `not_found` specifically means the session was **removed**
server-side (logout, admin/API termination, or a user/org/instance
cascade) while the browser still held the cookie.
- Mirrors the pattern upstream already applies inline in
`verify/page.tsx` (*"ignore error, as we might not have a session
yet"*), lifted to the shared function.
- **Related issue (not in this PR):** the sibling `loadSessionById`
helpers on the `?sessionId=` path (`passkey`, `otp`, `mfa`, `mfa/set`,
`signedin`, `authenticator/set`, `u2f`) call `getSession` directly and
share the same defect. Lower practical exposure (short-lived in-flow
URLs; a clean logout clears the cookie), so a follow-up PR should lift
those into a shared `loadSessionById` in `lib/session.ts`.
- Prior art: upstream added an equivalent guard to verify/page.tsx in
#11130, but the other call sites were never given the same treatment —
this PR resolves that inconsistency at the source.

Error trace:
```
{"level":"error","message":"⨯ Error [ConnectError]: [not_found] Session does not exist (QUERY-SFeaa)\n    at R (.next/server/chunks/ssr/_0uqqxk7._.js:3:25490)\n    at <unknown> (.next/server/chunks/ssr/_0uqqxk7._.js:3:26477)\n    at next (.next/server/chunks/ssr/_0uqqxk7._.js:3:68112)\n    at async (.next/server/chunks/ssr/_0uqqxk7._.js:10:31326)\n    at async (.next/server/chunks/ssr/_0uqqxk7._.js:10:33734)\n    at async Object.unary (.next/server/chunks/ssr/_0uqqxk7._.js:3:66838)\n    at async Object.getSession (.next/server/chunks/ssr/_0uqqxk7._.js:3:72371)\n    at async o (.next/server/chunks/ssr/[root-of-the-server]__0msfcq_._.js:1:7440) {\n  rawMessage: 'Session does not exist (QUERY-SFeaa)',\n  code: 5,\n  metadata: Headers {\n    'accept-encoding': 'gzip',\n    'alt-svc': 'h3=\":443\"; ma=2592000',\n    'content-length': '240',\n    'content-type': 'application/json',\n    date: 'Wed, 08 Jul 2026 09:06:09 GMT',\n     'strict-transport-security': 'max-age=63072000; includeSubDomains; preload',\n     vary: 'Origin',\n     },\n  details: [\n    {\n      type: 'zitadel.v1.ErrorDetail',\n      value: [Uint8Array],\n      debug: [Object]\n    }\n  ],\n  cause: undefined,\n  httpStatus: 404,\n  isUserError: true,\n  digest: '634020356'\n}","timestamp":"2026-07-08T09:06:09.969Z"}
```

---------

Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Marco A. <[email protected]>
Co-authored-by: Liam Neville <[email protected]>
2026-07-14 09:03:46 +00:00
aa76c3be27 fix(login): keep submit button disabled/loading during password set r… (#12429)
<!--
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

- On Login V2 after a user sets their password during the invite or
reset-password flows and clicks continue,
there is a few seconds delay before the redirect occurs because no
loading spinner is visible on the button
during this delay users assume nothing happened and click continue
again.
- Clicking the button again starts a duplicate form submission. Since
the first request was already fired the second request triggers an error
right before the redirect from the first request completion creating a
bad user experience

# How the Problems Are Solved

- Kept the `loading` state active `true` throughout the lifeline of the
request which includes the 2-second database consistency delay and the
subsequent `sendPassword` verification request.
- Added `setLoading(false)` calls only in early-return error handling
blocks and at the end of the
redirection flow ensuring the button remains disabled and shows the
spinner until the redirect happens.

# Additional Changes

- None

# Additional Context

- Solves  #12416

Co-authored-by: Max Peintner <[email protected]>
2026-07-14 10:05:09 +02:00
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]>
2026-07-13 12:43:47 -03:00
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]>
2026-07-13 11:37:43 +00:00
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]>
2026-07-10 09:52:56 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8b9d3637fd chore(deps): bump the go_modules group across 2 directories with 1 update (#12393)
Bumps the go_modules group with 1 update in the
/apps/login/acceptance/idp/saml directory:
[golang.org/x/crypto](https://github.com/golang/crypto).
Bumps the go_modules group with 1 update in the
/apps/login/acceptance/samlsp directory:
[golang.org/x/crypto](https://github.com/golang/crypto).

Updates `golang.org/x/crypto` from 0.36.0 to 0.52.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/crypto/commit/a1c0d9929856c8aba2b31f079340f00578eda803"><code>a1c0d99</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="https://github.com/golang/crypto/commit/3c7c86938f4541c333d506f719388d9c42d4763d"><code>3c7c869</code></a>
ssh: fix deadlock on unexpected channel responses</li>
<li><a
href="https://github.com/golang/crypto/commit/533fb3f7e4a5ae23f69d1837cd851d35ff5b76ce"><code>533fb3f</code></a>
ssh: fix source-address critical option bypass</li>
<li><a
href="https://github.com/golang/crypto/commit/abbc44d451a6f9236a2bbd26cbcd4d0fec473da3"><code>abbc44d</code></a>
ssh: fix incorrect operator order</li>
<li><a
href="https://github.com/golang/crypto/commit/e052873987615dc96fe67607a9a6adb76311344f"><code>e052873</code></a>
ssh: fix infinite loop on large channel writes due to integer
overflow</li>
<li><a
href="https://github.com/golang/crypto/commit/b61cf853a89d82cad68da5e12a6beca2116f8456"><code>b61cf85</code></a>
ssh: enforce user presence verification for security keys</li>
<li><a
href="https://github.com/golang/crypto/commit/9c2cd33e8d96a96133fd6ff732510ebba539c2bd"><code>9c2cd33</code></a>
ssh: enforce strict limits on DSA key parameters</li>
<li><a
href="https://github.com/golang/crypto/commit/890731877d85f71cfdc9554e7a27fec4684fc4c4"><code>8907318</code></a>
ssh: reject RSA keys with excessively large moduli</li>
<li><a
href="https://github.com/golang/crypto/commit/ffd87b4878fa98ca2908ec534e1a410bf095a35e"><code>ffd87b4</code></a>
ssh: fix panic when authority callbacks are nil</li>
<li><a
href="https://github.com/golang/crypto/commit/4e7a7384ecbc8d519f6f4c11b36fa9d761fc8946"><code>4e7a738</code></a>
ssh: fix deadlock on unexpected global responses</li>
<li>Additional commits viewable in <a
href="https://github.com/golang/crypto/compare/v0.36.0...v0.52.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `golang.org/x/crypto` from 0.36.0 to 0.52.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/crypto/commit/a1c0d9929856c8aba2b31f079340f00578eda803"><code>a1c0d99</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="https://github.com/golang/crypto/commit/3c7c86938f4541c333d506f719388d9c42d4763d"><code>3c7c869</code></a>
ssh: fix deadlock on unexpected channel responses</li>
<li><a
href="https://github.com/golang/crypto/commit/533fb3f7e4a5ae23f69d1837cd851d35ff5b76ce"><code>533fb3f</code></a>
ssh: fix source-address critical option bypass</li>
<li><a
href="https://github.com/golang/crypto/commit/abbc44d451a6f9236a2bbd26cbcd4d0fec473da3"><code>abbc44d</code></a>
ssh: fix incorrect operator order</li>
<li><a
href="https://github.com/golang/crypto/commit/e052873987615dc96fe67607a9a6adb76311344f"><code>e052873</code></a>
ssh: fix infinite loop on large channel writes due to integer
overflow</li>
<li><a
href="https://github.com/golang/crypto/commit/b61cf853a89d82cad68da5e12a6beca2116f8456"><code>b61cf85</code></a>
ssh: enforce user presence verification for security keys</li>
<li><a
href="https://github.com/golang/crypto/commit/9c2cd33e8d96a96133fd6ff732510ebba539c2bd"><code>9c2cd33</code></a>
ssh: enforce strict limits on DSA key parameters</li>
<li><a
href="https://github.com/golang/crypto/commit/890731877d85f71cfdc9554e7a27fec4684fc4c4"><code>8907318</code></a>
ssh: reject RSA keys with excessively large moduli</li>
<li><a
href="https://github.com/golang/crypto/commit/ffd87b4878fa98ca2908ec534e1a410bf095a35e"><code>ffd87b4</code></a>
ssh: fix panic when authority callbacks are nil</li>
<li><a
href="https://github.com/golang/crypto/commit/4e7a7384ecbc8d519f6f4c11b36fa9d761fc8946"><code>4e7a738</code></a>
ssh: fix deadlock on unexpected global responses</li>
<li>Additional commits viewable in <a
href="https://github.com/golang/crypto/compare/v0.36.0...v0.52.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/zitadel/zitadel/network/alerts).

</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-08 12:46:42 +02:00
Federico CoppedeGitHubzitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>Copilot Autofix powered by AI
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]>
2026-07-07 12:43:28 +00:00
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]>
2026-07-06 14:18:51 +00:00
1beabed8cf fix(login): preserve org domain suffix through account chooser navigation (#12304)
Closes #12024

# Which Problems Are Solved

When the login flow is started with an
`urn:zitadel:iam:org:domain:primary:{domain}` scope, the resolved domain
suffix is lost if the user navigates through the account chooser (e.g.
clicking "Use another account"). This causes the login to fail with
"User not found" because the organization can no longer be resolved.

Additionally, the `hideLoginNameSuffix` branding setting was never
respected by the login app.

# How the Problems Are Solved

- Forward the `orgDomain` parameter through `gotoAccounts()` and the
`/accounts` page, so it survives navigation to `/loginname` — the same
way `organization` and `requestId` are already forwarded.
- Respect `BrandingSettings.hideLoginNameSuffix`: when enabled, the
`@domain.com` suffix is hidden from the input UI but still used
internally for user search.

---------

Co-authored-by: Livio Spring <[email protected]>
2026-07-03 16:25:20 +02:00
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]>
2026-07-03 21:06:22 +07:00
e6aaea563e fix(login): migrate legacy Tailwind v4 opacity utilities and fix checkbox contrast color (#12360)
# Which Problems Are Solved

1. **Secondary button hover effect** was rendering a solid white/gray
background instead of a subtle transparent overlay. The legacy
`bg-opacity-*` utility (e.g. `hover:bg-gray-500 hover:bg-opacity-20`)
doesn't compose with `bg-*` in Tailwind v4 — the opacity is ignored,
leaving a solid color. The same issue affected `ring-opacity-*` and
`border-opacity-*` across other components.

2. **Checkbox checkmark color** was hardcoded to white (`fill='white'`
in the SVG), ignoring the theme's primary contrast color. On themes with
a light primary color, the white checkmark was invisible.

# How the Problems Are Solved


1. Migrated all legacy opacity utilities to the Tailwind v4 slash
syntax:
   - `hover:bg-gray-500 hover:bg-opacity-20` → `hover:bg-gray-500/20`
- `ring-primary-light-500 ring-opacity-60` → `ring-primary-light-500/60`
- `focus:ring-opacity-50` + `focus:ring-indigo-200` →
`focus:ring-indigo-200/50`
- Removed redundant `border-opacity-20` where `border-black/10` was
already applied

2. Replaced the static `background-image` checkbox SVG with a
`mask-image` + `::after` pseudo-element approach. The checkmark color
now uses `var(--theme-light-primary-contrast-500)` /
`var(--theme-dark-primary-contrast-500)`, so it dynamically follows the
theme's contrast color.

---------

Co-authored-by: Livio Spring <[email protected]>
2026-07-03 12:39:09 +00:00
Max PeintnerandGitHub 92ea32a34d fix(login): use correct requestId with oidc_ prefix in Prompt.LOGIN + loginHint flow (#12376)
Closes #11946

# Which Problems Are Solved

- OIDC redirect is broken when `Prompt.LOGIN` + `loginHint` is used —
the user lands on `/signedin` instead of being redirected to the OIDC
client's callback URL (fixes #11946)

# How the Problems Are Solved

- In `handleOIDCFlowInitiation`, the `Prompt.LOGIN` + `loginHint` code
path was passing `authRequest.id` (raw ID without `oidc_` prefix) to
`sendLoginname`. Without the prefix, `completeFlowOrGetUrl` does not
recognize the flow as OIDC and falls through to the "Regular flow" path,
redirecting to `/signedin` instead of calling `createCallback`. Fixed by
using the `requestId` parameter which already carries the `oidc_`
prefix.

# Additional Changes

- Standardized all request ID references in `handleOIDCFlowInitiation`
to consistently use the `requestId` parameter instead of reconstructing
it with `` `oidc_${authRequest.id}` ``. This eliminates the
inconsistency that caused the bug and prevents similar issues in the
future.
- Removed a redundant `if (authRequest.id)` guard in the LDAP redirect
path, since `requestId` is always present as a required parameter.
- Added regression tests verifying that `sendLoginname` receives the
`requestId` with the `oidc_` prefix in the `Prompt.LOGIN` + `loginHint`
code path.
2026-07-03 14:18:01 +02:00
f32acbfd24 fix(login): redirect to loginname instead of empty accounts page when org scope filters all sessions (#12346)
Closes #11914

# Which Problems Are Solved

When an OIDC auth request includes an organization scope
(`urn:zitadel:iam:org:id:{id}` or
`urn:zitadel:iam:org:domain:primary:{domain}`), users with existing
browser sessions from *other* organizations were shown an empty Account
Selection page with no selectable accounts. The only option was to click
"Add another account", adding a confusing and unnecessary extra step.

This happened because the `/login` route checked `sessions.length` (all
browser sessions, unfiltered) to decide whether to enter the "reuse
existing session" branch. Inside that branch, `findValidSession`
correctly filtered by organization and returned no match — but the
fallback redirected to `/accounts`, which also filters by org and
rendered empty.

# How the Problems Are Solved

Before deciding to redirect to `/accounts`, pre-filter sessions by the
requested organization using the same logic already used by the accounts
page and `findValidSession`. If no sessions are eligible for the target
organization, redirect directly to `/loginname` instead.

This applies to both the **default prompt** and
**`prompt=select_account`** branches. For `select_account`, this matches
the behavior of major OIDC providers like Google and Microsoft, which
skip the account chooser and go straight to the login input when there
are no sessions to select from. The OIDC spec describes `select_account`
as enabling selection "amongst multiple accounts that they might have
current sessions for" — showing an empty picker serves no purpose.

---------

Co-authored-by: Copilot Autofix powered by AI <[email protected]>
2026-07-02 07:18:43 +00:00
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]>
2026-07-01 09:35:06 -03:00
Livio SpringandGitHub 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
2026-07-01 04:53:16 +00:00
Max PeintnerandGitHub 782133dbc7 fix(login): improve error handling for user registration (#12338)
# Which Problems Are Solved

When the registerUser server action failed during registration, all
errors from addHumanUser, createSessionWithRetry, and getUserByID
propagated as uncaught exceptions to the client component's generic
catch block, which always showed the same "Could not register user"
message. This made it impossible to distinguish between user creation
failures, session creation failures, and user lookup failures — both for
end users and in server logs.

# How the Problems Are Solved

Added .catch() handlers on the three gRPC calls in registerUser that log
the actual error and return null, allowing the existing null-check guard
clauses to return step-specific error messages (couldNotCreateUser,
couldNotCreateSession, userNotFound) through handleServerActionResponse.
This gives users more specific feedback and provides server-side logging
to narrow down the root cause of flaky signup failures.
2026-06-30 13:35:10 +00:00
4dc89b499f fix(login): allow custom protocols for native apps again (#12332)
# Which Problems Are Solved

- Native apps (mobile/desktop) use custom protocol schemes (e.g.
`myapp://callback`, `io.zitadel.app://auth`) as OAuth/OIDC redirect
URIs. The previous redirect-safety check in the login UI only allowed
`http:`/`https:` absolute URLs, so these custom-scheme callbacks were
wrongly rejected and native app login flows were broken.
- Even for valid external redirects, `router.push()` triggers an RSC
prefetch `fetch()` that is blocked by the CSP `connect-src 'self'`
directive for non-same-origin URLs, so navigation to
external/custom-protocol targets failed.
- SAML and OIDC callback URLs returned from the backend were not
consistently validated before being used for redirects/POSTs across the
various flow entry points.

# How the Problems Are Solved

- Reworked `isSafeRedirectUri` to use `@braintree/sanitize-url` instead
of a strict `http/https`-only allowlist. Custom protocol schemes are now
permitted, while dangerous schemes (`javascript:`, `data:`, plus an
explicit block of `file:`, `blob:`, `about:`) are still rejected.
- Added `isExternalUrl` helper to distinguish internal relative paths
(`/...`) from external/custom-protocol targets.
- In `handleServerActionResponse`, external/custom-protocol redirects
now navigate via `window.location.href` (full navigation) instead of
`router.push()`, avoiding the CSP-blocked RSC prefetch. Internal paths
continue to use `router.push()`.
- Added consistent redirect-URI validation for SAML and OIDC callbacks
across all relevant entry points (`oidc.ts`, `saml.ts`,
`server/flow-initiation.ts` including the `prompt=none` path, and the
SAML `samlData` POST case), blocking unsafe URLs with a clear warning
and error response.

# Additional Changes

- Added `@braintree/sanitize-url` (`^7.1.2`) dependency to `apps/login`
(`package.json` / `pnpm-lock.yaml`).
- Added unit tests for `isExternalUrl` covering relative paths, absolute
HTTP/HTTPS URLs, custom protocol schemes, and protocol-relative URLs.
- Annotated the intentional `window.location.href` navigation with a
CodeQL suppression comment, documenting that the URL is validated by
`isSafeRedirectUri` beforehand.
- Minor formatting cleanup in `console` JWT provider component.

# Additional Context

- This restores behavior that previously worked for native apps but
regressed when redirect validation was tightened to `http`/`https` only.

---------

Co-authored-by: Max Peintner <[email protected]>
2026-06-30 13:19:40 +00:00
f9995ee39c fix: increase performance of ListUser by login name ignore case (#12350)
# Which Problems Are Solved

Login v2 uses the `users.v2.ListUsers`-endpoint to get a user by login
name. This query had an inefficient `WHERE`-clause.

# How the Problems Are Solved

Update the view and use a specific clause for this query.

# Additional information

Added in https://github.com/zitadel/zitadel/pull/10475

---------

Co-authored-by: Marco A. <[email protected]>
Co-authored-by: Livio Spring <[email protected]>
2026-06-30 11:20:21 +00:00
zitadel-knowledge-bot[bot]GitHubzitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>
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>
2026-06-30 07:33:25 -03:00
Max PeintnerandGitHub 3a18cef281 fix: prevent double triggering of verification emails (#11995)
# Which Problems Are Solved

This fixes a critical bug where initial verification emails (or invite
codes) were occasionally sent twice, invalidating the first code and
confusing users.

Previously, the initial verification email was triggered via a
`send=true` URL parameter executing inside a frontend useEffect exactly
when the `/verify` page mounted. This was fragile and prone to race
conditions caused by component remounts or partial hydration.

# How the Problems Are Solved

- Removed `send=true` from URL state and ripped out the doSend effect in
`VerifyForm.tsx`.
- Shifted execution strictly to the Next.js server. The email is now
automatically dispatched via await `initialSendVerification(...)` during
the POST requests (acting over `sendLoginname`, `register`, `password`,
`passkeys`, and `idp`).
- The login flow is now idempotent and robust against unintended
frontend re-renders.
- Refactored `checkEmailVerification()` to be async and updated the
associated unit-test coverage (all tests passing).
2026-06-29 11:42:45 +03:00
14874d6546 fix(login): Prevent IDP auto-creation failure when name fields are missing (#11070)
# Which Problems Are Solved

When using IDP auto-creation, the addHuman() call would fail if the IDP
didn't provide required profile fields (givenName or familyName),
resulting in a poor user experience.

# How the Problems Are Solved

Added validation before auto-creation to check if required profile
fields are present. If givenName or familyName is missing, users are now
redirected to the complete-registration page where they can manually
provide the missing information.

- Added profile field validation in CASE 4 (auto-creation) of
processIDPCallback
- Redirect to /idp/{provider}/complete-registration when required fields
are missing
- Pre-fill any available user data in the registration form

---------

Co-authored-by: David Skewis <[email protected]>
Co-authored-by: Florian Forster <[email protected]>
2026-06-26 08:01:09 +00:00
Federico CoppedeandGitHub f96080a4c1 docs: update applications image (#12331) 2026-06-24 14:25:32 -03:00
zitadel-knowledge-bot[bot]GitHubzitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>fcoppede
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]>
2026-06-24 15:32:38 +00:00
2397247925 fix(login): improve contrast of IDP processing message (#12309)
# Which Problems Are Solved

This improves the contrast of the “Processing authentication...” message
shown while Login V2 processes the IDP authentication flow.

The previous style used text-gray-600 without a dark-mode variant. This
color is hard-coded in the component and is not affected by the branding
colors, so users may see low contrast depending on the active
theme/background.

This PR updates the message text to use a higher-contrast light/dark
color pair.

---------

Co-authored-by: Max Peintner <[email protected]>
Co-authored-by: Max Peintner <[email protected]>
2026-06-24 10:30:44 +00:00
zitadel-knowledge-bot[bot]GitHubzitadel-knowledge-bot[bot] <296006658+zitadel-knowledge-bot[bot]@users.noreply.github.com>
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>
2026-06-23 18:19:29 -03:00
Livio SpringandGitHub 76fd6d859d Merge commit from fork
* fix: ensure external user's email is verified before auto-linking

* fix linking
2026-06-22 11:38:21 +02:00
Max PeintnerandGitHub 239620f112 fix(login): center text for generic IDP buttons without icons (#12211)
Closes #12182

# Which Problems Are Solved

Generic IDP buttons (OIDC, SAML, LDAP, JWT) used pl-20 to approximate
icon offset alignment, but since they have no icon, the text appeared
misaligned compared to branded IDPs (Google, Microsoft, Apple).

# How the Problems Are Solved

Replaced the left-padding hack with centered text so generic IDP names
display cleanly within the button.

before:

<img width="392" height="290" alt="Screenshot 2026-05-28 at 12 03 21"
src="https://github.com/user-attachments/assets/64c4e8eb-caec-4742-b61c-e3b0c5093dd7"
/>

after:

<img width="392" height="289" alt="Screenshot 2026-05-28 at 12 07 27"
src="https://github.com/user-attachments/assets/d1faf1f4-0aab-475f-8801-15b461e15da4"
/>
2026-06-22 08:57:18 +00:00
Max PeintnerandGitHub f691851d32 Merge commit from fork 2026-06-22 10:34:26 +02:00
Tim MöhlmannandGitHub e94d4c3986 feat(crypto): FIPS 140-3 compliant build and runtime checks (#12233)
# Which Problems Are Solved

Enable FIPS 140-3 compliant build.

# How the Problems Are Solved

- Add runtime config validation, if the FIPS flag is enabled fail the
application when a non-compliant hasher is used, or throw a warning when
a legacy verifier is used
- Add a build matrix for FIPS certified build:
  - Go binary is built with `GOFIPS140=certified`
- Login container uses a separate base:
[ubi9](https://catalog.redhat.com/en/software/containers/ubi9/ubi/615bcf606feffc5384e8452e)
from redhat which provides a FIPS certified OpenSSL (used by NodeJS TLS
stack)
- Non-FIPS images where already pushed to both Github Container Registry
and Google Artifact Repository (GAR). Fips images are only pushed to the
GAR.
- Tag versions are suffixed `-fips`. So on release the following images
will be additionally available:

```
europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel-login:vX.Y.Z-fips
europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel:vX.Y.Z-fips-debug
europe-docker.pkg.dev/zitadel-common/zitadel-repo/zitadel:vX.Y.Z-fips
```
  
# Other changes

- Bumb Go toolchain. At least v1.25.10 is required for a
GOFIPS140=certified setting.
 
# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/4335
- Build [test
run](https://github.com/zitadel/zitadel/actions/runs/27253916052)
pushing FIPS and non-FIPS images
2026-06-16 10:16:49 +02:00
Max PeintnerandGitHub fdafb95a63 fix(login): load custom font from branding settings and allow in CSP (#12279)
Closes #11200

# Which Problems Are Solved

Custom fonts uploaded via the branding/label policy were not supported.

# How the Problems Are Solved

- The login now correctly applies the branding settings, based on the
organization context
- Use the absolute `fontUrl` from the branding API directly in the
`@font-face` `src`, matching how logo and icon assets are already loaded
via absolute URLs.
- Add the Zitadel service URL to the `font-src` CSP directive so the
browser permits loading the cross-origin font.

# Additional Changes

- Updated CSP tests to reflect the new `font-src` behavior.
2026-06-15 14:33:54 +00:00
8e82ec1cb9 Merge commit from fork
* Add DenyLists parsing

* Remove unneeded returned error

* Plug global denylist into Command

* app creation: apply denylist to backchannel logout URI

* Inject denylist to backchannel logout worker

* webhook config: validate against blocked URLs

* Add notificationsWebhook denylist target

* command: Add SMTP endpoint validation against blocklist

* command: Add SMS endpoint validation against blocklist

* Validate webhook endpoint against denylist on channel notification

* Remove unused tests

* handle deprecated denylists

* remove unintended denylist entry in deprecated list

* use single http client

* fix tests

* update comments

* fixes

* cleanup

* address comments

* fix merge

---------

Co-authored-by: Livio Spring <[email protected]>
2026-06-15 15:36:14 +02:00
Livio SpringandGitHub d184e976fc Merge commit from fork
* feat(jwt idp): manage and validate audience

* translations

* fix tests

* address comments

* update migration version

* fix merge
2026-06-15 15:27:47 +02:00
Max PeintnerandGitHub 4459a1380c fix(login): accept IDP sessions on passkey registration (#12275)
# Which Problems Are Solved

The local `isSessionValid` in `passkeys.ts` only checked `password` and
`webAuthN` factors, so sessions authenticated via an external IDP
(`intent` factor) were treated as invalid. This caused a "You have to
authenticate" error when redirecting to `/passkey/set` after IDP login.

# How the Problems Are Solved

Added `session.factors.intent.verifiedAt` to the validity check,
consistent with the canonical `isSessionValid` in `session.ts`.
2026-06-12 12:17:37 +00:00
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]>
2026-06-10 10:38:37 +00:00
Max PeintnerandGitHub b27fdfc28c chore: update dependencies (#12242)
# Which Problems Are Solved

Fixes multiple high-severity Dependabot alerts for transitive
dependencies (axios, protobufjs, systeminformation, tmp, fast-uri,
@babel/plugin-transform-modules-systemjs).
 
# How the Problems Are Solved

**Direct dependency updates:**
- `@opentelemetry/*` 0.217 → 0.218 in login (new otlp-transformer
dropped protobufjs as direct dep)
- `posthog-js` 1.374 → 1.380 in console (dropped the
OpenTelemetry/protobufjs chain entirely)

**pnpm overrides** for remaining transitive deps that can't be updated
via their parents (all pulled by the gRPC stack, Cypress, karma, nx, or
ajv):
- `axios` → ≥1.17.0 (prototype pollution, proxy bypass)
- `protobufjs` → ≥7.5.7 / ≥8.0.2 (code injection, prototype pollution,
DoS)
- `systeminformation` → ≥5.31.6 (command injection)
- `tmp` → ≥0.2.6 (path traversal)
- `fast-uri` → ≥3.1.2 (path traversal, host confusion)

**Benchmark:** refreshed `package-lock.json` to resolve @babel and
fast-uri alerts.
2026-06-09 16:27:12 +02:00