mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
Feat: Path Verification (Security #4)
This commit is contained in:
29
main.js
29
main.js
@@ -101,21 +101,44 @@ async function createMainWindow() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateDownloadPath(userPath) {
|
||||||
|
const userHome = os.homedir(); // C:\Users\<User>
|
||||||
|
|
||||||
|
if (!userPath) return path.join(userHome, "Downloads", "Freedom Loader");
|
||||||
|
|
||||||
|
// Résolution canonique et suivi des symlinks
|
||||||
|
const resolved = fs.realpathSync(path.resolve(userPath));
|
||||||
|
const normalizedHome = path.resolve(userHome) + path.sep;
|
||||||
|
|
||||||
|
if (!resolved.startsWith(normalizedHome)) {
|
||||||
|
throw new Error("Chemin non autorisé : uniquement les sous-dossiers du dossier utilisateur sont permis !");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// IPC
|
// IPC
|
||||||
ipcMain.handle("select-download-folder", async () => {
|
ipcMain.handle("select-download-folder", async () => {
|
||||||
try {
|
try {
|
||||||
const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
|
const result = await dialog.showOpenDialog({ properties: ["openDirectory"] });
|
||||||
if (!result.canceled && result.filePaths.length > 0) {
|
if (!result.canceled && result.filePaths.length > 0) {
|
||||||
logger.info(`Dossier sélectionné : ${result.filePaths[0]}`);
|
const validatedPath = validateDownloadPath(result.filePaths[0]);
|
||||||
return result.filePaths[0];
|
logger.info(`Folder Checked and Valid : ${validatedPath}`);
|
||||||
|
return validatedPath;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Error when creating Output Folder : ${err.message}`);
|
logger.error(`An Error Occured when validating folder : ${err.message}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("validate-download-path", (event, userPath) => {
|
||||||
|
return validateDownloadPath(userPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
ipcMain.handle("get-default-download-path", () => defaultDownloadPath);
|
ipcMain.handle("get-default-download-path", () => defaultDownloadPath);
|
||||||
|
|
||||||
ipcMain.on("set-progress", (event, percent) => {
|
ipcMain.on("set-progress", (event, percent) => {
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
|||||||
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
|
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
|
||||||
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
|
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
|
||||||
setProgress: (percent) => ipcRenderer.send("set-progress", percent),
|
setProgress: (percent) => ipcRenderer.send("set-progress", percent),
|
||||||
getFeatures: () => ipcRenderer.invoke("features")
|
getFeatures: () => ipcRenderer.invoke("features"),
|
||||||
|
getValidatedDownloadPath: (path) => ipcRenderer.invoke("validate-download-path", path)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Contrôles de fenêtre et outils custom pour la topbar
|
// Contrôles de fenêtre et outils custom pour la topbar
|
||||||
|
|||||||
@@ -1,41 +1,62 @@
|
|||||||
window.addEventListener("DOMContentLoaded", async () => {
|
window.addEventListener("DOMContentLoaded", async () => {
|
||||||
const savePathElem = document.getElementById("savePath");
|
const savePathElem = document.getElementById("savePath");
|
||||||
|
const form = document.getElementById("downloadForm");
|
||||||
|
|
||||||
// Essayer de charger depuis le localStorage
|
// input caché = DERNIER chemin validé par le back
|
||||||
let savedPath = localStorage.getItem("customDownloadPath");
|
|
||||||
|
|
||||||
// Sinon demander le chemin par défaut à l'API Electron
|
|
||||||
if (!savedPath) {
|
|
||||||
savedPath = await window.electronAPI.getDefaultDownloadPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Afficher le chemin
|
|
||||||
if (savePathElem) {
|
|
||||||
savePathElem.textContent = savedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Créer l'input caché s'il n'existe pas déjà
|
|
||||||
let hidden = document.getElementById("savePathInput");
|
let hidden = document.getElementById("savePathInput");
|
||||||
if (!hidden) {
|
if (!hidden) {
|
||||||
hidden = document.createElement("input");
|
hidden = document.createElement("input");
|
||||||
hidden.type = "hidden";
|
hidden.type = "hidden";
|
||||||
hidden.name = "savePath";
|
hidden.name = "savePath";
|
||||||
hidden.id = "savePathInput";
|
hidden.id = "savePathInput";
|
||||||
document.getElementById("downloadForm").appendChild(hidden);
|
form.appendChild(hidden);
|
||||||
}
|
}
|
||||||
hidden.value = savedPath;
|
|
||||||
|
|
||||||
// Gestion du bouton de modification
|
async function applyPathFromBack(path) {
|
||||||
document.getElementById("changePath").addEventListener("click", async () => {
|
savePathElem.textContent = path;
|
||||||
const selectedPath = await window.electronAPI.selectDownloadFolder();
|
hidden.value = path;
|
||||||
if (selectedPath) {
|
localStorage.setItem("customDownloadPath", path); // UX only
|
||||||
// Met à jour l'affichage
|
}
|
||||||
savePathElem.textContent = selectedPath;
|
|
||||||
hidden.value = selectedPath;
|
|
||||||
|
|
||||||
// Et le stocke en localStorage pour la prochaine fois
|
async function loadInitialPath() {
|
||||||
localStorage.setItem("customDownloadPath", selectedPath);
|
// On affiche ce que le user a vu la dernière fois (UX)
|
||||||
|
const cached = localStorage.getItem("customDownloadPath");
|
||||||
|
if (cached) {
|
||||||
|
savePathElem.textContent = cached;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// MAIS la source de vérité reste le back
|
||||||
|
try {
|
||||||
|
const validatedPath = await window.electronAPI.getValidatedDownloadPath(
|
||||||
|
cached
|
||||||
|
);
|
||||||
|
await applyPathFromBack(validatedPath);
|
||||||
|
} catch {
|
||||||
|
// fallback sûr
|
||||||
|
const defaultPath =
|
||||||
|
await window.electronAPI.getDefaultDownloadPath();
|
||||||
|
await applyPathFromBack(defaultPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadInitialPath();
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("changePath")
|
||||||
|
.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
const selectedPath =
|
||||||
|
await window.electronAPI.selectDownloadFolder();
|
||||||
|
|
||||||
|
if (!selectedPath) return; // annulé
|
||||||
|
|
||||||
|
// Validation back obligatoire
|
||||||
|
const validatedPath =
|
||||||
|
await window.electronAPI.getValidatedDownloadPath(selectedPath);
|
||||||
|
|
||||||
|
await applyPathFromBack(validatedPath);
|
||||||
|
} catch (err) {
|
||||||
|
alert("Dossier non autorisé.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user