mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
feat: add granular signup disable flags (#2765)
This commit is contained in:
+9
-1
@@ -160,8 +160,16 @@ NEXT_PRIVATE_REDIS_PREFIX="documenso"
|
||||
NEXT_PUBLIC_POSTHOG_KEY=""
|
||||
# OPTIONAL: Leave blank to disable billing.
|
||||
NEXT_PUBLIC_FEATURE_BILLING_ENABLED=
|
||||
# OPTIONAL: Leave blank to allow users to signup through /signup page.
|
||||
# OPTIONAL: Set to "true" to disable all signup methods (email, Google, Microsoft, OIDC, including the organisation OIDC portal).
|
||||
NEXT_PUBLIC_DISABLE_SIGNUP=
|
||||
# OPTIONAL: Set to "true" to disable email/password signup only.
|
||||
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP=
|
||||
# OPTIONAL: Set to "true" to block new-account creation through Google. Existing linked users can still sign in.
|
||||
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP=
|
||||
# OPTIONAL: Set to "true" to block new-account creation through Microsoft. Existing linked users can still sign in.
|
||||
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=
|
||||
# OPTIONAL: Set to "true" to block new-account creation through OIDC (including the organisation portal).
|
||||
NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=
|
||||
# OPTIONAL: Comma-separated list of email domains allowed to sign up (e.g., example.com,acme.org).
|
||||
NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=
|
||||
# OPTIONAL: Set to true to use internal webapp url in browserless requests.
|
||||
|
||||
@@ -225,27 +225,40 @@ For detailed certificate setup, see [Signing Certificate](/docs/self-hosting/con
|
||||
## Feature Flags
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Disable public user registration entirely | `false` |
|
||||
| -------------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Master switch. Disable all signup methods application-wide | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP` | Disable email/password signup only. SSO signup is unaffected | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP` | Block new accounts via Google. Existing Google-linked users can still sign in | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP` | Block new accounts via Microsoft. Existing linked users can still sign in | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC, including the organisation portal | `false` |
|
||||
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of email domains allowed to sign up (e.g., `example.com,acme.org`) | |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | PostHog API key for analytics and feature flags | |
|
||||
| `NEXT_PUBLIC_FEATURE_BILLING_ENABLED` | Enable billing features | `false` |
|
||||
|
||||
### Signup Restrictions
|
||||
|
||||
You can control who is allowed to create accounts on your instance using two environment variables:
|
||||
You can control who is allowed to create accounts on your instance with the following environment variables:
|
||||
|
||||
- **`NEXT_PUBLIC_DISABLE_SIGNUP`**: Set to `true` to block all new signups. Existing users can still sign in. This applies to both email/password and OAuth signups.
|
||||
- **`NEXT_PUBLIC_DISABLE_SIGNUP`** (master switch): Set to `true` to block all new signups across every method (email/password, Google, Microsoft, OIDC). When set, this also blocks new-account creation through the organisation OIDC authentication portal.
|
||||
- **`NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP`**: Set to `true` to disable email/password signup only. SSO signup is still allowed.
|
||||
- **`NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP`**, **`NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP`**, **`NEXT_PUBLIC_DISABLE_OIDC_SIGNUP`**: Set to `true` to block brand-new account creation through the matching SSO provider. Existing users with the provider already linked can still sign in, and existing users can still link the provider to their account. `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` also blocks new-account creation through the organisation authentication portal.
|
||||
- **`NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS`**: Restrict signups to specific email domains. When set, only users whose email address matches one of the listed domains can create an account. Leave empty to allow all domains.
|
||||
|
||||
Both restrictions apply to email/password registration and OAuth (Google, Microsoft, OIDC). If a user attempts to sign up via OAuth with a disallowed domain, they are redirected to the sign-in page with an error.
|
||||
Sign-in for existing users is never affected — only the creation of brand-new accounts.
|
||||
|
||||
When both variables are set, `NEXT_PUBLIC_DISABLE_SIGNUP` takes precedence. Signups are blocked regardless of the domain list.
|
||||
Both the master switch and the domain allowlist apply to email/password registration and OAuth (Google, Microsoft, OIDC). If a user attempts to sign up via OAuth with a disallowed domain, they are redirected to the sign-in page with an error.
|
||||
|
||||
When both the master switch and the domain allowlist are set, the master switch takes precedence. Signups are blocked regardless of the domain list.
|
||||
|
||||
```bash
|
||||
# Allow signups only from specific domains
|
||||
NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS="example.com,acme.org"
|
||||
|
||||
# Allow OIDC signup only; block email/password, Google, Microsoft
|
||||
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP="true"
|
||||
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP="true"
|
||||
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP="true"
|
||||
|
||||
# Or disable signups entirely
|
||||
NEXT_PUBLIC_DISABLE_SIGNUP="true"
|
||||
```
|
||||
@@ -371,6 +384,10 @@ NEXT_PRIVATE_SIGNING_PASSPHRASE="your-certificate-password"
|
||||
|
||||
# Signup restrictions (optional)
|
||||
# NEXT_PUBLIC_DISABLE_SIGNUP="true"
|
||||
# NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP="true"
|
||||
# NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP="true"
|
||||
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP="true"
|
||||
# NEXT_PUBLIC_DISABLE_OIDC_SIGNUP="true"
|
||||
# NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS="example.com,acme.org"
|
||||
```
|
||||
|
||||
|
||||
@@ -155,7 +155,13 @@ PORT=3000
|
||||
NEXT_PRIVATE_SIGNING_PASSPHRASE=your-certificate-password
|
||||
|
||||
# Signup restrictions (optional)
|
||||
# Master switch — disables every signup method
|
||||
NEXT_PUBLIC_DISABLE_SIGNUP=false
|
||||
# Per-method switches (optional). Each disables brand-new account creation through that method.
|
||||
# NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP=true
|
||||
# NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP=true
|
||||
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=true
|
||||
# NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=true
|
||||
# NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=example.com,acme.org
|
||||
```
|
||||
|
||||
@@ -252,7 +258,10 @@ Navigate to the signup page and create your account. Verify your email address
|
||||
<Callout type="info">
|
||||
All accounts created through signup are regular user accounts. Admin access must be granted
|
||||
directly in the database. Once your accounts are set up, consider disabling public signups by
|
||||
setting `NEXT_PUBLIC_DISABLE_SIGNUP=true`, or restrict signups to specific email domains with
|
||||
setting `NEXT_PUBLIC_DISABLE_SIGNUP=true`. For finer control, use the per-method switches
|
||||
`NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP`, `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP`,
|
||||
`NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP`, `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP`, or restrict
|
||||
signups to specific email domains with
|
||||
`NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS`.
|
||||
</Callout>
|
||||
|
||||
|
||||
@@ -100,7 +100,11 @@ See [Email Configuration](/docs/self-hosting/configuration/email) for other tran
|
||||
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | Passphrase for the signing certificate | - |
|
||||
| `NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS` | Base64-encoded `.p12` certificate (alternative to file path) | - |
|
||||
| `NEXT_PUBLIC_UPLOAD_TRANSPORT` | Document storage: `database` or `s3` | `database` |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Disable public signups | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Master switch — disable all signup methods | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP` | Disable email/password signup only | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP` | Block new accounts via Google OAuth | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP` | Block new accounts via Microsoft OAuth | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC (incl. organisation portal) | `false` |
|
||||
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of allowed signup email domains | |
|
||||
|
||||
For the complete list, see [Environment Variables](/docs/self-hosting/configuration/environment).
|
||||
|
||||
@@ -153,7 +153,11 @@ NEXT_PRIVATE_SMTP_FROM_ADDRESS=noreply@yourdomain.com
|
||||
| Variable | Description | Default |
|
||||
| --------------------------------- | ---------------------------------- | ------- |
|
||||
| `PORT` | Application port | `3000` |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Disable public signups | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Master switch — disable all signup methods | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP` | Disable email/password signup only | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP` | Block new accounts via Google OAuth | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP`| Block new accounts via Microsoft OAuth | `false` |
|
||||
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC (incl. organisation portal)| `false` |
|
||||
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of allowed signup email domains | |
|
||||
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | Passphrase for signing certificate | - |
|
||||
| `DOCUMENSO_DISABLE_TELEMETRY` | Disable anonymous telemetry | `false` |
|
||||
|
||||
@@ -58,18 +58,20 @@ export type TSignUpFormSchema = z.infer<typeof ZSignUpFormSchema>;
|
||||
export type SignUpFormProps = {
|
||||
className?: string;
|
||||
initialEmail?: string;
|
||||
isGoogleSSOEnabled?: boolean;
|
||||
isMicrosoftSSOEnabled?: boolean;
|
||||
isOIDCSSOEnabled?: boolean;
|
||||
isEmailPasswordSignupEnabled?: boolean;
|
||||
isGoogleSignupEnabled?: boolean;
|
||||
isMicrosoftSignupEnabled?: boolean;
|
||||
isOidcSignupEnabled?: boolean;
|
||||
returnTo?: string;
|
||||
};
|
||||
|
||||
export const SignUpForm = ({
|
||||
className,
|
||||
initialEmail,
|
||||
isGoogleSSOEnabled,
|
||||
isMicrosoftSSOEnabled,
|
||||
isOIDCSSOEnabled,
|
||||
isEmailPasswordSignupEnabled = true,
|
||||
isGoogleSignupEnabled,
|
||||
isMicrosoftSignupEnabled,
|
||||
isOidcSignupEnabled,
|
||||
returnTo,
|
||||
}: SignUpFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
@@ -86,7 +88,7 @@ export const SignUpForm = ({
|
||||
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
||||
|
||||
const hasSocialAuthEnabled = isGoogleSSOEnabled || isMicrosoftSSOEnabled || isOIDCSSOEnabled;
|
||||
const hasSocialAuthEnabled = isGoogleSignupEnabled || isMicrosoftSignupEnabled || isOidcSignupEnabled;
|
||||
|
||||
const form = useForm<TSignUpFormSchema>({
|
||||
values: {
|
||||
@@ -145,7 +147,7 @@ export const SignUpForm = ({
|
||||
const onSignUpWithGoogleClick = async () => {
|
||||
try {
|
||||
await authClient.google.signIn();
|
||||
} catch (err) {
|
||||
} catch {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(msg`We encountered an unknown error while attempting to sign you Up. Please try again later.`),
|
||||
@@ -157,7 +159,7 @@ export const SignUpForm = ({
|
||||
const onSignUpWithMicrosoftClick = async () => {
|
||||
try {
|
||||
await authClient.microsoft.signIn();
|
||||
} catch (err) {
|
||||
} catch {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(msg`We encountered an unknown error while attempting to sign you Up. Please try again later.`),
|
||||
@@ -169,7 +171,7 @@ export const SignUpForm = ({
|
||||
const onSignUpWithOIDCClick = async () => {
|
||||
try {
|
||||
await authClient.oidc.signIn();
|
||||
} catch (err) {
|
||||
} catch {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(msg`We encountered an unknown error while attempting to sign you Up. Please try again later.`),
|
||||
@@ -235,6 +237,8 @@ export const SignUpForm = ({
|
||||
<Form {...form}>
|
||||
<form className="flex w-full flex-1 flex-col gap-y-4" onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset className="flex w-full flex-col gap-y-4" disabled={isSubmitting}>
|
||||
{isEmailPasswordSignupEnabled && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -294,13 +298,19 @@ export const SignUpForm = ({
|
||||
<Trans>Sign Here</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<SignaturePadDialog disabled={isSubmitting} value={value} onChange={(v) => onChange(v ?? '')} />
|
||||
<SignaturePadDialog
|
||||
disabled={isSubmitting}
|
||||
value={value}
|
||||
onChange={(v) => onChange(v ?? '')}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{turnstileSiteKey && (
|
||||
<Turnstile
|
||||
@@ -325,7 +335,7 @@ export const SignUpForm = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isGoogleSSOEnabled && (
|
||||
{isGoogleSignupEnabled && (
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
@@ -339,7 +349,7 @@ export const SignUpForm = ({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{isMicrosoftSSOEnabled && (
|
||||
{isMicrosoftSignupEnabled && (
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
@@ -353,7 +363,7 @@ export const SignUpForm = ({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{isOIDCSSOEnabled && (
|
||||
{isOidcSignupEnabled && (
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
@@ -377,9 +387,11 @@ export const SignUpForm = ({
|
||||
</p>
|
||||
</fieldset>
|
||||
|
||||
{isEmailPasswordSignupEnabled && (
|
||||
<Button loading={form.formState.isSubmitting} type="submit" size="lg" className="mt-6 w-full">
|
||||
<Trans>Create account</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
<p className="mt-6 text-muted-foreground text-xs">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import signingCelebration from '@documenso/assets/images/signing-celebration.png';
|
||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { useOptionalSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { isSignupEnabledForProvider } from '@documenso/lib/constants/auth';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-recipient-authorized';
|
||||
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
||||
@@ -8,7 +9,6 @@ import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-re
|
||||
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
|
||||
import { getUserByEmail } from '@documenso/lib/server-only/user/get-user-by-email';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
|
||||
import { SigningCard3D } from '@documenso/ui/components/signing-card';
|
||||
@@ -77,7 +77,7 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
||||
const recipientName =
|
||||
recipient.name || fields.find((field) => field.type === FieldType.NAME)?.customText || recipient.email;
|
||||
|
||||
const canSignUp = !isExistingUser && env('NEXT_PUBLIC_DISABLE_SIGNUP') !== 'true';
|
||||
const canSignUp = !isExistingUser && isSignupEnabledForProvider('email');
|
||||
|
||||
const canRedirectToFolder = user && document.userId === user.id && document.folderId && document.team?.url;
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import {
|
||||
IS_GOOGLE_SSO_ENABLED,
|
||||
IS_MICROSOFT_SSO_ENABLED,
|
||||
IS_OIDC_SSO_ENABLED,
|
||||
isSignupEnabledForProvider,
|
||||
OIDC_PROVIDER_LABEL,
|
||||
} from '@documenso/lib/constants/auth';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import { isValidReturnTo, normalizeReturnTo } from '@documenso/lib/utils/is-valid-return-to';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
@@ -32,6 +32,11 @@ export async function loader({ request }: Route.LoaderArgs) {
|
||||
const isMicrosoftSSOEnabled = IS_MICROSOFT_SSO_ENABLED;
|
||||
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED;
|
||||
const oidcProviderLabel = OIDC_PROVIDER_LABEL;
|
||||
const isSignupEnabled =
|
||||
isSignupEnabledForProvider('email') ||
|
||||
(IS_GOOGLE_SSO_ENABLED && isSignupEnabledForProvider('google')) ||
|
||||
(IS_MICROSOFT_SSO_ENABLED && isSignupEnabledForProvider('microsoft')) ||
|
||||
(IS_OIDC_SSO_ENABLED && isSignupEnabledForProvider('oidc'));
|
||||
|
||||
let returnTo = new URL(request.url).searchParams.get('returnTo') ?? undefined;
|
||||
|
||||
@@ -45,13 +50,15 @@ export async function loader({ request }: Route.LoaderArgs) {
|
||||
isGoogleSSOEnabled,
|
||||
isMicrosoftSSOEnabled,
|
||||
isOIDCSSOEnabled,
|
||||
isSignupEnabled,
|
||||
oidcProviderLabel,
|
||||
returnTo,
|
||||
};
|
||||
}
|
||||
|
||||
export default function SignIn({ loaderData }: Route.ComponentProps) {
|
||||
const { isGoogleSSOEnabled, isMicrosoftSSOEnabled, isOIDCSSOEnabled, oidcProviderLabel, returnTo } = loaderData;
|
||||
const { isGoogleSSOEnabled, isMicrosoftSSOEnabled, isOIDCSSOEnabled, isSignupEnabled, oidcProviderLabel, returnTo } =
|
||||
loaderData;
|
||||
|
||||
const { _ } = useLingui();
|
||||
|
||||
@@ -95,7 +102,7 @@ export default function SignIn({ loaderData }: Route.ComponentProps) {
|
||||
returnTo={returnTo}
|
||||
/>
|
||||
|
||||
{!isEmbeddedRedirect && env('NEXT_PUBLIC_DISABLE_SIGNUP') !== 'true' && (
|
||||
{!isEmbeddedRedirect && isSignupEnabled && (
|
||||
<p className="mt-6 text-center text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
Don't have an account?{' '}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { IS_GOOGLE_SSO_ENABLED, IS_MICROSOFT_SSO_ENABLED, IS_OIDC_SSO_ENABLED } from '@documenso/lib/constants/auth';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import {
|
||||
IS_GOOGLE_SSO_ENABLED,
|
||||
IS_MICROSOFT_SSO_ENABLED,
|
||||
IS_OIDC_SSO_ENABLED,
|
||||
isSignupEnabledForProvider,
|
||||
} from '@documenso/lib/constants/auth';
|
||||
import { isValidReturnTo, normalizeReturnTo } from '@documenso/lib/utils/is-valid-return-to';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { redirect } from 'react-router';
|
||||
@@ -14,14 +18,15 @@ export function meta() {
|
||||
}
|
||||
|
||||
export function loader({ request }: Route.LoaderArgs) {
|
||||
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
||||
const isEmailPasswordSignupEnabled = isSignupEnabledForProvider('email');
|
||||
const isGoogleSignupEnabled = IS_GOOGLE_SSO_ENABLED && isSignupEnabledForProvider('google');
|
||||
const isMicrosoftSignupEnabled = IS_MICROSOFT_SSO_ENABLED && isSignupEnabledForProvider('microsoft');
|
||||
const isOidcSignupEnabled = IS_OIDC_SSO_ENABLED && isSignupEnabledForProvider('oidc');
|
||||
|
||||
// SSR env variables.
|
||||
const isGoogleSSOEnabled = IS_GOOGLE_SSO_ENABLED;
|
||||
const isMicrosoftSSOEnabled = IS_MICROSOFT_SSO_ENABLED;
|
||||
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED;
|
||||
const isAnySignupEnabled =
|
||||
isEmailPasswordSignupEnabled || isGoogleSignupEnabled || isMicrosoftSignupEnabled || isOidcSignupEnabled;
|
||||
|
||||
if (NEXT_PUBLIC_DISABLE_SIGNUP === 'true') {
|
||||
if (!isAnySignupEnabled) {
|
||||
throw redirect('/signin');
|
||||
}
|
||||
|
||||
@@ -30,22 +35,30 @@ export function loader({ request }: Route.LoaderArgs) {
|
||||
returnTo = isValidReturnTo(returnTo) ? normalizeReturnTo(returnTo) : undefined;
|
||||
|
||||
return {
|
||||
isGoogleSSOEnabled,
|
||||
isMicrosoftSSOEnabled,
|
||||
isOIDCSSOEnabled,
|
||||
isEmailPasswordSignupEnabled,
|
||||
isGoogleSignupEnabled,
|
||||
isMicrosoftSignupEnabled,
|
||||
isOidcSignupEnabled,
|
||||
returnTo,
|
||||
};
|
||||
}
|
||||
|
||||
export default function SignUp({ loaderData }: Route.ComponentProps) {
|
||||
const { isGoogleSSOEnabled, isMicrosoftSSOEnabled, isOIDCSSOEnabled, returnTo } = loaderData;
|
||||
const {
|
||||
isEmailPasswordSignupEnabled,
|
||||
isGoogleSignupEnabled,
|
||||
isMicrosoftSignupEnabled,
|
||||
isOidcSignupEnabled,
|
||||
returnTo,
|
||||
} = loaderData;
|
||||
|
||||
return (
|
||||
<SignUpForm
|
||||
className="w-screen max-w-screen-2xl px-4 md:px-16 lg:-my-16"
|
||||
isGoogleSSOEnabled={isGoogleSSOEnabled}
|
||||
isMicrosoftSSOEnabled={isMicrosoftSSOEnabled}
|
||||
isOIDCSSOEnabled={isOIDCSSOEnabled}
|
||||
isEmailPasswordSignupEnabled={isEmailPasswordSignupEnabled}
|
||||
isGoogleSignupEnabled={isGoogleSignupEnabled}
|
||||
isMicrosoftSignupEnabled={isMicrosoftSignupEnabled}
|
||||
isOidcSignupEnabled={isOidcSignupEnabled}
|
||||
returnTo={returnTo}
|
||||
/>
|
||||
);
|
||||
|
||||
+5
-1
@@ -253,5 +253,9 @@ Here's a markdown table documenting all the provided environment variables:
|
||||
| `NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY` | The private key for DKIM signing with MailChannels for the `mailchannels` transport. |
|
||||
| `NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT` | The maximum document upload limit displayed to the user (in MB). |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | The optional PostHog key for analytics and feature flags. |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Whether to disable user signups through the /signup page. |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Master switch. Set to `true` to disable all signup methods (incl. organisation OIDC portal). |
|
||||
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP` | Set to `true` to disable email/password signup only. SSO signup is unaffected. |
|
||||
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP` | Set to `true` to block new accounts via Google. Existing Google-linked users can still sign in. |
|
||||
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP` | Set to `true` to block new accounts via Microsoft. Existing linked users can still sign in. |
|
||||
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Set to `true` to block new accounts via OIDC (incl. organisation portal). Existing users unaffected.|
|
||||
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of email domains allowed to sign up (e.g., `example.com,acme.org`). |
|
||||
|
||||
@@ -59,6 +59,10 @@ services:
|
||||
- NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT=${NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT}
|
||||
- NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
|
||||
- NEXT_PUBLIC_DISABLE_SIGNUP=${NEXT_PUBLIC_DISABLE_SIGNUP}
|
||||
- NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP=${NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP}
|
||||
- NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP=${NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP}
|
||||
- NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=${NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP}
|
||||
- NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=${NEXT_PUBLIC_DISABLE_OIDC_SIGNUP}
|
||||
- NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=${NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS}
|
||||
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH:-/opt/documenso/cert.p12}
|
||||
- NEXT_PRIVATE_SIGNING_PASSPHRASE=${NEXT_PRIVATE_SIGNING_PASSPHRASE}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { isEmailDomainAllowedForSignup } from '@documenso/lib/constants/auth';
|
||||
import {
|
||||
isEmailDomainAllowedForSignup,
|
||||
isSignupEnabledForProvider,
|
||||
} 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 { 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 { env } from '@documenso/lib/utils/env';
|
||||
import { isValidReturnTo, normalizeReturnTo } from '@documenso/lib/utils/is-valid-return-to';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
@@ -115,8 +117,8 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti
|
||||
return c.redirect(redirectPath, 302);
|
||||
}
|
||||
|
||||
// Check if signups are disabled.
|
||||
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
||||
// Check if signups are disabled for this provider.
|
||||
if (!isSignupEnabledForProvider(clientOptions.id as 'google' | 'microsoft' | 'oidc')) {
|
||||
const errorUrl = new URL('/signin', NEXT_PUBLIC_WEBAPP_URL());
|
||||
|
||||
errorUrl.searchParams.set('error', AuthenticationErrorCode.SignupDisabled);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { sendOrganisationAccountLinkConfirmationEmail } from '@documenso/ee/server-only/lib/send-organisation-account-link-confirmation-email';
|
||||
import { isSignupEnabledForProvider } from '@documenso/lib/constants/auth';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { onCreateUserHook } from '@documenso/lib/server-only/user/create-user';
|
||||
import { formatOrganisationLoginUrl } from '@documenso/lib/utils/organisation-authentication-portal';
|
||||
@@ -65,6 +66,14 @@ export const handleOAuthOrganisationCallbackUrl = async (options: HandleOAuthOrg
|
||||
|
||||
// Handle new user.
|
||||
if (!userToLink) {
|
||||
if (!isSignupEnabledForProvider('oidc')) {
|
||||
const errorUrl = new URL(formatOrganisationLoginUrl(orgUrl));
|
||||
|
||||
errorUrl.searchParams.set('error', AuthenticationErrorCode.SignupDisabled);
|
||||
|
||||
return c.redirect(errorUrl.toString(), 302);
|
||||
}
|
||||
|
||||
userToLink = await prisma.user.create({
|
||||
data: {
|
||||
email: email,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isEmailDomainAllowedForSignup } from '@documenso/lib/constants/auth';
|
||||
import { isEmailDomainAllowedForSignup, isSignupEnabledForProvider } 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';
|
||||
@@ -27,7 +27,6 @@ import { deletedServiceAccountEmail } from '@documenso/lib/server-only/user/serv
|
||||
import { legacyServiceAccountEmail } from '@documenso/lib/server-only/user/service-accounts/legacy-service-account';
|
||||
import { updatePassword } from '@documenso/lib/server-only/user/update-password';
|
||||
import { verifyEmail } from '@documenso/lib/server-only/user/verify-email';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { sValidator } from '@hono/standard-validator';
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
@@ -184,7 +183,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
.post('/signup', sValidator('json', ZSignUpSchema), async (c) => {
|
||||
const requestMetadata = c.get('requestMetadata');
|
||||
|
||||
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
||||
if (!isSignupEnabledForProvider('email')) {
|
||||
throw new AppError(AuthenticationErrorCode.SignupDisabled, {
|
||||
statusCode: 400,
|
||||
});
|
||||
|
||||
@@ -119,3 +119,24 @@ export const isEmailDomainAllowedForSignup = (email: string): boolean => {
|
||||
|
||||
return allowedDomains.includes(emailDomain);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if signup is enabled for the given provider.
|
||||
* The master switch takes precedence over the per-provider flags.
|
||||
*/
|
||||
export const isSignupEnabledForProvider = (
|
||||
provider: 'email' | 'google' | 'microsoft' | 'oidc',
|
||||
): boolean => {
|
||||
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const flagMap = {
|
||||
email: 'NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP',
|
||||
google: 'NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP',
|
||||
microsoft: 'NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP',
|
||||
oidc: 'NEXT_PUBLIC_DISABLE_OIDC_SIGNUP',
|
||||
} as const;
|
||||
|
||||
return env(flagMap[provider]) !== 'true';
|
||||
};
|
||||
|
||||
Vendored
+4
@@ -74,6 +74,10 @@ declare namespace NodeJS {
|
||||
NEXT_PRIVATE_SMTP_FROM_ADDRESS?: string;
|
||||
|
||||
NEXT_PUBLIC_DISABLE_SIGNUP?: string;
|
||||
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP?: string;
|
||||
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP?: string;
|
||||
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP?: string;
|
||||
NEXT_PUBLIC_DISABLE_OIDC_SIGNUP?: string;
|
||||
NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS?: string;
|
||||
|
||||
NEXT_PRIVATE_BROWSERLESS_URL?: string;
|
||||
|
||||
@@ -155,6 +155,14 @@ services:
|
||||
# Features Optional
|
||||
- key: NEXT_PUBLIC_DISABLE_SIGNUP
|
||||
sync: false
|
||||
- key: NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP
|
||||
sync: false
|
||||
- key: NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP
|
||||
sync: false
|
||||
- key: NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP
|
||||
sync: false
|
||||
- key: NEXT_PUBLIC_DISABLE_OIDC_SIGNUP
|
||||
sync: false
|
||||
- key: NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS
|
||||
sync: false
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
"NEXT_PUBLIC_POSTHOG_KEY",
|
||||
"NEXT_PUBLIC_FEATURE_BILLING_ENABLED",
|
||||
"NEXT_PUBLIC_DISABLE_SIGNUP",
|
||||
"NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP",
|
||||
"NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP",
|
||||
"NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP",
|
||||
"NEXT_PUBLIC_DISABLE_OIDC_SIGNUP",
|
||||
"NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS",
|
||||
"NEXT_PRIVATE_PLAIN_API_KEY",
|
||||
"NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT",
|
||||
|
||||
Reference in New Issue
Block a user