fix(shopinbit): don't pass auth for countries endpoint
What changed, and why it matters
This commit changes the Stack Wallet app so that when it fetches the list of supported countries from the ShopInBit service, it no longer sends the user's authentication token with the request. It also tidies up how login credentials are formatted before being sent. The main security-relevant change is removing unnecessary authentication from a public metadata endpoint, which reduces the risk that the user's token could be leaked or logged if that endpoint is compromised or misbehaves.
Treat this as a minor hardening improvement. Review whether any other read-only metadata endpoints (e.g., categories, products) also do not require authentication and should set `needsAuth: false`. Verify that the `/meta/countries` endpoint on the server side truly accepts and correctly handles unauthenticated requests, and confirm the token manager refactor does not alter the encoded credential payload.
Security signals we found
Authentication token removed from a metadata endpoint request
New `needsAuth` flag added to request helpers to support unauthenticated calls
Credentials formatting refactor in token manager (no functional change visible)
Evidence from the diff
The patch adds a needsAuth parameter (defaulting to true) to the ShopInBit client’s internal request helpers. The /meta/countries call is updated to pass needsAuth: false, which causes the client to omit the bearer token and customer-key headers, sending only Accept: application/json. Previously every request automatically retrieved and attached a valid token. A secondary refactor in token_manager.dart extracts the form-encoded body into a local variable before passing it to the HTTP client, with no functional change to the encoded output.
Changed components
lib/services/shopinbit/src/client.dartlib/services/shopinbit/src/token_manager.dartInspect captured patch +15 / −5
diff --git a/lib/services/shopinbit/src/client.dart b/lib/services/shopinbit/src/client.dart
index 16f7013..5e8c0ff 100644
--- a/lib/services/shopinbit/src/client.dart
+++ b/lib/services/shopinbit/src/client.dart
@@ -88,6 +88,7 @@ class ShopInBitClient {
'GET',
'/meta/countries',
needsCustomerKey: false,
+ needsAuth: false,
parse: (body) {
final decoded = jsonDecode(body);
if (decoded is List) {
@@ -510,14 +511,20 @@ class ShopInBitClient {
Map<String, dynamic>? body,
Map<String, String>? query,
bool needsCustomerKey = true,
+ bool needsAuth = true,
}) async {
- final token = await _tokenManager.getValidToken();
final resolved = _resolvePath(path);
var uri = Uri.parse('$baseUrl$resolved');
if (query != null && query.isNotEmpty) {
uri = uri.replace(queryParameters: query);
}
- final headers = _headers(token, needsCustomerKey: needsCustomerKey);
+ final Map<String, String> headers;
+ if (needsAuth) {
+ final token = await _tokenManager.getValidToken();
+ headers = _headers(token, needsCustomerKey: needsCustomerKey);
+ } else {
+ headers = {'Accept': 'application/json'};
+ }
final proxy = _proxyInfo;
Logging.instance.t("$_kTag $method $uri");
@@ -602,6 +609,7 @@ class ShopInBitClient {
Map<String, dynamic>? body,
Map<String, String>? query,
bool needsCustomerKey = true,
+ bool needsAuth = true,
required T Function(String) parse,
}) async {
try {
@@ -611,6 +619,7 @@ class ShopInBitClient {
body: body,
query: query,
needsCustomerKey: needsCustomerKey,
+ needsAuth: needsAuth,
);
final resolved = _resolvePath(path);
diff --git a/lib/services/shopinbit/src/token_manager.dart b/lib/services/shopinbit/src/token_manager.dart
index 0f77a99..a72d813 100644
--- a/lib/services/shopinbit/src/token_manager.dart
+++ b/lib/services/shopinbit/src/token_manager.dart
@@ -58,12 +58,13 @@ class TokenManager {
final Response response;
try {
+ final formBody = Uri(
+ queryParameters: {'username': accessKey, 'password': partnerSecret},
+ ).query;
response = await _httpClient.post(
url: uri,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- body: Uri(
- queryParameters: {'username': accessKey, 'password': partnerSecret},
- ).query,
+ body: formBody,
proxyInfo: !AppConfig.hasFeature(AppFeature.tor)
? null
: Prefs.instance.useTor
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.