From b6fde857a74b24cbc71631468b13656f01e370c7 Mon Sep 17 00:00:00 2001
From: BlackDex <black.dex@gmail.com>
Date: Thu, 28 May 2020 20:25:25 +0200
Subject: [PATCH] Added version check to diagnostics

- Added a version check based upon the github api information.
---
 src/api/admin.rs                           | 44 +++++++++-
 src/static/templates/admin/diagnostics.hbs | 94 ++++++++++++++++------
 2 files changed, 112 insertions(+), 26 deletions(-)

diff --git a/src/api/admin.rs b/src/api/admin.rs
index 8678e0d9..3eeffe05 100644
--- a/src/api/admin.rs
+++ b/src/api/admin.rs
@@ -319,6 +319,24 @@ pub struct WebVaultVersion {
     version: String,
 }
 
+fn get_github_api(url: &str) -> Result<Value, Error> {
+    use reqwest::{header::USER_AGENT, blocking::Client};
+    let github_api = Client::builder().build()?;
+
+    let res = github_api
+        .get(url)
+        .header(USER_AGENT, "Bitwarden_RS")
+        .send()?;
+
+    let res_status = res.status();
+    if res_status != 200 {
+        error!("Could not retrieve '{}', response code: {}", url, res_status);
+    }
+
+    let value: Value = res.error_for_status()?.json()?;
+    Ok(value)
+}
+
 #[get("/diagnostics")]
 fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
     use std::net::ToSocketAddrs;
@@ -331,10 +349,31 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
 
     let github_ips = ("github.com", 0).to_socket_addrs().map(|mut i| i.next());
     let dns_resolved = match github_ips {
-        Ok(Some(a)) => a.ip().to_string() ,
+        Ok(Some(a)) => a.ip().to_string(),
         _ => "Could not resolve domain name.".to_string(),
     };
 
+    let bitwarden_rs_releases = get_github_api("https://api.github.com/repos/dani-garcia/bitwarden_rs/releases/latest");
+    let latest_release = match &bitwarden_rs_releases {
+        Ok(j) => j["tag_name"].as_str().unwrap(),
+        _ => "-",
+    };
+
+    let bitwarden_rs_commits = get_github_api("https://api.github.com/repos/dani-garcia/bitwarden_rs/commits/master");
+    let mut latest_commit = match &bitwarden_rs_commits {
+        Ok(j) => j["sha"].as_str().unwrap(),
+        _ => "-",
+    };
+    if latest_commit.len() >= 8 {
+        latest_commit = &latest_commit[..8];
+    }
+
+    let bw_web_builds_releases = get_github_api("https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest");
+    let latest_web_build = match &bw_web_builds_releases {
+        Ok(j) => j["tag_name"].as_str().unwrap(),
+        _ => "-",
+    };
+
     let dt = Utc::now();
     let server_time = dt.format("%Y-%m-%d %H:%M:%S").to_string();
 
@@ -342,6 +381,9 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
         "dns_resolved": dns_resolved,
         "server_time": server_time,
         "web_vault_version": web_vault_version.version,
+        "latest_release": latest_release,
+        "latest_commit": latest_commit,
+        "latest_web_build": latest_web_build.replace("v", ""),
     });
 
     let text = AdminTemplateData::diagnostics(diagnostics_json).render()?;
diff --git a/src/static/templates/admin/diagnostics.hbs b/src/static/templates/admin/diagnostics.hbs
index dbf82c1e..b5eca6ce 100644
--- a/src/static/templates/admin/diagnostics.hbs
+++ b/src/static/templates/admin/diagnostics.hbs
@@ -6,14 +6,30 @@
         <div class="row">
             <div class="col-md">
                 <dl class="row">
-                    <dt class="col-sm-5">Server Installed</dt>
+                    <dt class="col-sm-5">Server Installed
+                        <span class="badge badge-success d-none" id="server-success" title="Latest version is installed.">Ok</span>
+                        <span class="badge badge-warning d-none" id="server-warning" title="There seems to be an update available.">Update</span>
+                        <span class="badge badge-danger d-none" id="server-failed" title="Unable to determine latest version.">Unknown</span>
+                    </dt>
                     <dd class="col-sm-7">
                         <span id="server-installed">{{version}}</span>
                     </dd>
