@starklabs/backend-core 1.1.1 → 1.2.1
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.
- package/.env.example +21 -0
- package/dist/js/config/cloudinary.js +18 -0
- package/dist/js/config/config.js +11 -0
- package/dist/js/config/duration.js +22 -0
- package/dist/js/core/app.js +122 -0
- package/dist/js/core/auth/OTP.js +115 -0
- package/dist/js/core/auth/auth.controller.js +63 -0
- package/dist/js/core/auth/auth.service.js +290 -0
- package/dist/js/core/auth/auth.validation.js +95 -0
- package/dist/js/core/crud/crud.controller.js +95 -0
- package/dist/js/core/crud/crud.service.js +296 -0
- package/dist/js/core/index.js +3 -0
- package/dist/js/index.js +44 -55
- package/dist/js/lib/db.js +40 -0
- package/dist/js/lib/field.types.js +174 -0
- package/dist/js/lib/model.factory.js +19 -0
- package/dist/js/lib/model.registry.js +4 -0
- package/dist/js/lib/schema.builder.js +35 -0
- package/dist/js/lib/zod.validations.js +247 -0
- package/dist/js/middleware/auth.middleware.js +51 -0
- package/dist/js/middleware/error.middleware.js +28 -0
- package/dist/js/middleware/socket.middleware.js +29 -0
- package/dist/js/utils/AppLog.js +2 -1
- package/dist/js/utils/deleteFile.js +22 -0
- package/dist/js/utils/index.js +10 -1
- package/dist/js/utils/jwt.js +12 -20
- package/dist/js/utils/libsodium.js +19 -3
- package/dist/js/utils/rateLimiter.js +25 -0
- package/dist/js/utils/uploadFile.js +43 -0
- package/handlerMap.js +33 -0
- package/package.json +17 -4
- package/dist/cjs/db.cjs +0 -17
- package/dist/cjs/index.cjs +0 -59
- package/dist/cjs/utils/AppError.cjs +0 -13
- package/dist/cjs/utils/AppLog.cjs +0 -13
- package/dist/cjs/utils/asyncHandler.cjs +0 -6
- package/dist/cjs/utils/jwt.cjs +0 -38
- package/dist/cjs/utils/libsodium.cjs +0 -145
- package/dist/cjs/utils/successResponse.cjs +0 -13
- 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,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
|
+
};
|