feat(storage): A.2 — persist TS3 identity across app restarts
A fresh `Identity::create()` was generated on every connect, which meant the server saw a different client UID each time. Long-lived features (bookmarks, server-side bans, group membership) depend on a stable UID — restoring that now via a minimal directory-backed identity file. * `chanora_storage::IdentityFileStore` reads / writes a single `identity.tskey` file under a caller-supplied directory. On Unix the file is created with `O_CREAT | O_TRUNC | mode 0600`; on non-Unix targets the platform sandbox does the access control. Writes are atomic (temp file + `fsync` + `rename`) so a crash mid-write cannot leave a half-written identity on disk. Empty files are treated as "no identity" rather than as an error. * `chanora_protocol::ProtocolClient::generate_identity()` exposes the `counterVbase64key` serialisation used by tsclientlib's `Identity::new_from_str`, so the core layer can mint an identity and store it before dialling. * `chanora_core::ChanoraSession::init_storage(dir)` wires the store. `connect()` then resolves the identity in this order: (1) `cfg.identity` if explicitly supplied; (2) persisted value if any; (3) generate-and-persist a fresh one. * `chanora_bridge::api::init_storage(dir: String)` is the Flutter-facing entrypoint; the matching Dart side resolves `path_provider`'s `getApplicationSupportDirectory()` and calls it once on app start. * `BridgeError` now maps `CoreError::Storage`. Beta caveat (RISK-PoC-002 / SS-RISK-FALLBACK): the identity is not encrypted at rest. The v0.4 storage rework lands proper Secret Service + Android Keystore + iOS Keychain backends. Documented under `IdentityFileStore`'s doc comment. Live-verified on Moto G Stylus 5G: first connect generated + persisted the identity (visible in the redacted diagnostic export as "generated + persisted fresh identity"); disconnect + reconnect in the same session logged "reusing persisted identity" and dialled with the same UID.
This commit is contained in:
@@ -13,6 +13,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'l10n/generated/app_localizations.dart';
|
||||
import 'src/rust/api.dart' as rust;
|
||||
@@ -21,14 +22,27 @@ import 'src/rust/frb_generated.dart';
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await RustLib.init();
|
||||
// Push the OS-reported connectivity state into the core supervisor.
|
||||
// The supervisor uses this to short-circuit reconnect backoff when
|
||||
// the network comes back, and to pre-charge the loss watchdog when
|
||||
// the OS already knows we're offline (A.6.1).
|
||||
// A.2 — wire identity persistence to the platform's app-private
|
||||
// directory so the same TS3 UID is presented on every restart.
|
||||
// Beta: stored as a plain 0600 file (RISK-PoC-002).
|
||||
unawaited(_wireStorage());
|
||||
// A.6.1 — push the OS-reported connectivity state into the core
|
||||
// supervisor so reconnects redial promptly when the network
|
||||
// returns.
|
||||
unawaited(_wireConnectivity());
|
||||
runApp(const ChanoraApp());
|
||||
}
|
||||
|
||||
Future<void> _wireStorage() async {
|
||||
try {
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
await rust.initStorage(dir: dir.path);
|
||||
} catch (_) {
|
||||
// Storage is best-effort — on failure the app still works but
|
||||
// each session gets a fresh ephemeral identity.
|
||||
}
|
||||
}
|
||||
|
||||
/// Map `connectivity_plus`' list-of-results to our coarse tri-state.
|
||||
/// We consider the device "Online" if any of the reported transports
|
||||
/// is non-`none`. This is intentionally permissive — the supervisor's
|
||||
|
||||
Reference in New Issue
Block a user