Files
HellreigN/backend/internal/repository/repository.go
T
d3m0k1d a2c71da3a0
ci-agent / build (push) Failing after 1m19s
chore: grpc + mtls working
2026-04-04 03:55:37 +03:00

247 lines
6.2 KiB
Go

package repository
import (
"database/sql"
"errors"
"strconv"
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/storage"
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/utils"
"golang.org/x/crypto/bcrypt"
)
// Repository wraps a SQLite database connection.
type Repository struct {
DB *sql.DB
}
// New creates a new Repository.
func New(db *sql.DB) *Repository {
return &Repository{DB: db}
}
var ErrNotFound = errors.New("not found")
// Init creates the tokens table if it does not exist.
func (r *Repository) Init() error {
_, err := r.DB.Exec(storage.CreateSqlite)
return err
}
// CreateToken inserts a new user record with hashed password and generated token.
func (r *Repository) CreateToken(tc TokenCreate) (string, error) {
hashed, err := bcrypt.GenerateFromPassword([]byte(tc.Password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
token, err := utils.RandomToken()
if err != nil {
return "", err
}
result, err := r.DB.Exec(
`INSERT INTO tokens (name, last_name, login, password, token, permission_view, permission_manage_agent, permission_admin)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
tc.Name, tc.LastName, tc.Login, string(hashed), token,
tc.PermissionView, tc.PermissionManage, tc.PermissionAdmin,
)
if err != nil {
return "", err
}
id, err := result.LastInsertId()
if err != nil {
return "", err
}
return strconv.FormatInt(id, 10), nil
}
// Login authenticates by login/password, generates a new token, and returns LoginResponse.
func (r *Repository) Login(login, password string) (*LoginResponse, error) {
var t Tokens
var hashedPassword string
err := r.DB.QueryRow(
`SELECT id, name, last_name, login, password, token, permission_view, permission_manage_agent, permission_admin
FROM tokens WHERE login = ?`,
login,
).Scan(&t.ID, &t.Name, &t.LastName, &t.Login, &hashedPassword, &t.Token,
&t.PermissionView, &t.PermissionManage, &t.PermissionAdmin)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
if err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)); err != nil {
return nil, ErrNotFound
}
// Generate new token on each login
newToken, err := utils.RandomToken()
if err != nil {
return nil, err
}
_, err = r.DB.Exec(`UPDATE tokens SET token = ? WHERE id = ?`, newToken, t.ID)
if err != nil {
return nil, err
}
return &LoginResponse{
Token: newToken,
Name: t.Name,
LastName: t.LastName,
Login: t.Login,
PermissionView: t.PermissionView,
PermissionManage: t.PermissionManage,
PermissionAdmin: t.PermissionAdmin,
}, nil
}
// GetTokenByToken retrieves a user record by token value.
func (r *Repository) GetToken(token string) (*Tokens, error) {
var t Tokens
err := r.DB.QueryRow(
`SELECT id, name, last_name, login, token, permission_view, permission_manage_agent, permission_admin
FROM tokens WHERE token = ?`,
token,
).Scan(&t.ID, &t.Name, &t.LastName, &t.Login, &t.Token,
&t.PermissionView, &t.PermissionManage, &t.PermissionAdmin)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
return &t, nil
}
// ListTokens returns all users without password and token.
func (r *Repository) ListTokens() ([]Tokens, error) {
rows, err := r.DB.Query(
`SELECT id, name, last_name, login, permission_view, permission_manage_agent, permission_admin
FROM tokens`,
)
if err != nil {
return nil, err
}
defer rows.Close()
var tokens []Tokens
for rows.Next() {
var t Tokens
if err := rows.Scan(&t.ID, &t.Name, &t.LastName, &t.Login,
&t.PermissionView, &t.PermissionManage, &t.PermissionAdmin); err != nil {
return nil, err
}
tokens = append(tokens, t)
}
return tokens, rows.Err()
}
// DeleteToken deletes a user by token value.
func (r *Repository) DeleteToken(token string) error {
result, err := r.DB.Exec(`DELETE FROM tokens WHERE token = ?`, token)
if err != nil {
return err
}
affected, err := result.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
return ErrNotFound
}
return nil
}
// DeleteTokenByLogin deletes a user by login.
func (r *Repository) DeleteTokenByLogin(login string) error {
result, err := r.DB.Exec(`DELETE FROM tokens WHERE login = ?`, login)
if err != nil {
return err
}
affected, err := result.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
return ErrNotFound
}
return nil
}
// ExistsByLogin checks if a user with given login exists.
func (r *Repository) ExistsByLogin(login string) bool {
var count int
err := r.DB.QueryRow(`SELECT COUNT(*) FROM tokens WHERE login = ?`, login).Scan(&count)
if err != nil {
return false
}
return count > 0
}
// InitRegistrationTokens creates the registration_tokens table if it does not exist.
func (r *Repository) InitRegistrationTokens() error {
_, err := r.DB.Exec(storage.CreateRegistrationTokensTable)
return err
}
// CreateRegistrationToken inserts a new one-time registration token.
func (r *Repository) CreateRegistrationToken(label string) (string, error) {
token, err := utils.RandomToken()
if err != nil {
return "", err
}
_, err = r.DB.Exec(
`INSERT INTO registration_tokens (token, label, used) VALUES (?, ?, 0)`,
token, label,
)
if err != nil {
return "", err
}
return token, nil
}
// GetRegistrationToken retrieves a registration token if it exists and is not used.
func (r *Repository) GetRegistrationToken(token string) (*RegistrationToken, error) {
var rt RegistrationToken
err := r.DB.QueryRow(
`SELECT id, token, label, used, created_at, used_at FROM registration_tokens WHERE token = ?`,
token,
).Scan(&rt.ID, &rt.Token, &rt.Label, &rt.Used, &rt.CreatedAt, &rt.UsedAt)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
return &rt, nil
}
// MarkRegistrationTokenUsed marks a registration token as used.
func (r *Repository) MarkRegistrationTokenUsed(token string) error {
result, err := r.DB.Exec(
`UPDATE registration_tokens SET used = 1, used_at = CURRENT_TIMESTAMP WHERE token = ? AND used = 0`,
token,
)
if err != nil {
return err
}
affected, err := result.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
return ErrNotFound
}
return nil
}