2018-06-20
Введение.
Этот проект был написан чтобы продемонстрировать пользовательскую аутентификацию и авторизацию с помощью различных методов. Эти методы были реализованы с помощью различных стратегий Passport.js. Таких как Local стратегия (использующая username и password). А также другие стратегии такие как Facebook, Twitter и многие другие.. При разработке своего проекта я использовал основные идеи из приложения Hackathon-starter.
Этот проект на GitHub можно посмотреть здесь
Демонстрацию этого проекта можно посмотреть здесь
Обзор
Простая, ненавязчивая аутентификация для Node.js — passportjs.org. PassportJS — это middleware для авторизации под node.js. Passport поддерживает авторизацию с помощью огромного количества сервисов, включая «ВКонтакте» и прочие твиттеры. Список сервисов можно просмотреть здесь.
Возможности
- Local Authentication используя Email и Password
- OAuth 1.0a Authentication через Twitter
- OAuth 2.0 Authentication через Facebook, Google, GitHub, LinkedIn, Instagram
- Flash сообщения
- MVC структура приложения
- Bootstrap 3 + jQuery
- Контактная форма (для передачи почты используется сервис Sendgrid)
- Управление Аккаунтами
- Gravatar
- Профайл Детали
- Изменить Пароль
- Забыли Пароль
- Сбросить Пароль
- Подключение мульти OAuth стратегий к одному аккаунту
- Удалить Аккаунт
- CSRF защита
- API Примеры: Facebook, Foursquare, Last.fm, Tumblr, Twitter, Stripe, LinkedIn и т.д.
Ресурсы
- Node.js (Chrome's V8 JavaScript engine)
- Документация
- Express (Node.js framework)
- Документация
- API
- Passport.js
- Документация
- jQuery
- Документация
- Bootstrap 3
- Документация
- Font Awesome - the iconic font and CSS toolkit
- Icons
- MongoDB (noSQL DataBase)
- Документация
- Atlas (облачный хостинг)
- mLab (облачный хостинг 500MB FREE)
- Сompass (инструмент для визуализации данных)
- Mongoosejs (NPM компонент для работы с моделями базы данных)
- Atlas (облачный хостинг)
- Интересные статьи
- Authenticating Node.js Applications With Passport
- Easy Node Authentication
- How To Implement Password Reset In Node.js
- Аутентификация в Node.js. Учебные руководства и возможные ошибки
- Easy Node Authentication
Установка приложения
Предварительные требования
Убедитесь в том, что у вас установлен NodeJS 8.0+ и MongoDB.
Клонируйте или загрузите express-passport проект с GitHub.
- Инсталируйте ваши зависимости
cd <project-name> npm install # Or yarn install
Переменные окружения
Файл .env
должен находиться в корне проекта, если он отсутствует, то его нужно создать на основе примера файла .env.example
, иначе приложение будет выдавать ошибку. Файл .env
устанавливает переменные окружения. В переменных окружения обычно указываются секретные данные пользователя например user_id, user_secret и т.д.
Запуск вашего проекта
Development
npm run dev
Приложение будет запущено в режиме Разработки и будет выполнятся на http://localhost:3000
Production
npm start
Приложение будет запущено в Рабочем режиме и будет выполнятся на рабочем хостинге пример можно посмотреть здесь
Структура приложения
Файловая структура
пр.1Имя файла | Описание |
---|---|
config/mongod.config.yml | Конфигурация для MongoDB. |
config/passport.js | Passport Local и OAuth стратегии, плюс логин middleware. |
controllers/api.js | Контроллер для /api маршрутизации и всех api примеров. |
controllers/contact.js | Котроллер для работы с формой контактов. |
controllers/home.js | Контроллер для домашней страницы (home). |
controllers/index.js | Привязка контроллеров к маршрутам. |
controllers/user.js | Контроллер для управления учетными записями пользователей. |
data/db/mongod | Папка расположения базы данных MongoDB. |
data/log/mongod | Папка расположения логов для базы данных MongoDB. |
data/uploads | Папка для загрузки файлов. |
models/index.js | Соединение с базой данных MongoDB. |
models/User.js | Mongoose схема и модель для пользователя. |
public/ | Папка для публичных данных (css, js, images). |
views/account/ | Шаблон для изменения данных профайла, изменения пароля, удаления аккаунта, добавление ссылок на другие аккаунты.. |
views/api/ | Шаблон для тестирования API различных сервисов. |
views/partials/flash.pug | Шаблон для отображения предупреждений об ошибках, информации и успешного завершения операции. |
views/partials/header.pug | Шаблон для отображения верхнего меню. |
views/partials/footer.pug | Шаблон для отображения нижнего колонтитула. |
views/contact.pug | Шаблон формы контактов. |
views/error.pug | Шаблон для отображения ошибок. |
views/home.pug | Шаблон для отображения домашней страницы. |
views/layout.pug | Шаблон основного макета. |
.env | Ваши реальные API ключи, уникальные данные, пароли и URI базы данных. |
.env.example | Примеры API ключей, уникальных данных, паролей и URI базы данных. |
.gitignore | Папки и файлы, которые будут игнорироваться системой управления версиями Git. |
app.js | Главный файл приложения. |
package.json | NPM зависимости. |
package-lock.json | Содержит точные версии зависимостей NPM в package.json. |
server.js | Запуск локального сервера. |
Замечание: Здесь представлен мой вариант организации структуры приложения. Вы можете изменять структуру приложения по своему усмотрению...
Список NPM пакетов
пр.2Пакет | Описание |
---|---|
@octokit/rest | GitHub API library. |
@sendgrid/mail | Mail library. |
bcrypt-nodejs | Library for hashing and salting user passwords. |
body-parser | Node.js body parsing middleware. |
chalk | Terminal string styling done right. |
cheerio | Scrape web pages using jQuery-style syntax. |
clockwork | Clockwork SMS API library. |
compression | Node.js compression middleware. |
cross-env | Setting environment variables. |
debug | Debug library. |
dotenv | Loads environment variables from .env file. |
express | Node.js web framework. |
express-flash | Provides flash messages for Express. |
express-session | Simple session middleware for Express. |
express-status-monitor | Reports real-time server metrics for Express. |
express-validator | Easy form validation for Express. |
fbgraph | Facebook Graph API library. |
instagram-node | Instagram API library. |
lastfm | Last.fm API library. |
lob | Lob API library. |
lusca | CSRF middleware. |
mongoose | MongoDB ODM. |
morgan | HTTP request logger middleware for node.js. |
multer | Node.js middleware for handling multipart/form-data . |
node-foursquare | Foursquare API library. |
node-linkedin | LinkedIn API library. |
passport | Simple and elegant authentication library for node.js. |
passport-facebook | Sign-in with Facebook plugin. |
passport-github | Sign-in with GitHub plugin. |
passport-google-oauth | Sign-in with Google plugin. |
passport-instagram | Sign-in with Instagram plugin. |
passport-linkedin-oauth2 | Sign-in with LinkedIn plugin. |
passport-local | Sign-in with Username and Password plugin. |
passport-openid | Sign-in with OpenId plugin. |
passport-oauth | Allows you to set up your own OAuth 1.0a and OAuth 2.0 strategies. |
passport-twitter | Sign-in with Twitter plugin. |
paypal-rest-sdk | PayPal APIs library. |
pug (jade) | Template engine for Express. |
request | Simplified HTTP request library. |
stripe | Offical Stripe API library. |
tumblr.js | Tumblr API library. |
twilio | Twilio API library. |
twit | Twitter API library. |
validator | Used in conjunction with express-validator in controllers/api.js. |
Конфигурация
.env
Файл .env
должен находиться в корне проекта, если он отсутствует, то его нужно создать на основе примера файла .env.example
, иначе приложение будет выдавать ошибку. Файл .env
устанавливает переменные окружения. В переменных окружения обычно указываются секретные данные пользователя например user_id, user_secret и т.д.
MY_EMAIL=my@mail.com
BASE_URL=http://localhost:8080
MONGODB_URI=mongodb://localhost:27017/test
SESSION_SECRET=Your Session Secret goes here
MAILGUN_USER=postmaster@sandbox697fcddc09814c6b83718b9fd5d4e5dc.mailgun.org
MAILGUN_PASSWORD=29eldds1uri6
SENDGRID_USER=hslogin
SENDGRID_PASSWORD=hspassword00
SENDGRID_API_KEY=SG.EV-XXXXXXXXXXXXXXXXXXX.XXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXX
NYT_KEY=9548be6f3a64163d23e1539f067fcabd:5:68537648
LASTFM_KEY=c8c0ea1c4a6b199b3429722512fbd17f
LASTFM_SECRET=is cb7857b8fba83f819ea46ca13681fe71
FACEBOOK_ID=754220301289665
FACEBOOK_SECRET=41860e58c256a3d7ad8267d3c1939a4a
INSTAGRAM_ID=9f5c39ab236a48e0aec354acb77eee9b
INSTAGRAM_SECRET=5920619aafe842128673e793a1c40028
GITHUB_ID=cb448b1d4f0c743a1e36
GITHUB_SECRET=815aa4606f476444691c5f1c16b9c70da6714dc6
TWITTER_KEY=6NNBDyJ2TavL407A3lWxPFKBI
TWITTER_SECRET=ZHaYyK3DQCqv49Z9ofsYdqiUgeoICyh6uoBgFfu7OeYC7wTQKa
GOOGLE_ID=828110519058.apps.googleusercontent.com
GOOGLE_SECRET=JdZsIaWhUFIchmC1a_IZzOHb
LINKEDIN_ID=77chexmowru601
LINKEDIN_SECRET=szdC8lN2s2SuMSy8
LINKEDIN_CALLBACK_URL=http://localhost:8080/auth/linkedin/callback
STEAM_KEY=D1240DEF4D41D416FD291D0075B6ED3F
TWILIO_SID=AC6f0edc4c47becc6d0a952536fc9a6025
TWILIO_TOKEN=a67170ff7afa2df3f4c7d97cd240d0f3
CLOCKWORK_KEY=9ffb267f88df55762f74ba2f517a66dc8bedac5a
STRIPE_SKEY=sk_test_BQokikJOvBiI2HlWgH4olfQ2
STRIPE_PKEY=pk_test_6pRNASCoBOKtIshFeQd4XMUh
TUMBLR_KEY=FaXbGf5gkhswzDqSMYI42QCPYoHsu5MIDciAhTyYjehotQpJvM
TUMBLR_SECRET=QpCTs5IMMCsCImwdvFiqyGtIZwowF5o3UXonjPoNp4HVtJAL4o
FOURSQUARE_ID=2STROLSFBMZLAHG3IBA141EM2HGRF0IRIBB4KXMOGA2EH3JG
FOURSQUARE_SECRET=UAABFAWTIHIUFBL0PDC3TDMSXJF2GTGWLD3BES1QHXKAIYQB
FOURSQUARE_REDIRECT_URL=http://localhost:8080/auth/foursquare/callback
PAYPAL_ID=AdGE8hDyixVoHmbhASqAThfbBcrbcgiJPBwlAM7u7Kfq3YU-iPGc6BXaTppt
PAYPAL_SECRET=EPN0WxB5PaRaumTB1ZpCuuTqLqIlF6_EWUcAbZV99Eu86YeNBVm9KVsw_Ez5
PAYPAL_RETURN_URL=http://localhost:8080/api/paypal/success
PAYPAL_CANCEL_URL=http://localhost:8080/api/paypal/cancel
LOB_KEY=test_814e892b199d65ef6dbb3e4ad24689559ca
PINTEREST_ID=4819282851912494691
PINTEREST_SECRET=b32f578ad83d94c058c6682329220feda7e5817e043a1cc4a5a1e28f51c70301
PINTEREST_REDIRECT_URL=https://localhost:8080/auth/pinterest/callback
GOOGLE_MAP_API_KEY=google-map-api-key
Замечание: Чтобы иметь реальные ключи для доступа к API различных сервисов нужно зарегистрировать ваше приложение в этих сервисах. Примеры регистрации приложения и получения ключей API в разных сервисах можно посмотреть здесь.
Работа приложения
- В приложении я использую базу данных MongoDB. Можно установить MongoDB локально на компьютер или использовать MongoDB на облачном хостинге Atlas или mLab.
- Чтобы тестировать в приложении возможность логирования с помощью таких сервисов как Facebook, Google, GitHub, LinkedIn, Instagram и т.д. нужно быть там зарегистрированным и иметь свой аккаунт.
- Если вы разработчик и хотите создавать приложения с возможностью логирования и доступа других пользователей к API различных сервисов, нужно зарегистрировать ваше приложение в этих сервисах. Примеры регистрации приложения и получения ключей API в разных сервисах можно посмотреть здесь
- Для передачи почтовых сообщений я пользуюсь сервисом SendGrid. Этот сервис дает возможность передавать бесплатно до 100 сообщений в день, но для этого нужно зарегистрироваться в нем и получить SENDGRID_API_KEY.
Замечание: Для безопасности этот сервис предлагает включать в свой белый список те IP адреса с которых вы хотите передавать сообщения. Важно, чтобы ваш IP был постоянен, иначе передача сообщений будет не возможна...
- Для построения пользовательского интерфейса используется Bootstrap 3, это один из популярных HTML, CSS, и JS фреймворков для разработки интерактивных мобильных проектов в Интернете.
Mongoose модель для аккаунтов пользователей
Аккаунты зарегистрированных пользователей храняться в базе данных MongoDB в таблице Users. Структрура таблицы задается схемой userSchema в модуле models/User.js
см. пр.4
const userSchema = new mongoose.Schema({
email: {type: String, unique: true},
password: String,
passwordResetToken: String,
passwordResetExpires: Date,
facebook: String,
twitter: String,
google: String,
github: String,
instagram: String,
linkedin: String,
steam: String,
tokens: Array,
profile: {
name: String,
gender: String,
location: String,
website: String,
picture: String
}
}, {timestamps: true});
Из структуры модели models/User.js
видно, что каждый аккаунт пользователя должен иметь обязательное уникальное поле адреса Email.Создать аккаунт пользователя можно двумя способами:
- Через форму Create Account например по адресу. В форме вы вводите адрес электронной почты и ваш пароль и если ваш email будет уникальным, то в базе создается запись с вашим аккаунтом с полями email и password
- Через форму Login например по адресу. В форме из списка вы выбираете сервис (Facebook, Google, LinkedIn...) и если этот сервис не присоединен к вашему уже существующему аккаунту и в этом сервисе, указанный email отличается от других электронных адресов, существующих аккаунтов, тогда создается новый аккаунт с электронным адресом этого сервиса и полем с названием этого сервиса со значением его id например
instagram: "2000082009", email: "my@instagram.com"
.При этом также из этого сервиса регистрируемый пользователь получаем данные для своего профайла в поле profile и получает ключи доступа к своему API в поле tokens.
Также в структуре модели мы видим два поля passwordResetToken: String, passwordResetExpires: Date
, которые используются при алгоритме восстановления пароля, когда пользователь забывает свой пароль. а параметр {timestamps: true}
указывает модели чтобы она создавала поля даты создания и изменения аккаунта например createdAt: 2018-06-19 11:18:12.867, updatedAt: 2018-06-23 08:39:29.442
.
В существующий аккаунт пользователя можно присоединить другие сервисы, по которым можно входить через этот аккаунт при логировании пользоватея. Для этого когда пользователь уже вошел под своим аккаунтом через форму логирования нужно через меню My Account открыть форму своего профайла например по адресу. В этой форме можно изменить данные своего профайла, изменить пароль, если он существует, удалить свой аккаут или присоедить к своему аккаунту сервис, с помощью которого можно входить через этом аккаут при логировании. В нижней части формы существует список сервисов, которые можно присоединить к текущему аккаунту.
Замечание: Присоединить к текущему аккаунту можно только такой сервис, который еще не был присоединен к нашим другим аккаунтам, т.е. выбранный сервис может быть присоединен к аккаунту только один раз.
При присоединении сервиса к аккаунту появляется поле этого сервиса с его id, например google: "110341449488589699610"
, и в поле tokens получаем ключи доступа к API этого сервиса, например tokens:[{kind: "google", accessToken: "ya29.xxx..."}]
А также из этого сервиса получаем данные профайла в поле profile значения которых были пустыми.
Таким образом при присоединении к аккаунту дополнительных сервисов есть возможность логироваться в этот аккаунт через разные сервисы, которые были присоединены к этому аккаунту.
Иногда возникает необходимость для некоторого сервиса просто сохранить коды доступа к его API в поле tokens в текущем аккаунте, при этом этот сервис не учавствует в аутентификации пользователя а используется только для доступа к своим ресурсам через свои tokens. Такой подход применяется для некоторых примеров API в модуле controllers/api.js
.
Стратегии Passport.js
Существует множество стратегий для Passport.js которые можно посмотреть здесь. В нашем проекте стратегии реализованы в модулеconfig/pasport.js
. Рассмотрим основные из них.Local
Стратегия Local использует email и password при аутентификации пользователя.пр.5
/**
* Sign in using Email and Password.
*/
passport.use('local', new LocalStrategy({usernameField: 'email'}, (email, password, done) => {
User.findOne({email: email.toLowerCase()}, (err, user) => {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {msg: `Email ${email} not found.`});
}
user.comparePassword(password, (err, isMatch) => {
if (err) {
return done(err);
}
if (isMatch) {
return done(null, user);
}
return done(null, false, {msg: 'Invalid email or password.'});
});
});
}));
Логика такая: пользователь передает свой email и password стратегия ищет аккаунт по переданному email, если не находит - выдает ошибку Email not found.
. Если находит, то сравнивает полученный password с существующим, если password отличается - выдает ошибку Invalid email or password
. Если password совпадает, то выдается положительный результат и происходит аутентификация пользователя.GitHub
Стратегия GitHub и другие стратегии которые используются при аутентификации и авторизации пользователя.пр.6
/**
* OAuth Strategy Overview
*
* - User is already logged in.
* - Check if there is an existing account with a provider id.
* - If there is, return an error message. (Account merging not supported)
* - Else link new OAuth account with currently logged-in user.
* - User is not logged in.
* - Check if it's a returning user.
* - If returning user, sign in and we are done.
* - Else check if there is an existing account with user's email.
* - If there is, return an error message.
* - Else create a new account.
*/
/**
* Sign in with GitHub.
*/
passport.use('github', new GitHubStrategy({
clientID: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
callbackURL: '/auth/github/callback',
passReqToCallback: true
}, (req, accessToken, refreshToken, profile, done) => {
if (req.user) {
User.findOne({github: profile.id}, (err, existingUser) => {
if (existingUser) {
req.flash('errors', {msg: 'There is already a GitHub account that belongs to you. Sign in with that account or delete it, then link it with your current account.'});
done(err);
} else {
User.findById(req.user.id, (err, user) => {
if (err) {
return done(err);
}
user.github = profile.id;
user.tokens.push({kind: 'github', accessToken});
user.profile.name = user.profile.name || profile.displayName;
user.profile.picture = user.profile.picture || profile._json.avatar_url;
user.profile.location = user.profile.location || profile._json.location;
user.profile.website = user.profile.website || profile._json.blog;
user.save((err) => {
req.flash('info', {msg: 'GitHub account has been linked.'});
done(err, user);
});
});
}
});
} else {
User.findOne({github: profile.id}, (err, existingUser) => {
if (err) {
return done(err);
}
if (existingUser) {
return done(null, existingUser);
}
User.findOne({email: profile._json.email}, (err, existingEmailUser) => {
if (err) {
return done(err);
}
if (existingEmailUser) {
req.flash('errors', {msg: 'There is already an account using this email address. Sign in to that account and link it with GitHub manually from Account Settings.'});
done(err);
} else {
const user = new User();
user.email = profile._json.email;
user.github = profile.id;
user.tokens.push({kind: 'github', accessToken});
user.profile.name = profile.displayName;
user.profile.picture = profile._json.avatar_url;
user.profile.location = profile._json.location;
user.profile.website = profile._json.blog;
user.save((err) => {
done(err, user);
});
}
});
});
}
}));
Логика такая: пользователь аутентифицируется на сервисе GitHub если аутентификация прошла успшно, то получает от сервиса (accessToken, refreshToken, profile).Если пользователь уже прошел аутентификацию на сайте, то по полю github: profile.id
в базе данных ищется соответствующий аккаунт пользователя. Если аккаунт с таким пользователем найден, то выдается ошибка There is already a GitHub account that belongs to you. Sign in with that account or delete it, then link it with your current account.
Это значит, что такой сервис может идентифицироваться только с одним аккаунтом. Если аккаунт не найден, то в него добавляется поле github = profile.id
, в поле tokens добавляется обьект {kind: 'github', accessToken}
, а в поле profile добавляются отсутствующие значения полей name, picture, location, website.
Если пользователь не прошел аутентификацию на сайте, то по полю github: profile.id
в базе данных ищется соответствующий аккаунт пользователя. Если аккаунт с таким пользователем найден, то выдается положительный результат и происходит аутентификация пользователя на сайте. Если аккаунт с таким пользователем не найден, то по полю email: profile._json.email
в базе данных ищется соответствующий аккаунт пользователя. Если аккаунт с email найден, то выдается ошибка There is already an account using this email address. Sign in to that account and link it with GitHub manually from Account Settings.
. Это значит, что значение email должно быть уникальным для всех зарегистрированных аккаунтах. Если аккаунт с email не найден, то создается новый аккаунт с уникальным полем email = profile._json.email
в аккаунт добавляется поле github = profile.id
, в поле tokens аккаунта добавляется обьект {kind: 'github', accessToken}
, а в поле profile аккаунта добавляются значения полей name, picture, location, website.
OAuth
Стратегия OAuth которя используются только при авторизации пользователя.пр.7
/**
* Tumblr API OAuth.
*/
passport.use('tumblr', new OAuthStrategy({
requestTokenURL: 'https://www.tumblr.com/oauth/request_token',
accessTokenURL: 'https://www.tumblr.com/oauth/access_token',
userAuthorizationURL: 'https://www.tumblr.com/oauth/authorize',
consumerKey: process.env.TUMBLR_KEY,
consumerSecret: process.env.TUMBLR_SECRET,
callbackURL: '/auth/tumblr/callback',
passReqToCallback: true
}, (req, token, tokenSecret, profile, done) => {
User.findById(req.user._id, (err, user) => {
if (err) {
return done(err);
}
user.tokens.push({kind: 'tumblr', accessToken: token, tokenSecret});
user.save((err) => {
done(err, user);
});
});
}));
Логика такая: стратегия используется чтобы сохранить ключи доступа (accessToken, tokenSecret) к API соответствующего сервиса в текущем аккаунте, в поле tokens аккаунта добавляется обьект {kind: 'tumblr', accessToken: token, tokenSecret}
.OAuth2
Стратегия OAuth2 которя используются только при авторизации пользователя.пр.8
/**
* Pinterest API OAuth2.
*/
passport.use('pinterest', new OAuth2Strategy({
authorizationURL: 'https://api.pinterest.com/oauth/',
tokenURL: 'https://api.pinterest.com/v1/oauth/token',
clientID: process.env.PINTEREST_ID,
clientSecret: process.env.PINTEREST_SECRET,
callbackURL: process.env.PINTEREST_REDIRECT_URL,
passReqToCallback: true
}, (req, accessToken, refreshToken, profile, done) => {
User.findById(req.user._id, (err, user) => {
if (err) {
return done(err);
}
user.tokens.push({kind: 'pinterest', accessToken});
user.save((err) => {
done(err, user);
});
});
}));
Логика такая: стратегия используется чтобы сохранить ключи доступа (accessToken) к API соответствующего сервиса в текущем аккаунте, в поле tokens аккаунта добавляется обьект {kind: 'pinterest', accessToken}
.Gravatar ( globally recognized avatar — глобально распознаваемый аватар)
Это Web 2.0 сервис, позволяющий интернет-пользователям хранить свой аватар на специальном сервере. Пользователь регистрируется на центральном сервере и сохраняет там свой аватар и адрес электронной почты. Когда он оставляет комментарий на сайте или блоге, поддерживающем Gravatar, и указывает свой адрес электронной почты, на стороне сайта вычисляется MD5-хэш от почтового адреса и отправляется на сервер Gravatar, в ответ возвращается аватар пользователя. Таким образом система Gravatar позволяет использовать аватары без регистрации на сайте или блоге.В приложении эта возможность реализована как метод модели User в модуле models/User.js
см. пр.9
/**
* Helper method for getting user's gravatar.
*/
userSchema.methods.gravatar = function gravatar(size) {
if (!size) {
size = 200;
}
if (!this.email) {
return `https://gravatar.com/avatar/?s=${size}&d=retro`;
}
const md5 = crypto.createHash('md5').update(this.email).digest('hex');
return `https://secure.gravatar.com/avatar/${md5}?s=${size}&d=retro`;
};
Передача Email
Для передачи почтовых сообщений я пользуюсь сервисом SendGrid. Этот сервис дает возможность передавать бесплатно до 100 сообщений в день, но для этого нужно зарегистрироваться в нем и получить SENDGRID_API_KEY.Замечание: Для безопасности этот сервис предлагает включать в свой белый список те IP адреса с которых вы хотите передавать сообщения. Важно, чтобы ваш IP был постоянен, иначе передача сообщений будет не возможна...
В приложении эта возможность реализована как метод контроллера postContact в модуле controllers/contact.js
см. пр.10
/**
* POST /contact
* Send a contact form via SendGrid.
*/
exports.postContact = (req, res) => {
let fromName;
let fromEmail;
if (!req.user) {
req.assert('name', 'Name cannot be blank').notEmpty();
req.assert('email', 'Email is not valid').isEmail();
}
req.assert('message', 'Message cannot be blank').notEmpty();
const errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('/contact');
}
if (!req.user) {
fromName = req.body.name;
fromEmail = req.body.email;
} else {
fromName = req.user.profile.name || '';
fromEmail = req.user.email;
if(!fromEmail){
req.flash('errors', {msg: 'Field fromEmail cannot be blank. Enter the email address in your profile.'});
return res.redirect('/contact');
}
}
const mailOptions = {
to: myEmail,
from: `${fromName} <${fromEmail}>`,
subject: 'Contact Form | Hackathon Starter',
text: req.body.message
};
sgMail.send(mailOptions);
req.flash('success', {msg: 'Email has been sent successfully!'});
res.redirect('/contact');
};