Make scanning an admin setting, and document SSO

The per-feed schedule picker is gone and global Settings is admin-only,
enforced in the handlers with 403s rather than just hidden: polling costs
bandwidth and affects everyone reading the feed, so it belongs to the
operator. Folders, keywords and per-feed limits stay open to anyone.

docs/sso.md covers Cloudflare Zero Trust and Authentik end to end,
including why trusted_proxies names the proxy and not a subnet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
2026-09-10 23:16:33 +00:00
parent 06f182b555
commit 4810bb5cfb
5 changed files with 261 additions and 14 deletions

View File

@@ -432,6 +432,13 @@ impl ApiError {
status: StatusCode::BAD_REQUEST,
}
}
fn forbidden(msg: impl Into<String>) -> Self {
Self {
error: anyhow::Error::msg(msg.into()),
status: StatusCode::FORBIDDEN,
}
}
}
impl IntoResponse for ApiError {
@@ -616,8 +623,14 @@ where
async fn patch_feed(
State(state): State<WebState>,
Path(id): Path<String>,
user: crate::db::User,
Json(body): Json<FeedPatch>,
) -> Result<StatusCode, ApiError> {
// How often a feed is polled is the operator's call: it costs bandwidth, it is what
// publishers notice, and one impatient setting affects everyone reading the feed.
if body.schedule.is_some() && !user.is_admin {
return Err(ApiError::forbidden("only an admin sets when feeds are scanned"));
}
let mut cfg = (*state.ctx.cfg()).clone();
// Derived feeds have no config entry. Editing one is the moment it earns a real
@@ -990,8 +1003,12 @@ struct SettingsPatch {
async fn patch_settings(
State(state): State<WebState>,
user: crate::db::User,
Json(body): Json<SettingsPatch>,
) -> Result<StatusCode, ApiError> {
if !user.is_admin {
return Err(ApiError::forbidden("only an admin changes these settings"));
}
let mut cfg = (*state.ctx.cfg()).clone();
if let Some(sched) = body.schedule {
let sched = sched.trim().to_owned();