{"record":{"id":"365d15dcc4a705a2","repo":"gethomepage/homepage","slug":"truenas-authentication-failed","errorCode":null,"errorMessage":"TrueNAS authentication failed","messagePattern":"TrueNAS authentication failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/widgets/truenas/proxy.js","lineNumber":101,"sourceCode":"\nasync function authenticate(ws, widget) {\n  if (widget?.key) {\n    try {\n      const apiKeyResult = await sendMethod(ws, \"auth.login_with_api_key\", [widget.key]);\n      if (apiKeyResult === true) return;\n      logger.warn(\"TrueNAS API key authentication failed, falling back to username/password when available.\");\n    } catch (err) {\n      logger.error(\"TrueNAS API key authentication failed: %s\", err?.message ?? err);\n    }\n  }\n\n  if (widget?.username && widget?.password) {\n    const loginResult = await sendMethod(ws, \"auth.login\", [widget.username, widget.password]);\n    if (loginResult === true) return;\n    logger.warn(\"TrueNAS username/password authentication failed.\");\n  }\n\n  throw new Error(\"TrueNAS authentication failed\");\n}\n\nexport default async function truenasProxyHandler(req, res, map) {\n  const { group, service, endpoint, index } = req.query;\n  if (!group || !service) {\n    logger.debug(\"Invalid or missing service '%s' or group '%s'\", service, group);\n    return res.status(400).json({ error: \"Invalid proxy service type\" });\n  }\n\n  const widget = await getServiceWidget(group, service, index);\n\n  if (!widget) {\n    logger.debug(\"Invalid or missing widget for service '%s' in group '%s'\", service, group);\n    return res.status(400).json({ error: \"Invalid proxy service type\" });\n  }\n\n  if (!endpoint) {\n    return res.status(204).end();","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/gethomepage/homepage/blob/b6dca1ae033e613d8e692f9a161a3cc53a5a2857/src/widgets/truenas/proxy.js#L83-L119","documentation":"Thrown at the end of authenticate() when neither the API-key path nor the username/password path returned true. The function tries auth.login_with_api_key first (if widget.key is set) and falls back to auth.login with username+password; only if both are absent or both fail does it raise. This is therefore a terminal 'no usable credential' error.","triggerScenarios":"widget.key is missing/invalid AND username/password are missing or wrong; API key auth throws (logged at error level) and no credentials are configured for the fallback; TrueNAS middleware websocket rejects both methods (wrong app version, locked account, IP not allowed).","commonSituations":"Only an API key configured but the key was revoked in TrueNAS; only username/password configured but the password changed; TrueNAS SCALE vs CORE version mismatch changing the auth.login behavior; TrueNAS IP-denylist blocking the Homepage host; credential fields left empty in the widget config.","solutions":["In TrueNAS, create/verify an API key (System > API Keys) and paste it into the widget's key field; prefer the API-key path.","If using username/password, confirm the account is enabled and the password is current.","Check the Homepage debug log for the two preceding warnings/errors ('API key authentication failed', 'username/password authentication failed') to see which path failed.","Ensure the TrueNAS websocket URL is correct and that the Homepage host's IP is not blocked by TrueNAS access rules.","If both methods are configured but both fail, test the API key directly with curl against the TrueNAS websocket/JSON-RPC endpoint."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Verify at least one TrueNAS credential path is configured and non-empty.\nfunction hasTruenasCredentials(w) {\n  return Boolean(\n    (typeof w?.key === \"string\" && w.key.length > 0) ||\n    (typeof w?.username === \"string\" && typeof w?.password === \"string\" && w.username && w.password),\n  );\n}\nif (!hasTruenasCredentials(widget)) throw new Error(\"TrueNAS widget has no API key and no username/password\");","typeGuard":"function isTruenasWidgetAuthenticatable(w) {\n  return Boolean(w) && (Boolean(w.key) || (Boolean(w.username) && Boolean(w.password)));\n}","tryCatchPattern":"try {\n  await authenticate(ws, widget);\n} catch (err) {\n  if (/authentication failed/i.test(err.message)) {\n    res.status(401).json({ error: \"TrueNAS authentication failed\", hint: \"verify API key or username/password\" });\n    return;\n  }\n  throw err;\n}","preventionTips":["Prefer a TrueNAS API key over username/password and rotate it on a schedule.","Grant the key the minimum-scope role needed for the endpoints the widget calls.","Confirm the Homepage host IP is permitted by any TrueNAS access restrictions.","Unit-test authenticate() with mock websocket responses for both success and failure to lock in the fallback order."],"tags":["network","truenas","authentication","websocket","widget"],"backgroundTag":null,"analyzedSha":"b6dca1ae033e613d8e692f9a161a3cc53a5a2857","analyzedAt":"2026-08-13T04:48:44.121Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}