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

Multiple domains support #3870

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
80d3c61
add configuration support for multiple domains
BlockListed Sep 9, 2023
40edfa5
implement mutli domain support for auth headers
BlockListed Sep 9, 2023
303eb30
remove domain_paths hashmap, since it's no longer used
BlockListed Sep 9, 2023
17923c3
replace domain with base_url
BlockListed Sep 9, 2023
0ebd877
make admin work with multi-domains
BlockListed Sep 9, 2023
2c7b739
make fido app-id.json work with multi-domains
BlockListed Sep 9, 2023
e313745
make domain protocol validation work with multi-domains
BlockListed Sep 9, 2023
0d7e678
make mail work with multi-domains
BlockListed Sep 9, 2023
5462b97
make cors work with multi-domains
BlockListed Sep 9, 2023
f82a142
get domain and origin with single extractor
BlockListed Sep 9, 2023
b5dea32
make attachments / ciphers support multi-domains
BlockListed Sep 9, 2023
968ed8a
make sends support multi-domain
BlockListed Sep 9, 2023
42e1018
make admin support hostinfo
BlockListed Sep 9, 2023
12c0005
make webauthn support multi-domain
BlockListed Sep 9, 2023
ac3c1d4
make web support hostinfo
BlockListed Sep 9, 2023
6867099
make headers use hostinfo
BlockListed Sep 9, 2023
2670db1
make accounts support multi-domains
BlockListed Sep 9, 2023
81dd479
make ciphers work with multi-domains
BlockListed Sep 9, 2023
3421dfc
make emergency access work with multi-domains
BlockListed Sep 9, 2023
ab96b26
make getting config work with multi-domains
BlockListed Sep 9, 2023
901bf57
make organizations work with multi-domains
BlockListed Sep 9, 2023
df524c7
make PublicToken support multi-domains
BlockListed Sep 9, 2023
7639a2b
make identity support multi-domains
BlockListed Sep 9, 2023
1dfc68a
make auth support multi-domains
BlockListed Sep 9, 2023
f208630
fix issue in config
BlockListed Sep 9, 2023
c0db0d8
make clippy happy
BlockListed Sep 9, 2023
3a66772
use single hashmap instead of two for domain lookups
BlockListed Sep 9, 2023
12bdcd4
clippy and format
BlockListed Sep 9, 2023
fc78b6f
implement error handling for HostInfo extractor
BlockListed Sep 9, 2023
d627b02
remove admin_path function
BlockListed Sep 9, 2023
96261f1
remove breaking parameter from to_json methods
BlockListed Sep 9, 2023
edcd264
cargo clippy and cargo fmt
BlockListed Sep 9, 2023
09c0367
re-add domain_origin field to configuration
BlockListed Sep 9, 2023
298cf8a
change back name of domain configuration option
BlockListed Sep 9, 2023
335984e
cargo clippy and cargo fmt
BlockListed Sep 9, 2023
d1cb726
fix bug when extracing host from domain
BlockListed Sep 9, 2023
6375a20
cargo clippy and cargo fmt
BlockListed Sep 9, 2023
aceaf61
switch back to admin_path, since cookies break otherwise
BlockListed Sep 9, 2023
fae770a
remove some outdated comments / move import
BlockListed Sep 9, 2023
c150818
rebase and fix rebase issues
BlockListed Mar 19, 2024
158f834
Merge branch 'main' into multiple-domains-support
BlackDex May 19, 2024
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
22 changes: 14 additions & 8 deletions src/api/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rocket::{
Catcher, Route,
};

