mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 13:25:18 +00:00
feat: safer backups
This commit is contained in:
@@ -10,6 +10,7 @@ import path from "path"
|
||||
|
||||
const SUPPORTED_BACKUP_VERSIONS = ["1.0"]
|
||||
const REMOVE_EXISTING_DATA = true
|
||||
const MAX_BACKUP_SIZE = 256 * 1024 * 1024 // 256MB
|
||||
|
||||
export async function restoreBackupAction(prevState: any, formData: FormData) {
|
||||
const user = await getCurrentUser()
|
||||
@@ -20,6 +21,10 @@ export async function restoreBackupAction(prevState: any, formData: FormData) {
|
||||
return { success: false, error: "No file provided" }
|
||||
}
|
||||
|
||||
if (file.size > MAX_BACKUP_SIZE) {
|
||||
return { success: false, error: `Backup file too large. Maximum size is ${MAX_BACKUP_SIZE / 1024 / 1024}MB` }
|
||||
}
|
||||
|
||||
// Read zip archive
|
||||
let zip: JSZip
|
||||
try {
|
||||
@@ -88,7 +93,7 @@ export async function restoreBackupAction(prevState: any, formData: FormData) {
|
||||
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
||||
|
||||
for (const file of files) {
|
||||
const filePathWithoutPrefix = file.path.replace(/^.*\/uploads\//, "")
|
||||
const filePathWithoutPrefix = path.normalize(file.path.replace(/^.*\/uploads\//, ""))
|
||||
const zipFilePath = path.join("data/uploads", filePathWithoutPrefix)
|
||||
const zipFile = zip.file(zipFilePath)
|
||||
if (!zipFile) {
|
||||
@@ -96,12 +101,16 @@ export async function restoreBackupAction(prevState: any, formData: FormData) {
|
||||
continue
|
||||
}
|
||||
|
||||
const fileContents = await zipFile.async("nodebuffer")
|
||||
const fullFilePath = path.join(userUploadsDirectory, filePathWithoutPrefix)
|
||||
const fileContent = await zipFile.async("nodebuffer")
|
||||
if (!fullFilePath.startsWith(path.normalize(userUploadsDirectory))) {
|
||||
console.error(`Attempted path traversal detected for file ${file.path}`)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.mkdir(path.dirname(fullFilePath), { recursive: true })
|
||||
await fs.writeFile(fullFilePath, fileContent)
|
||||
await fs.writeFile(fullFilePath, fileContents)
|
||||
restoredFilesCount++
|
||||
} catch (error) {
|
||||
console.error(`Error writing file ${fullFilePath}:`, error)
|
||||
|
||||
@@ -81,8 +81,8 @@ export async function saveFileAsTransactionAction(prevState: any, formData: Form
|
||||
const newRelativeFilePath = await getTransactionFileUploadPath(file.id, originalFileName, transaction)
|
||||
|
||||
// Move file to new location and name
|
||||
const oldFullFilePath = path.join(userUploadsDirectory, file.path)
|
||||
const newFullFilePath = path.join(userUploadsDirectory, newRelativeFilePath)
|
||||
const oldFullFilePath = path.join(userUploadsDirectory, path.normalize(file.path))
|
||||
const newFullFilePath = path.join(userUploadsDirectory, path.normalize(newRelativeFilePath))
|
||||
await mkdir(path.dirname(newFullFilePath), { recursive: true })
|
||||
await rename(path.resolve(oldFullFilePath), path.resolve(newFullFilePath))
|
||||
|
||||
|
||||
@@ -78,9 +78,7 @@ export const FormConvertCurrency = ({
|
||||
async function getCurrencyRate(currencyCodeFrom: string, currencyCodeTo: string, date: Date): Promise<number> {
|
||||
try {
|
||||
const formattedDate = format(date, "yyyy-MM-dd")
|
||||
const url = `/api/currency?from=${currencyCodeFrom}&to=${currencyCodeTo}&date=${formattedDate}`
|
||||
|
||||
const response = await fetch(url)
|
||||
const response = await fetch(`/api/currency?from=${currencyCodeFrom}&to=${currencyCodeTo}&date=${formattedDate}`)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function getTransactionFileUploadPath(fileUuid: string, filename: s
|
||||
|
||||
export async function fullPathForFile(user: User, file: File) {
|
||||
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
||||
return path.join(userUploadsDirectory, file.path)
|
||||
return path.join(userUploadsDirectory, path.normalize(file.path))
|
||||
}
|
||||
|
||||
function formatFilePath(filename: string, date: Date, format = "{YYYY}/{MM}/{name}{ext}") {
|
||||
@@ -46,7 +46,7 @@ function formatFilePath(filename: string, date: Date, format = "{YYYY}/{MM}/{nam
|
||||
|
||||
export async function fileExists(filePath: string) {
|
||||
try {
|
||||
await access(filePath, constants.F_OK)
|
||||
await access(path.normalize(filePath), constants.F_OK)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
|
||||
@@ -74,7 +74,7 @@ export const deleteFile = async (id: string, userId: string) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await unlink(path.resolve(file.path))
|
||||
await unlink(path.resolve(path.normalize(file.path)))
|
||||
} catch (error) {
|
||||
console.error("Error deleting file:", error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user