lesgo 0.7.7 → 1.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.
@@ -0,0 +1,125 @@
1
+ import client from 'Config/client'; // eslint-disable-line import/no-unresolved
2
+ import LesgoException from '../exceptions/LesgoException';
3
+
4
+ const FILE = 'Middlewares/basicAuthMiddleware';
5
+
6
+ export const generateBasicAuthorizationHash = (key, secret, opts = {}) => {
7
+ const { getPreHashString } = {
8
+ ...client,
9
+ ...opts,
10
+ };
11
+ const preHashString =
12
+ typeof getPreHashString === 'function'
13
+ ? getPreHashString(key, secret)
14
+ : `${key}:${secret}`;
15
+
16
+ return Buffer.from(preHashString).toString('base64');
17
+ };
18
+
19
+ const getClient = opts => {
20
+ if (opts && opts.client && Object.keys(opts.client).length > 0) {
21
+ return opts.client;
22
+ }
23
+
24
+ return client.clients;
25
+ };
26
+
27
+ const getHashFromHeaders = headers => {
28
+ const basicAuth = headers.Authorization || headers.authorization;
29
+
30
+ if (typeof basicAuth === 'undefined') {
31
+ return '';
32
+ }
33
+
34
+ if (
35
+ typeof basicAuth !== 'undefined' &&
36
+ !basicAuth.startsWith('basic ') &&
37
+ !basicAuth.startsWith('Basic ')
38
+ ) {
39
+ throw new LesgoException(
40
+ 'Invalid authorization type provided',
41
+ `${FILE}::AUTH_INVALID_AUTHORIZATION_TYPE`,
42
+ 403,
43
+ 'Use the basic authorization method'
44
+ );
45
+ }
46
+
47
+ const authEncoded = basicAuth.startsWith('basic ')
48
+ ? basicAuth.replace('basic ', '')
49
+ : basicAuth.replace('Basic ', '');
50
+
51
+ if (authEncoded.length <= 0) {
52
+ throw new LesgoException(
53
+ 'Empty basic authentication hash provided',
54
+ `${FILE}::AUTH_EMPTY_BASIC_HASH`,
55
+ 403,
56
+ 'Ensure basic authentication has is provided along with the keyword "Basic"'
57
+ );
58
+ }
59
+
60
+ return authEncoded;
61
+ };
62
+
63
+ const validateBasicAuth = (hash, clientObject, opts, siteId = undefined) => {
64
+ const site = Object.keys(clientObject).find(clientCode => {
65
+ const hashIsEquals =
66
+ generateBasicAuthorizationHash(
67
+ clientObject[clientCode].key,
68
+ clientObject[clientCode].secret,
69
+ opts
70
+ ) === hash;
71
+
72
+ return siteId ? siteId === clientCode && hashIsEquals : hashIsEquals;
73
+ });
74
+
75
+ if (!site) {
76
+ throw new LesgoException(
77
+ 'Invalid client key or secret provided',
78
+ `${FILE}::AUTH_INVALID_CLIENT_OR_SECRET_KEY`,
79
+ 403,
80
+ 'Ensure you are using the correct client key or secret key provided'
81
+ );
82
+ }
83
+ };
84
+
85
+ export const verifyBasicAuthBeforeHandler = async (handler, next, opts) => {
86
+ const { headers, platform } = handler.event;
87
+ const finalClient = getClient(opts);
88
+ const hashFromHeader = getHashFromHeaders(headers);
89
+ let isAuthOptional = platform ? platform.isAuthOptional : false;
90
+ if (isAuthOptional && typeof isAuthOptional.then === 'function') {
91
+ isAuthOptional = await isAuthOptional;
92
+ }
93
+
94
+ if (hashFromHeader) {
95
+ validateBasicAuth(
96
+ hashFromHeader,
97
+ finalClient,
98
+ opts,
99
+ platform ? platform.id : undefined
100
+ );
101
+ } else if (!platform || !isAuthOptional) {
102
+ /**
103
+ * An error will occur only when either the platform could not be determined, assuming a basic auth is needed.
104
+ * Or whenever the platform could be determined, but `isAuthOptional` is not true for that platform
105
+ */
106
+ throw new LesgoException(
107
+ 'Authorization header not found',
108
+ `${FILE}::AUTHORIZATION_HEADER_NOT_FOUND`,
109
+ 403,
110
+ 'Ensure you are have provided the basic authentication code using Authorization header'
111
+ );
112
+ }
113
+
114
+ next();
115
+ };
116
+
117
+ /* istanbul ignore next */
118
+ const basicAuthMiddleware = opts => {
119
+ return {
120
+ before: (handler, next) =>
121
+ verifyBasicAuthBeforeHandler(handler, next, opts),
122
+ };
123
+ };
124
+
125
+ export default basicAuthMiddleware;
@@ -0,0 +1,103 @@
1
+ import client from 'Config/client'; // eslint-disable-line import/no-unresolved
2
+ import validateFields from '../utils/validateFields';
3
+ import { LesgoException } from '../exceptions';
4
+
5
+ const FILE = 'Middlewares/clientAuthMiddleware';
6
+
7
+ const validateParams = params => {
8
+ const validFields = [
9
+ { key: 'clientKey', type: 'string', required: true },
10
+ { key: 'client', type: 'object', required: true },
11
+ ];
12
+
13
+ try {
14
+ return validateFields(params, validFields);
15
+ } catch (error) {
16
+ throw new LesgoException(error.message, `${FILE}::INVALID_AUTH_DATA`, 403, {
17
+ error,
18
+ });
19
+ }
20
+ };
21
+
22
+ const getClientKey = event => {
23
+ const foundExistingKey = client.headerKeys.find(headerKey => {
24
+ if (event.headers && typeof event.headers[headerKey] === 'string') {
25
+ return true;
26
+ }
27
+
28
+ if (
29
+ event.queryStringParameters &&
30
+ typeof event.queryStringParameters[headerKey] === 'string'
31
+ ) {
32
+ return true;
33
+ }
34
+
35
+ return false;
36
+ });
37
+
38
+ if (foundExistingKey) {
39
+ if (event.headers && event.headers[foundExistingKey]) {
40
+ return event.headers[foundExistingKey];
41
+ }
42
+
43
+ // There will always be one where this is found existing
44
+ return event.queryStringParameters[foundExistingKey];
45
+ }
46
+
47
+ if (event.input && typeof event.input.clientid === 'string') {
48
+ return event.input.clientid;
49
+ }
50
+
51
+ return undefined;
52
+ };
53
+
54
+ export const clientAuthMiddlewareBeforeHandler = async (
55
+ handler,
56
+ next,
57
+ opt = {}
58
+ ) => {
59
+ const { clients, callback } = {
60
+ ...client,
61
+ ...(typeof opt === 'function' ? { callback: opt } : opt),
62
+ };
63
+
64
+ const { client: validatedClient, clientKey } = validateParams({
65
+ clientKey: getClientKey(handler.event),
66
+ client: clients,
67
+ });
68
+
69
+ const platform = Object.keys(validatedClient).filter(clientPlatform => {
70
+ return validatedClient[clientPlatform].key === clientKey;
71
+ });
72
+
73
+ if (platform.length === 0) {
74
+ throw new LesgoException(
75
+ 'Invalid ClientId provided',
76
+ `${FILE}::INVALID_CLIENT_ID`,
77
+ 403,
78
+ 'Ensure you are using the correct Client Id provided'
79
+ );
80
+ }
81
+
82
+ // eslint-disable-next-line no-param-reassign,prefer-destructuring
83
+ handler.event.platform = {
84
+ id: platform[0],
85
+ ...client.clients[platform[0]],
86
+ };
87
+
88
+ if (typeof callback === 'function') {
89
+ await callback(handler);
90
+ }
91
+
92
+ next();
93
+ };
94
+
95
+ /* istanbul ignore next */
96
+ const clientAuthMiddleware = opt => {
97
+ return {
98
+ before: (handler, next) =>
99
+ clientAuthMiddlewareBeforeHandler(handler, next, opt),
100
+ };
101
+ };
102
+
103
+ export default clientAuthMiddleware;
@@ -66,7 +66,9 @@ export const errorHttpResponseHandler = async opts => {
66
66
  return {
67
67
  headers: options.headers,
68
68
  statusCode,
69
- body: JSON.stringify(jsonBody),
69
+ body: options.formatError
70
+ ? options.formatError(options)
71
+ : JSON.stringify(jsonBody),
70
72
  };
71
73
  };
