@axiom-lattice/gateway 2.1.108 → 2.1.110

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.
@@ -6,8 +6,9 @@ import {
6
6
  createSharePayload,
7
7
  TokenCache,
8
8
  SandboxLatticeManager,
9
+ buildNamedVolumeName,
9
10
  } from "@axiom-lattice/core";
10
- import { getContentTypeFromFilename, getFilenameFromPath } from "../utils/mime";
11
+ import { getContentTypeFromFilename } from "../utils/mime";
11
12
 
12
13
  interface ResourceControllerDeps {
13
14
  store: SharedResourceStore;
@@ -60,7 +61,7 @@ export class ResourceController {
60
61
  : "unknown";
61
62
  const tenantId = (request.headers["x-tenant-id"] as string) || "default";
62
63
  const shares = await this.deps.store.listByUser(tenantId, userId as string);
63
- return reply.send(shares);
64
+ return reply.send(shares.filter((r: any) => (r as any).visibility !== "internal"));
64
65
  }
65
66
 
66
67
  async updateShare(request: FastifyRequest, reply: FastifyReply) {
@@ -108,17 +109,29 @@ export class ResourceController {
108
109
 
109
110
  this.deps.logger.info("[share] resolve start", { token, subPath });
110
111
 
111
- // API runtime proxy to sandbox
112
- if (subPath && (subPath.endsWith(".js") || subPath.endsWith(".py")) && subPath.startsWith("api/")) {
112
+ const isApi = subPath && /(^|\/)api\//.test(subPath) && (subPath.endsWith(".js") || subPath.endsWith(".py"));
113
+
114
+ // Only API paths accept non-GET methods
115
+ if (request.method !== "GET" && !isApi) {
116
+ return reply.status(405).send({ error: "Method not allowed" });
117
+ }
118
+
119
+ // API runtime — proxy to sandbox. Detected by /api/ anywhere in subPath.
120
+ if (isApi) {
113
121
  const record = await this.deps.store.findByToken(token);
114
122
  if (!record || record.revoked) return reply.status(404).send("Not found");
115
123
  if (record.expiresAt && new Date(record.expiresAt) < new Date()) {
116
124
  return reply.status(410).send("Expired");
117
125
  }
118
126
  // Auth check for API calls (same as file access)
119
- if (!this._isInternalRequest(request) && record.visibility === "password") {
120
- if (!this._isUnlocked(request, token)) {
121
- return reply.status(401).send({ error: "Password required" });
127
+ if (!this._isInternalRequest(request)) {
128
+ if (record.visibility === "internal") {
129
+ return reply.status(401).send({ error: "Internal access only" });
130
+ }
131
+ if (record.visibility === "password") {
132
+ if (!this._isUnlocked(request, token)) {
133
+ return reply.status(401).send({ error: "Password required" });
134
+ }
122
135
  }
123
136
  }
124
137
  const ext = subPath.endsWith(".py") ? "py" : "js";
@@ -156,6 +169,10 @@ export class ResourceController {
156
169
  // 3. Auth
157
170
  const isInternal = this._isInternalRequest(request);
158
171
  if (!isInternal) {
172
+ // Internal shares: must be logged in
173
+ if (record.visibility === "internal") {
174
+ return reply.status(401).send({ error: "Internal access only" });
175
+ }
159
176
  if (record.visibility === "password") {
160
177
  const unlocked = this._isUnlocked(request, token);
161
178
  if (!unlocked) {
@@ -190,6 +207,39 @@ export class ResourceController {
190
207
  return this._serveFile(record.address, token, subPath, sandboxConfig, reply);
191
208
  }
192
209
 
210
+ /**
211
+ * Thin redirect: /f/{uuidHex3}/{path} → /s/{internal-token}/{path}
212
+ * Decodes 3 UUIDs (no dashes, 96 hex chars), ensures internal share, 302 redirects.
213
+ */
214
+ async resolveDirectFile(request: FastifyRequest, reply: FastifyReply) {
215
+ const params = request.params as unknown as { tid: string; wid: string; pid: string; "*"?: string };
216
+ const tenantId = params.tid;
217
+ const workspaceId = params.wid;
218
+ const projectId = params.pid;
219
+ const subPath = (params["*"] ?? "");
220
+
221
+ const isApi = subPath && /(^|\/)api\//.test(subPath) && (subPath.endsWith(".js") || subPath.endsWith(".py"));
222
+
223
+ // Only API paths accept non-GET methods
224
+ if (request.method !== "GET" && !isApi) {
225
+ return reply.status(405).send({ error: "Method not allowed" });
226
+ }
227
+
228
+ // API runtime
229
+ if (isApi) {
230
+ const ext = subPath.endsWith(".py") ? "py" : "js";
231
+ return this._execApiCore({
232
+ tenantId, workspaceId, projectId, assistantId: "",
233
+ resourcePath: subPath,
234
+ }, subPath, ext, request, reply);
235
+ }
236
+
237
+ const volume = buildNamedVolumeName("p", "project", tenantId, workspaceId, projectId);
238
+ return this._resolveAndServe(volume, subPath, reply, `/f/${tenantId}/${workspaceId}/${projectId}`,
239
+ { tenantId, workspaceId, projectId, assistantId: null },
240
+ );
241
+ }
242
+
193
243
  async unlockShare(request: FastifyRequest, reply: FastifyReply) {
194
244
  const { token } = request.params as unknown as { token: string };
195
245
  const { password } = request.body as unknown as { password: string };
@@ -219,9 +269,17 @@ export class ResourceController {
219
269
  }
220
270
 
221
271
  private _isInternalRequest(request: FastifyRequest): boolean {
222
- // Check for valid user session from auth middleware
223
272
  const user = (request as any).user;
224
- return !!user && !!user.id;
273
+ if (user && user.id) return true;
274
+ // Fallback: same-origin browser request (iframe/img embed — no Auth header)
275
+ const origin = request.headers.origin || request.headers.referer;
276
+ if (origin) {
277
+ try {
278
+ const { hostname } = new URL(origin);
279
+ if (hostname === request.hostname) return true;
280
+ } catch { /* malformed */ }
281
+ }
282
+ return false;
225
283
  }
226
284
 
227
285
  private _isUnlocked(request: FastifyRequest, token: string): boolean {
@@ -234,37 +292,33 @@ export class ResourceController {
234
292
  return /\.[a-zA-Z0-9]+$/.test(path);
235
293
  }
236
294
 
237
- private async _serveFile(
238
- address: ResourceAddress,
239
- token: string,
240
- subPath: string,
241
- sandboxConfig: { tenantId: string; workspaceId: string; projectId: string; assistantId: string | null },
295
+ /**
296
+ * Shared file resolution: directory → index.html, volume FS → sandbox, base tag, response.
297
+ */
298
+ private async _resolveAndServe(
299
+ volume: string,
300
+ resourcePath: string,
242
301
  reply: FastifyReply,
302
+ baseTagPrefix: string,
303
+ sandboxConfig?: { tenantId: string; workspaceId: string; projectId: string; assistantId: string | null },
243
304
  ) {
244
- const provider = this.deps.sandboxManager.getDefaultProvider();
245
- const resolver = provider.getResourceResolver();
246
- const sandboxPath = subPath
247
- ? this._resolveSafeSubPath(address.resourcePath, subPath)
248
- : address.resourcePath;
249
- // If entry has no file extension, treat as directory → try index.html
250
- const entryPath = (!subPath && !this._isFilePath(sandboxPath))
251
- ? `${sandboxPath}/index.html`
252
- : sandboxPath;
253
-
254
- this.deps.logger.info("[share] serving file", {
255
- token, volume: address.volume, sandboxPath: entryPath, subPath: subPath || "(none)",
256
- tenantId: sandboxConfig.tenantId, workspaceId: sandboxConfig.workspaceId, projectId: sandboxConfig.projectId,
257
- });
305
+ const cleanPath = resourcePath.replace(/\/+$/, "");
306
+ const entryPath = !cleanPath
307
+ ? "index.html"
308
+ : this._isFilePath(cleanPath)
309
+ ? cleanPath
310
+ : `${cleanPath}/index.html`;
311
+
312
+ const address: ResourceAddress = { volume, resourcePath: "" };
258
313
 
259
314
  let buf: Buffer;
260
315
  try {
261
- // Try volume FS first
262
- buf = await resolver.resolve({ ...address, resourcePath: entryPath });
263
- this.deps.logger.info("[share] resolved via volume FS", { token, size: buf.length });
264
- } catch (err) {
265
- this.deps.logger.warn("[share] volume FS failed, trying sandbox fallback", {
266
- token, path: entryPath, error: (err as Error).message,
267
- });
316
+ // Volume FS first
317
+ const provider = this.deps.sandboxManager.getDefaultProvider();
318
+ buf = await provider.getResourceResolver().resolve({ ...address, resourcePath: entryPath });
319
+ } catch {
320
+ // Sandbox fallback
321
+ if (!sandboxConfig) return reply.status(404).send("File not found");
268
322
  try {
269
323
  const sandbox = await this.deps.sandboxManager.getSandboxFromConfig({
270
324
  assistant_id: sandboxConfig.assistantId ?? "",
@@ -274,32 +328,56 @@ export class ResourceController {
274
328
  projectId: sandboxConfig.projectId,
275
329
  });
276
330
  buf = await sandbox.file.downloadFile({ file: `/project/${entryPath}` });
277
- this.deps.logger.info("[share] resolved via sandbox fallback", { token, size: buf.length });
278
- } catch (err) {
279
- this.deps.logger.warn("[share] all resolution attempts failed", { token, resourcePath: entryPath, error: (err as Error).message });
331
+ } catch {
280
332
  return reply.status(404).send("File not found");
281
333
  }
282
334
  }
283
335
 
284
- const fullPath = entryPath;
285
- const filename = getFilenameFromPath(fullPath);
286
- const isHtml = !subPath && /\.(html|htm)$/i.test(filename);
336
+ const filename = entryPath.split("/").pop() || "download";
337
+ const contentType = getContentTypeFromFilename(filename);
287
338
 
288
- if (isHtml) {
289
- buf = this._injectBaseTag(buf, token);
339
+ // Inject base tag for HTML
340
+ if (/\.(html|htm)$/i.test(filename)) {
341
+ const baseDir = cleanPath
342
+ ? (this._isFilePath(cleanPath) ? cleanPath.replace(/[^/]+$/, "") : cleanPath)
343
+ : "";
344
+ const html = buf.toString("utf-8");
345
+ if (!/<base\b/i.test(html)) {
346
+ const baseTag = `<base href="${baseTagPrefix}${baseDir ? "/" + baseDir : ""}/">`;
347
+ buf = Buffer.from(
348
+ html.includes("<head>") ? html.replace("<head>", `<head>${baseTag}`) : baseTag + html,
349
+ "utf-8"
350
+ );
351
+ }
290
352
  }
291
353
 
292
- const contentType = getContentTypeFromFilename(filename);
293
- const contentDisposition = `inline; filename="${filename.replace(/"/g, '\\"')}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
294
-
295
354
  return reply
296
355
  .status(200)
297
356
  .type(contentType)
298
- .header("Content-Disposition", contentDisposition)
357
+ .header("Content-Disposition", `inline; filename*=UTF-8''${encodeURIComponent(filename)}`)
299
358
  .header("Access-Control-Allow-Origin", "*")
300
359
  .send(buf);
301
360
  }
302
361
 
362
+ private async _serveFile(
363
+ address: ResourceAddress,
364
+ token: string,
365
+ subPath: string,
366
+ sandboxConfig: { tenantId: string; workspaceId: string; projectId: string; assistantId: string | null },
367
+ reply: FastifyReply,
368
+ ) {
369
+ const resourcePath = subPath
370
+ ? this._resolveSafeSubPath(address.resourcePath, subPath)
371
+ : address.resourcePath;
372
+ return this._resolveAndServe(
373
+ address.volume,
374
+ resourcePath,
375
+ reply,
376
+ `/s/${token}`,
377
+ sandboxConfig,
378
+ );
379
+ }
380
+
303
381
  private async _execApi(
304
382
  record: ShareRecord,
305
383
  apiSubPath: string,
@@ -307,26 +385,41 @@ export class ResourceController {
307
385
  request: FastifyRequest,
308
386
  reply: FastifyReply,
309
387
  ) {
310
- // Get or start sandbox
388
+ const basePath = this._isFilePath(record.address.resourcePath)
389
+ ? ""
390
+ : `${record.address.resourcePath}/`;
391
+ return this._execApiCore({
392
+ tenantId: record.tenantId,
393
+ workspaceId: record.workspaceId,
394
+ projectId: record.projectId,
395
+ assistantId: record.assistantId,
396
+ resourcePath: basePath + apiSubPath,
397
+ }, apiSubPath, ext, request, reply);
398
+ }
399
+
400
+ private async _execApiCore(
401
+ config: { tenantId: string; workspaceId: string; projectId: string; assistantId?: string | null; resourcePath: string },
402
+ apiSubPath: string,
403
+ ext: "js" | "py",
404
+ request: FastifyRequest,
405
+ reply: FastifyReply,
406
+ ) {
311
407
  let sandbox: any;
312
408
  try {
313
409
  sandbox = await this.deps.sandboxManager.getSandboxFromConfig({
314
- assistant_id: record.assistantId ?? "",
410
+ assistant_id: config.assistantId ?? "",
315
411
  thread_id: "",
316
- tenantId: record.tenantId,
317
- workspaceId: record.workspaceId,
318
- projectId: record.projectId,
412
+ tenantId: config.tenantId,
413
+ workspaceId: config.workspaceId,
414
+ projectId: config.projectId,
319
415
  });
320
416
  } catch (err) {
321
417
  this.deps.logger.error("[share] sandbox start failed for API", { error: (err as Error).message });
322
418
  return reply.status(502).send({ error: "Sandbox unavailable" });
323
419
  }
324
420
 
325
- // Build the command: node /project/api/hello.js or python3 /project/api/hello.py
326
- const scriptPath = `/project/${apiSubPath}`; // e.g., /project/api/hello.js
327
- const cmd = ext === "py"
328
- ? `python3 ${scriptPath}`
329
- : `node ${scriptPath}`;
421
+ const scriptPath = `/project/${config.resourcePath}`;
422
+ const cmd = ext === "py" ? `python3 ${scriptPath}` : `node ${scriptPath}`;
330
423
 
331
424
  // Pass query params and body via environment
332
425
  const queryStr = request.url.includes("?") ? (request.url as string).split("?")[1] : "";
@@ -344,30 +437,27 @@ export class ResourceController {
344
437
  `export API_BODY='${bodyStr.replace(/'/g, "'\\''")}'`,
345
438
  `export API_METHOD='${request.method}'`,
346
439
  `export API_GATEWAY_URL='${request.protocol}://${request.hostname}'`,
347
- `export API_TENANT_ID='${record.tenantId}'`,
348
- `export API_ASSISTANT_ID='${record.assistantId || ""}'`,
440
+ `export API_TENANT_ID='${config.tenantId}'`,
441
+ `export API_ASSISTANT_ID='${config.assistantId || ""}'`,
349
442
  ].join(" && ");
350
443
 
351
444
  const fullCmd = `${envVars} && ${cmd}`;
352
445
 
353
- this.deps.logger.info("[share] API exec", { token: record.token, cmd: cmd, scriptPath });
446
+ this.deps.logger.info("[api] exec", { scriptPath, cmd: cmd.substring(0, 80) });
354
447
 
355
448
  try {
356
- const result = await sandbox.shell.exec(fullCmd);
449
+ const result = await sandbox.shell.execCommand({ command: fullCmd });
357
450
  // Parse stdout as JSON
358
451
  let data: unknown;
359
452
  try {
360
- data = JSON.parse(result.stdout.trim() || "{}");
453
+ data = JSON.parse(result.output?.trim() || "{}");
361
454
  } catch {
362
- // If stdout is not JSON, wrap it
363
- data = { output: result.stdout.trim() };
455
+ data = { output: result.output?.trim() || "" };
364
456
  }
365
457
  return reply.status(200).type("application/json").send(data);
366
458
  } catch (err) {
367
- this.deps.logger.warn("[share] API exec failed", { token: record.token, cmd, error: (err as Error).message });
368
- // Try to extract error from stderr
369
- const msg = (err as any)?.stderr || (err as Error).message;
370
- return reply.status(500).send({ error: msg });
459
+ this.deps.logger.warn("[api] exec failed", { scriptPath, error: (err as Error).message });
460
+ return reply.status(500).send({ error: (err as Error).message });
371
461
  }
372
462
  }
373
463
 
@@ -382,24 +472,6 @@ export class ResourceController {
382
472
  return (entryDir + subPath).replace(/\/+/g, "/");
383
473
  }
384
474
 
385
- private _injectBaseTag(buf: Buffer, token: string): Buffer {
386
- const html = buf.toString("utf-8");
387
- if (/<base\b/i.test(html)) return buf;
388
- const baseTag = `<base href="/s/${token}/">`;
389
- if (html.includes("</head>")) {
390
- return Buffer.from(html.replace("</head>", `${baseTag}</head>`), "utf-8");
391
- }
392
- // No </head> — insert after <head> or <html> or after <!doctype>
393
- if (html.includes("<head>")) {
394
- return Buffer.from(html.replace("<head>", `<head>${baseTag}`), "utf-8");
395
- }
396
- if (html.includes("<html>")) {
397
- return Buffer.from(html.replace("<html>", `<html>${baseTag}`), "utf-8");
398
- }
399
- // Fallback: prepend (will be after <!doctype> if present since html starts with <!doctype)
400
- return Buffer.from(baseTag + html, "utf-8");
401
- }
402
-
403
475
  private _passwordPage(token: string): string {
404
476
  return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Password Protected</title><style>body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#f5f5f5}.card{background:#fff;padding:32px;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.1);width:100%;max-width:360px;text-align:center}h2{margin:0 0 8px;font-size:20px}p{margin:0 0 20px;color:#666;font-size:14px}input{width:100%;padding:10px 12px;border:1px solid #d9d9d9;border-radius:8px;font-size:14px;box-sizing:border-box;margin-bottom:12px}button{width:100%;padding:10px;background:#6366f1;color:#fff;border:none;border-radius:8px;font-size:14px;cursor:pointer}.error{color:#ef4444;font-size:13px;margin-top:8px;display:none}</style></head><body><div class="card"><h2>Password Protected</h2><p>This share requires a password to access.</p><form id="f"><input type="password" id="p" placeholder="Enter password" required><button type="submit">Unlock</button><div class="error" id="e">Incorrect password</div></form></div><script>document.getElementById('f').onsubmit=async(e)=>{e.preventDefault();const r=await fetch('/s/${token}/unlock',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:document.getElementById('p').value})});if(r.ok)location.reload();else document.getElementById('e').style.display='block'}</script></body></html>`;
405
477
  }
package/src/index.ts CHANGED
@@ -78,8 +78,8 @@ function initializeLogger(config: LoggerConfig) {
78
78
 
79
79
  // 创建 Fastify 应用
80
80
  const app = fastify({
81
- logger: false, // 禁用内置日志记录器
82
- bodyLimit: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024, // Default 50MB, configurable via BODY_LIMIT env var
81
+ logger: false,
82
+ bodyLimit: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024,
83
83
  });
84
84
 
85
85
  // Custom content type parser to handle DELETE requests with Content-Type but no body
@@ -138,6 +138,9 @@ app.addHook("preHandler", async (request, reply) => {
138
138
  const urlPath = request.url.split("?")[0];
139
139
  if (urlPath.includes("/viewfile") || urlPath.includes("/downloadfile") || urlPath.includes("/uploadfile")) return;
140
140
 
141
+ // Internal file access — auth handled by controller (Referer check)
142
+ if (urlPath.startsWith("/f/") || urlPath === "/f") return;
143
+
141
144
  return reply.status(401).send({
142
145
  success: false,
143
146
  error: "Unauthorized - Missing or invalid token",
@@ -19,6 +19,7 @@ import * as dataQueryController from "../controllers/data-query";
19
19
  import * as workflowTrackingController from "../controllers/workflow-tracking";
20
20
  import * as paController from "../controllers/personal-assistant";
21
21
  import * as tasksController from "../controllers/tasks";
22
+ import { middlewareTypesRoutes } from "../controllers/middleware-types";
22
23
  import {
23
24
  createRunSchema,
24
25
  getAllMemoryItemsSchema,
@@ -41,6 +42,7 @@ import { registerSandboxProxyRoutes } from "../controllers/sandbox";
41
42
  import { registerWorkspaceRoutes } from "../controllers/workspace";
42
43
  import { registerDatabaseConfigRoutes } from "../controllers/database-configs";
43
44
  import { registerMetricsServerConfigRoutes } from "../controllers/metrics-configs";
45
+ import { connectionRoutes } from "../controllers/connections";
44
46
  import { registerMcpServerConfigRoutes } from "../controllers/mcp-configs";
45
47
  import { registerEvalRoutes } from "../controllers/eval";
46
48
  import { registerUserRoutes } from "../controllers/users";
@@ -412,6 +414,11 @@ export const registerLatticeRoutes = (app: FastifyInstance, channelDeps?: { rout
412
414
 
413
415
  registerMetricsServerConfigRoutes(app);
414
416
 
417
+ connectionRoutes(app);
418
+
419
+ // Plugin middleware types discovery
420
+ middlewareTypesRoutes(app);
421
+
415
422
  // Data query route
416
423
  app.post<{
417
424
  Body: any;
@@ -8,6 +8,9 @@ export function registerResourceRoutes(
8
8
  // Public share access — MUST be before /api/** wildcard routes
9
9
  app.get("/s/:token", controller.resolveResource.bind(controller));
10
10
  app.get("/s/:token/*", controller.resolveResource.bind(controller));
11
+ app.post("/s/:token/*", controller.resolveResource.bind(controller));
12
+ app.put("/s/:token/*", controller.resolveResource.bind(controller));
13
+ app.delete("/s/:token/*", controller.resolveResource.bind(controller));
11
14
  app.post("/s/:token/unlock", controller.unlockShare.bind(controller));
12
15
  app.options("/s/:token", async (_req, reply) => {
13
16
  reply.header("Access-Control-Allow-Origin", "*");
@@ -16,6 +19,14 @@ export function registerResourceRoutes(
16
19
  return reply.status(204).send();
17
20
  });
18
21
 
22
+ // Internal file access: /f/{tenant}/{workspace}/{project}/*
23
+ const dirFile = controller.resolveDirectFile.bind(controller);
24
+ app.get("/f/:tid/:wid/:pid", dirFile);
25
+ app.get("/f/:tid/:wid/:pid/*", dirFile);
26
+ app.post("/f/:tid/:wid/:pid/*", dirFile);
27
+ app.put("/f/:tid/:wid/:pid/*", dirFile);
28
+ app.delete("/f/:tid/:wid/:pid/*", dirFile);
29
+
19
30
  // Management API
20
31
  app.post("/api/resources/share", controller.createShare.bind(controller));
21
32
  app.get("/api/resources/shares", controller.listShares.bind(controller));
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/controllers/resources.ts"],"sourcesContent":["import { FastifyRequest, FastifyReply } from \"fastify\";\nimport type { ResourceAddress, ShareRecord, SharedResourceStore } from \"@axiom-lattice/protocols\";\nimport bcrypt from \"bcryptjs\";\nimport {\n generateToken,\n createSharePayload,\n TokenCache,\n SandboxLatticeManager,\n} from \"@axiom-lattice/core\";\nimport { getContentTypeFromFilename, getFilenameFromPath } from \"../utils/mime\";\n\ninterface ResourceControllerDeps {\n store: SharedResourceStore;\n sandboxManager: SandboxLatticeManager;\n cache: TokenCache;\n logger: { info: (msg: string, obj?: object) => void; warn: (msg: string, obj?: object) => void; error: (msg: string, obj?: object) => void };\n}\n\nexport class ResourceController {\n constructor(private deps: ResourceControllerDeps) {}\n\n async createShare(request: FastifyRequest, reply: FastifyReply) {\n const userId = (request as any).user\n ? (request as any).user.id ?? \"unknown\"\n : \"unknown\";\n const tenantId = (request.headers[\"x-tenant-id\"] as string) || \"default\";\n const workspaceId = request.headers[\"x-workspace-id\"] as string;\n const projectId = request.headers[\"x-project-id\"] as string;\n const body = request.body as any;\n\n if (!workspaceId || !projectId) {\n return reply.status(400).send({ error: \"x-workspace-id and x-project-id headers required\" });\n }\n\n const payload = createSharePayload(\n tenantId,\n workspaceId,\n projectId,\n userId as string,\n body,\n );\n const token = generateToken();\n\n try {\n await this.deps.store.create({ ...payload, token });\n this.deps.logger.info(\n \"[share] created\",\n { token, originalPath: body.resourcePath, normalizedPath: payload.address.resourcePath, volume: payload.address.volume, userId },\n );\n } catch {\n return reply.status(500).send({ error: \"Failed to create share\" });\n }\n\n return reply.status(201).send({ token, url: `/s/${token}` });\n }\n\n async listShares(request: FastifyRequest, reply: FastifyReply) {\n const userId = (request as any).user\n ? (request as any).user.id ?? \"unknown\"\n : \"unknown\";\n const tenantId = (request.headers[\"x-tenant-id\"] as string) || \"default\";\n const shares = await this.deps.store.listByUser(tenantId, userId as string);\n return reply.send(shares);\n }\n\n async updateShare(request: FastifyRequest, reply: FastifyReply) {\n const { token } = request.params as unknown as { token: string };\n const userId = (request as any).user\n ? (request as any).user.id ?? \"unknown\"\n : \"unknown\";\n const patch = request.body as any;\n\n const record = await this.deps.store.findByToken(token);\n if (!record || record.createdBy !== userId) {\n return reply.status(404).send({ error: \"Share not found\" });\n }\n\n await this.deps.store.update(token, patch);\n\n if (patch.revoked) {\n this.deps.cache.invalidate(token);\n }\n\n return reply.send({ ok: true });\n }\n\n async revokeShare(request: FastifyRequest, reply: FastifyReply) {\n const { token } = request.params as unknown as { token: string };\n const userId = (request as any).user\n ? (request as any).user.id ?? \"unknown\"\n : \"unknown\";\n\n const record = await this.deps.store.findByToken(token);\n if (!record || record.createdBy !== userId) {\n return reply.status(404).send({ error: \"Share not found\" });\n }\n\n await this.deps.store.update(token, { revoked: true });\n this.deps.cache.invalidate(token);\n\n return reply.send({ ok: true });\n }\n\n async resolveResource(request: FastifyRequest, reply: FastifyReply) {\n const params = request.params as unknown as { token: string; \"*\"?: string };\n const { token } = params;\n const subPath = params[\"*\"] ?? \"\";\n\n this.deps.logger.info(\"[share] resolve start\", { token, subPath });\n\n // API runtime — proxy to sandbox\n if (subPath && (subPath.endsWith(\".js\") || subPath.endsWith(\".py\")) && subPath.startsWith(\"api/\")) {\n const record = await this.deps.store.findByToken(token);\n if (!record || record.revoked) return reply.status(404).send(\"Not found\");\n if (record.expiresAt && new Date(record.expiresAt) < new Date()) {\n return reply.status(410).send(\"Expired\");\n }\n // Auth check for API calls (same as file access)\n if (!this._isInternalRequest(request) && record.visibility === \"password\") {\n if (!this._isUnlocked(request, token)) {\n return reply.status(401).send({ error: \"Password required\" });\n }\n }\n const ext = subPath.endsWith(\".py\") ? \"py\" : \"js\";\n return this._execApi(record, subPath, ext, request, reply);\n }\n\n // 1. Check cache\n const cached = this.deps.cache.get(token);\n if (cached && !cached.requiresUnlock) {\n this.deps.logger.info(\"[share] cache hit\", { token, volume: cached.address.volume, resourcePath: cached.address.resourcePath });\n return this._serveFile(cached.address, token, subPath, cached.sandboxConfig ?? { tenantId: \"default\", workspaceId: \"\", projectId: \"\", assistantId: null }, reply);\n }\n this.deps.logger.info(\"[share] cache miss, querying DB\", { token });\n\n // 2. Single DB lookup\n const record = await this.deps.store.findByToken(token);\n if (!record || record.revoked) {\n this.deps.logger.warn(\"[share] token not found or revoked\", { token, found: !!record, revoked: record?.revoked });\n return reply.status(404).send(\"Not found\");\n }\n if (record.expiresAt && new Date(record.expiresAt) < new Date()) {\n this.deps.logger.info(\"[share] token expired\", { token, expiresAt: record.expiresAt });\n return reply.status(410).send(\"Expired\");\n }\n\n this.deps.logger.info(\"[share] record found\", {\n token,\n volume: record.address.volume,\n resourcePath: record.address.resourcePath,\n visibility: record.visibility,\n workspaceId: record.workspaceId,\n projectId: record.projectId,\n });\n\n // 3. Auth\n const isInternal = this._isInternalRequest(request);\n if (!isInternal) {\n if (record.visibility === \"password\") {\n const unlocked = this._isUnlocked(request, token);\n if (!unlocked) {\n this.deps.logger.info(\"[share] password protected, returning password page\", { token });\n this.deps.cache.set(token, {\n address: record.address,\n requiresUnlock: true,\n sandboxConfig: { tenantId: record.tenantId, workspaceId: record.workspaceId, projectId: record.projectId, assistantId: record.assistantId },\n });\n return reply.type(\"text/html\").send(this._passwordPage(token));\n }\n this.deps.logger.info(\"[share] password unlocked\", { token });\n }\n }\n\n // 4. Access count\n if (record.maxAccess !== null) {\n const ok = await this.deps.store.atomicIncrementAccess(token);\n if (!ok) return reply.status(410).send(\"Access limit reached\");\n } else {\n this.deps.store.incrementAccess(token).catch(() => {});\n }\n\n // 5. Cache\n const sandboxConfig = { tenantId: record.tenantId, workspaceId: record.workspaceId, projectId: record.projectId, assistantId: record.assistantId };\n this.deps.cache.set(token, {\n address: record.address,\n requiresUnlock: record.visibility === \"password\" && !isInternal,\n sandboxConfig,\n });\n\n return this._serveFile(record.address, token, subPath, sandboxConfig, reply);\n }\n\n async unlockShare(request: FastifyRequest, reply: FastifyReply) {\n const { token } = request.params as unknown as { token: string };\n const { password } = request.body as unknown as { password: string };\n\n const record = await this.deps.store.findByToken(token);\n if (!record || !record.passwordHash) {\n return reply.status(404).send({ error: \"Invalid request\" });\n }\n\n const valid = await bcrypt.compare(password, record.passwordHash!);\n if (!valid) {\n return reply.status(401).send({ error: \"Incorrect password\" });\n }\n\n const cookieOpts = [\n `share_unlock_${token}=1`,\n \"Max-Age=86400\",\n `Path=/s/${token}`,\n \"SameSite=Lax\",\n \"HttpOnly\",\n ];\n if (request.protocol === \"https\") {\n cookieOpts.push(\"Secure\");\n }\n reply.header(\"Set-Cookie\", cookieOpts.join(\"; \"));\n return reply.send({ ok: true });\n }\n\n private _isInternalRequest(request: FastifyRequest): boolean {\n // Check for valid user session from auth middleware\n const user = (request as any).user;\n return !!user && !!user.id;\n }\n\n private _isUnlocked(request: FastifyRequest, token: string): boolean {\n const cookie = request.headers.cookie ?? \"\";\n return cookie.includes(`share_unlock_${token}=1`);\n }\n\n /** Returns true if the path has a file extension indicating it's a file, not a directory */\n private _isFilePath(path: string): boolean {\n return /\\.[a-zA-Z0-9]+$/.test(path);\n }\n\n private async _serveFile(\n address: ResourceAddress,\n token: string,\n subPath: string,\n sandboxConfig: { tenantId: string; workspaceId: string; projectId: string; assistantId: string | null },\n reply: FastifyReply,\n ) {\n const provider = this.deps.sandboxManager.getDefaultProvider();\n const resolver = provider.getResourceResolver();\n const sandboxPath = subPath\n ? this._resolveSafeSubPath(address.resourcePath, subPath)\n : address.resourcePath;\n // If entry has no file extension, treat as directory → try index.html\n const entryPath = (!subPath && !this._isFilePath(sandboxPath))\n ? `${sandboxPath}/index.html`\n : sandboxPath;\n\n this.deps.logger.info(\"[share] serving file\", {\n token, volume: address.volume, sandboxPath: entryPath, subPath: subPath || \"(none)\",\n tenantId: sandboxConfig.tenantId, workspaceId: sandboxConfig.workspaceId, projectId: sandboxConfig.projectId,\n });\n\n let buf: Buffer;\n try {\n // Try volume FS first\n buf = await resolver.resolve({ ...address, resourcePath: entryPath });\n this.deps.logger.info(\"[share] resolved via volume FS\", { token, size: buf.length });\n } catch (err) {\n this.deps.logger.warn(\"[share] volume FS failed, trying sandbox fallback\", {\n token, path: entryPath, error: (err as Error).message,\n });\n try {\n const sandbox = await this.deps.sandboxManager.getSandboxFromConfig({\n assistant_id: sandboxConfig.assistantId ?? \"\",\n thread_id: \"\",\n tenantId: sandboxConfig.tenantId,\n workspaceId: sandboxConfig.workspaceId,\n projectId: sandboxConfig.projectId,\n });\n buf = await sandbox.file.downloadFile({ file: `/project/${entryPath}` });\n this.deps.logger.info(\"[share] resolved via sandbox fallback\", { token, size: buf.length });\n } catch (err) {\n this.deps.logger.warn(\"[share] all resolution attempts failed\", { token, resourcePath: entryPath, error: (err as Error).message });\n return reply.status(404).send(\"File not found\");\n }\n }\n\n const fullPath = entryPath;\n const filename = getFilenameFromPath(fullPath);\n const isHtml = !subPath && /\\.(html|htm)$/i.test(filename);\n\n if (isHtml) {\n buf = this._injectBaseTag(buf, token);\n }\n\n const contentType = getContentTypeFromFilename(filename);\n const contentDisposition = `inline; filename=\"${filename.replace(/\"/g, '\\\\\"')}\"; filename*=UTF-8''${encodeURIComponent(filename)}`;\n\n return reply\n .status(200)\n .type(contentType)\n .header(\"Content-Disposition\", contentDisposition)\n .header(\"Access-Control-Allow-Origin\", \"*\")\n .send(buf);\n }\n\n private async _execApi(\n record: ShareRecord,\n apiSubPath: string,\n ext: \"js\" | \"py\",\n request: FastifyRequest,\n reply: FastifyReply,\n ) {\n // Get or start sandbox\n let sandbox: any;\n try {\n sandbox = await this.deps.sandboxManager.getSandboxFromConfig({\n assistant_id: record.assistantId ?? \"\",\n thread_id: \"\",\n tenantId: record.tenantId,\n workspaceId: record.workspaceId,\n projectId: record.projectId,\n });\n } catch (err) {\n this.deps.logger.error(\"[share] sandbox start failed for API\", { error: (err as Error).message });\n return reply.status(502).send({ error: \"Sandbox unavailable\" });\n }\n\n // Build the command: node /project/api/hello.js or python3 /project/api/hello.py\n const scriptPath = `/project/${apiSubPath}`; // e.g., /project/api/hello.js\n const cmd = ext === \"py\"\n ? `python3 ${scriptPath}`\n : `node ${scriptPath}`;\n\n // Pass query params and body via environment\n const queryStr = request.url.includes(\"?\") ? (request.url as string).split(\"?\")[1] : \"\";\n const queryObj: Record<string, string> = {};\n if (queryStr) {\n for (const [k, v] of new URLSearchParams(queryStr)) {\n queryObj[k] = v;\n }\n }\n\n const bodyStr = (request.body as string) ?? \"\";\n\n const envVars = [\n `export API_QUERY='${JSON.stringify(queryObj).replace(/'/g, \"'\\\\''\")}'`,\n `export API_BODY='${bodyStr.replace(/'/g, \"'\\\\''\")}'`,\n `export API_METHOD='${request.method}'`,\n `export API_GATEWAY_URL='${request.protocol}://${request.hostname}'`,\n `export API_TENANT_ID='${record.tenantId}'`,\n `export API_ASSISTANT_ID='${record.assistantId || \"\"}'`,\n ].join(\" && \");\n\n const fullCmd = `${envVars} && ${cmd}`;\n\n this.deps.logger.info(\"[share] API exec\", { token: record.token, cmd: cmd, scriptPath });\n\n try {\n const result = await sandbox.shell.exec(fullCmd);\n // Parse stdout as JSON\n let data: unknown;\n try {\n data = JSON.parse(result.stdout.trim() || \"{}\");\n } catch {\n // If stdout is not JSON, wrap it\n data = { output: result.stdout.trim() };\n }\n return reply.status(200).type(\"application/json\").send(data);\n } catch (err) {\n this.deps.logger.warn(\"[share] API exec failed\", { token: record.token, cmd, error: (err as Error).message });\n // Try to extract error from stderr\n const msg = (err as any)?.stderr || (err as Error).message;\n return reply.status(500).send({ error: msg });\n }\n }\n\n private _resolveSafeSubPath(entryFile: string, subPath: string): string {\n if (!subPath) return entryFile;\n if (subPath.includes(\"..\")) throw new Error(\"Path traversal denied\");\n // If entry is a file (has extension), strip filename to get directory.\n // If entry is a directory (no extension), keep it as base.\n const entryDir = this._isFilePath(entryFile)\n ? entryFile.replace(/[^/]+$/, \"\")\n : entryFile + \"/\";\n return (entryDir + subPath).replace(/\\/+/g, \"/\");\n }\n\n private _injectBaseTag(buf: Buffer, token: string): Buffer {\n const html = buf.toString(\"utf-8\");\n if (/<base\\b/i.test(html)) return buf;\n const baseTag = `<base href=\"/s/${token}/\">`;\n if (html.includes(\"</head>\")) {\n return Buffer.from(html.replace(\"</head>\", `${baseTag}</head>`), \"utf-8\");\n }\n // No </head> — insert after <head> or <html> or after <!doctype>\n if (html.includes(\"<head>\")) {\n return Buffer.from(html.replace(\"<head>\", `<head>${baseTag}`), \"utf-8\");\n }\n if (html.includes(\"<html>\")) {\n return Buffer.from(html.replace(\"<html>\", `<html>${baseTag}`), \"utf-8\");\n }\n // Fallback: prepend (will be after <!doctype> if present since html starts with <!doctype)\n return Buffer.from(baseTag + html, \"utf-8\");\n }\n\n private _passwordPage(token: string): string {\n return `<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Password Protected</title><style>body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#f5f5f5}.card{background:#fff;padding:32px;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.1);width:100%;max-width:360px;text-align:center}h2{margin:0 0 8px;font-size:20px}p{margin:0 0 20px;color:#666;font-size:14px}input{width:100%;padding:10px 12px;border:1px solid #d9d9d9;border-radius:8px;font-size:14px;box-sizing:border-box;margin-bottom:12px}button{width:100%;padding:10px;background:#6366f1;color:#fff;border:none;border-radius:8px;font-size:14px;cursor:pointer}.error{color:#ef4444;font-size:13px;margin-top:8px;display:none}</style></head><body><div class=\"card\"><h2>Password Protected</h2><p>This share requires a password to access.</p><form id=\"f\"><input type=\"password\" id=\"p\" placeholder=\"Enter password\" required><button type=\"submit\">Unlock</button><div class=\"error\" id=\"e\">Incorrect password</div></form></div><script>document.getElementById('f').onsubmit=async(e)=>{e.preventDefault();const r=await fetch('/s/${token}/unlock',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:document.getElementById('p').value})});if(r.ok)location.reload();else document.getElementById('e').style.display='block'}</script></body></html>`;\n }\n}\n"],"mappings":"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","names":["record","err"]}