summaryrefslogtreecommitdiff
path: root/models
diff options
context:
space:
mode:
authorhaturatu <taro@eyes4you.org>2025-10-12 17:04:07 +0900
committerhaturatu <taro@eyes4you.org>2025-10-12 17:04:07 +0900
commit957dc75eddf69ccf26bd387828b9e205a5425f30 (patch)
tree15a3f5581dded804a383c1d42e5b7d4b967da11e /models
parent10c90d8654b94916bf0a4ffb8263e512a8e6be04 (diff)
add(api): api endpoint authorized Bearer token
Diffstat (limited to 'models')
-rw-r--r--models/token.go102
-rw-r--r--models/user.go37
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
+}