ソースを参照

Merge remote-tracking branch 'upstream/master' into email-codes

vpl 6 年 前
コミット
5d50b1ee3c

ファイルの差分が大きいため隠しています
+ 156 - 156
Cargo.lock


+ 4 - 4
Cargo.toml

@@ -40,8 +40,8 @@ rmpv = "0.4.0"
 chashmap = "2.2.2"
 
 # A generic serialization/deserialization framework
-serde = "1.0.98"
-serde_derive = "1.0.98"
+serde = "1.0.99"
+serde_derive = "1.0.99"
 serde_json = "1.0.40"
 
 # Logging
@@ -103,10 +103,10 @@ handlebars = "2.0.1"
 
 # For favicon extraction from main website
 soup = "0.4.1"
-regex = "1.2.0"
+regex = "1.2.1"
 
 # URL encoding library
-percent-encoding = "2.0.0"
+percent-encoding = "2.1.0"
 
 [patch.crates-io]
 # Add support for Timestamp type

+ 1 - 1
rust-toolchain

@@ -1 +1 @@
-nightly-2019-07-09
+nightly-2019-08-18

+ 13 - 0
src/api/admin.rs

@@ -28,6 +28,7 @@ pub fn routes() -> Vec<Route> {
         invite_user,
         delete_user,
         deauth_user,
+        remove_2fa,
         update_revision_users,
         post_config,
         delete_config,
@@ -196,6 +197,18 @@ fn deauth_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
     user.save(&conn)
 }
 
+#[post("/users/<uuid>/remove-2fa")]
+fn remove_2fa(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
+    let mut user = match User::find_by_uuid(&uuid, &conn) {
+        Some(user) => user,
+        None => err!("User doesn't exist"),
+    };
+
+    TwoFactor::delete_all_by_user(&user.uuid, &conn)?;
+    user.totp_recover = None;
+    user.save(&conn)
+}
+
 #[post("/users/update_revision")]
 fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
     User::update_all_revisions(&conn)

+ 24 - 9
src/api/core/mod.rs

@@ -132,18 +132,33 @@ fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbC
 
 #[get("/hibp/breach?<username>")]
 fn hibp_breach(username: String) -> JsonResult {
-    let url = format!("https://haveibeenpwned.com/api/v2/breachedaccount/{}", username);
     let user_agent = "Bitwarden_RS";
+    let url = format!(
+        "https://haveibeenpwned.com/api/v3/breachedaccount/{}?truncateResponse=false&includeUnverified=false",
+        username
+    );
 
     use reqwest::{header::USER_AGENT, Client};
 
-    let res = Client::new().get(&url).header(USER_AGENT, user_agent).send()?;
-
-    // If we get a 404, return a 404, it means no breached accounts
-    if res.status() == 404 {
-        return Err(Error::empty().with_code(404));
+    if let Some(api_key) = crate::CONFIG.hibp_api_key() {
+        let res = Client::new()
+            .get(&url)
+            .header(USER_AGENT, user_agent)
+            .header("hibp-api-key", api_key)
+            .send()?;
+
+        // If we get a 404, return a 404, it means no breached accounts
+        if res.status() == 404 {
+            return Err(Error::empty().with_code(404));
+        }
+
+        let value: Value = res.error_for_status()?.json()?;
+        Ok(Json(value))
+    } else {
+        Ok(Json(json!([{
+            "title": "--- Error! ---",
+            "description": "HaveIBeenPwned API key not set! Go to https://haveibeenpwned.com/API/Key",
+            "logopath": "/bwrs_images/error-x.svg"
+        }])))
     }
-
-    let value: Value = res.error_for_status()?.json()?;
-    Ok(Json(value))
 }

+ 1 - 3
src/api/core/two_factor/mod.rs

@@ -94,9 +94,7 @@ fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult {
     }
 
     // Remove all twofactors from the user
-    for twofactor in TwoFactor::find_by_user(&user.uuid, &conn) {
-        twofactor.delete(&conn)?;
-    }
+    TwoFactor::delete_all_by_user(&user.uuid, &conn)?;
 
     // Remove the recovery code, not needed without twofactors
     user.totp_recover = None;

+ 1 - 0
src/api/icons.rs

@@ -27,6 +27,7 @@ const ALLOWED_CHARS: &str = "_-.";
 lazy_static! {
     // Reuse the client between requests
     static ref CLIENT: Client = Client::builder()
+        .use_sys_proxy()
         .gzip(true)
         .timeout(Duration::from_secs(CONFIG.icon_download_timeout()))
         .default_headers(_header_map())

+ 7 - 1
src/api/identity.rs

@@ -101,7 +101,13 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult
     let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, &conn)?;
 
     if CONFIG.mail_enabled() && new_device {
-        mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &device.updated_at, &device.name)?
+        if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &device.updated_at, &device.name) {
+            error!("Error sending new device email: {:#?}", e);
+
+            if CONFIG.require_device_email() {
+                err!("Could not send login notification email. Please contact your administrator.")
+            }
+        }
     }
 
     // Common

