diff --git a/app.vue b/app.vue index a146950..82fd93e 100644 --- a/app.vue +++ b/app.vue @@ -10,7 +10,7 @@ import { invoke } from "@tauri-apps/api/core"; import { AppStatus, type AppState } from "./types.d.ts"; import { listen } from "@tauri-apps/api/event"; import { useAppState } from "./composables/app-state.js"; -import {useRouter} from "#vue-router"; +import { useRouter } from "#vue-router"; const router = useRouter(); @@ -23,7 +23,9 @@ router.beforeEach(async () => { switch (state.value.status) { case AppStatus.NotConfigured: - router.push({ path: "/setup" }).then(() => {console.log("Pushed Setup")}); + router.push({ path: "/setup" }).then(() => { + console.log("Pushed Setup"); + }); break; case AppStatus.SignedOut: router.push("/auth"); @@ -31,6 +33,9 @@ switch (state.value.status) { case AppStatus.SignedInNeedsReauth: router.push("/auth/signedout"); break; + case AppStatus.ServerUnavailable: + router.push("/error/serverunavailable"); + break; default: router.push("/store"); } @@ -48,6 +53,6 @@ listen("auth/finished", () => { }); useHead({ - title: "Drop" -}) + title: "Drop", +}); diff --git a/components/InitiateAuthModule.vue b/components/InitiateAuthModule.vue index ba263d1..5f7158c 100644 --- a/components/InitiateAuthModule.vue +++ b/components/InitiateAuthModule.vue @@ -40,23 +40,20 @@ Sign in with your browser + -
-
-
-
-
-

- {{ error }} -

-
+
+
+
+
+
+

+ {{ error }} +

- +
diff --git a/pages/error/serverunavailable.vue b/pages/error/serverunavailable.vue new file mode 100644 index 0000000..d0a2515 --- /dev/null +++ b/pages/error/serverunavailable.vue @@ -0,0 +1,74 @@ + + + diff --git a/src-tauri/src/auth.rs b/src-tauri/src/auth.rs index c27e2e4..a8bf764 100644 --- a/src-tauri/src/auth.rs +++ b/src-tauri/src/auth.rs @@ -1,55 +1,43 @@ use std::{ env, - sync::Mutex, time::{SystemTime, UNIX_EPOCH}, + fmt::{Display, Formatter}, + sync::Mutex, + time::{SystemTime, UNIX_EPOCH}, }; use log::{info, warn}; -use openssl::{ - ec::EcKey, - hash::MessageDigest, - pkey::PKey, - sign::{Signer}, -}; +use openssl::{ec::EcKey, hash::MessageDigest, pkey::PKey, sign::Signer}; use serde::{Deserialize, Serialize}; use tauri::{AppHandle, Emitter, Manager}; -use url::Url; +use url::{ParseError, Url}; -use crate::{db::{DatabaseAuth, DatabaseImpls}, AppState, AppStatus, User, DB}; +use crate::{ + db::{DatabaseAuth, DatabaseImpls}, + AppState, AppStatus, User, DB, +}; #[derive(Serialize)] -#[serde(rename_all="camelCase")] +#[serde(rename_all = "camelCase")] struct InitiateRequestBody { name: String, platform: String, } #[derive(Serialize)] -#[serde(rename_all="camelCase")] +#[serde(rename_all = "camelCase")] struct HandshakeRequestBody { client_id: String, token: String, } #[derive(Deserialize)] -#[serde(rename_all="camelCase")] +#[serde(rename_all = "camelCase")] struct HandshakeResponse { private: String, certificate: String, id: String, } -macro_rules! unwrap_or_return { - ( $e:expr, $app:expr ) => { - match $e { - Ok(x) => x, - Err(_) => { - $app.emit("auth/failed", ()).unwrap(); - return; - } - } - }; -} - pub fn sign_nonce(private_key: String, nonce: String) -> Result { 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(); @@ -80,42 +68,76 @@ pub fn generate_authorization_header() -> String { format!("Nonce {} {} {}", certs.client_id, nonce, signature) } -pub fn fetch_user() -> Result { +#[derive(Debug)] +pub enum RemoteAccessError { + FetchError(reqwest::Error), + ParsingError(ParseError), + GenericErrror(String), +} + +impl Display for RemoteAccessError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + RemoteAccessError::FetchError(error) => write!(f, "{}", error), + RemoteAccessError::GenericErrror(error) => write!(f, "{}", error), + RemoteAccessError::ParsingError(parse_error) => { + write!(f, "{}", parse_error) + } + } + } +} + +impl From for RemoteAccessError { + fn from(err: reqwest::Error) -> Self { + RemoteAccessError::FetchError(err) + } +} +impl From for RemoteAccessError { + fn from(err: String) -> Self { + RemoteAccessError::GenericErrror(err) + } +} +impl From for RemoteAccessError { + fn from(err: ParseError) -> Self { + RemoteAccessError::ParsingError(err) + } +} + +impl std::error::Error for RemoteAccessError {} + +pub fn fetch_user() -> Result { let base_url = DB.fetch_base_url(); - let endpoint = base_url.join("/api/v1/client/user").unwrap(); + 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() - .unwrap(); + .send()?; if response.status() != 200 { - warn!("Failed to fetch user: {}", response.status()); - return Err(()); + return Err(format!("Failed to fetch user: {}", response.status()).into()); } - let user = response.json::().unwrap(); + let user = response.json::()?; Ok(user) } -pub fn recieve_handshake(app: AppHandle, path: String) { - // Tell the app we're processing - app.emit("auth/processing", ()).unwrap(); - +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; + return Err(RemoteAccessError::GenericErrror( + "Invalid number of handshake chunks".to_string().into(), + )); } let base_url = { let handle = DB.borrow_data().unwrap(); - Url::parse(handle.base_url.as_str()).unwrap() + Url::parse(handle.base_url.as_str())? }; let client_id = path_chunks.get(1).unwrap(); @@ -125,11 +147,11 @@ pub fn recieve_handshake(app: AppHandle, path: String) { token: token.to_string(), }; - let endpoint = unwrap_or_return!(base_url.join("/api/v1/client/auth/handshake"), app); + let endpoint = base_url.join("/api/v1/client/auth/handshake")?; let client = reqwest::blocking::Client::new(); - let response = unwrap_or_return!(client.post(endpoint).json(&body).send(), app); + let response = client.post(endpoint).json(&body).send()?; info!("server responded with {}", response.status()); - let response_struct = unwrap_or_return!(response.json::(), app); + let response_struct = response.json::()?; { let mut handle = DB.borrow_data_mut().unwrap(); @@ -146,43 +168,62 @@ pub fn recieve_handshake(app: AppHandle, path: String) { let app_state = app.state::>(); let mut app_state_handle = app_state.lock().unwrap(); app_state_handle.status = AppStatus::SignedIn; - app_state_handle.user = Some(fetch_user().unwrap()); + app_state_handle.user = Some(fetch_user()?); + } + + return 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 handshake_result.is_err() { + app.emit("auth/failed", ()).unwrap(); + return; } app.emit("auth/finished", ()).unwrap(); } -#[tauri::command] -pub async fn auth_initiate<'a>() -> Result<(), String> { +async fn auth_initiate_wrapper() -> Result<(), RemoteAccessError> { let base_url = { let db_lock = DB.borrow_data().unwrap(); - Url::parse(&db_lock.base_url.clone()).unwrap() + Url::parse(&db_lock.base_url.clone())? }; - let endpoint = base_url.join("/api/v1/client/auth/initiate").unwrap(); + 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::Client::new(); - let response = client - .post(endpoint.to_string()) - .json(&body) - .send() - .await - .unwrap(); + let response = client.post(endpoint.to_string()).json(&body).send().await?; if response.status() != 200 { - return Err("Failed to create redirect URL. Please try again later.".to_string()); + return Err("Failed to create redirect URL. Please try again later." + .to_string() + .into()); } - let redir_url = response.text().await.unwrap(); - let complete_redir_url = base_url.join(&redir_url).unwrap(); + let redir_url = response.text().await?; + let complete_redir_url = base_url.join(&redir_url)?; info!("opening web browser to continue authentication"); webbrowser::open(complete_redir_url.as_ref()).unwrap(); + return Ok(()); +} + +#[tauri::command] +pub async fn auth_initiate<'a>() -> Result<(), String> { + let result = auth_initiate_wrapper().await; + if result.is_err() { + return Err(result.err().unwrap().to_string()); + } + Ok(()) } @@ -193,7 +234,14 @@ pub fn setup() -> Result<(AppStatus, Option), ()> { if data.auth.is_some() { let user_result = fetch_user(); if user_result.is_err() { - return Ok((AppStatus::SignedInNeedsReauth, None)); + let error = user_result.err().unwrap(); + warn!("auth setup failed with: {}", error); + match error { + RemoteAccessError::FetchError(_) => { + return Ok((AppStatus::ServerUnavailable, None)) + } + _ => return Ok((AppStatus::SignedInNeedsReauth, None)), + } } return Ok((AppStatus::SignedIn, Some(user_result.unwrap()))); } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0aefa92..a86c7b4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,6 +4,7 @@ mod library; mod remote; mod unpacker; +use crate::db::DatabaseImpls; use auth::{auth_initiate, generate_authorization_header, recieve_handshake}; use db::{DatabaseInterface, DATA_ROOT_DIR}; use env_logger::Env; @@ -13,20 +14,22 @@ use log::info; use remote::{gen_drop_url, use_remote}; use serde::{Deserialize, Serialize}; use std::{ - collections::HashMap, sync::{LazyLock, Mutex} + collections::HashMap, + sync::{LazyLock, Mutex}, }; use tauri_plugin_deep_link::DeepLinkExt; -use crate::db::DatabaseImpls; #[derive(Clone, Copy, Serialize)] pub enum AppStatus { NotConfigured, + ServerError, SignedOut, SignedIn, SignedInNeedsReauth, + ServerUnavailable, } #[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all="camelCase")] +#[serde(rename_all = "camelCase")] pub struct User { id: String, username: String, @@ -36,7 +39,7 @@ pub struct User { } #[derive(Clone, Serialize)] -#[serde(rename_all="camelCase")] +#[serde(rename_all = "camelCase")] pub struct AppState { status: AppStatus, user: Option, @@ -63,10 +66,10 @@ fn setup() -> AppState { }; } - let auth_result = auth::setup().unwrap(); + let (app_status, user) = auth::setup().unwrap(); AppState { - status: auth_result.0, - user: auth_result.1, + status: app_status, + user: user, games: HashMap::new(), } } @@ -130,7 +133,9 @@ pub fn run() { info!("handling drop:// url"); let binding = event.urls(); let url = binding.first().unwrap(); - if url.host_str().unwrap() == "handshake" { recieve_handshake(handle.clone(), url.path().to_string()) } + if url.host_str().unwrap() == "handshake" { + recieve_handshake(handle.clone(), url.path().to_string()) + } }); Ok(()) diff --git a/types.d.ts b/types.d.ts index f6e5c1b..b63d904 100644 --- a/types.d.ts +++ b/types.d.ts @@ -22,6 +22,7 @@ export enum AppStatus { SignedOut = "SignedOut", SignedIn = "SignedIn", SignedInNeedsReauth = "SignedInNeedsReauth", + ServerUnavailable = "ServerUnavailable", } export enum GameStatus {