nodester 0.0.1
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/LICENSE +21 -0
- package/Readme.md +125 -0
- package/docs/App.md +13 -0
- package/docs/Queries.md +61 -0
- package/docs/Readme.md +2 -0
- package/docs/Routing.md +34 -0
- package/examples/goal/index.js +23 -0
- package/examples/rest/index.js +25 -0
- package/examples/rest/node_modules/.package-lock.json +40 -0
- package/examples/rest/package-lock.json +72 -0
- package/examples/rest/package.json +14 -0
- package/lib/application/MiddlewareStack.js +125 -0
- package/lib/application/http/request.js +462 -0
- package/lib/application/http/response.js +1107 -0
- package/lib/application/http/utils.js +254 -0
- package/lib/application/index.js +292 -0
- package/lib/constants/ConstantsEnum.js +13 -0
- package/lib/constants/ResponseFormats.js +7 -0
- package/lib/controllers/Controller.js +474 -0
- package/lib/controllers/JWTController.js +240 -0
- package/lib/controllers/ServiceController.js +109 -0
- package/lib/controllers/WebController.js +75 -0
- package/lib/facades/Facade.js +388 -0
- package/lib/facades/FacadeParams.js +11 -0
- package/lib/facades/ServiceFacade.js +17 -0
- package/lib/facades/jwt.facade.js +273 -0
- package/lib/factories/errors/CustomError.js +22 -0
- package/lib/factories/errors/index.js +9 -0
- package/lib/factories/responses/api.js +90 -0
- package/lib/factories/responses/html.js +55 -0
- package/lib/logger/console.js +24 -0
- package/lib/models/DisabledRefreshToken.js +68 -0
- package/lib/models/Extractor.js +320 -0
- package/lib/models/define.js +62 -0
- package/lib/models/mixins.js +369 -0
- package/lib/policies/Role.js +77 -0
- package/lib/policies/RoleExtracting.js +97 -0
- package/lib/preprocessors/BodyPreprocessor.js +61 -0
- package/lib/preprocessors/IncludesPreprocessor.js +55 -0
- package/lib/preprocessors/QueryPreprocessor.js +64 -0
- package/lib/routers/Default/index.js +143 -0
- package/lib/routers/Default/layer.js +50 -0
- package/lib/routers/Main/index.js +10 -0
- package/lib/routers/Roles/index.js +81 -0
- package/lib/services/includes.service.js +79 -0
- package/lib/services/jwt.service.js +147 -0
- package/lib/tools/sql.tool.js +82 -0
- package/lib/utils/dates.util.js +23 -0
- package/lib/utils/forms.util.js +22 -0
- package/lib/utils/json.util.js +49 -0
- package/lib/utils/mappers/Routes/index.js +100 -0
- package/lib/utils/mappers/Routes/utils.js +20 -0
- package/lib/utils/modelAssociations.util.js +44 -0
- package/lib/utils/objects.util.js +69 -0
- package/lib/utils/params.util.js +19 -0
- package/lib/utils/path.util.js +26 -0
- package/lib/utils/queries.util.js +240 -0
- package/lib/utils/sanitizations.util.js +111 -0
- package/lib/utils/sql.util.js +78 -0
- package/lib/utils/strings.util.js +43 -0
- package/lib/utils/types.util.js +26 -0
- package/package.json +63 -0
- package/tests/index.test.js +35 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Constants:
|
|
2
|
+
const VISITOR = 'visitor';
|
|
3
|
+
const DefaultAvailableParamsForRoles = { [VISITOR]: [ 'skip', 'limit', 'order' ] };
|
|
4
|
+
const DefaultStaticParamsForRoles = { [VISITOR]: { limit: 50 } };
|
|
5
|
+
|
|
6
|
+
// Custom error.
|
|
7
|
+
const { Err } = require('nodester/factories/errors');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module.exports = class QueryPreprocessor {
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
availableParamsForRoles,
|
|
14
|
+
staticParamsForRoles,
|
|
15
|
+
customProcessFunction
|
|
16
|
+
) {
|
|
17
|
+
this.availableParamsForRoles = availableParamsForRoles ?? DefaultAvailableParamsForRoles;
|
|
18
|
+
this.staticParamsForRoles = staticParamsForRoles ?? DefaultStaticParamsForRoles;
|
|
19
|
+
|
|
20
|
+
this.customProcessFunction = customProcessFunction ? customProcessFunction : ()=>{};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async extract(
|
|
24
|
+
req,
|
|
25
|
+
role
|
|
26
|
+
) {
|
|
27
|
+
try {
|
|
28
|
+
const requestQuery = req.query;
|
|
29
|
+
|
|
30
|
+
if (!requestQuery || typeof requestQuery !== 'object') {
|
|
31
|
+
const err = new Err();
|
|
32
|
+
err.name = 'ValidationError';
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Get role or set "visitor"
|
|
37
|
+
const _role = typeof role === 'string' && role.length > 1 ? role : [VISITOR];
|
|
38
|
+
|
|
39
|
+
const resultQuery = {};
|
|
40
|
+
|
|
41
|
+
const params = this.availableParamsForRoles[_role] ?? [];
|
|
42
|
+
const staticValues = this.staticParamsForRoles[_role] ?? {};
|
|
43
|
+
|
|
44
|
+
params.forEach((param) => {
|
|
45
|
+
// If such param is set in query:
|
|
46
|
+
if (!!requestQuery[param]) {
|
|
47
|
+
resultQuery[param] = staticValues[param] ?? requestQuery[param];
|
|
48
|
+
}
|
|
49
|
+
// If such param is not set, but we have a "static" for it:
|
|
50
|
+
else if (!requestQuery[param] && !!staticValues[param]) {
|
|
51
|
+
resultQuery[param] = staticValues[param];
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Make further preprocessing using customly defined function.
|
|
56
|
+
await this.customProcessFunction.call(this, req, role, resultQuery);
|
|
57
|
+
|
|
58
|
+
return Promise.resolve(resultQuery);
|
|
59
|
+
}
|
|
60
|
+
catch(error) {
|
|
61
|
+
return Promise.reject(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const Layer = require('./layer');
|
|
2
|
+
|
|
3
|
+
// Utils:
|
|
4
|
+
const { getType } = require('../../utils/types.util');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module.exports = function NodesterDefaultRouter(app=null) {
|
|
8
|
+
this.markers = {};
|
|
9
|
+
this.layer = new Layer();
|
|
10
|
+
|
|
11
|
+
if (!!app) {
|
|
12
|
+
this.app = app
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Expose methods:
|
|
16
|
+
// Markers:
|
|
17
|
+
this.addMarker = _addMarker;
|
|
18
|
+
this.getMarker = _getMarker;
|
|
19
|
+
|
|
20
|
+
// Routing:
|
|
21
|
+
this.handle = _handle;
|
|
22
|
+
this.only = _only;
|
|
23
|
+
this.route = _route;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Adds marker to the stack.
|
|
28
|
+
* Returns itself for chaining.
|
|
29
|
+
* @return {NodesterDefaultRouter}
|
|
30
|
+
*
|
|
31
|
+
* @alias addMarker
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
function _addMarker(nameOrSymbol='', fn=()=>{}) {
|
|
35
|
+
if (typeof fn !== 'function') {
|
|
36
|
+
const err = new TypeError(`Router.addMarker() requires a middleware function but got a ${ getType(fn) }`);
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const marker = this.getMarker(nameOrSymbol);
|
|
41
|
+
if (marker.marker.index > -1) {
|
|
42
|
+
const err = new TypeError(`Marker with key ${ nameOrSymbol } is already set.`);
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.markers[nameOrSymbol] = fn;
|
|
47
|
+
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Tries to find marker's data by provided key.
|
|
54
|
+
* @return {Object}
|
|
55
|
+
*
|
|
56
|
+
* @alias getMarker
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
function _getMarker(nameOrSymbol='') {
|
|
60
|
+
const result = {
|
|
61
|
+
marker: {
|
|
62
|
+
key: nameOrSymbol,
|
|
63
|
+
index: -1,
|
|
64
|
+
},
|
|
65
|
+
middleware: undefined
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const keys = Object.keys(this.markers);
|
|
69
|
+
|
|
70
|
+
const index = keys.indexOf(nameOrSymbol);
|
|
71
|
+
if (keys.indexOf(nameOrSymbol) === -1) {
|
|
72
|
+
const err = new Error('NotFound');
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Marker found:
|
|
77
|
+
result.marker.index = index;
|
|
78
|
+
result.middleware = this.markers[nameOrSymbol];
|
|
79
|
+
}
|
|
80
|
+
catch(error) {
|
|
81
|
+
result.marker.index = -1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Start routes pipeline processing.
|
|
89
|
+
*
|
|
90
|
+
* If no callback is provided, then default error handlers will respond
|
|
91
|
+
* in the event of an error bubbling through the stack.
|
|
92
|
+
*
|
|
93
|
+
* @alias handle
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
function _handle(req, res, callback) {
|
|
97
|
+
const method = req.method;
|
|
98
|
+
const requestPath = req.url;
|
|
99
|
+
|
|
100
|
+
console.log(method, requestPath);
|
|
101
|
+
|
|
102
|
+
let markerName = null;
|
|
103
|
+
|
|
104
|
+
// Check if this request satisfies any markers:
|
|
105
|
+
const markers = Object.entries(this.markers);
|
|
106
|
+
console.log({ markers });
|
|
107
|
+
|
|
108
|
+
for (const [marker, fn] of markers) {
|
|
109
|
+
const result = fn(req, res, callback);
|
|
110
|
+
console.log(result);
|
|
111
|
+
|
|
112
|
+
if (result === true) {
|
|
113
|
+
markerName = marker;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log({ markerName });
|
|
119
|
+
|
|
120
|
+
return res.send(markerName ?? 'Hi!');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
*
|
|
126
|
+
* @alias only
|
|
127
|
+
* @public
|
|
128
|
+
*/
|
|
129
|
+
function _only(condition) {
|
|
130
|
+
this.layer.push('condition', condition);
|
|
131
|
+
// Return layer for chaining.
|
|
132
|
+
return this.layer;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
*
|
|
138
|
+
* @alias route
|
|
139
|
+
* @public
|
|
140
|
+
*/
|
|
141
|
+
function _route() {
|
|
142
|
+
|
|
143
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = function NodesterDefaultRouterLayer() {
|
|
3
|
+
this.conditions = [];
|
|
4
|
+
this.routesList = {};
|
|
5
|
+
|
|
6
|
+
this.push = _push;
|
|
7
|
+
this.route = _route;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @alias push
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
function _push(key='condition') {
|
|
17
|
+
let args = [...arguments].shift();
|
|
18
|
+
|
|
19
|
+
switch(key) {
|
|
20
|
+
case 'condition':
|
|
21
|
+
// this.conditions[]
|
|
22
|
+
break;
|
|
23
|
+
case 'route':
|
|
24
|
+
const route = args.shift();
|
|
25
|
+
this.routesList[route] = args;
|
|
26
|
+
break;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @alias route
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
function _route(path='/') {
|
|
40
|
+
const middlewares = [...arguments].shift();
|
|
41
|
+
|
|
42
|
+
console.log({ path });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @alias push
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
// Utils:
|
|
3
|
+
const cwd = process.cwd();
|
|
4
|
+
const Path = require('path');
|
|
5
|
+
// Custom error.
|
|
6
|
+
const { Err } = require('nodester/factories/errors');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RolesRouter extends express.Router {
|
|
10
|
+
constructor(
|
|
11
|
+
rolesToRoutesMap={},
|
|
12
|
+
pathToControllers=null
|
|
13
|
+
) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
// rolesToRoutesMap is a map of role -> route -> controller:
|
|
17
|
+
if (!rolesToRoutesMap) {
|
|
18
|
+
const err = new Err('"rolesToRoutesMap" argument is required.');
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!pathToControllers) {
|
|
23
|
+
const err = new Err('"pathToControllers" argument is required.');
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Extract all available roles.
|
|
28
|
+
const roles = Object.keys(rolesToRoutesMap);
|
|
29
|
+
|
|
30
|
+
// Flip map (Make it route -> role -> controller):
|
|
31
|
+
const routesToRolesMap = {};
|
|
32
|
+
for (const role of roles) {
|
|
33
|
+
const roleRoutes = rolesToRoutesMap[role]
|
|
34
|
+
|
|
35
|
+
for (const route in roleRoutes) {
|
|
36
|
+
// If this route is set:
|
|
37
|
+
if (!!routesToRolesMap[route]) {
|
|
38
|
+
// Add role to set.
|
|
39
|
+
routesToRolesMap[route][role] = roleRoutes[route];
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Create set for this role.
|
|
43
|
+
routesToRolesMap[route] = { [role]: roleRoutes[route] };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// At this point we have a map of route -> role -> controller.
|
|
49
|
+
// Let's now set these routes to this Router:
|
|
50
|
+
for (const route in routesToRolesMap) {
|
|
51
|
+
const rolesAndControllersMap = routesToRolesMap[route];
|
|
52
|
+
|
|
53
|
+
// Split route by space, as it has structure [<Rest method> <Route>]
|
|
54
|
+
const routeParts = route.split(/\s+/);
|
|
55
|
+
const routeMethod = routeParts[0].toLocaleLowerCase();
|
|
56
|
+
const routePath = routeParts[1].replace(/\s\s+/g, ' ');
|
|
57
|
+
|
|
58
|
+
// Set up this route:
|
|
59
|
+
this.route(routePath)[routeMethod]((req, res, next) => {
|
|
60
|
+
// Extract role:
|
|
61
|
+
const role = req?.user?.role ?? 'visitor';
|
|
62
|
+
|
|
63
|
+
// If no handler for this role-route, skip:
|
|
64
|
+
if (!rolesAndControllersMap[role]) {
|
|
65
|
+
return next();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const controllerAndMethod = rolesAndControllersMap[role].split('.');
|
|
69
|
+
const controllerName = controllerAndMethod[0];
|
|
70
|
+
const controllerMethod = controllerAndMethod[1];
|
|
71
|
+
|
|
72
|
+
// Get controller from path.
|
|
73
|
+
const controller = require(Path.join(cwd, pathToControllers, controllerName));
|
|
74
|
+
|
|
75
|
+
return controller[controllerMethod](req, res, next);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = RolesRouter;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const {
|
|
2
|
+
splitByComma,
|
|
3
|
+
splitByDot
|
|
4
|
+
} = require('nodester/utils/strings.util');
|
|
5
|
+
// Query util.
|
|
6
|
+
const {
|
|
7
|
+
hasSubIncludesQuery,
|
|
8
|
+
cutSubIncludesQuery,
|
|
9
|
+
parseSubIncludesQuery
|
|
10
|
+
} = require('nodester/utils/queries.util');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
parseIncludesQuery: _parseIncludesQuery
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function _parseIncludesQuery(
|
|
18
|
+
modelDefinition,
|
|
19
|
+
includesQueryString
|
|
20
|
+
) {
|
|
21
|
+
const associationAndSubs = splitByComma(includesQueryString);
|
|
22
|
+
const allowedAssociations = Object.keys(modelDefinition.associations);
|
|
23
|
+
|
|
24
|
+
associationAndSubs.forEach((a) => {
|
|
25
|
+
let association = a.split('.')[0];
|
|
26
|
+
|
|
27
|
+
if (hasSubIncludesQuery(association)) {
|
|
28
|
+
const [ nestedQuery, _association ] = cutSubIncludesQuery(association);
|
|
29
|
+
association = _association;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (allowedAssociations.indexOf(association) === -1) {
|
|
33
|
+
const err = new Error(`Association with name "${ association }" doesn't exist on this model.`);
|
|
34
|
+
err.name = 'NotFound';
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const result = associationAndSubs.map(associationAndSubsString => _parseSubIncludes(associationAndSubsString));
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _parseSubIncludes(associationAndSubsString) {
|
|
45
|
+
const associationAndSubs = splitByDot(associationAndSubsString);
|
|
46
|
+
|
|
47
|
+
// If association has SubIncludes:
|
|
48
|
+
if (associationAndSubs?.length > 0) {
|
|
49
|
+
const clearAssociation = associationAndSubs[0];
|
|
50
|
+
const subs = associationAndSubs.splice(1, associationAndSubs.length - 1);
|
|
51
|
+
|
|
52
|
+
if (subs.length > 0) {
|
|
53
|
+
const result = {
|
|
54
|
+
association: clearAssociation,
|
|
55
|
+
include: [ _parseSubIncludes(subs.join('.')) ]
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
_parseSubIncludesQueryIfPresent(result);
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// By default just return association.
|
|
65
|
+
const result = { association: associationAndSubsString };
|
|
66
|
+
_parseSubIncludesQueryIfPresent(result);
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function _parseSubIncludesQueryIfPresent(resultQuery) {
|
|
71
|
+
if (hasSubIncludesQuery(resultQuery.association)) {
|
|
72
|
+
const [ nestedQuery, clearAssociation ] = cutSubIncludesQuery(resultQuery.association);
|
|
73
|
+
|
|
74
|
+
resultQuery.association = clearAssociation;
|
|
75
|
+
parseSubIncludesQuery(clearAssociation, nestedQuery, resultQuery);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return resultQuery;
|
|
79
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Format of token: "Authorization: Bearer [token]".
|
|
2
|
+
const ACCESS_TOKEN_NAME = 'Authorization';
|
|
3
|
+
const REFRESH_TOKEN_NAME = 'x-refresh-token';
|
|
4
|
+
// JWT module.
|
|
5
|
+
const jwt = require('jsonwebtoken');
|
|
6
|
+
// Utils.
|
|
7
|
+
const { addSeconds } = require('nodester/utils/dates.util');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module.exports = class JWTService {
|
|
11
|
+
constructor(
|
|
12
|
+
accessTokenConfigs,
|
|
13
|
+
refreshTokenConfigs
|
|
14
|
+
) {
|
|
15
|
+
if (!accessTokenConfigs || !refreshTokenConfigs){
|
|
16
|
+
throw new Error('"accessTokenConfigs" and "refreshTokenConfigs" are required arguments.');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.accessTokenConfigs = { ...accessTokenConfigs };
|
|
20
|
+
this.refreshTokenConfigs = { ...refreshTokenConfigs };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
extractTokenFromRequest(request) {
|
|
24
|
+
let token;
|
|
25
|
+
|
|
26
|
+
if (request.header(ACCESS_TOKEN_NAME)) {
|
|
27
|
+
token = this._parseAccessToken(
|
|
28
|
+
request.header(ACCESS_TOKEN_NAME)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
else if (request.cookies[ACCESS_TOKEN_NAME]) {
|
|
32
|
+
token = this._parseAccessToken(
|
|
33
|
+
request.cookies[ACCESS_TOKEN_NAME]
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
// Check token in query:
|
|
37
|
+
else if (!!request.query.token) {
|
|
38
|
+
token = request.query.token;
|
|
39
|
+
delete request.body.token;
|
|
40
|
+
}
|
|
41
|
+
// Check token in body:
|
|
42
|
+
else if (!!request.body.token) {
|
|
43
|
+
token = request.body.token;
|
|
44
|
+
delete request.query.token;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const err = new Error(`No ${ACCESS_TOKEN_NAME} was found`);
|
|
48
|
+
err.name = 'NoToken';
|
|
49
|
+
err.details = { message:err.message };
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return token;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
extractRefreshTokenFromRequest(request) {
|
|
57
|
+
let token;
|
|
58
|
+
|
|
59
|
+
if (request.header(REFRESH_TOKEN_NAME)) {
|
|
60
|
+
token = request.header(REFRESH_TOKEN_NAME);
|
|
61
|
+
}
|
|
62
|
+
else if (request.cookies[REFRESH_TOKEN_NAME]) {
|
|
63
|
+
token = request.cookies[REFRESH_TOKEN_NAME];
|
|
64
|
+
}
|
|
65
|
+
// Check token in query:
|
|
66
|
+
else if (!!request.query.token) {
|
|
67
|
+
token = request.query.token;
|
|
68
|
+
delete request.body.token;
|
|
69
|
+
}
|
|
70
|
+
// Check token in body:
|
|
71
|
+
else if (!!request.body.token) {
|
|
72
|
+
token = request.body.token;
|
|
73
|
+
delete request.query.token;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const err = new Error(`No ${REFRESH_TOKEN_NAME} was found`);
|
|
77
|
+
err.name = 'NoToken';
|
|
78
|
+
err.details = { message:err.message };
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return token;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
issueAccessToken(payload) {
|
|
86
|
+
const { secret, expiresIn } = this.accessTokenConfigs;
|
|
87
|
+
return this._issueToken({ payload, secret, expiresIn });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
issueRefreshToken(payload) {
|
|
91
|
+
const { secret, expiresIn } = this.refreshTokenConfigs;
|
|
92
|
+
return this._issueToken({ payload, secret, expiresIn });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
verifyAccessToken(token) {
|
|
96
|
+
const { secret } = this.accessTokenConfigs;
|
|
97
|
+
return this._verifyToken({ token, secret });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
verifyRefreshToken(token) {
|
|
101
|
+
const { secret } = this.refreshTokenConfigs;
|
|
102
|
+
return this._verifyToken({ token, secret });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async _issueToken({ payload, secret, expiresIn }) {
|
|
106
|
+
try {
|
|
107
|
+
const token = jwt.sign(payload, secret, { expiresIn });
|
|
108
|
+
const expirationDateValue = (addSeconds(new Date(), expiresIn/1000)).valueOf();
|
|
109
|
+
|
|
110
|
+
const fullToken = { token, expiresIn, expirationDateValue };
|
|
111
|
+
return Promise.resolve(fullToken);
|
|
112
|
+
}
|
|
113
|
+
catch(error) {
|
|
114
|
+
return Promise.reject(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async _verifyToken({ token, secret }) {
|
|
119
|
+
try {
|
|
120
|
+
const parsedToken = await jwt.verify(token, secret, {});
|
|
121
|
+
return Promise.resolve(parsedToken);
|
|
122
|
+
}
|
|
123
|
+
catch(error) {
|
|
124
|
+
const err = new Error('Invalid signature.');
|
|
125
|
+
err.name = 'ValidationError';
|
|
126
|
+
return Promise.reject(err);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_parseAccessToken(token) {
|
|
131
|
+
let parsed = '';
|
|
132
|
+
|
|
133
|
+
const parts = token.split(' ');
|
|
134
|
+
|
|
135
|
+
if (parts.length === 2 && /^Bearer$/.test(parts[0])) {
|
|
136
|
+
parsed = parts[1];
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const err = new Error(`Format for ${ACCESS_TOKEN_NAME}: Bearer [token]`);
|
|
140
|
+
err.name = 'InvalidFormat';
|
|
141
|
+
err.details = { message: err.message };
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const { QueryTypes } = require('sequelize');
|
|
2
|
+
// Utils:
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { promisify } = require('util');
|
|
5
|
+
const readFile = promisify(fs.readFile);
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
parseSQLFileContents: _parseSQLFileContents,
|
|
10
|
+
|
|
11
|
+
rawSelect: _rawSelect,
|
|
12
|
+
rawUpdate: _rawUpdate,
|
|
13
|
+
rawInsert: _rawInsert
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function _parseSQLFileContents(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
const fileContent = await readFile(filePath, 'utf8');
|
|
19
|
+
const sqlCommands = fileContent.toString()
|
|
20
|
+
.split('\n')
|
|
21
|
+
.filter(command => command !== '');
|
|
22
|
+
|
|
23
|
+
const output = { commands: sqlCommands };
|
|
24
|
+
return Promise.resolve(output);
|
|
25
|
+
}
|
|
26
|
+
catch(error) {
|
|
27
|
+
return Promise.reject(error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _rawSelect(db, tableName) {
|
|
32
|
+
const query = `select * from ${ tableName }`;
|
|
33
|
+
return db.query(query, { type: QueryTypes.SELECT });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function _rawUpdate(db, tableName, where, data) {
|
|
37
|
+
const dataKeys = Object.keys(data);
|
|
38
|
+
|
|
39
|
+
let sqlAttributes = '';
|
|
40
|
+
|
|
41
|
+
for (let i=0; i < dataKeys.length; i++) {
|
|
42
|
+
const key = dataKeys[i];
|
|
43
|
+
const val = data[key];
|
|
44
|
+
|
|
45
|
+
sqlAttributes += `${ key }='${ val }'`;
|
|
46
|
+
|
|
47
|
+
if (i < dataKeys.length-1) {
|
|
48
|
+
sqlAttributes += ', ';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const sql = `UPDATE \`${ tableName }\` SET ${ sqlAttributes } where ${ where };`;
|
|
53
|
+
|
|
54
|
+
return db.query(sql, {
|
|
55
|
+
type: QueryTypes.INSERT
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _rawInsert(db, tableName, data) {
|
|
60
|
+
const dataKeys = Object.keys(data);
|
|
61
|
+
const dataValues = Object.values(data);
|
|
62
|
+
|
|
63
|
+
let values = '';
|
|
64
|
+
for (let i=0; i < dataValues.length-1; i++) {
|
|
65
|
+
const val = dataValues[i];
|
|
66
|
+
|
|
67
|
+
if (val === undefined || val === null)
|
|
68
|
+
values += 'null, ';
|
|
69
|
+
else if (typeof val === 'object')
|
|
70
|
+
values += `'${ JSON.stringify(val) }', `;
|
|
71
|
+
// values += `'{}', `;
|
|
72
|
+
else
|
|
73
|
+
values += `'${val}', `;
|
|
74
|
+
}
|
|
75
|
+
values += `'${ dataValues[dataValues.length-1] }'`;
|
|
76
|
+
|
|
77
|
+
const sql = `INSERT INTO \`${ tableName }\` (${ dataKeys.join(', ') }) VALUES (${ values });`;
|
|
78
|
+
|
|
79
|
+
return db.query(sql, {
|
|
80
|
+
type: QueryTypes.INSERT
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = {
|
|
3
|
+
addSeconds: _addSeconds,
|
|
4
|
+
addDays: _addDays
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function _addSeconds(
|
|
8
|
+
date=null,
|
|
9
|
+
seconds=0
|
|
10
|
+
) {
|
|
11
|
+
const newDate = new Date(date.valueOf());
|
|
12
|
+
newDate.setSeconds(newDate.getSeconds() + seconds);
|
|
13
|
+
return newDate;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function _addDays(
|
|
17
|
+
date=null,
|
|
18
|
+
days=0
|
|
19
|
+
) {
|
|
20
|
+
const newDate = new Date(date.valueOf());
|
|
21
|
+
newDate.setDate(newDate.getDate() + days);
|
|
22
|
+
return newDate;
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Utils.
|
|
2
|
+
const formidable = require('formidable');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
parseRequestForm: _parseRequestForm,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function _parseRequestForm(req) {
|
|
10
|
+
return new Promise(function (resolve, reject) {
|
|
11
|
+
const form = new formidable.IncomingForm();
|
|
12
|
+
|
|
13
|
+
form.parse(req, function (err, fields, files) {
|
|
14
|
+
if (err) {
|
|
15
|
+
return reject(err);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = { fields, files };
|
|
19
|
+
return resolve(result);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|