Node-red file repository
Objectif
Mettre en place un petit service de partage de fichiers :
- Upload protégé par login/mot de passe.
- Téléchargement public via une URL.
- Noms des fichiers pseudonymisés.
- Fichiers servis directement par Node-RED.
Architecture :
Internet
|
v
Reverse Proxy
|
v
Node-RED (main-host)
|
+--- Upload via flow Node-RED
|
+--- Fichiers statiques (/var/www/static-files)
Installation des modules
Copilot m'avait proposer d'Installer "à ma main" :
node-red-contrib-http-multipart @alexandrainst/node-red-http-basic-auth
Le premier permet de recevoir des formulaires HTML multipart/form-data.
Le second ajoute une authentification HTTP Basic.
Pour le coup l'interface graphique de node-red le permet tout aussi bien.
Dans le menu en haut a droite on a l'option "Gérer la palette". On a l'impression que c'est juste de la GUI mais non on passe par là pour ajouter des modules.
On clique sur "Gérer la palette" et on a une fenêtre de sélection.
On choisit l'onglet "Installer" et on recherche node-red-contrib-http-multipart.
Dans le screenshot ci-dessus le bouton "Installé" est grisé mais si le module n'était pas installé on aurait un bouton installer. Il suffit de cliquer dessus et c'est fait.
Répertoire de stockage
Créer un répertoire destiné à contenir les fichiers :
sudo mkdir -p /var/www/static-files/ sudo mkdir -p /var/www/static-files/assets sudo chown nodered:nodered /var/www/static-files/assets
Adapter l'utilisateur si nécessaire.
Activation du serveur statique Node-RED
Modifier :
~/.node-red/settings.js
Décommenter et adapter :
httpStatic: [
{
path: "/var/www/static-files/assets",
root: "/files/"
}
],
Redémarrer Node-RED :
sudo systemctl restart nodered
Test :
echo hello > /var/www/static-files/test.txt
Puis ouvrir :
https://node-red.mon-domaine.fr/files/test.txt
Si le texte "hello" apparaît, le serveur statique fonctionne.
Page d'upload
URL :
https://node-red.mon-domaine.fr/megaupload/
HTML :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>MegaUpload</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
background: #f4f7fb;
}
.container {
max-width: 700px;
margin: 80px auto;
background: white;
padding: 40px;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
}
.upload-box {
text-align: center;
}
button {
background: #2563eb;
color: white;
border: 0;
padding: 12px 30px;
border-radius: 8px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>📤 MegaUpload</h1>
<div class="upload-box">
pload/upload"
method="post"
enctype="multipart/form-data">
<input type="file"
name="fichier"
required>
<button type="submit">
Envoyer le fichier
</button>
</form>
</div>
</div>
</body>
</html>
Que l'on rangera dans /var/www/static-files/upload.html
Protection par mot de passe
Ajouter un noeud :
http basic auth
devant :
- GET /megaupload/
- POST /megaupload/upload
Ainsi :
[http in]
|
v
[http basic auth]
|
v
[template]
|
v
[http response]
Utilisateur unique
Configurer directement :
Utilisateur : upload Mot de passe : xxxx
Fichier d'utilisateurs
Je penses que c'est la seule solution acceptable car les mots de passes ne sont pas "en clair"
Format :
jean:motdepasse alice:motdepasse
Ou mieux, avec mots de passe bcrypt.
Exemple :
jean:$2y$10$... alice:$2y$10$...
J'ai donc crée la chaine de noeuds:
C'est tres simple :
- Ou expose l'url /megaupload (noeud index)
- au authentifie l'utilisateur
- on lit le fichier /var/www/static-files/upload.html
- On le renvoie au client
Réception du fichier
Flow :
[httpInMultipart]
|
v
[http basic auth]
|
v
[function]
|
v
[exec]
|
v
[template]
|
v
[http response]
Configuration HTTP Multipart :
[
{
"name": "fichier",
"maxCount": 1
}
]En effet, dans la page d'upload on ne propose qu'un seul fichier. SI on veut en envoyer plusieurs, soit on refait l'opération soit un fait un zip ou un tgz.
Pseudonymisation du nom de fichier
Function :
const f = msg.req.files.fichier[0];
// Extension
const pos = f.originalname.lastIndexOf(".");
const ext = (pos >= 0)
? f.originalname.substring(pos).toLowerCase()
: "";
// Identifiant unique
const id =
Date.now().toString(36) +
"-" +
Math.random().toString(36).substring(2, 10);
// Nom stocké
msg.safeName = id + ext;
// Nom affiché
msg.nomFichier =
f.originalname.replace(/[<>]/g, "");
// URL publique
msg.urlFichier =
"https://node-red.mon-domaine.fr/files/" +
encodeURIComponent(msg.safeName);
// Déplacement du fichier
msg.payload =
'-c \'mv "' + f.path +
'" "/var/www/static-files/' +
msg.safeName + '"\'';
return msg;
Exemple :
PhotoVacances.jpg
devient, par exemple :
mrggesvw-hwdhacrc.jpg
Déplacement du fichier
Noeud Exec :
On lance l'exécution du msg.payload construit au dessus.
Command : bash Append : msg.payload
Le fichier temporaire créé par Multipart est déplacé :
/tmp/abc123
->
/var/www/static-files/mrggesvw-hwdhacrc.jpg
Page de succès
Template :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Fichier enregistré</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
background: #f4f7fb;
}
.container {
max-width: 800px;
margin: 80px auto;
background: white;
padding: 40px;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.linkbox {
padding: 15px;
background: #f8fafc;
border-radius: 8px;
}
button {
background: #2563eb;
color: white;
border: 0;
padding: 10px 20px;
border-radius: 8px;
}
</style>
<script>
function copyLink() {
const text =
document.getElementById("downloadLink").value;
navigator.clipboard.writeText(text);
document.getElementById("status").innerText =
"Lien copié ✓";
}
</script>
</head>
<body>
<div class="container">
<h1>✅ Fichier enregistré</h1>
<p>
Nom du fichier :
<b>{{nomFichier}}</b>
</p>
<div class="linkbox">
<input id="downloadLink"
type="text"
value="{{urlFichier}}"
readonly
style="width:100%">
</div>
<br>
<button onclick="copyLink()">
Copier le lien
</button>
<span id="status"></span>
</div>
</body>
</html>
Récapitulatifs des actions ci-dessus.
Utilisation
Upload
L'utilisateur authentifié ouvre :
https://node-red.mon-domaine.fr/megaupload/
Sélectionne un fichier puis clique :
Envoyer le fichier
Téléchargement
Le destinataire reçoit un lien :
https://node-red.mon-domaine.fr/files/mrggesvw-hwdhacrc.jpg
Aucune authentification n'est nécessaire.
Améliorations possibles
- Limiter la taille maximale des fichiers.
- Limiter les extensions autorisées.
- Suppression automatique après X jours.
- Historique des uploads dans MariaDB.
- Génération de liens à durée de vie limitée.
- Statistiques de téléchargement.
- QR-Code du lien de téléchargement.
Résultat
On obtient un mini "WeTransfer" personnel :
- HTTPS via le reverse proxy.
- Upload privé.
- Download public.
- Noms de fichiers non prévisibles.
- Aucun serveur web supplémentaire requis.
- Entièrement réalisé avec Node-RED.
Ajouter une base de donnée
Creation
Sur le serveur de base de donnée
create database upload_db;
select PASSWORD('mon mot de passe');
+-------------------------------------------+
| PASSWORD('mon mot de passe') |
+-------------------------------------------+
| *DA793D3BC059685BB32D33B01DF458DD43CBE127 |
+-------------------------------------------+
CREATE USER 'upload_user'@'main-host.pilan' IDENTIFIED BY PASSWORD '*DA793D3BC059685BB32D33B01DF458DD43CBE127';
GRANT ALL PRIVILEGES ON upload_db.* to 'upload_user'@'main-host.pilan';
Attention ici j'ai le domaine par defaut ".pilan"
Connexion
Sur le serveur database (database.pilan)
admin@main-host:~ $ mariadb -h database -u upload_user -p upload_db
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 7743891
Server version: 11.8.6-MariaDB-0+deb13u1 from Debian -- Please help get to 10k stars at https://github.com/MariaDB/Server
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [upload_db]>
On crée la table:
USE upload_db;
CREATE TABLE upload_users (
id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE fichiers (
id INT AUTO_INCREMENT PRIMARY KEY,
upload_filename VARCHAR(255) NOT NULL,
download_filename VARCHAR(255) NOT NULL,
creation_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
user_id INT NOT NULL,
CONSTRAINT fk_fichiers_user
FOREIGN KEY (user_id)
REFERENCES upload_users(id)
);
CREATE TABLE connections (
id INT AUTO_INCREMENT PRIMARY KEY,
connection_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
user_id INT NOT NULL,
CONSTRAINT fk_connections_user
FOREIGN KEY (user_id)
REFERENCES upload_users(id)
);