Fermer

février 19, 2020

Formulaires, téléchargements de fichiers et sécurité avec Node.js et Express –


Si vous créez une application Web, vous risquez de rencontrer le besoin de créer des formulaires HTML dès le premier jour. Ils représentent une grande partie de l'expérience Web et peuvent être compliqués.

Généralement, le processus de gestion des formulaires implique:

  • l'affichage d'un formulaire HTML vide en réponse à une demande GET initiale [19659004] utilisateur soumettant le formulaire avec des données dans une demande POST de validation
  • à la fois sur le client et le serveur
  • réaffichant le formulaire rempli de données échappées et de messages d'erreur s'il n'est pas valide
  • faisant quelque chose avec les données filtrées sur le serveur si elles sont toutes valides
  • redirigeant l'utilisateur ou affichant un message de réussite après le traitement des données.

La gestion des données du formulaire comporte également des considérations de sécurité supplémentaires.

Nous allons les parcourir et expliquer comment les construire avec Node.js et Express – le framework web le plus populaire pour Node. Tout d'abord, nous allons créer un formulaire de contact simple où les gens peuvent envoyer un message et une adresse e-mail en toute sécurité, puis jeter un œil à ce qui est impliqué dans le traitement des téléchargements de fichiers.

 Un formulaire de contact avec e-mail et message avec des erreurs de validation ]</p data-recalc-dims=

Comme toujours, le code complet se trouve dans notre dépôt GitHub .

Configuration

Assurez-vous que vous disposez d'une version récente de Node.js installée. le noeud -v devrait renvoyer 8.9.0 ou supérieur.

Téléchargez le code de démarrage à partir d'ici avec Git:

 git clone -b starter https://github.com /sitepoint-editors/node-forms.git node-forms-starter
cd node-forms-starter
installation de npm
npm start

Remarque: Le dépôt a deux branches, démarreur et maître . La branche starter contient la configuration minimale dont vous avez besoin pour suivre cet article. La branche master contient une démonstration complète et fonctionnelle (lien ci-dessus).

Il n'y a pas trop de code là-dedans. C’est juste une configuration Express nue avec modèles EJS et des gestionnaires d’erreurs:

 // server.js
const path = require ('path');
const express = require ('express');
const layout = require ('express-layout');

const routes = require ('./ routes');
const app = express ();

app.set ('vues', path.join (__ dirname, 'vues'));
app.set («moteur de vue», «ejs»);

const middlewares = [
  layout(),
  express.static(path.join(__dirname, 'public')),
];
app.use (middlewares);

app.use ('/', routes);

app.use ((req, res, next) => {
  res.status (404) .send ("Désolé, je ne trouve pas ça!");
});

app.use ((err, req, res, next) => {
  console.error (err.stack);
  res.status (500) .send ('Quelque chose s'est cassé!');
});

app.listen (3000, () => {
  console.log ('Application exécutée sur http: // localhost: 3000');
});

L'URL racine / affiche simplement la vue index.ejs :

 // routes.js
const express = require ('express');
const router = express.Router ();

router.get ('/', (req, res) => {
  res.render ('index');
});

module.exports = routeur;

Affichage du formulaire

Lorsque les gens font une demande GET à / contact nous voulons afficher une nouvelle vue contact.ejs :

 // routes.js
router.get ('/ contact', (req, res) => {
  res.render («contact»);
});

Le formulaire de contact leur permettra de nous envoyer un message et leur adresse email:


Envoyez-nous un message

  

Voir à quoi il ressemble à http: // localhost: 3000 / contact .

Soumission de formulaire

Pour recevoir des valeurs POST dans Express, vous devez d'abord inclure le corps -parser middleware, qui expose les valeurs de formulaire soumises sur req.body dans vos gestionnaires d'itinéraire. Ajoutez-le à la fin du tableau middlewares :

 // server.js
const bodyParser = require ('body-parser');

const middlewares = [
  // ...
  bodyParser.urlencoded({ extended: true }),
];

C'est une convention courante pour les formulaires de POSTER les données vers la même URL que celle utilisée dans la demande GET initiale. Faisons-le ici et traitons POST / contact pour traiter l'entrée utilisateur.

Examinons d'abord la soumission invalide. S'il n'est pas valide, nous devons retransmettre les valeurs soumises à la vue (afin que les utilisateurs n'aient pas besoin de les ressaisir) ainsi que les messages d'erreur que nous voulons afficher:

 router.get ('/ contact', ( req, res) => {
  res.render ('contact', {
    Les données: {},
    les erreurs: {}
  });
});

