@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 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
- class EnvVar {
5
- fetch (var_name = '', is_required = false) {
6
- const value = process.env[var_name]
7
- if (is_required && !var_name) throw new Error(`Required EnvVar ${var_name} not found`)
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 = new EnvVar()
10
+ module.exports = { fetch }
@@ -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
- const [, token] = authorization.split(" ");
22
- if (!token) {
23
- return next(rootService.process_failed_response("Unauthorized", 403));
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
- if (token === GM_TOKEN) {
27
- request.tenant_id = { $exists: true };
28
- return next();
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
- const verified_data = await jwt.verify(token, JWT_SECRET, {
32
- issuer: JWT_ISSUER,
33
- });
37
+ if (error) throw new Error('Unauthorized')
34
38
 
35
- const { tenant_id, is_admin } = verified_data;
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
- const authenticate_api_key = async (request, response, next) => {
42
+ // main
43
+ const authenticate_user = async (request, response, next) => {
48
44
  try {
49
- const { authorization } = request.headers;
50
- if (!authorization) {
51
- return next(rootService.process_failed_response("Unauthorized", 403));
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 [, api_key] = authorization.split(" ");
55
- if (!api_key) {
56
- return next(rootService.process_failed_response("Unauthorized", 403));
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
- const payload = await verify_api_key(api_key);
65
- request.tenant_id = payload.org_id;
66
- next();
55
+ next()
67
56
  } catch (e) {
68
- logger.error(`${e.message}`, "authenticate_api_key");
69
- next(rootService.process_failed_response("Unauthorized", 403));
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
- const [, token] = authorization.split(" ");
92
- if (!token) {
93
- return next(rootService.process_failed_response("Unauthorized", 403));
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
- if (token === GM_TOKEN) {
97
- request.tenant_id = { $exists: true };
98
- return next();
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
- module.exports = { authenticate_api_key, authenticate_user };
85
+ return { default_key, param_key }
86
+ }
87
+
88
+ module.exports = { api, authenticate_user }
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ UNAUTHORIZED: {
3
+ payload: null,
4
+ error: 'Unauthorized',
5
+ status_code: 403
6
+ }
7
+ }
@@ -1,11 +1,9 @@
1
- /**
2
- * @author Oguntuberu Nathan O. <nateoguns.work@gmail.com>
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 (request, response, next) {
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, request, response, next) {
17
+ handle_error (error, __, response, ____) {
20
18
  // Log errors
21
- logger.error(error.error || error.message, 'handle_error')
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": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "main": "index.js",
5
- "repository": "git@noguntuberu:go-mailer-ltd/jarvis-node.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