Ispravka QR koda za 2FA — generisanje na serveru kao base64 PNG

This commit is contained in:
2026-06-02 22:29:53 +02:00
parent f918b76542
commit 2401f6d5ec
25 changed files with 2449 additions and 48 deletions
+23
View File
@@ -2,6 +2,7 @@ package db
import (
"context"
"time"
"ntech/internal/model"
)
@@ -74,3 +75,25 @@ type ProdajaRepository interface {
Obrisi(ctx context.Context, id int64) error
SledeciBroj(ctx context.Context) (string, error)
}
// KorisniciRepository definiše operacije nad korisnicima
type KorisniciRepository interface {
Kreiraj(ctx context.Context, korisnickoIme, lozinkaHash, uloga string) (*model.Korisnik, error)
DohvatiPoImenu(ctx context.Context, korisnickoIme string) (*model.Korisnik, error)
DohvatiPoID(ctx context.Context, id int64) (*model.Korisnik, error)
Lista(ctx context.Context) ([]model.Korisnik, error)
AzurirajUlogu(ctx context.Context, id int64, uloga string) error
AzurirajAktivan(ctx context.Context, id int64, aktivan bool) error
PromeniLozinku(ctx context.Context, id int64, hash string) error
SacuvajTotpTajnu(ctx context.Context, id int64, tajna string) error
PostojiIjedan(ctx context.Context) (bool, error)
}
// SesijeRepository definiše operacije nad sesijama
type SesijeRepository interface {
Kreiraj(ctx context.Context, korisnikID int64, token string, istice time.Time, totpPotvrdjeno bool) error
DohvatiPoTokenu(ctx context.Context, token string) (*model.Sesija, error)
PotvrdiTotp(ctx context.Context, token string, novoIstice time.Time) error
Obrisi(ctx context.Context, token string) error
ObrisiIstekle(ctx context.Context) error
}
+135
View File
@@ -0,0 +1,135 @@
package sqlite
import (
"context"
"database/sql"
"fmt"
"time"
"ntech/internal/model"
)
type sqliteKorisniciRepo struct{ db *sql.DB }
// NoviKorisniciRepo kreira SQLite implementaciju KorisniciRepository
func NoviKorisniciRepo(db *sql.DB) *sqliteKorisniciRepo {
return &sqliteKorisniciRepo{db: db}
}
func (r *sqliteKorisniciRepo) Kreiraj(ctx context.Context, korisnickoIme, lozinkaHash, uloga string) (*model.Korisnik, error) {
res, err := r.db.ExecContext(ctx,
`INSERT INTO korisnici (korisnicko_ime, lozinka_hash, uloga) VALUES (?, ?, ?)`,
korisnickoIme, lozinkaHash, uloga)
if err != nil {
return nil, fmt.Errorf("ntech: korisnici.Kreiraj: %w", err)
}
id, _ := res.LastInsertId()
return r.DohvatiPoID(ctx, id)
}
func (r *sqliteKorisniciRepo) DohvatiPoImenu(ctx context.Context, korisnickoIme string) (*model.Korisnik, error) {
k := &model.Korisnik{}
var aktivan int
var totpTajna sql.NullString
var datumKreiranja time.Time
err := r.db.QueryRowContext(ctx,
`SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''), datum_kreiranja
FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme).
Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &totpTajna, &datumKreiranja)
if err != nil {
return nil, fmt.Errorf("ntech: korisnici.DohvatiPoImenu: %w", err)
}
k.Aktivan = aktivan == 1
k.TotpTajna = totpTajna.String
k.DatumKreiranja = datumKreiranja
return k, nil
}
func (r *sqliteKorisniciRepo) DohvatiPoID(ctx context.Context, id int64) (*model.Korisnik, error) {
k := &model.Korisnik{}
var aktivan int
var datumKreiranja time.Time
err := r.db.QueryRowContext(ctx,
`SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''), datum_kreiranja
FROM korisnici WHERE id = ?`, id).
Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna, &datumKreiranja)
if err != nil {
return nil, fmt.Errorf("ntech: korisnici.DohvatiPoID: %w", err)
}
k.Aktivan = aktivan == 1
k.DatumKreiranja = datumKreiranja
return k, nil
}
func (r *sqliteKorisniciRepo) Lista(ctx context.Context) ([]model.Korisnik, error) {
rows, err := r.db.QueryContext(ctx,
`SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''), datum_kreiranja
FROM korisnici ORDER BY datum_kreiranja ASC`)
if err != nil {
return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err)
}
defer rows.Close()
var lista []model.Korisnik
for rows.Next() {
var k model.Korisnik
var aktivan int
var datumKreiranja time.Time
if err := rows.Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna, &datumKreiranja); err != nil {
return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err)
}
k.Aktivan = aktivan == 1
k.DatumKreiranja = datumKreiranja
lista = append(lista, k)
}
return lista, nil
}
func (r *sqliteKorisniciRepo) AzurirajUlogu(ctx context.Context, id int64, uloga string) error {
_, err := r.db.ExecContext(ctx, `UPDATE korisnici SET uloga = ? WHERE id = ?`, uloga, id)
if err != nil {
return fmt.Errorf("ntech: korisnici.AzurirajUlogu: %w", err)
}
return nil
}
func (r *sqliteKorisniciRepo) AzurirajAktivan(ctx context.Context, id int64, aktivan bool) error {
val := 0
if aktivan {
val = 1
}
_, err := r.db.ExecContext(ctx, `UPDATE korisnici SET aktivan = ? WHERE id = ?`, val, id)
if err != nil {
return fmt.Errorf("ntech: korisnici.AzurirajAktivan: %w", err)
}
return nil
}
func (r *sqliteKorisniciRepo) PromeniLozinku(ctx context.Context, id int64, hash string) error {
_, err := r.db.ExecContext(ctx, `UPDATE korisnici SET lozinka_hash = ? WHERE id = ?`, hash, id)
if err != nil {
return fmt.Errorf("ntech: korisnici.PromeniLozinku: %w", err)
}
return nil
}
func (r *sqliteKorisniciRepo) SacuvajTotpTajnu(ctx context.Context, id int64, tajna string) error {
var err error
if tajna == "" {
_, err = r.db.ExecContext(ctx, `UPDATE korisnici SET totp_tajna = NULL WHERE id = ?`, id)
} else {
_, err = r.db.ExecContext(ctx, `UPDATE korisnici SET totp_tajna = ? WHERE id = ?`, tajna, id)
}
if err != nil {
return fmt.Errorf("ntech: korisnici.SacuvajTotpTajnu: %w", err)
}
return nil
}
func (r *sqliteKorisniciRepo) PostojiIjedan(ctx context.Context) (bool, error) {
var broj int
err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM korisnici`).Scan(&broj)
if err != nil {
return false, fmt.Errorf("ntech: korisnici.PostojiIjedan: %w", err)
}
return broj > 0, nil
}
+74
View File
@@ -0,0 +1,74 @@
package sqlite
import (
"context"
"database/sql"
"fmt"
"time"
"ntech/internal/model"
)
type sqliteSesijeRepo struct{ db *sql.DB }
// NoviSesijeRepo kreira SQLite implementaciju SesijeRepository
func NoviSesijeRepo(db *sql.DB) *sqliteSesijeRepo {
return &sqliteSesijeRepo{db: db}
}
func (r *sqliteSesijeRepo) Kreiraj(ctx context.Context, korisnikID int64, token string, istice time.Time, totpPotvrdjeno bool) error {
potvrdjen := 1
if !totpPotvrdjeno {
potvrdjen = 0
}
_, err := r.db.ExecContext(ctx,
`INSERT INTO sesije (korisnik_id, token, totp_potvrdjeno, datum_isteka) VALUES (?, ?, ?, ?)`,
korisnikID, token, potvrdjen, istice)
if err != nil {
return fmt.Errorf("ntech: sesije.Kreiraj: %w", err)
}
return nil
}
func (r *sqliteSesijeRepo) DohvatiPoTokenu(ctx context.Context, token string) (*model.Sesija, error) {
s := &model.Sesija{}
var totpPotvrdjeno int
var datumIsteka, datumKreiranja time.Time
err := r.db.QueryRowContext(ctx,
`SELECT id, korisnik_id, token, totp_potvrdjeno, datum_isteka, datum_kreiranja
FROM sesije WHERE token = ?`, token).
Scan(&s.ID, &s.KorisnikID, &s.Token, &totpPotvrdjeno, &datumIsteka, &datumKreiranja)
if err != nil {
return nil, fmt.Errorf("ntech: sesije.DohvatiPoTokenu: %w", err)
}
s.TotpPotvrdjeno = totpPotvrdjeno == 1
s.DatumIsteka = datumIsteka
s.DatumKreiranja = datumKreiranja
return s, nil
}
func (r *sqliteSesijeRepo) PotvrdiTotp(ctx context.Context, token string, novoIstice time.Time) error {
_, err := r.db.ExecContext(ctx,
`UPDATE sesije SET totp_potvrdjeno = 1, datum_isteka = ? WHERE token = ?`,
novoIstice, token)
if err != nil {
return fmt.Errorf("ntech: sesije.PotvrdiTotp: %w", err)
}
return nil
}
func (r *sqliteSesijeRepo) Obrisi(ctx context.Context, token string) error {
_, err := r.db.ExecContext(ctx, `DELETE FROM sesije WHERE token = ?`, token)
if err != nil {
return fmt.Errorf("ntech: sesije.Obrisi: %w", err)
}
return nil
}
func (r *sqliteSesijeRepo) ObrisiIstekle(ctx context.Context) error {
_, err := r.db.ExecContext(ctx, `DELETE FROM sesije WHERE datum_isteka < ?`, time.Now())
if err != nil {
return fmt.Errorf("ntech: sesije.ObrisiIstekle: %w", err)
}
return nil
}