Rozwiązywanie Kontekstu (Tenant Resolver)

1. Architektura Bazy Danych i Hybrydowe Połączenia

Centralny punkt systemu to baza katalogowa (TenantCatalog), która przechowuje metadane klientów, ich NIP, plany subskrypcyjne oraz unikalną nazwę bazy danych (DbName).

System nie przechowuje haseł do serwerów SQL w bazie katalogowej, a jedynie unikatowe nazwy baz; przyjęto, że nazwy zawierają nip oraz fragment ID danego wpisu, ale możliwe jest też adhocowe przypisanie dowolnej nazwy bazy. Domyślnie baza główna może być jak najbardziej w innym miejscu, niż API, natomiast bazy klientów powinny być na tym samym serwerze co API, po pierwsze ze względu na szybkość odczytu i brak przesyłania żądań SQL po sieci, a po drugie ze względu

Połączenie z bazą klienta jest składane w locie: system pobiera bezpieczny "szablon" z pliku appsettings.json i wstrzykuje do niego nazwę bazy pobraną z katalogu.

System cachuje DbContextOptions - obiekty tworzące zapytania ale nie sam obiekt DbContext, który musi być tworzony i usuwany per request

2. Rola TenantMiddleware (Izolacja Żądań)

  • Wszystkie requesty HTTP do serwera przechodzą przez następujące Middleware'y: UseAuthentication (tu następuje walidacja JWT), TenantMiddleware, UseAuthorization i na końcu - wszelkie kontrolery
  • Middleware to pierwszy strażnik w potoku. Przechwytuje absolutnie każde żądanie HTTP skierowane do serwera.
  • Odpowiada wyłącznie za rozwiązanie kontekstu, czyli bezbłędne ustalenie, do jakiej firmy należy dane żądanie.
  • Obsługuje trzy ścieżki przepływu:

1. Anonimowa: Jeśli użytkownik nie ma tokenu, middleware go przepuszcza bez przypisywania tenanta (pozwala to na załadowanie plików interfejsu Blazor czy formularza logowania).

2. Logowanie: Wyciąga identyfikator firmy (NIP) bezpośrednio z ciała zapytania JSON, by wiedzieć, gdzie szukać konta użytkownika.

3. Zalogowana: Rygorystycznie wymaga obecności tenanta, priorytetowo traktując kryptograficznie podpisany token JWT.

3. Proces Logowania i Generowanie Tokenu (AuthController)

  • Kontroler przyjmuje login, hasło i NIP.
  • Najpierw odpytuje bazę centralną (Katalog), by sprawdzić, czy firma o danym NIP istnieje i jak nazywa się jej fizyczna baza danych.
  • Tworzy tymczasowe połączenie z tą konkretną bazą klienta, omijając standardowy system wstrzykiwania zależności.
  • Weryfikuje poprawność hasła w tabeli użytkowników danej firmy.
  • Po sukcesie generuje token JWT, w którym "zaszywa" identyfikator tenanta. Od tego momentu to token jest dowodem tożsamości i przynależności firmowej.

4. Wstrzykiwanie Zależności (DI) i Fabryki

  • Rejestracja ApplicationDBContext w pliku Program.cs oraz w TenantDbContextFactory odbywa się dynamicznie (per-request).
  • System pobiera rozwiązanego wcześniej tenanta z ITenantAccessor.
  • Złożony z szablonu connection string trafia do bufora w pamięci RAM (DbContextOptionsCache), co zapobiega ciągłemu przebudowywaniu opcji Entity Frameworka przy każdym zapytaniu i drastycznie zwiększa wydajność.

5. Bezpieczeństwo i Obrona w Głąb (Defense in Depth)

  • System opiera się na rozdzieleniu obowiązków: atrybut [Authorize] chroni dostęp do samych funkcji (endpointów API), a TenantMiddleware chroni dostęp do danych.
  • Zapobieganie podszywaniu się: Ponieważ TenantResolver czyta identyfikator firmy w pierwszej kolejności z tokenu JWT (podpisanego kluczem serwera), zalogowany haker nie może wymusić zmiany bazy poprzez modyfikację nagłówków HTTP.
  • Bezpieczeństwo w przypadku błędu dewelopera: Nawet jeśli programista zapomni nałożyć atrybut [Authorize] na endpoint, brak tokenu spowoduje brak przypisanego tenanta. Entity Framework nie otrzyma connection stringa i przerwie operację (zwracając błąd serwera 500), całkowicie chroniąc dane przed wyciekiem.