Skip to content

feat(identity): add read-only Passkey kind and passkey/email login providers - #727

Merged
yahyafakhroji merged 10 commits into
mainfrom
feat/passkey-identity-kind
Jul 20, 2026
Merged

feat(identity): add read-only Passkey kind and passkey/email login providers#727
yahyafakhroji merged 10 commits into
mainfrom
feat/passkey-identity-kind

Conversation

@yahyafakhroji

Copy link
Copy Markdown
Contributor

Summary

Phase A groundwork for datum-cloud/enhancements#738 (passkey support — management + login first, email+passkey signup second, per the discussion on the issue):

  1. LastLoginProvider enum: adds passkey and email to the allowed values on UserStatus so status writes don't get rejected by validation once passkey logins happen.
  2. New read-only Passkey virtual kind (identity.miloapis.com/v1alpha1): list/get only, served by the aggregated apiserver and backed by the auth provider via a new --passkeys-provider-url flag, following the UserIdentity/Session pattern (REST storage, DynamicProvider, StorageProvider wiring). The cloud portal will use it for a display-only passkeys card.

Everything is behind a new alpha feature gate (default off) — zero runtime change until the gate is enabled.

Design notes for reviewers

  • Read-only by construction: the storage implements Lister/Getter only (no GracefulDeleter/Creater/Updater). Passkey mutations (enroll/remove) live in auth-ui behind step-up re-auth, mirroring how SSO account linking works today.
  • status.userUID + field selector: registered exactly like Session/UserIdentity so self-scoped list works via X-Remote-Uid and cross-user reads go through the existing SAR path (staff support views).
  • metadata.name = Zitadel passkey ID; status.displayName; status.state: Active|Inactive.
  • Generated artifacts (CRD yaml, deepcopy, OpenAPI, docs/api/iam.md) are all regenerated — task generate produces no diff on this branch.

Testing

  • go build ./... clean; 12 new/updated tests across 7 packages, all passing (enum CRD validation, deepcopy independence, field-selector parity, REST list/get behavior, provider wiring, feature gate).
  • Smoke-tested shape against the local kind stack pattern; live smoke on the shared test-infra cluster is part of the test plan below.

Test plan

  • CI green
  • After merge: cut the next release so the auth provider repo can bump its dependency and add the passkeys REST backend (its provider-side PR is ready to go once the types are importable)
  • Local stack smoke with the gate enabled: task dev:redeploytask kubectl -- get passkeys (expect clean empty list)

Comment thread cmd/milo/apiserver/server_test.go Outdated

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.

is this file necessary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed. You're right — none of the other provider flags have a registration test, so this file didn't add value.

Comment thread cmd/milo/apiserver/server.go
@yahyafakhroji
yahyafakhroji force-pushed the feat/passkey-identity-kind branch from 79b4387 to caa1228 Compare July 20, 2026 14:26
@yahyafakhroji
yahyafakhroji requested a review from JoseSzycho July 20, 2026 14:27
@JoseSzycho

Copy link
Copy Markdown
Contributor

@yahyafakhroji could you validate this works correctly in staging before we merge it into main?

@scotwells

Copy link
Copy Markdown
Contributor

We should try testing this in a local environment so we can confirm the whole experience works end-to-end. We shouldn't need to use staging for this kind of testing.

@yahyafakhroji

Copy link
Copy Markdown
Contributor Author

@scotwells @JoseSzycho about the staging question — I haven't tested this on staging, because right now it can't really run there. The feature is behind a gate that's off by default, so even if we deploy this PR, nothing changes yet. Also, the passkey data comes from zitadel-provider, and that part can only be built after this PR is merged and released. So the order is: merge this → release → build the provider side → then turn it on in staging.

What I tested so far (agree with Scot — this was all in the local environment):

  • unit tests on this PR (all green, re-run after the rebase)
  • ran it on my local cluster (same setup shape as staging) with the gate turned on — the new passkeys resource shows up, everything else still works fine, no crashes

One small thing I found while testing: if the gate is on but the provider URL is not set, listing passkeys throws an error. Sessions and useridentities already behave the same way today, so it's not from this PR. I'll create a follow-up to give all three a proper error message.

Once the zitadel-provider side is ready, we can do the full end-to-end check (actual passkeys showing up in the portal) — locally first, then staging as the final check.

@scotwells

Copy link
Copy Markdown
Contributor

@yahyafakhroji we should be able to put all components (zitdel-provider, zitadel, milo, auth-ui, etc) all into a single local development environment and prove that everything works end-to-end before we merge any changes.

@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.

Everything looks ok. The dynamic and rest code matches the exact pattern we use with the other identities.

The API follows the same naming conventions and code is correctly formatted and code is generated.

Good work.

Do not forget to enable the feature-gate from the infra repo

@yahyafakhroji
yahyafakhroji merged commit c6db190 into main Jul 20, 2026
6 checks passed
@yahyafakhroji
yahyafakhroji deleted the feat/passkey-identity-kind branch July 20, 2026 23:52
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.

3 participants