package agentauth import ( "crypto/rand" "encoding/hex" "fmt" "strings" "time" "ezops/model" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // Issue 为指定主机轮换 Agent Token,旧 Token 会立即失效。 // Logan.Li func Issue(db *gorm.DB, hostname, issuedBy string) (string, error) { hostname = strings.TrimSpace(hostname) if hostname == "" { return "", fmt.Errorf("hostname is required") } prefix, err := randomHex(8) if err != nil { return "", err } // bcrypt 只处理前 72 字节,Token 总长度控制在 61 字节以内。 // Logan.Li secret, err := randomHex(20) if err != nil { return "", err } token := "agt_" + prefix + "." + secret hash, err := bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost) if err != nil { return "", err } now := time.Now() credential := model.AgentCredential{Hostname: hostname} err = db.Where("hostname = ?", hostname).Assign(map[string]interface{}{ "token_prefix": "agt_" + prefix, "token_hash": string(hash), "status": "active", "issued_by": issuedBy, "last_used_at": nil, "expires_at": nil, "rotated_at": now, }).FirstOrCreate(&credential).Error if err != nil { return "", err } return token, nil } // Authenticate 校验 Token 并返回绑定的主机名。 // Logan.Li func Authenticate(db *gorm.DB, token string) (*model.AgentCredential, error) { parts := strings.SplitN(token, ".", 2) if len(parts) != 2 || !strings.HasPrefix(parts[0], "agt_") || parts[1] == "" { return nil, fmt.Errorf("invalid agent token") } var credential model.AgentCredential if err := db.Where("token_prefix = ? AND status = ?", parts[0], "active").Take(&credential).Error; err != nil { return nil, fmt.Errorf("invalid agent token") } if credential.ExpiresAt != nil && credential.ExpiresAt.Before(time.Now()) { return nil, fmt.Errorf("agent token expired") } if bcrypt.CompareHashAndPassword([]byte(credential.TokenHash), []byte(token)) != nil { return nil, fmt.Errorf("invalid agent token") } now := time.Now() _ = db.Model(&credential).Update("last_used_at", now).Error credential.LastUsedAt = &now return &credential, nil } func randomHex(bytes int) (string, error) { buf := make([]byte, bytes) if _, err := rand.Read(buf); err != nil { return "", err } return hex.EncodeToString(buf), nil }