diff options
| author | haturatu <taro@eyes4you.org> | 2025-10-12 15:01:34 +0900 |
|---|---|---|
| committer | haturatu <taro@eyes4you.org> | 2025-10-12 15:01:34 +0900 |
| commit | 10c90d8654b94916bf0a4ffb8263e512a8e6be04 (patch) | |
| tree | dd109fb357abb40f943a7818fedc659e69ee5d11 /models | |
first commit
Diffstat (limited to 'models')
| -rw-r--r-- | models/token.go | 90 | ||||
| -rw-r--r-- | models/user.go | 147 |
2 files changed, 237 insertions, 0 deletions
diff --git a/models/token.go b/models/token.go new file mode 100644 index 0000000..831491a --- /dev/null +++ b/models/token.go @@ -0,0 +1,90 @@ +package models + +import ( + "auth-proxy/database" + "crypto/rand" + "database/sql" + "encoding/hex" + "time" +) + +// AuthToken represents a token in the database +type AuthToken struct { + ID int64 + UserID int64 + Token string + ExpiresAt time.Time +} + +// GenerateSecureToken creates a random, secure token. +func GenerateSecureToken(length int) (string, error) { + bytes := make([]byte, length) + if _, err := rand.Read(bytes); err != nil { + return "", err + } + return hex.EncodeToString(bytes), nil +} + +// CreateAuthToken creates a new token for a user. +func CreateAuthToken(userID int64, duration time.Duration) (*AuthToken, error) { + tokenString, err := GenerateSecureToken(32) + if err != nil { + return nil, err + } + + expiresAt := time.Now().Add(duration) + + query := database.Rebind("INSERT INTO auth_tokens (user_id, token, expires_at) VALUES (?, ?, ?)") + stmt, err := database.DB.Prepare(query) + if err != nil { + return nil, err + } + defer stmt.Close() + + _, err = stmt.Exec(userID, tokenString, expiresAt) + if err != nil { + return nil, err + } + + return &AuthToken{ + UserID: userID, + Token: tokenString, + ExpiresAt: expiresAt, + }, nil +} + +// GetUserByToken retrieves a user associated with a given token. +// It returns the user if the token is valid and not expired. +func GetUserByToken(tokenString string) (*User, error) { + query := database.Rebind("SELECT u.id, u.username, u.password_hash, u.role, u.is_active, u.failed_logins, u.last_login_at, u.created_at, u.updated_at FROM users u JOIN auth_tokens t ON u.id = t.user_id WHERE t.token = ? AND t.expires_at > ?") + + row := database.DB.QueryRow(query, tokenString, time.Now()) + + user := &User{} + err := row.Scan(&user.ID, &user.Username, &user.PasswordHash, &user.Role, &user.IsActive, &user.FailedLogins, &user.LastLoginAt, &user.CreatedAt, &user.UpdatedAt) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil // Token not found or expired, not a server error + } + return nil, err + } + + return user, nil +} + +// DeleteAuthToken deletes a token from the database. +func DeleteAuthToken(tokenString string) error { + query := database.Rebind("DELETE FROM auth_tokens WHERE token = ?") + _, err := database.DB.Exec(query, tokenString) + return err +} + +// DeleteExpiredTokens cleans up expired tokens from the database. +func DeleteExpiredTokens() (int64, error) { + query := database.Rebind("DELETE FROM auth_tokens WHERE expires_at < ?") + res, err := database.DB.Exec(query, time.Now()) + if err != nil { + return 0, err + } + return res.RowsAffected() +}
\ No newline at end of file diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..c5ecdd7 --- /dev/null +++ b/models/user.go @@ -0,0 +1,147 @@ +package models + +import ( + "auth-proxy/database" + "database/sql" + "time" + + "golang.org/x/crypto/bcrypt" +) + +// User represents a user in the database +type User struct { + ID int64 `json:"id"` + Username string `json:"username"` + PasswordHash string `json:"-"` + Role string `json:"role"` + IsActive bool `json:"is_active"` + FailedLogins int `json:"failed_logins"` + LastLoginAt sql.NullTime `json:"last_login_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// HashPassword generates a bcrypt hash of the password +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) + return string(bytes), err +} + +// CheckPasswordHash compares a password with a hash +func CheckPasswordHash(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} + +// CreateUser creates a new user in the database +func CreateUser(username, password, role string) (*User, error) { + passwordHash, err := HashPassword(password) + if err != nil { + return nil, err + } + + query := database.Rebind("INSERT INTO users(username, password_hash, role) VALUES(?, ?, ?)") + stmt, err := database.DB.Prepare(query) + if err != nil { + return nil, err + } + defer stmt.Close() + + _, err = stmt.Exec(username, passwordHash, role) + if err != nil { + return nil, err + } + + return GetUserByUsername(username) // Return the full user object +} + +// baseSelectQuery is the common set of columns for user queries +const baseSelectQuery = "SELECT id, username, password_hash, role, is_active, failed_logins, last_login_at, created_at, updated_at FROM users" + +// scanUser scans a row into a User struct +func scanUser(row *sql.Row) (*User, error) { + user := &User{} + err := row.Scan(&user.ID, &user.Username, &user.PasswordHash, &user.Role, &user.IsActive, &user.FailedLogins, &user.LastLoginAt, &user.CreatedAt, &user.UpdatedAt) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil // User not found + } + return nil, err + } + return user, nil +} + +// GetUserByUsername retrieves a user by their username +func GetUserByUsername(username string) (*User, error) { + query := database.Rebind(baseSelectQuery + " WHERE username = ?") + row := database.DB.QueryRow(query, username) + return scanUser(row) +} + +// GetUserByID retrieves a user by their ID +func GetUserByID(id int64) (*User, error) { + query := database.Rebind(baseSelectQuery + " WHERE id = ?") + row := database.DB.QueryRow(query, id) + return scanUser(row) +} + +// GetAllUsers retrieves all users from the database +func GetAllUsers() ([]User, error) { + rows, err := database.DB.Query(baseSelectQuery) + if err != nil { + return nil, err + } + defer rows.Close() + + var users []User + for rows.Next() { + var user User + if err := rows.Scan(&user.ID, &user.Username, &user.PasswordHash, &user.Role, &user.IsActive, &user.FailedLogins, &user.LastLoginAt, &user.CreatedAt, &user.UpdatedAt); err != nil { + return nil, err + } + users = append(users, user) + } + return users, nil +} + +// UpdateUserRole updates a user's role +func UpdateUserRole(id int64, role string) error { + query := database.Rebind("UPDATE users SET role = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?") + _, err := database.DB.Exec(query, role, id) + return err +} + +// DeleteUser deletes a user from the database +func DeleteUser(id int64) error { + query := database.Rebind("DELETE FROM users WHERE id = ?") + _, err := database.DB.Exec(query, id) + return err +} + +// UpdateUserPassword updates a user's password hash +func UpdateUserPassword(id int64, passwordHash string) error { + query := database.Rebind("UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?") + _, err := database.DB.Exec(query, passwordHash, id) + return err +} + +// RecordLoginSuccess resets failed login attempts and updates last login time +func RecordLoginSuccess(id int64) error { + query := database.Rebind("UPDATE users SET failed_logins = 0, last_login_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?") + _, err := database.DB.Exec(query, id) + return err +} + +// RecordLoginFailure increments the failed login counter +func RecordLoginFailure(id int64) error { + query := database.Rebind("UPDATE users SET failed_logins = failed_logins + 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?") + _, err := database.DB.Exec(query, id) + return err +} + +// SetUserActiveStatus updates the is_active flag for a user +func SetUserActiveStatus(id int64, isActive bool) error { + query := database.Rebind("UPDATE users SET is_active = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?") + _, err := database.DB.Exec(query, isActive, id) + return err +}
\ No newline at end of file |
