Add backend support for alternate base dir (subdir/subpath) hosting

To use this, include a path in the `DOMAIN` URL, e.g.:

* `DOMAIN=https://example.com/custom-path`
* `DOMAIN=https://example.com/multiple/levels/are/ok`
This commit is contained in:
Jeremy Lin 2020-02-18 21:27:00 -08:00
parent 63459c5f72
commit 29a0795219
21 changed files with 95 additions and 53 deletions

View File

@ -52,6 +52,10 @@ const ADMIN_PATH: &str = "/admin";
const BASE_TEMPLATE: &str = "admin/base"; const BASE_TEMPLATE: &str = "admin/base";
const VERSION: Option<&str> = option_env!("GIT_VERSION"); const VERSION: Option<&str> = option_env!("GIT_VERSION");
fn admin_path() -> String {
format!("{}{}", CONFIG.domain_path(), ADMIN_PATH)
}
#[get("/", rank = 2)] #[get("/", rank = 2)]
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> { fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
// If there is an error, show it // If there is an error, show it
@ -76,7 +80,7 @@ fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -
if !_validate_token(&data.token) { if !_validate_token(&data.token) {
error!("Invalid admin token. IP: {}", ip.ip); error!("Invalid admin token. IP: {}", ip.ip);
Err(Flash::error( Err(Flash::error(
Redirect::to(ADMIN_PATH), Redirect::to(admin_path()),
"Invalid admin token, please try again.", "Invalid admin token, please try again.",
)) ))
} else { } else {
@ -85,14 +89,14 @@ fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -
let jwt = encode_jwt(&claims); let jwt = encode_jwt(&claims);
let cookie = Cookie::build(COOKIE_NAME, jwt) let cookie = Cookie::build(COOKIE_NAME, jwt)
.path(ADMIN_PATH) .path(admin_path())
.max_age(chrono::Duration::minutes(20)) .max_age(chrono::Duration::minutes(20))
.same_site(SameSite::Strict) .same_site(SameSite::Strict)
.http_only(true) .http_only(true)
.finish(); .finish();
cookies.add(cookie); cookies.add(cookie);
Ok(Redirect::to(ADMIN_PATH)) Ok(Redirect::to(admin_path()))
} }
} }
@ -167,7 +171,7 @@ fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> Empt
#[get("/logout")] #[get("/logout")]
fn logout(mut cookies: Cookies) -> Result<Redirect, ()> { fn logout(mut cookies: Cookies) -> Result<Redirect, ()> {
cookies.remove(Cookie::named(COOKIE_NAME)); cookies.remove(Cookie::named(COOKIE_NAME));
Ok(Redirect::to(ADMIN_PATH)) Ok(Redirect::to(admin_path()))
} }
#[get("/users")] #[get("/users")]

View File

@ -172,7 +172,7 @@ fn hibp_breach(username: String) -> JsonResult {
"BreachDate": "2019-08-18T00:00:00Z", "BreachDate": "2019-08-18T00:00:00Z",
"AddedDate": "2019-08-18T00:00:00Z", "AddedDate": "2019-08-18T00:00:00Z",
"Description": format!("Go to: <a href=\"https://haveibeenpwned.com/account/{account}\" target=\"_blank\" rel=\"noopener\">https://haveibeenpwned.com/account/{account}</a> for a manual check.<br/><br/>HaveIBeenPwned API key not set!<br/>Go to <a href=\"https://haveibeenpwned.com/API/Key\" target=\"_blank\" rel=\"noopener\">https://haveibeenpwned.com/API/Key</a> to purchase an API key from HaveIBeenPwned.<br/><br/>", account=username), "Description": format!("Go to: <a href=\"https://haveibeenpwned.com/account/{account}\" target=\"_blank\" rel=\"noopener\">https://haveibeenpwned.com/account/{account}</a> for a manual check.<br/><br/>HaveIBeenPwned API key not set!<br/>Go to <a href=\"https://haveibeenpwned.com/API/Key\" target=\"_blank\" rel=\"noopener\">https://haveibeenpwned.com/API/Key</a> to purchase an API key from HaveIBeenPwned.<br/><br/>", account=username),
"LogoPath": "/bwrs_static/hibp.png", "LogoPath": "bwrs_static/hibp.png",
"PwnCount": 0, "PwnCount": 0,
"DataClasses": [ "DataClasses": [
"Error - No API key set!" "Error - No API key set!"

View File

@ -37,7 +37,17 @@ fn app_id() -> Cached<Content<Json<Value>>> {
{ {
"version": { "major": 1, "minor": 0 }, "version": { "major": 1, "minor": 0 },
"ids": [ "ids": [
&CONFIG.domain(), // Per <https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#determining-the-facetid-of-a-calling-application>:
//
// "In the Web case, the FacetID MUST be the Web Origin [RFC6454]
// of the web page triggering the FIDO operation, written as
// a URI with an empty path. Default ports are omitted and any
// path component is ignored."
//
// This leaves it unclear as to whether the path must be empty,
// or whether it can be non-empty and will be ignored. To be on
// the safe side, use a proper web origin (with empty path).
&CONFIG.domain_origin(),
"ios:bundle-id:com.8bit.bitwarden", "ios:bundle-id:com.8bit.bitwarden",
"android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ] "android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ]
}] }]
@ -75,6 +85,6 @@ fn static_files(filename: String) -> Result<Content<&'static [u8]>, Error> {
"bootstrap-native-v4.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native-v4.js"))), "bootstrap-native-v4.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native-v4.js"))),
"md5.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/md5.js"))), "md5.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/md5.js"))),
"identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))), "identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
_ => err!("Image not found"), _ => err!(format!("Static file not found: {}", filename)),
} }
} }

