feat: add email domain restriction for signups (#2266)

Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
This commit is contained in:
Ephraim Duncan
2026-03-14 05:32:34 +00:00
committed by GitHub
parent 3106fd7483
commit 66e357c9b3
16 changed files with 194 additions and 81 deletions
@@ -17,6 +17,7 @@ export const AuthenticationErrorCode = {
// TwoFactorMissingSecret: 'TWO_FACTOR_MISSING_SECRET',
// TwoFactorMissingCredentials: 'TWO_FACTOR_MISSING_CREDENTIALS',
InvalidTwoFactorCode: 'INVALID_TWO_FACTOR_CODE',
SignupDisabled: 'SIGNUP_DISABLED',
// IncorrectTwoFactorBackupCode: 'INCORRECT_TWO_FACTOR_BACKUP_CODE',
// IncorrectIdentityProvider: 'INCORRECT_IDENTITY_PROVIDER',
// IncorrectPassword: 'INCORRECT_PASSWORD',
@@ -3,8 +3,11 @@ import { OAuth2Client, decodeIdToken } from 'arctic';
import type { Context } from 'hono';
import { deleteCookie } from 'hono/cookie';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { isEmailDomainAllowedForSignup } from '@documenso/lib/constants/auth';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { onCreateUserHook } from '@documenso/lib/server-only/user/create-user';
import { env } from '@documenso/lib/utils/env';
import { deletedServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/deleted-account';
import { legacyServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/legacy-service-account';
import { isValidReturnTo, normalizeReturnTo } from '@documenso/lib/utils/is-valid-return-to';
@@ -114,6 +117,24 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti
return c.redirect(redirectPath, 302);
}
// Check if signups are disabled.
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
const errorUrl = new URL('/signin', NEXT_PUBLIC_WEBAPP_URL());
errorUrl.searchParams.set('error', AuthenticationErrorCode.SignupDisabled);
return c.redirect(errorUrl.toString(), 302);
}
// Check domain restriction for new SSO users.
if (!isEmailDomainAllowedForSignup(email)) {
const errorUrl = new URL('/signin', NEXT_PUBLIC_WEBAPP_URL());
errorUrl.searchParams.set('error', AuthenticationErrorCode.SignupDisabled);
return c.redirect(errorUrl.toString(), 302);
}
// Handle new user.
const createdUser = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
+14 -3
View File
@@ -6,6 +6,7 @@ import { HTTPException } from 'hono/http-exception';
import { DateTime } from 'luxon';
import { z } from 'zod';
import { isEmailDomainAllowedForSignup } from '@documenso/lib/constants/auth';
import { EMAIL_VERIFICATION_STATE } from '@documenso/lib/constants/email';
import { AppError } from '@documenso/lib/errors/app-error';
import { jobsClient } from '@documenso/lib/jobs/client';
@@ -122,7 +123,11 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
const is2faEnabled = isTwoFactorAuthenticationEnabled({ user });
if (is2faEnabled) {
const isValid = await validateTwoFactorAuthentication({ backupCode, totpCode, user });
const isValid = await validateTwoFactorAuthentication({
backupCode,
totpCode,
user,
});
if (!isValid) {
await prisma.userSecurityAuditLog.create({
@@ -178,8 +183,8 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
const requestMetadata = c.get('requestMetadata');
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
throw new AppError('SIGNUP_DISABLED', {
message: 'Signups are disabled.',
throw new AppError(AuthenticationErrorCode.SignupDisabled, {
statusCode: 400,
});
}
@@ -196,6 +201,12 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
res: signupLimited,
});
}
if (!isEmailDomainAllowedForSignup(email)) {
throw new AppError(AuthenticationErrorCode.SignupDisabled, {
statusCode: 400,
});
}
const user = await createUser({ name, email, password, signature }).catch((err) => {
console.error(err);