ti2 1.0.104 → 1.0.107

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.
@@ -47,7 +47,7 @@ describe('app', () => {
47
47
  }));
48
48
  expect(appKey).toBeTruthy();
49
49
  });
50
- it('should encode an airbitrary object', async () => {
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 shoul dbe re-syncjed');
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
  });
@@ -51,13 +51,19 @@ const tokenTemplate = async (req, res, next) => {
51
51
  const thePlugin = req.app.plugins.find(({ name }) => name === pluginName);
52
52
  assert(thePlugin);
53
53
  let template = thePlugin.tokenTemplate();
54
- const safeRegExp = el => ({
55
- ...el,
56
- regExp: {
57
- flags: el.regExp.flags,
58
- source: el.regExp.source,
59
- },
60
- });
54
+ const safeRegExp = el => {
55
+ if (!el || !(el.regExp instanceof RegExp)) {
56
+ return el;
57
+ }
58
+
59
+ return {
60
+ ...el,
61
+ regExp: {
62
+ flags: el.regExp.flags,
63
+ source: el.regExp.source,
64
+ },
65
+ };
66
+ };
61
67
  template = R.map(safeRegExp, template);
62
68
  return res.json({
63
69
  template: {
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: axiosSafeRequest(err.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
+ });
@@ -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
- try {
57
- const decryptedAppKey = decrypt(encryptedAppKey);
58
- if (!decryptedAppKey) return {};
59
- appKey = JSON.parse(decryptedAppKey);
60
- } catch (err) {
61
- throw buildCorruptedTokenError({
62
- integrationId: this.getDataValue('integrationId'),
63
- userId: this.getDataValue('userId'),
64
- hint: this.getDataValue('hint'),
65
- cause: err,
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
- ...R.pathOr({}, ['settings'], userIntegrationSettings),
75
- ...removeEmptyAttributes(appKey),
76
- ...(this.getDataValue('configuration') ? { configuration: this.getDataValue('configuration') } : {}),
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2",
3
- "version": "1.0.104",
3
+ "version": "1.0.107",
4
4
  "description": "Tourist Industry Exchange (TI2)",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test/plugin.js CHANGED
@@ -123,6 +123,11 @@ class Plugin {
123
123
  regExp: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/,
124
124
  description: 'Api Key',
125
125
  },
126
+ testMode: {
127
+ type: 'boolean',
128
+ default: false,
129
+ description: 'Use test mode',
130
+ },
126
131
  }));
127
132
  this.errorPathsAxiosErrors = jestPlugin.fn(() => ([
128
133
  ['response', 'data', 'what'],