@query-farm/vgi-rpc 0.3.3 → 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/common.d.ts.map +1 -1
- 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 +1479 -71
- package/dist/index.js.map +20 -15
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/util/conform.d.ts +5 -3
- package/dist/util/conform.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 -5
- package/src/http/auth.ts +47 -0
- package/src/http/common.ts +1 -6
- 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/util/conform.ts +68 -5
- 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";
|
|
@@ -66,17 +108,55 @@ var DESCRIBE_METHOD_NAME = "__describe__";
|
|
|
66
108
|
var STATE_KEY = "vgi_rpc.stream_state#b64";
|
|
67
109
|
|
|
68
110
|
// src/http/common.ts
|
|
69
|
-
import {
|
|
70
|
-
RecordBatchReader,
|
|
71
|
-
RecordBatchStreamWriter
|
|
72
|
-
} from "@query-farm/apache-arrow";
|
|
111
|
+
import { RecordBatchReader, RecordBatchStreamWriter } from "@query-farm/apache-arrow";
|
|
73
112
|
|
|
74
113
|
// src/util/conform.ts
|
|
75
|
-
import {
|
|
114
|
+
import {
|
|
115
|
+
makeData,
|
|
116
|
+
RecordBatch,
|
|
117
|
+
Struct,
|
|
118
|
+
Type,
|
|
119
|
+
vectorFromArray
|
|
120
|
+
} from "@query-farm/apache-arrow";
|
|
121
|
+
function needsValueCast(src, dst) {
|
|
122
|
+
if (src.typeId === dst.typeId)
|
|
123
|
+
return false;
|
|
124
|
+
if (src.constructor === dst.constructor)
|
|
125
|
+
return false;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
function isNumeric(t) {
|
|
129
|
+
return t.typeId === Type.Int || t.typeId === Type.Float;
|
|
130
|
+
}
|
|
76
131
|
function conformBatchToSchema(batch, schema) {
|
|
77
132
|
if (batch.numRows === 0)
|
|
78
133
|
return batch;
|
|
79
|
-
|
|
134
|
+
if (batch.schema.fields.length !== schema.fields.length) {
|
|
135
|
+
throw new TypeError(`Field count mismatch: expected ${schema.fields.length}, got ${batch.schema.fields.length}`);
|
|
136
|
+
}
|
|
137
|
+
for (let i = 0;i < schema.fields.length; i++) {
|
|
138
|
+
if (batch.schema.fields[i].name !== schema.fields[i].name) {
|
|
139
|
+
throw new TypeError(`Field name mismatch at index ${i}: expected '${schema.fields[i].name}', got '${batch.schema.fields[i].name}'`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const children = schema.fields.map((f, i) => {
|
|
143
|
+
const srcChild = batch.data.children[i];
|
|
144
|
+
const srcType = srcChild.type;
|
|
145
|
+
const dstType = f.type;
|
|
146
|
+
if (!needsValueCast(srcType, dstType)) {
|
|
147
|
+
return srcChild.clone(dstType);
|
|
148
|
+
}
|
|
149
|
+
if (isNumeric(srcType) && isNumeric(dstType)) {
|
|
150
|
+
const col = batch.getChildAt(i);
|
|
151
|
+
const values = [];
|
|
152
|
+
for (let r = 0;r < batch.numRows; r++) {
|
|
153
|
+
const v = col.get(r);
|
|
154
|
+
values.push(typeof v === "bigint" ? Number(v) : v);
|
|
155
|
+
}
|
|
156
|
+
return vectorFromArray(values, dstType).data[0];
|
|
157
|
+
}
|
|
158
|
+
return srcChild.clone(dstType);
|
|
159
|
+
});
|
|
80
160
|
const structType = new Struct(schema.fields);
|
|
81
161
|
const data = makeData({
|
|
82
162
|
type: structType,
|
|
@@ -142,30 +222,9 @@ import {
|
|
|
142
222
|
RecordBatchReader as RecordBatchReader3,
|
|
143
223
|
Struct as Struct2,
|
|
144
224
|
Utf8,
|
|
145
|
-
vectorFromArray
|
|
225
|
+
vectorFromArray as vectorFromArray2
|
|
146
226
|
} from "@query-farm/apache-arrow";
|
|
147
227
|
|
|
148
|
-
// src/errors.ts
|
|
149
|
-
class RpcError extends Error {
|
|
150
|
-
errorType;
|
|
151
|
-
errorMessage;
|
|
152
|
-
remoteTraceback;
|
|
153
|
-
constructor(errorType, errorMessage, remoteTraceback) {
|
|
154
|
-
super(`${errorType}: ${errorMessage}`);
|
|
155
|
-
this.errorType = errorType;
|
|
156
|
-
this.errorMessage = errorMessage;
|
|
157
|
-
this.remoteTraceback = remoteTraceback;
|
|
158
|
-
this.name = "RpcError";
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
class VersionError extends Error {
|
|
163
|
-
constructor(message) {
|
|
164
|
-
super(message);
|
|
165
|
-
this.name = "VersionError";
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
228
|
// src/wire/reader.ts
|
|
170
229
|
import { RecordBatchReader as RecordBatchReader2 } from "@query-farm/apache-arrow";
|
|
171
230
|
|
|
@@ -297,7 +356,7 @@ function buildRequestIpc(schema, params, method) {
|
|
|
297
356
|
}
|
|
298
357
|
const children = schema.fields.map((f) => {
|
|
299
358
|
const val = coerceForArrow(f.type, params[f.name]);
|
|
300
|
-
return
|
|
359
|
+
return vectorFromArray2([val], f.type).data[0];
|
|
301
360
|
});
|
|
302
361
|
const structType = new Struct2(schema.fields);
|
|
303
362
|
const data = makeData2({
|
|
@@ -451,18 +510,25 @@ async function httpIntrospect(baseUrl, options) {
|
|
|
451
510
|
const prefix = options?.prefix ?? "/vgi";
|
|
452
511
|
const emptySchema = new ArrowSchema([]);
|
|
453
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
|
+
}
|
|
454
517
|
const response = await fetch(`${baseUrl}${prefix}/${DESCRIBE_METHOD_NAME}`, {
|
|
455
518
|
method: "POST",
|
|
456
|
-
headers
|
|
519
|
+
headers,
|
|
457
520
|
body
|
|
458
521
|
});
|
|
522
|
+
if (response.status === 401) {
|
|
523
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
524
|
+
}
|
|
459
525
|
const responseBody = new Uint8Array(await response.arrayBuffer());
|
|
460
526
|
const { batches } = await readResponseBatches(responseBody);
|
|
461
527
|
return parseDescribeResponse(batches);
|
|
462
528
|
}
|
|
463
529
|
|
|
464
530
|
// src/client/stream.ts
|
|
465
|
-
import { Field, makeData as makeData3, RecordBatch as RecordBatch3, Schema, Struct as Struct3, vectorFromArray as
|
|
531
|
+
import { Field, makeData as makeData3, RecordBatch as RecordBatch3, Schema, Struct as Struct3, vectorFromArray as vectorFromArray3 } from "@query-farm/apache-arrow";
|
|
466
532
|
class HttpStreamSession {
|
|
467
533
|
_baseUrl;
|
|
468
534
|
_prefix;
|
|
@@ -477,6 +543,7 @@ class HttpStreamSession {
|
|
|
477
543
|
_compressionLevel;
|
|
478
544
|
_compressFn;
|
|
479
545
|
_decompressFn;
|
|
546
|
+
_authorization;
|
|
480
547
|
constructor(opts) {
|
|
481
548
|
this._baseUrl = opts.baseUrl;
|
|
482
549
|
this._prefix = opts.prefix;
|
|
@@ -491,6 +558,7 @@ class HttpStreamSession {
|
|
|
491
558
|
this._compressionLevel = opts.compressionLevel;
|
|
492
559
|
this._compressFn = opts.compressFn;
|
|
493
560
|
this._decompressFn = opts.decompressFn;
|
|
561
|
+
this._authorization = opts.authorization;
|
|
494
562
|
}
|
|
495
563
|
get header() {
|
|
496
564
|
return this._header;
|
|
@@ -503,6 +571,9 @@ class HttpStreamSession {
|
|
|
503
571
|
headers["Content-Encoding"] = "zstd";
|
|
504
572
|
headers["Accept-Encoding"] = "zstd";
|
|
505
573
|
}
|
|
574
|
+
if (this._authorization) {
|
|
575
|
+
headers.Authorization = this._authorization;
|
|
576
|
+
}
|
|
506
577
|
return headers;
|
|
507
578
|
}
|
|
508
579
|
_prepareBody(content) {
|
|
@@ -546,7 +617,7 @@ class HttpStreamSession {
|
|
|
546
617
|
const inputSchema = new Schema(fields);
|
|
547
618
|
const children = inputSchema.fields.map((f) => {
|
|
548
619
|
const values = input.map((row) => row[f.name]);
|
|
549
|
-
return
|
|
620
|
+
return vectorFromArray3(values, f.type).data[0];
|
|
550
621
|
});
|
|
551
622
|
const structType = new Struct3(inputSchema.fields);
|
|
552
623
|
const data = makeData3({
|
|
@@ -567,6 +638,9 @@ class HttpStreamSession {
|
|
|
567
638
|
headers: this._buildHeaders(),
|
|
568
639
|
body: this._prepareBody(body)
|
|
569
640
|
});
|
|
641
|
+
if (resp.status === 401) {
|
|
642
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
643
|
+
}
|
|
570
644
|
const responseBody = await this._readResponse(resp);
|
|
571
645
|
const { batches: responseBatches } = await readResponseBatches(responseBody);
|
|
572
646
|
let resultRows = [];
|
|
@@ -652,6 +726,9 @@ class HttpStreamSession {
|
|
|
652
726
|
headers: this._buildHeaders(),
|
|
653
727
|
body: this._prepareBody(body)
|
|
654
728
|
});
|
|
729
|
+
if (resp.status === 401) {
|
|
730
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
731
|
+
}
|
|
655
732
|
return this._readResponse(resp);
|
|
656
733
|
}
|
|
657
734
|
close() {}
|
|
@@ -662,6 +739,7 @@ function httpConnect(baseUrl, options) {
|
|
|
662
739
|
const prefix = (options?.prefix ?? "/vgi").replace(/\/+$/, "");
|
|
663
740
|
const onLog = options?.onLog;
|
|
664
741
|
const compressionLevel = options?.compressionLevel;
|
|
742
|
+
const authorization = options?.authorization;
|
|
665
743
|
let methodCache = null;
|
|
666
744
|
let compressFn;
|
|
667
745
|
let decompressFn;
|
|
@@ -684,6 +762,9 @@ function httpConnect(baseUrl, options) {
|
|
|
684
762
|
headers["Content-Encoding"] = "zstd";
|
|
685
763
|
headers["Accept-Encoding"] = "zstd";
|
|
686
764
|
}
|
|
765
|
+
if (authorization) {
|
|
766
|
+
headers.Authorization = authorization;
|
|
767
|
+
}
|
|
687
768
|
return headers;
|
|
688
769
|
}
|
|
689
770
|
function prepareBody(content) {
|
|
@@ -692,6 +773,11 @@ function httpConnect(baseUrl, options) {
|
|
|
692
773
|
}
|
|
693
774
|
return content;
|
|
694
775
|
}
|
|
776
|
+
function checkAuth(resp) {
|
|
777
|
+
if (resp.status === 401) {
|
|
778
|
+
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
779
|
+
}
|
|
780
|
+
}
|
|
695
781
|
async function readResponse(resp) {
|
|
696
782
|
let body = new Uint8Array(await resp.arrayBuffer());
|
|
697
783
|
if (resp.headers.get("Content-Encoding") === "zstd" && decompressFn) {
|
|
@@ -702,7 +788,7 @@ function httpConnect(baseUrl, options) {
|
|
|
702
788
|
async function ensureMethodCache() {
|
|
703
789
|
if (methodCache)
|
|
704
790
|
return methodCache;
|
|
705
|
-
const desc = await httpIntrospect(baseUrl, { prefix });
|
|
791
|
+
const desc = await httpIntrospect(baseUrl, { prefix, authorization });
|
|
706
792
|
methodCache = new Map(desc.methods.map((m) => [m.name, m]));
|
|
707
793
|
return methodCache;
|
|
708
794
|
}
|
|
@@ -721,6 +807,7 @@ function httpConnect(baseUrl, options) {
|
|
|
721
807
|
headers: buildHeaders(),
|
|
722
808
|
body: prepareBody(body)
|
|
723
809
|
});
|
|
810
|
+
checkAuth(resp);
|
|
724
811
|
const responseBody = await readResponse(resp);
|
|
725
812
|
const { batches } = await readResponseBatches(responseBody);
|
|
726
813
|
let resultBatch = null;
|
|
@@ -756,6 +843,7 @@ function httpConnect(baseUrl, options) {
|
|
|
756
843
|
headers: buildHeaders(),
|
|
757
844
|
body: prepareBody(body)
|
|
758
845
|
});
|
|
846
|
+
checkAuth(resp);
|
|
759
847
|
const responseBody = await readResponse(resp);
|
|
760
848
|
let header = null;
|
|
761
849
|
let stateToken = null;
|
|
@@ -861,7 +949,8 @@ function httpConnect(baseUrl, options) {
|
|
|
861
949
|
header,
|
|
862
950
|
compressionLevel,
|
|
863
951
|
compressFn,
|
|
864
|
-
decompressFn
|
|
952
|
+
decompressFn,
|
|
953
|
+
authorization
|
|
865
954
|
});
|
|
866
955
|
},
|
|
867
956
|
async describe() {
|
|
@@ -870,6 +959,53 @@ function httpConnect(baseUrl, options) {
|
|
|
870
959
|
close() {}
|
|
871
960
|
};
|
|
872
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
|
+
}
|
|
873
1009
|
// src/client/pipe.ts
|
|
874
1010
|
import {
|
|
875
1011
|
Field as Field2,
|
|
@@ -878,7 +1014,7 @@ import {
|
|
|
878
1014
|
RecordBatchStreamWriter as RecordBatchStreamWriter2,
|
|
879
1015
|
Schema as Schema2,
|
|
880
1016
|
Struct as Struct4,
|
|
881
|
-
vectorFromArray as
|
|
1017
|
+
vectorFromArray as vectorFromArray4
|
|
882
1018
|
} from "@query-farm/apache-arrow";
|
|
883
1019
|
class PipeIncrementalWriter {
|
|
884
1020
|
writer;
|
|
@@ -1001,7 +1137,7 @@ class PipeStreamSession {
|
|
|
1001
1137
|
}
|
|
1002
1138
|
const children = inputSchema.fields.map((f) => {
|
|
1003
1139
|
const values = input.map((row) => row[f.name]);
|
|
1004
|
-
return
|
|
1140
|
+
return vectorFromArray4(values, f.type).data[0];
|
|
1005
1141
|
});
|
|
1006
1142
|
const structType = new Struct4(inputSchema.fields);
|
|
1007
1143
|
const data = makeData4({
|
|
@@ -1303,6 +1439,35 @@ function subprocessConnect(cmd, options) {
|
|
|
1303
1439
|
};
|
|
1304
1440
|
return client;
|
|
1305
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
|
+
}
|
|
1306
1471
|
// src/http/handler.ts
|
|
1307
1472
|
import { randomBytes } from "node:crypto";
|
|
1308
1473
|
import { Schema as Schema5 } from "@query-farm/apache-arrow";
|
|
@@ -1317,7 +1482,7 @@ import {
|
|
|
1317
1482
|
makeData as makeData5,
|
|
1318
1483
|
RecordBatch as RecordBatch5,
|
|
1319
1484
|
Struct as Struct5,
|
|
1320
|
-
vectorFromArray as
|
|
1485
|
+
vectorFromArray as vectorFromArray5
|
|
1321
1486
|
} from "@query-farm/apache-arrow";
|
|
1322
1487
|
function coerceInt64(schema, values) {
|
|
1323
1488
|
const result = { ...values };
|
|
@@ -1356,7 +1521,7 @@ function buildResultBatch(schema, values, serverId, requestId) {
|
|
|
1356
1521
|
if (val instanceof Data) {
|
|
1357
1522
|
return val;
|
|
1358
1523
|
}
|
|
1359
|
-
const arr =
|
|
1524
|
+
const arr = vectorFromArray5([val], f.type);
|
|
1360
1525
|
return arr.data[0];
|
|
1361
1526
|
});
|
|
1362
1527
|
const structType = new Struct5(schema.fields);
|
|
@@ -1399,20 +1564,28 @@ function buildLogBatch(schema, level, message, extra, serverId, requestId) {
|
|
|
1399
1564
|
}
|
|
1400
1565
|
return buildEmptyBatch(schema, metadata);
|
|
1401
1566
|
}
|
|
1402
|
-
function
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
const
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
});
|
|
1414
|
-
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 });
|
|
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 });
|
|
1415
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));
|
|
1416
1589
|
const structType = new Struct5(schema.fields);
|
|
1417
1590
|
const data = makeData5({
|
|
1418
1591
|
type: structType,
|
|
@@ -1438,11 +1611,13 @@ class OutputCollector {
|
|
|
1438
1611
|
_outputSchema;
|
|
1439
1612
|
_serverId;
|
|
1440
1613
|
_requestId;
|
|
1441
|
-
|
|
1614
|
+
auth;
|
|
1615
|
+
constructor(outputSchema, producerMode = true, serverId = "", requestId = null, authContext) {
|
|
1442
1616
|
this._outputSchema = outputSchema;
|
|
1443
1617
|
this._producerMode = producerMode;
|
|
1444
1618
|
this._serverId = serverId;
|
|
1445
1619
|
this._requestId = requestId;
|
|
1620
|
+
this.auth = authContext ?? AuthContext.anonymous();
|
|
1446
1621
|
}
|
|
1447
1622
|
get outputSchema() {
|
|
1448
1623
|
return this._outputSchema;
|
|
@@ -1502,7 +1677,7 @@ import {
|
|
|
1502
1677
|
Schema as Schema3,
|
|
1503
1678
|
Struct as Struct6,
|
|
1504
1679
|
Utf8 as Utf82,
|
|
1505
|
-
vectorFromArray as
|
|
1680
|
+
vectorFromArray as vectorFromArray6
|
|
1506
1681
|
} from "@query-farm/apache-arrow";
|
|
1507
1682
|
|
|
1508
1683
|
// src/util/schema.ts
|
|
@@ -1566,16 +1741,16 @@ function buildDescribeBatch(protocolName, methods, serverId) {
|
|
|
1566
1741
|
hasHeaders.push(!!method.headerSchema);
|
|
1567
1742
|
headerSchemas.push(method.headerSchema ? serializeSchema(method.headerSchema) : null);
|
|
1568
1743
|
}
|
|
1569
|
-
const nameArr =
|
|
1570
|
-
const methodTypeArr =
|
|
1571
|
-
const docArr =
|
|
1572
|
-
const hasReturnArr =
|
|
1573
|
-
const paramsSchemaArr =
|
|
1574
|
-
const resultSchemaArr =
|
|
1575
|
-
const paramTypesArr =
|
|
1576
|
-
const paramDefaultsArr =
|
|
1577
|
-
const hasHeaderArr =
|
|
1578
|
-
const headerSchemaArr =
|
|
1744
|
+
const nameArr = vectorFromArray6(names, new Utf82);
|
|
1745
|
+
const methodTypeArr = vectorFromArray6(methodTypes, new Utf82);
|
|
1746
|
+
const docArr = vectorFromArray6(docs, new Utf82);
|
|
1747
|
+
const hasReturnArr = vectorFromArray6(hasReturns, new Bool2);
|
|
1748
|
+
const paramsSchemaArr = vectorFromArray6(paramsSchemas, new Binary2);
|
|
1749
|
+
const resultSchemaArr = vectorFromArray6(resultSchemas, new Binary2);
|
|
1750
|
+
const paramTypesArr = vectorFromArray6(paramTypesJsons, new Utf82);
|
|
1751
|
+
const paramDefaultsArr = vectorFromArray6(paramDefaultsJsons, new Utf82);
|
|
1752
|
+
const hasHeaderArr = vectorFromArray6(hasHeaders, new Bool2);
|
|
1753
|
+
const headerSchemaArr = vectorFromArray6(headerSchemas, new Binary2);
|
|
1579
1754
|
const children = [
|
|
1580
1755
|
nameArr.data[0],
|
|
1581
1756
|
methodTypeArr.data[0],
|
|
@@ -1745,7 +1920,7 @@ async function httpDispatchUnary(method, body, ctx) {
|
|
|
1745
1920
|
if (parsed.methodName !== method.name) {
|
|
1746
1921
|
throw new HttpRpcError(`Method name in request '${parsed.methodName}' does not match URL '${method.name}'`, 400);
|
|
1747
1922
|
}
|
|
1748
|
-
const out = new OutputCollector(schema, true, ctx.serverId, parsed.requestId);
|
|
1923
|
+
const out = new OutputCollector(schema, true, ctx.serverId, parsed.requestId, ctx.authContext);
|
|
1749
1924
|
try {
|
|
1750
1925
|
const result = await method.handler(parsed.params, out);
|
|
1751
1926
|
const resultBatch = buildResultBatch(schema, result, ctx.serverId, parsed.requestId);
|
|
@@ -1782,7 +1957,7 @@ async function httpDispatchStreamInit(method, body, ctx) {
|
|
|
1782
1957
|
let headerBytes = null;
|
|
1783
1958
|
if (method.headerSchema && method.headerInit) {
|
|
1784
1959
|
try {
|
|
1785
|
-
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);
|
|
1786
1961
|
const headerValues = method.headerInit(parsed.params, state, headerOut);
|
|
1787
1962
|
const headerBatch = buildResultBatch(method.headerSchema, headerValues, ctx.serverId, parsed.requestId);
|
|
1788
1963
|
const headerBatches = [...headerOut.batches.map((b) => b.batch), headerBatch];
|
|
@@ -1850,7 +2025,7 @@ async function httpDispatchStreamExchange(method, body, ctx) {
|
|
|
1850
2025
|
if (effectiveProducer) {
|
|
1851
2026
|
return produceStreamResponse(method, state, outputSchema, inputSchema, ctx, null, null);
|
|
1852
2027
|
} else {
|
|
1853
|
-
const out = new OutputCollector(outputSchema, effectiveProducer, ctx.serverId, null);
|
|
2028
|
+
const out = new OutputCollector(outputSchema, effectiveProducer, ctx.serverId, null, ctx.authContext);
|
|
1854
2029
|
const conformedBatch = conformBatchToSchema(reqBatch, inputSchema);
|
|
1855
2030
|
try {
|
|
1856
2031
|
if (method.exchangeFn) {
|
|
@@ -1900,7 +2075,7 @@ async function produceStreamResponse(method, state, outputSchema, inputSchema, c
|
|
|
1900
2075
|
const maxBytes = ctx.maxStreamResponseBytes;
|
|
1901
2076
|
let estimatedBytes = 0;
|
|
1902
2077
|
while (true) {
|
|
1903
|
-
const out = new OutputCollector(outputSchema, true, ctx.serverId, requestId);
|
|
2078
|
+
const out = new OutputCollector(outputSchema, true, ctx.serverId, requestId, ctx.authContext);
|
|
1904
2079
|
try {
|
|
1905
2080
|
if (method.producerFn) {
|
|
1906
2081
|
await method.producerFn(state, out);
|
|
@@ -1976,10 +2151,12 @@ function createHttpHandler(protocol, options) {
|
|
|
1976
2151
|
const maxRequestBytes = options?.maxRequestBytes;
|
|
1977
2152
|
const maxStreamResponseBytes = options?.maxStreamResponseBytes;
|
|
1978
2153
|
const serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
2154
|
+
const authenticate = options?.authenticate;
|
|
2155
|
+
const oauthMetadata = options?.oauthResourceMetadata;
|
|
1979
2156
|
const methods = protocol.getMethods();
|
|
1980
2157
|
const compressionLevel = options?.compressionLevel;
|
|
1981
2158
|
const stateSerializer = options?.stateSerializer ?? jsonStateSerializer;
|
|
1982
|
-
const
|
|
2159
|
+
const baseCtx = {
|
|
1983
2160
|
signingKey,
|
|
1984
2161
|
tokenTtl,
|
|
1985
2162
|
serverId,
|
|
@@ -2015,6 +2192,18 @@ function createHttpHandler(protocol, options) {
|
|
|
2015
2192
|
return async function handler(request) {
|
|
2016
2193
|
const url = new URL(request.url);
|
|
2017
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
|
+
}
|
|
2018
2207
|
if (request.method === "OPTIONS") {
|
|
2019
2208
|
if (path === `${prefix}/__capabilities__`) {
|
|
2020
2209
|
const headers = new Headers;
|
|
@@ -2050,6 +2239,22 @@ function createHttpHandler(protocol, options) {
|
|
|
2050
2239
|
if (contentEncoding === "zstd") {
|
|
2051
2240
|
body = zstdDecompress(body);
|
|
2052
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
|
+
}
|
|
2053
2258
|
if (path === `${prefix}/${DESCRIBE_METHOD_NAME}`) {
|
|
2054
2259
|
try {
|
|
2055
2260
|
const response = httpDispatchDescribe(protocol.name, methods, serverId);
|
|
@@ -2109,6 +2314,1200 @@ function createHttpHandler(protocol, options) {
|
|
|
2109
2314
|
}
|
|
2110
2315
|
};
|
|
2111
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
|
+
}
|
|
2112
3511
|
// src/protocol.ts
|
|
2113
3512
|
import { Schema as Schema7 } from "@query-farm/apache-arrow";
|
|
2114
3513
|
|
|
@@ -2303,8 +3702,11 @@ async function dispatchStream(method, params, writer, reader, serverId, requestI
|
|
|
2303
3702
|
if (expectedInputSchema && !isProducer && inputBatch.schema !== expectedInputSchema) {
|
|
2304
3703
|
try {
|
|
2305
3704
|
inputBatch = conformBatchToSchema(inputBatch, expectedInputSchema);
|
|
2306
|
-
} catch {
|
|
2307
|
-
|
|
3705
|
+
} catch (e) {
|
|
3706
|
+
if (e instanceof TypeError) {
|
|
3707
|
+
throw e;
|
|
3708
|
+
}
|
|
3709
|
+
console.debug?.(`Schema conformance skipped: ${e instanceof Error ? e.message : e}`);
|
|
2308
3710
|
}
|
|
2309
3711
|
}
|
|
2310
3712
|
const out = new OutputCollector(outputSchema, effectiveProducer, serverId, requestId);
|
|
@@ -2512,15 +3914,20 @@ export {
|
|
|
2512
3914
|
subprocessConnect,
|
|
2513
3915
|
str,
|
|
2514
3916
|
pipeConnect,
|
|
3917
|
+
parseResourceMetadataUrl,
|
|
2515
3918
|
parseDescribeResponse,
|
|
3919
|
+
oauthResourceMetadataToJson,
|
|
3920
|
+
jwtAuthenticate,
|
|
2516
3921
|
jsonStateSerializer,
|
|
2517
3922
|
int32,
|
|
2518
3923
|
int,
|
|
2519
3924
|
inferParamTypes,
|
|
3925
|
+
httpOAuthMetadata,
|
|
2520
3926
|
httpIntrospect,
|
|
2521
3927
|
httpConnect,
|
|
2522
3928
|
float32,
|
|
2523
3929
|
float,
|
|
3930
|
+
fetchOAuthMetadata,
|
|
2524
3931
|
createHttpHandler,
|
|
2525
3932
|
bytes,
|
|
2526
3933
|
bool,
|
|
@@ -2545,7 +3952,8 @@ export {
|
|
|
2545
3952
|
DESCRIBE_VERSION_KEY,
|
|
2546
3953
|
DESCRIBE_VERSION,
|
|
2547
3954
|
DESCRIBE_METHOD_NAME,
|
|
3955
|
+
AuthContext,
|
|
2548
3956
|
ARROW_CONTENT_TYPE
|
|
2549
3957
|
};
|
|
2550
3958
|
|
|
2551
|
-
//# debugId=
|
|
3959
|
+
//# debugId=FCA96ECA4C5D644864756E2164756E21
|