router.post ('/ contact', (req, res) => {
  res.render ('contact', {
    data: req.body, // {message, email}
    les erreurs: {
      message: {
        msg: 'Un message est requis'
      },
      email: {
        msg: "Cet e-mail ne semble pas correct"
      }
    }
  });
});

S'il y a des erreurs de validation, nous ferons ce qui suit:

  • afficher les erreurs en haut du formulaire
  • définir les valeurs d'entrée sur ce qui a été soumis au serveur
  • afficher les erreurs en ligne ci-dessous les entrées
  • ajoutent une classe form-field-invalid aux champs avec des erreurs.

<% if (Object.keys(errors).length === 0) { %>

Envoyez-nous un message

<% } else { %>

Oups, veuillez corriger ce qui suit:

    <% Object.values(errors).forEach(error => {%>         
  • <%= error.msg %>
  • <% }) %>
<% } %>
<div class = "form-field <%= errors.message ? 'form-field-invalid' : '' %>">      <% if (errors.message) { %>
<%= errors.message.msg %>
<% } %>
<div class = "form-field <%= errors.email ? 'form-field-invalid' : '' %>">          <input class = "input" id = "email" name = "email" type = "email" value = "<%= data.email %>" />     <% if (errors.email) { %>
<%= errors.email.msg %>
<% } %>
  

Soumettez le formulaire à http: // localhost: 3000 / contactez pour le voir en action. C'est tout ce dont nous avons besoin du côté de la vue.

Validation et assainissement

Il existe un middleware pratique appelé express-validator pour valider et assainir les données à l'aide de la bibliothèque validator.js . Ajoutons-le à notre application.

Validation

Avec les validateurs fournis, nous pouvons facilement vérifier qu'un message et une adresse e-mail valides ont été fournis:

 // routes.js
const {check, validationResult, matchedData} = require ('express-validator');

router.post ('/ contact', [
  check('message')
    .isLength({ min: 1 })
    .withMessage('Message is required'),
  check('email')
    .isEmail()
    .withMessage('That email doesn‘t look right')
](req, res) => {
  const errors = validationResult (req);
  res.render ('contact', {
    données: corps requis,
    erreurs: errors.mapped ()
  });
});

Désinfection

Avec les désinfectants fournis, nous pouvons découper les espaces blancs du début et de la fin des valeurs et normaliser l'adresse e-mail en un motif cohérent. Cela peut aider à supprimer les contacts en double créés par des entrées légèrement différentes. Par exemple, 'Mark@gmail.com' et 'mark@gmail.com ' seraient tous deux désinfectés en ' mark@gmail.com '. [19659010] Les désinfectants peuvent simplement être enchaînés à la fin des validateurs:

 // routes.js
router.post ('/ contact', [
  check('message')
    .isLength({ min: 1 })
    .withMessage('Message is required')
    .trim(),
  check('email')
    .isEmail()
    .withMessage('That email doesn‘t look right')
    .bail()
    .trim()
    .normalizeEmail()
](req, res) => {
  const errors = validationResult (req);
  res.render ('contact', {
    données: corps requis,
    erreurs: errors.mapped ()
  });

  const data = matchedData (req);
  console.log ('Sanitized:', données);
});

La fonction matchedData renvoie la sortie des désinfectants sur notre entrée.

Notez également notre utilisation de la méthode bail qui arrête l'exécution des validations si l'une des précédentes certains ont échoué. Nous en avons besoin car si un utilisateur soumet le formulaire sans entrer de valeur dans le champ e-mail, normalizeEmail tentera de normaliser une chaîne vide et la convertira en @ . Celui-ci sera ensuite inséré dans notre champ de courrier électronique lorsque nous rendrons à nouveau le formulaire.

Le formulaire valide

S'il y a des erreurs, nous devons restituer la vue. Sinon, nous devons faire quelque chose d'utile avec les données, puis montrer que la soumission a réussi. En règle générale, la personne est redirigée vers une page de réussite et affiche un message.

HTTP est sans état, vous ne pouvez donc pas rediriger vers une autre page et transmettre des messages sans l'aide d'une session cookie pour conserver ce message entre les requêtes HTTP. Un «message flash» est le nom donné à ce type de message unique que nous voulons conserver sur une redirection puis disparaître.

