@enfuce/nextgen-sdk 0.0.4 → 0.0.5

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.
@@ -1,13 +1,19 @@
1
1
  import { AxiosInstance } from 'axios';
2
2
  import { TokenManager } from './tokenManager';
3
3
  /**
4
- * Returns an {@link AxiosInstance} with a response interceptor that, on a `401`,
5
- * forces a token refresh and retries the request exactly once with the new bearer
6
- * token. Pass it as the third argument of a generated API class:
4
+ * Returns an {@link AxiosInstance} with OAuth bearer-token handling built in:
5
+ *
6
+ * - **Proactive:** a request interceptor attaches a fresh bearer token (from the
7
+ * {@link TokenManager}) to *every* request. This authenticates API modules whose spec declares no
8
+ * security scheme (e.g. exchange-rate), so no `accessToken` on the `Configuration` is required.
9
+ * - **Reactive:** a response interceptor, on a `401`, forces a token refresh and retries the request
10
+ * exactly once with the new bearer token.
11
+ *
12
+ * Pass it as the third argument of a generated API class:
7
13
  *
8
14
  * ```ts
9
15
  * const http = createOAuthAxios(tokens);
10
- * const config = new card.Configuration({ accessToken: () => tokens.getToken() });
16
+ * const config = new card.Configuration({ basePath });
11
17
  * const api = new card.CardApi(config, undefined, http);
12
18
  * ```
13
19
  */
@@ -22,18 +22,29 @@ function stripBearer(header) {
22
22
  : header;
23
23
  }
24
24
  /**
25
- * Returns an {@link AxiosInstance} with a response interceptor that, on a `401`,
26
- * forces a token refresh and retries the request exactly once with the new bearer
27
- * token. Pass it as the third argument of a generated API class:
25
+ * Returns an {@link AxiosInstance} with OAuth bearer-token handling built in:
26
+ *
27
+ * - **Proactive:** a request interceptor attaches a fresh bearer token (from the
28
+ * {@link TokenManager}) to *every* request. This authenticates API modules whose spec declares no
29
+ * security scheme (e.g. exchange-rate), so no `accessToken` on the `Configuration` is required.
30
+ * - **Reactive:** a response interceptor, on a `401`, forces a token refresh and retries the request
31
+ * exactly once with the new bearer token.
32
+ *
33
+ * Pass it as the third argument of a generated API class:
28
34
  *
29
35
  * ```ts
30
36
  * const http = createOAuthAxios(tokens);
31
- * const config = new card.Configuration({ accessToken: () => tokens.getToken() });
37
+ * const config = new card.Configuration({ basePath });
32
38
  * const api = new card.CardApi(config, undefined, http);
33
39
  * ```
34
40
  */
