From 2dd167e89896fb39944e694e5c3102588528f9f8 Mon Sep 17 00:00:00 2001 From: nzambello Date: Mon, 7 Aug 2023 13:35:07 +0200 Subject: [PATCH] feat: allow signup via env var, disabled by default --- app/config.server.ts | 12 ++++++++ app/models/user.server.ts | 4 +++ app/routes/join.tsx | 64 +++++++++++++++++++++++++++++---------- app/routes/login.tsx | 48 +++++++++++++++++++---------- 4 files changed, 96 insertions(+), 32 deletions(-) create mode 100644 app/config.server.ts diff --git a/app/config.server.ts b/app/config.server.ts new file mode 100644 index 0000000..9074526 --- /dev/null +++ b/app/config.server.ts @@ -0,0 +1,12 @@ +import { countUsers } from "./models/user.server"; + +export const ALLOW_USER_SIGNUP = process.env.ALLOW_USER_SIGNUP === "1" || null; + +export const isSignupAllowed = async () => { + let isFirstUser = (await countUsers()) === 0; + if (isFirstUser) { + return true; + } + + return !!ALLOW_USER_SIGNUP; +}; diff --git a/app/models/user.server.ts b/app/models/user.server.ts index 8209ce4..21eb3f8 100644 --- a/app/models/user.server.ts +++ b/app/models/user.server.ts @@ -5,6 +5,10 @@ import { prisma } from "~/db.server"; export type { User } from "@prisma/client"; +export async function countUsers() { + return prisma.user.count(); +} + export async function getUserById(id: User["id"]) { return prisma.user.findUnique({ where: { id } }); } diff --git a/app/routes/join.tsx b/app/routes/join.tsx index a9b45ba..26a629f 100644 --- a/app/routes/join.tsx +++ b/app/routes/join.tsx @@ -1,16 +1,30 @@ import type { ActionArgs, LoaderArgs, V2_MetaFunction } from "@remix-run/node"; import { json, redirect } from "@remix-run/node"; -import { Form, Link, useActionData, useSearchParams } from "@remix-run/react"; +import { + Form, + Link, + useActionData, + useLoaderData, + useSearchParams, +} from "@remix-run/react"; import { useEffect, useRef } from "react"; -import { createUser, getUserByEmail } from "~/models/user.server"; +import { createUser, getUserByEmail, countUsers } from "~/models/user.server"; import { createUserSession, getUserId } from "~/session.server"; import { safeRedirect, validateEmail } from "~/utils"; +import { isSignupAllowed } from "~/config.server"; export const loader = async ({ request }: LoaderArgs) => { const userId = await getUserId(request); if (userId) return redirect("/"); - return json({}); + + const isFirstUser = (await countUsers()) === 0; + + if (!(await isSignupAllowed())) { + return redirect("/login"); + } + + return json({ isFirstUser }); }; export const action = async ({ request }: ActionArgs) => { @@ -19,6 +33,21 @@ export const action = async ({ request }: ActionArgs) => { const password = formData.get("password"); const redirectTo = safeRedirect(formData.get("redirectTo"), "/"); + const isFirstUser = (await countUsers()) === 0; + + if (!isSignupAllowed() && !isFirstUser) { + return json( + { + errors: { + email: "User signup is disabled", + password: null, + confirmPassword: null, + }, + }, + { status: 418 } + ); + } + if (!validateEmail(email)) { return json( { errors: { email: "Email is invalid", password: null } }, @@ -69,6 +98,7 @@ export default function Join() { const [searchParams] = useSearchParams(); const redirectTo = searchParams.get("redirectTo") ?? undefined; const actionData = useActionData(); + const loaderData = useLoaderData(); const emailRef = useRef(null); const passwordRef = useRef(null); @@ -145,20 +175,22 @@ export default function Join() { > Create Account -
-
- Already have an account?{" "} - - Log in - + {!loaderData.isFirstUser && ( +
+
+ Already have an account?{" "} + + Log in + +
-
+ )}
diff --git a/app/routes/login.tsx b/app/routes/login.tsx index a650b79..d89b5db 100644 --- a/app/routes/login.tsx +++ b/app/routes/login.tsx @@ -1,16 +1,29 @@ import type { ActionArgs, LoaderArgs, V2_MetaFunction } from "@remix-run/node"; import { json, redirect } from "@remix-run/node"; -import { Form, Link, useActionData, useSearchParams } from "@remix-run/react"; +import { + Form, + Link, + useActionData, + useLoaderData, + useSearchParams, +} from "@remix-run/react"; import { useEffect, useRef } from "react"; +import { isSignupAllowed } from "~/config.server"; -import { verifyLogin } from "~/models/user.server"; +import { countUsers, verifyLogin } from "~/models/user.server"; import { createUserSession, getUserId } from "~/session.server"; import { safeRedirect, validateEmail } from "~/utils"; export const loader = async ({ request }: LoaderArgs) => { const userId = await getUserId(request); if (userId) return redirect("/"); - return json({}); + + const isFirstUser = (await countUsers()) === 0; + if (isFirstUser) return redirect("/signup"); + + return json({ + ALLOW_USER_SIGNUP: await isSignupAllowed(), + }); }; export const action = async ({ request }: ActionArgs) => { @@ -64,6 +77,7 @@ export default function LoginPage() { const [searchParams] = useSearchParams(); const redirectTo = searchParams.get("redirectTo") || "/t"; const actionData = useActionData(); + const loaderData = useLoaderData(); const emailRef = useRef(null); const passwordRef = useRef(null); @@ -140,7 +154,7 @@ export default function LoginPage() { > Log in -
+
-
- Don't have an account?{" "} - - Sign up - -
+ {!!loaderData?.ALLOW_USER_SIGNUP && ( +
+ Don't have an account?{" "} + + Sign up + +
+ )}