From a72a00bee77acfc01da874c5a29cbf19eceb683c Mon Sep 17 00:00:00 2001 From: depak Date: Wed, 12 Nov 2025 01:46:12 +0530 Subject: [PATCH 1/3] "Implement password sign-in option (#1036)" --- apps/web/app/(org)/login/form.tsx | 176 ++++++++++++++++++++-- apps/web/app/(org)/signup/form.tsx | 169 +++++++++++++++++++-- apps/web/app/(org)/verify-otp/form.tsx | 44 ++++++ apps/web/app/(org)/verify-otp/page.tsx | 4 +- packages/database/auth/auth-options.ts | 48 ++++++ packages/database/auth/drizzle-adapter.ts | 2 + packages/database/schema.ts | 1 + 7 files changed, 422 insertions(+), 22 deletions(-) diff --git a/apps/web/app/(org)/login/form.tsx b/apps/web/app/(org)/login/form.tsx index 4f4588f19c..d001ea1186 100644 --- a/apps/web/app/(org)/login/form.tsx +++ b/apps/web/app/(org)/login/form.tsx @@ -10,7 +10,7 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { AnimatePresence, motion } from "framer-motion"; import Cookies from "js-cookie"; -import { LucideArrowUpRight } from "lucide-react"; +import { KeyRound, LucideArrowUpRight } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; @@ -40,6 +40,8 @@ export function LoginForm() { const [lastEmailSentTime, setLastEmailSentTime] = useState( null, ); + const [password, setPassword] = useState(""); + const [showCredientialLogin,setShowCredientialLogin]=useState(false); const theme = Cookies.get("theme") || "light"; useEffect(() => { @@ -248,6 +250,60 @@ export function LoginForm() { e.preventDefault(); if (!email) return; + if (showCredientialLogin) { + if (!email || !password) { + toast.error("Please enter email and password"); + return; + } + + try { + setLoading(true); + trackEvent("auth_started", { method: "password", is_signup: false }); + + const res = await signIn("credentials", { + email, + password, + redirect: false, + ...(next && next.length > 0 ? { callbackUrl: next } : {}), + }); + + setLoading(false); + + if (res?.ok && !res?.error) { + trackEvent("auth_success", { method: "password", is_signup: false }); + router.push(next || "/"); + return; + } + + + // Handle specific known errors first + if (res?.error?.toLowerCase().includes("verify")) { + toast.error("Please verify your email before logging in."); + const res = await fetch("/api/signup/resend", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + }); + + const data = await res.json(); + if (!data?.status) { + throw "Something went wrong,try other login methods"; + } + router.push(`/verify-otp?email=${encodeURIComponent(email)}&type=credientials`); + return; + } + + // Otherwise generic error + toast.error("Invalid credentials – try again?"); + } catch (error) { + console.error("Credential login error:", error); + toast.error("Invalid credentials – try again?"); + } finally { + setLoading(false); + } + return; + } + // Check if we're rate limited on the client side if (lastEmailSentTime) { const timeSinceLastRequest = @@ -309,15 +365,28 @@ export function LoginForm() { }} className="flex flex-col space-y-3" > - + {showCredientialLogin ? ( + + ) : ( + + )} )} @@ -388,6 +457,79 @@ const LoginWithSSO = ({ ); }; +const LoginWithEmailAndPassword = ({ + email, + emailSent, + setEmail, + loading, + password, + setPassword, + setShowCredientialLogin +}: { + email: string; + emailSent: boolean; + setEmail: (email: string) => void; + password: string, + setPassword: (password: string) => void; + loading: boolean; + setShowCredientialLogin : (show : boolean) => void + +}) => { + return ( + + + { + setEmail(e.target.value); + }} + /> + { + setPassword(e.target.value); + }} + /> + } + > + Login with email + + setShowCredientialLogin(false)} + disabled={loading || emailSent} + > + + Login with OTP + + + + ); +}; + const NormalLogin = ({ setShowOrgInput, email, @@ -396,6 +538,7 @@ const NormalLogin = ({ loading, oauthError, handleGoogleSignIn, + setShowCredientialLogin }: { setShowOrgInput: (show: boolean) => void; email: string; @@ -404,6 +547,7 @@ const NormalLogin = ({ loading: boolean; oauthError: boolean; handleGoogleSignIn: () => void; + setShowCredientialLogin : (show : boolean) => void }) => { const publicEnv = usePublicEnv(); @@ -455,7 +599,17 @@ const NormalLogin = ({ Sign up here - + setShowCredientialLogin(true)} + disabled={loading || emailSent} + > + + Login with Password + {(publicEnv.googleAuthAvailable || publicEnv.workosAuthAvailable) && ( <>
diff --git a/apps/web/app/(org)/signup/form.tsx b/apps/web/app/(org)/signup/form.tsx index 10e9dede14..fa3453c256 100644 --- a/apps/web/app/(org)/signup/form.tsx +++ b/apps/web/app/(org)/signup/form.tsx @@ -10,7 +10,7 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { AnimatePresence, motion } from "framer-motion"; import Cookies from "js-cookie"; -import { LucideArrowUpRight } from "lucide-react"; +import { KeyRound, LucideArrowUpRight } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; @@ -40,6 +40,8 @@ export function SignupForm() { const [lastEmailSentTime, setLastEmailSentTime] = useState( null, ); + const [password, setPassword] = useState(""); + const [showCredientialLogin,setShowCredientialLogin]=useState(false); const theme = Cookies.get("theme") || "light"; useEffect(() => { @@ -248,6 +250,53 @@ export function SignupForm() { e.preventDefault(); if (!email) return; + if (showCredientialLogin) { + if (!email || !password) { + toast.error("Please enter email and password"); + return; + } + + if (password.length < 8) { + toast.error("Password must be at least 8 characters long"); + return; + } + try { + setLoading(true); + trackEvent("auth_started", { method: "password", is_signup: true }); + + const response = await fetch("/api/auth/signup", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + + const data = await response.json(); + setLoading(false); + + if (!response.ok) { + toast.error(data.error || "Failed to create account"); + return; + } + + sessionStorage.setItem("signup_password", password); + toast.success("Verification code sent to your email!"); + trackEvent("auth_email_sent", { email_domain: email.split("@")[1] }); + + const params = new URLSearchParams({ + email, + type: "credentials", + ...(next && { next }), + }); + router.push(`/verify-otp?${params.toString()}`); + } catch (error) { + console.error("Credential signup error:", error); + toast.error("Something went wrong during signup. Try again?"); + } finally { + setLoading(false); + } + return; + } + // Check if we're rate limited on the client side if (lastEmailSentTime) { const timeSinceLastRequest = @@ -309,15 +358,28 @@ export function SignupForm() { }} className="flex flex-col space-y-3" > - + {showCredientialLogin ? ( + + ) : ( + + )} )} @@ -402,6 +464,80 @@ const SignupWithSSO = ({ ); }; + +const SignUpWithEmailAndPassword = ({ + email, + emailSent, + setEmail, + loading, + password, + setPassword, + setShowCredientialLogin +}: { + email: string; + emailSent: boolean; + setEmail: (email: string) => void; + password: string, + setPassword: (password: string) => void; + loading: boolean; + setShowCredientialLogin: (show: boolean) => void + +}) => { + return ( + + + { + setEmail(e.target.value); + }} + /> + { + setPassword(e.target.value); + }} + /> + } + > + Signup with email + + setShowCredientialLogin(false)} + disabled={loading || emailSent} + > + + Signup with OTP + + + + ); +}; + const NormalSignup = ({ setShowOrgInput, email, @@ -410,6 +546,7 @@ const NormalSignup = ({ loading, oauthError, handleGoogleSignIn, + setShowCredientialLogin }: { setShowOrgInput: (show: boolean) => void; email: string; @@ -418,6 +555,7 @@ const NormalSignup = ({ loading: boolean; oauthError: boolean; handleGoogleSignIn: () => void; + setShowCredientialLogin: (show: boolean) => void; }) => { const publicEnv = usePublicEnv(); @@ -446,6 +584,17 @@ const NormalSignup = ({ > Sign up with email + setShowCredientialLogin(true)} + disabled={loading || emailSent} + > + + Sign up with Password + {(publicEnv.googleAuthAvailable || publicEnv.workosAuthAvailable) && ( <> diff --git a/apps/web/app/(org)/verify-otp/form.tsx b/apps/web/app/(org)/verify-otp/form.tsx index a7161c3466..f5cbc98848 100644 --- a/apps/web/app/(org)/verify-otp/form.tsx +++ b/apps/web/app/(org)/verify-otp/form.tsx @@ -15,10 +15,12 @@ export function VerifyOTPForm({ email, next, lastSent, + type }: { email: string; next?: string; lastSent?: string; + type?: "email" | "credentials"; }) { const [code, setCode] = useState(["", "", "", "", "", ""]); const [lastResendTime, setLastResendTime] = useState( @@ -75,6 +77,33 @@ export function VerifyOTPForm({ const otpCode = code.join(""); if (otpCode.length !== 6) throw "Please enter a complete 6-digit code"; + if (type === "credentials") { + const res = await fetch("/api/auth/verify-otp", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, otp: otpCode }), + }); + + const data = await res.json().catch(() => ({})); + + if (!res.ok || !data?.status) { + throw data?.message || "Invalid or expired OTP. Please try again."; + } + const password = sessionStorage.getItem("signup_password"); + if (!password) { + throw "Something went wrong. Please try logging in again."; + } + const result = await signIn("credentials", { + email, + password, + redirect: false, + }); + if (result?.error) { + throw "Login failed after verification. Please log in again."; + } + return; + } + // shoutout https://github.com/buoyad/Tally/pull/14 const res = await fetch( `/api/auth/callback/email?email=${encodeURIComponent(email)}&token=${encodeURIComponent(otpCode)}&callbackUrl=${encodeURIComponent("/login-success")}`, @@ -114,6 +143,21 @@ export function VerifyOTPForm({ } } + if (type === "credentials") { + const res = await fetch("/api/auth/resend", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + }); + + const data = await res.json(); + if (!data?.status) { + throw data?.message || "OTP resend failed."; + } + setLastResendTime(Date.now()); + return data; + } + const result = await signIn("email", { email, redirect: false, diff --git a/apps/web/app/(org)/verify-otp/page.tsx b/apps/web/app/(org)/verify-otp/page.tsx index a31859b07e..828f07ebbf 100644 --- a/apps/web/app/(org)/verify-otp/page.tsx +++ b/apps/web/app/(org)/verify-otp/page.tsx @@ -8,7 +8,7 @@ export const metadata = { }; export default async function VerifyOTPPage(props: { - searchParams: Promise<{ email?: string; next?: string; lastSent?: string }>; + searchParams: Promise<{ email?: string; next?: string; lastSent?: string ; type?: string }>; }) { const searchParams = await props.searchParams; const user = await getCurrentUser(); @@ -20,6 +20,7 @@ export default async function VerifyOTPPage(props: { if (!searchParams.email) { redirect("/login"); } + const verifyType = searchParams.type === "credentials" ? "credentials" : "email"; return (
@@ -28,6 +29,7 @@ export default async function VerifyOTPPage(props: { email={searchParams.email} next={searchParams.next} lastSent={searchParams.lastSent} + type={verifyType} />
diff --git a/packages/database/auth/auth-options.ts b/packages/database/auth/auth-options.ts index 8f39e2575e..6cc285f0fb 100644 --- a/packages/database/auth/auth-options.ts +++ b/packages/database/auth/auth-options.ts @@ -9,6 +9,7 @@ import EmailProvider from "next-auth/providers/email"; import GoogleProvider from "next-auth/providers/google"; import type { Provider } from "next-auth/providers/index"; import WorkOSProvider from "next-auth/providers/workos"; +import CredentialsProvider from "next-auth/providers/credentials"; import { dub } from "../dub.ts"; import { sendEmail } from "../emails/config.ts"; import { nanoId } from "../helpers.ts"; @@ -16,6 +17,7 @@ import { db } from "../index.ts"; import { organizationMembers, organizations, users } from "../schema.ts"; import { isEmailAllowedForSignup } from "./domain-utils.ts"; import { DrizzleAdapter } from "./drizzle-adapter.ts"; +import { verifyPassword } from "@cap/database/crypto"; export const maxDuration = 120; @@ -105,6 +107,52 @@ export const authOptions = (): NextAuthOptions => { } }, }), + CredentialsProvider({ + name: "Credentials", + credentials: { + email: { label: "Email", type: "text", placeholder: "you@domain.com" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + try { + if (!credentials?.email || !credentials?.password) { + throw new Error("Missing email or password"); + } + + const [user] = await db() + .select() + .from(users) + .where(eq(users.email, credentials.email)) + .limit(1); + + if (!user) throw new Error("Invalid email or password"); + + console.log(user); + + if(user.password==null){ + throw new Error("No password found for user"); + } + + // Require email verification before login + if (!user.emailVerified) { + throw new Error("Please verify your email before logging in."); + } + + const isValid = await verifyPassword(user.password,credentials.password); + if (!isValid) throw new Error("Invalid email or password"); + + return { + id: user.id, + name: user.name, + email: user.email, + image: user.image, + }; + } catch (err) { + console.error("Credential authorize error:", err); + throw err; + } + }, + }), ]; return _providers; diff --git a/packages/database/auth/drizzle-adapter.ts b/packages/database/auth/drizzle-adapter.ts index 7edaca2ea4..1dbbd23e19 100644 --- a/packages/database/auth/drizzle-adapter.ts +++ b/packages/database/auth/drizzle-adapter.ts @@ -19,6 +19,7 @@ export function DrizzleAdapter(db: MySql2Database): Adapter { return { async createUser(userData: any) { const userId = User.UserId.make(nanoId()); + const hashedPassword = userData.password; await db.transaction(async (tx) => { const [pendingInvite] = await tx .select({ id: organizationInvites.id }) @@ -37,6 +38,7 @@ export function DrizzleAdapter(db: MySql2Database): Adapter { emailVerified: userData.emailVerified, name: userData.name, image: userData.image, + password:hashedPassword, activeOrganizationId: Organisation.OrganisationId.make(""), }); diff --git a/packages/database/schema.ts b/packages/database/schema.ts index 14a2e9685e..0260a2149f 100644 --- a/packages/database/schema.ts +++ b/packages/database/schema.ts @@ -66,6 +66,7 @@ export const users = mysqlTable( lastName: varchar("lastName", { length: 255 }), email: varchar("email", { length: 255 }).notNull(), emailVerified: timestamp("emailVerified"), + password: encryptedTextNullable("password"), image: varchar("image", { length: 255 }).$type(), stripeCustomerId: varchar("stripeCustomerId", { length: 255 }), stripeSubscriptionId: varchar("stripeSubscriptionId", { From d7a63c2cc494f28e25adc5379fa0d75409968696 Mon Sep 17 00:00:00 2001 From: depak7 Date: Wed, 12 Nov 2025 02:45:38 +0530 Subject: [PATCH 2/3] "Implement password sign-in option (#1036)" --- apps/web/app/(org)/login/form.tsx | 2 +- apps/web/app/(org)/signup/form.tsx | 1 - apps/web/app/(org)/verify-otp/form.tsx | 14 +++--- packages/database/auth/auth-options.ts | 65 +++++++++++++++++++++++--- 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/apps/web/app/(org)/login/form.tsx b/apps/web/app/(org)/login/form.tsx index d001ea1186..b6d3de80b4 100644 --- a/apps/web/app/(org)/login/form.tsx +++ b/apps/web/app/(org)/login/form.tsx @@ -279,7 +279,7 @@ export function LoginForm() { // Handle specific known errors first if (res?.error?.toLowerCase().includes("verify")) { toast.error("Please verify your email before logging in."); - const res = await fetch("/api/signup/resend", { + const res = await fetch("/api/auth/resend", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email }), diff --git a/apps/web/app/(org)/signup/form.tsx b/apps/web/app/(org)/signup/form.tsx index fa3453c256..6f4ad375e1 100644 --- a/apps/web/app/(org)/signup/form.tsx +++ b/apps/web/app/(org)/signup/form.tsx @@ -278,7 +278,6 @@ export function SignupForm() { return; } - sessionStorage.setItem("signup_password", password); toast.success("Verification code sent to your email!"); trackEvent("auth_email_sent", { email_domain: email.split("@")[1] }); diff --git a/apps/web/app/(org)/verify-otp/form.tsx b/apps/web/app/(org)/verify-otp/form.tsx index f5cbc98848..25b4b385a7 100644 --- a/apps/web/app/(org)/verify-otp/form.tsx +++ b/apps/web/app/(org)/verify-otp/form.tsx @@ -87,19 +87,21 @@ export function VerifyOTPForm({ const data = await res.json().catch(() => ({})); if (!res.ok || !data?.status) { - throw data?.message || "Invalid or expired OTP. Please try again."; + throw "Invalid or expired OTP. Please try again."; } - const password = sessionStorage.getItem("signup_password"); - if (!password) { - throw "Something went wrong. Please try logging in again."; + if (!data?.authToken) { + throw "Authentication token not received. Please try again."; } + + // Use the temporary auth token to sign in via credentials provider const result = await signIn("credentials", { email, - password, + otp_token: data.authToken, redirect: false, }); + if (result?.error) { - throw "Login failed after verification. Please log in again."; + throw "Login failed after verification. Please try logging in again."; } return; } diff --git a/packages/database/auth/auth-options.ts b/packages/database/auth/auth-options.ts index 6cc285f0fb..636b815dac 100644 --- a/packages/database/auth/auth-options.ts +++ b/packages/database/auth/auth-options.ts @@ -1,7 +1,7 @@ import crypto from "node:crypto"; import { serverEnv } from "@cap/env"; import { Organisation, User } from "@cap/web-domain"; -import { eq } from "drizzle-orm"; +import {eq, and} from "drizzle-orm"; import type { NextAuthOptions } from "next-auth"; import { getServerSession as _getServerSession } from "next-auth"; import type { Adapter } from "next-auth/adapters"; @@ -14,7 +14,7 @@ import { dub } from "../dub.ts"; import { sendEmail } from "../emails/config.ts"; import { nanoId } from "../helpers.ts"; import { db } from "../index.ts"; -import { organizationMembers, organizations, users } from "../schema.ts"; +import {organizationMembers, organizations, users, verificationTokens} from "../schema.ts"; import { isEmailAllowedForSignup } from "./domain-utils.ts"; import { DrizzleAdapter } from "./drizzle-adapter.ts"; import { verifyPassword } from "@cap/database/crypto"; @@ -112,11 +112,12 @@ export const authOptions = (): NextAuthOptions => { credentials: { email: { label: "Email", type: "text", placeholder: "you@domain.com" }, password: { label: "Password", type: "password" }, + otp_token: { label: "OTP Token", type: "text" }, }, async authorize(credentials) { try { - if (!credentials?.email || !credentials?.password) { - throw new Error("Missing email or password"); + if (!credentials?.email) { + throw new Error("Missing email"); } const [user] = await db() @@ -127,9 +128,56 @@ export const authOptions = (): NextAuthOptions => { if (!user) throw new Error("Invalid email or password"); - console.log(user); + // If otp_token is provided, verify it instead of password + // This is used for completing signup after OTP verification + if (credentials.otp_token) { + const authTokenIdentifier = `auth-token:${credentials.email}`; + const [tokenRecord] = await db() + .select() + .from(verificationTokens) + .where( + and( + eq( + verificationTokens.identifier, + authTokenIdentifier, + ), + eq(verificationTokens.token, credentials.otp_token), + ), + ) + .limit(1); - if(user.password==null){ + if (!tokenRecord) { + throw new Error("Invalid or expired authentication token"); + } + + if (new Date(tokenRecord.expires) < new Date()) { + throw new Error("Authentication token expired"); + } + + // Require email verification + if (!user.emailVerified) { + throw new Error("Please verify your email before logging in."); + } + + // Delete the one-time token after use + await db() + .delete(verificationTokens) + .where(eq(verificationTokens.identifier, authTokenIdentifier)); + + return { + id: user.id, + name: user.name, + email: user.email, + image: user.image, + }; + } + + // Normal password authentication + if (!credentials?.password) { + throw new Error("Missing password"); + } + + if (user.password === null) { throw new Error("No password found for user"); } @@ -138,7 +186,10 @@ export const authOptions = (): NextAuthOptions => { throw new Error("Please verify your email before logging in."); } - const isValid = await verifyPassword(user.password,credentials.password); + const isValid = await verifyPassword( + user.password, + credentials.password, + ); if (!isValid) throw new Error("Invalid email or password"); return { From 52b1250d024965f3205b6330cd72129818ccfa04 Mon Sep 17 00:00:00 2001 From: depak7 Date: Wed, 12 Nov 2025 18:43:22 +0530 Subject: [PATCH 3/3] "Implement password sign-in option (#1036)" --- apps/web/app/(org)/login/form.tsx | 48 ++++++++++++++++---------- apps/web/app/(org)/signup/form.tsx | 4 +-- packages/database/auth/auth-options.ts | 12 +++---- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/apps/web/app/(org)/login/form.tsx b/apps/web/app/(org)/login/form.tsx index b6d3de80b4..6e3890a2d3 100644 --- a/apps/web/app/(org)/login/form.tsx +++ b/apps/web/app/(org)/login/form.tsx @@ -271,33 +271,33 @@ export function LoginForm() { if (res?.ok && !res?.error) { trackEvent("auth_success", { method: "password", is_signup: false }); - router.push(next || "/"); + router.push(next || "/dashboard"); return; } - - // Handle specific known errors first if (res?.error?.toLowerCase().includes("verify")) { toast.error("Please verify your email before logging in."); - const res = await fetch("/api/auth/resend", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ email }), + const resend = await fetch("/api/auth/resend", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), }); - - const data = await res.json(); - if (!data?.status) { - throw "Something went wrong,try other login methods"; - } - router.push(`/verify-otp?email=${encodeURIComponent(email)}&type=credientials`); + + const data = await resend.json(); + if (!data?.status) throw new Error("Something went wrong, try other login methods."); + + router.push(`/verify-otp?email=${encodeURIComponent(email)}&type=credentials`); return; } - - // Otherwise generic error - toast.error("Invalid credentials – try again?"); + + if (res?.error) { + toast.error(res.error); + return; + } + } catch (error) { console.error("Credential login error:", error); - toast.error("Invalid credentials – try again?"); + toast.error("Unexpected error, please try again later."); } finally { setLoading(false); } @@ -498,7 +498,7 @@ const LoginWithEmailAndPassword = ({ autoFocus type="password" placeholder={emailSent ? "" : "password"} - autoComplete="password" + autoComplete="current-password" required value={password} disabled={emailSent || loading} @@ -525,6 +525,18 @@ const LoginWithEmailAndPassword = ({ Login with OTP + + Don't have an account?{" "} + + Sign up here + + ); diff --git a/apps/web/app/(org)/signup/form.tsx b/apps/web/app/(org)/signup/form.tsx index 6f4ad375e1..b54596c38d 100644 --- a/apps/web/app/(org)/signup/form.tsx +++ b/apps/web/app/(org)/signup/form.tsx @@ -274,7 +274,7 @@ export function SignupForm() { setLoading(false); if (!response.ok) { - toast.error(data.error || "Failed to create account"); + toast.error(data.message || "Something went wrong during signup. Please try again."); return; } @@ -505,7 +505,7 @@ const SignUpWithEmailAndPassword = ({ autoFocus type="password" placeholder={emailSent ? "" : "password"} - autoComplete="password" + autoComplete="new-password" required value={password} disabled={emailSent || loading} diff --git a/packages/database/auth/auth-options.ts b/packages/database/auth/auth-options.ts index 636b815dac..7e7166f251 100644 --- a/packages/database/auth/auth-options.ts +++ b/packages/database/auth/auth-options.ts @@ -126,7 +126,7 @@ export const authOptions = (): NextAuthOptions => { .where(eq(users.email, credentials.email)) .limit(1); - if (!user) throw new Error("Invalid email or password"); + if (!user) throw new Error("We couldn’t find your account. Try signing up!"); // If otp_token is provided, verify it instead of password // This is used for completing signup after OTP verification @@ -173,15 +173,11 @@ export const authOptions = (): NextAuthOptions => { } // Normal password authentication - if (!credentials?.password) { - throw new Error("Missing password"); + if (!credentials?.password || user.password === null) { + throw new Error("Invalid email or password"); } - - if (user.password === null) { - throw new Error("No password found for user"); - } - // Require email verification before login + //navigation to verify-otp and sending otp handled at client side if (!user.emailVerified) { throw new Error("Please verify your email before logging in."); }