@health-samurai/aidbox-client 0.0.0-alpha.3 → 0.0.0-alpha.4
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/README.md +81 -18
- package/dist/src/auth-providers.d.ts +9 -0
- package/dist/src/auth-providers.d.ts.map +1 -1
- package/dist/src/auth-providers.js +136 -9
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/smart-backend-services.d.ts +44 -0
- package/dist/src/smart-backend-services.d.ts.map +1 -0
- package/dist/src/smart-backend-services.js +685 -0
- package/dist/src/utils.d.ts +10 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +22 -0
- package/dist/test/auth-providers.test.d.ts +2 -0
- package/dist/test/auth-providers.test.d.ts.map +1 -0
- package/dist/test/basic-auth.test.d.ts +2 -0
- package/dist/test/basic-auth.test.d.ts.map +1 -0
- package/dist/test/smart-backend-services.test.d.ts +2 -0
- package/dist/test/smart-backend-services.test.d.ts.map +1 -0
- package/dist/test/utils.test.d.ts +2 -0
- package/dist/test/utils.test.d.ts.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -253,12 +253,74 @@ Both methods can throw the `RequestError` class if the error happened before the
|
|
|
253
253
|
|
|
254
254
|
## Authentication Providers
|
|
255
255
|
|
|
256
|
-
Authentication is managed via the `AuthProvider` interface.
|
|
256
|
+
Authentication is managed via the `AuthProvider` interface. The client ships with three built-in providers:
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
| Provider | Environment | Auth Method |
|
|
259
|
+
|----------|-------------|-------------|
|
|
260
|
+
| `BrowserAuthProvider` | Browser | Cookie-based sessions |
|
|
261
|
+
| `BasicAuthProvider` | Any | HTTP Basic Auth |
|
|
262
|
+
| `SmartBackendServicesAuthProvider` | Server-side | OAuth 2.0 client_credentials with JWT bearer |
|
|
260
263
|
|
|
261
|
-
|
|
264
|
+
### BrowserAuthProvider
|
|
265
|
+
|
|
266
|
+
For browser applications. Uses cookie-based sessions and redirects to the login page on 401.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { AidboxClient, BrowserAuthProvider } from "@health-samurai/aidbox-client";
|
|
270
|
+
|
|
271
|
+
const baseUrl = "https://fhir-server.address";
|
|
272
|
+
const client = new AidboxClient(baseUrl, new BrowserAuthProvider(baseUrl));
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### BasicAuthProvider
|
|
276
|
+
|
|
277
|
+
For server-side applications using HTTP Basic Auth.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { AidboxClient, BasicAuthProvider } from "@health-samurai/aidbox-client";
|
|
281
|
+
|
|
282
|
+
const baseUrl = "https://fhir-server.address";
|
|
283
|
+
const client = new AidboxClient(
|
|
284
|
+
baseUrl,
|
|
285
|
+
new BasicAuthProvider(baseUrl, "username", "password"),
|
|
286
|
+
);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### SmartBackendServicesAuthProvider
|
|
290
|
+
|
|
291
|
+
For server-to-server authentication using [SMART Backend Services](https://www.hl7.org/fhir/smart-app-launch/backend-services.html) (OAuth 2.0 client_credentials grant with JWT bearer assertion).
|
|
292
|
+
|
|
293
|
+
Features:
|
|
294
|
+
- Token caching with proactive refresh before expiry
|
|
295
|
+
- Thundering herd prevention — concurrent requests share a single token fetch
|
|
296
|
+
- Automatic retry on 401 with fresh token
|
|
297
|
+
- OAuth2 discovery from `.well-known/smart-configuration`
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { AidboxClient, SmartBackendServicesAuthProvider } from "@health-samurai/aidbox-client";
|
|
301
|
+
|
|
302
|
+
// Generate or import your private key using Web Crypto API
|
|
303
|
+
const privateKey = await crypto.subtle.generateKey(
|
|
304
|
+
{ name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-384" },
|
|
305
|
+
true,
|
|
306
|
+
["sign", "verify"]
|
|
307
|
+
).then(kp => kp.privateKey);
|
|
308
|
+
|
|
309
|
+
const auth = new SmartBackendServicesAuthProvider({
|
|
310
|
+
baseUrl: "https://fhir-server.address",
|
|
311
|
+
clientId: "my-service",
|
|
312
|
+
privateKey: privateKey, // CryptoKey from Web Crypto API
|
|
313
|
+
keyId: "key-001", // Must match kid in JWKS
|
|
314
|
+
scope: "system/*.read",
|
|
315
|
+
// tokenExpirationBuffer: 30, // Optional: seconds before expiry to refresh (default: 30)
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const client = new AidboxClient("https://fhir-server.address", auth);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Custom Auth Provider
|
|
322
|
+
|
|
323
|
+
For other authentication methods, implement the `AuthProvider` interface:
|
|
262
324
|
|
|
263
325
|
```typescript
|
|
264
326
|
import type { AuthProvider } from "@health-samurai/aidbox-client";
|
|
@@ -267,30 +329,31 @@ export class CustomAuthProvider implements AuthProvider {
|
|
|
267
329
|
public baseUrl: string;
|
|
268
330
|
|
|
269
331
|
constructor(baseUrl: string) {
|
|
270
|
-
|
|
332
|
+
this.baseUrl = baseUrl;
|
|
271
333
|
}
|
|
272
334
|
|
|
273
335
|
public async establishSession() {
|
|
274
|
-
|
|
336
|
+
/* code to establish a session */
|
|
275
337
|
}
|
|
276
338
|
|
|
277
339
|
public async revokeSession() {
|
|
278
|
-
|
|
340
|
+
/* code to revoke the session */
|
|
279
341
|
}
|
|
280
342
|
|
|
343
|
+
/**
|
|
344
|
+
* A wrapper around the `fetch` function, that does all the
|
|
345
|
+
* necessary preparations and argument patching required for the
|
|
346
|
+
* request to go through.
|
|
347
|
+
*
|
|
348
|
+
* Optionally, security checks can be implemented, like verifying
|
|
349
|
+
* that the request indeed goes to the `baseUrl`, and not
|
|
350
|
+
* somewhere else.
|
|
351
|
+
*/
|
|
281
352
|
public async fetch(
|
|
282
|
-
|
|
283
|
-
|
|
353
|
+
input: RequestInfo | URL,
|
|
354
|
+
init?: RequestInit,
|
|
284
355
|
): Promise<Response> {
|
|
285
|
-
|
|
286
|
-
* A wrapper around the `fetch` function, that does all the
|
|
287
|
-
* necessary preparations and argument patching required for the
|
|
288
|
-
* request to go through.
|
|
289
|
-
*
|
|
290
|
-
* Optionally, security checks can be implemented, like verifying
|
|
291
|
-
* that the request indeed goes to the `baseUrl`, and not
|
|
292
|
-
* somewhere else.
|
|
293
|
-
*/
|
|
356
|
+
/* ... */
|
|
294
357
|
}
|
|
295
358
|
}
|
|
296
359
|
```
|
|
@@ -20,4 +20,13 @@ export declare class BrowserAuthProvider implements AuthProvider {
|
|
|
20
20
|
*/
|
|
21
21
|
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
22
22
|
}
|
|
23
|
+
export declare class BasicAuthProvider implements AuthProvider {
|
|
24
|
+
#private;
|
|
25
|
+
/** @ignore */
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
constructor(baseUrl: string, username: string, password: string);
|
|
28
|
+
establishSession(): Promise<void>;
|
|
29
|
+
revokeSession(): Promise<void>;
|
|
30
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
31
|
+
}
|
|
23
32
|
//# sourceMappingURL=auth-providers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-providers.d.ts","sourceRoot":"","sources":["../../src/auth-providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-providers.d.ts","sourceRoot":"","sources":["../../src/auth-providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C,qBAAa,mBAAoB,YAAW,YAAY;;IACvD,cAAc;IACP,OAAO,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE,MAAM;IAgB3B;;OAEG;IACU,gBAAgB;IAQ7B;;OAEG;IACU,aAAa;IAW1B;;;;;OAKG;IACU,KAAK,CACjB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,QAAQ,CAAC;CAepB;AAED,qBAAa,iBAAkB,YAAW,YAAY;;IACrD,cAAc;IACP,OAAO,EAAE,MAAM,CAAC;gBAGX,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IASlD,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,KAAK,CACjB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,QAAQ,CAAC;CAcpB"}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
function _array_like_to_array(arr, len) {
|
|
2
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
3
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
4
|
+
return arr2;
|
|
5
|
+
}
|
|
6
|
+
function _array_without_holes(arr) {
|
|
7
|
+
if (Array.isArray(arr)) return _array_like_to_array(arr);
|
|
8
|
+
}
|
|
1
9
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
2
10
|
try {
|
|
3
11
|
var info = gen[key](arg);
|
|
@@ -32,11 +40,46 @@ function _check_private_redeclaration(obj, privateCollection) {
|
|
|
32
40
|
throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
33
41
|
}
|
|
34
42
|
}
|
|
43
|
+
function _class_apply_descriptor_get(receiver, descriptor) {
|
|
44
|
+
if (descriptor.get) {
|
|
45
|
+
return descriptor.get.call(receiver);
|
|
46
|
+
}
|
|
47
|
+
return descriptor.value;
|
|
48
|
+
}
|
|
49
|
+
function _class_apply_descriptor_set(receiver, descriptor, value) {
|
|
50
|
+
if (descriptor.set) {
|
|
51
|
+
descriptor.set.call(receiver, value);
|
|
52
|
+
} else {
|
|
53
|
+
if (!descriptor.writable) {
|
|
54
|
+
throw new TypeError("attempted to set read only private field");
|
|
55
|
+
}
|
|
56
|
+
descriptor.value = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
35
59
|
function _class_call_check(instance, Constructor) {
|
|
36
60
|
if (!(instance instanceof Constructor)) {
|
|
37
61
|
throw new TypeError("Cannot call a class as a function");
|
|
38
62
|
}
|
|
39
63
|
}
|
|
64
|
+
function _class_extract_field_descriptor(receiver, privateMap, action) {
|
|
65
|
+
if (!privateMap.has(receiver)) {
|
|
66
|
+
throw new TypeError("attempted to " + action + " private field on non-instance");
|
|
67
|
+
}
|
|
68
|
+
return privateMap.get(receiver);
|
|
69
|
+
}
|
|
70
|
+
function _class_private_field_get(receiver, privateMap) {
|
|
71
|
+
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
|
|
72
|
+
return _class_apply_descriptor_get(receiver, descriptor);
|
|
73
|
+
}
|
|
74
|
+
function _class_private_field_init(obj, privateMap, value) {
|
|
75
|
+
_check_private_redeclaration(obj, privateMap);
|
|
76
|
+
privateMap.set(obj, value);
|
|
77
|
+
}
|
|
78
|
+
function _class_private_field_set(receiver, privateMap, value) {
|
|
79
|
+
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
|
|
80
|
+
_class_apply_descriptor_set(receiver, descriptor, value);
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
40
83
|
function _class_private_method_get(receiver, privateSet, fn) {
|
|
41
84
|
if (!privateSet.has(receiver)) {
|
|
42
85
|
throw new TypeError("attempted to get private field on non-instance");
|
|
@@ -81,6 +124,23 @@ function _instanceof(left, right) {
|
|
|
81
124
|
return left instanceof right;
|
|
82
125
|
}
|
|
83
126
|
}
|
|
127
|
+
function _iterable_to_array(iter) {
|
|
128
|
+
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
129
|
+
}
|
|
130
|
+
function _non_iterable_spread() {
|
|
131
|
+
throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
132
|
+
}
|
|
133
|
+
function _to_consumable_array(arr) {
|
|
134
|
+
return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
|
|
135
|
+
}
|
|
136
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
137
|
+
if (!o) return;
|
|
138
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
139
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
140
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
141
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
142
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
143
|
+
}
|
|
84
144
|
function _ts_generator(thisArg, body) {
|
|
85
145
|
var f, y, t, _ = {
|
|
86
146
|
label: 0,
|
|
@@ -172,6 +232,7 @@ function _ts_generator(thisArg, body) {
|
|
|
172
232
|
};
|
|
173
233
|
}
|
|
174
234
|
}
|
|
235
|
+
import { mergeHeaders, validateBaseUrl } from "./utils";
|
|
175
236
|
var _checkSession = /*#__PURE__*/ new WeakSet();
|
|
176
237
|
export var BrowserAuthProvider = /*#__PURE__*/ function() {
|
|
177
238
|
"use strict";
|
|
@@ -249,18 +310,16 @@ export var BrowserAuthProvider = /*#__PURE__*/ function() {
|
|
|
249
310
|
* Accepts the same arguments as `fetch`.
|
|
250
311
|
*/ function fetch1(input, init) {
|
|
251
312
|
return _async_to_generator(function() {
|
|
252
|
-
var
|
|
313
|
+
var requestInit, response;
|
|
253
314
|
return _ts_generator(this, function(_state) {
|
|
254
315
|
switch(_state.label){
|
|
255
316
|
case 0:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
i = init !== null && init !== void 0 ? init : {};
|
|
260
|
-
i.credentials = "include";
|
|
317
|
+
validateBaseUrl(input, this.baseUrl);
|
|
318
|
+
requestInit = init !== null && init !== void 0 ? init : {};
|
|
319
|
+
requestInit.credentials = "include";
|
|
261
320
|
return [
|
|
262
321
|
4,
|
|
263
|
-
fetch(input,
|
|
322
|
+
fetch(input, requestInit)
|
|
264
323
|
];
|
|
265
324
|
case 1:
|
|
266
325
|
response = _state.sent();
|
|
@@ -291,8 +350,7 @@ export var BrowserAuthProvider = /*#__PURE__*/ function() {
|
|
|
291
350
|
}
|
|
292
351
|
]);
|
|
293
352
|
return BrowserAuthProvider;
|
|
294
|
-
}
|
|
295
|
-
();
|
|
353
|
+
}();
|
|
296
354
|
function checkSession() {
|
|
297
355
|
return _async_to_generator(function() {
|
|
298
356
|
var response;
|
|
@@ -320,3 +378,72 @@ function checkSession() {
|
|
|
320
378
|
});
|
|
321
379
|
}).call(this);
|
|
322
380
|
}
|
|
381
|
+
var _authHeader = /*#__PURE__*/ new WeakMap();
|
|
382
|
+
export var BasicAuthProvider = /*#__PURE__*/ function() {
|
|
383
|
+
"use strict";
|
|
384
|
+
function BasicAuthProvider(baseUrl, username, password) {
|
|
385
|
+
var _String;
|
|
386
|
+
_class_call_check(this, BasicAuthProvider);
|
|
387
|
+
/** @ignore */ _define_property(this, "baseUrl", void 0);
|
|
388
|
+
_class_private_field_init(this, _authHeader, {
|
|
389
|
+
writable: true,
|
|
390
|
+
value: void 0
|
|
391
|
+
});
|
|
392
|
+
this.baseUrl = baseUrl;
|
|
393
|
+
// Create Base64-encoded credentials for Basic Auth header (RFC 7617: UTF-8 encoded)
|
|
394
|
+
var credentials = "".concat(username, ":").concat(password);
|
|
395
|
+
var utf8Bytes = new TextEncoder().encode(credentials);
|
|
396
|
+
var base64 = btoa((_String = String).fromCharCode.apply(_String, _to_consumable_array(utf8Bytes)));
|
|
397
|
+
_class_private_field_set(this, _authHeader, "Basic ".concat(base64));
|
|
398
|
+
}
|
|
399
|
+
_create_class(BasicAuthProvider, [
|
|
400
|
+
{
|
|
401
|
+
key: "establishSession",
|
|
402
|
+
value: function establishSession() {
|
|
403
|
+
return _async_to_generator(function() {
|
|
404
|
+
return _ts_generator(this, function(_state) {
|
|
405
|
+
return [
|
|
406
|
+
2
|
|
407
|
+
];
|
|
408
|
+
});
|
|
409
|
+
// No-op for basic auth - credentials are sent with each request
|
|
410
|
+
})();
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
key: "revokeSession",
|
|
415
|
+
value: function revokeSession() {
|
|
416
|
+
return _async_to_generator(function() {
|
|
417
|
+
return _ts_generator(this, function(_state) {
|
|
418
|
+
return [
|
|
419
|
+
2
|
|
420
|
+
];
|
|
421
|
+
});
|
|
422
|
+
// No-op for basic auth - stateless authentication
|
|
423
|
+
})();
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
key: "fetch",
|
|
428
|
+
value: function fetch1(input, init) {
|
|
429
|
+
return _async_to_generator(function() {
|
|
430
|
+
var requestInit, baseHeaders, initHeaders, headers;
|
|
431
|
+
return _ts_generator(this, function(_state) {
|
|
432
|
+
validateBaseUrl(input, this.baseUrl);
|
|
433
|
+
requestInit = init !== null && init !== void 0 ? init : {};
|
|
434
|
+
baseHeaders = _instanceof(input, Request) ? input.headers : undefined;
|
|
435
|
+
initHeaders = requestInit.headers ? new Headers(requestInit.headers) : undefined;
|
|
436
|
+
headers = mergeHeaders(baseHeaders, initHeaders);
|
|
437
|
+
headers.set("Authorization", _class_private_field_get(this, _authHeader));
|
|
438
|
+
requestInit.headers = headers;
|
|
439
|
+
return [
|
|
440
|
+
2,
|
|
441
|
+
fetch(input, requestInit)
|
|
442
|
+
];
|
|
443
|
+
});
|
|
444
|
+
}).call(this);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
]);
|
|
448
|
+
return BasicAuthProvider;
|
|
449
|
+
}();
|
package/dist/src/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type * from "./fhir-types/hl7-fhir-r4-core";
|
|
|
4
4
|
export * from "./fhir-types/hl7-fhir-r4-core";
|
|
5
5
|
export type * from "./result";
|
|
6
6
|
export * from "./result";
|
|
7
|
+
export * from "./smart-backend-services";
|
|
7
8
|
export type * from "./types";
|
|
8
9
|
export * from "./types";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,mBAAmB,+BAA+B,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,mBAAmB,UAAU,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,mBAAmB,SAAS,CAAC;AAC7B,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,mBAAmB,+BAA+B,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,mBAAmB,UAAU,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,0BAA0B,CAAC;AACzC,mBAAmB,SAAS,CAAC;AAC7B,cAAc,SAAS,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AuthProvider } from "./types";
|
|
2
|
+
export type SmartBackendServicesConfig = {
|
|
3
|
+
/** FHIR server base URL */
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
/** OAuth 2.0 client ID */
|
|
6
|
+
clientId: string;
|
|
7
|
+
/** Private key for signing JWTs (CryptoKey from Web Crypto API) */
|
|
8
|
+
privateKey: CryptoKey;
|
|
9
|
+
/** Key ID (kid) - must match the kid in JWKS registered on the server */
|
|
10
|
+
keyId: string;
|
|
11
|
+
/** OAuth 2.0 scopes (e.g., "system/*.read") */
|
|
12
|
+
scope: string;
|
|
13
|
+
/** Token expiration buffer in seconds (refresh token this many seconds before expiry, default: 30) */
|
|
14
|
+
tokenExpirationBuffer?: number;
|
|
15
|
+
/** Allow insecure HTTP requests (for testing only, default: false) */
|
|
16
|
+
allowInsecureRequests?: boolean;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* SMART Backend Services authentication provider.
|
|
20
|
+
*
|
|
21
|
+
* Implements OAuth 2.0 client_credentials grant with JWT bearer assertion
|
|
22
|
+
* for server-to-server authentication per SMART Backend Services spec.
|
|
23
|
+
*
|
|
24
|
+
* @see https://hl7.org/fhir/smart-app-launch/backend-services.html
|
|
25
|
+
*/
|
|
26
|
+
export declare class SmartBackendServicesAuthProvider implements AuthProvider {
|
|
27
|
+
#private;
|
|
28
|
+
baseUrl: string;
|
|
29
|
+
constructor(config: SmartBackendServicesConfig);
|
|
30
|
+
/**
|
|
31
|
+
* Establish session - for Backend Services this means getting a token.
|
|
32
|
+
*/
|
|
33
|
+
establishSession(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Revoke session - clear cached token.
|
|
36
|
+
*/
|
|
37
|
+
revokeSession(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Fetch wrapper that adds Bearer token authorization.
|
|
40
|
+
* Automatically obtains token on first request and retries once on 401.
|
|
41
|
+
*/
|
|
42
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=smart-backend-services.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smart-backend-services.d.ts","sourceRoot":"","sources":["../../src/smart-backend-services.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C,MAAM,MAAM,0BAA0B,GAAG;IACxC,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,UAAU,EAAE,SAAS,CAAC;IACtB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,sGAAsG;IACtG,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAiBF;;;;;;;GAOG;AACH,qBAAa,gCAAiC,YAAW,YAAY;;IAC7D,OAAO,EAAE,MAAM,CAAC;gBAMX,MAAM,EAAE,0BAA0B;IAsJ9C;;OAEG;IACU,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9C;;OAEG;IACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3C;;;OAGG;IACU,KAAK,CACjB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,QAAQ,CAAC;CAwCpB"}
|
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
function _array_like_to_array(arr, len) {
|
|
2
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
3
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
4
|
+
return arr2;
|
|
5
|
+
}
|
|
6
|
+
function _array_with_holes(arr) {
|
|
7
|
+
if (Array.isArray(arr)) return arr;
|
|
8
|
+
}
|
|
9
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
10
|
+
try {
|
|
11
|
+
var info = gen[key](arg);
|
|
12
|
+
var value = info.value;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
reject(error);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (info.done) {
|
|
18
|
+
resolve(value);
|
|
19
|
+
} else {
|
|
20
|
+
Promise.resolve(value).then(_next, _throw);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function _async_to_generator(fn) {
|
|
24
|
+
return function() {
|
|
25
|
+
var self = this, args = arguments;
|
|
26
|
+
return new Promise(function(resolve, reject) {
|
|
27
|
+
var gen = fn.apply(self, args);
|
|
28
|
+
function _next(value) {
|
|
29
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
30
|
+
}
|
|
31
|
+
function _throw(err) {
|
|
32
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
33
|
+
}
|
|
34
|
+
_next(undefined);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function _check_private_redeclaration(obj, privateCollection) {
|
|
39
|
+
if (privateCollection.has(obj)) {
|
|
40
|
+
throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function _class_apply_descriptor_get(receiver, descriptor) {
|
|
44
|
+
if (descriptor.get) {
|
|
45
|
+
return descriptor.get.call(receiver);
|
|
46
|
+
}
|
|
47
|
+
return descriptor.value;
|
|
48
|
+
}
|
|
49
|
+
function _class_apply_descriptor_set(receiver, descriptor, value) {
|
|
50
|
+
if (descriptor.set) {
|
|
51
|
+
descriptor.set.call(receiver, value);
|
|
52
|
+
} else {
|
|
53
|
+
if (!descriptor.writable) {
|
|
54
|
+
throw new TypeError("attempted to set read only private field");
|
|
55
|
+
}
|
|
56
|
+
descriptor.value = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function _class_call_check(instance, Constructor) {
|
|
60
|
+
if (!(instance instanceof Constructor)) {
|
|
61
|
+
throw new TypeError("Cannot call a class as a function");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function _class_extract_field_descriptor(receiver, privateMap, action) {
|
|
65
|
+
if (!privateMap.has(receiver)) {
|
|
66
|
+
throw new TypeError("attempted to " + action + " private field on non-instance");
|
|
67
|
+
}
|
|
68
|
+
return privateMap.get(receiver);
|
|
69
|
+
}
|
|
70
|
+
function _class_private_field_get(receiver, privateMap) {
|
|
71
|
+
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
|
|
72
|
+
return _class_apply_descriptor_get(receiver, descriptor);
|
|
73
|
+
}
|
|
74
|
+
function _class_private_field_init(obj, privateMap, value) {
|
|
75
|
+
_check_private_redeclaration(obj, privateMap);
|
|
76
|
+
privateMap.set(obj, value);
|
|
77
|
+
}
|
|
78
|
+
function _class_private_field_set(receiver, privateMap, value) {
|
|
79
|
+
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
|
|
80
|
+
_class_apply_descriptor_set(receiver, descriptor, value);
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
function _class_private_method_get(receiver, privateSet, fn) {
|
|
84
|
+
if (!privateSet.has(receiver)) {
|
|
85
|
+
throw new TypeError("attempted to get private field on non-instance");
|
|
86
|
+
}
|
|
87
|
+
return fn;
|
|
88
|
+
}
|
|
89
|
+
function _class_private_method_init(obj, privateSet) {
|
|
90
|
+
_check_private_redeclaration(obj, privateSet);
|
|
91
|
+
privateSet.add(obj);
|
|
92
|
+
}
|
|
93
|
+
function _defineProperties(target, props) {
|
|
94
|
+
for(var i = 0; i < props.length; i++){
|
|
95
|
+
var descriptor = props[i];
|
|
96
|
+
descriptor.enumerable = descriptor.enumerable || false;
|
|
97
|
+
descriptor.configurable = true;
|
|
98
|
+
if ("value" in descriptor) descriptor.writable = true;
|
|
99
|
+
Object.defineProperty(target, descriptor.key, descriptor);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function _create_class(Constructor, protoProps, staticProps) {
|
|
103
|
+
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
104
|
+
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
105
|
+
return Constructor;
|
|
106
|
+
}
|
|
107
|
+
function _define_property(obj, key, value) {
|
|
108
|
+
if (key in obj) {
|
|
109
|
+
Object.defineProperty(obj, key, {
|
|
110
|
+
value: value,
|
|
111
|
+
enumerable: true,
|
|
112
|
+
configurable: true,
|
|
113
|
+
writable: true
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
obj[key] = value;
|
|
117
|
+
}
|
|
118
|
+
return obj;
|
|
119
|
+
}
|
|
120
|
+
function _instanceof(left, right) {
|
|
121
|
+
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
|
|
122
|
+
return !!right[Symbol.hasInstance](left);
|
|
123
|
+
} else {
|
|
124
|
+
return left instanceof right;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function _iterable_to_array_limit(arr, i) {
|
|
128
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
129
|
+
if (_i == null) return;
|
|
130
|
+
var _arr = [];
|
|
131
|
+
var _n = true;
|
|
132
|
+
var _d = false;
|
|
133
|
+
var _s, _e;
|
|
134
|
+
try {
|
|
135
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
136
|
+
_arr.push(_s.value);
|
|
137
|
+
if (i && _arr.length === i) break;
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
_d = true;
|
|
141
|
+
_e = err;
|
|
142
|
+
} finally{
|
|
143
|
+
try {
|
|
144
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
145
|
+
} finally{
|
|
146
|
+
if (_d) throw _e;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return _arr;
|
|
150
|
+
}
|
|
151
|
+
function _non_iterable_rest() {
|
|
152
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
153
|
+
}
|
|
154
|
+
function _object_without_properties(source, excluded) {
|
|
155
|
+
if (source == null) return {};
|
|
156
|
+
var target = _object_without_properties_loose(source, excluded);
|
|
157
|
+
var key, i;
|
|
158
|
+
if (Object.getOwnPropertySymbols) {
|
|
159
|
+
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
|
160
|
+
for(i = 0; i < sourceSymbolKeys.length; i++){
|
|
161
|
+
key = sourceSymbolKeys[i];
|
|
162
|
+
if (excluded.indexOf(key) >= 0) continue;
|
|
163
|
+
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
|
164
|
+
target[key] = source[key];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return target;
|
|
168
|
+
}
|
|
169
|
+
function _object_without_properties_loose(source, excluded) {
|
|
170
|
+
if (source == null) return {};
|
|
171
|
+
var target = {};
|
|
172
|
+
var sourceKeys = Object.keys(source);
|
|
173
|
+
var key, i;
|
|
174
|
+
for(i = 0; i < sourceKeys.length; i++){
|
|
175
|
+
key = sourceKeys[i];
|
|
176
|
+
if (excluded.indexOf(key) >= 0) continue;
|
|
177
|
+
target[key] = source[key];
|
|
178
|
+
}
|
|
179
|
+
return target;
|
|
180
|
+
}
|
|
181
|
+
function _sliced_to_array(arr, i) {
|
|
182
|
+
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
|
|
183
|
+
}
|
|
184
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
185
|
+
if (!o) return;
|
|
186
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
187
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
188
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
189
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
190
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
191
|
+
}
|
|
192
|
+
function _ts_generator(thisArg, body) {
|
|
193
|
+
var f, y, t, _ = {
|
|
194
|
+
label: 0,
|
|
195
|
+
sent: function() {
|
|
196
|
+
if (t[0] & 1) throw t[1];
|
|
197
|
+
return t[1];
|
|
198
|
+
},
|
|
199
|
+
trys: [],
|
|
200
|
+
ops: []
|
|
201
|
+
}, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
202
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() {
|
|
203
|
+
return this;
|
|
204
|
+
}), g;
|
|
205
|
+
function verb(n) {
|
|
206
|
+
return function(v) {
|
|
207
|
+
return step([
|
|
208
|
+
n,
|
|
209
|
+
v
|
|
210
|
+
]);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function step(op) {
|
|
214
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
215
|
+
while(g && (g = 0, op[0] && (_ = 0)), _)try {
|
|
216
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
217
|
+
if (y = 0, t) op = [
|
|
218
|
+
op[0] & 2,
|
|
219
|
+
t.value
|
|
220
|
+
];
|
|
221
|
+
switch(op[0]){
|
|
222
|
+
case 0:
|
|
223
|
+
case 1:
|
|
224
|
+
t = op;
|
|
225
|
+
break;
|
|
226
|
+
case 4:
|
|
227
|
+
_.label++;
|
|
228
|
+
return {
|
|
229
|
+
value: op[1],
|
|
230
|
+
done: false
|
|
231
|
+
};
|
|
232
|
+
case 5:
|
|
233
|
+
_.label++;
|
|
234
|
+
y = op[1];
|
|
235
|
+
op = [
|
|
236
|
+
0
|
|
237
|
+
];
|
|
238
|
+
continue;
|
|
239
|
+
case 7:
|
|
240
|
+
op = _.ops.pop();
|
|
241
|
+
_.trys.pop();
|
|
242
|
+
continue;
|
|
243
|
+
default:
|
|
244
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
245
|
+
_ = 0;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
249
|
+
_.label = op[1];
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
if (op[0] === 6 && _.label < t[1]) {
|
|
253
|
+
_.label = t[1];
|
|
254
|
+
t = op;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
if (t && _.label < t[2]) {
|
|
258
|
+
_.label = t[2];
|
|
259
|
+
_.ops.push(op);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
if (t[2]) _.ops.pop();
|
|
263
|
+
_.trys.pop();
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
op = body.call(thisArg, _);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
op = [
|
|
269
|
+
6,
|
|
270
|
+
e
|
|
271
|
+
];
|
|
272
|
+
y = 0;
|
|
273
|
+
} finally{
|
|
274
|
+
f = t = 0;
|
|
275
|
+
}
|
|
276
|
+
if (op[0] & 5) throw op[1];
|
|
277
|
+
return {
|
|
278
|
+
value: op[0] ? op[1] : void 0,
|
|
279
|
+
done: true
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
import * as oauth from "oauth4webapi";
|
|
284
|
+
import { mergeHeaders, validateBaseUrl } from "./utils";
|
|
285
|
+
var _config = /*#__PURE__*/ new WeakMap(), _cachedToken = /*#__PURE__*/ new WeakMap(), _pendingTokenRequest = /*#__PURE__*/ new WeakMap(), _discoverAuthServer = /*#__PURE__*/ new WeakSet(), _requestToken = /*#__PURE__*/ new WeakSet(), _sanitizeTokenResponse = /*#__PURE__*/ new WeakSet(), /**
|
|
286
|
+
* Get valid cached token if not expired, or null if needs refresh.
|
|
287
|
+
*/ _getValidCachedToken = /*#__PURE__*/ new WeakSet(), _getAccessToken = /*#__PURE__*/ new WeakSet(), _fetchAndCacheToken = /*#__PURE__*/ new WeakSet();
|
|
288
|
+
/**
|
|
289
|
+
* SMART Backend Services authentication provider.
|
|
290
|
+
*
|
|
291
|
+
* Implements OAuth 2.0 client_credentials grant with JWT bearer assertion
|
|
292
|
+
* for server-to-server authentication per SMART Backend Services spec.
|
|
293
|
+
*
|
|
294
|
+
* @see https://hl7.org/fhir/smart-app-launch/backend-services.html
|
|
295
|
+
*/ export var SmartBackendServicesAuthProvider = /*#__PURE__*/ function() {
|
|
296
|
+
"use strict";
|
|
297
|
+
function SmartBackendServicesAuthProvider(config) {
|
|
298
|
+
_class_call_check(this, SmartBackendServicesAuthProvider);
|
|
299
|
+
_class_private_method_init(this, _discoverAuthServer);
|
|
300
|
+
/**
|
|
301
|
+
* Request access token from token endpoint using client_credentials grant.
|
|
302
|
+
*/ _class_private_method_init(this, _requestToken);
|
|
303
|
+
/**
|
|
304
|
+
* Fixes "refresh_token" = null which does not work with oauth4webapi
|
|
305
|
+
* Fixed in Aidbox 2601, but kept for backwards compatibility.
|
|
306
|
+
*/ _class_private_method_init(this, _sanitizeTokenResponse);
|
|
307
|
+
_class_private_method_init(this, _getValidCachedToken);
|
|
308
|
+
/**
|
|
309
|
+
* Get a valid access token, refreshing if necessary.
|
|
310
|
+
* Deduplicates concurrent requests to prevent thundering herd.
|
|
311
|
+
*/ _class_private_method_init(this, _getAccessToken);
|
|
312
|
+
/**
|
|
313
|
+
* Fetch token from server and cache it.
|
|
314
|
+
*/ _class_private_method_init(this, _fetchAndCacheToken);
|
|
315
|
+
_define_property(this, "baseUrl", void 0);
|
|
316
|
+
_class_private_field_init(this, _config, {
|
|
317
|
+
writable: true,
|
|
318
|
+
value: void 0
|
|
319
|
+
});
|
|
320
|
+
_class_private_field_init(this, _cachedToken, {
|
|
321
|
+
writable: true,
|
|
322
|
+
value: null
|
|
323
|
+
});
|
|
324
|
+
_class_private_field_init(this, _pendingTokenRequest, {
|
|
325
|
+
writable: true,
|
|
326
|
+
value: null
|
|
327
|
+
});
|
|
328
|
+
this.baseUrl = config.baseUrl;
|
|
329
|
+
var _config_tokenExpirationBuffer, _config_allowInsecureRequests;
|
|
330
|
+
_class_private_field_set(this, _config, {
|
|
331
|
+
baseUrl: config.baseUrl,
|
|
332
|
+
clientId: config.clientId,
|
|
333
|
+
privateKey: config.privateKey,
|
|
334
|
+
keyId: config.keyId,
|
|
335
|
+
scope: config.scope,
|
|
336
|
+
tokenExpirationBuffer: (_config_tokenExpirationBuffer = config.tokenExpirationBuffer) !== null && _config_tokenExpirationBuffer !== void 0 ? _config_tokenExpirationBuffer : 30,
|
|
337
|
+
allowInsecureRequests: (_config_allowInsecureRequests = config.allowInsecureRequests) !== null && _config_allowInsecureRequests !== void 0 ? _config_allowInsecureRequests : false
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
_create_class(SmartBackendServicesAuthProvider, [
|
|
341
|
+
{
|
|
342
|
+
key: "establishSession",
|
|
343
|
+
value: /**
|
|
344
|
+
* Establish session - for Backend Services this means getting a token.
|
|
345
|
+
*/ function establishSession() {
|
|
346
|
+
return _async_to_generator(function() {
|
|
347
|
+
return _ts_generator(this, function(_state) {
|
|
348
|
+
switch(_state.label){
|
|
349
|
+
case 0:
|
|
350
|
+
return [
|
|
351
|
+
4,
|
|
352
|
+
_class_private_method_get(this, _getAccessToken, getAccessToken).call(this)
|
|
353
|
+
];
|
|
354
|
+
case 1:
|
|
355
|
+
_state.sent();
|
|
356
|
+
return [
|
|
357
|
+
2
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}).call(this);
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
key: "revokeSession",
|
|
366
|
+
value: /**
|
|
367
|
+
* Revoke session - clear cached token.
|
|
368
|
+
*/ function revokeSession() {
|
|
369
|
+
return _async_to_generator(function() {
|
|
370
|
+
var pending, e;
|
|
371
|
+
return _ts_generator(this, function(_state) {
|
|
372
|
+
switch(_state.label){
|
|
373
|
+
case 0:
|
|
374
|
+
// Wait for any pending token request to settle before clearing
|
|
375
|
+
pending = _class_private_field_get(this, _pendingTokenRequest);
|
|
376
|
+
if (!pending) return [
|
|
377
|
+
3,
|
|
378
|
+
4
|
|
379
|
+
];
|
|
380
|
+
_state.label = 1;
|
|
381
|
+
case 1:
|
|
382
|
+
_state.trys.push([
|
|
383
|
+
1,
|
|
384
|
+
3,
|
|
385
|
+
,
|
|
386
|
+
4
|
|
387
|
+
]);
|
|
388
|
+
return [
|
|
389
|
+
4,
|
|
390
|
+
pending
|
|
391
|
+
];
|
|
392
|
+
case 2:
|
|
393
|
+
_state.sent();
|
|
394
|
+
return [
|
|
395
|
+
3,
|
|
396
|
+
4
|
|
397
|
+
];
|
|
398
|
+
case 3:
|
|
399
|
+
e = _state.sent();
|
|
400
|
+
return [
|
|
401
|
+
3,
|
|
402
|
+
4
|
|
403
|
+
];
|
|
404
|
+
case 4:
|
|
405
|
+
_class_private_field_set(this, _cachedToken, null);
|
|
406
|
+
return [
|
|
407
|
+
2
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}).call(this);
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
key: "fetch",
|
|
416
|
+
value: /**
|
|
417
|
+
* Fetch wrapper that adds Bearer token authorization.
|
|
418
|
+
* Automatically obtains token on first request and retries once on 401.
|
|
419
|
+
*/ function fetch1(input, init) {
|
|
420
|
+
return _async_to_generator(function() {
|
|
421
|
+
var accessToken, requestInit, baseHeaders, initHeaders, mergedHeaders, clonedInput, retryBody, _requestInit_body_tee, stream1, stream2, response, newToken;
|
|
422
|
+
return _ts_generator(this, function(_state) {
|
|
423
|
+
switch(_state.label){
|
|
424
|
+
case 0:
|
|
425
|
+
validateBaseUrl(input, this.baseUrl);
|
|
426
|
+
return [
|
|
427
|
+
4,
|
|
428
|
+
_class_private_method_get(this, _getAccessToken, getAccessToken).call(this)
|
|
429
|
+
];
|
|
430
|
+
case 1:
|
|
431
|
+
accessToken = _state.sent();
|
|
432
|
+
requestInit = init !== null && init !== void 0 ? init : {};
|
|
433
|
+
baseHeaders = _instanceof(input, Request) ? input.headers : undefined;
|
|
434
|
+
initHeaders = requestInit.headers ? new Headers(requestInit.headers) : undefined;
|
|
435
|
+
mergedHeaders = mergeHeaders(baseHeaders, initHeaders);
|
|
436
|
+
mergedHeaders.set("Authorization", "Bearer ".concat(accessToken));
|
|
437
|
+
requestInit.headers = mergedHeaders;
|
|
438
|
+
// Clone input/body to preserve for potential retry
|
|
439
|
+
clonedInput = _instanceof(input, Request) ? input.clone() : input;
|
|
440
|
+
retryBody = requestInit.body;
|
|
441
|
+
// If body is a ReadableStream, tee it for potential retry
|
|
442
|
+
if (_instanceof(requestInit.body, ReadableStream)) {
|
|
443
|
+
_requestInit_body_tee = _sliced_to_array(requestInit.body.tee(), 2), stream1 = _requestInit_body_tee[0], stream2 = _requestInit_body_tee[1];
|
|
444
|
+
requestInit.body = stream1;
|
|
445
|
+
retryBody = stream2;
|
|
446
|
+
}
|
|
447
|
+
return [
|
|
448
|
+
4,
|
|
449
|
+
fetch(clonedInput, requestInit)
|
|
450
|
+
];
|
|
451
|
+
case 2:
|
|
452
|
+
response = _state.sent();
|
|
453
|
+
if (!(response.status === 401)) return [
|
|
454
|
+
3,
|
|
455
|
+
5
|
|
456
|
+
];
|
|
457
|
+
_class_private_field_set(this, _cachedToken, null);
|
|
458
|
+
return [
|
|
459
|
+
4,
|
|
460
|
+
_class_private_method_get(this, _getAccessToken, getAccessToken).call(this)
|
|
461
|
+
];
|
|
462
|
+
case 3:
|
|
463
|
+
newToken = _state.sent();
|
|
464
|
+
mergedHeaders.set("Authorization", "Bearer ".concat(newToken));
|
|
465
|
+
if (retryBody !== undefined) {
|
|
466
|
+
requestInit.body = retryBody;
|
|
467
|
+
}
|
|
468
|
+
return [
|
|
469
|
+
4,
|
|
470
|
+
fetch(input, requestInit)
|
|
471
|
+
];
|
|
472
|
+
case 4:
|
|
473
|
+
response = _state.sent();
|
|
474
|
+
_state.label = 5;
|
|
475
|
+
case 5:
|
|
476
|
+
return [
|
|
477
|
+
2,
|
|
478
|
+
response
|
|
479
|
+
];
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}).call(this);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
]);
|
|
486
|
+
return SmartBackendServicesAuthProvider;
|
|
487
|
+
}();
|
|
488
|
+
function discoverAuthServer() {
|
|
489
|
+
return _async_to_generator(function() {
|
|
490
|
+
var url, response, metadata;
|
|
491
|
+
return _ts_generator(this, function(_state) {
|
|
492
|
+
switch(_state.label){
|
|
493
|
+
case 0:
|
|
494
|
+
url = new URL(_class_private_field_get(this, _config).baseUrl);
|
|
495
|
+
return [
|
|
496
|
+
4,
|
|
497
|
+
oauth.discoveryRequest(url, _define_property({
|
|
498
|
+
algorithm: "oauth2"
|
|
499
|
+
}, oauth.allowInsecureRequests, _class_private_field_get(this, _config).allowInsecureRequests))
|
|
500
|
+
];
|
|
501
|
+
case 1:
|
|
502
|
+
response = _state.sent();
|
|
503
|
+
return [
|
|
504
|
+
4,
|
|
505
|
+
oauth.processDiscoveryResponse(url, response)
|
|
506
|
+
];
|
|
507
|
+
case 2:
|
|
508
|
+
metadata = _state.sent();
|
|
509
|
+
if (!metadata.token_endpoint) {
|
|
510
|
+
throw new Error("Discovery response missing token_endpoint");
|
|
511
|
+
}
|
|
512
|
+
return [
|
|
513
|
+
2,
|
|
514
|
+
metadata
|
|
515
|
+
];
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
}).call(this);
|
|
519
|
+
}
|
|
520
|
+
function requestToken() {
|
|
521
|
+
return _async_to_generator(function() {
|
|
522
|
+
var as, client, privateKey, clientAuth, params, response, sanitizedResponse;
|
|
523
|
+
return _ts_generator(this, function(_state) {
|
|
524
|
+
switch(_state.label){
|
|
525
|
+
case 0:
|
|
526
|
+
return [
|
|
527
|
+
4,
|
|
528
|
+
_class_private_method_get(this, _discoverAuthServer, discoverAuthServer).call(this)
|
|
529
|
+
];
|
|
530
|
+
case 1:
|
|
531
|
+
as = _state.sent();
|
|
532
|
+
client = {
|
|
533
|
+
client_id: _class_private_field_get(this, _config).clientId
|
|
534
|
+
};
|
|
535
|
+
privateKey = {
|
|
536
|
+
key: _class_private_field_get(this, _config).privateKey,
|
|
537
|
+
kid: _class_private_field_get(this, _config).keyId
|
|
538
|
+
};
|
|
539
|
+
// Aidbox requires typ: "JWT" in the client assertion JWT header.
|
|
540
|
+
// oauth.modifyAssertion is a Symbol that allows customizing the JWT before signing.
|
|
541
|
+
clientAuth = oauth.PrivateKeyJwt(privateKey, _define_property({}, oauth.modifyAssertion, function(header) {
|
|
542
|
+
header.typ = "JWT";
|
|
543
|
+
}));
|
|
544
|
+
// Request parameters
|
|
545
|
+
params = new URLSearchParams();
|
|
546
|
+
params.set("scope", _class_private_field_get(this, _config).scope);
|
|
547
|
+
return [
|
|
548
|
+
4,
|
|
549
|
+
oauth.clientCredentialsGrantRequest(as, client, clientAuth, params, _define_property({}, oauth.allowInsecureRequests, _class_private_field_get(this, _config).allowInsecureRequests))
|
|
550
|
+
];
|
|
551
|
+
case 2:
|
|
552
|
+
response = _state.sent();
|
|
553
|
+
return [
|
|
554
|
+
4,
|
|
555
|
+
_class_private_method_get(this, _sanitizeTokenResponse, sanitizeTokenResponse).call(this, response)
|
|
556
|
+
];
|
|
557
|
+
case 3:
|
|
558
|
+
sanitizedResponse = _state.sent();
|
|
559
|
+
return [
|
|
560
|
+
2,
|
|
561
|
+
oauth.processClientCredentialsResponse(as, client, sanitizedResponse)
|
|
562
|
+
];
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}).call(this);
|
|
566
|
+
}
|
|
567
|
+
function sanitizeTokenResponse(response) {
|
|
568
|
+
return _async_to_generator(function() {
|
|
569
|
+
var cloned, body, _, sanitized;
|
|
570
|
+
return _ts_generator(this, function(_state) {
|
|
571
|
+
switch(_state.label){
|
|
572
|
+
case 0:
|
|
573
|
+
cloned = response.clone();
|
|
574
|
+
return [
|
|
575
|
+
4,
|
|
576
|
+
cloned.json()
|
|
577
|
+
];
|
|
578
|
+
case 1:
|
|
579
|
+
body = _state.sent();
|
|
580
|
+
if (!("refresh_token" in body) || body.refresh_token !== null) {
|
|
581
|
+
return [
|
|
582
|
+
2,
|
|
583
|
+
response
|
|
584
|
+
];
|
|
585
|
+
}
|
|
586
|
+
_ = body.refresh_token, sanitized = _object_without_properties(body, [
|
|
587
|
+
"refresh_token"
|
|
588
|
+
]);
|
|
589
|
+
return [
|
|
590
|
+
2,
|
|
591
|
+
new Response(JSON.stringify(sanitized), {
|
|
592
|
+
status: response.status,
|
|
593
|
+
statusText: response.statusText,
|
|
594
|
+
headers: response.headers
|
|
595
|
+
})
|
|
596
|
+
];
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
})();
|
|
600
|
+
}
|
|
601
|
+
function getValidCachedToken() {
|
|
602
|
+
if (!_class_private_field_get(this, _cachedToken)) return null;
|
|
603
|
+
var bufferMs = _class_private_field_get(this, _config).tokenExpirationBuffer * 1000;
|
|
604
|
+
if (_class_private_field_get(this, _cachedToken).expiresAt > Date.now() + bufferMs) {
|
|
605
|
+
return _class_private_field_get(this, _cachedToken).accessToken;
|
|
606
|
+
}
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
function getAccessToken() {
|
|
610
|
+
return _async_to_generator(function() {
|
|
611
|
+
var validToken;
|
|
612
|
+
return _ts_generator(this, function(_state) {
|
|
613
|
+
switch(_state.label){
|
|
614
|
+
case 0:
|
|
615
|
+
validToken = _class_private_method_get(this, _getValidCachedToken, getValidCachedToken).call(this);
|
|
616
|
+
if (validToken) {
|
|
617
|
+
return [
|
|
618
|
+
2,
|
|
619
|
+
validToken
|
|
620
|
+
];
|
|
621
|
+
}
|
|
622
|
+
// If a token request is already in progress, wait for it
|
|
623
|
+
if (_class_private_field_get(this, _pendingTokenRequest)) {
|
|
624
|
+
return [
|
|
625
|
+
2,
|
|
626
|
+
_class_private_field_get(this, _pendingTokenRequest)
|
|
627
|
+
];
|
|
628
|
+
}
|
|
629
|
+
// Request new token, storing the promise to deduplicate concurrent calls
|
|
630
|
+
_class_private_field_set(this, _pendingTokenRequest, _class_private_method_get(this, _fetchAndCacheToken, fetchAndCacheToken).call(this));
|
|
631
|
+
_state.label = 1;
|
|
632
|
+
case 1:
|
|
633
|
+
_state.trys.push([
|
|
634
|
+
1,
|
|
635
|
+
,
|
|
636
|
+
3,
|
|
637
|
+
4
|
|
638
|
+
]);
|
|
639
|
+
return [
|
|
640
|
+
4,
|
|
641
|
+
_class_private_field_get(this, _pendingTokenRequest)
|
|
642
|
+
];
|
|
643
|
+
case 2:
|
|
644
|
+
return [
|
|
645
|
+
2,
|
|
646
|
+
_state.sent()
|
|
647
|
+
];
|
|
648
|
+
case 3:
|
|
649
|
+
_class_private_field_set(this, _pendingTokenRequest, null);
|
|
650
|
+
return [
|
|
651
|
+
7
|
|
652
|
+
];
|
|
653
|
+
case 4:
|
|
654
|
+
return [
|
|
655
|
+
2
|
|
656
|
+
];
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}).call(this);
|
|
660
|
+
}
|
|
661
|
+
function fetchAndCacheToken() {
|
|
662
|
+
return _async_to_generator(function() {
|
|
663
|
+
var tokenResponse, now, _tokenResponse_expires_in;
|
|
664
|
+
return _ts_generator(this, function(_state) {
|
|
665
|
+
switch(_state.label){
|
|
666
|
+
case 0:
|
|
667
|
+
return [
|
|
668
|
+
4,
|
|
669
|
+
_class_private_method_get(this, _requestToken, requestToken).call(this)
|
|
670
|
+
];
|
|
671
|
+
case 1:
|
|
672
|
+
tokenResponse = _state.sent();
|
|
673
|
+
now = Date.now();
|
|
674
|
+
_class_private_field_set(this, _cachedToken, {
|
|
675
|
+
accessToken: tokenResponse.access_token,
|
|
676
|
+
expiresAt: now + ((_tokenResponse_expires_in = tokenResponse.expires_in) !== null && _tokenResponse_expires_in !== void 0 ? _tokenResponse_expires_in : 300) * 1000
|
|
677
|
+
});
|
|
678
|
+
return [
|
|
679
|
+
2,
|
|
680
|
+
_class_private_field_get(this, _cachedToken).accessToken
|
|
681
|
+
];
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
}).call(this);
|
|
685
|
+
}
|
package/dist/src/utils.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import type { ResponseWithMeta } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Validate that fetch input URL starts with baseUrl.
|
|
4
|
+
* Throws if the URL doesn't match baseUrl.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateBaseUrl(input: RequestInfo | URL, baseUrl: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Merge two Headers objects.
|
|
9
|
+
* Headers from `override` take precedence over `base`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeHeaders(base?: Headers, override?: Headers): Headers;
|
|
2
12
|
export declare const coerceBody: <T>(meta: ResponseWithMeta) => Promise<T>;
|
|
3
13
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/src/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGhD;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,OAAO,EAAE,MAAM,GACb,IAAI,CAMN;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAYxE;AAWD,eAAO,MAAM,UAAU,GAAU,CAAC,EAAE,MAAM,gBAAgB,KAAG,OAAO,CAAC,CAAC,CA2BrE,CAAC"}
|
package/dist/src/utils.js
CHANGED
|
@@ -127,6 +127,28 @@ function _ts_generator(thisArg, body) {
|
|
|
127
127
|
}
|
|
128
128
|
import YAML from "yaml";
|
|
129
129
|
import { ErrorResponse } from "./types";
|
|
130
|
+
/**
|
|
131
|
+
* Validate that fetch input URL starts with baseUrl.
|
|
132
|
+
* Throws if the URL doesn't match baseUrl.
|
|
133
|
+
*/ export function validateBaseUrl(input, baseUrl) {
|
|
134
|
+
var url = _instanceof(input, Request) ? input.url : input.toString();
|
|
135
|
+
if (!url.startsWith(baseUrl)) {
|
|
136
|
+
throw new Error("URL of the request must start with baseUrl");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Merge two Headers objects.
|
|
141
|
+
* Headers from `override` take precedence over `base`.
|
|
142
|
+
*/ export function mergeHeaders(base, override) {
|
|
143
|
+
var merged = new Headers();
|
|
144
|
+
base === null || base === void 0 ? void 0 : base.forEach(function(value, key) {
|
|
145
|
+
merged.set(key, value);
|
|
146
|
+
});
|
|
147
|
+
override === null || override === void 0 ? void 0 : override.forEach(function(value, key) {
|
|
148
|
+
merged.set(key, value);
|
|
149
|
+
});
|
|
150
|
+
return merged;
|
|
151
|
+
}
|
|
130
152
|
var normalizeContentType = function(contentType) {
|
|
131
153
|
var semicolon = contentType.indexOf(";");
|
|
132
154
|
if (semicolon !== -1) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-providers.test.d.ts","sourceRoot":"","sources":["../../test/auth-providers.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basic-auth.test.d.ts","sourceRoot":"","sources":["../../test/basic-auth.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smart-backend-services.test.d.ts","sourceRoot":"","sources":["../../test/smart-backend-services.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../test/utils.test.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@health-samurai/aidbox-client",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.4",
|
|
4
4
|
"description": "A client library for communicating with Aidbox",
|
|
5
5
|
"author": "Health Samurai",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@types/json-patch": "^0.0.33",
|
|
17
|
+
"oauth4webapi": "^3.8.3",
|
|
17
18
|
"yaml": "^2.8.1"
|
|
18
19
|
},
|
|
19
20
|
"publishConfig": {
|