Commit Graph
2149 Commits
Author SHA1 Message Date
a2bf528ca0 feat(domain): introduce error slugs (#12030)
# Which Problems Are Solved

As part of https://github.com/zitadel/zitadel/issues/11917 we want to
introduce error slugs so (API) clients can rely on stable,
machine-readable errors and act accordingly.

# How the Problems Are Solved

- Added a `NewSlug` helper function in the domain package.
- Added `ErrorDetails` to the `ZitadelError`
- Added an `zitadel.error.v2.ErrorDetail` proto message
- Updated the connectRPC error interceptor to map new slug based errors
to the new `ErrorDetail`
- Defined some common slugs and error functions like internal errors
- Defined (session) specific slugs used in the `DeleteSession` and
`CheckUser` functions and replaced old implementations
- Updated integration tests to check specific errors if the relation
database feature is enabled
- Updated doc and guideline to reflect the latest changes and decisions
- Updated DeleteSession endpoint API to list possible slugs

# Additional Changes

None

# Additional Context

- closes #11957

---------

Co-authored-by: Wim Van Laer <[email protected]>
2026-04-13 09:47:04 +02:00
fbd43ced6e fix(grpc): return 401 for unauthenticated v1 gateway errors (#11786)
Fixes #11730

The v1 gRPC middleware can return native gRPC status errors such as
`codes.Unauthenticated` when the auth header is missing.

Those errors were being passed through the generic gRPC error converter,
which only handled ZITADEL errors and downgraded raw gRPC statuses to
`codes.Unknown`.

Through grpc-gateway that caused `/auth/v1/users/me` to return HTTP 500
instead of HTTP 401.

So this change preserves native gRPC status errors in
`ZITADELToGRPCError`, keeps `ExtractZITADELError` aligned with those
transport codes for activity reporting, and adds regression coverage for
the converter, middleware, and gateway HTTP behavior.

---------

Co-authored-by: Gayathri Vijayan <[email protected]>
2026-04-10 12:22:40 +00:00
Vitor Bari BucciantiandGitHub 035ac4b9a2 fix(api): Add dummy implementation of new LinksSettings APIs to satisfy SettingsServiceHandler interface (#12034)
# Which Problems Are Solved

#11975 introduces new APIs to `SettingsService`, however,
`settingsconnect.SettingsServiceHandler` wasn't updated to satisfy the
interface containing the new APIs.

# How the Problems Are Solved

By adding a dummy implementation of new RPC methods to the
SettingsService Server.

# Additional Changes

n/a 

# Additional Context

n/a
2026-04-10 13:57:32 +02:00
Mridang AgarwallaandGitHub ce31ee2272 fix: propagate non-NotFound errors from instance interceptor (#12019) 2026-04-10 08:22:56 +00:00
b558a1f79f feat: allow ECDSA and ED25519 public keys (#11819)
# Which Problems Are Solved
Currently ZITADEL only allows the use of RSA public keys for machine
user authentication (jwt-bearer grant), including for system API users.
Attempting to use ECDSA (e.g. P-256) or ED25519 keys results in
`Errors.Internal` because `BytesToPublicKey` performs an
`ifc.(*rsa.PublicKey)` type assertion that returns `(nil, nil)` for
non-RSA keys, which then causes a nil key panic in go-jose during JWT
verification.

This is the same fix as #8433 (by @livio-a), rebased onto current
`main`.

# How the Problems Are Solved
- `BytesToPublicKey` now returns `crypto.PublicKey` (the standard
library interface) instead of `*rsa.PublicKey`
- A type switch validates the parsed key is one of `*rsa.PublicKey`,
`*ecdsa.PublicKey`, or `ed25519.PublicKey`
- A new `ErrNoPublicKey` sentinel error is returned for unsupported key
types instead of silently returning nil
- Callers in `system_token.go` and `query/key.go` are updated to use the
generic `crypto.PublicKey` interface

# Additional Changes
None

# Additional Context
Duplicate of #8433 which has been open since August 2024. We hit this
bug while implementing OIDC bootstrap for an SGX enclave that generates
ECDSA P-256 keys at runtime -- the `AddKey` API accepts the ECDSA SPKI
PEM fine, but the subsequent `jwt-bearer` token exchange fails with
`Errors.Internal` due to the nil key.

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Wim Van Laer <[email protected]>
2026-04-09 11:30:23 +02:00
Livio SpringandGitHub fbb6a406c8 chore: fix generators in crypto package (#12008)
# Which Problems Are Solved

`@zitadel/api:generate-go` failed due to duplicate generated files.
https://github.com/zitadel/zitadel/pull/11820 removed the old generated
file and added a generate command to the `internal/crypto/crypto.go`
file. However, there was already a `internal/crypto/generate.go` with a
different output file name (the old file).

# How the Problems Are Solved

Removed the `internal/crypto/generate.go` file and moved the second
generate into `internal/crypto/code.go`.

# Additional Changes

Noticed that mockgen is an old version and updated it. Also then checked
all other tools and updated them.

# Additional Context

- relates to #11820
- noted internally
2026-04-08 08:48:05 +02:00
Wim Van LaerandGitHub 99ad1ea83d test: added checks for timestamps in passkey test (#12003)
# Which Problems Are Solved

A test was failing because of timezone flakiness

# How the Problems Are Solved

The actual problem was an `updatedAt` field was not filled in because of
the `createdAt` was missing on an event. This caused a zero value to be
filled in into the `updatedAt` field. Postgres then stored this value
with seconds in it's timezone info, which go could not store.

By adding filling in the `createdAt` field, this does not occur.
2026-04-07 16:27:45 +02:00
c7f92a6887 feat: session challenge passkey for the RT model (#11944)
# Which Problems Are Solved

Add Passkey challenge needed for Create/Set session in the RT model.

# How the Problems Are Solved

Introduce `PasskeyChallengeCommand` (validate/execute/events) for
session creation checks
Add a generic `BeginWebAuthNLogin` helper that returns session data +
assertion JSON bytes.
Add unit tests

# Additional Changes
N/A

# Additional Context
- Related to https://github.com/zitadel/zitadel/issues/11035

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Marco A. <[email protected]>
2026-04-01 12:13:40 +02:00
5344a7f584 fix: invalid jwt assertion error handling (#11933)
# Which Problems Are Solved

Incorrectly mapped errors on the `/oauth/v2/token` endpoint lead to a
large number of HTTP status 500 errors.

# How the Problems Are Solved

By mapping invalid jwt assertion errors to OIDC `invalid_client` errors,
which returns a HTTP status 400.

# Additional Changes

N/A

# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/11924
- Follow-up: 
- Fix the [error
handling](https://github.com/zitadel/oidc/blob/main/pkg/oidc/verifier.go#L195)
in the oidc package

---------

Co-authored-by: Copilot <[email protected]>
2026-04-01 10:44:02 +02:00
Marco A.andGitHub 7a15a06d69 fix: reducer for PasskeyVerified sets VerifiedAt (#11929)
# Which Problems Are Solved

In https://github.com/zitadel/zitadel/pull/11858#discussion_r2993742914
we discussed on how to translate the ES passkey state to a relational
one. That is used to filter out passkeys during convertion to webauthn
credentials.

It was decided to consider passkeys in ready state those that have a
verified at `time.Time` set.

The change required fixing the reducer for the eventstore event.

# How the Problems Are Solved

  - Set `VerifiedAt` once `HumanWebAuthNVerifiedEvent` is received
  - Also set the verification to `nil`

# Additional Context

- Discussion #11858
- Follow-up for PR #11858
2026-03-31 17:38:47 +02:00
8e41d288ff fix: use preparation filter for org existence check during setup (#11932)
<!--
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

- Creating an organization via `SetUpOrg` failed when a custom domain
was provided in the same request.
- The org-domain setup path checked org existence only against persisted
state, so it could not see the newly created organization before the
batch was pushed.

# How the Problems Are Solved

- Added a preparation-aware org existence check that resolves the org
through the validation filter instead of the direct persisted-state
lookup.
- Reused the transaction-aware preparation filter so later validations
can see earlier in-flight commands from the same `PrepareCommands`
batch.

fixes #11677

---------

Co-authored-by: abhishek kumar gupta <[email protected]>
Co-authored-by: Wim Van Laer <[email protected]>
2026-03-31 09:58:42 +02:00
21b28b56ac fix: revert feature key for configs back to ConsoleUseV2UserApi (#11928)
# Which Problems Are Solved

https://github.com/zitadel/zitadel/pull/11390 renamed "Console" to
"Management Console". While
https://github.com/zitadel/zitadel/pull/11706 already reverted an
unintended rename of the feature key to enable the management console to
use the V2 API for user creation. It was now also discovered that the
rename of the feature itself also broke existing (default)
configurations.

# How the Problems Are Solved

Added a `mapstructure` tag on the instance feature to handle existing
configs.

# Additional Changes

Removed unused `TokenExchange` from the default configuration.

# Additional Context

- relates to #11390 
- relates to #11706
- requires backport to v4.x

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Marco A. <[email protected]>
2026-03-30 15:59:57 +00:00
Livio SpringandGitHub 0390e324e4 fix: do not send init mails for admins on instance setup (#11744)
# Which Problems Are Solved

Currently when setting up an instance, for example from the customer
portal through system api, an instance is created together with an
administrator. If no admin user is specified the default user from the
runtime configuration is automatically added.
For the new onboarding, we can to create instances without prompting the
user to set up a complete account right on start, but the current
behavior of the API prevents this, resp. the created account would
fallback to the default user or password.
By providing an empty `password` field in the `owner_password` in the
`AddInstanceRequest` or `huiman.password` in the
`CreateInstanceRequest`, it's already possible to create an account
without password. Until now this however ended up in an initial mail to
be sent to the user to finalize their account setup.

# How the Problems Are Solved

We simply set the `allowInitMail` to false in the `AddHumanCommand` for
setting up the admins. This will prevent an email to be sent out
immediately and gives us the possibility to use the invite flow later
on.

# Additional Changes

None

# Additional Context

- required for https://github.com/zitadel/website/issues/1611
- backport to v4.x
2026-03-30 15:31:51 +00:00
022bf74060 feat(session): Passkey Check API with relation tables (#11858)
# Which Problems Are Solved

As part of #11035 , this PR implements the Passkey check logic for
session validation

# How the Problems Are Solved

  - Refactor webauth FinishLogin to support new domain model
  - Add webauth config to defaults
  - Implement passkey check logic and tests
- Manual transaction management to avoid stalling the DB while
FinishLogin callback is executed
- Update passkey Type condition to allow passing a text operation
(equal, contains, etc..)

# Additional Context

This is a cherry-picked PR + minor changes, coming from
https://github.com/zitadel/zitadel/pull/11164
- Relates to #11035

---------

Co-authored-by: Fabienne Bühler <[email protected]>
Co-authored-by: Gayathri Vijayan <[email protected]>
2026-03-27 12:38:40 +01:00
ec7a6d5526 fix: make recovery code active on add (#11734)
# Which Problems Are Solved

- Recovery codes added via `GenerateRecoveryCodes` were never listed as
active authentication methods by `ListAuthenticationMethodTypes`. The
`user_auth_method` projection reducer (`reduceInitAuthMethod`) set the
state to `MFAStateNotReady`, expecting a subsequent verification event
to transition it to `MFAStateReady`. Unlike TOTP, U2F, and Passwordless
— which all have distinct verification events — recovery codes have no
such event, so they were permanently stuck in `NotReady`.

# How the Problems Are Solved

- Introduced a dedicated `reduceAddRecoveryCodes` reducer for
`HumanRecoveryCodesAddedEvent` that sets `MFAStateReady` immediately,
since recovery codes are usable as soon as they are generated.
- Uses `NewUpsertStatement` (instead of `NewCreateStatement`) so that
regenerating recovery codes for the same user updates the existing row
rather than failing on a duplicate key conflict.
- Removed the `HumanRecoveryCodesAddedEvent` case from
`reduceInitAuthMethod`.

# Additional Changes

- Added `UserAuthMethodTypeRecoveryCode` mapping in `authMethodTypeToPb`
unit test.
- Added integration tests for `ListAuthenticationMethodTypes` covering
both the "recovery codes present" and "recovery codes removed" cases.

# Additional Context

* https://github.com/zitadel/zitadel/issues/11317

---------

Co-authored-by: Marco A. <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-03-27 09:47:16 +00:00
Livio SpringandGitHub f94e4065c2 chore: update go dependencies (#11883)
Updates all dependencies to latests versions (apart from the ones where
there are already issues to solve their updates).
Some updates required minor changes.
2026-03-24 12:27:43 +01:00
70adaff793 feat: add option to use x.509 certificate system-api-user tokens (#11876)
# Which Problems Are Solved

System API users currently authenticate using raw RSA public keys
configured via Path or KeyData. This approach doesn't integrate well
with Kubernetes tooling.

# How the Problems Are Solved

Allow for the `path`/`keyData` to be an X.509 certificate. 

The `NotBefore` and `NotAfter` fields of the certificate are beeing
respected when validating the JWT.

# Additional Changes

# Additional Context

- Closes #11442

---------

Co-authored-by: Livio Spring <[email protected]>
2026-03-23 13:32:27 +00:00
c6386cb807 feat: rt recovery code check (#11848)
# Which Problems Are Solved

Adding recovery code check step required for the CreateSession RT
implementation.

# How the Problems Are Solved

- Introduce `RecoveryCodeCheckCommand` (validate/execute/events) for
session creation checks
- Add unit tests

# Additional Changes
- refactor password-check code to reuse a shared lockout-policy helper
- add some missing error translations in `internal/static/i18n/en.yaml`

# Additional Context
- Related to https://github.com/zitadel/zitadel/issues/11035

---------

Co-authored-by: Silvan <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
2026-03-23 10:56:40 +01:00
Mridang AgarwallaandGitHub 471ed4a5d7 feat: add DSN/URL connection string support for PostgreSQL and Redis (#11729) 2026-03-20 15:21:15 +00:00
ca4bba3992 feat: add delete session on relation tables (#11334)
# Which Problems Are Solved

As part of moving the session API to the relational database, this PR
adds the functionality to delete sessions through the session API v2 and
the OIDC end_session endpoint.

# How the Problems Are Solved

- added `SessionDeleteCommand` in domain package
- added possibility to pass the session token verifier to the domain
package
- use feature flag to switch API and OIDC usage from CQRS to relation
table
- added a `permissionCheck` condition to let permissions be checked
directly in the repository / sql
- part of it is still a placeholder until actual permission tables are
done
- added a `session_deleted` table to store (recently) deleted sessions
to handle necessary checks / cases like delete my own session, which
needs to be idempotent

# Additional Changes

Fixed `Matches` function of `existsCondition` (as missing an
implementation)

# Additional Context

- closes #11037

---------

Co-authored-by: Stefan Benz <[email protected]>
2026-03-19 08:35:53 +01:00
997aa607c4 feat(telemetry): unify request details (#11509)
# Which Problems Are Solved

In the "new" structured logging, request details were added in different
middlewares, such as request, instance and user IDs. This meant the the
upstream request logging middleware did not have access to metadata that
got added later, resulting in incomplete logs. Furthermore it was not
possible to correlate an API error response to log output.

# How the Problems Are Solved

A mutable request details object is added to the context early on. When
the api authz function run, the instance and user IDs are added to this
object as they become available. Every logline emitted after this
(time-wise) will then all contain these details under the `request` log
group.

<details>

<summary>example output in JSON</summary>

```json
{
  "time": "2026-03-13T19:21:20.890428806Z",
  "level": "INFO",
  "source": {
    "function": "github.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware.LogHandler.func1.1",
    "file": "/workspaces/zitadel/internal/api/grpc/server/connect_middleware/log_interceptor.go",
    "line": 34
  },
  "msg": "request served",
  "request": {
    "id": "d6q67c04vtjmi77cbbpg",
    "instance_host": "localhost:8080",
    "instance_id": "362349751439458307",
    "user_id": "362349751440048131"
  },
  "TraceID": "a9e0fee3522224f3583bbdcda737f4b4",
  "SpanID": "a563056eee36920a",
  "stream": "request",
  "version": "2026-03-13T19:20:59Z",
  "protocol": "connect",
  "service": "zitadel.user.v2.UserService",
  "http_method": "POST",
  "path": "/zitadel.user.v2.UserService/ListUsers",
  "code": "code_0",
  "duration": 12254350
}
```

</details>

Request IDs are now also returned with a response header or metadata.
Depending on the protocol:

- HTTP calls always return the request ID as header, regardless of
status
- gRPC calls always return the request ID as header, even if there was
an error
- connect RPC calls returns the request ID as header on success, trailer
in case of error. This is because header must be set on the response
object, which is nil in case of error. When there is an error, metadata
can be added which are then sent as trailers.

# Additional Changes

- Use the existing call duration middleware for both request ID and
logging for a consistent request start timestamp in all layers.
- Upgrade sloggcp for some fixes (notably TraceID)
- Modify the NoCache middleware so it uses `SetHeaders` instead of
`SendHeaders`. The latter prevented any other handler from setting
headers, including the new request ID middleware.

# Additional Context

Follow up on demo of:
  - https://github.com/zitadel/zitadel/pull/11159
  - https://github.com/zitadel/zitadel/pull/11435
  - backport to v4

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: muhlemmer <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Marco A. <[email protected]>
2026-03-18 14:07:33 +01:00
f138082b64 feat: add administrator repository (#11803)
Introduces the `administrators` relational table and repository layer
for the v3 backend, unifying instance, organization, project, and
project-grant membership into a single scoped model.

Closes #10220

## Changes

- **Domain**: `Administrator` struct, `AdministratorScope` enum,
`AdministratorRepository` interface with typed CRUD, column, condition,
and change helpers. Generated enumer and GoMock.
- **Migration** (`016`): `zitadel.administrators` table with a generated
`id`, scope-alignment check constraint, cascading foreign keys, and a
child `administrator_roles` table.
- **Repository**: CTE-based role mutations (`AddRole`, `RemoveRole`,
`SetRoles`) for atomicity. Roles aggregated via `ARRAY_AGG`.
- **Projection**: Event reducers for
`member.added/changed/removed/cascade_removed` across all four admin
scopes.
- **Fixes**: `Rows.Close()` error wrapping; `TextArray` nil-safe scan +
full `pgtype` array interface; `authorization` and `projectGrant` scan
helpers fixed for `ARRAY_AGG` under `database/sql`; removed unused `ctx`
parameter from `callReduce`.

---------

Co-authored-by: Marco Ardizzone <[email protected]>
2026-03-18 10:24:09 +01:00
c3a6bdb0bb Merge commit from fork
* fix: add `Scopes` to `Request` interface so that scopes can be validated on all requests

* feat: assert org from scope exists when authorizing requests

* fix: check all scopes

* comments

* check org on callback creation

* fix tests

* clarifications

* fix scope marshaling

* fix: enfore organization for authrequest in initiation

* fix: filter sessions on the accounts page by organization scope

* add integration tests

---------

Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Max Peintner <[email protected]>
2026-03-17 12:54:27 +01:00
Marco A.andGitHub aa4432dc99 feat(session): IDP Intent Check API with relation tables (#11820)
# Which Problems Are Solved

As part of #11035 , this PR implements the IDP intent check logic for
session validation

# How the Problems Are Solved

- Inject encryption algorithm for IDP intent tokens at DB initialization
  - Add the IDP intent repository as an invoke option
  - Rework crypto mocks using gomock
  - Implement IDP intent check logic
- minor - DB: expand IDP Intent deletion testing table with a test case
where IDP intent is not found

# Noteworthy

I did not implement `domain.Transactional` given the doubts that
@adlerhurst expressed in https://github.com/zitadel/zitadel/pull/11804 .
So, there is no transaction AT ALL in here because the `Execute()`
method contains one single DB call (Delete) and `Validate()` just does
Get calls.

# Additional Context

This is a cherry-picked PR + minor changes, coming from
https://github.com/zitadel/zitadel/pull/11164
- Relates to #11035
- Depends on https://github.com/zitadel/zitadel/pull/11804
2026-03-17 10:50:47 +00:00
500821ce0c fix: prevent possible nil pointer panics (#11728)
# Which Problems Are Solved

We found multiple cases where potentially a panic occurred or could
occur:
- when de/encrypting certain information
- returning apps without any configuration type (?)
- apple IdPs without a proper private key

# How the Problems Are Solved

- Added nil checks
- Check private key format for apple IdPs
- Added necessary helper function

# Additional Changes

Fixed i18n yaml where the IDP errors were indented under `org` instead
of directly under `errors`.

# Additional Context

- requires backport to v4.x

---------

Co-authored-by: Marco A. <[email protected]>
2026-03-16 14:30:17 +00:00
Wim Van LaerandGitHub 965e3fa621 fix: check whether org exists before creating user (#11647)
# Which Problems Are Solved

It was possible to create a user with an imaginary org.

# How the Problems Are Solved

Check whether org exists before creating the user.

# Additional Changes


# Additional Context

- Closes #11532
2026-03-16 13:12:21 +01:00
Marco A.andGitHub 3c3201e5a0 feat(session): Password Check API with relation tables (#11804)
# Which Problems Are Solved

As part of #11035 , this PR implements the password check logic for
session validation

# How the Problems Are Solved

- Add system config to default configuration of `domain` package for
easy initialization. Intialize the system settings when Zitadel starts
up
- Add password hasher verify logic to the default configuration of
`domain` package. Initialize it when Zitadel starts up.
  - Add settings repositories with their mocks
  - Implement the logic for doing a password check

# Additional Context

This is a cherry-picked PR + minor changes, coming from
https://github.com/zitadel/zitadel/pull/11164
- Relates to #11035
- Depends on https://github.com/zitadel/zitadel/pull/11777
2026-03-13 11:40:48 +01:00
d08779e279 feat: supporting metadata updates in actions v2 via RetrieveIdentityProviderIntent API (#11810)
# Which Problems Are Solved

Adds support for returning CreateUser/UpdateUser action payloads from
RetrieveIdentityProviderIntent so Actions v2 can update user metadata
(and other fields) using the v2 user APIs, ensuring parity with user
creation using Actions v2.

# How the Problems Are Solved

By:
- deprecating `AddHumanUser` and `UpdateHumanUser` fields in
`RetrieveIdentityProviderIntentResponse`
- adding a oneof field called `UserAction` with `CreateUser` and
`UpdateUser` fields to support user creation/update
- setting `UserAction` in the `RetrieveIdentityProviderIntentResponse`
for user creation/update

# Additional Changes
N/A

# Additional Context
- Related to https://github.com/zitadel/zitadel/issues/11369
- Follow-up for PRs https://github.com/zitadel/zitadel/pull/11719,
https://github.com/zitadel/zitadel/pull/11747
- Actions V2 example docs will be updated in a follow-up PR

---------

Co-authored-by: Copilot <[email protected]>
2026-03-12 16:01:58 +00:00
Livio SpringandGitHub 621fd9bdec test: fix matrix for relation table tests (#11757)
# Which Problems Are Solved

In #11239 , the helper function for matrix tests (without the relational
tables enabled) was changed, but the feature was never enabled in the
matrix. Some integration tests currently can't even succeed because of
missing implementations.

# How the Problems Are Solved

This PR correctly enables the feature where intended and returns the
state into the matrix.
This allows returning early for some tests which cannot succeed yet.
Corresponding todos have been re-added as well.

# Additional Changes

None

# Additional Context

 - relates to #11239
 - requires backport to v4.x
2026-03-12 06:24:40 +00:00
Tim MöhlmannandGitHub 64ee480f84 Merge commit from fork
When an expired invite code was used for webauthn (passkeys), the change date got updated by the failed event. This change date was used to test for expiry, meaning failed events would reset the expiry timeout.

This fix adds a Code Creation Date to the writemodel which gets set by the first event. This can be the added or requested event.

Other changes:

- Expiry renamed to CodeExpiry so it's consistent with similar write models using secret codes.
- humanVerifyPasswordlessInitCode takes an algorithm instead of the complete generator, so the method can be unit tested easier
- Added tests that reproduced the original issue
2026-03-11 08:25:21 +01:00
Livio SpringandGitHub 4a8a5e2fef Merge commit from fork 2026-03-11 08:21:42 +01:00
Livio SpringandGitHub ab51fd9563 Merge commit from fork 2026-03-11 08:12:46 +01:00
de1cab9976 refactor: merge relational table projections into one handler (#11740)
## Problem description

During tests we found concurrency issues in the relational table
projections. Tests got flaky if a table (e.g. organization domains) had
too many foreign keys to tables which were projected by another handler.

## Resolution

The projections of the relational tables got merged into one handler to
enforce sequential reduction of events.

## Additional info

- closes https://github.com/zitadel/zitadel/issues/11703
- closes https://github.com/zitadel/zitadel/issues/10810

---------

Co-authored-by: Marco Ardizzone <[email protected]>
2026-03-10 17:01:46 +00:00
62cb5bbb83 feat: delete metadata on SetOrganizationMetadata api (#11790)
# Which Problems Are Solved

Currently, to delete an Organization metadata key, callers must use a
separate
[DeleteMetadata](https://zitadel.com/docs/reference/api/org/zitadel.org.v2.OrganizationService.DeleteOrganizationMetadata)
API. This increases the complexity on client-side for operations where
metadata need to be synchronized.

# How the Problems Are Solved

Introduce deletion behavior to
[SetOrganizationMetadata](https://zitadel.com/docs/reference/api/org/zitadel.org.v2.OrganizationService.SetOrganizationMetadata):

When a metadata entry is passed with an empty value ("" / empty bytes):

- If the key exists → delete the key
- If the key does not exist → no-op (idempotent, no error)

# Additional Changes

n/a

# Additional Context

Relates to #11776

---------

Co-authored-by: Gayathri Vijayan <[email protected]>
Co-authored-by: Gayathri Vijayan <[email protected]>
2026-03-10 10:09:51 +00:00
3f258953ce test: add tests for recovery codes event reducers (#11773)
# Which Problems Are Solved

Add integration tests for recovery codes event reducers in the RT model.

# How the Problems Are Solved

Added integration tests for recovery codes event reducers

# Additional Changes

N/A

# Additional Context
- Closes https://github.com/zitadel/zitadel/issues/11707
- Todo: add tests for other reducers in the `users_relational`
projection.

---------

Co-authored-by: Livio Spring <[email protected]>
2026-03-09 10:40:02 +00:00
282b2486d9 feat: set metadata field in UpdateUser endpoint (#11747)
# Which Problems Are Solved

To be able to update metadata via the `UpdateUser` endpoint.

# How the Problems Are Solved
This is achieved by:
* adding the `Metadata` field to `UpdateUserRequest`
* update the server layer to convert/set metadata fields to domain layer
metadata
* update the command layer to handle metadata updates

# Additional Changes

N/A

# Additional Context
- Related to https://github.com/zitadel/zitadel/issues/11369,
https://github.com/zitadel/zitadel/issues/11759
- Follow-up for PR https://github.com/zitadel/zitadel/pull/11719

---------

Co-authored-by: Copilot <[email protected]>
2026-03-09 10:47:32 +01:00
247e278505 feat: delete metadata on SetUserMetadata api (#11776)
# Which Problems Are Solved

Currently, to delete metadata a key, callers must use a separate
[DeleteMetadata](https://zitadel.com/docs/reference/api/user/zitadel.user.v2.UserService.DeleteUserMetadata)
API. This increases the complexity on client-side for operations where
metadata need to be synchronized.

# How the Problems Are Solved

Introduce the behavior to
[SetUserMetadata](https://zitadel.com/docs/reference/api/user/zitadel.user.v2.UserService.SetUserMetadata):

When a metadata entry is passed with an empty value ("" / empty bytes):

- If the key exists → delete the key
- If the key does not exist → no-op (idempotent, no error)

# Additional Changes

- Updated [User Metadata
page](https://zitadel.com/docs/guides/manage/customize/user-metadata#manage-user-metadata-through-the-management-api)

# Additional Context

Similar behavior will be applied to
[UpdateUser](https://zitadel.com/docs/reference/api/user/zitadel.user.v2.UserService.UpdateUser)
and
[SetOrganizationMetadata](https://zitadel.com/docs/reference/api/org/zitadel.org.v2.OrganizationService.SetOrganizationMetadata)

Breaking change? 
Currently, sending an empty value returns an error — no client should be
intentionally relying on that error as part of a working workflow. The
change goes from "rejected input" to "accepted input with defined
semantics."

In other words, it relaxes a restriction rather than tightening one.
Existing valid calls continue to work exactly as before. The only
scenario where it could be "breaking" is if someone explicitly depends
on the error response for empty values (e.g., using it as a validation
check), which would be unusual.

---------

Co-authored-by: Gayathri Vijayan <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-03-06 14:57:52 +01:00
SilvanandGitHub 07dbb48cc7 refactor: relational organization projection to use repository (#11621)
- Transitioned from using `handler.Statements` to a repository pattern
for managing organization data in migrations.
- Introduced a new method to set the updated timestamp for
organizations.
- Updated the projection logic to utilize the repository for creating,
updating, and deleting organization records.
2026-03-05 14:55:42 +01:00
SilvanandGitHub 90faf440f4 refactor: relational identity provider table and projection (#11739) 2026-03-05 09:43:14 +00:00
5934e07960 fix: recover from request panics (#11713)
# Which Problems Are Solved

Panics may cause a service discruption by unexpectedly closing an
request's connection. Or in the case of gRPC completely killing the
service.

Allthough panics are still individual bugs that need to be solved, this
PR makes sure a panic is gracefully handled and an understandable error
is returned to the client.

# How the Problems Are Solved

- Recover in the middleware interceptors for the 3 API protocols (HTTP,
gRPC, connect).
- HTTP middleware uses formatted responses for:
  - OIDC errors (JSON formatted response)
  - UI (error page rendering)
  - SCIM
- Upon recovery an alert level log is printed (ERROR+4 for stdlib log
handlers)

# Additional Context

- internal observation

---------

Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Livio Spring <[email protected]>
2026-03-03 11:55:17 +00:00
Marco A.andGitHub bebee4c795 chore: naming consistency - Organization ID (#11733)
# Which Problems Are Solved

Renaming of:

  - Resource Owner
  - OrgID
  - OrganizationID
  - Organization Id

# How the Problems Are Solved

Find & Replace, evalue entries, is resource owner referring to an
organization ? If so, do change

# Additional Context

- Closes #11305
2026-03-03 10:24:44 +00:00
4aacfa842f feat: set metadata at the user level in CreateUser request (#11719)
# Which Problems Are Solved

Currently, metadata can be set/updated only for human users, but it
should be available also for service accounts (machine users).

# How the Problems Are Solved

This is achieved by:
- adding a metadata field at the root level in `CreateUserRequest`,
which makes it available for both `human` and `machine` user types
- returning an error when both the root level and human-level `metadata`
fields are set in the `CreateUserRequest`
- setting `human` and `machine` metadata from the root `metadata` field
in the server layer
- pushing `user.metadata.set` event during machine user creation in the
command layer
- adding integration tests

# Additional Changes
N/A

# Additional Context
- Related to https://github.com/zitadel/zitadel/issues/11369

---------

Co-authored-by: Vitor Bari Buccianti <[email protected]>
Co-authored-by: Silvan <[email protected]>
2026-03-03 08:34:43 +00:00
Marco A.andGitHub 3ebeb61b80 feat: test event reduction to relational tables (#11718)
# Which Problems Are Solved

This PR provides an example for testing DB reduction to relational
tables without exploiting integration tests.

# How the Problems Are Solved

- Set up a disposable embedded Postgres DB used to store the results of
the calls to the reducers and verify such results
- Add utility method to return transactions from the existing DB pool.
These transactions can be used by the single test cases to insert and
read data from DB
- Add a test to verify that the handler contains the expected reducers
(checking also that they are on the right aggregate type)
- Add tests to verify the single reduce functions. A parent test creates
the required objects to satisfy the foreign key constraints.

# Additional Changes

  - Typo fixed in `postgres.DecodeConfig()`
  - Discard unneeded `projectionName` parameter for `Executor` function
 
# Additional Context

- Closes #11430
2026-03-02 16:44:51 +00:00
SilvanandGitHub d3434dd46c fix(command): correct aggregate ID assignment of groups (#11725)
# Which Problems Are Solved

The group write model used the `event.ID` field instead of
`event.Aggregate().ID` as aggregate id.

# How the Problems Are Solved

replaced `event.ID` with `event.Aggregate().ID`
2026-03-02 09:57:26 +01:00
7a72e7207e fix(i18n): instance error translations in API (#11708)
# Which Problems Are Solved

#11351 renamed IAM to instance. This included also translation keys in
the backend. As most translation files already contained an `Instance`
in the error section, renaming `IAM` to `Instance` on the same level,
results in a duplicate key.

# How the Problems Are Solved

This PR merges the section to remove the duplictes.

# Additional Changes

None

# Additional Context

- relates to #11351 
- backport to v4.x

---------

Co-authored-by: Tim Möhlmann <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-02-27 10:25:33 +01:00
Livio SpringandGitHub 855bac2731 fix: revert renaming of (management) console feature flag (#11706)
# Which Problems Are Solved

#11390 renamed "Console" to "Management Console". This included a rename
of the feature key to enable the management console to use the V2 API
for user creation. Due to the rename generated events would also be
changed, resp. existing events would be ignored, which leads to
inconsistency in the data model.

# How the Problems Are Solved

Renamed the key back to `KeyConsoleUseV2UserApi`.
And added a comment to prevent future issues.

# Additional Changes

None

# Additional Context

- relates to #11390 
- backport to v4
2026-02-27 09:43:11 +01:00
Gayathri VijayanandGitHub fb35fdd3c7 feat: add recovery codes to user repository (#11702)
# Which Problems Are Solved

Adds a repository implementation to add/remove recovery codes to the
users relational table.

# How the Problems Are Solved

This is achieved by:
* adding the following columns to the users table: `recovery_codes`,
`recovery_code_last_successful_check`, `recovery_code_failed_attempts`
* adding the `RecoveryCodes` field to the `HumanUser` domain with fields
to set recovery `codes`, `lastSuccessfullyCheckedAt` timestamp, and
`failedAttempts`
* setting `recoveryCodes` in the `Get` user query statement to return a
json object with details related to the recovery codes
* adding the repository-layer implementation to add/remove recovery
codes and set fields related to recovery code checks.
* adding projection reducers to handle the following events:
`HumanRecoveryCodesAddedEvent`, `HumanRecoveryCodesRemovedEvent`,
`HumanRecoveryCodeCheckSucceededEvent`, and
`HumanRecoveryCodeCheckFailedEvent`
* adding unit tests

# Additional Changes
* fix the error message when the recovery is empty in
`internal/command/user_human_recovery_codes.go`
* add a new error message for empty recovery code during checks in
`en.yaml`

# Additional Context
- Closes https://github.com/zitadel/zitadel/issues/11212
- Follow-up: integration tests for the reducers will be added in a
different PR after this
[PR](https://github.com/zitadel/zitadel/pull/11478) is merged
2026-02-26 19:22:37 +01:00
e9e6ad4e17 fix(test): resolve nil-pointer panic in TestCommandSide_ChangeUserHuman (#11695)
# Which Problems Are Solved

1. `nx run @zitadel/api:test-unit` panics in
`TestCommandSide_ChangeUserHuman` due to two test cases (added in
0261536) missing the required `loginPaths` field. Since the field type
is `func(*testing.T) LoginPaths`, its zero value is `nil`, and calling
it causes a SIGSEGV.

2. Three targets in `apps/api/project.json` (`test-unit`, `build`,
`build-linux`) were silently non-cacheable because NX does not merge
`cache: true` from `targetDefaults` when a project-level target
overrides other properties like `dependsOn` or `inputs`.

3. `TestServer_AuthorizeOrDenyDeviceAuthorization` integration test is
flaky — it uses hardcoded `5*time.Second` timeouts for `EventuallyWithT`
polling, while the rest of the file uses
`WaitForAndTickWithMaxDuration(ctx, time.Minute)`. Under CI load, 5
seconds is insufficient and the empty ID cascades into a validation
error.

# How the Problems Are Solved

**Test panic fix:**
- Added missing `loginPaths: expectLoginPathsNoCall` to both broken test
cases ("change human email verified (self-management), not allowed" and
"change human phone verified (self-management), not allowed").

**NX cache fix:**
- Added explicit `"cache": true` to `test-unit`, `build`, and
`build-linux` targets in `apps/api/project.json`.
- Verified with `pnpm nx show project @zitadel/api --json` that all
three targets now resolve with `cache: true`.

**Integration test flakiness fix:**
- Replaced all 6 hardcoded `assert.EventuallyWithT(t, ...,
5*time.Second, 100*time.Millisecond)` calls in
`TestServer_AuthorizeOrDenyDeviceAuthorization` with
`require.EventuallyWithT(t, ..., retryDuration, tick)` using
`integration.WaitForAndTickWithMaxDuration(CTXLoginClient,
time.Minute)`.
- Changed from `assert` (non-fatal) to `require` (fatal) so timeout
failures stop the test immediately instead of cascading with empty IDs.

# Additional Context

- The broken unit test landed on main because CI skips `lint_test_build`
on pushes to main (`if: github.ref != 'refs/heads/main'`). A follow-up
issue was created: #11696.
- The integration test flakiness was missed by the previous fix in
#10752.

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Livio Spring <[email protected]>
2026-02-26 05:47:01 +00:00
Livio SpringandGitHub 0261536243 Merge commit from fork 2026-02-25 06:34:17 +01:00
Marco A.andGitHub b2532e9666 Merge commit from fork
* Inject DenyList from config to `StartCommands()`

* Implementation draft

* Move address checker to separate package

* Migrate usages of actions.AddressChecker to denylist.AddressChecker

* Rename denylist package files

* Pass []denylist.AddressChecker to StartCommand

* Add DenyList to defaults.yaml and add custom config parser

* net: add HostnameToIPList function

* denylist: Add IsHostBlocked()

* actions: use denylist.IsHostBlocked()

* command: Inject ip lookup function + extend add target validationt test

* command: Unexport ChangeTarget.IsValid()

* command: Add denyList check on ChangeTarget validation

* command: Export ActionsV2DenyList and IPLookupFunction params

* Lint fix

* Check denylist during action execution

* Fix integration tests

* Apply suggestions

* Add `mapstructure.StringToSliceHookFunc()` to decode `HTTPConfigDecodeHook`
2026-02-25 06:33:28 +01:00