Moduł internetowy dla kontrahentów - zakres w trzech systemach

Moduł internetowy dla kontrahentów udostępnia przez przeglądarkę wycinek danych systemu, który dotyczy jednej zewnętrznej firmy. Kontrahent nie instaluje oprogramowania i nie widzi danych innych klientów. Ten sam model dostępu obowiązuje w WMS. Powtarza się w YMS i w RMA, choć w każdym z tych systemów kontrahent jest kimś innym i ma inne zadania.

SystemKontrahentCo widziCo może zgłosić
WMSwłaściciel towaru, odbiorca, dostawcastany własnego towaru oraz historię dokumentów z PZ i WZ w PDFpolecenie wydania, awizo dostawy
YMSprzewoźnik, dostawcawłasne awizacje i ich statusy, wolne oknanową awizację lub jej zmianę albo anulowanie w dozwolonym czasie
RMAklient, dystrybutorstatus własnych spraw, oś czasu, decyzjęnowe zgłoszenie z załącznikami

Szczegóły dla magazynu opisuje artykuł o module internetowym w systemie WMS, a dla placu i bram tekst o awizacjach w Studio VSS.net. Zgłoszenia reklamacyjne przez portal przedstawia strona o programie RMA on-line. Ta strona porównuje te trzy przypadki pod kątem wspólnych mechanizmów: uwierzytelniania oraz uprawnień, a także izolacji danych.

W portalu magazynowym kontrahent widzi bieżący stan z podziałem na indeks i partię oraz z lokalizacją, a przy potrzebie uzgodnienia rozbieżności może odtworzyć zapas na wybrany dzień wstecz. W części wdrożeń dane eksportuje się do pliku CSV lub XLSX, dzięki czemu nie trzeba przepisywać ich z ekranu do własnego arkusza.

Ilustracja chmury i ekranów różnych urządzeń z napisem Any Device, Anywhere, Anytime
Dostęp do portalu z przeglądarki na różnych urządzeniach - kontrahent nie instaluje oprogramowania po swojej stronie

Reguła: kontrahent widzi rekord tylko wtedy, gdy jest jego właścicielem, a listę rekordów zawęża serwer, nie przeglądarka.

Portal odciąża zespół po stronie magazynu, ponieważ kontrahent sam sprawdza status i pobiera dokument, zamiast dzwonić. Tę samą ideę w szerszym ujęciu omawia artykuł o oprogramowaniu logistycznym, gdzie portal jest jednym z punktów styku z zewnętrznymi firmami.

Uwierzytelnianie kontrahenta i ochrona sesji

Uwierzytelnienie potwierdza, kim jest użytkownik, a autoryzacja ustala, do czego ma dostęp. W module dla kontrahentów każda osoba dostaje indywidualny login i hasło, a komunikacja między przeglądarką a serwerem jest szyfrowana protokołem TLS. W portalu reklamacyjnym logowanie odbywa się adresem e-mail i generowanym hasłem.

Wspólne konto całego działu zakupów kontrahenta jest złym rozwiązaniem. Nie da się wtedy ustalić, kto zatwierdził zamówienie, a odejście jednego pracownika wymaga zmiany hasła u wszystkich. Indywidualne konta pozwalają odebrać dostęp jednej osobie bez wpływu na resztę zespołu.

Przechowywanie haseł i blokada prób

Hasła nie zapisuje się w bazie w postaci jawnej. Zapisuje się iterowany skrót z losową solą, a sprawdzenie polega na policzeniu skrótu dla podanego hasła i porównaniu wyniku w stałym czasie. Poniższy przykład w C# ilustruje mechanizm i nie jest kodem produktu.

using System.Security.Cryptography;

static byte[] Hash(string password, byte[] salt) =>
    Rfc2898DeriveBytes.Pbkdf2(password, salt, 600_000, HashAlgorithmName.SHA256, 32);

static bool Verify(string password, byte[] salt, byte[] expected) =>
    CryptographicOperations.FixedTimeEquals(Hash(password, salt), expected);

