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 +69 -1
- package/cli.js +20 -0
- package/controllers/__tests__/user.js +28 -1
- package/controllers/app.js +25 -0
- package/migrations/20221114141152-encryptUserToken.js +45 -0
- package/models/userappkey.js +12 -7
- package/package.json +1 -1
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
|
|
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,
|
package/controllers/app.js
CHANGED
|
@@ -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
|
+
};
|
package/models/userappkey.js
CHANGED
|
@@ -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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
56
|
+
const appKey = JSON.parse(decrypt(this.getDataValue('appKey')));
|
|
57
|
+
return {
|
|
58
|
+
...pathOr({}, ['settings'], userIntegrationSettings),
|
|
59
|
+
...appKey,
|
|
60
|
+
};
|
|
56
61
|
},
|
|
57
62
|
},
|
|
58
63
|
}, {});
|