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 @@
+
+
+
+
+
+
+ Server is unavailable
+
+
+ We were unable to contact your Drop instance. See if you can open it
+ in your web browser, or contact your server admin for help.
+
+
+
+ Connect to different instance →
+
+
+
+
+
+
+

+
+
+
+
+
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 {