@prashant-neosoft-ecommerce/shared 1.0.3

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/index.js ADDED
@@ -0,0 +1,14 @@
1
+ module.exports = {
2
+ middleware: {
3
+ rateLimiter: require("./middleware/rateLimiter"),
4
+ encryption: require("./middleware/encryption"),
5
+ validation: require("./middleware/validation"),
6
+ auth: require("./middleware/auth"),
7
+ errorHandler: require("./middleware/errorHandler"),
8
+ },
9
+ utils: {
10
+ logger: require("./utils/logger"),
11
+ redis: require("./utils/redis"),
12
+ constants: require("./utils/constants"),
13
+ },
14
+ };
@@ -0,0 +1,38 @@
1
+ const jwt = require("jsonwebtoken");
2
+ const redisClient = require("../utils/redis");
3
+
4
+ const auth = async (req, res, next) => {
5
+ try {
6
+ const token = req.header("Authorization")?.replace("Bearer ", "");
7
+
8
+ if (!token) {
9
+ return res.status(401).json({ error: "Authentication required" });
10
+ }
11
+
12
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
13
+
14
+ const cachedUser = await redisClient.get(`user:${decoded.userId}`);
15
+ if (!cachedUser) {
16
+ return res.status(401).json({ error: "Session expired" });
17
+ }
18
+
19
+ req.user = JSON.parse(cachedUser);
20
+ req.token = token;
21
+ next();
22
+ } catch (error) {
23
+ res.status(401).json({ error: "Invalid authentication token" });
24
+ }
25
+ };
26
+
27
+ const authorize = (...roles) => {
28
+ return (req, res, next) => {
29
+ if (!req.user || !roles.includes(req.user.role)) {
30
+ return res.status(403).json({
31
+ error: "Access denied. Insufficient permissions.",
32
+ });
33
+ }
34
+ next();
35
+ };
36
+ };
37
+
38
+ module.exports = { auth, authorize };
@@ -0,0 +1,48 @@
1
+ const crypto = require("crypto");
2
+
3
+ const ALGORITHM = "aes-256-cbc";
4
+ const ENCRYPTION_KEY = Buffer.from(
5
+ process.env.ENCRYPTION_KEY ||
6
+ crypto.randomBytes(32).toString("hex").slice(0, 32),
7
+ "utf8"
8
+ );
9
+ const IV_LENGTH = 16;
10
+
11
+ const encrypt = (text) => {
12
+ const iv = crypto.randomBytes(IV_LENGTH);
13
+ const cipher = crypto.createCipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
14
+
15
+ let encrypted = cipher.update(text);
16
+ encrypted = Buffer.concat([encrypted, cipher.final()]);
17
+
18
+ return iv.toString("hex") + ":" + encrypted.toString("hex");
19
+ };
20
+
21
+ const decrypt = (text) => {
22
+ const textParts = text.split(":");
23
+ const iv = Buffer.from(textParts.shift(), "hex");
24
+ const encryptedText = Buffer.from(textParts.join(":"), "hex");
25
+
26
+ const decipher = crypto.createDecipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
27
+
28
+ let decrypted = decipher.update(encryptedText);
29
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
30
+
31
+ return decrypted.toString();
32
+ };
33
+
34
+ const encryptionMiddleware = (req, res, next) => {
35
+ if (req.body && req.body.encrypted) {
36
+ try {
37
+ const decrypted = decrypt(req.body.encrypted);
38
+ req.body = JSON.parse(decrypted);
39
+ } catch (error) {
40
+ return res.status(400).json({
41
+ error: "Invalid encrypted payload",
42
+ });
43
+ }
44
+ }
45
+ next();
46
+ };
47
+
48
+ module.exports = { encrypt, decrypt, encryptionMiddleware };
@@ -0,0 +1,49 @@
1
+ const logger = require("../utils/logger");
2
+
3
+ class AppError extends Error {
4
+ constructor(message, statusCode) {
5
+ super(message);
6
+ this.statusCode = statusCode;
7
+ this.isOperational = true;
8
+ Error.captureStackTrace(this, this.constructor);
9
+ }
10
+ }
11
+
12
+ const errorHandler = (err, req, res, next) => {
13
+ err.statusCode = err.statusCode || 500;
14
+ err.message = err.message || "Internal Server Error";
15
+
16
+ logger.error({
17
+ message: err.message,
18
+ stack: err.stack,
19
+ statusCode: err.statusCode,
20
+ path: req.path,
21
+ method: req.method,
22
+ });
23
+
24
+ if (process.env.NODE_ENV === "development") {
25
+ return res.status(err.statusCode).json({
26
+ success: false,
27
+ error: err.message,
28
+ stack: err.stack,
29
+ });
30
+ }
31
+
32
+ if (err.isOperational) {
33
+ return res.status(err.statusCode).json({
34
+ success: false,
35
+ error: err.message,
36
+ });
37
+ }
38
+
39
+ return res.status(500).json({
40
+ success: false,
41
+ error: "Something went wrong",
42
+ });
43
+ };
44
+
45
+ const asyncHandler = (fn) => (req, res, next) => {
46
+ Promise.resolve(fn(req, res, next)).catch(next);
47
+ };
48
+
49
+ module.exports = { AppError, errorHandler, asyncHandler };
@@ -0,0 +1,28 @@
1
+ const rateLimit = require("express-rate-limit");
2
+ const { RedisStore } = require("rate-limit-redis");
3
+ const redisClient = require("../utils/redis");
4
+
5
+ const createRateLimiter = (options = {}) => {
6
+ return rateLimit({
7
+ store: new RedisStore({
8
+ client: redisClient.client,
9
+ prefix: "rl:",
10
+ }),
11
+ windowMs: options.windowMs || 15 * 60 * 1000, // 15 minutes
12
+ max: options.max || 100,
13
+ message: options.message || "Too many requests, please try again later.",
14
+ standardHeaders: true,
15
+ legacyHeaders: false,
16
+ });
17
+ };
18
+
19
+ const strictLimiter = createRateLimiter({ max: 5, windowMs: 60 * 1000 });
20
+ const standardLimiter = createRateLimiter({ max: 100 });
21
+ const generousLimiter = createRateLimiter({ max: 1000 });
22
+
23
+ module.exports = {
24
+ createRateLimiter,
25
+ strictLimiter,
26
+ standardLimiter,
27
+ generousLimiter,
28
+ };
@@ -0,0 +1,65 @@
1
+ const Joi = require("joi");
2
+
3
+ const validate = (schema) => {
4
+ return (req, res, next) => {
5
+ const { error } = schema.validate(req.body, { abortEarly: false });
6
+
7
+ if (error) {
8
+ const errors = error.details.map((detail) => ({
9
+ field: detail.path.join("."),
10
+ message: detail.message,
11
+ }));
12
+
13
+ return res.status(400).json({
14
+ success: false,
15
+ errors,
16
+ });
17
+ }
18
+
19
+ next();
20
+ };
21
+ };
22
+
23
+ const schemas = {
24
+ register: Joi.object({
25
+ name: Joi.string().min(2).max(50).required(),
26
+ email: Joi.string().email().required(),
27
+ password: Joi.string().min(8).required(),
28
+ role: Joi.string().valid("user", "admin").default("user"),
29
+ }),
30
+
31
+ login: Joi.object({
32
+ email: Joi.string().email().required(),
33
+ password: Joi.string().required(),
34
+ }),
35
+
36
+ product: Joi.object({
37
+ name: Joi.string().min(2).max(100).required(),
38
+ description: Joi.string().max(1000).required(),
39
+ price: Joi.number().min(0).required(),
40
+ stock: Joi.number().integer().min(0).required(),
41
+ category: Joi.string().required(),
42
+ sku: Joi.string().required(),
43
+ }),
44
+
45
+ order: Joi.object({
46
+ items: Joi.array()
47
+ .items(
48
+ Joi.object({
49
+ productId: Joi.string().required(),
50
+ quantity: Joi.number().integer().min(1).required(),
51
+ })
52
+ )
53
+ .min(1)
54
+ .required(),
55
+ shippingAddress: Joi.object({
56
+ street: Joi.string().required(),
57
+ city: Joi.string().required(),
58
+ state: Joi.string().required(),
59
+ zipCode: Joi.string().required(),
60
+ country: Joi.string().required(),
61
+ }).required(),
62
+ }),
63
+ };
64
+
65
+ module.exports = { validate, schemas };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@prashant-neosoft-ecommerce/shared",
3
+ "version": "1.0.3",
4
+ "description": "added by prashant",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "express": "^4.18.2",
13
+ "express-rate-limit": "^7.1.5",
14
+ "rate-limit-redis": "^4.2.0",
15
+ "ioredis": "^5.3.2",
16
+ "jsonwebtoken": "^9.0.2",
17
+ "joi": "^17.11.0",
18
+ "winston": "^3.11.0",
19
+ "helmet": "^7.1.0",
20
+ "cors": "^2.8.5",
21
+ "compression": "^1.7.4"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "index.js",
28
+ "middleware",
29
+ "utils"
30
+ ]
31
+ }
@@ -0,0 +1,41 @@
1
+ module.exports = {
2
+ EVENTS: {
3
+ ORDER_CREATED: "OrderCreated",
4
+ ORDER_UPDATED: "OrderUpdated",
5
+ ORDER_CANCELLED: "OrderCancelled",
6
+ PAYMENT_PROCESSED: "PaymentProcessed",
7
+ PAYMENT_FAILED: "PaymentFailed",
8
+ PAYMENT_REFUNDED: "PaymentRefunded",
9
+ INVENTORY_RESERVED: "InventoryReserved",
10
+ INVENTORY_RELEASED: "InventoryReleased",
11
+ },
12
+
13
+ ORDER_STATUS: {
14
+ PENDING: "pending",
15
+ CONFIRMED: "confirmed",
16
+ PROCESSING: "processing",
17
+ SHIPPED: "shipped",
18
+ DELIVERED: "delivered",
19
+ CANCELLED: "cancelled",
20
+ FAILED: "failed",
21
+ },
22
+
23
+ PAYMENT_STATUS: {
24
+ PENDING: "pending",
25
+ PROCESSING: "processing",
26
+ COMPLETED: "completed",
27
+ FAILED: "failed",
28
+ REFUNDED: "refunded",
29
+ },
30
+
31
+ USER_ROLES: {
32
+ USER: "user",
33
+ ADMIN: "admin",
34
+ },
35
+
36
+ CACHE_TTL: {
37
+ SHORT: 300, // 5 minutes
38
+ MEDIUM: 1800, // 30 minutes
39
+ LONG: 3600, // 1 hour
40
+ },
41
+ };
@@ -0,0 +1,33 @@
1
+ const winston = require("winston");
2
+
3
+ const logger = winston.createLogger({
4
+ level: process.env.LOG_LEVEL || "info",
5
+ format: winston.format.combine(
6
+ winston.format.timestamp(),
7
+ winston.format.errors({ stack: true }),
8
+ winston.format.json()
9
+ ),
10
+ defaultMeta: { service: process.env.SERVICE_NAME || "unknown" },
11
+ transports: [
12
+ new winston.transports.File({
13
+ filename: "logs/error.log",
14
+ level: "error",
15
+ }),
16
+ new winston.transports.File({
17
+ filename: "logs/combined.log",
18
+ }),
19
+ ],
20
+ });
21
+
22
+ if (process.env.NODE_ENV !== "production") {
23
+ logger.add(
24
+ new winston.transports.Console({
25
+ format: winston.format.combine(
26
+ winston.format.colorize(),
27
+ winston.format.simple()
28
+ ),
29
+ })
30
+ );
31
+ }
32
+
33
+ module.exports = logger;
package/utils/redis.js ADDED
@@ -0,0 +1,72 @@
1
+ const Redis = require("ioredis");
2
+ const logger = require("./logger");
3
+
4
+ class RedisClient {
5
+ constructor() {
6
+ this.client = null;
7
+ this.isConnected = false;
8
+ }
9
+
10
+ connect() {
11
+ if (this.client) return this.client;
12
+
13
+ this.client = new Redis({
14
+ host: process.env.REDIS_HOST || "redis-srv",
15
+ port: process.env.REDIS_PORT || 6379,
16
+ maxRetriesPerRequest: null,
17
+ enableReadyCheck: true,
18
+ retryStrategy(times) {
19
+ const delay = Math.min(times * 50, 2000);
20
+ return delay;
21
+ },
22
+ });
23
+
24
+ this.client.on("connect", () => {
25
+ this.isConnected = true;
26
+ logger.info("Redis connected");
27
+ });
28
+
29
+ this.client.on("error", (err) => {
30
+ logger.error("Redis error", err);
31
+ });
32
+
33
+ return this.client;
34
+ }
35
+
36
+ getClient() {
37
+ if (!this.client) {
38
+ this.connect();
39
+ }
40
+ return this.client;
41
+ }
42
+
43
+ async get(key) {
44
+ try {
45
+ return await this.getClient().get(key);
46
+ } catch (err) {
47
+ logger.error("Redis GET error", err);
48
+ return null;
49
+ }
50
+ }
51
+
52
+ async set(key, value, ttl = 300) {
53
+ try {
54
+ if (typeof value === "object") {
55
+ value = JSON.stringify(value);
56
+ }
57
+ await this.getClient().set(key, value, "EX", ttl);
58
+ } catch (err) {
59
+ logger.error("Redis SET error", err);
60
+ }
61
+ }
62
+
63
+ async del(key) {
64
+ try {
65
+ await this.getClient().del(key);
66
+ } catch (err) {
67
+ logger.error("Redis DEL error", err);
68
+ }
69
+ }
70
+ }
71
+
72
+ module.exports = new RedisClient();