Repository navigation
Shopee Seller connector, query-signed HMAC and Shopee token refresh - #1147
Merged
Merged
Conversation
Captured on main before any signing change: the exact headers, URL and query Kaufland and the generic header template put on the wire.
- HMAC signing takes optional query placement (placement, paramName,
timestampParam, queryParams) and two template variables (${pathname},
${query.NAME}). Without those fields the original code path runs
unchanged; the header-placed signatures pinned on main stay identical.
- ShopeeTokenService: code exchange, renewal before expiry with the
rotated refresh token stored encrypted on the connector, one renewal
per connector (in process and under an advisory lock), needs_reauth
and one email when Shopee refuses the refresh token. Only connectors
with authType HMAC and authConfig.tokenProvider "shopee" reach it.
- Shopee's own authorize, callback and complete endpoints under
/api/shopee, on the same encrypted single-use attempt store as OAuth;
the generic OAuth callback is untouched.
- A Shopee connector without a shop authorization reports
needs_authorization and skips the import probe.
- Dashboard: "Authorize with Provider" card for Shopee connectors, the
completion page and the setup page route Shopee to its endpoints.
22 tools on the Shopee Open Platform API v2: shop info and profile, listings (list, search, details, performance, variations), orders and order details, orders to ship, shipping parameters, tracking number and history, logistics channels, returns, order income, discounts, Ads balance and daily performance. Stock and price updates install switched off. Host, partner id and partner key are variables; the shop is authorized from the connector page. Logo, catalog and quoted adapter counts (487).
Another signed-in user holding the state could burn the owner's pending authorization. Peek first, refuse a different user, then take it once.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a Shopee Seller connector. Shopee signs every call with a query-string HMAC and authorizes shops with its own flow (not OAuth 2), so the engine needed two additions. Both are opt-in: nothing changes for existing connectors.
Engine changes and why they are opt-in
HMAC signing (
rest.engine.ts,applyHmacSignature) takes new optional fields inauthConfig.signature:placement: "query"andparamName: put the signature in a query parameter.timestampParam: send the timestamp as a query parameter.queryParams: static signed parameters, such as the partner id.${pathname}(the path without the query) and${query.NAME}.The new path only runs when one of these fields is set. Otherwise the original code runs line for line.
The first commit pins Kaufland's exact headers, URL and query at a fixed clock. It was written and passing on main before any engine change, and it still passes unchanged. A further test checks that
kauflandandshopeeare the only HMAC adapters, and that Kaufland uses none of the new fields and never calls the token service.Shopee tokens (
ShopeeTokenService). It is only reached when the connector hasauthType: HMACandauthConfig.tokenProvider: "shopee". It follows the OAuth2 and bearer-renewal patterns:error_shop_refresh_token,refresh_token_expired,shop_access_expired,shop_no_linked, or the oldererror_auth),ConnectorAuthStatusServicemarks the connector needs_reauth with one owner email, and calls fail fast until the shop is authorized again.invalid_acceess_token, the token is renewed, the request is re-signed and sent once more.Other small opt-in branches, all keyed on Shopee:
needs_authorization, so MCP does not list it.PendingOAuthFlowgets an optionalproviderfield.How a user authorizes a shop
SHOPEE_HOST,SHOPEE_PARTNER_IDandSHOPEE_PARTNER_KEY.POST /api/shopee/connectors/:id/authorizereturns Shopee's signed/api/v2/shop/auth_partnerlink. The redirect is<SERVER_URL>/api/shopee/callback/<state>, with the state in the path because Shopee returns nostateon this link.codeandshop_idto the dashboard.POST /api/shopee/completeexchanges the code, but only for the user who started the attempt. This is the same two-step model as the OAuth callback.The generic OAuth callback and the existing providers are untouched. I chose this over a branch in
/api/mcp-oauth: Shopee's link is signed, returnsshop_id, and its token calls are signed JSON. Fitting all of that in would have meant changing the shared callback, and keeping it separate costs about 200 lines.Tests
rest.engine.hmac-pin.spec.ts: the Kaufland pin.rest.engine.shopee.spec.ts: signing through the engine, the retry, and the opt-in guarantees.shopee-token.service.spec.ts: code exchange, rotation across renewals, single flight (8 calls, 1 request), adopting a token another process stored, needs_reauth with a fail-fast afterwards, refusal on HTTP 200, a 5xx that does not mark the connector, and the re-store after a failed transaction.shopee-auth.controller.spec.ts: permissions, state, other-user refusal, and a generic flow being refused.shopee.live.spec.ts: all 22 tools through the real engine, plus a live block that is skipped by default.hmac. The guide shows a sample signature without the key that produced it, so a test key was used.The baseline failure is the timing test in
curl.parser.spec.ts(639 ms against a 250 ms limit under load). It passed in the second run.tscand eslint are clean. Frontendtscand eslint are clean on the touched files.validate-adapters,adapter-count --check(487) andregenerate-catalogall pass; the regenerated catalog is byte-identical. No new dependency, no migration./api/shopeeroutes are mapped. It stopped at the missing database, as expected.Not tested live. I had no Shopee partner account and did not create one. Everything was checked against the official API reference (open.shopee.com, v2) and how the widely used SDKs behave, not against a real shop.
Left out
open.shopee.com/authlink. The signedauth_partnerlink works on every API host.returns/get_return_listpage_no: the text says 0, but the samples and SDKs use 1. The default is 1.item_id_listis sent as repeated keys, like the SDKs do.