- 15.4.2:管理 API 与 Bearer 共用同一 Authorize 路径,X-API-Key 命中签发表即过门;未知/已吊销/空值 401 且不回落 Bearer(fail-closed) - 15.4.3:key 表去掉角色维度,任一有效 key 鉴权后即 owner;签发接口取消 role 分级(不再读请求体) - 10.4.5:新增 GET/PUT /api/v1/admin/owner-profile(owner-only,PUT 带审计 owner.profile.update),0600 原子落盘 + 活档案热替换;前端主人档案改接真实 API,读不到即禁用保存,删掉硬编码默认档案 - 08.4.2:persona 的 identity.systemPrompt 必填,缺失/空白 400 显式报错,不再派生回落
71 lines
2.2 KiB
Go
71 lines
2.2 KiB
Go
package user
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// ErrRequiredField is wrapped by validation errors so callers can detect an
|
|
// owner profile that is missing required fields.
|
|
var ErrRequiredField = errors.New("user: required field missing")
|
|
|
|
// LoadOwnerProfile reads and validates the owner profile YAML at path.
|
|
func LoadOwnerProfile(path string) (*OwnerProfile, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("user: read %s: %w", path, err)
|
|
}
|
|
p := &OwnerProfile{}
|
|
if err := yaml.Unmarshal(data, p); err != nil {
|
|
return nil, fmt.Errorf("user: parse %s: %w", path, err)
|
|
}
|
|
if err := p.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// Validate 用与 LoadOwnerProfile 完全相同的一套规则校验一份**内存中**的档案,
|
|
// 使管理面板的写路径能在落盘前就拒掉坏档案,而不是先写进磁盘、等下次启动才
|
|
// 报错。它只校验必填字段与半空的 accounts 项,不校验平台名是否已知
|
|
// (适配器可以后装)。
|
|
func (p *OwnerProfile) Validate() error {
|
|
if p == nil {
|
|
return fmt.Errorf("%w: empty profile", ErrRequiredField)
|
|
}
|
|
return p.validate()
|
|
}
|
|
|
|
// validate checks the owner profile for its required fields and for partial
|
|
// whitelist entries. An empty whitelist is allowed: on a fresh install nobody
|
|
// has been configured as owner, so the policy engine defaults to deny until the
|
|
// operator fills in account ids (fail-safe, per design/11).
|
|
func (p *OwnerProfile) validate() error {
|
|
var missing []string
|
|
if strings.TrimSpace(p.ID) == "" {
|
|
missing = append(missing, "id")
|
|
}
|
|
if strings.TrimSpace(p.DisplayName) == "" {
|
|
missing = append(missing, "displayName")
|
|
}
|
|
if len(missing) > 0 {
|
|
return fmt.Errorf("%w: missing %s", ErrRequiredField, strings.Join(missing, ", "))
|
|
}
|
|
for i, a := range p.Accounts {
|
|
pe := strings.TrimSpace(a.Platform) == ""
|
|
ae := strings.TrimSpace(a.AccountID) == ""
|
|
switch {
|
|
case pe && ae:
|
|
// Fully-empty entry: an unconfigured placeholder, skip it.
|
|
continue
|
|
case pe || ae:
|
|
return fmt.Errorf("%w: accounts[%d] requires both platform and accountId", ErrRequiredField, i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|