@skillswaveca/nova-shared-libraries 3.9.2 → 3.10.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/docs-base/package-info.js +10 -4
- package/index.js +1 -0
- package/package.json +2 -1
- package/packages/drivers/package.json +1 -1
- package/packages/nova-middleware/package.json +1 -1
- package/packages/nova-model/package.json +1 -1
- package/packages/nova-router/README.md +5 -0
- package/packages/nova-router/index.js +1 -0
- package/packages/nova-router/package.json +18 -0
- package/packages/nova-router/src/nova-router.js +293 -0
- package/packages/nova-utils/package.json +1 -1
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
export const packageInfo = [
|
|
2
2
|
{
|
|
3
3
|
"name": "@skillswaveca/nova-utils",
|
|
4
|
-
"version": "3.9.
|
|
4
|
+
"version": "3.9.2",
|
|
5
5
|
"description": "A collection of random utils used in nova repos",
|
|
6
6
|
"docsPath": "./nova-utils/index.html"
|
|
7
7
|
},
|
|
8
|
+
{
|
|
9
|
+
"name": "nova-router",
|
|
10
|
+
"version": "2.0.0",
|
|
11
|
+
"description": "An extended Koa router that enables better validation",
|
|
12
|
+
"docsPath": "./nova-router/index.html"
|
|
13
|
+
},
|
|
8
14
|
{
|
|
9
15
|
"name": "@skillswaveca/nova-model",
|
|
10
|
-
"version": "3.9.
|
|
16
|
+
"version": "3.9.2",
|
|
11
17
|
"description": "Nova model stuff",
|
|
12
18
|
"docsPath": "./nova-model/index.html"
|
|
13
19
|
},
|
|
14
20
|
{
|
|
15
21
|
"name": "@skillswaveca/nova-middleware",
|
|
16
|
-
"version": "3.9.
|
|
22
|
+
"version": "3.9.2",
|
|
17
23
|
"description": "A collection of middleware used by nova projects",
|
|
18
24
|
"docsPath": "./nova-middleware/index.html"
|
|
19
25
|
},
|
|
20
26
|
{
|
|
21
27
|
"name": "@skillswaveca/nova-drivers",
|
|
22
|
-
"version": "3.9.
|
|
28
|
+
"version": "3.9.2",
|
|
23
29
|
"description": "Some helper drivers for AWS services",
|
|
24
30
|
"docsPath": "./drivers/index.html"
|
|
25
31
|
}
|
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './packages/nova-utils/src/tenant-utils.js';
|
|
2
2
|
export * from './packages/nova-utils/src/logger.js';
|
|
3
3
|
export * from './packages/nova-utils/src/config.js';
|
|
4
|
+
export * from './packages/nova-router/src/nova-router.js';
|
|
4
5
|
export * from './packages/nova-middleware/src/oauth-middleware.js';
|
|
5
6
|
export * from './packages/nova-middleware/src/distributed-logging-middleware.js';
|
|
6
7
|
export * from './packages/drivers/src/wave.js';
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "@skillswaveca/nova-shared-libraries",
|
|
4
4
|
"description": "A monorepo of shared libraries for Nova projects.",
|
|
5
5
|
"repository": "https://github.com/SkillsWave/nova-shared-libraries",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.10.0",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"keywords": [],
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"@aws-sdk/client-sqs": "^3.682.0",
|
|
15
15
|
"@aws-sdk/client-ssm": "^3.682.0",
|
|
16
16
|
"@aws-sdk/lib-dynamodb": "^3.682.0",
|
|
17
|
+
"@koa/router": "^13.1.0",
|
|
17
18
|
"aws-xray-sdk-core": "^3.6.0",
|
|
18
19
|
"jsonwebtoken": "^9.0.2",
|
|
19
20
|
"jwk-to-pem": "^2.0.5",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "@skillswaveca/nova-drivers",
|
|
4
4
|
"description": "Some helper drivers for AWS services",
|
|
5
5
|
"repository": "https://github.com/SkillsWave/nova-shared-libraries",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.10.0",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"pre-release": "pnpm run create-index",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "@skillswaveca/nova-middleware",
|
|
4
4
|
"description": "A collection of middleware used by nova projects",
|
|
5
5
|
"repository": "https://github.com/SkillsWave/nova-shared-libraries",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.10.0",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"pre-release": "pnpm run create-index",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "@skillswaveca/nova-model",
|
|
4
4
|
"description": "Nova model stuff",
|
|
5
5
|
"repository": "https://github.com/SkillsWave/nova-shared-libraries",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.10.0",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"pre-release": "pnpm run create-index",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src/nova-router.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "nova-router",
|
|
4
|
+
"description": "An extended Koa router that enables better validation",
|
|
5
|
+
"repository": "https://github.com/SkillsWave/nova-shared-libraries",
|
|
6
|
+
"version": "3.10.0",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"pre-release": "pnpm run create-index",
|
|
10
|
+
"create-index": "node ../../scripts/create-index.js",
|
|
11
|
+
"generate-docs": "documentation build src/** -f html -o docs",
|
|
12
|
+
"test": "mocha --parallel 'test/**/*.test.js'"
|
|
13
|
+
},
|
|
14
|
+
"author": "SkillsWave",
|
|
15
|
+
"license": "UNLICENSED",
|
|
16
|
+
"devDependencies": {},
|
|
17
|
+
"dependencies": {}
|
|
18
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import Router from '@koa/router';
|
|
2
|
+
|
|
3
|
+
export const CATCH_ALL_SCOPE = 'all';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* NovaRouter is a custom router class that extends a base Router class.
|
|
7
|
+
* It provides additional functionality for input validation and scope checking
|
|
8
|
+
* on various HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, and ALL).
|
|
9
|
+
*
|
|
10
|
+
* @class NovaRouter
|
|
11
|
+
*
|
|
12
|
+
* NOTE: Each of the routing functions follow the same signature.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} path - The path for which the middleware function is invoked.
|
|
15
|
+
* @param {Object} middlewareConfig - Configuration object for middleware.
|
|
16
|
+
* @param {function} [middlewareConfig.validator] - A function that validates the input.
|
|
17
|
+
* It should be an async function that takes the context as an argument and throws an error with a property 'errors'.
|
|
18
|
+
* 'errors' should be either an array of error messages or null.
|
|
19
|
+
* @param {string[]} [middlewareConfig.scopes] - An array of required scopes.
|
|
20
|
+
* @param {...function} middleware - The middleware functions to execute once the input is validated.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const middlewareConfig = {
|
|
24
|
+
* validator: RouterValidator.Schema({
|
|
25
|
+
* queryValidator: // params validator here
|
|
26
|
+
* paramsValidator: // params validator here
|
|
27
|
+
* bodyValidator: // body validator here
|
|
28
|
+
* }),
|
|
29
|
+
* scopes: ['scope1', 'scope2']
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
export class NovaRouter extends Router {
|
|
34
|
+
_createInputValidatorMiddleware(inputValidator) {
|
|
35
|
+
return async(context, next) => {
|
|
36
|
+
if (typeof inputValidator !== 'function') {
|
|
37
|
+
const message = `[${context.request.method}] ${context.request.url} does not have a valid input validator`;
|
|
38
|
+
context.log.error(message);
|
|
39
|
+
|
|
40
|
+
context.status = 500;
|
|
41
|
+
context.body = {
|
|
42
|
+
message,
|
|
43
|
+
};
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await inputValidator(context);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
context.status = 400;
|
|
51
|
+
context.body = {
|
|
52
|
+
errors: error.errors,
|
|
53
|
+
body: context.request.body,
|
|
54
|
+
query: context.request.query,
|
|
55
|
+
params: context.params,
|
|
56
|
+
};
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await next();
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generates a middleware that checks if the user has the required scopes.
|
|
65
|
+
*
|
|
66
|
+
* @param scopes
|
|
67
|
+
* @returns {(function(*, *): Promise<void>)|*}
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
_createScopesMiddleware(scopes) {
|
|
71
|
+
if (!scopes || scopes.length === 0) return async(context, next) => {
|
|
72
|
+
if (!context.state.scopes.includes(CATCH_ALL_SCOPE)) {
|
|
73
|
+
context.status = 403;
|
|
74
|
+
context.body = {
|
|
75
|
+
message: 'Missing required scopes',
|
|
76
|
+
requiredScopes: [CATCH_ALL_SCOPE],
|
|
77
|
+
userScopes: context.state.scopes,
|
|
78
|
+
};
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
await next();
|
|
82
|
+
};
|
|
83
|
+
return async(context, next) => {
|
|
84
|
+
// If the user has the catch-all scope, allow the request
|
|
85
|
+
if (!context.state.scopes.includes(CATCH_ALL_SCOPE)) {
|
|
86
|
+
const userScopes = context.state.scopes;
|
|
87
|
+
const missingScopes = scopes.filter(scope => !userScopes.includes(scope));
|
|
88
|
+
if (missingScopes.length > 0) {
|
|
89
|
+
context.status = 403;
|
|
90
|
+
context.body = {
|
|
91
|
+
message: 'Missing required scopes',
|
|
92
|
+
requiredScopes: scopes,
|
|
93
|
+
userScopes,
|
|
94
|
+
};
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
await next();
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Constructs multiple middleware functions based on the given middleware configuration.
|
|
104
|
+
* @param middlewareInput
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
_constructMiddleware(middlewareInput) {
|
|
108
|
+
const { scopes, validator } = middlewareInput;
|
|
109
|
+
|
|
110
|
+
const ret = [];
|
|
111
|
+
ret.push(this._createScopesMiddleware(scopes));
|
|
112
|
+
|
|
113
|
+
if (validator) {
|
|
114
|
+
const middleware = this._createInputValidatorMiddleware(validator);
|
|
115
|
+
if (middleware) ret.push(middleware);
|
|
116
|
+
}
|
|
117
|
+
return ret;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Use given middleware.
|
|
122
|
+
*
|
|
123
|
+
* Middleware run in the order they are defined by `.use()`. They are invoked
|
|
124
|
+
* sequentially, requests start at the first middleware and work their way
|
|
125
|
+
* "down" the middleware stack.
|
|
126
|
+
*
|
|
127
|
+
* router.use('/path', middlewareConfig, middleware1, middleware2);
|
|
128
|
+
*/
|
|
129
|
+
use(...args) {
|
|
130
|
+
let updatedArgs = [];
|
|
131
|
+
if (typeof args[0] === 'string') {
|
|
132
|
+
const middleware = this._constructMiddleware(args[1]);
|
|
133
|
+
updatedArgs = [
|
|
134
|
+
...args.slice(0, 1),
|
|
135
|
+
...middleware,
|
|
136
|
+
...args.slice(2),
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
super.use(...updatedArgs);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Registers a GET request handler with input validation.
|
|
145
|
+
*
|
|
146
|
+
* router.get('/path', middlewareConfig, middleware1, middleware2);
|
|
147
|
+
*/
|
|
148
|
+
get(path, middlewareConfig, ...middleware) {
|
|
149
|
+
super.get(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Registers a POST request handler with input validation.
|
|
154
|
+
*
|
|
155
|
+
* router.post('/path', middlewareConfig, middleware1, middleware2);
|
|
156
|
+
*/
|
|
157
|
+
post(path, middlewareConfig, ...middleware) {
|
|
158
|
+
super.post(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Registers a PUT request handler with input validation.
|
|
163
|
+
*
|
|
164
|
+
* router.put('/path', middlewareConfig, middleware1, middleware2);
|
|
165
|
+
*/
|
|
166
|
+
put(path, middlewareConfig, ...middleware) {
|
|
167
|
+
super.put(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Registers a PATCH request handler with input validation.
|
|
172
|
+
*
|
|
173
|
+
* router.patch('/path', middlewareConfig, middleware1, middleware2);
|
|
174
|
+
*/
|
|
175
|
+
patch(path, middlewareConfig, ...middleware) {
|
|
176
|
+
super.patch(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Registers a DELETE request handler with input validation.
|
|
181
|
+
*
|
|
182
|
+
* router.delete('/path', middlewareConfig, middleware1, middleware2);
|
|
183
|
+
*/
|
|
184
|
+
delete(path, middlewareConfig, ...middleware) {
|
|
185
|
+
super.delete(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Registers a HEAD request handler with input validation.
|
|
190
|
+
*
|
|
191
|
+
* router.head('/path', middlewareConfig, middleware1, middleware2);
|
|
192
|
+
*/
|
|
193
|
+
head(path, middlewareConfig, ...middleware) {
|
|
194
|
+
super.head(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Registers a OPTIONS request handler with input validation.
|
|
199
|
+
*
|
|
200
|
+
* router.options('/path', middlewareConfig, middleware1, middleware2);
|
|
201
|
+
*/
|
|
202
|
+
options(path, middlewareConfig, ...middleware) {
|
|
203
|
+
super.options(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Registers a DELETE request handler with input validation.
|
|
208
|
+
*
|
|
209
|
+
* router.del('/path', middlewareConfig, middleware1, middleware2);
|
|
210
|
+
*/
|
|
211
|
+
del(path, middlewareConfig, ...middleware) {
|
|
212
|
+
super.del(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Registers a request handler for all HTTP verb with input validation.
|
|
217
|
+
*
|
|
218
|
+
* router.all('/path', middlewareConfig, middleware1, middleware2);
|
|
219
|
+
*/
|
|
220
|
+
all(path, middlewareConfig, ...middleware) {
|
|
221
|
+
super.all(path, ...this._constructMiddleware(middlewareConfig), ...middleware);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export class ValidationError extends Error {
|
|
226
|
+
constructor(errors) {
|
|
227
|
+
super('There was a validation error');
|
|
228
|
+
this.name = 'ValidationError';
|
|
229
|
+
this.errors = errors;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export const RouterValidator = {
|
|
234
|
+
None: () => {
|
|
235
|
+
return () => {
|
|
236
|
+
return;
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Defines a schema for validating the request input.
|
|
242
|
+
*
|
|
243
|
+
* @typedef {Object} Validators
|
|
244
|
+
* @param {Yup.Schema} bodyValidator - Yup schema used to validate the body object.
|
|
245
|
+
* @param {Yup.Schema} queryValidator - Yup schema used to validate the query object.
|
|
246
|
+
* @param {Yup.Schema} paramsValidator - Yup schema used to validate the params object.
|
|
247
|
+
* @example
|
|
248
|
+
* RouterValidator.Schema({
|
|
249
|
+
* queryValidator: object({
|
|
250
|
+
* from: number().min(0).integer(),
|
|
251
|
+
* size: number().positive().integer().max(1000),
|
|
252
|
+
* filters: object({
|
|
253
|
+
* providers: array().of(string().uuid()),
|
|
254
|
+
* statuses: array().of(string()),
|
|
255
|
+
* activities: array().of(string().uuid()),
|
|
256
|
+
* employers: array().of(string().uuid()),
|
|
257
|
+
* }).json(),
|
|
258
|
+
* sort: object({
|
|
259
|
+
* colName: string(),
|
|
260
|
+
* order: string().oneOf(['asc', 'desc']),
|
|
261
|
+
* }).json(),
|
|
262
|
+
* includeAggregations: boolean(),
|
|
263
|
+
* }),
|
|
264
|
+
* })
|
|
265
|
+
*/
|
|
266
|
+
Schema: ({ bodyValidator, queryValidator, paramsValidator }) => {
|
|
267
|
+
return context => {
|
|
268
|
+
const promises = [];
|
|
269
|
+
|
|
270
|
+
if (queryValidator) {
|
|
271
|
+
if (!context.request.query) {
|
|
272
|
+
throw new ValidationError(['Missing query parameters']);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
promises.push(queryValidator.validate(context.request.query, { abortEarly: false }));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (bodyValidator) {
|
|
279
|
+
if (!context.request.body) {
|
|
280
|
+
throw new ValidationError(['Missing body']);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
promises.push(bodyValidator.validate(context.request.body, { abortEarly: false }));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (paramsValidator) {
|
|
287
|
+
promises.push(paramsValidator.validate(context.params, { abortEarly: false }));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return Promise.all(promises);
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "@skillswaveca/nova-utils",
|
|
4
4
|
"description": "A collection of random utils used in nova repos",
|
|
5
5
|
"repository": "https://github.com/SkillsWave/nova-shared-libraries",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.10.0",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"pre-release": "pnpm run create-index",
|