Commit graph

79 commits

Author SHA1 Message Date
bdcce01f11
email: optional logo image in the header
A configurable logo URL renders as an image beside the brand name in the
email header (falling back to the brand text when unset). Email clients
need a hosted image, so it takes a URL rather than an inline asset.
2026-07-12 16:36:06 +00:00
98630c7d36
email: premium HTML template with accent colour and copy affordance
Redesigns the mail template (branded header bar, hero code block, copy
button, muted footer) and adds a configurable accent colour alongside the
brand name.
2026-07-12 16:07:36 +00:00
06e9da4dc8
email: HTML templates and multipart messages
Emails now render an HTML template from templates/email with a plaintext
fallback, sent as multipart/alternative. A configurable brand name shows
in the template. Reset and confirmation mails share one styled base.
2026-07-12 15:57:34 +00:00
fbcf0eaac7
nickserv: email confirmation (CONFIRM) on registration
When email is configured and a REGISTER includes an address, the account
starts unverified and a confirmation code is emailed. CONFIRM <code>
verifies it (a federated AccountVerified event); INFO shows an unconfirmed
email until then. Emailed codes now carry a purpose (reset vs confirm).
2026-07-12 15:36:30 +00:00
a2957ffe02
email: outbound mail subsystem and RESETPASS
Adds a config-driven mailer: a SendEmail action the link layer pipes to
a configured command (sendmail -t, msmtp, ...) off-thread. NickServ
RESETPASS <account> emails a short-lived code to the address on file;
RESETPASS <account> <code> <newpassword> completes the reset. Codes are
node-local; the password change itself federates.
2026-07-12 15:28:15 +00:00
020e9bd576
nickserv: grouped nicks (GROUP/GLIST/UNGROUP) and GHOST
A nick can be grouped to an account and then identifies to it; the store
resolves an alias to its account everywhere it authenticates. GHOST (and
RECOVER) rename off a session using a nick you own. Grouped nicks
federate and are dropped with their account.
2026-07-12 14:59:23 +00:00
ef89f97158
nickserv: add SET (password/email) and DROP
SET PASSWORD changes your password (derived off-thread like register) and
SET EMAIL sets an address. DROP deletes your account, releases and drops
the channels you founded, and logs you out. A dropped account federates,
and each node logs out any local session that was relying on it.
2026-07-12 14:32:53 +00:00
0427819f86
nickserv: add INFO and ALIST
INFO shows an account's registration date and (to its owner) email; ALIST
lists the channels an account founds or has access on. The UTC time
formatter moves to the db module so both services share it.
2026-07-12 14:19:29 +00:00
0681c7a3b2
chanserv: drop a lost account's channels on takeover
When a registration conflict takes an account name away from this node,
the channels it had registered locally as founder can't transfer to the
name's new owner, so they are dropped (releasing the registered mode) and
the former session is told which were released.
2026-07-12 13:45:07 +00:00
aab64baf86
nickserv: log out the loser of a registration conflict
When a peer's registration wins a name a local session was logged into,
that session authenticated against a credential that no longer exists.
The node now clears its accountname (the ircd emits RPL_LOGGEDOUT) and
tells the user why. A new outbound channel lets the engine push these
services-initiated actions to the uplink from the gossip path, drained
by the link loop alongside incoming traffic.
2026-07-12 13:34:38 +00:00
f46662db90
gossip: deterministic winner for a concurrently-registered account
Two nodes registering the same name before gossip converges no longer
end up disagreeing. Each account records its home node, and the merge
keeps the earliest registration (by timestamp, then home node), so every
node folds the log to the same owner regardless of delivery order. The
merge is commutative and idempotent.
2026-07-12 13:11:22 +00:00
d4b9172455
gossip: replicate account state only, keep channels node-local
Each event now has a scope: account identity is global and gossips to
every peer; channel state is local to the node that authored it. The
version vector, push, digest and compaction only carry global events,
and ingest refuses any non-global entry a peer sends. A node can no
longer be handed ownership of a channel registered on a network it was
never part of.
2026-07-12 12:20:24 +00:00
e8b55bdb27
chanserv: entrymsg, enforce, getkey, seen, clone, aop/sop/vop
Tracks channel membership (join/part/kick), the current key, and per-nick
last-seen in the network view. Entry messages greet joiners; enforce
re-applies the lock, access modes and auto-kicks to everyone present;
getkey reports the key; seen reports last activity; clone copies a
channel's settings; aop/sop/vop are tiered shortcuts over the access list.
2026-07-12 11:53:48 +00:00
674f543b40
chanserv: SET founder and description
SET <#channel> FOUNDER <account> transfers ownership; SET DESC <text>
stores a description shown in INFO. Both are founder-gated and persisted
to the event log.
2026-07-12 11:33:33 +00:00
ba3803e01c
chanserv: topic, invite, akick, list, status
Adds the remaining moderation and listing commands. Auto-kick masks are
stored in the event log and enforced on join: a matching user is banned
and kicked. Topic and invite are sourced from the ChanServ pseudoclient.
2026-07-12 11:30:07 +00:00
acd0e60946
chanserv: op, deop, voice, devoice, kick, ban, unban
Adds channel moderation commands sourced from the ChanServ pseudoclient.
Tracks connected users' nicks and hosts so targets can be resolved by
nick and banned by *!*@host. All commands require operator access.
2026-07-12 11:21:35 +00:00
f0ce2d9d1b
Add ChanServ ACCESS list with auto-op/voice on join
ACCESS <#channel> LIST|ADD|DEL manages op/voice by account (founder-only
changes). Entries are ChannelAccessAdd/Del events, so they replicate and
compact like the rest, and join grants +o/+v from the list.
2026-07-12 10:59:15 +00:00
353aee1b54
Add ChanServ mode lock (MLOCK)
Founders lock channel modes with MLOCK <#channel> [modes]. The lock is a
ChannelMlock event, so it replicates and compacts like everything else;
it is applied on channel creation alongside +r and enforced by reverting
any FMODE that breaks it. Our own changes are filtered so enforcement
can't loop. Simple (paramless) modes for now.
2026-07-12 09:33:23 +00:00
89593ebeb4
Add ChanServ with channel registration
REGISTER/INFO/DROP with founder auth, stored as ChannelRegistered and
ChannelDropped events so channel registrations replicate across nodes
and compact like accounts. Registration requires an identified account;
only the founder can drop.
2026-07-12 08:19:58 +00:00
cc0265548f
Compact the event log into per-account snapshots
Once the log grows past ~3x the account count it is rewritten to one
AccountRegistered per account (churned cert events dropped), via a temp
file and rename. Cert replay is now idempotent, so a snapshot plus
re-delivered events converge and a peer syncing from a compacted node
still gets every account. Runs on a 30m timer.
2026-07-12 07:24:44 +00:00
df7b413862
Push new log entries to peers on commit
A broadcast channel forwards each freshly committed entry straight to
connected peers, so a registration propagates in milliseconds. The
periodic digest drops to a 10s anti-entropy backstop.
2026-07-12 07:20:25 +00:00
d0556ebe8c
Add gossip replication between nodes
Nodes run anti-entropy over a TCP link: each advertises its version
vector, the peer replies with the log entries it lacks, and ingest is
idempotent so re-delivery and reconnect after a split both converge.
The engine is shared behind a mutex; config gains [gossip] and [[peer]].
2026-07-12 07:05:07 +00:00
3161a97a5e
Add Lamport clock and gossip ingest to the event log
Entries carry a Lamport timestamp alongside origin/seq. EventLog keeps a
per-origin version vector and ingests peer entries idempotently.
Db::ingest folds them in. No transport yet.
2026-07-12 06:30:37 +00:00
c066f1072f
Add EventLog with origin/seq metadata for replication 2026-07-12 06:08:27 +00:00
a382ac8ad3
Add SASL EXTERNAL 2026-07-12 03:59:24 +00:00
9a918839fe
Derive registration credentials off-thread; rate-limit REGISTER; constant-time SCRAM compare 2026-07-12 03:59:24 +00:00
496c0f99a6
Add SASL SCRAM-SHA-256 and SCRAM-SHA-512 2026-07-12 03:59:24 +00:00
65deeef2f7
Complete IRCv3 SASL 3.2 login for PLAIN
Turn the PLAIN verification exchange into a full account login:

- Advertise the mechanism list to the uplink as `saslmechlist` network
  metadata, so clients see `sasl=PLAIN` in CAP LS (SASL 3.2).
- On a valid response set the client's `accountname` metadata (drives
  RPL_LOGGEDIN 900) before reporting success (D S, drives 903); a bad
  credential or unknown mechanism reports failure (D F, drives 904).
- Reassemble the base64 response from the uplink's 400-byte AUTHENTICATE
  chunks, ending on a short chunk or a lone "+", with an upper bound to
  cap a pre-auth client's buffer.
- Log the user in on NickServ REGISTER and IDENTIFY too, through the same
  accountname metadata, and return the canonical account name from the
  store so the login keeps its registered casing.

Verified against irctest server_tests/sasl.py (PLAIN success/failure,
non-ASCII, no-authzid, chunked payloads, unknown mechanism, too-long,
retry) on InspIRCd + fedserv.
2026-07-12 00:36:51 +00:00
9285105afd
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.
2026-07-11 20:28:42 +00:00