新增 windows hello 登录

This commit is contained in:
2023-11-06 00:29:25 +08:00
parent e5958bb8d2
commit 1f68ad8ded
8 changed files with 230 additions and 8 deletions

View File

@ -19,12 +19,18 @@ class LoginDialog extends BaseUI<LoginDialogModel> {
children: [
const Row(),
if (model.loginStatus == 0) ...[
const Center(
Center(
child: Column(
children: [
Text("登录中..."),
SizedBox(height: 12),
ProgressRing()
const Text("登录中..."),
const SizedBox(height: 12),
const ProgressRing(),
const SizedBox(height: 24),
Text(
"* 若开启了自动填充,请留意弹出的 Windows Hello 窗口",
style: TextStyle(
fontSize: 13, color: Colors.white.withOpacity(.6)),
)
],
),
),
@ -32,8 +38,8 @@ class LoginDialog extends BaseUI<LoginDialogModel> {
Text("请输入RSI账户 [${model.nickname}] 的邮箱,以保存登录状态(输入错误会导致无法进入游戏!)"),
const SizedBox(height: 12),
TextFormBox(
// controller: model.emailCtrl,
),
// controller: model.emailCtrl,
),
const SizedBox(height: 6),
Text(
"*该操作同一账号只需执行一次,输入错误请在盒子设置中清理,切换账号请在汉化浏览器中操作。",

View File

@ -1,10 +1,13 @@
import 'dart:convert';
import 'dart:io';
import 'package:cryptography/cryptography.dart';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:hive/hive.dart';
import 'package:jwt_decode/jwt_decode.dart';
import 'package:local_auth/local_auth.dart';
import 'package:starcitizen_doctor/base/ui_model.dart';
import 'package:starcitizen_doctor/common/win32/credentials.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/ui/home/webview/webview.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -27,6 +30,8 @@ class LoginDialogModel extends BaseUIModel {
LoginDialogModel(this.installPath, this.homeUIModel);
final LocalAuthentication localAuth = LocalAuthentication();
@override
void initModel() {
_launchWebLogin();
@ -53,6 +58,34 @@ class LoginDialogModel extends BaseUIModel {
.replaceAll("\")", "");
Map<String, dynamic> payload = Jwt.parseJwt(authToken!);
nickname = payload["nickname"] ?? "";
final inputEmail = data["inputEmail"];
final inputPassword = data["inputPassword"];
if (inputEmail != null && inputEmail != "") {
final userBox = await Hive.openBox("rsi_account_data");
await userBox.put("account_email", inputEmail);
}
if (await localAuth.isDeviceSupported()) {
if (inputEmail != null &&
inputEmail != "" &&
inputPassword != null &&
inputPassword != "") {
final ok = await showConfirmDialogs(
context!,
"是否开启自动密码填充?",
const Text(
"盒子将使用 PIN 与 Windows 凭据加密保存您的密码,密码只存储在您的设备中。\n\n当下次登录需要输入密码时您只需授权PIN即可自动填充登录。"));
if (ok == true) {
if (await localAuth.authenticate(localizedReason: "输入PIN以启用加密") ==
true) {
await _savePwd(inputEmail, inputPassword);
}
}
}
}
final buildInfoFile = File("$installPath\\build_manifest.id");
if (await buildInfoFile.exists()) {
final buildInfo =
@ -150,10 +183,11 @@ class LoginDialogModel extends BaseUIModel {
// }
Future<void> _readyForLaunch() async {
final userBox = await Hive.openBox("rsi_account_data");
loginStatus = 2;
notifyListeners();
final launchData = {
"username": "",
"username": userBox.get("account_email", defaultValue: ""),
"token": webToken,
"auth_token": authToken,
"star_network": {
@ -196,4 +230,33 @@ class LoginDialogModel extends BaseUIModel {
}
return "LIVE";
}
_savePwd(String inputEmail, String inputPassword) async {
final algorithm = AesGcm.with256bits();
final secretKey = await algorithm.newSecretKey();
final nonce = algorithm.newNonce();
final secretBox = await algorithm.encrypt(utf8.encode(inputPassword),
secretKey: secretKey, nonce: nonce);
await algorithm.decrypt(
SecretBox(secretBox.cipherText,
nonce: secretBox.nonce, mac: secretBox.mac),
secretKey: secretKey);
final pwdEncrypted = base64.encode(secretBox.cipherText);
final userBox = await Hive.openBox("rsi_account_data");
await userBox.put("account_email", inputEmail);
await userBox.put("account_pwd_encrypted", pwdEncrypted);
await userBox.put("nonce", base64.encode(secretBox.nonce));
await userBox.put("mac", base64.encode(secretBox.mac.bytes));
final secretKeyStr = base64.encode((await secretKey.extractBytes()));
Win32Credentials.write(
credentialName: "SCToolbox_RSI_Account_secret",
userName: inputEmail,
password: secretKeyStr);
}
}

View File

@ -3,11 +3,14 @@
import 'dart:async';
import 'dart:convert';
import 'package:cryptography/cryptography.dart';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import 'package:local_auth/local_auth.dart';
import 'package:starcitizen_doctor/common/conf.dart';
import 'package:starcitizen_doctor/common/win32/credentials.dart';
import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart';
import '../../../api/api.dart';
@ -148,6 +151,7 @@ class WebViewModel {
final message = json.decode(messageString);
if (message["action"] == "webview_rsi_login_show_window") {
webview.setWebviewWindowVisibility(true);
_checkAutoLogin(webview);
} else if (message["action"] == "webview_rsi_login_success") {
_loginModeSuccess = true;
loginCallback?.call(message, true);
@ -247,4 +251,34 @@ class WebViewModel {
}
_isClosed = true;
}
Future<void> _checkAutoLogin(Webview webview) async {
final LocalAuthentication localAuth = LocalAuthentication();
if (!await localAuth.isDeviceSupported()) return;
final userBox = await Hive.openBox("rsi_account_data");
final email = await userBox.get("account_email", defaultValue: "");
final pwdE = await userBox.get("account_pwd_encrypted", defaultValue: "");
final nonceStr = await userBox.get("nonce", defaultValue: "");
final macStr = await userBox.get("mac", defaultValue: "");
if (email == "") return;
if (pwdE != "" && nonceStr != "" && macStr != "") {
// decrypt
if (await localAuth.authenticate(localizedReason: "请输入设备PIN以自动登录RSI账户") !=
true) return;
final kv = Win32Credentials.read("SCToolbox_RSI_Account_secret");
if (kv == null || kv.key != email) return;
final algorithm = AesGcm.with256bits();
final r = await algorithm.decrypt(
SecretBox(base64.decode(pwdE),
nonce: base64.decode(nonceStr), mac: Mac(base64.decode(macStr))),
secretKey: SecretKey(base64.decode(kv.value)));
final decryptedPwd = utf8.decode(r);
webview.evaluateJavaScript("RSIAutoLogin(\"$email\",\"$decryptedPwd\")");
} else {
webview.evaluateJavaScript("RSIAutoLogin(\"$email\",\"\")");
}
}
}