@opencloning/utils 1.8.1 → 1.9.1

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 CHANGED
@@ -1,5 +1,30 @@
1
1
  # @opencloning/utils
2
2
 
3
+ ## 1.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies []:
8
+ - @opencloning/store@1.9.1
9
+
10
+ ## 1.9.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#711](https://github.com/OpenCloning/OpenCloning_frontend/pull/711) [`6813620`](https://github.com/OpenCloning/OpenCloning_frontend/commit/6813620f4a47c44857045866d667474efbf9a64c) Thanks [@manulera](https://github.com/manulera)! - Use authentication for cloning endpoints in opencloning-db
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies []:
19
+ - @opencloning/store@1.9.0
20
+
21
+ ## 1.8.2
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies []:
26
+ - @opencloning/store@1.8.2
27
+
3
28
  ## 1.8.1
4
29
 
5
30
  ### Patch Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "license": "MIT",
3
3
  "name": "@opencloning/utils",
4
- "version": "1.8.1",
4
+ "version": "1.9.1",
5
5
  "type": "module",
6
6
  "main": "./src/utils/index.js",
7
7
  "exports": {
@@ -9,6 +9,7 @@
9
9
  "./readNwrite": "./src/utils/readNwrite.js",
10
10
  "./network": "./src/utils/network.js",
11
11
  "./getHttpClient": "./src/utils/getHttpClient.js",
12
+ "./httpClientAuth": "./src/utils/httpClientAuth.js",
12
13
  "./sourceFunctions": "./src/utils/sourceFunctions.js",
13
14
  "./error2String": "./src/utils/error2String.js",
14
15
  "./enzyme_utils": "./src/utils/enzyme_utils.js",
@@ -30,7 +31,7 @@
30
31
  "directory": "packages/utils"
31
32
  },
32
33
  "dependencies": {
33
- "@opencloning/store": "1.8.1",
34
+ "@opencloning/store": "1.9.1",
34
35
  "@teselagen/bio-parsers": "^0.4.38",
35
36
  "@teselagen/sequence-utils": "^0.3.42",
36
37
  "@zip.js/zip.js": "^2.8.26",
@@ -1,4 +1,5 @@
1
1
  import axios from 'axios';
2
+ import { attachAuthInterceptors } from './httpClientAuth';
2
3
  import urlWhitelist from '../config/urlWhitelist';
3
4
 
4
5
  export default function getHttpClient(extraUrls = []) {
@@ -22,3 +23,9 @@ export default function getHttpClient(extraUrls = []) {
22
23
 
23
24
  return client;
24
25
  }
26
+
27
+ export function getAuthenticatedHttpClient(extraUrls = []) {
28
+ const client = getHttpClient(extraUrls);
29
+ attachAuthInterceptors(client);
30
+ return client;
31
+ }
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import getHttpClient, { getAuthenticatedHttpClient } from './getHttpClient';
4
+
5
+ describe('getHttpClient', () => {
6
+ it('creates a generic client without auth interceptors', () => {
7
+ const client = getHttpClient();
8
+
9
+ expect(client.interceptors.request.handlers.filter(Boolean)).toHaveLength(1);
10
+ expect(client.interceptors.response.handlers.filter(Boolean)).toHaveLength(0);
11
+ });
12
+
13
+ it('creates an authenticated client with auth interceptors', () => {
14
+ const client = getAuthenticatedHttpClient();
15
+
16
+ expect(client.interceptors.request.handlers.filter(Boolean)).toHaveLength(2);
17
+ expect(client.interceptors.response.handlers.filter(Boolean)).toHaveLength(1);
18
+ });
19
+ });
@@ -0,0 +1,36 @@
1
+ const AUTH_INTERCEPTORS_ATTACHED = '__opencloningHttpClientAuthAttached';
2
+
3
+ let unauthorizedHandler = null;
4
+
5
+ export function setHttpClientUnauthorizedHandler(fn) {
6
+ unauthorizedHandler = fn;
7
+ }
8
+
9
+ export function attachAuthInterceptors(client, { tokenStorageKey = 'token' } = {}) {
10
+ if (client[AUTH_INTERCEPTORS_ATTACHED]) {
11
+ return client;
12
+ }
13
+
14
+ client[AUTH_INTERCEPTORS_ATTACHED] = true;
15
+
16
+ client.interceptors.request.use((config) => {
17
+ const token = localStorage.getItem(tokenStorageKey);
18
+ if (token) {
19
+ config.headers = config.headers ?? {};
20
+ config.headers.Authorization = `Bearer ${token}`;
21
+ }
22
+ return config;
23
+ });
24
+
25
+ client.interceptors.response.use(
26
+ (response) => response,
27
+ (error) => {
28
+ if (error.response?.status === 401 && unauthorizedHandler) {
29
+ unauthorizedHandler();
30
+ }
31
+ return Promise.reject(error);
32
+ },
33
+ );
34
+
35
+ return client;
36
+ }
@@ -0,0 +1,52 @@
1
+ import axios from 'axios';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { attachAuthInterceptors, setHttpClientUnauthorizedHandler } from './httpClientAuth';
5
+
6
+ describe('httpClientAuth', () => {
7
+ beforeEach(() => {
8
+ localStorage.clear();
9
+ setHttpClientUnauthorizedHandler(null);
10
+ });
11
+
12
+ afterEach(() => {
13
+ localStorage.clear();
14
+ setHttpClientUnauthorizedHandler(null);
15
+ });
16
+
17
+ it('adds the bearer token to authenticated requests', () => {
18
+ const client = attachAuthInterceptors(axios.create());
19
+ const requestFulfilled = client.interceptors.request.handlers[0].fulfilled;
20
+ const config = { headers: {} };
21
+
22
+ localStorage.setItem('token', '__TEST_TOKEN__');
23
+
24
+ const result = requestFulfilled(config);
25
+
26
+ expect(result).toBe(config);
27
+ expect(config.headers.Authorization).toBe('Bearer __TEST_TOKEN__');
28
+ });
29
+
30
+ it('calls the shared unauthorized handler for 401 responses', async () => {
31
+ const client = attachAuthInterceptors(axios.create());
32
+ const responseRejected = client.interceptors.response.handlers[0].rejected;
33
+ const handler = vi.fn();
34
+ const error = { response: { status: 401 } };
35
+
36
+ setHttpClientUnauthorizedHandler(handler);
37
+
38
+ await expect(responseRejected(error)).rejects.toBe(error);
39
+
40
+ expect(handler).toHaveBeenCalledTimes(1);
41
+ });
42
+
43
+ it('does not attach duplicate interceptors to the same client', () => {
44
+ const client = axios.create();
45
+
46
+ attachAuthInterceptors(client);
47
+ attachAuthInterceptors(client);
48
+
49
+ expect(client.interceptors.request.handlers.filter(Boolean)).toHaveLength(1);
50
+ expect(client.interceptors.response.handlers.filter(Boolean)).toHaveLength(1);
51
+ });
52
+ });
@@ -4,6 +4,7 @@
4
4
  export * from './readNwrite.js';
5
5
  export * from './network.js';
6
6
  export * from './getHttpClient.js';
7
+ export * from './httpClientAuth.js';
7
8
  export * from './sourceFunctions.js';
8
9
  export { default as error2String } from './error2String.js';
9
10
  export * from './enzyme_utils.js';