ti2 1.0.39 → 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.
@@ -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:
@@ -828,6 +840,98 @@ paths:
828
840
  content:
829
841
  appication/json:
830
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'
831
935
  /bookings/{appKey}/{userId}/search:
832
936
  post:
833
937
  security:
@@ -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,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`,
@@ -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,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,
@@ -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
+ };
@@ -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,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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "Tourist Industry Exchange (TI2)",
5
5
  "main": "index.js",
6
6
  "scripts": {