From adbf694222c5df03530ce2a2256bfe93414841a1 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Tue, 25 Feb 2025 23:09:28 +0000 Subject: [PATCH] added recoverPassword endpoints --- .../services/auth/routes/auth/password/put.js | 36 +++---- .../auth/routes/auth/recover-password/post.js | 94 +++++++++++++++++++ packages/server/utils/isEmail.js | 7 ++ 3 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 packages/server/services/auth/routes/auth/recover-password/post.js create mode 100644 packages/server/utils/isEmail.js diff --git a/packages/server/services/auth/routes/auth/password/put.js b/packages/server/services/auth/routes/auth/password/put.js index 8dba178e..c9f50853 100644 --- a/packages/server/services/auth/routes/auth/password/put.js +++ b/packages/server/services/auth/routes/auth/password/put.js @@ -2,22 +2,24 @@ import Account from "@classes/account" import requiredFields from "@shared-utils/requiredFields" export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - requiredFields(["old_password", "new_password"], req.body) + //middlewares: ["withAuthentication"], + fn: async (req) => { + requiredFields(["new_password"], req.body) - await Account.changePassword( - { - user_id: req.auth.session.user_id, - old_password: req.body.old_password, - new_password: req.body.new_password, - log_comment: "Changed from password change request" - }, - req - ) + await Account.changePassword( + { + user_id: req.auth?.session?.user_id ?? null, + old_password: req.body.old_password, + new_password: req.body.new_password, + verificationToken: req.body.verificationToken, + code: req.body.code, + log_comment: "Changed from password change request", + }, + req, + ) - return { - message: "Password changed" - } - } -} \ No newline at end of file + return { + message: "Password changed", + } + }, +} diff --git a/packages/server/services/auth/routes/auth/recover-password/post.js b/packages/server/services/auth/routes/auth/recover-password/post.js new file mode 100644 index 00000000..0c53dd6c --- /dev/null +++ b/packages/server/services/auth/routes/auth/recover-password/post.js @@ -0,0 +1,94 @@ +import { User, PasswordRecover } from "@db_models" +import AuthToken from "@shared-classes/AuthToken" + +import obscureEmail from "@shared-utils/obscureEmail" +import isEmail from "@shared-utils/isEmail" + +function getClientDeviceData(req) { + const ip = + req.headers["x-forwarded-for"] ?? req.socket?.remoteAddress ?? req.ip + const userAgent = req.headers["user-agent"] + + return { ip_address: ip, client: userAgent } +} + +export default { + fn: async (req) => { + // find user by email or username + const { account } = req.body + + const userSearchQuery = {} + + if (isEmail(account)) { + userSearchQuery.email = account + } else { + userSearchQuery.username = account + } + + const user = await User.findOne(userSearchQuery).select("+email") + + if (!user) { + throw new OperationError(404, "User not found") + } + + let passwordRecoverSession = await PasswordRecover.findOne({ + user_id: user._id.toString(), + }) + + // check if session exist, and if it's expired + if (passwordRecoverSession) { + const now = new Date() + const expires = passwordRecoverSession.expires_at + + // if not expired, thow a error + if (expires > now) { + throw new OperationError( + 400, + "Password recovery session is still active", + ) + } else { + // destroy session + await PasswordRecover.findOneAndDelete({ + _id: passwordRecoverSession._id.toString(), + }) + } + } + + // expires in 5 minutes + const expiresIn = 1000 * 60 * 5 + + passwordRecoverSession = new PasswordRecover({ + user_id: user._id.toString(), + created_at: new Date(), + expires_at: new Date(Date.now() + expiresIn), + code: global.nanoid(8), + ...getClientDeviceData(req), + }) + + await passwordRecoverSession.save() + + const verificationToken = await AuthToken.signToken({ + recoverySessionId: passwordRecoverSession._id.toString(), + user_id: user._id.toString(), + }) + + ipc.invoke("ems", "apr:send", { + code: passwordRecoverSession.code, + created_at: passwordRecoverSession.created_at, + expires_at: passwordRecoverSession.expires_at, + user: user.toObject(), + ...getClientDeviceData(req), + }) + + return { + user_id: user._id.toString(), + email: obscureEmail(user.email), + expires_at: passwordRecoverSession.expires_at, + expires_in: Math.floor( + (passwordRecoverSession.expires_at - Date.now()) / 1000 / 60, + ), + code_length: passwordRecoverSession.code.toString().length, + verificationToken: verificationToken, + } + }, +} diff --git a/packages/server/utils/isEmail.js b/packages/server/utils/isEmail.js new file mode 100644 index 00000000..39124d83 --- /dev/null +++ b/packages/server/utils/isEmail.js @@ -0,0 +1,7 @@ +export default (email) => { + const checkIfIsEmail = (email) => { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) + } + + return checkIfIsEmail(email) +}