Ролевая модель и аудит #6
Notifications
Due Date
No due date set.
Blocks
#2 Сделать доработки запрошенные фронтом
EventHub/EventHubBack
Reference: EventHub/EventHubBack#6
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Автосоздание суперадмина при первой инициализации базы
При старте приложения проверять, есть ли в таблице
usersхотя бы одна запись. Если пользователей нет — автоматически создавать учётную запись с рольюsuperadmin, используя параметры из.env:ADMIN_EMAILADMIN_PASSWORDADMIN_USERNAME(по умолчаниюadmin).Никогда не перезаписывать существующего пользователя.
Добавить миграцию для поля
roleв таблицеusersСейчас роль хранится как
<<"admin">>. Нужно зарезервировать значенияsuperadmin,moderator,supportи обновить существующих пользователей.Реализовать middleware проверки ролей
В каждом админском обработчике проверять JWT и извлекать роль. Возвращать
403при недостаточности прав (например,supportне может менять роли).Эндпоинт
GET /v1/admin/meВозвращает
{ id, email, role, permissions }для текущего администратора. Нужен для динамического скрытия элементов в UI.Управление ролями (только для
superadmin)GET /v1/admin/admins– список всех админов с ролями.PUT /v1/admin/admins/:id– изменение роли.POST /v1/admin/admins– приглашение нового админа (с отправкой email).Аудит действий администраторов
admin_audit(admin_id, email, role, action, entity_type, entity_id, timestamp, ip, reason).GET /v1/admin/auditс фильтрами (по дате, админу, действию) – доступен толькоsuperadmin.https://git.sabilin.com/EventHub/EventHubBack/raw/branch/master/src/eventhub_app.erl
🧩 Этап 0: Вынесение администраторов в отдельную таблицу
Цели:
Изолировать учётные записи администраторов от обычных пользователей.
Создать отдельную таблицу admin_session для административных сессий (refresh-токены).
Ограничить возможные роли обычных пользователей до user и bot.
Обеспечить обратную совместимость с существующим API регистрации и входа.
Локализация: include/records.hrl, infra_mnesia.erl, core_user.erl, core_admin.erl, eventhub_auth.erl, handler_login.erl, admin_handler_login.erl, handler_register.erl.
🧩 Этап 1: Обновление records.hrl
Добавляем две новые записи и изменяем поле role в #user{}.
erlang
% Вместо старого #user{role :: atom()}
-record(user, {
id :: binary(),
email :: binary(),
password_hash :: binary(),
role :: user | bot, % теперь только две роли
status :: active | blocked | deleted,
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
% Новая запись для администраторов
-record(admin, {
id :: binary(),
email :: binary(),
password_hash :: binary(),
role :: superadmin | moderator | support,
status :: active | blocked,
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
% Новая запись для сессий администраторов
-record(admin_session, {
token :: binary(), % refresh-токен
admin_id :: binary(),
expires_at :: calendar:datetime(),
type :: refresh
}).
🧩 Этап 2: Миграция данных при старте приложения
В infra_mnesia:init_tables/0:
Создаём таблицы admin и admin_session (как ram_copies + disc_copies).
Добавляем функцию миграции:
Если таблица users существует и в ней есть записи с role = admin (или superadmin, moderator, support), то переносим их в таблицу admin, а из users удаляем.
У обычных пользователей, у которых роль была admin (ошибочно), заменяем на user.
Логируем количество перенесённых записей.
Код миграции (псевдокод):
erlang
migrate_admins() ->
Users = mnesia:dirty_match_object(#user{_ = '_'}),
lists:foreach(fun(U) ->
case lists:member(U#user.role, [admin, superadmin, moderator, support]) of
true ->
Admin = #admin{...}, % копируем поля
mnesia:dirty_write(Admin),
mnesia:dirty_delete({user, U#user.id});
false ->
ok
end
end, Users).
🧩 Этап 3: Создание модуля core_admin.erl
Функции, аналогичные core_user, но работающие с таблицей admin:
erlang
-module(core_admin).
-export([create/3, get_by_email/1, get_by_id/1, list_all/0,
update_role/2, block/1, unblock/1]).
Реализация повторяет core_user, но использует #admin{}.
🧩 Этап 4: Изменение core_user.erl
Удаляем функции, связанные с админами (если они были, например, list_admins).
В create/3 принимаем только роли user или bot.
Функция get_by_email/1 остаётся без изменений — она нужна для пользовательского входа.
🧩 Этап 5: Адаптация аутентификации
Модуль eventhub_auth.erl:
authenticate_user_request/3 продолжает работать с core_user и logic_auth.
authenticate_admin_request/3 теперь вызывает core_admin:get_by_email(Email) вместо core_user:get_by_email.
generate_admin_token/2 использует AdminId и Role из #admin{}.
Модуль logic_auth.erl:
authenticate_user/2 теперь ищет пользователя через core_user:get_by_email, а если не найден — через core_admin:get_by_email (для обратной совместимости, пока все админы не перенесены). После полной миграции можно будет убрать этот запасной вариант.
🧩 Этап 6: Обновление обработчиков
handler_register.erl — разрешаем регистрацию только с ролями user и bot.
admin_handler_login.erl — теперь вызывает eventhub_auth:authenticate_admin_request, которая ходит в core_admin.
Все административные обработчики, которые проверяют is_admin/1, теперь должны использовать core_admin:get_by_id/1 вместо core_user. Выносим функцию is_admin/1 в admin_utils.erl, которая проверяет наличие записи в admin.
🧩 Этап 7: Обработка refresh-токенов
Для пользователей refresh-токены хранятся в таблице session (уже реализовано).
Для администраторов создаём отдельную таблицу admin_session и модуль core_admin_session.erl с функциями create/2, validate/1, delete/1.
handler_refresh.erl остаётся общим — он определяет, к кому относится токен (пользователь или админ), проверяя наличие в соответствующей таблице.
🧩 Этап 8: Тестирование
Обновляем все юнит-тесты, которые используют #user{role = admin} — заменяем на моки core_admin.
Добавляем новые тесты для core_admin, admin_session, миграции.
Интеграционные тесты (api_SUITE) должны продолжать работать без изменений, так как они используют HTTP-клиент.
Проверка: make eunit && rebar3 ct — все 526+ тестов должны остаться зелёными.