Skip to content

fix(iam): waitlist mails wait for a verified email address (Phase C, C11 reader) - #784

Merged
yahyafakhroji merged 1 commit into
mainfrom
feat/passkey-phase-c-reader
Sep 17, 2026
Merged

yahyafakhroji merged 1 commit into
mainfrom
feat/passkey-phase-c-reader

Conversation

@yahyafakhroji

@yahyafakhroji yahyafakhroji commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Phase C of the passkey program (datum-cloud/auth-ui#112), milo PR 2 of 2 — C11 reader.

What

One guard in user_waitlist_controller.go: before sending, require the User's EmailVerification status field to be Verified.

  • Both mails wait — the Approved (welcome) mail and the Rejected mail. Every mail this controller sends.
  • Not Verified (empty or Unverified) ⇒ log waiting for email verification and return ctrl.Result{}, nil without requeue. The controller already watches User, so the field flip re-triggers reconcile; polling would be redundant.
  • Pending stays disabled — unchanged, it still returns early on the pre-existing empty-condition path.
  • No feature flag (spec §2.1 Q8). The deploy is sequenced by the rollout rule below, not by code.

The exactly-once sent-condition guard, the deterministic Email name and the second idempotency layer are untouched — the controller diff is +16/−0. The gate sits deliberately after the sent-condition check, so users already mailed before this change short-circuit exactly as they do today rather than logging "waiting" on every resync. The mail goes out once, when both Approved and Verified hold, whichever arrives last.

Testsuser_waitlist_controller_test.go, this controller's first, four fake-client cases:

  1. approved + unverified (field empty and explicitly Unverified) sends nothing, does not requeue, and does not set the sent-condition;
  2. approved + verified sends exactly once across two reconciles;
  3. verification arriving after approval sends once, at the flip;
  4. rejected follows the same rule in both directions.

Why

user_waitlist_controller sent the welcome mail on PlatformAccess: Approved alone. An account that never verified its email address received the welcome mail (milo-os/zitadel-provider#134, gap 2), because nothing on User said whether the address had been proven — the chain from PlatformAccess through Status.PlatformAccess to getEmailStatusCondition had no verification signal to consult. #782 adds that signal as a typed status field; this PR reads it.

Spec: 2026-09-08-passkey-phase-c-recovery-design.md §7.2.

Rollout

Safe to MERGE now: nothing here depends on zitadel-provider code, only on the EmailVerificationState constants from #782.

NOT safe to DEPLOY before milo-os/zitadel-provider#135's sweeper has completed one pass in the target environment — until then existing users carry an empty field and every welcome/rejection mail would wait.

Stacking

Stacks on #782 (feat/passkey-phase-c-api), which carries the EmailVerification status field this PR consumes. Retarget to main when #782 merges.

E2E

No chainsaw fixture changed. Across all 37 chainsaw cases, none asserts a notification.miloapis.com Email object, and none drives PlatformAccess past Pending — every state: in test/ is Pending, which returns early on the pre-existing empty-condition path, before the new gate is ever evaluated. So no chainsaw outcome can change. The controller's coverage is the new unit tests.

Review

Sonnet review: APPROVE, no findings to fix; the gate was mutation-tested.

JoseSzycho
JoseSzycho previously approved these changes Sep 14, 2026

@JoseSzycho JoseSzycho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice touch. Good that you noticed this.

@yahyafakhroji
yahyafakhroji deleted the branch main September 14, 2026 15:25
@yahyafakhroji yahyafakhroji reopened this Sep 14, 2026
@yahyafakhroji
yahyafakhroji changed the base branch from feat/passkey-phase-c-api to main September 14, 2026 15:26
@yahyafakhroji
yahyafakhroji dismissed JoseSzycho’s stale review September 14, 2026 15:26

The base branch was changed.

@yahyafakhroji
yahyafakhroji force-pushed the feat/passkey-phase-c-reader branch from c5e1094 to 7ed9da1 Compare September 17, 2026 00:58
@yahyafakhroji
yahyafakhroji merged commit e310f0c into main Sep 17, 2026
6 checks passed
@yahyafakhroji
yahyafakhroji deleted the feat/passkey-phase-c-reader branch September 17, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants