Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2024-11-22 05:10:29 +01:00
Modify rustfmt file
Dieser Commit ist enthalten in:
Ursprung
93c881a7a9
Commit
3ab90259f2
30 geänderte Dateien mit 169 neuen und 395 gelöschten Zeilen
|
@ -1,2 +1,7 @@
|
||||||
version = "Two"
|
version = "Two"
|
||||||
|
edition = "2018"
|
||||||
max_width = 120
|
max_width = 120
|
||||||
|
newline_style = "Unix"
|
||||||
|
use_small_heuristics = "Off"
|
||||||
|
struct_lit_single_line = false
|
||||||
|
overflow_delimited_expr = true
|
||||||
|
|
|
@ -65,9 +65,7 @@ static DB_TYPE: Lazy<&str> = Lazy::new(|| {
|
||||||
});
|
});
|
||||||
|
|
||||||
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| {
|
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| {
|
||||||
DbConnType::from_url(&CONFIG.database_url())
|
DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::sqlite).unwrap_or(false)
|
||||||
.map(|t| t == DbConnType::sqlite)
|
|
||||||
.unwrap_or(false)
|
|
||||||
&& Command::new("sqlite3").arg("-version").status().is_ok()
|
&& Command::new("sqlite3").arg("-version").status().is_ok()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -171,10 +169,7 @@ fn post_admin_login(
|
||||||
// If the token is invalid, redirect to login page
|
// If the token is invalid, redirect to login page
|
||||||
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_url(referer)), "Invalid admin token, please try again."))
|
||||||
Redirect::to(admin_url(referer)),
|
|
||||||
"Invalid admin token, please try again.",
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
// If the token received is valid, generate JWT and save it as a cookie
|
// If the token received is valid, generate JWT and save it as a cookie
|
||||||
let claims = generate_admin_claims();
|
let claims = generate_admin_claims();
|
||||||
|
|
|
@ -320,15 +320,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt:
|
||||||
err!("The cipher is not owned by the user")
|
err!("The cipher is not owned by the user")
|
||||||
}
|
}
|
||||||
|
|
||||||
update_cipher_from_data(
|
update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::CipherUpdate)?
|
||||||
&mut saved_cipher,
|
|
||||||
cipher_data,
|
|
||||||
&headers,
|
|
||||||
false,
|
|
||||||
&conn,
|
|
||||||
&nt,
|
|
||||||
UpdateType::CipherUpdate,
|
|
||||||
)?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update user data
|
// Update user data
|
||||||
|
|
|
@ -91,19 +91,15 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect();
|
let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect();
|
||||||
|
|
||||||
let collections = Collection::find_by_user_uuid(&headers.user.uuid, &conn);
|
let collections = Collection::find_by_user_uuid(&headers.user.uuid, &conn);
|
||||||
let collections_json: Vec<Value> = collections
|
let collections_json: Vec<Value> =
|
||||||
.iter()
|
collections.iter().map(|c| c.to_json_details(&headers.user.uuid, &conn)).collect();
|
||||||
.map(|c| c.to_json_details(&headers.user.uuid, &conn))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let policies = OrgPolicy::find_by_user(&headers.user.uuid, &conn);
|
let policies = OrgPolicy::find_by_user(&headers.user.uuid, &conn);
|
||||||
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
|
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
|
||||||
|
|
||||||
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn);
|
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn);
|
||||||
let ciphers_json: Vec<Value> = ciphers
|
let ciphers_json: Vec<Value> =
|
||||||
.iter()
|
ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
|
||||||
.map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let sends = Send::find_by_user(&headers.user.uuid, &conn);
|
let sends = Send::find_by_user(&headers.user.uuid, &conn);
|
||||||
let sends_json: Vec<Value> = sends.iter().map(|s| s.to_json()).collect();
|
let sends_json: Vec<Value> = sends.iter().map(|s| s.to_json()).collect();
|
||||||
|
@ -130,10 +126,8 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> {
|
fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn);
|
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn);
|
||||||
|
|
||||||
let ciphers_json: Vec<Value> = ciphers
|
let ciphers_json: Vec<Value> =
|
||||||
.iter()
|
ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
|
||||||
.map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"Data": ciphers_json,
|
"Data": ciphers_json,
|
||||||
|
@ -583,11 +577,8 @@ fn post_collections_admin(
|
||||||
}
|
}
|
||||||
|
|
||||||
let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect();
|
let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect();
|
||||||
let current_collections: HashSet<String> = cipher
|
let current_collections: HashSet<String> =
|
||||||
.get_collections(&headers.user.uuid, &conn)
|
cipher.get_collections(&headers.user.uuid, &conn).iter().cloned().collect();
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for collection in posted_collections.symmetric_difference(¤t_collections) {
|
for collection in posted_collections.symmetric_difference(¤t_collections) {
|
||||||
match Collection::find_by_uuid(&collection, &conn) {
|
match Collection::find_by_uuid(&collection, &conn) {
|
||||||
|
@ -823,30 +814,25 @@ fn post_attachment(
|
||||||
let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10]));
|
let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10]));
|
||||||
let path = base_path.join(&file_name);
|
let path = base_path.join(&file_name);
|
||||||
|
|
||||||
let size = match field
|
let size =
|
||||||
.data
|
match field.data.save().memory_threshold(0).size_limit(size_limit).with_path(path.clone()) {
|
||||||
.save()
|
SaveResult::Full(SavedData::File(_, size)) => size as i32,
|
||||||
.memory_threshold(0)
|
SaveResult::Full(other) => {
|
||||||
.size_limit(size_limit)
|
std::fs::remove_file(path).ok();
|
||||||
.with_path(path.clone())
|
error = Some(format!("Attachment is not a file: {:?}", other));
|
||||||
{
|
return;
|
||||||
SaveResult::Full(SavedData::File(_, size)) => size as i32,
|
}
|
||||||
SaveResult::Full(other) => {
|
SaveResult::Partial(_, reason) => {
|
||||||
std::fs::remove_file(path).ok();
|
std::fs::remove_file(path).ok();
|
||||||
error = Some(format!("Attachment is not a file: {:?}", other));
|
error = Some(format!("Attachment size limit exceeded with this file: {:?}", reason));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SaveResult::Partial(_, reason) => {
|
SaveResult::Error(e) => {
|
||||||
std::fs::remove_file(path).ok();
|
std::fs::remove_file(path).ok();
|
||||||
error = Some(format!("Attachment size limit exceeded with this file: {:?}", reason));
|
error = Some(format!("Error: {:?}", e));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SaveResult::Error(e) => {
|
};
|
||||||
std::fs::remove_file(path).ok();
|
|
||||||
error = Some(format!("Error: {:?}", e));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size);
|
let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size);
|
||||||
attachment.akey = attachment_key.clone();
|
attachment.akey = attachment_key.clone();
|
||||||
|
@ -878,11 +864,7 @@ fn post_attachment_admin(
|
||||||
post_attachment(uuid, data, content_type, headers, conn, nt)
|
post_attachment(uuid, data, content_type, headers, conn, nt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post(
|
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
|
||||||
"/ciphers/<uuid>/attachment/<attachment_id>/share",
|
|
||||||
format = "multipart/form-data",
|
|
||||||
data = "<data>"
|
|
||||||
)]
|
|
||||||
fn post_attachment_share(
|
fn post_attachment_share(
|
||||||
uuid: String,
|
uuid: String,
|
||||||
attachment_id: String,
|
attachment_id: String,
|
||||||
|
|
|
@ -8,15 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn routes() -> Vec<rocket::Route> {
|
pub fn routes() -> Vec<rocket::Route> {
|
||||||
routes![
|
routes![get_folders, get_folder, post_folders, post_folder, put_folder, delete_folder_post, delete_folder,]
|
||||||
get_folders,
|
|
||||||
get_folder,
|
|
||||||
post_folders,
|
|
||||||
post_folder,
|
|
||||||
put_folder,
|
|
||||||
delete_folder_post,
|
|
||||||
delete_folder,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/folders")]
|
#[get("/folders")]
|
||||||
|
|
|
@ -8,14 +8,8 @@ pub mod two_factor;
|
||||||
pub use sends::start_send_deletion_scheduler;
|
pub use sends::start_send_deletion_scheduler;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
let mut mod_routes = routes![
|
let mut mod_routes =
|
||||||
clear_device_token,
|
routes![clear_device_token, put_device_token, get_eq_domains, post_eq_domains, put_eq_domains, hibp_breach,];
|
||||||
put_device_token,
|
|
||||||
get_eq_domains,
|
|
||||||
post_eq_domains,
|
|
||||||
put_eq_domains,
|
|
||||||
hibp_breach,
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut routes = Vec::new();
|
let mut routes = Vec::new();
|
||||||
routes.append(&mut accounts::routes());
|
routes.append(&mut accounts::routes());
|
||||||
|
@ -157,11 +151,7 @@ fn hibp_breach(username: String) -> JsonResult {
|
||||||
if let Some(api_key) = crate::CONFIG.hibp_api_key() {
|
if let Some(api_key) = crate::CONFIG.hibp_api_key() {
|
||||||
let hibp_client = Client::builder().build()?;
|
let hibp_client = Client::builder().build()?;
|
||||||
|
|
||||||
let res = hibp_client
|
let res = hibp_client.get(&url).header(USER_AGENT, user_agent).header("hibp-api-key", api_key).send()?;
|
||||||
.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 we get a 404, return a 404, it means no breached accounts
|
||||||
if res.status() == 404 {
|
if res.status() == 404 {
|
||||||
|
|
|
@ -446,10 +446,8 @@ struct OrgIdData {
|
||||||
#[get("/ciphers/organization-details?<data..>")]
|
#[get("/ciphers/organization-details?<data..>")]
|
||||||
fn get_org_details(data: Form<OrgIdData>, headers: Headers, conn: DbConn) -> Json<Value> {
|
fn get_org_details(data: Form<OrgIdData>, headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
let ciphers = Cipher::find_by_org(&data.organization_id, &conn);
|
let ciphers = Cipher::find_by_org(&data.organization_id, &conn);
|
||||||
let ciphers_json: Vec<Value> = ciphers
|
let ciphers_json: Vec<Value> =
|
||||||
.iter()
|
ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
|
||||||
.map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"Data": ciphers_json,
|
"Data": ciphers_json,
|
||||||
|
@ -904,16 +902,8 @@ fn post_org_import(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|cipher_data| {
|
.map(|cipher_data| {
|
||||||
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
|
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
|
||||||
update_cipher_from_data(
|
update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::CipherCreate)
|
||||||
&mut cipher,
|
.ok();
|
||||||
cipher_data,
|
|
||||||
&headers,
|
|
||||||
false,
|
|
||||||
&conn,
|
|
||||||
&nt,
|
|
||||||
UpdateType::CipherCreate,
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
cipher
|
cipher
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -16,15 +16,7 @@ use crate::{
|
||||||
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
|
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
|
||||||
|
|
||||||
pub fn routes() -> Vec<rocket::Route> {
|
pub fn routes() -> Vec<rocket::Route> {
|
||||||
routes![
|
routes![post_send, post_send_file, post_access, post_access_file, put_send, delete_send, put_remove_password]
|
||||||
post_send,
|
|
||||||
post_send_file,
|
|
||||||
post_access,
|
|
||||||
post_access_file,
|
|
||||||
put_send,
|
|
||||||
delete_send,
|
|
||||||
put_remove_password
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_send_deletion_scheduler(pool: crate::db::DbPool) {
|
pub fn start_send_deletion_scheduler(pool: crate::db::DbPool) {
|
||||||
|
@ -179,13 +171,7 @@ fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn
|
||||||
None => err!("No model entry present"),
|
None => err!("No model entry present"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = match data_entry
|
let size = match data_entry.data.save().memory_threshold(0).size_limit(size_limit).with_path(&file_path) {
|
||||||
.data
|
|
||||||
.save()
|
|
||||||
.memory_threshold(0)
|
|
||||||
.size_limit(size_limit)
|
|
||||||
.with_path(&file_path)
|
|
||||||
{
|
|
||||||
SaveResult::Full(SavedData::File(_, size)) => size as i32,
|
SaveResult::Full(SavedData::File(_, size)) => size as i32,
|
||||||
SaveResult::Full(other) => {
|
SaveResult::Full(other) => {
|
||||||
std::fs::remove_file(&file_path).ok();
|
std::fs::remove_file(&file_path).ok();
|
||||||
|
@ -206,10 +192,7 @@ fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn
|
||||||
if let Some(o) = data_value.as_object_mut() {
|
if let Some(o) = data_value.as_object_mut() {
|
||||||
o.insert(String::from("Id"), Value::String(file_id));
|
o.insert(String::from("Id"), Value::String(file_id));
|
||||||
o.insert(String::from("Size"), Value::Number(size.into()));
|
o.insert(String::from("Size"), Value::Number(size.into()));
|
||||||
o.insert(
|
o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(size)));
|
||||||
String::from("SizeName"),
|
|
||||||
Value::String(crate::util::get_display_size(size)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
send.data = serde_json::to_string(&data_value)?;
|
send.data = serde_json::to_string(&data_value)?;
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,7 @@ use crate::{
|
||||||
pub use crate::config::CONFIG;
|
pub use crate::config::CONFIG;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
routes![generate_authenticator, activate_authenticator, activate_authenticator_put,]
|
||||||
generate_authenticator,
|
|
||||||
activate_authenticator,
|
|
||||||
activate_authenticator_put,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/two-factor/get-authenticator", data = "<data>")]
|
#[post("/two-factor/get-authenticator", data = "<data>")]
|
||||||
|
@ -163,22 +159,11 @@ pub fn validate_totp_code(user_uuid: &str, totp_code: u64, secret: &str, ip: &Cl
|
||||||
twofactor.save(&conn)?;
|
twofactor.save(&conn)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if generated == totp_code && time_step <= twofactor.last_used as i64 {
|
} else if generated == totp_code && time_step <= twofactor.last_used as i64 {
|
||||||
warn!(
|
warn!("This or a TOTP code within {} steps back and forward has already been used!", steps);
|
||||||
"This or a TOTP code within {} steps back and forward has already been used!",
|
err!(format!("Invalid TOTP code! Server time: {} IP: {}", current_time.format("%F %T UTC"), ip.ip));
|
||||||
steps
|
|
||||||
);
|
|
||||||
err!(format!(
|
|
||||||
"Invalid TOTP code! Server time: {} IP: {}",
|
|
||||||
current_time.format("%F %T UTC"),
|
|
||||||
ip.ip
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else no valide code received, deny access
|
// Else no valide code received, deny access
|
||||||
err!(format!(
|
err!(format!("Invalid TOTP code! Server time: {} IP: {}", current_time.format("%F %T UTC"), ip.ip));
|
||||||
"Invalid TOTP code! Server time: {} IP: {}",
|
|
||||||
current_time.format("%F %T UTC"),
|
|
||||||
ip.ip
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,11 @@ impl DuoData {
|
||||||
ik.replace_range(digits.., replaced);
|
ik.replace_range(digits.., replaced);
|
||||||
sk.replace_range(digits.., replaced);
|
sk.replace_range(digits.., replaced);
|
||||||
|
|
||||||
Self { host, ik, sk }
|
Self {
|
||||||
|
host,
|
||||||
|
ik,
|
||||||
|
sk,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,10 +65,7 @@ pub fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
twofactor.data = twofactor_data.to_json();
|
twofactor.data = twofactor_data.to_json();
|
||||||
twofactor.save(&conn)?;
|
twofactor.save(&conn)?;
|
||||||
|
|
||||||
mail::send_token(
|
mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?;
|
||||||
&twofactor_data.email,
|
|
||||||
&twofactor_data.last_token.map_res("Token is empty")?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -128,17 +125,10 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -
|
||||||
let twofactor_data = EmailTokenData::new(data.Email, generated_token);
|
let twofactor_data = EmailTokenData::new(data.Email, generated_token);
|
||||||
|
|
||||||
// Uses EmailVerificationChallenge as type to show that it's not verified yet.
|
// Uses EmailVerificationChallenge as type to show that it's not verified yet.
|
||||||
let twofactor = TwoFactor::new(
|
let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json());
|
||||||
user.uuid,
|
|
||||||
TwoFactorType::EmailVerificationChallenge,
|
|
||||||
twofactor_data.to_json(),
|
|
||||||
);
|
|
||||||
twofactor.save(&conn)?;
|
twofactor.save(&conn)?;
|
||||||
|
|
||||||
mail::send_token(
|
mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?;
|
||||||
&twofactor_data.email,
|
|
||||||
&twofactor_data.last_token.map_res("Token is empty")?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,7 @@ pub mod u2f;
|
||||||
pub mod yubikey;
|
pub mod yubikey;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
let mut routes = routes![
|
let mut routes = routes![get_twofactor, get_recover, recover, disable_twofactor, disable_twofactor_put,];
|
||||||
get_twofactor,
|
|
||||||
get_recover,
|
|
||||||
recover,
|
|
||||||
disable_twofactor,
|
|
||||||
disable_twofactor_put,
|
|
||||||
];
|
|
||||||
|
|
||||||
routes.append(&mut authenticator::routes());
|
routes.append(&mut authenticator::routes());
|
||||||
routes.append(&mut duo::routes());
|
routes.append(&mut duo::routes());
|
||||||
|
|
|
@ -28,13 +28,7 @@ static APP_ID: Lazy<String> = Lazy::new(|| format!("{}/app-id.json", &CONFIG.dom
|
||||||
static U2F: Lazy<U2f> = Lazy::new(|| U2f::new(APP_ID.clone()));
|
static U2F: Lazy<U2f> = Lazy::new(|| U2f::new(APP_ID.clone()));
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
routes![generate_u2f, generate_u2f_challenge, activate_u2f, activate_u2f_put, delete_u2f,]
|
||||||
generate_u2f,
|
|
||||||
generate_u2f_challenge,
|
|
||||||
activate_u2f,
|
|
||||||
activate_u2f_put,
|
|
||||||
delete_u2f,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/two-factor/get-u2f", data = "<data>")]
|
#[post("/two-factor/get-u2f", data = "<data>")]
|
||||||
|
@ -161,10 +155,7 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn)
|
||||||
|
|
||||||
let response: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?;
|
let response: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?;
|
||||||
|
|
||||||
let error_code = response
|
let error_code = response.error_code.clone().map_or("0".into(), NumberOrString::into_string);
|
||||||
.error_code
|
|
||||||
.clone()
|
|
||||||
.map_or("0".into(), NumberOrString::into_string);
|
|
||||||
|
|
||||||
if error_code != "0" {
|
if error_code != "0" {
|
||||||
err!("Error registering U2F token")
|
err!("Error registering U2F token")
|
||||||
|
@ -300,20 +291,13 @@ fn _old_parse_registrations(registations: &str) -> Vec<Registration> {
|
||||||
|
|
||||||
let regs: Vec<Value> = serde_json::from_str(registations).expect("Can't parse Registration data");
|
let regs: Vec<Value> = serde_json::from_str(registations).expect("Can't parse Registration data");
|
||||||
|
|
||||||
regs.into_iter()
|
regs.into_iter().map(|r| serde_json::from_value(r).unwrap()).map(|Helper(r)| r).collect()
|
||||||
.map(|r| serde_json::from_value(r).unwrap())
|
|
||||||
.map(|Helper(r)| r)
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRequest> {
|
pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRequest> {
|
||||||
let challenge = _create_u2f_challenge(user_uuid, TwoFactorType::U2fLoginChallenge, conn);
|
let challenge = _create_u2f_challenge(user_uuid, TwoFactorType::U2fLoginChallenge, conn);
|
||||||
|
|
||||||
let registrations: Vec<_> = get_u2f_registrations(user_uuid, conn)?
|
let registrations: Vec<_> = get_u2f_registrations(user_uuid, conn)?.1.into_iter().map(|r| r.reg).collect();
|
||||||
.1
|
|
||||||
.into_iter()
|
|
||||||
.map(|r| r.reg)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if registrations.is_empty() {
|
if registrations.is_empty() {
|
||||||
err!("No U2F devices registered")
|
err!("No U2F devices registered")
|
||||||
|
|
|
@ -22,10 +22,7 @@ static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||||
// Generate the default headers
|
// Generate the default headers
|
||||||
let mut default_headers = header::HeaderMap::new();
|
let mut default_headers = header::HeaderMap::new();
|
||||||
default_headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15"));
|
default_headers.insert(header::USER_AGENT, header::HeaderValue::from_static("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15"));
|
||||||
default_headers.insert(
|
default_headers.insert(header::ACCEPT_LANGUAGE, header::HeaderValue::from_static("en-US,en;q=0.8"));
|
||||||
header::ACCEPT_LANGUAGE,
|
|
||||||
header::HeaderValue::from_static("en-US,en;q=0.8"),
|
|
||||||
);
|
|
||||||
default_headers.insert(header::CACHE_CONTROL, header::HeaderValue::from_static("no-cache"));
|
default_headers.insert(header::CACHE_CONTROL, header::HeaderValue::from_static("no-cache"));
|
||||||
default_headers.insert(header::PRAGMA, header::HeaderValue::from_static("no-cache"));
|
default_headers.insert(header::PRAGMA, header::HeaderValue::from_static("no-cache"));
|
||||||
default_headers.insert(
|
default_headers.insert(
|
||||||
|
@ -64,10 +61,7 @@ fn icon(domain: String) -> Cached<Content<Vec<u8>>> {
|
||||||
|
|
||||||
match get_icon(&domain) {
|
match get_icon(&domain) {
|
||||||
Some(i) => Cached::ttl(Content(ContentType::new("image", "x-icon"), i), CONFIG.icon_cache_ttl()),
|
Some(i) => Cached::ttl(Content(ContentType::new("image", "x-icon"), i), CONFIG.icon_cache_ttl()),
|
||||||
_ => Cached::ttl(
|
_ => Cached::ttl(Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()), CONFIG.icon_cache_negttl()),
|
||||||
Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
|
|
||||||
CONFIG.icon_cache_negttl(),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,10 +94,7 @@ fn is_valid_domain(domain: &str) -> bool {
|
||||||
|
|
||||||
for c in domain.chars() {
|
for c in domain.chars() {
|
||||||
if !c.is_alphanumeric() && !ALLOWED_CHARS.contains(c) {
|
if !c.is_alphanumeric() && !ALLOWED_CHARS.contains(c) {
|
||||||
debug!(
|
debug!("Domain validation error: '{}' contains an invalid character '{}'", domain, c);
|
||||||
"Domain validation error: '{}' contains an invalid character '{}'",
|
|
||||||
domain, c
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,12 +343,20 @@ struct Icon {
|
||||||
|
|
||||||
impl Icon {
|
impl Icon {
|
||||||
const fn new(priority: u8, href: String) -> Self {
|
const fn new(priority: u8, href: String) -> Self {
|
||||||
Self { href, priority }
|
Self {
|
||||||
|
href,
|
||||||
|
priority,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_favicons_node(node: &std::rc::Rc<markup5ever_rcdom::Node>, icons: &mut Vec<Icon>, url: &Url) {
|
fn get_favicons_node(node: &std::rc::Rc<markup5ever_rcdom::Node>, icons: &mut Vec<Icon>, url: &Url) {
|
||||||
if let markup5ever_rcdom::NodeData::Element { name, attrs, .. } = &node.data {
|
if let markup5ever_rcdom::NodeData::Element {
|
||||||
|
name,
|
||||||
|
attrs,
|
||||||
|
..
|
||||||
|
} = &node.data
|
||||||
|
{
|
||||||
if name.local.as_ref() == "link" {
|
if name.local.as_ref() == "link" {
|
||||||
let mut has_rel = false;
|
let mut has_rel = false;
|
||||||
let mut href = None;
|
let mut href = None;
|
||||||
|
|
|
@ -87,27 +87,18 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult
|
||||||
let username = data.username.as_ref().unwrap();
|
let username = data.username.as_ref().unwrap();
|
||||||
let user = match User::find_by_mail(username, &conn) {
|
let user = match User::find_by_mail(username, &conn) {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => err!(
|
None => err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username)),
|
||||||
"Username or password is incorrect. Try again",
|
|
||||||
format!("IP: {}. Username: {}.", ip.ip, username)
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check password
|
// Check password
|
||||||
let password = data.password.as_ref().unwrap();
|
let password = data.password.as_ref().unwrap();
|
||||||
if !user.check_valid_password(password) {
|
if !user.check_valid_password(password) {
|
||||||
err!(
|
err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username))
|
||||||
"Username or password is incorrect. Try again",
|
|
||||||
format!("IP: {}. Username: {}.", ip.ip, username)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user is disabled
|
// Check if the user is disabled
|
||||||
if !user.enabled {
|
if !user.enabled {
|
||||||
err!(
|
err!("This user has been disabled", format!("IP: {}. Username: {}.", ip.ip, username))
|
||||||
"This user has been disabled",
|
|
||||||
format!("IP: {}. Username: {}.", ip.ip, username)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
|
@ -137,10 +128,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult
|
||||||
}
|
}
|
||||||
|
|
||||||
// We still want the login to fail until they actually verified the email address
|
// We still want the login to fail until they actually verified the email address
|
||||||
err!(
|
err!("Please verify your email before trying again.", format!("IP: {}. Username: {}.", ip.ip, username))
|
||||||
"Please verify your email before trying again.",
|
|
||||||
format!("IP: {}. Username: {}.", ip.ip, username)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut device, new_device) = get_device(&data, &conn, &user);
|
let (mut device, new_device) = get_device(&data, &conn, &user);
|
||||||
|
@ -234,10 +222,7 @@ fn twofactor_auth(
|
||||||
|
|
||||||
let twofactor_code = match data.two_factor_token {
|
let twofactor_code = match data.two_factor_token {
|
||||||
Some(ref code) => code,
|
Some(ref code) => code,
|
||||||
None => err_json!(
|
None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA token not provided"),
|
||||||
_json_err_twofactor(&twofactor_ids, user_uuid, conn)?,
|
|
||||||
"2FA token not provided"
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let selected_twofactor = twofactors.into_iter().find(|tf| tf.atype == selected_id && tf.enabled);
|
let selected_twofactor = twofactors.into_iter().find(|tf| tf.atype == selected_id && tf.enabled);
|
||||||
|
@ -266,10 +251,9 @@ fn twofactor_auth(
|
||||||
Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => {
|
Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => {
|
||||||
remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time
|
remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time
|
||||||
}
|
}
|
||||||
_ => err_json!(
|
_ => {
|
||||||
_json_err_twofactor(&twofactor_ids, user_uuid, conn)?,
|
err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA Remember token not provided")
|
||||||
"2FA Remember token not provided"
|
}
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => err!("Invalid two factor provider"),
|
_ => err!("Invalid two factor provider"),
|
||||||
|
|
|
@ -54,9 +54,9 @@ impl NumberOrString {
|
||||||
use std::num::ParseIntError as PIE;
|
use std::num::ParseIntError as PIE;
|
||||||
match self {
|
match self {
|
||||||
NumberOrString::Number(n) => Ok(n),
|
NumberOrString::Number(n) => Ok(n),
|
||||||
NumberOrString::String(s) => s
|
NumberOrString::String(s) => {
|
||||||
.parse()
|
s.parse().map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string()))
|
||||||
.map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string())),
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,7 @@ static SHOW_WEBSOCKETS_MSG: AtomicBool = AtomicBool::new(true);
|
||||||
#[get("/hub")]
|
#[get("/hub")]
|
||||||
fn websockets_err() -> EmptyResult {
|
fn websockets_err() -> EmptyResult {
|
||||||
if CONFIG.websocket_enabled()
|
if CONFIG.websocket_enabled()
|
||||||
&& SHOW_WEBSOCKETS_MSG
|
&& SHOW_WEBSOCKETS_MSG.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed).is_ok()
|
||||||
.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed)
|
|
||||||
.is_ok()
|
|
||||||
{
|
{
|
||||||
err!(
|
err!(
|
||||||
"
|
"
|
||||||
|
@ -205,9 +203,7 @@ impl Handler for WsHandler {
|
||||||
let handler_insert = self.out.clone();
|
let handler_insert = self.out.clone();
|
||||||
let handler_update = self.out.clone();
|
let handler_update = self.out.clone();
|
||||||
|
|
||||||
self.users
|
self.users.map.upsert(user_uuid, || vec![handler_insert], |ref mut v| v.push(handler_update));
|
||||||
.map
|
|
||||||
.upsert(user_uuid, || vec![handler_insert], |ref mut v| v.push(handler_update));
|
|
||||||
|
|
||||||
// Schedule a ping to keep the connection alive
|
// Schedule a ping to keep the connection alive
|
||||||
self.out.timeout(PING_MS, PING)
|
self.out.timeout(PING_MS, PING)
|
||||||
|
@ -217,7 +213,11 @@ impl Handler for WsHandler {
|
||||||
if let Message::Text(text) = msg.clone() {
|
if let Message::Text(text) = msg.clone() {
|
||||||
let json = &text[..text.len() - 1]; // Remove last char
|
let json = &text[..text.len() - 1]; // Remove last char
|
||||||
|
|
||||||
if let Ok(InitialMessage { protocol, version }) = from_str::<InitialMessage>(json) {
|
if let Ok(InitialMessage {
|
||||||
|
protocol,
|
||||||
|
version,
|
||||||
|
}) = from_str::<InitialMessage>(json)
|
||||||
|
{
|
||||||
if &protocol == "messagepack" && version == 1 {
|
if &protocol == "messagepack" && version == 1 {
|
||||||
return self.out.send(&INITIAL_RESPONSE[..]); // Respond to initial message
|
return self.out.send(&INITIAL_RESPONSE[..]); // Respond to initial message
|
||||||
}
|
}
|
||||||
|
@ -296,10 +296,7 @@ impl WebSocketUsers {
|
||||||
// NOTE: The last modified date needs to be updated before calling these methods
|
// NOTE: The last modified date needs to be updated before calling these methods
|
||||||
pub fn send_user_update(&self, ut: UpdateType, user: &User) {
|
pub fn send_user_update(&self, ut: UpdateType, user: &User) {
|
||||||
let data = create_update(
|
let data = create_update(
|
||||||
vec![
|
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
|
||||||
("UserId".into(), user.uuid.clone().into()),
|
|
||||||
("Date".into(), serialize_date(user.updated_at)),
|
|
||||||
],
|
|
||||||
ut,
|
ut,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -76,48 +76,22 @@ fn alive() -> Json<String> {
|
||||||
#[get("/bwrs_static/<filename>")]
|
#[get("/bwrs_static/<filename>")]
|
||||||
fn static_files(filename: String) -> Result<Content<&'static [u8]>, Error> {
|
fn static_files(filename: String) -> Result<Content<&'static [u8]>, Error> {
|
||||||
match filename.as_ref() {
|
match filename.as_ref() {
|
||||||
"mail-github.png" => Ok(Content(
|
"mail-github.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/mail-github.png"))),
|
||||||
ContentType::PNG,
|
"logo-gray.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/logo-gray.png"))),
|
||||||
include_bytes!("../static/images/mail-github.png"),
|
"shield-white.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/shield-white.png"))),
|
||||||
)),
|
"error-x.svg" => Ok(Content(ContentType::SVG, include_bytes!("../static/images/error-x.svg"))),
|
||||||
"logo-gray.png" => Ok(Content(
|
|
||||||
ContentType::PNG,
|
|
||||||
include_bytes!("../static/images/logo-gray.png"),
|
|
||||||
)),
|
|
||||||
"shield-white.png" => Ok(Content(
|
|
||||||
ContentType::PNG,
|
|
||||||
include_bytes!("../static/images/shield-white.png"),
|
|
||||||
)),
|
|
||||||
"error-x.svg" => Ok(Content(
|
|
||||||
ContentType::SVG,
|
|
||||||
include_bytes!("../static/images/error-x.svg"),
|
|
||||||
)),
|
|
||||||
"hibp.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/hibp.png"))),
|
"hibp.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/hibp.png"))),
|
||||||
|
|
||||||
"bootstrap.css" => Ok(Content(
|
"bootstrap.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))),
|
||||||
ContentType::CSS,
|
"bootstrap-native.js" => {
|
||||||
include_bytes!("../static/scripts/bootstrap.css"),
|
Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native.js")))
|
||||||
)),
|
}
|
||||||
"bootstrap-native.js" => Ok(Content(
|
"identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
|
||||||
ContentType::JavaScript,
|
"datatables.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
|
||||||
include_bytes!("../static/scripts/bootstrap-native.js"),
|
"datatables.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
||||||
)),
|
"jquery-3.5.1.slim.js" => {
|
||||||
"identicon.js" => Ok(Content(
|
Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.5.1.slim.js")))
|
||||||
ContentType::JavaScript,
|
}
|
||||||
include_bytes!("../static/scripts/identicon.js"),
|
|
||||||
)),
|
|
||||||
"datatables.js" => Ok(Content(
|
|
||||||
ContentType::JavaScript,
|
|
||||||
include_bytes!("../static/scripts/datatables.js"),
|
|
||||||
)),
|
|
||||||
"datatables.css" => Ok(Content(
|
|
||||||
ContentType::CSS,
|
|
||||||
include_bytes!("../static/scripts/datatables.css"),
|
|
||||||
)),
|
|
||||||
"jquery-3.5.1.slim.js" => Ok(Content(
|
|
||||||
ContentType::JavaScript,
|
|
||||||
include_bytes!("../static/scripts/jquery-3.5.1.slim.js"),
|
|
||||||
)),
|
|
||||||
_ => err!(format!("Static file not found: {}", filename)),
|
_ => err!(format!("Static file not found: {}", filename)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
src/auth.rs
24
src/auth.rs
|
@ -260,7 +260,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for Host {
|
||||||
format!("{}://{}", protocol, host)
|
format!("{}://{}", protocol, host)
|
||||||
};
|
};
|
||||||
|
|
||||||
Outcome::Success(Host { host })
|
Outcome::Success(Host {
|
||||||
|
host,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,10 +318,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
|
||||||
};
|
};
|
||||||
|
|
||||||
if user.security_stamp != claims.sstamp {
|
if user.security_stamp != claims.sstamp {
|
||||||
if let Some(stamp_exception) = user
|
if let Some(stamp_exception) =
|
||||||
.stamp_exception
|
user.stamp_exception.as_deref().and_then(|s| serde_json::from_str::<UserStampException>(s).ok())
|
||||||
.as_deref()
|
|
||||||
.and_then(|s| serde_json::from_str::<UserStampException>(s).ok())
|
|
||||||
{
|
{
|
||||||
let current_route = match request.route().and_then(|r| r.name) {
|
let current_route = match request.route().and_then(|r| r.name) {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
|
@ -337,7 +337,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Outcome::Success(Headers { host, device, user })
|
Outcome::Success(Headers {
|
||||||
|
host,
|
||||||
|
device,
|
||||||
|
user,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,10 +643,10 @@ impl<'a, 'r> FromRequest<'a, 'r> for ClientIp {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let ip = ip
|
let ip = ip.or_else(|| req.remote().map(|r| r.ip())).unwrap_or_else(|| "0.0.0.0".parse().unwrap());
|
||||||
.or_else(|| req.remote().map(|r| r.ip()))
|
|
||||||
.unwrap_or_else(|| "0.0.0.0".parse().unwrap());
|
|
||||||
|
|
||||||
Outcome::Success(ClientIp { ip })
|
Outcome::Success(ClientIp {
|
||||||
|
ip,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -511,10 +511,7 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
|
||||||
|
|
||||||
let limit = 256;
|
let limit = 256;
|
||||||
if cfg.database_max_conns < 1 || cfg.database_max_conns > limit {
|
if cfg.database_max_conns < 1 || cfg.database_max_conns > limit {
|
||||||
err!(format!(
|
err!(format!("`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {}.", limit,));
|
||||||
"`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {}.",
|
|
||||||
limit,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let dom = cfg.domain.to_lowercase();
|
let dom = cfg.domain.to_lowercase();
|
||||||
|
@ -855,9 +852,7 @@ fn case_helper<'reg, 'rc>(
|
||||||
rc: &mut RenderContext<'reg, 'rc>,
|
rc: &mut RenderContext<'reg, 'rc>,
|
||||||
out: &mut dyn Output,
|
out: &mut dyn Output,
|
||||||
) -> HelperResult {
|
) -> HelperResult {
|
||||||
let param = h
|
let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"case\""))?;
|
||||||
.param(0)
|
|
||||||
.ok_or_else(|| RenderError::new("Param not found for helper \"case\""))?;
|
|
||||||
let value = param.value().clone();
|
let value = param.value().clone();
|
||||||
|
|
||||||
if h.params().iter().skip(1).any(|x| x.value() == &value) {
|
if h.params().iter().skip(1).any(|x| x.value() == &value) {
|
||||||
|
@ -874,16 +869,12 @@ fn js_escape_helper<'reg, 'rc>(
|
||||||
_rc: &mut RenderContext<'reg, 'rc>,
|
_rc: &mut RenderContext<'reg, 'rc>,
|
||||||
out: &mut dyn Output,
|
out: &mut dyn Output,
|
||||||
) -> HelperResult {
|
) -> HelperResult {
|
||||||
let param = h
|
let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?;
|
||||||
.param(0)
|
|
||||||
.ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?;
|
|
||||||
|
|
||||||
let no_quote = h.param(1).is_some();
|
let no_quote = h.param(1).is_some();
|
||||||
|
|
||||||
let value = param
|
let value =
|
||||||
.value()
|
param.value().as_str().ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?;
|
||||||
.as_str()
|
|
||||||
.ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?;
|
|
||||||
|
|
||||||
let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27");
|
let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27");
|
||||||
if !no_quote {
|
if !no_quote {
|
||||||
|
|
|
@ -47,9 +47,7 @@ pub fn get_random_64() -> Vec<u8> {
|
||||||
pub fn get_random(mut array: Vec<u8>) -> Vec<u8> {
|
pub fn get_random(mut array: Vec<u8>) -> Vec<u8> {
|
||||||
use ring::rand::{SecureRandom, SystemRandom};
|
use ring::rand::{SecureRandom, SystemRandom};
|
||||||
|
|
||||||
SystemRandom::new()
|
SystemRandom::new().fill(&mut array).expect("Error generating random values");
|
||||||
.fill(&mut array)
|
|
||||||
.expect("Error generating random values");
|
|
||||||
|
|
||||||
array
|
array
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,9 +314,7 @@ mod sqlite_migrations {
|
||||||
|
|
||||||
// Turn on WAL in SQLite
|
// Turn on WAL in SQLite
|
||||||
if crate::CONFIG.enable_db_wal() {
|
if crate::CONFIG.enable_db_wal() {
|
||||||
diesel::sql_query("PRAGMA journal_mode=wal")
|
diesel::sql_query("PRAGMA journal_mode=wal").execute(&connection).expect("Failed to turn on WAL");
|
||||||
.execute(&connection)
|
|
||||||
.expect("Failed to turn on WAL");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
embedded_migrations::run_with_output(&connection, &mut std::io::stdout())?;
|
embedded_migrations::run_with_output(&connection, &mut std::io::stdout())?;
|
||||||
|
|
|
@ -83,16 +83,9 @@ impl Cipher {
|
||||||
attachments.iter().map(|c| c.to_json(host)).collect()
|
attachments.iter().map(|c| c.to_json(host)).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let fields_json = self
|
let fields_json = self.fields.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
|
||||||
.fields
|
let password_history_json =
|
||||||
.as_ref()
|
self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null);
|
||||||
.and_then(|s| serde_json::from_str(s).ok())
|
|
||||||
.unwrap_or(Value::Null);
|
|
||||||
let password_history_json = self
|
|
||||||
.password_history
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|s| serde_json::from_str(s).ok())
|
|
||||||
.unwrap_or(Value::Null);
|
|
||||||
|
|
||||||
let (read_only, hide_passwords) = match self.get_access_restrictions(&user_uuid, conn) {
|
let (read_only, hide_passwords) = match self.get_access_restrictions(&user_uuid, conn) {
|
||||||
Some((ro, hp)) => (ro, hp),
|
Some((ro, hp)) => (ro, hp),
|
||||||
|
@ -195,12 +188,10 @@ impl Cipher {
|
||||||
None => {
|
None => {
|
||||||
// Belongs to Organization, need to update affected users
|
// Belongs to Organization, need to update affected users
|
||||||
if let Some(ref org_uuid) = self.organization_uuid {
|
if let Some(ref org_uuid) = self.organization_uuid {
|
||||||
UserOrganization::find_by_cipher_and_org(&self.uuid, &org_uuid, conn)
|
UserOrganization::find_by_cipher_and_org(&self.uuid, &org_uuid, conn).iter().for_each(|user_org| {
|
||||||
.iter()
|
User::update_uuid_revision(&user_org.user_uuid, conn);
|
||||||
.for_each(|user_org| {
|
user_uuids.push(user_org.user_uuid.clone())
|
||||||
User::update_uuid_revision(&user_org.user_uuid, conn);
|
});
|
||||||
user_uuids.push(user_org.user_uuid.clone())
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -127,11 +127,9 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_users_revision(&self, conn: &DbConn) {
|
pub fn update_users_revision(&self, conn: &DbConn) {
|
||||||
UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn)
|
UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).iter().for_each(|user_org| {
|
||||||
.iter()
|
User::update_uuid_revision(&user_org.user_uuid, conn);
|
||||||
.for_each(|user_org| {
|
});
|
||||||
User::update_uuid_revision(&user_org.user_uuid, conn);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
|
||||||
|
@ -170,10 +168,7 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
Self::find_by_user_uuid(user_uuid, conn)
|
Self::find_by_user_uuid(user_uuid, conn).into_iter().filter(|c| c.org_uuid == org_uuid).collect()
|
||||||
.into_iter()
|
|
||||||
.filter(|c| c.org_uuid == org_uuid)
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
|
@ -380,11 +375,9 @@ impl CollectionUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
CollectionUser::find_by_collection(&collection_uuid, conn)
|
CollectionUser::find_by_collection(&collection_uuid, conn).iter().for_each(|collection| {
|
||||||
.iter()
|
User::update_uuid_revision(&collection.user_uuid, conn);
|
||||||
.for_each(|collection| {
|
});
|
||||||
User::update_uuid_revision(&collection.user_uuid, conn);
|
|
||||||
});
|
|
||||||
|
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid)))
|
diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid)))
|
||||||
|
|
|
@ -74,26 +74,10 @@ impl Device {
|
||||||
let time_now = Utc::now().naive_utc();
|
let time_now = Utc::now().naive_utc();
|
||||||
self.updated_at = time_now;
|
self.updated_at = time_now;
|
||||||
|
|
||||||
let orgowner: Vec<_> = orgs
|
let orgowner: Vec<_> = orgs.iter().filter(|o| o.atype == 0).map(|o| o.org_uuid.clone()).collect();
|
||||||
.iter()
|
let orgadmin: Vec<_> = orgs.iter().filter(|o| o.atype == 1).map(|o| o.org_uuid.clone()).collect();
|
||||||
.filter(|o| o.atype == 0)
|
let orguser: Vec<_> = orgs.iter().filter(|o| o.atype == 2).map(|o| o.org_uuid.clone()).collect();
|
||||||
.map(|o| o.org_uuid.clone())
|
let orgmanager: Vec<_> = orgs.iter().filter(|o| o.atype == 3).map(|o| o.org_uuid.clone()).collect();
|
||||||
.collect();
|
|
||||||
let orgadmin: Vec<_> = orgs
|
|
||||||
.iter()
|
|
||||||
.filter(|o| o.atype == 1)
|
|
||||||
.map(|o| o.org_uuid.clone())
|
|
||||||
.collect();
|
|
||||||
let orguser: Vec<_> = orgs
|
|
||||||
.iter()
|
|
||||||
.filter(|o| o.atype == 2)
|
|
||||||
.map(|o| o.org_uuid.clone())
|
|
||||||
.collect();
|
|
||||||
let orgmanager: Vec<_> = orgs
|
|
||||||
.iter()
|
|
||||||
.filter(|o| o.atype == 3)
|
|
||||||
.map(|o| o.org_uuid.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Create the JWT claims struct, to send to the client
|
// Create the JWT claims struct, to send to the client
|
||||||
use crate::auth::{encode_jwt, LoginJwtClaims, DEFAULT_VALIDITY, JWT_LOGIN_ISSUER};
|
use crate::auth::{encode_jwt, LoginJwtClaims, DEFAULT_VALIDITY, JWT_LOGIN_ISSUER};
|
||||||
|
|
|
@ -116,10 +116,7 @@ impl PartialOrd<UserOrgType> for i32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn le(&self, other: &UserOrgType) -> bool {
|
fn le(&self, other: &UserOrgType) -> bool {
|
||||||
matches!(
|
matches!(self.partial_cmp(other), Some(Ordering::Less) | Some(Ordering::Equal) | None)
|
||||||
self.partial_cmp(other),
|
|
||||||
Some(Ordering::Less) | Some(Ordering::Equal) | None
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,11 +189,9 @@ use crate::error::MapResult;
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl Organization {
|
impl Organization {
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
UserOrganization::find_by_org(&self.uuid, conn)
|
UserOrganization::find_by_org(&self.uuid, conn).iter().for_each(|user_org| {
|
||||||
.iter()
|
User::update_uuid_revision(&user_org.user_uuid, conn);
|
||||||
.for_each(|user_org| {
|
});
|
||||||
User::update_uuid_revision(&user_org.user_uuid, conn);
|
|
||||||
});
|
|
||||||
|
|
||||||
db_run! { conn:
|
db_run! { conn:
|
||||||
sqlite, mysql {
|
sqlite, mysql {
|
||||||
|
|
|
@ -348,7 +348,9 @@ impl User {
|
||||||
|
|
||||||
impl Invitation {
|
impl Invitation {
|
||||||
pub const fn new(email: String) -> Self {
|
pub const fn new(email: String) -> Self {
|
||||||
Self { email }
|
Self {
|
||||||
|
email,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
|
|
|
@ -198,11 +198,7 @@ impl<'r> Responder<'r> for Error {
|
||||||
|
|
||||||
let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest);
|
let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest);
|
||||||
|
|
||||||
Response::build()
|
Response::build().status(code).header(ContentType::JSON).sized_body(Cursor::new(format!("{}", self))).ok()
|
||||||
.status(code)
|
|
||||||
.header(ContentType::JSON)
|
|
||||||
.sized_body(Cursor::new(format!("{}", self)))
|
|
||||||
.ok()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
src/mail.rs
26
src/mail.rs
|
@ -58,18 +58,12 @@ fn mailer() -> SmtpTransport {
|
||||||
|
|
||||||
let smtp_client = match CONFIG.smtp_auth_mechanism() {
|
let smtp_client = match CONFIG.smtp_auth_mechanism() {
|
||||||
Some(mechanism) => {
|
Some(mechanism) => {
|
||||||
let allowed_mechanisms = [
|
let allowed_mechanisms = [SmtpAuthMechanism::Plain, SmtpAuthMechanism::Login, SmtpAuthMechanism::Xoauth2];
|
||||||
SmtpAuthMechanism::Plain,
|
|
||||||
SmtpAuthMechanism::Login,
|
|
||||||
SmtpAuthMechanism::Xoauth2,
|
|
||||||
];
|
|
||||||
let mut selected_mechanisms = vec![];
|
let mut selected_mechanisms = vec![];
|
||||||
for wanted_mechanism in mechanism.split(',') {
|
for wanted_mechanism in mechanism.split(',') {
|
||||||
for m in &allowed_mechanisms {
|
for m in &allowed_mechanisms {
|
||||||
if m.to_string().to_lowercase()
|
if m.to_string().to_lowercase()
|
||||||
== wanted_mechanism
|
== wanted_mechanism.trim_matches(|c| c == '"' || c == '\'' || c == ' ').to_lowercase()
|
||||||
.trim_matches(|c| c == '"' || c == '\'' || c == ' ')
|
|
||||||
.to_lowercase()
|
|
||||||
{
|
{
|
||||||
selected_mechanisms.push(*m);
|
selected_mechanisms.push(*m);
|
||||||
}
|
}
|
||||||
|
@ -80,10 +74,7 @@ fn mailer() -> SmtpTransport {
|
||||||
smtp_client.authentication(selected_mechanisms)
|
smtp_client.authentication(selected_mechanisms)
|
||||||
} else {
|
} else {
|
||||||
// Only show a warning, and return without setting an actual authentication mechanism
|
// Only show a warning, and return without setting an actual authentication mechanism
|
||||||
warn!(
|
warn!("No valid SMTP Auth mechanism found for '{}', using default values", mechanism);
|
||||||
"No valid SMTP Auth mechanism found for '{}', using default values",
|
|
||||||
mechanism
|
|
||||||
);
|
|
||||||
smtp_client
|
smtp_client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,16 +318,9 @@ fn send_email(address: &str, subject: &str, body_html: String, body_text: String
|
||||||
|
|
||||||
let smtp_from = &CONFIG.smtp_from();
|
let smtp_from = &CONFIG.smtp_from();
|
||||||
let email = Message::builder()
|
let email = Message::builder()
|
||||||
.message_id(Some(format!(
|
.message_id(Some(format!("<{}@{}>", crate::util::get_uuid(), smtp_from.split('@').collect::<Vec<&str>>()[1])))
|
||||||
"<{}@{}>",
|
|
||||||
crate::util::get_uuid(),
|
|
||||||
smtp_from.split('@').collect::<Vec<&str>>()[1]
|
|
||||||
)))
|
|
||||||
.to(Mailbox::new(None, Address::from_str(&address)?))
|
.to(Mailbox::new(None, Address::from_str(&address)?))
|
||||||
.from(Mailbox::new(
|
.from(Mailbox::new(Some(CONFIG.smtp_from_name()), Address::from_str(smtp_from)?))
|
||||||
Some(CONFIG.smtp_from_name()),
|
|
||||||
Address::from_str(smtp_from)?,
|
|
||||||
))
|
|
||||||
.subject(subject)
|
.subject(subject)
|
||||||
.multipart(MultiPart::alternative().singlepart(text).singlepart(html))?;
|
.multipart(MultiPart::alternative().singlepart(text).singlepart(html))?;
|
||||||
|
|
||||||
|
|
16
src/util.rs
16
src/util.rs
|
@ -127,14 +127,8 @@ impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
|
||||||
|
|
||||||
// Log all the routes from the main paths list, and the attachments endpoint
|
// 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", "/admin", "/identity", "/icons", "/notifications/hub/negotiate", "/attachments"];
|
||||||
"/admin",
|
|
||||||
"/identity",
|
|
||||||
"/icons",
|
|
||||||
"/notifications/hub/negotiate",
|
|
||||||
"/attachments",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Boolean is extra debug, when true, we ignore the whitelist above and also print the mounts
|
// Boolean is extra debug, when true, we ignore the whitelist above and also print the mounts
|
||||||
pub struct BetterLogging(pub bool);
|
pub struct BetterLogging(pub bool);
|
||||||
|
@ -161,7 +155,11 @@ impl Fairing for BetterLogging {
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = rocket.config();
|
let config = rocket.config();
|
||||||
let scheme = if config.tls_enabled() { "https" } else { "http" };
|
let scheme = if config.tls_enabled() {
|
||||||
|
"https"
|
||||||
|
} else {
|
||||||
|
"http"
|
||||||
|
};
|
||||||
let addr = format!("{}://{}:{}", &scheme, &config.address, &config.port);
|
let addr = format!("{}://{}:{}", &scheme, &config.address, &config.port);
|
||||||
info!(target: "start", "Rocket has launched from {}", addr);
|
info!(target: "start", "Rocket has launched from {}", addr);
|
||||||
}
|
}
|
||||||
|
|
Laden …
In neuem Issue referenzieren