-                    <dt class="col-sm-5">Web Installed</dt>
+                    <dt class="col-sm-5">Server Latest</dt>
+                    <dd class="col-sm-7">
+                        <span id="server-latest">{{diagnostics.latest_release}}<span id="server-latest-commit" class="d-none">-{{diagnostics.latest_commit}}</span></span>
+                    </dd>
+                    <dt class="col-sm-5">Web Installed
+                        <span class="badge badge-success d-none" id="web-success" title="Latest version is installed.">Ok</span>
+                        <span class="badge badge-warning d-none" id="web-warning" title="There seems to be an update available.">Update</span>
+                        <span class="badge badge-danger d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span>
+                    </dt>
                     <dd class="col-sm-7">
                         <span id="web-installed">{{diagnostics.web_vault_version}}</span>
                     </dd>
+                    <dt class="col-sm-5">Web Latest</dt>
+                    <dd class="col-sm-7">
+                        <span id="web-latest">{{diagnostics.latest_web_build}}</span>
+                    </dd>
                 </dl>
             </div>
         </div>
@@ -45,29 +61,57 @@
 </main>
 
 <script>
-    const d = new Date();
-    const year = d.getUTCFullYear();
-    const month = String((d.getUTCMonth()+1)).padStart(2, '0');
-    const day = String(d.getUTCDate()).padStart(2, '0');
-    const hour = String(d.getUTCHours()).padStart(2, '0');
-    const minute = String(d.getUTCMinutes()).padStart(2, '0');
-    const seconds = String(d.getUTCSeconds()).padStart(2, '0');
-    const browserUTC = year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + seconds;
-    document.getElementById("time-browser-string").innerText = browserUTC;
+    (() => {
+        const d = new Date();
+        const year = d.getUTCFullYear();
+        const month = String((d.getUTCMonth()+1)).padStart(2, '0');
+        const day = String(d.getUTCDate()).padStart(2, '0');
+        const hour = String(d.getUTCHours()).padStart(2, '0');
+        const minute = String(d.getUTCMinutes()).padStart(2, '0');
+        const seconds = String(d.getUTCSeconds()).padStart(2, '0');
+        const browserUTC = year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + seconds;
+        document.getElementById("time-browser-string").innerText = browserUTC;
+
+        const serverUTC = document.getElementById("time-server-string").innerText;
+        const timeDrift = (Date.parse(serverUTC) - Date.parse(browserUTC)) / 1000;
+        if (timeDrift > 30 || timeDrift < -30) {
+            document.getElementById('time-warning').classList.remove('d-none');
+        } else {
+            document.getElementById('time-success').classList.remove('d-none');
+        }
+
+        // Check if the output is a valid IP
+        const isValidIp = value => (/^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/.test(value) ? true : false);
+        if (isValidIp(document.getElementById('dns-resolved').innerText)) {
+            document.getElementById('dns-success').classList.remove('d-none');
+        } else {
+            document.getElementById('dns-warning').classList.remove('d-none');
+        }
+
+        let serverInstalled = document.getElementById('server-installed').innerText;
+        let serverLatest = document.getElementById('server-latest').innerText;
+        if (serverInstalled.indexOf('-') > -1 && serverLatest !== '-') {
+            document.getElementById('server-latest-commit').classList.remove('d-none');
+            serverLatest += document.getElementById('server-latest-commit').innerText;
+        }
+
+        const webInstalled = document.getElementById('web-installed').innerText;
+        const webLatest = document.getElementById('web-latest').innerText;
+
+        checkVersions('server', serverInstalled, serverLatest);
+        checkVersions('web', webInstalled, webLatest);
 
-    const serverUTC = document.getElementById("time-server-string").innerText;
-    const timeDrift = (Date.parse(serverUTC) - Date.parse(browserUTC)) / 1000;
-    if (timeDrift > 30 || timeDrift < -30) {
-        document.getElementById('time-warning').classList.remove('d-none');
-    } else {
-        document.getElementById('time-success').classList.remove('d-none');
-    }
+        function checkVersions(platform, installed, latest) {
+            if (installed === '-' || latest === '-') {
+                document.getElementById(platform + '-failed').classList.remove('d-none');
+                return;
+            }
 
-    // Check if the output is a valid IP
-    const isValidIp = value => (/^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/.test(value) ? true : false);
-    if (isValidIp(document.getElementById('dns-resolved').innerText)) {
-        document.getElementById('dns-success').classList.remove('d-none');
-    } else {
-        document.getElementById('dns-warning').classList.remove('d-none');
-    }
+            if (installed !== latest) {
+                document.getElementById(platform + '-warning').classList.remove('d-none');
+            } else {
+                document.getElementById(platform + '-success').classList.remove('d-none');
+            }
+        }
+    })();
 </script>
\ No newline at end of file
-- 
GitLab