9a638545dd
Until now any member could change a group's roster or name. Groups now have admins, and every change to membership, roles or the name is an operation signed by an admin's identity key (XEdDSA). Recipients verify the signature against the admin's key from their own contacts, never a key carried in the same message; the relay plays no part. - Operations form a hash chain with an epoch; gaps or forks are held back and resolved by a signed state from an admin. - Groups created by older versions migrate with the creator as admin; if the creator has left, the smallest fingerprint takes over deterministically on every device. - Mixed groups with older clients keep working in a compatibility mode until every member advertises admin support; that capability can only be added, never withdrawn. - The last admin cannot step down or be removed. The verification rules live in a network-free policy module with its own tests.
221 lines
6.0 KiB
Dart
221 lines
6.0 KiB
Dart
/// Typované chyby, které se zobrazují uživateli.
|
|
///
|
|
/// Service vrstva nemá přístup k lokalizaci (žádný BuildContext), proto
|
|
/// nesmí formulovat texty pro uživatele — vyhazuje tyhle typy a UI je
|
|
/// překládá přes `describeError` (core/l10n/error_messages.dart).
|
|
/// `toString()` je anglický technický popis pro logy, ne pro UI.
|
|
library;
|
|
|
|
sealed class SepotUserError implements Exception {
|
|
const SepotUserError();
|
|
}
|
|
|
|
/// Trust root relaye nesedí s pinovaným — server nelze ověřit.
|
|
class TrustRootChangedError extends SepotUserError {
|
|
const TrustRootChangedError();
|
|
|
|
@override
|
|
String toString() => 'relay trust root changed — refusing to communicate';
|
|
}
|
|
|
|
/// Relay nedostupný (typicky fetch sender certificate).
|
|
class RelayUnavailableError extends SepotUserError {
|
|
const RelayUnavailableError();
|
|
|
|
@override
|
|
String toString() => 'relay unavailable';
|
|
}
|
|
|
|
/// Vlastní registrace na relay ještě nedoběhla — nemáme sender adresu.
|
|
/// Přechodný stav po čerstvé instalaci; registrace běží na pozadí.
|
|
class NotYetRegisteredError extends SepotUserError {
|
|
const NotYetRegisteredError();
|
|
|
|
@override
|
|
String toString() => 'not registered on relay yet';
|
|
}
|
|
|
|
/// Odkaz/QR kontaktu je neúplný (chybí routing aid+da) — vygenerovaný
|
|
/// dřív, než měla protistrana dokončenou registraci na relay.
|
|
class ContactLinkIncompleteError extends SepotUserError {
|
|
const ContactLinkIncompleteError();
|
|
|
|
@override
|
|
String toString() => 'contact link is missing routing (incomplete)';
|
|
}
|
|
|
|
/// Příloha přesahuje serverový limit.
|
|
class AttachmentTooLargeError extends SepotUserError {
|
|
const AttachmentTooLargeError(this.maxBytes);
|
|
|
|
final int maxBytes;
|
|
|
|
int get maxMb => (maxBytes / (1024 * 1024)).round();
|
|
|
|
@override
|
|
String toString() => 'attachment too large (max $maxBytes bytes)';
|
|
}
|
|
|
|
/// Příloha pro přeposlání už v telefonu není (smazaná, mizící, nestažená).
|
|
class AttachmentUnavailableError extends SepotUserError {
|
|
const AttachmentUnavailableError();
|
|
|
|
@override
|
|
String toString() => 'attachment not available locally';
|
|
}
|
|
|
|
/// Změnu skupiny smí udělat jen správce.
|
|
class NotGroupAdminError extends SepotUserError {
|
|
const NotGroupAdminError();
|
|
|
|
@override
|
|
String toString() => 'only a group admin can do this';
|
|
}
|
|
|
|
/// Poslední správce se nesmí odebrat ani zbavit role.
|
|
class LastGroupAdminError extends SepotUserError {
|
|
const LastGroupAdminError();
|
|
|
|
@override
|
|
String toString() => 'the last group admin cannot step down';
|
|
}
|
|
|
|
/// Skupina bez jediného dalšího člena nedává smysl.
|
|
class GroupNeedsMemberError extends SepotUserError {
|
|
const GroupNeedsMemberError();
|
|
|
|
@override
|
|
String toString() => 'group needs at least one member';
|
|
}
|
|
|
|
/// Překročen strop počtu členů skupiny.
|
|
class GroupTooLargeError extends SepotUserError {
|
|
const GroupTooLargeError(this.maxMembers);
|
|
|
|
final int maxMembers;
|
|
|
|
@override
|
|
String toString() => 'group exceeds $maxMembers members';
|
|
}
|
|
|
|
/// Člen skupiny nemá doručovací adresu (starý odkaz bez routingu).
|
|
class MemberNoDeliveryAddressError extends SepotUserError {
|
|
const MemberNoDeliveryAddressError(this.displayName);
|
|
|
|
final String displayName;
|
|
|
|
@override
|
|
String toString() => 'member "$displayName" has no delivery address';
|
|
}
|
|
|
|
/// Kontakt nemá doručovací adresu (starý odkaz bez routingu).
|
|
class ContactNoDeliveryAddressError extends SepotUserError {
|
|
const ContactNoDeliveryAddressError();
|
|
|
|
@override
|
|
String toString() => 'contact has no delivery address';
|
|
}
|
|
|
|
/// Identity klíč z relaye nesedí s uloženým kontaktem — možný MITM.
|
|
class IdentityKeyMismatchError extends SepotUserError {
|
|
const IdentityKeyMismatchError();
|
|
|
|
@override
|
|
String toString() => 'identity key mismatch — possible MITM, message not sent';
|
|
}
|
|
|
|
// === Obnova účtu (mnemonic) ===
|
|
|
|
/// Fráze má špatný počet slov.
|
|
class MnemonicWordCountError extends SepotUserError {
|
|
const MnemonicWordCountError(this.count, this.expected);
|
|
|
|
final int count;
|
|
final int expected;
|
|
|
|
@override
|
|
String toString() => 'mnemonic has $count words, expected $expected';
|
|
}
|
|
|
|
/// Fráze neprošla validací BIP39 (neznámé slovo, checksum…).
|
|
class MnemonicInvalidError extends SepotUserError {
|
|
const MnemonicInvalidError(this.detail);
|
|
|
|
final String detail;
|
|
|
|
@override
|
|
String toString() => 'invalid mnemonic: $detail';
|
|
}
|
|
|
|
// === Zálohy historie (backup_service) ===
|
|
|
|
/// Soubor zálohy je poškozený (nejde přečíst/dešifrovat strukturu).
|
|
class BackupCorruptedError extends SepotUserError {
|
|
const BackupCorruptedError();
|
|
|
|
@override
|
|
String toString() => 'corrupted backup file';
|
|
}
|
|
|
|
/// Soubor není záloha Šepotu.
|
|
class BackupUnknownFormatError extends SepotUserError {
|
|
const BackupUnknownFormatError();
|
|
|
|
@override
|
|
String toString() => 'unknown backup format';
|
|
}
|
|
|
|
/// Záloha je šifrovaná klíčem jiné identity.
|
|
class BackupWrongIdentityError extends SepotUserError {
|
|
const BackupWrongIdentityError();
|
|
|
|
@override
|
|
String toString() => 'backup belongs to a different identity';
|
|
}
|
|
|
|
// === Kontakty ===
|
|
|
|
/// Kontakt s tímto fingerprintem už existuje.
|
|
class ContactAlreadyExistsError extends SepotUserError {
|
|
const ContactAlreadyExistsError();
|
|
|
|
@override
|
|
String toString() => 'contact already exists';
|
|
}
|
|
|
|
// === Nahlášení zneužití (report_service) ===
|
|
|
|
/// Hlášení bez popisu nemá co řešit.
|
|
class ReportEmptyDescriptionError extends SepotUserError {
|
|
const ReportEmptyDescriptionError();
|
|
|
|
@override
|
|
String toString() => 'report has empty description';
|
|
}
|
|
|
|
/// Server hlášení je nedosažitelný.
|
|
class ReportConnectionError extends SepotUserError {
|
|
const ReportConnectionError();
|
|
|
|
@override
|
|
String toString() => 'report endpoint unreachable';
|
|
}
|
|
|
|
/// Rate limit hlášení (429).
|
|
class ReportRateLimitedError extends SepotUserError {
|
|
const ReportRateLimitedError();
|
|
|
|
@override
|
|
String toString() => 'report rate limited';
|
|
}
|
|
|
|
/// Server hlášení vrátil chybový kód.
|
|
class ReportServerError extends SepotUserError {
|
|
const ReportServerError(this.statusCode);
|
|
|
|
final int statusCode;
|
|
|
|
@override
|
|
String toString() => 'report failed with status $statusCode';
|
|
}
|