diff --git a/docs/AUTH_MODEL.md b/docs/AUTH_MODEL.md index 98ad0ee..d52e8ed 100644 --- a/docs/AUTH_MODEL.md +++ b/docs/AUTH_MODEL.md @@ -6,6 +6,23 @@ Launcher, Plane и будущие приложения не хранят пароли пользователей и не становятся главным источником truth по логину. +## User journey + +Целевой production flow: + +```text +nodedc.ru marketing site + -> Войти на платформу + -> platform login + -> NODE.DC Launcher + -> application tiles + -> Task Manager / future apps +``` + +UI платформы не должен показывать пользователю название identity-провайдера. В пользовательских кнопках и текстах используется нейтральное "Войти", "Вход на платформу", "Сессия NODE.DC". + +Прямые ссылки на приложения остаются допустимым пользовательским сценарием. Если session нет, приложение или внешний proxy/auth layer должен увести пользователя в login flow и после успешного входа вернуть к исходному приложению. + ## Required claims Минимальный normalized user object: @@ -40,16 +57,15 @@ nodedc:superadmin Launcher: ```text -nodedc:launcher:access nodedc:launcher:admin -nodedc:launcher:user-manager +nodedc:launcher:user ``` Task Manager: ```text -nodedc:taskmanager:access nodedc:taskmanager:admin +nodedc:taskmanager:user ``` Future apps: @@ -67,6 +83,8 @@ nodedc:dm:access Отвечает на вопрос, можно ли открыть приложение. +Launcher показывает каталог приложений как витрину, а не скрывает все недоступные плитки. Для приложения без доступа кнопка перехода отключена и отображается состояние "Нет доступа". Это важно для продаж, onboarding и понимания доступных модулей платформы. + Уровень 2: Launcher admin roles. Отвечает на вопрос, можно ли управлять пользователями, группами, app registry и audit. diff --git a/docs/DEPLOYMENT_LOCAL.md b/docs/DEPLOYMENT_LOCAL.md index f97cc88..5d6ba5f 100644 --- a/docs/DEPLOYMENT_LOCAL.md +++ b/docs/DEPLOYMENT_LOCAL.md @@ -18,12 +18,14 @@ ## Current local apps -Launcher сейчас запускается как Vite app из: +Launcher сейчас запускается как Vite app + local BFF из: ```text /Users/dcconstructions/Downloads/mnt/data/nodedc_launcher ``` +В dev режиме `npm run dev` запускает BFF на `:5173`; Vite подключен как middleware. Чистый Vite режим сохранен как `npm run dev:vite`. + Task Manager сейчас доступен как Plane self-host runtime: ```text @@ -116,6 +118,8 @@ This creates: - `NODE.DC Task Manager` application with OAuth2/OIDC provider; - group bindings for application access. +OIDC providers must have a signing key. Without it Authentik returns an empty JWKS (`{}`), and Launcher callback fails token validation. + The script fills missing `LAUNCHER_OIDC_CLIENT_SECRET` and `PLANE_OIDC_CLIENT_SECRET` values in ignored `infra/.env`. Secrets are not committed. Проверка: diff --git a/infra/authentik/bootstrap-dev.py b/infra/authentik/bootstrap-dev.py index a213d55..a988135 100644 --- a/infra/authentik/bootstrap-dev.py +++ b/infra/authentik/bootstrap-dev.py @@ -4,6 +4,7 @@ from django.db import transaction from authentik.common.oauth.constants import SubModes from authentik.core.models import Application, Group, User +from authentik.crypto.models import CertificateKeyPair from authentik.flows.models import Flow from authentik.policies.models import PolicyBinding from authentik.providers.oauth2.models import ( @@ -127,6 +128,13 @@ def default_scope_mappings(): def ensure_provider(spec, mappings): authorization_flow = Flow.objects.get(slug="default-provider-authorization-implicit-consent") invalidation_flow = Flow.objects.get(slug="default-provider-invalidation-flow") + signing_key = ( + CertificateKeyPair.objects.filter(name="authentik Self-signed Certificate").first() + or CertificateKeyPair.objects.first() + ) + + if signing_key is None: + raise RuntimeError("No Authentik CertificateKeyPair exists for OIDC signing") provider = OAuth2Provider.objects.filter(name=spec["provider_name"]).first() if provider is None: @@ -146,6 +154,7 @@ def ensure_provider(spec, mappings): provider.issuer_mode = IssuerMode.PER_PROVIDER provider.authorization_flow = authorization_flow provider.invalidation_flow = invalidation_flow + provider.signing_key = signing_key provider.save() provider.property_mappings.set(mappings) return provider