diff --git a/pages/api/email.ts b/pages/api/email.ts index 68fcb22..0a0cfb4 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -34,7 +34,8 @@ export default async function handler(req, res) { // Retrieve and validate the JWT token //response is a special action that does not require a token - if (action == "email_response") { + //PUBLIC + if (action == "email_response" || action == "account") { switch (emailaction) { case "coverMeAccept": //validate shiftId and assignmentId @@ -201,6 +202,83 @@ export default async function handler(req, res) { }); break; + + case "resetPassword": + // Send password reset form to the user + //parse the request body + + let email = req.body.email || req.query.email; + let actualUser = await prisma.publisher.findUnique({ + where: { + email: email + } + }); + if (!actualUser) { + return res.status(200).json({ message: "Няма потребител с този имейл" }); + } + else { + let requestGuid = req.query.guid; + if (!requestGuid) { + console.log("User: " + email + " requested a password reset"); + let requestGuid = uuidv4(); + //save the request in the database as EventLog + let eventLog = await prisma.eventLog.create({ + data: { + date: new Date(), + publisher: { connect: { id: actualUser.id } }, + type: EventLogType.PasswordResetRequested, + content: JSON.stringify({ guid: requestGuid }) + } + }); + logger.info("User: " + email + " requested a password reset. EventLogId: " + eventLog.id + ""); + + let model = { + email: email, + firstName: actualUser.firstName, + lastName: actualUser.lastName, + resetUrl: process.env.NEXTAUTH_URL + "/api/email?action=email_response&emailaction=resetPassword&guid=" + requestGuid + "&email=" + email, + sentDate: common.getDateFormated(new Date()) + }; + emailHelper.SendEmailHandlebars(to, "resetPassword", model); + res.status(200).json({ message: "Password reset request sent" }); + } + else { + //1. validate the guid + let eventLog = await prisma.eventLog.findFirst({ + where: {//can we query "{ guid: requestGuid }"? + type: EventLogType.PasswordResetRequested, + publisherId: actualUser.id, + date: { + gt: new Date(new Date().getTime() - 24 * 60 * 60 * 1000) //24 hours + } + } + }); + + if (!eventLog) { + return res.status(400).json({ message: "Invalid or expired password reset request" }); + } + else { + let eventLog = await prisma.eventLog.update({ + where: { + id: parseInt(requestGuid) + }, + data: { + type: EventLogType.PasswordResetEmailConfirmed + } + }); + //2. redirect to the password reset page + const messagePageUrl = `/auth/reset-password?email=${email}&resetToken=${requestGuid}`; + res.redirect(messagePageUrl); + } + + //2.login the user + + //3. redirect to the password reset page + } + + } + break; + } // //send email response to the user // const emailResponse = await common.sendEmail(user.email, "Email Action Processed", @@ -220,6 +298,7 @@ export default async function handler(req, res) { } }); + //PRIVATE ACTIONS switch (action) { case "sendCoverMeRequestByEmail": // Send CoverMe request to the users diff --git a/pages/auth/reset-password.tsx b/pages/auth/reset-password.tsx new file mode 100644 index 0000000..82f8a4a --- /dev/null +++ b/pages/auth/reset-password.tsx @@ -0,0 +1,135 @@ +import { use, useEffect, useState } from 'react'; +import Layout from '../../components/layout'; +import axiosInstance from "../../src/axiosSecure"; +import common from '../../src/helpers/common'; +import { EventLogType } from '@prisma/client'; +import { useRouter } from "next/router"; + +export default function ResetPassword(req, res) { + const [email, setEmail] = useState(''); + const [message, setMessage] = useState(''); + const [resetToken, setResetToken] = useState(req.query?.resetToken || ''); + const [isConfirmed, setIsConfirmed] = useState(false); + const router = useRouter(); + + + useEffect(async () => { + if (resetToken) { + const prisma = common.getPrismaClient(); + let eventLog = await prisma.eventLog.findUnique({ + where: { + content: resetToken, + type: EventLogType.PasswordResetEmailConfirmed, + date: { + gt: new Date(new Date().getTime() - 24 * 60 * 60 * 1000) //24 hours + } + } + }); + if (eventLog) { + setIsConfirmed(true); + } + } + }, [resetToken]); + + const handleResetRequest = async (event) => { + event.preventDefault(); + // Call your email API endpoint here + try { + const response = await axiosInstance.post('/api/email?action=account&emailaction=resetPassword', { email }, + { headers: { 'Content-Type': 'application/json' } }); + if (response.data.message) { + setMessage(response.data.message); + } else { + if (response.ok) { + setMessage('Провери твоя имейл за инструкции как да промениш паролата си.'); + } else { + if (response.error) { + setMessage(response.error); + } + } + } + } catch (error) { + setMessage(error.message); + } + }; + + const setNewPassword = async (event) => { + event.preventDefault(); + + try { + const prisma = common.getPrismaClient(); + const user = await prisma.user.findUnique({ + where: { + email + } + }); + if (!user) { + throw new Error('Няма потребител с този имейл.'); + } + + const passHash = await crypto.hash(event.target.newPassword.value, 10); + await prisma.user.update({ + where: { + email + }, + data: { + passwordHashLocalAccount: passHash + } + }); + setMessage('Паролата беше успешно променена.'); + router.push('/auth/signin'); + + } catch (error) { + setMessage(error.message); + } + } + + + return ( + +
+
+

Променете паролата си

+
+ {!isConfirmed && +
+ + setEmail(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> +
} + + {isConfirmed && +
+ + setNewPassword(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> +
} +
+ + +
+ {message &&
{message}
} +
+
+
+
+ ); +} diff --git a/pages/auth/signin.tsx b/pages/auth/signin.tsx index 0326411..632f9ae 100644 --- a/pages/auth/signin.tsx +++ b/pages/auth/signin.tsx @@ -15,7 +15,7 @@ export default function SignIn({ csrfToken }) { // Perform client-side validation if needed if (!email || !password) { - setError('All fields are required'); + setError('Всички полета са задължителни'); return; } @@ -45,49 +45,52 @@ export default function SignIn({ csrfToken }) {
-
-
- {/* Button to sign in with Google */} - + {/* Add more buttons for other SSO providers here in similar style */}
-
-
- -
- - setEmail(e.target.value)} - className="border p-2 w-full" - /> -
-
- - setPassword(e.target.value)} - className="border p-2 w-full" - /> -
- {error &&
{error}
} - - -
-
+ + {/* Email and Password Form */} +
+ +
+ + setEmail(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> +
+
+ + setPassword(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> +
+ {error &&
{error}
} + + {/* */} +
diff --git a/prisma/migrations/20240429233926_add_eventlog_types/migration.sql b/prisma/migrations/20240429233926_add_eventlog_types/migration.sql new file mode 100644 index 0000000..bb47961 --- /dev/null +++ b/prisma/migrations/20240429233926_add_eventlog_types/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE `Eventlog` +MODIFY `type` ENUM( + 'AssignmentReplacementRequested', 'AssignmentReplacementAccepted', 'SentEmail', 'PasswordResetRequested', 'PasswordResetEmailConfirmed', 'PasswordResetCompleted' +) NOT NULL; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 03d8a40..6fcc599 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -263,6 +263,9 @@ enum EventLogType { AssignmentReplacementRequested AssignmentReplacementAccepted SentEmail + PasswordResetRequested + PasswordResetEmailConfirmed + PasswordResetCompleted } model EventLog { diff --git a/src/templates/emails/resetPass.hbs b/src/templates/emails/resetPass.hbs new file mode 100644 index 0000000..15b0125 --- /dev/null +++ b/src/templates/emails/resetPass.hbs @@ -0,0 +1,22 @@ +{{!-- Subject: ССОМ: Нужен е заместник--}} +{{!-- Text: Plain text version of your email. If not provided, HTML tags will be stripped from the HTML version for the +text version. --}} + +
+

Промяна на парола

+

Здравей, {{firstName}} {{lastName}}

+

+ Получихме заявка за промяна на паролата на твоя акаунт. Ако това не си ти, моля игнорирай този имейл. +

+

+ Смени + паролата си +

+ {{!--

Thank you very much for considering my request.

+

Best regards,
{{name}}

--}} +
+ \ No newline at end of file