完善 API Key 能力范围可视化和维护

This commit is contained in:
2026-06-07 19:01:32 +08:00
parent dc14866210
commit f47132a653
19 changed files with 1165 additions and 49 deletions
+84 -2
View File
@@ -22,9 +22,34 @@ type Store struct {
pool *pgxpool.Pool
}
func defaultAPIKeyScopes() []string {
return []string{"chat", "embedding", "rerank", "image", "video", "music", "audio"}
}
func normalizeAPIKeyScopes(scopes []string) []string {
out := make([]string, 0, len(scopes))
seen := make(map[string]struct{}, len(scopes))
for _, scope := range scopes {
scope = strings.ToLower(strings.TrimSpace(scope))
if scope == "" {
continue
}
if scope == "*" {
scope = "all"
}
if _, ok := seen[scope]; ok {
continue
}
seen[scope] = struct{}{}
out = append(out, scope)
}
return out
}
var (
ErrInvalidCredentials = errors.New("invalid account or password")
ErrInvalidInvitation = errors.New("invalid or expired invitation code")
ErrInvalidAPIKeyScopes = errors.New("api key scopes must not be empty")
ErrAccessRuleResourceDenied = errors.New("access rule resource is not available")
ErrInsufficientWalletBalance = errors.New("insufficient wallet balance")
ErrLocalUserRequired = errors.New("local gateway user is required")
@@ -106,6 +131,10 @@ type CreateAPIKeyInput struct {
ExpiresAt string `json:"expiresAt"`
}
type UpdateAPIKeyScopesInput struct {
Scopes []string `json:"scopes"`
}
type APIKey struct {
ID string `json:"id"`
GatewayTenantID string `json:"gatewayTenantId,omitempty"`
@@ -1156,6 +1185,32 @@ ORDER BY priority ASC, group_key ASC`)
return items, rows.Err()
}
func (s *Store) ListUserGroupsBySubject(ctx context.Context, groupIDs []string, groupKeys []string) ([]UserGroup, error) {
rows, err := s.pool.Query(ctx, `
SELECT `+userGroupColumns+`
FROM gateway_user_groups
WHERE status = 'active'
AND (
id::text = ANY($1::text[])
OR group_key = ANY($2::text[])
)
ORDER BY priority ASC, group_key ASC`, groupIDs, groupKeys)
if err != nil {
return nil, err
}
defer rows.Close()
items := make([]UserGroup, 0)
for rows.Next() {
item, err := scanUserGroup(rows)
if err != nil {
return nil, err
}
items = append(items, item)
}
return items, rows.Err()
}
func (s *Store) ListAPIKeys(ctx context.Context, user *auth.User) ([]APIKey, error) {
gatewayUserID := localGatewayUserID(user)
if gatewayUserID == "" {
@@ -1201,7 +1256,7 @@ func (s *Store) ListPlayableAPIKeys(ctx context.Context, user *auth.User) ([]Pla
}
created, err := s.CreateAPIKey(ctx, CreateAPIKeyInput{
Name: "Playground API Key",
Scopes: []string{"chat", "image", "video"},
Scopes: defaultAPIKeyScopes(),
}, user)
if err != nil {
return nil, err
@@ -1256,8 +1311,9 @@ func (s *Store) CreateAPIKey(ctx context.Context, input CreateAPIKeyInput, user
}
scopes := input.Scopes
if len(scopes) == 0 {
scopes = []string{"chat", "image", "video"}
scopes = defaultAPIKeyScopes()
}
scopes = normalizeAPIKeyScopes(scopes)
secret, err := generateAPIKeySecret()
if err != nil {
return CreatedAPIKey{}, err
@@ -1317,6 +1373,32 @@ RETURNING id::text, COALESCE(gateway_tenant_id::text, ''), gateway_user_id::text
return CreatedAPIKey{APIKey: item, Secret: secret}, nil
}
func (s *Store) UpdateAPIKeyScopes(ctx context.Context, apiKeyID string, input UpdateAPIKeyScopesInput, user *auth.User) (APIKey, error) {
gatewayUserID := localGatewayUserID(user)
if gatewayUserID == "" {
return APIKey{}, ErrLocalUserRequired
}
scopes := normalizeAPIKeyScopes(input.Scopes)
if len(scopes) == 0 {
return APIKey{}, ErrInvalidAPIKeyScopes
}
scopesJSON, err := json.Marshal(scopes)
if err != nil {
return APIKey{}, err
}
return scanAPIKey(s.pool.QueryRow(ctx, `
UPDATE gateway_api_keys
SET scopes = $3::jsonb, updated_at = now()
WHERE id = $1::uuid AND gateway_user_id = $2::uuid AND deleted_at IS NULL
RETURNING id::text, COALESCE(gateway_tenant_id::text, ''), gateway_user_id::text,
COALESCE(tenant_id, ''), COALESCE(tenant_key, ''), COALESCE(user_id, ''),
key_prefix, name, scopes, COALESCE(user_group_id::text, ''),
rate_limit_policy, quota_policy, status, COALESCE(expires_at::text, ''),
COALESCE(last_used_at::text, ''), created_at, updated_at`,
apiKeyID, gatewayUserID, string(scopesJSON),
))
}
func (s *Store) DisableAPIKey(ctx context.Context, apiKeyID string, user *auth.User) (APIKey, error) {
gatewayUserID := localGatewayUserID(user)
if gatewayUserID == "" {