Skip to content

feat(auth): allow sign-in by verified email domain, not just login allowlist#107

Merged
danielnaab merged 1 commit intomainfrom
feat/auth-email-domain-allowlist
Apr 20, 2026
Merged

feat(auth): allow sign-in by verified email domain, not just login allowlist#107
danielnaab merged 1 commit intomainfrom
feat/auth-email-domain-allowlist

Conversation

@danielnaab
Copy link
Copy Markdown
Member

Previously the only path to authorization was a comma-separated GitHub login allowlist in `ALLOWED_USERS`, defaulting to `danielnaab`. A Flexion colleague whose GitHub login wasn't on that list got redirected with `error=unauthorized` — fine for a solo dev project but the wrong default for a team resource.

Change

A user now passes authorization if their GitHub login is in `ALLOWED_USERS` or any of their verified GitHub emails matches a domain in `ALLOWED_EMAIL_DOMAINS`. Either mechanism alone is sufficient; evaluated in that order so a personal-dev login still works on an instance with a strict corporate-domain policy.

Details:

  • New `fetchUserEmails(token)` → `/user/emails`. Requires `user:email` OAuth scope, which is now requested at sign-in alongside the existing `read:user read:org`.
  • Empty-list fallback on 404 (scope missing) rather than throw — the login allowlist still covers the user.
  • New `hasAllowedEmailDomain(emails, domains)` — case-insensitive exact domain match, verified emails only. Explicitly rejects substring matches (`not-flexion.us` is not admitted when the allowed domain is `flexion.us`).
  • Auth callback logs which path authorised the user so operators can audit who's getting in via domain vs. login.

Defaults

  • `ALLOWED_USERS=danielnaab` (unchanged).
  • `ALLOWED_EMAIL_DOMAINS` unset by default — no change in default behaviour on instances that don't set it.

Production rollout

`infrastructure/nixos/modules/deploy.nix` now sets `ALLOWED_EMAIL_DOMAINS=flexion.us` in the per-branch env file. Takes effect after `bun run cli nixos apply` on the EC2 instance and a branch redeploy. Existing users aren't affected — the login allowlist is still checked first.

Testing

  • `bun run check` — 1341 tests pass.
  • New unit tests for `fetchUserEmails` (happy path, scope-missing 404 fallback) and `hasAllowedEmailDomain` (verified/unverified, case, multi-domain, substring rejection, empty-list rejection).
  • Existing `auth-routes` scope assertion updated to the new scope string (`read:user read:org user:email`).

Security notes

  • Only `verified: true` emails count. A user can't claim an arbitrary domain they don't control because GitHub verifies by sending a confirmation email to that address.
  • The matcher does exact domain equality, not suffix matching. `evil.flexion.us` or `not-flexion.us` are rejected.
  • The existing session-cookie encryption and HttpOnly/SameSite=Lax flags are unchanged.

…lowlist

Previously the only authorization path was a comma-separated GitHub
login allowlist in \`ALLOWED_USERS\`, defaulting to \`danielnaab\`. That
meant even for colleagues at flexion, the operator had to know and
add each GitHub username up front — a Flexion employee whose login
wasn't on the list got redirected with \`error=unauthorized\`.

Adds a complementary path: \`ALLOWED_EMAIL_DOMAINS\`. A user passes
authorization if their GitHub login is in \`ALLOWED_USERS\` OR any of
their verified GitHub emails matches a domain in
\`ALLOWED_EMAIL_DOMAINS\`. Either alone is sufficient — personal-dev
logins still work on an instance with a strict corporate-domain
policy.

Mechanics:

- New \`fetchUserEmails(token)\` hits \`/user/emails\`. Requires the
  \`user:email\` OAuth scope, added to the signin redirect.
- Empty-list response on 404 (scope missing) rather than throw — the
  login allowlist can still cover the user.
- New \`hasAllowedEmailDomain(emails, domains)\` — case-insensitive
  exact domain match, verified emails only. Explicitly does not
  admit substring matches (\`not-flexion.us\` is rejected when the
  allowed domain is \`flexion.us\`).
- Auth callback checks login first, then falls through to email
  domain only if needed. Logs which path authorised the user.

Defaults:

- \`ALLOWED_USERS=danielnaab\` (unchanged)
- \`ALLOWED_EMAIL_DOMAINS=''\` (empty — no change in default behaviour)

Production deployment:

- NixOS per-branch env file now sets \`ALLOWED_EMAIL_DOMAINS=flexion.us\`.
- Takes effect on next \`bun run cli nixos apply\` + branch redeploy.

Tests added for \`fetchUserEmails\` (happy path, 404 fallback) and
\`hasAllowedEmailDomain\` (match, case, unverified rejection, empty
list, substring rejection). Existing auth-routes scope assertion
updated to the new scope string.
@danielnaab danielnaab merged commit 5d1612c into main Apr 20, 2026
4 checks passed
@danielnaab danielnaab deleted the feat/auth-email-domain-allowlist branch April 20, 2026 18:53
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.

1 participant