fix graphQL
This commit is contained in:
@@ -907,6 +907,7 @@ registerImportExportRoutes({
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
asyncHandler,
|
asyncHandler,
|
||||||
upload,
|
upload,
|
||||||
|
uploadDir,
|
||||||
config,
|
config,
|
||||||
backendRoot,
|
backendRoot,
|
||||||
getBackendVersion,
|
getBackendVersion,
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ type RegisterImportExportDeps = {
|
|||||||
fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<T>
|
fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<T>
|
||||||
) => express.RequestHandler;
|
) => express.RequestHandler;
|
||||||
upload: any;
|
upload: any;
|
||||||
|
uploadDir: string;
|
||||||
config: { nodeEnv: string };
|
config: { nodeEnv: string };
|
||||||
backendRoot: string;
|
backendRoot: string;
|
||||||
getBackendVersion: () => string;
|
getBackendVersion: () => string;
|
||||||
@@ -154,6 +155,42 @@ const parseOptionalJson = <T>(raw: unknown, fallback: T): T => {
|
|||||||
return fallback;
|
return fallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPathInsideDirectory = (candidatePath: string, rootDir: string): boolean => {
|
||||||
|
const relativePath = path.relative(rootDir, candidatePath);
|
||||||
|
return (
|
||||||
|
relativePath === "" ||
|
||||||
|
(!relativePath.startsWith("..") && !path.isAbsolute(relativePath))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveSafeUploadedFilePath = async (
|
||||||
|
filePath: unknown,
|
||||||
|
uploadRoot: string
|
||||||
|
): Promise<string> => {
|
||||||
|
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
||||||
|
throw new ImportValidationError("Invalid upload path");
|
||||||
|
}
|
||||||
|
|
||||||
|
const absoluteUploadRoot = path.resolve(uploadRoot);
|
||||||
|
const absoluteFilePath = path.resolve(filePath);
|
||||||
|
|
||||||
|
let canonicalUploadRoot = absoluteUploadRoot;
|
||||||
|
let canonicalFilePath = absoluteFilePath;
|
||||||
|
|
||||||
|
try {
|
||||||
|
canonicalUploadRoot = await fsPromises.realpath(absoluteUploadRoot);
|
||||||
|
canonicalFilePath = await fsPromises.realpath(absoluteFilePath);
|
||||||
|
} catch {
|
||||||
|
throw new ImportValidationError("Invalid upload path");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPathInsideDirectory(canonicalFilePath, canonicalUploadRoot)) {
|
||||||
|
throw new ImportValidationError("Invalid upload path");
|
||||||
|
}
|
||||||
|
|
||||||
|
return canonicalFilePath;
|
||||||
|
};
|
||||||
|
|
||||||
const openReadonlySqliteDb = (filePath: string): any => {
|
const openReadonlySqliteDb = (filePath: string): any => {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@@ -192,6 +229,7 @@ export const registerImportExportRoutes = (deps: RegisterImportExportDeps) => {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
asyncHandler,
|
asyncHandler,
|
||||||
upload,
|
upload,
|
||||||
|
uploadDir,
|
||||||
config,
|
config,
|
||||||
backendRoot,
|
backendRoot,
|
||||||
getBackendVersion,
|
getBackendVersion,
|
||||||
@@ -337,7 +375,15 @@ Drawings: ${drawings.length}
|
|||||||
if (!req.user) return res.status(401).json({ error: "Unauthorized" });
|
if (!req.user) return res.status(401).json({ error: "Unauthorized" });
|
||||||
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
||||||
|
|
||||||
const stagedPath = req.file.path;
|
let stagedPath: string;
|
||||||
|
try {
|
||||||
|
stagedPath = await resolveSafeUploadedFilePath(req.file.path, uploadDir);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ImportValidationError) {
|
||||||
|
return res.status(error.status).json({ error: "Invalid upload", message: error.message });
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const buffer = await fsPromises.readFile(stagedPath);
|
const buffer = await fsPromises.readFile(stagedPath);
|
||||||
const zip = await JSZip.loadAsync(buffer);
|
const zip = await JSZip.loadAsync(buffer);
|
||||||
@@ -417,7 +463,15 @@ Drawings: ${drawings.length}
|
|||||||
if (!req.user) return res.status(401).json({ error: "Unauthorized" });
|
if (!req.user) return res.status(401).json({ error: "Unauthorized" });
|
||||||
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
||||||
|
|
||||||
const stagedPath = req.file.path;
|
let stagedPath: string;
|
||||||
|
try {
|
||||||
|
stagedPath = await resolveSafeUploadedFilePath(req.file.path, uploadDir);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ImportValidationError) {
|
||||||
|
return res.status(error.status).json({ error: "Invalid upload", message: error.message });
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const buffer = await fsPromises.readFile(stagedPath);
|
const buffer = await fsPromises.readFile(stagedPath);
|
||||||
const zip = await JSZip.loadAsync(buffer);
|
const zip = await JSZip.loadAsync(buffer);
|
||||||
@@ -666,7 +720,15 @@ Drawings: ${drawings.length}
|
|||||||
if (!req.user) return res.status(401).json({ error: "Unauthorized" });
|
if (!req.user) return res.status(401).json({ error: "Unauthorized" });
|
||||||
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
||||||
|
|
||||||
const stagedPath = req.file.path;
|
let stagedPath: string;
|
||||||
|
try {
|
||||||
|
stagedPath = await resolveSafeUploadedFilePath(req.file.path, uploadDir);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ImportValidationError) {
|
||||||
|
return res.status(error.status).json({ error: "Invalid upload", message: error.message });
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const isValid = await verifyDatabaseIntegrityAsync(stagedPath);
|
const isValid = await verifyDatabaseIntegrityAsync(stagedPath);
|
||||||
if (!isValid) return res.status(400).json({ error: "Invalid database format" });
|
if (!isValid) return res.status(400).json({ error: "Invalid database format" });
|
||||||
@@ -744,7 +806,15 @@ Drawings: ${drawings.length}
|
|||||||
if (!req.user) return res.status(401).json({ error: "Unauthorized" });
|
if (!req.user) return res.status(401).json({ error: "Unauthorized" });
|
||||||
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
||||||
|
|
||||||
const stagedPath = req.file.path;
|
let stagedPath: string;
|
||||||
|
try {
|
||||||
|
stagedPath = await resolveSafeUploadedFilePath(req.file.path, uploadDir);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ImportValidationError) {
|
||||||
|
return res.status(error.status).json({ error: "Invalid upload", message: error.message });
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const isValid = await verifyDatabaseIntegrityAsync(stagedPath);
|
const isValid = await verifyDatabaseIntegrityAsync(stagedPath);
|
||||||
if (!isValid) return res.status(400).json({ error: "Invalid database format" });
|
if (!isValid) return res.status(400).json({ error: "Invalid database format" });
|
||||||
|
|||||||
Reference in New Issue
Block a user