Merge branch 'main' into fix/selectbox-alignment

This commit is contained in:
Lucas Smith
2023-06-17 11:48:11 +10:00
committed by GitHub
30 changed files with 834 additions and 108 deletions

View File

@ -0,0 +1,63 @@
import { NextApiRequest, NextApiResponse } from "next";
import { sendResetPassword } from "@documenso/lib/mail";
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
import prisma from "@documenso/prisma";
import crypto from "crypto";
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const { email } = req.body;
const cleanEmail = email.toLowerCase();
if (!cleanEmail || !/.+@.+/.test(cleanEmail)) {
res.status(400).json({ message: "Invalid email" });
return;
}
const user = await prisma.user.findFirst({
where: {
email: cleanEmail,
},
});
if (!user) {
return res.status(200).json({ message: "A password reset email has been sent." });
}
const existingToken = await prisma.passwordResetToken.findFirst({
where: {
userId: user.id,
createdAt: {
gte: new Date(Date.now() - 1000 * 60 * 60),
},
},
});
if (existingToken) {
return res.status(200).json({ message: "A password reset email has been sent." });
}
const token = crypto.randomBytes(64).toString("hex");
const expiry = new Date();
expiry.setHours(expiry.getHours() + 24); // Set expiry to one hour from now
let passwordResetToken;
try {
passwordResetToken = await prisma.passwordResetToken.create({
data: {
token,
expiry,
userId: user.id,
},
});
} catch (error) {
return res.status(500).json({ message: "Something went wrong" });
}
await sendResetPassword(user, passwordResetToken.token);
return res.status(200).json({ message: "A password reset email has been sent." });
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(postHandler) }),
});

View File

@ -0,0 +1,69 @@
import { NextApiRequest, NextApiResponse } from "next";
import { hashPassword, verifyPassword } from "@documenso/lib/auth";
import { sendResetPasswordSuccessMail } from "@documenso/lib/mail";
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
import prisma from "@documenso/prisma";
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const { token, password } = req.body;
if (!token) {
res.status(400).json({ message: "Invalid token" });
return;
}
const foundToken = await prisma.passwordResetToken.findUnique({
where: {
token,
},
include: {
User: true,
},
});
if (!foundToken) {
return res.status(404).json({ message: "Invalid token." });
}
const now = new Date();
if (now > foundToken.expiry) {
return res.status(400).json({ message: "Token has expired" });
}
const isSamePassword = await verifyPassword(password, foundToken.User.password!);
if (isSamePassword) {
return res.status(400).json({ message: "New password must be different" });
}
const hashedPassword = await hashPassword(password);
const transaction = await prisma.$transaction([
prisma.user.update({
where: {
id: foundToken.userId,
},
data: {
password: hashedPassword,
},
}),
prisma.passwordResetToken.deleteMany({
where: {
userId: foundToken.userId,
},
}),
]);
if (!transaction) {
return res.status(500).json({ message: "Error resetting password." });
}
await sendResetPasswordSuccessMail(foundToken.User);
res.status(200).json({ message: "Password reset successful." });
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(postHandler) }),
});

View File

@ -8,13 +8,13 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const { email, password, source } = req.body;
const cleanEmail = email.toLowerCase();
if (!cleanEmail || !cleanEmail.includes("@")) {
res.status(422).json({ message: "Invalid email" });
if (!cleanEmail || !/.+@.+/.test(cleanEmail)) {
res.status(400).json({ message: "Invalid email" });
return;
}
if (!password || password.trim().length < 7) {
return res.status(422).json({
return res.status(400).json({
message: "Password should be at least 7 characters long.",
});
}

View File

@ -6,53 +6,62 @@ import prisma from "@documenso/prisma";
import { Document as PrismaDocument, SendStatus } from "@prisma/client";
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const user = await getUserFromToken(req, res);
const { id: documentId } = req.query;
const { resendTo: resendTo = [] } = req.body;
try {
const user = await getUserFromToken(req, res);
const { id: documentId } = req.query;
const { resendTo: resendTo = [] } = req.body;
if (!user) return;
if (!user) {
return res.status(401).send("Unauthorized");
}
if (!documentId) {
res.status(400).send("Missing parameter documentId.");
return;
}
if (!documentId) {
return res.status(400).send("Missing parameter documentId.");
}
const document: PrismaDocument = await getDocument(+documentId, req, res);
const document: PrismaDocument = await getDocument(+documentId, req, res);
if (!document) res.status(404).end(`No document with id ${documentId} found.`);
if (!document) {
res.status(404).end(`No document with id ${documentId} found.`);
}
let recipientCondition: any = {
documentId: +documentId,
sendStatus: SendStatus.NOT_SENT,
};
if (resendTo.length) {
recipientCondition = {
let recipientCondition: any = {
documentId: +documentId,
id: { in: resendTo },
sendStatus: SendStatus.NOT_SENT,
};
}
const recipients = await prisma.recipient.findMany({
where: {
...recipientCondition,
},
});
if (resendTo.length) {
recipientCondition = {
documentId: +documentId,
id: { in: resendTo },
};
}
if (!recipients.length) return res.status(200).send(recipients.length);
let sentRequests = 0;
recipients.forEach(async (recipient) => {
await sendSigningRequest(recipient, document, user).catch((err) => {
console.log(err);
return res.status(502).end("Coud not send request for signing.");
const recipients = await prisma.recipient.findMany({
where: {
...recipientCondition,
},
});
if (!recipients.length) {
return res.status(200).send(recipients.length);
}
let sentRequests = 0;
recipients.forEach(async (recipient) => {
await sendSigningRequest(recipient, document, user);
sentRequests++;
});
sentRequests++;
if (sentRequests === recipients.length) {
return res.status(200).send(recipients.length);
}
});
return res.status(502).end("Coud not send request for signing.");
} catch (err) {
return res.status(502).end("Coud not send request for signing.");
}
}
export default defaultHandler({

View File

@ -0,0 +1,30 @@
import Head from "next/head";
import { getUserFromToken } from "@documenso/lib/server";
import ResetPassword from "../../../components/reset-password";
export default function ResetPasswordPage() {
return (
<>
<Head>
<title>Reset Password | Documenso</title>
</Head>
<ResetPassword />
</>
);
}
export async function getServerSideProps(context: any) {
const user = await getUserFromToken(context.req, context.res);
if (user)
return {
redirect: {
source: "/login",
destination: "/dashboard",
permanent: false,
},
};
return {
props: {},
};
}

View File

@ -0,0 +1,20 @@
import React from "react";
import Logo from "../../../components/logo";
export default function ResetPage() {
return (
<div className="flex min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md space-y-8">
<div>
<Logo className="mx-auto h-20 w-auto"></Logo>
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
Reset Password
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
The token you provided is invalid. Please try again.
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,32 @@
import { GetServerSideProps, GetServerSidePropsContext } from "next";
import Head from "next/head";
import { getUserFromToken } from "@documenso/lib/server";
import ForgotPassword from "../components/forgot-password";
export default function ForgotPasswordPage() {
return (
<>
<Head>
<title>Forgot Password | Documenso</title>
</Head>
<ForgotPassword />
</>
);
}
export async function getServerSideProps({ req }: GetServerSidePropsContext) {
const user = await getUserFromToken(req);
if (user)
return {
redirect: {
source: "/login",
destination: "/dashboard",
permanent: false,
},
};
return {
props: {},
};
}