email.rs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. use chrono::{Duration, NaiveDateTime, Utc};
  2. use rocket::serde::json::Json;
  3. use rocket::Route;
  4. use crate::{
  5. api::{core::two_factor::_generate_recover_code, EmptyResult, JsonResult, JsonUpcase, PasswordData},
  6. auth::Headers,
  7. crypto,
  8. db::{
  9. models::{TwoFactor, TwoFactorType},
  10. DbConn,
  11. },
  12. error::{Error, MapResult},
  13. mail, CONFIG,
  14. };
  15. pub fn routes() -> Vec<Route> {
  16. routes![get_email, send_email_login, send_email, email,]
  17. }
  18. #[derive(Deserialize)]
  19. #[allow(non_snake_case)]
  20. struct SendEmailLoginData {
  21. Email: String,
  22. MasterPasswordHash: String,
  23. }
  24. /// User is trying to login and wants to use email 2FA.
  25. /// Does not require Bearer token
  26. #[post("/two-factor/send-email-login", data = "<data>")] // JsonResult
  27. fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> EmptyResult {
  28. let data: SendEmailLoginData = data.into_inner().data;
  29. use crate::db::models::User;
  30. // Get the user
  31. let user = match User::find_by_mail(&data.Email, &conn) {
  32. Some(user) => user,
  33. None => err!("Username or password is incorrect. Try again."),
  34. };
  35. // Check password
  36. if !user.check_valid_password(&data.MasterPasswordHash) {
  37. err!("Username or password is incorrect. Try again.")
  38. }
  39. if !CONFIG._enable_email_2fa() {
  40. err!("Email 2FA is disabled")
  41. }
  42. send_token(&user.uuid, &conn)?;
  43. Ok(())
  44. }
  45. /// Generate the token, save the data for later verification and send email to user
  46. pub fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult {
  47. let type_ = TwoFactorType::Email as i32;
  48. let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, type_, conn).map_res("Two factor not found")?;
  49. let generated_token = crypto::generate_email_token(CONFIG.email_token_size());
  50. let mut twofactor_data = EmailTokenData::from_json(&twofactor.data)?;
  51. twofactor_data.set_token(generated_token);
  52. twofactor.data = twofactor_data.to_json();
  53. twofactor.save(conn)?;
  54. mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?;
  55. Ok(())
  56. }
  57. /// When user clicks on Manage email 2FA show the user the related information
  58. #[post("/two-factor/get-email", data = "<data>")]
  59. fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
  60. let data: PasswordData = data.into_inner().data;
  61. let user = headers.user;
  62. if !user.check_valid_password(&data.MasterPasswordHash) {
  63. err!("Invalid password");
  64. }
  65. let (enabled, mfa_email) = match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn) {
  66. Some(x) => {
  67. let twofactor_data = EmailTokenData::from_json(&x.data)?;
  68. (true, json!(twofactor_data.email))
  69. }
  70. _ => (false, json!(null)),
  71. };
  72. Ok(Json(json!({
  73. "Email": mfa_email,
  74. "Enabled": enabled,
  75. "Object": "twoFactorEmail"
  76. })))
  77. }
  78. #[derive(Deserialize)]
  79. #[allow(non_snake_case)]
  80. struct SendEmailData {
  81. /// Email where 2FA codes will be sent to, can be different than user email account.
  82. Email: String,
  83. MasterPasswordHash: String,
  84. }
  85. /// Send a verification email to the specified email address to check whether it exists/belongs to user.
  86. #[post("/two-factor/send-email", data = "<data>")]
  87. fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult {
  88. let data: SendEmailData = data.into_inner().data;
  89. let user = headers.user;
  90. if !user.check_valid_password(&data.MasterPasswordHash) {
  91. err!("Invalid password");
  92. }
  93. if !CONFIG._enable_email_2fa() {
  94. err!("Email 2FA is disabled")
  95. }
  96. let type_ = TwoFactorType::Email as i32;
  97. if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) {
  98. tf.delete(&conn)?;
  99. }
  100. let generated_token = crypto::generate_email_token(CONFIG.email_token_size());
  101. let twofactor_data = EmailTokenData::new(data.Email, generated_token);
  102. // Uses EmailVerificationChallenge as type to show that it's not verified yet.
  103. let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json());
  104. twofactor.save(&conn)?;
  105. mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?;
  106. Ok(())
  107. }
  108. #[derive(Deserialize, Serialize)]
  109. #[allow(non_snake_case)]
  110. struct EmailData {
  111. Email: String,
  112. MasterPasswordHash: String,
  113. Token: String,
  114. }
  115. /// Verify email belongs to user and can be used for 2FA email codes.
  116. #[put("/two-factor/email", data = "<data>")]
  117. fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonResult {
  118. let data: EmailData = data.into_inner().data;
  119. let mut user = headers.user;
  120. if !user.check_valid_password(&data.MasterPasswordHash) {
  121. err!("Invalid password");
  122. }
  123. let type_ = TwoFactorType::EmailVerificationChallenge as i32;
  124. let mut twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).map_res("Two factor not found")?;
  125. let mut email_data = EmailTokenData::from_json(&twofactor.data)?;
  126. let issued_token = match &email_data.last_token {
  127. Some(t) => t,
  128. _ => err!("No token available"),
  129. };
  130. if !crypto::ct_eq(issued_token, data.Token) {
  131. err!("Token is invalid")
  132. }
  133. email_data.reset_token();
  134. twofactor.atype = TwoFactorType::Email as i32;
  135. twofactor.data = email_data.to_json();
  136. twofactor.save(&conn)?;
  137. _generate_recover_code(&mut user, &conn);
  138. Ok(Json(json!({
  139. "Email": email_data.email,
  140. "Enabled": "true",
  141. "Object": "twoFactorEmail"
  142. })))
  143. }
  144. /// Validate the email code when used as TwoFactor token mechanism
  145. pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &DbConn) -> EmptyResult {
  146. let mut email_data = EmailTokenData::from_json(data)?;
  147. let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Email as i32, conn)
  148. .map_res("Two factor not found")?;
  149. let issued_token = match &email_data.last_token {
  150. Some(t) => t,
  151. _ => err!("No token available"),
  152. };
  153. if !crypto::ct_eq(issued_token, token) {
  154. email_data.add_attempt();
  155. if email_data.attempts >= CONFIG.email_attempts_limit() {
  156. email_data.reset_token();
  157. }
  158. twofactor.data = email_data.to_json();
  159. twofactor.save(conn)?;
  160. err!("Token is invalid")
  161. }
  162. email_data.reset_token();
  163. twofactor.data = email_data.to_json();
  164. twofactor.save(conn)?;
  165. let date = NaiveDateTime::from_timestamp(email_data.token_sent, 0);
  166. let max_time = CONFIG.email_expiration_time() as i64;
  167. if date + Duration::seconds(max_time) < Utc::now().naive_utc() {
  168. err!("Token has expired")
  169. }
  170. Ok(())
  171. }
  172. /// Data stored in the TwoFactor table in the db
  173. #[derive(Serialize, Deserialize)]
  174. pub struct EmailTokenData {
  175. /// Email address where the token will be sent to. Can be different from account email.
  176. pub email: String,
  177. /// Some(token): last valid token issued that has not been entered.
  178. /// None: valid token was used and removed.
  179. pub last_token: Option<String>,
  180. /// UNIX timestamp of token issue.
  181. pub token_sent: i64,
  182. /// Amount of token entry attempts for last_token.
  183. pub attempts: u64,
  184. }
  185. impl EmailTokenData {
  186. pub fn new(email: String, token: String) -> EmailTokenData {
  187. EmailTokenData {
  188. email,
  189. last_token: Some(token),
  190. token_sent: Utc::now().naive_utc().timestamp(),
  191. attempts: 0,
  192. }
  193. }
  194. pub fn set_token(&mut self, token: String) {
  195. self.last_token = Some(token);
  196. self.token_sent = Utc::now().naive_utc().timestamp();
  197. }
  198. pub fn reset_token(&mut self) {
  199. self.last_token = None;
  200. self.attempts = 0;
  201. }
  202. pub fn add_attempt(&mut self) {
  203. self.attempts += 1;
  204. }
  205. pub fn to_json(&self) -> String {
  206. serde_json::to_string(&self).unwrap()
  207. }
  208. pub fn from_json(string: &str) -> Result<EmailTokenData, Error> {
  209. let res: Result<EmailTokenData, crate::serde_json::Error> = serde_json::from_str(string);
  210. match res {
  211. Ok(x) => Ok(x),
  212. Err(_) => err!("Could not decode EmailTokenData from string"),
  213. }
  214. }
  215. }
  216. /// Takes an email address and obscures it by replacing it with asterisks except two characters.
  217. pub fn obscure_email(email: &str) -> String {
  218. let split: Vec<&str> = email.rsplitn(2, '@').collect();
  219. let mut name = split[1].to_string();
  220. let domain = &split[0];
  221. let name_size = name.chars().count();
  222. let new_name = match name_size {
  223. 1..=3 => "*".repeat(name_size),
  224. _ => {
  225. let stars = "*".repeat(name_size - 2);
  226. name.truncate(2);
  227. format!("{}{}", name, stars)
  228. }
  229. };
  230. format!("{}@{}", new_name, &domain)
  231. }
  232. #[cfg(test)]
  233. mod tests {
  234. use super::*;
  235. #[test]
  236. fn test_obscure_email_long() {
  237. let email = "[email protected]";
  238. let result = obscure_email(email);
  239. // Only first two characters should be visible.
  240. assert_eq!(result, "by***@example.ext");
  241. }
  242. #[test]
  243. fn test_obscure_email_short() {
  244. let email = "[email protected]";
  245. let result = obscure_email(email);
  246. // If it's smaller than 3 characters it should only show asterisks.
  247. assert_eq!(result, "***@example.ext");
  248. }
  249. }