Аудитория: суперадминистратор, интегратор. Закрывает вопрос: «как завести новую организацию (арендатора) и операторов для входа — без пересборки образа?». Связано: 21 — Вход и роли, 09-tenant-onboarding (seed-путь).
| Путь | Когда | Как |
|---|---|---|
| Seed (в образе) | штатные/эталонные арендаторы продукта (Артек, РГГУ) | запись в backend/app/seed_data.py + init_db.py, пересборка (см. 09) |
| Runtime (в БД) | боевой/ad-hoc арендатор у заказчика, без пересборки | админ-вставка в БД портала + загрузка ПО через коннектор (ст. 20) |
Ниже — runtime-путь (им заведён демо-арендатор «Моё рабочее место»). Ядро/экраны/контракт не меняются — изоляция (RLS tenant_isolation) работает «из коробки».
Записи делаются под админ-подключением к БД портала (ADMIN_DATABASE_URL, суперпользователь — обходит RLS). Хеш пароля — Argon2id черезapp.password.hash_password(Стрибог тут НЕ применяется).
INSERT INTO sam.tenants (tenant_id, name, kind, domain, branding)
VALUES ('acme', 'ООО «Акме»', 'customer', 'acme.local',
'{"shortName":"Акме","accent":"#1F6C9F","logoSvg":null}');
-- KPI-факты обязательны (NOT NULL); реальные суммы или 0 на старте:
INSERT INTO sam.tenant_facts (tenant_id, direct_savings_per_year, prevented_costs)
VALUES ('acme', 0, 0);
-- Хотя бы один отдел:
INSERT INTO sam.departments (dept_id, tenant_id, dept_name, dept_code, vlan_segment)
VALUES (1, 'acme', 'Рабочие места', 'ARM', 'vlan-arm');kind: `customer` (вход по паролю) или demo_public (анонимный read-only, без операторов).
Операторы заводятся через API (роль superadmin, только в пределах своего арендатора — кросс-арендаторные операции запрещены, 403). Пароль хешируется на сервере (Argon2id) — сырой пароль наружу не сохраняется. Эндпоинты (см. ст. 21 о входе/CSRF):
| Метод | Путь | Назначение |
|---|---|---|
GET | /api/{tenant}/users | список операторов (без passwordHash) |
POST | /api/{tenant}/users | создать оператора (409 при дубле логина) |
POST | /api/{tenant}/users/{login}/password | сбросить пароль |
POST | /api/{tenant}/users/{login}/active | включить/отключить вход |
# создать superadmin@acme (нужен токен superadmin этого арендатора)
curl -sX POST https://<host>/api/acme/users \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"login":"superadmin@acme","displayName":"Администратор","role":"superadmin","password":"СИЛЬНЫЙ-ПАРОЛЬ"}'- Логин — формат `роль@арендатор` (
it_admin@acme). - Роли:
viewer,it_manager,it_admin,dpo,superadmin(+ служебнаяcollectorдля форвардера).
Минимальная длина пароля — 8 символов; недопустимая роль/короткий пароль → 400.
- Заведите минимум
superadmin@acme(полный доступ) и нужные рабочие роли.
Курица-и-яйцо: первогоsuperadmin@acmeсоздать через API нельзя (нет токена этого арендатора). Заведите его одним из путей: скриптом ниже (контурapi/enclave), либо разовой SQL-вставкой подADMIN_DATABASE_URL. Дальше — управляйте операторами через API.
# add_user.py — bootstrap первого superadmin: docker compose ... exec -T -w /app api python add_user.py
import os, psycopg
from app.password import hash_password
TENANT, LOGIN, ROLE, PASSWORD = "acme", "superadmin@acme", "superadmin", "СИЛЬНЫЙ-ПАРОЛЬ"
pwh = hash_password(PASSWORD)
with psycopg.connect(os.environ["ADMIN_DATABASE_URL"]) as c, c.cursor() as cur:
cur.execute(
"INSERT INTO sam.app_users (user_login, tenant_id, display_name, role, password_hash) "
"VALUES (%s,%s,%s,%s,%s) "
"ON CONFLICT (tenant_id, user_login) DO UPDATE SET password_hash=EXCLUDED.password_hash",
(LOGIN, TENANT, "Администратор", ROLE, pwh))# сброс пароля
curl -sX POST https://<host>/api/acme/users/viewer@acme/password \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"password":"НОВЫЙ-ПАРОЛЬ"}'
# отключить вход (is_active=false)
curl -sX POST https://<host>/api/acme/users/viewer@acme/active \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"active":false}'is_active=false отключает вход (логин-роут это проверяет). Чтобы немедленно оборвать ВСЕ уже активные сессии (всех арендаторов) — сменить JWT_SECRET (см. ст. 21).
Инвентарь/лицензии — не вручную, а через коннектор (KSC/1С/syslog, ст. 20) или разовый импорт (как _gen_pc_import.py для демо). После наполнения: REFRESH MATERIALIZED VIEW sam.mv_license_utilization;
- Арендатор в списке:
GET /api/tenantsсодержитacme(в режимеDEMO_MODE=1) — или вводится
вручную на входе (DEMO_MODE=0, см. 21).
- Вход
superadmin@acmeпроходит, попадаете в контурacme. - Изоляция: по токену
acmeданные других арендаторов недоступны (RLS). GET /api/acme/dashboard-kpiотвечает (нули — норма до наполнения).
- `tenant_facts` обязателен (NOT NULL) — без него дашборд части арендатора упадёт.
- `demo_public` не имеет операторов и read-only — для входа по паролю нужен
customer. - Пароли только хешем (Argon2id) — не храните и не логируйте сырой пароль.
Связанные: 21 — Вход и роли · 20 — Коннекторы · 07 — RBAC.