@nsshunt/stsdatamanagement 1.10.4 → 1.11.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/blcauth.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const { status: { status } } = require('@nsshunt/stsutils');
2
2
  const bcrypt = require('bcryptjs');
3
+ const { STSResourceNotFoundError, STSDatabaseAccessError, STSResourceMalformedError } = require('./dberrors');
3
4
 
4
5
  class BLCAuth
5
6
  {
@@ -24,32 +25,35 @@ class BLCAuth
24
25
  try
25
26
  {
26
27
  let userid = BLCAuth.USER_ID_PREFIX + email;
27
- let existingUser = await this.#accessLayer.getLatestResource(userid);
28
- if (existingUser.status === 200) {
29
- return { status: status.conflict, error: 'User already exists.', detail: { message: 'User already exists.' }};
30
- }
31
-
32
- const hashedPassword = await bcrypt.hash(password, saltRounds);
33
-
34
- let user = {
35
- id: userid
36
- ,name: name
37
- ,email: email
38
- ,hash: hashedPassword
39
- ,roles: roles
40
- };
41
-
42
- await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, user.id, user);
43
-
44
- let payload =
45
- {
46
- id: userid
47
- ,name: name
48
- ,email: email
49
- ,roles: roles
50
- }
28
+ if (this.#ResourceExists('User', userid)) {
29
+ return {
30
+ status: status.conflict,
31
+ error: 'User already exists.',
32
+ detail: { message: `User already exists: [${userid}]` }
33
+ };
34
+ } else {
35
+ const hashedPassword = await bcrypt.hash(password, saltRounds);
36
+
37
+ let user = {
38
+ id: userid
39
+ ,name: name
40
+ ,email: email
41
+ ,hash: hashedPassword
42
+ ,roles: roles
43
+ };
51
44
 
52
- return { status: status.success, detail: payload };
45
+ await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, user.id, user);
46
+
47
+ let payload =
48
+ {
49
+ id: userid
50
+ ,name: name
51
+ ,email: email
52
+ ,roles: roles
53
+ }
54
+
55
+ return { status: status.success, detail: payload };
56
+ }
53
57
  } catch (error)
54
58
  {
55
59
  console.error(error);
@@ -63,20 +67,23 @@ class BLCAuth
63
67
  {
64
68
  const { name, permissions } = rolePermissions;
65
69
  let roleId = BLCAuth.ROLE_ID_PREFIX + name;
66
- let existingRole = await this.#accessLayer.getLatestResource(roleId);
67
- if (existingRole.status === 200) {
68
- return { status: status.conflict, error: 'Role already exists.', detail: { message: 'Role already exists.' }};
69
- }
70
-
71
- let roleResource = {
72
- id: roleId,
73
- name: name,
74
- permissions: permissions
75
- }
70
+ if (this.#ResourceExists('Role', roleId)) {
71
+ return {
72
+ status: status.conflict,
73
+ error: 'Role already exists.',
74
+ detail: { message: `Role already exists: [${roleId}]` }
75
+ };
76
+ } else {
77
+ let roleResource = {
78
+ id: roleId,
79
+ name: name,
80
+ permissions: permissions
81
+ }
76
82
 
77
- await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, roleId, roleResource);
83
+ await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, roleId, roleResource);
78
84
 
79
- return { status: status.success, detail: roleResource };
85
+ return { status: status.success, detail: roleResource };
86
+ }
80
87
  } catch (error)
81
88
  {
82
89
  console.error(error);
@@ -84,28 +91,65 @@ class BLCAuth
84
91
  }
85
92
  }
86
93
 
