cozy-harvest-lib 9.22.3 → 9.23.2
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/CHANGELOG.md +34 -0
- package/dist/components/InAppBrowser.js +109 -19
- package/dist/helpers/oauth.js +1 -1
- package/dist/helpers/oauth.spec.js +1 -1
- package/dist/services/biWebView.js +54 -12
- package/dist/services/biWebView.spec.js +67 -1
- package/package.json +3 -3
- package/src/components/InAppBrowser.jsx +62 -18
- package/src/helpers/oauth.js +1 -1
- package/src/helpers/oauth.spec.js +1 -1
- package/src/services/biWebView.js +45 -6
- package/src/services/biWebView.spec.js +30 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,40 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [9.23.2](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.23.1...cozy-harvest-lib@9.23.2) (2022-07-26)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Invalidate temporary token cache when there is a change of BI user ([0fa3893](https://github.com/cozy/cozy-libs/commit/0fa3893bb6ac3c02bc88d15dae79e3772a6aef97))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [9.23.1](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.23.0...cozy-harvest-lib@9.23.1) (2022-07-26)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* Google connector in web mode ([1777fcc](https://github.com/cozy/cozy-libs/commit/1777fccc6b9e1e581189ca4563f851b341a60644))
|
|
23
|
+
* OauthWindowInAppBrowser re-render ([334e7d8](https://github.com/cozy/cozy-libs/commit/334e7d8b7d777bf265803f81583a65f39fb0cabb))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# [9.23.0](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.22.3...cozy-harvest-lib@9.23.0) (2022-07-26)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Features
|
|
33
|
+
|
|
34
|
+
* Add BI aggregator releationship to BI accounts ([cb7a79c](https://github.com/cozy/cozy-libs/commit/cb7a79c6cd72a9f8e95ae71307f27f04b68f0e94))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
6
40
|
## [9.22.3](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.22.2...cozy-harvest-lib@9.22.3) (2022-07-26)
|
|
7
41
|
|
|
8
42
|
**Note:** Version bump only for package cozy-harvest-lib
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
2
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
-
import { useEffect } from 'react';
|
|
3
|
+
import React, { useEffect } from 'react';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
import { useWebviewIntent } from 'cozy-intent';
|
|
6
6
|
import logger from '../logger';
|
|
@@ -9,22 +9,27 @@ import { intentsApiProptype } from '../helpers/proptypes';
|
|
|
9
9
|
var InAppBrowser = function InAppBrowser(_ref) {
|
|
10
10
|
var url = _ref.url,
|
|
11
11
|
onClose = _ref.onClose,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return webviewIntent.call('showInAppBrowser', {
|
|
20
|
-
url: url
|
|
12
|
+
intentsApi = _ref.intentsApi;
|
|
13
|
+
|
|
14
|
+
if (intentsApi) {
|
|
15
|
+
return /*#__PURE__*/React.createElement(InAppBrowserWithIntentsApi, {
|
|
16
|
+
url: url,
|
|
17
|
+
onClose: onClose,
|
|
18
|
+
intentsApi: intentsApi
|
|
21
19
|
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
} else {
|
|
21
|
+
return /*#__PURE__*/React.createElement(InAppBrowserWithWebviewIntent, {
|
|
22
|
+
url: url,
|
|
23
|
+
onClose: onClose
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
var InAppBrowserWithWebviewIntent = function InAppBrowserWithWebviewIntent(_ref2) {
|
|
29
|
+
var url = _ref2.url,
|
|
30
|
+
onClose = _ref2.onClose;
|
|
31
|
+
var webviewIntent = useWebviewIntent();
|
|
32
|
+
var isReady = Boolean(webviewIntent);
|
|
28
33
|
useEffect(function () {
|
|
29
34
|
function insideEffect() {
|
|
30
35
|
return _insideEffect.apply(this, arguments);
|
|
@@ -45,19 +50,21 @@ var InAppBrowser = function InAppBrowser(_ref) {
|
|
|
45
50
|
_context.prev = 1;
|
|
46
51
|
logger.debug('url at the beginning: ', url);
|
|
47
52
|
_context.next = 5;
|
|
48
|
-
return fetchSessionCode
|
|
53
|
+
return webviewIntent.call('fetchSessionCode');
|
|
49
54
|
|
|
50
55
|
case 5:
|
|
51
56
|
sessionCode = _context.sent;
|
|
52
57
|
logger.debug('got session code', sessionCode);
|
|
53
58
|
iabUrl = new URL(url);
|
|
54
|
-
iabUrl.searchParams.append(
|
|
59
|
+
iabUrl.searchParams.append('session_code', sessionCode); // we need to decodeURIComponent since toString() encodes URL
|
|
55
60
|
// but native browser will also encode them.
|
|
56
61
|
|
|
57
62
|
urlToOpen = decodeURIComponent(iabUrl.toString());
|
|
58
63
|
logger.debug('url to open: ', urlToOpen);
|
|
59
64
|
_context.next = 13;
|
|
60
|
-
return showInAppBrowser
|
|
65
|
+
return webviewIntent.call('showInAppBrowser', {
|
|
66
|
+
url: urlToOpen
|
|
67
|
+
});
|
|
61
68
|
|
|
62
69
|
case 13:
|
|
63
70
|
result = _context.sent;
|
|
@@ -89,6 +96,89 @@ var InAppBrowser = function InAppBrowser(_ref) {
|
|
|
89
96
|
return _insideEffect.apply(this, arguments);
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
insideEffect();
|
|
100
|
+
return function cleanup() {
|
|
101
|
+
webviewIntent.call('closeInAppBrowser');
|
|
102
|
+
};
|
|
103
|
+
}, [isReady, url, onClose, webviewIntent]);
|
|
104
|
+
return null;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
var InAppBrowserWithIntentsApi = function InAppBrowserWithIntentsApi(_ref3) {
|
|
108
|
+
var url = _ref3.url,
|
|
109
|
+
onClose = _ref3.onClose,
|
|
110
|
+
_ref3$intentsApi = _ref3.intentsApi,
|
|
111
|
+
intentsApi = _ref3$intentsApi === void 0 ? {} : _ref3$intentsApi;
|
|
112
|
+
var fetchSessionCode = intentsApi.fetchSessionCode,
|
|
113
|
+
showInAppBrowser = intentsApi.showInAppBrowser,
|
|
114
|
+
closeInAppBrowser = intentsApi.closeInAppBrowser,
|
|
115
|
+
_intentsApi$tokenPara = intentsApi.tokenParamName,
|
|
116
|
+
tokenParamName = _intentsApi$tokenPara === void 0 ? 'session_code' : _intentsApi$tokenPara;
|
|
117
|
+
var isReady = Boolean((intentsApi === null || intentsApi === void 0 ? void 0 : intentsApi.fetchSessionCode) && (intentsApi === null || intentsApi === void 0 ? void 0 : intentsApi.showInAppBrowser) && (intentsApi === null || intentsApi === void 0 ? void 0 : intentsApi.closeInAppBrowser));
|
|
118
|
+
useEffect(function () {
|
|
119
|
+
function insideEffect() {
|
|
120
|
+
return _insideEffect2.apply(this, arguments);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function _insideEffect2() {
|
|
124
|
+
_insideEffect2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
|
|
125
|
+
var sessionCode, iabUrl, urlToOpen, result;
|
|
126
|
+
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
|
|
127
|
+
while (1) {
|
|
128
|
+
switch (_context2.prev = _context2.next) {
|
|
129
|
+
case 0:
|
|
130
|
+
if (!isReady) {
|
|
131
|
+
_context2.next = 21;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_context2.prev = 1;
|
|
136
|
+
logger.debug('url at the beginning: ', url);
|
|
137
|
+
_context2.next = 5;
|
|
138
|
+
return fetchSessionCode();
|
|
139
|
+
|
|
140
|
+
case 5:
|
|
141
|
+
sessionCode = _context2.sent;
|
|
142
|
+
logger.debug('got session code', sessionCode);
|
|
143
|
+
iabUrl = new URL(url);
|
|
144
|
+
iabUrl.searchParams.append(tokenParamName, sessionCode); // we need to decodeURIComponent since toString() encodes URL
|
|
145
|
+
// but native browser will also encode them.
|
|
146
|
+
|
|
147
|
+
urlToOpen = decodeURIComponent(iabUrl.toString());
|
|
148
|
+
logger.debug('url to open: ', urlToOpen);
|
|
149
|
+
_context2.next = 13;
|
|
150
|
+
return showInAppBrowser(urlToOpen);
|
|
151
|
+
|
|
152
|
+
case 13:
|
|
153
|
+
result = _context2.sent;
|
|
154
|
+
|
|
155
|
+
if ((result === null || result === void 0 ? void 0 : result.type) !== 'dismiss' && (result === null || result === void 0 ? void 0 : result.type) !== 'cancel') {
|
|
156
|
+
logger.error('Unexpected InAppBrowser result', result);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_context2.next = 20;
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case 17:
|
|
163
|
+
_context2.prev = 17;
|
|
164
|
+
_context2.t0 = _context2["catch"](1);
|
|
165
|
+
logger.error('unexpected fetchSessionCode result', _context2.t0);
|
|
166
|
+
|
|
167
|
+
case 20:
|
|
168
|
+
if (onClose) {
|
|
169
|
+
onClose();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case 21:
|
|
173
|
+
case "end":
|
|
174
|
+
return _context2.stop();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}, _callee2, null, [[1, 17]]);
|
|
178
|
+
}));
|
|
179
|
+
return _insideEffect2.apply(this, arguments);
|
|
180
|
+
}
|
|
181
|
+
|
|
92
182
|
insideEffect();
|
|
93
183
|
return function cleanup() {
|
|
94
184
|
closeInAppBrowser();
|
package/dist/helpers/oauth.js
CHANGED
|
@@ -118,7 +118,7 @@ export var getOAuthUrl = function getOAuthUrl(_ref) {
|
|
|
118
118
|
oAuthUrl.searchParams.set('nonce', nonce);
|
|
119
119
|
|
|
120
120
|
if (oAuthConf.scope !== undefined && oAuthConf.scope !== null && oAuthConf.scope !== false) {
|
|
121
|
-
var urlScope = Array.isArray(oAuthConf.scope) ? oAuthConf.scope.join('
|
|
121
|
+
var urlScope = Array.isArray(oAuthConf.scope) ? oAuthConf.scope.join('%2B') : oAuthConf.scope;
|
|
122
122
|
oAuthUrl.searchParams.set('scope', urlScope);
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -53,7 +53,7 @@ describe('Oauth helper', function () {
|
|
|
53
53
|
scope: ['thescope', 'thescope2']
|
|
54
54
|
}
|
|
55
55
|
}));
|
|
56
|
-
expect(url).toEqual('https://cozyurl/accounts/testslug/start?state=statekey&nonce=1234&scope=thescope
|
|
56
|
+
expect(url).toEqual('https://cozyurl/accounts/testslug/start?state=statekey&nonce=1234&scope=thescope%2Bthescope2');
|
|
57
57
|
});
|
|
58
58
|
it('should use redirectSlug if present', function () {
|
|
59
59
|
var url = getOAuthUrl(_objectSpread(_objectSpread({}, defaultConf), {}, {
|
|
@@ -33,6 +33,7 @@ import { waitForRealtimeEvent } from './jobUtils';
|
|
|
33
33
|
import '../types';
|
|
34
34
|
import { LOGIN_SUCCESS_EVENT } from '../models/flowEvents';
|
|
35
35
|
var TEMP_TOKEN_TIMOUT_S = 60;
|
|
36
|
+
export var ACCOUNTS_DOCTYPE = 'io.cozy.accounts';
|
|
36
37
|
export var isBiWebViewConnector = function isBiWebViewConnector(konnector) {
|
|
37
38
|
return flag('harvest.bi.webview') && isBudgetInsightConnector(konnector);
|
|
38
39
|
};
|
|
@@ -201,7 +202,7 @@ export var handleOAuthAccount = /*#__PURE__*/function () {
|
|
|
201
202
|
logger.info("Found a BI webview connection id: ".concat(connectionId));
|
|
202
203
|
flow.konnector = konnector;
|
|
203
204
|
_context3.next = 12;
|
|
204
|
-
return flow.saveAccount(setBIConnectionId(biWebviewAccount, connectionId));
|
|
205
|
+
return flow.saveAccount(_objectSpread(_objectSpread({}, setBIConnectionId(biWebviewAccount, connectionId)), getBiAggregatorParentRelationship(konnector)));
|
|
205
206
|
|
|
206
207
|
case 12:
|
|
207
208
|
biWebviewAccount = _context3.sent;
|
|
@@ -228,6 +229,34 @@ export var handleOAuthAccount = /*#__PURE__*/function () {
|
|
|
228
229
|
return _ref6.apply(this, arguments);
|
|
229
230
|
};
|
|
230
231
|
}();
|
|
232
|
+
/**
|
|
233
|
+
* Return the bi aggregator parent relationship configuration for a given konnector
|
|
234
|
+
*
|
|
235
|
+
* @param {io.cozy.konnectors} konnector connector manifest content
|
|
236
|
+
*
|
|
237
|
+
* @return {Object}
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
var getBiAggregatorParentRelationship = function getBiAggregatorParentRelationship(konnector) {
|
|
241
|
+
var _konnector$aggregator;
|
|
242
|
+
|
|
243
|
+
var biAggregatorId = konnector === null || konnector === void 0 ? void 0 : (_konnector$aggregator = konnector.aggregator) === null || _konnector$aggregator === void 0 ? void 0 : _konnector$aggregator.accountId;
|
|
244
|
+
|
|
245
|
+
if (!biAggregatorId) {
|
|
246
|
+
return {};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
relationships: {
|
|
251
|
+
parent: {
|
|
252
|
+
data: {
|
|
253
|
+
_id: biAggregatorId,
|
|
254
|
+
_type: ACCOUNTS_DOCTYPE
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
};
|
|
231
260
|
/**
|
|
232
261
|
* Gets BI webview connection id which is returned in the account by the stack
|
|
233
262
|
* via oauth callback url
|
|
@@ -237,6 +266,7 @@ export var handleOAuthAccount = /*#__PURE__*/function () {
|
|
|
237
266
|
* @return {Integer} Connection Id
|
|
238
267
|
*/
|
|
239
268
|
|
|
269
|
+
|
|
240
270
|
var getWebviewBIConnectionId = function getWebviewBIConnectionId(account) {
|
|
241
271
|
var _account$oauth, _account$oauth$query, _account$oauth$query$;
|
|
242
272
|
|
|
@@ -546,16 +576,22 @@ function _getBiTemporaryTokenFromCache() {
|
|
|
546
576
|
return _getBiTemporaryTokenFromCache.apply(this, arguments);
|
|
547
577
|
}
|
|
548
578
|
|
|
549
|
-
function isCacheExpired(_ref17) {
|
|
550
|
-
var tokenCache = _ref17.tokenCache
|
|
579
|
+
export function isCacheExpired(_ref17) {
|
|
580
|
+
var tokenCache = _ref17.tokenCache,
|
|
581
|
+
biUser = _ref17.biUser;
|
|
551
582
|
var cacheAge = Date.now() - Number(tokenCache === null || tokenCache === void 0 ? void 0 : tokenCache.timestamp);
|
|
552
583
|
logger.debug('tokenCache age', cacheAge / 1000 / 60, 'minutes');
|
|
553
584
|
var MAX_TOKEN_CACHE_AGE = 29 * 60 * 1000;
|
|
585
|
+
var isSameUserId = tokenCache.userId === (biUser === null || biUser === void 0 ? void 0 : biUser.userId);
|
|
554
586
|
|
|
555
|
-
if (tokenCache && cacheAge < MAX_TOKEN_CACHE_AGE) {
|
|
587
|
+
if (tokenCache && cacheAge < MAX_TOKEN_CACHE_AGE && isSameUserId) {
|
|
556
588
|
return false;
|
|
557
589
|
}
|
|
558
590
|
|
|
591
|
+
if (!isSameUserId) {
|
|
592
|
+
logger.warn("BI user id in cache ".concat(tokenCache.userId, " is different than current user id ").concat(biUser === null || biUser === void 0 ? void 0 : biUser.userId));
|
|
593
|
+
}
|
|
594
|
+
|
|
559
595
|
return true;
|
|
560
596
|
}
|
|
561
597
|
/**
|
|
@@ -568,7 +604,6 @@ function isCacheExpired(_ref17) {
|
|
|
568
604
|
* @return {createTemporaryTokenResponse}
|
|
569
605
|
*/
|
|
570
606
|
|
|
571
|
-
|
|
572
607
|
function updateCache(_x9) {
|
|
573
608
|
return _updateCache.apply(this, arguments);
|
|
574
609
|
}
|
|
@@ -633,7 +668,7 @@ export var createTemporaryToken = /*#__PURE__*/function () {
|
|
|
633
668
|
var _ref20 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8(_ref19) {
|
|
634
669
|
var _tokenCache;
|
|
635
670
|
|
|
636
|
-
var client, konnector, account, tokenCache, cozyBankIds, _tokenCache2, biMapping;
|
|
671
|
+
var client, konnector, account, tokenCache, cozyBankIds, _yield$client$query, biUser, _tokenCache2, biMapping;
|
|
637
672
|
|
|
638
673
|
return _regeneratorRuntime.wrap(function _callee8$(_context8) {
|
|
639
674
|
while (1) {
|
|
@@ -652,16 +687,23 @@ export var createTemporaryToken = /*#__PURE__*/function () {
|
|
|
652
687
|
konnector: konnector,
|
|
653
688
|
account: account
|
|
654
689
|
});
|
|
690
|
+
_context8.next = 8;
|
|
691
|
+
return client.query(Q('io.cozy.accounts').getById('bi-aggregator-user'));
|
|
692
|
+
|
|
693
|
+
case 8:
|
|
694
|
+
_yield$client$query = _context8.sent;
|
|
695
|
+
biUser = _yield$client$query.data;
|
|
655
696
|
|
|
656
697
|
if (!isCacheExpired({
|
|
657
|
-
tokenCache: tokenCache
|
|
698
|
+
tokenCache: tokenCache,
|
|
699
|
+
biUser: biUser
|
|
658
700
|
})) {
|
|
659
|
-
_context8.next =
|
|
701
|
+
_context8.next = 15;
|
|
660
702
|
break;
|
|
661
703
|
}
|
|
662
704
|
|
|
663
705
|
logger.debug('temporaryToken cache is expired. Updating');
|
|
664
|
-
_context8.next =
|
|
706
|
+
_context8.next = 14;
|
|
665
707
|
return updateCache({
|
|
666
708
|
client: client,
|
|
667
709
|
konnector: konnector,
|
|
@@ -669,10 +711,10 @@ export var createTemporaryToken = /*#__PURE__*/function () {
|
|
|
669
711
|
cozyBankIds: cozyBankIds
|
|
670
712
|
});
|
|
671
713
|
|
|
672
|
-
case
|
|
714
|
+
case 14:
|
|
673
715
|
tokenCache = _context8.sent;
|
|
674
716
|
|
|
675
|
-
case
|
|
717
|
+
case 15:
|
|
676
718
|
assert(cozyBankIds.length, 'createTemporaryToken: Could not determine cozyBankIds from account or konnector');
|
|
677
719
|
assert((_tokenCache = tokenCache) === null || _tokenCache === void 0 ? void 0 : _tokenCache.biMapping, 'createTemporaryToken: could not find a BI mapping in createTemporaryToken response, you should update your konnector to the last version');
|
|
678
720
|
_tokenCache2 = tokenCache, biMapping = _tokenCache2.biMapping;
|
|
@@ -681,7 +723,7 @@ export var createTemporaryToken = /*#__PURE__*/function () {
|
|
|
681
723
|
})));
|
|
682
724
|
return _context8.abrupt("return", tokenCache);
|
|
683
725
|
|
|
684
|
-
case
|
|
726
|
+
case 20:
|
|
685
727
|
case "end":
|
|
686
728
|
return _context8.stop();
|
|
687
729
|
}
|
|
@@ -7,7 +7,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
|
|
|
7
7
|
|
|
8
8
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
9
9
|
import CozyClient from 'cozy-client';
|
|
10
|
-
import { handleOAuthAccount, checkBIConnection, isBiWebViewConnector, fetchContractSynchronizationUrl, refreshContracts, fetchExtraOAuthUrlParams } from './biWebView';
|
|
10
|
+
import { handleOAuthAccount, checkBIConnection, isBiWebViewConnector, fetchContractSynchronizationUrl, refreshContracts, fetchExtraOAuthUrlParams, isCacheExpired } from './biWebView';
|
|
11
11
|
import ConnectionFlow from '../models/ConnectionFlow';
|
|
12
12
|
import { waitForRealtimeEvent } from './jobUtils';
|
|
13
13
|
import biPublicKeyProd from './bi-public-key-prod.json';
|
|
@@ -539,6 +539,72 @@ describe('refreshContracts', function () {
|
|
|
539
539
|
}, _callee9);
|
|
540
540
|
})));
|
|
541
541
|
});
|
|
542
|
+
describe('isCacheExpired', function () {
|
|
543
|
+
it('should not be marked as expired when userId did not change and cache is not old', function () {
|
|
544
|
+
var tokenCache = {
|
|
545
|
+
timestamp: Date.now(),
|
|
546
|
+
userId: 666
|
|
547
|
+
};
|
|
548
|
+
var biUser = {
|
|
549
|
+
userId: 666
|
|
550
|
+
};
|
|
551
|
+
expect(isCacheExpired({
|
|
552
|
+
tokenCache: tokenCache,
|
|
553
|
+
biUser: biUser
|
|
554
|
+
})).toBe(false);
|
|
555
|
+
});
|
|
556
|
+
it('should be marked as expired when userId did not change and cache is old', function () {
|
|
557
|
+
var tokenCache = {
|
|
558
|
+
timestamp: 0,
|
|
559
|
+
userId: 666
|
|
560
|
+
};
|
|
561
|
+
var biUser = {
|
|
562
|
+
userId: 666
|
|
563
|
+
};
|
|
564
|
+
expect(isCacheExpired({
|
|
565
|
+
tokenCache: tokenCache,
|
|
566
|
+
biUser: biUser
|
|
567
|
+
})).toBe(true);
|
|
568
|
+
});
|
|
569
|
+
it('should be marked as expired when userId did change and cache is old', function () {
|
|
570
|
+
var tokenCache = {
|
|
571
|
+
timestamp: 0,
|
|
572
|
+
userId: 666
|
|
573
|
+
};
|
|
574
|
+
var biUser = {
|
|
575
|
+
userId: 667
|
|
576
|
+
};
|
|
577
|
+
expect(isCacheExpired({
|
|
578
|
+
tokenCache: tokenCache,
|
|
579
|
+
biUser: biUser
|
|
580
|
+
})).toBe(true);
|
|
581
|
+
});
|
|
582
|
+
it('should be marked as expired when userId did change and cache is not old', function () {
|
|
583
|
+
var tokenCache = {
|
|
584
|
+
timestamp: Date.now(),
|
|
585
|
+
userId: 666
|
|
586
|
+
};
|
|
587
|
+
var biUser = {
|
|
588
|
+
userId: 667
|
|
589
|
+
};
|
|
590
|
+
expect(isCacheExpired({
|
|
591
|
+
tokenCache: tokenCache,
|
|
592
|
+
biUser: biUser
|
|
593
|
+
})).toBe(true);
|
|
594
|
+
});
|
|
595
|
+
it('should be marked as expired when cache has no user id', function () {
|
|
596
|
+
var tokenCache = {
|
|
597
|
+
timestamp: Date.now()
|
|
598
|
+
};
|
|
599
|
+
var biUser = {
|
|
600
|
+
userId: 666
|
|
601
|
+
};
|
|
602
|
+
expect(isCacheExpired({
|
|
603
|
+
tokenCache: tokenCache,
|
|
604
|
+
biUser: biUser
|
|
605
|
+
})).toBe(true);
|
|
606
|
+
});
|
|
607
|
+
});
|
|
542
608
|
describe('fetchExtraOAuthUrlParams', function () {
|
|
543
609
|
it('should asynchronously fetch BI token', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
|
|
544
610
|
var client, result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-harvest-lib",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.23.2",
|
|
4
4
|
"description": "Provides logic, modules and components for Cozy's harvest applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@cozy/minilog": "^1.0.0",
|
|
30
30
|
"@sentry/browser": "^6.0.1",
|
|
31
31
|
"cozy-bi-auth": "0.0.25",
|
|
32
|
-
"cozy-doctypes": "^1.
|
|
32
|
+
"cozy-doctypes": "^1.85.0",
|
|
33
33
|
"cozy-logger": "^1.9.0",
|
|
34
34
|
"date-fns": "^1.30.1",
|
|
35
35
|
"final-form": "^4.18.5",
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"react-router-dom": "^5.0.1"
|
|
91
91
|
},
|
|
92
92
|
"sideEffects": false,
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "c5c6e0d609fafbc41a4b8cbfa8e1d4582544bbdd"
|
|
94
94
|
}
|
|
@@ -1,30 +1,73 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
import { useWebviewIntent } from 'cozy-intent'
|
|
4
4
|
import logger from '../logger'
|
|
5
5
|
import { intentsApiProptype } from '../helpers/proptypes'
|
|
6
6
|
|
|
7
|
-
const InAppBrowser = ({ url, onClose, intentsApi
|
|
7
|
+
const InAppBrowser = ({ url, onClose, intentsApi }) => {
|
|
8
|
+
if (intentsApi) {
|
|
9
|
+
return (
|
|
10
|
+
<InAppBrowserWithIntentsApi
|
|
11
|
+
url={url}
|
|
12
|
+
onClose={onClose}
|
|
13
|
+
intentsApi={intentsApi}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
} else {
|
|
17
|
+
return <InAppBrowserWithWebviewIntent url={url} onClose={onClose} />
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const InAppBrowserWithWebviewIntent = ({ url, onClose }) => {
|
|
8
22
|
const webviewIntent = useWebviewIntent()
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
const isReady = Boolean(webviewIntent)
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
async function insideEffect() {
|
|
26
|
+
if (isReady) {
|
|
27
|
+
try {
|
|
28
|
+
logger.debug('url at the beginning: ', url)
|
|
29
|
+
const sessionCode = await webviewIntent.call('fetchSessionCode')
|
|
30
|
+
logger.debug('got session code', sessionCode)
|
|
31
|
+
const iabUrl = new URL(url)
|
|
32
|
+
iabUrl.searchParams.append('session_code', sessionCode)
|
|
33
|
+
// we need to decodeURIComponent since toString() encodes URL
|
|
34
|
+
// but native browser will also encode them.
|
|
35
|
+
const urlToOpen = decodeURIComponent(iabUrl.toString())
|
|
36
|
+
logger.debug('url to open: ', urlToOpen)
|
|
37
|
+
const result = await webviewIntent.call('showInAppBrowser', {
|
|
38
|
+
url: urlToOpen
|
|
39
|
+
})
|
|
40
|
+
if (result?.type !== 'dismiss' && result?.type !== 'cancel') {
|
|
41
|
+
logger.error('Unexpected InAppBrowser result', result)
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
logger.error('unexpected fetchSessionCode result', err)
|
|
45
|
+
}
|
|
46
|
+
if (onClose) {
|
|
47
|
+
onClose()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
insideEffect()
|
|
52
|
+
return function cleanup() {
|
|
53
|
+
webviewIntent.call('closeInAppBrowser')
|
|
54
|
+
}
|
|
55
|
+
}, [isReady, url, onClose, webviewIntent])
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
18
58
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
59
|
+
const InAppBrowserWithIntentsApi = ({ url, onClose, intentsApi = {} }) => {
|
|
60
|
+
const {
|
|
61
|
+
fetchSessionCode,
|
|
62
|
+
showInAppBrowser,
|
|
63
|
+
closeInAppBrowser,
|
|
64
|
+
tokenParamName = 'session_code'
|
|
65
|
+
} = intentsApi
|
|
22
66
|
|
|
23
67
|
const isReady = Boolean(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
intentsApi?.closeInAppBrowser)
|
|
68
|
+
intentsApi?.fetchSessionCode &&
|
|
69
|
+
intentsApi?.showInAppBrowser &&
|
|
70
|
+
intentsApi?.closeInAppBrowser
|
|
28
71
|
)
|
|
29
72
|
|
|
30
73
|
useEffect(() => {
|
|
@@ -52,6 +95,7 @@ const InAppBrowser = ({ url, onClose, intentsApi = {} }) => {
|
|
|
52
95
|
}
|
|
53
96
|
}
|
|
54
97
|
}
|
|
98
|
+
|
|
55
99
|
insideEffect()
|
|
56
100
|
return function cleanup() {
|
|
57
101
|
closeInAppBrowser()
|
package/src/helpers/oauth.js
CHANGED
|
@@ -130,7 +130,7 @@ export const getOAuthUrl = ({
|
|
|
130
130
|
oAuthConf.scope !== false
|
|
131
131
|
) {
|
|
132
132
|
const urlScope = Array.isArray(oAuthConf.scope)
|
|
133
|
-
? oAuthConf.scope.join('
|
|
133
|
+
? oAuthConf.scope.join('%2B')
|
|
134
134
|
: oAuthConf.scope
|
|
135
135
|
oAuthUrl.searchParams.set('scope', urlScope)
|
|
136
136
|
}
|
|
@@ -50,7 +50,7 @@ describe('Oauth helper', () => {
|
|
|
50
50
|
oAuthConf: { scope: ['thescope', 'thescope2'] }
|
|
51
51
|
})
|
|
52
52
|
expect(url).toEqual(
|
|
53
|
-
'https://cozyurl/accounts/testslug/start?state=statekey&nonce=1234&scope=thescope
|
|
53
|
+
'https://cozyurl/accounts/testslug/start?state=statekey&nonce=1234&scope=thescope%2Bthescope2'
|
|
54
54
|
)
|
|
55
55
|
})
|
|
56
56
|
it('should use redirectSlug if present', () => {
|
|
@@ -23,6 +23,7 @@ import '../types'
|
|
|
23
23
|
import { LOGIN_SUCCESS_EVENT } from '../models/flowEvents'
|
|
24
24
|
|
|
25
25
|
const TEMP_TOKEN_TIMOUT_S = 60
|
|
26
|
+
export const ACCOUNTS_DOCTYPE = 'io.cozy.accounts'
|
|
26
27
|
|
|
27
28
|
export const isBiWebViewConnector = konnector =>
|
|
28
29
|
flag('harvest.bi.webview') && isBudgetInsightConnector(konnector)
|
|
@@ -130,9 +131,11 @@ export const handleOAuthAccount = async ({
|
|
|
130
131
|
if (connectionId) {
|
|
131
132
|
logger.info(`Found a BI webview connection id: ${connectionId}`)
|
|
132
133
|
flow.konnector = konnector
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
|
|
135
|
+
biWebviewAccount = await flow.saveAccount({
|
|
136
|
+
...setBIConnectionId(biWebviewAccount, connectionId),
|
|
137
|
+
...getBiAggregatorParentRelationship(konnector)
|
|
138
|
+
})
|
|
136
139
|
|
|
137
140
|
await flow.handleFormSubmit({
|
|
138
141
|
client,
|
|
@@ -145,6 +148,30 @@ export const handleOAuthAccount = async ({
|
|
|
145
148
|
return connectionId
|
|
146
149
|
}
|
|
147
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Return the bi aggregator parent relationship configuration for a given konnector
|
|
153
|
+
*
|
|
154
|
+
* @param {io.cozy.konnectors} konnector connector manifest content
|
|
155
|
+
*
|
|
156
|
+
* @return {Object}
|
|
157
|
+
*/
|
|
158
|
+
const getBiAggregatorParentRelationship = konnector => {
|
|
159
|
+
const biAggregatorId = konnector?.aggregator?.accountId
|
|
160
|
+
if (!biAggregatorId) {
|
|
161
|
+
return {}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
relationships: {
|
|
165
|
+
parent: {
|
|
166
|
+
data: {
|
|
167
|
+
_id: biAggregatorId,
|
|
168
|
+
_type: ACCOUNTS_DOCTYPE
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
148
175
|
/**
|
|
149
176
|
* Gets BI webview connection id which is returned in the account by the stack
|
|
150
177
|
* via oauth callback url
|
|
@@ -335,13 +362,20 @@ async function getBiTemporaryTokenFromCache({ client }) {
|
|
|
335
362
|
* @param {createTemporaryTokenResponse} options.tokenCache
|
|
336
363
|
* @return {Boolean}
|
|
337
364
|
*/
|
|
338
|
-
function isCacheExpired({ tokenCache }) {
|
|
365
|
+
export function isCacheExpired({ tokenCache, biUser }) {
|
|
339
366
|
const cacheAge = Date.now() - Number(tokenCache?.timestamp)
|
|
340
367
|
logger.debug('tokenCache age', cacheAge / 1000 / 60, 'minutes')
|
|
341
368
|
const MAX_TOKEN_CACHE_AGE = 29 * 60 * 1000
|
|
342
|
-
|
|
369
|
+
const isSameUserId = tokenCache.userId === biUser?.userId
|
|
370
|
+
if (tokenCache && cacheAge < MAX_TOKEN_CACHE_AGE && isSameUserId) {
|
|
343
371
|
return false
|
|
344
372
|
}
|
|
373
|
+
|
|
374
|
+
if (!isSameUserId) {
|
|
375
|
+
logger.warn(
|
|
376
|
+
`BI user id in cache ${tokenCache.userId} is different than current user id ${biUser?.userId}`
|
|
377
|
+
)
|
|
378
|
+
}
|
|
345
379
|
return true
|
|
346
380
|
}
|
|
347
381
|
|
|
@@ -398,7 +432,12 @@ export const createTemporaryToken = async ({ client, konnector, account }) => {
|
|
|
398
432
|
|
|
399
433
|
let tokenCache = await getBiTemporaryTokenFromCache({ client })
|
|
400
434
|
const cozyBankIds = getCozyBankIds({ konnector, account })
|
|
401
|
-
|
|
435
|
+
|
|
436
|
+
const { data: biUser } = await client.query(
|
|
437
|
+
Q('io.cozy.accounts').getById('bi-aggregator-user')
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
if (isCacheExpired({ tokenCache, biUser })) {
|
|
402
441
|
logger.debug('temporaryToken cache is expired. Updating')
|
|
403
442
|
tokenCache = await updateCache({
|
|
404
443
|
client,
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
isBiWebViewConnector,
|
|
6
6
|
fetchContractSynchronizationUrl,
|
|
7
7
|
refreshContracts,
|
|
8
|
-
fetchExtraOAuthUrlParams
|
|
8
|
+
fetchExtraOAuthUrlParams,
|
|
9
|
+
isCacheExpired
|
|
9
10
|
} from './biWebView'
|
|
10
11
|
import ConnectionFlow from '../models/ConnectionFlow'
|
|
11
12
|
import { waitForRealtimeEvent } from './jobUtils'
|
|
@@ -330,6 +331,34 @@ describe('refreshContracts', () => {
|
|
|
330
331
|
})
|
|
331
332
|
})
|
|
332
333
|
|
|
334
|
+
describe('isCacheExpired', () => {
|
|
335
|
+
it('should not be marked as expired when userId did not change and cache is not old', () => {
|
|
336
|
+
const tokenCache = { timestamp: Date.now(), userId: 666 }
|
|
337
|
+
const biUser = { userId: 666 }
|
|
338
|
+
expect(isCacheExpired({ tokenCache, biUser })).toBe(false)
|
|
339
|
+
})
|
|
340
|
+
it('should be marked as expired when userId did not change and cache is old', () => {
|
|
341
|
+
const tokenCache = { timestamp: 0, userId: 666 }
|
|
342
|
+
const biUser = { userId: 666 }
|
|
343
|
+
expect(isCacheExpired({ tokenCache, biUser })).toBe(true)
|
|
344
|
+
})
|
|
345
|
+
it('should be marked as expired when userId did change and cache is old', () => {
|
|
346
|
+
const tokenCache = { timestamp: 0, userId: 666 }
|
|
347
|
+
const biUser = { userId: 667 }
|
|
348
|
+
expect(isCacheExpired({ tokenCache, biUser })).toBe(true)
|
|
349
|
+
})
|
|
350
|
+
it('should be marked as expired when userId did change and cache is not old', () => {
|
|
351
|
+
const tokenCache = { timestamp: Date.now(), userId: 666 }
|
|
352
|
+
const biUser = { userId: 667 }
|
|
353
|
+
expect(isCacheExpired({ tokenCache, biUser })).toBe(true)
|
|
354
|
+
})
|
|
355
|
+
it('should be marked as expired when cache has no user id', () => {
|
|
356
|
+
const tokenCache = { timestamp: Date.now() }
|
|
357
|
+
const biUser = { userId: 666 }
|
|
358
|
+
expect(isCacheExpired({ tokenCache, biUser })).toBe(true)
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
|
|
333
362
|
describe('fetchExtraOAuthUrlParams', () => {
|
|
334
363
|
it('should asynchronously fetch BI token', async () => {
|
|
335
364
|
const client = new CozyClient({
|