feat: refine api key permissions and admin routes
This commit is contained in:
@@ -22,12 +22,13 @@ type Store struct {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidCredentials = errors.New("invalid account or password")
|
||||
ErrInvalidInvitation = errors.New("invalid or expired invitation code")
|
||||
ErrLocalUserRequired = errors.New("local gateway user is required")
|
||||
ErrProtectedDefault = errors.New("protected default resource cannot be deleted")
|
||||
ErrUserAlreadyExists = errors.New("user already exists")
|
||||
ErrWeakPassword = errors.New("password must be at least 8 characters")
|
||||
ErrInvalidCredentials = errors.New("invalid account or password")
|
||||
ErrInvalidInvitation = errors.New("invalid or expired invitation code")
|
||||
ErrAccessRuleResourceDenied = errors.New("access rule resource is not available")
|
||||
ErrLocalUserRequired = errors.New("local gateway user is required")
|
||||
ErrProtectedDefault = errors.New("protected default resource cannot be deleted")
|
||||
ErrUserAlreadyExists = errors.New("user already exists")
|
||||
ErrWeakPassword = errors.New("password must be at least 8 characters")
|
||||
)
|
||||
|
||||
func Connect(ctx context.Context, databaseURL string) (*Store, error) {
|
||||
@@ -102,6 +103,7 @@ type APIKey struct {
|
||||
TenantKey string `json:"tenantKey,omitempty"`
|
||||
UserID string `json:"userId,omitempty"`
|
||||
KeyPrefix string `json:"keyPrefix"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
UserGroupID string `json:"userGroupId,omitempty"`
|
||||
@@ -627,14 +629,45 @@ RETURNING id::text, provider, platform_key, name, COALESCE(internal_name, ''), C
|
||||
}
|
||||
|
||||
func (s *Store) DeletePlatform(ctx context.Context, id string) error {
|
||||
result, err := s.pool.Exec(ctx, `DELETE FROM integration_platforms WHERE id = $1::uuid`, id)
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
rows, err := tx.Query(ctx, `SELECT id::text FROM platform_models WHERE platform_id = $1::uuid`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modelIDs := make([]string, 0)
|
||||
for rows.Next() {
|
||||
var modelID string
|
||||
if err := rows.Scan(&modelID); err != nil {
|
||||
rows.Close()
|
||||
return err
|
||||
}
|
||||
modelIDs = append(modelIDs, modelID)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
rows.Close()
|
||||
return err
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
result, err := tx.Exec(ctx, `DELETE FROM integration_platforms WHERE id = $1::uuid`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() == 0 {
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
if _, err := tx.Exec(ctx, `
|
||||
DELETE FROM gateway_access_rules
|
||||
WHERE (resource_type = 'platform' AND resource_id = $1::uuid)
|
||||
OR (resource_type = 'platform_model' AND resource_id::text = ANY($2::text[]))`, id, modelIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (s *Store) ListModels(ctx context.Context) ([]PlatformModel, error) {
|
||||
@@ -962,7 +995,11 @@ SELECT 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
|
||||
COALESCE(last_used_at::text, ''), created_at, updated_at,
|
||||
CASE WHEN status = 'active' AND (expires_at IS NULL OR expires_at > now())
|
||||
THEN COALESCE(key_secret, '')
|
||||
ELSE ''
|
||||
END
|
||||
FROM gateway_api_keys
|
||||
WHERE gateway_user_id = $1::uuid AND deleted_at IS NULL
|
||||
ORDER BY created_at DESC`, gatewayUserID)
|
||||
@@ -973,7 +1010,8 @@ ORDER BY created_at DESC`, gatewayUserID)
|
||||
|
||||
items := make([]APIKey, 0)
|
||||
for rows.Next() {
|
||||
item, err := scanAPIKey(rows)
|
||||
var secret string
|
||||
item, err := scanAPIKeyWithSecret(rows, &secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1155,13 +1193,45 @@ RETURNING id::text, COALESCE(gateway_tenant_id::text, ''), gateway_user_id::text
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteAPIKey(ctx context.Context, apiKeyID string, user *auth.User) error {
|
||||
gatewayUserID := localGatewayUserID(user)
|
||||
if gatewayUserID == "" {
|
||||
return ErrLocalUserRequired
|
||||
}
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
result, err := tx.Exec(ctx, `
|
||||
UPDATE gateway_api_keys
|
||||
SET status = 'revoked',
|
||||
key_secret = NULL,
|
||||
deleted_at = now(),
|
||||
updated_at = now()
|
||||
WHERE id = $1::uuid AND gateway_user_id = $2::uuid AND deleted_at IS NULL`, apiKeyID, gatewayUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() == 0 {
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
DELETE FROM gateway_access_rules
|
||||
WHERE subject_type = 'api_key' AND subject_id = $1::uuid`, apiKeyID); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (s *Store) VerifyLocalAPIKey(ctx context.Context, secret string) (*auth.User, error) {
|
||||
prefix := apiKeyPrefix(secret)
|
||||
if prefix == "" {
|
||||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT k.id::text, k.key_hash, k.key_prefix, k.name, COALESCE(k.user_group_id::text, ''),
|
||||
SELECT k.id::text, k.key_hash, k.key_prefix, k.name, COALESCE(k.user_group_id::text, u.default_user_group_id::text, ''),
|
||||
u.id::text, u.username, u.roles, COALESCE(u.gateway_tenant_id::text, ''),
|
||||
COALESCE(u.tenant_id, ''), COALESCE(u.tenant_key, '')
|
||||
FROM gateway_api_keys k
|
||||
@@ -1722,6 +1792,9 @@ func scanAPIKeyWithSecret(scanner apiKeyScanner, secret *string) (APIKey, error)
|
||||
item.Scopes = decodeStringArray(scopesBytes)
|
||||
item.RateLimitPolicy = decodeObject(rateLimitPolicy)
|
||||
item.QuotaPolicy = decodeObject(quotaPolicy)
|
||||
if secret != nil {
|
||||
item.Secret = *secret
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user