mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-11 14:27:39 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint) (push) Waiting to run
Tests / test (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Swap google_sign_in for the FOSS AccountManager plugin (google_account_token-v0.1.0); drop the local platform gate. Also fix an unmatched coverage:ignore pair that broke --coverage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWY1k4muZfdVJfb7YGJ
108 lines
4.7 KiB
Dart
108 lines
4.7 KiB
Dart
/// Wiring for the Firebase backend, during and after the GitHub cutover.
|
|
///
|
|
/// Split by what is safe to publish, because this repo is public:
|
|
///
|
|
/// * [kProject] holds the Web API key and database URL. Both are public
|
|
/// identifiers that already ship inside the APK; the security rules, not
|
|
/// their secrecy, are what protect the data.
|
|
/// * The account email and password are entered once per device and kept in
|
|
/// the OS keystore, next to the GitHub token this app already stores there.
|
|
///
|
|
/// Nothing here reads `~/.config/crdt-sync/` — that is the desktop/Python
|
|
/// half. On Android there is no such file.
|
|
library;
|
|
|
|
import 'dart:developer';
|
|
import 'package:crdt_sync/crdt_sync.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
|
|
/// The shared `kuhy-syncs` project.
|
|
///
|
|
/// `databaseUrl` is the **regional** host. The plain `*.firebaseio.com` form
|
|
/// answers 404 with a `correctUrl` body rather than an obvious error, which
|
|
/// reads like an auth failure and wastes a debugging session.
|
|
const kProject = FirebaseProject(
|
|
apiKey: 'AIzaSyCF_sA3xCMehAYXK8eND-rAygb9NXXW_8E',
|
|
databaseUrl:
|
|
'https://kuhy-syncs-default-rtdb.europe-west1.firebasedatabase.app',
|
|
);
|
|
|
|
/// Same options the GitHub token uses: off Android's deprecated
|
|
/// `encryptedSharedPreferences` path, and libsecret on Linux.
|
|
const _secure = FlutterSecureStorage();
|
|
|
|
// Everything below reaches the OS keystore through a platform channel, which
|
|
// `flutter test` has no binding for -- the same reason `main.dart` and
|
|
// `openRepository()` are excluded. The logic these wrap (parsing, the
|
|
// public/private split, sign-in) lives in `crdt_sync` and is covered there at
|
|
// 100%; what is left here is the two-line adapter.
|
|
// coverage:ignore-start
|
|
|
|
/// The keystore-backed home for the Firebase refresh token.
|
|
SecureCredentialStore credentialStore() => SecureCredentialStore(
|
|
read: (key) => _secure.read(key: key),
|
|
write: (key, value) => _secure.write(key: key, value: value),
|
|
delete: (key) => _secure.delete(key: key),
|
|
);
|
|
|
|
/// Reads the per-device account, or null when sync has not been set up.
|
|
Future<FirebaseAccount?> loadAccount() async {
|
|
try {
|
|
final stored = FirebaseAccount.tryParse(
|
|
await _secure.read(key: kFirebaseAccountKey),
|
|
);
|
|
if (stored != null) return stored;
|
|
// Disconnect must stick: without this the next launch would silently
|
|
// re-adopt the account and the button would look broken.
|
|
if (await _secure.read(key: kSyncAccountOptOutKey) != null) return null;
|
|
// Desktop only in practice: on Android the wrapper does not exist, the
|
|
// request fails, and this returns null exactly as before.
|
|
final provisioned = await accountFromWrapper(Uri.base);
|
|
if (provisioned != null) await saveAccount(provisioned);
|
|
return provisioned;
|
|
} on Object catch (error, stackTrace) {
|
|
// Still "not configured" rather than crashing the settings screen -- but
|
|
// never silent: this hid *why* provisioning failed, which is
|
|
// indistinguishable from "no account set" until you say so.
|
|
log(
|
|
'loadAccount failed; treating this device as not configured',
|
|
level: 1000,
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Reads the account from the keystore only, with no wrapper fallback.
|
|
///
|
|
/// [loadAccount] falls back to the desktop wrapper's `/sync-account` route
|
|
/// when the keystore is empty, which on Android resolves to `file:///` and
|
|
/// throws `No host specified in URI`. Callers reading back an account they
|
|
/// just wrote -- where a fallback would be wrong anyway -- use this instead.
|
|
/// Verified on the phone: without it, sign-in succeeded and then the settings
|
|
/// screen hung on "Signing in..." forever.
|
|
Future<FirebaseAccount?> storedAccount() async =>
|
|
FirebaseAccount.tryParse(await _secure.read(key: kFirebaseAccountKey));
|
|
|
|
/// Stores the per-device account. Keystore only — never prefs, never source.
|
|
Future<void> saveAccount(FirebaseAccount account) =>
|
|
_secure.write(key: kFirebaseAccountKey, value: account.toJsonString());
|
|
|
|
/// Forgets the account and any cached session.
|
|
Future<void> clearAccount() async {
|
|
await _secure.delete(key: kFirebaseAccountKey);
|
|
// Suppress wrapper re-provisioning; see loadAccount().
|
|
await _secure.write(key: kSyncAccountOptOutKey, value: 'true');
|
|
await credentialStore().clear();
|
|
}
|
|
|
|
/// Drops just the stored account marker, leaving the opt-out flag alone.
|
|
///
|
|
/// Deliberately narrower than [clearAccount]: a marker with no session behind
|
|
/// it cannot sign a request, but setting the opt-out flag as well would stop
|
|
/// the desktop wrapper re-provisioning after the next sign-in.
|
|
Future<void> forgetAccountMarker() =>
|
|
_secure.delete(key: kFirebaseAccountKey);
|
|
// coverage:ignore-end
|