Il y a trois middlewares que nous devons inclure pour câbler ceci:

 / / server.js
const cookieParser = require ('cookie-parser');
const session = require ('express-session');
const flash = require ('express-flash');

const middlewares = [
  // ...
  cookieParser(),
  session({
    secret: 'super-secret-key',
    key: 'super-secret-cookie',
    resave: false,
    saveUninitialized: false,
    cookie: { maxAge: 60000 }
  }),
  flash(),
];

L'intergiciel flash express ajoute req.flash (type, message) que nous pouvons utiliser dans nos gestionnaires d'itinéraire:

 // routes
router.post ('/ contact', [
  // validation ...
](req, res) => {
  const errors = validationResult (req);
  if (! errors.isEmpty ()) {
    return res.render ('contact', {
      données: corps requis,
      erreurs: errors.mapped ()
    });
  }

  const data = matchedData (req);
  console.log ('Sanitized:', données);
  // Devoirs: envoyer des données filtrées dans un e-mail ou persister sur une base de données

  req.flash ('succès', 'Merci pour le message! Je serai en contact :)');
  res.redirect ('/');
});

L'intergiciel flash express ajoute des messages à req.locals auxquels toutes les vues ont accès:


<% if (messages.success) { %>
  
<%= messages.success %>
<% } %>

Utilisation de formulaires dans Node.js

Vous devez maintenant être redirigé vers la vue d'index et voir un message de réussite lorsque le formulaire est soumis avec des données valides. Huzzah! Nous pouvons maintenant le déployer en production et recevoir des messages du prince du Nigeria.

Envoi d'e-mails avec nœud

Vous avez peut-être remarqué que l'envoi réel du courrier est laissé au lecteur comme devoir. Ce n'est pas aussi difficile que cela puisse paraître et cela peut être accompli en utilisant le paquet Nodemailer . Vous pouvez trouver des instructions simples sur la façon de configurer cela ici ou un tutoriel plus approfondi ici .

Considérations de sécurité

Si vous travaillez avec formulaires et sessions sur Internet, vous devez être conscient des failles de sécurité courantes dans les applications Web. Le meilleur conseil de sécurité que j'ai reçu est «Ne faites jamais confiance au client!»

TLS sur HTTPS

Utilisez toujours le cryptage TLS sur https: // lorsque vous travaillez avec formulaires afin que les données soumises soient cryptées lors de leur envoi sur Internet. Si vous envoyez des données de formulaire sur http: // elles sont envoyées en texte brut et peuvent être visibles par tous ceux qui écoutent ces paquets pendant leur voyage sur le Web.

Si vous souhaitez en savoir plus Pour en savoir plus sur l'utilisation de SSL / TLS dans Node.js, veuillez consulter cet article .

Portez votre casque

Il existe un petit middleware soigné appelé casque qui ajoute une certaine sécurité à partir de En-têtes HTTP. Il est préférable d'inclure juste en haut de vos middlewares et il est très facile d'inclure:

 // server.js
const casque = besoin («casque»);

middlewares = [
  helmet(),
  // ...
];

Falsification de demande intersite (CSRF)

Vous pouvez vous protéger contre la falsification de demande intersite en générant un jeton unique lorsque l'utilisateur est présenté avec un formulaire, puis en validant ce jeton avant le Les données POST sont traitées. Il existe également un middleware pour vous aider ici:

 // routes.js
const csrf = require ('csurf');
const csrfProtection = csrf ({cookie: true});

Dans la demande GET, nous générons un jeton:

 // routes.js
router.get ('/ contact', csrfProtection, (req, res) => {
  res.render ('contact', {
    Les données: {},
    les erreurs: {},
    csrfToken: req.csrfToken ()
  });
});

Et aussi dans la réponse aux erreurs de validation:

 router.post ('/ contact', csrfProtection, [
  // validations ...
](req, res) => {
  const errors = validationResult (req);
  if (! errors.isEmpty ()) {
    return res.render ('contact', {
      données: corps requis,
      erreurs: errors.mapped (),
      csrfToken: req.csrfToken ()
    });
  }

  // ...
});

Il nous suffit ensuite d'inclure le jeton dans une entrée masquée:


<input type = "hidden" name = "_ csrf" value = "<%= csrfToken %>">   

C'est tout ce qu'il faut.

