Изготовление сайтов в Донецке, ДНР. Формы, загрузка файлов и безопасность с Node.js и Express

Если вы создаете веб-приложение, вы, вероятно, столкнетесь с необходимостью создания HTML-форм в первый же день. Они составляют большую часть веб-опыта, и они могут быть сложными.

Как правило, процесс обработки формы включает в себя:

отображение пустой HTML-формы в ответ на первоначальный GETзапрос

пользователь, отправляющий форму с данными в POSTзапросе

проверка как на клиенте, так и на сервере

повторное отображение формы, заполненной экранированными данными и сообщениями об ошибках, если они недействительны

что-то делать с продезинфицированными данными на сервере, если все они действительны

перенаправление пользователя или отображение сообщения об успешном выполнении после обработки данных.

Обработка данных формы также связана с дополнительными соображениями безопасности.

Мы рассмотрим все это и объясним, как создавать их с помощью Node.js и Express — самого популярного веб-фреймворка для Node. Во-первых, мы создадим простую контактную форму, где люди смогут безопасно отправить сообщение и адрес электронной почты, а затем посмотрим, что связано с обработкой загрузки файлов.

Контактная форма с электронной почтой и сообщением с ошибками проверки

Как всегда, полный код можно найти в нашем репозитории GitHub.

Настраивать

Убедитесь, что у вас установлена ​​последняя версия Node.js. node -vдолжен вернуться 8.9.0или выше.

Загрузите исходный код отсюда с помощью Git:

git clone -b starter https://github.com/sitepoint-editors/node-forms.git node-forms-starter

cd node-forms-starter

npm install

npm start

Примечание. Репозиторий имеет две ветки starterи master. Ветка starterсодержит минимальную настройку, необходимую для выполнения этой статьи. Ветка masterсодержит полную рабочую демоверсию (ссылка выше).

Там не так много кода. Это просто простая настройка Express с шаблонами EJS и обработчиками ошибок:

// server.js

const path = require ('path’) ;

const express = require ('express’) ;

const layout = require ('express-layout’) ;

const routes = require ('. /routes’) ;

const app = express () ;

app.set ('views’, path.join (__dirname, 'views’));

app.set ('view engine’, '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 («Sorry can’t find that!») ;

}) ;

app.use ((err, req, res, next) => {

console.error (err.stack) ;

res.status (500).send ('Something broke!') ;

}) ;

app.listen (3000, () => {

console.log ('App running at http: //localhost:3000') ;

}) ;

Корневой URL -адрес /просто отображает index.ejsпредставление:

// routes.js

const express = require ('express’) ;

const router = express.Router () ;

router.get ('/', (req, res) => {

res.render ('index’) ;

}) ;

module.exports = router;

Отображение формы

Когда люди делают запрос GET /contact, мы хотим отобразить новое представление contact.ejs:

// routes.js

router.get ('/contact’, (req, res) => {

res.render ('contact’) ;

}) ;

Контактная форма позволит им отправить нам сообщение и адрес электронной почты:

 

 

Send us a message

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Посмотрите, как это выглядит на http: //localhost:3000/contact.

Отправка формы

Чтобы получать значения POST в Express, вам сначала нужно включить body-parserпромежуточное ПО, которое предоставляет отправленные значения формы req.bodyв ваших обработчиках маршрутов. Добавьте его в конец middlewaresмассива:

// server.js

const bodyParser = require ('body-parser’) ;

const middlewares = [

//...

bodyParser.urlencoded ({ extended: true }),

];

Для форм принято отправлять данные POST обратно на тот же URL-адрес, который использовался в исходном запросе GET. Давайте сделаем это здесь и обработаем POST /contactдля обработки пользовательского ввода.

Сначала рассмотрим недействительную отправку. Если они недействительны, нам нужно вернуть отправленные значения в представление (чтобы пользователям не нужно было вводить их повторно) вместе с любыми сообщениями об ошибках, которые мы хотим отобразить:

router.get ('/contact’, (req, res) => {

res.render ('contact’, {

data: {},

errors: {}

}) ;

}) ;

router.post ('/contact’, (req, res) => {

res.render ('contact’, {

data: req.body, // { message, email }

errors: {

message: {

msg: 'A message is required’

},

email: {

msg: 'That email doesn"t look right’

}

}

}) ;

}) ;

Если есть какие-либо ошибки проверки, мы сделаем следующее:

отображать ошибки в верхней части формы

установите входные значения на то, что было отправлено на сервер

отображать встроенные ошибки под входными данными

добавить form-field-invalidкласс к полям с ошибками.

 

 

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

Send us a message

<% } else {%>

Oops, please correct the following:

 

 

<% Object.values (errors).forEach (error => {%>

<% })%>

  • <%= error.msg%>

<% }%>

 

 

 

 

 

 

<% if (errors.message) {%>

 

<%= errors.message.msg%>

 

<% }%>

 

 

 

 

<% if (errors.email) {%>

 

<%= errors.email.msg%>

 

<% }%>

 

 

 

 

 

 

 

 

