ti2 1.0.39 → 1.0.42

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.
@@ -1,6 +1,6 @@
1
1
  name: Tests Workflow
2
2
 
3
- on: [workflow_dispatch, push, pull_request]
3
+ on: [workflow_dispatch, push]
4
4
 
5
5
  jobs:
6
6
  test:
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:
@@ -334,6 +339,13 @@ components:
334
339
  id:
335
340
  type: string
336
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
337
349
  paths:
338
350
  /ping:
339
351
  get:
@@ -623,7 +635,75 @@ paths:
623
635
  content:
624
636
  application/json:
625
637
  schema: '#/components/schemas/Message'
626
-
638
+ /{app}/{userId}/{hint}/token:
639
+ get:
640
+ tags:
641
+ - app
642
+ - user
643
+ - admin
644
+ summary: Get a user app+Token configuration, the authorization can be either from the app or the user
645
+ security:
646
+ - bearerAuth: ['app', 'user', 'admin']
647
+ operationId: getAppToken
648
+ parameters:
649
+ - name: app
650
+ in: path
651
+ description: app name to read the userId for (must match app token)
652
+ example: 'expedia'
653
+ required: true
654
+ schema:
655
+ type: string
656
+ - name: hint
657
+ in: path
658
+ description: a specific token hint to be used
659
+ required: true
660
+ example: 'travelgate-australia'
661
+ schema:
662
+ type: string
663
+ - name: userId
664
+ in: path
665
+ description: user to read the app token for
666
+ example: '536830b6ed19afa44a000002'
667
+ required: true
668
+ schema:
669
+ type: string
670
+ responses:
671
+ 200:
672
+ description: success
673
+ content:
674
+ application/json:
675
+ schema: '#/components/schemas/UserApp'
676
+ /{app}/{userId}/token:
677
+ get:
678
+ tags:
679
+ - app
680
+ - user
681
+ - admin
682
+ summary: Get a user app+Token configuration, the authorization can be either from the app or the user, it will return the first token found (since no hint is required)
683
+ security:
684
+ - bearerAuth: ['app', 'user', 'admin']
685
+ operationId: getAppToken
686
+ parameters:
687
+ - name: app
688
+ in: path
689
+ description: app name to read the userId for (must match app token)
690
+ example: 'expedia'
691
+ required: true
692
+ schema:
693
+ type: string
694
+ - name: userId
695
+ in: path
696
+ description: user to read the app token for
697
+ example: '536830b6ed19afa44a000002'
698
+ required: true
699
+ schema:
700
+ type: string
701
+ responses:
702
+ 200:
703
+ description: success
704
+ content:
705
+ application/json:
706
+ schema: '#/components/schemas/UserApp'
627
707
  /{app}/{userId}/validate:
628
708
  post:
629
709
  tags:
@@ -828,6 +908,98 @@ paths:
828
908
  content:
829
909
  appication/json:
830
910
  schema: '#/components/schemas/appMethods'
