# README
AuthService
Терминология:
1)В фигурных скобках будут писаться переменные. Пример: {path}, {ip}. 2)Для ответа в виде ошибки используется связка переменных errorCode (int) и error (string). errorCode необходим для идентефикации ошибки без необходимости сравнивать строки, а error - это текст ошибки для последующего вывода/обработки.
Функциональные возможности
- Регистрация
- Handler endpoint "{ip}:{port}/register"
-
Модель api: json. Формат:
{ "login":"", "password":"" }
-
Модель ответа: json. Формат:
{ "error":"", "errorCode":0 }
- Возвращаемые ошибки: ErrUserAlreadyExists,ErrTooShortLoginOrPassword, ErrServiceInternal, ErrInvalidInput
- Вариант использования - регистрация пользователя по логину и паролю.
- Аутентификация
- Handler endpoint "{ip}:{port}/authenticate"
-
Модель api: json. Формат:
{ "login":"", "password":"" }
-
Модель ответа: json. Формат:
{ "error":"", "errorCode":0, "otpEnabled":false, "intermediateToken:"", "authInfo":{ "accessToken":"", "refreshToken":"", } }
- Возвращаемые ошибки: ErrUserNotExists,ErrTooShortLoginOrPassword, ErrServiceInternal, ErrInvalidInput
- Вариант использования - отправка логина и пароля, при корректности которых происходит либо получение токена для доступа к сервисам и токена для обновления токенов, в случае если для пользователя отключена двухфакторная аутентификация, либо отправляется otpEnabled = true и промежуточный токен, который необходим для продолжения аутентификации с помощью {ip}:{port}/authenticate/continue
- Продолжение аутентификации при использовании otp
- Handler endpoint "{ip}:{port}/authenticate/continue"
-
Модель api: json. Формат:
{ "intermediateToken":"", "otpCode":"" }
-
Модель ответа: json. Формат:
{ "error":"", "errorCode":0, "authInfo":{ "accessToken":"", "refreshToken":"", } }
- Возвращаемые ошибки: ErrInvalidOtp, ErrInvalidIntermediateToken, ErrExpiredIntermediateToken, ErrServiceInternal, ErrInvalidInput
- Вариант использования - после попытки аутентификации, пользователь отправляет промежуточный токен и код из приложения аутентификации. Если они валидны, то в ответ получает токен доступа к сервисам и токен для обновления токенов.
- Авторизация
- Handler endpoint "{ip}:{port}/authorize"
-
Модель api: json. Формат:
{ "accessToken":"", "requiredRoleId":"" }
-
Модель ответа: json. Формат:
{ "userId":"", "error":"", "errorCode":0 }
- Возвращаемые ошибки: ErrUserNotExists, ErrExpiredAccessToken, ErrInvalidAccessToken, ErrRoleHasNoAccess, ErrServiceInternal, ErrInvalidInput
- Вариант использования - отправка токена доступа полученного ранее, и необходимой для действия роли и проверка валидности токена, с отправкой uuid, если всё успешно и пустого uuid и ошибки, если токен не валиден.
- Обновление токенов
- Handler endpoint "{ip}:{port}/refresh"
-
Модель api: json. Формат:
{ "accessToken":"", "refreshToken":"", }
-
Модель ответа: json. Формат:
{ "accessToken":"", "refreshToken":"", "error":"", "errorCode":0 }
- Возвращаемые ошибки: ErrExpiredRefreshToken, ErrInvalidRefreshToken, ErrInvalidAccessToken, ErrUserNotExists, ErrServiceInternal, ErrInvalidInput
- Вариант использования - если токен доступа истек, но в остальных аспектах валиден, то клиент может отправить запрос и получить новые токены. Токены для обновления ходят парой, т.к. refresh токен привязан к access токену. В ответ отправляются новые токены.
- Включение otp для пользователя
- Handler endpoint "{ip}:{port}/otp/enable"
- Модель api: json. Формат: тело запроса не читается. Однако необходимо наличие заголовка запроса Authorization в формате: "Bearer {accessToken}"
-
Модель ответа: json. Формат:
{ "error":"", "errorCode":0, "otpKey":"", "otpUrl":"", }
- Возвращаемые ошибки: ErrOtpAlreadyEnabled, ErrUserNotExists, ErrServiceInternal, ErrWrongAuthorizeMethod
- Вариант использования - если аутентифицированный пользователь захотел включить двухфакторную аутентификацию для своего аккаунта, то он отправляет пустой запрос со своим токеном доступа в заголовке auhtorize. В ответ он получает ключ otp, для ручного ввода в аутентификатор и ссылку, которую можно преобразовать в qr код для быстрого добавления кода в аутентификатор
- Выключение otp для пользователя
- Handler endpoint "{ip}:{port}/otp/disable"
- Модель api: json. Формат: тело запроса не читается. Однако необходимо наличие заголовка запроса Authorization в формате: "Bearer {accessToken}"
-
Модель ответа: json. Формат:
{ "error":"", "errorCode":0, }
- Возвращаемые ошибки: ErrUserNotExists, ErrOtpAlreadyDisabled, ErrServiceIntenal, ErrWrongAuthorizeMethod
- Вариант использования - если аутентифицированный пользователь захотел выключить двухфакторную аутентификацию для своего аккаунта, то он отправляет пустой запрос со своим токеном доступа в заголовке auhtorize. В случае успеха ему отправляется error="" и errorCode=0
База данных:
Если таблиц нет, то они создаются запросом (файлы находится в папке с миграциями и имеет название user_storage_up.sql и roles_storage_up.sql):
create table if not exists users (
UserId text PRIMARY KEY,
Login text UNIQUE,
Password text,
OtpEnabled boolean,
OtpKey text,
RoleId bigserial REFERENCES roles (RoleId)
);
create table if not exists roles (
RoleId bigserial PRIMARY KEY,
RoleName text UNIQUE
);
Таблица Users:
UserId - это UUID, который используется как внешний ключ, который возвращается при авторизации. С помощью него сервисы, которым необходимо авторизовать пользователя привязывают свои данные к модели пользователя. Login и Password - это поля для регистрации и аутентификации. RoleId - внешний ключ для связи с таблицой ролей. OtpEnabled - логическая переменная, говорящая включена ли двухфакторная аутентификация для пользователя. OtpKey - ключ для генерации кода двухфакторной аутентификации для сравнения на стороне сервера.
Таблица Roles
RoleId - число идентефикатор роли. RoleName - имя роли (для понимания людьми).
На данный момент используется база данных postgresql, для нее написан provider, реализующий интерфейс UserStorage и RolesStorage.
Архитектура проекта
Проект выполнен с использованием луковичной архитектуры. Самая высокоуровневая структура - это LogicProvider. Он является основной логикой проекта. Зависит от шести интерфейсов: UserStorage - хранилище пользователей, предоставляющее методы CRUD; RolesStorage - хранилище ролей, предоставляющее методы CRUD; PasswordHasher - хэшер паролей, предоставляющий методы хэширования и сравнения паролей; UUIDProvider - интерфейс предоставляющий доступ к генерации UUID; TokensProvider - интерфейс предоставляющий доступ к генерации и валидации access и refresh токенов; OtpGenerator - интерфейс предоставляющий доступ к генерации otp (используется для двухфакторной аутентификации).
Уровнем ниже идет CasesProvider. Он является прослойкой, которая отвечает за проверку полей и отклонение всех запросов по истечению контекста. Зависит от LogicProvider, интерфейса UseCasesConfig - интерфейсу предоставляющим методы чтения параметров для проверки данных ввода и от интерфейса логгирования предоставляющий доступ к логгированию в несколько источников.
Уровнем ниже идёт API, который использует CasesProvider. Задача слоя API десериализовать данные в аргументы для методов CasesProvider, а также сериализовать данные ответа. На данный момент реализованы методы API для фреймворка Fiber.
Функция main инициализирует все зависимости, и запускает API.
Развертывание проекта
Для развертывания web сервера используется Docker. При развертывании необходимо прокинуть в контейнер порт. Внешний порт может быть любым но внутренний только 8080. Для развертывания базы данных можно также использовать Docker, для чего написан конфиг в Docker-compose.
Тестирование
На данный момент написаны тесты для модуля CasesProvider без использования otp. Планируется написание интеграционных тестов.
Структура конфига
{
"organizationName":"",
"apiPort":"8080",
"minLoginLen":5,
"minPasswordLen":5,
"accessTokenKey":"",
"accessTokenLifeTime":60,
"refreshTokenKey":"",
"refreshTokenLifeTime":24,
"accessPartLen":5,
"intermediateTokenKey":"",
"intermediateTokenLifetime":5,
"postgresConfig":{
"login":"some_user",
"password":"some_password",
"ip":"db",
"port":"5432"
},
"migrationsPath":"./migrations/postgres",
"roles":[{
"roleId":1,
"roleName":"root"
},{
"roleId":2,
"roleName":"user"
}],
"defaultRoleId":2
}
Элементы конфига можно разделить на изменяемые и привязанные к развертыванию Docker контейнера. Изменяемые:
- organizationName - переменная, которая используется при генерации ссылки для добавления otp в аутентификатор (отображается в аутентификаторе)
- minLoginLen - минимальная длина логина проверяемая во всех методах запрашивающих логин.
- minPasswordLen - минимальная длина пароля проверяемая во всех методах запрашивающих пароль.
- accessTokenKey - ключ, использующийся для шифрования и валидации access токена.
- accessTokenLifeTime - время жизни (в минутах) access токена.
- refreshTokenKey - ключ, использующийся для шифрования и валидации refresh токена.
- refreshTokenLifeTime - время жизни (в часах) refresh токена.
- accessPartLen - длина части токена используемая для связывания access и refresh токена
- intermediateTokenKey - ключ, использующийся для шифрования и валидации intermediate токена
- intermediateTokenLifeTime - время жизни (в минутах) intermediate токена
- postgresConfig - конфиг для взаимодействия с postgres. Этого поля может не быть в случае использования другой базы данных (однако пока что не написаны provider для других баз данных).
- login - логин пользователя postgresql.
- password - пароль пользователя postgresql.
- ip - домен/ip сервера postgresql.
- port - порт сервера postgresql.
- roles - массив ролей, которые будут действовать при работе сервера. По умолчанию добавлены 2 роли root и user.
- defaultRoleId - роль, которая выдается по умолчанию для регистрирующегося пользователя.
- apiPort - порт сервера, внутри Docker контейнера. В описании образа Dockerfile открывается через EXPOSE 8080
- migrationsPath - путь внутри Docker контейнера с необходимыми файлами миграций. Копируется из папки проекта.
Пользовательские ошибки и их коды
- ErrServiceInternal - ошибка, возвращаемая при неожиданной ошибке в работе сервера. Код - 1.
- ErrExpiredAccessToken - ошибка, возвращаемая при истечении срока действия токена доступа к сервисам. Код - 101
- ErrExpiredRefreshToken - ошибка, возвращаемая при истечении срока действия токена для обновления токенов. Код - 102
- ErrExpiredIntermediateToken - ошибка, возвращаемая при истечении срока действия токена для продолжения аутентификации с помощью otp. Код - 103
- ErrInvalidPassword - внутренняя ошибка сигнализирующая при вводе неправильного пароля. Не возвращается в api, т.к. преобразуется в ErrInvalidLoginOrPassword. Код - 104
- ErrInvalidAccessToken - ошибка, возвращаемая при невалидности токена доступа к сервисам. Код - 105
- ErrInvalidRefreshToken - ошибка, возвращаемая при невалидности токена для обновления токенов. Код - 106
- ErrInvalidIntermediateToken - ошибка возвращаемая при невалидности токена для продолжения аутентификации с помощью otp. Код - 107
- ErrUserAlreadyExists - ошибка возвращаемая, если запрошенный пользователь уже зарегистрирован в системе. Код - 108
- ErrUserNotExists - ошибка возвращаемая, если запрошенный пользователь не зарегистрирован. Код - 109
- ErrInvalidOtp - ошибка возвращаемая, если пришедший otp код не валиден в текущий промежуток времени. Код - 110
- ErrRoleHasNoAccess - ошибка возвращаемая, если пользователь не имеет прав для выполнения операции. Код - 111
- ErrRoleAlreadyExists - внутренняя ошибка сигнализирующая о том, что роль уже зарегистрирована в системе. Не возвращается в api. Код - 112
- ErrRoleNotExists - ошибка возвращаемая, если запрошенная роль не зарегистрирована в системе. Код - 113
- ErrOtpAlreadyEnabled - ошибка возвращаемая, если для запрошенного пользователя уже включена двухфакторная аутентификация. Код - 114
- ErrOtpAlreadyDisabled - ошибка возвращаемая, если для запрошенного пользователя уже выключена двухфакторная аутентификация
- ErrInvalidLoginOrPassword - ошибка возвращаемая, если логин или пароль введенные пользователем при аутентификации не правильные. Код - 201
- ErrTooShortLoginOrPassword - ошибка возвращаемая, если введенные логин или пароль пользователя слишком короктие. Код - 202
- ErrInvalidInput - ошибка возвращаемая, когда не получилось прочитать ввод пользователя. Код - 301
- ErrWrongAuthorizeMethod - ошибка возвращаемая, когда метод авторизации не соответствует выбранному системой (В данном случае заголовок Authorization не существует или не соответствует формату "Bearer {accessToken}"). Код - 302