@kineticdata/react 6.1.6 → 7.0.0-rc1

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.
@@ -0,0 +1,360 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault")["default"];
4
+ var _regeneratorRuntime2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/regeneratorRuntime"));
5
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/asyncToGenerator"));
6
+ var _util = require("util");
7
+ var _axios = _interopRequireDefault(require("axios"));
8
+ var _authentication = require("./authentication");
9
+ // Polyfill TextEncoder for jsdom environment
10
+ global.TextEncoder = _util.TextEncoder;
11
+ jest.mock('../../helpers', function () {
12
+ return {
13
+ bundle: {
14
+ spaceLocation: function spaceLocation() {
15
+ return 'https://test.kinetic.com/space';
16
+ }
17
+ }
18
+ };
19
+ });
20
+ jest.mock('./profile', function () {
21
+ return {
22
+ fetchProfile: jest.fn()
23
+ };
24
+ });
25
+
26
+ /**
27
+ * Simulates the hidden iframe flow used by attemptAuthorize.
28
+ *
29
+ * @param {string|Function} hrefOrFn - Either a static href string the iframe
30
+ * will "navigate" to, or a function called on each load that returns the href.
31
+ */
32
+ function installIframeTrap(hrefOrFn) {
33
+ var origCreate = document.createElement.bind(document);
34
+ jest.spyOn(document, 'createElement').mockImplementation(function (tag) {
35
+ if (tag !== 'iframe') return origCreate(tag);
36
+ var iframe = origCreate('iframe');
37
+ var srcValue;
38
+ Object.defineProperty(iframe, 'src', {
39
+ get: function get() {
40
+ return srcValue;
41
+ },
42
+ set: function set(val) {
43
+ srcValue = val;
44
+ // Simulate async load on next microtask
45
+ Promise.resolve().then(function () {
46
+ var href = typeof hrefOrFn === 'function' ? hrefOrFn() : hrefOrFn;
47
+ Object.defineProperty(iframe, 'contentWindow', {
48
+ value: {
49
+ location: {
50
+ href: href
51
+ }
52
+ },
53
+ configurable: true
54
+ });
55
+ if (iframe.onload) iframe.onload();
56
+ });
57
+ }
58
+ });
59
+ return iframe;
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Like installIframeTrap but the first onload triggers a cross-origin error
65
+ * before a second onload resolves with the given href.
66
+ */
67
+ function installCrossOriginIframeTrap(successHref) {
68
+ var origCreate = document.createElement.bind(document);
69
+ var loadCount = 0;
70
+ jest.spyOn(document, 'createElement').mockImplementation(function (tag) {
71
+ if (tag !== 'iframe') return origCreate(tag);
72
+ var iframe = origCreate('iframe');
73
+ var srcValue;
74
+ Object.defineProperty(iframe, 'src', {
75
+ get: function get() {
76
+ return srcValue;
77
+ },
78
+ set: function set(val) {
79
+ srcValue = val;
80
+ Promise.resolve().then(function () {
81
+ loadCount++;
82
+ if (loadCount === 1) {
83
+ Object.defineProperty(iframe, 'contentWindow', {
84
+ get: function get() {
85
+ throw new DOMException('Blocked a frame');
86
+ },
87
+ configurable: true
88
+ });
89
+ if (iframe.onload) iframe.onload();
90
+
91
+ // Fire a second load with the successful response
92
+ Promise.resolve().then(function () {
93
+ Object.defineProperty(iframe, 'contentWindow', {
94
+ value: {
95
+ location: {
96
+ href: successHref
97
+ }
98
+ },
99
+ configurable: true
100
+ });
101
+ if (iframe.onload) iframe.onload();
102
+ });
103
+ }
104
+ });
105
+ }
106
+ });
107
+ return iframe;
108
+ });
109
+ }
110
+ describe('authentication api', function () {
111
+ var savedCrypto;
112
+ beforeAll(function () {
113
+ savedCrypto = global.crypto;
114
+ Object.defineProperty(global, 'crypto', {
115
+ value: {
116
+ getRandomValues: jest.fn(function (array) {
117
+ array.fill(0);
118
+ return array;
119
+ }),
120
+ subtle: {
121
+ digest: jest.fn(function () {
122
+ return Promise.resolve(new Uint8Array(32).buffer);
123
+ })
124
+ }
125
+ },
126
+ writable: true
127
+ });
128
+ });
129
+ afterAll(function () {
130
+ Object.defineProperty(global, 'crypto', {
131
+ value: savedCrypto,
132
+ writable: true
133
+ });
134
+ });
135
+ afterEach(function () {
136
+ jest.restoreAllMocks();
137
+ });
138
+ describe('#login', function () {
139
+ test('posts credentials to the login endpoint', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee() {
140
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee$(_context) {
141
+ while (1) switch (_context.prev = _context.next) {
142
+ case 0:
143
+ _axios["default"].post = jest.fn(function () {
144
+ return Promise.resolve({
145
+ status: 200,
146
+ data: {}
147
+ });
148
+ });
149
+ _context.next = 3;
150
+ return (0, _authentication.login)({
151
+ username: 'admin',
152
+ password: 'secret'
153
+ });
154
+ case 3:
155
+ expect(_axios["default"].post).toHaveBeenCalledWith('https://test.kinetic.com/space/app/login.do', {
156
+ j_username: 'admin',
157
+ j_password: 'secret'
158
+ }, {
159
+ __bypassAuthInterceptor: true
160
+ });
161
+ case 4:
162
+ case "end":
163
+ return _context.stop();
164
+ }
165
+ }, _callee);
166
+ })));
167
+ });
168
+ describe('#logoutDirect', function () {
169
+ test('uses the standard logout endpoint', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee2() {
170
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee2$(_context2) {
171
+ while (1) switch (_context2.prev = _context2.next) {
172
+ case 0:
173
+ _axios["default"].get = jest.fn(function () {
174
+ return Promise.resolve({
175
+ status: 200
176
+ });
177
+ });
178
+ _context2.next = 3;
179
+ return (0, _authentication.logoutDirect)(false);
180
+ case 3:
181
+ expect(_axios["default"].get).toHaveBeenCalledWith('https://test.kinetic.com/space/app/logout');
182
+ case 4:
183
+ case "end":
184
+ return _context2.stop();
185
+ }
186
+ }, _callee2);
187
+ })));
188
+ test('uses the SAML logout endpoint when isSaml is true', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee3() {
189
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee3$(_context3) {
190
+ while (1) switch (_context3.prev = _context3.next) {
191
+ case 0:
192
+ _axios["default"].get = jest.fn(function () {
193
+ return Promise.resolve({
194
+ status: 200
195
+ });
196
+ });
197
+ _context3.next = 3;
198
+ return (0, _authentication.logoutDirect)(true);
199
+ case 3:
200
+ expect(_axios["default"].get).toHaveBeenCalledWith('https://test.kinetic.com/space/app/saml2/logout');
201
+ case 4:
202
+ case "end":
203
+ return _context3.stop();
204
+ }
205
+ }, _callee3);
206
+ })));
207
+ });
208
+ describe('#retrieveJwt', function () {
209
+ test('returns the access token on success', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee4() {
210
+ var fakeToken, token, params;
211
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee4$(_context4) {
212
+ while (1) switch (_context4.prev = _context4.next) {
213
+ case 0:
214
+ fakeToken = 'jwt-token-123';
215
+ _axios["default"].post = jest.fn(function () {
216
+ return Promise.resolve({
217
+ data: {
218
+ access_token: fakeToken
219
+ }
220
+ });
221
+ });
222
+ installIframeTrap("".concat(window.location.origin, "/app/oauth/callback?code=auth-code-xyz"));
223
+ _context4.next = 5;
224
+ return (0, _authentication.retrieveJwt)();
225
+ case 5:
226
+ token = _context4.sent;
227
+ expect(token).toBe(fakeToken);
228
+ expect(_axios["default"].post).toHaveBeenCalledWith('https://test.kinetic.com/space/app/oauth2/token', expect.any(URLSearchParams), expect.objectContaining({
229
+ headers: {
230
+ 'Content-Type': 'application/x-www-form-urlencoded'
231
+ },
232
+ __bypassAuthInterceptor: true,
233
+ __bypassInitInterceptor: true
234
+ }));
235
+ params = _axios["default"].post.mock.calls[0][1];
236
+ expect(params.get('grant_type')).toBe('authorization_code');
237
+ expect(params.get('code')).toBe('auth-code-xyz');
238
+ expect(params.get('redirect_uri')).toContain('/app/oauth/callback');
239
+ case 12:
240
+ case "end":
241
+ return _context4.stop();
242
+ }
243
+ }, _callee4);
244
+ })));
245
+ test('returns undefined when the user is unauthenticated', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee5() {
246
+ var token;
247
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee5$(_context5) {
248
+ while (1) switch (_context5.prev = _context5.next) {
249
+ case 0:
250
+ installIframeTrap('https://test.kinetic.com/space/app/login');
251
+ _context5.next = 3;
252
+ return (0, _authentication.retrieveJwt)();
253
+ case 3:
254
+ token = _context5.sent;
255
+ expect(token).toBeUndefined();
256
+ case 5:
257
+ case "end":
258
+ return _context5.stop();
259
+ }
260
+ }, _callee5);
261
+ })));
262
+ test('returns undefined on OAuth error', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee6() {
263
+ var token;
264
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee6$(_context6) {
265
+ while (1) switch (_context6.prev = _context6.next) {
266
+ case 0:
267
+ installIframeTrap("".concat(window.location.origin, "/app/oauth/callback?error=server_error"));
268
+ _context6.next = 3;
269
+ return (0, _authentication.retrieveJwt)();
270
+ case 3:
271
+ token = _context6.sent;
272
+ expect(token).toBeUndefined();
273
+ case 5:
274
+ case "end":
275
+ return _context6.stop();
276
+ }
277
+ }, _callee6);
278
+ })));
279
+ test('returns undefined when token exchange fails', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee7() {
280
+ var token;
281
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee7$(_context7) {
282
+ while (1) switch (_context7.prev = _context7.next) {
283
+ case 0:
284
+ installIframeTrap("".concat(window.location.origin, "/app/oauth/callback?code=some-code"));
285
+ _axios["default"].post = jest.fn(function () {
286
+ return Promise.reject(new Error('network error'));
287
+ });
288
+ jest.spyOn(console, 'error').mockImplementation(function () {});
289
+ _context7.next = 5;
290
+ return (0, _authentication.retrieveJwt)();
291
+ case 5:
292
+ token = _context7.sent;
293
+ expect(token).toBeUndefined();
294
+ case 7:
295
+ case "end":
296
+ return _context7.stop();
297
+ }
298
+ }, _callee7);
299
+ })));
300
+ test('returns undefined when authorization times out', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee8() {
301
+ var origCreate, promise, token;
302
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee8$(_context8) {
303
+ while (1) switch (_context8.prev = _context8.next) {
304
+ case 0:
305
+ jest.useFakeTimers();
306
+ jest.spyOn(console, 'error').mockImplementation(function () {});
307
+
308
+ // Install an iframe trap that never fires onload
309
+ origCreate = document.createElement.bind(document);
310
+ jest.spyOn(document, 'createElement').mockImplementation(function (tag) {
311
+ if (tag !== 'iframe') return origCreate(tag);
312
+ var iframe = origCreate('iframe');
313
+ Object.defineProperty(iframe, 'src', {
314
+ get: function get() {
315
+ return '';
316
+ },
317
+ set: function set() {}
318
+ });
319
+ return iframe;
320
+ });
321
+ promise = (0, _authentication.retrieveJwt)();
322
+ jest.advanceTimersByTime(15000);
323
+ _context8.next = 8;
324
+ return promise;
325
+ case 8:
326
+ token = _context8.sent;
327
+ expect(token).toBeUndefined();
328
+ jest.useRealTimers();
329
+ case 11:
330
+ case "end":
331
+ return _context8.stop();
332
+ }
333
+ }, _callee8);
334
+ })));
335
+ test('handles cross-origin iframe errors gracefully during redirects', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/(0, _regeneratorRuntime2["default"])().mark(function _callee9() {
336
+ var token;
337
+ return (0, _regeneratorRuntime2["default"])().wrap(function _callee9$(_context9) {
338
+ while (1) switch (_context9.prev = _context9.next) {
339
+ case 0:
340
+ installCrossOriginIframeTrap("".concat(window.location.origin, "/app/oauth/callback?code=final-code"));
341
+ _axios["default"].post = jest.fn(function () {
342
+ return Promise.resolve({
343
+ data: {
344
+ access_token: 'cross-origin-token'
345
+ }
346
+ });
347
+ });
348
+ _context9.next = 4;
349
+ return (0, _authentication.retrieveJwt)();
350
+ case 4:
351
+ token = _context9.sent;
352
+ expect(token).toBe('cross-origin-token');
353
+ case 6:
354
+ case "end":
355
+ return _context9.stop();
356
+ }
357
+ }, _callee9);
358
+ })));
359
+ });
360
+ });
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault")["default"];
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.fetchCustomIndexes = exports.deleteCustomIndex = exports.createCustomIndex = void 0;
8
+ var _axios = _interopRequireDefault(require("axios"));
9
+ var _helpers = require("../../helpers");
10
+ var _http = require("../http");
11
+ var indexesUrl = function indexesUrl(_ref) {
12
+ var kappSlug = _ref.kappSlug,
13
+ formSlug = _ref.formSlug;
14
+ return "".concat(_helpers.bundle.apiLocation(), "/kapps/").concat(encodeURIComponent(kappSlug), "/forms/").concat(encodeURIComponent(formSlug), "/indexes");
15
+ };
16
+ var indexUrl = function indexUrl(_ref2) {
17
+ var kappSlug = _ref2.kappSlug,
18
+ formSlug = _ref2.formSlug,
19
+ indexName = _ref2.indexName;
20
+ return "".concat(indexesUrl({
21
+ kappSlug: kappSlug,
22
+ formSlug: formSlug
23
+ }), "/").concat(encodeURIComponent(indexName));
24
+ };
25
+
26
+ // `key` is server-populated on response and ignored on request. Strip it
27
+ // defensively in case a caller round-trips a fetched index back into create.
28
+ var sanitizePart = function sanitizePart(part) {
29
+ return {
30
+ type: part.type,
31
+ name: part.name
32
+ };
33
+ };
34
+
35
+ /**
36
+ * Fetches all custom Postgres indexes defined on a form.
37
+ *
38
+ * @param {Object} options
39
+ * @param {string} options.kappSlug
40
+ * @param {string} options.formSlug
41
+ * @returns {Promise<{indexes: Object[]}>}
42
+ */
43
+ var fetchCustomIndexes = exports.fetchCustomIndexes = function fetchCustomIndexes() {
44
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
45
+ (0, _http.validateOptions)('fetchCustomIndexes', ['kappSlug', 'formSlug'], options);
46
+ return _axios["default"].get(indexesUrl(options), {
47
+ params: (0, _http.paramBuilder)(options),
48
+ headers: (0, _http.headerBuilder)(options)
49
+ }).then(function (response) {
50
+ return {
51
+ indexes: response.data.indexes
52
+ };
53
+ })["catch"](_http.handleErrors);
54
+ };
55
+
56
+ /**
57
+ * Creates a custom Postgres index on a form's submissions.
58
+ *
59
+ * @param {Object} options
60
+ * @param {string} options.kappSlug
61
+ * @param {string} options.formSlug
62
+ * @param {{name: string, unique?: boolean, parts: Array<{type: 'value'|'property', name: string}>}} options.index
63
+ * @returns {Promise<{index: Object}|{error: Object}>}
64
+ */
65
+ var createCustomIndex = exports.createCustomIndex = function createCustomIndex() {
66
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
67
+ (0, _http.validateOptions)('createCustomIndex', ['kappSlug', 'formSlug', 'index'], options);
68
+ var index = options.index;
69
+ var payload = {
70
+ name: index.name,
71
+ unique: !!index.unique,
72
+ parts: (index.parts || []).map(sanitizePart)
73
+ };
74
+ return _axios["default"].post(indexesUrl(options), payload, {
75
+ params: (0, _http.paramBuilder)(options),
76
+ headers: (0, _http.headerBuilder)(options)
77
+ }).then(function (response) {
78
+ return {
79
+ index: response.data.index
80
+ };
81
+ })["catch"](_http.handleErrors);
82
+ };
83
+
84
+ /**
85
+ * Drops a custom Postgres index by name.
86
+ *
87
+ * @param {Object} options
88
+ * @param {string} options.kappSlug
89
+ * @param {string} options.formSlug
90
+ * @param {string} options.indexName
91
+ * @returns {Promise<{}|{error: Object}>}
92
+ */
93
+ var deleteCustomIndex = exports.deleteCustomIndex = function deleteCustomIndex() {
94
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
95
+ (0, _http.validateOptions)('deleteCustomIndex', ['kappSlug', 'formSlug', 'indexName'], options);
96
+ return _axios["default"]["delete"](indexUrl(options), {
97
+ params: (0, _http.paramBuilder)(options),
98
+ headers: (0, _http.headerBuilder)(options)
99
+ }).then(function () {
100
+ return {};
101
+ })["catch"](_http.handleErrors);
102
+ };