35
41
  export function createOAuthAxios(manager, instance) {
36
42
  const http = instance !== null && instance !== void 0 ? instance : axios.create();
43
+ // Proactive: attach a fresh bearer token to every outgoing request.
44
+ http.interceptors.request.use((config) => __awaiter(this, void 0, void 0, function* () {
45
+ config.headers.set('Authorization', BEARER_PREFIX + (yield manager.getToken()));
46
+ return config;
47
+ }));
37
48
  http.interceptors.response.use(undefined, (error) => __awaiter(this, void 0, void 0, function* () {
38
49
  var _a, _b, _c;
39
50
  const response = error.response;
@@ -13,10 +13,13 @@
13
13
  * clientSecret: 'my-secret',
14
14
  * scopes: ['cards:read'],
15
15
  * });
16
- * const http = oauth.createOAuthAxios(tokens);
17
- * const config = new card.Configuration({ accessToken: () => tokens.getToken() });
16
+ * const http = oauth.createOAuthAxios(tokens); // attaches the token + retries on 401
17
+ * const config = new card.Configuration({ basePath: 'https://api.example.com' });
18
18
  * const api = new card.CardApi(config, undefined, http);
19
19
  *
20
+ * The same `http` instance authenticates every API module, including ones whose spec declares no
21
+ * security scheme (e.g. exchange-rate) — no `accessToken` on the `Configuration` is required.
22
+ *
20
23
  * Server-side only — a client secret must never ship to a browser.
21
24
  */
22
25
  export { TokenManager } from './tokenManager';
@@ -1,13 +1,19 @@
1
1
  import { AxiosInstance } from 'axios';
2
2
  import { TokenManager } from './tokenManager';
3
3
  /**
4
- * Returns an {@link AxiosInstance} with a response interceptor that, on a `401`,
5
- * forces a token refresh and retries the request exactly once with the new bearer
6
- * token. Pass it as the third argument of a generated API class:
4
+ * Returns an {@link AxiosInstance} with OAuth bearer-token handling built in:
5
+ *
6
+ * - **Proactive:** a request interceptor attaches a fresh bearer token (from the
7
+ * {@link TokenManager}) to *every* request. This authenticates API modules whose spec declares no
8
+ * security scheme (e.g. exchange-rate), so no `accessToken` on the `Configuration` is required.
9
+ * - **Reactive:** a response interceptor, on a `401`, forces a token refresh and retries the request
10
+ * exactly once with the new bearer token.
11
+ *
12
+ * Pass it as the third argument of a generated API class:
7
13
  *
8
14
  * ```ts
9
15
  * const http = createOAuthAxios(tokens);
10
- * const config = new card.Configuration({ accessToken: () => tokens.getToken() });
16
+ * const config = new card.Configuration({ basePath });
11
17
  * const api = new card.CardApi(config, undefined, http);
12
18
  * ```
13
19
  */
@@ -25,18 +25,29 @@ function stripBearer(header) {
25
25
  : header;
26
26
  }
27
27
  /**
28
- * Returns an {@link AxiosInstance} with a response interceptor that, on a `401`,
29
- * forces a token refresh and retries the request exactly once with the new bearer
30
- * token. Pass it as the third argument of a generated API class:
28
+ * Returns an {@link AxiosInstance} with OAuth bearer-token handling built in:
29
+ *
30
+ * - **Proactive:** a request interceptor attaches a fresh bearer token (from the
31
+ * {@link TokenManager}) to *every* request. This authenticates API modules whose spec declares no
32
+ * security scheme (e.g. exchange-rate), so no `accessToken` on the `Configuration` is required.
33
+ * - **Reactive:** a response interceptor, on a `401`, forces a token refresh and retries the request
34
+ * exactly once with the new bearer token.
35
+ *
36
+ * Pass it as the third argument of a generated API class:
31
37
  *
32
38
  * ```ts
33
39
  * const http = createOAuthAxios(tokens);
34
- * const config = new card.Configuration({ accessToken: () => tokens.getToken() });
40
+ * const config = new card.Configuration({ basePath });
35
41
  * const api = new card.CardApi(config, undefined, http);
36
42
  * ```
37
43
  */
38
44
  function createOAuthAxios(manager, instance) {
39
45
  const http = instance !== null && instance !== void 0 ? instance : axios_1.default.create();
46
+ // Proactive: attach a fresh bearer token to every outgoing request.
47
+ http.interceptors.request.use((config) => __awaiter(this, void 0, void 0, function* () {
48
+ config.headers.set('Authorization', BEARER_PREFIX + (yield manager.getToken()));
49
+ return config;
50
+ }));
40
51
  http.interceptors.response.use(undefined, (error) => __awaiter(this, void 0, void 0, function* () {
41
52
  var _a, _b, _c;
42
53
  const response = error.response;
@@ -16,10 +16,13 @@ exports.createOAuthAxios = exports.clientCredentialsFetcher = exports.clientCred
16
16
  * clientSecret: 'my-secret',
17
17
  * scopes: ['cards:read'],
18
18
  * });
19
- * const http = oauth.createOAuthAxios(tokens);
20
- * const config = new card.Configuration({ accessToken: () => tokens.getToken() });
19
+ * const http = oauth.createOAuthAxios(tokens); // attaches the token + retries on 401
20
+ * const config = new card.Configuration({ basePath: 'https://api.example.com' });
21
21
  * const api = new card.CardApi(config, undefined, http);
22
22
  *
23
+ * The same `http` instance authenticates every API module, including ones whose spec declares no
24
+ * security scheme (e.g. exchange-rate) — no `accessToken` on the `Configuration` is required.
25
+ *
23
26
  * Server-side only — a client secret must never ship to a browser.
24
27
  */
25
28
  var tokenManager_1 = require("./tokenManager");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfuce/nextgen-sdk",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Enfuce nextgen client SDK (TypeScript). One namespaced export per API.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,19 +19,31 @@ function stripBearer(header: string): string {
19
19
  }
20
20
 
21
21
  /**
22
- * Returns an {@link AxiosInstance} with a response interceptor that, on a `401`,
23
- * forces a token refresh and retries the request exactly once with the new bearer
24
- * token. Pass it as the third argument of a generated API class:
22
+ * Returns an {@link AxiosInstance} with OAuth bearer-token handling built in:
23
+ *
24
+ * - **Proactive:** a request interceptor attaches a fresh bearer token (from the
25
+ * {@link TokenManager}) to *every* request. This authenticates API modules whose spec declares no
26
+ * security scheme (e.g. exchange-rate), so no `accessToken` on the `Configuration` is required.
27
+ * - **Reactive:** a response interceptor, on a `401`, forces a token refresh and retries the request
28
+ * exactly once with the new bearer token.
29
+ *
30
+ * Pass it as the third argument of a generated API class:
25
31
  *
26
32
  * ```ts
27
33
  * const http = createOAuthAxios(tokens);
28
- * const config = new card.Configuration({ accessToken: () => tokens.getToken() });
34
+ * const config = new card.Configuration({ basePath });
29
35
  * const api = new card.CardApi(config, undefined, http);
30
36
  * ```
31
37
  */
32
38
  export function createOAuthAxios(manager: TokenManager, instance?: AxiosInstance): AxiosInstance {
33
39
  const http = instance ?? axios.create();
34
40
 
41
+ // Proactive: attach a fresh bearer token to every outgoing request.
42
+ http.interceptors.request.use(async (config) => {
43
+ config.headers.set('Authorization', BEARER_PREFIX + (await manager.getToken()));
44
+ return config;
45
+ });
46
+
35
47
  http.interceptors.response.use(undefined, async (error: AxiosError) => {
36
48
  const response = error.response;
37
49
  const original = error.config as RetriableConfig | undefined;
@@ -13,10 +13,13 @@
13
13
  * clientSecret: 'my-secret',
14
14
  * scopes: ['cards:read'],
15
15
  * });
16
- * const http = oauth.createOAuthAxios(tokens);
17
- * const config = new card.Configuration({ accessToken: () => tokens.getToken() });
16
+ * const http = oauth.createOAuthAxios(tokens); // attaches the token + retries on 401
17
+ * const config = new card.Configuration({ basePath: 'https://api.example.com' });
18
18
  * const api = new card.CardApi(config, undefined, http);
19
19
  *
20
+ * The same `http` instance authenticates every API module, including ones whose spec declares no
21
+ * security scheme (e.g. exchange-rate) — no `accessToken` on the `Configuration` is required.
22
+ *
20
23
  * Server-side only — a client secret must never ship to a browser.
21
24
  */
22
25
  export { AccessToken, TokenFetcher, TokenManager } from './tokenManager';
@@ -139,6 +139,25 @@ describe('clientCredentialsFetcher', () => {
139
139
  });
140
140
 
141
141
  describe('createOAuthAxios', () => {
142
+ it('proactively attaches a bearer token to a request that has no Authorization header', async () => {
143
+ const { fetch, calls } = countingFetcher();
144
+ const mgr = new TokenManager(fetch, 60, makeClock().now);
145
+
146
+ const seenAuth: (string | undefined)[] = [];
147
+ const adapter: AxiosAdapter = async (config) => {
148
+ seenAuth.push(config.headers?.Authorization as string | undefined);
149
+ return { data: { ok: true }, status: 200, statusText: 'OK', headers: {}, config };
150
+ };
151
+ const http = createOAuthAxios(mgr, axios.create({ adapter }));
152
+
153
+ // No Authorization header supplied — the SDK must attach one (as for a no-security-scheme spec).
154
+ const response = await http.request({ url: 'http://localhost/v1/ecb/currencies', method: 'get' });
155
+
156
+ expect(response.status).toBe(200);
157
+ expect(seenAuth).toEqual(['Bearer token-1']);
158
+ expect(calls.n).toBe(1);
159
+ });
160
+
142
161
  it('retries the request with a refreshed token when the first call rejects with 401', async () => {
143
162
  const { fetch } = countingFetcher();
144
163
  const mgr = new TokenManager(fetch, 60, makeClock().now);