@@ -455,15 +455,54 @@ bool GarminConnect::performLogin(const QString &email, const QString &password,
455455 qDebug () << " GarminConnect: Login response length:" << response.length ();
456456 qDebug () << " GarminConnect: Response snippet:" << response.left (300 );
457457
458- // Check for success title (like Python garth library)
458+ // Check page title (like Python garth library)
459+ // garth checks ONLY the title for MFA detection, not the body
460+ // This is important because some servers (like garmin.cn) may have "MFA" text
461+ // in their Success page HTML body, which would cause false positives
462+ QString pageTitle;
459463 QRegularExpression titleRegex (" <title>(.+?)</title>" );
460464 QRegularExpressionMatch titleMatch = titleRegex.match (response);
461465 if (titleMatch.hasMatch ()) {
462- QString title = titleMatch.captured (1 );
463- qDebug () << " GarminConnect: Page title:" << title;
464- if (title == " Success" ) {
465- qDebug () << " GarminConnect: Login successful (Success page detected)" ;
466+ pageTitle = titleMatch.captured (1 );
467+ qDebug () << " GarminConnect: Page title:" << pageTitle;
468+ }
469+
470+ // Check if MFA is required by looking at the TITLE (garth approach)
471+ // This is more reliable than checking the body which may contain "MFA" in scripts/URLs
472+ if (pageTitle.contains (" MFA" , Qt::CaseInsensitive)) {
473+ m_lastError = " MFA Required" ;
474+ qDebug () << " GarminConnect: MFA detected in page title" ;
475+
476+ // Extract new CSRF token from MFA page - try multiple patterns
477+ QRegularExpression csrfRegex1 (" name=\" _csrf\" [^>]*value=\" ([^\" ]+)\" " );
478+ QRegularExpression csrfRegex2 (" value=\" ([^\" ]+)\" [^>]*name=\" _csrf\" " );
479+
480+ QRegularExpressionMatch match = csrfRegex1.match (response);
481+ if (!match.hasMatch ()) {
482+ match = csrfRegex2.match (response);
483+ }
484+ if (match.hasMatch ()) {
485+ m_csrfToken = match.captured (1 );
486+ qDebug () << " GarminConnect: CSRF token from MFA page:" << m_csrfToken.left (20 ) << " ..." ;
466487 }
488+
489+ // Update cookies
490+ m_cookies = m_manager->cookieJar ()->cookiesForUrl (url);
491+
492+ if (!suppressMfaSignal) {
493+ qDebug () << " GarminConnect: Emitting mfaRequired signal" ;
494+ emit mfaRequired ();
495+ } else {
496+ qDebug () << " GarminConnect: MFA required but signal suppressed (retrying with MFA code)" ;
497+ }
498+ reply->deleteLater ();
499+ return false ;
500+ }
501+
502+ // Check if login was successful (title is "Success")
503+ if (pageTitle == " Success" ) {
504+ qDebug () << " GarminConnect: Login successful (Success page detected)" ;
505+ // Continue to extract ticket below
467506 }
468507
469508 // Check for error messages in response
@@ -552,34 +591,6 @@ bool GarminConnect::performLogin(const QString &email, const QString &password,
552591 return false ;
553592 }
554593
555- // Check if MFA is required (legacy check for non-redirect MFA)
556- if (response.contains (" MFA" , Qt::CaseInsensitive) ||
557- response.contains (" Enter MFA Code" , Qt::CaseInsensitive)) {
558- m_lastError = " MFA Required" ;
559- qDebug () << " GarminConnect: MFA content detected in response" ;
560-
561- // Extract new CSRF token from MFA page - try multiple patterns
562- QRegularExpression csrfRegex1 (" name=\" _csrf\" [^>]*value=\" ([^\" ]+)\" " );
563- QRegularExpression csrfRegex2 (" value=\" ([^\" ]+)\" [^>]*name=\" _csrf\" " );
564-
565- QRegularExpressionMatch match = csrfRegex1.match (response);
566- if (!match.hasMatch ()) {
567- match = csrfRegex2.match (response);
568- }
569- if (match.hasMatch ()) {
570- m_csrfToken = match.captured (1 );
571- }
572-
573- // Update cookies
574- m_cookies = m_manager->cookieJar ()->cookiesForUrl (url);
575-
576- if (!suppressMfaSignal) {
577- emit mfaRequired ();
578- }
579- reply->deleteLater ();
580- return false ;
581- }
582-
583594 // Extract ticket from response URL (already declared above)
584595 if (responseUrl.isEmpty ()) {
585596 responseUrl = reply->url ();
0 commit comments