View File

@ -16,11 +16,11 @@ const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
lazy_static! { lazy_static! {
pub static ref DEFAULT_VALIDITY: Duration = Duration::hours(2); pub static ref DEFAULT_VALIDITY: Duration = Duration::hours(2);
static ref JWT_HEADER: Header = Header::new(JWT_ALGORITHM); static ref JWT_HEADER: Header = Header::new(JWT_ALGORITHM);
pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain()); pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain_origin());
pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain()); pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain_origin());
pub static ref JWT_DELETE_ISSUER: String = format!("{}|delete", CONFIG.domain()); pub static ref JWT_DELETE_ISSUER: String = format!("{}|delete", CONFIG.domain_origin());
pub static ref JWT_VERIFYEMAIL_ISSUER: String = format!("{}|verifyemail", CONFIG.domain()); pub static ref JWT_VERIFYEMAIL_ISSUER: String = format!("{}|verifyemail", CONFIG.domain_origin());
pub static ref JWT_ADMIN_ISSUER: String = format!("{}|admin", CONFIG.domain()); pub static ref JWT_ADMIN_ISSUER: String = format!("{}|admin", CONFIG.domain_origin());
static ref PRIVATE_RSA_KEY: Vec<u8> = match read_file(&CONFIG.private_rsa_key()) { static ref PRIVATE_RSA_KEY: Vec<u8> = match read_file(&CONFIG.private_rsa_key()) {
Ok(key) => key, Ok(key) => key,
Err(e) => panic!("Error loading private RSA Key.\n Error: {}", e), Err(e) => panic!("Error loading private RSA Key.\n Error: {}", e),

View File

@ -1,6 +1,8 @@
use std::process::exit; use std::process::exit;
use std::sync::RwLock; use std::sync::RwLock;
use reqwest::Url;
use crate::error::Error; use crate::error::Error;
use crate::util::{get_env, get_env_bool}; use crate::util::{get_env, get_env_bool};
@ -240,6 +242,10 @@ make_config! {
domain: String, true, def, "http://localhost".to_string(); domain: String, true, def, "http://localhost".to_string();
/// Domain Set |> Indicates if the domain is set by the admin. Otherwise the default will be used. /// Domain Set |> Indicates if the domain is set by the admin. Otherwise the default will be used.
domain_set: bool, false, def, false; domain_set: bool, false, def, false;
/// Domain origin |> Domain URL origin (in https://example.com:8443/path, https://example.com:8443 is the origin)
domain_origin: String, false, auto, |c| extract_url_origin(&c.domain);
/// Domain path |> Domain URL path (in https://example.com:8443/path, /path is the path)
domain_path: String, false, auto, |c| extract_url_path(&c.domain);
/// Enable web vault /// Enable web vault
web_vault_enabled: bool, false, def, true; web_vault_enabled: bool, false, def, true;
@ -457,6 +463,21 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
Ok(()) Ok(())
} }
/// Extracts an RFC 6454 web origin from a URL.
fn extract_url_origin(url: &str) -> String {
let url = Url::parse(url).expect("valid URL");
url.origin().ascii_serialization()
}
/// Extracts the path from a URL.
/// All trailing '/' chars are trimmed, even if the path is a lone '/'.
fn extract_url_path(url: &str) -> String {
let url = Url::parse(url).expect("valid URL");
url.path().trim_end_matches('/').to_string()
}
impl Config { impl Config {
pub fn load() -> Result<Self, Error> { pub fn load() -> Result<Self, Error> {
// Loading from env and file // Loading from env and file

View File

@ -255,18 +255,20 @@ mod migrations {
} }
fn launch_rocket(extra_debug: bool) { fn launch_rocket(extra_debug: bool) {
// Create Rocket object, this stores current log level and sets it's own // Create Rocket object, this stores current log level and sets its own
let rocket = rocket::ignite(); let rocket = rocket::ignite();
// If addding more base paths here, consider also adding them to let basepath = &CONFIG.domain_path();
// If adding more paths here, consider also adding them to
// crate::utils::LOGGED_ROUTES to make sure they appear in the log // crate::utils::LOGGED_ROUTES to make sure they appear in the log
let rocket = rocket let rocket = rocket
.mount("/", api::web_routes()) .mount(&[basepath, "/"].concat(), api::web_routes())
.mount("/api", api::core_routes()) .mount(&[basepath, "/api"].concat(), api::core_routes())
.mount("/admin", api::admin_routes()) .mount(&[basepath, "/admin"].concat(), api::admin_routes())
.mount("/identity", api::identity_routes()) .mount(&[basepath, "/identity"].concat(), api::identity_routes())
.mount("/icons", api::icons_routes()) .mount(&[basepath, "/icons"].concat(), api::icons_routes())
.mount("/notifications", api::notifications_routes()) .mount(&[basepath, "/notifications"].concat(), api::notifications_routes())
.manage(db::init_pool()) .manage(db::init_pool())
.manage(api::start_notification_server()) .manage(api::start_notification_server())
.attach(util::AppHeaders()) .attach(util::AppHeaders())

View File

@ -6,10 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Bitwarden_rs Admin Panel</title> <title>Bitwarden_rs Admin Panel</title>
<link rel="stylesheet" href="/bwrs_static/bootstrap.css" /> <link rel="stylesheet" href="bwrs_static/bootstrap.css" />
<script src="/bwrs_static/bootstrap-native-v4.js"></script> <script src="bwrs_static/bootstrap-native-v4.js"></script>
<script src="/bwrs_static/md5.js"></script> <script src="bwrs_static/md5.js"></script>
<script src="/bwrs_static/identicon.js"></script> <script src="bwrs_static/identicon.js"></script>
<style> <style>
body { body {
padding-top: 70px; padding-top: 70px;
@ -38,10 +38,10 @@
<div class="navbar-collapse"> <div class="navbar-collapse">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" href="/admin">Admin Panel</a> <a class="nav-link" href="admin">Admin Panel</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/">Vault</a> <a class="nav-link" href=".">Vault</a>
</li> </li>
</ul> </ul>
</div> </div>
@ -55,7 +55,7 @@
{{#if logged_in}} {{#if logged_in}}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/admin/logout">Log Out</a> <a class="nav-link" href="admin/logout">Log Out</a>
</li> </li>
{{/if}} {{/if}}
</ul> </ul>
@ -64,4 +64,4 @@
{{> (page_content) }} {{> (page_content) }}
</body> </body>
</html> </html>

View File

@ -225,7 +225,7 @@
var input_mail = prompt("To delete user '" + mail + "', please type the email below") var input_mail = prompt("To delete user '" + mail + "', please type the email below")
if (input_mail != null) { if (input_mail != null) {
if (input_mail == mail) { if (input_mail == mail) {
_post("/admin/users/" + id + "/delete", _post("admin/users/" + id + "/delete",
"User deleted correctly", "User deleted correctly",
"Error deleting user"); "Error deleting user");
} else { } else {
@ -235,19 +235,19 @@
return false; return false;
} }
function remove2fa(id) { function remove2fa(id) {
_post("/admin/users/" + id + "/remove-2fa", _post("admin/users/" + id + "/remove-2fa",
"2FA removed correctly", "2FA removed correctly",
"Error removing 2FA"); "Error removing 2FA");
return false; return false;
} }
function deauthUser(id) { function deauthUser(id) {
_post("/admin/users/" + id + "/deauth", _post("admin/users/" + id + "/deauth",
"Sessions deauthorized correctly", "Sessions deauthorized correctly",
"Error deauthorizing sessions"); "Error deauthorizing sessions");
return false; return false;
} }
function updateRevisions() { function updateRevisions() {
_post("/admin/users/update_revision", _post("admin/users/update_revision",
"Success, clients will sync next time they connect", "Success, clients will sync next time they connect",
"Error forcing clients to sync"); "Error forcing clients to sync");
return false; return false;
@ -256,7 +256,7 @@
inv = document.getElementById("email-invite"); inv = document.getElementById("email-invite");
data = JSON.stringify({ "email": inv.value }); data = JSON.stringify({ "email": inv.value });
inv.value = ""; inv.value = "";
_post("/admin/invite/", "User invited correctly", _post("admin/invite/", "User invited correctly",
"Error inviting user", data); "Error inviting user", data);
return false; return false;
} }
@ -278,7 +278,7 @@
} }
function saveConfig() { function saveConfig() {
data = JSON.stringify(getFormData()); data = JSON.stringify(getFormData());
_post("/admin/config/", "Config saved correctly", _post("admin/config/", "Config saved correctly",
"Error saving config", data); "Error saving config", data);
return false; return false;
} }
@ -286,7 +286,7 @@
var input = prompt("This will remove all user configurations, and restore the defaults and the " + var input = prompt("This will remove all user configurations, and restore the defaults and the " +
"values set by the environment. This operation could be dangerous. Type 'DELETE' to proceed:"); "values set by the environment. This operation could be dangerous. Type 'DELETE' to proceed:");
if (input === "DELETE") { if (input === "DELETE") {
_post("/admin/config/delete", _post("admin/config/delete",
"Config deleted correctly", "Config deleted correctly",
"Error deleting config"); "Error deleting config");
} else { } else {
@ -296,7 +296,7 @@
return false; return false;
} }
function backupDatabase() { function backupDatabase() {
_post("/admin/config/backup_db", _post("admin/config/backup_db",
"Backup created successfully", "Backup created successfully",
"Error creating backup"); "Error creating backup");
return false; return false;
@ -336,4 +336,4 @@
// {{#each config}} {{#if grouptoggle}} // {{#each config}} {{#if grouptoggle}}
masterCheck("input_{{grouptoggle}}", "#g_{{group}} input"); masterCheck("input_{{grouptoggle}}", "#g_{{group}} input");
// {{/if}} {{/each}} // {{/if}} {{/each}}
</script> </script>

View File

@ -3,6 +3,6 @@ Invitation accepted
<html> <html>
<p> <p>
Your invitation for <b>{{email}}</b> to join <b>{{org_name}}</b> was accepted. Your invitation for <b>{{email}}</b> to join <b>{{org_name}}</b> was accepted.
Please <a href="{{url}}">log in</a> to the bitwarden_rs server and confirm them from the organization management page. Please <a href="{{url}}/">log in</a> to the bitwarden_rs server and confirm them from the organization management page.
</p> </p>
</html> </html>

View File

@ -101,7 +101,7 @@ Invitation accepted
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
Please <a href="{{url}}">log in</a> to the bitwarden_rs server and confirm them from the organization management page. Please <a href="{{url}}/">log in</a> to the bitwarden_rs server and confirm them from the organization management page.
</td> </td>
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View File

@ -3,6 +3,6 @@ Invitation to {{org_name}} confirmed
<html> <html>
<p> <p>
Your invitation to join <b>{{org_name}}</b> was confirmed. Your invitation to join <b>{{org_name}}</b> was confirmed.
It will now appear under the Organizations the next time you <a href="{{url}}">log in</a> to the web vault. It will now appear under the Organizations the next time you <a href="{{url}}/">log in</a> to the web vault.
</p> </p>
</html> </html>

View File

@ -102,7 +102,7 @@ Invitation to {{org_name}} confirmed
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top"> <td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top">
Any collections and logins being shared with you by this organization will now appear in your Bitwarden vault. <br> Any collections and logins being shared with you by this organization will now appear in your Bitwarden vault. <br>
<a href="{{url}}">Log in</a> <a href="{{url}}/">Log in</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -9,6 +9,6 @@ New Device Logged In From {{device}}
Device Type: {{device}} Device Type: {{device}}
You can deauthorize all devices that have access to your account from the You can deauthorize all devices that have access to your account from the
<a href="{{url}}">web vault</a> under Settings > My Account > Deauthorize Sessions. <a href="{{url}}/">web vault</a> under Settings > My Account > Deauthorize Sessions.
</p> </p>
</html> </html>

View File

@ -116,7 +116,7 @@ New Device Logged In From {{device}}
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top"> <td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top">
You can deauthorize all devices that have access to your account from the <a href="{{url}}">web vault</a> under Settings > My Account > Deauthorize Sessions. You can deauthorize all devices that have access to your account from the <a href="{{url}}/">web vault</a> under Settings > My Account > Deauthorize Sessions.
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -3,7 +3,7 @@ Your master password hint
You (or someone) recently requested your master password hint. You (or someone) recently requested your master password hint.
Your hint is: "{{hint}}" Your hint is: "{{hint}}"
Log in: <a href="{{url}}">Web Vault</a> Log in: <a href="{{url}}/">Web Vault</a>
If you cannot remember your master password, there is no way to recover your data. The only option to gain access to your account again is to <a href="{{url}}/#/recover-delete">delete the account</a> so that you can register again and start over. All data associated with your account will be deleted. If you cannot remember your master password, there is no way to recover your data. The only option to gain access to your account again is to <a href="{{url}}/#/recover-delete">delete the account</a> so that you can register again and start over. All data associated with your account will be deleted.

View File

@ -102,7 +102,7 @@ Your master password hint
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top"> <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
Your hint is: "{{hint}}"<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" /> Your hint is: "{{hint}}"<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
Log in: <a href="{{url}}">Web Vault</a> Log in: <a href="{{url}}/">Web Vault</a>
</td> </td>
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View File

@ -2,7 +2,7 @@ Welcome
<!----------------> <!---------------->
<html> <html>
<p> <p>
Thank you for creating an account at <a href="{{url}}">{{url}}</a>. You may now log in with your new account. Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. You may now log in with your new account.
</p> </p>
<p>If you did not request to create an account, you can safely ignore this email.</p> <p>If you did not request to create an account, you can safely ignore this email.</p>
</html> </html>

View File

@ -96,7 +96,7 @@ Welcome
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center"> <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
Thank you for creating an account at <a href="{{url}}">{{url}}</a>. You may now log in with your new account. Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. You may now log in with your new account.
</td> </td>
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View File

@ -2,7 +2,7 @@ Welcome
<!----------------> <!---------------->
<html> <html>
<p> <p>
Thank you for creating an account at <a href="{{url}}">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below. Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below.
<br> <br>
<br> <br>
<a href="{{url}}/#/verify-email/?userId={{user_id}}&token={{token}}"> <a href="{{url}}/#/verify-email/?userId={{user_id}}&token={{token}}">

View File

@ -96,7 +96,7 @@ Welcome
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center"> <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
Thank you for creating an account at <a href="{{url}}">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below. Thank you for creating an account at <a href="{{url}}/">{{url}}</a>. Before you can login with your new account, you must verify this email address by clicking the link below.
</td> </td>
</tr> </tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">

View File

@ -109,7 +109,7 @@ impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
} }
} }
// Log all the routes from the main base paths list, and the attachments endoint // Log all the routes from the main paths list, and the attachments endpoint
// Effectively ignores, any static file route, and the alive endpoint // Effectively ignores, any static file route, and the alive endpoint
const LOGGED_ROUTES: [&str; 6] = [ const LOGGED_ROUTES: [&str; 6] = [
"/api", "/api",
@ -157,7 +157,10 @@ impl Fairing for BetterLogging {
} }
let uri = request.uri(); let uri = request.uri();
let uri_path = uri.path(); let uri_path = uri.path();
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_path.starts_with(r)) { // FIXME: trim_start_matches() could result in over-trimming in pathological cases;
// strip_prefix() would be a better option once it's stable.
let uri_subpath = uri_path.trim_start_matches(&CONFIG.domain_path());
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_subpath.starts_with(r)) {
match uri.query() { match uri.query() {
Some(q) => info!(target: "request", "{} {}?{}", method, uri_path, &q[..q.len().min(30)]), Some(q) => info!(target: "request", "{} {}?{}", method, uri_path, &q[..q.len().min(30)]),
None => info!(target: "request", "{} {}", method, uri_path), None => info!(target: "request", "{} {}", method, uri_path),
@ -169,8 +172,10 @@ impl Fairing for BetterLogging {
if !self.0 && request.method() == Method::Options { if !self.0 && request.method() == Method::Options {
return; return;
} }
let uri_path = request.uri().path(); // FIXME: trim_start_matches() could result in over-trimming in pathological cases;
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_path.starts_with(r)) { // strip_prefix() would be a better option once it's stable.
let uri_subpath = request.uri().path().trim_start_matches(&CONFIG.domain_path());
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_subpath.starts_with(r)) {
let status = response.status(); let status = response.status();
if let Some(ref route) = request.route() { if let Some(ref route) = request.route() {
info!(target: "response", "{} => {} {}", route, status.code, status.reason) info!(target: "response", "{} => {} {}", route, status.code, status.reason)