Summary
A live Tapo cloud login response can use outer error_code: 0 while the nested result is an MFA challenge and contains no token. TPLinkApi.login() currently treats outer code zero as success and returns this tokenless challenge object, so callers believe login completed but cannot discover Tapo devices.
Observed response shapes
Successful-login shape from this repository's WireMock fixture (token values omitted):
{
"error_code": 0,
"result": {
"token": "<redacted>",
"refreshToken": "<redacted>"
}
}
Observed live MFA response (all values redacted; field names only):
{
"error_code": 0,
"result": {
"MFAProcessId": "<redacted>",
"email": "<redacted>",
"errorCode": "<redacted>",
"errorMsg": "<redacted>",
"failedAttempts": "<redacted>",
"lockedMinutes": "<redacted>",
"mfaEmail": "<redacted>",
"remainAttempts": "<redacted>",
"supportedMFATypes": "<redacted>"
}
}
The observed MFA result had no token key. No credentials, token values, process IDs, or actual email addresses are included here.
Current behavior
TPLinkApi.login() returns response.result immediately whenever the outer error_code is zero. The MFA callback is only considered when the outer code is -20677. For the observed response, login therefore returns without a token; TPLinkDeviceManager then has no Tapo token and Tapo device discovery is skipped.
Expected behavior
Detect a tokenless MFA challenge from its nested challenge fields (including when the outer code is zero), invoke mfa_callback or raise TPLinkMFARequiredError, and complete the MFA exchange. A tokenless non-MFA response should still report its authentication/API error rather than be treated as success.
This was reproduced against the Tapo cloud using the v5.2.1 source. The local regression tests cover a nested challenge response and ordinary wrong-credential handling.
Summary
A live Tapo cloud login response can use outer
error_code: 0while the nestedresultis an MFA challenge and contains no token.TPLinkApi.login()currently treats outer code zero as success and returns this tokenless challenge object, so callers believe login completed but cannot discover Tapo devices.Observed response shapes
Successful-login shape from this repository's WireMock fixture (token values omitted):
{ "error_code": 0, "result": { "token": "<redacted>", "refreshToken": "<redacted>" } }Observed live MFA response (all values redacted; field names only):
{ "error_code": 0, "result": { "MFAProcessId": "<redacted>", "email": "<redacted>", "errorCode": "<redacted>", "errorMsg": "<redacted>", "failedAttempts": "<redacted>", "lockedMinutes": "<redacted>", "mfaEmail": "<redacted>", "remainAttempts": "<redacted>", "supportedMFATypes": "<redacted>" } }The observed MFA
resulthad notokenkey. No credentials, token values, process IDs, or actual email addresses are included here.Current behavior
TPLinkApi.login()returnsresponse.resultimmediately whenever the outererror_codeis zero. The MFA callback is only considered when the outer code is-20677. For the observed response, login therefore returns without a token;TPLinkDeviceManagerthen has no Tapo token and Tapo device discovery is skipped.Expected behavior
Detect a tokenless MFA challenge from its nested challenge fields (including when the outer code is zero), invoke
mfa_callbackor raiseTPLinkMFARequiredError, and complete the MFA exchange. A tokenless non-MFA response should still report its authentication/API error rather than be treated as success.This was reproduced against the Tapo cloud using the v5.2.1 source. The local regression tests cover a nested challenge response and ordinary wrong-credential handling.