powr-sdk-api 1.2.1 → 1.3.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/dist/index.js +15 -15
- package/dist/logger/index.js +35 -0
- package/dist/logger/s3.js +78 -0
- package/dist/middleware/auth.js +40 -10
- package/dist/middleware/error.js +2 -9
- package/dist/middleware/request.js +72 -12
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const errorMiddleware = require('./middleware/error');
|
|
4
|
-
const responseMiddleware = require('./middleware/response');
|
|
5
|
-
const requestMiddleware = require('./middleware/request');
|
|
6
|
-
const createSwaggerSpec = require('./swagger');
|
|
7
|
-
const logger = require('./services/logger');
|
|
8
3
|
const {
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
errorCatcher,
|
|
5
|
+
errorHandler
|
|
6
|
+
} = require("./middleware/error");
|
|
7
|
+
const {
|
|
8
|
+
requestHandler
|
|
9
|
+
} = require("./middleware/request");
|
|
11
10
|
const {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
validateAuth,
|
|
12
|
+
generateToken
|
|
13
|
+
} = require("./middleware/auth");
|
|
14
|
+
const createSwaggerSpec = require("./swagger");
|
|
15
|
+
const logger = require("./logger");
|
|
14
16
|
module.exports = {
|
|
15
|
-
errorCatcher
|
|
16
|
-
errorHandler
|
|
17
|
-
|
|
18
|
-
responseFormatter: responseMiddleware.responseFormatter,
|
|
19
|
-
responseLogger: responseMiddleware.responseLogger,
|
|
17
|
+
errorCatcher,
|
|
18
|
+
errorHandler,
|
|
19
|
+
requestHandler,
|
|
20
20
|
logger,
|
|
21
21
|
createSwaggerSpec,
|
|
22
22
|
generateToken,
|
|
23
|
-
|
|
23
|
+
validateAuth
|
|
24
24
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const winston = require('winston');
|
|
4
|
+
const {
|
|
5
|
+
S3Transport
|
|
6
|
+
} = require('./s3');
|
|
7
|
+
|
|
8
|
+
// Custom format for logs
|
|
9
|
+
const logFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({
|
|
10
|
+
stack: true
|
|
11
|
+
}), winston.format.json());
|
|
12
|
+
|
|
13
|
+
// Create base transports array with console transport
|
|
14
|
+
const transports = [new winston.transports.Console()];
|
|
15
|
+
|
|
16
|
+
// Add S3 transport only in production
|
|
17
|
+
if (process.env.NODE_ENV === 'production') {
|
|
18
|
+
transports.push(new S3Transport({
|
|
19
|
+
bucket: process.env.AWS_LOG_BUCKET_NAME,
|
|
20
|
+
prefix: 'logs',
|
|
21
|
+
bufferSize: 100,
|
|
22
|
+
flushInterval: 5000
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Create the logger
|
|
27
|
+
const logger = winston.createLogger({
|
|
28
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
29
|
+
format: logFormat,
|
|
30
|
+
defaultMeta: {
|
|
31
|
+
service: process.env.LOG_SERVICE_NAME
|
|
32
|
+
},
|
|
33
|
+
transports
|
|
34
|
+
});
|
|
35
|
+
module.exports = logger;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const winston = require('winston');
|
|
4
|
+
const {
|
|
5
|
+
S3Client,
|
|
6
|
+
PutObjectCommand
|
|
7
|
+
} = require('@aws-sdk/client-s3');
|
|
8
|
+
|
|
9
|
+
// Create S3 client
|
|
10
|
+
const s3Client = new S3Client({
|
|
11
|
+
region: process.env.AWS_REGION,
|
|
12
|
+
credentials: {
|
|
13
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
14
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Custom Winston transport for logging to S3
|
|
20
|
+
*/
|
|
21
|
+
class S3Transport extends winston.Transport {
|
|
22
|
+
constructor(opts) {
|
|
23
|
+
super(opts);
|
|
24
|
+
this.bucket = opts.bucket;
|
|
25
|
+
this.prefix = opts.prefix || '';
|
|
26
|
+
this.buffer = [];
|
|
27
|
+
this.bufferSize = opts.bufferSize || 100;
|
|
28
|
+
this.flushInterval = opts.flushInterval || 5000;
|
|
29
|
+
this.setupFlushInterval();
|
|
30
|
+
}
|
|
31
|
+
setupFlushInterval() {
|
|
32
|
+
setInterval(() => {
|
|
33
|
+
this.flush();
|
|
34
|
+
}, this.flushInterval);
|
|
35
|
+
}
|
|
36
|
+
async flush() {
|
|
37
|
+
if (this.buffer.length === 0) return;
|
|
38
|
+
const logs = this.buffer.splice(0, this.buffer.length);
|
|
39
|
+
const date = new Date().toISOString().split('T')[0];
|
|
40
|
+
const key = `${this.prefix}/${date}/${Date.now()}.json`;
|
|
41
|
+
try {
|
|
42
|
+
const command = new PutObjectCommand({
|
|
43
|
+
Bucket: this.bucket,
|
|
44
|
+
Key: key,
|
|
45
|
+
Body: JSON.stringify(logs),
|
|
46
|
+
ContentType: 'application/json'
|
|
47
|
+
});
|
|
48
|
+
await s3Client.send(command);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Failed to write logs to S3:', {
|
|
51
|
+
error: error.message,
|
|
52
|
+
code: error.code,
|
|
53
|
+
bucket: this.bucket,
|
|
54
|
+
key: key,
|
|
55
|
+
region: process.env.AWS_REGION,
|
|
56
|
+
stack: error.stack
|
|
57
|
+
});
|
|
58
|
+
// Put the logs back in the buffer
|
|
59
|
+
this.buffer.unshift(...logs);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
log(info, callback) {
|
|
63
|
+
setImmediate(() => {
|
|
64
|
+
this.emit('logged', info);
|
|
65
|
+
});
|
|
66
|
+
this.buffer.push({
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
...info
|
|
69
|
+
});
|
|
70
|
+
if (this.buffer.length >= this.bufferSize) {
|
|
71
|
+
this.flush();
|
|
72
|
+
}
|
|
73
|
+
callback();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
module.exports = {
|
|
77
|
+
S3Transport
|
|
78
|
+
};
|
package/dist/middleware/auth.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const jwt = require(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const jwt = require("jsonwebtoken");
|
|
4
|
+
const generateToken = user => {
|
|
5
|
+
return jwt.sign({
|
|
6
|
+
userId: user.userId
|
|
7
|
+
}, process.env.JWT_SECRET, {
|
|
8
|
+
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
const validateToken = async (req, res, next) => {
|
|
7
12
|
try {
|
|
8
13
|
let token;
|
|
9
14
|
|
|
10
15
|
// Check if token exists in headers
|
|
11
|
-
if (req.headers.authorization && req.headers.authorization.startsWith(
|
|
12
|
-
token = req.headers.authorization.split(
|
|
16
|
+
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer")) {
|
|
17
|
+
token = req.headers.authorization.split(" ")[1];
|
|
13
18
|
}
|
|
14
19
|
if (!token) {
|
|
15
|
-
return res.error(
|
|
20
|
+
return res.error("Not authorized to access this route", 401);
|
|
16
21
|
}
|
|
17
22
|
try {
|
|
18
23
|
// Verify token
|
|
@@ -29,12 +34,32 @@ exports.validateToken = async (req, res, next) => {
|
|
|
29
34
|
req.userId = decoded.userId;
|
|
30
35
|
next();
|
|
31
36
|
} catch (error) {
|
|
32
|
-
return res.error(
|
|
37
|
+
return res.error("Not authorized to access this route", 401, error);
|
|
33
38
|
}
|
|
34
39
|
} catch (error) {
|
|
35
|
-
return res.error(
|
|
40
|
+
return res.error("Error authenticating user", 500, error);
|
|
36
41
|
}
|
|
37
42
|
};
|
|
43
|
+
const validateAuth = (options = {}) => {
|
|
44
|
+
const {
|
|
45
|
+
publicPaths = ["/auth", "/", "/status", "/swagger"],
|
|
46
|
+
publicMethods = ["OPTIONS"]
|
|
47
|
+
} = options;
|
|
48
|
+
return async (req, res, next) => {
|
|
49
|
+
// Skip auth validation for public paths
|
|
50
|
+
if (publicPaths.some(path => req.path.startsWith(path))) {
|
|
51
|
+
return next();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Skip auth validation for public methods
|
|
55
|
+
if (publicMethods.includes(req.method)) {
|
|
56
|
+
return next();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Use the original validateToken middleware for protected routes
|
|
60
|
+
return validateToken(req, res, next);
|
|
61
|
+
};
|
|
62
|
+
};
|
|
38
63
|
|
|
39
64
|
// exports.authorize = (...roles) => {
|
|
40
65
|
// return (req, res, next) => {
|
|
@@ -43,4 +68,9 @@ exports.validateToken = async (req, res, next) => {
|
|
|
43
68
|
// }
|
|
44
69
|
// next();
|
|
45
70
|
// };
|
|
46
|
-
// };
|
|
71
|
+
// };
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
validateAuth,
|
|
75
|
+
generateToken
|
|
76
|
+
};
|
package/dist/middleware/error.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const logger = require("../services/logger");
|
|
4
3
|
const errorCatcher = fn => (req, res, next) => {
|
|
5
|
-
Promise.resolve(fn(req, res, next)).catch(
|
|
4
|
+
Promise.resolve(fn(req, res, next)).catch(res.error);
|
|
6
5
|
};
|
|
7
6
|
const errorHandler = (err, req, res, next) => {
|
|
8
|
-
|
|
9
|
-
error: err.message,
|
|
10
|
-
stack: err.stack,
|
|
11
|
-
path: req.path,
|
|
12
|
-
method: req.method
|
|
13
|
-
});
|
|
14
|
-
return res.error(err.message, err.statusCode, err.details);
|
|
7
|
+
return res.error(err);
|
|
15
8
|
};
|
|
16
9
|
module.exports = {
|
|
17
10
|
errorCatcher,
|
|
@@ -1,22 +1,82 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const logger = require("../
|
|
4
|
-
const
|
|
3
|
+
const logger = require("../logger");
|
|
4
|
+
const requestHandler = (req, res, next) => {
|
|
5
5
|
try {
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
// Add request timestamp
|
|
7
|
+
req.requestTime = new Date().toISOString();
|
|
8
|
+
|
|
9
|
+
// Add request ID for tracking
|
|
10
|
+
req.requestId = req.headers['x-request-id'] || Math.random().toString(36).substring(2, 15);
|
|
11
|
+
|
|
12
|
+
// Set default response headers
|
|
13
|
+
res.setHeader('X-Request-ID', req.requestId);
|
|
14
|
+
res.setHeader('X-Request-Time', req.requestTime);
|
|
15
|
+
|
|
16
|
+
// Log the incoming request with details
|
|
17
|
+
const requestDetails = {
|
|
18
|
+
method: req.method,
|
|
19
|
+
path: req.path,
|
|
20
|
+
query: req.query,
|
|
21
|
+
ip: req.ip,
|
|
22
|
+
userAgent: req.headers['user-agent'],
|
|
23
|
+
requestId: req.requestId,
|
|
24
|
+
timestamp: req.requestTime
|
|
9
25
|
};
|
|
10
|
-
|
|
11
|
-
if
|
|
12
|
-
|
|
26
|
+
|
|
27
|
+
// Log request body if present, redacting sensitive information
|
|
28
|
+
if (Object.keys(req.body).length > 0) {
|
|
29
|
+
const logBody = {
|
|
30
|
+
...req.body
|
|
31
|
+
};
|
|
32
|
+
if (logBody.password) logBody.password = "[REDACTED]";
|
|
33
|
+
if (logBody.token) logBody.token = "[REDACTED]";
|
|
34
|
+
requestDetails.body = logBody;
|
|
35
|
+
}
|
|
36
|
+
logger.info(`Incoming request: ${req.method} ${req.path}`, requestDetails);
|
|
37
|
+
|
|
38
|
+
// Add error handler to response object if not already present
|
|
39
|
+
if (!res.error) {
|
|
40
|
+
res.error = (message, statusCode = 500, error = null) => {
|
|
41
|
+
logger.error(message, {
|
|
42
|
+
statusCode,
|
|
43
|
+
error,
|
|
44
|
+
requestId: req.requestId
|
|
45
|
+
});
|
|
46
|
+
return res.status(statusCode).json({
|
|
47
|
+
success: false,
|
|
48
|
+
message,
|
|
49
|
+
requestId: req.requestId
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Add success handler to response object if not already present
|
|
55
|
+
if (!res.success) {
|
|
56
|
+
res.success = (data, message = 'Success', statusCode = 200) => {
|
|
57
|
+
logger.info(message, {
|
|
58
|
+
statusCode,
|
|
59
|
+
requestId: req.requestId
|
|
60
|
+
});
|
|
61
|
+
return res.status(statusCode).json({
|
|
62
|
+
success: true,
|
|
63
|
+
message,
|
|
64
|
+
data,
|
|
65
|
+
requestId: req.requestId
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Continue to the next middleware
|
|
13
71
|
next();
|
|
14
72
|
} catch (error) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
73
|
+
logger.error('Error in request handler', {
|
|
74
|
+
error,
|
|
75
|
+
requestId: req.requestId
|
|
76
|
+
});
|
|
77
|
+
return res.error('Internal server error', 500, error);
|
|
18
78
|
}
|
|
19
79
|
};
|
|
20
80
|
module.exports = {
|
|
21
|
-
|
|
81
|
+
requestHandler
|
|
22
82
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "powr-sdk-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Shared API core library for PowrStack projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@aws-sdk/client-s3": "^3.787.0",
|
|
40
40
|
"express": "^4.18.2",
|
|
41
|
+
"jsonwebtoken": "^9.0.2",
|
|
41
42
|
"swagger-jsdoc": "^6.2.8",
|
|
42
43
|
"swagger-ui-express": "^5.0.0",
|
|
43
44
|
"winston": "^3.17.0"
|