@bytescale/sdk 3.58.0 → 3.59.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/dist/browser/cjs/main.js +719 -316
- package/dist/browser/esm/main.mjs +719 -316
- package/dist/node/cjs/main.js +123 -29
- package/dist/node/esm/main.mjs +123 -29
- package/dist/types/private/AuthSessionState.d.ts +7 -0
- package/dist/types/private/UploadManagerBase.d.ts +0 -1
- package/dist/types/private/dtos/AuthSwSetConfigDto.d.ts +1 -1
- package/dist/types/private/model/AuthManagerInterface.d.ts +1 -105
- package/dist/types/private/model/AuthSession.d.ts +12 -5
- package/dist/types/private/model/AuthSessionConfig.d.ts +3 -0
- package/dist/types/private/model/AuthSessionConfigAuto.d.ts +6 -0
- package/dist/types/private/model/AuthSessionConfigBase.d.ts +12 -0
- package/dist/types/private/model/AuthSessionConfigManual.d.ts +7 -0
- package/dist/types/private/model/BeginAuthSessionParams.d.ts +3 -0
- package/dist/types/private/model/BeginAuthSessionParamsOptions.d.ts +2 -0
- package/dist/types/private/model/BeginAuthSessionParamsV1.d.ts +11 -0
- package/dist/types/private/model/BeginAuthSessionParamsV2.d.ts +16 -0
- package/dist/types/private/model/NonEmptyArray.d.ts +1 -0
- package/dist/types/private/model/UrlRewriteRule.d.ts +6 -0
- package/dist/types/public/browser/AuthManagerBrowser.d.ts +27 -11
- package/dist/types/public/node/AuthManagerNode.d.ts +12 -2
- package/dist/types/public/shared/generated/runtime.d.ts +13 -6
- package/dist/worker/cjs/main.js +123 -29
- package/dist/worker/esm/main.mjs +123 -29
- package/package.json +1 -1
- package/tests/ApiClientAuth.test.ts +222 -0
- package/tests/AuthManagerBrowser.test.ts +301 -237
- package/tests/AuthServiceWorkerRewrite.test.ts +63 -2
- package/tests/UploadManagerAuth.test.ts +156 -0
package/dist/worker/esm/main.mjs
CHANGED
|
@@ -210,6 +210,55 @@ var AuthSessionState = /*#__PURE__*/function () {
|
|
|
210
210
|
}
|
|
211
211
|
return window[AuthSessionState.stateKey];
|
|
212
212
|
}
|
|
213
|
+
/** Resolves request-time auth while remaining compatible with the single-token session used by SDK 3.54.0. */
|
|
214
|
+
}, {
|
|
215
|
+
key: "resolveAuthConfig",
|
|
216
|
+
value: function resolveAuthConfig(authConfigId, requireReadyDefault) {
|
|
217
|
+
if (authConfigId === false) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
var session = AuthSessionState.getSession();
|
|
221
|
+
if (session === undefined || !session.isActive) {
|
|
222
|
+
if (typeof authConfigId === "string") {
|
|
223
|
+
throw new Error("No active AuthManager configuration has ID '".concat(authConfigId, "'."));
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
if (Array.isArray(session.authConfigs)) {
|
|
228
|
+
var state = session.authConfigs.find(function (config) {
|
|
229
|
+
return config.config.authConfigId === authConfigId;
|
|
230
|
+
});
|
|
231
|
+
if (state === undefined) {
|
|
232
|
+
if (typeof authConfigId === "string") {
|
|
233
|
+
throw new Error("No active AuthManager configuration has ID '".concat(authConfigId, "'."));
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
if (state.accessToken === undefined || state.expiresAt === undefined || state.expiresAt <= Date.now() || state.jwt === undefined) {
|
|
238
|
+
var isV2Session = typeof session.params.authConfigs === "function";
|
|
239
|
+
if (typeof authConfigId === "string" || requireReadyDefault || isV2Session) {
|
|
240
|
+
throw new Error("AuthManager configuration '".concat(authConfigId !== null && authConfigId !== void 0 ? authConfigId : "default", "' is not ready."));
|
|
241
|
+
}
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
accessToken: state.accessToken,
|
|
246
|
+
accountId: state.config.accountId,
|
|
247
|
+
jwt: state.jwt
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (typeof authConfigId === "string") {
|
|
251
|
+
throw new Error("No active AuthManager configuration has ID '".concat(authConfigId, "'."));
|
|
252
|
+
}
|
|
253
|
+
if (session.accessToken === undefined || typeof session.params.accountId !== "string") {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
accessToken: session.accessToken,
|
|
258
|
+
accountId: session.params.accountId,
|
|
259
|
+
jwt: undefined
|
|
260
|
+
};
|
|
261
|
+
}
|
|
213
262
|
}]);
|
|
214
263
|
}();
|
|
215
264
|
AuthSessionState.stateKey = "BytescaleSessionState";
|
|
@@ -353,12 +402,47 @@ var BytescaleApiClientConfigUtils = /*#__PURE__*/function () {
|
|
|
353
402
|
}, {
|
|
354
403
|
key: "getAccountId",
|
|
355
404
|
value: function getAccountId(config) {
|
|
405
|
+
return BytescaleApiClientConfigUtils.resolveAuthentication(config).accountId;
|
|
406
|
+
}
|
|
407
|
+
}, {
|
|
408
|
+
key: "resolveAuthentication",
|
|
409
|
+
value: function resolveAuthentication(config) {
|
|
410
|
+
var _a, _b;
|
|
411
|
+
var apiKey = (_a = config.apiKey) !== null && _a !== void 0 ? _a : undefined;
|
|
412
|
+
var authConfig = AuthSessionState.resolveAuthConfig(config.authConfigId, apiKey === undefined);
|
|
413
|
+
var apiKeyAccountId = apiKey === undefined ? undefined : BytescaleApiClientConfigUtils.getApiKeyAccountId(apiKey);
|
|
414
|
+
if (apiKeyAccountId !== undefined && authConfig !== undefined && apiKeyAccountId !== authConfig.accountId) {
|
|
415
|
+
throw new Error("The API key belongs to account '".concat(apiKeyAccountId, "', but AuthManager configuration '").concat((_b = config.authConfigId) !== null && _b !== void 0 ? _b : "default", "' belongs to account '").concat(authConfig.accountId, "'."));
|
|
416
|
+
}
|
|
417
|
+
if (apiKey !== undefined) {
|
|
418
|
+
return {
|
|
419
|
+
accountId: apiKeyAccountId,
|
|
420
|
+
headers: Object.assign({
|
|
421
|
+
Authorization: "Bearer ".concat(apiKey)
|
|
422
|
+
}, authConfig === undefined ? {} : {
|
|
423
|
+
"Authorization-Token": authConfig.accessToken
|
|
424
|
+
})
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if ((authConfig === null || authConfig === void 0 ? void 0 : authConfig.jwt) !== undefined) {
|
|
428
|
+
return {
|
|
429
|
+
accountId: authConfig.accountId,
|
|
430
|
+
headers: {
|
|
431
|
+
Authorization: "Bearer ".concat(authConfig.jwt)
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
throw new Error("Please provide an API key via the 'apiKey' config parameter.");
|
|
436
|
+
}
|
|
437
|
+
}, {
|
|
438
|
+
key: "getApiKeyAccountId",
|
|
439
|
+
value: function getApiKeyAccountId(apiKey) {
|
|
356
440
|
var _a, _b;
|
|
357
441
|
var accountId;
|
|
358
|
-
if (BytescaleApiClientConfigUtils.specialApiKeys.includes(
|
|
442
|
+
if (BytescaleApiClientConfigUtils.specialApiKeys.includes(apiKey)) {
|
|
359
443
|
accountId = BytescaleApiClientConfigUtils.specialApiKeyAccountId;
|
|
360
444
|
} else {
|
|
361
|
-
accountId = (_b = (_a =
|
|
445
|
+
accountId = (_b = (_a = apiKey.split("_")[1]) === null || _a === void 0 ? void 0 : _a.substr(0, BytescaleApiClientConfigUtils.accountIdLength)) !== null && _b !== void 0 ? _b : "";
|
|
362
446
|
if (accountId.length !== BytescaleApiClientConfigUtils.accountIdLength) {
|
|
363
447
|
throw new Error("Invalid Bytescale API key.");
|
|
364
448
|
}
|
|
@@ -368,21 +452,24 @@ var BytescaleApiClientConfigUtils = /*#__PURE__*/function () {
|
|
|
368
452
|
}, {
|
|
369
453
|
key: "validate",
|
|
370
454
|
value: function validate(config) {
|
|
371
|
-
var _a;
|
|
372
455
|
// Defensive programming, for users not using TypeScript. Mainly because this is used by UploadWidget users.
|
|
373
456
|
if ((config !== null && config !== void 0 ? config : undefined) === undefined) {
|
|
374
457
|
throw new Error("Config parameter required.");
|
|
375
458
|
}
|
|
376
|
-
if (
|
|
377
|
-
throw new Error("
|
|
459
|
+
if (config.authConfigId !== undefined && config.authConfigId !== false && typeof config.authConfigId !== "string") {
|
|
460
|
+
throw new Error("The 'authConfigId' config parameter must be a string, false, or undefined.");
|
|
378
461
|
}
|
|
379
|
-
if (config.apiKey
|
|
462
|
+
if (config.apiKey !== undefined && typeof config.apiKey !== "string") {
|
|
463
|
+
throw new Error("The 'apiKey' config parameter must be a string when provided.");
|
|
464
|
+
}
|
|
465
|
+
if (config.apiKey !== undefined && config.apiKey.trim() !== config.apiKey) {
|
|
380
466
|
// We do not support API keys with whitespace (by trimming ourselves) because otherwise we'd need to support this
|
|
381
467
|
// everywhere in perpetuity (since removing the trimming would be a breaking change).
|
|
382
468
|
throw new Error("API key needs trimming (whitespace detected).");
|
|
383
469
|
}
|
|
384
|
-
|
|
385
|
-
|
|
470
|
+
if (config.apiKey !== undefined) {
|
|
471
|
+
BytescaleApiClientConfigUtils.getApiKeyAccountId(config.apiKey);
|
|
472
|
+
}
|
|
386
473
|
}
|
|
387
474
|
}]);
|
|
388
475
|
}();
|
|
@@ -409,12 +496,7 @@ var BaseAPI = /*#__PURE__*/function () {
|
|
|
409
496
|
try {
|
|
410
497
|
var _this = this;
|
|
411
498
|
var _a;
|
|
412
|
-
|
|
413
|
-
context.headers["Authorization"] = "Bearer ".concat(apiKey); // authorization-header authentication
|
|
414
|
-
var session = AuthSessionState.getSession();
|
|
415
|
-
if ((session === null || session === void 0 ? void 0 : session.accessToken) !== undefined) {
|
|
416
|
-
context.headers["Authorization-Token"] = session.accessToken;
|
|
417
|
-
}
|
|
499
|
+
Object.assign(context.headers, BytescaleApiClientConfigUtils.resolveAuthentication(_this.config).headers);
|
|
418
500
|
// Key: any possible value for 'baseUrlOverride'
|
|
419
501
|
// Value: user-overridden value for that base URL from the config.
|
|
420
502
|
var nonDefaultBasePaths = _defineProperty({}, BytescaleApiClientConfigUtils.defaultCdnUrl, BytescaleApiClientConfigUtils.getCdnUrl(_this.config));
|
|
@@ -454,9 +536,21 @@ var BaseAPI = /*#__PURE__*/function () {
|
|
|
454
536
|
url += "?" + querystring(context.query);
|
|
455
537
|
}
|
|
456
538
|
var configHeaders = _this2.config.headers;
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
539
|
+
return runtime_await(runtime_await(configHeaders === undefined ? {} : typeof configHeaders === "function" ? configHeaders() : configHeaders, function (resolvedConfigHeaders) {
|
|
540
|
+
for (var _i = 0, _Object$keys = Object.keys(resolvedConfigHeaders); _i < _Object$keys.length; _i++) {
|
|
541
|
+
var key = _Object$keys[_i];
|
|
542
|
+
if (key.toLowerCase() === "authorization" || key.toLowerCase() === "authorization-token") {
|
|
543
|
+
for (var _i2 = 0, _Object$keys2 = Object.keys(context.headers); _i2 < _Object$keys2.length; _i2++) {
|
|
544
|
+
var generatedKey = _Object$keys2[_i2];
|
|
545
|
+
if (generatedKey.toLowerCase() === key.toLowerCase()) {
|
|
546
|
+
// Custom auth headers have documented precedence; remove the generated spelling to make that deterministic.
|
|
547
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
548
|
+
delete context.headers[generatedKey];
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
var headers = Object.assign(Object.assign({}, context.headers), resolvedConfigHeaders);
|
|
460
554
|
Object.keys(headers).forEach(function (key) {
|
|
461
555
|
return headers[key] === undefined ? delete headers[key] : {};
|
|
462
556
|
});
|
|
@@ -468,12 +562,12 @@ var BaseAPI = /*#__PURE__*/function () {
|
|
|
468
562
|
headers: headers,
|
|
469
563
|
body: context.body
|
|
470
564
|
};
|
|
471
|
-
var _Object$
|
|
565
|
+
var _Object$assign = Object.assign({}, initParams);
|
|
472
566
|
return runtime_await(initOverrideFn({
|
|
473
567
|
init: initParams,
|
|
474
568
|
context: context
|
|
475
569
|
}), function (_initOverrideFn) {
|
|
476
|
-
var overriddenInit = Object.assign(_Object$
|
|
570
|
+
var overriddenInit = Object.assign(_Object$assign, _initOverrideFn);
|
|
477
571
|
var init = Object.assign(Object.assign({}, overriddenInit), {
|
|
478
572
|
body: JSON.stringify(overriddenInit.body)
|
|
479
573
|
});
|
|
@@ -2098,7 +2192,6 @@ var UploadManagerBase = /*#__PURE__*/function () {
|
|
|
2098
2192
|
this.defaultMaxConcurrentUploadParts = 4;
|
|
2099
2193
|
this.intervalMs = 500;
|
|
2100
2194
|
this.uploadApi = new UploadApi(config);
|
|
2101
|
-
this.accountId = BytescaleApiClientConfigUtils.getAccountId(config);
|
|
2102
2195
|
}
|
|
2103
2196
|
return UploadManagerBase_createClass(UploadManagerBase, [{
|
|
2104
2197
|
key: "upload",
|
|
@@ -2106,6 +2199,7 @@ var UploadManagerBase = /*#__PURE__*/function () {
|
|
|
2106
2199
|
try {
|
|
2107
2200
|
var _this = this;
|
|
2108
2201
|
_this.assertNotCancelled(request);
|
|
2202
|
+
var accountId = BytescaleApiClientConfigUtils.getAccountId(_this.config);
|
|
2109
2203
|
var source = _this.processUploadSource(request.data);
|
|
2110
2204
|
var preUploadInfo = _this.getPreUploadInfo(request, source);
|
|
2111
2205
|
var bytesTotal = preUploadInfo.size;
|
|
@@ -2115,7 +2209,7 @@ var UploadManagerBase = /*#__PURE__*/function () {
|
|
|
2115
2209
|
if (request.onProgress !== undefined) {
|
|
2116
2210
|
request.onProgress(_this.makeProgressEvent(0, bytesTotal));
|
|
2117
2211
|
}
|
|
2118
|
-
return UploadManagerBase_await(_this.beginUpload(request, preUploadInfo), function (uploadInfo) {
|
|
2212
|
+
return UploadManagerBase_await(_this.beginUpload(request, preUploadInfo, accountId), function (uploadInfo) {
|
|
2119
2213
|
var partCount = uploadInfo.uploadParts.count;
|
|
2120
2214
|
var parts = UploadManagerBase_toConsumableArray(Array(partCount).keys());
|
|
2121
2215
|
var _this$makeCancellatio = _this.makeCancellationMethods(),
|
|
@@ -2125,7 +2219,7 @@ var UploadManagerBase = /*#__PURE__*/function () {
|
|
|
2125
2219
|
var uploadedParts;
|
|
2126
2220
|
return UploadManagerBase_continue(UploadManagerBase_finallyRethrows(function () {
|
|
2127
2221
|
return UploadManagerBase_await(_this.mapAsync(parts, preUploadInfo.maxConcurrentUploadParts, _async(function (part) {
|
|
2128
|
-
return _this.uploadPart(request, source, part, uploadInfo, makeOnProgressForPart(), addCancellationHandler);
|
|
2222
|
+
return _this.uploadPart(request, source, part, uploadInfo, makeOnProgressForPart(), addCancellationHandler, accountId);
|
|
2129
2223
|
})), function (_this$mapAsync) {
|
|
2130
2224
|
uploadedParts = _this$mapAsync;
|
|
2131
2225
|
return UploadManagerBase_awaitIgnored(_this.postUpload(init));
|
|
@@ -2237,14 +2331,14 @@ var UploadManagerBase = /*#__PURE__*/function () {
|
|
|
2237
2331
|
}
|
|
2238
2332
|
}, {
|
|
2239
2333
|
key: "beginUpload",
|
|
2240
|
-
value: function beginUpload(request, _ref2) {
|
|
2334
|
+
value: function beginUpload(request, _ref2, accountId) {
|
|
2241
2335
|
var size = _ref2.size,
|
|
2242
2336
|
mime = _ref2.mime,
|
|
2243
2337
|
originalFileName = _ref2.originalFileName;
|
|
2244
2338
|
try {
|
|
2245
2339
|
var _this4 = this;
|
|
2246
2340
|
return UploadManagerBase_await(_this4.uploadApi.beginMultipartUpload({
|
|
2247
|
-
accountId:
|
|
2341
|
+
accountId: accountId,
|
|
2248
2342
|
beginMultipartUploadRequest: {
|
|
2249
2343
|
metadata: request.metadata,
|
|
2250
2344
|
mime: mime,
|
|
@@ -2261,16 +2355,16 @@ var UploadManagerBase = /*#__PURE__*/function () {
|
|
|
2261
2355
|
}
|
|
2262
2356
|
}, {
|
|
2263
2357
|
key: "uploadPart",
|
|
2264
|
-
value: function uploadPart(request, source, partIndex, uploadInfo, onProgress, addCancellationHandler) {
|
|
2358
|
+
value: function uploadPart(request, source, partIndex, uploadInfo, onProgress, addCancellationHandler, accountId) {
|
|
2265
2359
|
try {
|
|
2266
2360
|
var _this5 = this;
|
|
2267
2361
|
_this5.assertNotCancelled(request);
|
|
2268
|
-
return UploadManagerBase_await(_this5.getUploadPart(partIndex, uploadInfo), function (part) {
|
|
2362
|
+
return UploadManagerBase_await(_this5.getUploadPart(partIndex, uploadInfo, accountId), function (part) {
|
|
2269
2363
|
_this5.assertNotCancelled(request);
|
|
2270
2364
|
return UploadManagerBase_await(_this5.putUploadPart(part, source, onProgress, addCancellationHandler), function (etag) {
|
|
2271
2365
|
_this5.assertNotCancelled(request);
|
|
2272
2366
|
return UploadManagerBase_await(_this5.uploadApi.completeUploadPart({
|
|
2273
|
-
accountId:
|
|
2367
|
+
accountId: accountId,
|
|
2274
2368
|
uploadId: uploadInfo.uploadId,
|
|
2275
2369
|
uploadPartIndex: partIndex,
|
|
2276
2370
|
completeUploadPartRequest: {
|
|
@@ -2311,7 +2405,7 @@ var UploadManagerBase = /*#__PURE__*/function () {
|
|
|
2311
2405
|
}
|
|
2312
2406
|
}, {
|
|
2313
2407
|
key: "getUploadPart",
|
|
2314
|
-
value: function getUploadPart(partIndex, uploadInfo) {
|
|
2408
|
+
value: function getUploadPart(partIndex, uploadInfo, accountId) {
|
|
2315
2409
|
try {
|
|
2316
2410
|
var _this7 = this;
|
|
2317
2411
|
if (partIndex === 0) {
|
|
@@ -2319,7 +2413,7 @@ var UploadManagerBase = /*#__PURE__*/function () {
|
|
|
2319
2413
|
}
|
|
2320
2414
|
return UploadManagerBase_await(_this7.uploadApi.getUploadPart({
|
|
2321
2415
|
uploadId: uploadInfo.uploadId,
|
|
2322
|
-
accountId:
|
|
2416
|
+
accountId: accountId,
|
|
2323
2417
|
uploadPartIndex: partIndex
|
|
2324
2418
|
}));
|
|
2325
2419
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { Headers as NodeFetchHeaders, Response as NodeFetchResponse } from "node-fetch";
|
|
3
|
+
import { AuthSessionState } from "../src/private/AuthSessionState";
|
|
4
|
+
import { AuthSession, AuthSessionConfigState } from "../src/private/model/AuthSession";
|
|
5
|
+
import { BaseAPI, BytescaleApiClientConfig, FetchAPI, RequestOpts } from "../src/public/shared/generated/runtime";
|
|
6
|
+
import { BeginAuthSessionParams } from "../src/private/model/BeginAuthSessionParams";
|
|
7
|
+
import { AuthSessionConfig } from "../src/private/model/AuthSessionConfig";
|
|
8
|
+
|
|
9
|
+
const accountA = "A123abc";
|
|
10
|
+
const accountB = "B123abc";
|
|
11
|
+
const apiKeyA = `public_${accountA}_test`;
|
|
12
|
+
|
|
13
|
+
class TestApi extends BaseAPI {
|
|
14
|
+
async get(): Promise<Response> {
|
|
15
|
+
const request: RequestOpts = { headers: {}, method: "GET", path: "/test" };
|
|
16
|
+
return await this.request(request, undefined, undefined);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("API-client AuthManager configuration", () => {
|
|
21
|
+
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, "window");
|
|
22
|
+
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
Object.defineProperty(globalThis, "window", { configurable: true, value: {} });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
AuthSessionState.setSession(undefined);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterAll(() => {
|
|
32
|
+
if (originalWindow === undefined) {
|
|
33
|
+
Reflect.deleteProperty(globalThis, "window");
|
|
34
|
+
} else {
|
|
35
|
+
Object.defineProperty(globalThis, "window", originalWindow);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("retains API-key-only authentication when no default AuthManager config exists", async () => {
|
|
40
|
+
setModernSession([state("named", accountA, "jwt-a", "access-a")]);
|
|
41
|
+
const { api, fetchApi } = createApi({ apiKey: apiKeyA });
|
|
42
|
+
|
|
43
|
+
await api.get();
|
|
44
|
+
|
|
45
|
+
expect(requestHeaders(fetchApi).get("Authorization")).toBe(`Bearer ${apiKeyA}`);
|
|
46
|
+
expect(requestHeaders(fetchApi).has("Authorization-Token")).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("supplements an API key with the selected registered access token on every request", async () => {
|
|
50
|
+
const defaultState = state(undefined, accountA, "jwt-a", "access-a");
|
|
51
|
+
setModernSession([defaultState]);
|
|
52
|
+
const { api, fetchApi } = createApi({ apiKey: apiKeyA });
|
|
53
|
+
|
|
54
|
+
await api.get();
|
|
55
|
+
defaultState.accessToken = "access-refreshed";
|
|
56
|
+
await api.get();
|
|
57
|
+
|
|
58
|
+
expect(requestHeaders(fetchApi, 0).get("Authorization")).toBe(`Bearer ${apiKeyA}`);
|
|
59
|
+
expect(requestHeaders(fetchApi, 0).get("Authorization-Token")).toBe("access-a");
|
|
60
|
+
expect(requestHeaders(fetchApi, 1).get("Authorization-Token")).toBe("access-refreshed");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("uses the raw JWT as sole authentication for an API-key-less named client", async () => {
|
|
64
|
+
setModernSession([state("customer", accountB, "jwt-b", "access-b")]);
|
|
65
|
+
const { api, fetchApi } = createApi({ authConfigId: "customer" });
|
|
66
|
+
|
|
67
|
+
await api.get();
|
|
68
|
+
|
|
69
|
+
expect(requestHeaders(fetchApi).get("Authorization")).toBe("Bearer jwt-b");
|
|
70
|
+
expect(requestHeaders(fetchApi).has("Authorization-Token")).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("supports an API-key-less default client", async () => {
|
|
74
|
+
setModernSession([state(undefined, accountA, "jwt-a", "access-a")]);
|
|
75
|
+
const { api, fetchApi } = createApi({});
|
|
76
|
+
|
|
77
|
+
await api.get();
|
|
78
|
+
|
|
79
|
+
expect(requestHeaders(fetchApi).get("Authorization")).toBe("Bearer jwt-a");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("fails clearly for opt-out without an API key and for an unknown named config", async () => {
|
|
83
|
+
setModernSession([state(undefined, accountA, "jwt-a", "access-a")]);
|
|
84
|
+
|
|
85
|
+
await expect(createApi({ authConfigId: false }).api.get()).rejects.toThrow("provide an API key");
|
|
86
|
+
await expect(createApi({ apiKey: apiKeyA, authConfigId: "missing" }).api.get()).rejects.toThrow(
|
|
87
|
+
"No active AuthManager configuration has ID 'missing'"
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("fails before fetch when API-key and AuthManager accounts differ", async () => {
|
|
92
|
+
setModernSession([state(undefined, accountB, "jwt-b", "access-b")]);
|
|
93
|
+
const { api, fetchApi } = createApi({ apiKey: apiKeyA });
|
|
94
|
+
|
|
95
|
+
await expect(api.get()).rejects.toThrow("belongs to account");
|
|
96
|
+
expect(fetchApi).not.toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("allows construction before an API-key-less AuthManager config becomes available", async () => {
|
|
100
|
+
const { api, fetchApi } = createApi({ authConfigId: "later" });
|
|
101
|
+
await expect(api.get()).rejects.toThrow("No active AuthManager configuration");
|
|
102
|
+
|
|
103
|
+
setModernSession([state("later", accountA, "jwt-a", "access-a")]);
|
|
104
|
+
await api.get();
|
|
105
|
+
|
|
106
|
+
expect(requestHeaders(fetchApi).get("Authorization")).toBe("Bearer jwt-a");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("fails closed for an expired selected configuration", async () => {
|
|
110
|
+
const expired = state("expired", accountA, "jwt-a", "access-a");
|
|
111
|
+
expired.expiresAt = Date.now() - 1;
|
|
112
|
+
setModernSession([expired]);
|
|
113
|
+
|
|
114
|
+
await expect(createApi({ apiKey: apiKeyA, authConfigId: "expired" }).api.get()).rejects.toThrow("not ready");
|
|
115
|
+
await expect(createApi({ authConfigId: "expired" }).api.get()).rejects.toThrow("not ready");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("fails closed for an expired V2 default unless AuthManager is explicitly disabled", async () => {
|
|
119
|
+
const expired = state(undefined, accountA, "jwt-a", "access-a");
|
|
120
|
+
expired.expiresAt = Date.now() - 1;
|
|
121
|
+
setModernSession([expired]);
|
|
122
|
+
|
|
123
|
+
await expect(createApi({ apiKey: apiKeyA }).api.get()).rejects.toThrow("not ready");
|
|
124
|
+
const optedOut = createApi({ apiKey: apiKeyA, authConfigId: false });
|
|
125
|
+
await optedOut.api.get();
|
|
126
|
+
expect(requestHeaders(optedOut.fetchApi).get("Authorization")).toBe(`Bearer ${apiKeyA}`);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("recognizes a 3.54 session as the default supplemental token", async () => {
|
|
130
|
+
AuthSessionState.setSession({
|
|
131
|
+
accessToken: "legacy-access",
|
|
132
|
+
accessTokenRefreshHandle: undefined,
|
|
133
|
+
authServiceWorker: undefined,
|
|
134
|
+
isActive: true,
|
|
135
|
+
params: {
|
|
136
|
+
accountId: accountA,
|
|
137
|
+
authHeaders: async (): Promise<Record<string, string>> => ({}),
|
|
138
|
+
authUrl: "https://app.example.com/auth"
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const withKey = createApi({ apiKey: apiKeyA });
|
|
142
|
+
|
|
143
|
+
await withKey.api.get();
|
|
144
|
+
|
|
145
|
+
expect(requestHeaders(withKey.fetchApi).get("Authorization-Token")).toBe("legacy-access");
|
|
146
|
+
await expect(createApi({}).api.get()).rejects.toThrow("provide an API key");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("retains deterministic custom authentication-header precedence", async () => {
|
|
150
|
+
setModernSession([state(undefined, accountA, "jwt-a", "access-a")]);
|
|
151
|
+
const { api, fetchApi } = createApi({
|
|
152
|
+
apiKey: apiKeyA,
|
|
153
|
+
headers: async (): Promise<Record<string, string>> => ({
|
|
154
|
+
"authorization": "Custom authorization",
|
|
155
|
+
"AUTHORIZATION-TOKEN": "custom-token"
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await api.get();
|
|
160
|
+
|
|
161
|
+
const headers = requestHeaders(fetchApi);
|
|
162
|
+
expect(headers.get("Authorization")).toBe("Custom authorization");
|
|
163
|
+
expect(headers.get("Authorization-Token")).toBe("custom-token");
|
|
164
|
+
expect(headers.has("authConfigId")).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
function createApi(config: Omit<BytescaleApiClientConfig, "fetchApi">): {
|
|
169
|
+
api: TestApi;
|
|
170
|
+
fetchApi: jest.MockedFunction<FetchAPI>;
|
|
171
|
+
} {
|
|
172
|
+
const fetchApi = jest.fn<FetchAPI>(async () => new NodeFetchResponse("{}") as unknown as Response);
|
|
173
|
+
return { api: new TestApi({ ...config, fetchApi }), fetchApi };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function requestHeaders(
|
|
177
|
+
fetchApi: jest.MockedFunction<FetchAPI>,
|
|
178
|
+
call = fetchApi.mock.calls.length - 1
|
|
179
|
+
): NodeFetchHeaders {
|
|
180
|
+
return new NodeFetchHeaders(fetchApi.mock.calls[call][1]?.headers as Record<string, string>);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function state(
|
|
184
|
+
authConfigId: string | undefined,
|
|
185
|
+
accountId: string,
|
|
186
|
+
jwt: string,
|
|
187
|
+
accessToken: string
|
|
188
|
+
): AuthSessionConfigState {
|
|
189
|
+
const config: AuthSessionConfig = {
|
|
190
|
+
accountId,
|
|
191
|
+
authConfigId,
|
|
192
|
+
enableServiceWorkerAuth: false,
|
|
193
|
+
getAuthorizationToken: async () => jwt
|
|
194
|
+
};
|
|
195
|
+
return {
|
|
196
|
+
accessToken,
|
|
197
|
+
config,
|
|
198
|
+
expiresAt: Date.now() + 60_000,
|
|
199
|
+
jwt,
|
|
200
|
+
refreshHandle: undefined
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function setModernSession(states: AuthSessionConfigState[]): void {
|
|
205
|
+
const configs = states.map(value => value.config);
|
|
206
|
+
const nonEmptyConfigs: [AuthSessionConfig, ...AuthSessionConfig[]] = [configs[0], ...configs.slice(1)];
|
|
207
|
+
const params: BeginAuthSessionParams = {
|
|
208
|
+
authConfigs: async () => nonEmptyConfigs,
|
|
209
|
+
serviceWorkerScript: undefined
|
|
210
|
+
};
|
|
211
|
+
const session: AuthSession = {
|
|
212
|
+
accessToken: undefined,
|
|
213
|
+
accessTokenRefreshHandle: undefined,
|
|
214
|
+
authConfigs: states,
|
|
215
|
+
authServiceWorker: undefined,
|
|
216
|
+
isActive: true,
|
|
217
|
+
isReady: true,
|
|
218
|
+
params,
|
|
219
|
+
serviceWorkerConfigured: false
|
|
220
|
+
};
|
|
221
|
+
AuthSessionState.setSession(session);
|
|
222
|
+
}
|