Bezbednosni audit i refaktoring: HP popravke, RBAC, flash poruke, go:embed, CSP
This commit is contained in:
@@ -11,6 +11,33 @@ import (
|
||||
|
||||
type sqliteKorisniciRepo struct{ db *sql.DB }
|
||||
|
||||
// skeniraiKorisnika čita jedan red iz baze i popunjava model.Korisnik
|
||||
func skeniraiKorisnika(row interface{ Scan(...any) error }) (*model.Korisnik, error) {
|
||||
k := &model.Korisnik{}
|
||||
var aktivan, koristiLokalnuTemu int
|
||||
var lokalnaTema sql.NullString
|
||||
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
|
||||
var datumKreiranja time.Time
|
||||
if err := row.Scan(
|
||||
&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna,
|
||||
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
|
||||
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur,
|
||||
&lokalnaPozadinaBlurPozadine, &lokalnaPozadinaGlassOpacity,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.Aktivan = aktivan == 1
|
||||
k.LokalnaTema = lokalnaTema.String
|
||||
k.KoristiLokalnuTemu = koristiLokalnuTemu == 1
|
||||
k.DatumKreiranja = datumKreiranja
|
||||
k.LokalnaPozadina = lokalnaPozadina.String
|
||||
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
|
||||
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
|
||||
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
|
||||
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// NoviKorisniciRepo kreira SQLite implementaciju KorisniciRepository
|
||||
func NoviKorisniciRepo(db *sql.DB) *sqliteKorisniciRepo {
|
||||
return &sqliteKorisniciRepo{db: db}
|
||||
@@ -28,67 +55,32 @@ func (r *sqliteKorisniciRepo) Kreiraj(ctx context.Context, korisnickoIme, lozink
|
||||
}
|
||||
|
||||
func (r *sqliteKorisniciRepo) DohvatiPoImenu(ctx context.Context, korisnickoIme string) (*model.Korisnik, error) {
|
||||
k := &model.Korisnik{}
|
||||
var aktivan, koristiLokalnuTemu int
|
||||
var totpTajna, lokalnaTema sql.NullString
|
||||
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
|
||||
var datumKreiranja time.Time
|
||||
err := r.db.QueryRowContext(ctx,
|
||||
row := r.db.QueryRowContext(ctx,
|
||||
`SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''),
|
||||
COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja,
|
||||
COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'),
|
||||
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'),
|
||||
COALESCE(lokalna_pozadina_glass_opacity, '10')
|
||||
FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme).
|
||||
Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &totpTajna,
|
||||
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
|
||||
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine,
|
||||
&lokalnaPozadinaGlassOpacity)
|
||||
FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme)
|
||||
k, err := skeniraiKorisnika(row)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: korisnici.DohvatiPoImenu: %w", err)
|
||||
}
|
||||
k.Aktivan = aktivan == 1
|
||||
k.TotpTajna = totpTajna.String
|
||||
k.LokalnaTema = lokalnaTema.String
|
||||
k.KoristiLokalnuTemu = koristiLokalnuTemu == 1
|
||||
k.DatumKreiranja = datumKreiranja
|
||||
k.LokalnaPozadina = lokalnaPozadina.String
|
||||
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
|
||||
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
|
||||
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
|
||||
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (r *sqliteKorisniciRepo) DohvatiPoID(ctx context.Context, id int64) (*model.Korisnik, error) {
|
||||
k := &model.Korisnik{}
|
||||
var aktivan, koristiLokalnuTemu int
|
||||
var lokalnaTema sql.NullString
|
||||
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
|
||||
var datumKreiranja time.Time
|
||||
err := r.db.QueryRowContext(ctx,
|
||||
row := r.db.QueryRowContext(ctx,
|
||||
`SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''),
|
||||
COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja,
|
||||
COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'),
|
||||
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'),
|
||||
COALESCE(lokalna_pozadina_glass_opacity, '10')
|
||||
FROM korisnici WHERE id = ?`, id).
|
||||
Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna,
|
||||
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
|
||||
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine,
|
||||
&lokalnaPozadinaGlassOpacity)
|
||||
FROM korisnici WHERE id = ?`, id)
|
||||
k, err := skeniraiKorisnika(row)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: korisnici.DohvatiPoID: %w", err)
|
||||
}
|
||||
k.Aktivan = aktivan == 1
|
||||
k.LokalnaTema = lokalnaTema.String
|
||||
k.KoristiLokalnuTemu = koristiLokalnuTemu == 1
|
||||
k.DatumKreiranja = datumKreiranja
|
||||
k.LokalnaPozadina = lokalnaPozadina.String
|
||||
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
|
||||
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
|
||||
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
|
||||
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
|
||||
return k, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -305,44 +305,6 @@ func (h *Handler) AdminProfil(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// AdminSacuvajLokalnuTemu čuva korisnikovu lokalnu temu
|
||||
func (h *Handler) AdminSacuvajLokalnuTemu(w http.ResponseWriter, r *http.Request) {
|
||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||
if k == nil {
|
||||
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "tema.lokalno") {
|
||||
http.Error(w, "Pristup odbijen", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.")
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
koristi := r.FormValue("koristi_lokalnu_temu") == "1"
|
||||
lokalnaTema := r.FormValue("lokalna_tema")
|
||||
if lokalnaTema != "tamna" && lokalnaTema != "svetla" {
|
||||
lokalnaTema = "tamna"
|
||||
}
|
||||
|
||||
if err := h.KorisniciRepo.SacuvajLokalnuTemu(r.Context(), k.ID, lokalnaTema, koristi); err != nil {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.SetFlash(w, r, h.DB, "uspeh", "Tema je sačuvana.")
|
||||
// vrati korisnika na stranicu odakle je došao (Referer), ili na profil kao fallback
|
||||
if ref := r.Referer(); ref != "" {
|
||||
http.Redirect(w, r, ref, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// AdminPromeniLozinku menja lozinku prijavljenog korisnika
|
||||
func (h *Handler) AdminPromeniLozinku(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -70,7 +70,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||
if korisnikDash.Uloga == "radnik" {
|
||||
korisnikFilter.KorisnikID = &korisnikDash.ID
|
||||
}
|
||||
if n, err := h.PodsetniciFRepo.BrojAktivnih(ctx, korisnikFilter); err != nil {
|
||||
if n, err := h.PodsetnikRepo.BrojAktivnih(ctx, korisnikFilter); err != nil {
|
||||
log.Printf("dashboard: aktivni podsetnici: %v", err)
|
||||
} else {
|
||||
aktivniPodsetnici = n
|
||||
|
||||
@@ -25,7 +25,7 @@ type Handler struct {
|
||||
ProdajaRepo db.ProdajaRepository
|
||||
KorisniciRepo db.KorisniciRepository
|
||||
SesijeRepo db.SesijeRepository
|
||||
PodsetniciFRepo db.PodsetnikRepository
|
||||
PodsetnikRepo db.PodsetnikRepository
|
||||
PokusajiRepo db.PokusajiPrijaveRepository
|
||||
LoginIstorijsaRepo db.LoginIstorijsaRepository
|
||||
DozvoleRepo db.DozvoleRepository
|
||||
@@ -47,15 +47,15 @@ func Novi(baza *sql.DB) *Handler {
|
||||
ProdajaRepo: sqlite.NoviProdajaRepo(baza),
|
||||
KorisniciRepo: sqlite.NoviKorisniciRepo(baza),
|
||||
SesijeRepo: sqlite.NoviSesijeRepo(baza),
|
||||
PodsetniciFRepo: sqlite.NoviPodsetnikRepo(baza),
|
||||
PodsetnikRepo: sqlite.NoviPodsetnikRepo(baza),
|
||||
PokusajiRepo: sqlite.NoviPokusajiPrijaveRepo(baza),
|
||||
LoginIstorijsaRepo: sqlite.NoviLoginIstorijsaRepo(baza),
|
||||
DozvoleRepo: sqlite.NoviDozvoleRepo(baza, middleware.ImaDozvolu, middleware.SveAkcije()),
|
||||
}
|
||||
}
|
||||
|
||||
// reinicijalzijRepozitorijume zamenjuje sve repozitorijume posle obnove baze
|
||||
func (h *Handler) reinicijalzijRepozitorijume(novaDB *sql.DB) {
|
||||
// reinicijalizujRepozitorijume zamenjuje sve repozitorijume posle obnove baze
|
||||
func (h *Handler) reinicijalizujRepozitorijume(novaDB *sql.DB) {
|
||||
h.DB = novaDB
|
||||
h.Artikli = sqlite.NoviArtikalRepo(novaDB)
|
||||
h.KategorijeRepo = sqlite.NovaKategorijaRepo(novaDB)
|
||||
@@ -66,7 +66,7 @@ func (h *Handler) reinicijalzijRepozitorijume(novaDB *sql.DB) {
|
||||
h.ProdajaRepo = sqlite.NoviProdajaRepo(novaDB)
|
||||
h.KorisniciRepo = sqlite.NoviKorisniciRepo(novaDB)
|
||||
h.SesijeRepo = sqlite.NoviSesijeRepo(novaDB)
|
||||
h.PodsetniciFRepo = sqlite.NoviPodsetnikRepo(novaDB)
|
||||
h.PodsetnikRepo = sqlite.NoviPodsetnikRepo(novaDB)
|
||||
h.PokusajiRepo = sqlite.NoviPokusajiPrijaveRepo(novaDB)
|
||||
h.LoginIstorijsaRepo = sqlite.NoviLoginIstorijsaRepo(novaDB)
|
||||
h.DozvoleRepo = sqlite.NoviDozvoleRepo(novaDB, middleware.ImaDozvolu, middleware.SveAkcije())
|
||||
|
||||
@@ -82,27 +82,11 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
Verzija: h.Verzija,
|
||||
LogoGreska: r.URL.Query().Get("logo_greska"),
|
||||
Backupi: ucitajListuBackupa(),
|
||||
LoginPozadina: podesavanja["login_pozadina"],
|
||||
LoginPozadinaOpacity: func() string {
|
||||
v := podesavanja["login_pozadina_opacity"]
|
||||
if v == "" { return "50" }
|
||||
return v
|
||||
}(),
|
||||
LoginPozadinaBlurPozadine: func() string {
|
||||
v := podesavanja["login_pozadina_blur_pozadine"]
|
||||
if v == "" { return "0" }
|
||||
return v
|
||||
}(),
|
||||
LoginPozadinaBlurKartice: func() string {
|
||||
v := podesavanja["login_pozadina_blur_kartice"]
|
||||
if v == "" { return "12" }
|
||||
return v
|
||||
}(),
|
||||
LoginPozadinaZatamnjenjeKartice: func() string {
|
||||
v := podesavanja["login_pozadina_zatamnjenje_kartice"]
|
||||
if v == "" { return "0" }
|
||||
return v
|
||||
}(),
|
||||
LoginPozadina: podesavanja["login_pozadina"],
|
||||
LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"),
|
||||
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
|
||||
LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"),
|
||||
LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"),
|
||||
}
|
||||
|
||||
h.renderujTemplate(w, "podesavanja", podaci)
|
||||
@@ -136,6 +120,14 @@ func ucitajListuBackupa() []BackupInfo {
|
||||
return lista
|
||||
}
|
||||
|
||||
// vrednostIliDefault vraća vrednost iz mape ako postoji i nije prazan string, inače vraća podrazumevanu vrednost
|
||||
func vrednostIliDefault(m map[string]string, kljuc, podrazumevano string) string {
|
||||
if v := m[kljuc]; v != "" {
|
||||
return v
|
||||
}
|
||||
return podrazumevano
|
||||
}
|
||||
|
||||
// VratiBackup zamenjuje trenutnu bazu sa izabranim backup fajlom
|
||||
func (h *Handler) VratiBackup(w http.ResponseWriter, r *http.Request) {
|
||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||
@@ -197,7 +189,7 @@ func (h *Handler) VratiBackup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
h.reinicijalzijRepozitorijume(novaDB)
|
||||
h.reinicijalizujRepozitorijume(novaDB)
|
||||
log.Printf("Baza uspešno obnovljena iz: %s", ime)
|
||||
|
||||
http.Redirect(w, r, "/podesavanja?sacuvano=vraceno", http.StatusSeeOther)
|
||||
@@ -594,31 +586,11 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
|
||||
Verzija: h.Verzija,
|
||||
LogoGreska: r.URL.Query().Get("logo_greska"),
|
||||
Backupi: ucitajListuBackupa(),
|
||||
LoginPozadina: podesavanja["login_pozadina"],
|
||||
LoginPozadinaOpacity: func() string {
|
||||
if v := podesavanja["login_pozadina_opacity"]; v != "" {
|
||||
return v
|
||||
}
|
||||
return "50"
|
||||
}(),
|
||||
LoginPozadinaBlurPozadine: func() string {
|
||||
if v := podesavanja["login_pozadina_blur_pozadine"]; v != "" {
|
||||
return v
|
||||
}
|
||||
return "0"
|
||||
}(),
|
||||
LoginPozadinaBlurKartice: func() string {
|
||||
if v := podesavanja["login_pozadina_blur_kartice"]; v != "" {
|
||||
return v
|
||||
}
|
||||
return "12"
|
||||
}(),
|
||||
LoginPozadinaZatamnjenjeKartice: func() string {
|
||||
if v := podesavanja["login_pozadina_zatamnjenje_kartice"]; v != "" {
|
||||
return v
|
||||
}
|
||||
return "0"
|
||||
}(),
|
||||
LoginPozadina: podesavanja["login_pozadina"],
|
||||
LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"),
|
||||
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
|
||||
LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"),
|
||||
LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ func (h *Handler) Podsetnici(w http.ResponseWriter, r *http.Request) {
|
||||
filter.KorisnikID = &k.ID
|
||||
}
|
||||
|
||||
lista, err := h.PodsetniciFRepo.Lista(r.Context(), filter)
|
||||
lista, err := h.PodsetnikRepo.Lista(r.Context(), filter)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podsetnika", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -88,7 +88,7 @@ func (h *Handler) NoviPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
ps.NaslovStranice = "Novi podsetnik"
|
||||
|
||||
var korisnici []model.Korisnik
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
if middleware.JeAdmin(k) {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
|
||||
@@ -110,32 +110,14 @@ func (h *Handler) SacuvajPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
podsetnik, greska := parseFormuPodsetnika(r, k)
|
||||
|
||||
prikaziGresku := func(poruka string) {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "podsetnici"
|
||||
ps.NaslovStranice = "Novi podsetnik"
|
||||
var korisnici []model.Korisnik
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
h.renderujFormuPodsetnika(w, podaciPodsetnikForma{
|
||||
PodaciStranice: ps,
|
||||
Podsetnik: podsetnik,
|
||||
Greska: poruka,
|
||||
Izmena: false,
|
||||
Korisnici: korisnici,
|
||||
})
|
||||
}
|
||||
|
||||
if greska != "" {
|
||||
prikaziGresku(greska)
|
||||
h.prikaziGreskuPodsetnika(w, r, k, podsetnik, greska, false)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.PodsetniciFRepo.Kreiraj(r.Context(), &podsetnik); err != nil {
|
||||
if _, err := h.PodsetnikRepo.Kreiraj(r.Context(), &podsetnik); err != nil {
|
||||
log.Printf("greška pri čuvanju podsetnika: %v", err)
|
||||
prikaziGresku("Došlo je do greške pri čuvanju. Pokušajte ponovo.")
|
||||
h.prikaziGreskuPodsetnika(w, r, k, podsetnik, "Došlo je do greške pri čuvanju. Pokušajte ponovo.", false)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -152,7 +134,7 @@ func (h *Handler) IzmeniPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
podsetnik, err := h.PodsetniciFRepo.DohvatiID(r.Context(), id)
|
||||
podsetnik, err := h.PodsetnikRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Podsetnik nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
@@ -169,7 +151,7 @@ func (h *Handler) IzmeniPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
ps.NaslovStranice = "Izmeni podsetnik"
|
||||
|
||||
var korisnici []model.Korisnik
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
if middleware.JeAdmin(k) {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
|
||||
@@ -199,32 +181,14 @@ func (h *Handler) SacuvajIzmenePodsetnika(w http.ResponseWriter, r *http.Request
|
||||
podsetnik, greska := parseFormuPodsetnika(r, k)
|
||||
podsetnik.ID = id
|
||||
|
||||
prikaziGresku := func(poruka string) {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "podsetnici"
|
||||
ps.NaslovStranice = "Izmeni podsetnik"
|
||||
var korisnici []model.Korisnik
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
h.renderujFormuPodsetnika(w, podaciPodsetnikForma{
|
||||
PodaciStranice: ps,
|
||||
Podsetnik: podsetnik,
|
||||
Greska: poruka,
|
||||
Izmena: true,
|
||||
Korisnici: korisnici,
|
||||
})
|
||||
}
|
||||
|
||||
if greska != "" {
|
||||
prikaziGresku(greska)
|
||||
h.prikaziGreskuPodsetnika(w, r, k, podsetnik, greska, true)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.PodsetniciFRepo.Izmeni(r.Context(), &podsetnik); err != nil {
|
||||
if err := h.PodsetnikRepo.Izmeni(r.Context(), &podsetnik); err != nil {
|
||||
log.Printf("greška pri čuvanju izmene podsetnika: %v", err)
|
||||
prikaziGresku("Došlo je do greške pri čuvanju. Pokušajte ponovo.")
|
||||
h.prikaziGreskuPodsetnika(w, r, k, podsetnik, "Došlo je do greške pri čuvanju. Pokušajte ponovo.", true)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -240,13 +204,13 @@ func (h *Handler) OznaciPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// učitamo trenutni status pa ga preokrenemo
|
||||
podsetnik, err := h.PodsetniciFRepo.DohvatiID(r.Context(), id)
|
||||
podsetnik, err := h.PodsetnikRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Podsetnik nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.PodsetniciFRepo.OznaciZavrsenim(r.Context(), id, !podsetnik.Zavrseno); err != nil {
|
||||
if err := h.PodsetnikRepo.OznaciZavrsenim(r.Context(), id, !podsetnik.Zavrseno); err != nil {
|
||||
http.Error(w, "Greška pri ažuriranju statusa", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -262,7 +226,7 @@ func (h *Handler) ObrisiPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.PodsetniciFRepo.Obrisi(r.Context(), id); err != nil {
|
||||
if err := h.PodsetnikRepo.Obrisi(r.Context(), id); err != nil {
|
||||
http.Error(w, "Greška pri brisanju podsetnika", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -295,7 +259,7 @@ func parseFormuPodsetnika(r *http.Request, k *model.Korisnik) (model.Podsetnik,
|
||||
}
|
||||
|
||||
// admin/superadmin mogu dodeliti podsetnik drugom korisniku
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
if middleware.JeAdmin(k) {
|
||||
if kidStr := strings.TrimSpace(r.FormValue("korisnik_id")); kidStr != "" {
|
||||
if kid, err := strconv.ParseInt(kidStr, 10, 64); err == nil && kid > 0 {
|
||||
p.KorisnikID = &kid
|
||||
@@ -309,6 +273,29 @@ func parseFormuPodsetnika(r *http.Request, k *model.Korisnik) (model.Podsetnik,
|
||||
return p, ""
|
||||
}
|
||||
|
||||
// prikaziGreskuPodsetnika prikazuje formu podsetnika sa porukom o grešci
|
||||
func (h *Handler) prikaziGreskuPodsetnika(w http.ResponseWriter, r *http.Request, k *model.Korisnik, podsetnik model.Podsetnik, poruka string, izmena bool) {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "podsetnici"
|
||||
if izmena {
|
||||
ps.NaslovStranice = "Izmeni podsetnik"
|
||||
} else {
|
||||
ps.NaslovStranice = "Novi podsetnik"
|
||||
}
|
||||
var korisnici []model.Korisnik
|
||||
if middleware.JeAdmin(k) {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
h.renderujFormuPodsetnika(w, podaciPodsetnikForma{
|
||||
PodaciStranice: ps,
|
||||
Podsetnik: podsetnik,
|
||||
Greska: poruka,
|
||||
Izmena: izmena,
|
||||
Korisnici: korisnici,
|
||||
})
|
||||
}
|
||||
|
||||
// renderujFormuPodsetnika renderuje HTML šablon forme za unos ili izmenu podsetnika
|
||||
func (h *Handler) renderujFormuPodsetnika(w http.ResponseWriter, podaci podaciPodsetnikForma) {
|
||||
h.renderujTemplate(w, "podsetnik_forma", podaci)
|
||||
|
||||
@@ -250,3 +250,42 @@ func (h *Handler) ProfilSacuvajPozadinuStilove(w http.ResponseWriter, r *http.Re
|
||||
middleware.SetFlash(w, r, h.DB, "uspeh", "Podešavanja su sačuvana.")
|
||||
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// SacuvajLokalnuTemu čuva korisnikovu lokalnu temu
|
||||
func (h *Handler) SacuvajLokalnuTemu(w http.ResponseWriter, r *http.Request) {
|
||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||
if k == nil {
|
||||
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "tema.lokalno") {
|
||||
http.Error(w, "Pristup odbijen", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.")
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
koristi := r.FormValue("koristi_lokalnu_temu") == "1"
|
||||
lokalnaTema := r.FormValue("lokalna_tema")
|
||||
if lokalnaTema != "tamna" && lokalnaTema != "svetla" {
|
||||
lokalnaTema = "tamna"
|
||||
}
|
||||
|
||||
if err := h.KorisniciRepo.SacuvajLokalnuTemu(r.Context(), k.ID, lokalnaTema, koristi); err != nil {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.SetFlash(w, r, h.DB, "uspeh", "Tema je sačuvana.")
|
||||
// vrati korisnika na stranicu odakle je došao (Referer), ili na profil kao fallback
|
||||
if ref := r.Referer(); ref != "" {
|
||||
http.Redirect(w, r, ref, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ func BezbednostHeaders() func(http.Handler) http.Handler {
|
||||
h.Set("Content-Security-Policy",
|
||||
"default-src 'self'; "+
|
||||
"style-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com; "+
|
||||
"script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "+
|
||||
// Alpine.js v3 koristi new Function() interno — unsafe-eval je neophodan; CSP build zahteva značajan refaktoring.
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "+
|
||||
"img-src 'self' data: blob:; "+
|
||||
"font-src 'self'; "+
|
||||
"connect-src 'self'")
|
||||
|
||||
Reference in New Issue
Block a user