AI Gateway Go 0.10.0 源码快照 + 旗舰版需求规划报告

M0-M7 已完成:核心网关(身份/RBAC/TOTP/OIDC/SAML/Provider/配额/路由/内容策略/审计/定价)+ 资源市场(MCP/Skills/数字员工)。
含 22 个 PostgreSQL 迁移、管理端/门户端前端源码、OpenAPI 契约、部署 compose。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ben
2026-08-12 11:45:54 +08:00
commit 5759c1862e
807 changed files with 114727 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
package identity
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/subtle"
"encoding/base32"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"net/url"
"strings"
"time"
)
const (
totpPeriod = int64(30)
totpDigits = 6
totpWindow = int64(1)
backupCodeCount = 10
backupCodeLength = 8
)
var backupAlphabet = []byte("23456789ABCDEFGHJKLMNPQRSTUVWXYZ")
type BackupCodeRecord struct {
Hash string `json:"hash"`
UsedAt *time.Time `json:"used_at,omitempty"`
}
func GenerateTOTPSecret() (string, error) {
secret := make([]byte, 20)
if _, err := rand.Read(secret); err != nil {
return "", err
}
return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(secret), nil
}
func TOTPProvisioningURI(secret string, kind Kind, login string) string {
issuer := "AI Gateway"
label := issuer + ":" + string(kind) + ":" + login
query := url.Values{
"secret": []string{secret},
"issuer": []string{issuer},
"algorithm": []string{"SHA1"},
"digits": []string{fmt.Sprint(totpDigits)},
"period": []string{fmt.Sprint(totpPeriod)},
}
return "otpauth://totp/" + url.PathEscape(label) + "?" + query.Encode()
}
func VerifyTOTP(secret, code string, now time.Time) (int64, bool) {
code = strings.TrimSpace(code)
if len(code) != totpDigits {
return 0, false
}
step := now.Unix() / totpPeriod
for offset := -totpWindow; offset <= totpWindow; offset++ {
candidate, err := hotp(secret, step+offset, totpDigits)
if err == nil && subtle.ConstantTimeCompare([]byte(candidate), []byte(code)) == 1 {
return step + offset, true
}
}
return 0, false
}
func hotp(secret string, counter int64, digits int) (string, error) {
if counter < 0 || digits < 6 || digits > 8 {
return "", errors.New("invalid HOTP parameters")
}
decoded, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(strings.TrimSpace(secret)))
if err != nil {
return "", err
}
message := make([]byte, 8)
binary.BigEndian.PutUint64(message, uint64(counter))
mac := hmac.New(sha1.New, decoded)
_, _ = mac.Write(message)
digest := mac.Sum(nil)
offset := digest[len(digest)-1] & 0x0f
value := (uint32(digest[offset])&0x7f)<<24 |
uint32(digest[offset+1])<<16 |
uint32(digest[offset+2])<<8 |
uint32(digest[offset+3])
modulus := uint32(1)
for i := 0; i < digits; i++ {
modulus *= 10
}
return fmt.Sprintf("%0*d", digits, value%modulus), nil
}
func GenerateBackupCodes() ([]string, []BackupCodeRecord, error) {
codes := make([]string, 0, backupCodeCount)
records := make([]BackupCodeRecord, 0, backupCodeCount)
for range backupCodeCount {
random := make([]byte, backupCodeLength)
if _, err := rand.Read(random); err != nil {
return nil, nil, err
}
for index := range random {
random[index] = backupAlphabet[int(random[index])%len(backupAlphabet)]
}
raw := string(random)
code := raw[:4] + "-" + raw[4:]
codes = append(codes, code)
records = append(records, BackupCodeRecord{Hash: HashBackupCode(code)})
}
return codes, records, nil
}
func HashBackupCode(code string) string {
normalized := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(code), "-", ""))
digest := sha256.Sum256([]byte(normalized))
return hex.EncodeToString(digest[:])
}