Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/api/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ const ADMIN_PATH: &str = "/admin";
const BASE_TEMPLATE: &str = "admin/base";
const VERSION: Option<&str> = option_env!("GIT_VERSION");

fn admin_path() -> String {
format!("{}{}", CONFIG.domain_path(), ADMIN_PATH)
}

#[get("/", rank = 2)]
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
// If there is an error, show it
Expand All @@ -76,7 +80,7 @@ fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -
if !_validate_token(&data.token) {
error!("Invalid admin token. IP: {}", ip.ip);
Err(Flash::error(
Redirect::to(ADMIN_PATH),
Redirect::to(admin_path()),
"Invalid admin token, please try again.",
))
} else {
Expand All @@ -85,14 +89,14 @@ fn post_admin_login(data: Form<LoginForm>, mut cookies: Cookies, ip: ClientIp) -
let jwt = encode_jwt(&claims);

let cookie = Cookie::build(COOKIE_NAME, jwt)
.path(ADMIN_PATH)
.path(admin_path())
.max_age(chrono::Duration::minutes(20))
.same_site(SameSite::Strict)
.http_only(true)
.finish();

cookies.add(cookie);
Ok(Redirect::to(ADMIN_PATH))
Ok(Redirect::to(admin_path()))
}
}

Expand Down Expand Up @@ -167,7 +171,7 @@ fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> Empt
#[get("/logout")]
fn logout(mut cookies: Cookies) -> Result<Redirect, ()> {
cookies.remove(Cookie::named(COOKIE_NAME));
Ok(Redirect::to(ADMIN_PATH))
Ok(Redirect::to(admin_path()))
}

#[get("/users")]
Expand Down
2 changes: 1 addition & 1 deletion src/api/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ fn hibp_breach(username: String) -> JsonResult {
"BreachDate": "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),
"LogoPath": "/bwrs_static/hibp.png",
"LogoPath": "bwrs_static/hibp.png",
"PwnCount": 0,
"DataClasses": [
"Error - No API key set!"
Expand Down
14 changes: 12 additions & 2 deletions src/api/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,17 @@ fn app_id() -> Cached<Content<Json<Value>>> {
{
"version": { "major": 1, "minor": 0 },
"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",
"android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ]
}]
Expand Down Expand Up @@ -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"))),
"md5.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/md5.js"))),
"identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
_ => err!("Image not found"),
_ => err!(format!("Static file not found: {}", filename)),
}
}
10 changes: 5 additions & 5 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
lazy_static! {
pub static ref DEFAULT_VALIDITY: Duration = Duration::hours(2);
static ref JWT_HEADER: Header = Header::new(JWT_ALGORITHM);
pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain());
pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain());
pub static ref JWT_DELETE_ISSUER: String = format!("{}|delete", CONFIG.domain());
pub static ref JWT_VERIFYEMAIL_ISSUER: String = format!("{}|verifyemail", CONFIG.domain());
pub static ref JWT_ADMIN_ISSUER: String = format!("{}|admin", CONFIG.domain());
pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain_origin());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to domain_origin() instead of domain() to avoid JWT encode/decode issues in case the installation is moved to a different base dir. Note that in the usual case where the backend is hosted at the root of the domain, domain_origin() and domain() should be identical.

pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain_origin());
pub static ref JWT_DELETE_ISSUER: String = format!("{}|delete", CONFIG.domain_origin());
pub static ref JWT_VERIFYEMAIL_ISSUER: String = format!("{}|verifyemail", CONFIG.domain_origin());
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()) {
Ok(key) => key,
Err(e) => panic!("Error loading private RSA Key.\n Error: {}", e),
Expand Down
21 changes: 21 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::process::exit;
use std::sync::RwLock;

use reqwest::Url;

use crate::error::Error;
use crate::util::{get_env, get_env_bool};

Expand Down Expand Up @@ -240,6 +242,10 @@ make_config! {
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: bool, false, def, false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like domain_set could be made auto as well, but maybe there's some subtlety I'm missing, so I left it as is.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// 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
web_vault_enabled: bool, false, def, true;

Expand Down Expand Up @@ -457,6 +463,21 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
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 {
pub fn load() -> Result<Self, Error> {
// Loading from env and file
Expand Down
18 changes: 10 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,18 +255,20 @@ mod migrations {
}

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();

// 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
let rocket = rocket
.mount("/", api::web_routes())
.mount("/api", api::core_routes())
.mount("/admin", api::admin_routes())
.mount("/identity", api::identity_routes())
.mount("/icons", api::icons_routes())
.mount("/notifications", api::notifications_routes())
.mount(&[basepath, "/"].concat(), api::web_routes())
.mount(&[basepath, "/api"].concat(), api::core_routes())
.mount(&[basepath, "/admin"].concat(), api::admin_routes())
.mount(&[basepath, "/identity"].concat(), api::identity_routes())
.mount(&[basepath, "/icons"].concat(), api::icons_routes())
.mount(&[basepath, "/notifications"].concat(), api::notifications_routes())
.manage(db::init_pool())
.manage(api::start_notification_server())
.attach(util::AppHeaders())
Expand Down
16 changes: 8 additions & 8 deletions src/static/templates/admin/base.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Bitwarden_rs Admin Panel</title>

<link rel="stylesheet" href="/bwrs_static/bootstrap.css" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These paths all need to be relative to work with a different base dir.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed it to instead use an absolute path with the domain_path variable to avoid the / at the end issue that you mention for the emails

<script src="/bwrs_static/bootstrap-native-v4.js"></script>
<script src="/bwrs_static/md5.js"></script>
<script src="/bwrs_static/identicon.js"></script>
<link rel="stylesheet" href="bwrs_static/bootstrap.css" />
<script src="bwrs_static/bootstrap-native-v4.js"></script>
<script src="bwrs_static/md5.js"></script>
<script src="bwrs_static/identicon.js"></script>
<style>
body {
padding-top: 70px;
Expand Down Expand Up @@ -38,10 +38,10 @@
<div class="navbar-collapse">
<ul class="navbar-nav">
<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 class="nav-item">
<a class="nav-link" href="/">Vault</a>
<a class="nav-link" href=".">Vault</a>
</li>
</ul>
</div>
Expand All @@ -55,7 +55,7 @@

{{#if logged_in}}
<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>
{{/if}}
</ul>
Expand All @@ -64,4 +64,4 @@
{{> (page_content) }}
</body>

</html>
</html>
18 changes: 9 additions & 9 deletions src/static/templates/admin/page.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
var input_mail = prompt("To delete user '" + mail + "', please type the email below")
if (input_mail != null) {
if (input_mail == mail) {
_post("/admin/users/" + id + "/delete",
_post("admin/users/" + id + "/delete",
"User deleted correctly",
"Error deleting user");
} else {
Expand All @@ -235,19 +235,19 @@
return false;
}
function remove2fa(id) {
_post("/admin/users/" + id + "/remove-2fa",
_post("admin/users/" + id + "/remove-2fa",
"2FA removed correctly",
"Error removing 2FA");
return false;
}
function deauthUser(id) {
_post("/admin/users/" + id + "/deauth",
_post("admin/users/" + id + "/deauth",
"Sessions deauthorized correctly",
"Error deauthorizing sessions");
return false;
}
function updateRevisions() {
_post("/admin/users/update_revision",
_post("admin/users/update_revision",
"Success, clients will sync next time they connect",
"Error forcing clients to sync");
return false;
Expand All @@ -256,7 +256,7 @@
inv = document.getElementById("email-invite");
data = JSON.stringify({ "email": inv.value });
inv.value = "";
_post("/admin/invite/", "User invited correctly",
_post("admin/invite/", "User invited correctly",
"Error inviting user", data);
return false;
}
Expand All @@ -278,15 +278,15 @@
}
function saveConfig() {
data = JSON.stringify(getFormData());
_post("/admin/config/", "Config saved correctly",
_post("admin/config/", "Config saved correctly",
"Error saving config", data);
return false;
}
function deleteConf() {
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:");
if (input === "DELETE") {
_post("/admin/config/delete",
_post("admin/config/delete",
"Config deleted correctly",
"Error deleting config");
} else {
Expand All @@ -296,7 +296,7 @@
return false;
}
function backupDatabase() {
_post("/admin/config/backup_db",
_post("admin/config/backup_db",
"Backup created successfully",
"Error creating backup");
return false;
Expand Down Expand Up @@ -336,4 +336,4 @@
// {{#each config}} {{#if grouptoggle}}
masterCheck("input_{{grouptoggle}}", "#g_{{group}} input");
// {{/if}} {{/each}}
</script>
</script>
2 changes: 1 addition & 1 deletion src/static/templates/email/invite_accepted.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ Invitation accepted
<html>
<p>
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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a / after all bare {{url}} links because, for reasons I don't quite understand, if the base URL is https://example.com/my/path, then visiting the web vault at https://example.com/my/path results in rendering issues, while https://example.com/my/path/ (with trailing /) works fine.

</p>
</html>
2 changes: 1 addition & 1 deletion src/static/templates/email/invite_accepted.html.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Invitation accepted
</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;">
<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>
</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;">
Expand Down
2 changes: 1 addition & 1 deletion src/static/templates/email/invite_confirmed.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ Invitation to {{org_name}} confirmed
<html>
<p>
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>
</html>
2 changes: 1 addition & 1 deletion src/static/templates/email/invite_confirmed.html.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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;">
<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>
<a href="{{url}}">Log in</a>
<a href="{{url}}/">Log in</a>
</td>
</tr>
</table>
Expand Down
2 changes: 1 addition & 1 deletion src/static/templates/email/new_device_logged_in.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ New Device Logged In From {{device}}
Device Type: {{device}}

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>
</html>
2 changes: 1 addition & 1 deletion src/static/templates/email/new_device_logged_in.html.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ New Device Logged In From {{device}}
</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;">
<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>
</tr>
</table>
Expand Down
2 changes: 1 addition & 1 deletion src/static/templates/email/pw_hint_some.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Your master password hint
You (or someone) recently requested your master password 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.

Expand Down
2 changes: 1 addition & 1 deletion src/static/templates/email/pw_hint_some.html.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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;">
<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;" />
Log in: <a href="{{url}}">Web Vault</a>
Log in: <a href="{{url}}/">Web Vault</a>
</td>
</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;">
Expand Down
2 changes: 1 addition & 1 deletion src/static/templates/email/welcome.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Welcome
<!---------------->
<html>
<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>If you did not request to create an account, you can safely ignore this email.</p>
</html>
Loading