mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-14 00:31:33 +10:00
Implement better error system and segregate errors and commands (#23)
* chore: Progress on amend_settings command Signed-off-by: quexeky <git@quexeky.dev> * chore(errors): Progress on better error handling with segragation of files * chore: Progress on amend_settings command Signed-off-by: quexeky <git@quexeky.dev> * chore(commands): Separated commands under each subdirectory into respective commands.rs files Signed-off-by: quexeky <git@quexeky.dev> * chore(errors): Almost all errors and commands have been segregated * chore(errors): Added drop server error Signed-off-by: quexeky <git@quexeky.dev> * feat(core): Update to using nightly compiler Signed-off-by: quexeky <git@quexeky.dev> * chore(errors): More progress on error handling Signed-off-by: quexeky <git@quexeky.dev> * chore(errors): Implementing Try and FromResidual for UserValue Signed-off-by: quexeky <git@quexeky.dev> * refactor(errors): Segregated errors and commands from code, and made commands return UserValue struct Signed-off-by: quexeky <git@quexeky.dev> * fix(errors): Added missing files * chore(errors): Convert match statement to map_err * feat(settings): Implemented settings editing from UI * feat(errors): Clarified return values from retry_connect command * chore(errors): Moved autostart commands to autostart.rs * chore(process manager): Converted launch_process function for games to use game_id --------- Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
194
src-tauri/src/remote/auth.rs
Normal file
194
src-tauri/src/remote/auth.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use std::{env, sync::Mutex};
|
||||
|
||||
use chrono::Utc;
|
||||
use log::{debug, error, info, warn};
|
||||
use openssl::{ec::EcKey, hash::MessageDigest, pkey::PKey, sign::Signer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
database::db::{DatabaseAuth, DatabaseImpls},
|
||||
error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError},
|
||||
AppState, AppStatus, User, DB,
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct InitiateRequestBody {
|
||||
name: String,
|
||||
platform: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct HandshakeRequestBody {
|
||||
client_id: String,
|
||||
token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct HandshakeResponse {
|
||||
private: String,
|
||||
certificate: String,
|
||||
id: String,
|
||||
}
|
||||
|
||||
// TODO: Change return value on Err
|
||||
pub fn sign_nonce(private_key: String, nonce: String) -> Result<String, ()> {
|
||||
let client_private_key = EcKey::private_key_from_pem(private_key.as_bytes()).unwrap();
|
||||
let pkey_private_key = PKey::from_ec_key(client_private_key).unwrap();
|
||||
|
||||
let mut signer = Signer::new(MessageDigest::sha256(), &pkey_private_key).unwrap();
|
||||
signer.update(nonce.as_bytes()).unwrap();
|
||||
let signature = signer.sign_to_vec().unwrap();
|
||||
|
||||
let hex_signature = hex::encode(signature);
|
||||
|
||||
Ok(hex_signature)
|
||||
}
|
||||
|
||||
pub fn generate_authorization_header() -> String {
|
||||
let certs = {
|
||||
let db = DB.borrow_data().unwrap();
|
||||
db.auth.clone().unwrap()
|
||||
};
|
||||
|
||||
let nonce = Utc::now().timestamp_millis().to_string();
|
||||
|
||||
let signature = sign_nonce(certs.private, nonce.clone()).unwrap();
|
||||
|
||||
format!("Nonce {} {} {}", certs.client_id, nonce, signature)
|
||||
}
|
||||
|
||||
pub fn fetch_user() -> Result<User, RemoteAccessError> {
|
||||
let base_url = DB.fetch_base_url();
|
||||
|
||||
let endpoint = base_url.join("/api/v1/client/user")?;
|
||||
let header = generate_authorization_header();
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = client
|
||||
.get(endpoint.to_string())
|
||||
.header("Authorization", header)
|
||||
.send()?;
|
||||
if response.status() != 200 {
|
||||
let err: DropServerError = response.json().unwrap();
|
||||
warn!("{:?}", err);
|
||||
|
||||
if err.status_message == "Nonce expired" {
|
||||
return Err(RemoteAccessError::OutOfSync);
|
||||
}
|
||||
|
||||
return Err(RemoteAccessError::InvalidResponse(err));
|
||||
}
|
||||
|
||||
response.json::<User>().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAccessError> {
|
||||
let path_chunks: Vec<&str> = path.split("/").collect();
|
||||
if path_chunks.len() != 3 {
|
||||
app.emit("auth/failed", ()).unwrap();
|
||||
return Err(RemoteAccessError::HandshakeFailed(
|
||||
"failed to parse token".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let base_url = {
|
||||
let handle = DB.borrow_data().unwrap();
|
||||
Url::parse(handle.base_url.as_str())?
|
||||
};
|
||||
|
||||
let client_id = path_chunks.get(1).unwrap();
|
||||
let token = path_chunks.get(2).unwrap();
|
||||
let body = HandshakeRequestBody {
|
||||
client_id: client_id.to_string(),
|
||||
token: token.to_string(),
|
||||
};
|
||||
|
||||
let endpoint = base_url.join("/api/v1/client/auth/handshake")?;
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = client.post(endpoint).json(&body).send()?;
|
||||
debug!("Handshake responsded with {}", response.status().as_u16());
|
||||
let response_struct: HandshakeResponse = response.json()?;
|
||||
|
||||
{
|
||||
let mut handle = DB.borrow_data_mut().unwrap();
|
||||
handle.auth = Some(DatabaseAuth {
|
||||
private: response_struct.private,
|
||||
cert: response_struct.certificate,
|
||||
client_id: response_struct.id,
|
||||
});
|
||||
drop(handle);
|
||||
DB.save().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let app_state = app.state::<Mutex<AppState>>();
|
||||
let mut app_state_handle = app_state.lock().unwrap();
|
||||
app_state_handle.status = AppStatus::SignedIn;
|
||||
app_state_handle.user = Some(fetch_user()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recieve_handshake(app: AppHandle, path: String) {
|
||||
// Tell the app we're processing
|
||||
app.emit("auth/processing", ()).unwrap();
|
||||
|
||||
let handshake_result = recieve_handshake_logic(&app, path);
|
||||
if let Err(e) = handshake_result {
|
||||
warn!("error with authentication: {}", e);
|
||||
app.emit("auth/failed", e.to_string()).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
app.emit("auth/finished", ()).unwrap();
|
||||
}
|
||||
|
||||
pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
|
||||
let base_url = {
|
||||
let db_lock = DB.borrow_data().unwrap();
|
||||
Url::parse(&db_lock.base_url.clone())?
|
||||
};
|
||||
|
||||
let endpoint = base_url.join("/api/v1/client/auth/initiate")?;
|
||||
let body = InitiateRequestBody {
|
||||
name: "Drop Desktop Client".to_string(),
|
||||
platform: env::consts::OS.to_string(),
|
||||
};
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = client.post(endpoint.to_string()).json(&body).send()?;
|
||||
|
||||
if response.status() != 200 {
|
||||
let data: DropServerError = response.json()?;
|
||||
error!("Could not start handshake: {}", data.status_message);
|
||||
|
||||
return Err(RemoteAccessError::HandshakeFailed(data.status_message));
|
||||
}
|
||||
|
||||
let redir_url = response.text()?;
|
||||
let complete_redir_url = base_url.join(&redir_url)?;
|
||||
|
||||
debug!("opening web browser to continue authentication");
|
||||
webbrowser::open(complete_redir_url.as_ref()).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup() -> Result<(AppStatus, Option<User>), RemoteAccessError> {
|
||||
let data = DB.borrow_data().unwrap();
|
||||
let auth = data.auth.clone();
|
||||
drop(data);
|
||||
|
||||
if auth.is_some() {
|
||||
let user_result = fetch_user()?;
|
||||
return Ok((AppStatus::SignedIn, Some(user_result)));
|
||||
}
|
||||
|
||||
Ok((AppStatus::SignedOut, None))
|
||||
}
|
||||
Reference in New Issue
Block a user