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.
130 lines
4.2 KiB
Go
130 lines
4.2 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"ntech/internal/model"
|
|
)
|
|
|
|
// PokusajiPrijaveRepo implementira db.PokusajiPrijaveRepository nad SQLite
|
|
type PokusajiPrijaveRepo struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NoviPokusajiPrijaveRepo kreira novi repozitorijum za praćenje pokušaja prijave
|
|
func NoviPokusajiPrijaveRepo(db *sql.DB) *PokusajiPrijaveRepo {
|
|
return &PokusajiPrijaveRepo{db: db}
|
|
}
|
|
|
|
// Zabeleži upisuje novi pokušaj prijave (uspešan ili neuspešan)
|
|
func (r *PokusajiPrijaveRepo) Zabeleži(ctx context.Context, ip, korisnickoIme string, uspeh bool) error {
|
|
u := 0
|
|
if uspeh {
|
|
u = 1
|
|
}
|
|
_, err := r.db.ExecContext(ctx,
|
|
"INSERT INTO pokusaji_prijave (ip, korisnicko_ime, uspeh) VALUES (?, ?, ?)",
|
|
ip, korisnickoIme, u,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("ntech: PokusajiPrijaveRepo.Zabeleži: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// BrojNeuspeha vraća broj neuspelih pokušaja za datu IP adresu od zadatog trenutka
|
|
func (r *PokusajiPrijaveRepo) BrojNeuspeha(ctx context.Context, ip string, od time.Time) (int, error) {
|
|
var n int
|
|
err := r.db.QueryRowContext(ctx,
|
|
"SELECT COUNT(*) FROM pokusaji_prijave WHERE ip = ? AND uspeh = 0 AND vreme > ?",
|
|
ip, od.UTC().Format("2006-01-02 15:04:05"),
|
|
).Scan(&n)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("ntech: PokusajiPrijaveRepo.BrojNeuspeha: %w", err)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// VremePoslednjeg vraća vreme poslednjeg neuspelog pokušaja za datu IP adresu od zadatog trenutka
|
|
func (r *PokusajiPrijaveRepo) VremePoslednjeg(ctx context.Context, ip string, od time.Time) (time.Time, bool, error) {
|
|
var s sql.NullString
|
|
err := r.db.QueryRowContext(ctx,
|
|
"SELECT MAX(vreme) FROM pokusaji_prijave WHERE ip = ? AND uspeh = 0 AND vreme > ?",
|
|
ip, od.UTC().Format("2006-01-02 15:04:05"),
|
|
).Scan(&s)
|
|
if err != nil {
|
|
return time.Time{}, false, fmt.Errorf("ntech: PokusajiPrijaveRepo.VremePoslednjeg: %w", err)
|
|
}
|
|
if !s.Valid || s.String == "" {
|
|
return time.Time{}, false, nil
|
|
}
|
|
t, err := time.ParseInLocation("2006-01-02 15:04:05", s.String, time.UTC)
|
|
if err != nil {
|
|
return time.Time{}, false, fmt.Errorf("ntech: PokusajiPrijaveRepo.VremePoslednjeg: parse: %w", err)
|
|
}
|
|
return t, true, nil
|
|
}
|
|
|
|
// ObrisiStare briše sve zapise starije od zadatog trenutka
|
|
func (r *PokusajiPrijaveRepo) ObrisiStare(ctx context.Context, pre time.Time) error {
|
|
_, err := r.db.ExecContext(ctx,
|
|
"DELETE FROM pokusaji_prijave WHERE vreme < ?",
|
|
pre.UTC().Format("2006-01-02 15:04:05"),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("ntech: PokusajiPrijaveRepo.ObrisiStare: %w", err)
|
|
}
|
|
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
|
|
}
|