Отправьте форму http: //localhost:3000/contact, чтобы увидеть это в действии. Это все, что нам нужно на стороне просмотра.

Проверка и санитарная обработка

Существует удобное промежуточное программное обеспечение под названием экспресс-валидатор для проверки и очистки данных с использованием библиотеки validator.js. Давайте добавим его в наше приложение.

Проверка

С предоставленными валидаторами мы можем легко проверить, что сообщение и действительный адрес электронной почты были предоставлены:

// 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’, {

data: req.body,

errors: errors.mapped ()

}) ;

}) ;

Санитарная обработка

С помощью предоставленных дезинфицирующих средств мы можем обрезать пробелы в начале и в конце значений и нормализовать адрес электронной почты в соответствии с шаблоном. Это может помочь удалить повторяющиеся контакты, созданные с помощью немного разных входных данных. Например, ' Mark@gmail.com’и 'mark@gmail.com

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’, {

data: req.body,

errors: errors.mapped ()

}) ;

const data = matchedData (req) ;

console.log ('Sanitized:', data) ;

}) ;

Функция matchedDataвозвращает вывод дезинфицирующих средств на наш ввод.

Также обратите внимание на использование нами метода залога, который прекращает выполнение проверок, если какая-либо из предыдущих не удалась. Нам это нужно, потому что, если пользователь отправит форму, не введя значение в поле электронной почты, normalizeEmailон попытается нормализовать пустую строку и преобразовать ее в @. Затем это будет вставлено в наше поле электронной почты при повторном отображении формы.

Действительная форма

Если есть ошибки, нам нужно перерендерить представление. Если нет, нам нужно сделать что-то полезное с данными, а затем показать, что отправка прошла успешно. Как правило, человек перенаправляется на страницу успеха и показывает сообщение.

HTTP не имеет состояния, поэтому вы не можете перенаправить на другую страницу и передавать сообщения без помощи файла cookie сеанса, чтобы сохранить это сообщение между HTTP-запросами. «Мгновенное сообщение» — это имя, данное одноразовому сообщению такого типа, которое мы хотим сохранять при перенаправлении, а затем исчезать.

Есть три промежуточных программного обеспечения, которые нам нужно включить, чтобы подключить это:

// 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 (),

];

Промежуточное express-flashПО добавляет req.flash (type, message), которые мы можем использовать в наших обработчиках маршрутов:

// routes

router.post ('/contact’, [

// validation...

], (req, res) => {

const errors = validationResult (req) ;

if (! errors.isEmpty ()) {

return res.render ('contact’, {

data: req.body,

errors: errors.mapped ()

}) ;

}

const data = matchedData (req) ;

console.log ('Sanitized: ', data) ;

// Homework: send sanitized data in an email or persist to a db

req.flash ('success’, 'Thanks for the message! I"ll be in touch:)') ;

res.redirect ('/') ;

}) ;

Промежуточное express-flashПО добавляет messages, к req.localsкоторым имеют доступ все представления:

<% if (messages.success) {%>

 

<%= messages.success%>

 

<% }%>

Working With Forms in Node.js

Теперь вы должны быть перенаправлены в indexпредставление и увидеть сообщение об успехе, когда форма будет отправлена ​​с действительными данными. Ура! Теперь мы можем развернуть это в рабочей среде и получать сообщения от принца Нигерии.

Отправка электронной почты с помощью узла

Вы, возможно, заметили, что фактическая отправка почты оставлена ​​читателю в качестве домашнего задания. Это не так сложно, как может показаться, и может быть выполнено с помощью пакета Nodemailer. Вы можете найти простые инструкции по настройке здесь или более подробное руководство здесь.

Соображения безопасности

Если вы работаете с формами и сеансами в Интернете, вам необходимо знать об общих уязвимостях в веб-приложениях. Лучший совет по безопасности, который мне дали, — «Никогда не доверяйте клиенту!»

TLS через HTTPS

Всегда используйте шифрование TLS при https: //работе с формами, чтобы отправляемые данные шифровались при отправке через Интернет. Если вы отправляете данные формы через http: //, они отправляются в виде обычного текста и могут быть видны любому, кто подслушивает эти пакеты, когда они путешествуют по сети.

Если вы хотите узнать больше о работе с SSL/TLS в Node.js, ознакомьтесь с этой статьей.

Наденьте свой шлем

Есть аккуратное маленькое промежуточное ПО под названием шлем, которое добавляет некоторую защиту от заголовков HTTP. Лучше всего включать прямо в верхнюю часть вашего промежуточного программного обеспечения, и это очень легко включить:

// server.js

const helmet = require ('helmet’) ;

middlewares = [

helmet (),

//...

];

Подделка межсайтовых запросов (CSRF)

Вы можете защитить себя от подделки межсайтовых запросов, создав уникальный токен, когда пользователю предоставляется форма, а затем проверив этот токен перед обработкой данных POST. Здесь также есть промежуточное программное обеспечение, которое поможет вам:

// routes.js

const csrf = require ('csurf’) ;

const csrfProtection = csrf ({ cookie: true }) ;

В GET-запросе мы генерируем токен:

// routes.js

router.get ('/contact’, csrfProtection, (req, res) => {

res.render ('contact’, {

data: {},

errors: {},

csrfToken: req.csrfToken ()

}) ;

}) ;

