mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-12 15:52:43 +10:00
* feat: different local path in dev #73 * feat: better error output for downloads * feat: collections in library view * feat: improve download manager reliability * feat: new download UI, more stable downloads * fix: clippy * fix: only show admin link if user is admin * feat: check for libs before building
225 lines
6.5 KiB
Rust
225 lines
6.5 KiB
Rust
use std::{collections::HashMap, env, sync::Mutex};
|
|
|
|
use chrono::Utc;
|
|
use droplet_rs::ssl::sign_nonce;
|
|
use gethostname::gethostname;
|
|
use log::{debug, error, warn};
|
|
use serde::{Deserialize, Serialize};
|
|
use tauri::{AppHandle, Emitter, Manager};
|
|
use url::Url;
|
|
|
|
use crate::{
|
|
database::{
|
|
db::{borrow_db_checked, borrow_db_mut_checked},
|
|
models::data::DatabaseAuth,
|
|
}, error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError}, remote::{cache::clear_cached_object, requests::make_authenticated_get, utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC}}, AppState, AppStatus, User
|
|
};
|
|
|
|
use super::{
|
|
cache::{cache_object, get_cached_object},
|
|
requests::generate_url,
|
|
};
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct CapabilityConfiguration {}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct InitiateRequestBody {
|
|
name: String,
|
|
platform: String,
|
|
capabilities: HashMap<String, CapabilityConfiguration>,
|
|
mode: 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,
|
|
}
|
|
|
|
pub fn generate_authorization_header() -> String {
|
|
let certs = {
|
|
let db = borrow_db_checked();
|
|
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 async fn fetch_user() -> Result<User, RemoteAccessError> {
|
|
let response = make_authenticated_get(generate_url(&["/api/v1/client/user"], &[])?).await?;
|
|
if response.status() != 200 {
|
|
let err: DropServerError = response.json().await?;
|
|
warn!("{err:?}");
|
|
|
|
if err.status_message == "Nonce expired" {
|
|
return Err(RemoteAccessError::OutOfSync);
|
|
}
|
|
|
|
return Err(RemoteAccessError::InvalidResponse(err));
|
|
}
|
|
|
|
response
|
|
.json::<User>()
|
|
.await
|
|
.map_err(std::convert::Into::into)
|
|
}
|
|
|
|
async 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 = borrow_db_checked();
|
|
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 = DROP_CLIENT_ASYNC.clone();
|
|
let response = client.post(endpoint).json(&body).send().await?;
|
|
debug!("handshake responsded with {}", response.status().as_u16());
|
|
if !response.status().is_success() {
|
|
return Err(RemoteAccessError::InvalidResponse(response.json().await?));
|
|
}
|
|
let response_struct: HandshakeResponse = response.json().await?;
|
|
|
|
{
|
|
let mut handle = borrow_db_mut_checked();
|
|
handle.auth = Some(DatabaseAuth {
|
|
private: response_struct.private,
|
|
cert: response_struct.certificate,
|
|
client_id: response_struct.id,
|
|
web_token: None, // gets created later
|
|
});
|
|
}
|
|
|
|
let web_token = {
|
|
let header = generate_authorization_header();
|
|
let token = client
|
|
.post(base_url.join("/api/v1/client/user/webtoken").unwrap())
|
|
.header("Authorization", header)
|
|
.send()
|
|
.await
|
|
.unwrap();
|
|
|
|
token.text().await.unwrap()
|
|
};
|
|
|
|
let mut handle = borrow_db_mut_checked();
|
|
let mut_auth = handle.auth.as_mut().unwrap();
|
|
mut_auth.web_token = Some(web_token);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async 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).await;
|
|
if let Err(e) = handshake_result {
|
|
warn!("error with authentication: {e}");
|
|
app.emit("auth/failed", e.to_string()).unwrap();
|
|
return;
|
|
}
|
|
|
|
let app_state = app.state::<Mutex<AppState>>();
|
|
|
|
let (app_status, user) = setup().await;
|
|
|
|
let mut state_lock = app_state.lock().unwrap();
|
|
|
|
state_lock.status = app_status;
|
|
state_lock.user = user;
|
|
|
|
let _ = clear_cached_object("collections");
|
|
let _ = clear_cached_object("library");
|
|
|
|
drop(state_lock);
|
|
|
|
app.emit("auth/finished", ()).unwrap();
|
|
}
|
|
|
|
pub fn auth_initiate_logic(mode: String) -> Result<String, RemoteAccessError> {
|
|
let base_url = {
|
|
let db_lock = borrow_db_checked();
|
|
Url::parse(&db_lock.base_url.clone())?
|
|
};
|
|
|
|
let hostname = gethostname();
|
|
|
|
let endpoint = base_url.join("/api/v1/client/auth/initiate")?;
|
|
let body = InitiateRequestBody {
|
|
name: format!("{} (Desktop)", hostname.into_string().unwrap()),
|
|
platform: env::consts::OS.to_string(),
|
|
capabilities: HashMap::from([
|
|
("peerAPI".to_owned(), CapabilityConfiguration {}),
|
|
("cloudSaves".to_owned(), CapabilityConfiguration {}),
|
|
]),
|
|
mode,
|
|
};
|
|
|
|
let client = DROP_CLIENT_SYNC.clone();
|
|
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 response = response.text()?;
|
|
|
|
Ok(response)
|
|
}
|
|
|
|
pub async fn setup() -> (AppStatus, Option<User>) {
|
|
let auth = {
|
|
let data = borrow_db_checked();
|
|
data.auth.clone()
|
|
};
|
|
|
|
if auth.is_some() {
|
|
let user_result = match fetch_user().await {
|
|
Ok(data) => data,
|
|
Err(RemoteAccessError::FetchError(_)) => {
|
|
let user = get_cached_object::<User>("user").unwrap();
|
|
return (AppStatus::Offline, Some(user));
|
|
}
|
|
Err(_) => return (AppStatus::SignedInNeedsReauth, None),
|
|
};
|
|
cache_object("user", &user_result).unwrap();
|
|
return (AppStatus::SignedIn, Some(user_result));
|
|
}
|
|
|
|
(AppStatus::SignedOut, None)
|
|
}
|