mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 02:32:00 +10:00
Merge branch 'main' into fix/selectbox-alignment
This commit is contained in:
63
apps/web/pages/api/auth/forgot-password.ts
Normal file
63
apps/web/pages/api/auth/forgot-password.ts
Normal 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) }),
|
||||
});
|
||||
69
apps/web/pages/api/auth/reset-password.ts
Normal file
69
apps/web/pages/api/auth/reset-password.ts
Normal 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) }),
|
||||
});
|
||||
@ -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.",
|
||||
});
|
||||
}
|
||||
|
||||
@ -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({
|
||||
|
||||
30
apps/web/pages/auth/reset/[token].tsx
Normal file
30
apps/web/pages/auth/reset/[token].tsx
Normal 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: {},
|
||||
};
|
||||
}
|
||||
20
apps/web/pages/auth/reset/index.tsx
Normal file
20
apps/web/pages/auth/reset/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
32
apps/web/pages/forgot-password.tsx
Normal file
32
apps/web/pages/forgot-password.tsx
Normal 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: {},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user