diff --git a/scripts/iam/zitadel-provision.py b/scripts/iam/zitadel-provision.py index 8ecccc0..eca1409 100755 --- a/scripts/iam/zitadel-provision.py +++ b/scripts/iam/zitadel-provision.py @@ -299,6 +299,24 @@ def _ensure_project(token: str, name: str) -> str: return project_id +def _ensure_builtin_login(token: str) -> None: + """Force the built-in login page instead of the hosted Login UI. + + The hosted UI authenticates as the machine user `login-client`, which the + chart creates only from its one-shot setup job. When that user is missing, + every session write fails with "Could not create session for user" and no + one can sign in, at ZITADEL or at any relying party. + + Disabling the loginV2 feature makes the server serve its own login page, + which needs no machine user. This is instance state in the database, not a + values key, so it must be applied explicitly. + """ + code, d = _api("PUT", "/v2/features/instance", token, {"loginV2": {"required": False}}) + if code not in (200, 201): + die(f"disable loginV2 failed: {code} {d}") + print("login page : built-in (loginV2.required=false)") + + def _ensure_roles(token: str, project_id: str, roles: dict[str, str]) -> None: code, d = _api("POST", f"/management/v1/projects/{project_id}/roles/_search", token, {}) have = {r.get("roleKey") for r in (d.get("result") or [])} @@ -356,6 +374,7 @@ def phase_apply(_args: argparse.Namespace) -> int: if not token: die("no iam-admin PAT — run the keys phase first") + _ensure_builtin_login(token) project_id = _ensure_project(token, PROJECT_NAME) print(f"project : {PROJECT_NAME} ({project_id})") _ensure_roles(token, project_id, ROLES)