Nous n'avons pas besoin de modifier notre gestionnaire de requêtes POST, car toutes les requêtes POST nécessiteront désormais un jeton valide par le middleware csurf . Si aucun jeton CSRF valide n'est fourni, une erreur ForbiddenError sera levée, qui peut être gérée par le gestionnaire d'erreurs défini à la fin de server.js .

vous pouvez le tester vous-même en modifiant ou en supprimant le jeton du formulaire avec les outils de développement de votre navigateur et en le soumettant.

Cross-site Scripting (XSS)

Vous devez faire attention lors de l'affichage des données soumises par l'utilisateur dans une vue HTML car il peut vous ouvrir jusqu'à scriptage intersite (XSS) . Tous les langages de modèles proposent différentes méthodes pour générer des valeurs. L'EJS <%= value %> affiche la valeur HTML échappé pour vous protéger contre XSS, tandis que <%- value %> génère une chaîne brute.

Utilisez toujours la sortie d'échappement <%= value %> lorsque vous traitez avec des valeurs soumises par l'utilisateur. Utilisez uniquement des sorties brutes lorsque vous êtes sûr que cela est sûr.

Téléchargement de fichiers

Le téléchargement de fichiers dans des formulaires HTML est un cas particulier qui nécessite un type de codage de "multipart / form-data" . Voir le guide de MDN sur l'envoi de données de formulaire pour plus de détails sur ce qui se passe avec les soumissions de formulaires en plusieurs parties.

Vous aurez besoin d'un middleware supplémentaire pour gérer les téléchargements en plusieurs parties. Il existe un package Express nommé multer que nous utiliserons ici:

 // routes.js
const multer = require ('multer');
const upload = multer ({stockage: multer.memoryStorage ()});

router.post ('/ contact', upload.single ('photo'), csrfProtection, [
  // validation ...
](req, res) => {
  // la gestion des erreurs ...

  if (fichier de demande) {
    console.log ('Uploaded:', req.file);
    // Devoirs: télécharger un fichier sur S3
  }

  req.flash ('succès', 'Merci pour le message! Je serai en contact :)');
  res.redirect ('/');
});

Ce code demande au multer de télécharger le fichier dans le champ "photo" en mémoire et expose l'objet File dans req.file que nous pouvons inspecter ou traiter plus loin.

La dernière chose dont nous avons besoin est d'ajouter l'attribut enctype et notre entrée de fichier:

 <form method = "post" action = "/ contact? _csrf = <%= csrfToken %> "novalidate enctype =" multipart / form-data ">
  <input type = "hidden" name = "_ csrf" value = "<%= csrfToken %>">
  <div class = "form-field <%= errors.message ? 'form-field-invalid' : '' %>">
    
    
    <% if (errors.message) { %>
      
<%= errors.message.msg %>
<% } %>
<div class = "form-field <%= errors.email ? 'form-field-invalid' : '' %>">          <input class = "input" id = "email" name = "email" type = "email" value = "<%= data.email %>" />     <% if (errors.email) { %>
<%= errors.email.msg %>
<% } %>
  

Essayez de télécharger un fichier. Vous devriez voir les objets Fichier enregistrés dans la console.

Remplissage des entrées de fichier

En cas d'erreurs de validation, nous ne pouvons pas re-remplir les entrées de fichier comme nous l'avons fait pour les entrées de texte ( c'est un risque pour la sécurité ). Une approche courante pour résoudre ce problème implique les étapes suivantes:

En raison de la complexité supplémentaire du travail avec les téléchargements de fichiers et de parties multiples, ils sont souvent conservés sous des formes distinctes.

Téléchargement de fichiers avec nœud

Enfin, vous remarquerez qu'il a été laissé au lecteur de mettre en œuvre la fonctionnalité de téléchargement réelle. Ce n'est pas aussi difficile que cela puisse paraître et peut être accompli en utilisant divers packages, tels que Formidable ou express-fileupload . Vous pouvez trouver des instructions simples sur la façon de configurer cela ici ou un tutoriel plus approfondi ici .

Merci d'avoir lu

J'espère que vous avez aimé apprendre sur les formulaires HTML et comment les utiliser dans Express et Node.js. Voici un bref résumé de ce que nous avons couvert:

Laissez faites-moi savoir comment vous vous retrouvez sur via Twitter !



Le meilleur outil 2023 pour ta croissance Instagram !



Source link

Partager :

Revenir vers le haut