@axiom-lattice/gateway 2.1.105 → 2.1.106
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/.turbo/turbo-build.log +12 -12
- package/CHANGELOG.md +13 -0
- package/dist/{chunk-R4ZO3HZ3.mjs → chunk-LHQY46YB.mjs} +2 -1
- package/dist/chunk-LHQY46YB.mjs.map +1 -0
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{resources-NE6DFF5I.mjs → resources-HEVGN3JM.mjs} +15 -8
- package/dist/resources-HEVGN3JM.mjs.map +1 -0
- package/package.json +6 -6
- package/src/controllers/resources.ts +15 -9
- package/src/utils/mime.ts +1 -0
- package/dist/chunk-R4ZO3HZ3.mjs.map +0 -1
- package/dist/resources-NE6DFF5I.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
getContentTypeFromFilename,
|
|
8
8
|
getFilenameFromPath
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-LHQY46YB.mjs";
|
|
10
10
|
|
|
11
11
|
// src/index.ts
|
|
12
12
|
import fastify from "fastify";
|
|
@@ -8336,7 +8336,7 @@ var start = async (config) => {
|
|
|
8336
8336
|
logger4.info("Registered sandbox manager from env configuration");
|
|
8337
8337
|
}
|
|
8338
8338
|
try {
|
|
8339
|
-
const { ResourceController } = await import("./resources-
|
|
8339
|
+
const { ResourceController } = await import("./resources-HEVGN3JM.mjs");
|
|
8340
8340
|
const sharedResourceStore = getStoreLattice17("default", "sharedResource").store;
|
|
8341
8341
|
const cache = new TokenCache();
|
|
8342
8342
|
const resourceController = new ResourceController({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getContentTypeFromFilename
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LHQY46YB.mjs";
|
|
4
4
|
|
|
5
5
|
// src/controllers/resources.ts
|
|
6
6
|
import bcrypt from "bcryptjs";
|
|
@@ -48,7 +48,7 @@ var ResourceController = class {
|
|
|
48
48
|
}
|
|
49
49
|
async updateShare(request, reply) {
|
|
50
50
|
const { token } = request.params;
|
|
51
|
-
const userId = request.user ? request.user.id :
|
|
51
|
+
const userId = request.user ? request.user.id ?? "unknown" : "unknown";
|
|
52
52
|
const patch = request.body;
|
|
53
53
|
const record = await this.deps.store.findByToken(token);
|
|
54
54
|
if (!record || record.createdBy !== userId) {
|
|
@@ -62,7 +62,7 @@ var ResourceController = class {
|
|
|
62
62
|
}
|
|
63
63
|
async revokeShare(request, reply) {
|
|
64
64
|
const { token } = request.params;
|
|
65
|
-
const userId = request.user ? request.user.id :
|
|
65
|
+
const userId = request.user ? request.user.id ?? "unknown" : "unknown";
|
|
66
66
|
const record = await this.deps.store.findByToken(token);
|
|
67
67
|
if (!record || record.createdBy !== userId) {
|
|
68
68
|
return reply.status(404).send({ error: "Share not found" });
|
|
@@ -141,10 +141,17 @@ var ResourceController = class {
|
|
|
141
141
|
if (!valid) {
|
|
142
142
|
return reply.status(401).send({ error: "Incorrect password" });
|
|
143
143
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
const cookieOpts = [
|
|
145
|
+
`share_unlock_${token}=1`,
|
|
146
|
+
"Max-Age=86400",
|
|
147
|
+
`Path=/s/${token}`,
|
|
148
|
+
"SameSite=Lax",
|
|
149
|
+
"HttpOnly"
|
|
150
|
+
];
|
|
151
|
+
if (request.protocol === "https") {
|
|
152
|
+
cookieOpts.push("Secure");
|
|
153
|
+
}
|
|
154
|
+
reply.header("Set-Cookie", cookieOpts.join("; "));
|
|
148
155
|
return reply.send({ ok: true });
|
|
149
156
|
}
|
|
150
157
|
_isInternalRequest(request) {
|
|
@@ -230,4 +237,4 @@ var ResourceController = class {
|
|
|
230
237
|
export {
|
|
231
238
|
ResourceController
|
|
232
239
|
};
|
|
233
|
-
//# sourceMappingURL=resources-
|
|
240
|
+
//# sourceMappingURL=resources-HEVGN3JM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/controllers/resources.ts"],"sourcesContent":["import { FastifyRequest, FastifyReply } from \"fastify\";\nimport type { ResourceAddress, 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 } 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 // 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 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\n this.deps.logger.info(\"[share] serving file\", {\n token, volume: address.volume, sandboxPath, 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: sandboxPath });\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: sandboxPath, 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/${sandboxPath}` });\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: sandboxPath, error: (err as Error).message });\n return reply.status(404).send(\"File not found\");\n }\n }\n\n const fullPath = sandboxPath;\n\n const filename = fullPath.split(\"/\").pop() || \"download\";\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\n return reply\n .status(200)\n .type(contentType)\n .header(\"Content-Disposition\", `inline; filename=\"${filename}\"`)\n .header(\"Access-Control-Allow-Origin\", \"*\")\n .send(buf);\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 const entryDir = entryFile.replace(/[^/]+$/, \"\");\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":["err"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/gateway",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.106",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"redis": "^5.0.1",
|
|
42
42
|
"uuid": "^9.0.1",
|
|
43
43
|
"zod": "3.25.76",
|
|
44
|
-
"@axiom-lattice/agent-eval": "2.1.
|
|
45
|
-
"@axiom-lattice/core": "2.1.
|
|
46
|
-
"@axiom-lattice/pg-stores": "1.0.
|
|
47
|
-
"@axiom-lattice/protocols": "2.1.
|
|
48
|
-
"@axiom-lattice/queue-redis": "1.0.
|
|
44
|
+
"@axiom-lattice/agent-eval": "2.1.88",
|
|
45
|
+
"@axiom-lattice/core": "2.1.94",
|
|
46
|
+
"@axiom-lattice/pg-stores": "1.0.85",
|
|
47
|
+
"@axiom-lattice/protocols": "2.1.48",
|
|
48
|
+
"@axiom-lattice/queue-redis": "1.0.47"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/bcrypt": "^6.0.0",
|
|
@@ -66,8 +66,8 @@ export class ResourceController {
|
|
|
66
66
|
async updateShare(request: FastifyRequest, reply: FastifyReply) {
|
|
67
67
|
const { token } = request.params as unknown as { token: string };
|
|
68
68
|
const userId = (request as any).user
|
|
69
|
-
? (request as any).user.id
|
|
70
|
-
:
|
|
69
|
+
? (request as any).user.id ?? "unknown"
|
|
70
|
+
: "unknown";
|
|
71
71
|
const patch = request.body as any;
|
|
72
72
|
|
|
73
73
|
const record = await this.deps.store.findByToken(token);
|
|
@@ -87,8 +87,8 @@ export class ResourceController {
|
|
|
87
87
|
async revokeShare(request: FastifyRequest, reply: FastifyReply) {
|
|
88
88
|
const { token } = request.params as unknown as { token: string };
|
|
89
89
|
const userId = (request as any).user
|
|
90
|
-
? (request as any).user.id
|
|
91
|
-
:
|
|
90
|
+
? (request as any).user.id ?? "unknown"
|
|
91
|
+
: "unknown";
|
|
92
92
|
|
|
93
93
|
const record = await this.deps.store.findByToken(token);
|
|
94
94
|
if (!record || record.createdBy !== userId) {
|
|
@@ -182,16 +182,22 @@ export class ResourceController {
|
|
|
182
182
|
return reply.status(404).send({ error: "Invalid request" });
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
// Plain text comparison for now — bcrypt will come in Task 12
|
|
186
185
|
const valid = await bcrypt.compare(password, record.passwordHash!);
|
|
187
186
|
if (!valid) {
|
|
188
187
|
return reply.status(401).send({ error: "Incorrect password" });
|
|
189
188
|
}
|
|
190
189
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
const cookieOpts = [
|
|
191
|
+
`share_unlock_${token}=1`,
|
|
192
|
+
"Max-Age=86400",
|
|
193
|
+
`Path=/s/${token}`,
|
|
194
|
+
"SameSite=Lax",
|
|
195
|
+
"HttpOnly",
|
|
196
|
+
];
|
|
197
|
+
if (request.protocol === "https") {
|
|
198
|
+
cookieOpts.push("Secure");
|
|
199
|
+
}
|
|
200
|
+
reply.header("Set-Cookie", cookieOpts.join("; "));
|
|
195
201
|
return reply.send({ ok: true });
|
|
196
202
|
}
|
|
197
203
|
|
package/src/utils/mime.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/mime.ts"],"sourcesContent":["const EXT_TO_MIME: Record<string, string> = {\n \".txt\": \"text/plain\",\n \".html\": \"text/html\",\n \".htm\": \"text/html\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".json\": \"application/json\",\n \".pdf\": \"application/pdf\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".zip\": \"application/zip\",\n \".csv\": \"text/csv\",\n \".xml\": \"application/xml\",\n \".xlsx\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n \".xls\": \"application/vnd.ms-excel\",\n \".pptx\": \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n \".ppt\": \"application/vnd.ms-powerpoint\",\n \".docx\": \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n \".doc\": \"application/msword\",\n \".mp4\": \"video/mp4\",\n \".webm\": \"video/webm\",\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n \".ogg\": \"audio/ogg\",\n};\n\nexport function getContentTypeFromFilename(filename: string): string {\n const ext = filename.includes(\".\")\n ? filename.slice(filename.lastIndexOf(\".\")).toLowerCase()\n : \"\";\n return EXT_TO_MIME[ext] ?? \"application/octet-stream\";\n}\n\nexport function getFilenameFromPath(path: string): string {\n const segments = path.replace(/\\/+$/, \"\").split(\"/\");\n return segments[segments.length - 1] || \"download\";\n}\n"],"mappings":";AAAA,IAAM,cAAsC;AAAA,EAC1C,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,SAAS,2BAA2B,UAA0B;AACnE,QAAM,MAAM,SAAS,SAAS,GAAG,IAC7B,SAAS,MAAM,SAAS,YAAY,GAAG,CAAC,EAAE,YAAY,IACtD;AACJ,SAAO,YAAY,GAAG,KAAK;AAC7B;AAEO,SAAS,oBAAoB,MAAsB;AACxD,QAAM,WAAW,KAAK,QAAQ,QAAQ,EAAE,EAAE,MAAM,GAAG;AACnD,SAAO,SAAS,SAAS,SAAS,CAAC,KAAK;AAC1C;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/controllers/resources.ts"],"sourcesContent":["import { FastifyRequest, FastifyReply } from \"fastify\";\nimport type { ResourceAddress, 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 } 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 as string\n : undefined;\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 as string\n : undefined;\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 // 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 // Plain text comparison for now — bcrypt will come in Task 12\n const valid = await bcrypt.compare(password, record.passwordHash!);\n if (!valid) {\n return reply.status(401).send({ error: \"Incorrect password\" });\n }\n\n reply.header(\n \"Set-Cookie\",\n `share_unlock_${token}=1; Max-Age=86400; Path=/s/${token}; SameSite=Lax; HttpOnly; Secure`,\n );\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 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\n this.deps.logger.info(\"[share] serving file\", {\n token, volume: address.volume, sandboxPath, 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: sandboxPath });\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: sandboxPath, 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/${sandboxPath}` });\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: sandboxPath, error: (err as Error).message });\n return reply.status(404).send(\"File not found\");\n }\n }\n\n const fullPath = sandboxPath;\n\n const filename = fullPath.split(\"/\").pop() || \"download\";\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\n return reply\n .status(200)\n .type(contentType)\n .header(\"Content-Disposition\", `inline; filename=\"${filename}\"`)\n .header(\"Access-Control-Allow-Origin\", \"*\")\n .send(buf);\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 const entryDir = entryFile.replace(/[^/]+$/, \"\");\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":["err"]}
|