Add persisted account store with NickServ REGISTER/IDENTIFY

Accounts are an append-only JSONL event log (state is the fold of the log) with
argon2id-hashed passwords. NickServ gains REGISTER <password> [email] and
IDENTIFY <password>; the engine tracks UID->nick from the burst so a command
resolves to the sender's current nick. Classic and ircd-agnostic — the cap-based
account-registration forward is the next step.
This commit is contained in:
Jean Chevronnet 2026-07-11 20:28:42 +00:00
parent 7929f5f9f4
commit 9285105afd
No known key found for this signature in database
11 changed files with 348 additions and 41 deletions

View file

@ -1,25 +1,21 @@
pub mod db;
pub mod service;
pub mod state;
use crate::proto::{NetAction, NetEvent};
use service::{Service, ServiceCtx};
use state::{EventLog, Network};
use db::Db;
use service::{Sender, Service, ServiceCtx};
use state::Network;
pub struct Engine {
services: Vec<Box<dyn Service>>,
#[allow(dead_code)]
network: Network,
#[allow(dead_code)]
log: EventLog,
db: Db,
}
impl Engine {
pub fn new(services: Vec<Box<dyn Service>>) -> Self {
Self {
services,
network: Network::default(),
log: EventLog::default(),
}
pub fn new(services: Vec<Box<dyn Service>>, db: Db) -> Self {
Self { services, network: Network::default(), db }
}
// Sent right after the SERVER line: burst, introduce every service, endburst.
@ -41,18 +37,30 @@ impl Engine {
pub fn handle(&mut self, event: NetEvent) -> Vec<NetAction> {
match event {
NetEvent::Ping { token, from } => vec![NetAction::Pong { token, from }],
NetEvent::UserConnect { uid, nick } => {
self.network.user_connect(uid, nick);
Vec::new()
}
NetEvent::Quit { uid } => {
self.network.user_quit(&uid);
Vec::new()
}
NetEvent::Privmsg { from, to, text } => self.dispatch(&from, &to, &text),
_ => Vec::new(),
}
}
// Route a PRIVMSG addressed to a service (by uid or nick) into that service.
// Route a PRIVMSG addressed to a service (by uid or nick) into that service,
// handing it the sender's resolved nick and the account store.
fn dispatch(&mut self, from: &str, to: &str, text: &str) -> Vec<NetAction> {
let nick = self.network.nick_of(from).unwrap_or(from).to_string();
let mut ctx = ServiceCtx::default();
for svc in self.services.iter_mut() {
let sender = Sender { uid: from, nick: &nick };
let Self { services, db, .. } = self;
for svc in services.iter_mut() {
if to.eq_ignore_ascii_case(svc.uid()) || to.eq_ignore_ascii_case(svc.nick()) {
let args: Vec<&str> = text.split_whitespace().collect();
svc.on_command(from, &args, &mut ctx);
svc.on_command(&sender, &args, &mut ctx, db);
break;
}
}