@query-farm/vgi-rpc 0.3.4 → 0.4.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/auth.d.ts +13 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/client/connect.d.ts.map +1 -1
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/introspect.d.ts +1 -0
- package/dist/client/introspect.d.ts.map +1 -1
- package/dist/client/oauth.d.ts +26 -0
- package/dist/client/oauth.d.ts.map +1 -0
- package/dist/client/stream.d.ts +2 -0
- package/dist/client/stream.d.ts.map +1 -1
- package/dist/client/types.d.ts +2 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/dispatch/stream.d.ts.map +1 -1
- package/dist/http/auth.d.ts +21 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/dispatch.d.ts +2 -0
- package/dist/http/dispatch.d.ts.map +1 -1
- package/dist/http/handler.d.ts.map +1 -1
- package/dist/http/index.d.ts +4 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/jwt.d.ts +21 -0
- package/dist/http/jwt.d.ts.map +1 -0
- package/dist/http/types.d.ts +5 -0
- package/dist/http/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1416 -46
- package/dist/index.js.map +18 -13
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/wire/response.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/auth.ts +31 -0
- package/src/client/connect.ts +15 -1
- package/src/client/index.ts +2 -0
- package/src/client/introspect.ts +14 -2
- package/src/client/oauth.ts +74 -0
- package/src/client/stream.ts +12 -0
- package/src/client/types.ts +2 -0
- package/src/dispatch/stream.ts +11 -3
- package/src/http/auth.ts +47 -0
- package/src/http/dispatch.ts +6 -4
- package/src/http/handler.ts +41 -1
- package/src/http/index.ts +4 -0
- package/src/http/jwt.ts +66 -0
- package/src/http/types.ts +6 -0
- package/src/index.ts +7 -0
- package/src/types.ts +17 -3
- package/src/wire/response.ts +28 -14
package/dist/index.js
CHANGED
|
@@ -50,6 +50,48 @@ var init_zstd = __esm(() => {
|
|
|
50
50
|
isBun = typeof globalThis.Bun !== "undefined";
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
// src/errors.ts
|
|
54
|
+
class RpcError extends Error {
|
|
55
|
+
errorType;
|
|
56
|
+
errorMessage;
|
|
57
|
+
remoteTraceback;
|
|
58
|
+
constructor(errorType, errorMessage, remoteTraceback) {
|
|
59
|
+
super(`${errorType}: ${errorMessage}`);
|
|
60
|
+
this.errorType = errorType;
|
|
61
|
+
this.errorMessage = errorMessage;
|
|
62
|
+
this.remoteTraceback = remoteTraceback;
|
|
63
|
+
this.name = "RpcError";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class VersionError extends Error {
|
|
68
|
+
constructor(message) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = "VersionError";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/auth.ts
|
|
75
|
+
class AuthContext {
|
|
76
|
+
domain;
|
|
77
|
+
authenticated;
|
|
78
|
+
principal;
|
|
79
|
+
claims;
|
|
80
|
+
constructor(domain, authenticated, principal, claims = {}) {
|
|
81
|
+
this.domain = domain;
|
|
82
|
+
this.authenticated = authenticated;
|
|
83
|
+
this.principal = principal;
|
|
84
|
+
this.claims = claims;
|
|
85
|
+
}
|
|
86
|
+
static anonymous() {
|
|
87
|
+
return new AuthContext("", false, null);
|
|
88
|
+
}
|
|
89
|
+
requireAuthenticated() {
|
|
90
|
+
if (!this.authenticated) {
|
|
91
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
53
95
|
// src/constants.ts
|
|
54
96
|
var RPC_METHOD_KEY = "vgi_rpc.method";
|
|
55
97
|
var LOG_LEVEL_KEY = "vgi_rpc.log_level";
|
|
@@ -183,27 +225,6 @@ import {
|
|
|
183
225
|
vectorFromArray as vectorFromArray2
|
|
184
226
|
} from "@query-farm/apache-arrow";
|
|
185
227
|
|
|
186
|
-
// src/errors.ts
|
|
187
|
-
class RpcError extends Error {
|
|
188
|
-
errorType;
|
|
189
|
-
errorMessage;
|
|
190
|
-
remoteTraceback;
|
|
191
|
-
constructor(errorType, errorMessage, remoteTraceback) {
|
|
192
|
-
super(`${errorType}: ${errorMessage}`);
|
|
193
|
-
this.errorType = errorType;
|
|
194
|
-
this.errorMessage = errorMessage;
|
|
195
|
-
this.remoteTraceback = remoteTraceback;
|
|
196
|
-
this.name = "RpcError";
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
class VersionError extends Error {
|
|
201
|
-
constructor(message) {
|
|
202
|
-
super(message);
|
|
203
|
-
this.name = "VersionError";
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
228
|
// src/wire/reader.ts
|
|
208
229
|
import { RecordBatchReader as RecordBatchReader2 } from "@query-farm/apache-arrow";
|
|
209
230
|
|
|
@@ -489,11 +510,18 @@ async function httpIntrospect(baseUrl, options) {
|
|
|
489
510
|
const prefix = options?.prefix ?? "/vgi";
|
|
490
511
|
const emptySchema = new ArrowSchema([]);
|
|
491
512
|
const body = buildRequestIpc(emptySchema, {}, DESCRIBE_METHOD_NAME);
|
|
513
|
+
const headers = { "Content-Type": ARROW_CONTENT_TYPE };
|
|
514
|
+
if (options?.authorization) {
|
|
515
|
+
headers.Authorization = options.authorization;
|
|
516
|
+
}
|
|
492
517
|
const response = await fetch(`${baseUrl}${prefix}/${DESCRIBE_METHOD_NAME}`, {
|
|
493
518
|
method: "POST",
|
|
494
|
-
headers
|
|
519
|
+
headers,
|
|
495
520
|
body
|
|
496
521
|
});
|
|
522
|
+
if (response.status === 401) {
|
|
523
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
524
|
+
}
|
|
497
525
|
const responseBody = new Uint8Array(await response.arrayBuffer());
|
|
498
526
|
const { batches } = await readResponseBatches(responseBody);
|
|
499
527
|
return parseDescribeResponse(batches);
|
|
@@ -515,6 +543,7 @@ class HttpStreamSession {
|
|
|
515
543
|
_compressionLevel;
|
|
516
544
|
_compressFn;
|
|
517
545
|
_decompressFn;
|
|
546
|
+
_authorization;
|
|
518
547
|
constructor(opts) {
|
|
519
548
|
this._baseUrl = opts.baseUrl;
|
|
520
549
|
this._prefix = opts.prefix;
|
|
@@ -529,6 +558,7 @@ class HttpStreamSession {
|
|
|
529
558
|
this._compressionLevel = opts.compressionLevel;
|
|
530
559
|
this._compressFn = opts.compressFn;
|
|
531
560
|
this._decompressFn = opts.decompressFn;
|
|
561
|
+
this._authorization = opts.authorization;
|
|
532
562
|
}
|
|
533
563
|
get header() {
|
|
534
564
|
return this._header;
|
|
@@ -541,6 +571,9 @@ class HttpStreamSession {
|
|
|
541
571
|
headers["Content-Encoding"] = "zstd";
|
|
542
572
|
headers["Accept-Encoding"] = "zstd";
|
|
543
573
|
}
|
|
574
|
+
if (this._authorization) {
|
|
575
|
+
headers.Authorization = this._authorization;
|
|
576
|
+
}
|
|
544
577
|
return headers;
|
|
545
578
|
}
|
|
546
579
|
_prepareBody(content) {
|
|
@@ -605,6 +638,9 @@ class HttpStreamSession {
|
|
|
605
638
|
headers: this._buildHeaders(),
|
|
606
639
|
body: this._prepareBody(body)
|
|
607
640
|
});
|
|
641
|
+
if (resp.status === 401) {
|
|
642
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
643
|
+
}
|
|
608
644
|
const responseBody = await this._readResponse(resp);
|
|
609
645
|
const { batches: responseBatches } = await readResponseBatches(responseBody);
|
|
610
646
|
let resultRows = [];
|
|
@@ -690,6 +726,9 @@ class HttpStreamSession {
|
|
|
690
726
|
headers: this._buildHeaders(),
|
|
691
727
|
body: this._prepareBody(body)
|
|
692
728
|
});
|
|
729
|
+
if (resp.status === 401) {
|
|
730
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
731
|
+
}
|
|
693
732
|
return this._readResponse(resp);
|
|
694
733
|
}
|
|
695
734
|
close() {}
|
|
@@ -700,6 +739,7 @@ function httpConnect(baseUrl, options) {
|
|
|
700
739
|
const prefix = (options?.prefix ?? "/vgi").replace(/\/+$/, "");
|
|
701
740
|
const onLog = options?.onLog;
|
|
702
741
|
const compressionLevel = options?.compressionLevel;
|
|
742
|
+
const authorization = options?.authorization;
|
|
703
743
|
let methodCache = null;
|
|
704
744
|
let compressFn;
|
|
705
745
|
let decompressFn;
|
|
@@ -722,6 +762,9 @@ function httpConnect(baseUrl, options) {
|
|
|
722
762
|
headers["Content-Encoding"] = "zstd";
|
|
723
763
|
headers["Accept-Encoding"] = "zstd";
|
|
724
764
|
}
|
|
765
|
+
if (authorization) {
|
|
766
|
+
headers.Authorization = authorization;
|
|
767
|
+
}
|
|
725
768
|
return headers;
|
|
726
769
|
}
|
|
727
770
|
function prepareBody(content) {
|
|
@@ -730,6 +773,11 @@ function httpConnect(baseUrl, options) {
|
|
|
730
773
|
}
|
|
731
774
|
return content;
|
|
732
775
|
}
|
|
776
|
+
function checkAuth(resp) {
|
|
777
|
+
if (resp.status === 401) {
|
|
778
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
779
|
+
}
|
|
780
|
+
}
|
|
733
781
|
async function readResponse(resp) {
|
|
734
782
|
let body = new Uint8Array(await resp.arrayBuffer());
|
|
735
783
|
if (resp.headers.get("Content-Encoding") === "zstd" && decompressFn) {
|
|
@@ -740,7 +788,7 @@ function httpConnect(baseUrl, options) {
|
|
|
740
788
|
async function ensureMethodCache() {
|
|
741
789
|
if (methodCache)
|
|
742
790
|
return methodCache;
|
|
743
|
-
const desc = await httpIntrospect(baseUrl, { prefix });
|
|
791
|
+
const desc = await httpIntrospect(baseUrl, { prefix, authorization });
|
|
744
792
|
methodCache = new Map(desc.methods.map((m) => [m.name, m]));
|
|
745
793
|
return methodCache;
|
|
746
794
|
}
|
|
@@ -759,6 +807,7 @@ function httpConnect(baseUrl, options) {
|
|
|
759
807
|
headers: buildHeaders(),
|
|
760
808
|
body: prepareBody(body)
|
|
761
809
|
});
|
|
810
|
+
checkAuth(resp);
|
|
762
811
|
const responseBody = await readResponse(resp);
|
|
763
812
|
const { batches } = await readResponseBatches(responseBody);
|
|
764
813
|
let resultBatch = null;
|
|
@@ -794,6 +843,7 @@ function httpConnect(baseUrl, options) {
|
|
|
794
843
|
headers: buildHeaders(),
|
|
795
844
|
body: prepareBody(body)
|
|
796
845
|
});
|
|
846
|
+
checkAuth(resp);
|
|
797
847
|
const responseBody = await readResponse(resp);
|
|
798
848
|
let header = null;
|
|
799
849
|
let stateToken = null;
|
|
@@ -899,7 +949,8 @@ function httpConnect(baseUrl, options) {
|
|
|
899
949
|
header,
|
|
900
950
|
compressionLevel,
|
|
901
951
|
compressFn,
|
|
902
|
-
decompressFn
|
|
952
|
+
decompressFn,
|
|
953
|
+
authorization
|
|
903
954
|
});
|
|
904
955
|
},
|
|
905
956
|
async describe() {
|
|
@@ -908,6 +959,53 @@ function httpConnect(baseUrl, options) {
|
|
|
908
959
|
close() {}
|
|
909
960
|
};
|
|
910
961
|
}
|
|
962
|
+
// src/client/oauth.ts
|
|
963
|
+
function parseMetadataJson(json) {
|
|
964
|
+
const result = {
|
|
965
|
+
resource: json.resource,
|
|
966
|
+
authorizationServers: json.authorization_servers
|
|
967
|
+
};
|
|
968
|
+
if (json.scopes_supported)
|
|
969
|
+
result.scopesSupported = json.scopes_supported;
|
|
970
|
+
if (json.bearer_methods_supported)
|
|
971
|
+
result.bearerMethodsSupported = json.bearer_methods_supported;
|
|
972
|
+
if (json.resource_name)
|
|
973
|
+
result.resourceName = json.resource_name;
|
|
974
|
+
if (json.resource_documentation)
|
|
975
|
+
result.resourceDocumentation = json.resource_documentation;
|
|
976
|
+
if (json.resource_policy_uri)
|
|
977
|
+
result.resourcePolicyUri = json.resource_policy_uri;
|
|
978
|
+
if (json.resource_tos_uri)
|
|
979
|
+
result.resourceTosUri = json.resource_tos_uri;
|
|
980
|
+
return result;
|
|
981
|
+
}
|
|
982
|
+
async function httpOAuthMetadata(baseUrl, prefix) {
|
|
983
|
+
const effectivePrefix = (prefix ?? "/vgi").replace(/\/+$/, "");
|
|
984
|
+
const metadataUrl = `${baseUrl.replace(/\/+$/, "")}/.well-known/oauth-protected-resource${effectivePrefix}`;
|
|
985
|
+
try {
|
|
986
|
+
return await fetchOAuthMetadata(metadataUrl);
|
|
987
|
+
} catch {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async function fetchOAuthMetadata(metadataUrl) {
|
|
992
|
+
const response = await fetch(metadataUrl);
|
|
993
|
+
if (!response.ok) {
|
|
994
|
+
throw new Error(`Failed to fetch OAuth metadata from ${metadataUrl}: ${response.status}`);
|
|
995
|
+
}
|
|
996
|
+
const json = await response.json();
|
|
997
|
+
return parseMetadataJson(json);
|
|
998
|
+
}
|
|
999
|
+
function parseResourceMetadataUrl(wwwAuthenticate) {
|
|
1000
|
+
const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
|
|
1001
|
+
if (!bearerMatch)
|
|
1002
|
+
return null;
|
|
1003
|
+
const params = bearerMatch[1];
|
|
1004
|
+
const metadataMatch = params.match(/resource_metadata="([^"]+)"/);
|
|
1005
|
+
if (!metadataMatch)
|
|
1006
|
+
return null;
|
|
1007
|
+
return metadataMatch[1];
|
|
1008
|
+
}
|
|
911
1009
|
// src/client/pipe.ts
|
|
912
1010
|
import {
|
|
913
1011
|
Field as Field2,
|
|
@@ -1341,6 +1439,35 @@ function subprocessConnect(cmd, options) {
|
|
|
1341
1439
|
};
|
|
1342
1440
|
return client;
|
|
1343
1441
|
}
|
|
1442
|
+
// src/http/auth.ts
|
|
1443
|
+
function oauthResourceMetadataToJson(metadata) {
|
|
1444
|
+
const json = {
|
|
1445
|
+
resource: metadata.resource,
|
|
1446
|
+
authorization_servers: metadata.authorizationServers
|
|
1447
|
+
};
|
|
1448
|
+
if (metadata.scopesSupported)
|
|
1449
|
+
json.scopes_supported = metadata.scopesSupported;
|
|
1450
|
+
if (metadata.bearerMethodsSupported)
|
|
1451
|
+
json.bearer_methods_supported = metadata.bearerMethodsSupported;
|
|
1452
|
+
if (metadata.resourceName)
|
|
1453
|
+
json.resource_name = metadata.resourceName;
|
|
1454
|
+
if (metadata.resourceDocumentation)
|
|
1455
|
+
json.resource_documentation = metadata.resourceDocumentation;
|
|
1456
|
+
if (metadata.resourcePolicyUri)
|
|
1457
|
+
json.resource_policy_uri = metadata.resourcePolicyUri;
|
|
1458
|
+
if (metadata.resourceTosUri)
|
|
1459
|
+
json.resource_tos_uri = metadata.resourceTosUri;
|
|
1460
|
+
return json;
|
|
1461
|
+
}
|
|
1462
|
+
function wellKnownPath(prefix) {
|
|
1463
|
+
return `/.well-known/oauth-protected-resource${prefix}`;
|
|
1464
|
+
}
|
|
1465
|
+
function buildWwwAuthenticateHeader(metadataUrl) {
|
|
1466
|
+
if (metadataUrl) {
|
|
1467
|
+
return `Bearer resource_metadata="${metadataUrl}"`;
|
|
1468
|
+
}
|
|
1469
|
+
return "Bearer";
|
|
1470
|
+
}
|
|
1344
1471
|
// src/http/handler.ts
|
|
1345
1472
|
import { randomBytes } from "node:crypto";
|
|
1346
1473
|
import { Schema as Schema5 } from "@query-farm/apache-arrow";
|
|
@@ -1437,20 +1564,28 @@ function buildLogBatch(schema, level, message, extra, serverId, requestId) {
|
|
|
1437
1564
|
}
|
|
1438
1565
|
return buildEmptyBatch(schema, metadata);
|
|
1439
1566
|
}
|
|
1440
|
-
function
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
if (schema.fields.length === 0) {
|
|
1445
|
-
const structType2 = new Struct5(schema.fields);
|
|
1446
|
-
const data2 = makeData5({
|
|
1447
|
-
type: structType2,
|
|
1448
|
-
length: 0,
|
|
1449
|
-
children: [],
|
|
1450
|
-
nullCount: 0
|
|
1451
|
-
});
|
|
1452
|
-
return new RecordBatch5(schema, data2, metadata);
|
|
1567
|
+
function makeEmptyData(type) {
|
|
1568
|
+
if (DataType2.isStruct(type)) {
|
|
1569
|
+
const children = type.children.map((f) => makeEmptyData(f.type));
|
|
1570
|
+
return makeData5({ type, length: 0, children, nullCount: 0 });
|
|
1453
1571
|
}
|
|
1572
|
+
if (DataType2.isList(type)) {
|
|
1573
|
+
const childData = makeEmptyData(type.children[0].type);
|
|
1574
|
+
return makeData5({ type, length: 0, children: [childData], nullCount: 0, valueOffsets: new Int32Array([0]) });
|
|
1575
|
+
}
|
|
1576
|
+
if (DataType2.isFixedSizeList(type)) {
|
|
1577
|
+
const childData = makeEmptyData(type.children[0].type);
|
|
1578
|
+
return makeData5({ type, length: 0, child: childData, nullCount: 0 });
|
|
1579
|
+
}
|
|
1580
|
+
if (DataType2.isMap(type)) {
|
|
1581
|
+
const entryType = type.children[0]?.type;
|
|
1582
|
+
const entryData = entryType ? makeEmptyData(entryType) : makeData5({ type: new Struct5([]), length: 0, children: [], nullCount: 0 });
|
|
1583
|
+
return makeData5({ type, length: 0, children: [entryData], nullCount: 0, valueOffsets: new Int32Array([0]) });
|
|
1584
|
+
}
|
|
1585
|
+
return makeData5({ type, length: 0, nullCount: 0 });
|
|
1586
|
+
}
|
|
1587
|
+
function buildEmptyBatch(schema, metadata) {
|
|
1588
|
+
const children = schema.fields.map((f) => makeEmptyData(f.type));
|
|
1454
1589
|
const structType = new Struct5(schema.fields);
|
|
1455
1590
|
const data = makeData5({
|
|
1456
1591
|
type: structType,
|
|
@@ -1476,11 +1611,13 @@ class OutputCollector {
|
|
|
1476
1611
|
_outputSchema;
|
|
1477
1612
|
_serverId;
|
|
1478
1613
|
_requestId;
|
|
1479
|
-
|
|
1614
|
+
auth;
|
|
1615
|
+
constructor(outputSchema, producerMode = true, serverId = "", requestId = null, authContext) {
|
|
1480
1616
|
this._outputSchema = outputSchema;
|
|
1481
1617
|
this._producerMode = producerMode;
|
|
1482
1618
|
this._serverId = serverId;
|
|
1483
1619
|
this._requestId = requestId;
|
|
1620
|
+
this.auth = authContext ?? AuthContext.anonymous();
|
|
1484
1621
|
}
|
|
1485
1622
|
get outputSchema() {
|
|
1486
1623
|
return this._outputSchema;
|
|
@@ -1783,7 +1920,7 @@ async function httpDispatchUnary(method, body, ctx) {
|
|
|
1783
1920
|
if (parsed.methodName !== method.name) {
|
|
1784
1921
|
throw new HttpRpcError(`Method name in request '${parsed.methodName}' does not match URL '${method.name}'`, 400);
|
|
1785
1922
|
}
|
|
1786
|
-
const out = new OutputCollector(schema, true, ctx.serverId, parsed.requestId);
|
|
1923
|
+
const out = new OutputCollector(schema, true, ctx.serverId, parsed.requestId, ctx.authContext);
|
|
1787
1924
|
try {
|
|
1788
1925
|
const result = await method.handler(parsed.params, out);
|
|
1789
1926
|
const resultBatch = buildResultBatch(schema, result, ctx.serverId, parsed.requestId);
|
|
@@ -1820,7 +1957,7 @@ async function httpDispatchStreamInit(method, body, ctx) {
|
|
|
1820
1957
|
let headerBytes = null;
|
|
1821
1958
|
if (method.headerSchema && method.headerInit) {
|
|
1822
1959
|
try {
|
|
1823
|
-
const headerOut = new OutputCollector(method.headerSchema, true, ctx.serverId, parsed.requestId);
|
|
1960
|
+
const headerOut = new OutputCollector(method.headerSchema, true, ctx.serverId, parsed.requestId, ctx.authContext);
|
|
1824
1961
|
const headerValues = method.headerInit(parsed.params, state, headerOut);
|
|
1825
1962
|
const headerBatch = buildResultBatch(method.headerSchema, headerValues, ctx.serverId, parsed.requestId);
|
|
1826
1963
|
const headerBatches = [...headerOut.batches.map((b) => b.batch), headerBatch];
|
|
@@ -1888,7 +2025,7 @@ async function httpDispatchStreamExchange(method, body, ctx) {
|
|
|
1888
2025
|
if (effectiveProducer) {
|
|
1889
2026
|
return produceStreamResponse(method, state, outputSchema, inputSchema, ctx, null, null);
|
|
1890
2027
|
} else {
|
|
1891
|
-
const out = new OutputCollector(outputSchema, effectiveProducer, ctx.serverId, null);
|
|
2028
|
+
const out = new OutputCollector(outputSchema, effectiveProducer, ctx.serverId, null, ctx.authContext);
|
|
1892
2029
|
const conformedBatch = conformBatchToSchema(reqBatch, inputSchema);
|
|
1893
2030
|
try {
|
|
1894
2031
|
if (method.exchangeFn) {
|
|
@@ -1938,7 +2075,7 @@ async function produceStreamResponse(method, state, outputSchema, inputSchema, c
|
|
|
1938
2075
|
const maxBytes = ctx.maxStreamResponseBytes;
|
|
1939
2076
|
let estimatedBytes = 0;
|
|
1940
2077
|
while (true) {
|
|
1941
|
-
const out = new OutputCollector(outputSchema, true, ctx.serverId, requestId);
|
|
2078
|
+
const out = new OutputCollector(outputSchema, true, ctx.serverId, requestId, ctx.authContext);
|
|
1942
2079
|
try {
|
|
1943
2080
|
if (method.producerFn) {
|
|
1944
2081
|
await method.producerFn(state, out);
|
|
@@ -2014,10 +2151,12 @@ function createHttpHandler(protocol, options) {
|
|
|
2014
2151
|
const maxRequestBytes = options?.maxRequestBytes;
|
|
2015
2152
|
const maxStreamResponseBytes = options?.maxStreamResponseBytes;
|
|
2016
2153
|
const serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
2154
|
+
const authenticate = options?.authenticate;
|
|
2155
|
+
const oauthMetadata = options?.oauthResourceMetadata;
|
|
2017
2156
|
const methods = protocol.getMethods();
|
|
2018
2157
|
const compressionLevel = options?.compressionLevel;
|
|
2019
2158
|
const stateSerializer = options?.stateSerializer ?? jsonStateSerializer;
|
|
2020
|
-
const
|
|
2159
|
+
const baseCtx = {
|
|
2021
2160
|
signingKey,
|
|
2022
2161
|
tokenTtl,
|
|
2023
2162
|
serverId,
|
|
@@ -2053,6 +2192,18 @@ function createHttpHandler(protocol, options) {
|
|
|
2053
2192
|
return async function handler(request) {
|
|
2054
2193
|
const url = new URL(request.url);
|
|
2055
2194
|
const path = url.pathname;
|
|
2195
|
+
if (oauthMetadata && path === wellKnownPath(prefix)) {
|
|
2196
|
+
if (request.method !== "GET") {
|
|
2197
|
+
return new Response("Method Not Allowed", { status: 405 });
|
|
2198
|
+
}
|
|
2199
|
+
const body2 = JSON.stringify(oauthResourceMetadataToJson(oauthMetadata));
|
|
2200
|
+
const headers = new Headers({
|
|
2201
|
+
"Content-Type": "application/json",
|
|
2202
|
+
"Cache-Control": "public, max-age=3600"
|
|
2203
|
+
});
|
|
2204
|
+
addCorsHeaders(headers);
|
|
2205
|
+
return new Response(body2, { status: 200, headers });
|
|
2206
|
+
}
|
|
2056
2207
|
if (request.method === "OPTIONS") {
|
|
2057
2208
|
if (path === `${prefix}/__capabilities__`) {
|
|
2058
2209
|
const headers = new Headers;
|
|
@@ -2088,6 +2239,22 @@ function createHttpHandler(protocol, options) {
|
|
|
2088
2239
|
if (contentEncoding === "zstd") {
|
|
2089
2240
|
body = zstdDecompress(body);
|
|
2090
2241
|
}
|
|
2242
|
+
const ctx = { ...baseCtx };
|
|
2243
|
+
if (authenticate) {
|
|
2244
|
+
try {
|
|
2245
|
+
ctx.authContext = await authenticate(request);
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
const headers = new Headers({ "Content-Type": "text/plain" });
|
|
2248
|
+
addCorsHeaders(headers);
|
|
2249
|
+
if (oauthMetadata) {
|
|
2250
|
+
const metadataUrl = new URL(request.url);
|
|
2251
|
+
metadataUrl.pathname = wellKnownPath(prefix);
|
|
2252
|
+
metadataUrl.search = "";
|
|
2253
|
+
headers.set("WWW-Authenticate", buildWwwAuthenticateHeader(metadataUrl.toString()));
|
|
2254
|
+
}
|
|
2255
|
+
return new Response(error.message || "Unauthorized", { status: 401, headers });
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2091
2258
|
if (path === `${prefix}/${DESCRIBE_METHOD_NAME}`) {
|
|
2092
2259
|
try {
|
|
2093
2260
|
const response = httpDispatchDescribe(protocol.name, methods, serverId);
|
|
@@ -2147,6 +2314,1200 @@ function createHttpHandler(protocol, options) {
|
|
|
2147
2314
|
}
|
|
2148
2315
|
};
|
|
2149
2316
|
}
|
|
2317
|
+
// node_modules/oauth4webapi/build/index.js
|
|
2318
|
+
var USER_AGENT;
|
|
2319
|
+
if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
|
|
2320
|
+
const NAME = "oauth4webapi";
|
|
2321
|
+
const VERSION = "v3.8.5";
|
|
2322
|
+
USER_AGENT = `${NAME}/${VERSION}`;
|
|
2323
|
+
}
|
|
2324
|
+
function looseInstanceOf(input, expected) {
|
|
2325
|
+
if (input == null) {
|
|
2326
|
+
return false;
|
|
2327
|
+
}
|
|
2328
|
+
try {
|
|
2329
|
+
return input instanceof expected || Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag];
|
|
2330
|
+
} catch {
|
|
2331
|
+
return false;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
var ERR_INVALID_ARG_VALUE = "ERR_INVALID_ARG_VALUE";
|
|
2335
|
+
var ERR_INVALID_ARG_TYPE = "ERR_INVALID_ARG_TYPE";
|
|
2336
|
+
function CodedTypeError(message, code, cause) {
|
|
2337
|
+
const err = new TypeError(message, { cause });
|
|
2338
|
+
Object.assign(err, { code });
|
|
2339
|
+
return err;
|
|
2340
|
+
}
|
|
2341
|
+
var allowInsecureRequests = Symbol();
|
|
2342
|
+
var clockSkew = Symbol();
|
|
2343
|
+
var clockTolerance = Symbol();
|
|
2344
|
+
var customFetch = Symbol();
|
|
2345
|
+
var modifyAssertion = Symbol();
|
|
2346
|
+
var jweDecrypt = Symbol();
|
|
2347
|
+
var jwksCache = Symbol();
|
|
2348
|
+
var encoder = new TextEncoder;
|
|
2349
|
+
var decoder = new TextDecoder;
|
|
2350
|
+
function buf(input) {
|
|
2351
|
+
if (typeof input === "string") {
|
|
2352
|
+
return encoder.encode(input);
|
|
2353
|
+
}
|
|
2354
|
+
return decoder.decode(input);
|
|
2355
|
+
}
|
|
2356
|
+
var encodeBase64Url;
|
|
2357
|
+
if (Uint8Array.prototype.toBase64) {
|
|
2358
|
+
encodeBase64Url = (input) => {
|
|
2359
|
+
if (input instanceof ArrayBuffer) {
|
|
2360
|
+
input = new Uint8Array(input);
|
|
2361
|
+
}
|
|
2362
|
+
return input.toBase64({ alphabet: "base64url", omitPadding: true });
|
|
2363
|
+
};
|
|
2364
|
+
} else {
|
|
2365
|
+
const CHUNK_SIZE = 32768;
|
|
2366
|
+
encodeBase64Url = (input) => {
|
|
2367
|
+
if (input instanceof ArrayBuffer) {
|
|
2368
|
+
input = new Uint8Array(input);
|
|
2369
|
+
}
|
|
2370
|
+
const arr = [];
|
|
2371
|
+
for (let i = 0;i < input.byteLength; i += CHUNK_SIZE) {
|
|
2372
|
+
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
|
|
2373
|
+
}
|
|
2374
|
+
return btoa(arr.join("")).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
var decodeBase64Url;
|
|
2378
|
+
if (Uint8Array.fromBase64) {
|
|
2379
|
+
decodeBase64Url = (input) => {
|
|
2380
|
+
try {
|
|
2381
|
+
return Uint8Array.fromBase64(input, { alphabet: "base64url" });
|
|
2382
|
+
} catch (cause) {
|
|
2383
|
+
throw CodedTypeError("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE, cause);
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
} else {
|
|
2387
|
+
decodeBase64Url = (input) => {
|
|
2388
|
+
try {
|
|
2389
|
+
const binary = atob(input.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""));
|
|
2390
|
+
const bytes = new Uint8Array(binary.length);
|
|
2391
|
+
for (let i = 0;i < binary.length; i++) {
|
|
2392
|
+
bytes[i] = binary.charCodeAt(i);
|
|
2393
|
+
}
|
|
2394
|
+
return bytes;
|
|
2395
|
+
} catch (cause) {
|
|
2396
|
+
throw CodedTypeError("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE, cause);
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
function b64u(input) {
|
|
2401
|
+
if (typeof input === "string") {
|
|
2402
|
+
return decodeBase64Url(input);
|
|
2403
|
+
}
|
|
2404
|
+
return encodeBase64Url(input);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
class UnsupportedOperationError extends Error {
|
|
2408
|
+
code;
|
|
2409
|
+
constructor(message, options) {
|
|
2410
|
+
super(message, options);
|
|
2411
|
+
this.name = this.constructor.name;
|
|
2412
|
+
this.code = UNSUPPORTED_OPERATION;
|
|
2413
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
class OperationProcessingError extends Error {
|
|
2418
|
+
code;
|
|
2419
|
+
constructor(message, options) {
|
|
2420
|
+
super(message, options);
|
|
2421
|
+
this.name = this.constructor.name;
|
|
2422
|
+
if (options?.code) {
|
|
2423
|
+
this.code = options?.code;
|
|
2424
|
+
}
|
|
2425
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
function OPE(message, code, cause) {
|
|
2429
|
+
return new OperationProcessingError(message, { code, cause });
|
|
2430
|
+
}
|
|
2431
|
+
async function calculateJwkThumbprint(jwk) {
|
|
2432
|
+
let components;
|
|
2433
|
+
switch (jwk.kty) {
|
|
2434
|
+
case "EC":
|
|
2435
|
+
components = {
|
|
2436
|
+
crv: jwk.crv,
|
|
2437
|
+
kty: jwk.kty,
|
|
2438
|
+
x: jwk.x,
|
|
2439
|
+
y: jwk.y
|
|
2440
|
+
};
|
|
2441
|
+
break;
|
|
2442
|
+
case "OKP":
|
|
2443
|
+
components = {
|
|
2444
|
+
crv: jwk.crv,
|
|
2445
|
+
kty: jwk.kty,
|
|
2446
|
+
x: jwk.x
|
|
2447
|
+
};
|
|
2448
|
+
break;
|
|
2449
|
+
case "AKP":
|
|
2450
|
+
components = {
|
|
2451
|
+
alg: jwk.alg,
|
|
2452
|
+
kty: jwk.kty,
|
|
2453
|
+
pub: jwk.pub
|
|
2454
|
+
};
|
|
2455
|
+
break;
|
|
2456
|
+
case "RSA":
|
|
2457
|
+
components = {
|
|
2458
|
+
e: jwk.e,
|
|
2459
|
+
kty: jwk.kty,
|
|
2460
|
+
n: jwk.n
|
|
2461
|
+
};
|
|
2462
|
+
break;
|
|
2463
|
+
default:
|
|
2464
|
+
throw new UnsupportedOperationError("unsupported JWK key type", { cause: jwk });
|
|
2465
|
+
}
|
|
2466
|
+
return b64u(await crypto.subtle.digest("SHA-256", buf(JSON.stringify(components))));
|
|
2467
|
+
}
|
|
2468
|
+
function assertCryptoKey(key, it) {
|
|
2469
|
+
if (!(key instanceof CryptoKey)) {
|
|
2470
|
+
throw CodedTypeError(`${it} must be a CryptoKey`, ERR_INVALID_ARG_TYPE);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
function assertPrivateKey(key, it) {
|
|
2474
|
+
assertCryptoKey(key, it);
|
|
2475
|
+
if (key.type !== "private") {
|
|
2476
|
+
throw CodedTypeError(`${it} must be a private CryptoKey`, ERR_INVALID_ARG_VALUE);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
function assertPublicKey(key, it) {
|
|
2480
|
+
assertCryptoKey(key, it);
|
|
2481
|
+
if (key.type !== "public") {
|
|
2482
|
+
throw CodedTypeError(`${it} must be a public CryptoKey`, ERR_INVALID_ARG_VALUE);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
function normalizeTyp(value) {
|
|
2486
|
+
return value.toLowerCase().replace(/^application\//, "");
|
|
2487
|
+
}
|
|
2488
|
+
function isJsonObject(input) {
|
|
2489
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
2490
|
+
return false;
|
|
2491
|
+
}
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
function prepareHeaders(input) {
|
|
2495
|
+
if (looseInstanceOf(input, Headers)) {
|
|
2496
|
+
input = Object.fromEntries(input.entries());
|
|
2497
|
+
}
|
|
2498
|
+
const headers = new Headers(input ?? {});
|
|
2499
|
+
if (USER_AGENT && !headers.has("user-agent")) {
|
|
2500
|
+
headers.set("user-agent", USER_AGENT);
|
|
2501
|
+
}
|
|
2502
|
+
if (headers.has("authorization")) {
|
|
2503
|
+
throw CodedTypeError('"options.headers" must not include the "authorization" header name', ERR_INVALID_ARG_VALUE);
|
|
2504
|
+
}
|
|
2505
|
+
return headers;
|
|
2506
|
+
}
|
|
2507
|
+
function signal(url, value) {
|
|
2508
|
+
if (value !== undefined) {
|
|
2509
|
+
if (typeof value === "function") {
|
|
2510
|
+
value = value(url.href);
|
|
2511
|
+
}
|
|
2512
|
+
if (!(value instanceof AbortSignal)) {
|
|
2513
|
+
throw CodedTypeError('"options.signal" must return or be an instance of AbortSignal', ERR_INVALID_ARG_TYPE);
|
|
2514
|
+
}
|
|
2515
|
+
return value;
|
|
2516
|
+
}
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
function replaceDoubleSlash(pathname) {
|
|
2520
|
+
if (pathname.includes("//")) {
|
|
2521
|
+
return pathname.replace("//", "/");
|
|
2522
|
+
}
|
|
2523
|
+
return pathname;
|
|
2524
|
+
}
|
|
2525
|
+
function prependWellKnown(url, wellKnown, allowTerminatingSlash = false) {
|
|
2526
|
+
if (url.pathname === "/") {
|
|
2527
|
+
url.pathname = wellKnown;
|
|
2528
|
+
} else {
|
|
2529
|
+
url.pathname = replaceDoubleSlash(`${wellKnown}/${allowTerminatingSlash ? url.pathname : url.pathname.replace(/(\/)$/, "")}`);
|
|
2530
|
+
}
|
|
2531
|
+
return url;
|
|
2532
|
+
}
|
|
2533
|
+
function appendWellKnown(url, wellKnown) {
|
|
2534
|
+
url.pathname = replaceDoubleSlash(`${url.pathname}/${wellKnown}`);
|
|
2535
|
+
return url;
|
|
2536
|
+
}
|
|
2537
|
+
async function performDiscovery(input, urlName, transform, options) {
|
|
2538
|
+
if (!(input instanceof URL)) {
|
|
2539
|
+
throw CodedTypeError(`"${urlName}" must be an instance of URL`, ERR_INVALID_ARG_TYPE);
|
|
2540
|
+
}
|
|
2541
|
+
checkProtocol(input, options?.[allowInsecureRequests] !== true);
|
|
2542
|
+
const url = transform(new URL(input.href));
|
|
2543
|
+
const headers = prepareHeaders(options?.headers);
|
|
2544
|
+
headers.set("accept", "application/json");
|
|
2545
|
+
return (options?.[customFetch] || fetch)(url.href, {
|
|
2546
|
+
body: undefined,
|
|
2547
|
+
headers: Object.fromEntries(headers.entries()),
|
|
2548
|
+
method: "GET",
|
|
2549
|
+
redirect: "manual",
|
|
2550
|
+
signal: signal(url, options?.signal)
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
async function discoveryRequest(issuerIdentifier, options) {
|
|
2554
|
+
return performDiscovery(issuerIdentifier, "issuerIdentifier", (url) => {
|
|
2555
|
+
switch (options?.algorithm) {
|
|
2556
|
+
case undefined:
|
|
2557
|
+
case "oidc":
|
|
2558
|
+
appendWellKnown(url, ".well-known/openid-configuration");
|
|
2559
|
+
break;
|
|
2560
|
+
case "oauth2":
|
|
2561
|
+
prependWellKnown(url, ".well-known/oauth-authorization-server");
|
|
2562
|
+
break;
|
|
2563
|
+
default:
|
|
2564
|
+
throw CodedTypeError('"options.algorithm" must be "oidc" (default), or "oauth2"', ERR_INVALID_ARG_VALUE);
|
|
2565
|
+
}
|
|
2566
|
+
return url;
|
|
2567
|
+
}, options);
|
|
2568
|
+
}
|
|
2569
|
+
function assertString(input, it, code, cause) {
|
|
2570
|
+
try {
|
|
2571
|
+
if (typeof input !== "string") {
|
|
2572
|
+
throw CodedTypeError(`${it} must be a string`, ERR_INVALID_ARG_TYPE, cause);
|
|
2573
|
+
}
|
|
2574
|
+
if (input.length === 0) {
|
|
2575
|
+
throw CodedTypeError(`${it} must not be empty`, ERR_INVALID_ARG_VALUE, cause);
|
|
2576
|
+
}
|
|
2577
|
+
} catch (err) {
|
|
2578
|
+
if (code) {
|
|
2579
|
+
throw OPE(err.message, code, cause);
|
|
2580
|
+
}
|
|
2581
|
+
throw err;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
async function processDiscoveryResponse(expectedIssuerIdentifier, response) {
|
|
2585
|
+
const expected = expectedIssuerIdentifier;
|
|
2586
|
+
if (!(expected instanceof URL) && expected !== _nodiscoverycheck) {
|
|
2587
|
+
throw CodedTypeError('"expectedIssuerIdentifier" must be an instance of URL', ERR_INVALID_ARG_TYPE);
|
|
2588
|
+
}
|
|
2589
|
+
if (!looseInstanceOf(response, Response)) {
|
|
2590
|
+
throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
|
|
2591
|
+
}
|
|
2592
|
+
if (response.status !== 200) {
|
|
2593
|
+
throw OPE('"response" is not a conform Authorization Server Metadata response (unexpected HTTP status code)', RESPONSE_IS_NOT_CONFORM, response);
|
|
2594
|
+
}
|
|
2595
|
+
assertReadableResponse(response);
|
|
2596
|
+
const json = await getResponseJsonBody(response);
|
|
2597
|
+
assertString(json.issuer, '"response" body "issuer" property', INVALID_RESPONSE, { body: json });
|
|
2598
|
+
if (expected !== _nodiscoverycheck && new URL(json.issuer).href !== expected.href) {
|
|
2599
|
+
throw OPE('"response" body "issuer" property does not match the expected value', JSON_ATTRIBUTE_COMPARISON, { expected: expected.href, body: json, attribute: "issuer" });
|
|
2600
|
+
}
|
|
2601
|
+
return json;
|
|
2602
|
+
}
|
|
2603
|
+
function assertApplicationJson(response) {
|
|
2604
|
+
assertContentType(response, "application/json");
|
|
2605
|
+
}
|
|
2606
|
+
function notJson(response, ...types) {
|
|
2607
|
+
let msg = '"response" content-type must be ';
|
|
2608
|
+
if (types.length > 2) {
|
|
2609
|
+
const last = types.pop();
|
|
2610
|
+
msg += `${types.join(", ")}, or ${last}`;
|
|
2611
|
+
} else if (types.length === 2) {
|
|
2612
|
+
msg += `${types[0]} or ${types[1]}`;
|
|
2613
|
+
} else {
|
|
2614
|
+
msg += types[0];
|
|
2615
|
+
}
|
|
2616
|
+
return OPE(msg, RESPONSE_IS_NOT_JSON, response);
|
|
2617
|
+
}
|
|
2618
|
+
function assertContentTypes(response, ...types) {
|
|
2619
|
+
if (!types.includes(getContentType(response))) {
|
|
2620
|
+
throw notJson(response, ...types);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
function assertContentType(response, contentType) {
|
|
2624
|
+
if (getContentType(response) !== contentType) {
|
|
2625
|
+
throw notJson(response, contentType);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
function randomBytes2() {
|
|
2629
|
+
return b64u(crypto.getRandomValues(new Uint8Array(32)));
|
|
2630
|
+
}
|
|
2631
|
+
function psAlg(key) {
|
|
2632
|
+
switch (key.algorithm.hash.name) {
|
|
2633
|
+
case "SHA-256":
|
|
2634
|
+
return "PS256";
|
|
2635
|
+
case "SHA-384":
|
|
2636
|
+
return "PS384";
|
|
2637
|
+
case "SHA-512":
|
|
2638
|
+
return "PS512";
|
|
2639
|
+
default:
|
|
2640
|
+
throw new UnsupportedOperationError("unsupported RsaHashedKeyAlgorithm hash name", {
|
|
2641
|
+
cause: key
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
function rsAlg(key) {
|
|
2646
|
+
switch (key.algorithm.hash.name) {
|
|
2647
|
+
case "SHA-256":
|
|
2648
|
+
return "RS256";
|
|
2649
|
+
case "SHA-384":
|
|
2650
|
+
return "RS384";
|
|
2651
|
+
case "SHA-512":
|
|
2652
|
+
return "RS512";
|
|
2653
|
+
default:
|
|
2654
|
+
throw new UnsupportedOperationError("unsupported RsaHashedKeyAlgorithm hash name", {
|
|
2655
|
+
cause: key
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
function esAlg(key) {
|
|
2660
|
+
switch (key.algorithm.namedCurve) {
|
|
2661
|
+
case "P-256":
|
|
2662
|
+
return "ES256";
|
|
2663
|
+
case "P-384":
|
|
2664
|
+
return "ES384";
|
|
2665
|
+
case "P-521":
|
|
2666
|
+
return "ES512";
|
|
2667
|
+
default:
|
|
2668
|
+
throw new UnsupportedOperationError("unsupported EcKeyAlgorithm namedCurve", { cause: key });
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
function keyToJws(key) {
|
|
2672
|
+
switch (key.algorithm.name) {
|
|
2673
|
+
case "RSA-PSS":
|
|
2674
|
+
return psAlg(key);
|
|
2675
|
+
case "RSASSA-PKCS1-v1_5":
|
|
2676
|
+
return rsAlg(key);
|
|
2677
|
+
case "ECDSA":
|
|
2678
|
+
return esAlg(key);
|
|
2679
|
+
case "Ed25519":
|
|
2680
|
+
case "ML-DSA-44":
|
|
2681
|
+
case "ML-DSA-65":
|
|
2682
|
+
case "ML-DSA-87":
|
|
2683
|
+
return key.algorithm.name;
|
|
2684
|
+
case "EdDSA":
|
|
2685
|
+
return "Ed25519";
|
|
2686
|
+
default:
|
|
2687
|
+
throw new UnsupportedOperationError("unsupported CryptoKey algorithm name", { cause: key });
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
function getClockSkew(client) {
|
|
2691
|
+
const skew = client?.[clockSkew];
|
|
2692
|
+
return typeof skew === "number" && Number.isFinite(skew) ? skew : 0;
|
|
2693
|
+
}
|
|
2694
|
+
function getClockTolerance(client) {
|
|
2695
|
+
const tolerance = client?.[clockTolerance];
|
|
2696
|
+
return typeof tolerance === "number" && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1 ? tolerance : 30;
|
|
2697
|
+
}
|
|
2698
|
+
function epochTime() {
|
|
2699
|
+
return Math.floor(Date.now() / 1000);
|
|
2700
|
+
}
|
|
2701
|
+
function assertAs(as) {
|
|
2702
|
+
if (typeof as !== "object" || as === null) {
|
|
2703
|
+
throw CodedTypeError('"as" must be an object', ERR_INVALID_ARG_TYPE);
|
|
2704
|
+
}
|
|
2705
|
+
assertString(as.issuer, '"as.issuer"');
|
|
2706
|
+
}
|
|
2707
|
+
async function signJwt(header, payload, key) {
|
|
2708
|
+
if (!key.usages.includes("sign")) {
|
|
2709
|
+
throw CodedTypeError('CryptoKey instances used for signing assertions must include "sign" in their "usages"', ERR_INVALID_ARG_VALUE);
|
|
2710
|
+
}
|
|
2711
|
+
const input = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(payload)))}`;
|
|
2712
|
+
const signature = b64u(await crypto.subtle.sign(keyToSubtle(key), key, buf(input)));
|
|
2713
|
+
return `${input}.${signature}`;
|
|
2714
|
+
}
|
|
2715
|
+
var jwkCache;
|
|
2716
|
+
async function getSetPublicJwkCache(key, alg) {
|
|
2717
|
+
const { kty, e, n, x, y, crv, pub } = await crypto.subtle.exportKey("jwk", key);
|
|
2718
|
+
const jwk = { kty, e, n, x, y, crv, pub };
|
|
2719
|
+
if (kty === "AKP")
|
|
2720
|
+
jwk.alg = alg;
|
|
2721
|
+
jwkCache.set(key, jwk);
|
|
2722
|
+
return jwk;
|
|
2723
|
+
}
|
|
2724
|
+
async function publicJwk(key, alg) {
|
|
2725
|
+
jwkCache ||= new WeakMap;
|
|
2726
|
+
return jwkCache.get(key) || getSetPublicJwkCache(key, alg);
|
|
2727
|
+
}
|
|
2728
|
+
var URLParse = URL.parse ? (url, base) => URL.parse(url, base) : (url, base) => {
|
|
2729
|
+
try {
|
|
2730
|
+
return new URL(url, base);
|
|
2731
|
+
} catch {
|
|
2732
|
+
return null;
|
|
2733
|
+
}
|
|
2734
|
+
};
|
|
2735
|
+
function checkProtocol(url, enforceHttps) {
|
|
2736
|
+
if (enforceHttps && url.protocol !== "https:") {
|
|
2737
|
+
throw OPE("only requests to HTTPS are allowed", HTTP_REQUEST_FORBIDDEN, url);
|
|
2738
|
+
}
|
|
2739
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
2740
|
+
throw OPE("only HTTP and HTTPS requests are allowed", REQUEST_PROTOCOL_FORBIDDEN, url);
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
function validateEndpoint(value, endpoint, useMtlsAlias, enforceHttps) {
|
|
2744
|
+
let url;
|
|
2745
|
+
if (typeof value !== "string" || !(url = URLParse(value))) {
|
|
2746
|
+
throw OPE(`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`, value === undefined ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA, { attribute: useMtlsAlias ? `mtls_endpoint_aliases.${endpoint}` : endpoint });
|
|
2747
|
+
}
|
|
2748
|
+
checkProtocol(url, enforceHttps);
|
|
2749
|
+
return url;
|
|
2750
|
+
}
|
|
2751
|
+
function resolveEndpoint(as, endpoint, useMtlsAlias, enforceHttps) {
|
|
2752
|
+
if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
|
|
2753
|
+
return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias, enforceHttps);
|
|
2754
|
+
}
|
|
2755
|
+
return validateEndpoint(as[endpoint], endpoint, useMtlsAlias, enforceHttps);
|
|
2756
|
+
}
|
|
2757
|
+
class DPoPHandler {
|
|
2758
|
+
#header;
|
|
2759
|
+
#privateKey;
|
|
2760
|
+
#publicKey;
|
|
2761
|
+
#clockSkew;
|
|
2762
|
+
#modifyAssertion;
|
|
2763
|
+
#map;
|
|
2764
|
+
#jkt;
|
|
2765
|
+
constructor(client, keyPair, options) {
|
|
2766
|
+
assertPrivateKey(keyPair?.privateKey, '"DPoP.privateKey"');
|
|
2767
|
+
assertPublicKey(keyPair?.publicKey, '"DPoP.publicKey"');
|
|
2768
|
+
if (!keyPair.publicKey.extractable) {
|
|
2769
|
+
throw CodedTypeError('"DPoP.publicKey.extractable" must be true', ERR_INVALID_ARG_VALUE);
|
|
2770
|
+
}
|
|
2771
|
+
this.#modifyAssertion = options?.[modifyAssertion];
|
|
2772
|
+
this.#clockSkew = getClockSkew(client);
|
|
2773
|
+
this.#privateKey = keyPair.privateKey;
|
|
2774
|
+
this.#publicKey = keyPair.publicKey;
|
|
2775
|
+
branded.add(this);
|
|
2776
|
+
}
|
|
2777
|
+
#get(key) {
|
|
2778
|
+
this.#map ||= new Map;
|
|
2779
|
+
let item = this.#map.get(key);
|
|
2780
|
+
if (item) {
|
|
2781
|
+
this.#map.delete(key);
|
|
2782
|
+
this.#map.set(key, item);
|
|
2783
|
+
}
|
|
2784
|
+
return item;
|
|
2785
|
+
}
|
|
2786
|
+
#set(key, val) {
|
|
2787
|
+
this.#map ||= new Map;
|
|
2788
|
+
this.#map.delete(key);
|
|
2789
|
+
if (this.#map.size === 100) {
|
|
2790
|
+
this.#map.delete(this.#map.keys().next().value);
|
|
2791
|
+
}
|
|
2792
|
+
this.#map.set(key, val);
|
|
2793
|
+
}
|
|
2794
|
+
async calculateThumbprint() {
|
|
2795
|
+
if (!this.#jkt) {
|
|
2796
|
+
const jwk = await crypto.subtle.exportKey("jwk", this.#publicKey);
|
|
2797
|
+
this.#jkt ||= await calculateJwkThumbprint(jwk);
|
|
2798
|
+
}
|
|
2799
|
+
return this.#jkt;
|
|
2800
|
+
}
|
|
2801
|
+
async addProof(url, headers, htm, accessToken) {
|
|
2802
|
+
const alg = keyToJws(this.#privateKey);
|
|
2803
|
+
this.#header ||= {
|
|
2804
|
+
alg,
|
|
2805
|
+
typ: "dpop+jwt",
|
|
2806
|
+
jwk: await publicJwk(this.#publicKey, alg)
|
|
2807
|
+
};
|
|
2808
|
+
const nonce = this.#get(url.origin);
|
|
2809
|
+
const now = epochTime() + this.#clockSkew;
|
|
2810
|
+
const payload = {
|
|
2811
|
+
iat: now,
|
|
2812
|
+
jti: randomBytes2(),
|
|
2813
|
+
htm,
|
|
2814
|
+
nonce,
|
|
2815
|
+
htu: `${url.origin}${url.pathname}`,
|
|
2816
|
+
ath: accessToken ? b64u(await crypto.subtle.digest("SHA-256", buf(accessToken))) : undefined
|
|
2817
|
+
};
|
|
2818
|
+
this.#modifyAssertion?.(this.#header, payload);
|
|
2819
|
+
headers.set("dpop", await signJwt(this.#header, payload, this.#privateKey));
|
|
2820
|
+
}
|
|
2821
|
+
cacheNonce(response, url) {
|
|
2822
|
+
try {
|
|
2823
|
+
const nonce = response.headers.get("dpop-nonce");
|
|
2824
|
+
if (nonce) {
|
|
2825
|
+
this.#set(url.origin, nonce);
|
|
2826
|
+
}
|
|
2827
|
+
} catch {}
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
var tokenMatch = "[a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+";
|
|
2831
|
+
var token68Match = "[a-zA-Z0-9\\-\\._\\~\\+\\/]+={0,2}";
|
|
2832
|
+
var quotedMatch = '"((?:[^"\\\\]|\\\\[\\s\\S])*)"';
|
|
2833
|
+
var quotedParamMatcher = "(" + tokenMatch + ")\\s*=\\s*" + quotedMatch;
|
|
2834
|
+
var paramMatcher = "(" + tokenMatch + ")\\s*=\\s*(" + tokenMatch + ")";
|
|
2835
|
+
var schemeRE = new RegExp("^[,\\s]*(" + tokenMatch + ")");
|
|
2836
|
+
var quotedParamRE = new RegExp("^[,\\s]*" + quotedParamMatcher + "[,\\s]*(.*)");
|
|
2837
|
+
var unquotedParamRE = new RegExp("^[,\\s]*" + paramMatcher + "[,\\s]*(.*)");
|
|
2838
|
+
var token68ParamRE = new RegExp("^(" + token68Match + ")(?:$|[,\\s])(.*)");
|
|
2839
|
+
var jwksMap;
|
|
2840
|
+
function setJwksCache(as, jwks, uat, cache) {
|
|
2841
|
+
jwksMap ||= new WeakMap;
|
|
2842
|
+
jwksMap.set(as, {
|
|
2843
|
+
jwks,
|
|
2844
|
+
uat,
|
|
2845
|
+
get age() {
|
|
2846
|
+
return epochTime() - this.uat;
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
if (cache) {
|
|
2850
|
+
Object.assign(cache, { jwks: structuredClone(jwks), uat });
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
function isFreshJwksCache(input) {
|
|
2854
|
+
if (typeof input !== "object" || input === null) {
|
|
2855
|
+
return false;
|
|
2856
|
+
}
|
|
2857
|
+
if (!("uat" in input) || typeof input.uat !== "number" || epochTime() - input.uat >= 300) {
|
|
2858
|
+
return false;
|
|
2859
|
+
}
|
|
2860
|
+
if (!("jwks" in input) || !isJsonObject(input.jwks) || !Array.isArray(input.jwks.keys) || !Array.prototype.every.call(input.jwks.keys, isJsonObject)) {
|
|
2861
|
+
return false;
|
|
2862
|
+
}
|
|
2863
|
+
return true;
|
|
2864
|
+
}
|
|
2865
|
+
function clearJwksCache(as, cache) {
|
|
2866
|
+
jwksMap?.delete(as);
|
|
2867
|
+
delete cache?.jwks;
|
|
2868
|
+
delete cache?.uat;
|
|
2869
|
+
}
|
|
2870
|
+
async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
|
|
2871
|
+
const { alg, kid } = header;
|
|
2872
|
+
checkSupportedJwsAlg(header);
|
|
2873
|
+
if (!jwksMap?.has(as) && isFreshJwksCache(options?.[jwksCache])) {
|
|
2874
|
+
setJwksCache(as, options?.[jwksCache].jwks, options?.[jwksCache].uat);
|
|
2875
|
+
}
|
|
2876
|
+
let jwks;
|
|
2877
|
+
let age;
|
|
2878
|
+
if (jwksMap?.has(as)) {
|
|
2879
|
+
({ jwks, age } = jwksMap.get(as));
|
|
2880
|
+
if (age >= 300) {
|
|
2881
|
+
clearJwksCache(as, options?.[jwksCache]);
|
|
2882
|
+
return getPublicSigKeyFromIssuerJwksUri(as, options, header);
|
|
2883
|
+
}
|
|
2884
|
+
} else {
|
|
2885
|
+
jwks = await jwksRequest(as, options).then(processJwksResponse);
|
|
2886
|
+
age = 0;
|
|
2887
|
+
setJwksCache(as, jwks, epochTime(), options?.[jwksCache]);
|
|
2888
|
+
}
|
|
2889
|
+
let kty;
|
|
2890
|
+
switch (alg.slice(0, 2)) {
|
|
2891
|
+
case "RS":
|
|
2892
|
+
case "PS":
|
|
2893
|
+
kty = "RSA";
|
|
2894
|
+
break;
|
|
2895
|
+
case "ES":
|
|
2896
|
+
kty = "EC";
|
|
2897
|
+
break;
|
|
2898
|
+
case "Ed":
|
|
2899
|
+
kty = "OKP";
|
|
2900
|
+
break;
|
|
2901
|
+
case "ML":
|
|
2902
|
+
kty = "AKP";
|
|
2903
|
+
break;
|
|
2904
|
+
default:
|
|
2905
|
+
throw new UnsupportedOperationError("unsupported JWS algorithm", { cause: { alg } });
|
|
2906
|
+
}
|
|
2907
|
+
const candidates = jwks.keys.filter((jwk2) => {
|
|
2908
|
+
if (jwk2.kty !== kty) {
|
|
2909
|
+
return false;
|
|
2910
|
+
}
|
|
2911
|
+
if (kid !== undefined && kid !== jwk2.kid) {
|
|
2912
|
+
return false;
|
|
2913
|
+
}
|
|
2914
|
+
if (jwk2.alg !== undefined && alg !== jwk2.alg) {
|
|
2915
|
+
return false;
|
|
2916
|
+
}
|
|
2917
|
+
if (jwk2.use !== undefined && jwk2.use !== "sig") {
|
|
2918
|
+
return false;
|
|
2919
|
+
}
|
|
2920
|
+
if (jwk2.key_ops?.includes("verify") === false) {
|
|
2921
|
+
return false;
|
|
2922
|
+
}
|
|
2923
|
+
switch (true) {
|
|
2924
|
+
case (alg === "ES256" && jwk2.crv !== "P-256"):
|
|
2925
|
+
case (alg === "ES384" && jwk2.crv !== "P-384"):
|
|
2926
|
+
case (alg === "ES512" && jwk2.crv !== "P-521"):
|
|
2927
|
+
case (alg === "Ed25519" && jwk2.crv !== "Ed25519"):
|
|
2928
|
+
case (alg === "EdDSA" && jwk2.crv !== "Ed25519"):
|
|
2929
|
+
return false;
|
|
2930
|
+
}
|
|
2931
|
+
return true;
|
|
2932
|
+
});
|
|
2933
|
+
const { 0: jwk, length } = candidates;
|
|
2934
|
+
if (!length) {
|
|
2935
|
+
if (age >= 60) {
|
|
2936
|
+
clearJwksCache(as, options?.[jwksCache]);
|
|
2937
|
+
return getPublicSigKeyFromIssuerJwksUri(as, options, header);
|
|
2938
|
+
}
|
|
2939
|
+
throw OPE("error when selecting a JWT verification key, no applicable keys found", KEY_SELECTION, { header, candidates, jwks_uri: new URL(as.jwks_uri) });
|
|
2940
|
+
}
|
|
2941
|
+
if (length !== 1) {
|
|
2942
|
+
throw OPE('error when selecting a JWT verification key, multiple applicable keys found, a "kid" JWT Header Parameter is required', KEY_SELECTION, { header, candidates, jwks_uri: new URL(as.jwks_uri) });
|
|
2943
|
+
}
|
|
2944
|
+
return importJwk(alg, jwk);
|
|
2945
|
+
}
|
|
2946
|
+
var skipSubjectCheck = Symbol();
|
|
2947
|
+
function getContentType(input) {
|
|
2948
|
+
return input.headers.get("content-type")?.split(";")[0];
|
|
2949
|
+
}
|
|
2950
|
+
var idTokenClaims = new WeakMap;
|
|
2951
|
+
var jwtRefs = new WeakMap;
|
|
2952
|
+
function validateAudience(expected, result) {
|
|
2953
|
+
if (Array.isArray(result.claims.aud)) {
|
|
2954
|
+
if (!result.claims.aud.includes(expected)) {
|
|
2955
|
+
throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
|
|
2956
|
+
expected,
|
|
2957
|
+
claims: result.claims,
|
|
2958
|
+
claim: "aud"
|
|
2959
|
+
});
|
|
2960
|
+
}
|
|
2961
|
+
} else if (result.claims.aud !== expected) {
|
|
2962
|
+
throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
|
|
2963
|
+
expected,
|
|
2964
|
+
claims: result.claims,
|
|
2965
|
+
claim: "aud"
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
return result;
|
|
2969
|
+
}
|
|
2970
|
+
function validateIssuer(as, result) {
|
|
2971
|
+
const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
|
|
2972
|
+
if (result.claims.iss !== expected) {
|
|
2973
|
+
throw OPE('unexpected JWT "iss" (issuer) claim value', JWT_CLAIM_COMPARISON, {
|
|
2974
|
+
expected,
|
|
2975
|
+
claims: result.claims,
|
|
2976
|
+
claim: "iss"
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
return result;
|
|
2980
|
+
}
|
|
2981
|
+
var branded = new WeakSet;
|
|
2982
|
+
var nopkce = Symbol();
|
|
2983
|
+
var jwtClaimNames = {
|
|
2984
|
+
aud: "audience",
|
|
2985
|
+
c_hash: "code hash",
|
|
2986
|
+
client_id: "client id",
|
|
2987
|
+
exp: "expiration time",
|
|
2988
|
+
iat: "issued at",
|
|
2989
|
+
iss: "issuer",
|
|
2990
|
+
jti: "jwt id",
|
|
2991
|
+
nonce: "nonce",
|
|
2992
|
+
s_hash: "state hash",
|
|
2993
|
+
sub: "subject",
|
|
2994
|
+
ath: "access token hash",
|
|
2995
|
+
htm: "http method",
|
|
2996
|
+
htu: "http uri",
|
|
2997
|
+
cnf: "confirmation",
|
|
2998
|
+
auth_time: "authentication time"
|
|
2999
|
+
};
|
|
3000
|
+
function validatePresence(required, result) {
|
|
3001
|
+
for (const claim of required) {
|
|
3002
|
+
if (result.claims[claim] === undefined) {
|
|
3003
|
+
throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, {
|
|
3004
|
+
claims: result.claims
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
return result;
|
|
3009
|
+
}
|
|
3010
|
+
var expectNoNonce = Symbol();
|
|
3011
|
+
var skipAuthTimeCheck = Symbol();
|
|
3012
|
+
var UNSUPPORTED_OPERATION = "OAUTH_UNSUPPORTED_OPERATION";
|
|
3013
|
+
var PARSE_ERROR = "OAUTH_PARSE_ERROR";
|
|
3014
|
+
var INVALID_RESPONSE = "OAUTH_INVALID_RESPONSE";
|
|
3015
|
+
var INVALID_REQUEST = "OAUTH_INVALID_REQUEST";
|
|
3016
|
+
var RESPONSE_IS_NOT_JSON = "OAUTH_RESPONSE_IS_NOT_JSON";
|
|
3017
|
+
var RESPONSE_IS_NOT_CONFORM = "OAUTH_RESPONSE_IS_NOT_CONFORM";
|
|
3018
|
+
var HTTP_REQUEST_FORBIDDEN = "OAUTH_HTTP_REQUEST_FORBIDDEN";
|
|
3019
|
+
var REQUEST_PROTOCOL_FORBIDDEN = "OAUTH_REQUEST_PROTOCOL_FORBIDDEN";
|
|
3020
|
+
var JWT_TIMESTAMP_CHECK = "OAUTH_JWT_TIMESTAMP_CHECK_FAILED";
|
|
3021
|
+
var JWT_CLAIM_COMPARISON = "OAUTH_JWT_CLAIM_COMPARISON_FAILED";
|
|
3022
|
+
var JSON_ATTRIBUTE_COMPARISON = "OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED";
|
|
3023
|
+
var KEY_SELECTION = "OAUTH_KEY_SELECTION_FAILED";
|
|
3024
|
+
var MISSING_SERVER_METADATA = "OAUTH_MISSING_SERVER_METADATA";
|
|
3025
|
+
var INVALID_SERVER_METADATA = "OAUTH_INVALID_SERVER_METADATA";
|
|
3026
|
+
function checkJwtType(expected, result) {
|
|
3027
|
+
if (typeof result.header.typ !== "string" || normalizeTyp(result.header.typ) !== expected) {
|
|
3028
|
+
throw OPE('unexpected JWT "typ" header parameter value', INVALID_RESPONSE, {
|
|
3029
|
+
header: result.header
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
return result;
|
|
3033
|
+
}
|
|
3034
|
+
function assertReadableResponse(response) {
|
|
3035
|
+
if (response.bodyUsed) {
|
|
3036
|
+
throw CodedTypeError('"response" body has been used already', ERR_INVALID_ARG_VALUE);
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
async function jwksRequest(as, options) {
|
|
3040
|
+
assertAs(as);
|
|
3041
|
+
const url = resolveEndpoint(as, "jwks_uri", false, options?.[allowInsecureRequests] !== true);
|
|
3042
|
+
const headers = prepareHeaders(options?.headers);
|
|
3043
|
+
headers.set("accept", "application/json");
|
|
3044
|
+
headers.append("accept", "application/jwk-set+json");
|
|
3045
|
+
return (options?.[customFetch] || fetch)(url.href, {
|
|
3046
|
+
body: undefined,
|
|
3047
|
+
headers: Object.fromEntries(headers.entries()),
|
|
3048
|
+
method: "GET",
|
|
3049
|
+
redirect: "manual",
|
|
3050
|
+
signal: signal(url, options?.signal)
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
async function processJwksResponse(response) {
|
|
3054
|
+
if (!looseInstanceOf(response, Response)) {
|
|
3055
|
+
throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
|
|
3056
|
+
}
|
|
3057
|
+
if (response.status !== 200) {
|
|
3058
|
+
throw OPE('"response" is not a conform JSON Web Key Set response (unexpected HTTP status code)', RESPONSE_IS_NOT_CONFORM, response);
|
|
3059
|
+
}
|
|
3060
|
+
assertReadableResponse(response);
|
|
3061
|
+
const json = await getResponseJsonBody(response, (response2) => assertContentTypes(response2, "application/json", "application/jwk-set+json"));
|
|
3062
|
+
if (!Array.isArray(json.keys)) {
|
|
3063
|
+
throw OPE('"response" body "keys" property must be an array', INVALID_RESPONSE, { body: json });
|
|
3064
|
+
}
|
|
3065
|
+
if (!Array.prototype.every.call(json.keys, isJsonObject)) {
|
|
3066
|
+
throw OPE('"response" body "keys" property members must be JWK formatted objects', INVALID_RESPONSE, { body: json });
|
|
3067
|
+
}
|
|
3068
|
+
return json;
|
|
3069
|
+
}
|
|
3070
|
+
function supported(alg) {
|
|
3071
|
+
switch (alg) {
|
|
3072
|
+
case "PS256":
|
|
3073
|
+
case "ES256":
|
|
3074
|
+
case "RS256":
|
|
3075
|
+
case "PS384":
|
|
3076
|
+
case "ES384":
|
|
3077
|
+
case "RS384":
|
|
3078
|
+
case "PS512":
|
|
3079
|
+
case "ES512":
|
|
3080
|
+
case "RS512":
|
|
3081
|
+
case "Ed25519":
|
|
3082
|
+
case "EdDSA":
|
|
3083
|
+
case "ML-DSA-44":
|
|
3084
|
+
case "ML-DSA-65":
|
|
3085
|
+
case "ML-DSA-87":
|
|
3086
|
+
return true;
|
|
3087
|
+
default:
|
|
3088
|
+
return false;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
function checkSupportedJwsAlg(header) {
|
|
3092
|
+
if (!supported(header.alg)) {
|
|
3093
|
+
throw new UnsupportedOperationError('unsupported JWS "alg" identifier', {
|
|
3094
|
+
cause: { alg: header.alg }
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
function checkRsaKeyAlgorithm(key) {
|
|
3099
|
+
const { algorithm } = key;
|
|
3100
|
+
if (typeof algorithm.modulusLength !== "number" || algorithm.modulusLength < 2048) {
|
|
3101
|
+
throw new UnsupportedOperationError(`unsupported ${algorithm.name} modulusLength`, {
|
|
3102
|
+
cause: key
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
function ecdsaHashName(key) {
|
|
3107
|
+
const { algorithm } = key;
|
|
3108
|
+
switch (algorithm.namedCurve) {
|
|
3109
|
+
case "P-256":
|
|
3110
|
+
return "SHA-256";
|
|
3111
|
+
case "P-384":
|
|
3112
|
+
return "SHA-384";
|
|
3113
|
+
case "P-521":
|
|
3114
|
+
return "SHA-512";
|
|
3115
|
+
default:
|
|
3116
|
+
throw new UnsupportedOperationError("unsupported ECDSA namedCurve", { cause: key });
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
function keyToSubtle(key) {
|
|
3120
|
+
switch (key.algorithm.name) {
|
|
3121
|
+
case "ECDSA":
|
|
3122
|
+
return {
|
|
3123
|
+
name: key.algorithm.name,
|
|
3124
|
+
hash: ecdsaHashName(key)
|
|
3125
|
+
};
|
|
3126
|
+
case "RSA-PSS": {
|
|
3127
|
+
checkRsaKeyAlgorithm(key);
|
|
3128
|
+
switch (key.algorithm.hash.name) {
|
|
3129
|
+
case "SHA-256":
|
|
3130
|
+
case "SHA-384":
|
|
3131
|
+
case "SHA-512":
|
|
3132
|
+
return {
|
|
3133
|
+
name: key.algorithm.name,
|
|
3134
|
+
saltLength: parseInt(key.algorithm.hash.name.slice(-3), 10) >> 3
|
|
3135
|
+
};
|
|
3136
|
+
default:
|
|
3137
|
+
throw new UnsupportedOperationError("unsupported RSA-PSS hash name", { cause: key });
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
case "RSASSA-PKCS1-v1_5":
|
|
3141
|
+
checkRsaKeyAlgorithm(key);
|
|
3142
|
+
return key.algorithm.name;
|
|
3143
|
+
case "ML-DSA-44":
|
|
3144
|
+
case "ML-DSA-65":
|
|
3145
|
+
case "ML-DSA-87":
|
|
3146
|
+
case "Ed25519":
|
|
3147
|
+
return key.algorithm.name;
|
|
3148
|
+
}
|
|
3149
|
+
throw new UnsupportedOperationError("unsupported CryptoKey algorithm name", { cause: key });
|
|
3150
|
+
}
|
|
3151
|
+
async function validateJwsSignature(protectedHeader, payload, key, signature) {
|
|
3152
|
+
const data = buf(`${protectedHeader}.${payload}`);
|
|
3153
|
+
const algorithm = keyToSubtle(key);
|
|
3154
|
+
const verified = await crypto.subtle.verify(algorithm, key, signature, data);
|
|
3155
|
+
if (!verified) {
|
|
3156
|
+
throw OPE("JWT signature verification failed", INVALID_RESPONSE, {
|
|
3157
|
+
key,
|
|
3158
|
+
data,
|
|
3159
|
+
signature,
|
|
3160
|
+
algorithm
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
async function validateJwt(jws, checkAlg, clockSkew2, clockTolerance2, decryptJwt) {
|
|
3165
|
+
let { 0: protectedHeader, 1: payload, length } = jws.split(".");
|
|
3166
|
+
if (length === 5) {
|
|
3167
|
+
if (decryptJwt !== undefined) {
|
|
3168
|
+
jws = await decryptJwt(jws);
|
|
3169
|
+
({ 0: protectedHeader, 1: payload, length } = jws.split("."));
|
|
3170
|
+
} else {
|
|
3171
|
+
throw new UnsupportedOperationError("JWE decryption is not configured", { cause: jws });
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
if (length !== 3) {
|
|
3175
|
+
throw OPE("Invalid JWT", INVALID_RESPONSE, jws);
|
|
3176
|
+
}
|
|
3177
|
+
let header;
|
|
3178
|
+
try {
|
|
3179
|
+
header = JSON.parse(buf(b64u(protectedHeader)));
|
|
3180
|
+
} catch (cause) {
|
|
3181
|
+
throw OPE("failed to parse JWT Header body as base64url encoded JSON", PARSE_ERROR, cause);
|
|
3182
|
+
}
|
|
3183
|
+
if (!isJsonObject(header)) {
|
|
3184
|
+
throw OPE("JWT Header must be a top level object", INVALID_RESPONSE, jws);
|
|
3185
|
+
}
|
|
3186
|
+
checkAlg(header);
|
|
3187
|
+
if (header.crit !== undefined) {
|
|
3188
|
+
throw new UnsupportedOperationError('no JWT "crit" header parameter extensions are supported', {
|
|
3189
|
+
cause: { header }
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3192
|
+
let claims;
|
|
3193
|
+
try {
|
|
3194
|
+
claims = JSON.parse(buf(b64u(payload)));
|
|
3195
|
+
} catch (cause) {
|
|
3196
|
+
throw OPE("failed to parse JWT Payload body as base64url encoded JSON", PARSE_ERROR, cause);
|
|
3197
|
+
}
|
|
3198
|
+
if (!isJsonObject(claims)) {
|
|
3199
|
+
throw OPE("JWT Payload must be a top level object", INVALID_RESPONSE, jws);
|
|
3200
|
+
}
|
|
3201
|
+
const now = epochTime() + clockSkew2;
|
|
3202
|
+
if (claims.exp !== undefined) {
|
|
3203
|
+
if (typeof claims.exp !== "number") {
|
|
3204
|
+
throw OPE('unexpected JWT "exp" (expiration time) claim type', INVALID_RESPONSE, { claims });
|
|
3205
|
+
}
|
|
3206
|
+
if (claims.exp <= now - clockTolerance2) {
|
|
3207
|
+
throw OPE('unexpected JWT "exp" (expiration time) claim value, expiration is past current timestamp', JWT_TIMESTAMP_CHECK, { claims, now, tolerance: clockTolerance2, claim: "exp" });
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
if (claims.iat !== undefined) {
|
|
3211
|
+
if (typeof claims.iat !== "number") {
|
|
3212
|
+
throw OPE('unexpected JWT "iat" (issued at) claim type', INVALID_RESPONSE, { claims });
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
if (claims.iss !== undefined) {
|
|
3216
|
+
if (typeof claims.iss !== "string") {
|
|
3217
|
+
throw OPE('unexpected JWT "iss" (issuer) claim type', INVALID_RESPONSE, { claims });
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
if (claims.nbf !== undefined) {
|
|
3221
|
+
if (typeof claims.nbf !== "number") {
|
|
3222
|
+
throw OPE('unexpected JWT "nbf" (not before) claim type', INVALID_RESPONSE, { claims });
|
|
3223
|
+
}
|
|
3224
|
+
if (claims.nbf > now + clockTolerance2) {
|
|
3225
|
+
throw OPE('unexpected JWT "nbf" (not before) claim value', JWT_TIMESTAMP_CHECK, {
|
|
3226
|
+
claims,
|
|
3227
|
+
now,
|
|
3228
|
+
tolerance: clockTolerance2,
|
|
3229
|
+
claim: "nbf"
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
if (claims.aud !== undefined) {
|
|
3234
|
+
if (typeof claims.aud !== "string" && !Array.isArray(claims.aud)) {
|
|
3235
|
+
throw OPE('unexpected JWT "aud" (audience) claim type', INVALID_RESPONSE, { claims });
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
return { header, claims, jwt: jws };
|
|
3239
|
+
}
|
|
3240
|
+
function checkSigningAlgorithm(client, issuer, fallback, header) {
|
|
3241
|
+
if (client !== undefined) {
|
|
3242
|
+
if (typeof client === "string" ? header.alg !== client : !client.includes(header.alg)) {
|
|
3243
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
3244
|
+
header,
|
|
3245
|
+
expected: client,
|
|
3246
|
+
reason: "client configuration"
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
if (Array.isArray(issuer)) {
|
|
3252
|
+
if (!issuer.includes(header.alg)) {
|
|
3253
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
3254
|
+
header,
|
|
3255
|
+
expected: issuer,
|
|
3256
|
+
reason: "authorization server metadata"
|
|
3257
|
+
});
|
|
3258
|
+
}
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
if (fallback !== undefined) {
|
|
3262
|
+
if (typeof fallback === "string" ? header.alg !== fallback : typeof fallback === "function" ? !fallback(header.alg) : !fallback.includes(header.alg)) {
|
|
3263
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
3264
|
+
header,
|
|
3265
|
+
expected: fallback,
|
|
3266
|
+
reason: "default value"
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
throw OPE('missing client or server configuration to verify used JWT "alg" header parameter', undefined, { client, issuer, fallback });
|
|
3272
|
+
}
|
|
3273
|
+
var skipStateCheck = Symbol();
|
|
3274
|
+
var expectNoState = Symbol();
|
|
3275
|
+
function algToSubtle(alg) {
|
|
3276
|
+
switch (alg) {
|
|
3277
|
+
case "PS256":
|
|
3278
|
+
case "PS384":
|
|
3279
|
+
case "PS512":
|
|
3280
|
+
return { name: "RSA-PSS", hash: `SHA-${alg.slice(-3)}` };
|
|
3281
|
+
case "RS256":
|
|
3282
|
+
case "RS384":
|
|
3283
|
+
case "RS512":
|
|
3284
|
+
return { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${alg.slice(-3)}` };
|
|
3285
|
+
case "ES256":
|
|
3286
|
+
case "ES384":
|
|
3287
|
+
return { name: "ECDSA", namedCurve: `P-${alg.slice(-3)}` };
|
|
3288
|
+
case "ES512":
|
|
3289
|
+
return { name: "ECDSA", namedCurve: "P-521" };
|
|
3290
|
+
case "EdDSA":
|
|
3291
|
+
return "Ed25519";
|
|
3292
|
+
case "Ed25519":
|
|
3293
|
+
case "ML-DSA-44":
|
|
3294
|
+
case "ML-DSA-65":
|
|
3295
|
+
case "ML-DSA-87":
|
|
3296
|
+
return alg;
|
|
3297
|
+
default:
|
|
3298
|
+
throw new UnsupportedOperationError("unsupported JWS algorithm", { cause: { alg } });
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
async function importJwk(alg, jwk) {
|
|
3302
|
+
const { ext, key_ops, use, ...key } = jwk;
|
|
3303
|
+
return crypto.subtle.importKey("jwk", key, algToSubtle(alg), true, ["verify"]);
|
|
3304
|
+
}
|
|
3305
|
+
function normalizeHtu(htu) {
|
|
3306
|
+
const url = new URL(htu);
|
|
3307
|
+
url.search = "";
|
|
3308
|
+
url.hash = "";
|
|
3309
|
+
return url.href;
|
|
3310
|
+
}
|
|
3311
|
+
async function validateDPoP(request, accessToken, accessTokenClaims, options) {
|
|
3312
|
+
const headerValue = request.headers.get("dpop");
|
|
3313
|
+
if (headerValue === null) {
|
|
3314
|
+
throw OPE("operation indicated DPoP use but the request has no DPoP HTTP Header", INVALID_REQUEST, { headers: request.headers });
|
|
3315
|
+
}
|
|
3316
|
+
if (request.headers.get("authorization")?.toLowerCase().startsWith("dpop ") === false) {
|
|
3317
|
+
throw OPE(`operation indicated DPoP use but the request's Authorization HTTP Header scheme is not DPoP`, INVALID_REQUEST, { headers: request.headers });
|
|
3318
|
+
}
|
|
3319
|
+
if (typeof accessTokenClaims.cnf?.jkt !== "string") {
|
|
3320
|
+
throw OPE("operation indicated DPoP use but the JWT Access Token has no jkt confirmation claim", INVALID_REQUEST, { claims: accessTokenClaims });
|
|
3321
|
+
}
|
|
3322
|
+
const clockSkew2 = getClockSkew(options);
|
|
3323
|
+
const proof = await validateJwt(headerValue, checkSigningAlgorithm.bind(undefined, options?.signingAlgorithms, undefined, supported), clockSkew2, getClockTolerance(options), undefined).then(checkJwtType.bind(undefined, "dpop+jwt")).then(validatePresence.bind(undefined, ["iat", "jti", "ath", "htm", "htu"]));
|
|
3324
|
+
const now = epochTime() + clockSkew2;
|
|
3325
|
+
const diff = Math.abs(now - proof.claims.iat);
|
|
3326
|
+
if (diff > 300) {
|
|
3327
|
+
throw OPE("DPoP Proof iat is not recent enough", JWT_TIMESTAMP_CHECK, {
|
|
3328
|
+
now,
|
|
3329
|
+
claims: proof.claims,
|
|
3330
|
+
claim: "iat"
|
|
3331
|
+
});
|
|
3332
|
+
}
|
|
3333
|
+
if (proof.claims.htm !== request.method) {
|
|
3334
|
+
throw OPE("DPoP Proof htm mismatch", JWT_CLAIM_COMPARISON, {
|
|
3335
|
+
expected: request.method,
|
|
3336
|
+
claims: proof.claims,
|
|
3337
|
+
claim: "htm"
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
3340
|
+
if (typeof proof.claims.htu !== "string" || normalizeHtu(proof.claims.htu) !== normalizeHtu(request.url)) {
|
|
3341
|
+
throw OPE("DPoP Proof htu mismatch", JWT_CLAIM_COMPARISON, {
|
|
3342
|
+
expected: normalizeHtu(request.url),
|
|
3343
|
+
claims: proof.claims,
|
|
3344
|
+
claim: "htu"
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
{
|
|
3348
|
+
const expected = b64u(await crypto.subtle.digest("SHA-256", buf(accessToken)));
|
|
3349
|
+
if (proof.claims.ath !== expected) {
|
|
3350
|
+
throw OPE("DPoP Proof ath mismatch", JWT_CLAIM_COMPARISON, {
|
|
3351
|
+
expected,
|
|
3352
|
+
claims: proof.claims,
|
|
3353
|
+
claim: "ath"
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
{
|
|
3358
|
+
const expected = await calculateJwkThumbprint(proof.header.jwk);
|
|
3359
|
+
if (accessTokenClaims.cnf.jkt !== expected) {
|
|
3360
|
+
throw OPE("JWT Access Token confirmation mismatch", JWT_CLAIM_COMPARISON, {
|
|
3361
|
+
expected,
|
|
3362
|
+
claims: accessTokenClaims,
|
|
3363
|
+
claim: "cnf.jkt"
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
const { 0: protectedHeader, 1: payload, 2: encodedSignature } = headerValue.split(".");
|
|
3368
|
+
const signature = b64u(encodedSignature);
|
|
3369
|
+
const { jwk, alg } = proof.header;
|
|
3370
|
+
if (!jwk) {
|
|
3371
|
+
throw OPE("DPoP Proof is missing the jwk header parameter", INVALID_REQUEST, {
|
|
3372
|
+
header: proof.header
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
const key = await importJwk(alg, jwk);
|
|
3376
|
+
if (key.type !== "public") {
|
|
3377
|
+
throw OPE("DPoP Proof jwk header parameter must contain a public key", INVALID_REQUEST, {
|
|
3378
|
+
header: proof.header
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
await validateJwsSignature(protectedHeader, payload, key, signature);
|
|
3382
|
+
}
|
|
3383
|
+
async function validateJwtAccessToken(as, request, expectedAudience, options) {
|
|
3384
|
+
assertAs(as);
|
|
3385
|
+
if (!looseInstanceOf(request, Request)) {
|
|
3386
|
+
throw CodedTypeError('"request" must be an instance of Request', ERR_INVALID_ARG_TYPE);
|
|
3387
|
+
}
|
|
3388
|
+
assertString(expectedAudience, '"expectedAudience"');
|
|
3389
|
+
const authorization = request.headers.get("authorization");
|
|
3390
|
+
if (authorization === null) {
|
|
3391
|
+
throw OPE('"request" is missing an Authorization HTTP Header', INVALID_REQUEST, {
|
|
3392
|
+
headers: request.headers
|
|
3393
|
+
});
|
|
3394
|
+
}
|
|
3395
|
+
let { 0: scheme, 1: accessToken, length } = authorization.split(" ");
|
|
3396
|
+
scheme = scheme.toLowerCase();
|
|
3397
|
+
switch (scheme) {
|
|
3398
|
+
case "dpop":
|
|
3399
|
+
case "bearer":
|
|
3400
|
+
break;
|
|
3401
|
+
default:
|
|
3402
|
+
throw new UnsupportedOperationError("unsupported Authorization HTTP Header scheme", {
|
|
3403
|
+
cause: { headers: request.headers }
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
if (length !== 2) {
|
|
3407
|
+
throw OPE("invalid Authorization HTTP Header format", INVALID_REQUEST, {
|
|
3408
|
+
headers: request.headers
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
const requiredClaims = [
|
|
3412
|
+
"iss",
|
|
3413
|
+
"exp",
|
|
3414
|
+
"aud",
|
|
3415
|
+
"sub",
|
|
3416
|
+
"iat",
|
|
3417
|
+
"jti",
|
|
3418
|
+
"client_id"
|
|
3419
|
+
];
|
|
3420
|
+
if (options?.requireDPoP || scheme === "dpop" || request.headers.has("dpop")) {
|
|
3421
|
+
requiredClaims.push("cnf");
|
|
3422
|
+
}
|
|
3423
|
+
const { claims, header } = await validateJwt(accessToken, checkSigningAlgorithm.bind(undefined, options?.signingAlgorithms, undefined, supported), getClockSkew(options), getClockTolerance(options), undefined).then(checkJwtType.bind(undefined, "at+jwt")).then(validatePresence.bind(undefined, requiredClaims)).then(validateIssuer.bind(undefined, as)).then(validateAudience.bind(undefined, expectedAudience)).catch(reassignRSCode);
|
|
3424
|
+
for (const claim of ["client_id", "jti", "sub"]) {
|
|
3425
|
+
if (typeof claims[claim] !== "string") {
|
|
3426
|
+
throw OPE(`unexpected JWT "${claim}" claim type`, INVALID_REQUEST, { claims });
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
if ("cnf" in claims) {
|
|
3430
|
+
if (!isJsonObject(claims.cnf)) {
|
|
3431
|
+
throw OPE('unexpected JWT "cnf" (confirmation) claim value', INVALID_REQUEST, { claims });
|
|
3432
|
+
}
|
|
3433
|
+
const { 0: cnf, length: length2 } = Object.keys(claims.cnf);
|
|
3434
|
+
if (length2) {
|
|
3435
|
+
if (length2 !== 1) {
|
|
3436
|
+
throw new UnsupportedOperationError("multiple confirmation claims are not supported", {
|
|
3437
|
+
cause: { claims }
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
if (cnf !== "jkt") {
|
|
3441
|
+
throw new UnsupportedOperationError("unsupported JWT Confirmation method", {
|
|
3442
|
+
cause: { claims }
|
|
3443
|
+
});
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
const { 0: protectedHeader, 1: payload, 2: encodedSignature } = accessToken.split(".");
|
|
3448
|
+
const signature = b64u(encodedSignature);
|
|
3449
|
+
const key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
|
|
3450
|
+
await validateJwsSignature(protectedHeader, payload, key, signature);
|
|
3451
|
+
if (options?.requireDPoP || scheme === "dpop" || claims.cnf?.jkt !== undefined || request.headers.has("dpop")) {
|
|
3452
|
+
await validateDPoP(request, accessToken, claims, options).catch(reassignRSCode);
|
|
3453
|
+
}
|
|
3454
|
+
return claims;
|
|
3455
|
+
}
|
|
3456
|
+
function reassignRSCode(err) {
|
|
3457
|
+
if (err instanceof OperationProcessingError && err?.code === INVALID_REQUEST) {
|
|
3458
|
+
err.code = INVALID_RESPONSE;
|
|
3459
|
+
}
|
|
3460
|
+
throw err;
|
|
3461
|
+
}
|
|
3462
|
+
async function getResponseJsonBody(response, check = assertApplicationJson) {
|
|
3463
|
+
let json;
|
|
3464
|
+
try {
|
|
3465
|
+
json = await response.json();
|
|
3466
|
+
} catch (cause) {
|
|
3467
|
+
check(response);
|
|
3468
|
+
throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
|
|
3469
|
+
}
|
|
3470
|
+
if (!isJsonObject(json)) {
|
|
3471
|
+
throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
|
|
3472
|
+
}
|
|
3473
|
+
return json;
|
|
3474
|
+
}
|
|
3475
|
+
var _nodiscoverycheck = Symbol();
|
|
3476
|
+
var _expectedIssuer = Symbol();
|
|
3477
|
+
|
|
3478
|
+
// src/http/jwt.ts
|
|
3479
|
+
function jwtAuthenticate(options) {
|
|
3480
|
+
const principalClaim = options.principalClaim ?? "sub";
|
|
3481
|
+
const domain = options.domain ?? "jwt";
|
|
3482
|
+
const audience = options.audience;
|
|
3483
|
+
let asPromise = null;
|
|
3484
|
+
async function getAuthorizationServer() {
|
|
3485
|
+
if (options.jwksUri) {
|
|
3486
|
+
return {
|
|
3487
|
+
issuer: options.issuer,
|
|
3488
|
+
jwks_uri: options.jwksUri
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3491
|
+
const issuerUrl = new URL(options.issuer);
|
|
3492
|
+
const response = await discoveryRequest(issuerUrl);
|
|
3493
|
+
return processDiscoveryResponse(issuerUrl, response);
|
|
3494
|
+
}
|
|
3495
|
+
return async function authenticate(request) {
|
|
3496
|
+
if (!asPromise) {
|
|
3497
|
+
asPromise = getAuthorizationServer();
|
|
3498
|
+
}
|
|
3499
|
+
let as;
|
|
3500
|
+
try {
|
|
3501
|
+
as = await asPromise;
|
|
3502
|
+
} catch (error) {
|
|
3503
|
+
asPromise = null;
|
|
3504
|
+
throw error;
|
|
3505
|
+
}
|
|
3506
|
+
const claims = await validateJwtAccessToken(as, request, audience);
|
|
3507
|
+
const principal = claims[principalClaim] ?? null;
|
|
3508
|
+
return new AuthContext(domain, true, principal, claims);
|
|
3509
|
+
};
|
|
3510
|
+
}
|
|
2150
3511
|
// src/protocol.ts
|
|
2151
3512
|
import { Schema as Schema7 } from "@query-farm/apache-arrow";
|
|
2152
3513
|
|
|
@@ -2341,8 +3702,11 @@ async function dispatchStream(method, params, writer, reader, serverId, requestI
|
|
|
2341
3702
|
if (expectedInputSchema && !isProducer && inputBatch.schema !== expectedInputSchema) {
|
|
2342
3703
|
try {
|
|
2343
3704
|
inputBatch = conformBatchToSchema(inputBatch, expectedInputSchema);
|
|
2344
|
-
} catch {
|
|
2345
|
-
|
|
3705
|
+
} catch (e) {
|
|
3706
|
+
if (e instanceof TypeError) {
|
|
3707
|
+
throw e;
|
|
3708
|
+
}
|
|
3709
|
+
console.debug?.(`Schema conformance skipped: ${e instanceof Error ? e.message : e}`);
|
|
2346
3710
|
}
|
|
2347
3711
|
}
|
|
2348
3712
|
const out = new OutputCollector(outputSchema, effectiveProducer, serverId, requestId);
|
|
@@ -2550,15 +3914,20 @@ export {
|
|
|
2550
3914
|
subprocessConnect,
|
|
2551
3915
|
str,
|
|
2552
3916
|
pipeConnect,
|
|
3917
|
+
parseResourceMetadataUrl,
|
|
2553
3918
|
parseDescribeResponse,
|
|
3919
|
+
oauthResourceMetadataToJson,
|
|
3920
|
+
jwtAuthenticate,
|
|
2554
3921
|
jsonStateSerializer,
|
|
2555
3922
|
int32,
|
|
2556
3923
|
int,
|
|
2557
3924
|
inferParamTypes,
|
|
3925
|
+
httpOAuthMetadata,
|
|
2558
3926
|
httpIntrospect,
|
|
2559
3927
|
httpConnect,
|
|
2560
3928
|
float32,
|
|
2561
3929
|
float,
|
|
3930
|
+
fetchOAuthMetadata,
|
|
2562
3931
|
createHttpHandler,
|
|
2563
3932
|
bytes,
|
|
2564
3933
|
bool,
|
|
@@ -2583,7 +3952,8 @@ export {
|
|
|
2583
3952
|
DESCRIBE_VERSION_KEY,
|
|
2584
3953
|
DESCRIBE_VERSION,
|
|
2585
3954
|
DESCRIBE_METHOD_NAME,
|
|
3955
|
+
AuthContext,
|
|
2586
3956
|
ARROW_CONTENT_TYPE
|
|
2587
3957
|
};
|
|
2588
3958
|
|
|
2589
|
-
//# debugId=
|
|
3959
|
+
//# debugId=FCA96ECA4C5D644864756E2164756E21
|