Multi-user: email sign-in, invite/open registration, admin calls
- kv-proto: email on setup, AccountInfo.email/is_admin, ServerInfo.registration, admin DTOs. - kv-sync: prelogin by email or account id (setup QR / Emergency Kit), register with email, set_email, /v1/admin/* client calls. - kv-ffi: register_account(code, email), login(.., email), account_details, set_account_email, admin_create_invite. kv-wasm: the same plus the full admin API for the web vault. - Live test: two users on one server stay isolated; a new device signs in by email. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -112,8 +112,22 @@ pub struct ServerInfo {
|
||||
pub version: String,
|
||||
pub api: u32,
|
||||
pub single_tenant: bool,
|
||||
/// No account exists yet: the first one is created with the server's setup code and becomes its admin.
|
||||
pub setup_open: bool,
|
||||
pub min_client: String,
|
||||
/// How new accounts join once the server has an admin (docs/03 "Accounts"). Absent on older servers.
|
||||
#[serde(default)]
|
||||
pub registration: Option<Registration>,
|
||||
}
|
||||
|
||||
/// Who may create an account on a server that already has its admin.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Registration {
|
||||
/// Only with an invite code from the admin.
|
||||
Invite,
|
||||
/// Anyone who can reach the server (optionally limited to some email domains), or with an invite.
|
||||
Open,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -129,7 +143,11 @@ pub struct DeviceEnroll {
|
||||
/// `POST /v1/setup/start`: OPAQUE registration, first message. Needs the setup token.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SetupStart {
|
||||
/// The server's setup code (first account), an invite code, or empty with open sign-up.
|
||||
#[serde(default)]
|
||||
pub setup_token: String,
|
||||
#[serde(default)]
|
||||
pub email: Option<String>,
|
||||
pub account_id: Uuid,
|
||||
/// Serialized opaque-ke `RegistrationRequest`.
|
||||
#[serde(with = "b64")]
|
||||
@@ -150,7 +168,12 @@ pub type SetupFinish = SetupRequest;
|
||||
/// Body of `POST /v1/setup/finish` (alias [`SetupFinish`]).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SetupRequest {
|
||||
/// The server's setup code (first account), an invite code, or empty with open sign-up.
|
||||
#[serde(default)]
|
||||
pub setup_token: String,
|
||||
/// The address this account signs in with. Required on a server that already has an account.
|
||||
#[serde(default)]
|
||||
pub email: Option<String>,
|
||||
pub account_id: Uuid,
|
||||
pub kdf: KdfParams,
|
||||
/// Serialized opaque-ke `RegistrationUpload`.
|
||||
@@ -235,6 +258,82 @@ pub struct AccountInfo {
|
||||
#[serde(with = "b64")]
|
||||
pub sig_pk: Vec<u8>,
|
||||
pub wrapped_keyset: Envelope,
|
||||
#[serde(default)]
|
||||
pub email: Option<String>,
|
||||
#[serde(default)]
|
||||
pub is_admin: bool,
|
||||
}
|
||||
|
||||
/// `PUT /v1/account/email`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AccountEmail {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
/// `GET /v1/admin/users`: what an admin may know about an account. Never any ciphertext or key material.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AdminUser {
|
||||
pub account_id: Uuid,
|
||||
pub email: Option<String>,
|
||||
pub is_admin: bool,
|
||||
pub disabled: bool,
|
||||
/// RFC 3339.
|
||||
pub created_at: String,
|
||||
/// Unix seconds of the latest session activity, if any.
|
||||
pub last_seen: Option<i64>,
|
||||
pub devices: u32,
|
||||
pub items: u32,
|
||||
/// Bytes of encrypted vault data stored for the account.
|
||||
pub storage_bytes: u64,
|
||||
}
|
||||
|
||||
/// `PATCH /v1/admin/users/{id}`.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct AdminUserUpdate {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub disabled: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub is_admin: Option<bool>,
|
||||
}
|
||||
|
||||
/// `POST /v1/admin/invites`.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct InviteCreate {
|
||||
/// Only this address can use the invite.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub email: Option<String>,
|
||||
/// Days until it expires (1 to 30, default 7).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub days: Option<u32>,
|
||||
}
|
||||
|
||||
/// Reply to `POST /v1/admin/invites`. `code` is shown once; the server keeps only its hash.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InviteCreated {
|
||||
pub invite_id: Uuid,
|
||||
pub code: String,
|
||||
/// Unix seconds.
|
||||
pub expires_at: i64,
|
||||
}
|
||||
|
||||
/// `GET /v1/admin/invites`.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct InviteInfo {
|
||||
pub invite_id: Uuid,
|
||||
pub email: Option<String>,
|
||||
pub created_at: i64,
|
||||
pub expires_at: i64,
|
||||
pub used: bool,
|
||||
pub revoked: bool,
|
||||
}
|
||||
|
||||
/// `GET|PUT /v1/admin/settings`.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AdminSettings {
|
||||
pub registration: Registration,
|
||||
/// With open sign-up, only these email domains may register (empty: any).
|
||||
#[serde(default)]
|
||||
pub allowed_domains: Vec<String>,
|
||||
}
|
||||
|
||||
/// `POST /v1/account/credentials/start`: OPAQUE re-registration for a password change.
|
||||
|
||||
Reference in New Issue
Block a user