@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 +182 -111
- package/dbaccess.js +7 -1
- package/dberrors.js +57 -0
- package/package.json +4 -4
- package/pgaccesslayer.js +13 -0
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
83
|
+
await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, roleId, roleResource);
|
|
78
84
|
|
|
79
|
-
|
|
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
|
|
92
|
-
let
|
|
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
|
|
104
|
-
let
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
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,
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
263
|
+
await this.#accessLayer.saveResource(BLCAuth.SYSTEM_USER_ID, APIidentifier, payload);
|
|
194
264
|
|
|
195
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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 = {
|
|
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.
|
|
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.
|
|
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": "^
|
|
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.
|
|
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';
|