94
+ async #GetResource(resource, resourceId) {
95
+ let resourceRaw = null;
96
+ try {
97
+ resourceRaw = await this.#accessLayer.getLatestResource(resourceId);
98
+ } catch (error) {
99
+ throw new STSDatabaseAccessError(resource, resourceId, error);
100
+ }
101
+ if (resourceRaw.status !== 200) {
102
+ throw new STSResourceNotFoundError(resource, resourceId);
103
+ }
104
+ try {
105
+ let resource = JSON.parse(resourceRaw.detail.resdesc);
106
+ return { status: status.success, detail: resource };
107
+ } catch (error) {
108
+ throw new STSResourceMalformedError(resource, resourceId, resourceRaw.detail.resdesc, error);
109
+ }
110
+ }
111
+
112
+ async #ResourceExists(resource, resourceId) {
113
+ try {
114
+ this.#GetResource(resource, resourceId);
115
+ return true;
116
+ } catch (error) {
117
+ if (error instanceof STSResourceNotFoundError) {
118
+ return false;
119
+ } else {
120
+ throw error;
121
+ }
122
+ }
123
+ }
124
+
125
+ async GetUser(email) {
126
+ return this.#GetResource('User', BLCAuth.USER_ID_PREFIX + email);
127
+ }
128
+
129
+ async GetRole(role) {
130
+ return this.#GetResource('Role', BLCAuth.ROLE_ID_PREFIX + role);
131
+ }
132
+
133
+ async GetApplication(application) {
134
+ return this.#GetResource('Application', BLCAuth.APPLICATION_ID_PREFIX + application);
135
+ }
136
+
137
+ async GetAPI(identifier) {
138
+ return this.#GetResource('API', BLCAuth.API_ID_PREFIX + identifier);
139
+ }
140
+
87
141
  async GetUserPermissions(email)
