From 3647b32cc03d73f95134361f151852fbca6c46aa Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 6 Jul 2026 23:45:23 +1000 Subject: [PATCH] Fix login issues (#852) - Properly clear secure token - Better error messages --- lib/api.dart | 8 ++----- lib/user_profile.dart | 11 +++++++--- lib/widget/dialogs.dart | 48 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/lib/api.dart b/lib/api.dart index 1f07581c..81b34286 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -471,11 +471,7 @@ class InvenTreeAPI { if (!response.successful()) { debug("Server returned invalid response: ${response.statusCode}"); - showStatusCodeError( - apiUrl, - response.statusCode, - details: response.data.toString(), - ); + showStatusCodeError(apiUrl, response.statusCode, details: response.data); return false; } @@ -1535,7 +1531,7 @@ class InvenTreeAPI { showStatusCodeError( url, _response.statusCode, - details: response.data.toString(), + details: response.data, ); } } diff --git a/lib/user_profile.dart b/lib/user_profile.dart index f6173ea2..8e81131d 100644 --- a/lib/user_profile.dart +++ b/lib/user_profile.dart @@ -112,9 +112,11 @@ class UserProfileDBManager { profile.token = secureToken; } else if (profile.token.isNotEmpty) { // Legacy profile - migrate its plaintext token into secure storage, - // and strip it from the Sembast record on next write + // and strip it from the Sembast record now. Must use put() (full + // replace) rather than update() (merge), otherwise the stale legacy + // "token" field survives and gets re-migrated on every future read. await _saveToken(key, profile.token); - await store.record(key).update(await _db, profile.toJson()); + await store.record(key).put(await _db, profile.toJson()); } return profile; @@ -185,7 +187,10 @@ class UserProfileDBManager { return result; } - await store.record(profile.key).update(await _db, profile.toJson()); + // put() (full replace) rather than update() (merge) - a legacy record's + // stale "token" field must not survive a write, or it will be + // re-migrated into secure storage on the next read (see _loadToken) + await store.record(profile.key).put(await _db, profile.toJson()); await _saveToken(profile.key!, profile.token); return true; diff --git a/lib/widget/dialogs.dart b/lib/widget/dialogs.dart index ca5c754d..61da23e9 100644 --- a/lib/widget/dialogs.dart +++ b/lib/widget/dialogs.dart @@ -310,19 +310,61 @@ Future showServerError( Future showStatusCodeError( String url, int status, { - String details = "", + dynamic details, }) async { String msg = statusCodeToString(status); String extra = url + "\n" + "${L10().statusCode}: ${status}"; - if (details.isNotEmpty) { + String errorDetails = extractErrorDetails(details); + + if (errorDetails.isNotEmpty) { extra += "\n"; - extra += details; + extra += errorDetails; } showServerError(url, msg, extra); } +/* + * Attempt to extract a human-readable error message from API response data. + * The server commonly returns error information under a "detail", "error" + * or "errors" key - prefer displaying that over the raw JSON. + * Falls back to displaying the raw data as a list of key : value pairs. + */ +String extractErrorDetails(dynamic data) { + if (data is Map) { + for (final key in ["detail", "error", "errors"]) { + var value = data[key]; + + if (value == null) { + continue; + } + + if (value is List) { + return value.map((e) => e.toString()).join("\n"); + } + + return value.toString(); + } + + if (data.isNotEmpty) { + return data.entries.map((e) => "${e.key}: ${e.value}").join("\n"); + } + + return ""; + } + + if (data is List && data.isNotEmpty) { + return data.map((e) => e.toString()).join("\n"); + } + + if (data is String) { + return data; + } + + return ""; +} + /* * Provide a human-readable descriptor for a particular error code */