use crate::auth::HostInfo;
use crate::{
api::{
core::{log_event, two_factor},
Expand Down Expand Up @@ -97,10 +98,6 @@ const BASE_TEMPLATE: &str = "admin/base";

const ACTING_ADMIN_USER: &str = "vaultwarden-admin-00000-000000000000";

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

#[derive(Debug)]
struct IpHeader(Option<String>);

Expand All @@ -123,8 +120,12 @@ impl<'r> FromRequest<'r> for IpHeader {
}
}

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

fn admin_url(origin: &str) -> String {
format!("{}{}", origin, admin_path())
}

#[derive(Responder)]
Expand Down Expand Up @@ -668,7 +669,12 @@ async fn get_ntp_time(has_http_access: bool) -> String {
}

#[get("/diagnostics")]
async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn) -> ApiResult<Html<String>> {
async fn diagnostics(
_token: AdminToken,
ip_header: IpHeader,
host_info: HostInfo,
mut conn: DbConn,
) -> ApiResult<Html<String>> {
use chrono::prelude::*;
use std::net::ToSocketAddrs;

Expand Down Expand Up @@ -721,7 +727,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn)
"uses_proxy": uses_proxy,
"db_type": *DB_TYPE,
"db_version": get_sql_server_version(&mut conn).await,
"admin_url": format!("{}/diagnostics", admin_url()),
"admin_url": format!("{}/diagnostics", admin_url(&host_info.origin)),
"overrides": &CONFIG.get_overrides().join(", "),
"host_arch": std::env::consts::ARCH,
"host_os": std::env::consts::OS,
Expand Down
20 changes: 11 additions & 9 deletions src/api/core/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
register_push_device, unregister_push_device, AnonymousNotify, EmptyResult, JsonResult, JsonUpcase, Notify,
PasswordOrOtpData, UpdateType,
},
auth::{decode_delete, decode_invite, decode_verify_email, ClientHeaders, Headers},
auth::{decode_delete, decode_invite, decode_verify_email, ClientHeaders, Headers, HostInfo},
crypto,
db::{models::*, DbConn},
mail,
Expand Down Expand Up @@ -1118,6 +1118,7 @@ struct AuthRequestRequest {
async fn post_auth_request(
data: Json<AuthRequestRequest>,
headers: ClientHeaders,
host_info: HostInfo,
mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
Expand Down Expand Up @@ -1152,13 +1153,13 @@ async fn post_auth_request(
"creationDate": auth_request.creation_date.and_utc(),
"responseDate": null,
"requestApproved": false,
"origin": CONFIG.domain_origin(),
"origin": host_info.origin,
"object": "auth-request"
})))
}

#[get("/auth-requests/<uuid>")]
async fn get_auth_request(uuid: &str, mut conn: DbConn) -> JsonResult {
async fn get_auth_request(uuid: &str, host_info: HostInfo, mut conn: DbConn) -> JsonResult {
let auth_request = match AuthRequest::find_by_uuid(uuid, &mut conn).await {
Some(auth_request) => auth_request,
None => {
Expand All @@ -1179,7 +1180,7 @@ async fn get_auth_request(uuid: &str, mut conn: DbConn) -> JsonResult {
"creationDate": auth_request.creation_date.and_utc(),
"responseDate": response_date_utc,
"requestApproved": auth_request.approved,
"origin": CONFIG.domain_origin(),
"origin": host_info.origin,
"object":"auth-request"
}
)))
Expand All @@ -1198,6 +1199,7 @@ struct AuthResponseRequest {
async fn put_auth_request(
uuid: &str,
data: Json<AuthResponseRequest>,
host_info: HostInfo,
mut conn: DbConn,
ant: AnonymousNotify<'_>,
nt: Notify<'_>,
Expand Down Expand Up @@ -1234,14 +1236,14 @@ async fn put_auth_request(
"creationDate": auth_request.creation_date.and_utc(),
"responseDate": response_date_utc,
"requestApproved": auth_request.approved,
"origin": CONFIG.domain_origin(),
"origin": host_info.origin,
"object":"auth-request"
}
)))
}

#[get("/auth-requests/<uuid>/response?<code>")]
async fn get_auth_request_response(uuid: &str, code: &str, mut conn: DbConn) -> JsonResult {
async fn get_auth_request_response(uuid: &str, code: &str, host_info: HostInfo, mut conn: DbConn) -> JsonResult {
let auth_request = match AuthRequest::find_by_uuid(uuid, &mut conn).await {
Some(auth_request) => auth_request,
None => {
Expand All @@ -1266,14 +1268,14 @@ async fn get_auth_request_response(uuid: &str, code: &str, mut conn: DbConn) ->
"creationDate": auth_request.creation_date.and_utc(),
"responseDate": response_date_utc,
"requestApproved": auth_request.approved,
"origin": CONFIG.domain_origin(),
"origin": host_info.origin,
"object":"auth-request"
}
)))
}

