Bezbednost: superadmin može da odblokira IP adrese zaključane bruteforce zaštitom

Nova stranica /admin/blokirane-ip — lista IP adresa sa previše neuspelih pokušaja
prijave (broj neuspeha, poslednji pokušaj, preostalo vreme zaključavanja) i dugme
za ručno odblokiranje. Nova dozvola 'bezbednost.odblokiraj', namerno rezervisana
isključivo za ulogu superadmin (admin je nema — odblokiranje zaobilazi bruteforce
zaštitu). Repo metode PokusajiPrijaveRepo.ListaBlokiranih/Odblokiraj.
This commit is contained in:
2026-07-02 21:24:25 +02:00
parent b35b41b82e
commit 85a2ee1b64
9 changed files with 208 additions and 4 deletions
+50
View File
@@ -5,6 +5,8 @@ import (
"database/sql"
"fmt"
"time"
"ntech/internal/model"
)
// PokusajiPrijaveRepo implementira db.PokusajiPrijaveRepository nad SQLite
@@ -77,3 +79,51 @@ func (r *PokusajiPrijaveRepo) ObrisiStare(ctx context.Context, pre time.Time) er
}
return nil
}
// ListaBlokiranih vraća IP adrese sa najmanje prag neuspelih pokušaja prijave od
// zadatog trenutka, grupisano po IP adresi, poređano od najskorije zaključane.
func (r *PokusajiPrijaveRepo) ListaBlokiranih(ctx context.Context, od time.Time, prag int) ([]model.BlokiranaIP, error) {
redovi, err := r.db.QueryContext(ctx,
`SELECT ip, COUNT(*), MAX(vreme) FROM pokusaji_prijave
WHERE uspeh = 0 AND vreme > ?
GROUP BY ip HAVING COUNT(*) >= ?
ORDER BY MAX(vreme) DESC`,
od.UTC().Format("2006-01-02 15:04:05"), prag,
)
if err != nil {
return nil, fmt.Errorf("ntech: PokusajiPrijaveRepo.ListaBlokiranih: %w", err)
}
defer redovi.Close()
var lista []model.BlokiranaIP
for redovi.Next() {
var b model.BlokiranaIP
var poslednji string
if err := redovi.Scan(&b.IP, &b.BrojNeuspeha, &poslednji); err != nil {
return nil, fmt.Errorf("ntech: PokusajiPrijaveRepo.ListaBlokiranih: scan: %w", err)
}
t, err := time.ParseInLocation("2006-01-02 15:04:05", poslednji, time.UTC)
if err != nil {
return nil, fmt.Errorf("ntech: PokusajiPrijaveRepo.ListaBlokiranih: parse vremena: %w", err)
}
b.PoslednjiPokusaj = t
lista = append(lista, b)
}
if err := redovi.Err(); err != nil {
return nil, fmt.Errorf("ntech: PokusajiPrijaveRepo.ListaBlokiranih: %w", err)
}
return lista, nil
}
// Odblokiraj briše sve neuspele pokušaje prijave za datu IP adresu — brojač
// neuspeha odmah pada na nulu, pa IP prestaje da bude zaključana.
func (r *PokusajiPrijaveRepo) Odblokiraj(ctx context.Context, ip string) error {
_, err := r.db.ExecContext(ctx,
"DELETE FROM pokusaji_prijave WHERE ip = ? AND uspeh = 0",
ip,
)
if err != nil {
return fmt.Errorf("ntech: PokusajiPrijaveRepo.Odblokiraj: %w", err)
}
return nil
}