Skip to content

feat(auth): add optional ALLOWED_EMAILS sign-in allowlist - #36

Merged
diwenne merged 1 commit into
diwenne:mainfrom
paularro:feat/signin-allowlist
Aug 27, 2026
Merged

feat(auth): add optional ALLOWED_EMAILS sign-in allowlist#36
diwenne merged 1 commit into
diwenne:mainfrom
paularro:feat/signin-allowlist

Conversation

@paularro

Copy link
Copy Markdown
Contributor

A self-hosted instance on a public domain is open to signup. The email provider creates an account for whoever asks for a magic link, and events.createUser gives that account its own workspace. So anyone who finds the URL of an instance can register on it.

That is the right default for a hosted product, but most people following docs/setup.md are running this for their own accounts, where it is just an open door. There is currently no way to close it short of putting the whole app behind basic auth, which breaks the webhook and the OAuth callback.

Change

ALLOWED_EMAILS, a comma-separated, case-insensitive allowlist:

ALLOWED_EMAILS=you@example.com,teammate@example.com

Checked in the signIn callback, which NextAuth runs before the magic link is sent, so a blocked address never receives one and no User row is created.

Left unset, nothing changes. The helper returns true when the list is empty, so existing deployments are unaffected and the variable is purely opt-in.

The helper lives in lib/env.ts next to the other env accessors and reads process.env per call, matching getMetaGraphApiVersion rather than caching at module load. That is what makes it testable with vi.stubEnv.

Also documents it in .env.example and in the environment table in docs/setup.md.

Tests

Five cases added to __tests__/env.test.ts: unset list, list of only separators, listed vs unlisted address, case and whitespace on both sides, and a null/undefined/empty address while the list is set.

Test Files  14 passed (14)
     Tests  150 passed (150)

tsc --noEmit and eslint clean.

Behaviour when blocked

Verified against a live instance. A non-listed address redirects to /api/auth/error?error=AccessDenied and creates no user row; a listed address passes the callback and proceeds to the mail provider as normal.

A self-hosted instance on a public domain is open to signup: the email
provider creates an account for whoever asks for a magic link, and that
account gets its own workspace. On an instance run for a single operator
that is worth closing.

ALLOWED_EMAILS is a comma-separated, case-insensitive allowlist checked in
the signIn callback, so a blocked address never even receives a magic link.
Left unset the behaviour is unchanged, so existing deployments are not
affected.

The helper lives in lib/env.ts next to the other env accessors and reads
the variable per call, matching getMetaGraphApiVersion, which keeps it
testable with vi.stubEnv.
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

@paularro is attempting to deploy a commit to the diwenne's projects Team on Vercel.

A member of the Team first needs to authorize it.

@diwenne
diwenne merged commit 97e2775 into diwenne:main Aug 27, 2026
1 check failed
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