911
+ /settings/{appKey}/{userId}:
912
+ post:
913
+ security:
914
+ - bearerAuth: ['admin', 'user']
915
+ tags:
916
+ - user
917
+ summary: Creates user settings for an app, shared across tokens per user
918
+ operationId: createAppSettings
919
+ parameters:
920
+ - name: appKey
921
+ in: path
922
+ description: name of the app to use for the settings
923
+ required: true
924
+ example: 'expedia'
925
+ schema:
926
+ type: string
927
+ - name: userId
928
+ in: path
929
+ description: user id for the setting to be created
930
+ required: true
931
+ example: '536830b6ed19afa44a000002'
932
+ schema:
933
+ type: string
934
+ requestBody:
935
+ description: Creation Payload
936
+ required: true
937
+ content:
938
+ application/json:
939
+ schema:
940
+ $ref: '#/components/schemas/settingsBody'
941
+ responses:
942
+ 200:
943
+ description: success
944
+ content:
945
+ application/json:
946
+ schema: '#/components/schemas/Success'
947
+ get:
948
+ security:
949
+ - bearerAuth: ['admin', 'user']
950
+ tags:
951
+ - user
952
+ summary: Retrieves a user settings for an app, shared across tokens per user
953
+ operationId: getAppSettings
954
+ parameters:
955
+ - name: appKey
956
+ in: path
957
+ description: name of the app to use for the settings
958
+ required: true
959
+ example: 'expedia'
960
+ schema:
961
+ type: string
962
+ - name: userId
963
+ in: path
964
+ description: user id for the setting to be created
965
+ required: true
966
+ example: '536830b6ed19afa44a000002'
967
+ schema:
968
+ type: string
969
+ responses:
970
+ 200:
971
+ description: success
972
+ content:
973
+ application/json:
974
+ schema: '#/components/schemas/settingsBody'
975
+ delete:
976
+ security:
977
+ - bearerAuth: ['admin', 'user']
978
+ tags:
979
+ - user
980
+ summary: Deletes a user settings for an app, shared across tokens per user
981
+ operationId: deleteAppSettings
982
+ parameters:
983
+ - name: appKey
984
+ in: path
985
+ description: name of the app to use for the settings
986
+ required: true
987
+ example: 'expedia'
988
+ schema:
989
+ type: string
990
+ - name: userId
991
+ in: path
992
+ description: user id for the setting to be created
993
+ required: true
994
+ example: '536830b6ed19afa44a000002'
995
+ schema:
996
+ type: string
997
+ responses:
998
+ 200:
999
+ description: success
1000
+ content:
1001
+ application/json:
1002
+ $ref: '#/components/schemas/Status'
831
1003
  /bookings/{appKey}/{userId}/search:
832
1004
  post:
833
1005
  security:
package/cli.js CHANGED
@@ -4,6 +4,7 @@ const { Command, Argument } = require('commander');
4
4
 
5
5
  const { migrateApp } = require('./controllers/app')();
6
6
  const { migrate } = require('./controllers/admin');
7
+ const { decrypt, encrypt } = require('./lib/security');
7
8
 
8
9
  const program = new Command();
9
10
 
@@ -35,4 +36,23 @@ program.command('db')
35
36
  process.exit(0);
36
37
  });
37
38
 
39
+ program.command('decrypt')
40
+ .description('decrypt a string')
41
+ .addArgument(
42
+ new Argument('<key>', 'key to decrypt'),
43
+ )
44
+ .action(async key => {
45
+ console.log(await decrypt(key));
46
+ process.exit(0);
47
+ });
48
+ program.command('encrypt')
49
+ .description('encrypt a string')
50
+ .addArgument(
51
+ new Argument('<key>', 'key to encrypt'),
52
+ )
53
+ .action(async key => {
54
+ console.log(await encrypt(key));
55
+ process.exit(0);
56
+ });
57
+
38
58
  program.parse();
@@ -76,7 +76,6 @@ describe('app', () => {
76
76
  token: appKey,
77
77
  payload: {
78
78
  tokenHint: apiKey.split('-')[0],
79
- token,
80
79
  },
81
80
  });
82
81
  expect(valid).toBe(true);
@@ -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,71 @@ describe('user', () => {
133
135
  ]),
134
136
  );
135
137
  });