А также в ответе на ошибки проверки:

router.post ('/contact’, csrfProtection, [

// validations...

], (req, res) => {

const errors = validationResult (req) ;

if (! errors.isEmpty ()) {

return res.render ('contact’, {

data: req.body,

errors: errors.mapped (),

csrfToken: req.csrfToken ()

}) ;

}

//...

}) ;

Затем нам просто нужно включить токен в скрытый ввод:

 

 

 

 

Это все, что требуется.

Нам не нужно изменять наш обработчик POST-запросов, так как для всех POST-запросов теперь потребуется действительный токен от csurfпромежуточного программного обеспечения. Если действительный токен CSRF не предоставлен, ForbiddenErrorбудет выдано сообщение об ошибке, которое может быть обработано обработчиком ошибок, определенным в конце файла server.js.

Вы можете проверить это самостоятельно, отредактировав или удалив токен из формы с помощью инструментов разработчика вашего браузера и отправив его.

Межсайтовый скриптинг (XSS)

Вы должны проявлять осторожность при отображении данных, отправленных пользователем, в представлении HTML, так как это может открыть вам доступ к межсайтовым сценариям (XSS). Все языки шаблонов предоставляют разные методы вывода значений. EJS <%= value%>выводит экранированное значение HTML, чтобы защитить вас от XSS, тогда как <%- value%>выводит необработанную строку.

Всегда используйте экранированный вывод <%= value%>при работе со значениями, отправленными пользователем. Используйте необработанные выходные данные только в том случае, если вы уверены, что это безопасно.

Загрузка файлов

Загрузка файлов в формах HTML — это особый случай, требующий тип кодировки «multipart/form-data». Дополнительные сведения о том, что происходит при отправке составных форм, см. в руководстве MDN по отправке данных формы.

Вам потребуется дополнительное ПО промежуточного слоя для обработки многокомпонентных загрузок. Там есть пакет Express с именем multer, который мы будем использовать здесь:

// routes.js

const multer = require ('multer’) ;

const upload = multer ({ storage: multer.memoryStorage () }) ;

router.post ('/contact’, upload.single ('photo’), csrfProtection, [

// validation...

], (req, res) => {

// error handling...

if (req.file) {

console.log ('Uploaded: ', req.file) ;

// Homework: Upload file to S3

}

req.flash ('success’, 'Thanks for the message! I’ll be in touch:)') ;

res.redirect ('/') ;

}) ;

Этот код указывает multerзагрузить файл в поле «фото» в память и выставляет Fileобъект в req.file, который мы можем проверить или обработать дальше.

Последнее, что нам нужно, это добавить enctypeатрибут и наш входной файл:

 

 

 

 

<% if (errors.message) {%>

 

<%= errors.message.msg%>

 

<% }%>

 

 

 

 

<% if (errors.email) {%>

 

<%= errors.email.msg%>

 

<% }%>

 

 

 

 

 

 

 

 

 

 

 

 

Попробуйте загрузить файл. Вы должны увидеть Fileобъекты, зарегистрированные в консоли.

Заполнение входных файлов

В случае ошибок проверки мы не можем повторно заполнить файловые входы, как мы сделали для текстовых входов (это угроза безопасности). Общий подход к решению этой проблемы включает следующие шаги:

загрузка файла во временное место на сервере

показ эскиза и имени прикрепленного файла

добавление JavaScript в форму, чтобы люди могли удалить выбранный файл или загрузить новый

перемещение файла в постоянное место, когда все в порядке.

Из-за дополнительных сложностей работы с многокомпонентными и файловыми загрузками они часто хранятся в отдельных формах.

Загрузка файлов с помощью узла

Наконец, вы заметите, что читателю предоставлена ​​возможность реализовать фактическую функциональность загрузки. Это не так сложно, как может показаться, и может быть выполнено с помощью различных пакетов, таких как Formidable или express-fileupload. Вы можете найти простые инструкции по настройке здесь или более подробное руководство здесь.

Спасибо за чтение

Надеюсь, вам было интересно узнать о HTML-формах и о том, как работать с ними в Express и Node.js. Вот краткий обзор того, что мы рассмотрели:

отображение пустой формы в ответ на GET-запрос

обработка отправленных данных POST

отображение списка ошибок, встроенных ошибок и отправленных данных

проверка отправленных данных с помощью валидаторов

очистка отправленных данных с помощью дезинфицирующих средств

передача сообщений через перенаправления с флэш-сообщением

защитить себя от атак, таких как CSRF и XSS

обработка загрузки файлов в составных формах отправки.

Делитесь нашими материалами с друзьями!

 

 

Заказать разработку сайта