88
142
  {
89
143
  try
90
144
  {
91
- let userid = BLCAuth.USER_ID_PREFIX + email;
92
- let existingUser = await this.#accessLayer.getLatestResource(userid);
93
- if (existingUser.status !== 200) {
94
- return { status: status.notfound, error: 'User not found.', detail: { message: 'User not found.' }};
95
- }
96
-
97
- let userResource = JSON.parse(existingUser.detail.resdesc);
98
-
145
+ let userResourceRaw = await this.GetUser(email);
146
+ let userResource = userResourceRaw.detail;
99
147
  let permissions = [ ];
100
148
 
101
149
  for (let i=0; i < userResource.roles.length; i++) {
102
150
  let role = userResource.roles[i];
103
- let roleId = BLCAuth.ROLE_ID_PREFIX + role;
104
- let existingRole = await this.#accessLayer.getLatestResource(roleId);
105
- if (existingRole.status !== 200) {
106
- return { status: status.notfound, error: 'Role not found.', detail: { message: 'Role not found.' }};
107
- }
108
- let roleResource = JSON.parse(existingRole.detail.resdesc);
151
+ let roleResourceRaw = await this.GetRole(role);
152
+ let roleResource = roleResourceRaw.detail;
109
153
  for (let j=0; j < roleResource.permissions.length; j++) {
110
154
  let permission = roleResource.permissions[j];
111
155
  if (!permissions.includes(permission)) {
@@ -128,23 +172,25 @@ class BLCAuth
128
172
  {
129
173
  const { clientId } = application;
130
174
  let applicationId = BLCAuth.APPLICATION_ID_PREFIX + clientId;
131
- let existingApplication = await this.#accessLayer.getLatestResource(applicationId);
132
- if (existingApplication.status === 200) {
133
- return { status: status.conflict, error: 'Application already exists.', detail: { message: `Application already exists: [${applicationId}]` }};
134
- }
135
-
136
- let payload =
137
- {
138
- applicationId: applicationId,
139
- ...application
175
+ if (this.#ResourceExists('Application', applicationId)) {
176
+ return {
177
+ status: status.conflict,
178
+ error: 'Application already exists.',
179
+ detail: { message: `Application already exists: [${applicationId}]` }
180
+ };
181
+ } else {
182
+ let payload = {
183
+ applicationId,
184
+ ...application
185
+ }
186
+
187
+ await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, applicationId, payload);
188
+
189
+ // Client Secret is not returned. Seperate function used to display this field.
190
+ delete payload.clientSecret;
191
+
192
+ return { status: status.success, detail: payload };
140
193
  }
141
-
142
- await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, applicationId, payload);
143
-
144
- // Client Secret is not returned. Seperate function used to display this field.
145
- delete payload.clientSecret;
146
-
147
- return { status: status.success, detail: payload };
148
194
  } catch (error)
149
195
  {
150
196
  console.error(error);
@@ -152,47 +198,72 @@ class BLCAuth
152
198
  }
153
199
  }
154
200
 
201
+ #ValidateApplication = async (apiPermissions, app) => {
202
+ const { clientId, clientName, permissions } = app;
203
+
204
+ let applicationResource = null;
205
+ try {
206
+ applicationResource = await this.GetApplication(clientId)
207
+ } catch (error) {
208
+ return { status: status.notfound , error: 'Cannot find client application.', detail: { message: error.message }};
209
+ }
210
+ if (applicationResource.clientName.localeCompare(clientName) !== 0) {
211
+ return { status: status.error, error: 'clientName mismatch.', detail: { message: `clientName mismatch: Value: [${clientName}], Expecting: [${applicationResource.clientName}]` }};
212
+ }
213
+ for (let j=0; j < permissions.length; j++) {
214
+ const permission = permissions[j];
215
+ if (!apiPermissions.includes(permission)) {
216
+ return { status: status.error, error: 'M2M permission not found within API available permission list.', detail: { message: `Permission not found within API available permission list: [${permission}]` }};
217
+ }
218
+ }
219
+ return null;
220
+ }
221
+
155
222
  async AddAPI(api)
156
223
  {
157
224
  try
158
225
  {
159
- const { M2MApplications, permissions, identifier } = api;
226
+ const { M2MApplications, apiPermissions, identifier, SPA } = api;
160
227
  let APIidentifier = BLCAuth.API_ID_PREFIX + identifier;
161
- let existingAPI = await this.#accessLayer.getLatestResource(APIidentifier);
162
- if (existingAPI.status === 200) {
163
- return { status: status.conflict, error: 'API already exists.', detail: { message: `API already exists: [${APIidentifier}]` }};
164
- }
165
228
 
166
- // Validate M2MApplications
167
- for (let i=0; i < M2MApplications.length; i++) {
168
- const m2mapplication = M2MApplications[i];
169
- let appid = BLCAuth.APPLICATION_ID_PREFIX + m2mapplication.clientId;
170
- let retVal = await this.#accessLayer.getLatestResource(appid);
171
- if (retVal.status !== 200) {
172
- return { status: status.notfound , error: 'Cannot find client application.', detail: { message: `Cannot find client application: [${appid}].` }};
173
- }
174
- let applicationResource = JSON.parse(retVal.detail.resdesc);
175
- if (applicationResource.clientName.localeCompare(m2mapplication.clientName) !== 0) {
176
- return { status: status.error, error: 'clientName mismatch.', detail: { message: `clientName mismatch: Value: [${m2mapplication.clientName}], Expecting: [${applicationResource.clientName}]` }};
229
+ if (this.#ResourceExists('API', APIidentifier)) {
230
+ return {
231
+ status: status.conflict,
232
+ error: 'API already exists.',
233
+ detail: { message: `API already exists: [${APIidentifier}]` }
234
+ };
235
+ } else {
236
+ // Validate M2MApplications
237
+ if (M2MApplications) {
238
+ for (let i=0; i < M2MApplications.length; i++) {
239
+ let retVal = await this.#ValidateApplication(apiPermissions, M2MApplications[i]);
240
+ if (retVal !== null) {
241
+ return retVal;
242
+ }
243
+ }
177
244
  }
178
- for (let j=0; j < m2mapplication.permissions.length; j++) {
179
- const permission = m2mapplication.permissions[j];
180
- if (!permissions.includes(permission)) {
181
- return { status: status.error, error: 'M2M permission not found within API available permission list.', detail: { message: `M2M permission not found within API available permission list: [${permission}]` }};
245
+
246
+ // Validate SPA
247
+ if (SPA) {
248
+ for (let i=0; i < SPA.length; i++) {
249
+ let retVal = await this.#ValidateApplication(apiPermissions, SPA[i]);
250
+ if (retVal !== null) {
251
+ return retVal;
252
+ }
182
253
  }
183
254
  }
184
- }
185
-
186
- // Client Secret is not returned. Seperate function used to display this field.
187
- let payload =
188
- {
189
- APIidentifier: APIidentifier,
190
- ...api
191
- }
255
+
256
+ // Client Secret is not returned. Separate function used to display this field.
257
+ let payload =
258
+ {
259
+ APIidentifier,
260
+ ...api
261
+ }
192
262
 
193
- await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, APIidentifier, payload);
263
+ await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, APIidentifier, payload);
194
264
 
195
- return { status: status.success, detail: payload };
265
+ return { status: status.success, detail: payload };
266
+ }
196
267
  } catch (error)
197
268
  {
198
269
  console.error(error);
@@ -200,23 +271,23 @@ class BLCAuth
200
271
  }
201
272
  }
202
273
 
203
- async GetApplication(clientId) {
204
- let clientIdentifier = BLCAuth.APPLICATION_ID_PREFIX + clientId;
205
- let application = await this.#accessLayer.getLatestResource(clientIdentifier);
206
- if (application.status !== 200) {
207
- return { status: status.notfound, error: 'Application not found.', detail: { message: `Application not found: [${clientId}]` }};
208
- }
209
- return application;
274
+ /*
275
+ // Get all the scopes (permissions) defined for this application (client_id) using this API (audience).
276
+ // client_secret required for M2M applications. Not required for SPA.
277
+ async GetScopes(client_id, client_secret, audience) {
278
+
210
279
  }
211
280
 
212
- async GetAPI(identifier) {
213
- let APIidentifier = BLCAuth.API_ID_PREFIX + identifier;
214
- let api = await this.#accessLayer.getLatestResource(APIidentifier);
215
- if (api.status !== 200) {
216
- return { status: status.notfound, error: 'API not found.', detail: { message: `API not found: [${identifier}]` }};
217
- }
218
- return api;
281
+ // Get all the scopes that the user (user_id) has previously consented for this application (client_id) using this API (audience).
282
+ async GetConsentedScopes(user_id, client_id, audience) {
283
+
284
+ }
285
+
286
+ // Update scopes that the user (user_id) has consented for this application (client_id) using this API (audience).
287
+ async UpdateConsentedScopes(user_id, client_id, audience, scopes) {
288
+
219
289
  }
290
+ */
220
291
  }
221
292
 
222
293
  module.exports = { BLCAuth };
package/dbaccess.js CHANGED
@@ -5,5 +5,11 @@ const { PGUtils } = require('./pgutils');
5
5
  const { BLCAuth } = require('./blcauth');
6
6
  const { DatabaseUtils } = require('./databaseutils');
7
7
  const { DataGenerator } = require('./datagenerator');
8
+ const { STSDataManagementError, STSResourceNotFoundError, STSResourceMalformedError, STSDatabaseAccessError } = require('./dberrors');
8
9
 
9
- module.exports = { PGPoolManager, L1Cache, PGAccessLayer, PGUtils, BLCAuth, DatabaseUtils, DataGenerator };
10
+ module.exports = {
11
+ // Utilities and Helpers
12
+ PGPoolManager, L1Cache, PGAccessLayer, PGUtils, BLCAuth, DatabaseUtils, DataGenerator,
13
+ // Errors
14
+ STSDataManagementError, STSResourceNotFoundError, STSResourceMalformedError, STSDatabaseAccessError
15
+ };
package/dberrors.js ADDED
@@ -0,0 +1,57 @@
1
+ const { status: { status } } = require('@nsshunt/stsutils');
2
+
3
+ // https://rclayton.silvrback.com/custom-errors-in-node-js
4
+ class STSDataManagementError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+
8
+ // Ensure the name of this error is the same as the class name
9
+ this.name = this.constructor.name;
10
+
11
+ // This clips the constructor invocation from the stack trace.
12
+ // It's not absolutely essential, but it does make the stack trace a little nicer.
13
+ // @see Node.js reference (bottom)
14
+ Error.captureStackTrace(this, this.constructor);
15
+
16
+ this.detail = null;
17
+ this.status = status.error;
18
+ }
19
+ }
20
+
21
+ class STSResourceNotFoundError extends STSDataManagementError {
22
+ constructor(resource, resourceId) {
23
+ super(`Resource [${resource}] not found using ID: [${resourceId}]`);
24
+ this.detail = {
25
+ resourceId,
26
+ resource
27
+ }
28
+ this.status = status.notfound;
29
+ }
30
+ }
31
+
32
+ class STSResourceMalformedError extends STSDataManagementError {
33
+ constructor(resource, resourceId, resourceRaw, error) {
34
+ super(`Resource [${resource}:{resourceId}] is malformed.`);
35
+ this.detail = {
36
+ resourceId,
37
+ resource,
38
+ resourceRaw,
39
+ error
40
+ }
41
+ this.status = status.error;
42
+ }
43
+ }
44
+
45
+ class STSDatabaseAccessError extends STSDataManagementError {
46
+ constructor(resource, resourceId, error) {
47
+ super(`Database access error reading resource: [${resource}:{resourceId}]`);
48
+ this.detail = {
49
+ resourceId,
50
+ resource,
51
+ error
52
+ }
53
+ this.status = status.error;
54
+ }
55
+ }
56
+
57
+ module.exports = { STSDataManagementError, STSResourceNotFoundError, STSResourceMalformedError, STSDatabaseAccessError }
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@nsshunt/stsdatamanagement",
3
- "version": "1.10.4",
3
+ "version": "1.11.1",
4
4
  "description": "STS Data Management Modules, Utilities and Services",
5
5
  "main": "dbaccess.js",
6
6
  "dependencies": {
7
7
  "@nsshunt/stsconfig": "^1.20.0",
8
8
  "@nsshunt/stsinstrumentation": "^6.4.3",
9
- "@nsshunt/stsutils": "^1.7.6",
9
+ "@nsshunt/stsutils": "^1.8.5",
10
10
  "axios": "^0.26.0",
11
11
  "bcryptjs": "^2.4.3",
12
12
  "cli-progress": "^3.10.0",
13
13
  "colors": "^1.4.0",
14
14
  "debug": "^4.3.4",
15
- "ioredis": "^4.28.5",
15
+ "ioredis": "^5.0.3",
16
16
  "pg": "^8.7.3",
17
17
  "pg-copy-streams": "^6.0.2",
18
18
  "prompts": "^2.4.2",
@@ -31,7 +31,7 @@
31
31
  "@babel/eslint-parser": "^7.17.0",
32
32
  "@babel/plugin-proposal-class-properties": "^7.16.7",
33
33
  "@babel/plugin-proposal-private-methods": "^7.16.11",
34
- "eslint": "^8.11.0",
34
+ "eslint": "^8.12.0",
35
35
  "jest": "^27.5.1"
36
36
  },
37
37
  "scripts": {
package/pgaccesslayer.js CHANGED
@@ -332,6 +332,19 @@ class PGAccessLayer
332
332
  }
333
333
  }
334
334
 
335
+ async deleteLatestResource(userid, resid)
336
+ {
337
+ const fname = 'deleteLatestResource';
338
+ let retVal = await this.getLatestResource(resid);
339
+ if (retVal.status !== status.success)
340
+ {
341
+ return { status: retVal.status, error: `[${fname}]: Operation was not successful: [${resid}]`, detail: retVal };
342
+ } else {
343
+ let vnum = retVal.detail.vnum;
344
+ await this.deleteResource(userid, resid, vnum);
345
+ }
346
+ }
347
+
335
348
  async deleteResource(userid, resourceid, resourcevnum)
336
349
  {
337
350
  const fname = 'deleteResource';