feat(auth): domain default grants — auto-grant a per-app role at login - #2
Open
honzasladek wants to merge 2 commits into
Open
feat(auth): domain default grants — auto-grant a per-app role at login#2honzasladek wants to merge 2 commits into
honzasladek wants to merge 2 commits into
Conversation
…n (DOMAIN_DEFAULT_GRANTS)
A domain-admitted human (HUMAN_EMAIL_DOMAINS) self-provisions with zero
grants, so every app action still 403s until an admin grants a role by
hand. New opt-in deploy var PROPUSTKA_DOMAIN_DEFAULT_GRANTS
([{domain, app, role}]) closes that gap: /auth/callback ensures the
configured role grants (grant-if-missing, unscoped, non-expiring) for
every entry matching the verified email's domain.
- Ensured at EVERY login, not just the first — config additions reach
existing principals on their next sign-in. Removing an entry stops
future ensures but does not revoke already-granted rows.
- Skips when the principal already holds the role for the app (granted
here earlier or by an admin) — no duplicate rows.
- granted_by stays NULL (FK to principals; no human grantor).
- The role need not exist yet — resolution ignores unknown roles until
the app reconciles its schema.
- A grant failure logs a warning but never blocks the login.
- Default '[]' everywhere = feature off; no behavior change for
existing deployments.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… shadowing Two review follow-ups: the domain compare ignores email casing, and a SCOPED grant of the same role deliberately shadows the default (the ensure must not widen an admin-narrowed user to an unscoped grant). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
What this solves
HUMAN_EMAIL_DOMAINSalready admits anyone on a company domain (self-provisioning at/auth/callback), but the new principal is created with zero grants — so every app action (.require(action)) keeps returning 403 until an admin hands out a role manually.This PR adds an opt-in deploy var
PROPUSTKA_DOMAIN_DEFAULT_GRANTS:[{ "domain": "contember.com", "app": "revizor", "role": "user" }]On every successful login, a user whose email domain matches gets the corresponding role grants ensured idempotently (grant-if-missing) — "everyone on the company domain is a user of app X" without manual granting.
Behavior / deliberate tradeoffs
app IS NULL) shadows the default as well.idx_grants_uq_globalunique index from migration 0003 (it covers exactly this grant shape) — the second INSERT fails on the unique violation, the callback swallows it, and no duplicate can appear.granted_bystaysNULL— it is an FK toprincipalsand this grant has no human grantor (same as migrated rows).rolesyet — resolution simply skips an unknown role; it takes effect once the app reconciles its schema.console.warn).OIDC_REQUIRE_VERIFIED_EMAIL='false'this feature raises the impact of an unverified email from "principal with zero grants" to "principal with a role" — the same exposure asHUMAN_EMAIL_DOMAINS, one step further.'[]'everywhere (local dev, oblaka, workflow) = feature off; no behavior change for existing deployments.Changes
env.ts/services.ts— new JSON-typed var + parser (DomainDefaultGrant), domains lowercased at parse time, malformed input →[](fail-safe for deployments without the var too)auth/routes.ts—ensureDomainDefaultGrants()inhandleCallback, awaited before the 302 (the app's first request follows the redirect immediately)oblaka.ts+.github/workflows/deploy.yml— threadPROPUSTKA_DOMAIN_DEFAULT_GRANTSthroughauth-routes.test.ts(grant + attributes, idempotency, existing admin grant, scoped-grant shadowing, outside the domain, email-casing, multiple entries, empty config)bun test307/307, typecheck clean across all packages, dprint/biome unchanged.🤖 Generated with Claude Code