package auth import ( "crypto/rand" "crypto/sha256" "database/sql" "encoding/hex" "fmt" "time" ) const tokenBytes = 32 // CreateToken generates a bearer token and stores its hash. func CreateToken(database *sql.DB, userID int64, ttl time.Duration) (string, error) { raw := make([]byte, tokenBytes) if _, err := rand.Read(raw); err != nil { return "", fmt.Errorf("generate token: %w", err) } token := hex.EncodeToString(raw) hash := hashToken(token) expiresAt := time.Now().Add(ttl).UnixMilli() _, err := database.Exec( "INSERT INTO auth_tokens (token_hash, user_id, expires_at, created_at) VALUES (?, ?, ?, ?)", hash, userID, expiresAt, time.Now().UnixMilli(), ) if err != nil { return "", fmt.Errorf("store token: %w", err) } return token, nil } // ValidateToken checks a bearer token and returns the user ID. func ValidateToken(database *sql.DB, token string) (int64, error) { hash := hashToken(token) var userID int64 var expiresAt int64 err := database.QueryRow( "SELECT user_id, expires_at FROM auth_tokens WHERE token_hash = ?", hash, ).Scan(&userID, &expiresAt) if err != nil { return 0, fmt.Errorf("token not found") } if time.Now().UnixMilli() > expiresAt { // Clean up expired token _, _ = database.Exec("DELETE FROM auth_tokens WHERE token_hash = ?", hash) return 0, fmt.Errorf("token expired") } return userID, nil } // DeleteToken revokes a bearer token. func DeleteToken(database *sql.DB, token string) error { hash := hashToken(token) _, err := database.Exec("DELETE FROM auth_tokens WHERE token_hash = ?", hash) return err } func hashToken(token string) string { h := sha256.Sum256([]byte(token)) return hex.EncodeToString(h[:]) }