package agentcap import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "io" "strings" "time" ) const ModulePathPrefix = "ajzm1_" // ModulePathClaim 写入加密路径 token。根据用户/智能体 ID 生成路径,公开侧可用平台密钥解开。 type ModulePathClaim struct { TenantID int64 `json:"t"` OwnerID int64 `json:"o"` // user_id 或 agent_id(宇恒侧用户) Slug string `json:"s"` IssuedAt int64 `json:"iat"` } func modulePathKey(masterSecret string) []byte { sum := sha256.Sum256([]byte("aijianzhan-module-path-v1|" + masterSecret)) return sum[:] } // SealModulePath 根据用户 ID(及租户、模块 slug)加密,返回: // - token:单段密文(可作 URL 段) // - filePath:逻辑文件路径 m/{token}(不含明文用户 id / slug) func SealModulePath(masterSecret string, tenantID, ownerID int64, slug string) (token, filePath string, err error) { slug = strings.TrimSpace(slug) if slug == "" { return "", "", fmt.Errorf("slug required") } if ownerID <= 0 { return "", "", fmt.Errorf("owner id required") } if tenantID <= 0 { return "", "", fmt.Errorf("tenant id required") } claim := ModulePathClaim{ TenantID: tenantID, OwnerID: ownerID, Slug: slug, IssuedAt: time.Now().UTC().Unix(), } raw, err := json.Marshal(claim) if err != nil { return "", "", err } key := modulePathKey(masterSecret) block, err := aes.NewCipher(key) if err != nil { return "", "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", "", err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", "", err } ciphertext := gcm.Seal(nil, nonce, raw, nil) packed := append(nonce, ciphertext...) token = ModulePathPrefix + base64.RawURLEncoding.EncodeToString(packed) filePath = "m/" + token return token, filePath, nil } // OpenModulePath 解密路径 token,得到租户、用户、模块 slug。 func OpenModulePath(masterSecret, token string) (*ModulePathClaim, error) { token = strings.TrimSpace(token) token = strings.TrimPrefix(token, "/") if strings.HasPrefix(token, "m/") { token = strings.TrimPrefix(token, "m/") } if !strings.HasPrefix(token, ModulePathPrefix) { return nil, fmt.Errorf("invalid module path token") } packed, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(token, ModulePathPrefix)) if err != nil { return nil, fmt.Errorf("invalid module path token encoding") } key := modulePathKey(masterSecret) block, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } ns := gcm.NonceSize() if len(packed) < ns+1 { return nil, fmt.Errorf("invalid module path token length") } plain, err := gcm.Open(nil, packed[:ns], packed[ns:], nil) if err != nil { return nil, fmt.Errorf("module path decrypt failed") } var claim ModulePathClaim if err := json.Unmarshal(plain, &claim); err != nil { return nil, err } if claim.TenantID <= 0 || claim.OwnerID <= 0 || claim.Slug == "" { return nil, fmt.Errorf("module path claim incomplete") } return &claim, nil }