#[allow(clippy::wildcard_imports)]
use super::*;
#[derive(Deserialize)]
pub(crate) struct OrgSsoBody {
email_domain: String,
issuer: String,
client_id: String,
#[serde(default)]
client_secret: Option<String>,
}
pub(crate) async fn get_account_org_sso(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Json<Value>, (StatusCode, String)> {
let (user_id, _email) = auth_user(&state, &headers).await?;
let (status, json) = billing_forward(
&state.cfg,
"GET",
format!("/api/billing/org/{user_id}/sso"),
None,
)
.await?;
finish(status, json)
}
pub(crate) async fn put_account_org_sso(
State(state): State<AppState>,
headers: HeaderMap,
Json(body): Json<OrgSsoBody>,
) -> Result<Json<Value>, (StatusCode, String)> {
let (user_id, _email) = auth_user(&state, &headers).await?;
let (status, json) = billing_forward(
&state.cfg,
"PUT",
format!("/api/billing/org/{user_id}/sso"),
Some(json!({
"email_domain": body.email_domain,
"issuer": body.issuer,
"client_id": body.client_id,
"client_secret": body.client_secret,
})),
)
.await?;
finish(status, json)
}
pub(crate) async fn post_account_org_sso_verify(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Json<Value>, (StatusCode, String)> {
let (user_id, _email) = auth_user(&state, &headers).await?;
let (status, json) = billing_forward(
&state.cfg,
"POST",
format!("/api/billing/org/{user_id}/sso/verify"),
Some(json!({})),
)
.await?;
finish(status, json)
}
#[derive(Deserialize)]
pub(crate) struct OrgSsoRequiredBody {
sso_required: bool,
}
pub(crate) async fn put_account_org_sso_required(
State(state): State<AppState>,
headers: HeaderMap,
Json(body): Json<OrgSsoRequiredBody>,
) -> Result<Json<Value>, (StatusCode, String)> {
let (user_id, _email) = auth_user(&state, &headers).await?;
let (status, json) = billing_forward(
&state.cfg,
"PUT",
format!("/api/billing/org/{user_id}/sso/required"),
Some(json!({ "sso_required": body.sso_required })),
)
.await?;
finish(status, json)
}
pub(crate) async fn delete_account_org_sso(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Json<Value>, (StatusCode, String)> {
let (user_id, _email) = auth_user(&state, &headers).await?;
let (status, json) = billing_forward(
&state.cfg,
"DELETE",
format!("/api/billing/org/{user_id}/sso"),
None,
)
.await?;
if status == StatusCode::NO_CONTENT {
return Ok(Json(json!({ "removed": true })));
}
finish(status, json)
}
#[derive(Deserialize)]
pub(crate) struct AuditQuery {
#[serde(default)]
before: Option<i64>,
#[serde(default)]
limit: Option<i64>,
#[serde(default)]
event: Option<String>,
}
fn build_audit_query(q: &AuditQuery) -> String {
let mut parts: Vec<String> = Vec::new();
if let Some(b) = q.before
&& b > 0
{
parts.push(format!("before={b}"));
}
if let Some(l) = q.limit {
parts.push(format!("limit={}", l.clamp(1, 200)));
}
if let Some(ev) = q.event.as_deref() {
let ev = ev.trim();
if !ev.is_empty()
&& ev.len() <= 48
&& ev.bytes().all(|b| b.is_ascii_lowercase() || b == b'_')
{
parts.push(format!("event={ev}"));
}
}
if parts.is_empty() {
String::new()
} else {
format!("?{}", parts.join("&"))
}
}
pub(crate) async fn get_account_org_audit(
State(state): State<AppState>,
headers: HeaderMap,
Query(q): Query<AuditQuery>,
) -> Result<Json<Value>, (StatusCode, String)> {
let (user_id, _email) = auth_user(&state, &headers).await?;
let qs = build_audit_query(&q);
let (status, json) = billing_forward(
&state.cfg,
"GET",
format!("/api/billing/org/{user_id}/audit{qs}"),
None,
)
.await?;
finish(status, json)
}
pub(crate) async fn get_account_org_audit_export(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Response, (StatusCode, String)> {
let (user_id, _email) = auth_user(&state, &headers).await?;
let (status, body) = billing_forward_text(
&state.cfg,
format!("/api/billing/org/{user_id}/audit/export.csv"),
)
.await?;
if !status.is_success() {
return Err((status, "audit export failed".to_string()));
}
Ok((
[
(header::CONTENT_TYPE, "text/csv; charset=utf-8"),
(
header::CONTENT_DISPOSITION,
"attachment; filename=\"leanctx-audit-log.csv\"",
),
],
body,
)
.into_response())
}