Евгений Колесников 1 day ago
parent
commit
f7840521ea
2 changed files with 95 additions and 299 deletions
  1. 92 299
      articles/expressjs/express04.md
  2. 3 0
      readme.md

+ 92 - 299
articles/expressjs/express04.md

@@ -1,4 +1,4 @@
-# Использование sequelize для получения данных из БД
+# JWT-авторизация
 
 
 Продолжаем разработку АПИ для проекта "ресторан" используя **express.js**.
 Продолжаем разработку АПИ для проекта "ресторан" используя **express.js**.
 
 
@@ -7,282 +7,33 @@
 * [Создание проекта](./express01.md#создание-проекта)
 * [Создание проекта](./express01.md#создание-проекта)
 * [Подключение к БД](./express02.md#подключение-к-бд)
 * [Подключение к БД](./express02.md#подключение-к-бд)
 * [Использование sequelize для получения данных из БД](./express03.md#использование-sequelize-для-получения-данных-из-бд)
 * [Использование sequelize для получения данных из БД](./express03.md#использование-sequelize-для-получения-данных-из-бд)
-* [Создание скрипта для наполнения БД начальными данными](#создание-скрипта-для-наполнения-бд-начальными-данными)
-* [Добавление сущностей БД, реализация REST.](#добавление-сущностей-бд-реализация-rest)
+* [Создание скрипта для наполнения БД начальными данными](./express03.md#создание-скрипта-для-наполнения-бд-начальными-данными)
+* [Добавление сущностей БД, реализация REST.](./express03.md#добавление-сущностей-бд-реализация-rest)
 * [JWT-авторизация](#jwt-авторизация)
 * [JWT-авторизация](#jwt-авторизация)
 
 
-Возвращаемся к нашему `app.js`
+>JSON Web Token (JWT) — это JSON объект, который определен в открытом стандарте RFC 7519. Он считается одним из безопасных способов передачи информации между двумя участниками. Для его создания необходимо определить заголовок (header) с общей информацией по токену, полезные данные (payload), такие как `id` пользователя, его `роль` и т.д. и подписи (signature).
 
 
-Добавляем в начале файла импорт библиотеки
+## Структура JWT
 
 
-```js
-const { sequelize } = require('./models')
-const { QueryTypes } = require('sequelize')
-```
-
-И добавим _endpoint_ для получения списка блюд (я добавил префикс `/api`, он нам понадобится при настройке **nginx**, когда будем заворачивать апи в контейнер):
-
-```js
-app.get('/api/menu-item', async (req, res) => {
-  try {
-    res.json(await sequelize.query(`
-      SELECT *
-      FROM MenuItem
-    `, {
-      logging: false,
-      type: QueryTypes.SELECT
-    }))
-  } catch (error) {
-    console.error(error)
-  } finally {
-    res.end()
-  }
-})
-```
-
-1. Запросы к БД асинхронные, поэтому заворачиваем код в async/await
-1. Список блюд, возвращаемый запросом `sequelize.query` заворачиваем в `res.json()`, это добавит в заголовок ответа `Content-Type: application/json`.
+__JWT__ состоит из трех частей: заголовок `header`, полезные данные `payload` и подпись `signature`. Давайте пройдемся по каждой из них.
 
 
-Пока у нас таблица блюд пустая, поэтому в ответе тоже будет пустой массив
 
 
-## Создание скрипта для наполнения БД начальными данными
+### Шаг 1. Создаем HEADER
 
 
-**Sequelize** позволяет добавить записи в таблицы (можно использовать для первоначальной инициализации словарей или при тестировании)
+Хеадер __JWT__ содержит информацию о том, как должна вычисляться __JWT__ подпись. Хеадер — это тоже __JSON__ объект, который выглядит следующим образом:
 
 
-```
-npx sequelize-cli seed:generate --name menu-items
-```
-
-В каталоге `seeders` будет создан файл аналогичный файлам миграции (собственно и отличий между ними нет, просто логически выделены отдельно)
-
-С помощью метода `bulkInsert` формируем список блюд для добавления:
-
-```js
-'use strict';
-
-/** @type {import('sequelize-cli').Migration} */
-module.exports = {
-  async up (queryInterface, Sequelize) {
-    await queryInterface.bulkInsert('MenuItem', [   
-      {id: 1, title: 'Салат', image: 'Салат.jpg', price: 100},
-      {id: 2, title: 'Суп', image: 'Суп.jpg', price: 100},
-      {id: 3, title: 'Компот', image: 'Компот.jpg', price: 100}
-    ])
-  },
-
-  async down (queryInterface, Sequelize) {
-    await queryInterface.bulkDelete(
-      'MenuItem', 
-      null /* тут можно прописать условие WHERE */
-    )
-  }
-}
-```
-
-И запускаем добавление данных командой
-
-```
-npx sequelize-cli db:seed:all [--url=...]
-```
-
-Можно откатить отдельный импорт, но накатить можно только всё разом. Поэтому для заполнения словарей лучше использовать _bulkInsert_ в обычной миграции, а seeders использовать для тестов.
-
-## Добавление сущностей БД, реализация REST.
-
-Добавим в БД таблицу _Cart_ (корзина) и связи между _Cart_ и _MenuItem_
-
-```
-npx sequelize-cli migration:generate --name cart
-```
-
-И пропишем в созданном файле команды для создания таблицы с внешним ключем и команды для удаления:
-
->В реальном проекте ещё нужно добавить таблицу пользователей и привязать корзину к пользователю
-
-```js
-'use strict';
-
-/** @type {import('sequelize-cli').Migration} */
-module.exports = {
-  async up (queryInterface, Sequelize) {
-    await queryInterface.createTable('Cart', {
-      id: {
-        allowNull: false,
-        autoIncrement: true,
-        primaryKey: true,
-        type: Sequelize.DataTypes.INTEGER
-      },
-      menuItemId: {
-        type: Sequelize.DataTypes.INTEGER,
-        allowNull: false,
-        comment: 'для внешнего ключа'
-      },
-      quantity: {
-        type: Sequelize.DataTypes.INTEGER,
-        allowNull: false,
-        comment: 'количество'
-      }
-    })
-
-    /**
-     * Внешний ключ корзина_блюдо
-     */
-    await queryInterface.addConstraint('Cart', {
-      fields: ['menuItemId'],
-      type: 'foreign key',
-      name: 'FK_cart_menu-item',
-      references: {
-          table: 'MenuItem',
-          field: 'id'
-      },
-      onDelete: 'no action',
-      onUpdate: 'no action'
-    })
-
-  },
-
-  async down (queryInterface, Sequelize) {
-    await queryInterface.removeConstraint('Cart', 'FK_cart_menu-item')
-    await queryInterface.dropTable('Cart')
-  }
-}
-```
-
-Накатываем миграцию командой `npx sequelize-cli db:migrate` и у нас в схеме бд появится таблица Cart:
-
-![](./img/cart.png)
-
-### CRUD для корзины
-
-Для того, чтобы тело запроса автоматически преобразовывалось в объект _body_ нужно после создания экземпляра приложения подключить _middleware_ `express.json()` (_middleware_ это функции, которые выполняются до обработки _endpoints_, используются для служебных целей, как в нашем случае, и в целях авторизации. Позже добавим jwt-авторизацию и напишем для неё _middleware_):
-
-```js
-...
-const app = express()
-app.use(express.json())
-...
-```
-
-#### Create. Для добавления записей в таблицу используется метод POST
-
-```js
-app.post('/api/cart', async function(req, res) {
-  try {
-    await sequelize.query(`
-      INSERT INTO Cart (menuItemId, quantity)
-      -- значения :menuItemId и :quantity задаются в replacements
-      VALUES (:menuItemId, :quantity)
-    `,{
-      logging: false,
-      type: QueryTypes.INSERT,
-      // объект replacements должен содержать значения для замены
-      replacements: {
-        menuItemId: req.body.menuItemId,
-        quantity: req.body.quantity
-      }
-    })
-    // ничего не возвращаем, но код ответа меняем на 201 (Created)
-    // можно и не менять, по-умолчанию возвращает 200 (OK)
-    res.status(201)
-  } catch (error) {
-    console.warn('ошибка при добавлении блюда в корзину:', error.message)
-    // при ошибке возвращаем код ошибки 500 и текст ошибки
-    res.status(500).send(error.message)
-  } finally {
-    res.end()
-  }
-})
-```
-
-#### Read. Чтение
-
-Используется метод get. Пример приводить не буду, он аналогичен запросу списка блюд
-
-#### Update. Редактирование корзины
-
-Для редактирования в REST используются методы PUT или PATCH. PUT - полная перезапись (замещение объекта), PATCH - частичная перезапись (изменение части объекта)
-
-Мы будем только менять количество, поэтому используем PATCH
-
-```js
-// в пути можно описать параметр (поставив знак ":")
-app.patch('/api/cart/:id', async (req, res) => {
-  try {
-    await sequelize.query(`
-      UPDATE Cart 
-      SET quantity=:quantity
-      WHERE id=:id
-    `,{
-      logging: false,
-      replacements: {
-        // используем параметр из пути
-        id: req.params.id,
-        quantity: req.body.quantity
-      }
-    })
-  } catch (error) {
-    console.warn('ошибка при редактировании корзины:', error.message)
-    res.status(500).send(error.message)
-  } finally {
-    res.end()
-  }
-})
-```
-
-#### Delete. Удаление блюда из корзины
-
-Тут ничего нового
-
-```js
-app.delete('/api/cart/:id', async (req, res) => {
-  try {
-    await sequelize.query(`
-      DELETE 
-      FROM Cart
-      WHERE id=:id
-    `,{
-      logging: false,
-      replacements: {
-        id: req.params.id
-      }
-    })
-  } catch (error) {
-    console.warn('ошибка при удалении блюда из корзины:', error.message)
-    res.status(500).send(error.message)
-  } finally {
-    res.end()
-  }
-}) 
-```
-
----
-
-## JWT-авторизация
-
->JSON Web Token (JWT) — это JSON объект, который определен в открытом стандарте RFC 7519. Он считается одним из безопасных способов передачи информации между двумя участниками. Для его создания необходимо определить заголовок (header) с общей информацией по токену, полезные данные (payload), такие как id пользователя, его роль и т.д. и подписи (signature).
-
-### Структура JWT
-
-JWT состоит из трех частей: заголовок `header`, полезные данные `payload` и подпись `signature`. Давайте пройдемся по каждой из них.
-
-
-#### Шаг 1. Создаем HEADER
-
-Хедер JWT содержит информацию о том, как должна вычисляться JWT подпись. Хедер — это тоже JSON объект, который выглядит следующим образом:
-
-```js
-header = { "alg": "HS256", "typ": "JWT"}
+```json
+{ "alg": "HS256", "typ": "JWT"}
 ```
 ```
 
 
 Поле `typ` не говорит нам ничего нового, только то, что это _JSON Web Token_. Интереснее здесь будет поле `alg`, которое определяет алгоритм хеширования. Он будет использоваться при создании подписи. HS256 — не что иное, как HMAC-SHA256, для его вычисления нужен лишь один секретный ключ (более подробно об этом в шаге 3). Еще может использоваться другой алгоритм RS256 — в отличие от предыдущего, он является ассиметричным и создает два ключа: публичный и приватный. С помощью приватного ключа создается подпись, а с помощью публичного только лишь проверяется подлинность подписи, поэтому нам не нужно беспокоиться о его безопасности.
 Поле `typ` не говорит нам ничего нового, только то, что это _JSON Web Token_. Интереснее здесь будет поле `alg`, которое определяет алгоритм хеширования. Он будет использоваться при создании подписи. HS256 — не что иное, как HMAC-SHA256, для его вычисления нужен лишь один секретный ключ (более подробно об этом в шаге 3). Еще может использоваться другой алгоритм RS256 — в отличие от предыдущего, он является ассиметричным и создает два ключа: публичный и приватный. С помощью приватного ключа создается подпись, а с помощью публичного только лишь проверяется подлинность подписи, поэтому нам не нужно беспокоиться о его безопасности.
 
 
-
-#### Шаг 2. Создаем PAYLOAD
+### Шаг 2. Создаем PAYLOAD
 
 
 `Payload` — это полезные данные, которые хранятся внутри **JWT**. Эти данные также называют JWT-claims (заявки). В примере, который рассматриваем мы, сервер аутентификации создает **JWT** с информацией об `id` пользователя — `userId`.
 `Payload` — это полезные данные, которые хранятся внутри **JWT**. Эти данные также называют JWT-claims (заявки). В примере, который рассматриваем мы, сервер аутентификации создает **JWT** с информацией об `id` пользователя — `userId`.
 
 
-```js
-payload = { "userId": "b08f86af-35da-48f2-8fab-cef3904660bd" }
+```json
+{ "userId": "b08f86af-35da-48f2-8fab-cef3904660bd" }
 ```
 ```
 
 
 Мы положили только одну заявку (claim) в `payload`. Вы можете положить столько заявок, сколько захотите. Существует список стандартных заявок для JWT payload — вот некоторые из них:
 Мы положили только одну заявку (claim) в `payload`. Вы можете положить столько заявок, сколько захотите. Существует список стандартных заявок для JWT payload — вот некоторые из них:
@@ -293,7 +44,7 @@ payload = { "userId": "b08f86af-35da-48f2-8fab-cef3904660bd" }
 
 
 Эти поля могут быть полезными при создании **JWT**, но они не являются обязательными. Если хотите знать весь список доступных полей для **JWT**, можете заглянуть в **Wiki**. Но стоит помнить, что чем больше передается информации, тем больший получится в итоге сам **JWT**. Обычно с этим не бывает проблем, но все-таки это может негативно сказаться на производительности и вызвать задержки во взаимодействии с сервером.
 Эти поля могут быть полезными при создании **JWT**, но они не являются обязательными. Если хотите знать весь список доступных полей для **JWT**, можете заглянуть в **Wiki**. Но стоит помнить, что чем больше передается информации, тем больший получится в итоге сам **JWT**. Обычно с этим не бывает проблем, но все-таки это может негативно сказаться на производительности и вызвать задержки во взаимодействии с сервером.
 
 
-#### Шаг 3. Создаем SIGNATURE
+### Шаг 3. Создаем SIGNATURE
 
 
 Подпись вычисляется с использованием следующего псевдо-кода:
 Подпись вычисляется с использованием следующего псевдо-кода:
 
 
@@ -311,7 +62,7 @@ const signature = HMAC-SHA256(unsignedToken, SECRET_KEY)
 // signature -xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
 // signature -xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
 ```
 ```
 
 
-#### Шаг 4. Теперь объединим все три JWT компонента вместе
+### Шаг 4. Теперь объединим все три JWT компонента вместе
 
 
 Теперь, когда у нас есть все три составляющих, мы можем создать наш **JWT**. Это довольно просто, мы соединяем все полученные элементы в строку через точку.
 Теперь, когда у нас есть все три составляющих, мы можем создать наш **JWT**. Это довольно просто, мы соединяем все полученные элементы в строку через точку.
 
 
@@ -321,18 +72,40 @@ const token = encodeBase64Url(header) + '.' + encodeBase64Url(payload) + '.' + e
 // eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
 // eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
 ```
 ```
 
 
-Вы можете попробовать создать свой собственный JWT на сайте jwt.io.
+Вы можете попробовать создать свой собственный JWT на сайте [jwt.io](jwt.io).
 
 
-Вернемся к нашему примеру. Теперь сервер аутентификации может слать пользователю JWT.
+Вернемся к нашему примеру. Теперь сервер аутентификации может слать пользователю __JWT__.
 
 
-### Как JWT защищает наши данные?
+## Как JWT защищает наши данные?
 
 
 Очень важно понимать, что использование **JWT** НЕ скрывает и не маскирует данные. Причина, почему **JWT** используются — это проверка, что отправленные данные были действительно отправлены авторизованным источником. Как было продемонстрировано выше, данные внутри **JWT** закодированы и подписаны, обратите внимание, это не одно и тоже, что зашифрованы. Цель кодирования данных — преобразование структуры. Подписанные данные позволяют получателю данных проверить аутентификацию источника данных. Таким образом закодирование и подпись данных не защищает их. С другой стороны, главная цель шифрования — это защита данных от неавторизованного доступа.
 Очень важно понимать, что использование **JWT** НЕ скрывает и не маскирует данные. Причина, почему **JWT** используются — это проверка, что отправленные данные были действительно отправлены авторизованным источником. Как было продемонстрировано выше, данные внутри **JWT** закодированы и подписаны, обратите внимание, это не одно и тоже, что зашифрованы. Цель кодирования данных — преобразование структуры. Подписанные данные позволяют получателю данных проверить аутентификацию источника данных. Таким образом закодирование и подпись данных не защищает их. С другой стороны, главная цель шифрования — это защита данных от неавторизованного доступа.
 
 
-### Простыми словами
+## Простыми словами
 
 
 1. При авторизации по логину/паролю сервер убеждается, что такой пользователь есть, формирует **JWT**-токен, содержащий полезную информацию и подпись, и возвращает этот токен клиенту (сайт, приложение)
 1. При авторизации по логину/паролю сервер убеждается, что такой пользователь есть, формирует **JWT**-токен, содержащий полезную информацию и подпись, и возвращает этот токен клиенту (сайт, приложение)
 
 
+```mermaid
+sequenceDiagram
+  actor Клиент
+  Клиент->>АПИ: Запрос авторизации по логину/паролю
+  activate АПИ
+  box purple Запрос к базе данных
+    participant DB@{ "type" : "database" }
+  end
+  АПИ->>DB: Поиск пользователя по логину
+  activate DB
+  alt Пользователь существует
+    DB->>АПИ: Информация о пользователе
+    АПИ->>АПИ: Формирование токена
+    АПИ->>Клиент: JWT-токен
+  else Пользователя нет в базе
+    DB->>АПИ: NULL
+    АПИ->>Клиент: Ошибка 401 (не авторизован)
+  end
+  deactivate DB
+  deactivate АПИ
+```
+
 1. Клиент при последующих запрсах добавляет **JWT**-токен в заголовок запроса:
 1. Клиент при последующих запрсах добавляет **JWT**-токен в заголовок запроса:
 
 
     ```
     ```
@@ -341,9 +114,24 @@ const token = encodeBase64Url(header) + '.' + encodeBase64Url(payload) + '.' + e
 
 
 1. Сервер, получая **JWT**-токен уже не лезет в таблицу пользователей, а просто проверяет подпись токена, убеждаясь что она не подделана (ключ подписи есть только у севрера). А затем может использовать полезную информацию из токена в запросах к БД. Таким образом мы экономим на каждом входящем запросе одно обращение к БД (а работа с БД самое медленное действие, вычисление подписи намного дешевле)
 1. Сервер, получая **JWT**-токен уже не лезет в таблицу пользователей, а просто проверяет подпись токена, убеждаясь что она не подделана (ключ подписи есть только у севрера). А затем может использовать полезную информацию из токена в запросах к БД. Таким образом мы экономим на каждом входящем запросе одно обращение к БД (а работа с БД самое медленное действие, вычисление подписи намного дешевле)
 
 
-### Подключаем JWT-авторизацию к нашему проекту
+```mermaid
+sequenceDiagram
+  actor Клиент
+  Клиент->>АПИ: Запрос с токеном
+  activate АПИ
+  АПИ->>АПИ: Проверка токена
+  alt Подпись валидная
+    АПИ->>АПИ: Обработка запроса
+    АПИ->>Клиент: Ответ на запрос
+  else Подпись не валидная
+    АПИ->>Клиент: Ошибка 403 (доступ запрещен)
+  end
+  deactivate АПИ
+```
+
+## Подключаем JWT-авторизацию к нашему проекту
 
 
-#### Устанавливаем необходимые пакеты:
+### Устанавливаем необходимые пакеты:
 
 
 ```
 ```
 npm i jsonwebtoken md5
 npm i jsonwebtoken md5
@@ -352,47 +140,50 @@ npm i jsonwebtoken md5
 * **jsonwebtoken** - нужен для формирования и проверки токена
 * **jsonwebtoken** - нужен для формирования и проверки токена
 * **md5** - для хеширования пароля
 * **md5** - для хеширования пароля
 
 
-#### Подключаем зависимости
+### Подключаем зависимости
 
 
 ```js
 ```js
 const md5 = require('md5')
 const md5 = require('md5')
 const jwt = require('jsonwebtoken')
 const jwt = require('jsonwebtoken')
 ```
 ```
 
 
-#### Задаём подпись
+### Задаём подпись
 
 
-Этот параметр тоже нельзя светить, поэтому считываем из переменных окружения (добавьте в `jaunch.json`)
+Этот параметр тоже нельзя "светить", поэтому считываем из переменных окружения (добавьте в `jaunch.json`)
 
 
 ```js
 ```js
 const JWT_SECRET = process.env.JWT_SECRET
 const JWT_SECRET = process.env.JWT_SECRET
 ```
 ```
 
 
-#### Реализуем метод авторизации
+### Реализуем метод авторизации
 
 
 ```js
 ```js
 /**
 /**
+ * POST /api/user/login
  * В теле запроса должен быть объект с логином и паролем:
  * В теле запроса должен быть объект с логином и паролем:
  * {
  * {
  *  "login": "ваш логин",
  *  "login": "ваш логин",
  *  "password": "пароль"
  *  "password": "пароль"
  * }
  * }
  */
  */
-app.post('/api/user/login', async (req, res) => {
+router.post('/login', async (req, res) => {
   try {
   try {
-    // const user = await sequelize.query(`
-    //   SELECT *
-    //   FROM User
-    //   WHERE login=:login
-    // `, {
-    //   // параметр plain нужен, чтобы запрос вернул не массив записей, а конкретную запись
-    //   // если записи с таким логином нет, то вернет null
-    //   plain: true,
-    //   logging: false,
-    //   type: QueryTypes.SELECT,
-    //   replacements: {
-    //     login: req.body.login
-    //   }
-    // })
+    /*
+    const user = await sequelize.query(`
+      SELECT *
+      FROM User
+      WHERE login=:login
+    `, {
+      // параметр plain нужен, чтобы запрос вернул не массив записей, а конкретную запись
+      // если записи с таким логином нет, то вернет null
+      plain: true,
+      logging: false,
+      type: QueryTypes.SELECT,
+      replacements: {
+        login: req.body.login
+      }
+    })
+    */
 
 
     // у меня нет таблицы User, поэтому я сделал заглушку
     // у меня нет таблицы User, поэтому я сделал заглушку
     // вам нужно брать данные для авторизации из БД
     // вам нужно брать данные для авторизации из БД
@@ -433,7 +224,7 @@ app.post('/api/user/login', async (req, res) => {
 })
 })
 ```
 ```
 
 
-#### Реализуем аутентификацию пользователя в запросе корзины, используя middleware
+### Реализуем аутентификацию пользователя в запросе корзины, используя middleware
 
 
 **Middleware** функция принимает на входе параметры `req`, `res`, `next`, где `req`, `res` соответственно запрос и ответ, а `next` нужно вызвать, если всё нормально и можно вызывать следующую middleware функцию (их может быть несколько)
 **Middleware** функция принимает на входе параметры `req`, `res`, `next`, где `req`, `res` соответственно запрос и ответ, а `next` нужно вызвать, если всё нормально и можно вызывать следующую middleware функцию (их может быть несколько)
 
 
@@ -469,22 +260,24 @@ const authenticateJWT = (req, res, next) => {
 
 
 ```js
 ```js
 /**
 /**
- * Вторым параметром запроса можно добавить массив middleware
+ * GET /api/cart
+ * Вторым параметром метода добавлен массив middleware
  */
  */
-app.get('/api/cart', [authenticateJWT], async (req, res) => {
+router.get('/', [authenticateJWT], async (req, res) => {
   try {
   try {
     res.json(await sequelize.query(`
     res.json(await sequelize.query(`
       SELECT *
       SELECT *
       FROM Cart
       FROM Cart
-      -- в моей БД нет пользователей
-      -- WHERE userId=:userId
+      WHERE userId=:userId
     `, {
     `, {
       logging: false,
       logging: false,
       type: QueryTypes.SELECT
       type: QueryTypes.SELECT
-      // replacements: {
-      //   userId: req.user.id
-      //               ^^^^ обратите внимание, тут используется объект user, заполненный в middleware аутентификации
-      // }
+      replacements: {
+        userId: req.user.id
+      //            ^^^^ обратите внимание, 
+      // тут используется объект user, 
+      // заполненный в middleware аутентификации
+      }
     }))
     }))
   } catch (error) {
   } catch (error) {
     console.error(error)
     console.error(error)
@@ -496,4 +289,4 @@ app.get('/api/cart', [authenticateJWT], async (req, res) => {
 
 
 ## Задание
 ## Задание
 
 
-Реализовать АПИ для своего курсового проекта по образцу. В следующем году постараюсь написать лекции по DevOps (завернём АПИ, базу и приложение в контейнеры).
+Реализовать авторизацию по JWT-токену в своем АПИ

+ 3 - 0
readme.md

@@ -456,6 +456,9 @@ tablayout
 ## Практика №2. Разработка АПИ на express.js + ORM Sequelize.
 ## Практика №2. Разработка АПИ на express.js + ORM Sequelize.
 
 
 1. [Постановка задачи. Создание сервера express.js. Подключение и настройка sequelize.](./articles/expressjs/express01.md)
 1. [Постановка задачи. Создание сервера express.js. Подключение и настройка sequelize.](./articles/expressjs/express01.md)
+1. [Подключение к БД.](./articles/expressjs/express02.md)
+1. [Использование sequelize для получения данных из БД. Создание скрипта для наполнения БД начальными данными. Добавление сущностей БД, реализация REST.](./articles/expressjs/express03.md)
+1. [JWT-авторизация.](./articles/expressjs/express04.md)
 
 
 ## Полезное
 ## Полезное