feat: sdk migration in progress

This commit is contained in:
allanice001
2025-11-02 13:19:30 +00:00
commit 0d10d42442
492 changed files with 71067 additions and 0 deletions

105
internal/auth/refresh.go Normal file
View File

@@ -0,0 +1,105 @@
package auth
import (
"crypto/rand"
"encoding/base64"
"errors"
"time"
"github.com/glueops/autoglue/internal/models"
"github.com/google/uuid"
"gorm.io/gorm"
)
// random opaque token (returned to client once)
func generateOpaqueToken(n int) (string, error) {
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
}
type RefreshPair struct {
Plain string
Record models.RefreshToken
}
// Issue a new refresh token (new family if familyID == nil)
func IssueRefreshToken(db *gorm.DB, userID uuid.UUID, ttl time.Duration, familyID *uuid.UUID) (RefreshPair, error) {
plain, err := generateOpaqueToken(32)
if err != nil {
return RefreshPair{}, err
}
hash, err := HashSecretArgon2id(plain)
if err != nil {
return RefreshPair{}, err
}
fid := uuid.New()
if familyID != nil {
fid = *familyID
}
rec := models.RefreshToken{
UserID: userID,
FamilyID: fid,
TokenHash: hash,
ExpiresAt: time.Now().Add(ttl),
}
if err := db.Create(&rec).Error; err != nil {
return RefreshPair{}, err
}
return RefreshPair{Plain: plain, Record: rec}, nil
}
// ValidateRefreshToken refresh token; returns record if valid & not revoked/expired
func ValidateRefreshToken(db *gorm.DB, plain string) (*models.RefreshToken, error) {
if plain == "" {
return nil, errors.New("empty")
}
// var rec models.RefreshToken
// We can't query by hash w/ Argon; scan candidates by expiry window. Keep small TTL (e.g. 30d).
if err := db.Where("expires_at > ? AND revoked_at IS NULL", time.Now()).
Find(&[]models.RefreshToken{}).Error; err != nil {
return nil, err
}
// Better: add a prefix column to narrow scan; omitted for brevity.
// Pragmatic approach: single SELECT per token:
// Add a TokenHashSHA256 column for deterministic lookup if you want O(1). (Optional)
// Minimal: iterate limited set; for simplicity we fetch by created window:
var recs []models.RefreshToken
if err := db.Where("expires_at > ? AND revoked_at IS NULL", time.Now()).
Order("created_at desc").Limit(500).Find(&recs).Error; err != nil {
return nil, err
}
for _, r := range recs {
ok, _ := VerifySecretArgon2id(r.TokenHash, plain)
if ok {
return &r, nil
}
}
return nil, errors.New("invalid")
}
// RevokeFamily revokes all tokens in a family (logout everywhere)
func RevokeFamily(db *gorm.DB, familyID uuid.UUID) error {
now := time.Now()
return db.Model(&models.RefreshToken{}).
Where("family_id = ? AND revoked_at IS NULL", familyID).
Update("revoked_at", &now).Error
}
// RotateRefreshToken replaces one token with a fresh one within the same family
func RotateRefreshToken(db *gorm.DB, used *models.RefreshToken, ttl time.Duration) (RefreshPair, error) {
// revoke the used token (one-time use)
now := time.Now()
if err := db.Model(&models.RefreshToken{}).
Where("id = ? AND revoked_at IS NULL", used.ID).
Update("revoked_at", &now).Error; err != nil {
return RefreshPair{}, err
}
return IssueRefreshToken(db, used.UserID, ttl, &used.FamilyID)
}