ti2 1.0.41 → 1.0.43

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/api.yml CHANGED
@@ -635,7 +635,75 @@ paths:
635
635
  content:
636
636
  application/json:
637
637
  schema: '#/components/schemas/Message'
638
-
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'
639
707
  /{app}/{userId}/validate:
640
708
  post:
641
709
  tags:
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();
@@ -135,6 +135,33 @@ describe('user', () => {
135
135
  ]),
136
136
  );
137
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
+ });
138
165
  it('should be able to create an app setting', async () => {
139
166
  const returnValue = await doApiPost({
140
167
  url: `/settings/${appName}/${userId}`,
@@ -154,7 +181,7 @@ describe('user', () => {
154
181
  });
155
182
  expect(returnValue.settings.custom).toBe(true);
156
183
  });
157
- it('testing the user token should include the user app seting', async () => {
184
+ it('testing the user token should include the user app setting', async () => {
158
185
  const { valid } = await doApiPost({
159
186
  url: `/${appName}/${userId}/validate`,
160
187
  token: userKey,
@@ -386,8 +386,33 @@ const getAppSettings = async (req, res, next) => {
386
386
  return res.json({ settings: userIntegrationSettings.settings });
387
387
  };
388
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
+
389
413
  module.exports = plugins => ({
390
414
  createAppToken,
415
+ getAppToken,
391
416
  createAppSettings,
392
417
  getAppSettings,
393
418
  deleteAppToken,
@@ -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.setDataValue('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.setDataValue('appKey', jwtToken);
42
+ await currentToken.save();
43
+ }
44
+ },
45
+ };
@@ -1,6 +1,7 @@
1
1
  const jwt = require('jsonwebtoken');
2
2
  const Sequelize = require('sequelize');
3
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,12 @@ 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);
42
- },
43
- get: function getter(field) {
44
- const retValue = jwt.verify(this.getDataValue(field), dbCryptoKey);
45
- return omit(['iat'], retValue);
41
+ if (typeof value === 'object') {
42
+ const newValue = encrypt(JSON.stringify(value));
43
+ this.setDataValue(field, newValue);
44
+ } else {
45
+ this.setDataValue(field, encrypt(value));
46
+ }
46
47
  },
47
48
  },
48
49
  token: {
@@ -52,7 +53,11 @@ const UserAppKey = db.define('UserAppKey', {
52
53
  const userIntegrationSettings = await sqldb.UserIntegrationSettings.findOne({
53
54
  where: { userId: this.getDataValue('userId'), integrationId: this.getDataValue('integrationId') },
54
55
  });
55
- return { ...pathOr({}, ['settings'], userIntegrationSettings), ...this.appKey };
56
+ const appKey = JSON.parse(decrypt(this.getDataValue('appKey')));
57
+ return {
58
+ ...pathOr({}, ['settings'], userIntegrationSettings),
59
+ ...appKey,
60
+ };
56
61
  },
57
62
  },
58
63
  }, {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "Tourist Industry Exchange (TI2)",
5
5
  "main": "index.js",
6
6
  "scripts": {