@starklabs/backend-core 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.env.example +21 -0
  2. package/dist/js/config/cloudinary.js +18 -0
  3. package/dist/js/config/config.js +11 -0
  4. package/dist/js/config/duration.js +22 -0
  5. package/dist/js/core/app.js +122 -0
  6. package/dist/js/core/auth/OTP.js +115 -0
  7. package/dist/js/core/auth/auth.controller.js +63 -0
  8. package/dist/js/core/auth/auth.service.js +290 -0
  9. package/dist/js/core/auth/auth.validation.js +95 -0
  10. package/dist/js/core/crud/crud.controller.js +95 -0
  11. package/dist/js/core/crud/crud.service.js +296 -0
  12. package/dist/js/core/index.js +3 -0
  13. package/dist/js/index.js +44 -55
  14. package/dist/js/lib/db.js +40 -0
  15. package/dist/js/lib/field.types.js +174 -0
  16. package/dist/js/lib/model.factory.js +19 -0
  17. package/dist/js/lib/model.registry.js +4 -0
  18. package/dist/js/lib/schema.builder.js +35 -0
  19. package/dist/js/lib/zod.validations.js +247 -0
  20. package/dist/js/middleware/auth.middleware.js +51 -0
  21. package/dist/js/middleware/error.middleware.js +28 -0
  22. package/dist/js/middleware/socket.middleware.js +29 -0
  23. package/dist/js/utils/AppLog.js +2 -1
  24. package/dist/js/utils/deleteFile.js +22 -0
  25. package/dist/js/utils/index.js +10 -1
  26. package/dist/js/utils/jwt.js +12 -20
  27. package/dist/js/utils/libsodium.js +19 -3
  28. package/dist/js/utils/rateLimiter.js +25 -0
  29. package/dist/js/utils/uploadFile.js +43 -0
  30. package/handlerMap.js +33 -0
  31. package/package.json +17 -4
  32. package/test.js +36 -0
  33. package/dist/cjs/db.cjs +0 -17
  34. package/dist/cjs/index.cjs +0 -59
  35. package/dist/cjs/utils/AppError.cjs +0 -13
  36. package/dist/cjs/utils/AppLog.cjs +0 -13
  37. package/dist/cjs/utils/asyncHandler.cjs +0 -6
  38. package/dist/cjs/utils/jwt.cjs +0 -38
  39. package/dist/cjs/utils/libsodium.cjs +0 -145
  40. package/dist/cjs/utils/successResponse.cjs +0 -13
  41. package/dist/js/db.js +0 -19