#[get("/auth-requests")]
async fn get_auth_requests(headers: Headers, mut conn: DbConn) -> JsonResult {
async fn get_auth_requests(headers: Headers, host_info: HostInfo, mut conn: DbConn) -> JsonResult {
let auth_requests = AuthRequest::find_by_user(&headers.user.uuid, &mut conn).await;

Ok(Json(json!({
Expand All @@ -1293,7 +1295,7 @@ async fn get_auth_requests(headers: Headers, mut conn: DbConn) -> JsonResult {
"creationDate": request.creation_date.and_utc(),
"responseDate": response_date_utc,
"requestApproved": request.approved,
"origin": CONFIG.domain_origin(),
"origin": host_info.origin,
"object":"auth-request"
})
}).collect::<Vec<Value>>(),
Expand Down
22 changes: 11 additions & 11 deletions src/api/core/ciphers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ async fn sync(data: SyncData, headers: Headers, mut conn: DbConn) -> Json<Value>
let mut ciphers_json = Vec::with_capacity(ciphers.len());
for c in ciphers {
ciphers_json.push(
c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), CipherSyncType::User, &mut conn)
c.to_json(&headers.base_url, &headers.user.uuid, Some(&cipher_sync_data), CipherSyncType::User, &mut conn)
.await,
);
}
Expand Down Expand Up @@ -161,7 +161,7 @@ async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
let mut ciphers_json = Vec::with_capacity(ciphers.len());
for c in ciphers {
ciphers_json.push(
c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), CipherSyncType::User, &mut conn)
c.to_json(&headers.base_url, &headers.user.uuid, Some(&cipher_sync_data), CipherSyncType::User, &mut conn)
.await,
);
}
Expand All @@ -184,7 +184,7 @@ async fn get_cipher(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResul
err!("Cipher is not owned by user")
}

Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
Ok(Json(cipher.to_json(&headers.base_url, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
}

#[get("/ciphers/<uuid>/admin")]
Expand Down Expand Up @@ -324,7 +324,7 @@ async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, mut conn:
let mut cipher = Cipher::new(data.Type, data.Name.clone());
update_cipher_from_data(&mut cipher, data, &headers, None, &mut conn, &nt, UpdateType::SyncCipherCreate).await?;

Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
Ok(Json(cipher.to_json(&headers.base_url, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
}

/// Enforces the personal ownership policy on user-owned ciphers, if applicable.
Expand Down Expand Up @@ -658,7 +658,7 @@ async fn put_cipher(

update_cipher_from_data(&mut cipher, data, &headers, None, &mut conn, &nt, UpdateType::SyncCipherUpdate).await?;

Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
Ok(Json(cipher.to_json(&headers.base_url, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
}

#[post("/ciphers/<uuid>/partial", data = "<data>")]
Expand Down Expand Up @@ -702,7 +702,7 @@ async fn put_cipher_partial(
// Update favorite
cipher.set_favorite(Some(data.Favorite), &headers.user.uuid, &mut conn).await?;

Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
Ok(Json(cipher.to_json(&headers.base_url, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -933,7 +933,7 @@ async fn share_cipher_by_uuid(

update_cipher_from_data(&mut cipher, data.Cipher, headers, Some(shared_to_collections), conn, nt, ut).await?;

Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, conn).await))
Ok(Json(cipher.to_json(&headers.base_url, &headers.user.uuid, None, CipherSyncType::User, conn).await))
}

/// v2 API for downloading an attachment. This just redirects the client to
Expand All @@ -954,7 +954,7 @@ async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut c
}

match Attachment::find_by_id(attachment_id, &mut conn).await {
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.base_url))),
Some(_) => err!("Attachment doesn't belong to cipher"),
None => err!("Attachment doesn't exist"),
}
Expand Down Expand Up @@ -1016,7 +1016,7 @@ async fn post_attachment_v2(
"AttachmentId": attachment_id,
"Url": url,
"FileUploadType": FileUploadType::Direct as i32,
response_key: cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await,
response_key: cipher.to_json(&headers.base_url, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await,
})))
}

Expand Down Expand Up @@ -1243,7 +1243,7 @@ async fn post_attachment(

let (cipher, mut conn) = save_attachment(attachment, uuid, data, &headers, conn, nt).await?;

Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
Ok(Json(cipher.to_json(&headers.base_url, &headers.user.uuid, None, CipherSyncType::User, &mut conn).await))
}

#[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")]
Expand Down Expand Up @@ -1677,7 +1677,7 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbCon
.await;
}

Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, conn).await))
Ok(Json(cipher.to_json(&headers.base_url, &headers.user.uuid, None, CipherSyncType::User, conn).await))
}

