Skip to content

Commit

Permalink
Add 2FA Login Option
Browse files Browse the repository at this point in the history
Add UI and logic for 2FA.
Write error log if program panics.
Omit URL for GET request errors so the JWT token is not displayed.
Update screenshot.
  • Loading branch information
CMahaff committed Jul 14, 2023
1 parent 45a2e55 commit 8691ebc
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -15,3 +15,4 @@ Cargo.lock

# Generated when running application
profile*.json
error.log
Binary file modified LASIM.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions src/lemmy/api.rs
Expand Up @@ -27,12 +27,12 @@ impl API {
}
}

pub async fn login(&self, username: &String, password: &String) -> Result<String, Error> {
pub async fn login(&self, username: &String, password: &String, two_factor_token: Option<String>) -> Result<String, Error> {
let url = self.instance.join("/api/v3/user/login").unwrap();
let params = person::Login {
username_or_email: Sensitive::new(username.clone()),
password: Sensitive::new(password.clone()),
..Default::default() // TODO: Add totp_2fa_token for instances with 2-factor
totp_2fa_token: two_factor_token,
};

let response: Response = self.client
Expand Down Expand Up @@ -72,11 +72,11 @@ impl API {
let json_result = response.json::<site::GetSiteResponse>().await;
match json_result {
Ok(json) => return Ok(json),
Err(e) => return Err(e),
Err(e) => return Err(e.without_url()),
}
},
Err(e) => {
return Err(e);
return Err(e.without_url());
}
}
}
Expand All @@ -102,11 +102,11 @@ impl API {
let json_result = response.json::<community::GetCommunityResponse>().await;
match json_result {
Ok(json) => return Ok(json),
Err(e) => return Err(e),
Err(e) => return Err(e.without_url()),
}
},
Err(e) => {
return Err(e);
return Err(e.without_url());
}
}
}
Expand Down Expand Up @@ -194,11 +194,11 @@ impl API {
let json_result = response.json::<person::GetPersonDetailsResponse>().await;
match json_result {
Ok(json) => return Ok(json),
Err(e) => return Err(e),
Err(e) => return Err(e.without_url()),
}
},
Err(e) => {
return Err(e);
return Err(e.without_url());
}
}
}
Expand Down
60 changes: 58 additions & 2 deletions src/main.rs
Expand Up @@ -19,12 +19,39 @@ slint::include_modules!();

// TODO: In the future, if needed, support versioning of this file. For now, hard-code.
const PROFILE_FILENAME: &str = "profile_v1.json";
const PANIC_LOG: &str = "error.log";

struct ProcessingInstruction {
instruction_type: SharedString,
instance: SharedString,
username: SharedString,
password: SharedString,
two_factor_token: SharedString,
}

fn write_panic_info(info: &String) {
let path = Path::new(PANIC_LOG);
let mut file = match File::create(&path) {
Ok(file) => file,
Err(_) => return
};

file.write_all(info.as_bytes()).ok();
}

fn evaluate_two_factor_token(token: &String) -> Result<Option<String>, &str> {
if token.is_empty() {
return Ok(None);
}

if token.chars().count() != 6 {
return Err("2FA Token should be 6 characters")
}

match token.parse::<u32>() {
Ok(_) => Ok(Some(token.clone())),
Err(_) => Err("2FA Token should be a number"),
}
}

