fixes #2552, implement FLAG_DISABLE_SIGNUPS and FLAG_DISABLE_EMAIL_AUTH

This commit is contained in:
Amruth Pillai
2026-01-23 01:45:42 +01:00
parent 6db9fee823
commit 11cbeb27f8
69 changed files with 2098 additions and 2029 deletions
+10 -3
View File
@@ -16,7 +16,8 @@ import { ConfirmDialogProvider } from "@/hooks/use-confirm";
import { PromptDialogProvider } from "@/hooks/use-prompt";
import { getSession } from "@/integrations/auth/functions";
import type { AuthSession } from "@/integrations/auth/types";
import type { orpc } from "@/integrations/orpc/client";
import { client, type orpc } from "@/integrations/orpc/client";
import type { FeatureFlags } from "@/integrations/orpc/services/flags";
import { getLocale, isRTL, type Locale, loadLocale } from "@/utils/locale";
import { getTheme, type Theme } from "@/utils/theme";
import appCss from "../styles/globals.css?url";
@@ -27,6 +28,7 @@ type RouterContext = {
orpc: typeof orpc;
queryClient: QueryClient;
session: AuthSession | null;
flags: FeatureFlags;
};
const appName = "Reactive Resume";
@@ -70,9 +72,14 @@ export const Route = createRootRouteWithContext<RouterContext>()({
};
},
beforeLoad: async () => {
const [theme, locale, session] = await Promise.all([getTheme(), getLocale(), getSession()]);
const [theme, locale, session, flags] = await Promise.all([
getTheme(),
getLocale(),
getSession(),
client.flags.get(),
]);
return { theme, locale, session };
return { theme, locale, session, flags };
},
});
+4 -1
View File
@@ -2,7 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { ArrowRightIcon } from "@phosphor-icons/react";
import { createFileRoute, Link } from "@tanstack/react-router";
import { createFileRoute, Link, redirect } from "@tanstack/react-router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -14,6 +14,9 @@ import { authClient } from "@/integrations/auth/client";
export const Route = createFileRoute("/auth/forgot-password")({
component: RouteComponent,
beforeLoad: async ({ context }) => {
if (context.flags.disableEmailAuth) throw redirect({ to: "/auth/login", replace: true });
},
});
const formSchema = z.object({
+71 -66
View File
@@ -33,6 +33,7 @@ function RouteComponent() {
const router = useRouter();
const navigate = useNavigate();
const [showPassword, toggleShowPassword] = useToggle(false);
const { flags } = Route.useRouteContext();
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
@@ -86,80 +87,84 @@ function RouteComponent() {
<Trans>Sign in to your account</Trans>
</h1>
<div className="text-muted-foreground">
<Trans>
Don't have an account?{" "}
<Button asChild variant="link" className="h-auto gap-1.5 px-1! py-0">
<Link to="/auth/register">
Create one now <ArrowRightIcon />
</Link>
</Button>
</Trans>
</div>
{!flags.disableSignups && (
<div className="text-muted-foreground">
<Trans>
Don't have an account?{" "}
<Button asChild variant="link" className="h-auto gap-1.5 px-1! py-0">
<Link to="/auth/register">
Create one now <ArrowRightIcon />
</Link>
</Button>
</Trans>
</div>
)}
</div>
<Form {...form}>
<form className="space-y-6" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="identifier"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Email Address</Trans>
</FormLabel>
<FormControl>
<Input autoComplete="email" placeholder="john.doe@example.com" className="lowercase" {...field} />
</FormControl>
<FormMessage />
<FormDescription>
<Trans>You can also use your username to login.</Trans>
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<div className="flex items-center justify-between">
{!flags.disableEmailAuth && (
<Form {...form}>
<form className="space-y-6" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="identifier"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Password</Trans>
<Trans>Email Address</Trans>
</FormLabel>
<Button asChild tabIndex={-1} variant="link" className="h-auto p-0 text-xs leading-none">
<Link to="/auth/forgot-password">
<Trans>Forgot Password?</Trans>
</Link>
</Button>
</div>
<div className="flex items-center gap-x-1.5">
<FormControl>
<Input
min={6}
max={64}
type={showPassword ? "text" : "password"}
autoComplete="current-password"
{...field}
/>
<Input autoComplete="email" placeholder="john.doe@example.com" className="lowercase" {...field} />
</FormControl>
<FormMessage />
<FormDescription>
<Trans>You can also use your username to login.</Trans>
</FormDescription>
</FormItem>
)}
/>
<Button size="icon" variant="ghost" onClick={toggleShowPassword}>
{showPassword ? <EyeIcon /> : <EyeSlashIcon />}
</Button>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<div className="flex items-center justify-between">
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<Button type="submit" className="w-full">
<Trans>Sign in</Trans>
</Button>
</form>
</Form>
<Button asChild tabIndex={-1} variant="link" className="h-auto p-0 text-xs leading-none">
<Link to="/auth/forgot-password">
<Trans>Forgot Password?</Trans>
</Link>
</Button>
</div>
<div className="flex items-center gap-x-1.5">
<FormControl>
<Input
min={6}
max={64}
type={showPassword ? "text" : "password"}
autoComplete="current-password"
{...field}
/>
</FormControl>
<Button size="icon" variant="ghost" onClick={toggleShowPassword}>
{showPassword ? <EyeIcon /> : <EyeSlashIcon />}
</Button>
</div>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
<Trans>Sign in</Trans>
</Button>
</form>
</Form>
)}
<SocialAuth />
</>
+91 -87
View File
@@ -19,6 +19,7 @@ export const Route = createFileRoute("/auth/register")({
component: RouteComponent,
beforeLoad: async ({ context }) => {
if (context.session) throw redirect({ to: "/dashboard", replace: true });
if (context.flags.disableSignups) throw redirect({ to: "/auth/login", replace: true });
return { session: null };
},
});
@@ -43,6 +44,7 @@ type FormValues = z.infer<typeof formSchema>;
function RouteComponent() {
const [submitted, setSubmitted] = useState(false);
const [showPassword, toggleShowPassword] = useToggle(false);
const { flags } = Route.useRouteContext();
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
@@ -96,102 +98,104 @@ function RouteComponent() {
</div>
</div>
<Form {...form}>
<form className="space-y-6" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Name</Trans>
</FormLabel>
<FormControl>
<Input min={3} max={64} autoComplete="name" placeholder="John Doe" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{!flags.disableEmailAuth && (
<Form {...form}>
<form className="space-y-6" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Name</Trans>
</FormLabel>
<FormControl>
<Input min={3} max={64} autoComplete="name" placeholder="John Doe" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Username</Trans>
</FormLabel>
<FormControl>
<Input
min={3}
max={64}
autoComplete="username"
placeholder="john.doe"
className="lowercase"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Email Address</Trans>
</FormLabel>
<FormControl>
<Input
type="email"
autoComplete="email"
placeholder="john.doe@example.com"
className="lowercase"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<div className="flex items-center gap-x-1.5">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Username</Trans>
</FormLabel>
<FormControl>
<Input
min={6}
min={3}
max={64}
type={showPassword ? "text" : "password"}
autoComplete="new-password"
autoComplete="username"
placeholder="john.doe"
className="lowercase"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button size="icon" variant="ghost" onClick={toggleShowPassword}>
{showPassword ? <EyeIcon /> : <EyeSlashIcon />}
</Button>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Email Address</Trans>
</FormLabel>
<FormControl>
<Input
type="email"
autoComplete="email"
placeholder="john.doe@example.com"
className="lowercase"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
<Trans>Sign up</Trans>
</Button>
</form>
</Form>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<div className="flex items-center gap-x-1.5">
<FormControl>
<Input
min={6}
max={64}
type={showPassword ? "text" : "password"}
autoComplete="new-password"
{...field}
/>
</FormControl>
<Button size="icon" variant="ghost" onClick={toggleShowPassword}>
{showPassword ? <EyeIcon /> : <EyeSlashIcon />}
</Button>
</div>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
<Trans>Sign up</Trans>
</Button>
</form>
</Form>
)}
<SocialAuth />
</>
+3
View File
@@ -18,6 +18,9 @@ const searchSchema = z.object({ token: z.string().min(1) });
export const Route = createFileRoute("/auth/reset-password")({
component: RouteComponent,
validateSearch: zodValidator(searchSchema),
beforeLoad: async ({ context }) => {
if (context.flags.disableEmailAuth) throw redirect({ to: "/auth/login", replace: true });
},
onError: (error) => {
if (error instanceof SearchParamError) {
throw redirect({ to: "/auth/login" });