oidc: init debug

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2022-10-22 22:19:14 +08:00
parent 87e53501e3
commit a84ee7a6ec
10 changed files with 322 additions and 247 deletions

View File

@ -66,8 +66,7 @@ errno = "0.2.8"
rdev = { git = "https://github.com/asur4s/rdev" } rdev = { git = "https://github.com/asur4s/rdev" }
url = { version = "2.1", features = ["serde"] } url = { version = "2.1", features = ["serde"] }
[target.'cfg(not(target_os = "linux"))'.dependencies] reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features=false }
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
cpal = "0.13.5" cpal = "0.13.5"

View File

@ -2,15 +2,12 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
import '../../common.dart'; import '../../common.dart';
import '../widgets/button.dart';
class _IconOP extends StatelessWidget { class _IconOP extends StatelessWidget {
final String icon; final String icon;
@ -92,10 +89,12 @@ class ConfigOP {
class WidgetOP extends StatefulWidget { class WidgetOP extends StatefulWidget {
final ConfigOP config; final ConfigOP config;
final RxString curOP; final RxString curOP;
final Function(String) cbLogin;
const WidgetOP({ const WidgetOP({
Key? key, Key? key,
required this.config, required this.config,
required this.curOP, required this.curOP,
required this.cbLogin,
}) : super(key: key); }) : super(key: key);
@override @override
@ -107,9 +106,8 @@ class WidgetOP extends StatefulWidget {
class _WidgetOPState extends State<WidgetOP> { class _WidgetOPState extends State<WidgetOP> {
Timer? _updateTimer; Timer? _updateTimer;
String _stateMsg = ''; String _stateMsg = '';
String _stateFailedMsg = ''; String _FailedMsg = '';
String _url = ''; String _url = '';
String _username = '';
@override @override
void initState() { void initState() {
@ -138,17 +136,28 @@ class _WidgetOPState extends State<WidgetOP> {
return; return;
} }
final String stateMsg = resultMap['state_msg']; final String stateMsg = resultMap['state_msg'];
final String failedMsg = resultMap['failed_msg']; String failedMsg = resultMap['failed_msg'];
// to-do: test null url final String? url = resultMap['url'];
final String url = resultMap['url']; final authBody = resultMap['auth_body'];
if (_stateMsg != stateMsg) { if (_stateMsg != stateMsg || _FailedMsg != failedMsg) {
if (_url.isEmpty && url.isNotEmpty) { if (_url.isEmpty && url != null && url.isNotEmpty) {
launchUrl(Uri.parse(url)); launchUrl(Uri.parse(url));
_url = url; _url = url;
} }
if (authBody != null) {
_updateTimer?.cancel();
final String username = authBody['user']['name'];
widget.curOP.value = '';
widget.cbLogin(username);
}
setState(() { setState(() {
_stateMsg = stateMsg; _stateMsg = stateMsg;
_stateFailedMsg = failedMsg; _FailedMsg = failedMsg;
if (failedMsg.isNotEmpty) {
widget.curOP.value = '';
_updateTimer?.cancel();
}
}); });
} }
}); });
@ -156,16 +165,13 @@ class _WidgetOPState extends State<WidgetOP> {
_resetState() { _resetState() {
_stateMsg = ''; _stateMsg = '';
_stateFailedMsg = ''; _FailedMsg = '';
_url = ''; _url = '';
_username = '';
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ConstrainedBox( return Column(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
children: [ children: [
ButtonOP( ButtonOP(
op: widget.config.op, op: widget.config.op,
@ -173,18 +179,38 @@ class _WidgetOPState extends State<WidgetOP> {
iconWidth: widget.config.iconWidth, iconWidth: widget.config.iconWidth,
primaryColor: str2color(widget.config.op, 0x7f), primaryColor: str2color(widget.config.op, 0x7f),
height: 40, height: 40,
onTap: () { onTap: () async {
_resetState();
widget.curOP.value = widget.config.op; widget.curOP.value = widget.config.op;
bind.mainAccountAuth(op: widget.config.op); await bind.mainAccountAuth(op: widget.config.op);
_beginQueryState(); _beginQueryState();
}, },
), ),
Obx(() => Offstage( Obx(() {
offstage: widget.curOP.value != widget.config.op, if (widget.curOP.isNotEmpty &&
child: Text( widget.curOP.value != widget.config.op) {
_FailedMsg = '';
}
return Offstage(
offstage:
_FailedMsg.isEmpty && widget.curOP.value != widget.config.op,
child: Row(
children: [
Text(
_stateMsg, _stateMsg,
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
))), ),
SizedBox(width: 8),
Text(
_FailedMsg,
style: TextStyle(
fontSize: 14,
color: Colors.red,
),
),
],
));
}),
Obx( Obx(
() => Offstage( () => Offstage(
offstage: widget.curOP.value != widget.config.op, offstage: widget.curOP.value != widget.config.op,
@ -203,6 +229,7 @@ class _WidgetOPState extends State<WidgetOP> {
widget.curOP.value = ''; widget.curOP.value = '';
_updateTimer?.cancel(); _updateTimer?.cancel();
_resetState(); _resetState();
bind.mainAccountAuthCancel();
}, },
child: Text( child: Text(
translate('Cancel'), translate('Cancel'),
@ -213,17 +240,20 @@ class _WidgetOPState extends State<WidgetOP> {
), ),
), ),
], ],
)); );
} }
} }
class LoginWidgetOP extends StatelessWidget { class LoginWidgetOP extends StatelessWidget {
final List<ConfigOP> ops; final List<ConfigOP> ops;
final RxString curOP = ''.obs; final RxString curOP;
final Function(String) cbLogin;
LoginWidgetOP({ LoginWidgetOP({
Key? key, Key? key,
required this.ops, required this.ops,
required this.curOP,
required this.cbLogin,
}) : super(key: key); }) : super(key: key);
@override @override
@ -233,6 +263,7 @@ class LoginWidgetOP extends StatelessWidget {
WidgetOP( WidgetOP(
config: op, config: op,
curOP: curOP, curOP: curOP,
cbLogin: cbLogin,
), ),
const Divider() const Divider()
]) ])
@ -256,6 +287,7 @@ class LoginWidgetUserPass extends StatelessWidget {
final String usernameMsg; final String usernameMsg;
final String passMsg; final String passMsg;
final bool isInProgress; final bool isInProgress;
final RxString curOP;
final Function(String, String) onLogin; final Function(String, String) onLogin;
const LoginWidgetUserPass({ const LoginWidgetUserPass({
Key? key, Key? key,
@ -264,6 +296,7 @@ class LoginWidgetUserPass extends StatelessWidget {
required this.usernameMsg, required this.usernameMsg,
required this.passMsg, required this.passMsg,
required this.isInProgress, required this.isInProgress,
required this.curOP,
required this.onLogin, required this.onLogin,
}) : super(key: key); }) : super(key: key);
@ -271,9 +304,7 @@ class LoginWidgetUserPass extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var userController = TextEditingController(text: username); var userController = TextEditingController(text: username);
var pwdController = TextEditingController(text: pass); var pwdController = TextEditingController(text: pass);
return ConstrainedBox( return Column(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox( const SizedBox(
@ -308,8 +339,8 @@ class LoginWidgetUserPass extends StatelessWidget {
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text('${translate("Password")}:') child:
.marginOnly(bottom: 16.0)), Text('${translate("Password")}:').marginOnly(bottom: 16.0)),
const SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
@ -337,20 +368,26 @@ class LoginWidgetUserPass extends StatelessWidget {
child: Container( child: Container(
height: 50, height: 50,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: ElevatedButton( child: Obx(() => ElevatedButton(
style: curOP.value.isEmpty || curOP.value == 'rustdesk'
? null
: ElevatedButton.styleFrom(
primary: Colors.grey,
),
child: const Text( child: const Text(
'Login', 'Login',
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
), ),
onPressed: () { onPressed: curOP.value.isEmpty || curOP.value == 'rustdesk'
? () {
onLogin(userController.text, pwdController.text); onLogin(userController.text, pwdController.text);
}, }
), : null,
)),
), ),
), ),
]), ]),
], ],
),
); );
} }
} }
@ -364,6 +401,7 @@ Future<bool> loginDialog() async {
var passMsg = ''; var passMsg = '';
var isInProgress = false; var isInProgress = false;
var completer = Completer<bool>(); var completer = Completer<bool>();
final RxString curOP = ''.obs;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
cancel() { cancel() {
@ -379,6 +417,7 @@ Future<bool> loginDialog() async {
isInProgress = true; isInProgress = true;
}); });
cancel() { cancel() {
curOP.value = '';
if (isInProgress) { if (isInProgress) {
setState(() { setState(() {
isInProgress = false; isInProgress = false;
@ -386,17 +425,16 @@ Future<bool> loginDialog() async {
} }
} }
curOP.value = 'rustdesk';
username = username0; username = username0;
pass = pass0; pass = pass0;
if (username.isEmpty) { if (username.isEmpty) {
usernameMsg = translate('Username missed'); usernameMsg = translate('Username missed');
debugPrint('REMOVE ME ====================== username empty');
cancel(); cancel();
return; return;
} }
if (pass.isEmpty) { if (pass.isEmpty) {
passMsg = translate('Password missed'); passMsg = translate('Password missed');
debugPrint('REMOVE ME ====================== password empty');
cancel(); cancel();
return; return;
} }
@ -404,7 +442,6 @@ Future<bool> loginDialog() async {
final resp = await gFFI.userModel.login(username, pass); final resp = await gFFI.userModel.login(username, pass);
if (resp.containsKey('error')) { if (resp.containsKey('error')) {
passMsg = resp['error']; passMsg = resp['error'];
debugPrint('REMOVE ME ====================== password error');
cancel(); cancel();
return; return;
} }
@ -414,8 +451,6 @@ Future<bool> loginDialog() async {
completer.complete(true); completer.complete(true);
} catch (err) { } catch (err) {
debugPrint(err.toString()); debugPrint(err.toString());
debugPrint(
'REMOVE ME ====================== login error ${err.toString()}');
cancel(); cancel();
return; return;
} }
@ -438,6 +473,7 @@ Future<bool> loginDialog() async {
usernameMsg: usernameMsg, usernameMsg: usernameMsg,
passMsg: passMsg, passMsg: passMsg,
isInProgress: isInProgress, isInProgress: isInProgress,
curOP: curOP,
onLogin: onLogin, onLogin: onLogin,
), ),
const SizedBox( const SizedBox(
@ -451,11 +487,19 @@ Future<bool> loginDialog() async {
const SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
LoginWidgetOP(ops: [ LoginWidgetOP(
ops: [
ConfigOP(op: 'Github', iconWidth: 24), ConfigOP(op: 'Github', iconWidth: 24),
ConfigOP(op: 'Google', iconWidth: 24), ConfigOP(op: 'Google', iconWidth: 24),
ConfigOP(op: 'Okta', iconWidth: 46), ConfigOP(op: 'Okta', iconWidth: 46),
]), ],
curOP: curOP,
cbLogin: (String username) {
gFFI.userModel.userName.value = username;
completer.complete(true);
close();
},
),
], ],
), ),
), ),

View File

@ -291,12 +291,12 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
return SettingsList( return SettingsList(
sections: [ sections: [
SettingsSection( SettingsSection(
title: Text(translate("Account")), title: Text(translate('Account')),
tiles: [ tiles: [
SettingsTile.navigation( SettingsTile.navigation(
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
? translate("Login") ? translate('Login')
: '${translate("Logout")} (${gFFI.userModel.userName.value})')), : '${translate('Logout')} (${gFFI.userModel.userName.value})')),
leading: Icon(Icons.person), leading: Icon(Icons.person),
onPressed: (context) { onPressed: (context) {
if (gFFI.userModel.userName.value.isEmpty) { if (gFFI.userModel.userName.value.isEmpty) {

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -9,7 +10,7 @@ import 'model.dart';
import 'platform_model.dart'; import 'platform_model.dart';
class UserModel { class UserModel {
var userName = "".obs; var userName = ''.obs;
WeakReference<FFI> parent; WeakReference<FFI> parent;
UserModel(this.parent) { UserModel(this.parent) {
@ -18,7 +19,7 @@ class UserModel {
void refreshCurrentUser() async { void refreshCurrentUser() async {
await getUserName(); await getUserName();
final token = await bind.mainGetLocalOption(key: "access_token"); final token = await bind.mainGetLocalOption(key: 'access_token');
if (token == '') return; if (token == '') return;
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
final body = { final body = {
@ -28,8 +29,8 @@ class UserModel {
try { try {
final response = await http.post(Uri.parse('$url/api/currentUser'), final response = await http.post(Uri.parse('$url/api/currentUser'),
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
"Authorization": "Bearer $token" 'Authorization': 'Bearer $token'
}, },
body: json.encode(body)); body: json.encode(body));
final status = response.statusCode; final status = response.statusCode;
@ -44,9 +45,9 @@ class UserModel {
} }
void resetToken() async { void resetToken() async {
await bind.mainSetLocalOption(key: "access_token", value: ""); await bind.mainSetLocalOption(key: 'access_token', value: '');
await bind.mainSetLocalOption(key: "user_info", value: ""); await bind.mainSetLocalOption(key: 'user_info', value: '');
userName.value = ""; userName.value = '';
} }
Future<String> _parseResp(String body) async { Future<String> _parseResp(String body) async {
@ -57,13 +58,13 @@ class UserModel {
} }
final token = data['access_token']; final token = data['access_token'];
if (token != null) { if (token != null) {
await bind.mainSetLocalOption(key: "access_token", value: token); await bind.mainSetLocalOption(key: 'access_token', value: token);
} }
final info = data['user']; final info = data['user'];
if (info != null) { if (info != null) {
final value = json.encode(info); final value = json.encode(info);
await bind.mainSetOption(key: "user_info", value: value); await bind.mainSetOption(key: 'user_info', value: value);
userName.value = info["name"]; userName.value = info['name'];
} }
return ''; return '';
} }
@ -74,7 +75,7 @@ class UserModel {
} }
final userInfo = await bind.mainGetLocalOption(key: 'user_info'); final userInfo = await bind.mainGetLocalOption(key: 'user_info');
if (userInfo.trim().isEmpty) { if (userInfo.trim().isEmpty) {
return ""; return '';
} }
final m = jsonDecode(userInfo); final m = jsonDecode(userInfo);
if (m == null) { if (m == null) {
@ -88,10 +89,10 @@ class UserModel {
Future<void> logOut() async { Future<void> logOut() async {
final tag = gFFI.dialogManager.showLoading(translate('Waiting')); final tag = gFFI.dialogManager.showLoading(translate('Waiting'));
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
final _ = await http.post(Uri.parse("$url/api/logout"), final _ = await http.post(Uri.parse('$url/api/logout'),
body: { body: {
"id": await bind.mainGetMyId(), 'id': await bind.mainGetMyId(),
"uuid": await bind.mainGetUuid(), 'uuid': await bind.mainGetUuid(),
}, },
headers: await getHttpHeaders()); headers: await getHttpHeaders());
await Future.wait([ await Future.wait([
@ -100,30 +101,30 @@ class UserModel {
bind.mainSetLocalOption(key: 'selected-tags', value: ''), bind.mainSetLocalOption(key: 'selected-tags', value: ''),
]); ]);
parent.target?.abModel.clear(); parent.target?.abModel.clear();
userName.value = ""; userName.value = '';
gFFI.dialogManager.dismissByTag(tag); gFFI.dialogManager.dismissByTag(tag);
} }
Future<Map<String, dynamic>> login(String userName, String pass) async { Future<Map<String, dynamic>> login(String userName, String pass) async {
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
try { try {
final resp = await http.post(Uri.parse("$url/api/login"), final resp = await http.post(Uri.parse('$url/api/login'),
headers: {"Content-Type": "application/json"}, headers: {'Content-Type': 'application/json'},
body: jsonEncode({ body: jsonEncode({
"username": userName, 'username': userName,
"password": pass, 'password': pass,
"id": await bind.mainGetMyId(), 'id': await bind.mainGetMyId(),
"uuid": await bind.mainGetUuid() 'uuid': await bind.mainGetUuid()
})); }));
final body = jsonDecode(resp.body); final body = jsonDecode(resp.body);
bind.mainSetLocalOption( bind.mainSetLocalOption(
key: "access_token", value: body['access_token'] ?? ""); key: 'access_token', value: body['access_token'] ?? '');
bind.mainSetLocalOption( bind.mainSetLocalOption(
key: "user_info", value: jsonEncode(body['user'])); key: 'user_info', value: jsonEncode(body['user']));
this.userName.value = body['user']?['name'] ?? ""; this.userName.value = body['user']?['name'] ?? '';
return body; return body;
} catch (err) { } catch (err) {
return {"error": "$err"}; return {'error': '$err'};
} }
} }
} }

View File

@ -1085,7 +1085,13 @@ pub fn install_install_path() -> SyncReturn<String> {
} }
pub fn main_account_auth(op: String) { pub fn main_account_auth(op: String) {
account_auth(op); let id = get_id();
let uuid = get_uuid();
account_auth(op, id, uuid);
}
pub fn main_account_auth_cancel() {
account_auth_cancel()
} }
pub fn main_account_auth_result() -> String { pub fn main_account_auth_result() -> String {

View File

@ -2,13 +2,14 @@ use hbb_common::{
anyhow::{self, bail}, anyhow::{self, bail},
tokio, ResultType, tokio, ResultType,
}; };
use reqwest::Response; use reqwest::blocking::Response;
use serde::de::DeserializeOwned;
use serde_derive::Deserialize; use serde_derive::Deserialize;
use serde_json::{Map, Value}; use serde_json::{Map, Value};
use serde::de::DeserializeOwned;
pub mod account; pub mod account;
#[derive(Debug)]
pub enum HbbHttpResponse<T> { pub enum HbbHttpResponse<T> {
ErrorFormat, ErrorFormat,
Error(String), Error(String),
@ -16,16 +17,11 @@ pub enum HbbHttpResponse<T> {
Data(T), Data(T),
} }
#[tokio::main(flavor = "current_thread")]
async fn resp_to_serde_map(resp: Response) -> reqwest::Result<Map<String, Value>> {
resp.json().await
}
impl<T: DeserializeOwned> TryFrom<Response> for HbbHttpResponse<T> { impl<T: DeserializeOwned> TryFrom<Response> for HbbHttpResponse<T> {
type Error = reqwest::Error; type Error = reqwest::Error;
fn try_from(resp: Response) -> Result<Self, <Self as TryFrom<Response>>::Error> { fn try_from(resp: Response) -> Result<Self, <Self as TryFrom<Response>>::Error> {
let map = resp_to_serde_map(resp)?; let map = resp.json::<Map<String, Value>>()?;
if let Some(error) = map.get("error") { if let Some(error) = map.get("error") {
if let Some(err) = error.as_str() { if let Some(err) = error.as_str() {
Ok(Self::Error(err.to_owned())) Ok(Self::Error(err.to_owned()))

View File

@ -1,9 +1,13 @@
use super::HbbHttpResponse; use super::HbbHttpResponse;
use hbb_common::{config::Config, log, sleep, tokio, tokio::sync::RwLock, ResultType}; use hbb_common::{
config::{Config, LocalConfig},
log, sleep, tokio, ResultType,
};
use reqwest::blocking::Client;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::Arc, sync::{Arc, RwLock},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use url::Url; use url::Url;
@ -15,12 +19,12 @@ lazy_static::lazy_static! {
} }
const QUERY_INTERVAL_SECS: f32 = 1.0; const QUERY_INTERVAL_SECS: f32 = 1.0;
const QUERY_TIMEOUT_SECS: u64 = 60; const QUERY_TIMEOUT_SECS: u64 = 60 * 3;
const REQUESTING_ACCOUNT_AUTH: &str = "Requesting account auth"; const REQUESTING_ACCOUNT_AUTH: &str = "Requesting account auth";
const WAITING_ACCOUNT_AUTH: &str = "Waiting account auth"; const WAITING_ACCOUNT_AUTH: &str = "Waiting account auth";
const LOGIN_ACCOUNT_AUTH: &str = "Login account auth"; const LOGIN_ACCOUNT_AUTH: &str = "Login account auth";
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone, Debug)]
pub struct OidcAuthUrl { pub struct OidcAuthUrl {
code: String, code: String,
url: Url, url: Url,
@ -45,7 +49,7 @@ pub struct AuthBody {
} }
pub struct OidcSession { pub struct OidcSession {
client: reqwest::Client, client: Client,
state_msg: &'static str, state_msg: &'static str,
failed_msg: String, failed_msg: String,
code_url: Option<OidcAuthUrl>, code_url: Option<OidcAuthUrl>,
@ -66,7 +70,7 @@ pub struct AuthResult {
impl OidcSession { impl OidcSession {
fn new() -> Self { fn new() -> Self {
Self { Self {
client: reqwest::Client::new(), client: Client::new(),
state_msg: REQUESTING_ACCOUNT_AUTH, state_msg: REQUESTING_ACCOUNT_AUTH,
failed_msg: "".to_owned(), failed_msg: "".to_owned(),
code_url: None, code_url: None,
@ -77,30 +81,28 @@ impl OidcSession {
} }
} }
async fn auth(op: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<OidcAuthUrl>> { fn auth(op: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<OidcAuthUrl>> {
Ok(OIDC_SESSION Ok(OIDC_SESSION
.read() .read()
.await .unwrap()
.client .client
.post(format!("{}/api/oidc/auth", *API_SERVER)) .post(format!("{}/api/oidc/auth", *API_SERVER))
.json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)])) .json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)]))
.send() .send()?
.await?
.try_into()?) .try_into()?)
} }
async fn query(code: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<AuthBody>> { fn query(code: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<AuthBody>> {
let url = reqwest::Url::parse_with_params( let url = reqwest::Url::parse_with_params(
&format!("{}/api/oidc/auth-query", *API_SERVER), &format!("{}/api/oidc/auth-query", *API_SERVER),
&[("code", code), ("id", id), ("uuid", uuid)], &[("code", code), ("id", id), ("uuid", uuid)],
)?; )?;
Ok(OIDC_SESSION Ok(OIDC_SESSION
.read() .read()
.await .unwrap()
.client .client
.get(url) .get(url)
.send() .send()?
.await?
.try_into()?) .try_into()?)
} }
@ -113,36 +115,42 @@ impl OidcSession {
self.auth_body = None; self.auth_body = None;
} }
async fn before_task(&mut self) { fn before_task(&mut self) {
self.reset(); self.reset();
self.running = true; self.running = true;
} }
async fn after_task(&mut self) { fn after_task(&mut self) {
self.running = false; self.running = false;
} }
async fn auth_task(op: String, id: String, uuid: String) { fn sleep(secs: f32) {
let code_url = match Self::auth(&op, &id, &uuid).await { std::thread::sleep(std::time::Duration::from_secs_f32(secs));
}
fn auth_task(op: String, id: String, uuid: String) {
let auth_request_res = Self::auth(&op, &id, &uuid);
log::info!("Request oidc auth result: {:?}", &auth_request_res);
let code_url = match auth_request_res {
Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url, Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url,
Ok(HbbHttpResponse::<_>::Error(err)) => { Ok(HbbHttpResponse::<_>::Error(err)) => {
OIDC_SESSION OIDC_SESSION
.write() .write()
.await .unwrap()
.set_state(REQUESTING_ACCOUNT_AUTH, err); .set_state(REQUESTING_ACCOUNT_AUTH, err);
return; return;
} }
Ok(_) => { Ok(_) => {
OIDC_SESSION OIDC_SESSION
.write() .write()
.await .unwrap()
.set_state(REQUESTING_ACCOUNT_AUTH, "Invalid auth response".to_owned()); .set_state(REQUESTING_ACCOUNT_AUTH, "Invalid auth response".to_owned());
return; return;
} }
Err(err) => { Err(err) => {
OIDC_SESSION OIDC_SESSION
.write() .write()
.await .unwrap()
.set_state(REQUESTING_ACCOUNT_AUTH, err.to_string()); .set_state(REQUESTING_ACCOUNT_AUTH, err.to_string());
return; return;
} }
@ -150,22 +158,29 @@ impl OidcSession {
OIDC_SESSION OIDC_SESSION
.write() .write()
.await .unwrap()
.set_state(WAITING_ACCOUNT_AUTH, "".to_owned()); .set_state(WAITING_ACCOUNT_AUTH, "".to_owned());
OIDC_SESSION.write().await.code_url = Some(code_url.clone()); OIDC_SESSION.write().unwrap().code_url = Some(code_url.clone());
let begin = Instant::now(); let begin = Instant::now();
let query_timeout = OIDC_SESSION.read().await.query_timeout; let query_timeout = OIDC_SESSION.read().unwrap().query_timeout;
while OIDC_SESSION.read().await.keep_querying && begin.elapsed() < query_timeout { while OIDC_SESSION.read().unwrap().keep_querying && begin.elapsed() < query_timeout {
match Self::query(&code_url.code, &id, &uuid).await { match Self::query(&code_url.code, &id, &uuid) {
Ok(HbbHttpResponse::<_>::Data(auth_body)) => { Ok(HbbHttpResponse::<_>::Data(auth_body)) => {
LocalConfig::set_option(
"access_token".to_owned(),
auth_body.access_token.clone(),
);
LocalConfig::set_option(
"user_info".to_owned(),
serde_json::to_string(&auth_body.user).unwrap_or_default(),
);
OIDC_SESSION OIDC_SESSION
.write() .write()
.await .unwrap()
.set_state(LOGIN_ACCOUNT_AUTH, "".to_owned()); .set_state(LOGIN_ACCOUNT_AUTH, "".to_owned());
OIDC_SESSION.write().await.auth_body = Some(auth_body); OIDC_SESSION.write().unwrap().auth_body = Some(auth_body);
return; return;
// to-do, set access-token
} }
Ok(HbbHttpResponse::<_>::Error(err)) => { Ok(HbbHttpResponse::<_>::Error(err)) => {
if err.contains("No authed oidc is found") { if err.contains("No authed oidc is found") {
@ -173,7 +188,7 @@ impl OidcSession {
} else { } else {
OIDC_SESSION OIDC_SESSION
.write() .write()
.await .unwrap()
.set_state(WAITING_ACCOUNT_AUTH, err); .set_state(WAITING_ACCOUNT_AUTH, err);
return; return;
} }
@ -186,13 +201,13 @@ impl OidcSession {
// ignore // ignore
} }
} }
sleep(QUERY_INTERVAL_SECS).await; Self::sleep(QUERY_INTERVAL_SECS);
} }
if begin.elapsed() >= query_timeout { if begin.elapsed() >= query_timeout {
OIDC_SESSION OIDC_SESSION
.write() .write()
.await .unwrap()
.set_state(WAITING_ACCOUNT_AUTH, "timeout".to_owned()); .set_state(WAITING_ACCOUNT_AUTH, "timeout".to_owned());
} }
@ -204,20 +219,20 @@ impl OidcSession {
self.failed_msg = failed_msg; self.failed_msg = failed_msg;
} }
pub async fn account_auth(op: String, id: String, uuid: String) { fn wait_stop_querying() {
if OIDC_SESSION.read().await.running {
OIDC_SESSION.write().await.keep_querying = false;
}
let wait_secs = 0.3; let wait_secs = 0.3;
sleep(wait_secs).await; while OIDC_SESSION.read().unwrap().running {
while OIDC_SESSION.read().await.running { Self::sleep(wait_secs);
sleep(wait_secs).await; }
} }
tokio::spawn(async move { pub fn account_auth(op: String, id: String, uuid: String) {
OIDC_SESSION.write().await.before_task().await; Self::auth_cancel();
Self::auth_task(op, id, uuid).await; Self::wait_stop_querying();
OIDC_SESSION.write().await.after_task().await; OIDC_SESSION.write().unwrap().before_task();
std::thread::spawn(|| {
Self::auth_task(op, id, uuid);
OIDC_SESSION.write().unwrap().after_task();
}); });
} }
@ -230,7 +245,11 @@ impl OidcSession {
} }
} }
pub async fn get_result() -> AuthResult { pub fn auth_cancel() {
OIDC_SESSION.read().await.get_result_() OIDC_SESSION.write().unwrap().keep_querying = false;
}
pub fn get_result() -> AuthResult {
OIDC_SESSION.read().unwrap().get_result_()
} }
} }

View File

@ -32,8 +32,8 @@ fn main() {
if !common::global_init() { if !common::global_init() {
return; return;
} }
use hbb_common::log;
use clap::App; use clap::App;
use hbb_common::log;
let args = format!( let args = format!(
"-p, --port-forward=[PORT-FORWARD-OPTIONS] 'Format: remote-id:local-port:remote-port[:remote-host]' "-p, --port-forward=[PORT-FORWARD-OPTIONS] 'Format: remote-id:local-port:remote-port[:remote-host]'
-k, --key=[KEY] '' -k, --key=[KEY] ''
@ -45,7 +45,7 @@ fn main() {
.about("RustDesk command line tool") .about("RustDesk command line tool")
.args_from_usage(&args) .args_from_usage(&args)
.get_matches(); .get_matches();
use hbb_common::{env_logger::*, config::LocalConfig}; use hbb_common::{config::LocalConfig, env_logger::*};
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
if let Some(p) = matches.value_of("port-forward") { if let Some(p) = matches.value_of("port-forward") {
let options: Vec<String> = p.split(":").map(|x| x.to_owned()).collect(); let options: Vec<String> = p.split(":").map(|x| x.to_owned()).collect();
@ -73,7 +73,14 @@ fn main() {
} }
let key = matches.value_of("key").unwrap_or("").to_owned(); let key = matches.value_of("key").unwrap_or("").to_owned();
let token = LocalConfig::get_option("access_token"); let token = LocalConfig::get_option("access_token");
cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key, token); cli::start_one_port_forward(
options[0].clone(),
port,
remote_host,
remote_port,
key,
token,
);
} }
common::global_clean(); common::global_clean();
} }

View File

@ -1,4 +1,5 @@
use crate::ipc::Data; use crate::ipc::Data;
use bytes::Bytes;
pub use connection::*; pub use connection::*;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
@ -20,7 +21,6 @@ use std::{
sync::{Arc, Mutex, RwLock, Weak}, sync::{Arc, Mutex, RwLock, Weak},
time::Duration, time::Duration,
}; };
use bytes::Bytes;
pub mod audio_service; pub mod audio_service;
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -140,7 +140,8 @@ pub async fn create_tcp_connection(
.write_to_bytes() .write_to_bytes()
.unwrap_or_default(), .unwrap_or_default(),
&sk, &sk,
).into(), )
.into(),
..Default::default() ..Default::default()
}); });
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??; timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;

View File

@ -842,14 +842,16 @@ pub(crate) fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc
tx tx
} }
#[tokio::main(flavor = "current_thread")] pub fn account_auth(op: String, id: String, uuid: String) {
pub async fn account_auth(op: String) { account::OidcSession::account_auth(op, id, uuid);
account::OidcSession::account_auth(op, get_id(), get_uuid()).await;
} }
#[tokio::main(flavor = "current_thread")] pub fn account_auth_cancel() {
pub async fn account_auth_result() -> String { account::OidcSession::auth_cancel();
serde_json::to_string(&account::OidcSession::get_result().await).unwrap_or_default() }
pub fn account_auth_result() -> String {
serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default()
} }
// notice: avoiding create ipc connecton repeatly, // notice: avoiding create ipc connecton repeatly,