@tom2012/cc-web 2026.5.20-b → 2026.5.24-b
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 +1 -1
- package/backend/dist/__tests__/browser-proxy.test.d.ts +2 -0
- package/backend/dist/__tests__/browser-proxy.test.d.ts.map +1 -0
- package/backend/dist/__tests__/browser-proxy.test.js +340 -0
- package/backend/dist/__tests__/browser-proxy.test.js.map +1 -0
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +5 -0
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/notify-service.d.ts +1 -0
- package/backend/dist/notify-service.d.ts.map +1 -1
- package/backend/dist/notify-service.js +1 -0
- package/backend/dist/notify-service.js.map +1 -1
- package/backend/dist/routes/browser-proxy.d.ts +43 -0
- package/backend/dist/routes/browser-proxy.d.ts.map +1 -0
- package/backend/dist/routes/browser-proxy.js +400 -0
- package/backend/dist/routes/browser-proxy.js.map +1 -0
- package/frontend/dist/assets/{ChatOverlay-C6C_KmoQ.js → ChatOverlay-Cm7M9uTo.js} +1 -1
- package/frontend/dist/assets/{GraphPreview-3Jt6HVoR.js → GraphPreview-Bu-zdWO0.js} +1 -1
- package/frontend/dist/assets/{MobilePage-Ccfw-PQN.js → MobilePage-DHSd8DBY.js} +3 -3
- package/frontend/dist/assets/{OfficePreview-CcNfdvDk.js → OfficePreview-CKOQ-aQd.js} +2 -2
- package/frontend/dist/assets/{PdfPreview-C_TVMGcZ.js → PdfPreview-G8fpvo6_.js} +1 -1
- package/frontend/dist/assets/{ProjectPage-DdDicE9q.js → ProjectPage-D8O-uvJ3.js} +5 -5
- package/frontend/dist/assets/SettingsPage-a3v0GKkG.js +13 -0
- package/frontend/dist/assets/{SkillHubPage-qYjj_wYZ.js → SkillHubPage-BcohQzQq.js} +2 -2
- package/frontend/dist/assets/{chevron-down-UeSwXtvj.js → chevron-down-BjD1KDpf.js} +1 -1
- package/frontend/dist/assets/{index-BoEETvjx.js → index-BxSzFzfM.js} +1 -1
- package/frontend/dist/assets/{index-Bf5BlYFK.js → index-Fi22OrSt.js} +3 -3
- package/frontend/dist/assets/{index-e0TiJwv-.js → index-dlR56s5t.js} +1 -1
- package/frontend/dist/assets/{jszip.min-PNrxBYIs.js → jszip.min-j8-Zud6N.js} +1 -1
- package/frontend/dist/assets/{search-DY3wMq7s.js → search-CfhpkpXs.js} +1 -1
- package/frontend/dist/assets/select-nwQBXqLw.js +13 -0
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
- package/frontend/dist/assets/SettingsPage-GCr7_tnl.js +0 -13
- package/frontend/dist/assets/select-CYI-GI6f.js +0 -13
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
declare const router: Router;
|
|
3
|
+
interface ParsedHostport {
|
|
4
|
+
host: string;
|
|
5
|
+
port: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseHostport(raw: string): ParsedHostport | null;
|
|
8
|
+
/**
|
|
9
|
+
* Allowlist check on a literal IPv4/IPv6 address. Unlike notify-service's
|
|
10
|
+
* isPrivateAddress (which is a deny-list for outbound webhooks and uses
|
|
11
|
+
* loose startsWith on the *hostname*), this is strictly numeric and refuses
|
|
12
|
+
* the cloud-metadata link-local addresses even though they're RFC1918.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isAllowedProxyIp(addr: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve hostname to a single IP (or accept literal IP) and confirm it's
|
|
17
|
+
* in the allowed private range. Returns the resolved IP to pin against
|
|
18
|
+
* DNS-rebinding TOCTOU: callers fetch by IP, not by hostname.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveAllowedTarget(host: string): Promise<string | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Strip `_bp_tok=<jwt>` from a subPath string ("/foo/bar?x=1&_bp_tok=...&y=2#z")
|
|
23
|
+
* so the upstream never sees the session token. Idempotent: missing token
|
|
24
|
+
* is a no-op. Handles token in any query position and an empty resulting
|
|
25
|
+
* query (leaves no orphan "?").
|
|
26
|
+
*/
|
|
27
|
+
export declare function stripTokenFromSubPath(subPath: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Rewrite root-relative absolute paths (src/href/action="/...") through
|
|
30
|
+
* this proxy. Also strip any upstream `<base href="...">` so the original
|
|
31
|
+
* declaration cannot redirect relative resolution back out to the bare
|
|
32
|
+
* ccweb origin. Each rewritten URL carries the session token so the iframe
|
|
33
|
+
* can fetch it without relying on cookies that the sandbox would drop.
|
|
34
|
+
* Protocol-relative and relative paths are untouched — relative URLs are
|
|
35
|
+
* resolved by the browser against the iframe URL (which already has the
|
|
36
|
+
* token in its query), and the browser preserves that query on the resolved
|
|
37
|
+
* URL? No — relative resolution does NOT inherit query. So relative paths
|
|
38
|
+
* within the page will appear without the token. They will fail. v0 limit.
|
|
39
|
+
*/
|
|
40
|
+
export declare function rewriteHtml(html: string, prefix: string, token: string): string;
|
|
41
|
+
export declare function rewriteLocationHeader(loc: string, parsed: ParsedHostport, mountPrefix: string, token: string): string;
|
|
42
|
+
export default router;
|
|
43
|
+
//# sourceMappingURL=browser-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-proxy.d.ts","sourceRoot":"","sources":["../../src/routes/browser-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA0B,MAAM,SAAS,CAAC;AAUzD,QAAA,MAAM,MAAM,EAAE,MAAiB,CAAC;AAoDhC,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAShE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAetD;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa/E;AAWD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAa7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAU/E;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,CAoBR;AAgLD,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseHostport = parseHostport;
|
|
37
|
+
exports.isAllowedProxyIp = isAllowedProxyIp;
|
|
38
|
+
exports.resolveAllowedTarget = resolveAllowedTarget;
|
|
39
|
+
exports.stripTokenFromSubPath = stripTokenFromSubPath;
|
|
40
|
+
exports.rewriteHtml = rewriteHtml;
|
|
41
|
+
exports.rewriteLocationHeader = rewriteLocationHeader;
|
|
42
|
+
const express_1 = require("express");
|
|
43
|
+
const dns = __importStar(require("dns/promises"));
|
|
44
|
+
const net = __importStar(require("net"));
|
|
45
|
+
const jwt = __importStar(require("jsonwebtoken"));
|
|
46
|
+
const auth_1 = require("../auth");
|
|
47
|
+
const config_1 = require("../config");
|
|
48
|
+
const logger_1 = require("../logger");
|
|
49
|
+
const log = (0, logger_1.modLogger)('browser-proxy');
|
|
50
|
+
const router = (0, express_1.Router)();
|
|
51
|
+
const BLOCKED_PORTS = new Set([
|
|
52
|
+
22, 23, 25, 110, 143, 465, 587, 993, 995,
|
|
53
|
+
2049, 3389, 5432, 5984, 6379, 9200, 11211, 27017,
|
|
54
|
+
]);
|
|
55
|
+
const STRIPPED_RESPONSE_HEADERS = new Set([
|
|
56
|
+
'x-frame-options',
|
|
57
|
+
'frame-options',
|
|
58
|
+
'content-security-policy',
|
|
59
|
+
'content-security-policy-report-only',
|
|
60
|
+
'cross-origin-opener-policy',
|
|
61
|
+
'cross-origin-embedder-policy',
|
|
62
|
+
'cross-origin-resource-policy',
|
|
63
|
+
'set-cookie',
|
|
64
|
+
'content-length',
|
|
65
|
+
'transfer-encoding',
|
|
66
|
+
'content-encoding',
|
|
67
|
+
// P1 (codex review): upstream must not be able to clobber ccweb's
|
|
68
|
+
// own cookies/storage or register service workers on the daemon origin.
|
|
69
|
+
'clear-site-data',
|
|
70
|
+
'service-worker-allowed',
|
|
71
|
+
'permissions-policy',
|
|
72
|
+
'feature-policy',
|
|
73
|
+
]);
|
|
74
|
+
// CSP sandbox without allow-same-origin forces an opaque origin even for
|
|
75
|
+
// same-origin iframes — proxied page JS cannot reach ccweb's localStorage
|
|
76
|
+
// / IndexedDB / cookies. Pair with the `<iframe sandbox>` attribute on the
|
|
77
|
+
// frontend so a direct GET to /api/browser-proxy/... in another tab is
|
|
78
|
+
// also opaque.
|
|
79
|
+
const PROXY_HTML_CSP = "sandbox allow-scripts allow-forms allow-popups";
|
|
80
|
+
const MAX_PROXY_SIZE = 16 * 1024 * 1024;
|
|
81
|
+
const UPSTREAM_TIMEOUT_MS = 15000;
|
|
82
|
+
// Cloud metadata services + IPv4 link-local that should NEVER be reachable.
|
|
83
|
+
// 169.254.169.254 = AWS/GCP/Azure metadata. 169.254.170.2 = ECS task role.
|
|
84
|
+
const BLOCKED_IPS = new Set(['169.254.169.254', '169.254.170.2']);
|
|
85
|
+
const BLOCKED_HOSTS = new Set(['metadata.google.internal', 'metadata']);
|
|
86
|
+
// Session token presented as ?_bp_tok=<jwt> on every proxy GET. Cookies
|
|
87
|
+
// would have been cleaner but a sandboxed iframe (no allow-same-origin)
|
|
88
|
+
// is treated as opaque origin → SameSite=Lax cookies are NOT sent on
|
|
89
|
+
// subresource requests originating inside the iframe. The query-param
|
|
90
|
+
// approach trades access-log exposure for working sandboxed iframes.
|
|
91
|
+
// Token is stripped before the URL is forwarded upstream so it never
|
|
92
|
+
// reaches the proxied site.
|
|
93
|
+
const TOKEN_QUERY_PARAM = '_bp_tok';
|
|
94
|
+
const SESSION_MAX_AGE_SEC = 60 * 60;
|
|
95
|
+
function parseHostport(raw) {
|
|
96
|
+
// v0: http only. IPv6 literals out of scope (path-segment ambiguity).
|
|
97
|
+
const m = raw.match(/^([a-zA-Z0-9.\-_]+):(\d{1,5})$/);
|
|
98
|
+
if (!m)
|
|
99
|
+
return null;
|
|
100
|
+
const host = m[1].toLowerCase();
|
|
101
|
+
const port = Number(m[2]);
|
|
102
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
103
|
+
return null;
|
|
104
|
+
if (BLOCKED_PORTS.has(port))
|
|
105
|
+
return null;
|
|
106
|
+
return { host, port };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Allowlist check on a literal IPv4/IPv6 address. Unlike notify-service's
|
|
110
|
+
* isPrivateAddress (which is a deny-list for outbound webhooks and uses
|
|
111
|
+
* loose startsWith on the *hostname*), this is strictly numeric and refuses
|
|
112
|
+
* the cloud-metadata link-local addresses even though they're RFC1918.
|
|
113
|
+
*/
|
|
114
|
+
function isAllowedProxyIp(addr) {
|
|
115
|
+
const a = addr.replace(/^\[|\]$/g, '').toLowerCase();
|
|
116
|
+
const mapped = a.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
|
|
117
|
+
if (mapped)
|
|
118
|
+
return isAllowedProxyIp(mapped[1]);
|
|
119
|
+
// Reject anything that isn't a real IP — without this `10.evil.com`
|
|
120
|
+
// would match the `/^10\./` prefix below and slip through.
|
|
121
|
+
if (!net.isIP(a))
|
|
122
|
+
return false;
|
|
123
|
+
if (BLOCKED_IPS.has(a))
|
|
124
|
+
return false;
|
|
125
|
+
if (a === '0.0.0.0' || a === '::1')
|
|
126
|
+
return true;
|
|
127
|
+
if (/^127\./.test(a))
|
|
128
|
+
return true;
|
|
129
|
+
if (/^10\./.test(a))
|
|
130
|
+
return true;
|
|
131
|
+
if (/^192\.168\./.test(a))
|
|
132
|
+
return true;
|
|
133
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(a))
|
|
134
|
+
return true;
|
|
135
|
+
if (/^fc/.test(a) || /^fd/.test(a))
|
|
136
|
+
return true; // ULA v6
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Resolve hostname to a single IP (or accept literal IP) and confirm it's
|
|
141
|
+
* in the allowed private range. Returns the resolved IP to pin against
|
|
142
|
+
* DNS-rebinding TOCTOU: callers fetch by IP, not by hostname.
|
|
143
|
+
*/
|
|
144
|
+
async function resolveAllowedTarget(host) {
|
|
145
|
+
const h = host.replace(/\.$/, '').toLowerCase();
|
|
146
|
+
if (BLOCKED_HOSTS.has(h))
|
|
147
|
+
return null;
|
|
148
|
+
if (h === 'localhost')
|
|
149
|
+
return '127.0.0.1';
|
|
150
|
+
if (net.isIP(h))
|
|
151
|
+
return isAllowedProxyIp(h) ? h : null;
|
|
152
|
+
try {
|
|
153
|
+
const records = await dns.lookup(h, { all: true, verbatim: true });
|
|
154
|
+
if (records.length === 0)
|
|
155
|
+
return null;
|
|
156
|
+
if (!records.every((r) => isAllowedProxyIp(r.address)))
|
|
157
|
+
return null;
|
|
158
|
+
return records[0].address;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function appendToken(url, token) {
|
|
165
|
+
// Preserve fragment, insert token into query string.
|
|
166
|
+
const hashIdx = url.indexOf('#');
|
|
167
|
+
const hash = hashIdx >= 0 ? url.slice(hashIdx) : '';
|
|
168
|
+
const beforeHash = hashIdx >= 0 ? url.slice(0, hashIdx) : url;
|
|
169
|
+
const sep = beforeHash.includes('?') ? '&' : '?';
|
|
170
|
+
return `${beforeHash}${sep}${TOKEN_QUERY_PARAM}=${encodeURIComponent(token)}${hash}`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Strip `_bp_tok=<jwt>` from a subPath string ("/foo/bar?x=1&_bp_tok=...&y=2#z")
|
|
174
|
+
* so the upstream never sees the session token. Idempotent: missing token
|
|
175
|
+
* is a no-op. Handles token in any query position and an empty resulting
|
|
176
|
+
* query (leaves no orphan "?").
|
|
177
|
+
*/
|
|
178
|
+
function stripTokenFromSubPath(subPath) {
|
|
179
|
+
const hashIdx = subPath.indexOf('#');
|
|
180
|
+
const hash = hashIdx >= 0 ? subPath.slice(hashIdx) : '';
|
|
181
|
+
const beforeHash = hashIdx >= 0 ? subPath.slice(0, hashIdx) : subPath;
|
|
182
|
+
const qIdx = beforeHash.indexOf('?');
|
|
183
|
+
if (qIdx < 0)
|
|
184
|
+
return subPath;
|
|
185
|
+
const pathPart = beforeHash.slice(0, qIdx);
|
|
186
|
+
const queryPart = beforeHash.slice(qIdx + 1);
|
|
187
|
+
const kept = queryPart
|
|
188
|
+
.split('&')
|
|
189
|
+
.filter((kv) => kv !== '' && !kv.startsWith(`${TOKEN_QUERY_PARAM}=`) && kv !== TOKEN_QUERY_PARAM);
|
|
190
|
+
const newQuery = kept.length > 0 ? `?${kept.join('&')}` : '';
|
|
191
|
+
return `${pathPart}${newQuery}${hash}`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Rewrite root-relative absolute paths (src/href/action="/...") through
|
|
195
|
+
* this proxy. Also strip any upstream `<base href="...">` so the original
|
|
196
|
+
* declaration cannot redirect relative resolution back out to the bare
|
|
197
|
+
* ccweb origin. Each rewritten URL carries the session token so the iframe
|
|
198
|
+
* can fetch it without relying on cookies that the sandbox would drop.
|
|
199
|
+
* Protocol-relative and relative paths are untouched — relative URLs are
|
|
200
|
+
* resolved by the browser against the iframe URL (which already has the
|
|
201
|
+
* token in its query), and the browser preserves that query on the resolved
|
|
202
|
+
* URL? No — relative resolution does NOT inherit query. So relative paths
|
|
203
|
+
* within the page will appear without the token. They will fail. v0 limit.
|
|
204
|
+
*/
|
|
205
|
+
function rewriteHtml(html, prefix, token) {
|
|
206
|
+
return html
|
|
207
|
+
.replace(/<base\b[^>]*>/gi, '')
|
|
208
|
+
.replace(/\b(src|href|action)\s*=\s*(["'])\/(?!\/)([^"']*)\2/gi, (_, attr, quote, rest) => {
|
|
209
|
+
const rewritten = appendToken(`${prefix}/${rest}`, token);
|
|
210
|
+
return `${attr}=${quote}${rewritten}${quote}`;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function rewriteLocationHeader(loc, parsed, mountPrefix, token) {
|
|
214
|
+
try {
|
|
215
|
+
const absUrl = new URL(loc);
|
|
216
|
+
// Same scheme + same host + same port → safe to rewrite. Different
|
|
217
|
+
// scheme on the same host (e.g. http page issuing https:// redirect)
|
|
218
|
+
// is treated as cross-target and left untouched.
|
|
219
|
+
const expectedProto = 'http:';
|
|
220
|
+
const sameProto = absUrl.protocol === expectedProto;
|
|
221
|
+
const sameHost = absUrl.hostname.toLowerCase() === parsed.host;
|
|
222
|
+
const samePort = Number(absUrl.port || 80) === parsed.port;
|
|
223
|
+
if (sameProto && sameHost && samePort) {
|
|
224
|
+
return appendToken(`${mountPrefix}${absUrl.pathname}${absUrl.search}${absUrl.hash}`, token);
|
|
225
|
+
}
|
|
226
|
+
return loc;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
if (loc.startsWith('/') && !loc.startsWith('//')) {
|
|
230
|
+
return appendToken(`${mountPrefix}${loc}`, token);
|
|
231
|
+
}
|
|
232
|
+
return loc;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function hasValidSessionToken(req) {
|
|
236
|
+
const token = req.query[TOKEN_QUERY_PARAM];
|
|
237
|
+
if (typeof token !== 'string' || !token)
|
|
238
|
+
return false;
|
|
239
|
+
try {
|
|
240
|
+
const config = (0, config_1.getConfig)();
|
|
241
|
+
const decoded = jwt.verify(token, config.jwtSecret);
|
|
242
|
+
return decoded.typ === 'browser-proxy' && typeof decoded.username === 'string';
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Explicit-Bearer admin gate. Deliberately does NOT fall back to localhost
|
|
250
|
+
* auto-auth: any same-machine browser can blind-CSRF /_session if we trust
|
|
251
|
+
* the socket address, which would let a malicious page mint cookies and
|
|
252
|
+
* then drive arbitrary GETs through /api/browser-proxy/. Caller must
|
|
253
|
+
* present a real admin JWT (the frontend already has one via
|
|
254
|
+
* /api/auth/local-token or /api/auth/login).
|
|
255
|
+
*/
|
|
256
|
+
function requireBearerAdmin(req, res, next) {
|
|
257
|
+
const authHeader = req.headers['authorization'];
|
|
258
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
259
|
+
res.status(401).json({ error: 'Bearer token required' });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const user = (0, auth_1.verifyToken)(authHeader.slice(7));
|
|
263
|
+
if (!user || !(0, config_1.isAdminUser)(user.username)) {
|
|
264
|
+
res.status(403).json({ error: 'Admin only' });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
req.user = user;
|
|
268
|
+
next();
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Issues a short-lived JWT that the frontend attaches as ?_bp_tok=... to
|
|
272
|
+
* the iframe src. We deliberately do NOT use a cookie here — sandboxed
|
|
273
|
+
* iframes (no allow-same-origin) are treated as opaque origins, and Lax
|
|
274
|
+
* cookies are not sent on subresource requests originating inside such
|
|
275
|
+
* iframes. The query-param token is the only way to authenticate iframe
|
|
276
|
+
* subresource fetches without relaxing the sandbox.
|
|
277
|
+
*/
|
|
278
|
+
router.post('/_session', requireBearerAdmin, (req, res) => {
|
|
279
|
+
const config = (0, config_1.getConfig)();
|
|
280
|
+
const token = jwt.sign({ username: req.user?.username, typ: 'browser-proxy' }, config.jwtSecret, { expiresIn: `${SESSION_MAX_AGE_SEC}s` });
|
|
281
|
+
res.json({ token, maxAge: SESSION_MAX_AGE_SEC });
|
|
282
|
+
});
|
|
283
|
+
async function handle(req, res) {
|
|
284
|
+
// Trust ONLY the query-param session token. Route mounted without
|
|
285
|
+
// authMiddleware so localhost auto-auth / any CSRF-able header cannot
|
|
286
|
+
// reach the proxy. Token can only be issued by POST /_session which
|
|
287
|
+
// requires explicit Bearer admin.
|
|
288
|
+
if (!hasValidSessionToken(req)) {
|
|
289
|
+
res.status(403).send('Browser proxy session required — call POST /_session first');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const sessionToken = req.query[TOKEN_QUERY_PARAM];
|
|
293
|
+
const hostportRaw = req.params['hostport'];
|
|
294
|
+
if (!hostportRaw) {
|
|
295
|
+
res.status(400).send('hostport required');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const parsed = parseHostport(hostportRaw);
|
|
299
|
+
if (!parsed) {
|
|
300
|
+
res.status(400).send('Invalid hostport (expected host:port, http only, ports 1-65535 except blocked)');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const resolvedIp = await resolveAllowedTarget(parsed.host);
|
|
304
|
+
if (!resolvedIp) {
|
|
305
|
+
res.status(403).send('Target not allowed — must resolve to RFC1918 / loopback (excluding cloud-metadata)');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// req.url is mount-relative. Strip hostport segment + the session-token
|
|
309
|
+
// query param before forwarding to upstream (token must NEVER leak to
|
|
310
|
+
// the proxied site).
|
|
311
|
+
const afterMount = req.url.replace(/^\/+/, '');
|
|
312
|
+
const slashIdx = afterMount.indexOf('/');
|
|
313
|
+
const rawSubPath = slashIdx >= 0 ? afterMount.slice(slashIdx) : '/';
|
|
314
|
+
const subPath = stripTokenFromSubPath(rawSubPath);
|
|
315
|
+
// Fetch by resolved IP — pins the connection to the address we just
|
|
316
|
+
// validated, so DNS rebinding between validation and fetch is harmless.
|
|
317
|
+
const targetUrl = `http://${resolvedIp}:${parsed.port}${subPath}`;
|
|
318
|
+
const mountPrefix = `/api/browser-proxy/${hostportRaw}`;
|
|
319
|
+
try {
|
|
320
|
+
const upstream = await fetch(targetUrl, {
|
|
321
|
+
method: 'GET',
|
|
322
|
+
redirect: 'manual',
|
|
323
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),
|
|
324
|
+
headers: {
|
|
325
|
+
// Send original hostname as Host so vhost-aware servers route correctly.
|
|
326
|
+
'Host': `${parsed.host}:${parsed.port}`,
|
|
327
|
+
'User-Agent': req.headers['user-agent'] || 'ccweb-browser-proxy',
|
|
328
|
+
'Accept': req.headers['accept'] || '*/*',
|
|
329
|
+
'Accept-Language': req.headers['accept-language'] || 'en',
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
const declaredLen = Number(upstream.headers.get('content-length') || '0');
|
|
333
|
+
if (declaredLen > MAX_PROXY_SIZE) {
|
|
334
|
+
res.status(413).send('Upstream response too large for proxy');
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
res.status(upstream.status);
|
|
338
|
+
upstream.headers.forEach((value, name) => {
|
|
339
|
+
const lower = name.toLowerCase();
|
|
340
|
+
if (STRIPPED_RESPONSE_HEADERS.has(lower))
|
|
341
|
+
return;
|
|
342
|
+
if (lower === 'location') {
|
|
343
|
+
res.setHeader('Location', rewriteLocationHeader(value, parsed, mountPrefix, sessionToken));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
res.setHeader(name, value);
|
|
347
|
+
});
|
|
348
|
+
const contentType = upstream.headers.get('content-type') || '';
|
|
349
|
+
const isHtml = contentType.includes('text/html');
|
|
350
|
+
// Stream-aware cap: read in chunks, abort if we exceed limit before
|
|
351
|
+
// the whole body lands in memory. Compatible with chunked responses
|
|
352
|
+
// that don't declare Content-Length.
|
|
353
|
+
const chunks = [];
|
|
354
|
+
let total = 0;
|
|
355
|
+
if (upstream.body) {
|
|
356
|
+
const reader = upstream.body.getReader();
|
|
357
|
+
while (true) {
|
|
358
|
+
const { done, value } = await reader.read();
|
|
359
|
+
if (done)
|
|
360
|
+
break;
|
|
361
|
+
total += value.byteLength;
|
|
362
|
+
if (total > MAX_PROXY_SIZE) {
|
|
363
|
+
reader.cancel().catch(() => { });
|
|
364
|
+
if (!res.headersSent)
|
|
365
|
+
res.status(413);
|
|
366
|
+
res.end();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
chunks.push(value);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const buf = Buffer.concat(chunks.map((c) => Buffer.from(c)));
|
|
373
|
+
if (isHtml) {
|
|
374
|
+
res.setHeader('Content-Security-Policy', PROXY_HTML_CSP);
|
|
375
|
+
const rewritten = rewriteHtml(buf.toString('utf-8'), mountPrefix, sessionToken);
|
|
376
|
+
const out = Buffer.from(rewritten, 'utf-8');
|
|
377
|
+
res.setHeader('Content-Length', String(out.byteLength));
|
|
378
|
+
res.end(out);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
res.setHeader('Content-Length', String(buf.byteLength));
|
|
382
|
+
res.end(buf);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
const name = err?.name;
|
|
387
|
+
const reason = name === 'TimeoutError' ? 'Upstream timeout' : 'Upstream fetch failed';
|
|
388
|
+
log.warn({ err, targetUrl }, 'browser-proxy upstream error');
|
|
389
|
+
if (!res.headersSent) {
|
|
390
|
+
res.status(502).send(reason);
|
|
391
|
+
}
|
|
392
|
+
else if (!res.writableEnded) {
|
|
393
|
+
res.end();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
router.get('/:hostport', handle);
|
|
398
|
+
router.get('/:hostport/*', handle);
|
|
399
|
+
exports.default = router;
|
|
400
|
+
//# sourceMappingURL=browser-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-proxy.js","sourceRoot":"","sources":["../../src/routes/browser-proxy.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,sCASC;AAQD,4CAeC;AAOD,oDAaC;AAiBD,sDAaC;AAcD,kCAUC;AAED,sDAyBC;AAxMD,qCAAyD;AACzD,kDAAoC;AACpC,yCAA2B;AAC3B,kDAAoC;AACpC,kCAAmD;AACnD,sCAAmD;AACnD,sCAAsC;AAEtC,MAAM,GAAG,GAAG,IAAA,kBAAS,EAAC,eAAe,CAAC,CAAC;AAEvC,MAAM,MAAM,GAAW,IAAA,gBAAM,GAAE,CAAC;AAEhC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;IACxC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK;CACjD,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IACxC,iBAAiB;IACjB,eAAe;IACf,yBAAyB;IACzB,qCAAqC;IACrC,4BAA4B;IAC5B,8BAA8B;IAC9B,8BAA8B;IAC9B,YAAY;IACZ,gBAAgB;IAChB,mBAAmB;IACnB,kBAAkB;IAClB,kEAAkE;IAClE,wEAAwE;IACxE,iBAAiB;IACjB,wBAAwB;IACxB,oBAAoB;IACpB,gBAAgB;CACjB,CAAC,CAAC;AAEH,yEAAyE;AACzE,0EAA0E;AAC1E,2EAA2E;AAC3E,uEAAuE;AACvE,eAAe;AACf,MAAM,cAAc,GAAG,gDAAgD,CAAC;AAExE,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACxC,MAAM,mBAAmB,GAAG,KAAM,CAAC;AAEnC,4EAA4E;AAC5E,2EAA2E;AAC3E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC,CAAC;AAClE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,0BAA0B,EAAE,UAAU,CAAC,CAAC,CAAC;AAExE,wEAAwE;AACxE,wEAAwE;AACxE,qEAAqE;AACrE,sEAAsE;AACtE,qEAAqE;AACrE,qEAAqE;AACrE,4BAA4B;AAC5B,MAAM,iBAAiB,GAAG,SAAS,CAAC;AACpC,MAAM,mBAAmB,GAAG,EAAE,GAAG,EAAE,CAAC;AAOpC,SAAgB,aAAa,CAAC,GAAW;IACvC,sEAAsE;IACtE,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACrD,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IAC7D,IAAI,MAAM;QAAE,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,oEAAoE;IACpE,2DAA2D;IAC3D,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,SAAS;IAC1D,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,oBAAoB,CAAC,IAAY;IACrD,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAChD,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC,KAAK,WAAW;QAAE,OAAO,WAAW,CAAC;IAC1C,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,KAAa;IAC7C,qDAAqD;IACrD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,MAAM,UAAU,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC9D,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACjD,OAAO,GAAG,UAAU,GAAG,GAAG,GAAG,iBAAiB,IAAI,kBAAkB,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC;AACvF,CAAC;AAED;;;;;GAKG;AACH,SAAgB,qBAAqB,CAAC,OAAe;IACnD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,MAAM,UAAU,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACtE,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,IAAI,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IAC7B,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,SAAS;SACnB,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,iBAAiB,GAAG,CAAC,IAAI,EAAE,KAAK,iBAAiB,CAAC,CAAC;IACpG,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,EAAE,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,WAAW,CAAC,IAAY,EAAE,MAAc,EAAE,KAAa;IACrE,OAAO,IAAI;SACR,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;SAC9B,OAAO,CACN,sDAAsD,EACtD,CAAC,CAAC,EAAE,IAAY,EAAE,KAAa,EAAE,IAAY,EAAE,EAAE;QAC/C,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC;QAC1D,OAAO,GAAG,IAAI,IAAI,KAAK,GAAG,SAAS,GAAG,KAAK,EAAE,CAAC;IAChD,CAAC,CACF,CAAC;AACN,CAAC;AAED,SAAgB,qBAAqB,CACnC,GAAW,EACX,MAAsB,EACtB,WAAmB,EACnB,KAAa;IAEb,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,mEAAmE;QACnE,qEAAqE;QACrE,iDAAiD;QACjD,MAAM,aAAa,GAAG,OAAO,CAAC;QAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,KAAK,aAAa,CAAC;QACpD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC;QAC/D,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC;QAC3D,IAAI,SAAS,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;YACtC,OAAO,WAAW,CAAC,GAAG,WAAW,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,OAAO,WAAW,CAAC,GAAG,WAAW,GAAG,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAgB;IAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAA,kBAAS,GAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,CAAmB,CAAC;QACtE,OAAO,OAAO,CAAC,GAAG,KAAK,eAAe,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,kBAAkB,CAAC,GAAgB,EAAE,GAAa,EAAE,IAAkB;IAC7E,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,IAAA,kBAAW,EAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAA,oBAAW,EAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IACD,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,EAAE,CAAC;AACT,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,EAAE,CAAC,GAAgB,EAAE,GAAa,EAAQ,EAAE;IACrF,MAAM,MAAM,GAAG,IAAA,kBAAS,GAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CACpB,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,EAAE,EACtD,MAAM,CAAC,SAAS,EAChB,EAAE,SAAS,EAAE,GAAG,mBAAmB,GAAG,EAAE,CACzC,CAAC;IACF,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,MAAM,CAAC,GAAgB,EAAE,GAAa;IACnD,kEAAkE;IAClE,sEAAsE;IACtE,oEAAoE;IACpE,kCAAkC;IAClC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;QACnF,OAAO;IACT,CAAC;IACD,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAW,CAAC;IAE5D,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,WAAW,EAAE,CAAC;QAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IAExE,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gFAAgF,CAAC,CAAC;QACvG,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3D,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oFAAoF,CAAC,CAAC;QAC3G,OAAO;IACT,CAAC;IAED,wEAAwE;IACxE,sEAAsE;IACtE,qBAAqB;IACrB,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACpE,MAAM,OAAO,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAElD,oEAAoE;IACpE,wEAAwE;IACxE,MAAM,SAAS,GAAG,UAAU,UAAU,IAAI,MAAM,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC;IAClE,MAAM,WAAW,GAAG,sBAAsB,WAAW,EAAE,CAAC;IAExD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;YACtC,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,mBAAmB,CAAC;YAChD,OAAO,EAAE;gBACP,yEAAyE;gBACzE,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE;gBACvC,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,qBAAqB;gBAChE,QAAQ,EAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAY,IAAI,KAAK;gBACpD,iBAAiB,EAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAY,IAAI,IAAI;aACtE;SACF,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,CAAC;QAC1E,IAAI,WAAW,GAAG,cAAc,EAAE,CAAC;YACjC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAE5B,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YACvC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACjC,IAAI,yBAAyB,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO;YACjD,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;gBACzB,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;gBAC3F,OAAO;YACT,CAAC;YACD,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC/D,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAEjD,oEAAoE;QACpE,oEAAoE;QACpE,qCAAqC;QACrC,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;YAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACzC,OAAO,IAAI,EAAE,CAAC;gBACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI;oBAAE,MAAM;gBAChB,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC;gBAC1B,IAAI,KAAK,GAAG,cAAc,EAAE,CAAC;oBAC3B,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBAChC,IAAI,CAAC,GAAG,CAAC,WAAW;wBAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBACtC,GAAG,CAAC,GAAG,EAAE,CAAC;oBACV,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7D,IAAI,MAAM,EAAE,CAAC;YACX,GAAG,CAAC,SAAS,CAAC,yBAAyB,EAAE,cAAc,CAAC,CAAC;YACzD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;YAChF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC5C,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;YACxD,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;YACxD,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAAyB,EAAE,IAAI,CAAC;QAC9C,MAAM,MAAM,GAAG,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,uBAAuB,CAAC;QACtF,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,8BAA8B,CAAC,CAAC;QAC7D,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YAC9B,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;AACjC,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAEnC,kBAAe,MAAM,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{bH as e,bI as t,r as n,bJ as r,c as a,h as i,bK as o,aX as s,j as l,R as c,d as u,U as d,bL as p,t as m,b2 as g,B as h,A as f,q as b,bM as y,a as E,a8 as v,aa as S,a6 as w,a5 as k,a9 as x,a4 as T,bk as A,
|
|
1
|
+
import{bH as e,bI as t,r as n,bJ as r,c as a,h as i,bK as o,aX as s,j as l,R as c,d as u,U as d,bL as p,t as m,b2 as g,B as h,A as f,q as b,bM as y,a as E,a8 as v,aa as S,a6 as w,a5 as k,a9 as x,a4 as T,bk as A,aH as _,bz as I,bA as N,bB as R,i as C,bN as O,bO as L,s as D,bP as M,bQ as P,bC as F,bR as B,bE as U,bF as z,S as $,l as G,p as j}from"./index-Fi22OrSt.js";import{C as H}from"./chevron-down-BjD1KDpf.js";import{a as q,C as V,u as W,c as Y}from"./index-BxSzFzfM.js";function K(){!e.current&&t();const[a]=n.useState(r.current);return a}
|
|
2
2
|
/**
|
|
3
3
|
* @license lucide-react v0.309.0 - ISC
|
|
4
4
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as e,r as t,b as n,j as i,U as r,B as o,m as a}from"./index-
|
|
1
|
+
import{c as e,r as t,b as n,j as i,U as r,B as o,m as a}from"./index-Fi22OrSt.js";import{Z as l,a as s}from"./ProjectPage-D8O-uvJ3.js";import"./purify.es-CgRAQgUo.js";import"./ChatOverlay-Cm7M9uTo.js";import"./chevron-down-BjD1KDpf.js";import"./index-BxSzFzfM.js";import"./select-nwQBXqLw.js";import"./search-CfhpkpXs.js";
|
|
2
2
|
/**
|
|
3
3
|
* @license lucide-react v0.309.0 - ISC
|
|
4
4
|
*
|