+ 5 - 5
src/api/web.rs

@@ -65,11 +65,11 @@ fn alive() -> Json<String> {
 }
 
 #[get("/bwrs_images/<filename>")]
-fn images(filename: String) -> Result<Content<Vec<u8>>, Error> {
-    let image_type = ContentType::new("image", "png");
+fn images(filename: String) -> Result<Content<&'static [u8]>, Error> {
     match filename.as_ref() {
-        "mail-github.png" => Ok(Content(image_type , include_bytes!("../static/images/mail-github.png").to_vec())),
-        "logo-gray.png" => Ok(Content(image_type, include_bytes!("../static/images/logo-gray.png").to_vec())),
-        _ => err!("Image not found")
+        "mail-github.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/mail-github.png"))),
+        "logo-gray.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/logo-gray.png"))),
+        "error-x.svg" => Ok(Content(ContentType::SVG, include_bytes!("../static/images/error-x.svg"))),
+        _ => err!("Image not found"),
     }
 }

+ 7 - 0
src/config.rs

@@ -234,6 +234,9 @@ make_config! {
         /// Enable web vault
         web_vault_enabled:      bool,   false,  def,    true;
 
+        /// HIBP Api Key |> HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
+        hibp_api_key:           Pass,   true,   option;
+
         /// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from
         /// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
         /// otherwise it will delete them and they won't be downloaded again.
@@ -269,6 +272,10 @@ make_config! {
         /// Note that the checkbox would still be present, but ignored.
         disable_2fa_remember:   bool,   true,   def,    false;
 
+        /// Require new device emails |> When a user logs in an email is required to be sent.
+        /// If sending the email fails the login attempt will fail.
+        require_device_email:   bool,   true,   def,     false;
+
         /// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request.
         /// ONLY use this during development, as it can slow down the server
         reload_templates:       bool,   true,   def,    false;

+ 6 - 0
src/static/images/error-x.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="450" height="450" version="1">
+  <circle cx="225" cy="225" r="225" fill="#C33"/>
+  <g fill="#FFF" stroke="#FFF" stroke-width="70">
+    <path d="M107 110l236 237M107 347l236-237"/>
+  </g>
+</svg>

+ 13 - 3
src/static/templates/admin/page.hbs

@@ -26,9 +26,13 @@
                                 {{/each}}
                             </span>
                         </div>
-                        <div style="flex: 0 0 240px;">
-                            <a class="mr-3" href="#" onclick='deauthUser({{jsesc Id}})'>Deauthorize sessions</a>
-                            <a class="mr-3" href="#" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Delete User</a>
+                        <div style="flex: 0 0 300px; font-size: 90%; text-align: right; padding-right: 15px">
+                            {{#if TwoFactorEnabled}}
+                            <a class="mr-2" href="#" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</a>
+                            {{/if}}
+
+                            <a class="mr-2" href="#" onclick='deauthUser({{jsesc Id}})'>Deauthorize sessions</a>
+                            <a class="mr-2" href="#" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Delete User</a>
                         </div>
                     </div>
                 </div>
@@ -227,6 +231,12 @@
         }
         return false;
     }
+    function remove2fa(id) {
+        _post("/admin/users/" + id + "/remove-2fa",
+            "2FA removed correctly",
+            "Error removing 2FA");
+        return false;
+    }
     function deauthUser(id) {
         _post("/admin/users/" + id + "/deauth",
             "Sessions deauthorized correctly",

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません