diff --git a/cmd/ntech/main.go b/cmd/ntech/main.go index 77c51cd..ae6bdbe 100644 --- a/cmd/ntech/main.go +++ b/cmd/ntech/main.go @@ -189,6 +189,13 @@ func main() { r.Group(func(r chi.Router) { r.Use(ntechmw.RequireAuth(db, totpKljuc)) + // doz vraća middleware koji na ruteru zahteva datu dozvolu za mutirajuću + // rutu (403 ako uloga nema dozvolu). Ruter je time garantovani sloj zaštite + // — zaboravljena provera u handleru ne ostavlja endpoint nezaštićenim. + doz := func(akcija string) func(http.Handler) http.Handler { + return ntechmw.RequireDozvolaMut(h.DozvoleRepo.ImaDozvolu, akcija) + } + r.Get("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/dashboard", http.StatusFound) }) @@ -197,56 +204,56 @@ func main() { r.Get("/admin/podesavanja/opste", h.PodesavanjaOpste) r.Get("/admin/podesavanja/izgled", h.PodesavanjaIzgled) r.Get("/admin/podesavanja/sistem", h.PodesavanjaSistem) - r.Post("/podesavanja/sacuvaj", h.SacuvajPodesavanja) - r.Post("/podesavanja/logo", h.OtpremiLogo) - r.Post("/podesavanja/login-pozadina", h.OtpremiLoginPozadinu) - r.Post("/podesavanja/login-pozadina/ukloni", h.UkloniLoginPozadinu) - r.Post("/podesavanja/login-pozadina/stilovi", h.SacuvajLoginPozadinaStilove) + r.With(doz("podesavanja.izmeni")).Post("/podesavanja/sacuvaj", h.SacuvajPodesavanja) + r.With(doz("podesavanja.izmeni")).Post("/podesavanja/logo", h.OtpremiLogo) + r.With(doz("podesavanja.login_pozadina")).Post("/podesavanja/login-pozadina", h.OtpremiLoginPozadinu) + r.With(doz("podesavanja.login_pozadina")).Post("/podesavanja/login-pozadina/ukloni", h.UkloniLoginPozadinu) + r.With(doz("podesavanja.login_pozadina")).Post("/podesavanja/login-pozadina/stilovi", h.SacuvajLoginPozadinaStilove) r.Get("/podesavanja/backup", h.BackupBaze) - r.Post("/podesavanja/backup/vrati", h.VratiBackup) + r.With(doz("backup.pokreni")).Post("/podesavanja/backup/vrati", h.VratiBackup) r.Get("/magacin", h.Magacin) r.Get("/magacin/novi", h.NoviArtikal) - r.Post("/magacin/novi", h.SacuvajArtikal) + r.With(doz("artikal.dodaj")).Post("/magacin/novi", h.SacuvajArtikal) r.Get("/magacin/izmeni/{id}", h.IzmeniArtikal) - r.Post("/magacin/izmeni/{id}", h.SacuvajIzmenuArtikla) - r.Get("/magacin/obrisi/{id}", h.ObrisiArtikal) - r.Post("/magacin/premesti/{id}", h.PremestiArtikal) + r.With(doz("artikal.izmeni")).Post("/magacin/izmeni/{id}", h.SacuvajIzmenuArtikla) + r.With(doz("artikal.obrisi")).Get("/magacin/obrisi/{id}", h.ObrisiArtikal) + r.With(doz("artikal.premesti")).Post("/magacin/premesti/{id}", h.PremestiArtikal) r.Get("/magacin/kategorije", h.Kategorije) - r.Post("/magacin/kategorije/dodaj", h.DodajKategoriju) - r.Get("/magacin/kategorije/obrisi/{id}", h.ObrisiKategoriju) + r.With(doz("kategorija.dodaj")).Post("/magacin/kategorije/dodaj", h.DodajKategoriju) + r.With(doz("kategorija.obrisi")).Get("/magacin/kategorije/obrisi/{id}", h.ObrisiKategoriju) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke", h.Nabavke) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke/nova", h.NovaNabavka) - r.Post("/nabavke/nova", h.SacuvajNabavku) + r.With(doz("nabavka.dodaj")).Post("/nabavke/nova", h.SacuvajNabavku) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke/{id}", h.DetaljiNabavke) - r.Post("/nabavke/obrisi/{id}", h.ObrisiNabavku) + r.With(doz("nabavka.obrisi")).Post("/nabavke/obrisi/{id}", h.ObrisiNabavku) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "dobavljac.pregled")).Get("/dobavljaci", h.Dobavljaci) r.Get("/dobavljaci/novi", h.NoviDobavljac) - r.Post("/dobavljaci/novi", h.SacuvajDobavljaca) + r.With(doz("dobavljac.dodaj")).Post("/dobavljaci/novi", h.SacuvajDobavljaca) r.Get("/dobavljaci/izmeni/{id}", h.IzmeniDobavljaca) - r.Post("/dobavljaci/izmeni/{id}", h.SacuvajIzmeneDobavljaca) - r.Post("/dobavljaci/obrisi/{id}", h.ObrisiDobavljaca) + r.With(doz("dobavljac.izmeni")).Post("/dobavljaci/izmeni/{id}", h.SacuvajIzmeneDobavljaca) + r.With(doz("dobavljac.obrisi")).Post("/dobavljaci/obrisi/{id}", h.ObrisiDobavljaca) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "klijent.pregled")).Get("/klijenti", h.Klijenti) r.Get("/klijenti/novi", h.NoviKlijent) - r.Post("/klijenti/novi", h.SacuvajKlijenta) + r.With(doz("klijent.dodaj")).Post("/klijenti/novi", h.SacuvajKlijenta) r.Get("/klijenti/izmeni/{id}", h.IzmeniKlijenta) - r.Post("/klijenti/izmeni/{id}", h.SacuvajIzmenuKlijenta) - r.Post("/klijenti/obrisi/{id}", h.ObrisiKlijenta) + r.With(doz("klijent.izmeni")).Post("/klijenti/izmeni/{id}", h.SacuvajIzmenuKlijenta) + r.With(doz("klijent.obrisi")).Post("/klijenti/obrisi/{id}", h.ObrisiKlijenta) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis", h.Servis) r.Get("/servis/novi", h.NoviNalog) - r.Post("/servis/novi", h.SacuvajNalog) + r.With(doz("servis.dodaj")).Post("/servis/novi", h.SacuvajNalog) r.Get("/servis/izmeni/{id}", h.IzmeniNalog) - r.Post("/servis/izmeni/{id}", h.SacuvajIzmenaNaloga) - r.Post("/servis/obrisi/{id}", h.ObrisiNalog) + r.With(doz("servis.izmeni")).Post("/servis/izmeni/{id}", h.SacuvajIzmenaNaloga) + r.With(doz("servis.obrisi")).Post("/servis/obrisi/{id}", h.ObrisiNalog) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}", h.DetaljiNaloga) - r.Post("/servis/{id}/delovi", h.DodajDeloNalogu) - r.Post("/servis/{id}/delovi/{deo_id}/obrisi", h.ObrisiDeloNaloga) + r.With(doz("servis.izmeni")).Post("/servis/{id}/delovi", h.DodajDeloNalogu) + r.With(doz("servis.izmeni")).Post("/servis/{id}/delovi/{deo_id}/obrisi", h.ObrisiDeloNaloga) r.Get("/izvestaji", h.Izvestaji) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja", h.Prodaja) r.Get("/prodaja/nova", h.NovaProdaja) - r.Post("/prodaja/nova", h.SacuvajProdaju) - r.Post("/prodaja/obrisi/{id}", h.ObrisiProdaju) - r.Post("/prodaja/storno/{id}", h.StornoProdaje) + r.With(doz("prodaja.dodaj")).Post("/prodaja/nova", h.SacuvajProdaju) + r.With(doz("prodaja.obrisi")).Post("/prodaja/obrisi/{id}", h.ObrisiProdaju) + r.With(doz("prodaja.storno")).Post("/prodaja/storno/{id}", h.StornoProdaje) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}/stampa", h.StampaProdaje) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}", h.DetaljiProdaje) @@ -284,11 +291,11 @@ func main() { r.Get("/admin/profil/totp/pokreni", h.AdminTotpPokreni) r.Post("/admin/profil/totp/aktiviraj", h.AdminTotpAktivacija) r.Post("/admin/profil/totp/deaktiviraj", h.AdminTotpDeaktivacija) - r.Post("/profil/tema", h.SacuvajLokalnuTemu) + r.With(doz("tema.lokalno")).Post("/profil/tema", h.SacuvajLokalnuTemu) r.Get("/profil/tema", h.ProfilTema) - r.Post("/profil/pozadina", h.ProfilOtpremiPozadinu) - r.Post("/profil/pozadina/ukloni", h.ProfilUkloniPozadinu) - r.Post("/profil/pozadina/stilovi", h.ProfilSacuvajPozadinuStilove) + r.With(doz("tema.lokalno")).Post("/profil/pozadina", h.ProfilOtpremiPozadinu) + r.With(doz("tema.lokalno")).Post("/profil/pozadina/ukloni", h.ProfilUkloniPozadinu) + r.With(doz("tema.lokalno")).Post("/profil/pozadina/stilovi", h.ProfilSacuvajPozadinuStilove) }) log.Printf("NTech pokrenut na portu %s", port) diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 416ae03..bf84384 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -128,6 +128,24 @@ func RequireDozvola(proveri func(ctx context.Context, uloga, akcija string) bool } } +// RequireDozvolaMut je kao RequireDozvola, ali namenjen mutirajućim rutama +// (POST i akcije brisanja). Na odbijanje vraća 403 sa srpskom porukom umesto +// redirecta — isto kao handler.zahtevajDozvolu, pa ne kvari AJAX/fetch pozive. +// Postavljanjem ove provere na ruteru, zaštita mutacije više ne zavisi od toga +// da li je programer dodao proveru u samom handleru. +func RequireDozvolaMut(proveri func(ctx context.Context, uloga, akcija string) bool, akcija string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + k := KorisnikIzKonteksta(r.Context()) + if k == nil || !proveri(r.Context(), k.Uloga, akcija) { + http.Error(w, "Nemate dozvolu za ovu akciju.", http.StatusForbidden) + return + } + next.ServeHTTP(w, r) + }) + } +} + // postaviFlashGresku upisuje jednokratnu poruku o grešci u kolačić func postaviFlashGresku(w http.ResponseWriter, poruka string) { http.SetCookie(w, &http.Cookie{