chore: [app-router-migration 32] migrate the "/auth/forgot-password/*" pages (#13197)
Co-authored-by: Dmytro Hryshyn <dev.dmytroh@gmail.com> Co-authored-by: Benny Joo <sldisek783@gmail.com> Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
co-authored by
Dmytro Hryshyn
Benny Joo
zomars
parent
728a386bf0
commit
bf958c82da
@@ -0,0 +1,19 @@
|
||||
import SetNewUserPassword from "@pages/auth/forgot-password/[id]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/forgot-password/[id]/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
(t) => t("reset_password"),
|
||||
(t) => t("change_your_password")
|
||||
);
|
||||
};
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: SetNewUserPassword,
|
||||
getData: withAppDirSsr(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -0,0 +1,19 @@
|
||||
import ForgotPassword from "@pages/auth/forgot-password";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/forgot-password/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
(t) => t("reset_password"),
|
||||
(t) => t("change_your_password")
|
||||
);
|
||||
};
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: ForgotPassword,
|
||||
getData: withAppDirSsr(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -1,22 +1,21 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { getCsrfToken } from "next-auth/react";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import type { CSSProperties } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Button, PasswordField, Form } from "@calcom/ui";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
import AuthContainer from "@components/ui/AuthContainer";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/forgot-password/[id]/getServerSideProps";
|
||||
|
||||
type Props = {
|
||||
requestId: string;
|
||||
isRequestExpired: boolean;
|
||||
csrfToken: string;
|
||||
csrfToken: string | undefined;
|
||||
};
|
||||
|
||||
export default function Page({ requestId, isRequestExpired, csrfToken }: Props) {
|
||||
@@ -143,33 +142,4 @@ export default function Page({ requestId, isRequestExpired, csrfToken }: Props)
|
||||
}
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const id = context.params?.id as string;
|
||||
|
||||
let resetPasswordRequest = await prisma.resetPasswordRequest.findFirst({
|
||||
where: {
|
||||
id,
|
||||
expires: {
|
||||
gt: new Date(),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
try {
|
||||
resetPasswordRequest &&
|
||||
(await prisma.user.findUniqueOrThrow({ where: { email: resetPasswordRequest.email } }));
|
||||
} catch (e) {
|
||||
resetPasswordRequest = null;
|
||||
}
|
||||
const locale = await getLocale(context.req);
|
||||
return {
|
||||
props: {
|
||||
isRequestExpired: !resetPasswordRequest,
|
||||
requestId: id,
|
||||
csrfToken: await getCsrfToken({ req: context.req }),
|
||||
...(await serverSideTranslations(locale, ["common"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
export { getServerSideProps };
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
"use client";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { debounce } from "lodash";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { getCsrfToken } from "next-auth/react";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import Link from "next/link";
|
||||
import type { CSSProperties, SyntheticEvent } from "react";
|
||||
import React from "react";
|
||||
|
||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, EmailField } from "@calcom/ui";
|
||||
|
||||
import { type inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
import AuthContainer from "@components/ui/AuthContainer";
|
||||
|
||||
export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
||||
import { getServerSideProps } from "@server/lib/forgot-password/getServerSideProps";
|
||||
|
||||
export default function ForgotPassword(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
const csrfToken = "csrfToken" in props ? (props.csrfToken as string) : undefined;
|
||||
const { t } = useLocale();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [error, setError] = React.useState<{ message: string } | null>(null);
|
||||
@@ -145,23 +147,4 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
||||
|
||||
ForgotPassword.PageWrapper = PageWrapper;
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const { req, res } = context;
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
|
||||
// @TODO res will not be available in future pages (app dir)
|
||||
if (session) {
|
||||
res.writeHead(302, { Location: "/" });
|
||||
res.end();
|
||||
return { props: {} };
|
||||
}
|
||||
const locale = await getLocale(context.req);
|
||||
|
||||
return {
|
||||
props: {
|
||||
csrfToken: await getCsrfToken(context),
|
||||
...(await serverSideTranslations(locale, ["common"])),
|
||||
},
|
||||
};
|
||||
};
|
||||
export { getServerSideProps };
|
||||
|
||||
@@ -5,89 +5,92 @@ import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { test } from "../lib/fixtures";
|
||||
import { testBothFutureAndLegacyRoutes } from "../lib/future-legacy-routes";
|
||||
|
||||
test.afterEach(({ users }) => users.deleteAll());
|
||||
|
||||
test("Can reset forgotten password", async ({ page, users }) => {
|
||||
const user = await users.create();
|
||||
testBothFutureAndLegacyRoutes.describe("Forgot password", async () => {
|
||||
test("Can reset forgotten password", async ({ page, users }) => {
|
||||
const user = await users.create();
|
||||
|
||||
// Got to reset password flow
|
||||
await page.goto("/auth/forgot-password");
|
||||
// Got to reset password flow
|
||||
await page.goto("/auth/forgot-password");
|
||||
|
||||
await page.fill('input[name="email"]', `${user.username}@example.com`);
|
||||
await page.press('input[name="email"]', "Enter");
|
||||
await page.fill('input[name="email"]', `${user.username}@example.com`);
|
||||
await page.press('input[name="email"]', "Enter");
|
||||
|
||||
// wait for confirm page.
|
||||
await page.waitForSelector("text=Reset link sent");
|
||||
// wait for confirm page.
|
||||
await page.waitForSelector("text=Reset link sent");
|
||||
|
||||
// As a workaround, we query the db for the last created password request
|
||||
// there should be one, otherwise we throw
|
||||
const { id } = await prisma.resetPasswordRequest.findFirstOrThrow({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
// As a workaround, we query the db for the last created password request
|
||||
// there should be one, otherwise we throw
|
||||
const { id } = await prisma.resetPasswordRequest.findFirstOrThrow({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
// Test when a user changes his email after starting the password reset flow
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
data: {
|
||||
email: `${user.username}-2@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto(`/auth/forgot-password/${id}`);
|
||||
|
||||
await page.waitForSelector("text=That request is expired.");
|
||||
|
||||
// Change the email back to continue testing.
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
email: `${user.username}-2@example.com`,
|
||||
},
|
||||
data: {
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto(`/auth/forgot-password/${id}`);
|
||||
|
||||
const newPassword = `${user.username}-123CAL-${uuid().toString()}`; // To match the password policy
|
||||
|
||||
// Wait for page to fully load
|
||||
await page.waitForSelector("text=Reset Password");
|
||||
|
||||
await page.fill('input[name="new_password"]', newPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await page.waitForSelector("text=Password updated");
|
||||
|
||||
await expect(page.locator(`text=Password updated`)).toBeVisible();
|
||||
// now we check our DB to confirm the password was indeed updated.
|
||||
// we're not logging in to the UI to speed up test performance.
|
||||
const updatedUser = await prisma.user.findUniqueOrThrow({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
password: true,
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expect(await verifyPassword(newPassword, updatedUser.password!)).toBeTruthy();
|
||||
|
||||
// finally, make sure the same URL cannot be used to reset the password again, as it should be expired.
|
||||
await page.goto(`/auth/forgot-password/${id}`);
|
||||
|
||||
await expect(page.locator(`text=Whoops`)).toBeVisible();
|
||||
});
|
||||
|
||||
// Test when a user changes his email after starting the password reset flow
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
data: {
|
||||
email: `${user.username}-2@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto(`/auth/forgot-password/${id}`);
|
||||
|
||||
await page.waitForSelector("text=That request is expired.");
|
||||
|
||||
// Change the email back to continue testing.
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
email: `${user.username}-2@example.com`,
|
||||
},
|
||||
data: {
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto(`/auth/forgot-password/${id}`);
|
||||
|
||||
const newPassword = `${user.username}-123CAL-${uuid().toString()}`; // To match the password policy
|
||||
|
||||
// Wait for page to fully load
|
||||
await page.waitForSelector("text=Reset Password");
|
||||
|
||||
await page.fill('input[name="new_password"]', newPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await page.waitForSelector("text=Password updated");
|
||||
|
||||
await expect(page.locator(`text=Password updated`)).toBeVisible();
|
||||
// now we check our DB to confirm the password was indeed updated.
|
||||
// we're not logging in to the UI to speed up test performance.
|
||||
const updatedUser = await prisma.user.findUniqueOrThrow({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
password: true,
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expect(await verifyPassword(newPassword, updatedUser.password!)).toBeTruthy();
|
||||
|
||||
// finally, make sure the same URL cannot be used to reset the password again, as it should be expired.
|
||||
await page.goto(`/auth/forgot-password/${id}`);
|
||||
|
||||
await expect(page.locator(`text=Whoops`)).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { getCsrfToken } from "next-auth/react";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
|
||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const id = context.params?.id as string;
|
||||
|
||||
let resetPasswordRequest = await prisma.resetPasswordRequest.findFirst({
|
||||
where: {
|
||||
id,
|
||||
expires: {
|
||||
gt: new Date(),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
try {
|
||||
resetPasswordRequest &&
|
||||
(await prisma.user.findUniqueOrThrow({ where: { email: resetPasswordRequest.email } }));
|
||||
} catch (e) {
|
||||
resetPasswordRequest = null;
|
||||
}
|
||||
const locale = await getLocale(context.req);
|
||||
return {
|
||||
props: {
|
||||
isRequestExpired: !resetPasswordRequest,
|
||||
requestId: id,
|
||||
csrfToken: await getCsrfToken({ req: context.req }),
|
||||
...(await serverSideTranslations(locale, ["common"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { getCsrfToken } from "next-auth/react";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
|
||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const { req, res } = context;
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
|
||||
// @TODO res will not be available in future pages (app dir)
|
||||
if (session) {
|
||||
res.writeHead(302, { Location: "/" });
|
||||
res.end();
|
||||
return { props: {} };
|
||||
}
|
||||
const locale = await getLocale(context.req);
|
||||
|
||||
return {
|
||||
props: {
|
||||
csrfToken: await getCsrfToken(context),
|
||||
...(await serverSideTranslations(locale, ["common"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user