package/.env.example ADDED
@@ -0,0 +1,21 @@
1
+ # development@v3
2
+ PORT=4000
3
+ API_VERSION=1 # must be an integer
4
+
5
+ # Duration format: 1m | 5m | 10m | 15m | 30m | 1h | 6h | 12h | 1d | 3d | 7d | 14d | 30d
6
+ TOKEN_EXPIRY=7d
7
+ JWT_SECRET=
8
+ MASTER_KEY=
9
+ ISOFFLINE=true
10
+ ENV=development
11
+ INTERNAL_ROLES=["developer","admin"]
12
+ RESEND_API_KEY=
13
+
14
+ # Duration format: 1m | 5m | 10m | 15m | 30m | 1h | 6h | 12h | 1d | 3d | 7d | 14d | 30d
15
+ RATE_LIMIT_DURATION=15m
16
+ RATE_LIMIT_REQ=200
17
+ RATE_LIMIT_MSG="Too many requests, please try again later."
18
+ CLOUDINARY_API_SECRET=
19
+ CLOUDINARY_API_KEY=5
20
+ CLOUDINARY_CLOUD_NAME=
21
+ CLOUDINARY_FOLDER_NAME=uploads
@@ -0,0 +1,18 @@
1
+ import multer from "multer";
2
+ import { v2 as cloudinary } from "cloudinary";
3
+
4
+ const upload = multer({ storage: multer.memoryStorage() });
5
+
6
+ const configureCloudinary = ({
7
+ cloudinaryAPIKey,
8
+ cloudinaryCloudName,
9
+ cloudinaryAPISecret,
10
+ }) => {
11
+ cloudinary.config({
12
+ cloud_name: cloudinaryCloudName,
13
+ api_key: cloudinaryAPIKey,
14
+ api_secret: cloudinaryAPISecret,
15
+ });
16
+ };
17
+
18
+ export { cloudinary, configureCloudinary, upload };
@@ -0,0 +1,11 @@
1
+ let config = {};
2
+
3
+ const setConfig = (options) => {
4
+ config = options;
5
+ };
6
+
7
+ const getConfig = () => {
8
+ return config;
9
+ };
10
+
11
+ export { setConfig, getConfig, config };
@@ -0,0 +1,22 @@
1
+ export const DURATIONS = {
2
+ "1m": 60 * 1000,
3
+ "5m": 5 * 60 * 1000,
4
+ "10m": 10 * 60 * 1000,
5
+ "15m": 15 * 60 * 1000,
6
+ "30m": 30 * 60 * 1000,
7
+ "1h": 60 * 60 * 1000,
8
+ "6h": 6 * 60 * 60 * 1000,
9
+ "12h": 12 * 60 * 60 * 1000,
10
+ "1d": 24 * 60 * 60 * 1000,
11
+ "3d": 3 * 24 * 60 * 60 * 1000,
12
+ "7d": 7 * 24 * 60 * 60 * 1000,
13
+ "14d": 14 * 24 * 60 * 60 * 1000,
14
+ "30d": 30 * 24 * 60 * 60 * 1000,
15
+ };
16
+
17
+ const getDuration = (key) => {
18
+ if (!DURATIONS[key]) throw new Error("Invalid expiry key");
19
+ return DURATIONS[key];
20
+ };
21
+
22
+ export default getDuration;
@@ -0,0 +1,122 @@
1
+ import express from "express";
2
+ import AppLog from "../utils/AppLog.js";
3
+ import connectDB from "../lib/db.js";
4
+ import { crud, auth } from "./index.js";
5
+ import mongoose from "mongoose";
6
+ import createModel from "../lib/model.factory.js";
7
+ import registerModel from "../lib/model.registry.js";
8
+ import errorMiddleware from "../middleware/error.middleware.js";
9
+ import { getConfig, config } from "../config/config.js";
10
+ import AppError from "../utils/AppError.js";
11
+ import helmet from "helmet";
12
+ import cors from "cors";
13
+ import cookieParser from "cookie-parser";
14
+ import { apiLimiter } from "../utils/rateLimiter.js";
15
+ import { configureCloudinary } from "../config/cloudinary.js";
16
+
17
+ const app = express();
18
+ app.use(express.json());
19
+ app.use(express.urlencoded({ extended: true }));
20
+
21
+ app.use(express.json());
22
+
23
+ const registerCollections = (collections, apiVersion) => {
24
+ collections.forEach(
25
+ ({
26
+ route,
27
+ modelName,
28
+ mongooseSchema,
29
+ otpSchema = false,
30
+ routes,
31
+ validations,
32
+ }) => {
33
+ // 1. create model dynamically
34
+ if (modelName && mongooseSchema) {
35
+ const model = createModel(modelName, mongooseSchema);
36
+
37
+ // register model
38
+ registerModel[modelName] = model;
39
+
40
+ if (otpSchema) {
41
+ const otpModel = createModel("otpUser", otpSchema);
42
+ registerModel["otpModel"] = otpModel;
43
+ }
44
+ }
45
+
46
+ // 3. register CRUD routes
47
+ if (route === "auth") {
48
+ auth(route, routes, modelName, validations, apiVersion);
49
+ config.userModel = modelName;
50
+ } else {
51
+ crud(route, routes, modelName, validations, apiVersion);
52
+ }
53
+ },
54
+ );
55
+ };
56
+
57
+ const configValidation = ({ port, collections }) => {
58
+ if (!port) {
59
+ throw new Error("Port is missing in StarkCore.create({})");
60
+ }
61
+
62
+ if (!collections) {
63
+ throw new Error("Collections array is missing in StarkCore.create({})");
64
+ } else if (!Array.isArray(collections)) {
65
+ throw new Error("Collections must be an array datatype");
66
+ } else if (collections.length === 0) {
67
+ throw new Error("Collections shouldn't be empty in StarkCore.create({})");
68
+ }
69
+ };
70
+
71
+ const startServer = async () => {
72
+ try {
73
+ const config = getConfig();
74
+ configValidation(config);
75
+ const {
76
+ port,
77
+ collections,
78
+ rateLimitDuration,
79
+ maxReqLimit,
80
+ rateLimitMsg,
81
+ cloudinaryAPIKey,
82
+ cloudinaryCloudName,
83
+ cloudinaryAPISecret,
84
+ apiVersion,
85
+ } = config;
86
+
87
+ app.use(cors());
88
+ app.use(helmet());
89
+ app.use(cookieParser());
90
+ app.use(apiLimiter({ rateLimitDuration, maxReqLimit, rateLimitMsg }));
91
+
92
+ connectDB();
93
+
94
+ configureCloudinary({
95
+ cloudinaryAPIKey,
96
+ cloudinaryCloudName,
97
+ cloudinaryAPISecret,
98
+ });
99
+
100
+ registerCollections(collections, apiVersion);
101
+
102
+ app.use((req, res) => {
103
+ res.status(404).json({
104
+ success: false,
105
+ message: `Cannot ${req.method} ${req.originalUrl}`,
106
+ });
107
+ });
108
+
109
+ app.use(errorMiddleware);
110
+
111
+ app.listen(port, () => {
112
+ console.log(
113
+ `Server running at http://localhost:${port}/api/v${apiVersion}`,
114
+ );
115
+ });
116
+ } catch (err) {
117
+ console.log(err);
118
+ AppLog("X", "startServer", err.message);
119
+ }
120
+ };
121
+
122
+ export { startServer, app };
@@ -0,0 +1,115 @@
1
+ // module imports
2
+ import { Resend } from "resend";
3
+ import crypto from "crypto";
4
+
5
+ // custom imports
6
+ import AppError from "../../utils/AppError.js";
7
+ import { getConfig } from "../../config/config.js";
8
+
9
+ // sendOTP()
10
+ const sendOTP = async (email) => {
11
+ // OTP
12
+ const OTP = crypto.randomInt(100000, 1000000).toString();
13
+ const otpExpiry = Date.now() + 1000 * 60 * 5;
14
+
15
+ // config
16
+ const { isOffline, resendAPIKey } = getConfig();
17
+ if (isOffline) return { OTP, otpExpiry };
18
+
19
+ if (!resendAPIKey)
20
+ throw new AppError("resendAPIKey is missing in StarkCore.create({})");
21
+
22
+ // html
23
+ const htmlTemp = `<!DOCTYPE html>
24
+ <html>
25
+ <head>
26
+ <meta charset="UTF-8" />
27
+ <title>Email Verification</title>
28
+ </head>
29
+ <body style="margin:0; padding:0; font-family:Arial, sans-serif;">
30
+ <table width="100%" cellpadding="0" cellspacing="0" border="0" style="padding:40px 0; padding-top: 0px;">
31
+ <tr>
32
+ <td align="center">
33
+ <table width="500" cellpadding="0" cellspacing="0" border="0" style="border-radius:12px; box-shadow:0 10px 25px rgba(0,0,0,0.08); padding:40px; padding-top: 10px;">
34
+
35
+ <tr>
36
+ <td align="center" style="padding-bottom:20px;">
37
+ <h2 style="margin:0; color:#111827; font-weight:600;">Stark Vault</h2>
38
+ </td>
39
+ </tr>
40
+
41
+ <tr>
42
+ <td align="center" style="padding-bottom:20px;">
43
+ <h1 style="margin:0; font-size:22px; color:#111827;">
44
+ Email Verification Code
45
+ </h1>
46
+ </td>
47
+ </tr>
48
+
49
+ <tr>
50
+ <td align="center" style="padding-bottom:30px; color:#6b7280; font-size:15px; line-height:1.6;">
51
+ Use the verification code below to complete your sign-in process.
52
+ This code will expire in <strong>5 minutes</strong>.
53
+ </td>
54
+ </tr>
55
+
56
+ <tr>
57
+ <td align="center" style="padding-bottom:30px;">
58
+ <div style="
59
+ display:inline-block;
60
+ padding:18px 32px;
61
+ font-size:28px;
62
+ letter-spacing:8px;
63
+ font-weight:bold;
64
+ color:#111827;
65
+ background:#f3f4f6;
66
+ border-radius:8px;
67
+ border:1px solid #e5e7eb;">
68
+ ${OTP}
69
+ </div>
70
+ </td>
71
+ </tr>
72
+
73
+ <tr>
74
+ <td align="center" style="color:#9ca3af; font-size:13px; line-height:1.5;">
75
+ If you didn’t request this code, you can safely ignore this email.
76
+ Never share your verification code with anyone.
77
+ </td>
78
+ </tr>
79
+
80
+ <tr>
81
+ <td style="padding-top:30px;">
82
+ <hr style="border:none; border-top:1px solid #e5e7eb;" />
83
+ </td>
84
+ </tr>
85
+
86
+ <tr>
87
+ <td align="center" style="padding-top:15px; font-size:12px; color:#9ca3af;">
88
+ © 2026 Your Company. All rights reserved.
89
+ </td>
90
+ </tr>
91
+
92
+ </table>
93
+ </td>
94
+ </tr>
95
+ </table>
96
+ </body>
97
+ </html>`;
98
+
99
+ const resend = new Resend(resendAPIKey);
100
+ const { data, error } = await resend.emails.send({
101
+ from: "Stark Vault <noreply@vault.musastark.space>",
102
+ to: email,
103
+ subject: "OTP - StarkVault",
104
+ html: htmlTemp,
105
+ });
106
+
107
+ if (error) {
108
+ console.log("[OTP.js] 102: ", error);
109
+ throw new AppError(error.message, 409);
110
+ }
111
+
112
+ return { OTP, otpExpiry };
113
+ };
114
+
115
+ export default sendOTP;
@@ -0,0 +1,63 @@
1
+ import { app } from "../app.js";
2
+ import mongoose, { model } from "mongoose";
3
+ import asyncHandler from "../../utils/asyncHandler.js";
4
+ import authService from "./auth.service.js";
5
+ import registerModel from "../../lib/model.registry.js";
6
+ import getDuration from "../../config/duration.js";
7
+ import AppError from "../../utils/AppError.js";
8
+ import zodValidations from "../../lib/zod.validations.js";
9
+ import z from "zod";
10
+ import { getConfig } from "../../config/config.js";
11
+
12
+ const auth = (route, routes, modelName, validations, apiVersion) => {
13
+ const { ENV, tokenExpiry } = getConfig();
14
+
15
+ routes.forEach((el) => {
16
+ app[el.method](
17
+ `/api/v${apiVersion}/${route}${el.path}`,
18
+ asyncHandler(async (req, res) => {
19
+ const Model = registerModel[modelName];
20
+ if (!Model) throw new Error(`Model not found for endpoint: ${el.path}`);
21
+ const OTPModel = registerModel["otpModel"];
22
+ if (!OTPModel)
23
+ throw new AppError("otpSchema is missing in auth collection");
24
+
25
+ const zodObj = z.object(validations[el.handler]);
26
+ const isValid = await zodObj.safeParse(req.body || {});
27
+
28
+ if (!isValid.success) {
29
+ const issue = isValid.error.issues[0];
30
+
31
+ if (issue.code === "invalid_type")
32
+ throw new AppError(`${issue.path.join(".")} is required`);
33
+
34
+ throw new AppError(issue.message, 409);
35
+ }
36
+
37
+ // send data to service
38
+ const result = await authService[el.handler]({
39
+ body: isValid.data,
40
+ Model,
41
+ OTPModel,
42
+ });
43
+ if (result?.token) {
44
+ res.cookie("authToken", result.token, {
45
+ httpOnly: true,
46
+ maxAge: getDuration(tokenExpiry),
47
+ sameSite: ENV === "production" ? "none" : "lax",
48
+ secure: ENV === "production" ? true : false,
49
+ domain: ENV === "production" ? ".musastark.space" : undefined,
50
+ });
51
+ }
52
+
53
+ return res.json({
54
+ success: true,
55
+ user: result?.user,
56
+ message: result?.msg,
57
+ });
58
+ }),
59
+ );
60
+ });
61
+ };
62
+
63
+ export default auth;
@@ -0,0 +1,290 @@
1
+ import registerModel from "../../lib/model.registry.js";
2
+ import {
3
+ AppError,
4
+ hash,
5
+ verifyHash,
6
+ signJWT,
7
+ AppLog,
8
+ } from "../../utils/index.js";
9
+ import { getConfig } from "../../config/config.js";
10
+
11
+ // custom imports
12
+ import sendOTP from "./OTP.js";
13
+
14
+ // signup
15
+ const signup = async ({ body, Model, OTPModel }) => {
16
+ const foundUser = await Model.findOne({ email: body.email });
17
+ if (foundUser) {
18
+ if (foundUser.provider === "local")
19
+ throw new AppError(
20
+ "User with this email already exists. Please login.",
21
+ 409,
22
+ );
23
+
24
+ if (foundUser.provider === "google")
25
+ throw new AppError(
26
+ "Please login with Google. User with this email already exists with Google provider.",
27
+ 409,
28
+ );
29
+ }
30
+
31
+ const reqOTPUser = await OTPModel.findOne({ email: body.email });
32
+ if (reqOTPUser) throw new AppError("You already requested an OTP.");
33
+
34
+ const { OTP, otpExpiry } = await sendOTP(body.email);
35
+ if (getConfig().isOffline) {
36
+ console.log("OTP:", OTP);
37
+ }
38
+
39
+ const hashedPassword = await hash(body.password);
40
+ const hashedOTP = await hash(OTP);
41
+
42
+ body.password = hashedPassword;
43
+ body.role = "user";
44
+ body = { ...body, otp: hashedOTP, otpExpiry, provider: "local" };
45
+
46
+ await OTPModel.create(body);
47
+
48
+ return { msg: "OTP sent successfully" };
49
+ };
50
+
51
+ // twoFactorAuth
52
+ const twoFactorAuth = async ({ body, Model, OTPModel }) => {
53
+ const { email, otp } = body;
54
+ const otpuser = await OTPModel.findOneAndUpdate(
55
+ { email },
56
+ { $inc: { otpCount: 1 } },
57
+ { returnDocument: "after" },
58
+ )
59
+ .select("+password")
60
+ .lean();
61
+
62
+ if (!otpuser)
63
+ throw new AppError(
64
+ "You didn't request OTP. Please try re-login, signup or check your email.",
65
+ 409,
66
+ );
67
+
68
+ // ifExpire
69
+ if (Date.now() > otpuser.otpExpiry) {
70
+ throw new AppError("OTP expired. Please request another one.", 409);
71
+ }
72
+
73
+ // anti brute force
74
+ if (otpuser.otpCount >= 10)
75
+ throw new AppError(
76
+ "OTP verification limit reached. Please request another one.",
77
+ 409,
78
+ );
79
+
80
+ // ifInValid
81
+ const isValid = await verifyHash(otp.toString(), otpuser.otp);
82
+ if (!isValid) throw new AppError("Invalid OTP", 409);
83
+
84
+ if (otpuser.type === "forgotPassword") {
85
+ await OTPModel.updateOne({ email }, { $set: { status: "verified" } });
86
+
87
+ return { msg: "Verified successfully" };
88
+ }
89
+
90
+ // insert in user
91
+ delete otpuser.otpExpiry;
92
+ delete otpuser.otp;
93
+ delete otpuser.otpCount;
94
+
95
+ const foundUser = await Model.findOne({ email });
96
+ if (!foundUser) {
97
+ await Model.create(otpuser);
98
+ }
99
+
100
+ [
101
+ "password",
102
+ "createdAt",
103
+ "updatedAt",
104
+ "__v",
105
+ "provider",
106
+ "type",
107
+ "status",
108
+ ].forEach((el) => {
109
+ delete otpuser[el];
110
+ });
111
+
112
+ const { jwtSecret, tokenExpiry, isOffline } = getConfig();
113
+ if (!jwtSecret) throw new AppError("jwtSecret is missing in StarkCore({})");
114
+ if (!tokenExpiry)
115
+ throw new AppError("jwtExpiry is missing in StarkCore.create({})");
116
+
117
+ const token = signJWT(
118
+ {
119
+ email: otpuser.email,
120
+ id: otpuser._id,
121
+ role: otpuser.role,
122
+ },
123
+ jwtSecret,
124
+ tokenExpiry,
125
+ );
126
+
127
+ await OTPModel.deleteOne({ email });
128
+
129
+ return { token, user: otpuser, msg: "Verified successfully" };
130
+ };
131
+
132
+ // resendOTP
133
+ const resendOTP = async ({ body, OTPModel }) => {
134
+ const user = await OTPModel.findOne({ email: body.email });
135
+ if (!user)
136
+ throw new AppError(
137
+ "OTP can't be sent. Please try re-login or signup.",
138
+ 409,
139
+ );
140
+
141
+ const { OTP, otpExpiry } = await sendOTP(body.email);
142
+ if (getConfig().isOffline) {
143
+ console.log("OTP: ", OTP);
144
+ }
145
+ const hashedOTP = await hash(OTP);
146
+
147
+ await OTPModel.updateOne(
148
+ { email: body.email },
149
+ {
150
+ otp: hashedOTP,
151
+ otpExpiry,
152
+ otpCount: 0,
153
+ },
154
+ );
155
+
156
+ return { msg: "OTP sent successfully" };
157
+ };
158
+
159
+ // login
160
+ const login = async ({ body, Model, OTPModel }) => {
161
+ let user = await Model.findOne({ email: body.email })
162
+ .select("+password")
163
+ .lean();
164
+
165
+ const reqOTPUser = await OTPModel.findOne({ email: body.email });
166
+ if (reqOTPUser) throw new AppError("You already requested an OTP.");
167
+
168
+ if (!user)
169
+ throw new AppError(
170
+ "User with this email not found. Please create your account.",
171
+ 404,
172
+ );
173
+
174
+ if (user.provider === "google")
175
+ throw new AppError(
176
+ "Please login with Google. User with this email already exists with Google provider.",
177
+ 409,
178
+ );
179
+
180
+ const isValid = await verifyHash(body.password, user.password);
181
+ if (!isValid) throw new AppError("Invalid password", 409);
182
+
183
+ const { OTP, otpExpiry } = await sendOTP(body.email);
184
+ if (getConfig().isOffline) {
185
+ console.log("OTP: ", OTP);
186
+ }
187
+ const hashedOTP = await hash(OTP);
188
+
189
+ user = { ...user, otp: hashedOTP, otpExpiry };
190
+
191
+ await OTPModel.create(user);
192
+
193
+ return { msg: "OTP sent successfully" };
194
+ };
195
+
196
+ // forgotPassword
197
+ const forgotPassword = async ({ body, Model, OTPModel }) => {
198
+ let user = await Model.findOne({ email: body.email })
199
+ .select("+password")
200
+ .lean();
201
+ if (!user)
202
+ throw new AppError(
203
+ "User with this email not found. Please create your account.",
204
+ 404,
205
+ );
206
+
207
+ const reqOTPUser = await OTPModel.findOne({ email: body.email });
208
+ if (reqOTPUser) {
209
+ if (reqOTPUser.type === "forgotPassword") {
210
+ throw new AppError("You already requested an OTP");
211
+ } else {
212
+ await OTPModel.deleteOne({ email: body.email });
213
+ }
214
+ }
215
+
216
+ const { OTP, otpExpiry } = await sendOTP(body.email);
217
+ if (getConfig().isOffline) {
218
+ console.log("OTP: ", OTP);
219
+ }
220
+ const hashedOTP = await hash(OTP);
221
+
222
+ await OTPModel.create({
223
+ ...user,
224
+ otp: hashedOTP,
225
+ otpExpiry,
226
+ type: "forgotPassword",
227
+ });
228
+
229
+ return { msg: "OTP sent successfully" };
230
+ };
231
+
232
+ // resetPassword
233
+ const resetPassword = async ({ body, Model, OTPModel }) => {
234
+ const { email, password } = body;
235
+
236
+ const otpUser = await OTPModel.findOne({ email: body.email });
237
+ if (!otpUser)
238
+ throw new AppError(
239
+ "You didn't requested OTP. Please send request to forgot-password.",
240
+ );
241
+
242
+ if (otpUser.type !== "forgotPassword")
243
+ throw new AppError("Unauthorized. Please request to forgot-password");
244
+
245
+ if (otpUser.status === "pending")
246
+ throw new AppError(
247
+ "You haven't verfied your email yet. Please use the OTP we just sent on your email",
248
+ );
249
+
250
+ let user = await Model.findOne({ email: body.email }).select("+password");
251
+ if (!user)
252
+ throw new AppError(
253
+ "User with this email not found. Please create your account.",
254
+ 404,
255
+ );
256
+
257
+ user.password = await hash(body.password);
258
+
259
+ await user.save();
260
+
261
+ await OTPModel.deleteOne({ email });
262
+
263
+ return { msg: "Password updated successfully" };
264
+ };
265
+
266
+ const deleteAccount = async ({ body, Model }) => {
267
+ const { email, password } = body;
268
+
269
+ let user = await Model.findOne({ email }).select("+password").lean();
270
+
271
+ if (!user)
272
+ throw new AppError("User not found. Please check your email.", 404);
273
+
274
+ const isValid = await verifyHash(body.password, user.password);
275
+ if (!isValid) throw new AppError("Invalid password", 409);
276
+
277
+ await Model.deleteOne({ email });
278
+
279
+ return { msg: "Account deleted successfully" };
280
+ };
281
+
282
+ export default {
283
+ login,
284
+ signup,
285
+ twoFactorAuth,
286
+ resendOTP,
287
+ forgotPassword,
288
+ resetPassword,
289
+ deleteAccount,
290
+ };