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 SUPPORTED_BACKUP_VERSIONS = ["1.0"]
|
||||||
const REMOVE_EXISTING_DATA = true
|
const REMOVE_EXISTING_DATA = true
|
||||||
|
const MAX_BACKUP_SIZE = 256 * 1024 * 1024 // 256MB
|
||||||
|
|
||||||
export async function restoreBackupAction(prevState: any, formData: FormData) {
|
export async function restoreBackupAction(prevState: any, formData: FormData) {
|
||||||
const user = await getCurrentUser()
|
const user = await getCurrentUser()
|
||||||
@@ -20,6 +21,10 @@ export async function restoreBackupAction(prevState: any, formData: FormData) {
|
|||||||
return { success: false, error: "No file provided" }
|
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
|
// Read zip archive
|
||||||
let zip: JSZip
|
let zip: JSZip
|
||||||
try {
|
try {
|
||||||
@@ -88,7 +93,7 @@ export async function restoreBackupAction(prevState: any, formData: FormData) {
|
|||||||
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
||||||
|
|
||||||
for (const file of files) {
|
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 zipFilePath = path.join("data/uploads", filePathWithoutPrefix)
|
||||||
const zipFile = zip.file(zipFilePath)
|
const zipFile = zip.file(zipFilePath)
|
||||||
if (!zipFile) {
|
if (!zipFile) {
|
||||||
@@ -96,12 +101,16 @@ export async function restoreBackupAction(prevState: any, formData: FormData) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileContents = await zipFile.async("nodebuffer")
|
||||||
const fullFilePath = path.join(userUploadsDirectory, filePathWithoutPrefix)
|
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 {
|
try {
|
||||||
await fs.mkdir(path.dirname(fullFilePath), { recursive: true })
|
await fs.mkdir(path.dirname(fullFilePath), { recursive: true })
|
||||||
await fs.writeFile(fullFilePath, fileContent)
|
await fs.writeFile(fullFilePath, fileContents)
|
||||||
restoredFilesCount++
|
restoredFilesCount++
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error writing file ${fullFilePath}:`, 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)
|
const newRelativeFilePath = await getTransactionFileUploadPath(file.id, originalFileName, transaction)
|
||||||
|
|
||||||
// Move file to new location and name
|
// Move file to new location and name
|
||||||
const oldFullFilePath = path.join(userUploadsDirectory, file.path)
|
const oldFullFilePath = path.join(userUploadsDirectory, path.normalize(file.path))
|
||||||
const newFullFilePath = path.join(userUploadsDirectory, newRelativeFilePath)
|
const newFullFilePath = path.join(userUploadsDirectory, path.normalize(newRelativeFilePath))
|
||||||
await mkdir(path.dirname(newFullFilePath), { recursive: true })
|
await mkdir(path.dirname(newFullFilePath), { recursive: true })
|
||||||
await rename(path.resolve(oldFullFilePath), path.resolve(newFullFilePath))
|
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> {
|
async function getCurrencyRate(currencyCodeFrom: string, currencyCodeTo: string, date: Date): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const formattedDate = format(date, "yyyy-MM-dd")
|
const formattedDate = format(date, "yyyy-MM-dd")
|
||||||
const url = `/api/currency?from=${currencyCodeFrom}&to=${currencyCodeTo}&date=${formattedDate}`
|
const response = await fetch(`/api/currency?from=${currencyCodeFrom}&to=${currencyCodeTo}&date=${formattedDate}`)
|
||||||
|
|
||||||
const response = await fetch(url)
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json()
|
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) {
|
export async function fullPathForFile(user: User, file: File) {
|
||||||
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
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}") {
|
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) {
|
export async function fileExists(filePath: string) {
|
||||||
try {
|
try {
|
||||||
await access(filePath, constants.F_OK)
|
await access(path.normalize(filePath), constants.F_OK)
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const deleteFile = async (id: string, userId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await unlink(path.resolve(file.path))
|
await unlink(path.resolve(path.normalize(file.path)))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting file:", error)
|
console.error("Error deleting file:", error)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user