👾
Node.js
  • 🧑‍💻Full-Stack Web Developer
  • 📚Теорія
    • 1️⃣Основи Node.js
      • Вступ
      • Модулі Node.js
      • Запуск скриптів модулів в Node.js
      • Структура проєкту, експорт-імпорт, index.js як хаб
      • Модулі CommonJS
      • Модулі MJS
      • Модулі ECMAScript
      • Модулі NPM + базові модулі
      • Глобальні змінні
      • Робота з файлами
    • 2️⃣Консольні додатки
      • Створення консольних додатків
    • 3️⃣Фреймворк Express
      • Про Express
      • Nodemon і запуски скриптів
      • Postman
      • Проміжне ПЗ middleware
      • Передача даних на сервер
      • Роутінг
      • CRUD
      • Налаштування лінтера
    • 4️⃣REST API
      • Змінні оточення
      • Логування
      • REST
      • Методи HTTP
      • CORS
      • Формування URL для REST API
      • Контроллери відсутнього роуту і непередбачуваної помилки
      • Валідація даних Joi
      • Рефакторинг додатку за MVC архітектурою
      • Express автогенератор додатку
    • 5️⃣База даних Mongo.DB
      • Основи MongoDB
      • Налаштування Mongo Atlas
      • Встановлення локальної MongoDB і основні команди
    • 6️⃣ODM Mongoose
      • Mongoose
      • Порядок планування бекенд додатку
      • чорнетка
    • 7️⃣Автентифякація WJT
      • чорнетка
      • чорнетка 2
    • 8️⃣Файли
      • чернетка
    • 9️⃣тестування
      • чернетка
    • 🔟Page 14
      • імейли
    • чорнетка докер
    • чорнетка сокети
    • додаткові матеріали
    • 👷Практика
      • 1️⃣Page 4
      • 2️⃣Page 5
      • 3️⃣Page 6
      • 4️⃣Page 7
      • 5️⃣Page 8
      • 6️⃣Page 9
  • Про мене
    • Про мене
Powered by GitBook
On this page
  1. Теорія
  2. Page 14

імейли

відправляти повідомлення на мило будемо у 2 випадках.

  1. при реєстрації велкам меседж

  2. запит на відновлення паролю

Необхідно створити два роути у файлі authRoutes.js

перший знаходитиме імейл в базі і відправлятиме на нього тимчасовий пароль для відновлення основного пароля

другий роут ща отриманим тимчасовим паролем буде оновлювати основний пароль

// find user and send OTP by email to restore password
router.post('/forgot-password', authController.forgotPassword);

// update password in DB
router.patch('/reset-password/:otp', authController.resetPassword);

частина береться з юзер сервіс отримання юзера по імейлу

/**
 * Find user by email.
 * @param {string} email - user email
 * @returns {Promise<User>}
 */
exports.getUserByEmail = (email) => User.findOne({ email });

тепер пропишемо контролер:

exports.forgotPassword = catchAsync(async (req, res) => {
  const user = await userService.getUserByEmail(req.body.email);
  if (!user) {
    return res.status(200).json({
      msg: 'Password reset instruction sent to email..',
    });
  }
  const otp = user.createPasswordResetToken();
  await user.save();
  console.log('||=============>>>>>>>>>>>');
  console.log(otp);
  console.log('<<<<<<<<<<<=============||');

  res.status(200).json({
    msg: 'Password reset instruction sent to email..',
  });
});

в юзер модел пропишемо ще один метод

userSchema.methods.createPasswordResetToken = function () {
  const resetToken = crypto.randomBytes(32).toString("hex");

  this.passwordResetToken = crypto
    .createHash("sha256")
    .update(resetToken)
    .digest("hex");
  this.passwordResetExpires = Date.now() + 10 * 60 * 1000;

  return resetToken;
};

розберемо код. спрочатку за допомогою crypto ми генероуємо рандомне число на 32 байти, і преводимо у шістнадцятковий рядок. (так званий скидочний токен для пароля саме його далі ми будемо переселати в імейлі для юзера)

створимо метод passwordResetToken де createHash - вказує алгоритм шифрування, update - що саме оновимо, digest - шістнадцятковий код.

У методі passwordResetExpires - ми генеруємо дату і збільшуємо її на 10 хвилин.

тобто в контролер повертається згенерований токен, а в базу даних запишеться захешований(це прописано в контролері).

а також додамо два поля в нашу модель. тут зуде зберігатися захещований токен на скидання паролю і дата до якої він діє

