1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
package auth
import (
"database/sql"
"errors"
"time"
"golang.org/x/crypto/bcrypt"
)
var (
ErrInvalidCredentials = errors.New("invalid username or password")
ErrUserNotFound = errors.New("user not found")
ErrUserExists = errors.New("username already exists")
)
// User represents an authenticated user
type User struct {
ID int64
Username string
PasswordHash string
CreatedAt time.Time
}
// Service handles authentication operations
type Service struct {
db *sql.DB
}
// NewService creates a new auth service
func NewService(db *sql.DB) *Service {
return &Service{db: db}
}
// Authenticate verifies username and password, returns user ID if valid
func (s *Service) Authenticate(username, password string) (*User, error) {
user, err := s.GetUserByUsername(username)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
return nil, ErrInvalidCredentials
}
return nil, err
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
return nil, ErrInvalidCredentials
}
return user, nil
}
// GetUserByUsername retrieves a user by username
func (s *Service) GetUserByUsername(username string) (*User, error) {
var user User
err := s.db.QueryRow(
`SELECT id, username, password_hash, created_at FROM users WHERE username = ?`,
username,
).Scan(&user.ID, &user.Username, &user.PasswordHash, &user.CreatedAt)
if err == sql.ErrNoRows {
return nil, ErrUserNotFound
}
if err != nil {
return nil, err
}
return &user, nil
}
// GetUserByID retrieves a user by ID
func (s *Service) GetUserByID(id int64) (*User, error) {
var user User
err := s.db.QueryRow(
`SELECT id, username, password_hash, created_at FROM users WHERE id = ?`,
id,
).Scan(&user.ID, &user.Username, &user.PasswordHash, &user.CreatedAt)
if err == sql.ErrNoRows {
return nil, ErrUserNotFound
}
if err != nil {
return nil, err
}
return &user, nil
}
// CreateUser creates a new user with the given username and password
func (s *Service) CreateUser(username, password string) (*User, error) {
// Check if user exists
_, err := s.GetUserByUsername(username)
if err == nil {
return nil, ErrUserExists
}
if !errors.Is(err, ErrUserNotFound) {
return nil, err
}
// Hash password
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
// Insert user
result, err := s.db.Exec(
`INSERT INTO users (username, password_hash) VALUES (?, ?)`,
username, string(hash),
)
if err != nil {
return nil, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, err
}
return s.GetUserByID(id)
}
// UserCount returns the number of users in the database
func (s *Service) UserCount() (int, error) {
var count int
err := s.db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&count)
return count, err
}
// EnsureDefaultUser creates a default admin user if no users exist
func (s *Service) EnsureDefaultUser(username, password string) error {
count, err := s.UserCount()
if err != nil {
return err
}
if count == 0 {
_, err = s.CreateUser(username, password)
return err
}
return nil
}
|