{"record":{"id":"436456366acb42d3","repo":"slopus/happy","slug":"authentication-failed","errorCode":null,"errorMessage":"Authentication failed","messagePattern":"Authentication failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/happy-cli/src/api/auth.ts","lineNumber":33,"sourceCode":" * @param serverUrl - The URL of the server to authenticate with\n * @param secret - The secret key to use for authentication\n * @returns The authentication token\n */\nexport async function authGetToken(secret: Uint8Array): Promise<string> {\n  const { challenge, publicKey, signature } = authChallenge(secret);\n  \n  const response = await axios.post(`${configuration.serverUrl}/v1/auth`, {\n    challenge: encodeBase64(challenge),\n    publicKey: encodeBase64(publicKey),\n    signature: encodeBase64(signature)\n  }, {\n    headers: {\n      'X-Happy-Client': `cli/${configuration.currentCliVersion}`\n    }\n  });\n\n  if (!response.data.success || !response.data.token) {\n    throw new Error('Authentication failed');\n  }\n\n  return response.data.token;\n}\n\n/**\n * Generate a URL for the mobile app to connect to the server\n * @param secret - The secret key to use for authentication\n * @returns The URL for the mobile app to connect to the server\n */\nexport function generateAppUrl(secret: Uint8Array): string {\n  const secretBase64Url = encodeBase64Url(secret);\n  return `handy://${secretBase64Url}`;\n}","sourceCodeStart":15,"sourceCodeEnd":47,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/api/auth.ts#L15-L47","documentation":"authGetToken performs challenge-response authentication against the server's /v1/auth endpoint using a TweetNaCl-signed challenge. The server is expected to respond with {success: true, token: '...'}. When the response reports success=false or omits a token, the CLI rejects the auth handshake with this generic 'Authentication failed' error.","triggerScenarios":"POST ${serverUrl}/v1/auth returns an HTTP 200 whose body has success=false or a missing/empty token field — e.g. the server does not recognize the public key, the signature/challenge pair is malformed, or a proxy/API layer intercepts with a non-auth JSON body.","commonSituations":"Pointing HAPPY_SERVER_URL at the wrong server (dev server vs hosted api.happy-servers.com) that returns a success-shaped but non-auth response; a stale or corrupted ~/.handy/access.key whose public key is not registered; an outdated CLI whose auth payload format the server rejects; corporate proxies returning HTML/JSON error pages with 200 status.","solutions":["Verify configuration.serverUrl points at the correct Happy server (check HAPPY_SERVER_URL env var).","Delete ~/.handy/access.key so the CLI regenerates a fresh key pair and re-authenticates.","Inspect the raw /v1/auth response (curl with the same payload) to see the server's success=false reason.","Upgrade the CLI to the latest version to ensure the auth payload matches the server's expected schema.","Check for proxy/VPN interference that could rewrite the response body."],"exampleFix":"// before\nif (!response.data.success || !response.data.token) {\n  throw new Error('Authentication failed');\n}\n// after\nif (!response.data.success || !response.data.token) {\n  throw new Error(`Authentication failed (server=${configuration.serverUrl}, reason=${response.data.error ?? 'unknown'})`);\n}","handlingStrategy":"try-catch","validationCode":"if (!configuration.serverUrl || !/^https?:\\/\\//.test(configuration.serverUrl)) throw new Error('HAPPY_SERVER_URL is not set to a valid http(s) URL');\nif (!secret || secret.length === 0) throw new Error('No secret key loaded — run happy auth first');","typeGuard":"function isAuthResponse(d: unknown): d is { success: true; token: string } {\n  return typeof d === 'object' && d !== null && (d as any).success === true && typeof (d as any).token === 'string' && (d as any).token.length > 0;\n}","tryCatchPattern":"try {\n  const token = await authGetToken(secret);\n} catch (err) {\n  if ((err as Error).message === 'Authentication failed') {\n    logger.error(`Auth rejected for ${configuration.serverUrl} — delete ~/.handy/access.key to re-pair, and verify HAPPY_SERVER_URL`);\n    process.exitCode = 1;\n  } else throw err;\n}","preventionTips":["Pin HAPPY_SERVER_URL explicitly in your shell profile or daemon config.","Re-pair (delete ~/.handy/access.key) after switching servers.","Keep the CLI updated so auth payload format matches the server.","Test connectivity to /v1/auth with curl before reporting auth bugs."],"tags":["authentication","network","api"],"backgroundTag":"auth-challenge-rejected","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}