feat: organizational identity domain verification and JIT provisioning - #462
Open
mfcarroll wants to merge 2 commits into
Open
feat: organizational identity domain verification and JIT provisioning#462mfcarroll wants to merge 2 commits into
mfcarroll wants to merge 2 commits into
Conversation
- Remove the users.org_id self-healing fallback from OrgRepository::get_member: it silently re-added removed members with the 'owner' role. Pre-migration users were backfilled by 0011_org_members.sql, and every membership-creating path inserts the row explicitly, so the fallback was purely a privilege escalation hazard. - Validate org membership on every authenticated request (session and API-key paths), so access is revoked immediately when a user is removed from an org instead of persisting until token expiry. - Resolve the session role from the database rather than the JWT claim, so role changes take effect without re-login. - Adjust the billing transfer test cleanup to switch the billing user's session back to their own org before removing their membership, since a session pointing at an org the user has left is now rejected.
Organizations on Business tier and above can verify ownership of their email domains via a DNS TXT challenge. Users signing in with an email on a verified org domain are automatically provisioned into that organization as a member: existing users gain membership, and brand-new users skip personal-org creation and land directly in the org. Backend: - org_domains table (migration 0041), OrgDomain model, and OrgDomainRepository; distinct from the custom link domains feature - OrgService domain methods with owner/admin + Business+ enforcement - Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain), named to avoid colliding with the custom-domains API from piffio#369 - DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side Cloudflare-nameserver hint (is_cloudflare) computed during list so the frontend needs no cross-origin DNS calls under the CSP - JIT provisioning in the OAuth flow for all three login paths; matching pending invitations are auto-accepted, and explicitly invited users may sign in even when public signups are disabled - Invite flow: already-members (e.g. via JIT) get a graceful accept that upgrades their role if the invitation offers a different one, and invite info reports is_member so the UI can skip the accept step Frontend: - OrgDomains management card on the org page (owner + Business+), with TXT record instructions, copy button, verify and remove actions - Invite page routes already-members straight to the dashboard Includes integration tests for the org-domain endpoints and an OpenAPI spec regeneration.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #216.
Organizations on Business tier and above verify ownership of their organizational identity domain(s) via a DNS TXT challenge. Users signing in with an email on a verified domain are auto-provisioned into that org as a member (existing users gain membership, brand-new users skip personal-org creation).
Scope vs the 216 proposal: this builds the DNS-verification path (Option A) and the auto-join flow. OIDC (Option B) and home realm discovery are left as future work as described in 216. One deliberate divergence: auto-join is based on the verified email domain for any provider not the Google
hdclaim I'd originally imagined. This is broader, covering GitHub SSO now and other potential future sign in methods as well.Naming: routes are
/api/orgs/:id/org-domains(+verify-org-domain), not/domainsas that was taken by the custom link-domains feature. These are the org's identity domains, as opposed to the link-serving hostnames. It's a distinct table (org_domains).Commit 1 is org-membership hardening that this feature depends on
OrgRepository::get_member'susers.org_idself-heal fallback would silently mint anownermembership row for such a user which is a privilege escalation. (On the current main that fallback is effectively dead code as nothing setsusers.org_idwithout also inserting the membership row, so it never fires. But... JIT is precisely the flow that would trip it.) This removes the fallback so membership creation is always explicit.Commit 2 is the actual feature
utils/dns.rs). List endpoint computes anis_cloudflarehint server-side so the frontend needs no cross-origin DNS calls under the CSP.Running in production on my work instance, including the auto-join flow and signups-disabled bypass for verified domains.