fix: resolve settings save failure - boolean type mismatch and improve error messages
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 4s
CI Pipeline / Clippy Lints (push) Successful in 57s
CI Pipeline / Rust Unit Tests (push) Successful in 1m19s
CI Pipeline / Security Audit (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 4s
CI Pipeline / Clippy Lints (push) Successful in 57s
CI Pipeline / Rust Unit Tests (push) Successful in 1m19s
CI Pipeline / Security Audit (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
This commit is contained in:
@ -293,62 +293,57 @@ async fn update_settings(
|
|||||||
admin_only(&auth)?;
|
admin_only(&auth)?;
|
||||||
|
|
||||||
// Update Azure SSO config
|
// Update Azure SSO config
|
||||||
if let Some(azure) = &req.azure_sso {
|
// Use static queries with proper typed bindings to avoid boolean→string mismatch
|
||||||
// Build dynamic UPDATE query — only set fields that are Some
|
if let Some(azure) = req.azure_sso {
|
||||||
let mut sets = Vec::new();
|
let update_secret = azure.client_secret.as_ref().is_some_and(|s| s != MASKED);
|
||||||
let mut vals: Vec<String> = Vec::new();
|
|
||||||
let mut idx = 1;
|
|
||||||
|
|
||||||
if let Some(v) = azure.enabled {
|
let result = if update_secret {
|
||||||
sets.push(format!("enabled = ${}", idx));
|
sqlx::query(
|
||||||
vals.push(v.to_string());
|
"UPDATE azure_sso_config SET \
|
||||||
idx += 1;
|
enabled = COALESCE($1, enabled), \
|
||||||
}
|
tenant_id = COALESCE($2, tenant_id), \
|
||||||
if let Some(ref v) = azure.tenant_id {
|
client_id = COALESCE($3, client_id), \
|
||||||
sets.push(format!("tenant_id = ${}", idx));
|
client_secret = $4, \
|
||||||
vals.push(v.clone());
|
redirect_uri = COALESCE($5, redirect_uri), \
|
||||||
idx += 1;
|
scopes = COALESCE($6, scopes), \
|
||||||
}
|
updated_at = NOW() \
|
||||||
if let Some(ref v) = azure.client_id {
|
WHERE id = 1",
|
||||||
sets.push(format!("client_id = ${}", idx));
|
)
|
||||||
vals.push(v.clone());
|
.bind(azure.enabled)
|
||||||
idx += 1;
|
.bind(&azure.tenant_id)
|
||||||
}
|
.bind(&azure.client_id)
|
||||||
if let Some(ref v) = azure.client_secret {
|
.bind(azure.client_secret.as_deref().unwrap_or(""))
|
||||||
if v != MASKED {
|
.bind(&azure.redirect_uri)
|
||||||
sets.push(format!("client_secret = ${}", idx));
|
.bind(&azure.scopes)
|
||||||
vals.push(v.clone());
|
.execute(&state.db)
|
||||||
idx += 1;
|
.await
|
||||||
}
|
} else {
|
||||||
}
|
sqlx::query(
|
||||||
if let Some(ref v) = azure.redirect_uri {
|
"UPDATE azure_sso_config SET \
|
||||||
sets.push(format!("redirect_uri = ${}", idx));
|
enabled = COALESCE($1, enabled), \
|
||||||
vals.push(v.clone());
|
tenant_id = COALESCE($2, tenant_id), \
|
||||||
idx += 1;
|
client_id = COALESCE($3, client_id), \
|
||||||
}
|
redirect_uri = COALESCE($4, redirect_uri), \
|
||||||
if let Some(ref v) = azure.scopes {
|
scopes = COALESCE($5, scopes), \
|
||||||
sets.push(format!("scopes = ${}", idx));
|
updated_at = NOW() \
|
||||||
vals.push(v.clone());
|
WHERE id = 1",
|
||||||
idx += 1;
|
)
|
||||||
}
|
.bind(azure.enabled)
|
||||||
|
.bind(&azure.tenant_id)
|
||||||
|
.bind(&azure.client_id)
|
||||||
|
.bind(&azure.redirect_uri)
|
||||||
|
.bind(&azure.scopes)
|
||||||
|
.execute(&state.db)
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
if !sets.is_empty() {
|
result.map_err(|e| {
|
||||||
let sql = format!(
|
|
||||||
"UPDATE azure_sso_config SET {}, updated_at = NOW() WHERE id = 1",
|
|
||||||
sets.join(", ")
|
|
||||||
);
|
|
||||||
let mut q = sqlx::query(&sql);
|
|
||||||
for val in &vals {
|
|
||||||
q = q.bind(val);
|
|
||||||
}
|
|
||||||
q.execute(&state.db).await.map_err(|e| {
|
|
||||||
tracing::error!(error = %e, "Failed to update azure_sso_config");
|
tracing::error!(error = %e, "Failed to update azure_sso_config");
|
||||||
(
|
(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(json!({ "error": { "code": "internal_error", "message": "Database error" } })),
|
Json(json!({ "error": { "code": "internal_error", "message": format!("Failed to update Azure SSO config: {}", e) } })),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
}
|
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
&state.db,
|
&state.db,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
IconButton, InputLabel, MenuItem, Select, Snackbar, Switch, TextField,
|
IconButton, InputLabel, MenuItem, Select, Snackbar, Switch, TextField,
|
||||||
Toolbar, Typography,
|
Toolbar, Typography,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||||
import SaveIcon from '@mui/icons-material/Save'
|
import SaveIcon from '@mui/icons-material/Save'
|
||||||
import DeleteIcon from '@mui/icons-material/Delete'
|
import DeleteIcon from '@mui/icons-material/Delete'
|
||||||
@ -78,8 +79,12 @@ export default function SettingsPage() {
|
|||||||
notification,
|
notification,
|
||||||
})
|
})
|
||||||
setSuccess('Settings saved successfully')
|
setSuccess('Settings saved successfully')
|
||||||
} catch {
|
} catch (err: unknown) {
|
||||||
setError('Failed to save settings')
|
const axiosErr = err as AxiosError<{ error?: { message?: string } }>
|
||||||
|
const msg =
|
||||||
|
axiosErr.response?.data?.error?.message ??
|
||||||
|
(err instanceof Error ? err.message : 'Failed to save settings')
|
||||||
|
setError(msg)
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user