Skrót chroni hasła przy wycieku bazy, ale nie chroni przed zgadywaniem na formularzu logowania. Przed tym zabezpiecza blokada konta po serii nieudanych prób albo rosnące opóźnienie odpowiedzi. Reset hasła wykonuje się jednorazowym odnośnikiem z ograniczonym czasem ważności, wysłanym na adres konta.

Sesja i ciasteczka

Po zalogowaniu serwer wystawia identyfikator sesji w ciasteczku. Atrybuty ciasteczka decydują o tym, czy skrypt na stronie albo połączenie bez szyfrowania mogą je przechwycić. Opis atrybutów zawiera dokumentacja ciasteczek HTTP w MDN, a mechanizmy uwierzytelniania po stronie serwera przedstawia dokumentacja uwierzytelniania w ASP.NET Core.

Set-Cookie: sid=9f2c41d0b7; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1800
MechanizmPrzed czym chroniGdzie działa
TLSpodsłuchiwaniem transmisjipołączenie przeglądarka - serwer
Skrót hasła z soląodczytem haseł po wycieku bazybaza danych
Blokada po nieudanych próbachzgadywaniem hasłaformularz logowania
Ciasteczko Secure i HttpOnlykradzieżą sesji przez skryptprzeglądarka
Limit czasu sesjiużyciem pozostawionego komputeraserwer aplikacji

Role i zakres uprawnień po stronie kontrahenta

Uprawnienia mają dwa wymiary. Rola odpowiada na pytanie, co wolno użytkownikowi, a zakres na pytanie, czyich danych dotyczy dostęp. Pracownik działu zakupów może składać zamówienia, ale tylko dla własnej firmy, a przewoźnik zakłada awizacje, ale widzi wyłącznie swoje zgłoszenia.

Rola po stronie kontrahentaZakres dostępu
Administrator konta kontrahentapełny podgląd danych, zarządzanie użytkownikami, pobieranie wszystkich dokumentów
Pracownik działu zakupówskładanie zamówień i awizacji, podgląd stanów magazynowych
Pracownik działu finansowegohistoria transakcji i dokumenty rozliczeniowe
Użytkownik tylko do odczytupodgląd stanów magazynowych bez składania zamówień

Ten podział dotyczy portalu WMS. W module awizacji rolę przewoźnika ogranicza się dodatkowo do wybranych ramp albo kontrahentów, a ustawienia globalne pozostają u supervisora po stronie magazynu. W portalu reklamacyjnym osobnym wariantem jest konto dystrybutora, który zgłasza sprawy w imieniu wielu odbiorców.

Uśmiechnięta kobieta siedzi przy monitorze z kolorowym widokiem kalendarza w pomarańczowo-niebieskim biurze
Konfiguracja kont i ról dla firm zewnętrznych - administrator ustala, kto z danej firmy widzi które dane

Reguła: rola odpowiada na pytanie „co wolno”, a zakres na pytanie „czyje dane”, i oba warunki sprawdza serwer przy każdym żądaniu.

Administrator konta i dziennik zdarzeń

Administrator po stronie kontrahenta zarządza własnymi użytkownikami. Gdy pracownik zmienia stanowisko albo odchodzi, odbiera mu dostęp od razu, bez angażowania działu IT magazynu. Dziennik logowań pozwala ustalić, kto i kiedy wykonał daną operację w portalu.

W dzienniku zapisuje się, kto wykonał operację i kiedy, oraz którego obiektu dotyczyła. Przy sporze o zamówienie albo awizację wystarczy odtworzyć zapis, zamiast rekonstruować przebieg z wiadomości e-mail. Zasady kont i kluczy dla systemów zewnętrznych opisuje tekst o awizacji transportu i jej interfejsie.

Przegląd kont i wygaszanie dostępu

Konta kontrahentów starzeją się szybciej niż konta pracowników, bo osoby po drugiej stronie zmieniają stanowiska bez informowania magazynu. Przydatny jest okresowy przegląd: lista kont bez logowania od ustalonego czasu trafia do administratora konta kontrahenta z pytaniem o potrzebę dostępu. Nieużywane konto dezaktywuje się, a nie usuwa, żeby zachować powiązanie z historią operacji.

Dezaktywacja natychmiast unieważnia aktywne sesje użytkownika. Sprawdzenie statusu konta przy każdym żądaniu kosztuje jedno wyszukiwanie po kluczu głównym, więc nie obciąża bazy, a zamyka lukę, w której zwolniona osoba korzysta z sesji do końca jej ważności.

