feat(gateway): hivectl gateway user management + fix htpasswdFile assertion
Add `hivectl gateway {create-user,delete-user,list-users}` subcommands for
managing htpasswd files used by gateway Basic auth. Pure Rust bcrypt
(cost 12, $2y$ prefix nginx accepts). No external htpasswd binary required.
Also fix the NixOS module assertion: `cfg.auth ? htpasswdFile` is always
true in the module system (declared options always exist as keys); switch
to `nullOr path; default = null` + `!= null` check so the assertion
actually fires with a useful error when enable=true but no file is set.
Guard bind-mount and nginx config against null to prevent eval errors.
Update docs/gateway.md to show hivectl commands instead of raw htpasswd.
This commit is contained in:
parent
25d2951d1e
commit
4bff450343
61 changed files with 1084 additions and 547 deletions
|
|
@ -268,12 +268,7 @@ impl Broker {
|
|||
/// broker. Used by the scheduler to skip re-delivery of the same
|
||||
/// scheduled prompt without blocking distinct schedules whose
|
||||
/// bodies differ.
|
||||
pub fn has_pending_with_body(
|
||||
&self,
|
||||
recipient: &str,
|
||||
sender: &str,
|
||||
body: &str,
|
||||
) -> Result<bool> {
|
||||
pub fn has_pending_with_body(&self, recipient: &str, sender: &str, body: &str) -> Result<bool> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let n: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM messages
|
||||
|
|
@ -391,7 +386,13 @@ impl Broker {
|
|||
)?;
|
||||
let rows: Vec<(i64, String, String, String, Option<i64>)> = stmt
|
||||
.query_map(params![recipient, max_i], |row| {
|
||||
Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?))
|
||||
Ok((
|
||||
row.get(0)?,
|
||||
row.get(1)?,
|
||||
row.get(2)?,
|
||||
row.get(3)?,
|
||||
row.get(4)?,
|
||||
))
|
||||
})?
|
||||
.collect::<rusqlite::Result<_>>()?;
|
||||
drop(stmt);
|
||||
|
|
@ -673,7 +674,11 @@ impl Broker {
|
|||
/// Reminder rollup stats for an agent over a time window. Returns
|
||||
/// counts of scheduled, delivered, and pending reminders created
|
||||
/// in the last `since_secs` seconds (0 = all reminders).
|
||||
pub fn reminder_rollup_for(&self, agent: &str, since_secs: u64) -> Result<hive_sh4re::ReminderStats> {
|
||||
pub fn reminder_rollup_for(
|
||||
&self,
|
||||
agent: &str,
|
||||
since_secs: u64,
|
||||
) -> Result<hive_sh4re::ReminderStats> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let cutoff_time = if since_secs > 0 {
|
||||
let now = std::time::SystemTime::now()
|
||||
|
|
@ -740,9 +745,7 @@ impl Broker {
|
|||
|| canceller == hive_sh4re::OPERATOR_RECIPIENT
|
||||
|| canceller == hive_sh4re::MANAGER_AGENT;
|
||||
if !authorised {
|
||||
anyhow::bail!(
|
||||
"reminder {id}: '{canceller}' not allowed to cancel (owner = '{owner}')"
|
||||
);
|
||||
anyhow::bail!("reminder {id}: '{canceller}' not allowed to cancel (owner = '{owner}')");
|
||||
}
|
||||
let n = conn.execute(
|
||||
"DELETE FROM reminders WHERE id = ?1 AND sent_at IS NULL",
|
||||
|
|
@ -862,7 +865,9 @@ impl Broker {
|
|||
}
|
||||
drop(conn);
|
||||
// Emit per-row Sent events (only for rows that succeeded).
|
||||
for (((id, agent, body), result), msg_id) in items.iter().zip(results.iter()).zip(msg_ids.iter()) {
|
||||
for (((id, agent, body), result), msg_id) in
|
||||
items.iter().zip(results.iter()).zip(msg_ids.iter())
|
||||
{
|
||||
if result.is_ok() {
|
||||
let _ = self.events.send(MessageEvent::Sent {
|
||||
id: *msg_id,
|
||||
|
|
@ -1029,10 +1034,7 @@ mod tests {
|
|||
assert_eq!(broker.requeue_inflight("b").unwrap(), 1);
|
||||
let d2 = pop_one(broker, "b").expect("popped again");
|
||||
assert_eq!(d2.message.body, "hi");
|
||||
assert!(
|
||||
d2.redelivered,
|
||||
"second pop should be tagged redelivered"
|
||||
);
|
||||
assert!(d2.redelivered, "second pop should be tagged redelivered");
|
||||
assert_eq!(broker.ack_turn("b").unwrap(), 1);
|
||||
}
|
||||
|
||||
|
|
@ -1275,4 +1277,3 @@ mod tests {
|
|||
assert!(pop_one(broker, "bob").is_none());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue