not-node 5.1.44 → 6.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.
Files changed (53) hide show
  1. package/.eslintrc.json +32 -38
  2. package/index.js +6 -0
  3. package/package.json +12 -11
  4. package/src/app.js +2 -2
  5. package/src/auth/index.js +0 -2
  6. package/src/auth/routes.js +25 -61
  7. package/src/auth/rules.js +8 -7
  8. package/src/common.js +19 -0
  9. package/src/identity/exceptions.js +17 -0
  10. package/src/identity/identity.js +61 -0
  11. package/src/identity/index.js +35 -0
  12. package/src/identity/providers/session.js +137 -0
  13. package/src/identity/providers/token.js +255 -0
  14. package/src/manifest/result.filter.js +268 -0
  15. package/src/manifest/route.js +6 -36
  16. package/static2.js +24 -0
  17. package/test/auth/identity.js +0 -0
  18. package/test/auth/routes.js +1 -1
  19. package/test/auth.js +427 -229
  20. package/test/env.js +20 -20
  21. package/test/fields.js +3 -2
  22. package/test/identity/identity.js +1 -0
  23. package/test/identity/index.js +12 -0
  24. package/test/identity/providers/session.js +227 -0
  25. package/test/identity/providers/token.js +244 -0
  26. package/test/identity.js +5 -0
  27. package/test/init/app.js +359 -365
  28. package/test/init/bodyparser.js +37 -39
  29. package/test/init/compression.js +29 -31
  30. package/test/init/cors.js +38 -39
  31. package/test/init/db.js +60 -64
  32. package/test/init/env.js +109 -114
  33. package/test/init/express.js +50 -47
  34. package/test/init/fileupload.js +30 -32
  35. package/test/init/http.js +258 -240
  36. package/test/init/informer.js +20 -24
  37. package/test/init/methodoverride.js +29 -31
  38. package/test/init/middleware.js +56 -58
  39. package/test/init/modules.js +19 -19
  40. package/test/init/monitoring.js +22 -22
  41. package/test/init/routes.js +185 -171
  42. package/test/init/security.js +77 -103
  43. package/test/init/sessions/mongoose.js +56 -57
  44. package/test/init/sessions/redis.js +59 -61
  45. package/test/init/sessions.js +84 -79
  46. package/test/init/static.js +108 -113
  47. package/test/init/template.js +46 -41
  48. package/test/notInit.js +217 -217
  49. package/test/notManifest.js +232 -191
  50. package/test/notRoute.js +1022 -799
  51. package/test/result.filter.js +422 -0
  52. package/src/auth/session.js +0 -151
  53. package/test/auth/session.js +0 -266
@@ -0,0 +1,255 @@
1
+ const { error, log } = require("not-log")(module, "Identity//Token");
2
+ const { notRequestError } = require("not-error");
3
+ const CONST = require("../../auth/const");
4
+ const ROLES = require("../../auth/roles");
5
+ const { objHas } = require("../../common");
6
+ const phrase = require("not-locale").modulePhrase("not-node");
7
+
8
+ const JWT = require("jsonwebtoken");
9
+
10
+ module.exports = class IdentityProviderToken {
11
+ #tokenContent = null;
12
+ #token = null;
13
+
14
+ static #options = {};
15
+
16
+ static setOptions(options = {}) {
17
+ this.#options = options;
18
+ }
19
+
20
+ static #getOptions() {
21
+ return this.#options;
22
+ }
23
+
24
+ constructor(req) {
25
+ this.req = req;
26
+ return this;
27
+ }
28
+
29
+ static getTokenFromRequest(req) {
30
+ const auth = req.get("Authorization");
31
+ if (auth && auth.length) {
32
+ const [, token] = auth.split(" ");
33
+ return token;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ #extractToken(req) {
39
+ const token = this.getTokenFromRequest(req);
40
+ if (token) {
41
+ this.#token = token;
42
+ }
43
+ }
44
+
45
+ #updateToken() {
46
+ this.#token = this.#encodeTokenContent();
47
+ return this.#token;
48
+ }
49
+
50
+ #decodeTokenContent() {
51
+ try {
52
+ if (this.#token) {
53
+ const secret = this.#getOptions().secret;
54
+ JWT.verify(this.#token, secret);
55
+ return JWT.decode(this.#token, secret);
56
+ }
57
+ return null;
58
+ } catch (e) {
59
+ error(e.message);
60
+ return null;
61
+ }
62
+ }
63
+
64
+ #encodeTokenContent() {
65
+ try {
66
+ if (this.#token) {
67
+ const secret = this.#getOptions().secret;
68
+ return JWT.sign(this.#tokenContent, secret);
69
+ }
70
+ return null;
71
+ } catch (e) {
72
+ error(e.message);
73
+ return null;
74
+ }
75
+ }
76
+
77
+ #extractTokenContent(req) {
78
+ this.#tokenContent = this.#decodeTokenContent(req);
79
+ }
80
+
81
+ get tokenContent() {
82
+ return this.#tokenContent;
83
+ }
84
+
85
+ get token() {
86
+ return this.#token;
87
+ }
88
+
89
+ static #validateSecretForToken({ secret, context }) {
90
+ if (
91
+ !secret ||
92
+ typeof secret === "undefined" ||
93
+ secret === null ||
94
+ secret === ""
95
+ ) {
96
+ throw new notRequestError(phrase("user_token_secret_not_valid"), {
97
+ ...context,
98
+ code: 500,
99
+ });
100
+ }
101
+ }
102
+
103
+ static #validateTTLForToken(tokenTTL) {
104
+ if (tokenTTL <= 0 || isNaN(tokenTTL)) {
105
+ log(phrase("user_token_ttl_not_set"));
106
+ tokenTTL = CONST.TOKEN_TTL;
107
+ }
108
+ return tokenTTL;
109
+ }
110
+
111
+ static #composeUserTokenPayload({ user, additionalFields = [] }) {
112
+ const addons = {};
113
+ if (Array.isArray(additionalFields)) {
114
+ additionalFields.forEach((fieldName) => {
115
+ if (objHas(user, fieldName)) {
116
+ addons[fieldName] = user[fieldName];
117
+ }
118
+ });
119
+ }
120
+ return {
121
+ _id: user._id,
122
+ role: user.role,
123
+ active: user.active,
124
+ username: user.username,
125
+ ...addons,
126
+ };
127
+ }
128
+
129
+ static #composeGuestTokenPayload() {
130
+ return {
131
+ _id: false,
132
+ role: CONST.DEFAULT_USER_ROLE_FOR_GUEST,
133
+ active: true,
134
+ username: phrase("user_role_guest"),
135
+ };
136
+ }
137
+
138
+ static createToken({
139
+ ip,
140
+ user,
141
+ additionalFields = ["emailConfirmed", "telephoneConfirmed"],
142
+ }) {
143
+ const context = { ip };
144
+ const secret = this.#getOptions().secret;
145
+ this.#validateSecretForToken({ secret, context });
146
+ let payload = {};
147
+ if (user) {
148
+ payload = this.#composeUserTokenPayload({ user, additionalFields });
149
+ } else {
150
+ payload = this.#composeGuestTokenPayload();
151
+ }
152
+ this.#setTokenExpiration(payload);
153
+ return JWT.sign(payload, secret);
154
+ }
155
+
156
+ static #setTokenExpiration(payload) {
157
+ const tokenTTL = this.#validateTTLForToken(this.#getOptions().ttl);
158
+ if (!objHas(payload, "exp")) {
159
+ payload.exp = Date.now() / 1000 + tokenTTL;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Checks if user is authenticated
165
+ * @return {boolean} true - authenticated, false - guest
166
+ **/
167
+ isUser() {
168
+ return !!this.tokenContent?._id;
169
+ }
170
+
171
+ /**
172
+ * Returns user role from token object
173
+ * @return user role
174
+ **/
175
+ getRole() {
176
+ return this.tokenContent?.role ?? CONST.DEFAULT_USER_ROLE_FOR_GUEST;
177
+ }
178
+
179
+ /**
180
+ * Set user role for active session
181
+ * @param {Array<string>} role array of roles
182
+ **/
183
+ setRole(role) {
184
+ if (this.tokenContent) {
185
+ this.#tokenContent.role = [...role];
186
+ this.#updateToken();
187
+ }
188
+ return this;
189
+ }
190
+
191
+ /**
192
+ * Set user id for active session
193
+ * @param {string} _id user id
194
+ **/
195
+ setUserId(_id) {
196
+ if (this.tokenContent) {
197
+ this.#tokenContent._id = _id;
198
+ this.#updateToken();
199
+ }
200
+ return this;
201
+ }
202
+
203
+ isRoot() {
204
+ return (
205
+ this.isUser() &&
206
+ ROLES.compareRoles(
207
+ this.getRole(),
208
+ CONST.DEFAULT_USER_ROLE_FOR_ADMIN
209
+ )
210
+ );
211
+ }
212
+
213
+ /**
214
+ * Get user id for active session
215
+ **/
216
+ getUserId() {
217
+ return this.#tokenContent?._id;
218
+ }
219
+
220
+ /**
221
+ * returns token
222
+ **/
223
+ getSessionId() {
224
+ return this.token;
225
+ }
226
+
227
+ /**
228
+ * Set auth data in session, user id and role
229
+ * @param {string} id user id
230
+ * @param {string} role user role
231
+ **/
232
+ setAuth(id, role) {
233
+ this.setUserId(id);
234
+ this.setRole(role);
235
+ }
236
+
237
+ /**
238
+ * Set auth data in token to Guest
239
+ **/
240
+ setGuest() {
241
+ this.setAuth(null, [CONST.DEFAULT_USER_ROLE_FOR_GUEST]);
242
+ }
243
+
244
+ /**
245
+ * Reset session
246
+ * @param {object} req Express Request
247
+ **/
248
+ cleanse() {
249
+ this.setGuest();
250
+ }
251
+
252
+ static test(req) {
253
+ return !!this.getTokenFromRequest(req);
254
+ }
255
+ };
@@ -0,0 +1,268 @@
1
+ const notPath = require("not-path");
2
+ const { objHas, copyObj } = require("../common");
3
+
4
+ const PROP_NAME_RETURN_ROOT = "returnRoot"; //path to object to filter
5
+ const PROP_NAME_RETURN_RULE = "return"; //filtering rule
6
+ const PROP_NAME_RETURN_STRICT = "returnStrict"; //if filtering should be strict
7
+
8
+ /**
9
+ * @const {boolean} DEFAULT_STRICT_MODE
10
+ * active only in complex rules presented as objects
11
+ * defines if filtering should exclude any not mentioned property and sub property in map object
12
+ * Example:
13
+ * target structure =
14
+ * {
15
+ * id,
16
+ * user:{
17
+ * id,
18
+ * username,
19
+ * aliases: [
20
+ * {id, title, createdAt},
21
+ * {id, title, createdAt, banned}
22
+ * ],
23
+ * createdAt
24
+ * },
25
+ * clean
26
+ * }
27
+ * rule object =
28
+ * {
29
+ * id,
30
+ * 'user.aliases': ['id', 'title', 'banned']
31
+ * }
32
+ * in strict==true
33
+ * filtered target structure =
34
+ * {
35
+ * id,
36
+ * user:{
37
+ * aliases: [
38
+ * {id, title},
39
+ * {id, title, banned}
40
+ * ]
41
+ * }
42
+ * }
43
+ * in strict==false
44
+ * filtered target structure = {
45
+ * id,
46
+ * user:{
47
+ * id,
48
+ * username,
49
+ * aliases: [
50
+ * {id, title},
51
+ * {id, title, banned}
52
+ * ],
53
+ * createdAt
54
+ * }
55
+ * }
56
+ */
57
+ const DEFAULT_STRICT_MODE = false;
58
+
59
+ module.exports = class notManifestRouteResultFilter {
60
+ /**
61
+ * Removes fields from result object acctoding to actionData.return array
62
+ * if presented
63
+ * @param {object} notRouteData request rules and preferencies
64
+ * @param {object} result result returned by main action processor
65
+ */
66
+ static filter(notRouteData, result) {
67
+ if (!(result && typeof result === "object")) return;
68
+ const filteringRule = this.getFilteringRule(notRouteData);
69
+ if (!filteringRule) return;
70
+ const filteringTarget = this.getFilteringTarget(result, notRouteData);
71
+ if (!filteringTarget) {
72
+ return;
73
+ }
74
+ this.filterByRule(
75
+ filteringTarget,
76
+ filteringRule,
77
+ this.getFilteringStrictMode(notRouteData)
78
+ );
79
+ return result;
80
+ }
81
+
82
+ /**
83
+ * modifies target according to rule
84
+ * @param {object} target object which properties should be filtered
85
+ * @param {array} rule filtering rule
86
+ * @param {boolean} strict filtering mode
87
+ */
88
+
89
+ static filterByRule(target, rule, strict = DEFAULT_STRICT_MODE) {
90
+ if (!rule || !target) {
91
+ return;
92
+ }
93
+ if (Array.isArray(rule)) {
94
+ this.filterByArrayRule(target, rule); //flat map always strict==true
95
+ } else if (typeof rule === "object") {
96
+ this.filterByMapRule(target, rule, strict); //object map strict could vary
97
+ }
98
+ }
99
+
100
+ /**
101
+ * filters target by rule.
102
+ * Rule form: {pathToProperty: filteringRuleArray, pathToProperty: filteringRuleMap }
103
+ * pathToProperty should be in notPath notation except starting symbol (default: ':')
104
+ * if pathToProperty targets array in target, each item of array will be filtered by
105
+ * @param {object} target
106
+ * @param {object} rule map of properties.
107
+ * @param {boolean} strict filtering mode
108
+ * example:
109
+ * {
110
+ * 'user': ['username', 'id', 'email'], //filtering properties of object target.user
111
+ * 'posts': ['id', 'title'], //filtering properties of array of objects target.posts
112
+ * 'friends.list': ['id', 'username'] //filtering props of array of objects target.friends.list by path.
113
+ * //if strict==false: will not drop any from friends.*, only friends.list cotent will be processed
114
+ * //if strict==true: will drop all properties of friends except list
115
+ * 'friends': { //this will clear friends sub-object from any properties except 'list' in any strict mode
116
+ * 'list': ['id', 'username'] //and filter 'list' items
117
+ * },
118
+ * 'storage': { //filtering complex sub object by map
119
+ * 'files':{
120
+ * 'cloud': ['id','title'],
121
+ * 'local': ['id', 'title', 'size']
122
+ * }
123
+ * }
124
+ * }
125
+ */
126
+ static filterByMapRule(target, rule, strict = DEFAULT_STRICT_MODE) {
127
+ //to form ['id', 'user.username', 'files']
128
+ const filteringArray = Object.keys(rule);
129
+ //filtering direct chilren if in strict mode
130
+ if (strict) {
131
+ this.filterStrict(target, filteringArray);
132
+ }
133
+ //filter properties
134
+ for (let filteringTargetName of filteringArray) {
135
+ const subTarget = notPath.get(
136
+ `${notPath.PATH_START_OBJECT}${filteringTargetName}`,
137
+ target
138
+ );
139
+ //if sub target Array filtering each item individualy
140
+ if (Array.isArray(subTarget)) {
141
+ subTarget.forEach((subTargetItem) => {
142
+ this.filterByRule(
143
+ subTargetItem,
144
+ rule[filteringTargetName],
145
+ strict
146
+ );
147
+ });
148
+ } else if (typeof subTarget == "object") {
149
+ this.filterByRule(subTarget, rule[filteringTargetName], strict);
150
+ }
151
+ }
152
+ }
153
+
154
+ static filterStrict(target, filteringArray) {
155
+ console.log(target, filteringArray);
156
+ //to form ['id', 'user', 'files']
157
+ const filteringArrayDirectChildren = filteringArray.map(
158
+ (propName) => propName.split(notPath.PATH_SPLIT)[0]
159
+ );
160
+ //filter strict direct children
161
+ this.filterByArrayRule(target, filteringArrayDirectChildren);
162
+ //filter strict each level in props paths in filteringArray
163
+ for (let path of filteringArray) {
164
+ let pathParts = path.split(notPath.PATH_SPLIT);
165
+ if (pathParts.length === 1) {
166
+ continue;
167
+ }
168
+ //property name in which we will clean content except pathParts[1]
169
+ const directPropName = pathParts.shift();
170
+ //
171
+ const subTarget = target[directPropName];
172
+ if (Array.isArray(subTarget)) continue;
173
+ const subFilteringArray = [pathParts.join(notPath.PATH_SPLIT)];
174
+ this.filterStrict(subTarget, subFilteringArray);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * filters target properties by rule, all properties which names are not presented in rule
180
+ * array will be removed from target
181
+ * @param {object} target object to filter
182
+ * @param {Array} rule list of enabled properties. example: ['id', 'title', 'createdAt']
183
+ */
184
+ static filterByArrayRule(target, rule) {
185
+ const presented = Object.keys(target);
186
+ presented.forEach((fieldName) => {
187
+ if (!rule.includes(fieldName)) {
188
+ delete target[fieldName];
189
+ }
190
+ });
191
+ }
192
+
193
+ /**
194
+ * returns filtering rule object or undefined
195
+ * @param {object} notRouteData
196
+ * @returns {object|Array|undefined} rule to filter result object properties
197
+ */
198
+ static getFilteringRule(notRouteData) {
199
+ if (objHas(notRouteData.rule, PROP_NAME_RETURN_RULE)) {
200
+ return copyObj(notRouteData.rule[PROP_NAME_RETURN_RULE]);
201
+ } else if (objHas(notRouteData.actionData, PROP_NAME_RETURN_RULE)) {
202
+ return copyObj(notRouteData.actionData[PROP_NAME_RETURN_RULE]);
203
+ } else {
204
+ return undefined;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Returns path to sub object which properties should be filtered
210
+ * @param {object} notRouteData
211
+ * @returns {string} path of sub object in notPath notation
212
+ */
213
+ static getFilteringTargetPath(notRouteData) {
214
+ let path = notPath.PATH_START_OBJECT;
215
+ if (notRouteData) {
216
+ if (objHas(notRouteData.rule, PROP_NAME_RETURN_ROOT)) {
217
+ path += notRouteData.rule[PROP_NAME_RETURN_ROOT];
218
+ } else if (objHas(notRouteData.actionData, PROP_NAME_RETURN_ROOT)) {
219
+ path += notRouteData.actionData[PROP_NAME_RETURN_ROOT];
220
+ }
221
+ }
222
+ return path;
223
+ }
224
+
225
+ /**
226
+ * Returns object which properties should be filtered
227
+ * @param {object} result request result
228
+ * @param {object} notRouteData request route rules and prefs
229
+ * @returns {object} object to filter
230
+ */
231
+ static getFilteringTarget(result, notRouteData) {
232
+ if (result && typeof result == "object") {
233
+ const returnListRoot = this.getFilteringTargetPath(notRouteData);
234
+ return notPath.get(returnListRoot, result);
235
+ } else {
236
+ return result;
237
+ }
238
+ }
239
+
240
+ static getFilteringStrictMode(notRouteData) {
241
+ if (notRouteData) {
242
+ if (objHas(notRouteData.rule, PROP_NAME_RETURN_STRICT)) {
243
+ return !!notRouteData.rule[PROP_NAME_RETURN_STRICT];
244
+ } else if (
245
+ objHas(notRouteData.actionData, PROP_NAME_RETURN_STRICT)
246
+ ) {
247
+ return !!notRouteData.actionData[PROP_NAME_RETURN_STRICT];
248
+ }
249
+ }
250
+ return this.DEFAULT_STRICT_MODE;
251
+ }
252
+
253
+ static get PROP_NAME_RETURN_ROOT() {
254
+ return PROP_NAME_RETURN_ROOT;
255
+ }
256
+
257
+ static get PROP_NAME_RETURN_RULE() {
258
+ return PROP_NAME_RETURN_RULE;
259
+ }
260
+
261
+ static get PROP_NAME_RETURN_STRICT() {
262
+ return PROP_NAME_RETURN_STRICT;
263
+ }
264
+
265
+ static get DEFAULT_STRICT_MODE() {
266
+ return DEFAULT_STRICT_MODE;
267
+ }
268
+ };
@@ -2,10 +2,13 @@ const CONST_BEFORE_ACTION = "before";
2
2
  const CONST_AFTER_ACTION = "after";
3
3
 
4
4
  const obsoleteWarning = require("../obsolete");
5
+
6
+ const notAppIdentity = require("../identity");
5
7
  const Auth = require("../auth"),
6
8
  HttpError = require("../error").Http;
7
9
 
8
- const { objHas, copyObj, executeObjectFunction } = require("../common");
10
+ const notManifestRouteResultFilter = require("./result.filter");
11
+ const { copyObj, executeObjectFunction } = require("../common");
9
12
 
10
13
  /**
11
14
  * Route representation
@@ -67,7 +70,7 @@ class notRoute {
67
70
  * @return {object} rule or null
68
71
  */
69
72
  selectRule(req) {
70
- const user = Auth.extractAuthData(req);
73
+ const user = notAppIdentity.extractAuthData(req);
71
74
  if (this.actionData) {
72
75
  return notRoute.actionAvailableByRule(this.actionData, user);
73
76
  }
@@ -171,39 +174,6 @@ class notRoute {
171
174
  );
172
175
  }
173
176
 
174
- extractReturn(notRouteData) {
175
- if (objHas(notRouteData.rule, "return")) {
176
- return [...notRouteData.rule.return];
177
- } else if (objHas(notRouteData.actionData, "return")) {
178
- return [...notRouteData.actionData.return];
179
- } else {
180
- return undefined;
181
- }
182
- }
183
-
184
- /**
185
- * Removes fields from result object acctoding to actionData.return array
186
- * if presented
187
- * @param {ExpressRequest} req request object
188
- * @param {object} result result returned by main action processor
189
- */
190
- filterResultByReturnRule(req, result) {
191
- const returnList = this.extractReturn(req.notRouteData);
192
- if (
193
- result &&
194
- typeof result === "object" &&
195
- returnList &&
196
- Array.isArray(returnList)
197
- ) {
198
- let presented = Object.keys(result);
199
- presented.forEach((fieldName) => {
200
- if (!returnList.includes(fieldName)) {
201
- delete result[fieldName];
202
- }
203
- });
204
- }
205
- }
206
-
207
177
  async executeRoute(modRoute, actionName, { req, res, next }) {
208
178
  try {
209
179
  //waiting preparation
@@ -220,7 +190,7 @@ class notRoute {
220
190
  prepared,
221
191
  ]);
222
192
  //filter result IF actionData.return specified
223
- this.filterResultByReturnRule(req, result);
193
+ notManifestRouteResultFilter.filter(req.notRouteData, result);
224
194
  //run after with results, continue without waiting when it finished
225
195
  return this.executeFunction(modRoute, CONST_AFTER_ACTION, [
226
196
  req,
package/static2.js ADDED
@@ -0,0 +1,24 @@
1
+ class Parent {
2
+ static getOptions() {
3
+ return this.#options;
4
+ }
5
+
6
+ static setOptions(opts) {
7
+ this.#options = opts;
8
+ }
9
+ }
10
+
11
+ class Child extends Parent {
12
+ static #options = {};
13
+ constructor() {
14
+ super();
15
+ }
16
+
17
+ run() {
18
+ Child.getOptions();
19
+ }
20
+ }
21
+
22
+ Parent.setOptions({ 1: "1" });
23
+
24
+ console.log(new Child().run());
File without changes
@@ -3,7 +3,7 @@ const {
3
3
  HttpExceptionForbidden,
4
4
  } = require("../../src/exceptions/http");
5
5
 
6
- module.exports = ({ Auth, HttpError, expect }) => {
6
+ module.exports = ({ Auth, expect }) => {
7
7
  describe("Routes", () => {
8
8
  describe("getIP", () => {
9
9
  it("req.header[x-forwarded-for]", () => {