mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 00:03:08 +00:00
fix(workspaces): split get_settings into admin-only + public endpoint (#8990)
* fix: redact GitHub App tokens and Slack OAuth secret for non-admins `GET /workspaces/get_settings` returned the full `git_app_installations` JSONB to any workspace member. That column caches the GitHub App JWT and installation token used by git-sync; the installation token is refreshed on every git-sync action and valid for ~55 minutes, so the value sitting in the DB is essentially always live. Null it out for non-admins, matching the existing `slack_oauth_client_secret` redaction. The tarball export's v2 settings format (added in #8935) included `slack_oauth_client_secret` with no admin gating, regressing the same redaction. Mirror the admin check there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: split get_settings into admin-only + public endpoint Adds `WorkspacePublicSettings` and `GET /workspaces/get_public_settings`, which returns only fields safe for any workspace member to read (workspace_id, slack/teams team identity, mute_critical_alerts, deploy_ui, large_file_storage, datatable). `get_settings` is now admin-only via `require_admin`. Migrates frontend callers: every caller that read non-sensitive fields (deploy_ui on trigger pages, mute_critical_alerts on the root layout, slack team identity for handler pickers, etc.) now uses `getPublicSettings`. The admin-managed settings UI, git-sync admin context, operator settings, checkout polling, and full settings page stay on `getSettings`. This replaces the field-level redactions added in the previous commit: the type system itself defines the public surface, so adding a sensitive column to `workspace_settings` no longer defaults to leaking — it stays out of `WorkspacePublicSettings` unless explicitly added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
568d9cc8a0
commit
4483d0cab9
@@ -83,6 +83,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get_imports/{*importer_path}", get(get_imports))
|
||||
.route("/get_dependents_amounts", post(get_dependents_amounts))
|
||||
.route("/get_settings", get(get_settings))
|
||||
.route("/get_public_settings", get(get_public_settings))
|
||||
.route(
|
||||
"/get_copilot_settings_state",
|
||||
get(get_copilot_settings_state),
|
||||
@@ -279,6 +280,34 @@ pub struct WorkspaceSettings {
|
||||
pub public_app_execution_limit_per_minute: Option<i32>,
|
||||
}
|
||||
|
||||
/// Subset of `WorkspaceSettings` that is safe to return to any workspace
|
||||
/// member. Adding a field here means it will be readable by every authed user
|
||||
/// in the workspace — anything sensitive (OAuth secrets, GitHub App tokens,
|
||||
/// billing/customer info, integration credentials, etc.) must NOT be added.
|
||||
/// The full `WorkspaceSettings` struct is admin-only via `get_settings`.
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
pub struct WorkspacePublicSettings {
|
||||
pub workspace_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub slack_team_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub slack_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub teams_team_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub teams_team_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub teams_team_guid: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mute_critical_alerts: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deploy_ui: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub large_file_storage: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub datatable: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct CopilotSettingsState {
|
||||
pub has_instance_ai_config: bool,
|
||||
@@ -622,6 +651,10 @@ async fn get_settings(
|
||||
Path(w_id): Path<String>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
) -> JsonResult<WorkspaceSettings> {
|
||||
// Admin-only: this struct contains OAuth secrets, GitHub App tokens, billing
|
||||
// info, and other admin-managed integration credentials. Non-admin callers
|
||||
// should use `get_public_settings`.
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let settings = sqlx::query_as!(
|
||||
WorkspaceSettings,
|
||||
@@ -669,12 +702,46 @@ async fn get_settings(
|
||||
.await
|
||||
.map_err(|e| Error::internal_err(format!("getting settings: {e:#}")))?;
|
||||
|
||||
let mut settings = not_found_if_none(settings, "workspace settings", &w_id)?;
|
||||
let settings = not_found_if_none(settings, "workspace settings", &w_id)?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
async fn get_public_settings(
|
||||
authed: ApiAuthed,
|
||||
Path(w_id): Path<String>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
) -> JsonResult<WorkspacePublicSettings> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let settings = sqlx::query_as!(
|
||||
WorkspacePublicSettings,
|
||||
r#"
|
||||
SELECT
|
||||
workspace_id,
|
||||
slack_team_id,
|
||||
slack_name,
|
||||
teams_team_id,
|
||||
teams_team_name,
|
||||
teams_team_guid,
|
||||
mute_critical_alerts,
|
||||
deploy_ui,
|
||||
large_file_storage,
|
||||
datatable
|
||||
FROM
|
||||
workspace_settings
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
"#,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| Error::internal_err(format!("getting public settings: {e:#}")))?;
|
||||
|
||||
let settings = not_found_if_none(settings, "workspace settings", &w_id)?;
|
||||
tx.commit().await?;
|
||||
|
||||
if !authed.is_admin {
|
||||
settings.slack_oauth_client_secret = None;
|
||||
}
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user