72
74
 
@@ -0,0 +1,91 @@
1
+ import app from 'Config/app'; // eslint-disable-line import/no-unresolved
2
+ import { normalizeHttpRequestBeforeHandler } from './normalizeHttpRequestMiddleware';
3
+ import { successHttpResponseHandler } from './successHttpResponseMiddleware';
4
+ import { errorHttpResponseHandler } from './errorHttpResponseMiddleware';
5
+
6
+ const allowResponse = () => {
7
+ return app.debug;
8
+ };
9
+
10
+ const successHttpNoOutputResponseHandler = async opts => {
11
+ const { queryStringParameters } = opts.event;
12
+ const debug = queryStringParameters && queryStringParameters.debug;
13
+ const response = await successHttpResponseHandler(opts);
14
+ const shouldAllowResponse = opts.allowResponse || allowResponse;
15
+
16
+ /* istanbul ignore next */
17
+ if (!debug || !shouldAllowResponse(opts)) {
18
+ response.body = '';
19
+ }
20
+
21
+ return response;
22
+ };
23
+
24
+ export const successHttpNoOutputResponseAfterHandler = async (
25
+ handler,
26
+ next,
27
+ opts
28
+ ) => {
29
+ const defaults = {
30
+ response: handler.response,
31
+ event: handler.event,
32
+ allowResponse,
33
+ };
34
+ const options = { ...defaults, ...opts };
35
+
36
+ // eslint-disable-next-line no-param-reassign
37
+ handler.response = await successHttpNoOutputResponseHandler(options);
38
+
39
+ /* istanbul ignore next */
40
+ next();
41
+ };
42
+
43
+ const errorHttpResponseNoOutputHandler = async opts => {
44
+ const { queryStringParameters } = opts.event;
45
+ const debug = queryStringParameters && queryStringParameters.debug;
46
+ const response = await errorHttpResponseHandler({
47
+ ...opts,
48
+ debugMode: opts.debugMode || debug,
49
+ });
50
+ const shouldAllowResponse = opts.allowResponse || allowResponse;
51
+
52
+ if (!debug || !shouldAllowResponse(opts)) {
53
+ response.statusCode = 200;
54
+ response.body = '';
55
+ }
56
+
57
+ return response;
58
+ };
59
+
60
+ export const errorHttpNoOutputResponseAfterHandler = async (
61
+ handler,
62
+ next,
63
+ opts
64
+ ) => {
65
+ const defaults = {
66
+ error: handler.error,
67
+ event: handler.event,
68
+ logger: console.error, // eslint-disable-line no-console
69
+ allowResponse,
70
+ };
71
+
72
+ const options = { ...defaults, ...opts };
73
+
74
+ // eslint-disable-next-line no-param-reassign
75
+ handler.response = await errorHttpResponseNoOutputHandler(options);
76
+ /* istanbul ignore next */
77
+ next();
78
+ };
79
+
80
+ /* istanbul ignore next */
81
+ const httpNoOutputMiddleware = opts => {
82
+ return {
83
+ before: (handler, next) => normalizeHttpRequestBeforeHandler(handler, next),
84
+ after: (handler, next) =>
85
+ successHttpNoOutputResponseAfterHandler(handler, next, opts),
86
+ onError: (handler, next) =>
87
+ errorHttpNoOutputResponseAfterHandler(handler, next, opts),
88
+ };
89
+ };
90
+
91
+ export default httpNoOutputMiddleware;
@@ -4,6 +4,8 @@ import successHttpResponseMiddleware from './successHttpResponseMiddleware';
4
4
  import errorHttpResponseMiddleware from './errorHttpResponseMiddleware';
