ti2 1.0.38 → 1.0.41
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/.github/workflows/tests.yml +1 -1
- package/api.yml +133 -0
- package/controllers/__tests__/admin.js +7 -0
- package/controllers/__tests__/app.js +0 -1
- package/controllers/__tests__/user.js +40 -0
- package/controllers/admin.js +10 -0
- package/controllers/allotment.js +1 -1
- package/controllers/app.js +83 -4
- package/controllers/bookings.js +7 -7
- package/lib/security.js +31 -0
- package/migrations/20221108160527-userIntegrationSettings.js +28 -0
- package/models/integration.js +4 -24
- package/models/user.js +3 -0
- package/models/userappkey.js +11 -1
- package/models/userintegrationsetting.js +52 -0
- package/package.json +1 -1
package/api.yml
CHANGED
|
@@ -29,6 +29,11 @@ components:
|
|
|
29
29
|
properties:
|
|
30
30
|
value:
|
|
31
31
|
type: string
|
|
32
|
+
Status:
|
|
33
|
+
type: object
|
|
34
|
+
properties:
|
|
35
|
+
success:
|
|
36
|
+
type: boolean
|
|
32
37
|
JobSpec:
|
|
33
38
|
type: object
|
|
34
39
|
properties:
|
|
@@ -132,6 +137,20 @@ components:
|
|
|
132
137
|
type: string
|
|
133
138
|
updatedAt:
|
|
134
139
|
type: string
|
|
140
|
+
UserList:
|
|
141
|
+
type: object
|
|
142
|
+
properties:
|
|
143
|
+
users:
|
|
144
|
+
type: array
|
|
145
|
+
items:
|
|
146
|
+
type: object
|
|
147
|
+
properties:
|
|
148
|
+
userId:
|
|
149
|
+
type: string
|
|
150
|
+
createdAt:
|
|
151
|
+
type: string
|
|
152
|
+
updatedAt:
|
|
153
|
+
type: string
|
|
135
154
|
AppTokenList:
|
|
136
155
|
type: object
|
|
137
156
|
properties:
|
|
@@ -320,6 +339,13 @@ components:
|
|
|
320
339
|
id:
|
|
321
340
|
type: string
|
|
322
341
|
description: a quote Id to make a reservation for
|
|
342
|
+
settingsBody:
|
|
343
|
+
type: object
|
|
344
|
+
required: true
|
|
345
|
+
properties:
|
|
346
|
+
settings:
|
|
347
|
+
type: object
|
|
348
|
+
description: an object with the app+user seettings
|
|
323
349
|
paths:
|
|
324
350
|
/ping:
|
|
325
351
|
get:
|
|
@@ -419,6 +445,21 @@ paths:
|
|
|
419
445
|
application/json:
|
|
420
446
|
schema:
|
|
421
447
|
$ref: '#/components/schemas/IntegrationList'
|
|
448
|
+
/users:
|
|
449
|
+
get:
|
|
450
|
+
security:
|
|
451
|
+
- bearerAuth: ['admin']
|
|
452
|
+
tags:
|
|
453
|
+
- admin
|
|
454
|
+
summary: list all created users
|
|
455
|
+
operationId: listUsers
|
|
456
|
+
responses:
|
|
457
|
+
200:
|
|
458
|
+
description: success
|
|
459
|
+
content:
|
|
460
|
+
application/json:
|
|
461
|
+
schema:
|
|
462
|
+
$ref: '#/components/schemas/UserList'
|
|
422
463
|
/app/resetAppKey/{appKey}:
|
|
423
464
|
get:
|
|
424
465
|
security:
|
|
@@ -799,6 +840,98 @@ paths:
|
|
|
799
840
|
content:
|
|
800
841
|
appication/json:
|
|
801
842
|
schema: '#/components/schemas/appMethods'
|
|
843
|
+
/settings/{appKey}/{userId}:
|
|
844
|
+
post:
|
|
845
|
+
security:
|
|
846
|
+
- bearerAuth: ['admin', 'user']
|
|
847
|
+
tags:
|
|
848
|
+
- user
|
|
849
|
+
summary: Creates user settings for an app, shared across tokens per user
|
|
850
|
+
operationId: createAppSettings
|
|
851
|
+
parameters:
|
|
852
|
+
- name: appKey
|
|
853
|
+
in: path
|
|
854
|
+
description: name of the app to use for the settings
|
|
855
|
+
required: true
|
|
856
|
+
example: 'expedia'
|
|
857
|
+
schema:
|
|
858
|
+
type: string
|
|
859
|
+
- name: userId
|
|
860
|
+
in: path
|
|
861
|
+
description: user id for the setting to be created
|
|
862
|
+
required: true
|
|
863
|
+
example: '536830b6ed19afa44a000002'
|
|
864
|
+
schema:
|
|
865
|
+
type: string
|
|
866
|
+
requestBody:
|
|
867
|
+
description: Creation Payload
|
|
868
|
+
required: true
|
|
869
|
+
content:
|
|
870
|
+
application/json:
|
|
871
|
+
schema:
|
|
872
|
+
$ref: '#/components/schemas/settingsBody'
|
|
873
|
+
responses:
|
|
874
|
+
200:
|
|
875
|
+
description: success
|
|
876
|
+
content:
|
|
877
|
+
application/json:
|
|
878
|
+
schema: '#/components/schemas/Success'
|
|
879
|
+
get:
|
|
880
|
+
security:
|
|
881
|
+
- bearerAuth: ['admin', 'user']
|
|
882
|
+
tags:
|
|
883
|
+
- user
|
|
884
|
+
summary: Retrieves a user settings for an app, shared across tokens per user
|
|
885
|
+
operationId: getAppSettings
|
|
886
|
+
parameters:
|
|
887
|
+
- name: appKey
|
|
888
|
+
in: path
|
|
889
|
+
description: name of the app to use for the settings
|
|
890
|
+
required: true
|
|
891
|
+
example: 'expedia'
|
|
892
|
+
schema:
|
|
893
|
+
type: string
|
|
894
|
+
- name: userId
|
|
895
|
+
in: path
|
|
896
|
+
description: user id for the setting to be created
|
|
897
|
+
required: true
|
|
898
|
+
example: '536830b6ed19afa44a000002'
|
|
899
|
+
schema:
|
|
900
|
+
type: string
|
|
901
|
+
responses:
|
|
902
|
+
200:
|
|
903
|
+
description: success
|
|
904
|
+
content:
|
|
905
|
+
application/json:
|
|
906
|
+
schema: '#/components/schemas/settingsBody'
|
|
907
|
+
delete:
|
|
908
|
+
security:
|
|
909
|
+
- bearerAuth: ['admin', 'user']
|
|
910
|
+
tags:
|
|
911
|
+
- user
|
|
912
|
+
summary: Deletes a user settings for an app, shared across tokens per user
|
|
913
|
+
operationId: deleteAppSettings
|
|
914
|
+
parameters:
|
|
915
|
+
- name: appKey
|
|
916
|
+
in: path
|
|
917
|
+
description: name of the app to use for the settings
|
|
918
|
+
required: true
|
|
919
|
+
example: 'expedia'
|
|
920
|
+
schema:
|
|
921
|
+
type: string
|
|
922
|
+
- name: userId
|
|
923
|
+
in: path
|
|
924
|
+
description: user id for the setting to be created
|
|
925
|
+
required: true
|
|
926
|
+
example: '536830b6ed19afa44a000002'
|
|
927
|
+
schema:
|
|
928
|
+
type: string
|
|
929
|
+
responses:
|
|
930
|
+
200:
|
|
931
|
+
description: success
|
|
932
|
+
content:
|
|
933
|
+
application/json:
|
|
934
|
+
$ref: '#/components/schemas/Status'
|
|
802
935
|
/bookings/{appKey}/{userId}/search:
|
|
803
936
|
post:
|
|
804
937
|
security:
|
|
@@ -59,4 +59,11 @@ describe('admin', () => {
|
|
|
59
59
|
});
|
|
60
60
|
expect(value).toBeTruthy();
|
|
61
61
|
});
|
|
62
|
+
it('the created user should be on the user list', async () => {
|
|
63
|
+
const { users } = await doApiGet({
|
|
64
|
+
url: '/users',
|
|
65
|
+
token: adminKey,
|
|
66
|
+
});
|
|
67
|
+
expect(users.find(currentUser => currentUser.userId === userId)).toBeTruthy();
|
|
68
|
+
});
|
|
62
69
|
});
|
|
@@ -26,11 +26,13 @@ describe('user', () => {
|
|
|
26
26
|
endpoint: chance.url(),
|
|
27
27
|
apiKey,
|
|
28
28
|
};
|
|
29
|
+
let plugins;
|
|
29
30
|
beforeAll(async () => {
|
|
30
31
|
({
|
|
31
32
|
doApiDelete,
|
|
32
33
|
doApiGet,
|
|
33
34
|
doApiPost,
|
|
35
|
+
plugins,
|
|
34
36
|
} = await testUtils({
|
|
35
37
|
plugins: [appName],
|
|
36
38
|
}));
|
|
@@ -133,6 +135,44 @@ describe('user', () => {
|
|
|
133
135
|
]),
|
|
134
136
|
);
|
|
135
137
|
});
|
|
138
|
+
it('should be able to create an app setting', async () => {
|
|
139
|
+
const returnValue = await doApiPost({
|
|
140
|
+
url: `/settings/${appName}/${userId}`,
|
|
141
|
+
token: userKey,
|
|
142
|
+
payload: {
|
|
143
|
+
settings: {
|
|
144
|
+
custom: true,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
expect(returnValue.success).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
it('should be able to get an app settings', async () => {
|
|
151
|
+
const returnValue = await doApiGet({
|
|
152
|
+
url: `/settings/${appName}/${userId}`,
|
|
153
|
+
token: userKey,
|
|
154
|
+
});
|
|
155
|
+
expect(returnValue.settings.custom).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
it('testing the user token should include the user app seting', async () => {
|
|
158
|
+
const { valid } = await doApiPost({
|
|
159
|
+
url: `/${appName}/${userId}/validate`,
|
|
160
|
+
token: userKey,
|
|
161
|
+
payload: {
|
|
162
|
+
tokenHint: apiKey.split('-')[0],
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
expect(plugins[0].validateToken).toHaveBeenCalled();
|
|
166
|
+
expect(valid).toBe(true);
|
|
167
|
+
expect(plugins[0].validateToken.mock.calls[0][0].token).toEqual({ custom: true, ...token });
|
|
168
|
+
});
|
|
169
|
+
it('should be able to delete an app settings', async () => {
|
|
170
|
+
const returnValue = await doApiDelete({
|
|
171
|
+
url: `/settings/${appName}/${userId}`,
|
|
172
|
+
token: userKey,
|
|
173
|
+
});
|
|
174
|
+
expect(returnValue.success).toBe(true);
|
|
175
|
+
});
|
|
136
176
|
it('should be able to get all the methods for an app', async () => {
|
|
137
177
|
const { methods } = await doApiGet({
|
|
138
178
|
url: `/app/${appName}/methods`,
|
package/controllers/admin.js
CHANGED
|
@@ -60,6 +60,15 @@ const listApps = async (req, res, next) => {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
const listUsers = async (req, res, next) => {
|
|
64
|
+
try {
|
|
65
|
+
const users = await sqldb.User.findAll();
|
|
66
|
+
return res.json({ users });
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return next(err);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
63
72
|
const createUserToken = async (req, res, next) => {
|
|
64
73
|
const {
|
|
65
74
|
body: {
|
|
@@ -140,6 +149,7 @@ module.exports = {
|
|
|
140
149
|
createUserToken,
|
|
141
150
|
deleteApp,
|
|
142
151
|
listApps,
|
|
152
|
+
listUsers,
|
|
143
153
|
migrate,
|
|
144
154
|
resetIntegrationToken,
|
|
145
155
|
};
|
package/controllers/allotment.js
CHANGED
|
@@ -22,7 +22,7 @@ const queryAllotment = plugins => async (req, res, next) => {
|
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
24
|
assert(userAppKeys, 'could not find the app key');
|
|
25
|
-
const token = userAppKeys.
|
|
25
|
+
const token = await userAppKeys.token;
|
|
26
26
|
assert(app.queryAllotment, 'could not find the allotment method');
|
|
27
27
|
const results = await app.queryAllotment({
|
|
28
28
|
token,
|
package/controllers/app.js
CHANGED
|
@@ -73,7 +73,7 @@ const createAppToken = async (req, res, next) => {
|
|
|
73
73
|
if (!userRecord) { // create the user record
|
|
74
74
|
await sqldb.User.create({ userId });
|
|
75
75
|
}
|
|
76
|
-
// check if the user
|
|
76
|
+
// check if the user already has the same app with the same hint
|
|
77
77
|
const userAppKeyDup = await sqldb.UserAppKey.findOne({
|
|
78
78
|
where: { userId, integrationId, hint },
|
|
79
79
|
});
|
|
@@ -186,7 +186,7 @@ const validateAppToken = plugins => async (req, res, next) => {
|
|
|
186
186
|
},
|
|
187
187
|
});
|
|
188
188
|
assert(userAppKeys, 'could not find the app key');
|
|
189
|
-
const token = userAppKeys.
|
|
189
|
+
const token = await userAppKeys.token;
|
|
190
190
|
assert(app.validateToken, `could not find the validateToken method for ${appKey}`);
|
|
191
191
|
const valid = await app.validateToken({
|
|
192
192
|
token,
|
|
@@ -197,13 +197,12 @@ const validateAppToken = plugins => async (req, res, next) => {
|
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
199
|
|
|
200
|
-
|
|
201
200
|
const listAppTokens = async (req, res, next) => {
|
|
202
201
|
const { params: { app: integrationId } } = req;
|
|
203
202
|
try {
|
|
204
203
|
const userAppKeys = (await sqldb.UserAppKey.findAll({ where: { integrationId } }))
|
|
205
204
|
.map(userAppKey => userAppKey.dataValues);
|
|
206
|
-
return res.json({ userAppKeys: userAppKeys.map(userAppKey => omit(['appKey', 'id'], userAppKey)) });
|
|
205
|
+
return res.json({ userAppKeys: userAppKeys.map(userAppKey => omit(['appKey', 'id', 'token'], userAppKey)) });
|
|
207
206
|
} catch (err) {
|
|
208
207
|
return next(err);
|
|
209
208
|
}
|
|
@@ -310,9 +309,89 @@ const getJobStatus = async (req, res, next) => {
|
|
|
310
309
|
}
|
|
311
310
|
};
|
|
312
311
|
|
|
312
|
+
const createAppSettings = async (req, res, next) => {
|
|
313
|
+
const {
|
|
314
|
+
body: {
|
|
315
|
+
settings,
|
|
316
|
+
},
|
|
317
|
+
params: {
|
|
318
|
+
appKey: integrationId,
|
|
319
|
+
userId,
|
|
320
|
+
},
|
|
321
|
+
} = req;
|
|
322
|
+
try {
|
|
323
|
+
const payload = {
|
|
324
|
+
integrationId,
|
|
325
|
+
userId,
|
|
326
|
+
settings,
|
|
327
|
+
};
|
|
328
|
+
// check if the user exists
|
|
329
|
+
const userRecord = await sqldb.User.findOne({ where: { userId } });
|
|
330
|
+
if (!userRecord) { // create the user record
|
|
331
|
+
await sqldb.User.create({ userId });
|
|
332
|
+
}
|
|
333
|
+
// check if the user already has the same app settings
|
|
334
|
+
const userSettings = await sqldb.UserIntegrationSettings.findOne({
|
|
335
|
+
where: { userId, integrationId },
|
|
336
|
+
});
|
|
337
|
+
if (userSettings) {
|
|
338
|
+
userSettings.settings = settings;
|
|
339
|
+
await userSettings.save();
|
|
340
|
+
} else {
|
|
341
|
+
await sqldb.UserIntegrationSettings.create(payload);
|
|
342
|
+
}
|
|
343
|
+
return res.json({ success: true });
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.log(err.stack);
|
|
346
|
+
return next(err);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const deleteAppSettings = async (req, res, next) => {
|
|
351
|
+
const {
|
|
352
|
+
params: {
|
|
353
|
+
appKey: integrationId,
|
|
354
|
+
userId,
|
|
355
|
+
},
|
|
356
|
+
} = req;
|
|
357
|
+
// check if the user exists
|
|
358
|
+
const userRecord = await sqldb.User.findOne({ where: { userId } });
|
|
359
|
+
if (!userRecord) return next({ status: 404, message: 'User does not exists' });
|
|
360
|
+
const retVal = await sqldb.UserIntegrationSettings.destroy({
|
|
361
|
+
where: {
|
|
362
|
+
integrationId,
|
|
363
|
+
userId,
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
if (retVal === 0) return next({ status: 404, message: 'Settings not found' });
|
|
367
|
+
return res.json({ success: true });
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const getAppSettings = async (req, res, next) => {
|
|
371
|
+
const {
|
|
372
|
+
params: {
|
|
373
|
+
appKey: integrationId,
|
|
374
|
+
userId,
|
|
375
|
+
},
|
|
376
|
+
} = req;
|
|
377
|
+
// check if the user exists
|
|
378
|
+
const userRecord = await sqldb.User.findOne({ where: { userId } });
|
|
379
|
+
if (!userRecord) return next({ status: 404, message: 'User does not exists' });
|
|
380
|
+
const userIntegrationSettings = await sqldb.UserIntegrationSettings.findOne({
|
|
381
|
+
where: { userId, integrationId },
|
|
382
|
+
});
|
|
383
|
+
if (!userIntegrationSettings) {
|
|
384
|
+
return res.json({ settings: {} });
|
|
385
|
+
}
|
|
386
|
+
return res.json({ settings: userIntegrationSettings.settings });
|
|
387
|
+
};
|
|
388
|
+
|
|
313
389
|
module.exports = plugins => ({
|
|
314
390
|
createAppToken,
|
|
391
|
+
createAppSettings,
|
|
392
|
+
getAppSettings,
|
|
315
393
|
deleteAppToken,
|
|
394
|
+
deleteAppSettings,
|
|
316
395
|
getAppScheduledJobs,
|
|
317
396
|
getJobStatus,
|
|
318
397
|
jwtEncode,
|
package/controllers/bookings.js
CHANGED
|
@@ -32,7 +32,7 @@ const bookingsSearch = plugins => async (req, res, next) => {
|
|
|
32
32
|
},
|
|
33
33
|
});
|
|
34
34
|
assert(userAppKeys, 'could not find the app key');
|
|
35
|
-
const token = userAppKeys.
|
|
35
|
+
const token = await userAppKeys.token;
|
|
36
36
|
const search = app.searchHotelBooking || app.searchBooking;
|
|
37
37
|
const results = await search({
|
|
38
38
|
token,
|
|
@@ -61,7 +61,7 @@ const bookingsCancel = plugins => async (req, res, next) => {
|
|
|
61
61
|
},
|
|
62
62
|
}));
|
|
63
63
|
assert(userAppKeys, 'could not find the app key');
|
|
64
|
-
const token = userAppKeys.
|
|
64
|
+
const token = await userAppKeys.token;
|
|
65
65
|
const results = await app.cancelBooking({
|
|
66
66
|
token,
|
|
67
67
|
payload: body,
|
|
@@ -89,7 +89,7 @@ const $bookingsProductSearch = plugins => async ({
|
|
|
89
89
|
},
|
|
90
90
|
}));
|
|
91
91
|
assert(userAppKeys, 'could not find the app key');
|
|
92
|
-
const token = userAppKeys.
|
|
92
|
+
const token = await userAppKeys.token;
|
|
93
93
|
const results = await app.searchProducts({
|
|
94
94
|
token,
|
|
95
95
|
payload,
|
|
@@ -126,7 +126,7 @@ const bookingsAvailabilitySearch = plugins => async (req, res, next) => {
|
|
|
126
126
|
},
|
|
127
127
|
}));
|
|
128
128
|
assert(userAppKeys, 'could not find the app key');
|
|
129
|
-
const token = userAppKeys.
|
|
129
|
+
const token = await userAppKeys.token;
|
|
130
130
|
const results = await app.searchAvailability({
|
|
131
131
|
token,
|
|
132
132
|
payload,
|
|
@@ -154,7 +154,7 @@ const $bookingsAvailabilityCalendar = plugins => async ({
|
|
|
154
154
|
},
|
|
155
155
|
}));
|
|
156
156
|
assert(userAppKeys, 'could not find the app key');
|
|
157
|
-
const token = userAppKeys.
|
|
157
|
+
const token = await userAppKeys.token;
|
|
158
158
|
return app.availabilityCalendar({
|
|
159
159
|
token,
|
|
160
160
|
payload,
|
|
@@ -190,7 +190,7 @@ const searchQuote = plugins => async (req, res, next) => {
|
|
|
190
190
|
},
|
|
191
191
|
}));
|
|
192
192
|
assert(userAppKeys, 'could not find the app key');
|
|
193
|
-
const token = userAppKeys.
|
|
193
|
+
const token = await userAppKeys.token;
|
|
194
194
|
assert(payload.id, 'the availability id is required');
|
|
195
195
|
const results = await app.searchQuote({
|
|
196
196
|
token,
|
|
@@ -219,7 +219,7 @@ const createBooking = plugins => async (req, res, next) => {
|
|
|
219
219
|
},
|
|
220
220
|
}));
|
|
221
221
|
assert(userAppKeys, 'could not find the app key');
|
|
222
|
-
const token = userAppKeys.
|
|
222
|
+
const token = await userAppKeys.token;
|
|
223
223
|
assert(payload.id, 'the quote id is required');
|
|
224
224
|
const results = await app.createBooking({
|
|
225
225
|
token,
|
package/lib/security.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
const { env: { dbCryptoKey } } = process;
|
|
4
|
+
|
|
5
|
+
const generateKey = () => {
|
|
6
|
+
const newKey = `${crypto.randomBytes(5).toString('base64').replace(/\W/g, '')}.${crypto.randomBytes(32).toString('base64').replace(/\W/g, '')}`;
|
|
7
|
+
return newKey;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const encrypt = text => {
|
|
11
|
+
const iv = crypto.randomBytes(16);
|
|
12
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(dbCryptoKey, 'base64'), iv);
|
|
13
|
+
let encrypted = cipher.update(text);
|
|
14
|
+
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
15
|
+
return `${iv.toString('hex')}%${encrypted.toString('hex')}`;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const decrypt = text => {
|
|
19
|
+
const iv = Buffer.from(text.split('%')[0], 'hex');
|
|
20
|
+
const encryptedText = Buffer.from(text.split('%')[1], 'hex');
|
|
21
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(dbCryptoKey, 'base64'), iv);
|
|
22
|
+
let decrypted = decipher.update(encryptedText);
|
|
23
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
24
|
+
return decrypted.toString();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
generateKey,
|
|
29
|
+
encrypt,
|
|
30
|
+
decrypt,
|
|
31
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
up: (queryInterface, Sequelize) => queryInterface
|
|
3
|
+
.createTable('UserIntegrationSettings', {
|
|
4
|
+
integrationId: {
|
|
5
|
+
allowNull: false,
|
|
6
|
+
primaryKey: true,
|
|
7
|
+
type: Sequelize.STRING,
|
|
8
|
+
},
|
|
9
|
+
userId: {
|
|
10
|
+
allowNull: false,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
type: Sequelize.STRING,
|
|
13
|
+
},
|
|
14
|
+
settings: {
|
|
15
|
+
allowNull: false,
|
|
16
|
+
type: Sequelize.TEXT,
|
|
17
|
+
},
|
|
18
|
+
createdAt: {
|
|
19
|
+
allowNull: false,
|
|
20
|
+
type: Sequelize.DATE,
|
|
21
|
+
},
|
|
22
|
+
updatedAt: {
|
|
23
|
+
allowNull: false,
|
|
24
|
+
type: Sequelize.DATE,
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
down: queryInterface => queryInterface.dropTable('UserIntegrationSettings'),
|
|
28
|
+
};
|
package/models/integration.js
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
1
|
const Sequelize = require('sequelize');
|
|
3
2
|
const db = require('./db');
|
|
4
|
-
|
|
5
|
-
const { env: { dbCryptoKey } } = process;
|
|
6
|
-
const generateKey = () => {
|
|
7
|
-
const newKey = `${crypto.randomBytes(5).toString('base64').replace(/\W/g, '')}.${crypto.randomBytes(32).toString('base64').replace(/\W/g, '')}`;
|
|
8
|
-
return newKey;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const encrypt = text => {
|
|
12
|
-
const iv = crypto.randomBytes(16);
|
|
13
|
-
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(dbCryptoKey, 'base64'), iv);
|
|
14
|
-
let encrypted = cipher.update(text);
|
|
15
|
-
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
16
|
-
return `${iv.toString('hex')}%${encrypted.toString('hex')}`;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const decrypt = text => {
|
|
20
|
-
const iv = Buffer.from(text.split('%')[0], 'hex');
|
|
21
|
-
const encryptedText = Buffer.from(text.split('%')[1], 'hex');
|
|
22
|
-
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(dbCryptoKey, 'base64'), iv);
|
|
23
|
-
let decrypted = decipher.update(encryptedText);
|
|
24
|
-
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
25
|
-
return decrypted.toString();
|
|
26
|
-
};
|
|
3
|
+
const { generateKey, encrypt, decrypt } = require('../lib/security');
|
|
27
4
|
|
|
28
5
|
const Integration = db.define('Integration', {
|
|
29
6
|
name: {
|
|
@@ -65,6 +42,9 @@ Integration.associate = models => {
|
|
|
65
42
|
Integration.hasMany(models.UserAppKey, {
|
|
66
43
|
foreignKey: 'integrationId',
|
|
67
44
|
});
|
|
45
|
+
Integration.hasMany(models.UserIntegrationSettings, {
|
|
46
|
+
foreignKey: 'integrationId',
|
|
47
|
+
});
|
|
68
48
|
};
|
|
69
49
|
Integration.generateKey = generateKey;
|
|
70
50
|
|
package/models/user.js
CHANGED
package/models/userappkey.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const jwt = require('jsonwebtoken');
|
|
2
2
|
const Sequelize = require('sequelize');
|
|
3
|
-
const { omit } = require('ramda');
|
|
3
|
+
const { omit, pathOr } = require('ramda');
|
|
4
4
|
const db = require('./db');
|
|
5
5
|
|
|
6
6
|
const { env: { dbCryptoKey } } = process;
|
|
@@ -45,6 +45,16 @@ const UserAppKey = db.define('UserAppKey', {
|
|
|
45
45
|
return omit(['iat'], retValue);
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
|
+
token: {
|
|
49
|
+
type: Sequelize.VIRTUAL,
|
|
50
|
+
async get() {
|
|
51
|
+
const sqldb = require('./index');
|
|
52
|
+
const userIntegrationSettings = await sqldb.UserIntegrationSettings.findOne({
|
|
53
|
+
where: { userId: this.getDataValue('userId'), integrationId: this.getDataValue('integrationId') },
|
|
54
|
+
});
|
|
55
|
+
return { ...pathOr({}, ['settings'], userIntegrationSettings), ...this.appKey };
|
|
56
|
+
},
|
|
57
|
+
},
|
|
48
58
|
}, {});
|
|
49
59
|
UserAppKey.associate = models => {
|
|
50
60
|
UserAppKey.belongsTo(models.User, {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const Sequelize = require('sequelize');
|
|
2
|
+
const db = require('./db');
|
|
3
|
+
const { encrypt, decrypt } = require('../lib/security');
|
|
4
|
+
|
|
5
|
+
const UserIntegrationSettings = db.define('UserIntegrationSettings', {
|
|
6
|
+
userId: {
|
|
7
|
+
primaryKey: true,
|
|
8
|
+
type: Sequelize.STRING,
|
|
9
|
+
allowNull: false,
|
|
10
|
+
validate: {
|
|
11
|
+
is: /^((?!true|false|TRUE|FALSE).){1,255}$/,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
integrationId: {
|
|
15
|
+
primaryKey: true,
|
|
16
|
+
type: Sequelize.STRING,
|
|
17
|
+
allowNull: false,
|
|
18
|
+
validate: {
|
|
19
|
+
is: /^((?!true|false|TRUE|FALSE).){1,255}$/,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
settings: {
|
|
23
|
+
type: Sequelize.TEXT,
|
|
24
|
+
allowNull: false,
|
|
25
|
+
},
|
|
26
|
+
}, {
|
|
27
|
+
getterMethods: {
|
|
28
|
+
settings() {
|
|
29
|
+
if (!this.getDataValue('settings')) { // there is no setting
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
return JSON.parse(decrypt(this.getDataValue('settings')));
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
setterMethods: {
|
|
36
|
+
settings(value) {
|
|
37
|
+
this.setDataValue('settings', encrypt(JSON.stringify(value)));
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
UserIntegrationSettings.associate = models => {
|
|
43
|
+
UserIntegrationSettings.belongsTo(models.User, {
|
|
44
|
+
foreignKey: 'userId',
|
|
45
|
+
as: 'user',
|
|
46
|
+
});
|
|
47
|
+
UserIntegrationSettings.belongsTo(models.Integration, {
|
|
48
|
+
foreignKey: 'integrationId',
|
|
49
|
+
as: 'integration',
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
module.exports = UserIntegrationSettings;
|