A trusted caller can now do everything a user does through NickServ commands — Register, Authenticate, SetPassword, SetEmail, Confirm, Drop, ForceLogout, GroupNick, UngroupNick — over gRPC instead of IRC. Each mirrors its NickServ command's exact behavior (Drop reuses the same cleanup a peer's gossiped drop already triggers; SetPassword/Register derive credentials off the shared engine lock, same as the IRC path) but is privileged: the bearer token is the authorization, so most calls skip the account's own password check the IRC command requires — the same trust model as an admin-level JSON-RPC integration. A write commits locally and gossips to every other node exactly like an IRC-originated one, no new propagation path needed. Verified end-to-end over the real wire, not just unit tests: registered an account via gRPC, changed its password via gRPC, then logged into a live IRC session with that exact password.
140 lines
5.4 KiB
Protocol Buffer
140 lines
5.4 KiB
Protocol Buffer
// Directory replication API: lets a website (e.g. a Django backend) mirror the
|
|
// account/channel directory this node owns, without touching IRC at all.
|
|
//
|
|
// Deliberately excludes anything credential-shaped (password hashes, SCRAM
|
|
// verifiers, TLS cert fingerprints) — a subscriber gets identity and metadata
|
|
// only, never a way to authenticate as an account. Channel events are the
|
|
// events already scoped Local to this node (see Scope in engine/db.rs), so a
|
|
// website only ever sees its own network's channels, matching what gossip
|
|
// federates.
|
|
syntax = "proto3";
|
|
package fedserv.v1;
|
|
|
|
service Directory {
|
|
// One-shot full read of current state, for a subscriber's initial load.
|
|
rpc Snapshot(SnapshotRequest) returns (SnapshotResponse);
|
|
|
|
// Live feed of directory changes from the moment of the call onward. A
|
|
// dropped stream should be followed by a fresh Snapshot (this is at-least-
|
|
// once delivery, not a resumable cursor) — appliers should be idempotent,
|
|
// keyed by account/channel name.
|
|
rpc Subscribe(SubscribeRequest) returns (stream ReplicationEvent);
|
|
}
|
|
|
|
message SnapshotRequest {}
|
|
|
|
message AccountRecord {
|
|
string name = 1;
|
|
string email = 2; // empty = none on file
|
|
bool verified = 3;
|
|
uint64 registered_at = 4; // unix seconds
|
|
string home = 5; // origin node sid that first registered it
|
|
}
|
|
|
|
message ChannelRecord {
|
|
string name = 1;
|
|
string founder = 2; // account name
|
|
uint64 registered_at = 3;
|
|
string description = 4;
|
|
}
|
|
|
|
message SnapshotResponse {
|
|
repeated AccountRecord accounts = 1;
|
|
repeated ChannelRecord channels = 2;
|
|
}
|
|
|
|
message SubscribeRequest {}
|
|
|
|
// The gossip envelope (origin/seq/lamport) is carried alongside the payload so
|
|
// a subscriber can log/dedupe by (origin, seq) if it wants to, even though the
|
|
// stream itself is not resumable by cursor in this version.
|
|
message ReplicationEvent {
|
|
string origin = 1;
|
|
uint64 seq = 2;
|
|
uint64 lamport = 3;
|
|
|
|
oneof kind {
|
|
AccountRegistered account_registered = 10;
|
|
AccountEmailSet account_email_set = 11;
|
|
AccountVerified account_verified = 12;
|
|
AccountDropped account_dropped = 13;
|
|
NickGrouped nick_grouped = 14;
|
|
NickUngrouped nick_ungrouped = 15;
|
|
ChannelRegistered channel_registered = 20;
|
|
ChannelDropped channel_dropped = 21;
|
|
ChannelFounderSet channel_founder_set = 22;
|
|
ChannelDescSet channel_desc_set = 23;
|
|
}
|
|
}
|
|
|
|
message AccountRegistered {
|
|
string name = 1;
|
|
string email = 2;
|
|
bool verified = 3;
|
|
uint64 registered_at = 4;
|
|
string home = 5;
|
|
}
|
|
message AccountEmailSet { string account = 1; string email = 2; }
|
|
message AccountVerified { string account = 1; }
|
|
message AccountDropped { string account = 1; }
|
|
message NickGrouped { string nick = 1; string account = 2; }
|
|
message NickUngrouped { string nick = 1; }
|
|
message ChannelRegistered { string name = 1; string founder = 2; uint64 registered_at = 3; }
|
|
message ChannelDropped { string name = 1; }
|
|
message ChannelFounderSet { string name = 1; string founder = 2; }
|
|
message ChannelDescSet { string name = 1; string description = 2; }
|
|
|
|
// Account authority API: the write side, for a trusted caller (e.g. a website's
|
|
// own backend, already having done its own login/session check) to manage
|
|
// accounts the same way an IRC user does through NickServ — registration,
|
|
// credentials, grouping, and forced logout. The bearer token IS the
|
|
// authorization: unlike the NickServ commands these mirror, most calls here do
|
|
// NOT re-check the account's own password (the caller is trusted, the same
|
|
// model as an admin-level override) — Register and Authenticate are the two
|
|
// exceptions, since the password is the actual input/question there.
|
|
// A write committed here replicates to every other fedserv node exactly like
|
|
// an IRC-originated one does — gossip doesn't know or care where it came from.
|
|
service Accounts {
|
|
rpc Register(RegisterRequest) returns (AccountReply);
|
|
rpc Authenticate(AuthenticateRequest) returns (AuthenticateReply);
|
|
rpc SetPassword(SetPasswordRequest) returns (AccountReply);
|
|
rpc SetEmail(SetEmailRequest) returns (AccountReply);
|
|
rpc Confirm(ConfirmRequest) returns (AccountReply);
|
|
rpc Drop(DropRequest) returns (AccountReply);
|
|
rpc ForceLogout(ForceLogoutRequest) returns (ForceLogoutReply);
|
|
rpc GroupNick(GroupNickRequest) returns (AccountReply);
|
|
rpc UngroupNick(UngroupNickRequest) returns (AccountReply);
|
|
}
|
|
|
|
enum Status {
|
|
OK = 0;
|
|
ALREADY_EXISTS = 1;
|
|
NOT_FOUND = 2;
|
|
RATE_LIMITED = 3;
|
|
INVALID = 4; // bad password/code/input, or a name-shaped invariant violation
|
|
INTERNAL = 5;
|
|
}
|
|
|
|
message AccountReply {
|
|
Status status = 1;
|
|
string message = 2; // human-readable detail, safe to show a user
|
|
}
|
|
|
|
message RegisterRequest { string name = 1; string password = 2; string email = 3; }
|
|
|
|
message AuthenticateRequest { string name = 1; string password = 2; }
|
|
message AuthenticateReply {
|
|
Status status = 1;
|
|
string account = 2; // canonical account name (resolves a grouped-nick alias)
|
|
}
|
|
|
|
message SetPasswordRequest { string account = 1; string password = 2; }
|
|
message SetEmailRequest { string account = 1; string email = 2; } // empty = clear
|
|
message ConfirmRequest { string account = 1; string code = 2; }
|
|
message DropRequest { string account = 1; }
|
|
|
|
message ForceLogoutRequest { string account = 1; }
|
|
message ForceLogoutReply { Status status = 1; uint32 sessions_cleared = 2; }
|
|
|
|
message GroupNickRequest { string nick = 1; string account = 2; }
|
|
message UngroupNickRequest { string nick = 1; }
|