auth: warn on unknown [[oper]] privilege names instead of silently dropping them
All checks were successful
CI / check (push) Successful in 3m53s

This commit is contained in:
Jean Chevronnet 2026-07-17 12:45:41 +00:00
parent cfe05481d6
commit 1fb3615b7e
No known key found for this signature in database
4 changed files with 76 additions and 14 deletions

View file

@ -130,6 +130,18 @@ impl Config {
}
map
}
// (account, unrecognised-privilege-name) pairs across all [[oper]] blocks, so
// the caller can warn about a typo that would otherwise silently grant nothing.
pub fn oper_priv_warnings(&self) -> Vec<(String, String)> {
self.oper
.iter()
.flat_map(|o| {
let (_, unknown) = echo_api::Privs::parse_names(&o.privs);
unknown.into_iter().map(move |name| (o.account.clone(), name))
})
.collect()
}
}
// The service modules to bring up at burst. Each name maps to a compiled-in

View file

@ -851,6 +851,13 @@ impl Engine {
if self.service_oper_type.is_empty() { "(none)" } else { &self.service_oper_type },
)));
out.push(notice("Not reloadable (need a RESTART): server name/SID/protocol, uplink, service umodes, keycard.".to_string()));
// Flag any typo'd privilege names so they don't silently grant nothing.
for (account, name) in cfg.oper_priv_warnings() {
out.push(notice(format!(
"\x02Warning:\x02 oper \x02{account}\x02 has an unknown privilege \x02{name}\x02 (valid: {}) — ignored.",
echo_api::Priv::valid_names()
)));
}
out
}

View file

@ -187,6 +187,9 @@ async fn main() -> Result<()> {
// Channel for services-initiated actions to reach the uplink (drained by the link loop).
let (irc_tx, irc_rx) = tokio::sync::mpsc::unbounded_channel();
engine.lock().await.set_irc_out(irc_tx);
for (account, name) in cfg.oper_priv_warnings() {
tracing::warn!(%account, privilege = %name, valid = %echo_api::Priv::valid_names(), "unknown privilege in [[oper]] — ignored (typo?)");
}
engine.lock().await.set_opers(cfg.opers());
engine.lock().await.set_config_path(path.clone());
engine.lock().await.set_sid(cfg.server.sid.clone());