Модуль авторизации
Общие сведения.
Репозиторий содержит 4 пакета:
-
@libs/security - библиотека для работы с jwt и csrf токенами.
-
@skeleton/common - пакет для общего функционала, используемого в других skeleton-модулях. (планируется в будущем вынести в отдельный репозиторий)
-
@skeleton/auth - framework agnostic модуль авторизации (подключается к express, как middleware).
-
@skeleton/auth-nest - модуль для nestjs, оборачивающий функционал @skeleton/auth.
demo-auth и demo-auth-nest - тестовые с подключенными модулями авторизации (на чистом экспресс и на nestjs соотвественно)
Порядок развёртывания репозитория.
- Для комфортной работы с репозиторием установите глобально пакеты @nestjs/cli и ts-node.
- Склонируйте репозиторий.
- Из корня репозитория выполняем последовательно:
npm install
npm run bootsrap #поздаёт симлинки между пакетами, т. к. они зависят друга)
npm run build
-
В папках demo-auth и demo-auth-nest создайте копии .example.env c названием .env
-
В случае, если у вас локально установлен REDIS в файлах .env установите параметры подключения (SESS_REDIS_*) к нему. Если не установлен - два варианта: а) поставьте :-) б) разверните редис в контейнере (см. скрипты в sess_redis);
-
Для проверки корректности развёртывания выполните:
npm run test # из корня проекта.
Если все тесты пройдены.. отлично )
- Для запуска сервисов с модулем авторизации, требуется поднятый сервер аутентификации (тесты поднимают его автоматически). Чтобы просто запустить сервис для отладки, можно поднять моковый сервер аутентификации:
npm run tools:mock-server
# Сервис ищёт файл api-mock.config.js, в котором указано, что и в каких случаях надо возвращать.
Публикация пакетов.
Подготовка.
Для работы с пакетами @skeleton/* и @libs/* (как для скачивания, так и для публикации) необходимо настроить npm:
npm config set @skeleton:registry http://host:port
npm config set @libs:registry http://host:port
npm login --registry http://host:port
На сегодняшний день в качестве registry используется: http://nexus.infra.medpoint24.lo/repository/medpoint24_npm/
Публикация.
Изначально для публикации пакетов использовалась lerna (команда npm run pub из корня репозитория). Но лерна при публикации делает некоторые не совсем очевидные вещи (добавляет теги, делает git push и т. п.), что не всегда удобно.
Поэтому на данный момент пакеты публикуются при помощи
npm publish # из папки пакета
Стремимся к CI. То есть публикация пакетов будет происходить при пуше в мастер.
API
Оба demo-сервиса имеют идентичное api:
GET /user (требуется разрешение: 'VIEW_USER')
POST /user (требуется разрешение: 'CREATE_USER')
DELETE /user (требуются разрешения: 'DELETE_USER' И 'VIEW_USER')
POST /drop/db (требуются разрешения: 'ADMIN' ИЛИ 'SUPERADMIN')
GET /ignored (разрешения не требуется, как и авторизация вообще)
Все методы не требует параметров и при успешной авторизации возвращают 200 true.
Описание системы.
Авторизация пользователя проходит в два этапа:
- Валидация jwt и csrf токенов (на каждый запрос).
- Проверка прав доступа (права устанавливаются на каждый роут отдельно).
Порядок действий следующий:
- Для получения доступа клиент должен в заголовке Authorization приложить jwt-токен, полученный у сервера аутентификации. Authorization: Bearer df3gf....
# Для создания тестового токена можно воспользоваться утилитой:
npm run tools:jwt
# Выведет валидный токен в консоль.
-
Модуль авторизации хэширует токен и по хэшу пытается найти сохранённые данные в REDIS. Если на сервис уже приходили с данным токеном, расшиврованные данные из него, а также права пользователя уже хранятся в REDIS. Если нет - запускается полная процедура (см. ниже)
-
В случае, если сессия в REDIS не найдена, jwt-токен валидируется и расшифровывается. Затем отправляется запрос на сервер аутентификации для получения прав. Права (permissions) пользователя представляют из себя массив строк вида: ['CREATE_USER', 'UPDATE_USER']. Полученные права используются для получения доступа к конкретному роуту.
ВАЖНО! При успешной авторизации, в каждом ответе модуль прикладывает csrf-токен (в заголовке X-Csrf-Token). Его необходимо приложить в таком же заголовке к следующему запросу. ПЕРЕГЕНЕРИРУЕТСЯ ПРИ КАЖДОМ ЗАПРОСЕ
-
Если сессия в REDIS найдена, проверяется не истёк ли срок действия токена, а также валидируется csrf-токен. Если всё ОК, пропускаем дальше на этап проверки прав доступа.
-
Права доступа устанавливаются на каждый роут отдельно. Если указывается несколько, то по умолчанию будет проверяться наличие ВСЕХ (это можно изменить дополнительным параметром) - см. demo-сервисы.
-
В случае неудачной попытки доступа клиенту возвращаются код ошибки.
Дополнительные параметры
Настройки модуля авторизации берутся из .env файлов. За что отвечают конкретные значения - написано в комментариях.
Следуюет отметить следующии возможности:
- Для печати в консоль всех логов модуля авторизации установите LOG_LEVEL=verbose
- Чтобы клиенту кроме кода ошибки возвращалось также описание, установите VERBOSE_ERRORS=true
- Для роутов перечисленных через запятую в параметре IGNORE_ROUTES авторизация будет полностью пропущена. ВАЖНО! Если указать '/public', будут открыты все роуты, содержащие данную подстроку.
- Авторизация может быть полностью отключена (в тестовых целях) путём установки параметра DISABLE_AUTH=true В этом случае, каждый запрос получит разрешения перечисленные через запятую в параметре TEST_PERMISSIONS
- Все запросы с ошибками 401 и 403 будут возвращаться клиенту с задержкой указанной в параметре AUTH_ERRORS_DELAY (в миллисекундах)
Сценарии:
Incorrect Jwt.
STATE: any
✓ should return 401 if JWT is missing.
✓ should return 401 if JWT signature is incorrect.
1) should return 401 if JWT is expired
✓ should return 401 if JWT payload can`t be parsed (109ms)
Correct Jwt.
STATE: no session.
X-Csrf-Token header exists.
✓ should return 401 if Csrf token EXISTS. (125ms)
X-Csrf-Token header NOT exists.
State: auth-server UNREACHABLE
✓ should return 500
STATE: auth-server returns 401
✓ should return 401
STATE: auth-server provides SUFFICIENT permissions
✓ should return 200
STATE: auth-server provides INSUFFICIENT permissions
✓ should return 403 (107ms)
STATE: session exists.
Csrf valid.
STATE: SUFFICIENT permissions
✓ should return 200
STATE: INSUFFICIENT permissions
✓ should return 403 (224ms)
Csrf invalid.
✓ should return 401 if Csrf is MISSING (132ms)
✓ should return 401 if Csrf is WRONG (133ms)
В табличном виде: XWIKI
TODO:
- [x] Валидация схемы сonfig'a (чтобы пустые значения нельзя было ввести в обязательные поля)
- [x] Интеграционные тесты для demo-приложений
- [x] Выделить mock-server в отдельную тулзу @tools/mock-server? (Возможно, завести отдельный репозиторий для тулзов)
- [x] Инструкции в README файлах.
- [x] Обсудить с Игорем и утвердить (при необходимости - исправить) формат ответов в случае различных ошибок.
- [x] В случае обращения с протухшим токеном или обнаружения протухшей сессии возвращать 444.
- [x] Проверить, заходит ли программа вообще в логику удаления сессии, по видимому нет - нужно исправить. Или вообще удалить - обсудить с Рустом.
- [x] Сделать тесты для demo-auth-nest
- [x] Решить, что делает nestjs модуль при ошибках авторизации - выкидывает HttpException, как сейчас или что-то другое?
- [ ] Авторизация при соединении через Web Socket?
- [ ] Документация кода (кроме demo-) - apiDoc
- [x] Ревью работы с исключениями (свежий взгляд).
- [x] Протестировать подключение в отсутствии Redis и с отключенной авторизацией. (скорее всего работать не будет) - ИСПРАВЛЕНО
- [ ] Добавить валидацию Identity и IJwt (class-validator)