passwordResetToken: String,
passwordResetExpires: Date,

ТОБТО НАЗОВНІ МИ ВІДДАВАТИМЕМО НЕЗАХЕЩОВАНИЙ ТОКЕН, А В БАЗІ БУДЕМО МАТИ ЗАХЕШОВАНИЙ

-------------

етпер напишемо бзерсервіс на скидання паролю

exports.resetPassword = async (otp, password) => {
  const hashedToken = crypto.createHash("sha256").update(otp).digest("hex");

  const user = await User.findOne({
    passwordResetToken: hashedToken,
    passwordResetExpires: { $gt: Date.now() },
  });

  if (!user) throw new AppError(400, "Token is invalid..");

  user.password = password;
  user.passwordResetToken = undefined;
  user.passwordResetExpires = undefined;

  await user.save();

  user.password = undefined;

  return user;
};

не забуваємо заімпортувати крипто

const crypto = require("crypto");

пописуємо контролер

exports.resetPassword = catchAsync(async (req, res) => {
  const updatedUser = await userService.resetPassword(req.params.otp, req.body.password);
  res.status(200).json({
    user: updatedUser,
  });
});

На цьому етапі код має вже працювати тільки поки що без відправки імейлів

і передамо в боді обʼєкт імейла користувача який нібито защубив пароль

{
"email": "khomiak@gmail.com"
}

отримаємо наш респонс

{
    "msg": "Password reset instruction sent to email.."
}

перевіримо в базі даних, зо у нас зїявилися нові поля

база
passwordResetExpires
2023-08-10T14:25:22.926+00:00
passwordResetToken
"5beec8c62ad62da6ac0ecb019b5be91594e536fccf5dd236f959f29841d1d872"

а в консолі у нас виявився інший токен

||=============>>>>>>>>>>>iming config:load:flatten Completed in 1ms
aad2cc6043dd1ed5281bd11d8da4daef58403e19dfaf6c31ca86f7755850948a
<<<<<<<<<<<=============||

це по суті хеши одного і того самого токена

прим. якщо в боді передати неіснуючий імейл, т буде те саме повідомлення

Тепер треба перевірити токен

той токен, який прийшов в консолі для відноавлення для імейра використаємо для перевірки перезапису пароля

і передаємо обʼєкт з пародем

```postman_json
{
"password": "Password1234$"
}
```

отримуємо респонс

{
    "user": {
        "_id": "64beb1bf56ef3c789231a1b0",
        "name": "Khomiak",
        "email": "khomiak@gmail.com",
        "year": 1998,
        "role": "user",
        "__v": 0,
        "updatedAt": "2023-08-10T14:25:18.962Z"
    }
}

Після цього перевіримо базу даних переконаємося, що тимчасовий токен видалено, час його дії видалено і пароль помінявся

Тепер перевіримо чи можна задогінитись із цим логіном і новим паролем. пост запит

і в боді передамо

req
{
"email": "khomiak@gmail.com",
"password": "Password1234$"
}

ів респонсі отримаємо вебтокен, тобто зміна пароля спрацювала

{
    "user": {
        "_id": "64beb1bf56ef3c789231a1b0",
        "name": "Khomiak",
        "email": "khomiak@gmail.com",
        "year": 1998,
        "role": "user",
        "__v": 0,
        "updatedAt": "2023-08-10T14:25:18.962Z"
    },
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2NGJlYjFiZjU2ZWYzYzc4OTIzMWExYjAiLCJpYXQiOjE2OTE2NzgxNjYsImV4cCI6MTY5MjU0MjE2Nn0.Gd2S4u-6Ln-mIrhvZkebXKGktgoN45QmvypPZrsCldU"
}

НАЛАШТУВАННЯ РОЗСИЛКИ ЧЕРЕЗ ІМЕЙЛ

npm i nodemailer

nodemailer конфігурує те, що ми будемо вірпавляти в коді

прописуємо цей функціонал і контролерах

const nodemailer = require("nodemailer")
const emailTransport = nodemailer.createTransport({
  service: 'Gmail',
    auth: {
       user: 'user',
      pass: 'passwd'
    }
  });

ctreateTransport - це метод конфігурування двіжка

service - налаштовує конфігурацію сервісу рохсилки

auth - на сервісі з якого буде проводитися розсилка треба буде авторизуватися

Це приклад вище, але ми налаштуємо зараз інакше вручну:

const emailTransport = nodemailer.createTransport({
    host: 'sandbox.smtp.mailtrap.io',
     port: 2525,
    auth: {
       user: process.env.EMAIL_USER,
       pass: process.env.EMAIL_PASSWORD,
    },
   });

хост і порт будемо брати із сервісі mailtrap паролі і доніг до цього сервіса звичайно будемо зберігати в енвайромент

Налаштовуємо безпосередньо сам імейл

  const emailConfig = {
    from: 'Todos app admin <admin@example.com>',
    to: user.email,
    subject: 'Password reset instruction',
    text: resetUrl,
  };

відповідно вище ми маємо визначити лінку скидання цього імейла

  const resetUrl = `${req.protocol}://${req.get(
    "host"
  )}/auth/reset-password/${otp}`;

req.protocol - тут треба вказувати протокол саме фронтенда (буває що фронтенд і бекенд на різних серверах), бо відкриватиметься посилання на фронті

req.get('host') а тут вказується зост на бекенд. в результаті треба слідкувати уважно за цими двома параметрами, зоб вони працювали.

після всіх цих операцій ми вже можемо відправити імейл

await emailTransport.sendMail(emailConfig);

у нього два редими розробки. 1 реальний 2. тестовий, коли всі імейои будуть відправлятися на один тестовий імейл

мейдтрап будемо використовувати саме для параметрів налаштування

Спочатку треба залогінитися (можна використати пароль гітхаб чи гугл)

після авторизації вибираємо email testing - inboxes - my inbox - smtp settings. У меню інтернацій треба вибрати nodemailer.

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

також тут вказуються логін і пароль для авторизації які потрібно прописати в енвайромент

наприклад такі дані

EMAIL_USER = "42926234679aa4"
EMAIL_PASSWORD = "ce916b064f9464"

Тепер спробужмо знову подати запит на скидання паролю відправивши пост запит із обʼєктом з імейлом

після цього в нашому мейлтрап ми отримаємо повідомлення

це посилання має бути сформоване для фронтенда, а знаючи отриманий код ми вде потім на фронті сформуємо запит на скидання паролю.

Але імейли ніхто не відправляє в чисто текстовому варіанті, а роблять у форматі оформленої вестки далі розберемо формування верстки.

ДЛЯ ФОМУВАННЯ ВЕРСТКИ ІМЕЙЛУ ВИКОРИСТАЄМО ТЕМПЛЕЙТИ

будемо фромувати html. імейди досі верстаються таличною версткою

і також скористаємося корвертором HTML В PUG

вставивши виществорену верстку сюжи можна зненерувати паг вестку

у папці views створимо папку emails a в ньому файл hello.pug куди і вставимо згенерований наш паг листа

всередині створимо папку layout і винесемо в неї стилі з нагошо імейл листа у файл _style.pug

всі стилі відповідно виносимо у цей файл і зберігаємо і потім шмпортуємо їх в base.pug командою include _style

також в layout створимо файл base.pug і весь код перекидаємо в нього забравщи коментарі

і вкінці знак пробіла має бути такий td &nbsp; (і напочатку td - так само. Це треба зробити бо інакще поламається верстка)

після цього потрібно витягнути основний контент (вирізати), а замість нього написати block content

при цьому цей фрагмент покласти в hello.pug

(прим, щоб красиво оформити можна встановити плагін pug beautify)

Таккод створимо файл passwordreset.pug і закинемо в нього такий само темплейт

тільки треба дописати

extends layout/base

block content

Змінні можна з бекенда передати #{name_of_var}

але там де passwordreset.pug внесемо інше урл (для скиання пароля.)

також там де сабджект будемо передавати назву сабджекта

title #{subject}

на цьому етапі ми підготували шаблони, Щоб відправляти

ТЕПЕР НАПИТЕМО СЕРВІС ВІДПРАВКИ ІМЕЙЛІВ

у файлі emailService.js

поставимо пакет

npm i html-to-text         

Винесемо імейл від якого вілправлятиметься сервіс в енвайромент

EMAIL_FROM = "Message from Admin <khomiak@gmail.com>"

НАПИШЕМО КЛАС ДЛЯ ІСЕЙЛ РОЗСИЛКИ

