82 lines
2.3 KiB
Go
82 lines
2.3 KiB
Go
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
|
|
}
|