Initial commit

This commit is contained in:
2026-09-03 10:39:26 +02:00
commit 496e8bdb27
85 changed files with 8134 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
# Formularze SoftSPM - konfiguracja i uruchomienie
Wszystkie formularze strony (newsletter PL, kontakt EN, zgłoszenie na szkolenie,
rozmowa konsultingowa) wysyłają dane przez [send.php](send.php), który dopiero
woła `https://api.softspm.pl/send_email_api.php` zgodnie z
[doc/manual_send_email.html](../doc/manual_send_email.html).
**Dlaczego przez pośrednika, a nie prosto z przeglądarki:** endpoint autoryzuje
się kluczem API w nagłówku `Authorization`. Klucz wysłany z JavaScriptu byłby
widoczny w źródle strony dla każdego, kto naciśnie Ctrl+U - i każdy mógłby nim
wysyłać maile na dowolny adres. Klucz zostaje na serwerze; przeglądarka rozmawia
wyłącznie z `send.php`.
Wymagania: **PHP 7.4 lub nowszy**, rozszerzenie `curl` (jest fallback na
`file_get_contents`, ale cURL jest zalecany).
---
## 1. Co dopisać w `/etc/hydronexis/config.php`
`api/config.php` czyta ten plik automatycznie. Obsługiwane są dwa zapisy -
wybierz ten, który pasuje do tego, co już masz w pliku.
### Wariant A - plik definiuje stałe (najczęstszy)
```php
// --- formularze softspm.com -------------------------------------------
define('SOFTSPM_MAIL_API_KEY', 'tutaj_klucz_do_send_email_api');
define('SOFTSPM_FORM_SECRET', 'tutaj_losowy_ciag_min_32_znaki');
define('SOFTSPM_FORM_RECIPIENT', 'pzators@go2.pl');
```
### Wariant B - plik zwraca tablicę (`return [...]`)
```php
return [
// ... to, co już tam jest ...
// --- formularze softspm.com ---------------------------------------
'softspm_mail_api_key' => 'tutaj_klucz_do_send_email_api',
'softspm_form_secret' => 'tutaj_losowy_ciag_min_32_znaki',
'softspm_form_recipient' => 'pzators@go2.pl',
];
```
### Wpisy opcjonalne
```php
// Domeny, z których wolno wysyłać formularze. Bez tego wpisu obowiązuje
// lista z api/config.php (softspm.com, www.softspm.com, localhost).
define('SOFTSPM_FORM_HOSTS', ['softspm.com', 'www.softspm.com']);
// Osobna skrzynka dla wybranego formularza. Identyfikatory formularzy:
// newsletter, newsletter-en, szkolenia, konsulting
define('SOFTSPM_FORM_RECIPIENTS', ['szkolenia' => 'szkolenia@softspm.com']);
```
### Skąd wziąć `SOFTSPM_FORM_SECRET`
To nie jest klucz do niczego zewnętrznego - to sekret, którym `send.php`
podpisuje własne tokeny antybotowe. Wygeneruj dowolny losowy ciąg:
```bash
php -r "echo bin2hex(random_bytes(32)), PHP_EOL;"
```
Zmiana tej wartości unieważnia formularze otwarte w tej chwili w
przeglądarkach; nic poza tym się nie dzieje.
### Uwagi
- Plik musi być czytelny dla użytkownika, na którym działa PHP (zwykle
`www-data`), i **nieczytelny dla świata**: `chmod 640`, właściciel
`root:www-data`.
- `api/config.php` robi na tym pliku `require`, więc plik musi być poprawnym
PHP. Jeśli wykonuje jakieś operacje przy dołączeniu (łączenie z bazą i tym
podobne), wykonają się także przy wysyłce formularza.
- Ścieżkę można zmienić zmienną środowiskową `SOFTSPM_SHARED_CONFIG`.
## 2. Kolejność szukania ustawień
Wygrywa pierwsze znalezione:
1. zmienne środowiskowe `SOFTSPM_MAIL_API_KEY`, `SOFTSPM_FORM_SECRET`
2. `/etc/hydronexis/config.php` (stałe albo klucze tablicy - patrz wyżej)
3. `api/config.local.php` - tylko do pracy lokalnej, wzór w
[config.local.sample.php](config.local.sample.php)
Adresat (`recipient`) i lista domen działają tak samo, z tym że wartość
z `/etc/hydronexis/config.php` nadpisuje domyślną z `api/config.php`.
## 3. Katalog roboczy
`send.php` zakłada przy pierwszym uruchomieniu `api/storage/` na zużyte tokeny,
liczniki adresów IP i log. Katalog dostaje `.htaccess` z `Require all denied`.
Na serwerze innym niż Apache **zablokuj ten katalog samodzielnie** - nie
powinien być dostępny z zewnątrz. Musi być zapisywalny dla PHP:
```bash
mkdir -p /sciezka/do/strony/api/storage
chown www-data:www-data /sciezka/do/strony/api/storage
chmod 750 /sciezka/do/strony/api/storage
```
W logu (`api/storage/log/RRRR-MM.log`) zapisujemy tylko fakt wysyłki, nazwę
formularza i skrót adresu IP - bez treści zgłoszeń i bez danych osobowych.
## 4. Format wiadomości: czysty tekst, nie HTML
`send_email_api.php` **sanityzuje treść** przed osadzeniem jej we własnym
szablonie (manual, punkt 7). Znaczniki wysłane z `send.php` docierają więc do
skrzynki jako widoczny tekst `<p style="...">`. Dlatego wiadomości budujemy
z czystego tekstu i **nie wolno tu wstawiać HTML-a**.
Układ jest odporny na to, czy szablon po drugiej stronie zamienia znaki nowej
linii na `<br>`, czy nie: każda pozycja to osobna linia `Etykieta: wartość`,
która zostaje czytelna nawet po sklejeniu w jeden akapit.
```
Nowe zgłoszenie ze strony softspm.com
Formularz: Zgłoszenie - szkolenia i webinaria
Odpowiedz na: jan.kowalski@pwik.example.pl
--- DANE Z FORMULARZA ---
Imię i nazwisko: Jan Kowalski
Przedsiębiorstwo: PWiK Testowo
E-mail: jan.kowalski@pwik.example.pl
Telefon: 600 100 200
Interesujący temat: Prawo wodne i wniosek taryfowy
Wiadomość:
Sześć osób z działu rozliczeń.
Najlepiej pierwszy kwartał.
--- ZGODA UDZIELONA PRZY WYSYŁCE ---
Wyrażam zgodę na przetwarzanie moich danych osobowych przez SoftSPM (...)
--- METRYKA ---
Strona: https://softspm.com/szkolenia.html#zgloszenie
Data (UTC): 2026-09-01 09:12:33
Adres IP: 203.0.113.7
```
Jeśli w otrzymanym mailu linie nadal są sklejone w jeden akapit, znaczy to, że
szablon endpointu nie zamienia `\n` na `<br>`. `send_email_api.php` jest wasz,
więc najczystszą poprawką jest tam jedno `nl2br()` na sanityzowanej treści -
nic po stronie tej strony nie wymaga wtedy zmian.
## 5. Zabezpieczenia przed botami
Suwak sam z siebie niczego nie gwarantuje - jest elementem interfejsu, a nie
kryptografią. Blokady, które faktycznie odsiewają automaty, działają na
serwerze i składają się na całość:
| Warstwa | Co robi |
|---|---|
| Token podpisany HMAC | Wysyłka wymaga tokenu wydanego wcześniej przez `send.php`. Skrypt musi wykonać dwa dodatkowe żądania, nie jeden POST. |
| Potwierdzenie suwakiem | Osobne, podpisane potwierdzenie wydawane dopiero po przesunięciu suwaka; musi mieć ten sam identyfikator co token. |
| Jednorazowość | Każdy token przechodzi raz. Powtórka tego samego żądania dostaje HTTP 409. |
| Bramka czasowa | Minimum 3 sekundy między pierwszym dotknięciem formularza a wysyłką (`min_fill_seconds`). Automat robi to w ułamku sekundy. |
| Pole-pułapka | Ukryte pole `firma_www`. Wypełnione = bot. Odpowiadamy sukcesem, żeby nie podpowiadać, co go zdradziło. |
| Kontrola pochodzenia | `Origin`/`Referer` musi wskazywać na domenę z listy. |
| Limit adresu IP | 5 zgłoszeń na godzinę, 20 na dobę (`rate_limit` w `config.php`). |
| Stały adresat | Adres odbiorcy pochodzi wyłącznie z konfiguracji serwera, nigdy z formularza - endpoint nie jest przekaźnikiem spamu. |
Jeśli mimo to zacznie przechodzić spam, następnym krokiem jest Cloudflare
Turnstile albo hCaptcha - obie działają na tej samej zasadzie co obecny token,
więc podmiana dotknie tylko `send.php` i `assets/forms.js`.
## 6. Test po wdrożeniu
```bash
# 1. Token - powinien zwrócić {"ok":true,"token":"t.szkolenia...."}
curl -s -H "Referer: https://softspm.com/szkolenia.html" \
"https://softspm.com/api/send.php?action=token&form=szkolenia"
# 2. Bez nagłówka Referer/Origin - powinno być HTTP 403
curl -s -o /dev/null -w '%{http_code}\n' \
"https://softspm.com/api/send.php?action=token&form=szkolenia"
# 3. GET na nieznany formularz - HTTP 400
curl -s -o /dev/null -w '%{http_code}\n' -H "Referer: https://softspm.com/" \
"https://softspm.com/api/send.php?action=token&form=cokolwiek"
# 4. POST bez tokenu - HTTP 400 (bad_token)
curl -s -H "Content-Type: application/json" -H "Origin: https://softspm.com" \
-d '{"form":"szkolenia","zgoda":true,"imie":"Test"}' \
"https://softspm.com/api/send.php"
```
Jeśli krok 1 zwraca `{"error":"not_configured"}`, PHP nie widzi klucza albo
sekretu - sprawdź uprawnienia `/etc/hydronexis/config.php`.
Test całości: otwórz formularz w przeglądarce, wypełnij, przesuń suwak,
zaznacz zgodę i wyślij. Wiadomość powinna dojść na adres z
`SOFTSPM_FORM_RECIPIENT`, a w `api/storage/log/` pojawi się wpis `sent`.