Przejdź do głównej zawartości

Wyszukiwanie jest dostępne tylko w buildach produkcyjnych. Spróbuj zbudować i uruchomić aplikację, aby przetestować lokalnie.

Rejestracja aplikacji partnerskiej (klient OAuth2) w DealerID

Żeby nowa aplikacja platformy (np. BMS, Service, Analytics) mogła logować userów przez DealerID, musi być wpisana jako klient OAuth2 do tabeli oauth2_client w bazie DealerID. Na chwilę obecną jest to ręczna operacja SQL (migracja Doctrine lub UPDATE po stronie DBA). W przyszłości planowany jest admin UI w DealerID dla staff, na razie zostajemy przy DB-as-source-of-truth.

Uwaga terminologiczna: w tym dokumencie „aplikacja partnerska” / „klient OAuth2” oznacza zewnętrzną apkę (z perspektywy DealerID) chcącą integrować się przez OAuth2 — np. nowy moduł BMS, Service, integracja third-party. To NIE to samo co „Application” w sensie modelu wewnętrznego CRM (instancja CRM pod konkretnym hostem, rekord w tabeli application). Jedna aplikacja partnerska (oauth2_client.identifier = 'crm-frontend') wystawia wiele wewnętrznych Applications (host-based). Patrz Multi-tenancy: Aplikacje.

Schema (z apps/dealerid/migrations/Version20260512142135.php

  • Version20260518100000.php + Version20260520120000.php):
KolumnaTypOpis
identifierVARCHAR(32)PK. Krótka stabilna nazwa klienta, np. crm-frontend, bms-frontend. Używana w logach, w mapie APP_PRESENTATION (logo/kolor karty na DealerID) i jako client_id w OAuth.
nameVARCHAR(128)Human-readable nazwa pokazywana na ekranie zgody, np. „DealerCRM Frontend”.
secretVARCHAR(128)Bcrypt/argon hash client_secret. Klienci public (SPA / Next.js BFF z PKCE) mogą mieć NULL jeśli confidential = false — patrz konfiguracja league/oauth2-server-bundle. Confidential klienci (machine-to-machine) muszą mieć secret.
redirect_urisTEXTSeparowane białymi znakami. Whitelist callbacków po loginie. W produkcji wyłącznie HTTPS. W dev: http://<app>.localhost:<port>/auth/callback.
grantsTEXTLista grantów separowana białymi znakami. Standard dla naszych SPA / BFF: authorization_code refresh_token. Dla M2M można dodać client_credentials.
scopesTEXTLista scope’ów separowana białymi znakami. Patrz Flow OAuth2 → scopes.
activeBOOLEANHard kill switch dla klienta — FALSE zwalnia wszystkie tokeny przy następnej walidacji.
allow_plain_text_pkceBOOLEANZawsze FALSE w naszej platformie. Tylko S256 (code_challenge_method=S256).
is_first_partyBOOLEANGdy TRUE, ekran zgody jest pomijany (auto-approve). Ustawiamy TRUE dla apek Grupy Dealer (CRM, BMS, Service, Analytics). FALSE dla integracji third-party (np. zewnętrznych systemów dealerskich).
app_directory_urlVARCHAR(255)Opcjonalny endpoint server-to-server (HTTP wewnątrz sieci Docker / VPN), z którego DealerID pobiera listę „kart Applications” dla zalogowanego usera. Patrz App Directory — kontrakt. Może być NULL.

Uwaga: kolumny is_first_party i app_directory_url zostały dodane w późniejszych migracjach (Version20260518100000 i Version20260520120000). Jeśli czytasz to po świeżym wgraniu bazy bez tych migracji — najpierw odpal php bin/console doctrine:migrations:migrate w apps/dealerid.

Konwencja: <app>-<surface>. Surface to frontend (SPA / BFF konsumujący OAuth2), service (M2M klient bez usera), cli, itd.

identifier = 'bms-frontend'
name = 'DealerBMS'

Public client (SPA + PKCE, bez secretu)? Można NULL. Confidential? Wygeneruj 64-bajtowy random:

Okno terminala
openssl rand -base64 64

Hashuj przez bundle (Symfony command — czytaj vendor/league/oauth2-server-bundle/src/Command/CreateClientCommand.php — lub przez nasz własny admin script gdy powstanie). W bazie trzymamy hash, nigdy plaintextu.