Izolacja danych kontrahentów w bazie SQL Server

Izolacja danych jest najważniejszym wymaganiem portalu. Wszystkie firmy pracują na jednej bazie, więc każde zapytanie musi być zawężone do jednego kontrahenta. Identyfikator kontrahenta pobiera się z sesji po zalogowaniu, nigdy z parametru żądania, bo parametr można podmienić w przeglądarce.

Szczególnie groźny jest błąd polegający na zaufaniu numerowi rekordu z adresu. Jeśli portal pokaże dokument o numerze 1042 każdemu zalogowanemu, kolejne numery da się odgadnąć. Numery spraw w portalu reklamacyjnym są kolejne, więc sam numer nigdy nie wystarcza do wglądu w dane. Podobne wymaganie dotyczy magazynów obsługujących wielu klientów, opisanych w artykule o kontraktowej logistyce magazynowej.

  • Filtr w aplikacji - każde zapytanie portalu zawiera warunek na identyfikator kontrahenta z sesji.
  • Filtr w bazie - zabezpieczenie na poziomie wierszy odrzuca wiersze innych kontrahentów, nawet jeśli aplikacja pominie warunek.

Drugi poziom nie zastępuje pierwszego, tylko domyka lukę wynikającą z pomyłki programisty. W SQL Server od wersji 2016 służy do tego zabezpieczenie na poziomie wierszy, opisane w dokumentacji Row-Level Security. Poniższy przykład jest ilustracją mechanizmu, a nie opisem implementacji produktów SoftwareStudio, w których filtr może być realizowany w warstwie aplikacji.

CREATE SCHEMA Security;
GO
CREATE FUNCTION Security.fn_ContractorFilter(@ContractorId int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS Allowed
WHERE @ContractorId = CAST(SESSION_CONTEXT(N'ContractorId') AS int);
GO
CREATE SECURITY POLICY Security.ContractorPolicy
ADD FILTER PREDICATE Security.fn_ContractorFilter(ContractorId) ON dbo.StockBalance
WITH (STATE = ON);
GO
EXEC sp_set_session_context @key = N'ContractorId', @value = 42, @read_only = 1;

Kontekst sesji ustawia aplikacja po pobraniu połączenia z puli i przed pierwszym zapytaniem. Pominięcie tego kroku daje puste wyniki, a błędnie zapisany identyfikator ujawniłby dane innego kontrahenta, dlatego ustawienie wykonuje się w jednym miejscu kodu. Kolumna ContractorId powinna otwierać indeksy używane przez portal, żeby filtr nie zamieniał zapytań na skany całych tabel.

Statusy i powiadomienia widoczne dla kontrahenta

Kontrahent nie potrzebuje wszystkich pól rekordu. Portal reklamacyjny pokazuje uproszczone statusy zamiast wewnętrznych, a przejścia czysto wewnętrzne, na przykład przekazanie sprawy między serwisantami, w ogóle nie trafiają do wyniku zapytania. Mapowanie zapisuje się w słowniku, więc zmiana nazwy statusu wewnętrznego nie wymaga zmian w kodzie portalu.

Status wewnętrznyStatus widoczny dla kontrahenta
Nowa, zarejestrowanaZgłoszenie przyjęte
W ocenieW trakcie oceny
Decyzja wydanaRozstrzygnięcie
Rozliczona, zamkniętaZakończone
Kobieta z upiętymi włosami siedzi przy biurku i patrzy na monitor z pomarańczowo-niebieskim ekranem
Widok klienta na własne zgłoszenie - status i oś czasu bez wewnętrznych notatek serwisu

Technicznie taki filtr realizuje widok przeznaczony wyłącznie dla portalu. Widok wystawia tylko dozwolone kolumny, a portal nie ma prawa czytać tabel bazowych. Dodanie nowego pola wewnętrznego do tabeli nie ujawni go kontrahentowi, dopóki nikt nie doda go świadomie do widoku.

CREATE VIEW portal.vw_ClaimForCustomer AS
SELECT c.ClaimNo, c.CustomerId, m.NameForCustomer AS Status, c.RegisteredAt
FROM dbo.Claim AS c
JOIN dbo.CustomerStatusMap AS m ON m.StatusId = c.StatusId AND m.Visible = 1;

W portalu magazynowym kontrahent dostaje powiadomienie e-mail, gdy zgłoszenie trafia do realizacji, gdy towar zostaje skompletowany i gdy dokument WZ jest gotowy do pobrania. Adres pochodzi z konta użytkownika, więc wiadomość trafia do osoby, która złożyła zamówienie, a nie do wspólnej skrzynki firmy.

Powiadomienia wynikają ze zmian statusów. Zmiana statusu dopisuje wiersz do kolejki w tej samej transakcji, a osobny proces wysyła wiadomość. Awaria serwera poczty nie blokuje wtedy pracy magazynu, bo niewysłana wiadomość czeka w kolejce. Wiadomość zawiera odsyłacz do portalu, a nie dane sprawy i nie załączniki, więc błędnie zaadresowana poczta nie ujawnia informacji. Ten schemat rozwija artykuł o reklamacjach online.

API i EDI jako uzupełnienie formularza w portalu

Kontrahent obsługujący duży wolumen nie będzie wpisywał każdego zamówienia w formularzu. Dla takich firm portal uzupełnia się interfejsem API albo wymianą komunikatów EDI, a formularz w przeglądarce zostaje rozwiązaniem zapasowym i narzędziem dla mniejszych partnerów. W module awizacji administrator zarządza kluczami API do integracji.

await fetch('/api/awizacje', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': apiKey,
    'Idempotency-Key': crypto.randomUUID()
  },
  body: JSON.stringify(awizacja)
});

