@@ -391,6 +391,9 @@ bool GarminConnect::fetchCsrfToken()
391391bool GarminConnect::performLogin (const QString &email, const QString &password, bool suppressMfaSignal)
392392{
393393 qDebug () << " GarminConnect: Performing login..." ;
394+ qDebug () << " GarminConnect: Using domain:" << m_domain;
395+ qDebug () << " GarminConnect: SSO URL:" << ssoUrl ();
396+ qDebug () << " GarminConnect: Connect API URL:" << connectApiUrl ();
394397
395398 QString ssoEmbedUrl = ssoUrl () + SSO_EMBED_PATH ;
396399
@@ -452,15 +455,54 @@ bool GarminConnect::performLogin(const QString &email, const QString &password,
452455 qDebug () << " GarminConnect: Login response length:" << response.length ();
453456 qDebug () << " GarminConnect: Response snippet:" << response.left (300 );
454457
455- // 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;
456463 QRegularExpression titleRegex (" <title>(.+?)</title>" );
457464 QRegularExpressionMatch titleMatch = titleRegex.match (response);
458465 if (titleMatch.hasMatch ()) {
459- QString title = titleMatch.captured (1 );
460- qDebug () << " GarminConnect: Page title:" << title;
461- if (title == " Success" ) {
462- 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 ) << " ..." ;
487+ }
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)" ;
463497 }
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
464506 }
465507
466508 // Check for error messages in response
@@ -549,39 +591,17 @@ bool GarminConnect::performLogin(const QString &email, const QString &password,
549591 return false ;
550592 }
551593
552- // Check if MFA is required (legacy check for non-redirect MFA)
553- if (response.contains (" MFA" , Qt::CaseInsensitive) ||
554- response.contains (" Enter MFA Code" , Qt::CaseInsensitive)) {
555- m_lastError = " MFA Required" ;
556- qDebug () << " GarminConnect: MFA content detected in response" ;
557-
558- // Extract new CSRF token from MFA page - try multiple patterns
559- QRegularExpression csrfRegex1 (" name=\" _csrf\" [^>]*value=\" ([^\" ]+)\" " );
560- QRegularExpression csrfRegex2 (" value=\" ([^\" ]+)\" [^>]*name=\" _csrf\" " );
561-
562- QRegularExpressionMatch match = csrfRegex1.match (response);
563- if (!match.hasMatch ()) {
564- match = csrfRegex2.match (response);
565- }
566- if (match.hasMatch ()) {
567- m_csrfToken = match.captured (1 );
568- }
569-
570- // Update cookies
571- m_cookies = m_manager->cookieJar ()->cookiesForUrl (url);
572-
573- if (!suppressMfaSignal) {
574- emit mfaRequired ();
575- }
576- reply->deleteLater ();
577- return false ;
578- }
579-
580594 // Extract ticket from response URL (already declared above)
581595 if (responseUrl.isEmpty ()) {
582596 responseUrl = reply->url ();
583597 }
584598
599+ if (DEBUG_GARMIN_VERBOSE ) {
600+ qDebug () << " GarminConnect: Response URL:" << responseUrl.toString ();
601+ qDebug () << " GarminConnect: Response length:" << response.length ();
602+ qDebug () << " GarminConnect: Full response body:" << response;
603+ }
604+
585605 QUrlQuery responseQuery (responseUrl);
586606 QString ticket = responseQuery.queryItemValue (" ticket" );
587607
@@ -599,6 +619,8 @@ bool GarminConnect::performLogin(const QString &email, const QString &password,
599619 if (match.hasMatch ()) {
600620 ticket = match.captured (1 );
601621 qDebug () << " GarminConnect: Found ticket with fallback pattern:" << ticket.left (20 ) << " ..." ;
622+ } else if (DEBUG_GARMIN_VERBOSE ) {
623+ qDebug () << " GarminConnect: No ticket patterns matched in response body" ;
602624 }
603625 }
604626 }
@@ -608,6 +630,9 @@ bool GarminConnect::performLogin(const QString &email, const QString &password,
608630 if (ticket.isEmpty ()) {
609631 m_lastError = " Failed to extract ticket from login response" ;
610632 qDebug () << " GarminConnect:" << m_lastError;
633+ if (DEBUG_GARMIN_VERBOSE ) {
634+ qDebug () << " GarminConnect: Response snippet:" << response.left (1000 );
635+ }
611636 return false ;
612637 }
613638
@@ -708,8 +733,12 @@ void GarminConnect::handleMfaReplyFinished()
708733 qDebug () << " GarminConnect: MFA response status code:" << statusCode;
709734 qDebug () << " GarminConnect: MFA response redirect URL:" << responseUrl.toString ();
710735
711- // If no redirect, log response body to understand what happened
712- if (responseUrl.isEmpty ()) {
736+ // Log detailed response information
737+ if (DEBUG_GARMIN_VERBOSE ) {
738+ qDebug () << " GarminConnect: MFA response length:" << response.length ();
739+ qDebug () << " GarminConnect: Full MFA response body:" << response;
740+ } else if (responseUrl.isEmpty ()) {
741+ // If no redirect, log response body to understand what happened (non-verbose)
713742 qDebug () << " GarminConnect: MFA response body (first 500 chars):" << response.left (500 );
714743 }
715744
@@ -748,6 +777,9 @@ void GarminConnect::handleMfaReplyFinished()
748777
749778 // If not found in redirect URL, try response body
750779 if (ticket.isEmpty () && !response.isEmpty ()) {
780+ if (DEBUG_GARMIN_VERBOSE ) {
781+ qDebug () << " GarminConnect: Attempting to extract ticket from MFA response body" ;
782+ }
751783 // Try multiple patterns for ticket extraction
752784 QRegularExpression ticketRegex1 (" embed\\ ?ticket=([^\" ]+)\" " );
753785 QRegularExpression ticketRegex2 (" ticket=([^&\" ']+)" );
@@ -761,6 +793,16 @@ void GarminConnect::handleMfaReplyFinished()
761793 if (match.hasMatch ()) {
762794 ticket = match.captured (1 );
763795 qDebug () << " GarminConnect: Found ticket in response body (pattern 2):" << ticket.left (20 ) << " ..." ;
796+ } else if (DEBUG_GARMIN_VERBOSE ) {
797+ qDebug () << " GarminConnect: No MFA ticket patterns matched. Checking for other patterns..." ;
798+ // Check for JSON format
799+ if (response.contains (" ticket" )) {
800+ qDebug () << " GarminConnect: Response contains 'ticket' keyword, may be JSON or different format" ;
801+ }
802+ // Check for common response patterns
803+ if (response.contains (" \" " )) {
804+ qDebug () << " GarminConnect: Response contains quoted strings (may be JSON)" ;
805+ }
764806 }
765807 }
766808 }
@@ -770,6 +812,9 @@ void GarminConnect::handleMfaReplyFinished()
770812 if (ticket.isEmpty ()) {
771813 m_lastError = " Failed to extract ticket after MFA" ;
772814 qDebug () << " GarminConnect:" << m_lastError;
815+ if (DEBUG_GARMIN_VERBOSE ) {
816+ qDebug () << " GarminConnect: Response snippet:" << response.left (1000 );
817+ }
773818 emit authenticationFailed (m_lastError);
774819 return ;
775820 }
@@ -1401,6 +1446,7 @@ void GarminConnect::loadTokensFromSettings()
14011446 m_oauth1Token.oauth_token = settings.value (QZSettings::garmin_oauth1_token, QZSettings::default_garmin_oauth1_token).toString ();
14021447 m_oauth1Token.oauth_token_secret = settings.value (QZSettings::garmin_oauth1_token_secret, QZSettings::default_garmin_oauth1_token_secret).toString ();
14031448 m_domain = settings.value (QZSettings::garmin_domain, QZSettings::default_garmin_domain).toString ();
1449+ qDebug () << " GarminConnect: Loaded Garmin domain from settings:" << m_domain;
14041450
14051451 if (!m_oauth2Token.access_token .isEmpty ()) {
14061452 qDebug () << " GarminConnect: Loaded tokens from settings (OAuth1 + OAuth2)" ;
0 commit comments