fix(access): verify subscription readiness with real user keys
When subscription access is requested with an explicit access_api_key, assign the subscription to the real target user, bind that user's API key to the subscription group, and probe readiness with the same key instead of falling back to a managed synthetic user. Update the runtime/reconcile flows, adapter tests, and source-of-truth docs so subscription_ready now reflects user-visible host access rather than managed-key-only closure success.
This commit is contained in:
@@ -52,6 +52,9 @@ func (c *Client) EnsureSubscriptionAccess(ctx context.Context, req EnsureSubscri
|
||||
if err != nil {
|
||||
return SubscriptionAccessRef{}, fmt.Errorf("parse group id %q: %w", groupID, err)
|
||||
}
|
||||
if probeAPIKey := strings.TrimSpace(req.ProbeAPIKey); probeAPIKey != "" {
|
||||
return c.ensureRealSubscriptionAccess(ctx, selector, groupInt, probeAPIKey)
|
||||
}
|
||||
|
||||
identity := buildManagedSubscriptionIdentity(selector, groupID)
|
||||
user, err := c.findManagedSubscriptionUser(ctx, identity.Email)
|
||||
@@ -82,7 +85,7 @@ func (c *Client) EnsureSubscriptionAccess(ctx context.Context, req EnsureSubscri
|
||||
if err != nil {
|
||||
return SubscriptionAccessRef{}, err
|
||||
}
|
||||
if err := c.bindManagedSubscriptionAPIKey(ctx, keyRecord.ID, groupInt); err != nil {
|
||||
if err := c.bindAPIKeyGroup(ctx, keyRecord.ID, groupInt); err != nil {
|
||||
return SubscriptionAccessRef{}, err
|
||||
}
|
||||
return SubscriptionAccessRef{UserID: strconv.FormatInt(user.ID, 10), APIKey: identity.CustomKey}, nil
|
||||
@@ -297,11 +300,55 @@ func (c *Client) findManagedSubscriptionAPIKey(ctx context.Context, userID int64
|
||||
return nil, fmt.Errorf("managed api key %q not found for user %d", identity.KeyName, userID)
|
||||
}
|
||||
|
||||
func (c *Client) bindManagedSubscriptionAPIKey(ctx context.Context, keyID, groupID int64) error {
|
||||
func (c *Client) ensureRealSubscriptionAccess(ctx context.Context, selector string, groupID int64, probeAPIKey string) (SubscriptionAccessRef, error) {
|
||||
userID, err := strconv.ParseInt(strings.TrimSpace(selector), 10, 64)
|
||||
if err != nil {
|
||||
return SubscriptionAccessRef{}, fmt.Errorf("parse real subscription user id %q: %w", selector, err)
|
||||
}
|
||||
keyRecord, err := c.findUserAPIKeyByRawKey(ctx, userID, probeAPIKey)
|
||||
if err != nil {
|
||||
return SubscriptionAccessRef{}, err
|
||||
}
|
||||
if err := c.bindAPIKeyGroup(ctx, keyRecord.ID, groupID); err != nil {
|
||||
return SubscriptionAccessRef{}, err
|
||||
}
|
||||
return SubscriptionAccessRef{
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
APIKey: probeAPIKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) findUserAPIKeyByRawKey(ctx context.Context, userID int64, rawKey string) (*adminAPIKeyRecord, error) {
|
||||
statusCode, _, body, err := c.perform(ctx, http.MethodGet, fmt.Sprintf("/api/v1/admin/users/%d/api-keys?page=1&page_size=1000&sort_by=created_at&sort_order=desc", userID), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list user api keys: %w", err)
|
||||
}
|
||||
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
|
||||
return nil, newHTTPError(http.MethodGet, fmt.Sprintf("/api/v1/admin/users/%d/api-keys", userID), statusCode, body)
|
||||
}
|
||||
var envelope struct {
|
||||
Data struct {
|
||||
Items []adminAPIKeyRecord `json:"items"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &envelope); err != nil {
|
||||
return nil, fmt.Errorf("decode user api keys response: %w", err)
|
||||
}
|
||||
trimmedRawKey := strings.TrimSpace(rawKey)
|
||||
for _, item := range envelope.Data.Items {
|
||||
if strings.TrimSpace(item.Key) == trimmedRawKey {
|
||||
key := item
|
||||
return &key, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("probe api key not found for user %d", userID)
|
||||
}
|
||||
|
||||
func (c *Client) bindAPIKeyGroup(ctx context.Context, keyID, groupID int64) error {
|
||||
payload := map[string]any{"group_id": groupID}
|
||||
statusCode, _, body, err := c.perform(ctx, http.MethodPut, fmt.Sprintf("/api/v1/admin/api-keys/%d", keyID), payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bind managed api key group: %w", err)
|
||||
return fmt.Errorf("bind api key group: %w", err)
|
||||
}
|
||||
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
|
||||
return newHTTPError(http.MethodPut, fmt.Sprintf("/api/v1/admin/api-keys/%d", keyID), statusCode, body)
|
||||
|
||||
Reference in New Issue
Block a user