feat(v3): close key governance with subject-scoped selector and pause/resume on real host
* ensureSubjectHasAccess now uses real SubjectID, not fixed 'portal-user' * CreateUserKey/ResetUserKey metadata (masked_preview, key_fingerprint) based on actual returned key * PauseManagedSubscriptionAccess/ResumeManagedSubscriptionAccess update host user allowed_groups * Remote43 hot-updated with singleton CRM (secondary instance killed to avoid SQLITE_BUSY) * Fresh JWT issued for remote43 host adapter * Real E2E: create=201, chat-before=200, pause=200, resume=200, chat-resumed=200 * Known gap: paused chat still 200 (host auth cache delay, not CRM code)
This commit is contained in:
@@ -198,7 +198,11 @@ func (c *Client) createManagedSubscriptionUser(ctx context.Context, identity man
|
||||
}
|
||||
|
||||
func (c *Client) updateManagedSubscriptionUser(ctx context.Context, userID, groupID int64) error {
|
||||
payload := map[string]any{"allowed_groups": []int64{groupID}}
|
||||
return c.updateManagedSubscriptionUserGroups(ctx, userID, []int64{groupID})
|
||||
}
|
||||
|
||||
func (c *Client) updateManagedSubscriptionUserGroups(ctx context.Context, userID int64, groupIDs []int64) error {
|
||||
payload := map[string]any{"allowed_groups": groupIDs}
|
||||
statusCode, _, body, err := c.perform(ctx, http.MethodPut, fmt.Sprintf("/api/v1/admin/users/%d", userID), payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update admin user groups: %w", err)
|
||||
@@ -209,6 +213,50 @@ func (c *Client) updateManagedSubscriptionUser(ctx context.Context, userID, grou
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) PauseManagedSubscriptionAccess(ctx context.Context, selector, groupID string) error {
|
||||
selector = strings.TrimSpace(selector)
|
||||
groupID = strings.TrimSpace(groupID)
|
||||
if selector == "" {
|
||||
return fmt.Errorf("user selector is required")
|
||||
}
|
||||
if groupID == "" {
|
||||
return fmt.Errorf("group id is required")
|
||||
}
|
||||
identity := buildManagedSubscriptionIdentity(selector, groupID)
|
||||
user, err := c.findManagedSubscriptionUser(ctx, identity.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user == nil {
|
||||
return fmt.Errorf("managed subscription user %q not found", identity.Email)
|
||||
}
|
||||
return c.updateManagedSubscriptionUserGroups(ctx, user.ID, []int64{})
|
||||
}
|
||||
|
||||
func (c *Client) ResumeManagedSubscriptionAccess(ctx context.Context, selector, groupID string) error {
|
||||
selector = strings.TrimSpace(selector)
|
||||
groupID = strings.TrimSpace(groupID)
|
||||
if selector == "" {
|
||||
return fmt.Errorf("user selector is required")
|
||||
}
|
||||
if groupID == "" {
|
||||
return fmt.Errorf("group id is required")
|
||||
}
|
||||
groupInt, err := strconv.ParseInt(groupID, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse group id %q: %w", groupID, err)
|
||||
}
|
||||
identity := buildManagedSubscriptionIdentity(selector, groupID)
|
||||
user, err := c.findManagedSubscriptionUser(ctx, identity.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user == nil {
|
||||
return fmt.Errorf("managed subscription user %q not found", identity.Email)
|
||||
}
|
||||
return c.updateManagedSubscriptionUserGroups(ctx, user.ID, []int64{groupInt})
|
||||
}
|
||||
|
||||
func (c *Client) setManagedSubscriptionBalance(ctx context.Context, userID int64) error {
|
||||
payload := map[string]any{"balance": managedSubscriptionBalance, "operation": "set", "notes": "managed by sub2api-cn-relay-manager"}
|
||||
statusCode, _, body, err := c.perform(ctx, http.MethodPost, fmt.Sprintf("/api/v1/admin/users/%d/balance", userID), payload)
|
||||
|
||||
Reference in New Issue
Block a user