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:
Sasha
2024-01-19 21:08:05 +00:00
committed by GitHub
co-authored by Dmytro Hryshyn Benny Joo zomars
parent 728a386bf0
commit bf958c82da
7 changed files with 197 additions and 139 deletions
@@ -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">;
+6 -36
View File
@@ -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 };
+9 -26
View File
@@ -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 };
+80 -77
View File
@@ -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"])),
},
};
}