package auth import ( "crypto/rand" "crypto/sha256" "encoding/base64" "fmt" "time" "git.jnss.me/joakim/opal/internal/engine" ) func GenerateRefreshToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", err } return base64.URLEncoding.EncodeToString(b), nil } func HashToken(token string) string { hash := sha256.Sum256([]byte(token)) return base64.URLEncoding.EncodeToString(hash[:]) } func StoreRefreshToken(userID int, token string, expiresIn int) error { db := engine.GetDB() if db == nil { return fmt.Errorf("database not initialized") } hash := HashToken(token) expiresAt := time.Now().Add(time.Duration(expiresIn) * time.Second).Unix() _, err := db.Exec(` INSERT INTO refresh_tokens (user_id, token_hash, expires_at, created_at) VALUES (?, ?, ?, ?) `, userID, hash, expiresAt, time.Now().Unix()) return err } func ValidateRefreshToken(token string) (int, error) { db := engine.GetDB() if db == nil { return 0, fmt.Errorf("database not initialized") } hash := HashToken(token) var userID int var expiresAt int64 var revoked int err := db.QueryRow(` SELECT user_id, expires_at, revoked FROM refresh_tokens WHERE token_hash = ? `, hash).Scan(&userID, &expiresAt, &revoked) if err != nil { return 0, err } if revoked == 1 { return 0, fmt.Errorf("token revoked") } if time.Now().Unix() > expiresAt { return 0, fmt.Errorf("token expired") } return userID, nil } func RevokeRefreshToken(token string) error { db := engine.GetDB() if db == nil { return fmt.Errorf("database not initialized") } hash := HashToken(token) _, err := db.Exec(` UPDATE refresh_tokens SET revoked = 1 WHERE token_hash = ? `, hash) return err }