fix(files): get_task_file отдаёт внешний presigned-URL через прокси приложения вместо внутреннего minio:9000
Живой тест показал: presigned-ссылка формировалась против внутреннего хоста (http://minio:9000/...) — снаружи docker-сети недоступна, для ботов бесполезна. - presigned-токены вынесены в server/utils/presigned-tokens.ts (общие для index.ts и mcp.ts); - MCP get_task_file возвращает /api/files/<key>?presigned=<token> — работает через https://iistwin.ru без авторизации ~5 минут, в S3 и локальном режимах.
This commit is contained in:
@@ -28,6 +28,7 @@ import { authenticateToken, authenticateFileToken, type AuthenticatedRequest } f
|
||||
import { EXT_TO_MIME, getFileExt } from "./utils/upload";
|
||||
import { normalizeApiKeyScopes, isFormAllowedByScopes } from "./utils/api-key";
|
||||
import { trackFileOnDemand } from "./utils/file-tracking";
|
||||
import { generatePresignedToken, validatePresignedToken, sweepPresignedTokens, PRESIGNED_TTL_MS } from "./utils/presigned-tokens";
|
||||
import crypto from "crypto";
|
||||
import { logger } from "./utils/logger";
|
||||
|
||||
@@ -55,41 +56,9 @@ app.use('/api', (req, res, next) => {
|
||||
app.use(cookieParser());
|
||||
|
||||
// ── Presigned URLs for file preview (temporary unauthenticated access) ──────
|
||||
interface PresignedToken {
|
||||
fileKey: string;
|
||||
organizationId: number;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
const presignedTokens = new Map<string, PresignedToken>();
|
||||
const PRESIGNED_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
function generatePresignedToken(fileKey: string, organizationId: number): string {
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
presignedTokens.set(token, { fileKey, organizationId, expiresAt: Date.now() + PRESIGNED_TTL_MS });
|
||||
return token;
|
||||
}
|
||||
|
||||
function validatePresignedToken(token: string, fileKey: string): number | null {
|
||||
const entry = presignedTokens.get(token);
|
||||
if (!entry) return null;
|
||||
if (entry.fileKey !== fileKey) return null;
|
||||
if (entry.expiresAt <= Date.now()) {
|
||||
presignedTokens.delete(token);
|
||||
return null;
|
||||
}
|
||||
return entry.organizationId;
|
||||
}
|
||||
|
||||
function sweepPresignedTokens(): void {
|
||||
const now = Date.now();
|
||||
for (const [token, entry] of presignedTokens.entries()) {
|
||||
if (entry.expiresAt <= now) {
|
||||
presignedTokens.delete(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
setInterval(sweepPresignedTokens, PRESIGNED_TTL_MS);
|
||||
// Реализация — server/utils/presigned-tokens.ts (общая с MCP get_task_file).
|
||||
const presignedSweep = setInterval(sweepPresignedTokens, PRESIGNED_TTL_MS);
|
||||
presignedSweep.unref?.();
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Helper: check file ownership — fail-closed (deny if untracked or DB error).
|
||||
|
||||
Reference in New Issue
Block a user