diff --git a/platforms/esigner-api/src/controllers/FileController.ts b/platforms/esigner-api/src/controllers/FileController.ts index 9483322c6..1fbea406a 100644 --- a/platforms/esigner-api/src/controllers/FileController.ts +++ b/platforms/esigner-api/src/controllers/FileController.ts @@ -2,8 +2,10 @@ import { Request, Response } from "express"; import { FileService, ReservedFileNameError } from "../services/FileService"; import multer from "multer"; +export const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB limit + const upload = multer({ - limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit + limits: { fileSize: MAX_FILE_SIZE }, storage: multer.memoryStorage(), }); @@ -200,7 +202,7 @@ export class FileController { } const signatures = await this.fileService.getFileSignatures(fileId); - + res.json(signatures.map(sig => ({ id: sig.id, userId: sig.userId, diff --git a/platforms/esigner-api/src/index.ts b/platforms/esigner-api/src/index.ts index 9efbce9a1..746a817a4 100644 --- a/platforms/esigner-api/src/index.ts +++ b/platforms/esigner-api/src/index.ts @@ -5,7 +5,8 @@ import { config } from "dotenv"; import { AppDataSource } from "./database/data-source"; import path from "path"; import { AuthController } from "./controllers/AuthController"; -import { FileController } from "./controllers/FileController"; +import { FileController, MAX_FILE_SIZE } from "./controllers/FileController"; +import multer from "multer"; import { InvitationController } from "./controllers/InvitationController"; import { SignatureController } from "./controllers/SignatureController"; import { UserController } from "./controllers/UserController"; @@ -24,12 +25,12 @@ AppDataSource.initialize() .then(async () => { console.log("Database connection established"); console.log("Web3 adapter initialized"); - + // Initialize platform eVault for eSigner try { const platformService = PlatformEVaultService.getInstance(); const exists = await platformService.checkPlatformEVaultExists(); - + if (!exists) { console.log("🔧 Creating platform eVault for eSigner..."); const result = await platformService.createPlatformEVault(); @@ -61,8 +62,8 @@ app.use( credentials: true, }), ); -app.use(express.json({ limit: "50mb" })); -app.use(express.urlencoded({ limit: "50mb", extended: true })); +app.use(express.json({ limit: "20mb" })); +app.use(express.urlencoded({ limit: "20mb", extended: true })); // Controllers const authController = new AuthController(); @@ -105,6 +106,27 @@ app.post("/api/signatures/session", authGuard, signatureController.createSigning app.get("/api/signatures/session/:id", signatureController.getSigningSessionStatus); app.post("/api/signatures/callback", signatureController.handleSignedPayload); +// Global error handler for multer file size errors +app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => { + if (err instanceof multer.MulterError && err.code === "LIMIT_FILE_SIZE") { + const maxSizeMB = Math.round(MAX_FILE_SIZE / (1024 * 1024)); + return res.status(413).json({ + error: `File size exceeds the maximum limit of ${maxSizeMB} MB`, + code: "LIMIT_FILE_SIZE", + maxSize: MAX_FILE_SIZE, + }); + } + // Handle other multer errors + if (err instanceof multer.MulterError) { + return res.status(400).json({ + error: err.message, + code: err.code, + }); + } + // Pass other errors to the default handler + next(err); +}); + // Start server app.listen(port, () => { console.log(`eSigner API server running on port ${port}`); diff --git a/platforms/esigner/src/lib/stores/files.ts b/platforms/esigner/src/lib/stores/files.ts index c594ed940..570eef2d7 100644 --- a/platforms/esigner/src/lib/stores/files.ts +++ b/platforms/esigner/src/lib/stores/files.ts @@ -65,7 +65,25 @@ export const fetchDocuments = async () => { // Keep fetchFiles alias for backward compatibility export const fetchFiles = fetchDocuments; +const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB limit + +export class FileSizeError extends Error { + constructor(public fileSize: number, public maxSize: number = MAX_FILE_SIZE) { + const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2); + const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(0); + super(`File size (${fileSizeMB} MB) exceeds the maximum limit of ${maxSizeMB} MB`); + this.name = 'FileSizeError'; + } +} + export const uploadFile = async (file: File, displayName?: string, description?: string) => { + // Client-side file size validation + if (file.size > MAX_FILE_SIZE) { + const err = new FileSizeError(file.size, MAX_FILE_SIZE); + error.set(err.message); + throw err; + } + try { isLoading.set(true); error.set(null); @@ -84,7 +102,20 @@ export const uploadFile = async (file: File, displayName?: string, description?: }); await fetchDocuments(); return response.data; - } catch (err) { + } catch (err: unknown) { + // Handle HTTP 413 Payload Too Large + if (err && typeof err === 'object' && 'response' in err) { + const axiosError = err as { response?: { status?: number; data?: { error?: string; maxSize?: number; fileSize?: number } } }; + if (axiosError.response?.status === 413) { + const data = axiosError.response.data; + const fileSizeErr = new FileSizeError( + data?.fileSize || file.size, + data?.maxSize || MAX_FILE_SIZE + ); + error.set(fileSizeErr.message); + throw fileSizeErr; + } + } error.set(err instanceof Error ? err.message : 'Failed to upload file'); throw err; } finally { diff --git a/platforms/esigner/src/routes/(protected)/files/new/+page.svelte b/platforms/esigner/src/routes/(protected)/files/new/+page.svelte index f5e4ec402..26ea6d7e5 100644 --- a/platforms/esigner/src/routes/(protected)/files/new/+page.svelte +++ b/platforms/esigner/src/routes/(protected)/files/new/+page.svelte @@ -2,7 +2,7 @@ import { onMount } from 'svelte'; import { goto } from '$app/navigation'; import { isAuthenticated } from '$lib/stores/auth'; - import { uploadFile } from '$lib/stores/files'; + import { files, fetchFiles, uploadFile, FileSizeError } from '$lib/stores/files'; import { apiClient } from '$lib/utils/axios'; import { inviteSignees } from '$lib/stores/invitations'; @@ -59,7 +59,11 @@ } } catch (err) { console.error('Upload failed:', err); - alert('Failed to upload file'); + if (err instanceof FileSizeError) { + alert(err.message); + } else { + alert('Failed to upload file. Please try again.'); + } throw err; } finally { isLoading = false; @@ -126,7 +130,7 @@ alert('You cannot invite yourself. You are automatically added as a signee.'); return; } - + if (!selectedUsers.find(u => u.id === user.id)) { selectedUsers = [...selectedUsers, user]; } @@ -147,10 +151,10 @@ try { isSubmitting = true; const userIds = selectedUsers.map(u => u.id); - + // Backend will automatically add owner as signee await inviteSignees(selectedFile.id, userIds); - + goto(`/files/${selectedFile.id}`); } catch (err) { console.error('Failed to create invitations:', err);