138
+ const apiKey2 = chance.guid();
139
+ const token2 = { ...token };
140
+ it('create a second token', async () => {
141
+ // set up new token
142
+ await doApiPost({
143
+ url: `/${appName}/${userId}`,
144
+ token: userKey,
145
+ payload: {
146
+ tokenHint: apiKey2.split('-')[1],
147
+ token: token2,
148
+ },
149
+ });
150
+ });
151
+ it('should be able to read a user/app token', async () => {
152
+ const returnValue = await doApiGet({
153
+ url: `/${appName}/${userId}/token`,
154
+ token: userKey,
155
+ });
156
+ expect(returnValue.token).toEqual(token);
157
+ });
158
+ it('should be able to read the second user/app token by hint', async () => {
159
+ const returnValue = await doApiGet({
160
+ url: `/${appName}/${userId}/${apiKey2.split('-')[1]}/token`,
161
+ token: userKey,
162
+ });
163
+ expect(returnValue.token).toEqual(token2);
164
+ });
165
+ it('should be able to create an app setting', async () => {
166
+ const returnValue = await doApiPost({
167
+ url: `/settings/${appName}/${userId}`,
168
+ token: userKey,
169
+ payload: {
170
+ settings: {
171
+ custom: true,
172
+ },
173
+ },
174
+ });
175
+ expect(returnValue.success).toBe(true);
176
+ });
177
+ it('should be able to get an app settings', async () => {
178
+ const returnValue = await doApiGet({
179
+ url: `/settings/${appName}/${userId}`,
180
+ token: userKey,
181
+ });
182
+ expect(returnValue.settings.custom).toBe(true);
183
+ });
184
+ it('testing the user token should include the user app setting', async () => {
185
+ const { valid } = await doApiPost({
186
+ url: `/${appName}/${userId}/validate`,
187
+ token: userKey,
188
+ payload: {
189
+ tokenHint: apiKey.split('-')[0],
190
+ },
191
+ });
192
+ expect(plugins[0].validateToken).toHaveBeenCalled();
193
+ expect(valid).toBe(true);
194
+ expect(plugins[0].validateToken.mock.calls[0][0].token).toEqual({ custom: true, ...token });
195
+ });
196
+ it('should be able to delete an app settings', async () => {
197
+ const returnValue = await doApiDelete({
198
+ url: `/settings/${appName}/${userId}`,
199
+ token: userKey,
200
+ });
201
+ expect(returnValue.success).toBe(true);
202
+ });
136
203
  it('should be able to get all the methods for an app', async () => {
137
204
  const { methods } = await doApiGet({
138
205
  url: `/app/${appName}/methods`,
@@ -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.appKey;
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,
@@ -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 alrady has the same app with the same hint
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.appKey;
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,114 @@ 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
+
389
+ const getAppToken = async (req, res, next) => {
390
+ const {
391
+ params: {
392
+ app: integrationId,
393
+ userId,
394
+ hint,
395
+ },
396
+ } = req;
397
+ // check if the user exists
398
+ const userRecord = await sqldb.User.findOne({ where: { userId } });
399
+ if (!userRecord) return next({ status: 404, message: 'User does not exists' });
400
+ const userAppKey = await sqldb.UserAppKey.findOne({
401
+ where: {
402
+ userId,
403
+ integrationId,
404
+ ...(hint ? { hint } : {}),
405
+ },
406
+ });
407
+ if (!userAppKey) {
408
+ return next({ status: 404, message: 'User integratio is not found' });
409
+ }
410
+ return res.json({ token: await userAppKey.token });
411
+ };
412
+
313
413
  module.exports = plugins => ({
314
414
  createAppToken,
415
+ getAppToken,
416
+ createAppSettings,
417
+ getAppSettings,
315
418
  deleteAppToken,
419
+ deleteAppSettings,
316
420
  getAppScheduledJobs,
317
421
  getJobStatus,
318
422
  jwtEncode,
@@ -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.appKey;
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.appKey;
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.appKey;
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.appKey;
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.appKey;
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.appKey;
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.appKey;
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,
@@ -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
+ };
@@ -0,0 +1,45 @@
1
+ const jwt = require('jsonwebtoken');
2
+ const R = require('ramda');
3
+ const assert = require('assert');
4
+ const sqldb = require('../models/index');
5
+
6
+ const { encrypt, decrypt } = require('../lib/security');
7
+
8
+ const { env: { dbCryptoKey } } = process;
9
+
10
+ module.exports = {
11
+ up: async () => {
12
+ // encrypt all data
13
+ const existingTokens = await sqldb.UserAppKey.findAll();
14
+ const valuesToUpdate = [];
15
+ for (const currentToken of existingTokens) {
16
+ const decodedToken = JSON.stringify(R.omit(
17
+ ['iat'],
18
+ jwt.verify(currentToken.dataValues.appKey, dbCryptoKey),
19
+ ));
20
+ const encryptedToken = encrypt(decodedToken);
21
+ // make sure we can decrypt it
22
+ const decodedValue = decrypt(encryptedToken);
23
+ assert(R.equals(decodedToken, decodedValue));
24
+ currentToken.appKey = encryptedToken;
25
+ valuesToUpdate.push({
26
+ id: currentToken.id,
27
+ appToken: encryptedToken,
28
+ });
29
+ }
30
+ for (const { id, appToken } of valuesToUpdate) {
31
+ const currentToken = await sqldb.UserAppKey.findOne({ where: { id } });
32
+ currentToken.appKey = appToken;
33
+ await currentToken.save();
34
+ }
35
+ },
36
+ down: async () => {
37
+ const existingTokens = await sqldb.UserAppKey.findAll();
38
+ for (const currentToken of existingTokens) {
39
+ const decodedToken = decrypt(currentToken.dataValues.appKey);
40
+ const jwtToken = jwt.sign(decodedToken, dbCryptoKey);
41
+ currentToken.appKey = jwtToken;
42
+ await currentToken.save();
43
+ }
44
+ },
45
+ };
@@ -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
@@ -11,6 +11,9 @@ User.associate = models => {
11
11
  User.hasMany(models.UserAppKey, {
12
12
  foreignKey: 'userId',
13
13
  });
14
+ User.hasMany(models.UserIntegrationSettings, {
15
+ foreignKey: 'userId',
16
+ });
14
17
  // associations can be defined here
15
18
  };
16
19
 
@@ -1,6 +1,7 @@
1
1
  const jwt = require('jsonwebtoken');
2
2
  const Sequelize = require('sequelize');
3
- const { omit } = require('ramda');
3
+ const { omit, pathOr } = require('ramda');
4
+ const { encrypt, decrypt } = require('../lib/security');
4
5
  const db = require('./db');
5
6
 
6
7
  const { env: { dbCryptoKey } } = process;
@@ -37,12 +38,26 @@ const UserAppKey = db.define('UserAppKey', {
37
38
  type: Sequelize.TEXT,
38
39
  allowNull: false,
39
40
  set: function setter(value, field) {
40
- const newValue = jwt.sign(value, dbCryptoKey);
41
- this.setDataValue(field, newValue);
41
+ if (typeof value === 'object') {
42
+ const newValue = encrypt(JSON.stringify(value));
43
+ this.setDataValue(field, newValue);
44
+ } else {
45
+ this.setDataValue(field, value);
46
+ }
42
47
  },
43
- get: function getter(field) {
44
- const retValue = jwt.verify(this.getDataValue(field), dbCryptoKey);
45
- return omit(['iat'], retValue);
48
+ },
49
+ token: {
50
+ type: Sequelize.VIRTUAL,
51
+ async get() {
52
+ const sqldb = require('./index');
53
+ const userIntegrationSettings = await sqldb.UserIntegrationSettings.findOne({
54
+ where: { userId: this.getDataValue('userId'), integrationId: this.getDataValue('integrationId') },
55
+ });
56
+ const appKey = JSON.parse(decrypt(this.getDataValue('appKey')));
57
+ return {
58
+ ...pathOr({}, ['settings'], userIntegrationSettings),
59
+ ...appKey,
60
+ };
46
61
  },
47
62
  },
48
63
  }, {});
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2",
3
- "version": "1.0.39",
3
+ "version": "1.0.42",
4
4
  "description": "Tourist Industry Exchange (TI2)",
5
5
  "main": "index.js",
6
6
  "scripts": {