{"record":{"id":"19c84ee9e22f8194","repo":"atuinsh/atuin","slug":"error","errorCode":null,"errorMessage":"{}","messagePattern":"\\{\\}","errorType":"exception","errorClass":null,"httpStatus":403,"severity":"error","filePath":"crates/atuin-client/src/auth.rs","lineNumber":312,"sourceCode":"            .context(\"failed to connect to Atuin Hub\")?;\n\n        let status = resp.status();\n\n        if status.is_success() {\n            let login: LoginResponse = resp.json().await?;\n            return Ok(AuthResponse::Success {\n                session: login.session,\n                auth_type: login.auth,\n            });\n        }\n\n        if status == StatusCode::FORBIDDEN\n            && let Ok(err) = resp.json::<HubErrorResponse>().await\n        {\n            if err.code.as_deref() == Some(\"2fa_required\") {\n                return Ok(AuthResponse::TwoFactorRequired);\n            }\n            bail!(\"{}\", err.reason);\n        }\n\n        if status == StatusCode::UNAUTHORIZED {\n            bail!(\"invalid credentials\");\n        }\n\n        bail!(\"Hub login failed with status {status}\");\n    }\n\n    async fn register(&self, username: &str, email: &str, password: &str) -> Result<AuthResponse> {\n        let url = self.address.append_path(\"api/v0/register\")?;\n        let client = reqwest::Client::new();\n\n        let resp = client\n            .post(url)\n            .header(USER_AGENT, APP_USER_AGENT)\n            .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)\n            .json(&serde_json::json!({","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/atuinsh/atuin/blob/900007c48045867e7f44b0f9bc55ad9dbe99ee0d/crates/atuin-client/src/auth.rs#L294-L330","documentation":"In `HubAuthClient::login`, a 403 (FORBIDDEN) response whose body parses as `HubErrorResponse` is turned into this bail carrying the server's `reason` string, unless the code is `2fa_required` (which returns TwoFactorRequired instead). The message text is dynamic — it is whatever the Hub server put in `reason` — so it can represent many refusal causes.","triggerScenarios":"POSTing to `api/v0/login` and receiving 403 with a JSON body `{\"reason\": ..., \"code\": ...}` where `code != \"2fa_required\"` — e.g. account disabled/suspended, login forbidden from this client, or any server-side policy rejection.","commonSituations":"Account suspended or banned on Atuin Hub; organization/IP policy blocking login; server-side maintenance flags; using a client version the server no longer permits (server rejects with explanatory reason).","solutions":["Read the `reason` text in the error — it comes verbatim from the Hub and states the specific cause","Check account status on the Atuin Hub web UI (suspension, verification required)","Complete email verification or contact Hub support if the account is flagged","If 2FA is involved, supply a valid `totp_code` so the server doesn't reach a forbidden state","Update the Atuin CLI if the server indicates the client version is rejected"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_hub_forbidden_reason(err: &Report) -> Option<String> {\n    let msg = err.to_string();\n    if msg == \"invalid credentials\" || msg.starts_with(\"Hub login failed\") { None }\n    else { Some(msg) } // 403 reason text is surfaced verbatim\n}","tryCatchPattern":"match hub.login(username, password, totp).await {\n    Ok(AuthResponse::Success { .. }) => println!(\"logged in\"),\n    Ok(AuthResponse::TwoFactorRequired) => {\n        // prompt for TOTP and retry with the code\n    }\n    Err(e) => {\n        // 403 reason is server-provided: display it and check account status on the Hub\n        eprintln!(\"login refused by Hub: {e}\");\n    }\n}","preventionTips":["Read the server `reason` text — it states the exact refusal cause","Keep the account in good standing (verified email, not suspended) on the Hub","Supply a TOTP code when the account has 2FA enabled","Keep the CLI updated so the server does not reject the client version"],"tags":["http","authentication","atuin-hub","login"],"backgroundTag":"api-error-response","analyzedSha":"900007c48045867e7f44b0f9bc55ad9dbe99ee0d","analyzedAt":"2026-09-19T12:52:51.491Z","contentChangedAt":"2026-09-19T12:52:51.491Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}