echo/proto/fedserv.proto
Jean 3f8649414f
gRPC Accounts: Provision from pre-derived SCRAM verifiers
Adds Accounts.Provision(name, scram256, scram512, email) so an external
authority that stores verifiers rather than passwords (a website) can bulk-
backfill accounts it never had the plaintext for. It creates the account
with the given SCRAM verifiers — SCRAM and keycard login work immediately;
typed-password login stays disabled (empty password_hash) until a real
password lands via Register/SetPassword. Refuses an existing account, so a
re-run can't clobber a fully-credentialed one. scram512 may be empty
(SCRAM-SHA-512 then falls back to 256).
2026-07-14 03:33:17 +00:00

157 lines
6.1 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);
// Create-or-update an account from pre-derived SCRAM verifiers instead of a
// plaintext password. For bulk backfill from an authority (e.g. a website)
// that stores verifiers, not passwords. scram512 may be empty.
rpc Provision(ProvisionRequest) 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 ProvisionRequest { string name = 1; string scram256 = 2; string scram512 = 3; string email = 4; }
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; }
// Stats API: a flat, namespaced counter map any service contributes to
// (e.g. "nickserv.command", "botserv.kick", "channels.total"). Read-only.
service Stats {
rpc Get(StatsRequest) returns (StatsResponse);
}
message StatsRequest {}
message StatsResponse {
map<string, uint64> counters = 1;
uint64 collected_at = 2; // unix seconds when this snapshot was taken
}