Let identified users manage CERT without a password
All checks were successful
CI / check (push) Successful in 4m18s
All checks were successful
CI / check (push) Successful in 4m18s
This commit is contained in:
parent
bfff7dba91
commit
6149a15b34
3 changed files with 48 additions and 16 deletions
|
|
@ -1,17 +1,15 @@
|
|||
use echo_api::{CertError, Store};
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
|
||||
// CERT ADD|DEL|LIST <password> [fingerprint]: manage the TLS certificate
|
||||
// fingerprints that may log in to your account via SASL EXTERNAL. Each
|
||||
// subcommand is password-gated, so it needs no identified-session state.
|
||||
// CERT ADD|DEL|LIST [password] [fingerprint]: manage the TLS certificate
|
||||
// fingerprints that may log in to your account via SASL EXTERNAL. An identified
|
||||
// caller acts on their own account with no password; an unidentified one must
|
||||
// supply a throttled password (so CERT still works mid-session and can't be
|
||||
// abused as a guessing oracle).
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
match args.get(1).map(|s| s.to_ascii_uppercase()).as_deref() {
|
||||
Some("LIST") => {
|
||||
let Some(password) = args.get(2) else {
|
||||
ctx.notice(me, from.uid, "Syntax: CERT LIST <password>");
|
||||
return;
|
||||
};
|
||||
let Some(account) = verify(me, from, ctx, db, password) else {
|
||||
let Some((account, _)) = resolve(me, from, ctx, db, &args[2..]) else {
|
||||
return;
|
||||
};
|
||||
let fps = db.certfps(&account);
|
||||
|
|
@ -22,11 +20,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
}
|
||||
Some("ADD") => {
|
||||
let (Some(password), Some(fp)) = (args.get(2), args.get(3)) else {
|
||||
ctx.notice(me, from.uid, "Syntax: CERT ADD <password> <fingerprint>");
|
||||
let Some((account, rest)) = resolve(me, from, ctx, db, &args[2..]) else {
|
||||
return;
|
||||
};
|
||||
let Some(account) = verify(me, from, ctx, db, password) else {
|
||||
let Some(&fp) = rest.first() else {
|
||||
ctx.notice(me, from.uid, "Syntax: CERT ADD [password] <fingerprint>");
|
||||
return;
|
||||
};
|
||||
match db.certfp_add(&account, fp) {
|
||||
|
|
@ -38,11 +36,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
}
|
||||
Some("DEL") | Some("REMOVE") => {
|
||||
let (Some(password), Some(fp)) = (args.get(2), args.get(3)) else {
|
||||
ctx.notice(me, from.uid, "Syntax: CERT DEL <password> <fingerprint>");
|
||||
let Some((account, rest)) = resolve(me, from, ctx, db, &args[2..]) else {
|
||||
return;
|
||||
};
|
||||
let Some(account) = verify(me, from, ctx, db, password) else {
|
||||
let Some(&fp) = rest.first() else {
|
||||
ctx.notice(me, from.uid, "Syntax: CERT DEL [password] <fingerprint>");
|
||||
return;
|
||||
};
|
||||
match db.certfp_del(&account, fp) {
|
||||
|
|
@ -51,10 +49,26 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
Err(_) => ctx.notice(me, from.uid, "Could not remove the fingerprint, please try again later."),
|
||||
}
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, "Syntax: CERT ADD|DEL|LIST <password> [fingerprint]"),
|
||||
_ => ctx.notice(me, from.uid, "Syntax: CERT ADD|DEL|LIST [password] [fingerprint]"),
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the acting account and the arguments that follow it. An identified
|
||||
// caller operates on their own account and passes no password, so `rest` is
|
||||
// returned untouched; otherwise the first argument is the throttled password and
|
||||
// the remainder follows it.
|
||||
fn resolve<'a>(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store, rest: &'a [&'a str]) -> Option<(String, &'a [&'a str])> {
|
||||
if let Some(account) = from.account {
|
||||
return Some((account.to_string(), rest));
|
||||
}
|
||||
let Some((&password, tail)) = rest.split_first() else {
|
||||
ctx.notice(me, from.uid, "Identify to NickServ first, or start the command with your password.");
|
||||
return None;
|
||||
};
|
||||
let account = verify(me, from, ctx, db, password)?;
|
||||
Some((account, tail))
|
||||
}
|
||||
|
||||
// Verify the caller's password against the account matching their current nick,
|
||||
// throttled and recorded like IDENTIFY — so CERT can't be used as an unthrottled
|
||||
// guessing oracle (a user can `/nick victim` then CERT LIST <guess>) and each ~1s
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue