Add APP_EMAIL_ALLOWLIST gate on account creation
Build / build-and-push (push) Successful in 14s

An optional comma-separated list of glob patterns restricting which
addresses may register, applied on top of APP_ALLOW_REGISTRATION. A
non-matching new address is silently ignored exactly like registration
being off; an address that already has an account can still sign in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-06 21:26:33 +01:00
co-authored by Claude Sonnet 5
parent 06d5b721a3
commit 8732e0e5f5
9 changed files with 120 additions and 7 deletions
+40
View File
@@ -117,4 +117,44 @@ final class AuthTest extends ApiTestCase
self::assertSame(202, $response->getStatusCode());
self::assertSame('ada@example.com', $this->lastEmail()['to']);
}
public function test_the_email_allowlist_blocks_a_new_address_that_does_not_match(): void
{
$this->reconfigure(['APP_EMAIL_ALLOWLIST' => '*@example.com, someone@gmail.com']);
$response = $this->request('POST', '/api/auth/magic-link', ['email' => 'ada@other.test']);
// Same silent no-op as registration being off -- no enumeration signal.
self::assertSame(202, $response->getStatusCode());
self::assertSame([], $this->sentEmails());
self::assertSame(0, (int) $this->db()->query('SELECT COUNT(*) FROM users')->fetchColumn());
}
public function test_the_email_allowlist_lets_a_matching_new_address_register(): void
{
$this->reconfigure(['APP_EMAIL_ALLOWLIST' => '*@example.com, someone@gmail.com']);
$this->request('POST', '/api/auth/magic-link', ['email' => 'Ada@example.com']);
$this->request('POST', '/api/auth/magic-link', ['email' => 'someone@gmail.com']);
self::assertSame(
['ada@example.com', 'someone@gmail.com'],
array_column($this->sentEmails(), 'to'),
);
}
public function test_the_email_allowlist_does_not_block_an_existing_user(): void
{
$this->request('POST', '/api/auth/magic-link', ['email' => 'ada@other.test']);
$this->request('POST', '/api/auth/verify-email', ['token' => $this->tokenFromEmail()]);
$this->db()->prepare('UPDATE users SET verification_email_sent_at = NULL WHERE email = :e')
->execute(['e' => 'ada@other.test']);
$this->reconfigure(['APP_EMAIL_ALLOWLIST' => '*@example.com']);
$response = $this->request('POST', '/api/auth/magic-link', ['email' => 'ada@other.test']);
self::assertSame(202, $response->getStatusCode());
self::assertSame('ada@other.test', $this->lastEmail()['to']);
}
}