From ffbcbd6fd7c1fa235bc36b973825d0b20bcc4091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Sun, 5 Jul 2026 03:16:27 +0200 Subject: [PATCH] Fiskalizacija: SSRF/DNS test veze, DEMO prefiks verzije, servis fiskalni prazna stranica, Fisk BE_HOST/VERIFY_HOST docker fix --- Fisk/server.py | 2 +- Readme.md | 13 +++++++++++++ Readme_sr.md | 13 +++++++++++++ cmd/ntech/main.go | 2 +- docker-compose.fisk.yml | 6 ++++++ internal/handler/podesavanja.go | 19 +++++++++++++++++-- web/templates/stranice/servis_detalji.html | 15 ++++++++++++--- 7 files changed, 63 insertions(+), 7 deletions(-) diff --git a/Fisk/server.py b/Fisk/server.py index 64fcced..847ca38 100755 --- a/Fisk/server.py +++ b/Fisk/server.py @@ -392,7 +392,7 @@ def _build_invoice_response(req, request_id): json.dumps(vl_payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8") ).decode("ascii") scheme = "https" if VERIFY_HOST.startswith("https://") else "http" - host = VERIFY_HOST.removeprefix("https://").removeprefix("http://") + host = VERIFY_HOST.removeprefix("https://").removeprefix("http://").rstrip("/") verification_url = f"{scheme}://{host}/v/?vl={urllib.parse.quote(vl, safe='')}" else: verification_url = f"https://sandbox.suf.purs.gov.rs/v/?vl={invoice_number}" diff --git a/Readme.md b/Readme.md index f98bfc0..6c05f83 100644 --- a/Readme.md +++ b/Readme.md @@ -253,6 +253,13 @@ services: image: ghcr.io/dalibor31/ntech-fisk:latest container_name: teron_mock restart: unless-stopped + environment: + - BE_HOST=ntech # NTech service name on the shared network — required so the + - BE_PORT=4567 # mock can read company data (name/PIB/address) from the card emulator + - VERIFY_HOST=https://ntech.your-domain.com # takes precedence over the "verify_host" setting + # in NTech's UI (the mock can't see ntech.db) — + # without it the QR links to sandbox.suf.purs.gov.rs + # and is sparse instead of encoding the full invoice volumes: - teron-data:/app/data networks: @@ -267,6 +274,8 @@ networks: The `teron-mock` service is reachable from `ntech` at `http://teron-mock:4566` over the internal Docker network — the port is not exposed to the host. +`BE_HOST`/`BE_PORT` must point to the NTech container's card emulator (`internal/be`, TCP port 4567). Without them, `teron-mock` defaults to `127.0.0.1:4567`, which inside its own container never reaches NTech — the mock then silently falls back to placeholder company data ("Test Company DOO", TIN `RS000000000`) instead of your real business profile. + To run as a standalone Docker container: ```yaml @@ -276,6 +285,10 @@ services: image: ghcr.io/dalibor31/ntech-fisk:latest container_name: teron_mock restart: unless-stopped + environment: + - BE_HOST=ntech # adjust to the NTech container's name/hostname on this network + - BE_PORT=4567 + - VERIFY_HOST=https://ntech.your-domain.com ports: - "4566:4566" volumes: diff --git a/Readme_sr.md b/Readme_sr.md index 87d011e..17b98bc 100644 --- a/Readme_sr.md +++ b/Readme_sr.md @@ -253,6 +253,13 @@ services: image: ghcr.io/dalibor31/ntech-fisk:latest container_name: teron_mock restart: unless-stopped + environment: + - BE_HOST=ntech # naziv NTech servisa na deljenoj mreži — neophodno da bi mock + - BE_PORT=4567 # mogao da pročita podatke firme (naziv/PIB/adresa) sa kartica emulatora + - VERIFY_HOST=https://ntech.tvoja-firma.rs # ima prednost nad podešavanjem "verify_host" u + # NTech UI-ju (mock ne vidi ntech.db) — bez ovoga + # QR vodi na sandbox.suf.purs.gov.rs i redak je + # umesto da enkoduje ceo račun volumes: - teron-data:/app/data networks: @@ -267,6 +274,8 @@ networks: Servis `teron-mock` je dostupan iz `ntech` kontejnera na adresi `http://teron-mock:4566` preko interne Docker mreže — port nije izložen spolja. +`BE_HOST`/`BE_PORT` moraju pokazivati na kartica emulator NTech kontejnera (`internal/be`, TCP port 4567). Bez toga, `teron-mock` po defaultu koristi `127.0.0.1:4567`, što unutar sopstvenog kontejnera nikad ne stiže do NTech-a — mock onda tiho pada na test podatke firme ("Test Company DOO", PIB `RS000000000`) umesto tvog stvarnog profila firme. + Za pokretanje kao samostalni Docker kontejner: ```yaml @@ -276,6 +285,10 @@ services: image: ghcr.io/dalibor31/ntech-fisk:latest container_name: teron_mock restart: unless-stopped + environment: + - BE_HOST=ntech # prilagodi imenu/hostname-u NTech kontejnera na ovoj mreži + - BE_PORT=4567 + - VERIFY_HOST=https://ntech.tvoja-firma.rs ports: - "4566:4566" volumes: diff --git a/cmd/ntech/main.go b/cmd/ntech/main.go index 2252034..f1d1f94 100644 --- a/cmd/ntech/main.go +++ b/cmd/ntech/main.go @@ -151,7 +151,7 @@ func main() { h.Verzija = Verzija h.JelDemo = os.Getenv("NTECH_ENV") == "demo" if h.JelDemo { - h.Verzija = "DEMO verzija" + h.Verzija = "DEMO - " + h.Verzija if err := postaviDemoKorisnika(context.Background(), h.KorisniciRepo); err != nil { slog.Warn("demo: greška pri postavljanju demo korisnika", "error", err) } diff --git a/docker-compose.fisk.yml b/docker-compose.fisk.yml index 675f346..bdffd96 100644 --- a/docker-compose.fisk.yml +++ b/docker-compose.fisk.yml @@ -3,6 +3,12 @@ services: image: ghcr.io/dalibor31/ntech-fisk:latest container_name: teron_mock restart: unless-stopped + environment: + - BE_HOST=ntech # naziv NTech servisa na ntech-net mreži — bez ovoga mock ne + - BE_PORT=4567 # može da pročita firmu sa kartica emulatora (pada na test podatke) + - VERIFY_HOST=https://ntech.tvoja-firma.rs # ima prednost nad "Podešavanja → Fiskalizacija" + # u NTech UI-ju (mock ne vidi ntech.db); bez ovoga + # QR vodi na sandbox.suf.purs.gov.rs i redak je volumes: - teron-data:/app/data networks: diff --git a/internal/handler/podesavanja.go b/internal/handler/podesavanja.go index 05a58c7..fde4584 100644 --- a/internal/handler/podesavanja.go +++ b/internal/handler/podesavanja.go @@ -1125,10 +1125,25 @@ func jePrivatnaAdresa(hostname string) bool { if hostname == "localhost" { return true } - ip := net.ParseIP(hostname) - if ip == nil { + if ip := net.ParseIP(hostname); ip != nil { + return jePrivatnaIP(ip) + } + // Hostname (npr. Docker Compose service name kao "teron-mock") — razreši + // preko DNS-a i dozvoli samo ako SVE dobijene adrese spadaju u privatne + // opsege (localhost/127/10/172.16-31/192.168). + ips, err := net.LookupIP(hostname) + if err != nil || len(ips) == 0 { return false } + for _, ip := range ips { + if !jePrivatnaIP(ip) { + return false + } + } + return true +} + +func jePrivatnaIP(ip net.IP) bool { ip4 := ip.To4() if ip4 == nil { return false diff --git a/web/templates/stranice/servis_detalji.html b/web/templates/stranice/servis_detalji.html index 21c9e8a..f5f32e2 100644 --- a/web/templates/stranice/servis_detalji.html +++ b/web/templates/stranice/servis_detalji.html @@ -228,6 +228,15 @@ } var observer = new MutationObserver(function(){ observer.disconnect(); + // fiskalizacija je best-effort — ako ESIR/PFR nije bio dostupan, + // .fiskalni-racun kartica se neće pojaviti (v. FiskalGreska upozorenje + // na stranici); tada ne navigiramo tab na nepostojeći račun (praznina/404), + // nego prazan tab zatvorimo. + var imaRacun = sadrzaj.querySelector('.fiskalni-racun'); + if (!imaRacun) { + if (tab && !tab.closed) tab.close(); + return; + } // jedan prozor — server u istu stranicu ubacuje i povraćaj viška // avansa (ako postoji), da brauzer ne blokira drugi popup if (tab && !tab.closed) { @@ -1015,7 +1024,7 @@ {{if .FiskalniRacun}} -
+
{{if eq .FiskalniRacun.TipRacuna "Advance"}}Avansni fiskalni račun{{else}}Fiskalni račun{{end}} @@ -1046,7 +1055,7 @@ {{end}} {{if .AvansRacun}} -
+
Avansni fiskalni račun Štampaj kopiju @@ -1068,7 +1077,7 @@ {{end}} {{if .PovracajAvansa}} -
+
Povraćaj viška avansa Štampaj kopiju