@ariestools/cli 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/bin/aries.mjs +13510 -462
- package/dist/bin/daemons/dapp-server.mjs +4 -1
- package/dist/bin/daemons/datalake-dev.mjs +906 -451
- package/dist/node/datalake.d.ts +6 -1
- package/dist/node/datalake.mjs +28 -13
- package/package.json +7 -7
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import * as nc from "node:crypto";
|
|
5
|
-
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
6
4
|
import http from "http";
|
|
7
5
|
import https from "https";
|
|
8
6
|
import { gunzipSync } from "zlib";
|
|
9
|
-
import { createHash as createHash$
|
|
7
|
+
import { createHash as createHash$2, createHmac as createHmac$1, pbkdf2Sync, randomBytes as randomBytes$6 } from "crypto";
|
|
8
|
+
import * as nc from "node:crypto";
|
|
9
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
10
10
|
import os, { cpus } from "node:os";
|
|
11
11
|
import process$1, { cwd } from "node:process";
|
|
12
12
|
import { Worker } from "node:worker_threads";
|
|
@@ -57,189 +57,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
57
57
|
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp$14({}, "__esModule", { value: true }), mod);
|
|
58
58
|
var __require$1 = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
59
59
|
//#endregion
|
|
60
|
-
//#region ../datalake-core/dist/node/index.mjs
|
|
61
|
-
var API_ERROR_STATUS$1 = {
|
|
62
|
-
bad_request: 400,
|
|
63
|
-
unauthorized: 401,
|
|
64
|
-
forbidden: 403,
|
|
65
|
-
not_found: 404,
|
|
66
|
-
conflict: 409,
|
|
67
|
-
unprocessable: 422,
|
|
68
|
-
rate_limited: 429,
|
|
69
|
-
internal: 500
|
|
70
|
-
};
|
|
71
|
-
function datalakeCollectionPath() {
|
|
72
|
-
return `/v1/datalakes`;
|
|
73
|
-
}
|
|
74
|
-
function datalakeResourcePath(idOrName) {
|
|
75
|
-
return `${datalakeCollectionPath()}/${encodeURIComponent(idOrName)}`;
|
|
76
|
-
}
|
|
77
|
-
function datalakeGrantsPath(idOrName) {
|
|
78
|
-
return `${datalakeResourcePath(idOrName)}/grants`;
|
|
79
|
-
}
|
|
80
|
-
function datalakeGrantPath(idOrName, principal) {
|
|
81
|
-
return `${datalakeGrantsPath(idOrName)}/${encodeURIComponent(principal)}`;
|
|
82
|
-
}
|
|
83
|
-
function datalakeTokensPath(idOrName) {
|
|
84
|
-
return `${datalakeResourcePath(idOrName)}/tokens`;
|
|
85
|
-
}
|
|
86
|
-
function datalakeHealthPath() {
|
|
87
|
-
return `/v1/health`;
|
|
88
|
-
}
|
|
89
|
-
var DATALAKE_HEADER_DUPLICATES = "x-datalake-duplicates";
|
|
90
|
-
var DATALAKE_HEADER_REJECTED = "x-datalake-rejected";
|
|
91
|
-
var DATALAKE_HEADER_NEXT_CURSOR = "x-datalake-next-cursor";
|
|
92
|
-
function base64UrlEncode(input) {
|
|
93
|
-
return (typeof input === "string" ? Buffer.from(input) : input).toString("base64url");
|
|
94
|
-
}
|
|
95
|
-
function base64UrlDecode(input) {
|
|
96
|
-
return Buffer.from(input, "base64url");
|
|
97
|
-
}
|
|
98
|
-
function signSessionToken(input) {
|
|
99
|
-
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
100
|
-
const expiresAt = issuedAt + input.ttlSeconds;
|
|
101
|
-
const payload = {
|
|
102
|
-
sub: input.address.toLowerCase(),
|
|
103
|
-
typ: "session",
|
|
104
|
-
iat: issuedAt,
|
|
105
|
-
exp: expiresAt,
|
|
106
|
-
jti: randomBytes(8).toString("hex")
|
|
107
|
-
};
|
|
108
|
-
const toSign = `${base64UrlEncode(JSON.stringify({
|
|
109
|
-
alg: "HS256",
|
|
110
|
-
typ: "JWT"
|
|
111
|
-
}))}.${base64UrlEncode(JSON.stringify(payload))}`;
|
|
112
|
-
return {
|
|
113
|
-
token: `${toSign}.${createHmac("sha256", input.signingSecret).update(toSign).digest("base64url")}`,
|
|
114
|
-
expiresAt: (/* @__PURE__ */ new Date(expiresAt * 1e3)).toISOString()
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
var SessionTokenVerificationError = class extends Error {
|
|
118
|
-
code;
|
|
119
|
-
constructor(code, message) {
|
|
120
|
-
super(message);
|
|
121
|
-
this.name = "SessionTokenVerificationError";
|
|
122
|
-
this.code = code;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
function verifySessionToken(input) {
|
|
126
|
-
const parts = input.token.split(".");
|
|
127
|
-
if (parts.length !== 3) throw new SessionTokenVerificationError("malformed", "Token is not a JWS compact serialization");
|
|
128
|
-
const [header, body, providedSignature] = parts;
|
|
129
|
-
const expected = createHmac("sha256", input.signingSecret).update(`${header}.${body}`).digest("base64url");
|
|
130
|
-
const expectedBytes = Buffer.from(expected);
|
|
131
|
-
const providedBytes = Buffer.from(providedSignature);
|
|
132
|
-
if (expectedBytes.length !== providedBytes.length || !timingSafeEqual(expectedBytes, providedBytes)) throw new SessionTokenVerificationError("bad_signature", "Signature mismatch");
|
|
133
|
-
let payload;
|
|
134
|
-
try {
|
|
135
|
-
payload = JSON.parse(base64UrlDecode(body).toString("utf8"));
|
|
136
|
-
} catch {
|
|
137
|
-
throw new SessionTokenVerificationError("malformed", "Payload is not valid JSON");
|
|
138
|
-
}
|
|
139
|
-
if (payload.typ !== "session") throw new SessionTokenVerificationError("wrong_type", "Not a session token");
|
|
140
|
-
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
141
|
-
if (payload.exp <= now) throw new SessionTokenVerificationError("expired", "Token has expired");
|
|
142
|
-
return payload;
|
|
143
|
-
}
|
|
144
|
-
function signChallenge(input) {
|
|
145
|
-
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
146
|
-
const expiresAt = issuedAt + input.ttlSeconds;
|
|
147
|
-
const payload = {
|
|
148
|
-
address: input.address.toLowerCase(),
|
|
149
|
-
typ: "challenge",
|
|
150
|
-
iat: issuedAt,
|
|
151
|
-
exp: expiresAt,
|
|
152
|
-
jti: randomBytes(8).toString("hex")
|
|
153
|
-
};
|
|
154
|
-
const toSign = `${base64UrlEncode(JSON.stringify({
|
|
155
|
-
alg: "HS256",
|
|
156
|
-
typ: "JWT"
|
|
157
|
-
}))}.${base64UrlEncode(JSON.stringify(payload))}`;
|
|
158
|
-
return {
|
|
159
|
-
challenge: `${toSign}.${createHmac("sha256", input.signingSecret).update(toSign).digest("base64url")}`,
|
|
160
|
-
expiresAt: (/* @__PURE__ */ new Date(expiresAt * 1e3)).toISOString()
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
function verifyChallenge(input) {
|
|
164
|
-
const parts = input.challenge.split(".");
|
|
165
|
-
if (parts.length !== 3) throw new SessionTokenVerificationError("malformed", "Challenge is not a JWS compact serialization");
|
|
166
|
-
const [header, body, providedSignature] = parts;
|
|
167
|
-
const expected = createHmac("sha256", input.signingSecret).update(`${header}.${body}`).digest("base64url");
|
|
168
|
-
const expectedBytes = Buffer.from(expected);
|
|
169
|
-
const providedBytes = Buffer.from(providedSignature);
|
|
170
|
-
if (expectedBytes.length !== providedBytes.length || !timingSafeEqual(expectedBytes, providedBytes)) throw new SessionTokenVerificationError("bad_signature", "Challenge signature mismatch");
|
|
171
|
-
let payload;
|
|
172
|
-
try {
|
|
173
|
-
payload = JSON.parse(base64UrlDecode(body).toString("utf8"));
|
|
174
|
-
} catch {
|
|
175
|
-
throw new SessionTokenVerificationError("malformed", "Challenge payload not valid JSON");
|
|
176
|
-
}
|
|
177
|
-
if (payload.typ !== "challenge") throw new SessionTokenVerificationError("wrong_type", "Not a challenge token");
|
|
178
|
-
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
179
|
-
if (payload.exp <= now) throw new SessionTokenVerificationError("expired", "Challenge has expired");
|
|
180
|
-
return payload;
|
|
181
|
-
}
|
|
182
|
-
function base64UrlEncode2(input) {
|
|
183
|
-
return (typeof input === "string" ? Buffer.from(input) : input).toString("base64url");
|
|
184
|
-
}
|
|
185
|
-
function base64UrlDecode2(input) {
|
|
186
|
-
return Buffer.from(input, "base64url");
|
|
187
|
-
}
|
|
188
|
-
function signToken(input) {
|
|
189
|
-
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
190
|
-
const expiresAt = issuedAt + input.ttlSeconds;
|
|
191
|
-
const payload = {
|
|
192
|
-
sub: input.ownerId,
|
|
193
|
-
dl: input.datalakeId,
|
|
194
|
-
role: input.role,
|
|
195
|
-
iat: issuedAt,
|
|
196
|
-
exp: expiresAt,
|
|
197
|
-
jti: randomBytes(8).toString("hex")
|
|
198
|
-
};
|
|
199
|
-
const toSign = `${base64UrlEncode2(JSON.stringify({
|
|
200
|
-
alg: "HS256",
|
|
201
|
-
typ: "JWT"
|
|
202
|
-
}))}.${base64UrlEncode2(JSON.stringify(payload))}`;
|
|
203
|
-
return {
|
|
204
|
-
token: `${toSign}.${createHmac("sha256", input.signingSecret).update(toSign).digest("base64url")}`,
|
|
205
|
-
datalakeId: input.datalakeId,
|
|
206
|
-
role: input.role,
|
|
207
|
-
expiresAt: (/* @__PURE__ */ new Date(expiresAt * 1e3)).toISOString(),
|
|
208
|
-
url: input.url
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
var TokenVerificationError = class extends Error {
|
|
212
|
-
code;
|
|
213
|
-
constructor(code, message) {
|
|
214
|
-
super(message);
|
|
215
|
-
this.name = "TokenVerificationError";
|
|
216
|
-
this.code = code;
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
function verifyToken(input) {
|
|
220
|
-
const parts = input.token.split(".");
|
|
221
|
-
if (parts.length !== 3) throw new TokenVerificationError("malformed", "Token is not a JWS compact serialization");
|
|
222
|
-
const [header, body, providedSignature] = parts;
|
|
223
|
-
const expected = createHmac("sha256", input.signingSecret).update(`${header}.${body}`).digest("base64url");
|
|
224
|
-
const expectedBytes = Buffer.from(expected);
|
|
225
|
-
const providedBytes = Buffer.from(providedSignature);
|
|
226
|
-
if (expectedBytes.length !== providedBytes.length || !timingSafeEqual(expectedBytes, providedBytes)) throw new TokenVerificationError("bad_signature", "Signature mismatch");
|
|
227
|
-
let payload;
|
|
228
|
-
try {
|
|
229
|
-
payload = JSON.parse(base64UrlDecode2(body).toString("utf8"));
|
|
230
|
-
} catch {
|
|
231
|
-
throw new TokenVerificationError("malformed", "Payload is not valid JSON");
|
|
232
|
-
}
|
|
233
|
-
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
234
|
-
if (payload.exp <= now) throw new TokenVerificationError("expired", "Token has expired");
|
|
235
|
-
if (payload.iat > now + 60) throw new TokenVerificationError("not_yet_valid", "Token issued in the future");
|
|
236
|
-
return payload;
|
|
237
|
-
}
|
|
238
|
-
var PUBLIC_PRINCIPAL = "public";
|
|
239
|
-
function isPublicPrincipal(principal) {
|
|
240
|
-
return principal === PUBLIC_PRINCIPAL;
|
|
241
|
-
}
|
|
242
|
-
//#endregion
|
|
243
60
|
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
|
|
244
61
|
var _a$1;
|
|
245
62
|
function $constructor(name, initializer, params) {
|
|
@@ -37171,10 +36988,10 @@ Object.freeze(scryptSync);
|
|
|
37171
36988
|
//#endregion
|
|
37172
36989
|
//#region ../../node_modules/.pnpm/ethers@6.17.0/node_modules/ethers/lib.esm/crypto/sha2.js
|
|
37173
36990
|
const _sha256 = function(data) {
|
|
37174
|
-
return createHash$
|
|
36991
|
+
return createHash$2("sha256").update(data).digest();
|
|
37175
36992
|
};
|
|
37176
36993
|
const _sha512 = function(data) {
|
|
37177
|
-
return createHash$
|
|
36994
|
+
return createHash$2("sha512").update(data).digest();
|
|
37178
36995
|
};
|
|
37179
36996
|
let __sha256 = _sha256;
|
|
37180
36997
|
let __sha512 = _sha512;
|
|
@@ -46561,7 +46378,7 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
46561
46378
|
const http$6 = __require$1("http");
|
|
46562
46379
|
const net = __require$1("net");
|
|
46563
46380
|
const tls = __require$1("tls");
|
|
46564
|
-
const { randomBytes: randomBytes$3, createHash: createHash$
|
|
46381
|
+
const { randomBytes: randomBytes$3, createHash: createHash$1 } = __require$1("crypto");
|
|
46565
46382
|
const { Duplex: Duplex$2, Readable: Readable$3 } = __require$1("stream");
|
|
46566
46383
|
const { URL: URL$2 } = __require$1("url");
|
|
46567
46384
|
const PerMessageDeflate = require_permessage_deflate();
|
|
@@ -47229,7 +47046,7 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
47229
47046
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
47230
47047
|
return;
|
|
47231
47048
|
}
|
|
47232
|
-
const digest = createHash$
|
|
47049
|
+
const digest = createHash$1("sha1").update(key + GUID).digest("base64");
|
|
47233
47050
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
47234
47051
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
47235
47052
|
return;
|
|
@@ -47703,7 +47520,7 @@ var require_websocket_server = /* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
47703
47520
|
const EventEmitter$11 = __require$1("events");
|
|
47704
47521
|
const http$5 = __require$1("http");
|
|
47705
47522
|
const { Duplex } = __require$1("stream");
|
|
47706
|
-
const { createHash
|
|
47523
|
+
const { createHash } = __require$1("crypto");
|
|
47707
47524
|
const extension = require_extension();
|
|
47708
47525
|
const PerMessageDeflate = require_permessage_deflate();
|
|
47709
47526
|
const subprotocol = require_subprotocol();
|
|
@@ -47972,7 +47789,7 @@ var require_websocket_server = /* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
47972
47789
|
"HTTP/1.1 101 Switching Protocols",
|
|
47973
47790
|
"Upgrade: websocket",
|
|
47974
47791
|
"Connection: Upgrade",
|
|
47975
|
-
`Sec-WebSocket-Accept: ${createHash
|
|
47792
|
+
`Sec-WebSocket-Accept: ${createHash("sha1").update(key + GUID).digest("base64")}`
|
|
47976
47793
|
];
|
|
47977
47794
|
const ws = new this.options.WebSocket(null, void 0, this.options);
|
|
47978
47795
|
if (protocols.size) {
|
|
@@ -78105,6 +77922,142 @@ var AdhocWitness = class extends AbstractWitness {
|
|
|
78105
77922
|
};
|
|
78106
77923
|
HttpBridge.factory(), ViewArchivist.factory(), ViewNode.factory(), AdhocWitness.factory(), GenericPayloadDiviner.factory(), MemoryBoundWitnessDiviner.factory(), IdentityDiviner.factory(), MemoryArchivist.factory(), MemoryArchivist.factory(), MemoryNode.factory(), MemorySentinel.factory(), GenericPayloadDiviner.factory();
|
|
78107
77924
|
//#endregion
|
|
77925
|
+
//#region ../jwt/dist/neutral/index.mjs
|
|
77926
|
+
var ADDRESS_PATTERN$1 = /^(?:0x)?[\da-fA-F]{40}$/;
|
|
77927
|
+
var PUBLIC_KEY_PATTERN = /^[\da-fA-F]{128}$/;
|
|
77928
|
+
var DEFAULT_MAX_LIFETIME_SECONDS = 300;
|
|
77929
|
+
var DEFAULT_FUTURE_SKEW_SECONDS = 60;
|
|
77930
|
+
async function verifyWalletJwtPolicy(token, options) {
|
|
77931
|
+
const reasons = [];
|
|
77932
|
+
let decoded;
|
|
77933
|
+
try {
|
|
77934
|
+
decoded = decodeJwt(token);
|
|
77935
|
+
} catch {
|
|
77936
|
+
return {
|
|
77937
|
+
ok: false,
|
|
77938
|
+
reasons: ["token is not a valid compact JWT"]
|
|
77939
|
+
};
|
|
77940
|
+
}
|
|
77941
|
+
const header = asRecord(decoded.header);
|
|
77942
|
+
const payload = asRecord(decoded.payload);
|
|
77943
|
+
if (header === void 0 || payload === void 0) return {
|
|
77944
|
+
ok: false,
|
|
77945
|
+
reasons: ["JWT header and payload must be JSON objects"]
|
|
77946
|
+
};
|
|
77947
|
+
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
77948
|
+
const maxLifetimeSeconds = options.maxLifetimeSeconds ?? DEFAULT_MAX_LIFETIME_SECONDS;
|
|
77949
|
+
const futureSkewSeconds = options.futureSkewSeconds ?? DEFAULT_FUTURE_SKEW_SECONDS;
|
|
77950
|
+
validateHeader(header, reasons);
|
|
77951
|
+
validatePayload(payload, options.audience, now, maxLifetimeSeconds, futureSkewSeconds, reasons);
|
|
77952
|
+
const scopes = validateProfile(payload, options.profile, reasons);
|
|
77953
|
+
await validateSignature(token, options.audience, now, reasons);
|
|
77954
|
+
const kid = typeof header.kid === "string" ? header.kid : "";
|
|
77955
|
+
const principal = resolvePrincipal(header, payload, reasons);
|
|
77956
|
+
const uniqueReasons = [...new Set(reasons)];
|
|
77957
|
+
if (uniqueReasons.length > 0 || principal === void 0) return {
|
|
77958
|
+
ok: false,
|
|
77959
|
+
reasons: uniqueReasons.length > 0 ? uniqueReasons : ["wallet principal is invalid"]
|
|
77960
|
+
};
|
|
77961
|
+
return {
|
|
77962
|
+
ok: true,
|
|
77963
|
+
header: {
|
|
77964
|
+
alg: JwtAlg.ES256K,
|
|
77965
|
+
typ: JwtTyp.JWT,
|
|
77966
|
+
kid,
|
|
77967
|
+
pub: header.pub
|
|
77968
|
+
},
|
|
77969
|
+
payload,
|
|
77970
|
+
principal,
|
|
77971
|
+
scopes
|
|
77972
|
+
};
|
|
77973
|
+
}
|
|
77974
|
+
async function validateSignature(token, audience, now, reasons) {
|
|
77975
|
+
try {
|
|
77976
|
+
const verified = await verifyJwt(token, {
|
|
77977
|
+
audience,
|
|
77978
|
+
now
|
|
77979
|
+
});
|
|
77980
|
+
if (!verified.ok) reasons.push(...verified.reasons);
|
|
77981
|
+
} catch {
|
|
77982
|
+
reasons.push("wallet JWT cryptographic verification failed");
|
|
77983
|
+
}
|
|
77984
|
+
}
|
|
77985
|
+
function resolvePrincipal(header, payload, reasons) {
|
|
77986
|
+
const kid = typeof header.kid === "string" ? header.kid : "";
|
|
77987
|
+
const issuer = typeof payload.iss === "string" ? payload.iss : "";
|
|
77988
|
+
if (!ADDRESS_PATTERN$1.test(kid) || !ADDRESS_PATTERN$1.test(issuer)) return void 0;
|
|
77989
|
+
try {
|
|
77990
|
+
const normalizedKid = normalizeAddress(kid);
|
|
77991
|
+
if (normalizedKid === normalizeAddress(issuer)) return normalizedKid;
|
|
77992
|
+
reasons.push("header.kid does not match payload.iss");
|
|
77993
|
+
} catch {
|
|
77994
|
+
reasons.push("kid or iss is not a valid XYO address");
|
|
77995
|
+
}
|
|
77996
|
+
}
|
|
77997
|
+
function validateHeader(header, reasons) {
|
|
77998
|
+
if (header.alg !== JwtAlg.ES256K) reasons.push("header.alg must be ES256K");
|
|
77999
|
+
if (header.typ !== JwtTyp.JWT) reasons.push("header.typ must be JWT");
|
|
78000
|
+
if (typeof header.kid !== "string" || !ADDRESS_PATTERN$1.test(header.kid)) reasons.push("header.kid must be a 20-byte hex address");
|
|
78001
|
+
if (typeof header.pub !== "string" || !PUBLIC_KEY_PATTERN.test(header.pub)) reasons.push("header.pub must be an embedded 64-byte secp256k1 public key");
|
|
78002
|
+
}
|
|
78003
|
+
function validatePayload(payload, audience, now, maxLifetimeSeconds, futureSkewSeconds, reasons) {
|
|
78004
|
+
if (typeof payload.iss !== "string" || !ADDRESS_PATTERN$1.test(payload.iss)) reasons.push("payload.iss must be a 20-byte hex address");
|
|
78005
|
+
if (payload.aud !== audience) reasons.push("payload.aud does not exactly match the required audience");
|
|
78006
|
+
if (payload.schema !== JwtSchema.Signin) reasons.push(`payload.schema must be ${JwtSchema.Signin}`);
|
|
78007
|
+
const iat = payload.iat;
|
|
78008
|
+
const exp = payload.exp;
|
|
78009
|
+
if (!isFiniteInteger$1(iat)) reasons.push("payload.iat must be a finite integer");
|
|
78010
|
+
if (!isFiniteInteger$1(exp)) reasons.push("payload.exp must be a finite integer");
|
|
78011
|
+
if (isFiniteInteger$1(iat) && isFiniteInteger$1(exp)) {
|
|
78012
|
+
if (exp <= iat) reasons.push("payload.exp must be greater than payload.iat");
|
|
78013
|
+
if (exp - iat > maxLifetimeSeconds) reasons.push(`wallet JWT lifetime exceeds ${maxLifetimeSeconds} seconds`);
|
|
78014
|
+
if (iat > now + futureSkewSeconds) reasons.push(`payload.iat is more than ${futureSkewSeconds} seconds in the future`);
|
|
78015
|
+
if (exp <= now) reasons.push("wallet JWT has expired");
|
|
78016
|
+
}
|
|
78017
|
+
if (payload.nbf !== void 0) {
|
|
78018
|
+
if (!isFiniteInteger$1(payload.nbf)) reasons.push("payload.nbf must be a finite integer");
|
|
78019
|
+
else if (payload.nbf > now) reasons.push("wallet JWT is not yet valid");
|
|
78020
|
+
}
|
|
78021
|
+
}
|
|
78022
|
+
function validateProfile(payload, profile, reasons) {
|
|
78023
|
+
if (profile.kind === "service") {
|
|
78024
|
+
if (payload.origin !== void 0 || payload.scope !== void 0) reasons.push("browser origin/scope claims are not accepted by the service JWT profile");
|
|
78025
|
+
return [];
|
|
78026
|
+
}
|
|
78027
|
+
const origin = profile.origin;
|
|
78028
|
+
if (origin === void 0 || origin.length === 0) reasons.push("browser request Origin is required");
|
|
78029
|
+
if (origin !== void 0 && !profile.allowedOrigins.includes(origin)) reasons.push("browser request Origin is not allowed");
|
|
78030
|
+
if (typeof payload.origin !== "string" || payload.origin.length === 0) reasons.push("payload.origin is required for browser JWTs");
|
|
78031
|
+
else {
|
|
78032
|
+
if (payload.origin !== origin) reasons.push("payload.origin does not match the request Origin");
|
|
78033
|
+
if (!profile.allowedOrigins.includes(payload.origin)) reasons.push("payload.origin is not allowed");
|
|
78034
|
+
}
|
|
78035
|
+
return parseCanonicalScope(payload.scope, profile.allowedScopes, reasons);
|
|
78036
|
+
}
|
|
78037
|
+
function parseCanonicalScope(value, allowedScopes, reasons) {
|
|
78038
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
78039
|
+
reasons.push("payload.scope is required for browser JWTs");
|
|
78040
|
+
return [];
|
|
78041
|
+
}
|
|
78042
|
+
const scopes = value.split(" ");
|
|
78043
|
+
if (scopes.some((scope) => scope.length === 0)) {
|
|
78044
|
+
reasons.push("payload.scope must use single-space separators with no surrounding whitespace");
|
|
78045
|
+
return [];
|
|
78046
|
+
}
|
|
78047
|
+
const canonical = [...new Set(scopes)].sort();
|
|
78048
|
+
if (canonical.length !== scopes.length || canonical.join(" ") !== value) reasons.push("payload.scope must be sorted, unique, and space-delimited");
|
|
78049
|
+
const allowed = new Set(allowedScopes);
|
|
78050
|
+
for (const scope of scopes) if (!allowed.has(scope)) reasons.push(`payload.scope contains unknown operation: ${scope}`);
|
|
78051
|
+
return scopes;
|
|
78052
|
+
}
|
|
78053
|
+
function isFiniteInteger$1(value) {
|
|
78054
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
|
|
78055
|
+
}
|
|
78056
|
+
function asRecord(value) {
|
|
78057
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
78058
|
+
return value;
|
|
78059
|
+
}
|
|
78060
|
+
//#endregion
|
|
78108
78061
|
//#region ../../node_modules/.pnpm/prom-client@15.1.3/node_modules/prom-client/lib/util.js
|
|
78109
78062
|
var require_util$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
78110
78063
|
exports.getValueAsString = function getValueString(value) {
|
|
@@ -80782,24 +80735,10 @@ var import_prom_client = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
80782
80735
|
exports.aggregators = require_metricAggregators().aggregators;
|
|
80783
80736
|
exports.AggregatorRegistry = require_cluster();
|
|
80784
80737
|
})))();
|
|
80785
|
-
var
|
|
80786
|
-
|
|
80787
|
-
|
|
80788
|
-
|
|
80789
|
-
}
|
|
80790
|
-
async resolve(token) {
|
|
80791
|
-
let alg;
|
|
80792
|
-
try {
|
|
80793
|
-
alg = decodeJwt(token).header.alg;
|
|
80794
|
-
} catch {
|
|
80795
|
-
return;
|
|
80796
|
-
}
|
|
80797
|
-
if (alg !== JwtAlg.ES256K) return void 0;
|
|
80798
|
-
const result = await verifyJwt(token, { audience: this.audience });
|
|
80799
|
-
if (!result.ok) return void 0;
|
|
80800
|
-
return normalizeAddress(result.payload.iss);
|
|
80801
|
-
}
|
|
80802
|
-
};
|
|
80738
|
+
var TOKEN_REJECTED = Object.freeze({ rejected: true });
|
|
80739
|
+
function isRejectedToken(value) {
|
|
80740
|
+
return typeof value === "object" && value !== null && "rejected" in value && value.rejected === true;
|
|
80741
|
+
}
|
|
80803
80742
|
var StaticTokenStore = class _StaticTokenStore {
|
|
80804
80743
|
tokens;
|
|
80805
80744
|
constructor(tokens) {
|
|
@@ -80825,14 +80764,62 @@ var ChainedTokenStore = class {
|
|
|
80825
80764
|
constructor(stores) {
|
|
80826
80765
|
this.stores = stores;
|
|
80827
80766
|
}
|
|
80828
|
-
async resolve(token) {
|
|
80767
|
+
async resolve(token, context) {
|
|
80829
80768
|
for (const store of this.stores) {
|
|
80830
|
-
const result = await store.resolve(token);
|
|
80769
|
+
const result = await store.resolve(token, context);
|
|
80770
|
+
if (isRejectedToken(result)) return result;
|
|
80831
80771
|
if (result !== void 0) return result;
|
|
80832
80772
|
}
|
|
80833
80773
|
}
|
|
80834
80774
|
};
|
|
80835
|
-
var
|
|
80775
|
+
var SelfSignedWalletJwtTokenStore = class {
|
|
80776
|
+
audience;
|
|
80777
|
+
options;
|
|
80778
|
+
constructor(audience, options = {}) {
|
|
80779
|
+
this.audience = audience;
|
|
80780
|
+
this.options = options;
|
|
80781
|
+
}
|
|
80782
|
+
async resolve(token, context = {}) {
|
|
80783
|
+
if (!isWalletJwtCandidate(token)) return void 0;
|
|
80784
|
+
const isBrowser = context.origin !== void 0 || hasBrowserClaims$1(token);
|
|
80785
|
+
const browser = this.options.browser;
|
|
80786
|
+
if (isBrowser && browser === void 0) return TOKEN_REJECTED;
|
|
80787
|
+
const isServiceProfileAllowed = this.options.isServiceProfileAllowed ?? browser === void 0;
|
|
80788
|
+
if (!isBrowser && !isServiceProfileAllowed) return TOKEN_REJECTED;
|
|
80789
|
+
const result = await verifyWalletJwtPolicy(token, {
|
|
80790
|
+
audience: this.audience,
|
|
80791
|
+
profile: isBrowser ? {
|
|
80792
|
+
kind: "browser",
|
|
80793
|
+
origin: context.origin,
|
|
80794
|
+
allowedOrigins: browser?.allowedOrigins ?? [],
|
|
80795
|
+
allowedScopes: browser?.allowedScopes ?? []
|
|
80796
|
+
} : { kind: "service" }
|
|
80797
|
+
});
|
|
80798
|
+
if (!result.ok) return TOKEN_REJECTED;
|
|
80799
|
+
return {
|
|
80800
|
+
userId: result.principal,
|
|
80801
|
+
kind: "wallet-jwt",
|
|
80802
|
+
profile: isBrowser ? "browser" : "service",
|
|
80803
|
+
scopes: result.scopes
|
|
80804
|
+
};
|
|
80805
|
+
}
|
|
80806
|
+
};
|
|
80807
|
+
function isWalletJwtCandidate(token) {
|
|
80808
|
+
try {
|
|
80809
|
+
return decodeJwt(token).header.alg === "ES256K" || hasBrowserClaims$1(token);
|
|
80810
|
+
} catch {
|
|
80811
|
+
return false;
|
|
80812
|
+
}
|
|
80813
|
+
}
|
|
80814
|
+
function hasBrowserClaims$1(token) {
|
|
80815
|
+
try {
|
|
80816
|
+
const payload = decodeJwt(token).payload;
|
|
80817
|
+
return typeof payload === "object" && payload !== null && !Array.isArray(payload) && ("origin" in payload || "scope" in payload);
|
|
80818
|
+
} catch {
|
|
80819
|
+
return false;
|
|
80820
|
+
}
|
|
80821
|
+
}
|
|
80822
|
+
var API_ERROR_STATUS$1 = {
|
|
80836
80823
|
bad_request: 400,
|
|
80837
80824
|
unauthorized: 401,
|
|
80838
80825
|
forbidden: 403,
|
|
@@ -80849,7 +80836,7 @@ var HttpError = class extends Error {
|
|
|
80849
80836
|
super(message);
|
|
80850
80837
|
this.name = "HttpError";
|
|
80851
80838
|
this.code = code;
|
|
80852
|
-
this.statusCode = API_ERROR_STATUS[code];
|
|
80839
|
+
this.statusCode = API_ERROR_STATUS$1[code];
|
|
80853
80840
|
}
|
|
80854
80841
|
toErrorBody(requestId) {
|
|
80855
80842
|
return {
|
|
@@ -80867,11 +80854,93 @@ function registerAuth(app, tokenStore, publicPaths, options = {}) {
|
|
|
80867
80854
|
const authorization = request.headers.authorization;
|
|
80868
80855
|
if (!authorization?.toLowerCase().startsWith("bearer ")) throw new HttpError("unauthorized", "Missing or malformed Authorization header");
|
|
80869
80856
|
const token = authorization.slice(7).trim();
|
|
80870
|
-
const
|
|
80871
|
-
if (!
|
|
80872
|
-
|
|
80857
|
+
const resolution = await tokenStore.resolve(token, { origin: request.headers.origin });
|
|
80858
|
+
if (!resolution || isRejectedToken(resolution)) throw new HttpError("unauthorized", "Invalid or expired token");
|
|
80859
|
+
const authenticatedUser = typeof resolution === "string" ? {
|
|
80860
|
+
userId: resolution,
|
|
80861
|
+
kind: "opaque",
|
|
80862
|
+
profile: "service",
|
|
80863
|
+
scopes: []
|
|
80864
|
+
} : resolution;
|
|
80865
|
+
if (request.headers.origin !== void 0 && (authenticatedUser.kind !== "wallet-jwt" || authenticatedUser.profile !== "browser")) throw new HttpError("unauthorized", "Browser requests require an origin-bound wallet JWT");
|
|
80866
|
+
request.authenticatedUser = authenticatedUser;
|
|
80867
|
+
request.userId = authenticatedUser.userId;
|
|
80873
80868
|
});
|
|
80874
80869
|
}
|
|
80870
|
+
var ALLOWED_METHODS = [
|
|
80871
|
+
"GET",
|
|
80872
|
+
"POST",
|
|
80873
|
+
"DELETE",
|
|
80874
|
+
"OPTIONS"
|
|
80875
|
+
];
|
|
80876
|
+
var ALLOWED_REQUEST_HEADERS = [
|
|
80877
|
+
"Authorization",
|
|
80878
|
+
"Content-Type",
|
|
80879
|
+
"Accept"
|
|
80880
|
+
];
|
|
80881
|
+
function parseCorsOrigins(value) {
|
|
80882
|
+
if (value === void 0 || value.trim().length === 0) return [];
|
|
80883
|
+
const origins = value.split(",").map((origin) => origin.trim()).filter((origin) => origin.length > 0);
|
|
80884
|
+
const unique = [...new Set(origins)];
|
|
80885
|
+
for (const origin of unique) {
|
|
80886
|
+
let parsed;
|
|
80887
|
+
try {
|
|
80888
|
+
parsed = new URL(origin);
|
|
80889
|
+
} catch {
|
|
80890
|
+
throw new Error(`Invalid CORS origin: ${origin}`);
|
|
80891
|
+
}
|
|
80892
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.origin !== origin) throw new Error(`CORS origin must be an exact HTTP(S) origin with no path: ${origin}`);
|
|
80893
|
+
}
|
|
80894
|
+
return unique;
|
|
80895
|
+
}
|
|
80896
|
+
function registerExactCors(app, options) {
|
|
80897
|
+
if (options.allowedOrigins.length === 0) return;
|
|
80898
|
+
const allowed = new Set(options.allowedOrigins);
|
|
80899
|
+
app.addHook("onRequest", async (request, reply) => {
|
|
80900
|
+
const origin = request.headers.origin;
|
|
80901
|
+
setVaryOrigin(reply);
|
|
80902
|
+
if (origin === void 0) return;
|
|
80903
|
+
if (!allowed.has(origin)) {
|
|
80904
|
+
reply.code(403).send({
|
|
80905
|
+
code: "forbidden",
|
|
80906
|
+
message: "Origin is not allowed"
|
|
80907
|
+
});
|
|
80908
|
+
return;
|
|
80909
|
+
}
|
|
80910
|
+
setCorsResponseHeaders(reply, origin, options.exposedHeaders ?? []);
|
|
80911
|
+
if (request.method !== "OPTIONS") return;
|
|
80912
|
+
if (!isValidPreflight(request)) {
|
|
80913
|
+
reply.code(400).send({
|
|
80914
|
+
code: "bad_request",
|
|
80915
|
+
message: "Invalid CORS preflight request"
|
|
80916
|
+
});
|
|
80917
|
+
return;
|
|
80918
|
+
}
|
|
80919
|
+
reply.code(204).send();
|
|
80920
|
+
});
|
|
80921
|
+
}
|
|
80922
|
+
function isValidPreflight(request) {
|
|
80923
|
+
const requestedMethod = request.headers["access-control-request-method"];
|
|
80924
|
+
if (requestedMethod === void 0 || !ALLOWED_METHODS.includes(requestedMethod.toUpperCase())) return false;
|
|
80925
|
+
const requestedHeaders = request.headers["access-control-request-headers"];
|
|
80926
|
+
if (requestedHeaders === void 0) return true;
|
|
80927
|
+
const allowed = new Set(ALLOWED_REQUEST_HEADERS.map((header) => header.toLowerCase()));
|
|
80928
|
+
return requestedHeaders.split(",").map((header) => header.trim().toLowerCase()).filter((header) => header.length > 0).every((header) => allowed.has(header));
|
|
80929
|
+
}
|
|
80930
|
+
function setCorsResponseHeaders(reply, origin, exposedHeaders) {
|
|
80931
|
+
reply.header("access-control-allow-origin", origin);
|
|
80932
|
+
reply.header("access-control-allow-methods", ALLOWED_METHODS.join(", "));
|
|
80933
|
+
reply.header("access-control-allow-headers", ALLOWED_REQUEST_HEADERS.join(", "));
|
|
80934
|
+
if (exposedHeaders.length > 0) reply.header("access-control-expose-headers", exposedHeaders.join(", "));
|
|
80935
|
+
}
|
|
80936
|
+
function setVaryOrigin(reply) {
|
|
80937
|
+
const existing = reply.getHeader("vary");
|
|
80938
|
+
if (existing === void 0) {
|
|
80939
|
+
reply.header("vary", "Origin");
|
|
80940
|
+
return;
|
|
80941
|
+
}
|
|
80942
|
+
if (!String(existing).split(",").map((value) => value.trim().toLowerCase()).includes("origin")) reply.header("vary", `${String(existing)}, Origin`);
|
|
80943
|
+
}
|
|
80875
80944
|
function registerErrorHandler(app) {
|
|
80876
80945
|
app.setErrorHandler((error, request, reply) => {
|
|
80877
80946
|
const requestId = request.id;
|
|
@@ -80939,7 +81008,7 @@ function registerMetrics$1(app, options) {
|
|
|
80939
81008
|
registers: [registry]
|
|
80940
81009
|
});
|
|
80941
81010
|
app.addHook("onResponse", (request, reply, done) => {
|
|
80942
|
-
const route =
|
|
81011
|
+
const route = request.routeOptions?.url?.split("?", 1)[0] ?? "unmatched";
|
|
80943
81012
|
const labels = {
|
|
80944
81013
|
method: request.method,
|
|
80945
81014
|
route,
|
|
@@ -80995,6 +81064,287 @@ var JsonSnapshotFile = class {
|
|
|
80995
81064
|
}
|
|
80996
81065
|
};
|
|
80997
81066
|
//#endregion
|
|
81067
|
+
//#region ../datalake-core/dist/node/index.mjs
|
|
81068
|
+
var API_ERROR_STATUS = {
|
|
81069
|
+
bad_request: 400,
|
|
81070
|
+
unauthorized: 401,
|
|
81071
|
+
forbidden: 403,
|
|
81072
|
+
not_found: 404,
|
|
81073
|
+
conflict: 409,
|
|
81074
|
+
unprocessable: 422,
|
|
81075
|
+
rate_limited: 429,
|
|
81076
|
+
internal: 500
|
|
81077
|
+
};
|
|
81078
|
+
function datalakeCollectionPath() {
|
|
81079
|
+
return `/v1/datalakes`;
|
|
81080
|
+
}
|
|
81081
|
+
function datalakeResourcePath(idOrName) {
|
|
81082
|
+
return `${datalakeCollectionPath()}/${encodeURIComponent(idOrName)}`;
|
|
81083
|
+
}
|
|
81084
|
+
function datalakeGrantsPath(idOrName) {
|
|
81085
|
+
return `${datalakeResourcePath(idOrName)}/grants`;
|
|
81086
|
+
}
|
|
81087
|
+
function datalakeGrantPath(idOrName, principal) {
|
|
81088
|
+
return `${datalakeGrantsPath(idOrName)}/${encodeURIComponent(principal)}`;
|
|
81089
|
+
}
|
|
81090
|
+
function datalakeTokensPath(idOrName) {
|
|
81091
|
+
return `${datalakeResourcePath(idOrName)}/tokens`;
|
|
81092
|
+
}
|
|
81093
|
+
function datalakeHealthPath() {
|
|
81094
|
+
return `/v1/health`;
|
|
81095
|
+
}
|
|
81096
|
+
var DATALAKE_HEADER_DUPLICATES = "x-datalake-duplicates";
|
|
81097
|
+
var DATALAKE_HEADER_REJECTED = "x-datalake-rejected";
|
|
81098
|
+
var DATALAKE_HEADER_NEXT_CURSOR = "x-datalake-next-cursor";
|
|
81099
|
+
var StrictHs256Error = class extends Error {
|
|
81100
|
+
code;
|
|
81101
|
+
constructor(code, message) {
|
|
81102
|
+
super(message);
|
|
81103
|
+
this.name = "StrictHs256Error";
|
|
81104
|
+
this.code = code;
|
|
81105
|
+
}
|
|
81106
|
+
};
|
|
81107
|
+
function verifyStrictHs256(token, signingSecret) {
|
|
81108
|
+
const parts = token.split(".");
|
|
81109
|
+
if (parts.length !== 3) throw new StrictHs256Error("malformed", "Token is not a JWS compact serialization");
|
|
81110
|
+
const [headerPart, payloadPart, signaturePart] = parts;
|
|
81111
|
+
const header = decodeJsonObject(headerPart, "Header");
|
|
81112
|
+
assertExactKeys(header, ["alg", "typ"], "Header");
|
|
81113
|
+
if (header.alg !== "HS256") throw new StrictHs256Error("malformed", "Header alg must be HS256");
|
|
81114
|
+
if (header.typ !== "JWT") throw new StrictHs256Error("malformed", "Header typ must be JWT");
|
|
81115
|
+
const provided = decodeBase64Url(signaturePart, "Signature");
|
|
81116
|
+
const expected = createHmac("sha256", signingSecret).update(`${headerPart}.${payloadPart}`).digest();
|
|
81117
|
+
if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) throw new StrictHs256Error("bad_signature", "Signature mismatch");
|
|
81118
|
+
return { payload: decodeJsonObject(payloadPart, "Payload") };
|
|
81119
|
+
}
|
|
81120
|
+
function assertExactKeys(value, expected, label) {
|
|
81121
|
+
const actual = Object.keys(value).sort();
|
|
81122
|
+
const canonical = [...expected].sort();
|
|
81123
|
+
if (actual.length !== canonical.length || actual.some((key, index) => key !== canonical[index])) throw new StrictHs256Error("malformed", `${label} fields must be exactly: ${canonical.join(", ")}`);
|
|
81124
|
+
}
|
|
81125
|
+
function isFiniteInteger(value) {
|
|
81126
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
|
|
81127
|
+
}
|
|
81128
|
+
function isNonEmptyString(value) {
|
|
81129
|
+
return typeof value === "string" && value.length > 0;
|
|
81130
|
+
}
|
|
81131
|
+
function decodeJsonObject(value, label) {
|
|
81132
|
+
let parsed;
|
|
81133
|
+
try {
|
|
81134
|
+
parsed = JSON.parse(decodeBase64Url(value, label).toString("utf8"));
|
|
81135
|
+
} catch (error) {
|
|
81136
|
+
if (error instanceof StrictHs256Error) throw error;
|
|
81137
|
+
throw new StrictHs256Error("malformed", `${label} is not valid JSON`);
|
|
81138
|
+
}
|
|
81139
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new StrictHs256Error("malformed", `${label} must be a JSON object`);
|
|
81140
|
+
return parsed;
|
|
81141
|
+
}
|
|
81142
|
+
function decodeBase64Url(value, label) {
|
|
81143
|
+
if (value.length === 0 || !/^[\w-]+$/.test(value)) throw new StrictHs256Error("malformed", `${label} is not canonical base64url`);
|
|
81144
|
+
const decoded = Buffer.from(value, "base64url");
|
|
81145
|
+
if (decoded.toString("base64url") !== value) throw new StrictHs256Error("malformed", `${label} is not canonical base64url`);
|
|
81146
|
+
return decoded;
|
|
81147
|
+
}
|
|
81148
|
+
function base64UrlEncode(input) {
|
|
81149
|
+
return (typeof input === "string" ? Buffer.from(input) : input).toString("base64url");
|
|
81150
|
+
}
|
|
81151
|
+
function signSessionToken(input) {
|
|
81152
|
+
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
81153
|
+
const expiresAt = issuedAt + input.ttlSeconds;
|
|
81154
|
+
const payload = {
|
|
81155
|
+
sub: input.address.toLowerCase(),
|
|
81156
|
+
typ: "session",
|
|
81157
|
+
iat: issuedAt,
|
|
81158
|
+
exp: expiresAt,
|
|
81159
|
+
jti: randomBytes(8).toString("hex")
|
|
81160
|
+
};
|
|
81161
|
+
const toSign = `${base64UrlEncode(JSON.stringify({
|
|
81162
|
+
alg: "HS256",
|
|
81163
|
+
typ: "JWT"
|
|
81164
|
+
}))}.${base64UrlEncode(JSON.stringify(payload))}`;
|
|
81165
|
+
return {
|
|
81166
|
+
token: `${toSign}.${createHmac("sha256", input.signingSecret).update(toSign).digest("base64url")}`,
|
|
81167
|
+
expiresAt: (/* @__PURE__ */ new Date(expiresAt * 1e3)).toISOString()
|
|
81168
|
+
};
|
|
81169
|
+
}
|
|
81170
|
+
var SessionTokenVerificationError = class extends Error {
|
|
81171
|
+
code;
|
|
81172
|
+
constructor(code, message) {
|
|
81173
|
+
super(message);
|
|
81174
|
+
this.name = "SessionTokenVerificationError";
|
|
81175
|
+
this.code = code;
|
|
81176
|
+
}
|
|
81177
|
+
};
|
|
81178
|
+
function verifySessionToken(input) {
|
|
81179
|
+
let raw;
|
|
81180
|
+
try {
|
|
81181
|
+
raw = verifyStrictHs256(input.token, input.signingSecret).payload;
|
|
81182
|
+
assertExactKeys(raw, [
|
|
81183
|
+
"exp",
|
|
81184
|
+
"iat",
|
|
81185
|
+
"jti",
|
|
81186
|
+
"sub",
|
|
81187
|
+
"typ"
|
|
81188
|
+
], "Payload");
|
|
81189
|
+
} catch (error) {
|
|
81190
|
+
if (error instanceof StrictHs256Error) throw new SessionTokenVerificationError(error.code, error.message);
|
|
81191
|
+
throw error;
|
|
81192
|
+
}
|
|
81193
|
+
if (raw.typ !== "session") throw new SessionTokenVerificationError("wrong_type", "Not a session token");
|
|
81194
|
+
if (!isNonEmptyString(raw.sub)) throw new SessionTokenVerificationError("malformed", "Payload sub must be a non-empty string");
|
|
81195
|
+
if (!isNonEmptyString(raw.jti)) throw new SessionTokenVerificationError("malformed", "Payload jti must be a non-empty string");
|
|
81196
|
+
if (!isFiniteInteger(raw.iat)) throw new SessionTokenVerificationError("malformed", "Payload iat must be a finite integer");
|
|
81197
|
+
if (!isFiniteInteger(raw.exp)) throw new SessionTokenVerificationError("malformed", "Payload exp must be a finite integer");
|
|
81198
|
+
if (raw.exp <= raw.iat) throw new SessionTokenVerificationError("malformed", "Payload exp must be greater than iat");
|
|
81199
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
81200
|
+
if (raw.exp <= now) throw new SessionTokenVerificationError("expired", "Token has expired");
|
|
81201
|
+
if (raw.iat > now + 60) throw new SessionTokenVerificationError("not_yet_valid", "Token issued in the future");
|
|
81202
|
+
return {
|
|
81203
|
+
sub: raw.sub,
|
|
81204
|
+
typ: raw.typ,
|
|
81205
|
+
iat: raw.iat,
|
|
81206
|
+
exp: raw.exp,
|
|
81207
|
+
jti: raw.jti
|
|
81208
|
+
};
|
|
81209
|
+
}
|
|
81210
|
+
function signChallenge(input) {
|
|
81211
|
+
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
81212
|
+
const expiresAt = issuedAt + input.ttlSeconds;
|
|
81213
|
+
const payload = {
|
|
81214
|
+
address: input.address.toLowerCase(),
|
|
81215
|
+
typ: "challenge",
|
|
81216
|
+
iat: issuedAt,
|
|
81217
|
+
exp: expiresAt,
|
|
81218
|
+
jti: randomBytes(8).toString("hex")
|
|
81219
|
+
};
|
|
81220
|
+
const toSign = `${base64UrlEncode(JSON.stringify({
|
|
81221
|
+
alg: "HS256",
|
|
81222
|
+
typ: "JWT"
|
|
81223
|
+
}))}.${base64UrlEncode(JSON.stringify(payload))}`;
|
|
81224
|
+
return {
|
|
81225
|
+
challenge: `${toSign}.${createHmac("sha256", input.signingSecret).update(toSign).digest("base64url")}`,
|
|
81226
|
+
expiresAt: (/* @__PURE__ */ new Date(expiresAt * 1e3)).toISOString()
|
|
81227
|
+
};
|
|
81228
|
+
}
|
|
81229
|
+
function verifyChallenge(input) {
|
|
81230
|
+
let raw;
|
|
81231
|
+
try {
|
|
81232
|
+
raw = verifyStrictHs256(input.challenge, input.signingSecret).payload;
|
|
81233
|
+
assertExactKeys(raw, [
|
|
81234
|
+
"address",
|
|
81235
|
+
"exp",
|
|
81236
|
+
"iat",
|
|
81237
|
+
"jti",
|
|
81238
|
+
"typ"
|
|
81239
|
+
], "Challenge payload");
|
|
81240
|
+
} catch (error) {
|
|
81241
|
+
if (error instanceof StrictHs256Error) throw new SessionTokenVerificationError(error.code, error.message);
|
|
81242
|
+
throw error;
|
|
81243
|
+
}
|
|
81244
|
+
if (raw.typ !== "challenge") throw new SessionTokenVerificationError("wrong_type", "Not a challenge token");
|
|
81245
|
+
if (!isNonEmptyString(raw.address) || !/^(?:0x)?[\da-fA-F]{40}$/.test(raw.address)) throw new SessionTokenVerificationError("malformed", "Challenge address must be a 20-byte hex address");
|
|
81246
|
+
if (!isNonEmptyString(raw.jti)) throw new SessionTokenVerificationError("malformed", "Challenge jti must be a non-empty string");
|
|
81247
|
+
if (!isFiniteInteger(raw.iat)) throw new SessionTokenVerificationError("malformed", "Challenge iat must be a finite integer");
|
|
81248
|
+
if (!isFiniteInteger(raw.exp)) throw new SessionTokenVerificationError("malformed", "Challenge exp must be a finite integer");
|
|
81249
|
+
if (raw.exp <= raw.iat) throw new SessionTokenVerificationError("malformed", "Challenge exp must be greater than iat");
|
|
81250
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
81251
|
+
if (raw.exp <= now) throw new SessionTokenVerificationError("expired", "Challenge has expired");
|
|
81252
|
+
if (raw.iat > now + 60) throw new SessionTokenVerificationError("not_yet_valid", "Challenge issued in the future");
|
|
81253
|
+
return {
|
|
81254
|
+
address: raw.address,
|
|
81255
|
+
typ: raw.typ,
|
|
81256
|
+
iat: raw.iat,
|
|
81257
|
+
exp: raw.exp,
|
|
81258
|
+
jti: raw.jti
|
|
81259
|
+
};
|
|
81260
|
+
}
|
|
81261
|
+
function base64UrlEncode2(input) {
|
|
81262
|
+
return (typeof input === "string" ? Buffer.from(input) : input).toString("base64url");
|
|
81263
|
+
}
|
|
81264
|
+
function signToken(input) {
|
|
81265
|
+
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
81266
|
+
const expiresAt = issuedAt + input.ttlSeconds;
|
|
81267
|
+
const payload = {
|
|
81268
|
+
sub: input.ownerId,
|
|
81269
|
+
dl: input.datalakeId,
|
|
81270
|
+
role: input.role,
|
|
81271
|
+
iat: issuedAt,
|
|
81272
|
+
exp: expiresAt,
|
|
81273
|
+
jti: randomBytes(8).toString("hex")
|
|
81274
|
+
};
|
|
81275
|
+
const toSign = `${base64UrlEncode2(JSON.stringify({
|
|
81276
|
+
alg: "HS256",
|
|
81277
|
+
typ: "JWT"
|
|
81278
|
+
}))}.${base64UrlEncode2(JSON.stringify(payload))}`;
|
|
81279
|
+
return {
|
|
81280
|
+
token: `${toSign}.${createHmac("sha256", input.signingSecret).update(toSign).digest("base64url")}`,
|
|
81281
|
+
datalakeId: input.datalakeId,
|
|
81282
|
+
role: input.role,
|
|
81283
|
+
expiresAt: (/* @__PURE__ */ new Date(expiresAt * 1e3)).toISOString(),
|
|
81284
|
+
url: input.url
|
|
81285
|
+
};
|
|
81286
|
+
}
|
|
81287
|
+
var TokenVerificationError = class extends Error {
|
|
81288
|
+
code;
|
|
81289
|
+
constructor(code, message) {
|
|
81290
|
+
super(message);
|
|
81291
|
+
this.name = "TokenVerificationError";
|
|
81292
|
+
this.code = code;
|
|
81293
|
+
}
|
|
81294
|
+
};
|
|
81295
|
+
function verifyToken(input) {
|
|
81296
|
+
let raw;
|
|
81297
|
+
try {
|
|
81298
|
+
raw = verifyStrictHs256(input.token, input.signingSecret).payload;
|
|
81299
|
+
assertExactKeys(raw, [
|
|
81300
|
+
"dl",
|
|
81301
|
+
"exp",
|
|
81302
|
+
"iat",
|
|
81303
|
+
"jti",
|
|
81304
|
+
"role",
|
|
81305
|
+
"sub"
|
|
81306
|
+
], "Payload");
|
|
81307
|
+
} catch (error) {
|
|
81308
|
+
if (error instanceof StrictHs256Error) throw new TokenVerificationError(error.code, error.message);
|
|
81309
|
+
throw error;
|
|
81310
|
+
}
|
|
81311
|
+
if (!isNonEmptyString(raw.sub)) throw new TokenVerificationError("malformed", "Payload sub must be a non-empty string");
|
|
81312
|
+
if (!isNonEmptyString(raw.dl)) throw new TokenVerificationError("malformed", "Payload dl must be a non-empty string");
|
|
81313
|
+
if (raw.role !== "viewer" && raw.role !== "runner") throw new TokenVerificationError("malformed", "Payload role must be viewer or runner");
|
|
81314
|
+
if (!isFiniteInteger(raw.iat)) throw new TokenVerificationError("malformed", "Payload iat must be a finite integer");
|
|
81315
|
+
if (!isFiniteInteger(raw.exp)) throw new TokenVerificationError("malformed", "Payload exp must be a finite integer");
|
|
81316
|
+
if (!isNonEmptyString(raw.jti)) throw new TokenVerificationError("malformed", "Payload jti must be a non-empty string");
|
|
81317
|
+
if (raw.exp <= raw.iat) throw new TokenVerificationError("malformed", "Payload exp must be greater than iat");
|
|
81318
|
+
const payload = {
|
|
81319
|
+
sub: raw.sub,
|
|
81320
|
+
dl: raw.dl,
|
|
81321
|
+
role: raw.role,
|
|
81322
|
+
iat: raw.iat,
|
|
81323
|
+
exp: raw.exp,
|
|
81324
|
+
jti: raw.jti
|
|
81325
|
+
};
|
|
81326
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
81327
|
+
if (payload.exp <= now) throw new TokenVerificationError("expired", "Token has expired");
|
|
81328
|
+
if (payload.iat > now + 60) throw new TokenVerificationError("not_yet_valid", "Token issued in the future");
|
|
81329
|
+
return payload;
|
|
81330
|
+
}
|
|
81331
|
+
var PUBLIC_PRINCIPAL = "public";
|
|
81332
|
+
function isPublicPrincipal(principal) {
|
|
81333
|
+
return principal === PUBLIC_PRINCIPAL;
|
|
81334
|
+
}
|
|
81335
|
+
var DATALAKE_CONTROL_OPERATIONS = [
|
|
81336
|
+
"datalake:create",
|
|
81337
|
+
"datalake:describe",
|
|
81338
|
+
"datalake:list"
|
|
81339
|
+
];
|
|
81340
|
+
var DATALAKE_DATA_OPERATIONS = [
|
|
81341
|
+
"payload:append",
|
|
81342
|
+
"payload:read",
|
|
81343
|
+
"usage:read"
|
|
81344
|
+
];
|
|
81345
|
+
DATALAKE_CONTROL_OPERATIONS.join(" ");
|
|
81346
|
+
DATALAKE_DATA_OPERATIONS.join(" ");
|
|
81347
|
+
//#endregion
|
|
80998
81348
|
//#region ../../node_modules/.pnpm/reusify@1.1.0/node_modules/reusify/reusify.js
|
|
80999
81349
|
var require_reusify = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
81000
81350
|
function reusify(Constructor) {
|
|
@@ -111141,6 +111491,7 @@ function registerAuthRoutes(app, deps) {
|
|
|
111141
111491
|
const challengeTtl = deps.challengeTtlSeconds ?? 300;
|
|
111142
111492
|
const sessionTtl = deps.sessionTtlSeconds ?? 1440 * 60;
|
|
111143
111493
|
app.post(AUTH_CHALLENGE_PATH, async (request) => {
|
|
111494
|
+
requireServiceOnlyRequest(request);
|
|
111144
111495
|
const address = request.body?.address;
|
|
111145
111496
|
if (!address || !isLikelyAddress(address)) throw new HttpError("bad_request", "address is required (0x-prefixed 20-byte hex)");
|
|
111146
111497
|
const { challenge, expiresAt } = signChallenge({
|
|
@@ -111154,6 +111505,7 @@ function registerAuthRoutes(app, deps) {
|
|
|
111154
111505
|
};
|
|
111155
111506
|
});
|
|
111156
111507
|
app.post(AUTH_VERIFY_PATH, async (request) => {
|
|
111508
|
+
requireServiceOnlyRequest(request);
|
|
111157
111509
|
const { address, challenge, signature } = request.body ?? {};
|
|
111158
111510
|
if (!address || !challenge || !signature) throw new HttpError("bad_request", "address, challenge, and signature are required");
|
|
111159
111511
|
if (!isLikelyAddress(address)) throw new HttpError("bad_request", "address must be 0x-prefixed 20-byte hex");
|
|
@@ -111187,6 +111539,9 @@ function registerAuthRoutes(app, deps) {
|
|
|
111187
111539
|
};
|
|
111188
111540
|
});
|
|
111189
111541
|
}
|
|
111542
|
+
function requireServiceOnlyRequest(request) {
|
|
111543
|
+
if (request.headers.origin !== void 0) throw new HttpError("forbidden", "Legacy challenge/session authentication is service-only");
|
|
111544
|
+
}
|
|
111190
111545
|
function isLikelyAddress(value) {
|
|
111191
111546
|
return /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
111192
111547
|
}
|
|
@@ -111197,6 +111552,7 @@ function registerDatalakeRoutes(app, deps) {
|
|
|
111197
111552
|
const idGenerator = deps.idGenerator ?? defaultIdGenerator;
|
|
111198
111553
|
const originUrl = deps.datalakeOriginUrl.replace(/\/$/, "");
|
|
111199
111554
|
app.post(datalakeCollectionPath(), async (request) => {
|
|
111555
|
+
requireControlOperation(request, "datalake:create");
|
|
111200
111556
|
const body = request.body;
|
|
111201
111557
|
if (!body || typeof body.name !== "string" || !body.config) throw new HttpError("bad_request", "name and config are required");
|
|
111202
111558
|
if (await deps.store.findByIdOrName(request.userId, body.name)) throw new HttpError("conflict", `Datalake already exists: ${body.name}`);
|
|
@@ -111211,21 +111567,25 @@ function registerDatalakeRoutes(app, deps) {
|
|
|
111211
111567
|
return descriptor;
|
|
111212
111568
|
});
|
|
111213
111569
|
app.get(datalakeCollectionPath(), async (request) => {
|
|
111570
|
+
requireControlOperation(request, "datalake:list");
|
|
111214
111571
|
return deps.store.listByOwner(request.userId);
|
|
111215
111572
|
});
|
|
111216
111573
|
app.get(`${datalakeCollectionPath()}/:idOrName`, async (request) => {
|
|
111574
|
+
requireControlOperation(request, "datalake:describe");
|
|
111217
111575
|
return requireDatalake(deps, request.userId, request.params.idOrName);
|
|
111218
111576
|
});
|
|
111219
111577
|
app.delete(`${datalakeCollectionPath()}/:idOrName`, async (request, reply) => {
|
|
111578
|
+
requireControlOperation(request);
|
|
111220
111579
|
const descriptor = await requireDatalake(deps, request.userId, request.params.idOrName);
|
|
111221
111580
|
await deps.store.remove(descriptor.id);
|
|
111222
111581
|
reply.code(204).send();
|
|
111223
111582
|
});
|
|
111224
111583
|
app.post(`${datalakeResourcePath(":idOrName")}/grants`.replaceAll("%3A", ":"), async (request) => {
|
|
111584
|
+
requireControlOperation(request);
|
|
111225
111585
|
const body = request.body;
|
|
111226
111586
|
if (!body || typeof body.principal !== "string" || !body.role) throw new HttpError("bad_request", "principal and role are required");
|
|
111227
111587
|
if (body.role !== "viewer" && body.role !== "runner") throw new HttpError("unprocessable", `Invalid role: ${String(body.role)}`);
|
|
111228
|
-
const principal = normalizePrincipal(body.principal);
|
|
111588
|
+
const principal = normalizePrincipal$1(body.principal);
|
|
111229
111589
|
const descriptor = await requireDatalake(deps, request.userId, request.params.idOrName);
|
|
111230
111590
|
return deps.store.upsertAcl(descriptor.id, {
|
|
111231
111591
|
principal,
|
|
@@ -111236,12 +111596,14 @@ function registerDatalakeRoutes(app, deps) {
|
|
|
111236
111596
|
});
|
|
111237
111597
|
const grantPathPattern = datalakeGrantPath(":idOrName", ":principal").replaceAll("%3A", ":");
|
|
111238
111598
|
app.delete(grantPathPattern, async (request) => {
|
|
111239
|
-
|
|
111599
|
+
requireControlOperation(request);
|
|
111600
|
+
const principal = normalizePrincipal$1(request.params.principal);
|
|
111240
111601
|
const descriptor = await requireDatalake(deps, request.userId, request.params.idOrName);
|
|
111241
111602
|
return deps.store.removeAcl(descriptor.id, principal);
|
|
111242
111603
|
});
|
|
111243
111604
|
const tokensPathPattern = datalakeTokensPath(":idOrName").replaceAll("%3A", ":");
|
|
111244
111605
|
app.post(tokensPathPattern, async (request) => {
|
|
111606
|
+
requireControlOperation(request);
|
|
111245
111607
|
const body = request.body;
|
|
111246
111608
|
if (!body?.role) throw new HttpError("bad_request", "role is required");
|
|
111247
111609
|
if (body.role !== "viewer" && body.role !== "runner") throw new HttpError("unprocessable", `Invalid role: ${String(body.role)}`);
|
|
@@ -111260,19 +111622,39 @@ function registerDatalakeRoutes(app, deps) {
|
|
|
111260
111622
|
});
|
|
111261
111623
|
});
|
|
111262
111624
|
}
|
|
111625
|
+
function requireControlOperation(request, operation) {
|
|
111626
|
+
const auth = request.authenticatedUser;
|
|
111627
|
+
if (request.headers.origin !== void 0 && (auth.kind !== "wallet-jwt" || auth.profile !== "browser")) throw new HttpError("forbidden", "Browser requests require an origin-bound wallet JWT");
|
|
111628
|
+
if (auth.kind !== "wallet-jwt" || auth.profile !== "browser") return;
|
|
111629
|
+
if (operation === void 0 || !auth.scopes.includes(operation)) throw new HttpError("forbidden", "Browser wallet JWT does not authorize this operation");
|
|
111630
|
+
}
|
|
111263
111631
|
async function requireDatalake(deps, ownerId, idOrName) {
|
|
111264
111632
|
const descriptor = await deps.store.findByIdOrName(ownerId, idOrName);
|
|
111265
111633
|
if (!descriptor) throw new HttpError("not_found", `Datalake not found: ${idOrName}`);
|
|
111266
111634
|
return descriptor;
|
|
111267
111635
|
}
|
|
111268
111636
|
var ADDRESS_PATTERN = /^(0x)?[\da-fA-F]{40}$/;
|
|
111269
|
-
function normalizePrincipal(value) {
|
|
111637
|
+
function normalizePrincipal$1(value) {
|
|
111270
111638
|
if (isPublicPrincipal(value)) return value;
|
|
111271
111639
|
if (ADDRESS_PATTERN.test(value)) return normalizeAddress(value);
|
|
111272
111640
|
return value;
|
|
111273
111641
|
}
|
|
111274
111642
|
async function buildServer(options) {
|
|
111275
|
-
const app = (0, import_fastify.default)({
|
|
111643
|
+
const app = (0, import_fastify.default)({
|
|
111644
|
+
logger: options.logger ?? false,
|
|
111645
|
+
logController: new import_fastify.LogController({ disableRequestLogging: true })
|
|
111646
|
+
});
|
|
111647
|
+
registerExactCors(app, {
|
|
111648
|
+
allowedOrigins: options.corsOrigins ?? [],
|
|
111649
|
+
exposedHeaders: [
|
|
111650
|
+
DATALAKE_HEADER_DUPLICATES,
|
|
111651
|
+
DATALAKE_HEADER_REJECTED,
|
|
111652
|
+
DATALAKE_HEADER_NEXT_CURSOR,
|
|
111653
|
+
"x-ratelimit-limit",
|
|
111654
|
+
"x-ratelimit-remaining",
|
|
111655
|
+
"retry-after"
|
|
111656
|
+
]
|
|
111657
|
+
});
|
|
111276
111658
|
registerErrorHandler(app);
|
|
111277
111659
|
registerAuth(app, options.tokenStore, [
|
|
111278
111660
|
datalakeHealthPath(),
|
|
@@ -111498,29 +111880,253 @@ function escapeRegex(input) {
|
|
|
111498
111880
|
return input.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
111499
111881
|
}
|
|
111500
111882
|
//#endregion
|
|
111883
|
+
//#region src/errors/planeErrors.ts
|
|
111884
|
+
/**
|
|
111885
|
+
* Thrown inside data-plane handlers to produce a structured JSON error.
|
|
111886
|
+
* Distinct from the control-plane `HttpError` only for type hygiene; the
|
|
111887
|
+
* wire format is identical.
|
|
111888
|
+
*/
|
|
111889
|
+
var PlaneHttpError = class extends Error {
|
|
111890
|
+
code;
|
|
111891
|
+
statusCode;
|
|
111892
|
+
constructor(code, message) {
|
|
111893
|
+
super(message);
|
|
111894
|
+
this.name = "PlaneHttpError";
|
|
111895
|
+
this.code = code;
|
|
111896
|
+
this.statusCode = API_ERROR_STATUS[code];
|
|
111897
|
+
}
|
|
111898
|
+
};
|
|
111899
|
+
function toPlaneErrorBody(error, requestId) {
|
|
111900
|
+
return {
|
|
111901
|
+
code: error.code,
|
|
111902
|
+
message: error.message,
|
|
111903
|
+
requestId
|
|
111904
|
+
};
|
|
111905
|
+
}
|
|
111906
|
+
//#endregion
|
|
111907
|
+
//#region src/auth/accessContext.ts
|
|
111908
|
+
const VIEWER_OPERATIONS = ["payload:read", "usage:read"];
|
|
111909
|
+
const RUNNER_OPERATIONS = [
|
|
111910
|
+
...DATALAKE_DATA_OPERATIONS,
|
|
111911
|
+
"payload:clear",
|
|
111912
|
+
"payload:delete"
|
|
111913
|
+
];
|
|
111914
|
+
/** Resolves and attaches access once, before quota and route authorization. */
|
|
111915
|
+
function registerAccessContext(app, options) {
|
|
111916
|
+
app.addHook("preHandler", async (request) => {
|
|
111917
|
+
const datalakeId = extractDatalakeId$2(request);
|
|
111918
|
+
if (datalakeId === void 0) return;
|
|
111919
|
+
request.datalakeAccess = await resolveAccess({
|
|
111920
|
+
allowedOrigins: options.allowedOrigins,
|
|
111921
|
+
authorizationHeader: request.headers.authorization,
|
|
111922
|
+
controlStore: options.controlStore,
|
|
111923
|
+
datalakeId,
|
|
111924
|
+
origin: request.headers.origin,
|
|
111925
|
+
isServiceWalletJwtAllowed: options.isServiceWalletJwtAllowed,
|
|
111926
|
+
signingSecret: options.signingSecret
|
|
111927
|
+
});
|
|
111928
|
+
});
|
|
111929
|
+
}
|
|
111930
|
+
/**
|
|
111931
|
+
* Non-throwing access resolution keeps invalid credentials available to the
|
|
111932
|
+
* later anonymous-IP rate limiter. Routes call `requireAccessContext` to
|
|
111933
|
+
* surface the stored error and enforce their operation without re-verifying.
|
|
111934
|
+
*/
|
|
111935
|
+
async function resolveAccess(input) {
|
|
111936
|
+
const descriptor = await input.controlStore.findById(input.datalakeId);
|
|
111937
|
+
if (!descriptor) return failure(false, new PlaneHttpError("not_found", `Datalake not found: ${input.datalakeId}`));
|
|
111938
|
+
const tokenValue = extractBearer(input.authorizationHeader);
|
|
111939
|
+
if (tokenValue !== void 0) {
|
|
111940
|
+
const verified = await verifyBearer(tokenValue, descriptor, input);
|
|
111941
|
+
if (!verified.ok) return {
|
|
111942
|
+
authenticated: false,
|
|
111943
|
+
descriptor,
|
|
111944
|
+
error: new PlaneHttpError("unauthorized", verified.message)
|
|
111945
|
+
};
|
|
111946
|
+
const maxRole = findLiveRole(descriptor, verified.principal);
|
|
111947
|
+
if (maxRole === void 0) return {
|
|
111948
|
+
authenticated: true,
|
|
111949
|
+
descriptor,
|
|
111950
|
+
principal: verified.principal,
|
|
111951
|
+
error: new PlaneHttpError("forbidden", "Verified principal has no live access to this datalake")
|
|
111952
|
+
};
|
|
111953
|
+
const effectiveOperations = intersectOperations(verified.operations, operationsForRole(maxRole));
|
|
111954
|
+
const context = {
|
|
111955
|
+
authenticated: true,
|
|
111956
|
+
descriptor,
|
|
111957
|
+
principal: verified.principal,
|
|
111958
|
+
maxRole,
|
|
111959
|
+
effectiveOperations
|
|
111960
|
+
};
|
|
111961
|
+
return {
|
|
111962
|
+
authenticated: true,
|
|
111963
|
+
descriptor,
|
|
111964
|
+
principal: verified.principal,
|
|
111965
|
+
context
|
|
111966
|
+
};
|
|
111967
|
+
}
|
|
111968
|
+
const publicRole = findPublicLiveRole(descriptor);
|
|
111969
|
+
if (publicRole !== void 0) return {
|
|
111970
|
+
authenticated: false,
|
|
111971
|
+
descriptor,
|
|
111972
|
+
context: {
|
|
111973
|
+
authenticated: false,
|
|
111974
|
+
descriptor,
|
|
111975
|
+
maxRole: publicRole,
|
|
111976
|
+
effectiveOperations: operationsForRole(publicRole)
|
|
111977
|
+
}
|
|
111978
|
+
};
|
|
111979
|
+
return {
|
|
111980
|
+
authenticated: false,
|
|
111981
|
+
descriptor,
|
|
111982
|
+
error: new PlaneHttpError("unauthorized", "Authentication required")
|
|
111983
|
+
};
|
|
111984
|
+
}
|
|
111985
|
+
function requireAccessContext(request, operation) {
|
|
111986
|
+
const resolution = request.datalakeAccess;
|
|
111987
|
+
if (resolution?.error !== void 0) throw resolution.error;
|
|
111988
|
+
const context = resolution?.context;
|
|
111989
|
+
if (context === void 0) throw new PlaneHttpError("internal", "Datalake access context is missing");
|
|
111990
|
+
if (!context.effectiveOperations.includes(operation)) throw new PlaneHttpError("forbidden", `Operation ${operation} is not authorized`);
|
|
111991
|
+
return context;
|
|
111992
|
+
}
|
|
111993
|
+
async function verifyBearer(token, descriptor, input) {
|
|
111994
|
+
if (isEs256kJwt(token)) return verifyWalletBearer(token, descriptor, input);
|
|
111995
|
+
if (input.origin !== void 0) return {
|
|
111996
|
+
ok: false,
|
|
111997
|
+
message: "Browser requests require an origin-bound wallet JWT"
|
|
111998
|
+
};
|
|
111999
|
+
try {
|
|
112000
|
+
const payload = verifyToken({
|
|
112001
|
+
token,
|
|
112002
|
+
signingSecret: input.signingSecret
|
|
112003
|
+
});
|
|
112004
|
+
if (payload.dl !== descriptor.id) return {
|
|
112005
|
+
ok: false,
|
|
112006
|
+
message: "Token is not scoped to this datalake"
|
|
112007
|
+
};
|
|
112008
|
+
return {
|
|
112009
|
+
ok: true,
|
|
112010
|
+
principal: normalizePrincipal(payload.sub),
|
|
112011
|
+
operations: operationsForRole(payload.role)
|
|
112012
|
+
};
|
|
112013
|
+
} catch (error) {
|
|
112014
|
+
return {
|
|
112015
|
+
ok: false,
|
|
112016
|
+
message: error instanceof Error ? error.message : "Invalid token"
|
|
112017
|
+
};
|
|
112018
|
+
}
|
|
112019
|
+
}
|
|
112020
|
+
async function verifyWalletBearer(token, descriptor, input) {
|
|
112021
|
+
const isBrowser = input.origin !== void 0 || hasBrowserClaims(token);
|
|
112022
|
+
if (!isBrowser && !input.isServiceWalletJwtAllowed) return {
|
|
112023
|
+
ok: false,
|
|
112024
|
+
message: "Non-browser wallet JWT profile is not enabled"
|
|
112025
|
+
};
|
|
112026
|
+
const result = await verifyWalletJwtPolicy(token, {
|
|
112027
|
+
audience: descriptor.id,
|
|
112028
|
+
profile: isBrowser ? {
|
|
112029
|
+
kind: "browser",
|
|
112030
|
+
origin: input.origin,
|
|
112031
|
+
allowedOrigins: input.allowedOrigins,
|
|
112032
|
+
allowedScopes: DATALAKE_DATA_OPERATIONS
|
|
112033
|
+
} : { kind: "service" }
|
|
112034
|
+
});
|
|
112035
|
+
if (!result.ok) return {
|
|
112036
|
+
ok: false,
|
|
112037
|
+
message: `Wallet JWT verification failed: ${result.reasons.join("; ")}`
|
|
112038
|
+
};
|
|
112039
|
+
return {
|
|
112040
|
+
ok: true,
|
|
112041
|
+
principal: result.principal,
|
|
112042
|
+
operations: isBrowser ? result.scopes : RUNNER_OPERATIONS
|
|
112043
|
+
};
|
|
112044
|
+
}
|
|
112045
|
+
function findLiveRole(descriptor, principal) {
|
|
112046
|
+
if (normalizePrincipal(descriptor.ownerId) === principal) return "runner";
|
|
112047
|
+
let role;
|
|
112048
|
+
for (const candidate of descriptor.acl) {
|
|
112049
|
+
if (isPublicPrincipal(candidate.principal) || normalizePrincipal(candidate.principal) !== principal) continue;
|
|
112050
|
+
if (candidate.role === "runner") return "runner";
|
|
112051
|
+
role = "viewer";
|
|
112052
|
+
}
|
|
112053
|
+
return role;
|
|
112054
|
+
}
|
|
112055
|
+
function findPublicLiveRole(descriptor) {
|
|
112056
|
+
let role;
|
|
112057
|
+
for (const candidate of descriptor.acl) {
|
|
112058
|
+
if (!isPublicPrincipal(candidate.principal)) continue;
|
|
112059
|
+
if (candidate.role === "runner") return "runner";
|
|
112060
|
+
role = "viewer";
|
|
112061
|
+
}
|
|
112062
|
+
return role;
|
|
112063
|
+
}
|
|
112064
|
+
function operationsForRole(role) {
|
|
112065
|
+
return role === "runner" ? RUNNER_OPERATIONS : VIEWER_OPERATIONS;
|
|
112066
|
+
}
|
|
112067
|
+
function intersectOperations(tokenOperations, liveOperations) {
|
|
112068
|
+
const live = new Set(liveOperations);
|
|
112069
|
+
return tokenOperations.filter((operation) => live.has(operation));
|
|
112070
|
+
}
|
|
112071
|
+
function isEs256kJwt(token) {
|
|
112072
|
+
try {
|
|
112073
|
+
return decodeJwt(token).header.alg === JwtAlg.ES256K;
|
|
112074
|
+
} catch {
|
|
112075
|
+
return false;
|
|
112076
|
+
}
|
|
112077
|
+
}
|
|
112078
|
+
function hasBrowserClaims(token) {
|
|
112079
|
+
try {
|
|
112080
|
+
const payload = decodeJwt(token).payload;
|
|
112081
|
+
return typeof payload === "object" && payload !== null && !Array.isArray(payload) && ("origin" in payload || "scope" in payload);
|
|
112082
|
+
} catch {
|
|
112083
|
+
return false;
|
|
112084
|
+
}
|
|
112085
|
+
}
|
|
112086
|
+
function extractBearer(authorization) {
|
|
112087
|
+
if (authorization === void 0 || !authorization.toLowerCase().startsWith("bearer ")) return void 0;
|
|
112088
|
+
const value = authorization.slice(7).trim();
|
|
112089
|
+
return value.length > 0 ? value : void 0;
|
|
112090
|
+
}
|
|
112091
|
+
function normalizePrincipal(value) {
|
|
112092
|
+
if (/^(?:0x)?[\da-fA-F]{40}$/.test(value)) return normalizeAddress(value);
|
|
112093
|
+
return value;
|
|
112094
|
+
}
|
|
112095
|
+
function extractDatalakeId$2(request) {
|
|
112096
|
+
const value = request.params?.id;
|
|
112097
|
+
return typeof value === "string" ? value : void 0;
|
|
112098
|
+
}
|
|
112099
|
+
function failure(authenticated, error) {
|
|
112100
|
+
return {
|
|
112101
|
+
authenticated,
|
|
112102
|
+
error
|
|
112103
|
+
};
|
|
112104
|
+
}
|
|
112105
|
+
//#endregion
|
|
111501
112106
|
//#region src/plugins/auditPlugin.ts
|
|
111502
112107
|
const START_TIME_KEY = "__ariesAuditStart__";
|
|
111503
112108
|
/**
|
|
111504
112109
|
* Installs onRequest + onResponse hooks that record one JSONL row per
|
|
111505
112110
|
* request to the configured `AuditLogger`. Health checks are skipped.
|
|
111506
112111
|
*/
|
|
111507
|
-
function registerAudit(app, logger) {
|
|
112112
|
+
function registerAudit(app, logger, principalHashSecret) {
|
|
111508
112113
|
app.addHook("onRequest", async (request) => {
|
|
111509
112114
|
request[START_TIME_KEY] = Date.now();
|
|
111510
112115
|
});
|
|
111511
112116
|
app.addHook("onResponse", async (request, reply) => {
|
|
111512
|
-
const
|
|
111513
|
-
if (
|
|
112117
|
+
const path = routeTemplate(request);
|
|
112118
|
+
if (path.endsWith("/health")) return;
|
|
111514
112119
|
const start = request[START_TIME_KEY] ?? Date.now();
|
|
111515
112120
|
const durationMs = Date.now() - start;
|
|
111516
|
-
const { subject, authenticated } = resolveSubject$1(request);
|
|
112121
|
+
const { subject, authenticated } = resolveSubject$1(request, principalHashSecret);
|
|
111517
112122
|
const datalakeId = extractDatalakeId$1(request);
|
|
112123
|
+
const datalakeSubject = datalakeId === void 0 ? void 0 : `datalake:${stableDatalakeHash(datalakeId, principalHashSecret)}`;
|
|
111518
112124
|
logger.log({
|
|
111519
112125
|
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
111520
112126
|
requestId: request.id,
|
|
111521
112127
|
method: request.method,
|
|
111522
|
-
path
|
|
111523
|
-
|
|
112128
|
+
path,
|
|
112129
|
+
datalakeSubject,
|
|
111524
112130
|
subject,
|
|
111525
112131
|
authenticated,
|
|
111526
112132
|
statusCode: reply.statusCode,
|
|
@@ -111529,49 +112135,33 @@ function registerAudit(app, logger) {
|
|
|
111529
112135
|
});
|
|
111530
112136
|
});
|
|
111531
112137
|
}
|
|
111532
|
-
function resolveSubject$1(request) {
|
|
111533
|
-
const
|
|
111534
|
-
if (
|
|
111535
|
-
|
|
111536
|
-
|
|
111537
|
-
|
|
111538
|
-
authenticated: true
|
|
111539
|
-
};
|
|
111540
|
-
}
|
|
112138
|
+
function resolveSubject$1(request, principalHashSecret) {
|
|
112139
|
+
const principal = request.datalakeAccess?.principal;
|
|
112140
|
+
if (principal !== void 0) return {
|
|
112141
|
+
subject: `principal:${stablePrincipalHash(principal, principalHashSecret)}`,
|
|
112142
|
+
authenticated: true
|
|
112143
|
+
};
|
|
111541
112144
|
return {
|
|
111542
112145
|
subject: `anonymous:${request.ip}`,
|
|
111543
112146
|
authenticated: false
|
|
111544
112147
|
};
|
|
111545
112148
|
}
|
|
112149
|
+
function stablePrincipalHash(principal, secret) {
|
|
112150
|
+
return createHmac("sha256", secret).update("aries-datalake-principal-v1\0").update(principal).digest("hex").slice(0, 32);
|
|
112151
|
+
}
|
|
112152
|
+
function stableDatalakeHash(datalakeId, secret) {
|
|
112153
|
+
return createHmac("sha256", secret).update("aries-datalake-id-v1\0").update(datalakeId).digest("hex").slice(0, 32);
|
|
112154
|
+
}
|
|
112155
|
+
function routeTemplate(request) {
|
|
112156
|
+
const route = request.routeOptions?.url;
|
|
112157
|
+
if (typeof route !== "string" || route.length === 0) return "unmatched";
|
|
112158
|
+
return route.split("?", 1)[0] ?? "unmatched";
|
|
112159
|
+
}
|
|
111546
112160
|
function extractDatalakeId$1(request) {
|
|
111547
112161
|
const value = request.params?.id;
|
|
111548
112162
|
return typeof value === "string" ? value : void 0;
|
|
111549
112163
|
}
|
|
111550
112164
|
//#endregion
|
|
111551
|
-
//#region src/errors/planeErrors.ts
|
|
111552
|
-
/**
|
|
111553
|
-
* Thrown inside data-plane handlers to produce a structured JSON error.
|
|
111554
|
-
* Distinct from the control-plane `HttpError` only for type hygiene; the
|
|
111555
|
-
* wire format is identical.
|
|
111556
|
-
*/
|
|
111557
|
-
var PlaneHttpError = class extends Error {
|
|
111558
|
-
code;
|
|
111559
|
-
statusCode;
|
|
111560
|
-
constructor(code, message) {
|
|
111561
|
-
super(message);
|
|
111562
|
-
this.name = "PlaneHttpError";
|
|
111563
|
-
this.code = code;
|
|
111564
|
-
this.statusCode = API_ERROR_STATUS$1[code];
|
|
111565
|
-
}
|
|
111566
|
-
};
|
|
111567
|
-
function toPlaneErrorBody(error, requestId) {
|
|
111568
|
-
return {
|
|
111569
|
-
code: error.code,
|
|
111570
|
-
message: error.message,
|
|
111571
|
-
requestId
|
|
111572
|
-
};
|
|
111573
|
-
}
|
|
111574
|
-
//#endregion
|
|
111575
112165
|
//#region src/plugins/errorHandler.ts
|
|
111576
112166
|
function registerPlaneErrorHandler(app) {
|
|
111577
112167
|
app.setErrorHandler((error, request, reply) => {
|
|
@@ -111695,7 +112285,7 @@ function registerMetrics(app) {
|
|
|
111695
112285
|
registers: [registry]
|
|
111696
112286
|
});
|
|
111697
112287
|
app.addHook("onResponse", (request, reply, done) => {
|
|
111698
|
-
const route =
|
|
112288
|
+
const route = request.routeOptions?.url?.split("?", 1)[0] ?? "unmatched";
|
|
111699
112289
|
const labels = {
|
|
111700
112290
|
method: request.method,
|
|
111701
112291
|
route,
|
|
@@ -111718,17 +112308,15 @@ function registerMetrics(app) {
|
|
|
111718
112308
|
* request. Fires after route matching, so the health endpoint (no `:id`
|
|
111719
112309
|
* param) is not rate limited.
|
|
111720
112310
|
*
|
|
111721
|
-
*
|
|
111722
|
-
*
|
|
111723
|
-
* tolerant of forged tokens because an unverified attacker still ends up
|
|
111724
|
-
* in their own bucket (and 401s at the route handler).
|
|
112311
|
+
* Access resolution runs first. Verified tokens key by stable principal;
|
|
112312
|
+
* missing or invalid credentials consume the anonymous IP bucket.
|
|
111725
112313
|
*/
|
|
111726
112314
|
function registerRateLimit(app, deps) {
|
|
111727
112315
|
const config = "check" in deps ? { limiter: deps } : deps;
|
|
111728
112316
|
app.addHook("preHandler", async (request, reply) => {
|
|
111729
112317
|
const datalakeId = extractDatalakeId(request);
|
|
111730
112318
|
if (!datalakeId) return;
|
|
111731
|
-
const override =
|
|
112319
|
+
const override = resolveOverride(request);
|
|
111732
112320
|
const { subject, authenticated } = resolveSubject(request);
|
|
111733
112321
|
const decision = config.limiter.check({
|
|
111734
112322
|
datalakeId,
|
|
@@ -111748,22 +112336,18 @@ function extractDatalakeId(request) {
|
|
|
111748
112336
|
return typeof value === "string" ? value : void 0;
|
|
111749
112337
|
}
|
|
111750
112338
|
function resolveSubject(request) {
|
|
111751
|
-
const
|
|
111752
|
-
if (
|
|
111753
|
-
|
|
111754
|
-
|
|
111755
|
-
|
|
111756
|
-
authenticated: true
|
|
111757
|
-
};
|
|
111758
|
-
}
|
|
112339
|
+
const principal = request.datalakeAccess?.principal;
|
|
112340
|
+
if (principal !== void 0) return {
|
|
112341
|
+
subject: `auth:${principal}`,
|
|
112342
|
+
authenticated: true
|
|
112343
|
+
};
|
|
111759
112344
|
return {
|
|
111760
112345
|
subject: `anonymous:${request.ip}`,
|
|
111761
112346
|
authenticated: false
|
|
111762
112347
|
};
|
|
111763
112348
|
}
|
|
111764
|
-
|
|
111765
|
-
|
|
111766
|
-
return (await store.findById(datalakeId))?.config.rateLimits;
|
|
112349
|
+
function resolveOverride(request) {
|
|
112350
|
+
return request.datalakeAccess?.descriptor?.config.rateLimits;
|
|
111767
112351
|
}
|
|
111768
112352
|
//#endregion
|
|
111769
112353
|
//#region src/rateLimit/TokenBucketRateLimiter.ts
|
|
@@ -111859,119 +112443,11 @@ function registerPlaneHealthRoute(app, version) {
|
|
|
111859
112443
|
});
|
|
111860
112444
|
}
|
|
111861
112445
|
//#endregion
|
|
111862
|
-
//#region src/auth/accessContext.ts
|
|
111863
|
-
const ROLE_RANK = {
|
|
111864
|
-
viewer: 1,
|
|
111865
|
-
runner: 2
|
|
111866
|
-
};
|
|
111867
|
-
/**
|
|
111868
|
-
* Resolves whether a request may proceed. Order of precedence:
|
|
111869
|
-
*
|
|
111870
|
-
* 1. A valid bearer token scoped to this datalake → authenticated, uses the
|
|
111871
|
-
* role encoded in the JWT.
|
|
111872
|
-
* 2. No token but the datalake has a `public` ACL grant with at least the
|
|
111873
|
-
* required role → unauthenticated public access.
|
|
111874
|
-
*
|
|
111875
|
-
* Any other state is an error: 401 for bad/missing creds on a private
|
|
111876
|
-
* datalake, 403 for insufficient role.
|
|
111877
|
-
*/
|
|
111878
|
-
async function resolveAccess(input) {
|
|
111879
|
-
const descriptor = await input.controlStore.findById(input.datalakeId);
|
|
111880
|
-
if (!descriptor) throw new PlaneHttpError("not_found", `Datalake not found: ${input.datalakeId}`);
|
|
111881
|
-
const tokenValue = extractBearer(input.authorizationHeader);
|
|
111882
|
-
if (tokenValue) {
|
|
111883
|
-
if (isWalletJwt(tokenValue)) {
|
|
111884
|
-
const result = await resolveWalletJwt(tokenValue, descriptor);
|
|
111885
|
-
ensureRole(result.role, input.requiredRole);
|
|
111886
|
-
return {
|
|
111887
|
-
descriptor,
|
|
111888
|
-
role: result.role,
|
|
111889
|
-
authenticated: true,
|
|
111890
|
-
token: result.token
|
|
111891
|
-
};
|
|
111892
|
-
}
|
|
111893
|
-
const payload = verifyBearer(tokenValue, input.signingSecret, descriptor.id);
|
|
111894
|
-
ensureRole(payload.role, input.requiredRole);
|
|
111895
|
-
return {
|
|
111896
|
-
descriptor,
|
|
111897
|
-
role: payload.role,
|
|
111898
|
-
authenticated: true,
|
|
111899
|
-
token: payload
|
|
111900
|
-
};
|
|
111901
|
-
}
|
|
111902
|
-
const publicGrant = descriptor.acl.find((entry) => isPublicPrincipal(entry.principal));
|
|
111903
|
-
if (publicGrant) {
|
|
111904
|
-
ensureRole(publicGrant.role, input.requiredRole);
|
|
111905
|
-
return {
|
|
111906
|
-
descriptor,
|
|
111907
|
-
role: publicGrant.role,
|
|
111908
|
-
authenticated: false
|
|
111909
|
-
};
|
|
111910
|
-
}
|
|
111911
|
-
throw new PlaneHttpError("unauthorized", "Authentication required");
|
|
111912
|
-
}
|
|
111913
|
-
function extractBearer(authorization) {
|
|
111914
|
-
if (!authorization) return void 0;
|
|
111915
|
-
if (!authorization.toLowerCase().startsWith("bearer ")) return void 0;
|
|
111916
|
-
return authorization.slice(7).trim();
|
|
111917
|
-
}
|
|
111918
|
-
function verifyBearer(token, signingSecret, datalakeId) {
|
|
111919
|
-
try {
|
|
111920
|
-
const payload = verifyToken({
|
|
111921
|
-
token,
|
|
111922
|
-
signingSecret
|
|
111923
|
-
});
|
|
111924
|
-
if (payload.dl !== datalakeId) throw new PlaneHttpError("forbidden", "Token is not scoped to this datalake");
|
|
111925
|
-
return payload;
|
|
111926
|
-
} catch (error) {
|
|
111927
|
-
if (error instanceof PlaneHttpError) throw error;
|
|
111928
|
-
throw new PlaneHttpError("unauthorized", error instanceof Error ? error.message : "Invalid token");
|
|
111929
|
-
}
|
|
111930
|
-
}
|
|
111931
|
-
function ensureRole(granted, required) {
|
|
111932
|
-
if (ROLE_RANK[granted] < ROLE_RANK[required]) throw new PlaneHttpError("forbidden", `Role ${granted} is insufficient; ${required} required`);
|
|
111933
|
-
}
|
|
111934
|
-
function isWalletJwt(token) {
|
|
111935
|
-
try {
|
|
111936
|
-
return decodeJwt(token).header.alg === JwtAlg.ES256K;
|
|
111937
|
-
} catch {
|
|
111938
|
-
return false;
|
|
111939
|
-
}
|
|
111940
|
-
}
|
|
111941
|
-
async function resolveWalletJwt(token, descriptor) {
|
|
111942
|
-
const result = await verifyJwt(token, { audience: descriptor.id });
|
|
111943
|
-
if (!result.ok) throw new PlaneHttpError("unauthorized", `Wallet JWT verification failed: ${result.reasons.join("; ")}`);
|
|
111944
|
-
const callerAddress = normalizeAddress(result.payload.iss);
|
|
111945
|
-
const ownerAddress = normalizeAddress(descriptor.ownerId);
|
|
111946
|
-
let role;
|
|
111947
|
-
if (callerAddress === ownerAddress) role = "runner";
|
|
111948
|
-
else role = descriptor.acl.find((e) => !isPublicPrincipal(e.principal) && normalizeAddress(e.principal) === callerAddress)?.role;
|
|
111949
|
-
if (!role) throw new PlaneHttpError("forbidden", `Address ${callerAddress} has no access to this datalake`);
|
|
111950
|
-
const synthesized = {
|
|
111951
|
-
sub: callerAddress,
|
|
111952
|
-
dl: descriptor.id,
|
|
111953
|
-
role,
|
|
111954
|
-
iat: result.payload.iat,
|
|
111955
|
-
exp: result.payload.exp,
|
|
111956
|
-
jti: `wallet:${callerAddress}:${result.payload.iat}`
|
|
111957
|
-
};
|
|
111958
|
-
return {
|
|
111959
|
-
role,
|
|
111960
|
-
token: synthesized
|
|
111961
|
-
};
|
|
111962
|
-
}
|
|
111963
|
-
//#endregion
|
|
111964
112446
|
//#region src/routes/payloadRoutes.ts
|
|
111965
112447
|
const PLANE_PREFIX$1 = `/v1/datalakes/:id`;
|
|
111966
112448
|
function registerPayloadRoutes(app, deps) {
|
|
111967
112449
|
app.post(`${PLANE_PREFIX$1}/insert`, async (request, reply) => {
|
|
111968
|
-
const access =
|
|
111969
|
-
controlStore: deps.controlStore,
|
|
111970
|
-
signingSecret: deps.signingSecret,
|
|
111971
|
-
datalakeId: request.params.id,
|
|
111972
|
-
authorizationHeader: request.headers.authorization,
|
|
111973
|
-
requiredRole: "runner"
|
|
111974
|
-
});
|
|
112450
|
+
const access = requireAccessContext(request, "payload:append");
|
|
111975
112451
|
const payloads = normalizeInsertBody(request.body);
|
|
111976
112452
|
if (payloads.length === 0) {
|
|
111977
112453
|
setInsertSummaryHeaders(reply, 0, []);
|
|
@@ -111984,38 +112460,20 @@ function registerPayloadRoutes(app, deps) {
|
|
|
111984
112460
|
return result.inserted;
|
|
111985
112461
|
});
|
|
111986
112462
|
app.post(`${PLANE_PREFIX$1}/get`, async (request) => {
|
|
111987
|
-
const access =
|
|
111988
|
-
controlStore: deps.controlStore,
|
|
111989
|
-
signingSecret: deps.signingSecret,
|
|
111990
|
-
datalakeId: request.params.id,
|
|
111991
|
-
authorizationHeader: request.headers.authorization,
|
|
111992
|
-
requiredRole: "viewer"
|
|
111993
|
-
});
|
|
112463
|
+
const access = requireAccessContext(request, "payload:read");
|
|
111994
112464
|
const hashes = parseHashesBody(request.body);
|
|
111995
112465
|
if (hashes.length === 0) throw new PlaneHttpError("bad_request", "Body must be a non-empty array of hashes");
|
|
111996
112466
|
if (hashes.length > deps.maxPageSize) throw new PlaneHttpError("unprocessable", `Cannot request more than ${deps.maxPageSize} hashes per call`);
|
|
111997
112467
|
return deps.payloadStore.get(access.descriptor.id, hashes);
|
|
111998
112468
|
});
|
|
111999
112469
|
app.get(`${PLANE_PREFIX$1}/get/:hash`, async (request) => {
|
|
112000
|
-
const access =
|
|
112001
|
-
controlStore: deps.controlStore,
|
|
112002
|
-
signingSecret: deps.signingSecret,
|
|
112003
|
-
datalakeId: request.params.id,
|
|
112004
|
-
authorizationHeader: request.headers.authorization,
|
|
112005
|
-
requiredRole: "viewer"
|
|
112006
|
-
});
|
|
112470
|
+
const access = requireAccessContext(request, "payload:read");
|
|
112007
112471
|
const [payload] = await deps.payloadStore.get(access.descriptor.id, [request.params.hash]);
|
|
112008
112472
|
if (!payload) throw new PlaneHttpError("not_found", `Payload not found: ${request.params.hash}`);
|
|
112009
112473
|
return payload;
|
|
112010
112474
|
});
|
|
112011
112475
|
app.get(`${PLANE_PREFIX$1}/next`, async (request, reply) => {
|
|
112012
|
-
const access =
|
|
112013
|
-
controlStore: deps.controlStore,
|
|
112014
|
-
signingSecret: deps.signingSecret,
|
|
112015
|
-
datalakeId: request.params.id,
|
|
112016
|
-
authorizationHeader: request.headers.authorization,
|
|
112017
|
-
requiredRole: "viewer"
|
|
112018
|
-
});
|
|
112476
|
+
const access = requireAccessContext(request, "payload:read");
|
|
112019
112477
|
const options = {
|
|
112020
112478
|
limit: request.query.limit === void 0 ? void 0 : Number(request.query.limit),
|
|
112021
112479
|
cursor: request.query.cursor,
|
|
@@ -112025,35 +112483,17 @@ function registerPayloadRoutes(app, deps) {
|
|
|
112025
112483
|
return runNext(deps, access.descriptor.id, options, reply);
|
|
112026
112484
|
});
|
|
112027
112485
|
app.post(`${PLANE_PREFIX$1}/next`, async (request, reply) => {
|
|
112028
|
-
const access =
|
|
112029
|
-
controlStore: deps.controlStore,
|
|
112030
|
-
signingSecret: deps.signingSecret,
|
|
112031
|
-
datalakeId: request.params.id,
|
|
112032
|
-
authorizationHeader: request.headers.authorization,
|
|
112033
|
-
requiredRole: "viewer"
|
|
112034
|
-
});
|
|
112486
|
+
const access = requireAccessContext(request, "payload:read");
|
|
112035
112487
|
const options = request.body ?? {};
|
|
112036
112488
|
return runNext(deps, access.descriptor.id, options, reply);
|
|
112037
112489
|
});
|
|
112038
112490
|
app.delete(`${PLANE_PREFIX$1}/delete/:hash`, async (request, reply) => {
|
|
112039
|
-
const access =
|
|
112040
|
-
controlStore: deps.controlStore,
|
|
112041
|
-
signingSecret: deps.signingSecret,
|
|
112042
|
-
datalakeId: request.params.id,
|
|
112043
|
-
authorizationHeader: request.headers.authorization,
|
|
112044
|
-
requiredRole: "runner"
|
|
112045
|
-
});
|
|
112491
|
+
const access = requireAccessContext(request, "payload:delete");
|
|
112046
112492
|
if (!await deps.payloadStore.delete(access.descriptor.id, request.params.hash)) throw new PlaneHttpError("not_found", `Payload not found: ${request.params.hash}`);
|
|
112047
112493
|
reply.code(204).send();
|
|
112048
112494
|
});
|
|
112049
112495
|
app.post(`${PLANE_PREFIX$1}/clear`, async (request) => {
|
|
112050
|
-
const access =
|
|
112051
|
-
controlStore: deps.controlStore,
|
|
112052
|
-
signingSecret: deps.signingSecret,
|
|
112053
|
-
datalakeId: request.params.id,
|
|
112054
|
-
authorizationHeader: request.headers.authorization,
|
|
112055
|
-
requiredRole: "runner"
|
|
112056
|
-
});
|
|
112496
|
+
const access = requireAccessContext(request, "payload:clear");
|
|
112057
112497
|
return { removed: await deps.payloadStore.clear(access.descriptor.id) };
|
|
112058
112498
|
});
|
|
112059
112499
|
}
|
|
@@ -112127,13 +112567,7 @@ const PLANE_PREFIX = `/v1/datalakes/:id`;
|
|
|
112127
112567
|
*/
|
|
112128
112568
|
function registerUsageRoute(app, deps) {
|
|
112129
112569
|
app.get(`${PLANE_PREFIX}/usage`, async (request) => {
|
|
112130
|
-
const access =
|
|
112131
|
-
controlStore: deps.controlStore,
|
|
112132
|
-
signingSecret: deps.signingSecret,
|
|
112133
|
-
datalakeId: request.params.id,
|
|
112134
|
-
authorizationHeader: request.headers.authorization,
|
|
112135
|
-
requiredRole: "viewer"
|
|
112136
|
-
});
|
|
112570
|
+
const access = requireAccessContext(request, "usage:read");
|
|
112137
112571
|
const descriptor = access.descriptor;
|
|
112138
112572
|
const payloadCount = await deps.payloadStore.count(descriptor.id);
|
|
112139
112573
|
const response = {
|
|
@@ -112142,10 +112576,10 @@ function registerUsageRoute(app, deps) {
|
|
|
112142
112576
|
};
|
|
112143
112577
|
if (deps.rateLimiter) {
|
|
112144
112578
|
const override = descriptor.config.rateLimits;
|
|
112145
|
-
const authSubject = access.
|
|
112579
|
+
const authSubject = access.principal ?? descriptor.ownerId;
|
|
112146
112580
|
const authPeek = deps.rateLimiter.peek({
|
|
112147
112581
|
datalakeId: descriptor.id,
|
|
112148
|
-
subject: `auth
|
|
112582
|
+
subject: `auth:${authSubject}`
|
|
112149
112583
|
}, true, override);
|
|
112150
112584
|
const anonPeek = deps.rateLimiter.peek({
|
|
112151
112585
|
datalakeId: descriptor.id,
|
|
@@ -112173,11 +112607,29 @@ function registerUsageRoute(app, deps) {
|
|
|
112173
112607
|
*/
|
|
112174
112608
|
async function buildPlaneServer(options) {
|
|
112175
112609
|
const app = (0, import_fastify.default)({
|
|
112610
|
+
bodyLimit: 10 * 1024 * 1024,
|
|
112176
112611
|
logger: options.logger ?? false,
|
|
112177
|
-
|
|
112612
|
+
logController: new import_fastify.LogController({ disableRequestLogging: true })
|
|
112613
|
+
});
|
|
112614
|
+
registerExactCors(app, {
|
|
112615
|
+
allowedOrigins: options.corsOrigins ?? [],
|
|
112616
|
+
exposedHeaders: [
|
|
112617
|
+
DATALAKE_HEADER_DUPLICATES,
|
|
112618
|
+
DATALAKE_HEADER_REJECTED,
|
|
112619
|
+
DATALAKE_HEADER_NEXT_CURSOR,
|
|
112620
|
+
"x-ratelimit-limit",
|
|
112621
|
+
"x-ratelimit-remaining",
|
|
112622
|
+
"retry-after"
|
|
112623
|
+
]
|
|
112178
112624
|
});
|
|
112179
112625
|
registerGzipJsonParser(app);
|
|
112180
112626
|
registerPlaneErrorHandler(app);
|
|
112627
|
+
registerAccessContext(app, {
|
|
112628
|
+
allowedOrigins: options.corsOrigins ?? [],
|
|
112629
|
+
controlStore: options.controlStore,
|
|
112630
|
+
isServiceWalletJwtAllowed: options.isServiceWalletJwtAllowed ?? false,
|
|
112631
|
+
signingSecret: options.signingSecret
|
|
112632
|
+
});
|
|
112181
112633
|
let limiter;
|
|
112182
112634
|
if (options.rateLimiter !== false) {
|
|
112183
112635
|
limiter = options.rateLimiter ?? new TokenBucketRateLimiter({
|
|
@@ -112185,26 +112637,19 @@ async function buildPlaneServer(options) {
|
|
|
112185
112637
|
anonymousPerMinute: 20,
|
|
112186
112638
|
burstFactor: 2
|
|
112187
112639
|
});
|
|
112188
|
-
registerRateLimit(app, {
|
|
112189
|
-
limiter,
|
|
112190
|
-
controlStore: options.controlStore
|
|
112191
|
-
});
|
|
112640
|
+
registerRateLimit(app, { limiter });
|
|
112192
112641
|
}
|
|
112193
|
-
registerAudit(app, options.auditLogger ?? new NullAuditLogger());
|
|
112642
|
+
registerAudit(app, options.auditLogger ?? new NullAuditLogger(), options.auditPrincipalSecret ?? options.signingSecret);
|
|
112194
112643
|
registerMetrics(app);
|
|
112195
112644
|
registerPlaneHealthRoute(app, options.version);
|
|
112196
112645
|
const maxInsertBatch = options.maxInsertBatch ?? 1e3;
|
|
112197
112646
|
registerPayloadRoutes(app, {
|
|
112198
|
-
controlStore: options.controlStore,
|
|
112199
112647
|
payloadStore: options.payloadStore,
|
|
112200
|
-
signingSecret: options.signingSecret,
|
|
112201
112648
|
maxInsertBatch,
|
|
112202
112649
|
maxPageSize: options.maxPageSize ?? 1e3
|
|
112203
112650
|
});
|
|
112204
112651
|
registerUsageRoute(app, {
|
|
112205
|
-
controlStore: options.controlStore,
|
|
112206
112652
|
payloadStore: options.payloadStore,
|
|
112207
|
-
signingSecret: options.signingSecret,
|
|
112208
112653
|
rateLimiter: limiter
|
|
112209
112654
|
});
|
|
112210
112655
|
return app;
|
|
@@ -112401,6 +112846,7 @@ const PERSIST_DIR = process.env.DATALAKE_PERSIST_DIR;
|
|
|
112401
112846
|
const AUDIT_LOG = process.env.DATALAKE_AUDIT_LOG;
|
|
112402
112847
|
const AUDIT_DIR = process.env.DATALAKE_AUDIT_DIR;
|
|
112403
112848
|
const AUDIT_MAX_AGE = process.env.DATALAKE_AUDIT_MAX_AGE_DAYS;
|
|
112849
|
+
const CORS_ORIGINS = parseCorsOrigins(process.env.DATALAKE_CORS_ORIGINS);
|
|
112404
112850
|
function makeStores() {
|
|
112405
112851
|
if (!PERSIST_DIR) return {
|
|
112406
112852
|
controlStore: new InMemoryDatalakeStore(),
|
|
@@ -112414,7 +112860,13 @@ function makeStores() {
|
|
|
112414
112860
|
};
|
|
112415
112861
|
}
|
|
112416
112862
|
function makeTokenStore() {
|
|
112417
|
-
const stores = [new SelfSignedWalletJwtTokenStore(DATALAKE_CONTROL_AUDIENCE
|
|
112863
|
+
const stores = [new SelfSignedWalletJwtTokenStore(DATALAKE_CONTROL_AUDIENCE, {
|
|
112864
|
+
browser: {
|
|
112865
|
+
allowedOrigins: CORS_ORIGINS,
|
|
112866
|
+
allowedScopes: DATALAKE_CONTROL_OPERATIONS
|
|
112867
|
+
},
|
|
112868
|
+
isServiceProfileAllowed: true
|
|
112869
|
+
}), new WalletJwtTokenStore(SIGNING_SECRET)];
|
|
112418
112870
|
const labels = [`self-signed-jwt(aud=${DATALAKE_CONTROL_AUDIENCE})`, "session-jwt"];
|
|
112419
112871
|
const staticTokens = process.env.DATALAKE_CONTROL_TOKENS;
|
|
112420
112872
|
if (staticTokens && staticTokens.length > 0) {
|
|
@@ -112456,7 +112908,8 @@ async function main() {
|
|
|
112456
112908
|
datalakeOriginUrl: DATALAKE_ORIGIN_URL,
|
|
112457
112909
|
tokenSigningSecret: SIGNING_SECRET,
|
|
112458
112910
|
version: VERSION,
|
|
112459
|
-
logger: { level: process.env.LOG_LEVEL ?? "info" }
|
|
112911
|
+
logger: { level: process.env.LOG_LEVEL ?? "info" },
|
|
112912
|
+
corsOrigins: CORS_ORIGINS
|
|
112460
112913
|
});
|
|
112461
112914
|
const { logger: auditLogger, description: auditDescription } = makeAuditLogger();
|
|
112462
112915
|
const plane = await buildPlaneServer({
|
|
@@ -112465,7 +112918,9 @@ async function main() {
|
|
|
112465
112918
|
signingSecret: SIGNING_SECRET,
|
|
112466
112919
|
version: VERSION,
|
|
112467
112920
|
logger: { level: process.env.LOG_LEVEL ?? "info" },
|
|
112468
|
-
auditLogger
|
|
112921
|
+
auditLogger,
|
|
112922
|
+
corsOrigins: CORS_ORIGINS,
|
|
112923
|
+
isServiceWalletJwtAllowed: true
|
|
112469
112924
|
});
|
|
112470
112925
|
await control.listen({
|
|
112471
112926
|
port: CONTROL_PORT,
|