```javascript
const nodemailer = require("nodemailer");
const path = require("path");
const pug = require("pug");
const { convert } = require("html-to-text");

class Email {
  constructor(user, url) {
    this.to = user.email;
    this.name = user.name;
    this.url = url;
    this.from = process.env.EMAIL_FROM;
  }

  // #initTransport() - experimental JS private method syntax
  _initTransport() {
    if (process.env.NODE_ENV === "development") {
      // use SENDGRID
    }

    // use MAILTRAP for tests
    return nodemailer.createTransport({
      host: "sandbox.smtp.mailtrap.io",
      port: 2525,
      auth: {
        user: process.env.EMAIL_USER,
        pass: process.env.EMAIL_PASSWORD,
      },
    });
  }

  async _send(template, subject) {
    const html = pug.renderFile(
      path.join(__dirname, "..", "views", "emails", `${template}.pug`),
      {
        name: this.name,
        url: this.url,
        subject,
      }
    );

    const emailConfig = {
      from: this.from,
      to: this.to,
      subject,
      html,
      text: convert(html),
    };

    await this._initTransport().sendMail(emailConfig);
  }

  async sendHello() {
    await this._send("hello", "Welcome mail!");
  }

  async sendRestorePassword() {
    await this._send("passwordreset", "Password reset instruction..");
  }
}

module.exports = Email;

// https://github.com/leemunroe/responsive-html-email-template
// https://html2pug.vercel.app/

```

прикрутимо цей сервіс до signup (в аусконтролерс)

exports.signup = catchAsync(async (req, res) => {
  const { user, token } = await userService.signupUser(req.body);
  try {
    await new Email(user, "localhost:3000").sendHello();
  } catch (err) {
    console.log(err);
  }

  res.status(201).json({
    user,
    token,
  });
});

і так само повідомлення на фогот пасфорд прикрутимо

 try {
  const resetUrl = `${req.protocol}://${req.get(
    "host"
  )}/auth/reset-password/${otp}`;
  
    await new Email(user, resetUrl).sendRestorePassword();
  } catch (err) {
    console.log(err);
    user.passwordResetToken = undefined;
    user.passwordResetExpires = undefined;
    await user.save();
  }

Тепер перевіримо пост запит

відправимо в боді

{
"email": "khomiak@gmail.com",
"name": "Alex",
"year": 1994,
"password": "Password1234$"
}

якщо це новий кормтсувач, то його має зареєструвати і додати в базу даних І ПРИЙТИ ПОВІДОМЛЕННЯ НА ПОШТУ!!!!

SENDGRID

email api - integration guide

вибираємо SMTP RELAY

створюємо ключ і параметри вставляємо в енвайромент

SENDGRID_USERNAME = "apikey"
SENDGRID_APIKEY = "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"

а в імейл сервісі дописуємо метод

  _initTransport() {
    if (process.env.NODE_ENV === "development") {
      // use SENDGRID
        return nodemailer.createTransport({
          service: "SendGrid",
          auth: {
            user: process.env.SENDGRID_USERNAME,
            pass: process.env.SENDGRID_APIKEY,
          },
        });
    }

це по суті наш конфіг під продакшн там тільки треба перевірити якщо завантадився енвайромент production

налаштування settings - sender authentication

і проходимо верифікацію імейла сендера юзера

Verify an Address

цей сервіс генерує тимчасові недовготривалі імейли саме доя випалків тестування

аможна і на своєму власному імейлі. створюємо пост запит нового користувача він має додатися в базу, а на вказаний імейл має прийти повідомленя про реєстрацію

Якшо лист прийшов на пошту, то значить сервіс працює і можна його підтвердити що він працює. Ждя цього заходимо email api - integration guide

вибираємо SMTP RELAY

внизу натискаємо галочку і вибираємо веріфай інтегрейшн (якщо не закрили попрееднб стоірнку)

PreviousPage 14Nextчорнетка докер

Last updated 1 year ago

етпер відправимо пост запит

робимо патч запит

Тепер налаштування сервіса

Але є готові темплейти розпроблені інщими програмістами наприклад оцей

крім того треба налаштувати сендера;

Тестувати можна на сервісі

📚
🔟
http://localhost:3000/auth/forgot-password
http://localhost:3000/auth/reset-password/aad2cc6043dd1ed5281bd11d8da4daef58403e19dfaf6c31ca86f7755850948a
http://localhost:3000/auth/login
https://mailtrap.io/
http://localhost:3000/auth/reset-password/0e9a9a0bf0052eb32c88ae4dc25fecdedc4e17f43f80d8a197f66e865bbf873b
https://github.com/leemunroe/responsive-html-email-template
https://html2pug.vercel.app/
http://localhost:3000/auth/signup
https://app.sendgrid.com/guide/integrate
https://app.sendgrid.com/settings/sender_auth
https://mailsac.com/