5
5
  import normalizeSQSMessageMiddleware from './normalizeSQSMessageMiddleware';
6
6
  import verifyJwtMiddleware from './verifyJwtMiddleware';
7
+ import basicAuthMiddleware from './basicAuthMiddleware';
8
+ import clientAuthMiddleware from './clientAuthMiddleware';
7
9
 
8
10
  export {
9
11
  httpMiddleware,
@@ -12,4 +14,6 @@ export {
12
14
  errorHttpResponseMiddleware,
13
15
  normalizeSQSMessageMiddleware,
14
16
  verifyJwtMiddleware,
17
+ basicAuthMiddleware,
18
+ clientAuthMiddleware,
15
19
  };
@@ -26,9 +26,11 @@ export const normalizeHandler = records => {
26
26
  export const disconnectConnections = async opts => {
27
27
  try {
28
28
  const disconnect = [];
29
- if (!isEmpty(opts.cache)) disconnect.push(opts.cache.end());
30
- if (!isEmpty(opts.db)) disconnect.push(opts.db.end());
31
- if (!isEmpty(opts.dbRead)) disconnect.push(opts.dbRead.end());
29
+ if (opts) {
30
+ if (!isEmpty(opts.cache)) disconnect.push(opts.cache.end());
31
+ if (!isEmpty(opts.db)) disconnect.push(opts.db.end());
32
+ if (!isEmpty(opts.dbRead)) disconnect.push(opts.dbRead.end());
33
+ }
32
34
 
33
35
  if (disconnect.length > 0) {
34
36
  await Promise.all(disconnect);
@@ -43,11 +43,13 @@ export const successHttpResponseHandler = async opts => {
43
43
  return {
44
44
  headers: options.headers,
45
45
  statusCode: options.statusCode,
46
- body: JSON.stringify({
47
- status: 'success',
48
- data: options.response,
49
- _meta: options.debugMode ? options.event : {},
50
- }),
46
+ body: options.formatSuccess
47
+ ? options.formatSuccess(options)
48
+ : JSON.stringify({
49
+ status: 'success',
50
+ data: options.response,
51
+ _meta: options.debugMode ? options.event : {},
52
+ }),
51
53
  };
52
54
  };
53
55
 
@@ -26,31 +26,47 @@ export const token = headers => {
26
26
  return parsed[1];
27
27
  };
28
28
 
29
- export const verifyJwtMiddlewareBeforeHandler = (handler, next) => {
29
+ export const verifyJwtMiddlewareBeforeHandler = async (handler, next, opts) => {
30
30
  const { headers } = handler.event;
31
31
 
32
+ const finalConfig =
33
+ typeof opts !== 'undefined' && 'jwtConfig' in opts
34
+ ? opts.jwtConfig
35
+ : config;
36
+
37
+ const { secret, callback } = finalConfig;
38
+
32
39
  try {
33
- const service = new JwtService(token(headers), config);
40
+ const service = new JwtService(token(headers), {
41
+ ...finalConfig,
42
+ ...{
43
+ secret: typeof secret === 'function' ? secret(handler) : secret,
44
+ },
45
+ });
34
46
 
35
47
  // eslint-disable-next-line no-param-reassign
36
48
  handler.event.decodedJwt = service.validate().decoded;
37
49
 
50
+ if (typeof callback === 'function') {
51
+ await callback(handler);
52
+ }
53
+
38
54
  next();
39
55
  } catch (err) {
40
56
  if (err.name === 'JsonWebTokenError') {
41
- throw new LesgoException(err.message, 'JWT_ERROR', 403);
57
+ throw new LesgoException(err.message, 'JWT_ERROR', 403, err);
42
58
  } else if (err.name === 'TokenExpiredError') {
43
- throw new LesgoException(err.message, 'JWT_EXPIRED', 403);
59
+ throw new LesgoException(err.message, 'JWT_EXPIRED', 403, err);
44
60
  } else {
45
61
  throw err;
46
62
  }
47
63
  }
48
64
  };
49
65
 
50
- /* istanbul ignore next */
51
- const verifyJwtMiddleware = () => {
66
+ const verifyJwtMiddleware /* istanbul ignore next */ = opts => {
52
67
  return {
53
- before: (handler, next) => verifyJwtMiddlewareBeforeHandler(handler, next),
68
+ before: (handler, next) =>
69
+ verifyJwtMiddlewareBeforeHandler(handler, next, opts),
54
70
  };
55
71
  };
56
72
 
@@ -30,8 +30,9 @@ export default class LoggerService {
30
30
  this.logLevels = {
31
31
  error: 0,
32
32
  warn: 1,
33
- info: 2,
34
- debug: 3,
33
+ notice: 2,
34
+ info: 3,
35
+ debug: 4,
35
36
  };
36
37
  }
37
38
 
@@ -63,6 +64,10 @@ export default class LoggerService {
63
64
  this.log('debug', message, extra);
64
65
  }
65
66
 
67
+ notice(message, extra = {}) {
68
+ this.log('notice', message, extra);
69
+ }
70
+
66
71
  addMeta(meta = {}) {
67
72
  this.meta = {
68
73
  ...this.meta,
@@ -73,7 +78,9 @@ export default class LoggerService {
73
78
  consoleLogger(level, message) {
74
79
  if (!this.checkIsLogRequired('console', level)) return null;
75
80
  const refinedMessage = this.refineMessagePerTransport('console', message);
76
- return console[level](JSON.stringify(refinedMessage)); // eslint-disable-line no-console
81
+ const consoleFunc = level === 'notice' ? 'log' : level;
82
+
83
+ return console[consoleFunc](JSON.stringify(refinedMessage)); // eslint-disable-line no-console
77
84
  }
78
85
 
79
86
  checkIsLogRequired(transportName, level) {
@@ -13,8 +13,9 @@ describe('ServicesGroup: test LoggerService instantiation', () => {
13
13
  expect(logger.logLevels).toMatchObject({
14
14
  error: 0,
15
15
  warn: 1,
16
- info: 2,
17
- debug: 3,
16
+ notice: 2,
17
+ info: 3,
18
+ debug: 4,
18
19
  });
19
20
  });
20
21
 
@@ -146,6 +147,20 @@ describe('ServicesGroup: test log LoggerService with console transport', () => {
146
147
  );
147
148
  });
148
149
 
150
+ it('test log with notice level', () => {
151
+ const logger = new LoggerService({ transports: [{ logType: 'console' }] });
152
+ logger.notice('some notice log');
153
+
154
+ expect(console.log).toHaveBeenCalledWith(
155
+ JSON.stringify({
156
+ level: 'notice',
157
+ message: 'some notice log',
158
+ logger: 'lesgo-logger',
159
+ extra: {},
160
+ })
161
+ );
162
+ });
163
+
149
164
  it('test log with error level', () => {
150
165
  const logger = new LoggerService({ transports: [{ logType: 'console' }] });
151
166
  logger.error('some error log');