diff options
Diffstat (limited to 'models')
| -rw-r--r-- | models/token.go | 102 | ||||
| -rw-r--r-- | models/user.go | 37 |
2 files changed, 95 insertions, 44 deletions
diff --git a/models/token.go b/models/token.go index 831491a..de2880a 100644 --- a/models/token.go +++ b/models/token.go @@ -2,20 +2,13 @@ package models import ( "auth-proxy/database" + "auth-proxy/types" "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) @@ -25,8 +18,8 @@ func GenerateSecureToken(length int) (string, error) { return hex.EncodeToString(bytes), nil } -// CreateAuthToken creates a new token for a user. -func CreateAuthToken(userID int64, duration time.Duration) (*AuthToken, error) { +// CreateAuthToken creates a new token for a user, deleting any existing ones. +func CreateAuthToken(userID int64, duration time.Duration) (*types.AuthToken, error) { tokenString, err := GenerateSecureToken(32) if err != nil { return nil, err @@ -34,19 +27,29 @@ func CreateAuthToken(userID int64, duration time.Duration) (*AuthToken, error) { expiresAt := time.Now().Add(duration) - query := database.Rebind("INSERT INTO auth_tokens (user_id, token, expires_at) VALUES (?, ?, ?)") - stmt, err := database.DB.Prepare(query) + tx, err := database.DB.Begin() if err != nil { return nil, err } - defer stmt.Close() + defer tx.Rollback() //nolint:errcheck - _, err = stmt.Exec(userID, tokenString, expiresAt) - if err != nil { + // Delete existing tokens for the user + deleteQuery := database.Rebind("DELETE FROM auth_tokens WHERE user_id = ?") + if _, err := tx.Exec(deleteQuery, userID); err != nil { + return nil, err + } + + // Insert the new token + insertQuery := database.Rebind("INSERT INTO auth_tokens (user_id, token, expires_at) VALUES (?, ?, ?)") + if _, err := tx.Exec(insertQuery, userID, tokenString, expiresAt); err != nil { return nil, err } - return &AuthToken{ + if err := tx.Commit(); err != nil { + return nil, err + } + + return &types.AuthToken{ UserID: userID, Token: tokenString, ExpiresAt: expiresAt, @@ -55,12 +58,12 @@ func CreateAuthToken(userID int64, duration time.Duration) (*AuthToken, error) { // 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) { +func GetUserByToken(tokenString string) (*types.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{} + user := &types.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 { @@ -87,4 +90,65 @@ func DeleteExpiredTokens() (int64, error) { return 0, err } return res.RowsAffected() -}
\ No newline at end of file +} + +// CreateRefreshToken creates a new refresh token for a user. +func CreateRefreshToken(userID int64, duration time.Duration) (*types.RefreshToken, error) { + tokenString, err := GenerateSecureToken(32) + if err != nil { + return nil, err + } + + expiresAt := time.Now().Add(duration) + + tx, err := database.DB.Begin() + if err != nil { + return nil, err + } + defer tx.Rollback() //nolint:errcheck + + // Delete existing, non-revoked refresh tokens for the user + deleteQuery := database.Rebind("DELETE FROM refresh_tokens WHERE user_id = ?") + if _, err := tx.Exec(deleteQuery, userID); err != nil { + return nil, err + } + + // Insert the new refresh token + insertQuery := database.Rebind("INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES (?, ?, ?)") + if _, err := tx.Exec(insertQuery, userID, tokenString, expiresAt); err != nil { + return nil, err + } + + if err := tx.Commit(); err != nil { + return nil, err + } + + return &types.RefreshToken{ + UserID: userID, + Token: tokenString, + ExpiresAt: expiresAt, + }, nil +} + +// GetRefreshTokenByToken retrieves a refresh token by its token string. +func GetRefreshTokenByToken(tokenString string) (*types.RefreshToken, error) { + query := database.Rebind("SELECT id, user_id, token, expires_at, is_revoked FROM refresh_tokens WHERE token = ?") + row := database.DB.QueryRow(query, tokenString) + + token := &types.RefreshToken{} + err := row.Scan(&token.ID, &token.UserID, &token.Token, &token.ExpiresAt, &token.IsRevoked) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil // Not found + } + return nil, err + } + return token, nil +} + +// DeleteRefreshTokenByToken deletes a refresh token from the database by its token string. +func DeleteRefreshTokenByToken(tokenString string) error { + query := database.Rebind("DELETE FROM refresh_tokens WHERE token = ?") + _, err := database.DB.Exec(query, tokenString) + return err +} diff --git a/models/user.go b/models/user.go index c5ecdd7..f2b4e22 100644 --- a/models/user.go +++ b/models/user.go @@ -2,25 +2,12 @@ package models import ( "auth-proxy/database" + "auth-proxy/types" "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) @@ -34,7 +21,7 @@ func CheckPasswordHash(password, hash string) bool { } // CreateUser creates a new user in the database -func CreateUser(username, password, role string) (*User, error) { +func CreateUser(username, password, role string) (*types.User, error) { passwordHash, err := HashPassword(password) if err != nil { return nil, err @@ -45,7 +32,7 @@ func CreateUser(username, password, role string) (*User, error) { if err != nil { return nil, err } - defer stmt.Close() + defer stmt.Close() //nolint:errcheck _, err = stmt.Exec(username, passwordHash, role) if err != nil { @@ -59,8 +46,8 @@ func CreateUser(username, password, role string) (*User, error) { 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{} +func scanUser(row *sql.Row) (*types.User, error) { + user := &types.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 { @@ -72,30 +59,30 @@ func scanUser(row *sql.Row) (*User, error) { } // GetUserByUsername retrieves a user by their username -func GetUserByUsername(username string) (*User, error) { +func GetUserByUsername(username string) (*types.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) { +func GetUserByID(id int64) (*types.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) { +func GetAllUsers() ([]types.User, error) { rows, err := database.DB.Query(baseSelectQuery) if err != nil { return nil, err } - defer rows.Close() + defer rows.Close() //nolint:errcheck - var users []User + var users []types.User for rows.Next() { - var user User + var user types.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 } @@ -144,4 +131,4 @@ 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 +} |
