cozy-harvest-lib 9.1.1 → 9.2.0
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 +20 -0
- package/dist/components/TriggerManager.js +2 -1
- package/dist/helpers/oauth.js +3 -2
- package/dist/konnector-policies.js +2 -1
- package/dist/services/biWebView.js +271 -0
- package/dist/services/biWebView.spec.js +314 -0
- package/dist/services/budget-insight.js +5 -9
- package/dist/services/budget-insight.spec.js +1 -1
- package/package.json +3 -3
- package/src/components/TriggerManager.jsx +2 -1
- package/src/helpers/oauth.js +2 -2
- package/src/konnector-policies.js +6 -1
- package/src/services/biWebView.js +192 -0
- package/src/services/biWebView.spec.js +185 -0
- package/src/services/budget-insight.js +4 -12
- package/src/services/budget-insight.spec.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
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.2.0](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.1.2...cozy-harvest-lib@9.2.0) (2022-05-09)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add BI connection creation via BI webview ([35ea078](https://github.com/cozy/cozy-libs/commit/35ea07805b5604d7882051bc0f7b43b38516922f))
|
|
12
|
+
* Unit tests + comments ([93e960e](https://github.com/cozy/cozy-libs/commit/93e960ee3236df742b8665d4d79cf6cf675d4f8f))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## 9.1.2 (2022-05-05)
|
|
19
|
+
|
|
20
|
+
**Note:** Version bump only for package cozy-harvest-lib
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## [9.1.1](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.1.0...cozy-harvest-lib@9.1.1) (2022-05-02)
|
|
7
27
|
|
|
8
28
|
|
|
@@ -522,8 +522,9 @@ export var DumbTriggerManager = /*#__PURE__*/function (_Component) {
|
|
|
522
522
|
var showSpinner = submitting && selectedCipher && step === 'ciphersList';
|
|
523
523
|
var showCiphersList = step === 'ciphersList';
|
|
524
524
|
var showAccountForm = step === 'accountForm';
|
|
525
|
+
var konnectorPolicy = findKonnectorPolicy(konnector);
|
|
525
526
|
|
|
526
|
-
if (oauth) {
|
|
527
|
+
if (oauth || konnectorPolicy.isBIWebView) {
|
|
527
528
|
return /*#__PURE__*/React.createElement(OAuthForm, {
|
|
528
529
|
client: client,
|
|
529
530
|
flow: flow,
|
package/dist/helpers/oauth.js
CHANGED
|
@@ -88,7 +88,7 @@ export var handleOAuthResponse = function handleOAuthResponse() {
|
|
|
88
88
|
* @param {string} cozyUrl cozy url
|
|
89
89
|
* @param {string} accountType connector slug
|
|
90
90
|
* @param {string} oAuthStateKey localStorage key
|
|
91
|
-
* @param {Object} oAuthConf
|
|
91
|
+
* @param {Object} [oAuthConf={}] connector manifest oauth configuration
|
|
92
92
|
* @param {string} redirectSlug The app we want to redirect the user on after the end of the flow
|
|
93
93
|
* @param {string} nonce unique nonce string
|
|
94
94
|
* @param {Object} extraParams some extra parameters to add to the query string
|
|
@@ -98,7 +98,8 @@ export var getOAuthUrl = function getOAuthUrl(_ref) {
|
|
|
98
98
|
var cozyUrl = _ref.cozyUrl,
|
|
99
99
|
accountType = _ref.accountType,
|
|
100
100
|
oAuthStateKey = _ref.oAuthStateKey,
|
|
101
|
-
oAuthConf = _ref.oAuthConf,
|
|
101
|
+
_ref$oAuthConf = _ref.oAuthConf,
|
|
102
|
+
oAuthConf = _ref$oAuthConf === void 0 ? {} : _ref$oAuthConf,
|
|
102
103
|
nonce = _ref.nonce,
|
|
103
104
|
redirectSlug = _ref.redirectSlug,
|
|
104
105
|
extraParams = _ref.extraParams;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logger from './logger';
|
|
2
2
|
import { konnectorPolicy as biKonnectorPolicy } from './services/budget-insight';
|
|
3
|
+
import { konnectorPolicy as biWebViewPolicy } from './services/biWebView';
|
|
3
4
|
var defaultKonnectorPolicy = {
|
|
4
5
|
accountContainsAuth: true,
|
|
5
6
|
saveInVault: true,
|
|
@@ -9,7 +10,7 @@ var defaultKonnectorPolicy = {
|
|
|
9
10
|
},
|
|
10
11
|
name: 'default'
|
|
11
12
|
};
|
|
12
|
-
var policies = [biKonnectorPolicy, defaultKonnectorPolicy].filter(Boolean);
|
|
13
|
+
var policies = [biWebViewPolicy, biKonnectorPolicy, defaultKonnectorPolicy].filter(Boolean);
|
|
13
14
|
logger.info('Available konnector policies', policies);
|
|
14
15
|
export var findKonnectorPolicy = function findKonnectorPolicy(konnector) {
|
|
15
16
|
var policy = policies.find(function (policy) {
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
|
|
3
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
4
|
+
var _excluded = ["code"];
|
|
5
|
+
|
|
6
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
7
|
+
|
|
8
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
9
|
+
|
|
10
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Interface between Budget Insight and Cozy using BI's webview
|
|
14
|
+
*
|
|
15
|
+
* - Deals with the konnector to get temporary tokens
|
|
16
|
+
*/
|
|
17
|
+
import { getBIConnection } from './bi-http';
|
|
18
|
+
import assert from '../assert';
|
|
19
|
+
import logger from '../logger';
|
|
20
|
+
import flag from 'cozy-flags';
|
|
21
|
+
import { fetchExtraOAuthUrlParams, createTemporaryToken, setBIConnectionId, saveBIConfig, findAccountWithBiConnection, convertBIErrortoKonnectorJobError, isBudgetInsightConnector } from './budget-insight';
|
|
22
|
+
import { KonnectorJobError } from '../helpers/konnectors';
|
|
23
|
+
export var isBiWebViewConnector = function isBiWebViewConnector(konnector) {
|
|
24
|
+
return flag('harvest.bi.webview') && isBudgetInsightConnector(konnector);
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Runs multiple checks on the bi connection referenced in the given account
|
|
28
|
+
*
|
|
29
|
+
* @param {io.cozy.accounts} options.account The account content
|
|
30
|
+
* @param {ConnectionFlow} options.flow
|
|
31
|
+
* @param {io.cozy.konnectors} options.konnector connector manifest content
|
|
32
|
+
* @param {CozyClient} options.client CozyClient object
|
|
33
|
+
*
|
|
34
|
+
* @return {Integer} Connection Id
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export var checkBIConnection = /*#__PURE__*/function () {
|
|
38
|
+
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(_ref) {
|
|
39
|
+
var account, client, konnector, flow, connId, biConfig, tempToken, config, connection, sameAccount, err;
|
|
40
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
41
|
+
while (1) {
|
|
42
|
+
switch (_context.prev = _context.next) {
|
|
43
|
+
case 0:
|
|
44
|
+
account = _ref.account, client = _ref.client, konnector = _ref.konnector, flow = _ref.flow;
|
|
45
|
+
_context.prev = 1;
|
|
46
|
+
connId = getWebviewBIConnectionId(account);
|
|
47
|
+
logger.info('Creating temporary token...');
|
|
48
|
+
_context.next = 6;
|
|
49
|
+
return createTemporaryToken({
|
|
50
|
+
client: client,
|
|
51
|
+
konnector: konnector,
|
|
52
|
+
account: account
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
case 6:
|
|
56
|
+
biConfig = _context.sent;
|
|
57
|
+
saveBIConfig(flow, biConfig);
|
|
58
|
+
tempToken = biConfig.code, config = _objectWithoutProperties(biConfig, _excluded);
|
|
59
|
+
logger.info('Created temporary token');
|
|
60
|
+
assert(tempToken, 'No temporary token');
|
|
61
|
+
logger.info("fetch connection ".concat(connId, "..."));
|
|
62
|
+
_context.next = 14;
|
|
63
|
+
return getBIConnection(config, connId, tempToken);
|
|
64
|
+
|
|
65
|
+
case 14:
|
|
66
|
+
connection = _context.sent;
|
|
67
|
+
_context.next = 17;
|
|
68
|
+
return findAccountWithBiConnection({
|
|
69
|
+
client: client,
|
|
70
|
+
konnector: konnector,
|
|
71
|
+
connectionId: connection.id
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
case 17:
|
|
75
|
+
sameAccount = _context.sent;
|
|
76
|
+
|
|
77
|
+
if (!sameAccount) {
|
|
78
|
+
_context.next = 22;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
err = new KonnectorJobError('ACCOUNT_WITH_SAME_IDENTIFIER_ALREADY_DEFINED');
|
|
83
|
+
err.accountId = sameAccount._id;
|
|
84
|
+
throw err;
|
|
85
|
+
|
|
86
|
+
case 22:
|
|
87
|
+
return _context.abrupt("return", connection);
|
|
88
|
+
|
|
89
|
+
case 25:
|
|
90
|
+
_context.prev = 25;
|
|
91
|
+
_context.t0 = _context["catch"](1);
|
|
92
|
+
return _context.abrupt("return", convertBIErrortoKonnectorJobError(_context.t0));
|
|
93
|
+
|
|
94
|
+
case 28:
|
|
95
|
+
case "end":
|
|
96
|
+
return _context.stop();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}, _callee, null, [[1, 25]]);
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
return function checkBIConnection(_x) {
|
|
103
|
+
return _ref2.apply(this, arguments);
|
|
104
|
+
};
|
|
105
|
+
}();
|
|
106
|
+
/**
|
|
107
|
+
* Handles webview connection
|
|
108
|
+
*
|
|
109
|
+
* @param {io.cozy.accounts} options.account The account content
|
|
110
|
+
* @param {ConnectionFlow} options.flow
|
|
111
|
+
* @param {io.cozy.konnectors} options.konnector connector manifest content
|
|
112
|
+
* @param {CozyClient} options.client CozyClient object
|
|
113
|
+
*
|
|
114
|
+
* @return {Integer} Connection Id
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
export var handleOAuthAccount = /*#__PURE__*/function () {
|
|
118
|
+
var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(_ref3) {
|
|
119
|
+
var account, flow, konnector, client, t, cozyBankId, biWebviewAccount, connectionId;
|
|
120
|
+
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
|
|
121
|
+
while (1) {
|
|
122
|
+
switch (_context2.prev = _context2.next) {
|
|
123
|
+
case 0:
|
|
124
|
+
account = _ref3.account, flow = _ref3.flow, konnector = _ref3.konnector, client = _ref3.client, t = _ref3.t;
|
|
125
|
+
cozyBankId = getCozyBankId({
|
|
126
|
+
konnector: konnector,
|
|
127
|
+
account: account
|
|
128
|
+
});
|
|
129
|
+
biWebviewAccount = _objectSpread(_objectSpread({}, account), cozyBankId ? {
|
|
130
|
+
auth: {
|
|
131
|
+
bankId: cozyBankId
|
|
132
|
+
}
|
|
133
|
+
} : {});
|
|
134
|
+
connectionId = getWebviewBIConnectionId(biWebviewAccount);
|
|
135
|
+
|
|
136
|
+
if (!connectionId) {
|
|
137
|
+
_context2.next = 12;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
logger.info("Found a BI webview connection id: ".concat(connectionId));
|
|
142
|
+
flow.konnector = konnector;
|
|
143
|
+
_context2.next = 9;
|
|
144
|
+
return flow.saveAccount(setBIConnectionId(biWebviewAccount, connectionId));
|
|
145
|
+
|
|
146
|
+
case 9:
|
|
147
|
+
biWebviewAccount = _context2.sent;
|
|
148
|
+
_context2.next = 12;
|
|
149
|
+
return flow.handleFormSubmit({
|
|
150
|
+
client: client,
|
|
151
|
+
account: biWebviewAccount,
|
|
152
|
+
konnector: konnector,
|
|
153
|
+
t: t
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
case 12:
|
|
157
|
+
return _context2.abrupt("return", connectionId);
|
|
158
|
+
|
|
159
|
+
case 13:
|
|
160
|
+
case "end":
|
|
161
|
+
return _context2.stop();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}, _callee2);
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
return function handleOAuthAccount(_x2) {
|
|
168
|
+
return _ref4.apply(this, arguments);
|
|
169
|
+
};
|
|
170
|
+
}();
|
|
171
|
+
/**
|
|
172
|
+
* Gets BI webview connection id which is returned in the account by the stack
|
|
173
|
+
* via oauth callback url
|
|
174
|
+
*
|
|
175
|
+
* @param {io.cozy.accounts} The account content created by the stack
|
|
176
|
+
*
|
|
177
|
+
* @return {Integer} Connection Id
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
var getWebviewBIConnectionId = function getWebviewBIConnectionId(account) {
|
|
181
|
+
var _account$oauth, _account$oauth$query, _account$oauth$query$;
|
|
182
|
+
|
|
183
|
+
return Number((account === null || account === void 0 ? void 0 : (_account$oauth = account.oauth) === null || _account$oauth === void 0 ? void 0 : (_account$oauth$query = _account$oauth.query) === null || _account$oauth$query === void 0 ? void 0 : (_account$oauth$query$ = _account$oauth$query.connection_id) === null || _account$oauth$query$ === void 0 ? void 0 : _account$oauth$query$[0]) || null);
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Hook from ConnectionFlow after account creation
|
|
187
|
+
*
|
|
188
|
+
* @param {io.cozy.accounts} options.account - created account
|
|
189
|
+
* @param {io.cozy.konnectors} options.konnector - manifest of the konnector for which the account is created
|
|
190
|
+
* @param {ConnectionFlow} options.flow - current ConnectionFlow instance
|
|
191
|
+
* @param {CozyClient} options.client - current CozyClient instance
|
|
192
|
+
*
|
|
193
|
+
* @returns {Promise<io.cozy.accounts>}
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
export var onBIAccountCreation = /*#__PURE__*/function () {
|
|
198
|
+
var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(_ref5) {
|
|
199
|
+
var fullAccount, client, flow, konnector, account, biConnection;
|
|
200
|
+
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
|
|
201
|
+
while (1) {
|
|
202
|
+
switch (_context3.prev = _context3.next) {
|
|
203
|
+
case 0:
|
|
204
|
+
fullAccount = _ref5.account, client = _ref5.client, flow = _ref5.flow, konnector = _ref5.konnector;
|
|
205
|
+
_context3.next = 3;
|
|
206
|
+
return flow.saveAccount(fullAccount);
|
|
207
|
+
|
|
208
|
+
case 3:
|
|
209
|
+
account = _context3.sent;
|
|
210
|
+
_context3.next = 6;
|
|
211
|
+
return checkBIConnection({
|
|
212
|
+
account: _objectSpread(_objectSpread({}, fullAccount), {}, {
|
|
213
|
+
_id: account._id
|
|
214
|
+
}),
|
|
215
|
+
client: client,
|
|
216
|
+
konnector: konnector,
|
|
217
|
+
flow: flow
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
case 6:
|
|
221
|
+
biConnection = _context3.sent;
|
|
222
|
+
flow.setData({
|
|
223
|
+
biConnection: biConnection
|
|
224
|
+
});
|
|
225
|
+
_context3.next = 10;
|
|
226
|
+
return flow.saveAccount(setBIConnectionId(account, biConnection.id));
|
|
227
|
+
|
|
228
|
+
case 10:
|
|
229
|
+
return _context3.abrupt("return", _context3.sent);
|
|
230
|
+
|
|
231
|
+
case 11:
|
|
232
|
+
case "end":
|
|
233
|
+
return _context3.stop();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}, _callee3);
|
|
237
|
+
}));
|
|
238
|
+
|
|
239
|
+
return function onBIAccountCreation(_x3) {
|
|
240
|
+
return _ref6.apply(this, arguments);
|
|
241
|
+
};
|
|
242
|
+
}();
|
|
243
|
+
/**
|
|
244
|
+
* Finds the current bankIid in a given konnector or account
|
|
245
|
+
*
|
|
246
|
+
* @param {io.cozy.accounts} options.account The account content
|
|
247
|
+
* @param {io.cozy.konnectors} options.konnector connector manifest content
|
|
248
|
+
*/
|
|
249
|
+
|
|
250
|
+
export var getCozyBankId = function getCozyBankId(_ref7) {
|
|
251
|
+
var _konnector$parameters, _account$auth;
|
|
252
|
+
|
|
253
|
+
var konnector = _ref7.konnector,
|
|
254
|
+
account = _ref7.account;
|
|
255
|
+
var cozyBankId = (konnector === null || konnector === void 0 ? void 0 : (_konnector$parameters = konnector.parameters) === null || _konnector$parameters === void 0 ? void 0 : _konnector$parameters.bankId) || (account === null || account === void 0 ? void 0 : (_account$auth = account.auth) === null || _account$auth === void 0 ? void 0 : _account$auth.bankId);
|
|
256
|
+
|
|
257
|
+
if (!cozyBankId) {
|
|
258
|
+
logger.error('Could not find any bank id');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return cozyBankId;
|
|
262
|
+
};
|
|
263
|
+
export var konnectorPolicy = {
|
|
264
|
+
isBIWebView: true,
|
|
265
|
+
name: 'budget-insight-webview',
|
|
266
|
+
match: isBiWebViewConnector,
|
|
267
|
+
saveInVault: false,
|
|
268
|
+
onAccountCreation: onBIAccountCreation,
|
|
269
|
+
fetchExtraOAuthUrlParams: fetchExtraOAuthUrlParams,
|
|
270
|
+
handleOAuthAccount: handleOAuthAccount
|
|
271
|
+
};
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
3
|
+
|
|
4
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
5
|
+
|
|
6
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
7
|
+
|
|
8
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
9
|
+
import CozyClient from 'cozy-client';
|
|
10
|
+
import { handleOAuthAccount, checkBIConnection, isBiWebViewConnector } from './biWebView';
|
|
11
|
+
import ConnectionFlow from '../models/ConnectionFlow';
|
|
12
|
+
import { waitForRealtimeEvent } from './jobUtils';
|
|
13
|
+
import biPublicKeyProd from './bi-public-key-prod.json';
|
|
14
|
+
import flag from 'cozy-flags';
|
|
15
|
+
jest.mock('./bi-http', function () {
|
|
16
|
+
return {
|
|
17
|
+
createBIConnection: jest.fn().mockResolvedValue({
|
|
18
|
+
text: Promise.resolve('{}')
|
|
19
|
+
}),
|
|
20
|
+
updateBIConnection: jest.fn(),
|
|
21
|
+
getBIConnection: jest.fn()
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
import { getBIConnection } from './bi-http';
|
|
25
|
+
jest.mock('cozy-logger', function () {
|
|
26
|
+
return {
|
|
27
|
+
namespace: function namespace() {
|
|
28
|
+
return function () {};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
jest.mock('./jobUtils', function () {
|
|
33
|
+
return {
|
|
34
|
+
waitForRealtimeEvent: jest.fn()
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
var sleep = function sleep(duration) {
|
|
39
|
+
return new Promise(function (resolve) {
|
|
40
|
+
return setTimeout(resolve, duration);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
var TEST_BANK_COZY_ID = '100000';
|
|
45
|
+
var konnector = {
|
|
46
|
+
slug: 'boursorama83',
|
|
47
|
+
parameters: {
|
|
48
|
+
bankId: TEST_BANK_COZY_ID
|
|
49
|
+
},
|
|
50
|
+
partnership: {
|
|
51
|
+
domain: 'https://budget-insight.com'
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var account = {
|
|
55
|
+
_id: '1337'
|
|
56
|
+
};
|
|
57
|
+
describe('handleOAuthAccount', function () {
|
|
58
|
+
it('should handle bi webview authentication if any connection is found in the account', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
|
|
59
|
+
var client, flow, account, t;
|
|
60
|
+
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
|
|
61
|
+
while (1) {
|
|
62
|
+
switch (_context2.prev = _context2.next) {
|
|
63
|
+
case 0:
|
|
64
|
+
client = new CozyClient({
|
|
65
|
+
uri: 'http://testcozy.mycozy.cloud'
|
|
66
|
+
});
|
|
67
|
+
flow = new ConnectionFlow(client, null, konnector);
|
|
68
|
+
flow.account = account;
|
|
69
|
+
flow.handleFormSubmit = jest.fn();
|
|
70
|
+
|
|
71
|
+
flow.saveAccount = /*#__PURE__*/function () {
|
|
72
|
+
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(account) {
|
|
73
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
74
|
+
while (1) {
|
|
75
|
+
switch (_context.prev = _context.next) {
|
|
76
|
+
case 0:
|
|
77
|
+
return _context.abrupt("return", account);
|
|
78
|
+
|
|
79
|
+
case 1:
|
|
80
|
+
case "end":
|
|
81
|
+
return _context.stop();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}, _callee);
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
return function (_x) {
|
|
88
|
+
return _ref2.apply(this, arguments);
|
|
89
|
+
};
|
|
90
|
+
}();
|
|
91
|
+
|
|
92
|
+
account = {
|
|
93
|
+
oauth: {
|
|
94
|
+
query: {
|
|
95
|
+
connection_id: ['12']
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
t = jest.fn();
|
|
100
|
+
_context2.next = 9;
|
|
101
|
+
return handleOAuthAccount({
|
|
102
|
+
account: account,
|
|
103
|
+
flow: flow,
|
|
104
|
+
client: client,
|
|
105
|
+
konnector: konnector,
|
|
106
|
+
t: t
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
case 9:
|
|
110
|
+
expect(flow.handleFormSubmit).toHaveBeenCalledWith({
|
|
111
|
+
client: client,
|
|
112
|
+
konnector: konnector,
|
|
113
|
+
t: t,
|
|
114
|
+
account: _objectSpread(_objectSpread(_objectSpread({}, account), {
|
|
115
|
+
auth: {
|
|
116
|
+
bankId: TEST_BANK_COZY_ID
|
|
117
|
+
}
|
|
118
|
+
}), {
|
|
119
|
+
data: {
|
|
120
|
+
auth: {
|
|
121
|
+
bi: {
|
|
122
|
+
connId: 12
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
case 10:
|
|
130
|
+
case "end":
|
|
131
|
+
return _context2.stop();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}, _callee2);
|
|
135
|
+
})));
|
|
136
|
+
});
|
|
137
|
+
describe('checkBIConnection', function () {
|
|
138
|
+
var setup = function setup() {
|
|
139
|
+
var client = new CozyClient({
|
|
140
|
+
uri: 'http://testcozy.mycozy.cloud'
|
|
141
|
+
});
|
|
142
|
+
var flow = new ConnectionFlow(client, {
|
|
143
|
+
konnector: konnector,
|
|
144
|
+
account: account
|
|
145
|
+
});
|
|
146
|
+
client.stackClient.jobs.create = jest.fn().mockReturnValue({
|
|
147
|
+
data: {
|
|
148
|
+
attributes: {
|
|
149
|
+
_id: 'job-id-1337'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
waitForRealtimeEvent.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
|
|
154
|
+
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
|
|
155
|
+
while (1) {
|
|
156
|
+
switch (_context3.prev = _context3.next) {
|
|
157
|
+
case 0:
|
|
158
|
+
sleep(2);
|
|
159
|
+
return _context3.abrupt("return", {
|
|
160
|
+
data: {
|
|
161
|
+
result: {
|
|
162
|
+
mode: 'prod',
|
|
163
|
+
url: 'https://cozy.biapi.pro/2.0',
|
|
164
|
+
publicKey: biPublicKeyProd,
|
|
165
|
+
code: 'bi-temporary-access-token-145613'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
case 2:
|
|
171
|
+
case "end":
|
|
172
|
+
return _context3.stop();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}, _callee3);
|
|
176
|
+
})));
|
|
177
|
+
jest.spyOn(client, 'query').mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
|
|
178
|
+
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
|
|
179
|
+
while (1) {
|
|
180
|
+
switch (_context4.prev = _context4.next) {
|
|
181
|
+
case 0:
|
|
182
|
+
return _context4.abrupt("return", {
|
|
183
|
+
data: []
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
case 1:
|
|
187
|
+
case "end":
|
|
188
|
+
return _context4.stop();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}, _callee4);
|
|
192
|
+
})));
|
|
193
|
+
return {
|
|
194
|
+
client: client,
|
|
195
|
+
flow: flow
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
it('should refuse to create an account with a bi connection id which already exists', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
|
|
200
|
+
var _setup, client, flow, account, konnector;
|
|
201
|
+
|
|
202
|
+
return _regeneratorRuntime.wrap(function _callee6$(_context6) {
|
|
203
|
+
while (1) {
|
|
204
|
+
switch (_context6.prev = _context6.next) {
|
|
205
|
+
case 0:
|
|
206
|
+
_setup = setup(), client = _setup.client, flow = _setup.flow;
|
|
207
|
+
getBIConnection.mockReset().mockResolvedValue({
|
|
208
|
+
id: 12
|
|
209
|
+
});
|
|
210
|
+
account = {};
|
|
211
|
+
konnector = {
|
|
212
|
+
slug: 'bankingconnectortest',
|
|
213
|
+
parameters: {
|
|
214
|
+
bankId: TEST_BANK_COZY_ID
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
jest.spyOn(flow, 'saveAccount').mockImplementation(function (account) {
|
|
218
|
+
return _objectSpread({
|
|
219
|
+
_id: 'created-account-id'
|
|
220
|
+
}, account);
|
|
221
|
+
});
|
|
222
|
+
jest.spyOn(client, 'query').mockImplementation( /*#__PURE__*/function () {
|
|
223
|
+
var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(_ref6) {
|
|
224
|
+
var doctype;
|
|
225
|
+
return _regeneratorRuntime.wrap(function _callee5$(_context5) {
|
|
226
|
+
while (1) {
|
|
227
|
+
switch (_context5.prev = _context5.next) {
|
|
228
|
+
case 0:
|
|
229
|
+
doctype = _ref6.doctype;
|
|
230
|
+
|
|
231
|
+
if (!(doctype === 'io.cozy.accounts')) {
|
|
232
|
+
_context5.next = 5;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return _context5.abrupt("return", {
|
|
237
|
+
data: [{
|
|
238
|
+
_id: 'account_id',
|
|
239
|
+
auth: {
|
|
240
|
+
bi: {
|
|
241
|
+
connId: 12
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}]
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
case 5:
|
|
248
|
+
if (!(doctype === 'io.cozy.triggers')) {
|
|
249
|
+
_context5.next = 9;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return _context5.abrupt("return", {
|
|
254
|
+
data: [{
|
|
255
|
+
message: {
|
|
256
|
+
account: 'account_id'
|
|
257
|
+
}
|
|
258
|
+
}]
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
case 9:
|
|
262
|
+
throw new Error('unexpected doctype ' + doctype);
|
|
263
|
+
|
|
264
|
+
case 10:
|
|
265
|
+
case "end":
|
|
266
|
+
return _context5.stop();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}, _callee5);
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
return function (_x2) {
|
|
273
|
+
return _ref7.apply(this, arguments);
|
|
274
|
+
};
|
|
275
|
+
}());
|
|
276
|
+
_context6.next = 8;
|
|
277
|
+
return expect(checkBIConnection({
|
|
278
|
+
client: client,
|
|
279
|
+
flow: flow,
|
|
280
|
+
account: account,
|
|
281
|
+
konnector: konnector
|
|
282
|
+
})).rejects.toEqual(new Error('ACCOUNT_WITH_SAME_IDENTIFIER_ALREADY_DEFINED'));
|
|
283
|
+
|
|
284
|
+
case 8:
|
|
285
|
+
case "end":
|
|
286
|
+
return _context6.stop();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}, _callee6);
|
|
290
|
+
})));
|
|
291
|
+
});
|
|
292
|
+
describe('isBiWebViewConnector', function () {
|
|
293
|
+
var BIConnector = {
|
|
294
|
+
slug: 'biconnector',
|
|
295
|
+
partnership: {
|
|
296
|
+
domain: 'budget-insight.com'
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
var notBIConnector = {
|
|
300
|
+
slug: 'otherconnector'
|
|
301
|
+
};
|
|
302
|
+
it('should return true if the connector is a BI connector and if the "harvest.bi.webview" is activated', function () {
|
|
303
|
+
flag('harvest.bi.webview', true);
|
|
304
|
+
expect(isBiWebViewConnector(BIConnector)).toEqual(true);
|
|
305
|
+
});
|
|
306
|
+
it('should return false if the connector is not a BI connector', function () {
|
|
307
|
+
flag('harvest.bi.webview', true);
|
|
308
|
+
expect(isBiWebViewConnector(notBIConnector)).toEqual(false);
|
|
309
|
+
});
|
|
310
|
+
it('should return false if the "harvest.bi.webview" flag is not activated', function () {
|
|
311
|
+
flag('harvest.bi.webview', false);
|
|
312
|
+
expect(isBiWebViewConnector(BIConnector)).toEqual(false);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -61,7 +61,7 @@ var extraBIErrorMap = {
|
|
|
61
61
|
* Converts and chains error
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
|
-
var convertBIErrortoKonnectorJobError = function convertBIErrortoKonnectorJobError(error) {
|
|
64
|
+
export var convertBIErrortoKonnectorJobError = function convertBIErrortoKonnectorJobError(error) {
|
|
65
65
|
var errorCode = error ? error.code : null;
|
|
66
66
|
var cozyErrorMessage = errorCode ? biErrorMap[errorCode] || extraBIErrorMap[errorCode] || null : null;
|
|
67
67
|
var errorMessage = cozyErrorMessage || (errorCode ? "UNKNOWN_ERROR.".concat(errorCode) : 'UNKNOWN_ERROR');
|
|
@@ -69,12 +69,12 @@ var convertBIErrortoKonnectorJobError = function convertBIErrortoKonnectorJobErr
|
|
|
69
69
|
err.original = error;
|
|
70
70
|
throw err;
|
|
71
71
|
};
|
|
72
|
-
|
|
73
72
|
export var isBudgetInsightConnector = function isBudgetInsightConnector(konnector) {
|
|
74
|
-
|
|
75
|
-
};
|
|
73
|
+
var _konnector$partnershi;
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
return (konnector === null || konnector === void 0 ? void 0 : (_konnector$partnershi = konnector.partnership) === null || _konnector$partnershi === void 0 ? void 0 : _konnector$partnershi.domain) === 'budget-insight.com';
|
|
76
|
+
};
|
|
77
|
+
export var createTemporaryToken = /*#__PURE__*/function () {
|
|
78
78
|
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(_ref) {
|
|
79
79
|
var client, konnector, account, cozyBankId, jobResponse, event;
|
|
80
80
|
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
@@ -116,7 +116,6 @@ var createTemporaryToken = /*#__PURE__*/function () {
|
|
|
116
116
|
return _ref2.apply(this, arguments);
|
|
117
117
|
};
|
|
118
118
|
}();
|
|
119
|
-
|
|
120
119
|
export var saveBIConfig = function saveBIConfig(flow, biConfig) {
|
|
121
120
|
return flow.setData({
|
|
122
121
|
biConfig: biConfig
|
|
@@ -281,9 +280,6 @@ export var setBIConnectionId = function setBIConnectionId(originalAccount, biCon
|
|
|
281
280
|
set(account, 'data.auth.bi.connId', biConnectionId);
|
|
282
281
|
return account;
|
|
283
282
|
};
|
|
284
|
-
export var getBIConnectionId = function getBIConnectionId(account) {
|
|
285
|
-
return get(account, 'data.auth.bi.connId');
|
|
286
|
-
};
|
|
287
283
|
/**
|
|
288
284
|
* Handles webauth connection
|
|
289
285
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-harvest-lib",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "Provides logic, modules and components for Cozy's harvest applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"cozy-client": "27.17.0",
|
|
56
56
|
"cozy-device-helper": "^2.0.0",
|
|
57
57
|
"cozy-flags": "^2.8.7",
|
|
58
|
-
"cozy-intent": "^1.17.
|
|
58
|
+
"cozy-intent": "^1.17.2",
|
|
59
59
|
"cozy-keys-lib": "^4.1.9",
|
|
60
60
|
"cozy-realtime": "^4.0.8",
|
|
61
61
|
"cozy-ui": "60.6.0",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"react-router-dom": "^5.0.1"
|
|
88
88
|
},
|
|
89
89
|
"sideEffects": false,
|
|
90
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "a07bb44357ebb8ad18b7e16419a634c42c416a0d"
|
|
91
91
|
}
|
|
@@ -326,8 +326,9 @@ export class DumbTriggerManager extends Component {
|
|
|
326
326
|
const showSpinner = submitting && selectedCipher && step === 'ciphersList'
|
|
327
327
|
const showCiphersList = step === 'ciphersList'
|
|
328
328
|
const showAccountForm = step === 'accountForm'
|
|
329
|
+
const konnectorPolicy = findKonnectorPolicy(konnector)
|
|
329
330
|
|
|
330
|
-
if (oauth) {
|
|
331
|
+
if (oauth || konnectorPolicy.isBIWebView) {
|
|
331
332
|
return (
|
|
332
333
|
<OAuthForm
|
|
333
334
|
client={client}
|
package/src/helpers/oauth.js
CHANGED
|
@@ -92,7 +92,7 @@ export const handleOAuthResponse = (options = {}) => {
|
|
|
92
92
|
* @param {string} cozyUrl cozy url
|
|
93
93
|
* @param {string} accountType connector slug
|
|
94
94
|
* @param {string} oAuthStateKey localStorage key
|
|
95
|
-
* @param {Object} oAuthConf
|
|
95
|
+
* @param {Object} [oAuthConf={}] connector manifest oauth configuration
|
|
96
96
|
* @param {string} redirectSlug The app we want to redirect the user on after the end of the flow
|
|
97
97
|
* @param {string} nonce unique nonce string
|
|
98
98
|
* @param {Object} extraParams some extra parameters to add to the query string
|
|
@@ -101,7 +101,7 @@ export const getOAuthUrl = ({
|
|
|
101
101
|
cozyUrl,
|
|
102
102
|
accountType,
|
|
103
103
|
oAuthStateKey,
|
|
104
|
-
oAuthConf,
|
|
104
|
+
oAuthConf = {},
|
|
105
105
|
nonce,
|
|
106
106
|
redirectSlug,
|
|
107
107
|
extraParams
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logger from './logger'
|
|
2
2
|
import { konnectorPolicy as biKonnectorPolicy } from './services/budget-insight'
|
|
3
|
+
import { konnectorPolicy as biWebViewPolicy } from './services/biWebView'
|
|
3
4
|
|
|
4
5
|
const defaultKonnectorPolicy = {
|
|
5
6
|
accountContainsAuth: true,
|
|
@@ -9,7 +10,11 @@ const defaultKonnectorPolicy = {
|
|
|
9
10
|
name: 'default'
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const policies = [
|
|
13
|
+
const policies = [
|
|
14
|
+
biWebViewPolicy,
|
|
15
|
+
biKonnectorPolicy,
|
|
16
|
+
defaultKonnectorPolicy
|
|
17
|
+
].filter(Boolean)
|
|
13
18
|
|
|
14
19
|
logger.info('Available konnector policies', policies)
|
|
15
20
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface between Budget Insight and Cozy using BI's webview
|
|
3
|
+
*
|
|
4
|
+
* - Deals with the konnector to get temporary tokens
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getBIConnection } from './bi-http'
|
|
8
|
+
import assert from '../assert'
|
|
9
|
+
import logger from '../logger'
|
|
10
|
+
import flag from 'cozy-flags'
|
|
11
|
+
import {
|
|
12
|
+
fetchExtraOAuthUrlParams,
|
|
13
|
+
createTemporaryToken,
|
|
14
|
+
setBIConnectionId,
|
|
15
|
+
saveBIConfig,
|
|
16
|
+
findAccountWithBiConnection,
|
|
17
|
+
convertBIErrortoKonnectorJobError,
|
|
18
|
+
isBudgetInsightConnector
|
|
19
|
+
} from './budget-insight'
|
|
20
|
+
import { KonnectorJobError } from '../helpers/konnectors'
|
|
21
|
+
|
|
22
|
+
export const isBiWebViewConnector = konnector =>
|
|
23
|
+
flag('harvest.bi.webview') && isBudgetInsightConnector(konnector)
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Runs multiple checks on the bi connection referenced in the given account
|
|
27
|
+
*
|
|
28
|
+
* @param {io.cozy.accounts} options.account The account content
|
|
29
|
+
* @param {ConnectionFlow} options.flow
|
|
30
|
+
* @param {io.cozy.konnectors} options.konnector connector manifest content
|
|
31
|
+
* @param {CozyClient} options.client CozyClient object
|
|
32
|
+
*
|
|
33
|
+
* @return {Integer} Connection Id
|
|
34
|
+
*/
|
|
35
|
+
export const checkBIConnection = async ({
|
|
36
|
+
account,
|
|
37
|
+
client,
|
|
38
|
+
konnector,
|
|
39
|
+
flow
|
|
40
|
+
}) => {
|
|
41
|
+
try {
|
|
42
|
+
let connId = getWebviewBIConnectionId(account)
|
|
43
|
+
|
|
44
|
+
logger.info('Creating temporary token...')
|
|
45
|
+
|
|
46
|
+
const biConfig = await createTemporaryToken({
|
|
47
|
+
client,
|
|
48
|
+
konnector,
|
|
49
|
+
account
|
|
50
|
+
})
|
|
51
|
+
saveBIConfig(flow, biConfig)
|
|
52
|
+
|
|
53
|
+
const { code: tempToken, ...config } = biConfig
|
|
54
|
+
|
|
55
|
+
logger.info('Created temporary token')
|
|
56
|
+
assert(tempToken, 'No temporary token')
|
|
57
|
+
|
|
58
|
+
logger.info(`fetch connection ${connId}...`)
|
|
59
|
+
|
|
60
|
+
const connection = await getBIConnection(config, connId, tempToken)
|
|
61
|
+
|
|
62
|
+
const sameAccount = await findAccountWithBiConnection({
|
|
63
|
+
client,
|
|
64
|
+
konnector,
|
|
65
|
+
connectionId: connection.id
|
|
66
|
+
})
|
|
67
|
+
if (sameAccount) {
|
|
68
|
+
const err = new KonnectorJobError(
|
|
69
|
+
'ACCOUNT_WITH_SAME_IDENTIFIER_ALREADY_DEFINED'
|
|
70
|
+
)
|
|
71
|
+
err.accountId = sameAccount._id
|
|
72
|
+
throw err
|
|
73
|
+
}
|
|
74
|
+
return connection
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return convertBIErrortoKonnectorJobError(err)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Handles webview connection
|
|
82
|
+
*
|
|
83
|
+
* @param {io.cozy.accounts} options.account The account content
|
|
84
|
+
* @param {ConnectionFlow} options.flow
|
|
85
|
+
* @param {io.cozy.konnectors} options.konnector connector manifest content
|
|
86
|
+
* @param {CozyClient} options.client CozyClient object
|
|
87
|
+
*
|
|
88
|
+
* @return {Integer} Connection Id
|
|
89
|
+
*/
|
|
90
|
+
export const handleOAuthAccount = async ({
|
|
91
|
+
account,
|
|
92
|
+
flow,
|
|
93
|
+
konnector,
|
|
94
|
+
client,
|
|
95
|
+
t
|
|
96
|
+
}) => {
|
|
97
|
+
const cozyBankId = getCozyBankId({ konnector, account })
|
|
98
|
+
let biWebviewAccount = {
|
|
99
|
+
...account,
|
|
100
|
+
...(cozyBankId ? { auth: { bankId: cozyBankId } } : {})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const connectionId = getWebviewBIConnectionId(biWebviewAccount)
|
|
104
|
+
|
|
105
|
+
if (connectionId) {
|
|
106
|
+
logger.info(`Found a BI webview connection id: ${connectionId}`)
|
|
107
|
+
flow.konnector = konnector
|
|
108
|
+
biWebviewAccount = await flow.saveAccount(
|
|
109
|
+
setBIConnectionId(biWebviewAccount, connectionId)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
await flow.handleFormSubmit({
|
|
113
|
+
client,
|
|
114
|
+
account: biWebviewAccount,
|
|
115
|
+
konnector,
|
|
116
|
+
t
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return connectionId
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Gets BI webview connection id which is returned in the account by the stack
|
|
125
|
+
* via oauth callback url
|
|
126
|
+
*
|
|
127
|
+
* @param {io.cozy.accounts} The account content created by the stack
|
|
128
|
+
*
|
|
129
|
+
* @return {Integer} Connection Id
|
|
130
|
+
*/
|
|
131
|
+
const getWebviewBIConnectionId = account => {
|
|
132
|
+
return Number(account?.oauth?.query?.connection_id?.[0] || null)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Hook from ConnectionFlow after account creation
|
|
137
|
+
*
|
|
138
|
+
* @param {io.cozy.accounts} options.account - created account
|
|
139
|
+
* @param {io.cozy.konnectors} options.konnector - manifest of the konnector for which the account is created
|
|
140
|
+
* @param {ConnectionFlow} options.flow - current ConnectionFlow instance
|
|
141
|
+
* @param {CozyClient} options.client - current CozyClient instance
|
|
142
|
+
*
|
|
143
|
+
* @returns {Promise<io.cozy.accounts>}
|
|
144
|
+
*/
|
|
145
|
+
export const onBIAccountCreation = async ({
|
|
146
|
+
account: fullAccount,
|
|
147
|
+
client,
|
|
148
|
+
flow,
|
|
149
|
+
konnector
|
|
150
|
+
}) => {
|
|
151
|
+
const account = await flow.saveAccount(fullAccount)
|
|
152
|
+
|
|
153
|
+
const biConnection = await checkBIConnection({
|
|
154
|
+
account: {
|
|
155
|
+
...fullAccount,
|
|
156
|
+
_id: account._id
|
|
157
|
+
},
|
|
158
|
+
client,
|
|
159
|
+
konnector,
|
|
160
|
+
flow
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
flow.setData({
|
|
164
|
+
biConnection
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
return await flow.saveAccount(setBIConnectionId(account, biConnection.id))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Finds the current bankIid in a given konnector or account
|
|
172
|
+
*
|
|
173
|
+
* @param {io.cozy.accounts} options.account The account content
|
|
174
|
+
* @param {io.cozy.konnectors} options.konnector connector manifest content
|
|
175
|
+
*/
|
|
176
|
+
export const getCozyBankId = ({ konnector, account }) => {
|
|
177
|
+
const cozyBankId = konnector?.parameters?.bankId || account?.auth?.bankId
|
|
178
|
+
if (!cozyBankId) {
|
|
179
|
+
logger.error('Could not find any bank id')
|
|
180
|
+
}
|
|
181
|
+
return cozyBankId
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const konnectorPolicy = {
|
|
185
|
+
isBIWebView: true,
|
|
186
|
+
name: 'budget-insight-webview',
|
|
187
|
+
match: isBiWebViewConnector,
|
|
188
|
+
saveInVault: false,
|
|
189
|
+
onAccountCreation: onBIAccountCreation,
|
|
190
|
+
fetchExtraOAuthUrlParams: fetchExtraOAuthUrlParams,
|
|
191
|
+
handleOAuthAccount
|
|
192
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import CozyClient from 'cozy-client'
|
|
2
|
+
import {
|
|
3
|
+
handleOAuthAccount,
|
|
4
|
+
checkBIConnection,
|
|
5
|
+
isBiWebViewConnector
|
|
6
|
+
} from './biWebView'
|
|
7
|
+
import ConnectionFlow from '../models/ConnectionFlow'
|
|
8
|
+
import { waitForRealtimeEvent } from './jobUtils'
|
|
9
|
+
import biPublicKeyProd from './bi-public-key-prod.json'
|
|
10
|
+
import flag from 'cozy-flags'
|
|
11
|
+
|
|
12
|
+
jest.mock('./bi-http', () => ({
|
|
13
|
+
createBIConnection: jest
|
|
14
|
+
.fn()
|
|
15
|
+
.mockResolvedValue({ text: Promise.resolve('{}') }),
|
|
16
|
+
updateBIConnection: jest.fn(),
|
|
17
|
+
getBIConnection: jest.fn()
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
import { getBIConnection } from './bi-http'
|
|
21
|
+
|
|
22
|
+
jest.mock('cozy-logger', () => ({
|
|
23
|
+
namespace: () => () => {}
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
jest.mock('./jobUtils', () => ({
|
|
27
|
+
waitForRealtimeEvent: jest.fn()
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
const sleep = duration => new Promise(resolve => setTimeout(resolve, duration))
|
|
31
|
+
|
|
32
|
+
const TEST_BANK_COZY_ID = '100000'
|
|
33
|
+
|
|
34
|
+
const konnector = {
|
|
35
|
+
slug: 'boursorama83',
|
|
36
|
+
parameters: {
|
|
37
|
+
bankId: TEST_BANK_COZY_ID
|
|
38
|
+
},
|
|
39
|
+
partnership: {
|
|
40
|
+
domain: 'https://budget-insight.com'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const account = {
|
|
45
|
+
_id: '1337'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('handleOAuthAccount', () => {
|
|
49
|
+
it('should handle bi webview authentication if any connection is found in the account', async () => {
|
|
50
|
+
const client = new CozyClient({
|
|
51
|
+
uri: 'http://testcozy.mycozy.cloud'
|
|
52
|
+
})
|
|
53
|
+
const flow = new ConnectionFlow(client, null, konnector)
|
|
54
|
+
flow.account = account
|
|
55
|
+
flow.handleFormSubmit = jest.fn()
|
|
56
|
+
flow.saveAccount = async account => account
|
|
57
|
+
const account = { oauth: { query: { connection_id: ['12'] } } }
|
|
58
|
+
const t = jest.fn()
|
|
59
|
+
await handleOAuthAccount({
|
|
60
|
+
account,
|
|
61
|
+
flow,
|
|
62
|
+
client,
|
|
63
|
+
konnector,
|
|
64
|
+
t
|
|
65
|
+
})
|
|
66
|
+
expect(flow.handleFormSubmit).toHaveBeenCalledWith({
|
|
67
|
+
client,
|
|
68
|
+
konnector,
|
|
69
|
+
t,
|
|
70
|
+
account: {
|
|
71
|
+
...account,
|
|
72
|
+
...{ auth: { bankId: TEST_BANK_COZY_ID } },
|
|
73
|
+
...{ data: { auth: { bi: { connId: 12 } } } }
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('checkBIConnection', () => {
|
|
80
|
+
const setup = () => {
|
|
81
|
+
const client = new CozyClient({
|
|
82
|
+
uri: 'http://testcozy.mycozy.cloud'
|
|
83
|
+
})
|
|
84
|
+
const flow = new ConnectionFlow(client, { konnector, account })
|
|
85
|
+
client.stackClient.jobs.create = jest.fn().mockReturnValue({
|
|
86
|
+
data: {
|
|
87
|
+
attributes: {
|
|
88
|
+
_id: 'job-id-1337'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
waitForRealtimeEvent.mockImplementation(async () => {
|
|
93
|
+
sleep(2)
|
|
94
|
+
return {
|
|
95
|
+
data: {
|
|
96
|
+
result: {
|
|
97
|
+
mode: 'prod',
|
|
98
|
+
url: 'https://cozy.biapi.pro/2.0',
|
|
99
|
+
publicKey: biPublicKeyProd,
|
|
100
|
+
code: 'bi-temporary-access-token-145613'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
jest.spyOn(client, 'query').mockImplementation(async () => ({ data: [] }))
|
|
106
|
+
|
|
107
|
+
return { client, flow }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
it('should refuse to create an account with a bi connection id which already exists', async () => {
|
|
111
|
+
const { client, flow } = setup()
|
|
112
|
+
|
|
113
|
+
getBIConnection.mockReset().mockResolvedValue({
|
|
114
|
+
id: 12
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const account = {}
|
|
118
|
+
|
|
119
|
+
const konnector = {
|
|
120
|
+
slug: 'bankingconnectortest',
|
|
121
|
+
parameters: {
|
|
122
|
+
bankId: TEST_BANK_COZY_ID
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
jest.spyOn(flow, 'saveAccount').mockImplementation(account => ({
|
|
127
|
+
_id: 'created-account-id',
|
|
128
|
+
...account
|
|
129
|
+
}))
|
|
130
|
+
|
|
131
|
+
jest.spyOn(client, 'query').mockImplementation(async ({ doctype }) => {
|
|
132
|
+
if (doctype === 'io.cozy.accounts') {
|
|
133
|
+
return {
|
|
134
|
+
data: [
|
|
135
|
+
{
|
|
136
|
+
_id: 'account_id',
|
|
137
|
+
auth: { bi: { connId: 12 } }
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
} else if (doctype === 'io.cozy.triggers') {
|
|
142
|
+
return {
|
|
143
|
+
data: [
|
|
144
|
+
{
|
|
145
|
+
message: { account: 'account_id' }
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
throw new Error('unexpected doctype ' + doctype)
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
await expect(
|
|
155
|
+
checkBIConnection({
|
|
156
|
+
client,
|
|
157
|
+
flow,
|
|
158
|
+
account,
|
|
159
|
+
konnector
|
|
160
|
+
})
|
|
161
|
+
).rejects.toEqual(new Error('ACCOUNT_WITH_SAME_IDENTIFIER_ALREADY_DEFINED'))
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('isBiWebViewConnector', () => {
|
|
166
|
+
const BIConnector = {
|
|
167
|
+
slug: 'biconnector',
|
|
168
|
+
partnership: { domain: 'budget-insight.com' }
|
|
169
|
+
}
|
|
170
|
+
const notBIConnector = {
|
|
171
|
+
slug: 'otherconnector'
|
|
172
|
+
}
|
|
173
|
+
it('should return true if the connector is a BI connector and if the "harvest.bi.webview" is activated', () => {
|
|
174
|
+
flag('harvest.bi.webview', true)
|
|
175
|
+
expect(isBiWebViewConnector(BIConnector)).toEqual(true)
|
|
176
|
+
})
|
|
177
|
+
it('should return false if the connector is not a BI connector', () => {
|
|
178
|
+
flag('harvest.bi.webview', true)
|
|
179
|
+
expect(isBiWebViewConnector(notBIConnector)).toEqual(false)
|
|
180
|
+
})
|
|
181
|
+
it('should return false if the "harvest.bi.webview" flag is not activated', () => {
|
|
182
|
+
flag('harvest.bi.webview', false)
|
|
183
|
+
expect(isBiWebViewConnector(BIConnector)).toEqual(false)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
@@ -52,7 +52,7 @@ const extraBIErrorMap = {
|
|
|
52
52
|
/**
|
|
53
53
|
* Converts and chains error
|
|
54
54
|
*/
|
|
55
|
-
const convertBIErrortoKonnectorJobError = error => {
|
|
55
|
+
export const convertBIErrortoKonnectorJobError = error => {
|
|
56
56
|
const errorCode = error ? error.code : null
|
|
57
57
|
const cozyErrorMessage = errorCode
|
|
58
58
|
? biErrorMap[errorCode] || extraBIErrorMap[errorCode] || null
|
|
@@ -65,14 +65,10 @@ const convertBIErrortoKonnectorJobError = error => {
|
|
|
65
65
|
throw err
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export const isBudgetInsightConnector = konnector =>
|
|
69
|
-
|
|
70
|
-
konnector.partnership &&
|
|
71
|
-
konnector.partnership.domain.includes('budget-insight')
|
|
72
|
-
)
|
|
73
|
-
}
|
|
68
|
+
export const isBudgetInsightConnector = konnector =>
|
|
69
|
+
konnector?.partnership?.domain === 'budget-insight.com'
|
|
74
70
|
|
|
75
|
-
const createTemporaryToken = async ({ client, konnector, account }) => {
|
|
71
|
+
export const createTemporaryToken = async ({ client, konnector, account }) => {
|
|
76
72
|
assert(
|
|
77
73
|
konnector.slug,
|
|
78
74
|
'createTemporaryToken: konnector passed in options has no slug'
|
|
@@ -215,10 +211,6 @@ export const setBIConnectionId = (originalAccount, biConnectionId) => {
|
|
|
215
211
|
return account
|
|
216
212
|
}
|
|
217
213
|
|
|
218
|
-
export const getBIConnectionId = account => {
|
|
219
|
-
return get(account, 'data.auth.bi.connId')
|
|
220
|
-
}
|
|
221
|
-
|
|
222
214
|
/**
|
|
223
215
|
* Handles webauth connection
|
|
224
216
|
*
|