@medialane/sdk 0.198.0 → 0.199.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/README.md +85 -39
- package/dist/chains-CmwjZVU7.d.cts +85 -0
- package/dist/chains-CmwjZVU7.d.ts +85 -0
- package/dist/chunk-3HPMEDYH.cjs +967 -0
- package/dist/chunk-3HPMEDYH.cjs.map +1 -0
- package/dist/{chunk-3ME5YKTW.js → chunk-5KY6P434.js} +2 -2
- package/dist/chunk-5KY6P434.js.map +1 -0
- package/dist/chunk-F7W6IWTL.js +660 -0
- package/dist/chunk-F7W6IWTL.js.map +1 -0
- package/dist/chunk-GIBIHGSV.js +916 -0
- package/dist/chunk-GIBIHGSV.js.map +1 -0
- package/dist/chunk-GV4A2WNO.js +444 -0
- package/dist/chunk-GV4A2WNO.js.map +1 -0
- package/dist/{chunk-7EYJC5CB.cjs → chunk-H2NJB3AK.cjs} +2 -2
- package/dist/chunk-H2NJB3AK.cjs.map +1 -0
- package/dist/chunk-KGAGWULG.js +932 -0
- package/dist/chunk-KGAGWULG.js.map +1 -0
- package/dist/chunk-MEJ47LC3.cjs +463 -0
- package/dist/chunk-MEJ47LC3.cjs.map +1 -0
- package/dist/chunk-W76AZ5WC.cjs +376 -0
- package/dist/chunk-W76AZ5WC.cjs.map +1 -0
- package/dist/chunk-WRBXQPRL.js +323 -0
- package/dist/chunk-WRBXQPRL.js.map +1 -0
- package/dist/chunk-ZLMIHQWM.cjs +975 -0
- package/dist/chunk-ZLMIHQWM.cjs.map +1 -0
- package/dist/chunk-ZVANOWIB.cjs +688 -0
- package/dist/chunk-ZVANOWIB.cjs.map +1 -0
- package/dist/contract/index.cjs +88 -261
- package/dist/contract/index.cjs.map +1 -1
- package/dist/contract/index.d.cts +139 -4
- package/dist/contract/index.d.ts +139 -4
- package/dist/contract/index.js +4 -220
- package/dist/contract/index.js.map +1 -1
- package/dist/evm/index.cjs +3 -3
- package/dist/evm/index.d.cts +2 -1
- package/dist/evm/index.d.ts +2 -1
- package/dist/evm/index.js +1 -1
- package/dist/fetch-Cx9Pr7-B.d.cts +3 -0
- package/dist/fetch-Cx9Pr7-B.d.ts +3 -0
- package/dist/index.cjs +177 -833
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -131
- package/dist/index.d.ts +53 -131
- package/dist/index.js +6 -659
- package/dist/index.js.map +1 -1
- package/dist/passkey-errors-Bu6I_5rn.d.ts +1041 -0
- package/dist/passkey-errors-D0XNQ3_g.d.cts +1041 -0
- package/dist/provisioning-CwRkCN8D.d.cts +14 -0
- package/dist/provisioning-CwRkCN8D.d.ts +14 -0
- package/dist/{provisioning-BUYiU8Tr.d.ts → responses-BLpM_cH_.d.cts} +40 -48
- package/dist/{provisioning-BUYiU8Tr.d.cts → responses-BLpM_cH_.d.ts} +40 -48
- package/dist/server/index.cjs +117 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +98 -0
- package/dist/server/index.d.ts +98 -0
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -0
- package/dist/services-C9CkOsYi.d.cts +728 -0
- package/dist/services-CI62hzNf.d.ts +728 -0
- package/dist/solana/index.cjs +2 -2
- package/dist/solana/index.d.cts +2 -1
- package/dist/solana/index.d.ts +2 -1
- package/dist/solana/index.js +1 -1
- package/dist/starknet/index.cjs +898 -1729
- package/dist/starknet/index.cjs.map +1 -1
- package/dist/starknet/index.d.cts +98 -467
- package/dist/starknet/index.d.ts +98 -467
- package/dist/starknet/index.js +704 -1680
- package/dist/starknet/index.js.map +1 -1
- package/dist/stellar/index.cjs +2 -2
- package/dist/stellar/index.d.cts +2 -1
- package/dist/stellar/index.d.ts +2 -1
- package/dist/stellar/index.js +1 -1
- package/dist/types-BW6ktwcT.d.ts +71 -0
- package/dist/types-BftPOVEJ.d.cts +71 -0
- package/dist/wallet/index.cjs +240 -0
- package/dist/wallet/index.cjs.map +1 -0
- package/dist/wallet/index.d.cts +319 -0
- package/dist/wallet/index.d.ts +319 -0
- package/dist/wallet/index.js +7 -0
- package/dist/wallet/index.js.map +1 -0
- package/package.json +13 -2
- package/dist/chunk-2R7XBA34.cjs +0 -1938
- package/dist/chunk-2R7XBA34.cjs.map +0 -1
- package/dist/chunk-3ME5YKTW.js.map +0 -1
- package/dist/chunk-6WVUUBFI.js +0 -1853
- package/dist/chunk-6WVUUBFI.js.map +0 -1
- package/dist/chunk-7EYJC5CB.cjs.map +0 -1
- package/dist/errors-CZm-NZK0.d.cts +0 -1754
- package/dist/errors-h6H6cj3Q.d.ts +0 -1754
- package/dist/types-DC4jNPAF.d.cts +0 -153
- package/dist/types-DC4jNPAF.d.ts +0 -153
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var hmac_js = require('@noble/hashes/hmac.js');
|
|
4
|
+
var sha2_js = require('@noble/hashes/sha2.js');
|
|
5
|
+
var base = require('@scure/base');
|
|
6
|
+
|
|
7
|
+
// src/server/origin.ts
|
|
8
|
+
function isSameOrigin(req) {
|
|
9
|
+
const origin = req.headers.get("origin");
|
|
10
|
+
if (!origin) return true;
|
|
11
|
+
const host = req.headers.get("host");
|
|
12
|
+
try {
|
|
13
|
+
return new URL(origin).host === host;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/server/app-gateway.ts
|
|
20
|
+
var ACCOUNT_SESSION_COOKIE = "ml_account_session";
|
|
21
|
+
var SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
|
|
22
|
+
var SIGN_IN_PATHS = /* @__PURE__ */ new Set(["auth/email/register-account", "auth/email/verify-code", "auth/siws/verify"]);
|
|
23
|
+
var EDGE_CACHE_CONTROL = "public, s-maxage=30, stale-while-revalidate=120";
|
|
24
|
+
var DROPPED_REQUEST_HEADERS = /* @__PURE__ */ new Set([
|
|
25
|
+
"connection",
|
|
26
|
+
"keep-alive",
|
|
27
|
+
"proxy-authenticate",
|
|
28
|
+
"proxy-authorization",
|
|
29
|
+
"te",
|
|
30
|
+
"trailers",
|
|
31
|
+
"transfer-encoding",
|
|
32
|
+
"upgrade",
|
|
33
|
+
"host",
|
|
34
|
+
"content-length",
|
|
35
|
+
"accept-encoding",
|
|
36
|
+
"cookie",
|
|
37
|
+
"x-api-key",
|
|
38
|
+
"x-app-id",
|
|
39
|
+
"x-app-secret",
|
|
40
|
+
"x-account-session",
|
|
41
|
+
"authorization"
|
|
42
|
+
]);
|
|
43
|
+
var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
|
|
44
|
+
"connection",
|
|
45
|
+
"keep-alive",
|
|
46
|
+
"transfer-encoding",
|
|
47
|
+
"content-length",
|
|
48
|
+
"content-encoding",
|
|
49
|
+
"set-cookie"
|
|
50
|
+
]);
|
|
51
|
+
function readCookie(req, name) {
|
|
52
|
+
const raw = req.headers.get("cookie");
|
|
53
|
+
if (!raw) return null;
|
|
54
|
+
for (const part of raw.split(";")) {
|
|
55
|
+
const eq = part.indexOf("=");
|
|
56
|
+
if (eq !== -1 && part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim() || null;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function sessionCookie(value, maxAge = SESSION_MAX_AGE_SECONDS) {
|
|
61
|
+
const secure = process.env.NODE_ENV === "production" ? "; Secure" : "";
|
|
62
|
+
return `${ACCOUNT_SESSION_COOKIE}=${value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}${secure}`;
|
|
63
|
+
}
|
|
64
|
+
function json(body, status) {
|
|
65
|
+
return Response.json(body, { status });
|
|
66
|
+
}
|
|
67
|
+
function createAppGateway(config) {
|
|
68
|
+
const doFetch = config.fetchImpl ?? fetch;
|
|
69
|
+
const base = config.backendUrl.replace(/\/$/, "");
|
|
70
|
+
return async function appGateway(req, path) {
|
|
71
|
+
if (!isSameOrigin(req)) return json({ error: "Cross-origin requests are not allowed" }, 403);
|
|
72
|
+
if (!config.apiKey) return json({ error: "Service unavailable" }, 500);
|
|
73
|
+
if (path.some((s) => s === "." || s === ".." || s === "")) return json({ error: "Invalid path" }, 400);
|
|
74
|
+
const joined = path.join("/");
|
|
75
|
+
const method = req.method.toUpperCase();
|
|
76
|
+
if (!(config.allow[method] ?? []).some((re) => re.test(joined))) {
|
|
77
|
+
return json({ error: `Path not allowed: ${method} /v1/${joined}` }, 403);
|
|
78
|
+
}
|
|
79
|
+
const headers2 = new Headers();
|
|
80
|
+
req.headers.forEach((v, k) => {
|
|
81
|
+
if (!DROPPED_REQUEST_HEADERS.has(k.toLowerCase())) headers2.set(k, v);
|
|
82
|
+
});
|
|
83
|
+
headers2.set("x-api-key", config.apiKey);
|
|
84
|
+
headers2.set("x-app-id", config.appId);
|
|
85
|
+
if (config.appSecret) headers2.set("x-app-secret", config.appSecret);
|
|
86
|
+
const session = readCookie(req, ACCOUNT_SESSION_COOKIE);
|
|
87
|
+
if (session) headers2.set("x-account-session", session);
|
|
88
|
+
const search = new URL(req.url).search;
|
|
89
|
+
const target = `${base}/v1/${path.map(encodeURIComponent).join("/")}${search}`;
|
|
90
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
91
|
+
let res;
|
|
92
|
+
try {
|
|
93
|
+
res = await doFetch(target, {
|
|
94
|
+
method,
|
|
95
|
+
headers: headers2,
|
|
96
|
+
body: hasBody ? await req.arrayBuffer() : void 0,
|
|
97
|
+
cache: "no-store",
|
|
98
|
+
redirect: "manual",
|
|
99
|
+
signal: AbortSignal.timeout(config.timeoutMs ?? 55e3)
|
|
100
|
+
});
|
|
101
|
+
} catch (e) {
|
|
102
|
+
if (e instanceof DOMException && e.name === "TimeoutError") {
|
|
103
|
+
return json({ error: "The service took too long to respond" }, 504);
|
|
104
|
+
}
|
|
105
|
+
return json({ error: "Service unavailable" }, 502);
|
|
106
|
+
}
|
|
107
|
+
const out = new Headers();
|
|
108
|
+
res.headers.forEach((v, k) => {
|
|
109
|
+
if (!DROPPED_RESPONSE_HEADERS.has(k.toLowerCase())) out.set(k, v);
|
|
110
|
+
});
|
|
111
|
+
if (method === "POST" && res.ok && SIGN_IN_PATHS.has(joined)) {
|
|
112
|
+
const text = await res.text();
|
|
113
|
+
let body = null;
|
|
114
|
+
try {
|
|
115
|
+
body = JSON.parse(text);
|
|
116
|
+
} catch {
|
|
117
|
+
body = null;
|
|
118
|
+
}
|
|
119
|
+
if (!body) return new Response(text, { status: res.status, headers: out });
|
|
120
|
+
const token = typeof body.accountToken === "string" ? body.accountToken : null;
|
|
121
|
+
delete body.accountToken;
|
|
122
|
+
delete body.token;
|
|
123
|
+
out.set("content-type", "application/json");
|
|
124
|
+
if (token) out.append("set-cookie", sessionCookie(token));
|
|
125
|
+
return new Response(JSON.stringify(body), { status: res.status, headers: out });
|
|
126
|
+
}
|
|
127
|
+
if (method === "GET" && res.ok && !session && (config.cacheableGets ?? []).some((re) => re.test(joined))) {
|
|
128
|
+
out.set("cache-control", EDGE_CACHE_CONTROL);
|
|
129
|
+
}
|
|
130
|
+
return new Response(res.body, { status: res.status, headers: out });
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/server/app-session.ts
|
|
135
|
+
function signedOut() {
|
|
136
|
+
const res = Response.json(null);
|
|
137
|
+
res.headers.append("set-cookie", sessionCookie("", 0));
|
|
138
|
+
return res;
|
|
139
|
+
}
|
|
140
|
+
function createAppSessionHandler(config) {
|
|
141
|
+
const doFetch = config.fetchImpl ?? fetch;
|
|
142
|
+
const base = config.backendUrl.replace(/\/$/, "");
|
|
143
|
+
return {
|
|
144
|
+
async GET(req) {
|
|
145
|
+
const session = readCookie(req, ACCOUNT_SESSION_COOKIE);
|
|
146
|
+
if (!session) return Response.json(null);
|
|
147
|
+
const res = await doFetch(`${base}/v1/users/me`, {
|
|
148
|
+
headers: {
|
|
149
|
+
"x-api-key": config.apiKey ?? "",
|
|
150
|
+
"x-app-id": config.appId,
|
|
151
|
+
...config.appSecret ? { "x-app-secret": config.appSecret } : {},
|
|
152
|
+
"x-account-session": session
|
|
153
|
+
},
|
|
154
|
+
cache: "no-store"
|
|
155
|
+
});
|
|
156
|
+
if (res.status === 401 || res.status === 403) return signedOut();
|
|
157
|
+
if (!res.ok) return Response.json({ error: "Session check failed" }, { status: 502 });
|
|
158
|
+
return Response.json(await res.json());
|
|
159
|
+
},
|
|
160
|
+
async DELETE() {
|
|
161
|
+
return signedOut();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/server/ssrf-guard.ts
|
|
167
|
+
var ALLOWED_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
168
|
+
"image/jpeg",
|
|
169
|
+
"image/jpg",
|
|
170
|
+
"image/png",
|
|
171
|
+
"image/gif",
|
|
172
|
+
"image/webp",
|
|
173
|
+
"image/svg+xml",
|
|
174
|
+
"image/avif",
|
|
175
|
+
"image/bmp",
|
|
176
|
+
"image/tiff"
|
|
177
|
+
]);
|
|
178
|
+
var MAX_IMAGE_REDIRECTS = 5;
|
|
179
|
+
var MAX_IMAGE_PROXY_BYTES = 15 * 1024 * 1024;
|
|
180
|
+
function parseIpv4(ip) {
|
|
181
|
+
const parts = ip.split(".");
|
|
182
|
+
if (parts.length !== 4) return null;
|
|
183
|
+
const bytes = [];
|
|
184
|
+
for (const part of parts) {
|
|
185
|
+
if (!/^\d{1,3}$/.test(part)) return null;
|
|
186
|
+
const n = Number(part);
|
|
187
|
+
if (n > 255) return null;
|
|
188
|
+
bytes.push(n);
|
|
189
|
+
}
|
|
190
|
+
return bytes;
|
|
191
|
+
}
|
|
192
|
+
function isPrivateIpv4(bytes) {
|
|
193
|
+
const a = bytes[0];
|
|
194
|
+
const b = bytes[1];
|
|
195
|
+
const c = bytes[2];
|
|
196
|
+
if (a === 0) return true;
|
|
197
|
+
if (a === 10) return true;
|
|
198
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
199
|
+
if (a === 127) return true;
|
|
200
|
+
if (a === 169 && b === 254) return true;
|
|
201
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
202
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
203
|
+
if (a === 192 && b === 168) return true;
|
|
204
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
205
|
+
if (a >= 224) return true;
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
function expandIpv6(rawIp) {
|
|
209
|
+
let ip = rawIp;
|
|
210
|
+
let embeddedV4 = null;
|
|
211
|
+
const v4Tail = ip.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
212
|
+
if (v4Tail && ip.includes(":")) {
|
|
213
|
+
embeddedV4 = parseIpv4(v4Tail[1]);
|
|
214
|
+
if (!embeddedV4) return null;
|
|
215
|
+
ip = ip.slice(0, ip.length - v4Tail[1].length) + "0:0";
|
|
216
|
+
}
|
|
217
|
+
const sides = ip.split("::");
|
|
218
|
+
if (sides.length > 2) return null;
|
|
219
|
+
const head = sides[0] ? sides[0].split(":").filter(Boolean) : [];
|
|
220
|
+
const tail = sides.length === 2 && sides[1] ? sides[1].split(":").filter(Boolean) : [];
|
|
221
|
+
let groups;
|
|
222
|
+
if (sides.length === 1) {
|
|
223
|
+
groups = head;
|
|
224
|
+
if (groups.length !== 8) return null;
|
|
225
|
+
} else {
|
|
226
|
+
const missing = 8 - head.length - tail.length;
|
|
227
|
+
if (missing < 0) return null;
|
|
228
|
+
groups = [...head, ...Array(missing).fill("0"), ...tail];
|
|
229
|
+
}
|
|
230
|
+
if (groups.length !== 8) return null;
|
|
231
|
+
const bytes = [];
|
|
232
|
+
for (const group of groups) {
|
|
233
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(group)) return null;
|
|
234
|
+
const n = parseInt(group, 16);
|
|
235
|
+
bytes.push(n >> 8 & 255, n & 255);
|
|
236
|
+
}
|
|
237
|
+
if (embeddedV4) {
|
|
238
|
+
bytes[12] = embeddedV4[0];
|
|
239
|
+
bytes[13] = embeddedV4[1];
|
|
240
|
+
bytes[14] = embeddedV4[2];
|
|
241
|
+
bytes[15] = embeddedV4[3];
|
|
242
|
+
}
|
|
243
|
+
return bytes;
|
|
244
|
+
}
|
|
245
|
+
function isPrivateIpv6(bytes) {
|
|
246
|
+
if (bytes.every((b) => b === 0)) return true;
|
|
247
|
+
if (bytes.slice(0, 15).every((b) => b === 0) && bytes[15] === 1) return true;
|
|
248
|
+
if ((bytes[0] & 254) === 252) return true;
|
|
249
|
+
if (bytes[0] === 254 && (bytes[1] & 192) === 128) return true;
|
|
250
|
+
if (bytes.slice(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255) {
|
|
251
|
+
return isPrivateIpv4(bytes.slice(12));
|
|
252
|
+
}
|
|
253
|
+
if (bytes.slice(0, 12).every((b) => b === 0)) return isPrivateIpv4(bytes.slice(12));
|
|
254
|
+
if (bytes[0] === 0 && bytes[1] === 100 && bytes[2] === 255 && bytes[3] === 155 && bytes.slice(4, 12).every((b) => b === 0)) {
|
|
255
|
+
return isPrivateIpv4(bytes.slice(12));
|
|
256
|
+
}
|
|
257
|
+
if (bytes[0] === 32 && bytes[1] === 2) return isPrivateIpv4(bytes.slice(2, 6));
|
|
258
|
+
if (bytes[0] === 254 && (bytes[1] & 192) === 192) return true;
|
|
259
|
+
if (bytes[0] === 255) return true;
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
function parseNumber(part) {
|
|
263
|
+
if (/^0x[0-9a-f]+$/i.test(part)) return parseInt(part.slice(2), 16);
|
|
264
|
+
if (/^0[0-7]+$/.test(part)) return parseInt(part, 8);
|
|
265
|
+
if (/^\d+$/.test(part)) return parseInt(part, 10);
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function normalizeNumericHostname(host) {
|
|
269
|
+
const parts = host.trim().split(".");
|
|
270
|
+
if (parts.length < 1 || parts.length > 4) return null;
|
|
271
|
+
const values = [];
|
|
272
|
+
for (const part of parts) {
|
|
273
|
+
const value = parseNumber(part);
|
|
274
|
+
if (value === null || value < 0) return null;
|
|
275
|
+
values.push(value);
|
|
276
|
+
}
|
|
277
|
+
const leading = values.slice(0, -1);
|
|
278
|
+
if (leading.some((v) => v > 255)) return null;
|
|
279
|
+
const tail = values[values.length - 1];
|
|
280
|
+
const tailBytes = 4 - leading.length;
|
|
281
|
+
if (tail > 2 ** (8 * tailBytes) - 1) return null;
|
|
282
|
+
const bytes = [...leading];
|
|
283
|
+
for (let i = tailBytes - 1; i >= 0; i--) bytes.push(tail >>> 8 * i & 255);
|
|
284
|
+
return bytes.join(".");
|
|
285
|
+
}
|
|
286
|
+
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
287
|
+
"localhost",
|
|
288
|
+
"metadata.google.internal",
|
|
289
|
+
"metadata.azure.internal"
|
|
290
|
+
]);
|
|
291
|
+
function isPrivateHost(hostname) {
|
|
292
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
293
|
+
if (BLOCKED_HOSTNAMES.has(host)) return true;
|
|
294
|
+
if (host.endsWith(".local") || host.endsWith(".internal") || host.endsWith(".localhost")) return true;
|
|
295
|
+
const candidate = normalizeNumericHostname(host) ?? host;
|
|
296
|
+
const v4 = parseIpv4(candidate);
|
|
297
|
+
if (v4) return isPrivateIpv4(v4);
|
|
298
|
+
const v6 = expandIpv6(candidate);
|
|
299
|
+
if (v6) return isPrivateIpv6(v6);
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
function validateUrl(raw, options = {}) {
|
|
303
|
+
const requireHttps = options.requireHttps ?? true;
|
|
304
|
+
let parsed;
|
|
305
|
+
try {
|
|
306
|
+
parsed = new URL(raw);
|
|
307
|
+
} catch {
|
|
308
|
+
return { error: "Invalid url", status: 400 };
|
|
309
|
+
}
|
|
310
|
+
if (requireHttps && parsed.protocol !== "https:") {
|
|
311
|
+
return { error: "Only https URLs allowed", status: 400 };
|
|
312
|
+
}
|
|
313
|
+
if (!requireHttps && parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
314
|
+
return { error: "Only http and https URLs allowed", status: 400 };
|
|
315
|
+
}
|
|
316
|
+
if (parsed.username || parsed.password) {
|
|
317
|
+
return { error: "URL credentials not allowed", status: 400 };
|
|
318
|
+
}
|
|
319
|
+
if (isPrivateHost(parsed.hostname)) {
|
|
320
|
+
return { error: "URL not allowed", status: 400 };
|
|
321
|
+
}
|
|
322
|
+
return { url: parsed };
|
|
323
|
+
}
|
|
324
|
+
function isPrivateOrInsecureUrl(raw, requireHttps = true) {
|
|
325
|
+
return "error" in validateUrl(raw, { requireHttps });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/server/image-proxy.ts
|
|
329
|
+
var DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; Medialane/1.0; +https://www.medialane.io)";
|
|
330
|
+
function jsonError(message, status) {
|
|
331
|
+
return Response.json({ error: message }, { status });
|
|
332
|
+
}
|
|
333
|
+
async function readBodyWithCap(res, maxBytes) {
|
|
334
|
+
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
335
|
+
if (declared > maxBytes) {
|
|
336
|
+
return { ok: false, error: "Image too large", status: 413 };
|
|
337
|
+
}
|
|
338
|
+
const reader = res.body?.getReader();
|
|
339
|
+
if (!reader) return { ok: false, error: "Empty response body", status: 502 };
|
|
340
|
+
const chunks = [];
|
|
341
|
+
let total = 0;
|
|
342
|
+
for (; ; ) {
|
|
343
|
+
const { done, value } = await reader.read();
|
|
344
|
+
if (done) break;
|
|
345
|
+
if (!value) continue;
|
|
346
|
+
total += value.byteLength;
|
|
347
|
+
if (total > maxBytes) {
|
|
348
|
+
await reader.cancel().catch(() => {
|
|
349
|
+
});
|
|
350
|
+
return { ok: false, error: "Image too large", status: 413 };
|
|
351
|
+
}
|
|
352
|
+
chunks.push(value);
|
|
353
|
+
}
|
|
354
|
+
const body = new Uint8Array(total);
|
|
355
|
+
let offset = 0;
|
|
356
|
+
for (const chunk of chunks) {
|
|
357
|
+
body.set(chunk, offset);
|
|
358
|
+
offset += chunk.byteLength;
|
|
359
|
+
}
|
|
360
|
+
return { ok: true, body };
|
|
361
|
+
}
|
|
362
|
+
function createImageProxyHandler(config) {
|
|
363
|
+
const doFetch = config.fetchImpl ?? fetch;
|
|
364
|
+
const userAgent = config.userAgent ?? DEFAULT_USER_AGENT;
|
|
365
|
+
const backendOrigin = config.backend ? new URL(config.backend.backendUrl).origin : null;
|
|
366
|
+
function headersFor(url) {
|
|
367
|
+
const headers2 = { "User-Agent": userAgent };
|
|
368
|
+
if (!config.backend || url.origin !== backendOrigin) return headers2;
|
|
369
|
+
if (config.backend.apiKey) headers2["x-api-key"] = config.backend.apiKey;
|
|
370
|
+
headers2["x-app-id"] = config.backend.appId;
|
|
371
|
+
if (config.backend.appSecret) headers2["x-app-secret"] = config.backend.appSecret;
|
|
372
|
+
return headers2;
|
|
373
|
+
}
|
|
374
|
+
async function resolvesToPrivateHost(hostname) {
|
|
375
|
+
try {
|
|
376
|
+
const addresses = await config.resolveHostname(hostname);
|
|
377
|
+
if (addresses.length === 0) return true;
|
|
378
|
+
return addresses.some((address) => isPrivateHost(address));
|
|
379
|
+
} catch {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function safeFetch(url, hopsLeft) {
|
|
384
|
+
if (hopsLeft < 0) throw new Error("Too many redirects");
|
|
385
|
+
if (await resolvesToPrivateHost(url.hostname)) {
|
|
386
|
+
throw new Error("Blocked: hostname resolves to a private address");
|
|
387
|
+
}
|
|
388
|
+
const res = await doFetch(url.toString(), {
|
|
389
|
+
redirect: "manual",
|
|
390
|
+
headers: headersFor(url)
|
|
391
|
+
});
|
|
392
|
+
if (res.status >= 300 && res.status < 400) {
|
|
393
|
+
const location = res.headers.get("location");
|
|
394
|
+
if (!location) throw new Error("Redirect with no Location header");
|
|
395
|
+
const next = new URL(location, url);
|
|
396
|
+
const validated = validateUrl(next.toString());
|
|
397
|
+
if ("error" in validated) throw new Error(`Redirect blocked: ${validated.error}`);
|
|
398
|
+
return safeFetch(validated.url, hopsLeft - 1);
|
|
399
|
+
}
|
|
400
|
+
return res;
|
|
401
|
+
}
|
|
402
|
+
return async function handleImageProxy(req) {
|
|
403
|
+
const raw = new URL(req.url).searchParams.get("url");
|
|
404
|
+
if (!raw) return jsonError("Missing url", 400);
|
|
405
|
+
const validated = validateUrl(raw);
|
|
406
|
+
if ("error" in validated) return jsonError(validated.error, validated.status);
|
|
407
|
+
let upstream;
|
|
408
|
+
try {
|
|
409
|
+
const isBackendImage = backendOrigin && /^\/media\/[^/]+\/[^/]+\/[^/]+\/image$/.test(validated.url.pathname);
|
|
410
|
+
upstream = await safeFetch(
|
|
411
|
+
isBackendImage ? new URL(validated.url.pathname, backendOrigin) : validated.url,
|
|
412
|
+
MAX_IMAGE_REDIRECTS
|
|
413
|
+
);
|
|
414
|
+
} catch {
|
|
415
|
+
return jsonError("Failed to fetch image", 502);
|
|
416
|
+
}
|
|
417
|
+
if (!upstream.ok) {
|
|
418
|
+
return jsonError(`Upstream returned ${upstream.status}`, upstream.status);
|
|
419
|
+
}
|
|
420
|
+
const contentType = upstream.headers.get("content-type") ?? "";
|
|
421
|
+
const baseType = contentType.split(";")[0].trim().toLowerCase();
|
|
422
|
+
if (!ALLOWED_IMAGE_CONTENT_TYPES.has(baseType)) {
|
|
423
|
+
return jsonError("Not an image", 400);
|
|
424
|
+
}
|
|
425
|
+
const capped = await readBodyWithCap(upstream, MAX_IMAGE_PROXY_BYTES);
|
|
426
|
+
if (!capped.ok) return jsonError(capped.error, capped.status);
|
|
427
|
+
return new Response(capped.body, {
|
|
428
|
+
status: 200,
|
|
429
|
+
headers: {
|
|
430
|
+
"Content-Type": contentType,
|
|
431
|
+
"X-Content-Type-Options": "nosniff",
|
|
432
|
+
"Content-Security-Policy": "default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; sandbox",
|
|
433
|
+
"Cache-Control": "public, max-age=86400, s-maxage=86400, stale-while-revalidate=604800",
|
|
434
|
+
"Access-Control-Allow-Origin": "*"
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// src/server/backend-metadata.ts
|
|
441
|
+
function endpoint(config, path) {
|
|
442
|
+
return `${config.backendUrl.replace(/\/$/, "")}/v1/metadata/${path}`;
|
|
443
|
+
}
|
|
444
|
+
function headers(config, extra) {
|
|
445
|
+
return { "x-api-key": config.apiKey, ...extra };
|
|
446
|
+
}
|
|
447
|
+
async function uploadJsonToBackend(config, json2) {
|
|
448
|
+
const res = await (config.fetchImpl ?? fetch)(endpoint(config, "upload"), {
|
|
449
|
+
method: "POST",
|
|
450
|
+
headers: headers(config, { "Content-Type": "application/json" }),
|
|
451
|
+
body: JSON.stringify(json2)
|
|
452
|
+
});
|
|
453
|
+
const data = await res.json().catch(() => ({}));
|
|
454
|
+
if (!res.ok || !data.data) throw new Error(data.error ?? "Metadata upload failed");
|
|
455
|
+
return { cid: data.data.cid, uri: data.data.url };
|
|
456
|
+
}
|
|
457
|
+
async function uploadFileToBackend(config, file) {
|
|
458
|
+
const form = new FormData();
|
|
459
|
+
form.append("file", file, file.name);
|
|
460
|
+
const res = await (config.fetchImpl ?? fetch)(endpoint(config, "upload-file"), {
|
|
461
|
+
method: "POST",
|
|
462
|
+
headers: headers(config),
|
|
463
|
+
body: form
|
|
464
|
+
});
|
|
465
|
+
const data = await res.json().catch(() => ({}));
|
|
466
|
+
if (!res.ok || !data.data) throw new Error(data.error ?? "File upload failed");
|
|
467
|
+
return { cid: data.data.cid, uri: data.data.url };
|
|
468
|
+
}
|
|
469
|
+
async function uploadDirectoryToBackend(config, files) {
|
|
470
|
+
const res = await (config.fetchImpl ?? fetch)(endpoint(config, "upload-directory"), {
|
|
471
|
+
method: "POST",
|
|
472
|
+
headers: headers(config, { "Content-Type": "application/json" }),
|
|
473
|
+
body: JSON.stringify({ files })
|
|
474
|
+
});
|
|
475
|
+
const data = await res.json().catch(() => ({}));
|
|
476
|
+
if (!res.ok || !data.data) throw new Error(data.error ?? "Directory pin failed");
|
|
477
|
+
return data.data;
|
|
478
|
+
}
|
|
479
|
+
async function getBackendSignedUrl(config, kind = "image") {
|
|
480
|
+
const res = await (config.fetchImpl ?? fetch)(`${endpoint(config, "signed-url")}?kind=${kind}`, {
|
|
481
|
+
method: "GET",
|
|
482
|
+
headers: headers(config)
|
|
483
|
+
});
|
|
484
|
+
const data = await res.json().catch(() => ({}));
|
|
485
|
+
if (!res.ok || !data.data) throw new Error(data.error ?? "Failed to create upload URL");
|
|
486
|
+
return data.data.url;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/server/app-routes.ts
|
|
490
|
+
var SEG = "[^/]+";
|
|
491
|
+
var PUBLIC_ROUTES = {
|
|
492
|
+
GET: [
|
|
493
|
+
"orders",
|
|
494
|
+
`orders/${SEG}`,
|
|
495
|
+
`orders/token/${SEG}/${SEG}`,
|
|
496
|
+
`orders/user/${SEG}`,
|
|
497
|
+
`orders/received/${SEG}`,
|
|
498
|
+
"orders/counter-offers",
|
|
499
|
+
"tokens",
|
|
500
|
+
`tokens/owned/${SEG}`,
|
|
501
|
+
`tokens/${SEG}/${SEG}`,
|
|
502
|
+
`tokens/${SEG}/${SEG}/(history|comments|remixes)`,
|
|
503
|
+
`tokens/${SEG}/${SEG}/balances/${SEG}`,
|
|
504
|
+
"collections",
|
|
505
|
+
`collections/${SEG}`,
|
|
506
|
+
`collections/${SEG}/tokens`,
|
|
507
|
+
`collections/${SEG}/profile`,
|
|
508
|
+
"activities",
|
|
509
|
+
`activities/${SEG}`,
|
|
510
|
+
"search",
|
|
511
|
+
"metadata/resolve",
|
|
512
|
+
"creators",
|
|
513
|
+
`creators/${SEG}/profile`,
|
|
514
|
+
`creators/by-username/${SEG}`,
|
|
515
|
+
`creators/${SEG}/hidden`,
|
|
516
|
+
"coins",
|
|
517
|
+
"coins/prices",
|
|
518
|
+
"coins/claims",
|
|
519
|
+
`coins/${SEG}`,
|
|
520
|
+
`coins/${SEG}/guarantees`,
|
|
521
|
+
`pop/${SEG}/claim-info`,
|
|
522
|
+
"drops",
|
|
523
|
+
`drops/${SEG}`,
|
|
524
|
+
`drops/${SEG}/mints`,
|
|
525
|
+
`drops/${SEG}/state`,
|
|
526
|
+
"rewards",
|
|
527
|
+
"rewards/config",
|
|
528
|
+
"rewards/batch",
|
|
529
|
+
`rewards/${SEG}`,
|
|
530
|
+
`rewards/${SEG}/events`,
|
|
531
|
+
`club/${SEG}/${SEG}`,
|
|
532
|
+
`club/${SEG}/${SEG}/member/${SEG}`,
|
|
533
|
+
`tickets/${SEG}/${SEG}`,
|
|
534
|
+
`tickets/${SEG}/count`,
|
|
535
|
+
`ipnft/${SEG}/${SEG}`,
|
|
536
|
+
"stats",
|
|
537
|
+
"prices",
|
|
538
|
+
`remix-offers/${SEG}`,
|
|
539
|
+
"sponsorship/offers",
|
|
540
|
+
`sponsorship/offers/${SEG}`,
|
|
541
|
+
`sponsorship/offers/${SEG}/bids`,
|
|
542
|
+
"sponsorship/proposals",
|
|
543
|
+
`sponsorship/proposals/${SEG}`,
|
|
544
|
+
"sponsorship/licenses",
|
|
545
|
+
`sponsorship/licenses/${SEG}`,
|
|
546
|
+
"auth/email/exists",
|
|
547
|
+
`media/${SEG}/${SEG}/${SEG}/image`
|
|
548
|
+
].map((p) => new RegExp(`^${p}$`)),
|
|
549
|
+
POST: [
|
|
550
|
+
"auth/email/(request-code|verify-code|register-account|confirm)",
|
|
551
|
+
"auth/siws/(nonce|verify)",
|
|
552
|
+
"users/register"
|
|
553
|
+
].map((p) => new RegExp(`^${p}$`))
|
|
554
|
+
};
|
|
555
|
+
function isPublicAppRoute(method, path) {
|
|
556
|
+
if (path.split("/").some((s) => s === "." || s === ".." || s === "")) return false;
|
|
557
|
+
return (PUBLIC_ROUTES[method.toUpperCase()] ?? []).some((re) => re.test(path));
|
|
558
|
+
}
|
|
559
|
+
var IDENTITY_PREFIX = "siws_";
|
|
560
|
+
var ACCOUNT_SESSION_PREFIX = "account_session_";
|
|
561
|
+
var IDENTITY_DOMAIN = "siws-identity-v1";
|
|
562
|
+
var ACCOUNT_SESSION_DOMAIN = "account-session-v1";
|
|
563
|
+
var IDENTITY_TTL_SECONDS = 86400;
|
|
564
|
+
var ACCOUNT_SESSION_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
565
|
+
var CLOCK_SKEW_SECONDS = 60;
|
|
566
|
+
var encoder = new TextEncoder();
|
|
567
|
+
var decoder = new TextDecoder();
|
|
568
|
+
function toHex(bytes) {
|
|
569
|
+
let out = "";
|
|
570
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
571
|
+
return out;
|
|
572
|
+
}
|
|
573
|
+
function sign(secret, domain, payload) {
|
|
574
|
+
return toHex(hmac_js.hmac(sha2_js.sha256, encoder.encode(secret), encoder.encode(`${domain}.${payload}`)));
|
|
575
|
+
}
|
|
576
|
+
function constantTimeEquals(a, b) {
|
|
577
|
+
if (a.length !== b.length) return false;
|
|
578
|
+
let diff = 0;
|
|
579
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
580
|
+
return diff === 0;
|
|
581
|
+
}
|
|
582
|
+
function signatureMatches(secret, domain, payload, provided) {
|
|
583
|
+
return constantTimeEquals(provided, sign(secret, domain, payload));
|
|
584
|
+
}
|
|
585
|
+
function encodePayload(value) {
|
|
586
|
+
return base.base64urlnopad.encode(encoder.encode(JSON.stringify(value)));
|
|
587
|
+
}
|
|
588
|
+
function decodePayload(payload) {
|
|
589
|
+
try {
|
|
590
|
+
return JSON.parse(decoder.decode(base.base64urlnopad.decode(payload)));
|
|
591
|
+
} catch {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function splitToken(raw, prefix) {
|
|
596
|
+
if (!raw.startsWith(prefix)) return null;
|
|
597
|
+
const inner = raw.slice(prefix.length);
|
|
598
|
+
const dot = inner.lastIndexOf(".");
|
|
599
|
+
if (dot === -1) return null;
|
|
600
|
+
return { payload: inner.slice(0, dot), signature: inner.slice(dot + 1) };
|
|
601
|
+
}
|
|
602
|
+
function withinLifetime(iat, exp) {
|
|
603
|
+
if (!iat || !exp) return false;
|
|
604
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
605
|
+
return exp >= now && iat <= now + CLOCK_SKEW_SECONDS;
|
|
606
|
+
}
|
|
607
|
+
function issueSiwsToken(secret, chain, wallet, ttlSeconds = IDENTITY_TTL_SECONDS) {
|
|
608
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
609
|
+
const payload = encodePayload({ sub: wallet, chain, iat, exp: iat + ttlSeconds });
|
|
610
|
+
return `${IDENTITY_PREFIX}${payload}.${sign(secret, IDENTITY_DOMAIN, payload)}`;
|
|
611
|
+
}
|
|
612
|
+
function verifySiwsToken(secret, raw) {
|
|
613
|
+
const parts = splitToken(raw, IDENTITY_PREFIX);
|
|
614
|
+
if (!parts) return null;
|
|
615
|
+
if (!signatureMatches(secret, IDENTITY_DOMAIN, parts.payload, parts.signature)) return null;
|
|
616
|
+
const data = decodePayload(parts.payload);
|
|
617
|
+
if (!data?.sub || !withinLifetime(data.iat, data.exp)) return null;
|
|
618
|
+
return { address: data.sub, chain: data.chain ?? "STARKNET" };
|
|
619
|
+
}
|
|
620
|
+
function issueAccountSessionToken(secret, accountId, ttlSeconds = ACCOUNT_SESSION_TTL_SECONDS) {
|
|
621
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
622
|
+
const payload = encodePayload({ accountId, iat, exp: iat + ttlSeconds });
|
|
623
|
+
return `${ACCOUNT_SESSION_PREFIX}${payload}.${sign(secret, ACCOUNT_SESSION_DOMAIN, payload)}`;
|
|
624
|
+
}
|
|
625
|
+
function verifyAccountSessionToken(secret, raw) {
|
|
626
|
+
const parts = splitToken(raw, ACCOUNT_SESSION_PREFIX);
|
|
627
|
+
if (!parts) return null;
|
|
628
|
+
if (!signatureMatches(secret, ACCOUNT_SESSION_DOMAIN, parts.payload, parts.signature)) return null;
|
|
629
|
+
const data = decodePayload(parts.payload);
|
|
630
|
+
if (!data?.accountId || !withinLifetime(data.iat, data.exp)) return null;
|
|
631
|
+
return data.accountId;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/utils/ipfs-gateway.ts
|
|
635
|
+
var CID_PATH_PATTERN = /^(Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[a-z2-7]{58,})(\/[\w.\-/]*)?$/;
|
|
636
|
+
function isValidIpfsCidPath(cidPath) {
|
|
637
|
+
if (!CID_PATH_PATTERN.test(cidPath)) return false;
|
|
638
|
+
if (cidPath.split("/").includes("..")) return false;
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
var IPFS_SAFE_CONTENT_TYPE_PREFIXES = [
|
|
642
|
+
"image/jpeg",
|
|
643
|
+
"image/png",
|
|
644
|
+
"image/gif",
|
|
645
|
+
"image/webp",
|
|
646
|
+
"image/avif",
|
|
647
|
+
"image/svg+xml",
|
|
648
|
+
"video/",
|
|
649
|
+
"audio/",
|
|
650
|
+
"model/",
|
|
651
|
+
"font/",
|
|
652
|
+
"application/json",
|
|
653
|
+
"application/octet-stream"
|
|
654
|
+
];
|
|
655
|
+
function resolveSafeImageContentType(contentType) {
|
|
656
|
+
return IPFS_SAFE_CONTENT_TYPE_PREFIXES.some((p) => contentType.startsWith(p)) ? contentType : "application/octet-stream";
|
|
657
|
+
}
|
|
658
|
+
var MAX_IPFS_GATEWAY_RESPONSE_BYTES = 25 * 1024 * 1024;
|
|
659
|
+
|
|
660
|
+
exports.ACCOUNT_SESSION_COOKIE = ACCOUNT_SESSION_COOKIE;
|
|
661
|
+
exports.ACCOUNT_SESSION_TTL_SECONDS = ACCOUNT_SESSION_TTL_SECONDS;
|
|
662
|
+
exports.ALLOWED_IMAGE_CONTENT_TYPES = ALLOWED_IMAGE_CONTENT_TYPES;
|
|
663
|
+
exports.IDENTITY_TTL_SECONDS = IDENTITY_TTL_SECONDS;
|
|
664
|
+
exports.IPFS_SAFE_CONTENT_TYPE_PREFIXES = IPFS_SAFE_CONTENT_TYPE_PREFIXES;
|
|
665
|
+
exports.MAX_IMAGE_PROXY_BYTES = MAX_IMAGE_PROXY_BYTES;
|
|
666
|
+
exports.MAX_IMAGE_REDIRECTS = MAX_IMAGE_REDIRECTS;
|
|
667
|
+
exports.MAX_IPFS_GATEWAY_RESPONSE_BYTES = MAX_IPFS_GATEWAY_RESPONSE_BYTES;
|
|
668
|
+
exports.createAppGateway = createAppGateway;
|
|
669
|
+
exports.createAppSessionHandler = createAppSessionHandler;
|
|
670
|
+
exports.createImageProxyHandler = createImageProxyHandler;
|
|
671
|
+
exports.getBackendSignedUrl = getBackendSignedUrl;
|
|
672
|
+
exports.isPrivateHost = isPrivateHost;
|
|
673
|
+
exports.isPrivateOrInsecureUrl = isPrivateOrInsecureUrl;
|
|
674
|
+
exports.isPublicAppRoute = isPublicAppRoute;
|
|
675
|
+
exports.isSameOrigin = isSameOrigin;
|
|
676
|
+
exports.isValidIpfsCidPath = isValidIpfsCidPath;
|
|
677
|
+
exports.issueAccountSessionToken = issueAccountSessionToken;
|
|
678
|
+
exports.issueSiwsToken = issueSiwsToken;
|
|
679
|
+
exports.readBodyWithCap = readBodyWithCap;
|
|
680
|
+
exports.resolveSafeImageContentType = resolveSafeImageContentType;
|
|
681
|
+
exports.uploadDirectoryToBackend = uploadDirectoryToBackend;
|
|
682
|
+
exports.uploadFileToBackend = uploadFileToBackend;
|
|
683
|
+
exports.uploadJsonToBackend = uploadJsonToBackend;
|
|
684
|
+
exports.validateUrl = validateUrl;
|
|
685
|
+
exports.verifyAccountSessionToken = verifyAccountSessionToken;
|
|
686
|
+
exports.verifySiwsToken = verifySiwsToken;
|
|
687
|
+
//# sourceMappingURL=chunk-ZVANOWIB.cjs.map
|
|
688
|
+
//# sourceMappingURL=chunk-ZVANOWIB.cjs.map
|