{"record":{"id":"da1fc5b284ae9520","repo":"sigoden/dufs","slug":"invalid-token","errorCode":null,"errorMessage":"Invalid token","messagePattern":"Invalid token","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/auth.rs","lineNumber":184,"sourceCode":"            .ok_or_else(|| anyhow!(\"Not found user '{user}'\"))?;\n        let exp = unix_now().as_millis() as u64 + TOKEN_EXPIRATION;\n        let message = format!(\"{path}:{exp}\");\n        let mut signing_key = derive_secret_key(user, pass);\n        let sig = signing_key.sign(message.as_bytes()).to_bytes();\n\n        let mut raw = Vec::with_capacity(64 + 8 + user.len());\n        raw.extend_from_slice(&sig);\n        raw.extend_from_slice(&exp.to_be_bytes());\n        raw.extend_from_slice(user.as_bytes());\n\n        Ok(hex::encode(raw))\n    }\n\n    fn verify_token<'a>(&'a self, token: &str, path: &str) -> Result<(String, &'a AccessPaths)> {\n        let raw = hex::decode(token)?;\n\n        if raw.len() < 72 {\n            bail!(\"Invalid token\");\n        }\n\n        let sig_bytes = &raw[..64];\n        let exp_bytes = &raw[64..72];\n        let user_bytes = &raw[72..];\n\n        let exp = u64::from_be_bytes(exp_bytes.try_into()?);\n        if unix_now().as_millis() as u64 > exp {\n            bail!(\"Token expired\");\n        }\n\n        let user = std::str::from_utf8(user_bytes)?;\n        let (pass, ap) = self\n            .users\n            .get(user)\n            .ok_or_else(|| anyhow!(\"Not found user '{user}'\"))?;\n\n        let sig = Signature::from_bytes(&<[u8; 64]>::try_from(sig_bytes)?);","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/sigoden/dufs/blob/fe7fd564f80dfbac361c8e0589c3845638149d38/src/auth.rs#L166-L202","documentation":"`verify_token` hex-decodes a bearer token and requires at least 72 bytes: a 64-byte signature, 8-byte expiry, and a user name. Shorter tokens cannot possibly contain that layout, so they are rejected immediately with 'Invalid token' before signature verification.","triggerScenarios":"Calling `guard` (via `verify_token`) with a token string that is truncated, not the output of `generate_token`, hex-corrupted, or includes only the signature portion.","commonSituations":"Manually copying part of a token out of logs/URLs; an intermediary stripping characters; hand-crafting tokens for testing; old tokens from a previous token scheme.","solutions":["Regenerate the token with `Auth::generate_token(path, user)` and use the full returned string.","Confirm the token is valid hex and sent intact (no truncation by proxies or copy/paste).","Ensure the Authorization header uses the token format the server expects.","If tokens come from an older version, re-login/re-issue since formats may differ."],"exampleFix":"// before\nlet token = \"a1b2c3\"; // truncated\n// after\nlet token = auth.generate_token(\"/files\", \"alice\")?; // full 64+8+user hex","handlingStrategy":"try-catch","validationCode":"fn token_plausible(t: &str) -> bool { t.len() >= 144 && hex::decode(t).map(|r| r.len() >= 72).unwrap_or(false) }","typeGuard":null,"tryCatchPattern":"match err.downcast_ref::<String>() { Some(m) if m == \"Invalid token\" => reissue_token_and_retry(), _ => return_401() }","preventionTips":["Always transmit the complete token string","Do not hand-edit or truncate tokens","Re-issue tokens after server upgrades","Store tokens in variables, not copy-pasted from logs"],"tags":["rust","authentication","token","hex"],"backgroundTag":"invalid-argument-format","analyzedSha":"fe7fd564f80dfbac361c8e0589c3845638149d38","analyzedAt":"2026-09-09T13:01:22.843Z","contentChangedAt":"2026-09-09T13:01:22.843Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}