feat: add granular signin disable flags and OIDC auto-redirect (#2857)

This commit is contained in:
Kendry Grullon
2026-06-30 02:08:09 -04:00
committed by GitHub
parent 3b110cf70d
commit 562d78e2d7
28 changed files with 371 additions and 56 deletions
+63 -49
View File
@@ -58,6 +58,7 @@ export type TSignInFormSchema = z.infer<typeof ZSignInFormSchema>;
export type SignInFormProps = {
className?: string;
initialEmail?: string;
isEmailPasswordSigninEnabled?: boolean;
isGoogleSSOEnabled?: boolean;
isMicrosoftSSOEnabled?: boolean;
isOIDCSSOEnabled?: boolean;
@@ -68,6 +69,7 @@ export type SignInFormProps = {
export const SignInForm = ({
className,
initialEmail,
isEmailPasswordSigninEnabled = true,
isGoogleSSOEnabled,
isMicrosoftSSOEnabled,
isOIDCSSOEnabled,
@@ -324,66 +326,78 @@ export const SignInForm = ({
<Form {...form}>
<form className={cn('flex w-full flex-col gap-y-4', className)} onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex w-full flex-col gap-y-4" disabled={isSubmitting || isPasskeyLoading}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
{isEmailPasswordSigninEnabled && (
<>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput {...field} />
</FormControl>
<FormControl>
<PasswordInput {...field} />
</FormControl>
<FormMessage />
<FormMessage />
<p className="mt-2 text-right">
<Link to="/forgot-password" className="text-muted-foreground text-sm duration-200 hover:opacity-70">
<Trans>Forgot your password?</Trans>
</Link>
</p>
</FormItem>
)}
/>
<p className="mt-2 text-right">
<Link
to="/forgot-password"
className="text-muted-foreground text-sm duration-200 hover:opacity-70"
>
<Trans>Forgot your password?</Trans>
</Link>
</p>
</FormItem>
)}
/>
{turnstileSiteKey && !isTwoFactorAuthenticationDialogOpen && (
<Turnstile
ref={turnstileRef}
siteKey={turnstileSiteKey}
options={{
size: 'flexible',
appearance: 'always',
}}
/>
{turnstileSiteKey && !isTwoFactorAuthenticationDialogOpen && (
<Turnstile
ref={turnstileRef}
siteKey={turnstileSiteKey}
options={{
size: 'flexible',
appearance: 'always',
}}
/>
)}
<Button
type="submit"
size="lg"
loading={isSubmitting}
className="dark:bg-documenso dark:hover:opacity-90"
>
{isSubmitting ? <Trans>Signing in...</Trans> : <Trans>Sign In</Trans>}
</Button>
</>
)}
<Button type="submit" size="lg" loading={isSubmitting} className="dark:bg-documenso dark:hover:opacity-90">
{isSubmitting ? <Trans>Signing in...</Trans> : <Trans>Sign In</Trans>}
</Button>
{!isEmbeddedRedirect && (
<>
{hasSocialAuthEnabled && (
{isEmailPasswordSigninEnabled && hasSocialAuthEnabled && (
<div className="relative flex items-center justify-center gap-x-4 py-2 text-xs uppercase">
<div className="h-px flex-1 bg-border" />
<span className="bg-transparent text-muted-foreground">
@@ -1,6 +1,7 @@
import { isSigninEnabledForProvider } from '@documenso/lib/constants/auth';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Link } from 'react-router';
import { Link, redirect } from 'react-router';
import { ForgotPasswordForm } from '~/components/forms/forgot-password';
import { appMetaTags } from '~/utils/meta';
@@ -9,6 +10,14 @@ export function meta() {
return appMetaTags(msg`Forgot Password`);
}
export async function loader() {
if (!isSigninEnabledForProvider('email')) {
throw redirect('/signin');
}
return null;
}
export default function ForgotPasswordPage() {
return (
<div className="w-screen max-w-lg px-4">
@@ -1,3 +1,4 @@
import { isSigninEnabledForProvider } from '@documenso/lib/constants/auth';
import { getResetTokenValidity } from '@documenso/lib/server-only/user/get-reset-token-validity';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
@@ -13,6 +14,10 @@ export function meta() {
}
export async function loader({ params }: Route.LoaderArgs) {
if (!isSigninEnabledForProvider('email')) {
throw redirect('/signin');
}
const { token } = params;
const isValid = await getResetTokenValidity({ token });
@@ -1,7 +1,8 @@
import { isSigninEnabledForProvider } from '@documenso/lib/constants/auth';
import { Button } from '@documenso/ui/primitives/button';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Link } from 'react-router';
import { Link, redirect } from 'react-router';
import { appMetaTags } from '~/utils/meta';
@@ -9,6 +10,14 @@ export function meta() {
return appMetaTags(msg`Reset Password`);
}
export async function loader() {
if (!isSigninEnabledForProvider('email')) {
throw redirect('/signin');
}
return null;
}
export default function ResetPasswordPage() {
return (
<div className="w-screen max-w-lg px-4">
@@ -1,8 +1,11 @@
import { authClient } from '@documenso/auth/client';
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
import {
IS_GOOGLE_SSO_ENABLED,
IS_MICROSOFT_SSO_ENABLED,
IS_OIDC_AUTO_REDIRECT_DISABLED,
IS_OIDC_SSO_ENABLED,
isSigninEnabledForProvider,
isSignupEnabledForProvider,
OIDC_PROVIDER_LABEL,
} from '@documenso/lib/constants/auth';
@@ -11,6 +14,7 @@ import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Loader2Icon } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Link, redirect, useSearchParams } from 'react-router';
@@ -28,10 +32,20 @@ export async function loader({ request }: Route.LoaderArgs) {
const { isAuthenticated } = await getOptionalSession(request);
// SSR env variables.
const isGoogleSSOEnabled = IS_GOOGLE_SSO_ENABLED;
const isMicrosoftSSOEnabled = IS_MICROSOFT_SSO_ENABLED;
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED;
const isEmailPasswordSigninEnabled = isSigninEnabledForProvider('email');
const isGoogleSSOEnabled = IS_GOOGLE_SSO_ENABLED && isSigninEnabledForProvider('google');
const isMicrosoftSSOEnabled = IS_MICROSOFT_SSO_ENABLED && isSigninEnabledForProvider('microsoft');
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED && isSigninEnabledForProvider('oidc');
// Automatically redirect to OIDC when it is the only enabled signin transport,
// unless the redirect has been explicitly disabled via env.
const isOIDCOnlyTransport =
isOIDCSSOEnabled && !isEmailPasswordSigninEnabled && !isGoogleSSOEnabled && !isMicrosoftSSOEnabled;
const shouldAutoRedirectToOIDC = isOIDCOnlyTransport && !IS_OIDC_AUTO_REDIRECT_DISABLED;
const oidcProviderLabel = OIDC_PROVIDER_LABEL;
const isSignupEnabled =
isSignupEnabledForProvider('email') ||
(IS_GOOGLE_SSO_ENABLED && isSignupEnabledForProvider('google')) ||
@@ -47,18 +61,28 @@ export async function loader({ request }: Route.LoaderArgs) {
}
return {
isEmailPasswordSigninEnabled,
isGoogleSSOEnabled,
isMicrosoftSSOEnabled,
isOIDCSSOEnabled,
isSignupEnabled,
oidcProviderLabel,
returnTo,
shouldAutoRedirectToOIDC,
};
}
export default function SignIn({ loaderData }: Route.ComponentProps) {
const { isGoogleSSOEnabled, isMicrosoftSSOEnabled, isOIDCSSOEnabled, isSignupEnabled, oidcProviderLabel, returnTo } =
loaderData;
const {
isEmailPasswordSigninEnabled,
isGoogleSSOEnabled,
isMicrosoftSSOEnabled,
isOIDCSSOEnabled,
isSignupEnabled,
oidcProviderLabel,
returnTo,
shouldAutoRedirectToOIDC,
} = loaderData;
const { _ } = useLingui();
@@ -76,6 +100,27 @@ export default function SignIn({ loaderData }: Route.ComponentProps) {
setIsEmbeddedRedirect(params.get('embedded') === 'true');
}, []);
useEffect(() => {
if (!shouldAutoRedirectToOIDC) {
return;
}
void authClient.oidc.signIn({ redirectPath: returnTo ?? '/' });
}, [shouldAutoRedirectToOIDC, returnTo]);
if (shouldAutoRedirectToOIDC) {
return (
<div className="w-screen max-w-lg px-4">
<div className="flex flex-col items-center justify-center gap-y-4 py-12">
<Loader2Icon className="h-8 w-8 animate-spin text-muted-foreground" />
<p className="text-muted-foreground text-sm">
<Trans>Redirecting to {oidcProviderLabel || 'OIDC'}...</Trans>
</p>
</div>
</div>
);
}
return (
<div className="w-screen max-w-lg px-4">
<div className="z-10 rounded-xl border border-border bg-neutral-100 p-6 dark:bg-background">
@@ -95,6 +140,7 @@ export default function SignIn({ loaderData }: Route.ComponentProps) {
<hr className="-mx-6 my-4" />
<SignInForm
isEmailPasswordSigninEnabled={isEmailPasswordSigninEnabled}
isGoogleSSOEnabled={isGoogleSSOEnabled}
isMicrosoftSSOEnabled={isMicrosoftSSOEnabled}
isOIDCSSOEnabled={isOIDCSSOEnabled}