From 93130b0180ff7bc49fb183e8d209b1e328810e99 Mon Sep 17 00:00:00 2001 From: highesttt Date: Wed, 7 Oct 2026 16:59:28 -0400 Subject: [PATCH 1/2] fix: reject failed LF1 polling before login finalization --- pkg/line/client.go | 16 ++++++++++++++-- pkg/line/errors.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/pkg/line/client.go b/pkg/line/client.go index 80c9004..9d1925c 100644 --- a/pkg/line/client.go +++ b/pkg/line/client.go @@ -331,16 +331,28 @@ func (c *Client) waitForLoginLF1(verifier string) (*LoginResult, error) { } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read LF1 polling response: %w", err) + } + if resp.StatusCode != http.StatusOK { + return nil, loginPollingFailure(resp.StatusCode, body) + } var wrapper struct { - Code int `json:"code"` + Code *int `json:"code"` Message string `json:"message"` Data LoginPollingResult `json:"data"` } if err := json.Unmarshal(body, &wrapper); err != nil { return nil, fmt.Errorf("failed to parse LF1 polling response: %w", err) } + if wrapper.Code == nil { + return nil, errors.New("LF1 polling returned an invalid response without a success code") + } + if *wrapper.Code != 0 { + return nil, loginPollingFailure(resp.StatusCode, body) + } meta := wrapper.Data.Result.Metadata diff --git a/pkg/line/errors.go b/pkg/line/errors.go index 3960efc..1afc977 100644 --- a/pkg/line/errors.go +++ b/pkg/line/errors.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "strconv" "strings" ) @@ -256,6 +257,47 @@ type talkExceptionData struct { Reason string `json:"reason"` } +func loginPollingFailure(httpStatus int, body []byte) error { + var response struct { + Code int `json:"code"` + Message string `json:"message"` + Data talkExceptionData `json:"data"` + } + decodeErr := json.Unmarshal(body, &response) + details := fmt.Sprintf("LINE response code %d", response.Code) + if decodeErr == nil && response.Code == 10051 && strings.EqualFold(response.Message, "RESPONSE_ERROR") && strings.EqualFold(response.Data.Name, "TalkException") { + response.Message = "RESPONSE_ERROR" + response.Data.Name = "TalkException" + response.Data.Message = safeLoginPollingReason(response.Data.Message) + response.Data.Reason = safeLoginPollingReason(response.Data.Reason) + if sanitized, err := json.Marshal(response); err == nil { + details = string(sanitized) + } + } + if httpStatus != http.StatusOK { + return fmt.Errorf("LF1 polling failed: API error %d: %s", httpStatus, details) + } + return fmt.Errorf("LF1 polling failed (code %d): %s", response.Code, details) +} + +func safeLoginPollingReason(reason string) string { + // Arbitrary provider text can echo secrets, so retain only fixed rejection messages. + switch strings.ToLower(strings.TrimSpace(reason)) { + case "authentication failed": + return "authentication failed" + case "failed to issue v3 token": + return "Failed to issue V3 token" + case "blocked user": + return "blocked user" + case "account id or password is invalid": + return "Account ID or password is invalid" + case "too many login attempts": + return "Too many login attempts" + default: + return "" + } +} + // IsE2EEGroupMemberMismatch identifies a rejected registration that needs a // fresh server member/key snapshot, not a plaintext fallback. func IsE2EEGroupMemberMismatch(err error) bool { From 9809ab206cfca5cc8510aaa728763aefafd793a6 Mon Sep 17 00:00:00 2001 From: highesttt Date: Wed, 7 Oct 2026 19:25:54 -0400 Subject: [PATCH 2/2] line: avoid fabricated codes in LF1 failure diagnostics --- pkg/line/errors.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/line/errors.go b/pkg/line/errors.go index 1afc977..ff30ce4 100644 --- a/pkg/line/errors.go +++ b/pkg/line/errors.go @@ -259,13 +259,16 @@ type talkExceptionData struct { func loginPollingFailure(httpStatus int, body []byte) error { var response struct { - Code int `json:"code"` + Code *int `json:"code"` Message string `json:"message"` Data talkExceptionData `json:"data"` } decodeErr := json.Unmarshal(body, &response) - details := fmt.Sprintf("LINE response code %d", response.Code) - if decodeErr == nil && response.Code == 10051 && strings.EqualFold(response.Message, "RESPONSE_ERROR") && strings.EqualFold(response.Data.Name, "TalkException") { + details := "LINE response without a valid code" + if decodeErr == nil && response.Code != nil { + details = fmt.Sprintf("LINE response code %d", *response.Code) + } + if decodeErr == nil && response.Code != nil && *response.Code == 10051 && strings.EqualFold(response.Message, "RESPONSE_ERROR") && strings.EqualFold(response.Data.Name, "TalkException") { response.Message = "RESPONSE_ERROR" response.Data.Name = "TalkException" response.Data.Message = safeLoginPollingReason(response.Data.Message) @@ -277,7 +280,7 @@ func loginPollingFailure(httpStatus int, body []byte) error { if httpStatus != http.StatusOK { return fmt.Errorf("LF1 polling failed: API error %d: %s", httpStatus, details) } - return fmt.Errorf("LF1 polling failed (code %d): %s", response.Code, details) + return fmt.Errorf("LF1 polling failed: %s", details) } func safeLoginPollingReason(reason string) string {