Dla SPA/BFF typowo jedno:

http://bms.localhost:3004/auth/callback # dev
https://bms.dealercrm.pl/auth/callback # prod

Wpisuj dokładnie taki URL, z jakim przyjdzie request — redirect_uri w /authorize musi byte-for-byte pasować, inaczej OAuth2 server odrzuci.

Domyślne dla apek platformy:

grants = 'authorization_code refresh_token'
scopes = 'openid email profile'

Dodatkowe scope’y (np. app.directory.read dla M2M discovery) — patrz Flow OAuth2 → scopes.

Apkais_first_partyPowód
CRM, BMS, Service, AnalyticsTRUEApki Grupy Dealer. User wie, że loguje się do platformy — ekran zgody to friction bez wartości.
Portal KlientaTRUEFirst-party z perspektywy platformy (user nie wybiera czy mu się zgadza).
External integracja (BIK, GUS, …)FALSEUser powinien świadomie zgodzić się, że apka odczyta jego dane z DealerID.

Jeśli twój klient OAuth2 chce się pojawić na dashboardzie /account/apps DealerID — wystaw endpoint (patrz Implementacja) i wpisz jego URL z perspektywy sieci DealerID:

UPDATE oauth2_client
SET app_directory_url = 'http://bms-nginx/api/dealerid/user-apps'
WHERE identifier = 'bms-frontend';

W produkcji to będzie wewnętrzny URL VPN / mesh service, nie publiczny https://.

Co zwraca ten endpoint: listę wszystkich Applications (po stronie apki partnerskiej — instancji per host) widocznych dla danego usera. Apka mająca model multi-tenant (jak CRM) może wystawić N kart per user. Apka single-tenant — typowo 1 kartę. Patrz App Directory — kontrakt.

INSERT INTO oauth2_client (
identifier, name, secret, redirect_uris, grants, scopes,
active, allow_plain_text_pkce, is_first_party, app_directory_url
) VALUES (
'bms-frontend',
'DealerBMS',
NULL, -- public client (PKCE)
'http://bms.localhost:3004/auth/callback https://bms.dealercrm.pl/auth/callback',
'authorization_code refresh_token',
'openid email profile',
TRUE,
FALSE,
TRUE,
'http://bms-nginx/api/dealerid/user-apps'
);

Logo + kolor karty na /account/apps zdefiniowane są lokalnie w apps/dealerid/src/Module/User/Infrastructure/Directory/HttpAppDirectoryFetcher.php:

private const array APP_PRESENTATION = [
'crm-frontend' => ['name' => 'DealerCRM', 'monogram' => 'DC', 'color' => 'oklch(0.52 0.19 258)'],
'bms-frontend' => ['name' => 'DealerBMS', 'monogram' => 'BM', 'color' => 'oklch(0.45 0.15 60)'],
'service-frontend' => ['name' => 'DealerService', 'monogram' => 'DS', 'color' => 'oklch(0.55 0.10 25)'],
'analytics-frontend' => ['name' => 'DealerAnalytics', 'monogram' => 'DA', 'color' => 'oklch(0.5 0.2 295)'],
'portal-frontend' => ['name' => 'Portal Klienta', 'monogram' => 'PK', 'color' => 'oklch(0.5 0.15 175)'],
];

Dodanie nowej apki = PR do DealerID z dorzuceniem wpisu.

Dlaczego tak? Trzymamy spójność wizualną po stronie DealerID — klient OAuth2 mówi „co user może” (lista Applications dla usera), DealerID mówi „jak to pokazać”. Apka partnerska nie podpina własnego logo / koloru dla karty na DealerID. (Branding na poziomie konkretnej Application — typu gezet-renault.localhost → skin Renault — to inny wymiar i mieszka po stronie apki, w organization.branding_skin_slug. Patrz Multi-tenancy: Aplikacje.)

  • ✅ DB-as-source-of-truth dla klientów OAuth2
  • 🟡 Admin UI w DealerID (/staff/oauth-clients) — CRUD klientów, generator secretów, validator URI
  • 🟡 Rotacja secretów (dual-secret window)
  • 🟡 Self-service registration dla third-party (poza scope na razie)