diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/app/api/auth/forgot-password/route.ts similarity index 52% rename from apps/web/pages/api/auth/forgot-password.ts rename to apps/web/app/api/auth/forgot-password/route.ts index 3b9f4b5d85..084e30ef8c 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/app/api/auth/forgot-password/route.ts @@ -1,22 +1,23 @@ -import type { NextApiRequest, NextApiResponse } from "next"; +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; import { passwordResetRequest } from "@calcom/features/auth/lib/passwordResetRequest"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { emailSchema } from "@calcom/lib/emailSchema"; -import { defaultHandler } from "@calcom/lib/server/defaultHandler"; import prisma from "@calcom/prisma"; -async function handler(req: NextApiRequest, res: NextApiResponse) { - const email = emailSchema.transform((val) => val.toLowerCase()).safeParse(req.body?.email); +async function handler(req: NextRequest) { + const email = emailSchema.transform((val) => val.toLowerCase()).safeParse((await req.json())?.email); if (!email.success) { - return res.status(400).json({ message: "email is required" }); + return NextResponse.json({ message: "email is required" }, { status: 400 }); } // fallback to email if ip is not present - let ip = (req.headers["x-real-ip"] as string) ?? email.data; + let ip = (req.headers.get("x-real-ip") as string) ?? email.data; - const forwardedFor = req.headers["x-forwarded-for"] as string; + const forwardedFor = req.headers.get("x-forwarded-for") as string; if (!ip && forwardedFor) { ip = forwardedFor?.split(",").at(0) ?? email.data; } @@ -34,15 +35,15 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { select: { name: true, email: true, locale: true }, }); // Don't leak info about whether the user exists - if (!user) return res.status(201).json({ message: "password_reset_email_sent" }); + if (!user) return NextResponse.json({ message: "password_reset_email_sent" }, { status: 201 }); await passwordResetRequest(user); - return res.status(201).json({ message: "password_reset_email_sent" }); + return NextResponse.json({ message: "password_reset_email_sent" }, { status: 201 }); } catch (reason) { console.error(reason); - return res.status(500).json({ message: "Unable to create password reset request" }); + return NextResponse.json({ message: "Unable to create password reset request" }, { status: 500 }); } } -export default defaultHandler({ - POST: Promise.resolve({ default: handler }), -}); +const postHandler = defaultResponderForAppDir(handler); + +export { postHandler as POST }; diff --git a/apps/web/pages/api/auth/reset-password.ts b/apps/web/app/api/auth/reset-password/route.ts similarity index 83% rename from apps/web/pages/api/auth/reset-password.ts rename to apps/web/app/api/auth/reset-password/route.ts index 866169af60..95eaa80b4b 100644 --- a/apps/web/pages/api/auth/reset-password.ts +++ b/apps/web/app/api/auth/reset-password/route.ts @@ -1,4 +1,5 @@ -import type { NextApiRequest, NextApiResponse } from "next"; +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; import { z } from "zod"; import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; @@ -13,11 +14,10 @@ const passwordResetRequestSchema = z.object({ requestId: z.string(), // format doesn't matter. }); -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - // Bad Method when not POST - if (req.method !== "POST") return res.status(405).end(); - - const { password: rawPassword, requestId: rawRequestId } = passwordResetRequestSchema.parse(req.body); +async function handler(req: NextRequest) { + const { password: rawPassword, requestId: rawRequestId } = passwordResetRequestSchema.parse( + await req.json() + ); // rate-limited there is a low, very low chance that a password request stays valid long enough // to brute force 3.8126967e+40 options. const maybeRequest = await prisma.resetPasswordRequest.findFirstOrThrow({ @@ -54,12 +54,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }, }); } catch (e) { - return res.status(404).end(); + return NextResponse.json({}, { status: 404 }); } await expireResetPasswordRequest(rawRequestId); - return res.status(201).json({ message: "Password reset." }); + return NextResponse.json({ message: "Password reset." }, { status: 201 }); } async function expireResetPasswordRequest(rawRequestId: string) { @@ -73,3 +73,5 @@ async function expireResetPasswordRequest(rawRequestId: string) { }, }); } + +export { handler as POST }; diff --git a/apps/web/pages/api/auth/setup.ts b/apps/web/app/api/auth/setup/route.ts similarity index 80% rename from apps/web/pages/api/auth/setup.ts rename to apps/web/app/api/auth/setup/route.ts index 78d5f976de..23ef90eb47 100644 --- a/apps/web/pages/api/auth/setup.ts +++ b/apps/web/app/api/auth/setup/route.ts @@ -1,12 +1,12 @@ -import type { NextApiRequest } from "next"; +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; import z from "zod"; import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid"; import { emailRegex } from "@calcom/lib/emailSchema"; import { HttpError } from "@calcom/lib/http-error"; -import { defaultHandler } from "@calcom/lib/server/defaultHandler"; -import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import slugify from "@calcom/lib/slugify"; import prisma from "@calcom/prisma"; import { IdentityProvider } from "@calcom/prisma/enums"; @@ -24,13 +24,13 @@ const querySchema = z.object({ }), }); -async function handler(req: NextApiRequest) { +async function handler(req: NextRequest) { const userCount = await prisma.user.count(); if (userCount !== 0) { throw new HttpError({ statusCode: 400, message: "No setup needed." }); } - const parsedQuery = querySchema.safeParse(req.body); + const parsedQuery = querySchema.safeParse(await req.json()); if (!parsedQuery.success) { throw new HttpError({ statusCode: 422, message: parsedQuery.error.message }); } @@ -54,9 +54,9 @@ async function handler(req: NextApiRequest) { }, }); - return { message: "First admin user created successfully." }; + return NextResponse.json({ message: "First admin user created successfully." }); } -export default defaultHandler({ - POST: Promise.resolve({ default: defaultResponder(handler) }), -}); +const postHandler = defaultResponderForAppDir(handler); + +export { postHandler as POST }; diff --git a/apps/web/components/security/ChangePasswordSection.tsx b/apps/web/components/security/ChangePasswordSection.tsx deleted file mode 100644 index 3683066310..0000000000 --- a/apps/web/components/security/ChangePasswordSection.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import type { SyntheticEvent } from "react"; -import { useState } from "react"; - -import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Button, showToast } from "@calcom/ui"; - -const ChangePasswordSection = () => { - const [oldPassword, setOldPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [errorMessage, setErrorMessage] = useState(null); - const [isSubmitting, setIsSubmitting] = useState(false); - const { t, isLocaleReady } = useLocale(); - // hold display until the locale is loaded - if (!isLocaleReady) { - return null; - } - - const errorMessages: { [key: string]: string } = { - [ErrorCode.IncorrectPassword]: t("current_incorrect_password"), - [ErrorCode.NewPasswordMatchesOld]: t("new_password_matches_old_password"), - }; - - async function changePasswordHandler(e: SyntheticEvent) { - e.preventDefault(); - - if (isSubmitting) { - return; - } - - setIsSubmitting(true); - setErrorMessage(null); - - try { - const response = await fetch("/api/auth/changepw", { - method: "PATCH", - body: JSON.stringify({ oldPassword, newPassword }), - headers: { - "Content-Type": "application/json", - }, - }); - - if (response.status === 200) { - setOldPassword(""); - setNewPassword(""); - showToast(t("password_has_been_changed"), "success"); - return; - } - - const body = await response.json(); - setErrorMessage(errorMessages[body.error] || `${t("something_went_wrong")}${t("please_try_again")}`); - } catch (err) { - console.error(t("error_changing_password"), err); - setErrorMessage(`${t("something_went_wrong")}${t("please_try_again")}`); - } finally { - setIsSubmitting(false); - } - } - - return ( - <> -
-
-
-

{t("change_password")}

-
-
-
- -
- setOldPassword(e.currentTarget.value)} - name="current_password" - id="current_password" - required - className="border-default block w-full rounded-sm text-sm" - placeholder={t("your_old_password")} - /> -
-
-
- -
- setNewPassword(e.currentTarget.value)} - className="border-default block w-full rounded-sm text-sm" - placeholder={t("super_secure_new_password")} - /> -
-
-
- {errorMessage &&

{errorMessage}

} -
- -
-
-
- - ); -}; - -export default ChangePasswordSection; diff --git a/apps/web/pages/api/auth/changepw.ts b/apps/web/pages/api/auth/changepw.ts deleted file mode 100644 index 20dc10cb9f..0000000000 --- a/apps/web/pages/api/auth/changepw.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode"; -import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; -import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; -import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword"; -import prisma from "@calcom/prisma"; -import { IdentityProvider } from "@calcom/prisma/enums"; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const session = await getServerSession({ req }); - - if (!session || !session.user || !session.user.email) { - res.status(401).json({ message: "Not authenticated" }); - return; - } - - const user = await prisma.user.findFirst({ - where: { - email: session.user.email, - }, - select: { - id: true, - password: true, - identityProvider: true, - }, - }); - - if (!user) { - res.status(404).json({ message: "User not found" }); - return; - } - - if (user.identityProvider !== IdentityProvider.CAL) { - return res.status(400).json({ error: ErrorCode.ThirdPartyIdentityProviderEnabled }); - } - - const oldPassword = req.body.oldPassword; - const newPassword = req.body.newPassword; - - const currentPassword = user.password?.hash; - if (!currentPassword) { - return res.status(400).json({ error: ErrorCode.UserMissingPassword }); - } - - const passwordsMatch = await verifyPassword(oldPassword, currentPassword); - if (!passwordsMatch) { - return res.status(403).json({ error: ErrorCode.IncorrectPassword }); - } - - if (oldPassword === newPassword) { - return res.status(400).json({ error: ErrorCode.NewPasswordMatchesOld }); - } - - const hashedPassword = await hashPassword(newPassword); - await prisma.userPassword.upsert({ - where: { - userId: user.id, - }, - create: { - hash: hashedPassword, - userId: user.id, - }, - update: { - hash: hashedPassword, - }, - }); - - res.status(200).json({ message: "Password updated successfully" }); -}