Skip to content

fix(frontend): RI-exchange account chip sends CloudAccount UUID where AWS and Azure handlers expect the provider account ID #162

Description

@cristim

The account-filter chip carries a CloudAccount UUID, but both RI-exchange listing endpoints interpret their account parameter as the provider's own account identifier (external_id). The values never match, so the endpoints return empty.

This affects AWS and Azure, not just Azure. resolveScope() (frontend/src/riexchange.ts:62-66) is a single function feeding both loaders from the same UUID:

  • loadConvertibleRIs (AWS, riexchange.ts:109) — the handler expects an AWS account number (TestListConvertibleRIs_AccountScopeMismatchReturnsEmpty sends account_id: "123456789012")
  • loadExchangeableAzureRIs (Azure, riexchange.ts:100) — the handler expects an Azure subscription ID

sourceIdentity.ExternalID() (internal/api/handler.go:835) returns AccountID for aws, SubscriptionID for azure, ProjectID for gcp — so external_id is the correct value for every provider, and the UUID is correct for none of them here.

Current user-visible behaviour

Why a one-line fix does not exist

Sized during the LeanerCloud/cloud-commitments-cli#1711 review; both obvious approaches fail:

Changing the chip value globally is wrong. state.getCurrentAccountIDs() is consumed by seven non-test source files — dashboard.ts, history.ts, inventory.ts, plans.ts, recommendations.ts, riexchange.ts, modules/savings-history.ts. Six of them send it as account_ids matched against cloud_account_id (a CloudAccount UUID field) and are correct as-is. Swapping topbar-filters.ts:107 from a.id to a.external_id would fix RI-exchange and break the other six.

Resolving at the call site needs new state. There is no UUID -> external_id lookup anywhere in the frontend. populateAccountOptions does fetch api.listAccountsMinimal(), which carries external_id (frontend/src/api/accounts.ts:117-121, already used for the option label), but discards the list after building the chip options. Nothing caches it.

Suggested fix

Introduce a UUID-keyed account cache populated alongside the chip options and exposed for riexchange.ts, then resolve to external_id at the two RI-exchange call sites only. Leave the chip value itself as the UUID, since that is right for the other six consumers.

Handle the async race: resolveScope() can run before the accounts fetch resolves. A miss must not silently degrade to "send the UUID anyway" or "send nothing and show everything" — decide explicitly and fail visibly.

Cover both providers at that one call site. Add a test asserting the value sent as account_id/subscription_id is a provider account identifier and not a CloudAccount UUID; the mismatch survived because both are opaque strings and neither side validates the shape.

Do not change either handler's scoping or 404 posture, which exist to prevent account enumeration.

Found during the adversarial review of LeanerCloud/cloud-commitments-cli#1711; the AWS half was found while sizing the Azure fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions