feat(rt): IDP Intents Relational Table (#11374)

# Which Problems Are Solved

In the context of moving our datastore to relational tables, Identity
Provider Intents have no definition of a relational table nor any DB API
that can be used to store the IDP intents as relational objects.

# How the Problems Are Solved

The PR follows the guidelines left in #11190

- A table `zitadel.identity_provider_intents` is created: it defines its
relational constraints (foreign keys) with the other tables, as well as
an ENUM to represent the state of the intent.
- An IDP Intent repository has been created, which offers CRUD
operations implemented as `Create`, `Get`, `Update` and `Delete` methods
for the IDP intent.
- The repository also defines all columns and conditions useful for
performing DB operations on the IDP intents.
- DB mocks have been added as well to ease testing on the `domain`
package.
- Reduction functions have been implemented for the existing IDP events
so to keep the relational table in sync with the event store.

# Additional Changes

To allow testing LDAP intent flow, the `Session.FetchUser()` method has
been extracted to a separate file. A corresponding mock has been created
as well in `fetch_user_mock.go`. The files are mutually excluded and
added at build time through go build tags: the mock is added when the
`integration` tag is specified, while the real implementation is added
when the same tag IS NOT added to the build tags.

# Additional Context

- Closes #11190

---------

Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Silvan <[email protected]>
This commit is contained in:
Marco A.
2026-02-05 03:44:07 +00:00
committed by GitHub
co-authored by Livio Spring Silvan
parent ca9c7619c6
commit 4936bdf2c9
23 changed files with 4570 additions and 471 deletions
@@ -0,0 +1,16 @@
package migration
import (
_ "embed"
)
var (
//go:embed 012_identity_provider_intents_table/up.sql
up012IDPIntentsTable string
//go:embed 012_identity_provider_intents_table/down.sql
down012IDPIntentsTable string
)
func init() {
registerSQLMigration(12, up012IDPIntentsTable, down012IDPIntentsTable)
}
@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS zitadel.identity_provider_intents;
DROP TYPE IF EXISTS zitadel.idp_intent_state;
@@ -0,0 +1,44 @@
CREATE TYPE zitadel.idp_intent_state AS ENUM (
'started',
'succeeded',
'failed',
'consumed'
);
CREATE TABLE zitadel.identity_provider_intents
(
instance_id TEXT NOT NULL,
id TEXT NOT NULL CHECK ( id <> '' ),
state zitadel.idp_intent_state NOT NULL DEFAULT 'started',
success_url TEXT,
failure_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
idp_id TEXT NOT NULL,
idp_arguments JSONB,
idp_user BYTEA,
idp_user_id TEXT,
idp_username TEXT,
user_id TEXT,
idp_access_token JSONB,
idp_id_token TEXT,
idp_entry_attributes JSONB,
request_id TEXT,
assertion JSONB,
succeeded_at TIMESTAMPTZ,
fail_reason TEXT,
failed_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
PRIMARY KEY (instance_id, id),
-- TODO(@adlerhurst): easiest would be to create the index in the user migrations so that we can merge this one but the problem is that the migration might fail if there are already intents created.
-- FOREIGN KEY (instance_id, user_id) REFERENCES zitadel.users (instance_id, id) ON DELETE CASCADE,
FOREIGN KEY (instance_id, idp_id) REFERENCES zitadel.identity_providers (instance_id, id) ON DELETE CASCADE
);
CREATE TRIGGER trigger_set_updated_at
BEFORE UPDATE
ON zitadel.identity_provider_intents
FOR EACH ROW
WHEN (NEW.updated_at IS NULL)
EXECUTE FUNCTION zitadel.set_updated_at();