АРХ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: Authentik profile projection
This commit is contained in:
parent
1d37acdb83
commit
cfccdf6ddb
|
|
@ -7,30 +7,35 @@
|
||||||
Целевой пользовательский сценарий:
|
Целевой пользовательский сценарий:
|
||||||
|
|
||||||
1. Пользователь открывает Launcher.
|
1. Пользователь открывает Launcher.
|
||||||
2. Если сессии нет, его отправляет в Authentik.
|
2. Если сессии нет, его отправляет в платформенный login flow без публичного брендинга Authentik.
|
||||||
3. После логина Launcher получает identity и app access.
|
3. После логина Launcher получает identity, профиль и access projection.
|
||||||
4. Launcher показывает только доступные приложения.
|
4. Launcher показывает каталог приложений и состояние доступа.
|
||||||
5. Переход в Task Manager не требует повторного логина.
|
5. Переход в Task Manager не требует повторного логина.
|
||||||
6. Прямой URL Task Manager тоже защищен.
|
6. Прямой URL Task Manager тоже защищен.
|
||||||
|
|
||||||
## Роли компонентов
|
## Роли компонентов
|
||||||
|
|
||||||
`Authentik` является единым Identity Provider:
|
`Authentik` является внутренним Identity Provider / SSO layer:
|
||||||
|
|
||||||
- логин;
|
- логин;
|
||||||
- пароль;
|
- пароль;
|
||||||
- активность пользователя;
|
- активность identity;
|
||||||
- группы;
|
- технические SSO-группы/entitlements;
|
||||||
- app access;
|
- OIDC app access projection;
|
||||||
- OIDC claims;
|
- OIDC claims;
|
||||||
- будущие invite/enrollment/MFA flows.
|
- будущие enrollment/MFA flows, вызываемые из Launcher.
|
||||||
|
|
||||||
|
Authentik не является пользовательским control plane и не должен быть видим обычным пользователям/админам в штатном UI.
|
||||||
|
|
||||||
|
Hosted Authentik login используется только как временный dev flow. Production-вход должен быть NODE.DC-branded login facade или полностью приведенный к NODE.DC UX Authentik flow без публичного упоминания Authentik.
|
||||||
|
|
||||||
`Launcher` является control plane:
|
`Launcher` является control plane:
|
||||||
|
|
||||||
- входная точка пользователя;
|
- входная точка пользователя;
|
||||||
- список доступных приложений;
|
- витрина всех приложений и состояния доступа;
|
||||||
- admin UI управления пользователями и доступами;
|
- admin UI управления клиентами, пользователями, группами, инвайтами и доступами;
|
||||||
- backend-интеграция с Authentik API;
|
- мастер-данные пользователей и профиль платформы;
|
||||||
|
- backend-интеграция с Authentik API как внутренняя sync/projection;
|
||||||
- audit log админских действий.
|
- audit log админских действий.
|
||||||
|
|
||||||
`Task Manager / Plane` остается отдельным приложением:
|
`Task Manager / Plane` остается отдельным приложением:
|
||||||
|
|
@ -39,7 +44,8 @@
|
||||||
- собственные workspace/project/task/comment модели;
|
- собственные workspace/project/task/comment модели;
|
||||||
- собственные роли workspace/project;
|
- собственные роли workspace/project;
|
||||||
- OIDC login через Authentik;
|
- OIDC login через Authentik;
|
||||||
- mapping внешней identity на существующего Plane user.
|
- mapping внешней identity на существующего Plane user;
|
||||||
|
- возможность работать standalone вне NODE.DC stack при сохранении стандартных Plane auth/API механизмов.
|
||||||
|
|
||||||
`Reverse proxy` является внешним защитным и маршрутизирующим слоем:
|
`Reverse proxy` является внешним защитным и маршрутизирующим слоем:
|
||||||
|
|
||||||
|
|
@ -75,14 +81,31 @@ NODEDC/
|
||||||
Логическая схема:
|
Логическая схема:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
authentik_db -> identity, groups, app access
|
launcher_db -> clients, users, memberships, groups, invites, app registry, access model, profiles, audit
|
||||||
launcher_db -> app registry, local profiles, audit
|
authentik_db -> identity/session/OIDC projection from Launcher
|
||||||
plane_db -> workspace, projects, tasks, comments, app roles
|
plane_db -> workspace, projects, tasks, comments, app roles
|
||||||
future_app_db -> доменная логика будущих приложений
|
future_app_db -> доменная логика будущих приложений
|
||||||
```
|
```
|
||||||
|
|
||||||
Связь между identity и локальными пользователями выполняется через explicit mapping, а не через прямое чтение чужих таблиц.
|
Связь между identity и локальными пользователями выполняется через explicit mapping, а не через прямое чтение чужих таблиц.
|
||||||
|
|
||||||
|
Launcher является источником бизнес-пользователя. Authentik хранит техническую identity, необходимую для SSO, но не заменяет Launcher в администрировании клиентов, команд, доступов и профиля.
|
||||||
|
|
||||||
|
## App standalone rule
|
||||||
|
|
||||||
|
Подключаемые приложения не должны становиться невынимаемыми частями NODE.DC.
|
||||||
|
|
||||||
|
Для Plane это означает:
|
||||||
|
|
||||||
|
- NODE.DC интеграция добавляется как адаптерный слой: OIDC provider, external identity link, app access projection и будущий service adapter;
|
||||||
|
- доменные таблицы Plane не становятся частью Launcher DB;
|
||||||
|
- Launcher не читает и не пишет Plane DB напрямую;
|
||||||
|
- управление workspace/project/task/comment остается внутри Plane;
|
||||||
|
- интеграция с Plane выполняется через публичные Plane API, API tokens или явно выделенные adapter endpoints;
|
||||||
|
- стандартные Plane auth/API механизмы не удаляются без отдельного решения, чтобы можно было поставить Plane клиенту standalone.
|
||||||
|
|
||||||
|
Если клиенту нужен только Task Manager, целевая поставка должна позволять развернуть Plane отдельно, отключить NODE.DC Launcher/Auth projection и оставить штатную модель Plane.
|
||||||
|
|
||||||
## Auth flow
|
## Auth flow
|
||||||
|
|
||||||
Для приложений, которые контролируются кодом:
|
Для приложений, которые контролируются кодом:
|
||||||
|
|
@ -91,7 +114,7 @@ future_app_db -> доменная логика будущих приложе
|
||||||
- backend/session или BFF layer;
|
- backend/session или BFF layer;
|
||||||
- JWT/JWKS validation server-side;
|
- JWT/JWKS validation server-side;
|
||||||
- проверка `issuer`, `audience`, `exp`, `sub`;
|
- проверка `issuer`, `audience`, `exp`, `sub`;
|
||||||
- проверка app access group;
|
- проверка app access projection group/entitlement;
|
||||||
- локальный user profile или external identity link.
|
- локальный user profile или external identity link.
|
||||||
|
|
||||||
Для legacy/временных приложений допускается reverse proxy forward-auth, но это временный внешний слой, а не единственная долгосрочная модель.
|
Для legacy/временных приложений допускается reverse proxy forward-auth, но это временный внешний слой, а не единственная долгосрочная модель.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
# Auth Model
|
# Auth Model
|
||||||
|
|
||||||
## Identity source
|
## Identity и control-plane source
|
||||||
|
|
||||||
Единственный источник identity: Authentik.
|
Пользовательский и административный источник истины: Launcher backend.
|
||||||
|
|
||||||
Launcher, Plane и будущие приложения не хранят пароли пользователей и не становятся главным источником truth по логину.
|
Launcher владеет продуктовыми сущностями платформы: клиенты/компании, участники, роли клиента, группы клиента, инвайты, доступы к сервисам, audit log и пользовательский профиль платформы.
|
||||||
|
|
||||||
|
Authentik остается внутренним identity/session слоем. Он хранит техническую identity, password/session/MFA/OIDC и получает из Launcher только синхронизированную проекцию, нужную для SSO: пользователей, enrollment/reset flows, группы/entitlements и профильные claims.
|
||||||
|
|
||||||
|
Пользователь и обычный администратор не должны видеть бренд Authentik в платформенном UI. Прямой доступ к Authentik допускается только через отдельную внутреннюю/служебную ссылку для системной настройки.
|
||||||
|
|
||||||
|
Plane и будущие приложения не хранят пароли пользователей и не становятся главным источником truth по платформенному пользователю. Они принимают OIDC/session claims и связывают их со своими локальными доменными моделями.
|
||||||
|
|
||||||
## User journey
|
## User journey
|
||||||
|
|
||||||
|
|
@ -23,6 +29,25 @@ UI платформы не должен показывать пользоват
|
||||||
|
|
||||||
Прямые ссылки на приложения остаются допустимым пользовательским сценарием. Если session нет, приложение или внешний proxy/auth layer должен увести пользователя в login flow и после успешного входа вернуть к исходному приложению.
|
Прямые ссылки на приложения остаются допустимым пользовательским сценарием. Если session нет, приложение или внешний proxy/auth layer должен увести пользователя в login flow и после успешного входа вернуть к исходному приложению.
|
||||||
|
|
||||||
|
## Platform login UX
|
||||||
|
|
||||||
|
Текущий hosted Authentik login допустим только как dev/runtime bootstrap для проверки OIDC, JWKS, session и app redirect.
|
||||||
|
|
||||||
|
Production login должен быть NODE.DC-branded:
|
||||||
|
|
||||||
|
- пользователь видит только NODE.DC экран входа;
|
||||||
|
- тексты, ошибки, recovery/enrollment и logout не светят бренд Authentik;
|
||||||
|
- Authentik остается внутренним password/session/MFA/OIDC слоем;
|
||||||
|
- пароль, service tokens и refresh tokens не попадают во frontend bundle;
|
||||||
|
- прямые ссылки на Launcher/Task Manager/future apps ведут через этот же NODE.DC login facade и возвращают пользователя в исходное приложение.
|
||||||
|
|
||||||
|
Допустимые реализации:
|
||||||
|
|
||||||
|
1. Кастомизированный Authentik flow, если его можно привести к NODE.DC UX без компромиссов по дизайну.
|
||||||
|
2. Launcher/BFF login facade, который server-side вызывает внутренний Authentik flow/API и сохраняет безопасность IdP.
|
||||||
|
|
||||||
|
Запрещено делать быстрый frontend-only password form, который обходит IdP protections, хранит секреты в браузере или ломает MFA/recovery/audit.
|
||||||
|
|
||||||
## Required claims
|
## Required claims
|
||||||
|
|
||||||
Минимальный normalized user object:
|
Минимальный normalized user object:
|
||||||
|
|
@ -32,6 +57,7 @@ type AuthUser = {
|
||||||
sub: string;
|
sub: string;
|
||||||
email: string;
|
email: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
avatarUrl?: string | null;
|
||||||
groups: string[];
|
groups: string[];
|
||||||
entitlements?: string[];
|
entitlements?: string[];
|
||||||
};
|
};
|
||||||
|
|
@ -44,7 +70,8 @@ type AuthUser = {
|
||||||
- `exp` не истек;
|
- `exp` не истек;
|
||||||
- `sub` непустой и стабилен;
|
- `sub` непустой и стабилен;
|
||||||
- `email` присутствует для user-facing приложений;
|
- `email` присутствует для user-facing приложений;
|
||||||
- `groups` или `entitlements` содержат требуемый app access.
|
- `name`, `picture`/`avatar_url` используются как единый профильный контекст, но мастер-данные профиля должны приходить из Launcher;
|
||||||
|
- `groups` или `entitlements` содержат требуемую техническую app access projection.
|
||||||
|
|
||||||
## Groups
|
## Groups
|
||||||
|
|
||||||
|
|
@ -79,9 +106,11 @@ nodedc:dm:access
|
||||||
|
|
||||||
## Access levels
|
## Access levels
|
||||||
|
|
||||||
Уровень 1: Authentik app access.
|
Уровень 1: Launcher access model.
|
||||||
|
|
||||||
Отвечает на вопрос, можно ли открыть приложение.
|
Отвечает на вопрос, можно ли открыть приложение. Источник решения — данные Launcher: клиент, членство, группы клиента, user grants, deny exceptions, статус сервиса и период доступа.
|
||||||
|
|
||||||
|
Authentik app access является технической проекцией этого решения для SSO/enforcement, а не местом ручного бизнес-администрирования.
|
||||||
|
|
||||||
Launcher показывает каталог приложений как витрину, а не скрывает все недоступные плитки. Для приложения без доступа кнопка перехода отключена и отображается состояние "Нет доступа". Это важно для продаж, onboarding и понимания доступных модулей платформы.
|
Launcher показывает каталог приложений как витрину, а не скрывает все недоступные плитки. Для приложения без доступа кнопка перехода отключена и отображается состояние "Нет доступа". Это важно для продаж, onboarding и понимания доступных модулей платформы.
|
||||||
|
|
||||||
|
|
@ -98,11 +127,28 @@ Launcher показывает каталог приложений как вит
|
||||||
Backend должен:
|
Backend должен:
|
||||||
|
|
||||||
- хранить Authentik service token только server-side;
|
- хранить Authentik service token только server-side;
|
||||||
- выполнять admin calls к Authentik API;
|
- хранить клиентов, пользователей, членства, группы, инвайты, grants/exceptions и профиль платформы;
|
||||||
|
- выполнять server-side sync в Authentik API без раскрытия Authentik пользователю;
|
||||||
|
- создавать invite/enrollment flows из Launcher UI;
|
||||||
- хранить audit log;
|
- хранить audit log;
|
||||||
- возвращать frontend только нормализованные данные пользователя и разрешенные действия;
|
- возвращать frontend только нормализованные данные пользователя и разрешенные действия;
|
||||||
- не отдавать access/refresh/service tokens в browser bundle.
|
- не отдавать access/refresh/service tokens в browser bundle.
|
||||||
|
|
||||||
|
## Live control-plane data rule
|
||||||
|
|
||||||
|
Демо-пользователи Launcher допустимы только как fixture на этапе frontend MVP.
|
||||||
|
|
||||||
|
Как только появляется backend/admin API, Launcher control-plane должен перейти на живой seed:
|
||||||
|
|
||||||
|
- реальные платформенные пользователи вместо `root@nodedc.local`, `ivan@romashka.ru` и прочих декоративных участников;
|
||||||
|
- явный superadmin NODE.DC;
|
||||||
|
- реальные клиентские пользователи и группы, подтвержденные по Plane/Auth/Launcher данным;
|
||||||
|
- idempotent seed/migration script с backup текущего `launcher-data.json`;
|
||||||
|
- отсутствие прямого изменения Plane users/workspace/task данных из Launcher seed;
|
||||||
|
- все дальнейшие проверки клиентов, групп, инвайтов и доступов выполняются через Launcher admin UI.
|
||||||
|
|
||||||
|
Если пользователь существует в Plane, но еще не существует в Authentik, он должен быть заведен через Launcher/Auth sync flow, а не ручным рассинхроном.
|
||||||
|
|
||||||
## Plane identity link
|
## Plane identity link
|
||||||
|
|
||||||
Минимальная таблица или эквивалентная модель в Plane:
|
Минимальная таблица или эквивалентная модель в Plane:
|
||||||
|
|
@ -124,3 +170,15 @@ external_identity_link
|
||||||
- если link найден, логинить связанный `plane_user_id`;
|
- если link найден, логинить связанный `plane_user_id`;
|
||||||
- если link не найден, но email совпадает и включен migration auto-link, создать link;
|
- если link не найден, но email совпадает и включен migration auto-link, создать link;
|
||||||
- если доступа нет, вернуть deny.
|
- если доступа нет, вернуть deny.
|
||||||
|
|
||||||
|
## Plane standalone compatibility
|
||||||
|
|
||||||
|
Plane является подключаемым приложением NODE.DC, но не должен становиться технически невынимаемым модулем.
|
||||||
|
|
||||||
|
Правила интеграции:
|
||||||
|
|
||||||
|
- OIDC NODE.DC добавляется как отдельный auth provider path, а не как замена всей Plane auth системы;
|
||||||
|
- стандартные Plane session/API-token возможности сохраняются, пока нет отдельного решения их убрать;
|
||||||
|
- Launcher хранит только платформенные привязки и access projection, но не владеет Plane workspace/project/task/comment;
|
||||||
|
- связь с Plane строится через external identity link и будущий service adapter/API, а не через прямое изменение Plane DB из Launcher;
|
||||||
|
- env-флаги должны позволять включать NODE.DC SSO в составе платформы и выключать его для standalone-поставки Plane.
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,42 @@ def ensure_groups_scope_mapping():
|
||||||
return mapping
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_profile_scope_mapping():
|
||||||
|
expression = """
|
||||||
|
attributes = request.user.attributes or {}
|
||||||
|
display_name = request.user.name or request.user.username
|
||||||
|
name_parts = display_name.split(" ", 1)
|
||||||
|
avatar_url = attributes.get("picture") or attributes.get("avatar_url") or attributes.get("avatar")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": display_name,
|
||||||
|
"given_name": name_parts[0] if name_parts else display_name,
|
||||||
|
"family_name": name_parts[1] if len(name_parts) > 1 else "",
|
||||||
|
"preferred_username": request.user.username,
|
||||||
|
"nickname": request.user.username,
|
||||||
|
"picture": avatar_url,
|
||||||
|
"avatar_url": avatar_url,
|
||||||
|
}
|
||||||
|
""".strip()
|
||||||
|
mapping, _ = ScopeMapping.objects.get_or_create(
|
||||||
|
name="NODE.DC OAuth Mapping: profile context",
|
||||||
|
defaults={
|
||||||
|
"scope_name": "profile",
|
||||||
|
"description": "Adds normalized NODE.DC profile claims to OIDC tokens.",
|
||||||
|
"expression": expression,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mapping.scope_name = "profile"
|
||||||
|
mapping.description = "Adds normalized NODE.DC profile claims to OIDC tokens."
|
||||||
|
mapping.expression = expression
|
||||||
|
mapping.save()
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
def default_scope_mappings():
|
def default_scope_mappings():
|
||||||
scope_names = ["openid", "email", "profile", "offline_access"]
|
scope_names = ["openid", "email", "offline_access"]
|
||||||
mappings = list(ScopeMapping.objects.filter(scope_name__in=scope_names))
|
mappings = list(ScopeMapping.objects.filter(scope_name__in=scope_names))
|
||||||
|
mappings.append(ensure_profile_scope_mapping())
|
||||||
mappings.append(ensure_groups_scope_mapping())
|
mappings.append(ensure_groups_scope_mapping())
|
||||||
return mappings
|
return mappings
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue