feat(auth,gateway,user): X-API-Key 真接线 + key 一律超管 + owner 档案真落盘(裁决 15.4.2/15.4.3/10.4.5/08.4.2)
- 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 显式报错,不再派生回落
This commit is contained in:
1 parent
8c6466cb00
commit
adef36538f
13 files changed
+1223
-199
No files matched your search
+25
-20
@@ -11,15 +11,16 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
)
|
||||
|
||||
// APIKey manages a bounded, in-memory constant table of API keys, each bound to
|
||||
// a Role so keys can be minted for admin/owner to reach administrative
|
||||
// endpoints. Keys are generated with crypto/rand and stored by their string
|
||||
// form. This suits the current single-binary milestone; production should
|
||||
// persist keys (e.g. hashed) and support revocation.
|
||||
// APIKey manages a bounded, in-memory constant table of API keys. 按裁决
|
||||
// 15.4.3(主人原话:「key 始终给超级管理员权限」),表里**不再绑定角色**:
|
||||
// 任何已登记且未吊销的 key 鉴权后一律 owner 级 —— key 用于管理员调试,甚至
|
||||
// 生产环境救火,因此不做权限分级。Keys are generated with crypto/rand and
|
||||
// stored by their string form. This suits the current single-binary milestone;
|
||||
// production should persist keys (e.g. hashed) and support revocation.
|
||||
type APIKey struct {
|
||||
mu sync.RWMutex
|
||||
cfg config.AuthConfig
|
||||
keys map[string]Role
|
||||
keys map[string]struct{}
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
@@ -28,16 +29,18 @@ type APIKey struct {
|
||||
func NewAPIKey(cfg config.AuthConfig) *APIKey {
|
||||
return &APIKey{
|
||||
cfg: cfg,
|
||||
keys: make(map[string]Role),
|
||||
keys: make(map[string]struct{}),
|
||||
log: logger.WithSource(slog.Default(), source),
|
||||
}
|
||||
}
|
||||
|
||||
// Generate mints a new random key bound to role and stores it. Returns the
|
||||
// plaintext key exactly once; it must not be logged.
|
||||
func (k *APIKey) Generate(role Role) (string, error) {
|
||||
if !role.Valid() {
|
||||
return "", fmt.Errorf("%w: %q", ErrInvalidRole, role)
|
||||
// Generate 铸造一个新 key 并登记进表,返回**仅此一次**可见的明文(绝不记录到
|
||||
// 日志)。role 是可选形参:传入时只校验它是不是已知角色名(未知即报
|
||||
// ErrInvalidRole),**不参与存储、不影响授权** —— 按裁决 15.4.3,key 表已无
|
||||
// 角色维度,任何有效 key 都是超管级(见 Lookup)。
|
||||
func (k *APIKey) Generate(role ...Role) (string, error) {
|
||||
if len(role) > 0 && !role[0].Valid() {
|
||||
return "", fmt.Errorf("%w: %q", ErrInvalidRole, role[0])
|
||||
}
|
||||
raw := make([]byte, keyLength)
|
||||
if _, err := rand.Read(raw); err != nil {
|
||||
@@ -46,7 +49,7 @@ func (k *APIKey) Generate(role Role) (string, error) {
|
||||
key := base64.RawURLEncoding.EncodeToString(raw)
|
||||
|
||||
k.mu.Lock()
|
||||
k.keys[key] = role
|
||||
k.keys[key] = struct{}{}
|
||||
k.mu.Unlock()
|
||||
return key, nil
|
||||
}
|
||||
@@ -57,12 +60,13 @@ func (k *APIKey) Verify(key string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// Lookup returns the role bound to key.
|
||||
// Lookup 报告 key 是否已登记,并返回其授权角色。按裁决 15.4.3,**任何**已登记
|
||||
// 的 key 都是超管(owner)级。key 不在表中时 ok 为 false,调用方必须据此拒绝。
|
||||
func (k *APIKey) Lookup(key string) (Role, bool) {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
role, ok := k.keys[key]
|
||||
return role, ok
|
||||
_, ok := k.keys[key]
|
||||
return RoleOwner, ok
|
||||
}
|
||||
|
||||
// Revoke removes key from the constant table.
|
||||
@@ -79,7 +83,7 @@ func (k *APIKey) RevokeAll() int {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
n := len(k.keys)
|
||||
k.keys = make(map[string]Role)
|
||||
k.keys = make(map[string]struct{})
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -106,13 +110,14 @@ func (k *APIKey) RevokeByMask(mask string) bool {
|
||||
// List returns a summary of every currently registered API key for the admin
|
||||
// panel. Key material is never returned; each identifier is masked so the panel
|
||||
// can tell keys apart without exposing the secret. The in-memory store keeps no
|
||||
// creation time, so CreatedAt is nil for every entry.
|
||||
// creation time, so CreatedAt is nil for every entry. 每条的角色恒为 owner
|
||||
// (裁决 15.4.3:key 不做角色分级)。
|
||||
func (k *APIKey) List() []UserSummary {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
out := make([]UserSummary, 0, len(k.keys))
|
||||
for key, role := range k.keys {
|
||||
out = append(out, UserSummary{ID: maskKey(key), Username: "apikey", Role: role})
|
||||
for key := range k.keys {
|
||||
out = append(out, UserSummary{ID: maskKey(key), Username: "apikey", Role: RoleOwner})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user