Skip to content

Commit 86be7e5

Browse files
committed
fix(eu-data-act): actionable login error messages + don't restart-loop on bad creds
The login flow used to throw a single generic 'Login failed - check email and password' for every IdP refusal. The Identity Portal actually exposes a specific error code in the landing-URL query string (?error=...) and the templateModel.error in the body — we now parse both and map known codes to user-actionable messages: password_invalid -> 'password incorrect' email_invalid -> 'email not recognised' throttled -> 'too many failed attempts, wait ~30 min' account_disabled -> 'account locked, reset password' tenants.notAuthorized -> 'not entitled to EU Data Act portal, complete browser-side first-time setup' Plus, the runEuDataAct catch in main.js no longer schedules a 30-min restart loop for credential failures (pointless — won't self-heal). It logs once, marks info.connection=false, and stays put until the user fixes the config and restarts manually. Network/transient failures still get the 30-min retry as before. Verified live: wrong password now reports 'Login failed: password incorrect (login.errors.password_invalid).'
1 parent 65623f5 commit 86be7e5

2 files changed

Lines changed: 67 additions & 2 deletions

File tree

lib/euDataAct.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,59 @@ function loginErrorText(html) {
140140
return String(err);
141141
}
142142

143+
/**
144+
* Map a failed-login landing page (URL + HTML) to a user-actionable message.
145+
*
146+
* Identity portal surfaces the failure reason in three places:
147+
* 1. ?error=<code> on the landing URL (e.g. login.errors.password_invalid)
148+
* 2. window._IDK.templateModel.error in the body
149+
* 3. nothing at all — bare "back to signin-service" redirect
150+
*
151+
* Known error codes (observed in the wild):
152+
* login.errors.password_invalid - wrong password
153+
* login.errors.email_invalid - email not registered / typo
154+
* login.error.throttled - too many failed attempts, locked out
155+
* login.errors.tenants.notAuthorized - account ineligible for this client
156+
* login.errors.account_disabled - account locked by VW
157+
*/
158+
function diagnoseLoginFailure(landing) {
159+
const errCode = (() => {
160+
try {
161+
return new URL(landing.url).searchParams.get("error");
162+
} catch {
163+
return null;
164+
}
165+
})();
166+
const modelText = loginErrorText(landing.body);
167+
const code = errCode || modelText || "";
168+
169+
if (/password_invalid/i.test(code)) {
170+
return "Login failed: password incorrect (login.errors.password_invalid).";
171+
}
172+
if (/email_invalid|user_id|identifier/i.test(code)) {
173+
return "Login failed: email not recognised by VW Identity (login.errors.email_invalid).";
174+
}
175+
if (/throttle|rate_limit|too_many/i.test(code)) {
176+
return "Login failed: too many failed attempts, account temporarily throttled by VW. " +
177+
"Wait ~30 min before retrying.";
178+
}
179+
if (/account_disabled|locked|blocked/i.test(code)) {
180+
return "Login failed: VW account is locked or disabled. Reset the password at " +
181+
"https://identity.vwgroup.io/ before retrying.";
182+
}
183+
if (/tenants?\.?notAuthorized|client_not_allowed|consent/i.test(code)) {
184+
return "Login failed: this VW account is not entitled to use the EU Data Act portal. " +
185+
"Open https://eu-data-act.drivesomethinggreater.com/ in a browser and complete " +
186+
"first-time setup (terms, vehicle linking).";
187+
}
188+
if (code) {
189+
return `Login failed: ${code}`;
190+
}
191+
// Last resort: include the URL so the user can paste it into a browser
192+
// and see what the portal is complaining about.
193+
return `Login failed (no error code reported by IdP). Landing URL: ${landing.url}`;
194+
}
195+
143196
// --- value parsing (mirrors data.py.parse_value) ---------------------------
144197

145198
const _DURATION_RE = /^(-?\d+(?:\.\d+)?)\s*s$/i;
@@ -450,7 +503,7 @@ class EuDataActClient {
450503
throw new Error(err || `Login rejected (HTTP ${landing.status})`);
451504
}
452505
if (landing.url.includes("signin-service") || landing.url.includes("/error")) {
453-
throw new Error("Login failed - check email and password");
506+
throw new Error(diagnoseLoginFailure(landing));
454507
}
455508
if (new URL(landing.url).host !== new URL(BASE_URL).host) {
456509
throw new Error("Login did not complete (ended at " + landing.url + ")");

main.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,19 @@ class VwWeconnect extends utils.Adapter {
312312
// portal-based pipeline.
313313
if (this.config.type === "id") {
314314
this.runEuDataAct().catch((err) => {
315-
this.log.error("EU Data Act flow failed: " + (err && err.message ? err.message : err));
315+
const msg = (err && err.message) || String(err);
316+
this.log.error("EU Data Act flow failed: " + msg);
317+
// Credential / account problems don't self-heal on a restart — just
318+
// log once and stay down until the user fixes the config.
319+
if (/login failed|password_invalid|email_invalid|account.*(locked|disabled)|not entitled/i.test(msg)) {
320+
this.log.error(
321+
"Adapter staying down until credentials are corrected. " +
322+
"Update user/password in the adapter settings, then restart manually.",
323+
);
324+
this.setState("info.connection", false, true);
325+
return;
326+
}
327+
// Anything else (network, portal 5xx, IdP transient): retry in 30 min.
316328
this.log.error("Restart Adapter in 30min");
317329
this.restartTimeout && clearTimeout(this.restartTimeout);
318330
this.restartTimeout = setTimeout(() => {

0 commit comments

Comments
 (0)