@nsshunt/stsdatamanagement 1.10.5 → 1.11.2

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 (await 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 (await 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,62 @@ 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
+ return JSON.parse(resourceRaw.detail.resdesc);
106
+ } catch (error) {
107
+ throw new STSResourceMalformedError(resource, resourceId, resourceRaw.detail.resdesc, error);
108
+ }
109
+ }
110
+
111
+ async #ResourceExists(resource, resourceId) {
112
+ try {
113
+ await this.#GetResource(resource, resourceId);
114
+ return true;
115
+ } catch (error) {
116
+ if (error instanceof STSResourceNotFoundError) {
117
+ return false;
118
+ } else {
119
+ throw error;
120
+ }
121
+ }
122
+ }
123
+
124
+ async GetUser(email) {
125
+ return this.#GetResource('User', BLCAuth.USER_ID_PREFIX + email);
126
+ }
127
+
128
+ async GetRole(role) {
129
+ return this.#GetResource('Role', BLCAuth.ROLE_ID_PREFIX + role);
130
+ }
131
+
132
+ async GetApplication(application) {
133
+ return this.#GetResource('Application', BLCAuth.APPLICATION_ID_PREFIX + application);
134
+ }
135
+
136
+ async GetAPI(identifier) {
137
+ return this.#GetResource('API', BLCAuth.API_ID_PREFIX + identifier);
138
+ }
139
+
87
140
  async GetUserPermissions(email)
88
141
  {
89
142
  try
90
143
  {
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
-
144
+ let userResource = await this.GetUser(email);
99
145
  let permissions = [ ];
100
146
 
101
147
  for (let i=0; i < userResource.roles.length; i++) {
102
148
  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);
149
+ let roleResource = await this.GetRole(role);
109
150
  for (let j=0; j < roleResource.permissions.length; j++) {
110
151
  let permission = roleResource.permissions[j];
111
152
  if (!permissions.includes(permission)) {
@@ -128,23 +169,25 @@ class BLCAuth
128
169
  {
129
170
  const { clientId } = application;
130
171
  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
172
+ if (await this.#ResourceExists('Application', applicationId)) {
173
+ return {
174
+ status: status.conflict,
175
+ error: 'Application already exists.',
176
+ detail: { message: `Application already exists: [${applicationId}]` }
177
+ };
178
+ } else {
179
+ let payload = {
180
+ applicationId,
181
+ ...application
182
+ }
183
+
184
+ await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, applicationId, payload);
185
+
186
+ // Client Secret is not returned. Seperate function used to display this field.
187
+ delete payload.clientSecret;
188
+
189
+ return { status: status.success, detail: payload };
140
190
  }
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
191
  } catch (error)
149
192
  {
150
193
  console.error(error);
@@ -152,47 +195,73 @@ class BLCAuth
152
195
  }
153
196
  }
154
197
 
198
+ #ValidateApplication = async (apiPermissions, app) => {
199
+ const { clientId, clientName, permissions } = app;
200
+
201
+ let applicationResource = null;
202
+ try {
203
+ applicationResource = await this.GetApplication(clientId)
204
+ } catch (error) {
205
+ return { status: status.notfound , error: 'Cannot find client application.', detail: { message: error.message }};
206
+ }
207
+ if (applicationResource.clientName.localeCompare(clientName) !== 0) {
208
+ return { status: status.error, error: 'clientName mismatch.', detail: { message: `clientName mismatch: Value: [${clientName}], Expecting: [${applicationResource.clientName}]` }};
209
+ }
210
+ for (let j=0; j < permissions.length; j++) {
211
+ const permission = permissions[j];
212
+ if (!apiPermissions.includes(permission)) {
213
+ 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}]` }};
214
+ }
215
+ }
216
+ return null;
217
+ }
218
+
155
219
  async AddAPI(api)
156
220
  {
157
221
  try
158
222
  {
159
- const { M2MApplications, permissions, identifier } = api;
223
+ const { M2MApplications, permissions: apiPermissions, identifier, SPA } = api;
224
+
160
225
  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
226
 
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}]` }};
227
+ if (await this.#ResourceExists('API', APIidentifier)) {
228
+ return {
229
+ status: status.conflict,
230
+ error: 'API already exists.',
231
+ detail: { message: `API already exists: [${APIidentifier}]` }
232
+ };
233
+ } else {
234
+ // Validate M2MApplications
235
+ if (M2MApplications) {
236
+ for (let i=0; i < M2MApplications.length; i++) {
237
+ let retVal = await this.#ValidateApplication(apiPermissions, M2MApplications[i]);
238
+ if (retVal !== null) {
239
+ return retVal;
240
+ }
241
+ }
177
242
  }
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}]` }};
243
+
244
+ // Validate SPA
245
+ if (SPA) {
246
+ for (let i=0; i < SPA.length; i++) {
247
+ let retVal = await this.#ValidateApplication(apiPermissions, SPA[i]);
248
+ if (retVal !== null) {
249
+ return retVal;
250
+ }
182
251
  }
183
252
  }
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
- }
253
+
254
+ // Client Secret is not returned. Separate function used to display this field.
255
+ let payload =
256
+ {
257
+ APIidentifier,
258
+ ...api
259
+ }
192
260
 
193
- await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, APIidentifier, payload);
261
+ await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, APIidentifier, payload);
194
262
 
195
- return { status: status.success, detail: payload };
263
+ return { status: status.success, detail: payload };
264
+ }
196
265
  } catch (error)
197
266
  {
198
267
  console.error(error);
@@ -200,23 +269,23 @@ class BLCAuth
200
269
  }
201
270
  }
202
271
 
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;
272
+ /*
273
+ // Get all the scopes (permissions) defined for this application (client_id) using this API (audience).
274
+ // client_secret required for M2M applications. Not required for SPA.
275
+ async GetScopes(client_id, client_secret, audience) {
276
+
210
277
  }
211
278
 
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;
279
+ // Get all the scopes that the user (user_id) has previously consented for this application (client_id) using this API (audience).
280
+ async GetConsentedScopes(user_id, client_id, audience) {
281
+
282
+ }
283
+
284
+ // Update scopes that the user (user_id) has consented for this application (client_id) using this API (audience).
285
+ async UpdateConsentedScopes(user_id, client_id, audience, scopes) {
286
+
219
287
  }
288
+ */
220
289
  }
221
290
 
222
291
  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.5",
3
+ "version": "1.11.2",
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": {