sigoden/dufs · error · anyhow::Error

Token expired

Error message

Token expired

What it means

Token payloads embed a millisecond Unix expiry timestamp after the 64-byte signature. `verify_token` compares it against current time and rejects the request with 'Token expired' once `now > exp`. This bounds how long a signed token remains usable.

Solutions

  1. Request a fresh token from `generate_token` and retry.
  2. Synchronize the client clock (NTP) if it is ahead of the server's.
  3. Increase TOKEN_EXPIRATION in the server config/code if longer validity is intended.
  4. Implement client-side refresh before expiry.

Example fix

// before
let token = stored_token; // may be old
// after
let token = if token_expired(stored_token) { auth.generate_token(path, user)? } else { stored_token };
Defensive patterns

Strategy: retry

Validate before calling

fn token_expired(token: &str, skew_ms: u64) -> bool {
    hex::decode(token).ok().and_then(|r| r.get(64..72).map(|e| u64::from_be_bytes(e.try_into().unwrap())))
        .map(|exp| now_ms() + skew_ms > exp).unwrap_or(true)
}

Try / catch

match verify_result { Err(e) if e.to_string() == "Token expired" => { let t = auth.generate_token(path, user)?; retry_with(t); }, other => other }

Prevention

When it happens

Trigger: Presenting a token to `guard`/`verify_token` after TOKEN_EXPIRATION milliseconds have elapsed since `generate_token` created it (system clock far ahead also triggers it).

Common situations: Long-lived browser sessions reusing an old URL token; client clocks skewed ahead of server time; caching a token across days.

Understand the failure class

Related errors


AI-assisted analysis of sigoden/dufs@fe7fd564f8 (2026-09-09). Data as JSON: /api/errors/01d6a65ddcedf021. Report an issue: GitHub.

Appendix: source

Thrown at src/auth.rs:193

        raw.extend_from_slice(user.as_bytes());

        Ok(hex::encode(raw))
    }

    fn verify_token<'a>(&'a self, token: &str, path: &str) -> Result<(String, &'a AccessPaths)> {
        let raw = hex::decode(token)?;

        if raw.len() < 72 {
            bail!("Invalid token");
        }

        let sig_bytes = &raw[..64];
        let exp_bytes = &raw[64..72];
        let user_bytes = &raw[72..];

        let exp = u64::from_be_bytes(exp_bytes.try_into()?);
        if unix_now().as_millis() as u64 > exp {
            bail!("Token expired");
        }

        let user = std::str::from_utf8(user_bytes)?;
        let (pass, ap) = self
            .users
            .get(user)
            .ok_or_else(|| anyhow!("Not found user '{user}'"))?;

        let sig = Signature::from_bytes(&<[u8; 64]>::try_from(sig_bytes)?);

        let message = format!("{path}:{exp}");
        derive_secret_key(user, pass).verify(message.as_bytes(), &sig)?;
        Ok((user.to_string(), ap))
    }
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct AccessPaths {

View on GitHub (pinned to fe7fd564f8)