Some checks failed
CI / Lint (push) Has been cancelled
修正 cipher_guard_sdk 4 个关键密码学差异使其与老 Flutter 项目 (im-client-im-dev) 和 iOS
EncryptionManager 完全互操作:
1. AES: 显式 SIC/CTR 模式 + 16 zero-byte IV(原 SDK 用随机 IV + KDF 派生密钥)
2. RSA: bare RSAEngine 无 PKCS1 padding(原 SDK 用 PKCS1Encoding)
3. Session key: 32-char alphanumeric ASCII(原 SDK 用 base64 random bytes)
4. Wire format: base64(ciphertext) 无 IV 前缀
新增 EncryptionManager:
- Per-chat round-based key chain(最多 10 rounds/chat,FIFO 淘汰)
- 登录后自动 setup:RSA 密钥对生成/存储 + 公钥上传 + chat 密钥拉取解密
- API 集成:cipher/v2/key/my, key/set, chat/my
- 消息加密返回 JSON envelope {"round":N,"data":"<base64>"}
- 消息解密兼容 JSON envelope + legacy raw base64
集成到消息流:
- SendMessageUseCase: 发送前加密 content → wireContent
- WsMessageService: 收到消息后解密 content + lastMsg
- 无密钥时 fallback 到明文(对齐 iOS 行为)
注意:/app/api/cipher/v2/key/set 仍为预发布接口,仅测试阶段使用
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
189 lines
6.7 KiB
Dart
189 lines
6.7 KiB
Dart
import 'package:cipher_guard_sdk/cipher_guard_sdk.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
||
import 'package:im_app/core/foundation/device_info.dart';
|
||
import 'package:im_app/core/services/app_initializer.dart';
|
||
import 'package:im_app/core/services/encryption_manager.dart';
|
||
import 'package:im_app/app/di/network_provider.dart';
|
||
|
||
// ── 认证 ──────────────────────────────────────────────────────────────────────
|
||
|
||
/// 登录状态管理
|
||
///
|
||
/// 同时继承 [ChangeNotifier],作为 go_router [GoRouter.refreshListenable] 使用,
|
||
/// 登录 / 退出时 go_router 自动重新执行 redirect,无需手动触发。
|
||
///
|
||
/// ## 当前状态
|
||
///
|
||
/// Demo 实现,无持久化。storage_sdk 就绪后替换为:
|
||
/// - `build`:从安全存储读取 token,有则视为已登录
|
||
/// - `login` / `logout`:同步更新安全存储
|
||
class AuthNotifier extends ChangeNotifier {
|
||
bool _isLoggedIn = false;
|
||
int? _currentUid;
|
||
|
||
bool get isLoggedIn => _isLoggedIn;
|
||
|
||
/// 登录用户的 UID,登录成功后由 LoginViewModel 写入
|
||
int? get currentUid => _currentUid;
|
||
|
||
/// E2E EncryptionManager — 由外部注入(见 LoginViewModel)
|
||
EncryptionManager? _encryptionManager;
|
||
NetworksSdkApi? _api;
|
||
|
||
void setEncryptionDeps(EncryptionManager encMgr, NetworksSdkApi api) {
|
||
_encryptionManager = encMgr;
|
||
_api = api;
|
||
}
|
||
|
||
void login({required int uid}) {
|
||
_isLoggedIn = true;
|
||
_currentUid = uid;
|
||
notifyListeners();
|
||
// E2E setup: 对齐 iOS AppCoordinator.onLogin → EncryptionManager.setup()
|
||
if (_encryptionManager != null && _api != null) {
|
||
_encryptionManager!.setup(_api!);
|
||
}
|
||
}
|
||
|
||
void logout() {
|
||
_isLoggedIn = false;
|
||
_currentUid = null;
|
||
// E2E teardown: 清除所有加密密钥
|
||
_encryptionManager?.clearKeys();
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
/// 登录状态 Provider
|
||
///
|
||
/// 自动注入 EncryptionManager + API client,login() 后自动触发 E2E setup。
|
||
final authNotifierProvider = Provider<AuthNotifier>((ref) {
|
||
final auth = AuthNotifier();
|
||
auth.setEncryptionDeps(
|
||
ref.read(encryptionManagerProvider),
|
||
ref.read(networkSdkApiProvider),
|
||
);
|
||
return auth;
|
||
});
|
||
|
||
// ── E2E 加密 ────────────────────────────────────────────────────────────────
|
||
|
||
/// CipherGuardSdkApi 单例 — 对齐老项目加密引擎
|
||
final cipherSdkProvider = Provider<CipherGuardSdkApi>((ref) {
|
||
return CipherGuardSdkApi();
|
||
});
|
||
|
||
/// EncryptionManager 单例 — per-chat key chain + API integration
|
||
///
|
||
/// 登录后调用 `encMgr.setup(api)` 启动 E2E,退出时调用 `encMgr.clearKeys()`。
|
||
/// 对齐 iOS EncryptionManager + 老项目 EncryptionMgr。
|
||
final encryptionManagerProvider = Provider<EncryptionManager>((ref) {
|
||
return EncryptionManager(cipherSdk: ref.read(cipherSdkProvider));
|
||
});
|
||
|
||
// ── 主题 ──────────────────────────────────────────────────────────────────────
|
||
|
||
/// 主题模式 Notifier — 控制应用全局亮 / 暗主题
|
||
///
|
||
/// 启动时从持久化存储读取上次保存的主题模式,无则默认跟随系统。
|
||
/// 切换时先更新内存状态,再写入持久化存储。
|
||
///
|
||
/// ## storage_sdk 接入步骤
|
||
///
|
||
/// 1. 在 `build()` 解开 TODO:读取存储值作为初始模式
|
||
/// 2. 在 `setMode()` 解开 TODO:每次切换后写入存储
|
||
/// 3. 若存储接口是异步的,将 `Notifier<ThemeMode>` 改为
|
||
/// `AsyncNotifier<ThemeMode>`,`build()` 改为 `Future<ThemeMode>`
|
||
class ThemeModeNotifier extends Notifier<ThemeMode> {
|
||
@override
|
||
ThemeMode build() {
|
||
// TODO: storage_sdk 就绪后从持久化读取初始值:
|
||
// final saved = ref.read(themeStorageProvider).readThemeMode();
|
||
// return saved ?? ThemeMode.system;
|
||
return ThemeMode.system;
|
||
}
|
||
|
||
void setMode(ThemeMode mode) {
|
||
state = mode;
|
||
// TODO: storage_sdk 就绪后写入持久化:
|
||
// ref.read(themeStorageProvider).saveThemeMode(mode);
|
||
}
|
||
}
|
||
|
||
/// 主题模式 Provider
|
||
///
|
||
/// ## Setting 页切换(只需一行)
|
||
///
|
||
/// ```dart
|
||
/// ref.read(themeModeProvider.notifier).setMode(ThemeMode.system);
|
||
/// ref.read(themeModeProvider.notifier).setMode(ThemeMode.light);
|
||
/// ref.read(themeModeProvider.notifier).setMode(ThemeMode.dark);
|
||
/// ```
|
||
///
|
||
/// ## 持久化(storage_sdk TODO)
|
||
///
|
||
/// 读取和写入的 TODO 均在 [ThemeModeNotifier] 内,接入 storage_sdk 后解开即可。
|
||
final themeModeProvider = NotifierProvider<ThemeModeNotifier, ThemeMode>(
|
||
ThemeModeNotifier.new,
|
||
);
|
||
|
||
// ── 启动初始化 ────────────────────────────────────────────────────────────────
|
||
|
||
/// AppInitializer Provider
|
||
///
|
||
/// 集中声明所有启动初始化任务,app.dart 只需一行 `.run()`。
|
||
///
|
||
/// ## 任务分类规则
|
||
///
|
||
/// 问自己:「这个任务不完成,用户能正常看到首页吗?」
|
||
/// - **不能** → 放 critical(谨慎,每多一个都拖慢启动)
|
||
/// - **能** → 放 deferred(绝大多数情况)
|
||
///
|
||
/// ## 当前任务清单
|
||
///
|
||
/// | 阶段 | 任务 | 说明 |
|
||
/// |---|---|---|
|
||
/// | Critical | NetworkMonitor | 后续 HTTP、WebSocket 都依赖网络状态 |
|
||
/// | Deferred | (待扩展) | 推送注册、登录态恢复、缓存预热等 |
|
||
final appInitializerProvider = Provider<AppInitializer>((ref) {
|
||
return AppInitializer(
|
||
onLog: (message, {tag}) {
|
||
// ignore: avoid_print
|
||
print('[${tag ?? 'AppInit'}] $message');
|
||
},
|
||
critical: [
|
||
// 网络监听必须最先就绪(后续 HTTP、WebSocket 都依赖它)
|
||
InitTask(
|
||
name: 'NetworkMonitor',
|
||
task: () => ref.read(networkMonitorProvider).initialize(),
|
||
),
|
||
// 预取设备 ID / 设备名,platformHeaders 同步读取
|
||
InitTask(
|
||
name: 'DeviceInfo',
|
||
task: DeviceInfo.init,
|
||
),
|
||
],
|
||
deferred: [
|
||
// TODO: 推送注册
|
||
// InitTask(
|
||
// name: 'PushNotification',
|
||
// task: () => ref.read(pushServiceProvider).register(),
|
||
// ),
|
||
//
|
||
// TODO: 登录态恢复(从安全存储读取 token → 自动登录)
|
||
// InitTask(
|
||
// name: 'AuthRestore',
|
||
// task: () => ref.read(authRestoreUseCaseProvider).execute(),
|
||
// ),
|
||
//
|
||
// TODO: 缓存预热
|
||
// InitTask(
|
||
// name: 'CacheWarmup',
|
||
// task: () => ref.read(cacheServiceProvider).warmup(),
|
||
// ),
|
||
],
|
||
);
|
||
});
|