use rocket_contrib::{Json, Value}; use data_encoding::BASE32; use db::DbConn; use crypto; use api::{PasswordData, JsonResult}; use auth::Headers; #[get("/two-factor")] fn get_twofactor(headers: Headers) -> JsonResult { let data = if headers.user.totp_secret.is_none() { Value::Null } else { json!([{ "Enabled": true, "Type": 0, "Object": "twoFactorProvider" }]) }; Ok(Json(json!({ "Data": data, "Object": "list" }))) } #[post("/two-factor/get-recover", data = "")] fn get_recover(data: Json, headers: Headers) -> JsonResult { let data: PasswordData = data.into_inner(); if !headers.user.check_valid_password(&data.masterPasswordHash) { err!("Invalid password"); } Ok(Json(json!({ "Code": headers.user.totp_recover, "Object": "twoFactorRecover" }))) } #[derive(Deserialize)] #[allow(non_snake_case)] struct RecoverTwoFactor { masterPasswordHash: String, email: String, recoveryCode: String, } #[post("/two-factor/recover", data = "")] fn recover(data: Json, conn: DbConn) -> JsonResult { let data: RecoverTwoFactor = data.into_inner(); use db::models::User; // Get the user let mut user = match User::find_by_mail(&data.email, &conn) { Some(user) => user, None => err!("Username or password is incorrect. Try again.") }; // Check password if !user.check_valid_password(&data.masterPasswordHash) { err!("Username or password is incorrect. Try again.") } // Check if recovery code is correct if !user.check_valid_recovery_code(&data.recoveryCode) { err!("Recovery code is incorrect. Try again.") } user.totp_secret = None; user.totp_recover = None; user.save(&conn); Ok(Json(json!({}))) } #[post("/two-factor/get-authenticator", data = "")] fn generate_authenticator(data: Json, headers: Headers) -> JsonResult { let data: PasswordData = data.into_inner(); if !headers.user.check_valid_password(&data.masterPasswordHash) { err!("Invalid password"); } let (enabled, key) = match headers.user.totp_secret { Some(secret) => (true, secret), _ => (false, BASE32.encode(&crypto::get_random(vec![0u8; 20]))) }; Ok(Json(json!({ "Enabled": enabled, "Key": key, "Object": "twoFactorAuthenticator" }))) } #[derive(Deserialize)] #[allow(non_snake_case)] struct EnableTwoFactorData { masterPasswordHash: String, key: String, token: u64, } #[post("/two-factor/authenticator", data = "")] fn activate_authenticator(data: Json, headers: Headers, conn: DbConn) -> JsonResult { let data: EnableTwoFactorData = data.into_inner(); let password_hash = data.masterPasswordHash; let key = data.key; let token = data.token; if !headers.user.check_valid_password(&password_hash) { err!("Invalid password"); } // Validate key as base32 and 20 bytes length let decoded_key: Vec = match BASE32.decode(key.as_bytes()) { Ok(decoded) => decoded, _ => err!("Invalid totp secret") }; if decoded_key.len() != 20 { err!("Invalid key length") } // Set key in user.totp_secret let mut user = headers.user; user.totp_secret = Some(key.to_uppercase()); // Validate the token provided with the key if !user.check_totp_code(Some(token)) { err!("Invalid totp code") } // Generate totp_recover let totp_recover = BASE32.encode(&crypto::get_random(vec![0u8; 20])); user.totp_recover = Some(totp_recover); user.save(&conn); Ok(Json(json!({ "Enabled": true, "Key": key, "Object": "twoFactorAuthenticator" }))) } #[derive(Deserialize)] #[allow(non_snake_case)] struct DisableTwoFactorData { masterPasswordHash: String, #[serde(rename = "type")] type_: u32, } #[post("/two-factor/disable", data = "")] fn disable_authenticator(data: Json, headers: Headers, conn: DbConn) -> JsonResult { let data: DisableTwoFactorData = data.into_inner(); let password_hash = data.masterPasswordHash; if !headers.user.check_valid_password(&password_hash) { err!("Invalid password"); } let mut user = headers.user; user.totp_secret = None; user.totp_recover = None; user.save(&conn); Ok(Json(json!({ "Enabled": false, "Type": 0, "Object": "twoFactorProvider" }))) }