diff --git a/backend/windmill-api/src/auth.rs b/backend/windmill-api/src/auth.rs index 00148a2961..0765f701f9 100644 --- a/backend/windmill-api/src/auth.rs +++ b/backend/windmill-api/src/auth.rs @@ -121,11 +121,10 @@ impl AuthCache { match jwt_result { Ok(claims) => { - if w_id.is_some_and(|w_id| w_id != claims.workspace_id) { + if w_id.is_some_and(|w_id| !claims.allowed_in_workspace(&w_id)) { tracing::error!("JWT auth error: workspace_id mismatch"); return None; } - let username_override = username_override_from_label(claims.label); let authed = crate::db::ApiAuthed { email: claims.email, diff --git a/backend/windmill-common/src/auth.rs b/backend/windmill-common/src/auth.rs index c2b5b5dffd..e48cf3728e 100644 --- a/backend/windmill-common/src/auth.rs +++ b/backend/windmill-common/src/auth.rs @@ -140,13 +140,26 @@ pub struct JWTAuthClaims { pub groups: Vec, pub folders: Vec<(String, bool, bool)>, pub label: Option, - pub workspace_id: String, + pub workspace_id: Option, + pub workspace_ids: Option>, pub exp: usize, pub job_id: Option, pub scopes: Option>, pub audit_span: Option, } +impl JWTAuthClaims { + pub fn allowed_in_workspace(&self, w_id: &str) -> bool { + self.workspace_id + .as_ref() + .is_some_and(|token_w_id| w_id == token_w_id) + || self + .workspace_ids + .as_ref() + .is_some_and(|token_w_ids| token_w_ids.iter().any(|token_w_id| w_id == token_w_id)) + } +} + #[derive(Deserialize, Debug)] pub struct JobPerms { pub email: String, @@ -411,7 +424,8 @@ pub async fn create_jwt_token( groups: authed.groups.clone(), folders: authed.folders.clone(), label, - workspace_id: workspace_id.to_string(), + workspace_id: Some(workspace_id.to_string()), + workspace_ids: None, exp: (chrono::Utc::now() + chrono::Duration::seconds(expires_in_seconds as i64)).timestamp() as usize, job_id: job_id.map(|id| id.to_string()),