{"record":{"id":"e53bffb30f2e7118","repo":"glanceapp/glance","slug":"parsing-authentication-response-v","errorCode":null,"errorMessage":"parsing authentication response: %v","messagePattern":"parsing authentication response: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-dns-stats.go","lineNumber":646,"sourceCode":"\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"sending authentication request: %v\", err)\n\t}\n\tdefer response.Body.Close()\n\n\tbody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"reading authentication response: %v\", err)\n\t}\n\n\tvar jsonResponse struct {\n\t\tSession struct {\n\t\t\tSID     string `json:\"sid\"`\n\t\t\tMessage string `json:\"message\"`\n\t\t} `json:\"session\"`\n\t}\n\n\tif err := json.Unmarshal(body, &jsonResponse); err != nil {\n\t\treturn \"\", fmt.Errorf(\"parsing authentication response: %v\", err)\n\t}\n\n\tif response.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"authentication request returned status %s with message '%s'\",\n\t\t\tresponse.Status, jsonResponse.Session.Message,\n\t\t)\n\t}\n\n\tif jsonResponse.Session.SID == \"\" {\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"authentication response returned empty session ID, status code %d, message '%s'\",\n\t\t\tresponse.StatusCode, jsonResponse.Session.Message,\n\t\t)\n\t}\n\n\treturn jsonResponse.Session.SID, nil\n}","sourceCodeStart":628,"sourceCodeEnd":664,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-dns-stats.go#L628-L664","documentation":"Returned by fetchPiholeSessionID when json.Unmarshal of the auth response body into {session:{sid,message}} fails. The endpoint replied but with a body that is not the expected JSON object — typically HTML (login/proxy error page) or an empty body, so unmarshal errors with 'invalid character' or 'unexpected end of JSON input'.","triggerScenarios":"POST /api/auth returns an HTML page from a reverse proxy, a captive portal, or a redirect target; an empty 200 body; or a Pi-hole version whose /api/auth response shape differs (pre-v6 firmware without the v6 API).","commonSituations":"URL pointing at Pi-hole v5 (no /api/auth JSON contract); proxy redirecting http→https with an HTML interstitial; trailing path like /admin causing a UI page to be served.","solutions":["curl -i -X POST {url}/api/auth and inspect Content-Type and body — JSON is expected.","Confirm the target actually runs Pi-hole v6 (v5 uses a different auth model; use service: pihole).","Remove path suffixes from url so the API root is hit.","Fix proxy rules that rewrite or redirect /api/auth."],"exampleFix":"# before\nurl: http://pihole.local/admin\nservice: pihole_v6\n\n# after\nurl: http://pihole.local\nservice: pihole_v6","handlingStrategy":"try-catch","validationCode":"// confirm the endpoint speaks v6 JSON before relying on it\nreq, _ := http.NewRequest(\"POST\", instanceURL+\"/api/auth\", strings.NewReader(`{\"password\":\"x\"}`))\nreq.Header.Set(\"Content-Type\", \"application/json\")\nresp, err := client.Do(req)\nif err == nil {\n    defer resp.Body.Close()\n    if ct := resp.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"json\") {\n        return errors.New(\"not a Pi-hole v6 API — body is not JSON\")\n    }\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"parsing authentication response\") {\n    // HTML/empty body: wrong version, wrong path, or proxy interference\n    slog.Error(\"pihole auth returned non-JSON; check url and version\", \"error\", err)\n}","preventionTips":["Confirm Pi-hole v6 is deployed; v5 lacks /api/auth JSON.","Use the instance root URL without /admin suffixes.","Ensure proxies pass through /api/* untouched."],"tags":["json","pihole","network","dns-stats"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}