Klucz API powinien podlegać tym samym regułom co konto użytkownika: identyfikować jednego kontrahenta i mieć zawężony zakres operacji. Nagłówek z kluczem idempotencji sprawia, że ponowna wysyłka po przerwaniu połączenia nie tworzy drugiej awizacji. Ogólne zasady wymiany komunikatów z ERP opisuje artykuł o integracji WMS z ERP, a wersję internetową programu magazynowego przedstawia tekst o internetowym magazynie.

Interfejs wymaga też limitu liczby żądań na klucz. Błąd w systemie kontrahenta, na przykład pętla ponawiająca wysyłkę bez przerwy, potrafi wygenerować tysiące wywołań na minutę i spowolnić portal pozostałym firmom. Po przekroczeniu limitu serwer zwraca kod 429 z nagłówkiem Retry-After, a nadawca wznawia wysyłkę po podanym czasie.

Komunikacja z kontrahentami zagranicznymi

Portal obsługuje użytkowników, którzy nie znają języka zespołu magazynu. Wersja językowa interfejsu i komunikaty w języku kierowcy skracają liczbę pomyłek przy zgłoszeniach, co opisuje artykuł o wielojęzycznej komunikacji w VSS. Produktowe opisy modułów znajdują się w serwisach awizacje.softwarestudio.com.pl oraz rma.softwarestudio.com.pl.

Testy izolacji przed uruchomieniem

Izolację danych sprawdza się testem, a nie deklaracją. Tworzy się dwa konta testowe należące do różnych kontrahentów i próbuje z konta pierwszego otworzyć rekord drugiego, podając jego numer w adresie. Poprawna odpowiedź to brak wyniku, a nie komunikat o braku uprawnień, bo drugi wariant potwierdza istnienie rekordu.

GET /api/awizacje/1042 HTTP/1.1
Cookie: sid=sesja-kontrahenta-A

HTTP/1.1 404 Not Found

Drugi test dotyczy ról. Konto z uprawnieniem tylko do odczytu próbuje wysłać zamówienie i powinno dostać odmowę z kodem 403. Oba testy powtarza się po każdej zmianie w portalu, bo nowy ekran albo nowy punkt końcowy najłatwiej pomija filtr kontrahenta.

Wdrożenie portalu zaczyna się od spisu kontrahentów i przypisania im ról oraz zakresów danych, a dopiero potem od konfiguracji kont. Kolejność odwrotna kończy się kontami o zbyt szerokim dostępie, których nikt później nie porządkuje.