package handler
import (
"html/template"
"net/http"
"time"
"ntech/internal/auth"
)
const imeKolacica = "ntech_sesija"
const trajanjeSeije = 7 * 24 * time.Hour
const trajanjePredSeije = 5 * time.Minute
// PrikazPrijave renderuje formu za prijavu
func (h *Handler) PrikazPrijave(w http.ResponseWriter, r *http.Request) {
// ako nema korisnika, preusmeri na setup wizard
postoji, err := h.KorisniciRepo.PostojiIjedan(r.Context())
if err == nil && !postoji {
http.Redirect(w, r, "/setup", http.StatusSeeOther)
return
}
greska := r.URL.Query().Get("greska")
renderujStandaloneTemplate(w, "web/templates/stranice/prijava.html", map[string]any{
"Greska": greska,
})
}
// Prijava obrađuje POST formu za prijavu
func (h *Handler) Prijava(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/prijava?greska=1", http.StatusSeeOther)
return
}
korisnickoIme := r.FormValue("korisnicko_ime")
lozinka := r.FormValue("lozinka")
korisnik, err := h.KorisniciRepo.DohvatiPoImenu(r.Context(), korisnickoIme)
if err != nil || !korisnik.Aktivan {
http.Redirect(w, r, "/prijava?greska=1", http.StatusSeeOther)
return
}
if !auth.ProveriLozinku(korisnik.LozinkaHash, lozinka) {
http.Redirect(w, r, "/prijava?greska=1", http.StatusSeeOther)
return
}
token := auth.GenerisiToken()
if korisnik.TotpTajna != "" {
// kreira privremenu sesiju koja čeka TOTP verifikaciju
if err := h.SesijeRepo.Kreiraj(r.Context(), korisnik.ID, token, time.Now().Add(trajanjePredSeije), false); err != nil {
http.Redirect(w, r, "/prijava?greska=2", http.StatusSeeOther)
return
}
http.SetCookie(w, napraviKolacic(token, time.Now().Add(trajanjePredSeije)))
http.Redirect(w, r, "/prijava/totp", http.StatusSeeOther)
return
}
// direktna sesija bez TOTP
if err := h.SesijeRepo.Kreiraj(r.Context(), korisnik.ID, token, time.Now().Add(trajanjeSeije), true); err != nil {
http.Redirect(w, r, "/prijava?greska=2", http.StatusSeeOther)
return
}
http.SetCookie(w, napraviKolacic(token, time.Now().Add(trajanjeSeije)))
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
}
// PrikazTotp renderuje formu za unos TOTP koda
func (h *Handler) PrikazTotp(w http.ResponseWriter, r *http.Request) {
kolacic, err := r.Cookie(imeKolacica)
if err != nil {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
sesija, err := h.SesijeRepo.DohvatiPoTokenu(r.Context(), kolacic.Value)
if err != nil || sesija.TotpPotvrdjeno || time.Now().After(sesija.DatumIsteka) {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
greska := r.URL.Query().Get("greska")
renderujStandaloneTemplate(w, "web/templates/stranice/totp_provera.html", map[string]any{
"Greska": greska,
})
}
// VerifikujTotp obrađuje POST formu za TOTP verifikaciju
func (h *Handler) VerifikujTotp(w http.ResponseWriter, r *http.Request) {
kolacic, err := r.Cookie(imeKolacica)
if err != nil {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
sesija, err := h.SesijeRepo.DohvatiPoTokenu(r.Context(), kolacic.Value)
if err != nil || sesija.TotpPotvrdjeno || time.Now().After(sesija.DatumIsteka) {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
korisnik, err := h.KorisniciRepo.DohvatiPoID(r.Context(), sesija.KorisnikID)
if err != nil {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
kod := r.FormValue("kod")
if !auth.VerifikujTotpKod(kod, korisnik.TotpTajna) {
http.Redirect(w, r, "/prijava/totp?greska=1", http.StatusSeeOther)
return
}
novoIstice := time.Now().Add(trajanjeSeije)
if err := h.SesijeRepo.PotvrdiTotp(r.Context(), kolacic.Value, novoIstice); err != nil {
http.Redirect(w, r, "/prijava?greska=2", http.StatusSeeOther)
return
}
http.SetCookie(w, napraviKolacic(kolacic.Value, novoIstice))
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
}
// PrikazSetupa renderuje setup wizard za prvog korisnika
func (h *Handler) PrikazSetupa(w http.ResponseWriter, r *http.Request) {
postoji, err := h.KorisniciRepo.PostojiIjedan(r.Context())
if err == nil && postoji {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
greska := r.URL.Query().Get("greska")
renderujStandaloneTemplate(w, "web/templates/stranice/setup.html", map[string]any{
"Greska": greska,
})
}
// SacuvajSetup kreira prvog superadmin korisnika
func (h *Handler) SacuvajSetup(w http.ResponseWriter, r *http.Request) {
postoji, err := h.KorisniciRepo.PostojiIjedan(r.Context())
if err == nil && postoji {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/setup?greska=1", http.StatusSeeOther)
return
}
ime := r.FormValue("korisnicko_ime")
lozinka := r.FormValue("lozinka")
potvrda := r.FormValue("lozinka_potvrda")
if len(ime) < 3 || len(lozinka) < 8 || lozinka != potvrda {
http.Redirect(w, r, "/setup?greska=1", http.StatusSeeOther)
return
}
hash, err := auth.HashujLozinku(lozinka)
if err != nil {
http.Redirect(w, r, "/setup?greska=2", http.StatusSeeOther)
return
}
if _, err := h.KorisniciRepo.Kreiraj(r.Context(), ime, hash, "superadmin"); err != nil {
http.Redirect(w, r, "/setup?greska=2", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/prijava?sacuvano=1", http.StatusSeeOther)
}
// Odjava briše sesiju i kolačić
func (h *Handler) Odjava(w http.ResponseWriter, r *http.Request) {
if kolacic, err := r.Cookie(imeKolacica); err == nil {
_ = h.SesijeRepo.Obrisi(r.Context(), kolacic.Value)
}
http.SetCookie(w, &http.Cookie{
Name: imeKolacica,
Value: "",
Path: "/",
Expires: time.Unix(0, 0),
MaxAge: -1,
})
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
}
func napraviKolacic(token string, istice time.Time) *http.Cookie {
return &http.Cookie{
Name: imeKolacica,
Value: token,
Path: "/",
Expires: istice,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
}
}
func renderujStandaloneTemplate(w http.ResponseWriter, putanja string, podaci any) {
tmpl, err := template.ParseFiles(putanja)
if err != nil {
http.Error(w, "Greška pri učitavanju stranice", http.StatusInternalServerError)
return
}
if err := tmpl.Execute(w, podaci); err != nil {
http.Error(w, "Greška pri prikazu stranice", http.StatusInternalServerError)
}
}