510 lines
12 KiB
Go
510 lines
12 KiB
Go
package repository
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"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")
|
|
var ErrAccountInactive = errors.New("account is not activated")
|
|
|
|
// Init creates the tokens table if it does not exist.
|
|
func (r *Repository) Init() error {
|
|
_, err := r.DB.Exec(storage.CreateSqlite)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Migration: add is_active column if it doesn't exist (SQLite ignores errors for duplicate column)
|
|
_, _ = r.DB.Exec(storage.AddIsActiveColumn)
|
|
return nil
|
|
}
|
|
|
|
// CreateToken inserts a new user record with hashed password and generated token.
|
|
// New users are created with is_active=false by default.
|
|
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, is_active)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
tc.Name,
|
|
tc.LastName,
|
|
tc.Login,
|
|
string(hashed),
|
|
token,
|
|
tc.PermissionView,
|
|
tc.PermissionManage,
|
|
tc.PermissionAdmin,
|
|
tc.IsActive,
|
|
)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
id, err := result.LastInsertId()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strconv.FormatInt(id, 10), nil
|
|
}
|
|
|
|
// RegisterUser inserts a new user with all permissions set to false and is_active=false.
|
|
func (r *Repository) RegisterUser(ur UserRegister) (string, error) {
|
|
hashed, err := bcrypt.GenerateFromPassword([]byte(ur.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return "", fmt.Errorf("hash password: %w", err)
|
|
}
|
|
|
|
token, err := utils.RandomToken()
|
|
if err != nil {
|
|
return "", fmt.Errorf("generate token: %w", err)
|
|
}
|
|
|
|
result, err := r.DB.Exec(
|
|
`INSERT INTO tokens (name, last_name, login, password, token, permission_view, permission_manage_agent, permission_admin, is_active)
|
|
VALUES (?, ?, ?, ?, ?, 0, 0, 0, 0)`,
|
|
ur.Name,
|
|
ur.LastName,
|
|
ur.Login,
|
|
string(hashed),
|
|
token,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("insert user: %w", err)
|
|
}
|
|
|
|
id, err := result.LastInsertId()
|
|
if err != nil {
|
|
return "", fmt.Errorf("get last insert id: %w", err)
|
|
}
|
|
log.Printf("[register] user created: id=%s login=%s", strconv.FormatInt(id, 10), ur.Login)
|
|
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, is_active
|
|
FROM tokens WHERE login = ?`,
|
|
login,
|
|
).Scan(&t.ID, &t.Name, &t.LastName, &t.Login, &hashedPassword, &t.Token,
|
|
&t.PermissionView, &t.PermissionManage, &t.PermissionAdmin, &t.IsActive)
|
|
|
|
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
|
|
}
|
|
|
|
if !t.IsActive {
|
|
return nil, ErrAccountInactive
|
|
}
|
|
|
|
// 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,
|
|
IsActive: t.IsActive,
|
|
}, 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, is_active
|
|
FROM tokens WHERE token = ?`,
|
|
token,
|
|
).Scan(&t.ID, &t.Name, &t.LastName, &t.Login, &t.Token,
|
|
&t.PermissionView, &t.PermissionManage, &t.PermissionAdmin, &t.IsActive)
|
|
|
|
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, is_active
|
|
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, &t.IsActive); 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
|
|
}
|
|
|
|
// ActivateToken activates a user by token value.
|
|
func (r *Repository) ActivateToken(token string) error {
|
|
result, err := r.DB.Exec(
|
|
`UPDATE tokens SET is_active = 1 WHERE token = ?`,
|
|
token,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
affected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if affected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeactivateToken deactivates a user by token value.
|
|
func (r *Repository) DeactivateToken(token string) error {
|
|
result, err := r.DB.Exec(
|
|
`UPDATE tokens SET is_active = 0 WHERE token = ?`,
|
|
token,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
affected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if affected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ActivateUserByLogin activates a user by login.
|
|
func (r *Repository) ActivateUserByLogin(login string) error {
|
|
result, err := r.DB.Exec(
|
|
`UPDATE tokens SET is_active = 1 WHERE login = ?`,
|
|
login,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("activate exec: %w", err)
|
|
}
|
|
affected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("rows affected: %w", err)
|
|
}
|
|
log.Printf("[activate] login=%s affected=%d", login, affected)
|
|
if affected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeactivateUserByLogin deactivates a user by login.
|
|
func (r *Repository) DeactivateUserByLogin(login string) error {
|
|
result, err := r.DB.Exec(
|
|
`UPDATE tokens SET is_active = 0 WHERE login = ?`,
|
|
login,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
affected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if affected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListInactiveTokens returns all users that are not activated.
|
|
func (r *Repository) ListInactiveTokens() ([]Tokens, error) {
|
|
rows, err := r.DB.Query(
|
|
`SELECT id, name, last_name, login, token, permission_view, permission_manage_agent, permission_admin, is_active
|
|
FROM tokens WHERE is_active = 0`,
|
|
)
|
|
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.Token,
|
|
&t.PermissionView, &t.PermissionManage, &t.PermissionAdmin, &t.IsActive); err != nil {
|
|
return nil, err
|
|
}
|
|
tokens = append(tokens, t)
|
|
}
|
|
return tokens, rows.Err()
|
|
}
|
|
|
|
// GetTokenByLogin retrieves a user by login.
|
|
func (r *Repository) GetTokenByLogin(login string) (*Tokens, error) {
|
|
var t Tokens
|
|
err := r.DB.QueryRow(
|
|
`SELECT id, name, last_name, login, token, permission_view, permission_manage_agent, permission_admin, is_active
|
|
FROM tokens WHERE login = ?`,
|
|
login,
|
|
).Scan(&t.ID, &t.Name, &t.LastName, &t.Login, &t.Token,
|
|
&t.PermissionView, &t.PermissionManage, &t.PermissionAdmin, &t.IsActive)
|
|
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return &t, nil
|
|
}
|
|
|
|
// UpdateToken updates name and last_name for a user by login.
|
|
func (r *Repository) UpdateToken(login string, update TokenUpdate) error {
|
|
result, err := r.DB.Exec(
|
|
`UPDATE tokens SET name = ?, last_name = ? WHERE login = ?`,
|
|
update.Name, update.LastName, login,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
affected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if affected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdatePermissions updates permissions and is_active for a user by login.
|
|
func (r *Repository) UpdatePermissions(login string, update TokenUpdatePermissions) error {
|
|
user, err := r.GetTokenByLogin(login)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Use existing values if not provided
|
|
newView := user.PermissionView
|
|
newManage := user.PermissionManage
|
|
newAdmin := user.PermissionAdmin
|
|
newActive := user.IsActive
|
|
|
|
if update.PermissionView != nil {
|
|
newView = *update.PermissionView
|
|
}
|
|
if update.PermissionManage != nil {
|
|
newManage = *update.PermissionManage
|
|
}
|
|
if update.PermissionAdmin != nil {
|
|
newAdmin = *update.PermissionAdmin
|
|
}
|
|
if update.IsActive != nil {
|
|
newActive = *update.IsActive
|
|
}
|
|
|
|
result, err := r.DB.Exec(
|
|
`UPDATE tokens SET permission_view = ?, permission_manage_agent = ?, permission_admin = ?, is_active = ? WHERE login = ?`,
|
|
newView,
|
|
newManage,
|
|
newAdmin,
|
|
newActive,
|
|
login,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
affected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if affected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdatePassword updates the password for a user by login.
|
|
func (r *Repository) UpdatePassword(login string, newPassword string) error {
|
|
hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result, err := r.DB.Exec(
|
|
`UPDATE tokens SET password = ? WHERE login = ?`,
|
|
string(hashed), login,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
affected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if affected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|