fn write_profile(profile_local: &profile::ProfileConfiguration, mut logger: impl FnMut(String)) {
Expand Down Expand Up @@ -54,6 +81,13 @@ async fn process_download(processing_instruction: ProcessingInstruction, mut log
let mut instance = processing_instruction.instance.to_string();
let username = processing_instruction.username.to_string();
let password = processing_instruction.password.to_string();
let two_factor_token = match evaluate_two_factor_token(&processing_instruction.two_factor_token.to_string()) {
Ok(token) => token,
Err(e) => {
logger(format!("ERROR: Invalid 2FA Token - {}", e));
return;
},
};

if !instance.starts_with("http") {
instance.insert_str(0, "https://");
Expand All @@ -69,7 +103,7 @@ async fn process_download(processing_instruction: ProcessingInstruction, mut log

// Login
logger(format!("Logging in as {}", username));
let jwt_token_future = api.login(&username, &password);
let jwt_token_future = api.login(&username, &password, two_factor_token);
let jwt_token_result = block_on(jwt_token_future);
if jwt_token_result.is_err() {
logger(format!("ERROR: Failed Login - {}", jwt_token_result.unwrap_err()));
Expand Down Expand Up @@ -129,6 +163,13 @@ async fn process_upload(processing_instruction: ProcessingInstruction, mut logge
let mut instance = processing_instruction.instance.to_string();
let username = processing_instruction.username.to_string();
let password = processing_instruction.password.to_string();
let two_factor_token = match evaluate_two_factor_token(&processing_instruction.two_factor_token.to_string()) {
Ok(token) => token,
Err(e) => {
logger(format!("ERROR: Invalid 2FA Token - {}", e));
return;
},
};

if !instance.starts_with("http") {
instance.insert_str(0, "https://");
Expand All @@ -144,7 +185,7 @@ async fn process_upload(processing_instruction: ProcessingInstruction, mut logge

// Login
logger(format!("Logging in as {}", username));
let jwt_token_future = api.login(&username, &password);
let jwt_token_future = api.login(&username, &password, two_factor_token);
let jwt_token_result = block_on(jwt_token_future);
if jwt_token_result.is_err() {
logger(format!("ERROR: Failed Login - {}", jwt_token_result.unwrap_err()));
Expand Down Expand Up @@ -268,6 +309,18 @@ async fn process_upload(processing_instruction: ProcessingInstruction, mut logge
}

fn main() {
// Setup some kind of logging for if we crash
let panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
write_panic_info(&format!("Unexpected Error Occurred: {s:?}"));
} else {
write_panic_info(&"Unknown Error Occurred!".to_string());
}
panic_hook(panic_info);
std::process::exit(1);
}));

// Setup processing thread communication
let (instruct_tx, instruct_rx): (Sender<ProcessingInstruction>, Receiver<ProcessingInstruction>) = mpsc::channel();
let instruct_tx_copy = instruct_tx.clone();
Expand Down Expand Up @@ -334,6 +387,7 @@ fn main() {
instance: app_weak_clone.unwrap().get_download_instance_url(),
username: app_weak_clone.unwrap().get_download_username_input(),
password: app_weak_clone.unwrap().get_download_password_input(),
two_factor_token: app_weak_clone.unwrap().get_download_two_factor_input(),
};

instruct_tx.send(download_instruction).unwrap();
Expand All @@ -346,6 +400,7 @@ fn main() {
instance: app_weak_clone.unwrap().get_upload_instance_url(),
username: app_weak_clone.unwrap().get_upload_username_input(),
password: app_weak_clone.unwrap().get_upload_password_input(),
two_factor_token: app_weak_clone.unwrap().get_upload_two_factor_input(),
};

instruct_tx.send(upload_instruction).unwrap();
Expand All @@ -362,6 +417,7 @@ fn main() {
instance: "".into(),
username: "".into(),
password: "".into(),
two_factor_token: "".into(),
}).unwrap();
main_thread.join().unwrap();
}
4 changes: 3 additions & 1 deletion src/ui/app.slint
Expand Up @@ -6,18 +6,20 @@ export component App inherits Window {
out property <string> download_instance_url: download_page.instance_url;
out property <string> download_username_input: download_page.username_input;
out property <string> download_password_input: download_page.password_input;
out property <string> download_two_factor_input: download_page.two_factor_input;
in property <string> download_log_output <=> download_page.log_output;
in property <bool> download_ui_enabled <=> download_page.ui_enabled;

out property <string> upload_instance_url: upload_page.instance_url;
out property <string> upload_username_input: upload_page.username_input;
out property <string> upload_password_input: upload_page.password_input;
out property <string> upload_two_factor_input: upload_page.two_factor_input;
in property <string> upload_log_output <=> upload_page.log_output;
in property <bool> upload_ui_enabled <=> upload_page.ui_enabled;

title: "LASIM";
width: 280px;
height: 330px;
height: 370px;

VerticalBox {
alignment: start;
Expand Down
6 changes: 6 additions & 0 deletions src/ui/control_page.slint
Expand Up @@ -10,6 +10,7 @@ export component ControlPage inherits VerticalBox {
out property <string> instance_url: instance_url_object.text;
out property <string> username_input: username_input_object.text;
out property <string> password_input: password_input_object.text;
out property <string> two_factor_input: two_factor_input_object.text;
in property <string> log_output;
in property <bool> ui_enabled: true;

Expand All @@ -31,6 +32,11 @@ export component ControlPage inherits VerticalBox {
width: 250px;
enabled: ui_enabled;
}
two_factor_input_object := LineEdit {
placeholder-text: "2FA Token (if enabled)";
width: 250px;
enabled: ui_enabled;
}

HorizontalLayout {
alignment: start;
Expand Down

0 comments on commit 8691ebc

Please sign in to comment.