@starklabs/backend-core 1.1.0 → 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.
- package/.env.example +21 -0
- package/README.md +7 -7
- 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 +18 -5
- package/test.js +36 -0
- 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
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getConfig } from "../config/config.js";
|
|
3
|
+
import "dotenv/config";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* =========================
|
|
7
|
+
* STRING HELPERS
|
|
8
|
+
* =========================
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const requiredString = z
|
|
12
|
+
.string()
|
|
13
|
+
.trim()
|
|
14
|
+
.min(1, "At least 1 character in string is required");
|
|
15
|
+
|
|
16
|
+
export const optionalString = z.string().trim().default("");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* =========================
|
|
20
|
+
* EMAIL
|
|
21
|
+
* =========================
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const email = z
|
|
25
|
+
.string()
|
|
26
|
+
.trim()
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.email("Invalid email address");
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* =========================
|
|
32
|
+
* PASSWORD
|
|
33
|
+
* =========================
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export const password = z
|
|
37
|
+
.string({
|
|
38
|
+
required_error: "Password is required",
|
|
39
|
+
invalid_type_error: "Password is required",
|
|
40
|
+
})
|
|
41
|
+
.min(6, "Password must be at least 6 characters");
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* =========================
|
|
45
|
+
* NUMBER HELPERS
|
|
46
|
+
* =========================
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
export const requiredNumber = z.coerce
|
|
50
|
+
.number()
|
|
51
|
+
.min(1, "Number must be 1 or greater");
|
|
52
|
+
|
|
53
|
+
export const optionalNumber = z.coerce.number().default(0);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* =========================
|
|
57
|
+
* BOOLEAN HELPERS
|
|
58
|
+
* =========================
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
export const booleanTrue = z.boolean().default(true);
|
|
62
|
+
export const booleanFalse = z.boolean().default(false);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* =========================
|
|
66
|
+
* DATE
|
|
67
|
+
* =========================
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
export const dateNow = z.date().default(() => new Date());
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* =========================
|
|
74
|
+
* ARRAYS
|
|
75
|
+
* =========================
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
export const stringArray = z.preprocess((val) => {
|
|
79
|
+
if (Array.isArray(val)) return val;
|
|
80
|
+
|
|
81
|
+
if (typeof val === "string") {
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(val);
|
|
84
|
+
if (Array.isArray(parsed)) return parsed;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return val.split(",").map((v) => v.trim());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return [];
|
|
91
|
+
}, z.array(z.string()).default([]));
|
|
92
|
+
|
|
93
|
+
export const requiredStringArray = z.preprocess(
|
|
94
|
+
(val) => {
|
|
95
|
+
// already correct
|
|
96
|
+
if (Array.isArray(val)) return val;
|
|
97
|
+
|
|
98
|
+
// string case (your problem case)
|
|
99
|
+
if (typeof val === "string") {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(val);
|
|
102
|
+
if (Array.isArray(parsed)) return parsed;
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// fallback: treat as comma-separated string
|
|
105
|
+
return val.split(",").map((v) => v.trim());
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// weird multer case: array of broken strings
|
|
110
|
+
if (Array.isArray(val)) {
|
|
111
|
+
const joined = val.join("");
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse(joined);
|
|
114
|
+
if (Array.isArray(parsed)) return parsed;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return val;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return [];
|
|
121
|
+
},
|
|
122
|
+
z.array(z.string()).min(1, "Array must have at least 1 element"),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
export const objectArray = z.preprocess(
|
|
126
|
+
(val) => {
|
|
127
|
+
if (Array.isArray(val)) return val;
|
|
128
|
+
|
|
129
|
+
if (typeof val === "string") {
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(val);
|
|
132
|
+
if (Array.isArray(parsed)) return parsed;
|
|
133
|
+
} catch (e) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return [];
|
|
139
|
+
},
|
|
140
|
+
z.array(z.object({})).default([]),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
export const requiredObjectArray = z.preprocess(
|
|
144
|
+
(val) => {
|
|
145
|
+
// already correct
|
|
146
|
+
if (Array.isArray(val)) return val;
|
|
147
|
+
|
|
148
|
+
// string case (JSON from form-data)
|
|
149
|
+
if (typeof val === "string") {
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(val);
|
|
152
|
+
if (Array.isArray(parsed)) return parsed;
|
|
153
|
+
} catch (e) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return [];
|
|
159
|
+
},
|
|
160
|
+
z.array(z.object({})).min(1, "Array must have at least 1 object"),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* =========================
|
|
165
|
+
* OBJECT ID / REFERENCES
|
|
166
|
+
* =========================
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
export const objectId = z
|
|
170
|
+
.string({
|
|
171
|
+
required_error: "ObjectId is required",
|
|
172
|
+
invalid_type_error: "Invalid ObjectId",
|
|
173
|
+
})
|
|
174
|
+
.regex(/^[0-9a-fA-F]{24}$/, "Invalid ObjectId");
|
|
175
|
+
|
|
176
|
+
export const requiredObjectId = objectId;
|
|
177
|
+
|
|
178
|
+
export const userRef = objectId;
|
|
179
|
+
export const requiredUserRef = objectId;
|
|
180
|
+
|
|
181
|
+
export const userRefArray = z.array(objectId).default([]);
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* =========================
|
|
185
|
+
* ENUMS
|
|
186
|
+
* =========================
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
export const provider = z.enum(["local", "google"]).default("local");
|
|
190
|
+
|
|
191
|
+
const internalRoles = JSON.parse(process.env.INTERNAL_ROLES || "[]");
|
|
192
|
+
|
|
193
|
+
export const role = z.enum(["user", ...internalRoles]).default("user");
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* =========================
|
|
197
|
+
* TIMESTAMPS
|
|
198
|
+
* =========================
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
export const timestamps = {
|
|
202
|
+
createdAt: dateNow,
|
|
203
|
+
updatedAt: dateNow,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// files
|
|
207
|
+
const requiredImage = z
|
|
208
|
+
.object({
|
|
209
|
+
fieldname: z.string(),
|
|
210
|
+
originalname: z.string(),
|
|
211
|
+
mimetype: z.string(),
|
|
212
|
+
size: z.number(),
|
|
213
|
+
})
|
|
214
|
+
.passthrough()
|
|
215
|
+
.refine((file) => file.mimetype.startsWith("image/"), {
|
|
216
|
+
message: "Only image files are allowed",
|
|
217
|
+
});
|
|
218
|
+
/**
|
|
219
|
+
* =========================
|
|
220
|
+
* EXPORT ALL
|
|
221
|
+
* =========================
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
export default {
|
|
225
|
+
requiredString,
|
|
226
|
+
optionalString,
|
|
227
|
+
email,
|
|
228
|
+
password,
|
|
229
|
+
requiredNumber,
|
|
230
|
+
optionalNumber,
|
|
231
|
+
booleanTrue,
|
|
232
|
+
booleanFalse,
|
|
233
|
+
dateNow,
|
|
234
|
+
stringArray,
|
|
235
|
+
requiredStringArray,
|
|
236
|
+
objectArray,
|
|
237
|
+
requiredObjectArray,
|
|
238
|
+
objectId,
|
|
239
|
+
requiredObjectId,
|
|
240
|
+
userRef,
|
|
241
|
+
requiredUserRef,
|
|
242
|
+
userRefArray,
|
|
243
|
+
timestamps,
|
|
244
|
+
provider,
|
|
245
|
+
role,
|
|
246
|
+
requiredImage,
|
|
247
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { verifyJWT, AppError } from "../utils/index.js";
|
|
2
|
+
import registerModel from "../lib/model.registry.js";
|
|
3
|
+
import { getConfig } from "../config/config.js";
|
|
4
|
+
|
|
5
|
+
// find user safely
|
|
6
|
+
const findUser = async (email) => {
|
|
7
|
+
const { userModel } = getConfig();
|
|
8
|
+
const Model = registerModel[userModel];
|
|
9
|
+
|
|
10
|
+
return await Model.findOne({ email });
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// protect middleware
|
|
14
|
+
const protect = async (req, res, next) => {
|
|
15
|
+
try {
|
|
16
|
+
const token = req.cookies?.authToken;
|
|
17
|
+
|
|
18
|
+
if (!token) {
|
|
19
|
+
return next(new AppError("Authentication required", 401));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { jwtSecret } = getConfig();
|
|
23
|
+
|
|
24
|
+
const payload = verifyJWT(token, jwtSecret);
|
|
25
|
+
|
|
26
|
+
if (!payload?.email) {
|
|
27
|
+
return next(new AppError("Invalid token payload", 401));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const user = await findUser(payload.email);
|
|
31
|
+
|
|
32
|
+
if (!user) {
|
|
33
|
+
return next(new AppError("This account no longer exists", 401));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// optional: ensure provider consistency
|
|
37
|
+
if (payload.provider && payload.provider !== user.provider) {
|
|
38
|
+
return next(new AppError("Provider mismatch", 401));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// attach clean user (not raw JWT)
|
|
42
|
+
const { iat, exp, ...cleanPayload } = payload;
|
|
43
|
+
req.user = { ...cleanPayload, authProvider: user?.provider };
|
|
44
|
+
|
|
45
|
+
next();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return next(new AppError("Unauthorized", 401));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default protect;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// custom imports
|
|
2
|
+
import AppLog from "../utils/AppLog.js";
|
|
3
|
+
|
|
4
|
+
const errorMiddleware = (err, req, res, next) => {
|
|
5
|
+
let statusCode = err.statusCode || 500;
|
|
6
|
+
let message = err.message || "Internal Server Error";
|
|
7
|
+
|
|
8
|
+
// Log everything for debugging
|
|
9
|
+
AppLog("X", "error.middleware.js", message);
|
|
10
|
+
console.log(err);
|
|
11
|
+
|
|
12
|
+
// Handle known / operational errors
|
|
13
|
+
if (err.isOperational) {
|
|
14
|
+
return res.status(statusCode).json({
|
|
15
|
+
success: false,
|
|
16
|
+
message,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// Unknown / programming errors
|
|
22
|
+
return res.status(500).json({
|
|
23
|
+
success: false,
|
|
24
|
+
message: "Something went wrong",
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default errorMiddleware;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// module imports
|
|
2
|
+
import cookie from "cookie";
|
|
3
|
+
|
|
4
|
+
// custom imports
|
|
5
|
+
import envs from "../config/envs.js";
|
|
6
|
+
import { verifyJWT, AppError } from "../utils/index.js";
|
|
7
|
+
|
|
8
|
+
export const socketProtect = (io) => {
|
|
9
|
+
io.use(async (socket, next) => {
|
|
10
|
+
try {
|
|
11
|
+
const cookies = cookie.parse(socket.handshake.headers.cookie || "");
|
|
12
|
+
const token = cookies.authToken;
|
|
13
|
+
if (!token) throw new AppError("Unauthorized");
|
|
14
|
+
|
|
15
|
+
const payLoad = verifyJWT(token);
|
|
16
|
+
delete payLoad.iat;
|
|
17
|
+
delete payLoad.exp;
|
|
18
|
+
|
|
19
|
+
payLoad.user = payLoad.id;
|
|
20
|
+
|
|
21
|
+
delete payLoad.id;
|
|
22
|
+
|
|
23
|
+
socket.user = payLoad;
|
|
24
|
+
next();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return next(new Error("Unauthorized"));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
package/dist/js/utils/AppLog.js
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cloudinary } from "../config/cloudinary.js";
|
|
2
|
+
import AppError from "./AppError.js";
|
|
3
|
+
|
|
4
|
+
const deleteFile = async (id, fileType) => {
|
|
5
|
+
try {
|
|
6
|
+
if (!fileType) throw new Error(`fileType is required`);
|
|
7
|
+
if (!id) throw new Error(`${fileType} public_id is required`);
|
|
8
|
+
|
|
9
|
+
const item = await cloudinary.uploader.destroy(id, {
|
|
10
|
+
resource_type: fileType,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (item.result === "not found")
|
|
14
|
+
throw new AppError("File not found to delete");
|
|
15
|
+
|
|
16
|
+
return true;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new AppError(error.message);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default deleteFile;
|
package/dist/js/utils/index.js
CHANGED
|
@@ -4,4 +4,13 @@ export { default as successResponse } from "./successResponse.js";
|
|
|
4
4
|
export { default as asyncHandler } from "./asyncHandler.js";
|
|
5
5
|
export { default as AppError } from "./AppError.js";
|
|
6
6
|
export { signJWT, verifyJWT } from "./jwt.js";
|
|
7
|
-
export {
|
|
7
|
+
export {
|
|
8
|
+
hash,
|
|
9
|
+
verifyHash,
|
|
10
|
+
seal,
|
|
11
|
+
unSeal,
|
|
12
|
+
generateJWTSecret,
|
|
13
|
+
generateMasterKey,
|
|
14
|
+
} from "./libsodium.js";
|
|
15
|
+
export { default as uploadFile } from "./uploadFile.js";
|
|
16
|
+
export {default as deleteFile} from "./deleteFile.js"
|
package/dist/js/utils/jwt.js
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
2
|
import AppError from "./AppError.js";
|
|
3
|
+
import getDuration from "../config/duration.js";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
"2m": 1000 * 60 * 2,
|
|
6
|
-
"10m": 1000 * 60 * 10,
|
|
7
|
-
"1h": 1000 * 60 * 60,
|
|
8
|
-
"6h": 1000 * 60 * 60 * 6,
|
|
9
|
-
"12h": 1000 * 60 * 60 * 12,
|
|
10
|
-
"1d": 1000 * 60 * 60 * 24,
|
|
11
|
-
"3d": 1000 * 60 * 60 * 24 * 3,
|
|
12
|
-
"7d": 1000 * 60 * 60 * 24 * 7,
|
|
13
|
-
"14d": 1000 * 60 * 60 * 24 * 14,
|
|
14
|
-
"30d": 1000 * 60 * 60 * 24 * 30,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const getExpiry = (key) => {
|
|
18
|
-
if (!EXPIRY[key]) throw new Error("Invalid expiry key");
|
|
19
|
-
return EXPIRY[key];
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const signJWT = (payLoad, JWT_SECRET, JWT_EXPIRY) => {
|
|
5
|
+
const signJWT = (payLoad, JWT_SECRET, tokenExpiry) => {
|
|
23
6
|
try {
|
|
24
|
-
|
|
7
|
+
if (!payLoad) throw new Error("payLoad is required");
|
|
8
|
+
if (!JWT_SECRET) throw new Error("jwtSecret is required");
|
|
9
|
+
if (!tokenExpiry) throw new Error("tokenExpiry is required");
|
|
10
|
+
|
|
11
|
+
return jwt.sign(payLoad, JWT_SECRET, {
|
|
12
|
+
expiresIn: getDuration(tokenExpiry),
|
|
13
|
+
});
|
|
25
14
|
} catch (error) {
|
|
26
15
|
throw new AppError(error.message);
|
|
27
16
|
}
|
|
@@ -29,6 +18,9 @@ const signJWT = (payLoad, JWT_SECRET, JWT_EXPIRY) => {
|
|
|
29
18
|
|
|
30
19
|
const verifyJWT = (token, JWT_SECRET) => {
|
|
31
20
|
try {
|
|
21
|
+
if (!JWT_SECRET) throw new Error("jwtSecret is required");
|
|
22
|
+
if (!token) throw new Error("JWT token is required");
|
|
23
|
+
|
|
32
24
|
return jwt.verify(token, JWT_SECRET);
|
|
33
25
|
} catch (error) {
|
|
34
26
|
throw new AppError("Invalid or expired token", 401);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import sodium from "libsodium-wrappers-sumo";
|
|
2
2
|
import AppError from "./AppError.js";
|
|
3
|
-
// import envs from "../config/envs.js";
|
|
4
3
|
|
|
5
4
|
// Hash password
|
|
6
5
|
export const hash = async (password) => {
|
|
6
|
+
if (!password) throw new AppError("String/password is required to hash.");
|
|
7
7
|
await sodium.ready;
|
|
8
8
|
|
|
9
9
|
const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
|
|
@@ -23,6 +23,10 @@ export const hash = async (password) => {
|
|
|
23
23
|
|
|
24
24
|
// verifyPassword
|
|
25
25
|
export const verifyHash = async (password, storedHash) => {
|
|
26
|
+
if (!password) throw new AppError("String/password is required to verify");
|
|
27
|
+
if (!storedHash)
|
|
28
|
+
throw new AppError("storedHash is required for verification");
|
|
29
|
+
|
|
26
30
|
await sodium.ready;
|
|
27
31
|
|
|
28
32
|
const [saltB64, hashB64] = storedHash.split(":");
|
|
@@ -63,7 +67,10 @@ export const generateJWTSecret = async () => {
|
|
|
63
67
|
const secret = sodium.randombytes_buf(64);
|
|
64
68
|
|
|
65
69
|
// Convert to base64 for .env storage
|
|
66
|
-
const base64Secret = sodium.to_base64(
|
|
70
|
+
const base64Secret = sodium.to_base64(
|
|
71
|
+
secret,
|
|
72
|
+
sodium.base64_variants.ORIGINAL,
|
|
73
|
+
);
|
|
67
74
|
console.log(base64Secret);
|
|
68
75
|
return base64Secret;
|
|
69
76
|
};
|
|
@@ -71,6 +78,9 @@ export const generateJWTSecret = async () => {
|
|
|
71
78
|
// seal
|
|
72
79
|
export const seal = async (string, masterKey) => {
|
|
73
80
|
try {
|
|
81
|
+
if (!string) throw new Error("Data is required to encrypt");
|
|
82
|
+
if (!masterKey) throw new Error("masterKey is required for encryption");
|
|
83
|
+
|
|
74
84
|
await sodium.ready;
|
|
75
85
|
|
|
76
86
|
const keyPair = sodium.crypto_box_keypair();
|
|
@@ -106,7 +116,13 @@ export const seal = async (string, masterKey) => {
|
|
|
106
116
|
};
|
|
107
117
|
|
|
108
118
|
// unSeal
|
|
109
|
-
export const unSeal = async (
|
|
119
|
+
export const unSeal = async (
|
|
120
|
+
str,
|
|
121
|
+
nonce,
|
|
122
|
+
publicKey,
|
|
123
|
+
securedPrivateKey,
|
|
124
|
+
masterKey,
|
|
125
|
+
) => {
|
|
110
126
|
try {
|
|
111
127
|
if (!str || !nonce || !publicKey || !securedPrivateKey)
|
|
112
128
|
throw new AppError(
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import rateLimit from "express-rate-limit";
|
|
2
|
+
import getDuration from "../config/duration.js";
|
|
3
|
+
import AppError from "./AppError.js";
|
|
4
|
+
|
|
5
|
+
export const apiLimiter = ({
|
|
6
|
+
rateLimitDuration,
|
|
7
|
+
maxReqLimit,
|
|
8
|
+
rateLimitMsg,
|
|
9
|
+
}) => {
|
|
10
|
+
if (!rateLimitDuration || !maxReqLimit || !rateLimitMsg)
|
|
11
|
+
throw new AppError(
|
|
12
|
+
"rateLimitDuration, maxReqLimit and rateLimitMsg are required",
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return rateLimit({
|
|
16
|
+
windowMs: getDuration(rateLimitDuration) || 15 * 60 * 1000, // 15 minutes
|
|
17
|
+
max: maxReqLimit || 200, // limit each IP to 200 requests per windowMs
|
|
18
|
+
standardHeaders: true,
|
|
19
|
+
legacyHeaders: false,
|
|
20
|
+
message: {
|
|
21
|
+
success: false,
|
|
22
|
+
message: rateLimitMsg || "Too many requests, please try again later.",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getConfig } from "../config/config.js";
|
|
2
|
+
import { cloudinary, upload } from "../config/cloudinary.js";
|
|
3
|
+
import AppError from "./AppError.js";
|
|
4
|
+
import AppLog from "./AppLog.js";
|
|
5
|
+
|
|
6
|
+
const uploadFile = async (fileData, uploadFolderName) => {
|
|
7
|
+
try {
|
|
8
|
+
if (!fileData) throw new Error("fileData not found");
|
|
9
|
+
if (!uploadFolderName) throw new Error("uploadFolder is required");
|
|
10
|
+
|
|
11
|
+
const uploadResult = await new Promise((resolve, reject) => {
|
|
12
|
+
const stream = cloudinary.uploader.upload_stream(
|
|
13
|
+
{
|
|
14
|
+
resource_type: "auto",
|
|
15
|
+
folder: uploadFolderName,
|
|
16
|
+
},
|
|
17
|
+
(error, result) => {
|
|
18
|
+
if (error) return reject(error);
|
|
19
|
+
resolve(result);
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
stream.end(fileData.buffer);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (uploadResult.name === "Error")
|
|
27
|
+
throw new AppError(uploadResult.message.split(".")[0]);
|
|
28
|
+
|
|
29
|
+
const { public_id, secure_url, width, height } = uploadResult;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
public_id,
|
|
33
|
+
secure_url,
|
|
34
|
+
width,
|
|
35
|
+
height,
|
|
36
|
+
};
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new AppError(error.message);
|
|
39
|
+
AppLog("X", "uploadFile", error.message);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default uploadFile;
|
package/handlerMap.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const handlerMap = {
|
|
2
|
+
// Auth
|
|
3
|
+
login: "Authenticate a user",
|
|
4
|
+
signup: "Register a new user",
|
|
5
|
+
logout: "Sign out a user",
|
|
6
|
+
resendOTP: "Resend one-time password",
|
|
7
|
+
twoFactorAuth: "Verify two-factor authentication",
|
|
8
|
+
forgotPassword: "Request password reset",
|
|
9
|
+
resetPassword: "Reset password",
|
|
10
|
+
changePassword: "Change password",
|
|
11
|
+
verifyEmail: "Verify email address",
|
|
12
|
+
refreshToken: "Refresh access token",
|
|
13
|
+
deleteAccount: "Delete current account",
|
|
14
|
+
|
|
15
|
+
// Generic CRUD
|
|
16
|
+
getAll: "Get all resources",
|
|
17
|
+
getById: "Get a resource by ID",
|
|
18
|
+
create: "Create a resource",
|
|
19
|
+
update: "Update a resource",
|
|
20
|
+
patch: "Partially update a resource",
|
|
21
|
+
delete: "Delete a resource",
|
|
22
|
+
|
|
23
|
+
// Query
|
|
24
|
+
search: "Search resources",
|
|
25
|
+
filter: "Filter resources",
|
|
26
|
+
paginate: "Get paginated resources",
|
|
27
|
+
count: "Get resource count",
|
|
28
|
+
|
|
29
|
+
// Files
|
|
30
|
+
uploadFile: "Upload a file",
|
|
31
|
+
downloadFile: "Download a file",
|
|
32
|
+
deleteFile: "Delete a file",
|
|
33
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starklabs/backend-core",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A comprehensive backend authentication library featuring MongoDB integration, JWT-based authentication, encryption/decryption using libsodium, and utilities for error handling and logging. Supports both ES modules and CommonJS. Requires
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "A comprehensive backend authentication library featuring MongoDB integration, JWT-based authentication, encryption/decryption using libsodium, and utilities for error handling and logging. Supports both ES modules and CommonJS. Requires MONGODB_URI, DB_NAME, MASTER_KEY, and JWT_SECRET environment variables.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth-mongo",
|
|
7
7
|
"backend-core"
|
|
@@ -9,20 +9,33 @@
|
|
|
9
9
|
"license": "ISC",
|
|
10
10
|
"author": "Musa",
|
|
11
11
|
"type": "module",
|
|
12
|
-
"main": "index.js",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
13
|
"scripts": {
|
|
14
|
-
"test": "node test.js"
|
|
14
|
+
"test": "node test.js",
|
|
15
|
+
"env-open": "npx dotenv-vault@latest open",
|
|
16
|
+
"env-push": "npx dotenv-vault@latest push",
|
|
17
|
+
"env-pull": "npx dotenv-vault@latest pull",
|
|
18
|
+
"env-login": "npx dotenv-vault@latest login",
|
|
19
|
+
"env-new": "npx dotenv-vault@latest new"
|
|
15
20
|
},
|
|
16
21
|
"dependencies": {
|
|
22
|
+
"cloudinary": "^2.10.0",
|
|
17
23
|
"cookie-parser": "^1.4.7",
|
|
18
24
|
"cors": "^2.8.6",
|
|
19
25
|
"dotenv": "^17.4.2",
|
|
20
26
|
"express": "^5.2.1",
|
|
21
|
-
"
|
|
27
|
+
"express-rate-limit": "^8.5.2",
|
|
28
|
+
"helmet": "^8.2.0",
|
|
22
29
|
"jsonwebtoken": "^9.0.3",
|
|
23
30
|
"libsodium-wrappers-sumo": "^0.8.4",
|
|
24
31
|
"mongoose": "^9.6.1",
|
|
32
|
+
"multer": "^2.2.0",
|
|
25
33
|
"resend": "^6.12.2",
|
|
26
34
|
"zod": "^4.4.1"
|
|
35
|
+
},
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"import": "./dist/index.js"
|
|
39
|
+
}
|
|
27
40
|
}
|
|
28
41
|
}
|