ti2 1.0.104 → 1.0.106
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/controllers/__tests__/app.js +47 -2
- package/index.js +1 -1
- package/models/__tests__/userappkey.js +35 -0
- package/models/userappkey.js +54 -16
- package/package.json +1 -1
|
@@ -47,7 +47,7 @@ describe('app', () => {
|
|
|
47
47
|
}));
|
|
48
48
|
expect(appKey).toBeTruthy();
|
|
49
49
|
});
|
|
50
|
-
it('should encode an
|
|
50
|
+
it('should encode an arbitrary object', async () => {
|
|
51
51
|
({ value: encodedKey } = await doApiPut({
|
|
52
52
|
url: `/app/encode/${appName}`,
|
|
53
53
|
token: appKey,
|
|
@@ -96,7 +96,7 @@ describe('app', () => {
|
|
|
96
96
|
cron: '0 9 * * *',
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
|
-
it.todo('if a new version of the plugin has a different set of scheduled tasks, they
|
|
99
|
+
it.todo('if a new version of the plugin has a different set of scheduled tasks, they should be re-synced');
|
|
100
100
|
let jobId;
|
|
101
101
|
it('should be able to run a job', async () => {
|
|
102
102
|
let status;
|
|
@@ -142,4 +142,49 @@ describe('app', () => {
|
|
|
142
142
|
]),
|
|
143
143
|
);
|
|
144
144
|
});
|
|
145
|
+
|
|
146
|
+
describe('token and settings merge', () => {
|
|
147
|
+
const hint = apiKey.split('-')[0];
|
|
148
|
+
const settingsOverrideEndpoint = 'https://settings-override.example.com';
|
|
149
|
+
|
|
150
|
+
it('getAppToken returns token with appKey payload (endpoint, apiKey)', async () => {
|
|
151
|
+
// No settings created yet – token is purely from appKey.
|
|
152
|
+
const body = await doApiGet({
|
|
153
|
+
url: `/${appName}/${userId}/${hint}/token`,
|
|
154
|
+
token: appKey,
|
|
155
|
+
payload: {},
|
|
156
|
+
});
|
|
157
|
+
expect(body.token).toEqual(expect.objectContaining({
|
|
158
|
+
endpoint: token.endpoint,
|
|
159
|
+
apiKey: token.apiKey,
|
|
160
|
+
}));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('createAppSettings updates user settings for the app', async () => {
|
|
164
|
+
await doApiPost({
|
|
165
|
+
url: `/settings/${appName}/${userId}`,
|
|
166
|
+
token: adminKey,
|
|
167
|
+
payload: {
|
|
168
|
+
settings: {
|
|
169
|
+
endpoint: settingsOverrideEndpoint,
|
|
170
|
+
onlyInSettings: true,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('getAppToken returns merged token with settings overriding appKey for same keys', async () => {
|
|
177
|
+
const body = await doApiGet({
|
|
178
|
+
url: `/${appName}/${userId}/${hint}/token`,
|
|
179
|
+
token: appKey,
|
|
180
|
+
payload: {},
|
|
181
|
+
});
|
|
182
|
+
// Settings override: endpoint from appKey is overridden by UserIntegrationSettings.settings
|
|
183
|
+
expect(body.token.endpoint).toBe(settingsOverrideEndpoint);
|
|
184
|
+
// Settings add new keys
|
|
185
|
+
expect(body.token.onlyInSettings).toBe(true);
|
|
186
|
+
// appKey-only keys still present
|
|
187
|
+
expect(body.token.apiKey).toBe(token.apiKey);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
145
190
|
});
|
package/index.js
CHANGED
|
@@ -257,7 +257,7 @@ module.exports = async ({
|
|
|
257
257
|
requestId: req.requestId,
|
|
258
258
|
err: errMsg,
|
|
259
259
|
...(err.request ? { request: axiosSafeRequest(err.request) } : {}),
|
|
260
|
-
...(err.response ? { response:
|
|
260
|
+
...(err.response ? { response: axiosSafeResponse(err.response) } : {}),
|
|
261
261
|
});
|
|
262
262
|
}
|
|
263
263
|
return Promise.reject(errMsg);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* globals describe it expect */
|
|
2
|
+
const { UserAppKey } = require('../index');
|
|
3
|
+
|
|
4
|
+
describe('UserAppKey.mergeTokenPayload', () => {
|
|
5
|
+
it('uses appKey as base, then settings override same keys, then configuration', () => {
|
|
6
|
+
const appKey = { endpoint: 'https://old.example.com', apiKey: 'secret' };
|
|
7
|
+
const settings = { endpoint: 'https://updated.example.com', onlyInSettings: true };
|
|
8
|
+
const configuration = { foo: 'bar' };
|
|
9
|
+
|
|
10
|
+
const result = UserAppKey.mergeTokenPayload(appKey, settings, configuration);
|
|
11
|
+
|
|
12
|
+
expect(result.endpoint).toBe('https://updated.example.com');
|
|
13
|
+
expect(result.onlyInSettings).toBe(true);
|
|
14
|
+
expect(result.apiKey).toBe('secret');
|
|
15
|
+
expect(result.configuration).toEqual({ foo: 'bar' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns only appKey when settings and configuration are empty', () => {
|
|
19
|
+
const appKey = { access_token: 'x', refresh_token: 'y' };
|
|
20
|
+
const result = UserAppKey.mergeTokenPayload(appKey, {}, null);
|
|
21
|
+
expect(result).toEqual({ access_token: 'x', refresh_token: 'y' });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('strips empty and nil values from appKey', () => {
|
|
25
|
+
const appKey = { a: 1, b: '', c: null, d: [] };
|
|
26
|
+
const result = UserAppKey.mergeTokenPayload(appKey, {}, null);
|
|
27
|
+
expect(result).toEqual({ a: 1 });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('handles null/undefined appKey or settings', () => {
|
|
31
|
+
expect(UserAppKey.mergeTokenPayload(null, { x: 1 }, null)).toEqual({ x: 1 });
|
|
32
|
+
expect(UserAppKey.mergeTokenPayload({ a: 1 }, null, null)).toEqual({ a: 1 });
|
|
33
|
+
expect(UserAppKey.mergeTokenPayload({}, {}, undefined)).toEqual({});
|
|
34
|
+
});
|
|
35
|
+
});
|
package/models/userappkey.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const Sequelize = require('sequelize');
|
|
2
2
|
const R = require('ramda');
|
|
3
|
+
const jwt = require('jsonwebtoken');
|
|
3
4
|
const { encrypt, decrypt } = require('../lib/security');
|
|
4
5
|
const buildCorruptedTokenError = require('../lib/corrupted-token-error');
|
|
5
6
|
const db = require('./db');
|
|
@@ -53,28 +54,52 @@ const UserAppKey = db.define('UserAppKey', {
|
|
|
53
54
|
if (!encryptedAppKey) return {};
|
|
54
55
|
|
|
55
56
|
let appKey;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
if (encryptedAppKey.includes('%')) {
|
|
58
|
+
try {
|
|
59
|
+
const decryptedAppKey = decrypt(encryptedAppKey);
|
|
60
|
+
if (!decryptedAppKey) return {};
|
|
61
|
+
appKey = JSON.parse(decryptedAppKey);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
throw buildCorruptedTokenError({
|
|
64
|
+
integrationId: this.getDataValue('integrationId'),
|
|
65
|
+
userId: this.getDataValue('userId'),
|
|
66
|
+
hint: this.getDataValue('hint'),
|
|
67
|
+
cause: err,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// Legacy unencrypted JWT stored in appKey – decode payload as token object
|
|
72
|
+
try {
|
|
73
|
+
const decodedAppKey = jwt.decode(encryptedAppKey);
|
|
74
|
+
if (!decodedAppKey) {
|
|
75
|
+
throw buildCorruptedTokenError({
|
|
76
|
+
integrationId: this.getDataValue('integrationId'),
|
|
77
|
+
userId: this.getDataValue('userId'),
|
|
78
|
+
hint: this.getDataValue('hint'),
|
|
79
|
+
cause: new Error('Invalid or corrupted JWT'),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
appKey = decodedAppKey;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (err.name === 'CorruptedTokenError') throw err;
|
|
85
|
+
throw buildCorruptedTokenError({
|
|
86
|
+
integrationId: this.getDataValue('integrationId'),
|
|
87
|
+
userId: this.getDataValue('userId'),
|
|
88
|
+
hint: this.getDataValue('hint'),
|
|
89
|
+
cause: err,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
67
92
|
}
|
|
68
93
|
|
|
69
94
|
const sqldb = require('./index');
|
|
70
95
|
const userIntegrationSettings = await sqldb.UserIntegrationSettings.findOne({
|
|
71
96
|
where: { userId: this.getDataValue('userId'), integrationId: this.getDataValue('integrationId') },
|
|
72
97
|
});
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
98
|
+
return UserAppKey.mergeTokenPayload(
|
|
99
|
+
appKey,
|
|
100
|
+
R.pathOr({}, ['settings'], userIntegrationSettings),
|
|
101
|
+
this.getDataValue('configuration'),
|
|
102
|
+
);
|
|
78
103
|
},
|
|
79
104
|
},
|
|
80
105
|
configuration: {
|
|
@@ -82,6 +107,19 @@ const UserAppKey = db.define('UserAppKey', {
|
|
|
82
107
|
allowNull: true,
|
|
83
108
|
},
|
|
84
109
|
}, {});
|
|
110
|
+
|
|
111
|
+
/** Merge order: appKey base, then settings (so updated UserIntegrationSettings are used), then configuration. */
|
|
112
|
+
UserAppKey.mergeTokenPayload = (appKey, settings, configuration) => {
|
|
113
|
+
const base = appKey ? removeEmptyAttributes(appKey) : {};
|
|
114
|
+
const settingsObj = settings || {};
|
|
115
|
+
const configPart = configuration != null ? { configuration } : {};
|
|
116
|
+
return {
|
|
117
|
+
...base,
|
|
118
|
+
...settingsObj,
|
|
119
|
+
...configPart,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
85
123
|
UserAppKey.associate = models => {
|
|
86
124
|
UserAppKey.belongsTo(models.User, {
|
|
87
125
|
foreignKey: 'userId',
|