rate limit token creation on CLOUD_HOSTED (10/min per user) (#8664)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-04-02 01:52:02 +00:00
committed by GitHub
parent 7ab0ea581d
commit c86846ac19
3 changed files with 42 additions and 0 deletions

1
backend/Cargo.lock generated
View File

@@ -16568,6 +16568,7 @@ dependencies = [
"argon2",
"axum 0.8.4",
"chrono",
"dashmap 6.1.0",
"http 1.4.0",
"hyper 1.9.0",
"lazy_static",

View File

@@ -21,6 +21,7 @@ windmill-api-auth.workspace = true
windmill-audit.workspace = true
windmill-git-sync.workspace = true
dashmap.workspace = true
argon2.workspace = true
axum.workspace = true
chrono.workspace = true

View File

@@ -12,6 +12,7 @@ use sqlx::{Postgres, Transaction};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::LazyLock;
use std::time::Duration;
use windmill_api_auth::ApiAuthed;
@@ -60,6 +61,43 @@ use windmill_git_sync::handle_deployment_metadata;
pub const COOKIE_PATH: &str = "/";
const TOKEN_CREATE_LIMIT_PER_MINUTE: i32 = 10;
struct TokenRateLimitEntry {
count: i32,
minute_bucket: i64,
}
static TOKEN_CREATE_RATE_LIMIT: LazyLock<dashmap::DashMap<String, TokenRateLimitEntry>> =
LazyLock::new(dashmap::DashMap::new);
fn check_token_create_rate_limit(username: &str) -> Result<()> {
if !*CLOUD_HOSTED {
return Ok(());
}
let current_minute = chrono::Utc::now().timestamp() / 60;
let mut entry = TOKEN_CREATE_RATE_LIMIT
.entry(username.to_string())
.or_insert(TokenRateLimitEntry { count: 0, minute_bucket: current_minute });
if entry.minute_bucket != current_minute {
entry.count = 0;
entry.minute_bucket = current_minute;
}
if entry.count >= TOKEN_CREATE_LIMIT_PER_MINUTE {
return Err(Error::Generic(
StatusCode::TOO_MANY_REQUESTS,
"Too many token creation requests. Please try again later.".to_string(),
));
}
entry.count += 1;
Ok(())
}
pub fn workspaced_service() -> Router {
Router::new()
.route("/list", get(list_users))
@@ -1975,6 +2013,8 @@ async fn create_token(
authed: ApiAuthed,
Json(token_config): Json<NewToken>,
) -> Result<(StatusCode, String)> {
check_token_create_rate_limit(&authed.username)?;
let mut tx = db.begin().await?;
let token = create_token_internal(&mut *tx, &db, &authed, token_config).await?;