feat(auth): support optional expiry for bot API tokens

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-01 07:22:57 +02:00
parent ed18d95bb0
commit 3cba9ecf95
8 changed files with 139 additions and 8 deletions

View File

@@ -75,8 +75,16 @@ pub struct AuthResponse {
#[derive(Debug, Deserialize)]
pub struct CreateTokenInput {
pub name: String,
/// Optional lifetime in days. Omit (or `null`) for a non-expiring
/// token (the historical behaviour). When set, must be 1..=3650.
#[serde(default)]
pub expires_in_days: Option<i64>,
}
/// Upper bound on a requested token lifetime (~10 years). A token that needs
/// to outlive this should be rotated, not minted once forever.
const MAX_TOKEN_EXPIRY_DAYS: i64 = 3650;
#[derive(Debug, Deserialize)]
pub struct ChangePassword {
pub current_password: String,
@@ -312,8 +320,22 @@ async fn create_token(
details: serde_json::json!({ "name": "max 64 characters" }),
});
}
let expires_at = match input.expires_in_days {
None => None,
Some(days) if (1..=MAX_TOKEN_EXPIRY_DAYS).contains(&days) => {
Some(Utc::now() + Duration::days(days))
}
Some(_) => {
return Err(AppError::ValidationFailed {
message: "token expiry out of range".into(),
details: serde_json::json!({
"expires_in_days": format!("must be between 1 and {MAX_TOKEN_EXPIRY_DAYS}")
}),
});
}
};
let (raw, hash) = generate_token();
let token = repo::api_token::create(&state.db, user.id, name, &hash).await?;
let token = repo::api_token::create(&state.db, user.id, name, &hash, expires_at).await?;
Ok((
StatusCode::CREATED,
Json(CreatedTokenResponse { token, bearer: raw }),