async fn _restore_multiple_ciphers(
Expand Down
2 changes: 1 addition & 1 deletion src/api/core/emergency_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ async fn view_emergency_access(emer_id: &str, headers: Headers, mut conn: DbConn
for c in ciphers {
ciphers_json.push(
c.to_json(
&headers.host,
&headers.base_url,
&emergency_access.grantor_uuid,
Some(&cipher_sync_data),
CipherSyncType::User,
Expand Down
3 changes: 2 additions & 1 deletion src/api/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ fn version() -> Json<&'static str> {

#[get("/config")]
fn config() -> Json<Value> {
let domain = crate::CONFIG.domain();
// TODO: maybe this should be extracted from the current request params
let domain = crate::CONFIG.main_domain();
let mut feature_states =
parse_experimental_client_feature_flags(&crate::CONFIG.experimental_client_feature_flags());
// Force the new key rotation feature
Expand Down
10 changes: 5 additions & 5 deletions src/api/core/organizations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -749,20 +749,20 @@ struct OrgIdData {
#[get("/ciphers/organization-details?<data..>")]
async fn get_org_details(data: OrgIdData, headers: Headers, mut conn: DbConn) -> Json<Value> {
Json(json!({
"Data": _get_org_details(&data.organization_id, &headers.host, &headers.user.uuid, &mut conn).await,
"Data": _get_org_details(&data.organization_id, &headers.base_url, &headers.user.uuid, &mut conn).await,
"Object": "list",
"ContinuationToken": null,
}))
}

async fn _get_org_details(org_id: &str, host: &str, user_uuid: &str, conn: &mut DbConn) -> Value {
async fn _get_org_details(org_id: &str, base_url: &str, user_uuid: &str, conn: &mut DbConn) -> Value {
let ciphers = Cipher::find_by_org(org_id, conn).await;
let cipher_sync_data = CipherSyncData::new(user_uuid, CipherSyncType::Organization, conn).await;

let mut ciphers_json = Vec::with_capacity(ciphers.len());
for c in ciphers {
ciphers_json
.push(c.to_json(host, user_uuid, Some(&cipher_sync_data), CipherSyncType::Organization, conn).await);
.push(c.to_json(base_url, user_uuid, Some(&cipher_sync_data), CipherSyncType::Organization, conn).await);
}
json!(ciphers_json)
}
Expand Down Expand Up @@ -2906,7 +2906,7 @@ async fn get_org_export(org_id: &str, headers: AdminHeaders, mut conn: DbConn) -
"continuationToken": null,
},
"ciphers": {
"data": convert_json_key_lcase_first(_get_org_details(org_id, &headers.host, &headers.user.uuid, &mut conn).await),
"data": convert_json_key_lcase_first(_get_org_details(org_id, &headers.base_url, &headers.user.uuid, &mut conn).await),
"object": "list",
"continuationToken": null,
}
Expand All @@ -2915,7 +2915,7 @@ async fn get_org_export(org_id: &str, headers: AdminHeaders, mut conn: DbConn) -
// v2023.1.0 and newer response
Json(json!({
"collections": convert_json_key_lcase_first(_get_org_collections(org_id, &mut conn).await),
"ciphers": convert_json_key_lcase_first(_get_org_details(org_id, &headers.host, &headers.user.uuid, &mut conn).await),
"ciphers": convert_json_key_lcase_first(_get_org_details(org_id, &headers.base_url, &headers.user.uuid, &mut conn).await),
}))
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/api/core/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,13 @@ impl<'r> FromRequest<'r> for PublicToken {
err_handler!("Token expired");
}
// Check if claims.iss is host|claims.scope[0]
let host = match auth::Host::from_request(request).await {
Outcome::Success(host) => host,
let host_info = match auth::HostInfo::from_request(request).await {
Outcome::Success(host_info) => host_info,
_ => err_handler!("Error getting Host"),
};
let complete_host = format!("{}|{}", host.host, claims.scope[0]);
// TODO check if this is fine
// using origin, because that's what they're generated with in auth.rs
let complete_host = format!("{}|{}", host_info.origin, claims.scope[0]);
if complete_host != claims.iss {
err_handler!("Token not issued by this server");
}
Expand Down
6 changes: 3 additions & 3 deletions src/api/core/sends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use serde_json::Value;

use crate::{
api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, Notify, UpdateType},
auth::{ClientIp, Headers, Host},
auth::{ClientIp, Headers, HostInfo},
db::{models::*, DbConn, DbPool},
util::{NumberOrString, SafeString},
CONFIG,
Expand Down Expand Up @@ -465,7 +465,7 @@ async fn post_access_file(
send_id: &str,
file_id: &str,
data: JsonUpcase<SendAccessData>,
host: Host,
host_info: HostInfo,
mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
Expand Down Expand Up @@ -520,7 +520,7 @@ async fn post_access_file(
Ok(Json(json!({
"Object": "send-fileDownload",
"Id": file_id,
"Url": format!("{}/api/sends/{}/{}?t={}", &host.host, send_id, file_id, token)
"Url": format!("{}/api/sends/{}/{}?t={}", &host_info.base_url, send_id, file_id, token)
})))
}

Expand Down
Loading
Loading