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") 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 } // 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 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 } // 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 err } affected, err := result.RowsAffected() if err != nil { return err } 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 }