@go-mailer/jarvis 1.0.0 → 2.0.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/index.js +5 -0
- package/lib/env.js +5 -7
- package/lib/middlewares/auth.js +66 -94
- package/lib/middlewares/errors.js +7 -0
- package/lib/middlewares/{request.js → http.js} +10 -8
- package/lib/middlewares/logger.js +93 -0
- package/lib/query.js +0 -0
- package/package.json +3 -5
- package/lib/logger.js +0 -126
- package/test.js +0 -0
package/index.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const Authenticator = require('./lib/middlewares/auth')
|
|
2
|
+
const HTTP = require('@go-mailer/jarvis/lib/middlewares/http')
|
|
3
|
+
const { RequestLogger, ProcessLogger } = require('./lib/logger')
|
|
4
|
+
const QueryBuilder = require('./lib/query')
|
|
5
|
+
module.exports = { RequestLogger, ProcessLogger, Authenticator, HTTP, QueryBuilder }
|
package/lib/env.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
require('dotenv').config()
|
|
2
2
|
|
|
3
3
|
/** */
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return value
|
|
9
|
-
}
|
|
4
|
+
const fetch = (var_name = '', is_required = false) => {
|
|
5
|
+
const value = process.env[var_name]
|
|
6
|
+
if (is_required && !value) throw new Error(`Required EnvVar ${var_name} not found`)
|
|
7
|
+
return value
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
module.exports =
|
|
10
|
+
module.exports = { fetch }
|
package/lib/middlewares/auth.js
CHANGED
|
@@ -1,116 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User Authentication Middleware
|
|
3
3
|
*/
|
|
4
|
-
const jwt = require("jsonwebtoken");
|
|
5
|
-
const { verify_api_key } = require("../clients/iam");
|
|
6
|
-
const RootService = require("../services/_root");
|
|
7
|
-
const rootService = new RootService();
|
|
8
|
-
const { JWT_ISSUER, JWT_SECRET, DEFAULT_TOKEN: GM_TOKEN } = require("../../.config");
|
|
9
|
-
|
|
10
|
-
const { app_logger } = require("../utilities/logger");
|
|
11
|
-
const logger = app_logger("Authentication Middleware");
|
|
12
|
-
|
|
13
|
-
class Authentication {
|
|
14
|
-
async authenticate_user(request, response, next) {
|
|
15
|
-
try {
|
|
16
|
-
const { authorization } = request.headers;
|
|
17
|
-
if (!authorization) {
|
|
18
|
-
return next(rootService.process_failed_response("Unauthorized", 403));
|
|
19
|
-
}
|
|
20
4
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
5
|
+
const jwt = require("jsonwebtoken");
|
|
6
|
+
const Env = require('../env');
|
|
7
|
+
const Errors = require('./errors');
|
|
8
|
+
const { ProcessLogger } = require('../logger/index')
|
|
9
|
+
const authLogger = new ProcessLogger('Authenticator')
|
|
10
|
+
|
|
11
|
+
// env vars
|
|
12
|
+
const ISSUER = Env.fetch('JWT_ISSUER', true)
|
|
13
|
+
const SECRET = Env.fetch('JWT_SECRET', true)
|
|
14
|
+
const IAM_URI = Env.fetch('IAM_SERVICE_URI', true)
|
|
15
|
+
const DEFAULT_TOKEN = Env.fetch('DEFAULT_TOKEN', true)
|
|
16
|
+
|
|
17
|
+
// helpers
|
|
18
|
+
const extract_token = (headers) => {
|
|
19
|
+
const { authorization } = headers
|
|
20
|
+
if (!authorization) throw new Error('Unauthorized')
|
|
21
|
+
|
|
22
|
+
const [, token] = authorization.split(' ')
|
|
23
|
+
if (!token) throw new Error('Unauthorized')
|
|
24
|
+
|
|
25
|
+
return token
|
|
26
|
+
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const verify_key = async (key) => {
|
|
29
|
+
const { error, payload } = (
|
|
30
|
+
await axios.get(`${IAM_URI}/keys/verify/${key}`, {
|
|
31
|
+
headers: {
|
|
32
|
+
authorization: `Bearer ${DEFAULT_TOKEN}`
|
|
29
33
|
}
|
|
34
|
+
})
|
|
35
|
+
).data
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
issuer: JWT_ISSUER,
|
|
33
|
-
});
|
|
37
|
+
if (error) throw new Error('Unauthorized')
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
request.tenant_id = tenant_id;
|
|
37
|
-
if (is_admin) process_request(request);
|
|
38
|
-
|
|
39
|
-
next();
|
|
40
|
-
} catch (e) {
|
|
41
|
-
logger.error(`[Auth Error] ${e.message}`);
|
|
42
|
-
next(rootService.process_failed_response("Unauthorized", 403));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
39
|
+
return payload.org_id
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
|
|
42
|
+
// main
|
|
43
|
+
const authenticate_user = async (request, response, next) => {
|
|
48
44
|
try {
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
|
|
45
|
+
const token = extract_token(request.headers)
|
|
46
|
+
if (token === DEFAULT_TOKEN) { // service level requests
|
|
47
|
+
request.is_service_request = true
|
|
48
|
+
return next()
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (api_key === GM_TOKEN) {
|
|
60
|
-
request.tenant_id = request.body.tenant_id;
|
|
61
|
-
return next();
|
|
62
|
-
}
|
|
51
|
+
const { tenant_id, is_admin } = await jwt.verify(token, SECRET, { issuer: ISSUER })
|
|
52
|
+
request.is_admin = !!is_admin
|
|
53
|
+
request.tenant_id = tenant_id
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
request.tenant_id = payload.org_id;
|
|
66
|
-
next();
|
|
55
|
+
next()
|
|
67
56
|
} catch (e) {
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
authLogger.error(e, 'authenticate_user')
|
|
58
|
+
return response.status(403).json(Errors.UNAUTHORIZED)
|
|
70
59
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** All requests not made from the 'admin console' must be scoped by tenant */
|
|
74
|
-
const process_request = (request) => {
|
|
75
|
-
const { query, params, body } = request;
|
|
76
|
-
let tenant_id = { $exists: true };
|
|
77
|
-
if (query.tenant_id) tenant_id = query.tenant_id;
|
|
78
|
-
if (params.tenant_id) tenant_id = params.tenant_id;
|
|
79
|
-
if (body.tenant_id) tenant_id = body.tenant_id;
|
|
80
|
-
|
|
81
|
-
request.tenant_id = tenant_id;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const authenticate_user = async (request, __, next) => {
|
|
85
|
-
try {
|
|
86
|
-
const { authorization } = request.headers;
|
|
87
|
-
if (!authorization) {
|
|
88
|
-
return next(rootService.process_failed_response("Unauthorized", 403));
|
|
89
|
-
}
|
|
60
|
+
}
|
|
90
61
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
62
|
+
const api = () => {
|
|
63
|
+
const param_key = async (request, response, next) => {
|
|
64
|
+
try {
|
|
65
|
+
const { api_key: token} = request.params
|
|
66
|
+
request.tenant_id = await verify_key(token)
|
|
67
|
+
next()
|
|
68
|
+
} catch (e) {
|
|
69
|
+
authLogger.error(e, 'param_key')
|
|
70
|
+
return response.status(403).json(Errors.UNAUTHORIZED)
|
|
94
71
|
}
|
|
72
|
+
}
|
|
95
73
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
74
|
+
const default_key = async (request, response, next) => {
|
|
75
|
+
try {
|
|
76
|
+
const token = extract_token(request.headers)
|
|
77
|
+
request.tenant_id = await verify_key(token)
|
|
78
|
+
next()
|
|
79
|
+
} catch (e) {
|
|
80
|
+
authLogger.error(e, 'default_key')
|
|
81
|
+
return response.status(403).json(Errors.UNAUTHORIZED)
|
|
99
82
|
}
|
|
100
|
-
|
|
101
|
-
const verified_data = await jwt.verify(token, JWT_SECRET, {
|
|
102
|
-
issuer: JWT_ISSUER,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const { tenant_id, is_admin } = verified_data;
|
|
106
|
-
request.tenant_id = tenant_id;
|
|
107
|
-
if (is_admin) process_request(request);
|
|
108
|
-
|
|
109
|
-
next();
|
|
110
|
-
} catch (e) {
|
|
111
|
-
logger.error(`[Auth Error] ${e.message}`);
|
|
112
|
-
next(rootService.process_failed_response("Unauthorized", 403));
|
|
113
83
|
}
|
|
114
|
-
};
|
|
115
84
|
|
|
116
|
-
|
|
85
|
+
return { default_key, param_key }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { api, authenticate_user }
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { app_logger } = require('../utilities/logger')
|
|
5
|
-
const logger = app_logger('HTTP Middleware')
|
|
1
|
+
/** **/
|
|
2
|
+
const { ProcessLogger } = require('@go-mailer/jarvis/lib/logger')
|
|
3
|
+
const HTTPLogger = new ProcessLogger('HTTPSetup')
|
|
6
4
|
|
|
7
5
|
module.exports = {
|
|
8
|
-
handle_404 (
|
|
6
|
+
handle_404 (_, __, next) {
|
|
9
7
|
const return_data = {
|
|
10
8
|
status_code: 404,
|
|
11
9
|
success: false,
|
|
@@ -16,9 +14,13 @@ module.exports = {
|
|
|
16
14
|
next(return_data)
|
|
17
15
|
},
|
|
18
16
|
|
|
19
|
-
handle_error (error,
|
|
17
|
+
handle_error (error, __, response, ____) {
|
|
20
18
|
// Log errors
|
|
21
|
-
|
|
19
|
+
if (error.error) {
|
|
20
|
+
HTTPLogger.info(error.error, 'handle_error')
|
|
21
|
+
} else {
|
|
22
|
+
HTTPLogger.error(error, 'handle_error')
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
// return error
|
|
24
26
|
return response.status(error.status_code || 500).json({
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Oguntuberu Nathan O. <nateoguns.work@gmail.com>
|
|
3
|
+
**/
|
|
4
|
+
|
|
5
|
+
const Env = require("../env");
|
|
6
|
+
const { randomUUID } = require("crypto");
|
|
7
|
+
const LOGTAIL_SECRET = Env.fetch("LOGTAIL_SECRET", true);
|
|
8
|
+
const { Logtail } = require("@logtail/node");
|
|
9
|
+
const logtail = new Logtail(LOGTAIL_SECRET);
|
|
10
|
+
|
|
11
|
+
function RequestLogger(request, response, next) {
|
|
12
|
+
const app_name = Env.fetch("APP_NAME", true);
|
|
13
|
+
if (!request.request_id) request.request_id = randomUUID();
|
|
14
|
+
|
|
15
|
+
//
|
|
16
|
+
const {
|
|
17
|
+
query,
|
|
18
|
+
params,
|
|
19
|
+
headers: { host, origin, "user-agent": user_agent, "sec-ch-ua-platform": os, referer },
|
|
20
|
+
request_id,
|
|
21
|
+
tenant_id,
|
|
22
|
+
} = request;
|
|
23
|
+
|
|
24
|
+
response.on("finish", () => {
|
|
25
|
+
const {
|
|
26
|
+
_parsedUrl: { pathname },
|
|
27
|
+
httpVersion,
|
|
28
|
+
_startTime,
|
|
29
|
+
_remoteAddress,
|
|
30
|
+
payload,
|
|
31
|
+
} = response.req;
|
|
32
|
+
const { statusCode, statusMessage } = response.req.res;
|
|
33
|
+
const duration = Date.now() - Date.parse(_startTime);
|
|
34
|
+
const log = {
|
|
35
|
+
app_name,
|
|
36
|
+
request_id,
|
|
37
|
+
tenant_id,
|
|
38
|
+
query,
|
|
39
|
+
params,
|
|
40
|
+
host,
|
|
41
|
+
origin,
|
|
42
|
+
user_agent,
|
|
43
|
+
os,
|
|
44
|
+
referer,
|
|
45
|
+
httpVersion,
|
|
46
|
+
_remoteAddress,
|
|
47
|
+
pathname,
|
|
48
|
+
duration,
|
|
49
|
+
type: "request",
|
|
50
|
+
status_code: payload ? payload.status_code : statusCode,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
let error = payload ? payload.error : statusMessage;
|
|
54
|
+
if (error) {
|
|
55
|
+
log.error = error;
|
|
56
|
+
logtail.error(pathname, log);
|
|
57
|
+
} else {
|
|
58
|
+
logtail.info(pathname, log);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
logtail.flush();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
next();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class ProcessLogger {
|
|
68
|
+
constructor(service = "System") {
|
|
69
|
+
this.app_name = Env.fetch("APP_NAME", true);
|
|
70
|
+
this.service = service;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
error(error, method = "unspecified_method") {
|
|
74
|
+
logtail.error(`${this.service}:${method}:${error.message}`, {
|
|
75
|
+
app_name: this.app_name,
|
|
76
|
+
type: "process",
|
|
77
|
+
message: error.message,
|
|
78
|
+
trace: error.stack,
|
|
79
|
+
});
|
|
80
|
+
logtail.flush();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
info(info, method = "unspecified_method") {
|
|
84
|
+
logtail.info(`${this.service}:${method}:${info}`, {
|
|
85
|
+
app_name: this.app_name,
|
|
86
|
+
type: "process",
|
|
87
|
+
message: info,
|
|
88
|
+
});
|
|
89
|
+
logtail.flush();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { RequestLogger, ProcessLogger };
|
package/lib/query.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-mailer/jarvis",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "index.js",
|
|
5
|
-
"repository": "git@
|
|
5
|
+
"repository": "git@github.com:go-mailer-ltd/jarvis-node.git",
|
|
6
6
|
"author": "Nathan Oguntuberu <nateoguns.work@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@logtail/node": "^0.3.3",
|
|
10
10
|
"axios": "^1.3.4",
|
|
11
11
|
"dotenv": "^16.0.3",
|
|
12
|
-
"jsonwebtoken": "^9.0.0"
|
|
13
|
-
"morgan": "^1.10.0",
|
|
14
|
-
"winston": "^3.8.2"
|
|
12
|
+
"jsonwebtoken": "^9.0.0"
|
|
15
13
|
}
|
|
16
14
|
}
|
package/lib/logger.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @author Oguntuberu Nathan O. <nateoguns.work@gmail.com>
|
|
3
|
-
**/
|
|
4
|
-
|
|
5
|
-
const envVar = require('./env')
|
|
6
|
-
const { Logtail } = require("@logtail/node");
|
|
7
|
-
const logtail = new Logtail("oTNABUxtJb6erTRpZ2jhJhEs");
|
|
8
|
-
|
|
9
|
-
const request_logger = (request, __, next) => {
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** MORGAN */
|
|
14
|
-
const { createWriteStream } = require('fs')
|
|
15
|
-
const { resolve } = require('path')
|
|
16
|
-
|
|
17
|
-
/** REQUEST LOG */
|
|
18
|
-
const morgan = require('morgan')
|
|
19
|
-
const request_log_format = '[:date[web] :remote-addr :remote-user ] :method :url HTTP/:http-version :referrer - :user-agent | :status :response-time ms'
|
|
20
|
-
|
|
21
|
-
const request_log_stream = createWriteStream(resolve(__dirname, '../../logs/request.log'), { flags: 'a' })
|
|
22
|
-
const morgan_logger = morgan(request_log_format, { stream: request_log_stream })
|
|
23
|
-
|
|
24
|
-
/** WINSTON */
|
|
25
|
-
const {
|
|
26
|
-
createLogger,
|
|
27
|
-
format,
|
|
28
|
-
transports
|
|
29
|
-
} = require('winston')
|
|
30
|
-
|
|
31
|
-
const {
|
|
32
|
-
colorize,
|
|
33
|
-
combine,
|
|
34
|
-
printf,
|
|
35
|
-
timestamp
|
|
36
|
-
} = format
|
|
37
|
-
|
|
38
|
-
const log_transports = {
|
|
39
|
-
client_log: new transports.File({ level: 'error', filename: 'logs/client.log' }),
|
|
40
|
-
console: new transports.Console({ level: 'warn' }),
|
|
41
|
-
combined_log: new transports.File({ level: 'info', filename: 'logs/combined.log' }),
|
|
42
|
-
error_log: new transports.File({ level: 'error', filename: 'logs/error.log' }),
|
|
43
|
-
exception_log: new transports.File({ filename: 'logs/exception.log' }),
|
|
44
|
-
mailer_log: new transports.File({ level: 'error', filename: 'logs/mailer.log' }),
|
|
45
|
-
stream_log: new transports.File({ level: 'error', filename: 'logs/stream.log' })
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const log_format = printf(({ level, message, timestamp }) => `[${timestamp} : ${level}] - ${message}`)
|
|
49
|
-
|
|
50
|
-
const logger = createLogger({
|
|
51
|
-
transports: [
|
|
52
|
-
log_transports.console,
|
|
53
|
-
log_transports.combined_log,
|
|
54
|
-
log_transports.error_log
|
|
55
|
-
],
|
|
56
|
-
exceptionHandlers: [
|
|
57
|
-
log_transports.exception_log
|
|
58
|
-
],
|
|
59
|
-
exitOnError: false,
|
|
60
|
-
format: combine(
|
|
61
|
-
colorize(),
|
|
62
|
-
timestamp(),
|
|
63
|
-
log_format
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const client_logger = createLogger({
|
|
68
|
-
transports: [
|
|
69
|
-
log_transports.console,
|
|
70
|
-
log_transports.client_log
|
|
71
|
-
],
|
|
72
|
-
exitOnError: false,
|
|
73
|
-
format: combine(
|
|
74
|
-
colorize(),
|
|
75
|
-
timestamp(),
|
|
76
|
-
log_format
|
|
77
|
-
)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const mail_logger = createLogger({
|
|
81
|
-
transports: [
|
|
82
|
-
log_transports.console,
|
|
83
|
-
log_transports.mailer_log
|
|
84
|
-
],
|
|
85
|
-
exitOnError: false,
|
|
86
|
-
format: combine(
|
|
87
|
-
colorize(),
|
|
88
|
-
timestamp(),
|
|
89
|
-
log_format
|
|
90
|
-
)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
const stream_logger = createLogger({
|
|
94
|
-
transports: [
|
|
95
|
-
log_transports.console,
|
|
96
|
-
log_transports.stream_log
|
|
97
|
-
],
|
|
98
|
-
exitOnError: false,
|
|
99
|
-
format: combine(
|
|
100
|
-
colorize(),
|
|
101
|
-
timestamp(),
|
|
102
|
-
log_format
|
|
103
|
-
)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const log = console.log
|
|
107
|
-
const app_logger = (service = 'System') => {
|
|
108
|
-
const console = (message, method = 'unspecified_method') => {
|
|
109
|
-
const formatted_message = `[${service} ${method}()]: ${message}`
|
|
110
|
-
log(formatted_message)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const error = (message, method = 'unspecified_method') => {
|
|
114
|
-
const formatted_message = `[${service} ${method}()]: ${message}`
|
|
115
|
-
logger.error(`${formatted_message}`)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const info = (message, method = 'unspecified_method') => {
|
|
119
|
-
const formatted_message = `[${service} ${method}()]: ${message}`
|
|
120
|
-
logger.info(`${formatted_message} ${message}`)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return { console, error, info }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
module.exports = { app_logger, client_logger, logger, mail_logger, morgan: morgan_logger, stream_logger }
|
package/test.js
DELETED
|
File without changes
|