mirror of
https://github.com/dani-garcia/vaultwarden.wiki.git
synced 2026-07-31 00:49:06 +03:00
Compare commits
6
Commits
8f0e99b875
...
3f28b583db
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f28b583db | ||
|
|
d4f67429d6 | ||
|
|
fc43737868 | ||
|
|
43df0fb7f4 | ||
|
|
d29cd29f55 | ||
|
|
2811df2953 |
+2
-1
@@ -480,7 +480,6 @@ async fn deauth_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Noti
|
|||||||
#[post("/users/<user_id>/disable", format = "application/json")]
|
#[post("/users/<user_id>/disable", format = "application/json")]
|
||||||
async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let mut user = get_user_or_404(&user_id, &conn).await?;
|
let mut user = get_user_or_404(&user_id, &conn).await?;
|
||||||
Device::delete_all_by_user(&user.uuid, &conn).await?;
|
|
||||||
user.reset_security_stamp(&conn).await?;
|
user.reset_security_stamp(&conn).await?;
|
||||||
user.enabled = false;
|
user.enabled = false;
|
||||||
|
|
||||||
@@ -488,6 +487,8 @@ async fn disable_user(user_id: UserId, _token: AdminToken, conn: DbConn, nt: Not
|
|||||||
|
|
||||||
nt.send_logout(&user, None, &conn).await;
|
nt.send_logout(&user, None, &conn).await;
|
||||||
|
|
||||||
|
Device::delete_all_by_user(&user.uuid, &conn).await?;
|
||||||
|
|
||||||
save_result
|
save_result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ async fn post_password(data: Json<ChangePassData>, headers: Headers, conn: DbCon
|
|||||||
// Prevent logging out the client where the user requested this endpoint from.
|
// Prevent logging out the client where the user requested this endpoint from.
|
||||||
// If you do logout the user it will causes issues at the client side.
|
// If you do logout the user it will causes issues at the client side.
|
||||||
// Adding the device uuid will prevent this.
|
// Adding the device uuid will prevent this.
|
||||||
nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await;
|
nt.send_logout(&user, Some(&headers.device), &conn).await;
|
||||||
|
|
||||||
save_result
|
save_result
|
||||||
}
|
}
|
||||||
@@ -638,7 +638,7 @@ async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, conn: DbConn, nt:
|
|||||||
.await?;
|
.await?;
|
||||||
let save_result = user.save(&conn).await;
|
let save_result = user.save(&conn).await;
|
||||||
|
|
||||||
nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await;
|
nt.send_logout(&user, Some(&headers.device), &conn).await;
|
||||||
|
|
||||||
save_result
|
save_result
|
||||||
}
|
}
|
||||||
@@ -912,7 +912,7 @@ async fn post_rotatekey(data: Json<KeyData>, headers: Headers, conn: DbConn, nt:
|
|||||||
// Prevent logging out the client where the user requested this endpoint from.
|
// Prevent logging out the client where the user requested this endpoint from.
|
||||||
// If you do logout the user it will causes issues at the client side.
|
// If you do logout the user it will causes issues at the client side.
|
||||||
// Adding the device uuid will prevent this.
|
// Adding the device uuid will prevent this.
|
||||||
nt.send_logout(&user, Some(headers.device.uuid.clone()), &conn).await;
|
nt.send_logout(&user, Some(&headers.device), &conn).await;
|
||||||
|
|
||||||
save_result
|
save_result
|
||||||
}
|
}
|
||||||
@@ -924,12 +924,13 @@ async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbCo
|
|||||||
|
|
||||||
data.validate(&user, true, &conn).await?;
|
data.validate(&user, true, &conn).await?;
|
||||||
|
|
||||||
Device::delete_all_by_user(&user.uuid, &conn).await?;
|
|
||||||
user.reset_security_stamp(&conn).await?;
|
user.reset_security_stamp(&conn).await?;
|
||||||
let save_result = user.save(&conn).await;
|
let save_result = user.save(&conn).await;
|
||||||
|
|
||||||
nt.send_logout(&user, None, &conn).await;
|
nt.send_logout(&user, None, &conn).await;
|
||||||
|
|
||||||
|
Device::delete_all_by_user(&user.uuid, &conn).await?;
|
||||||
|
|
||||||
save_result
|
save_result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -500,6 +500,10 @@ async fn post_organization_collections(
|
|||||||
let data: FullCollectionData = data.into_inner();
|
let data: FullCollectionData = data.into_inner();
|
||||||
data.validate(&org_id, &conn).await?;
|
data.validate(&org_id, &conn).await?;
|
||||||
|
|
||||||
|
if headers.membership.atype == MembershipType::Manager && !headers.membership.access_all {
|
||||||
|
err!("You don't have permission to create collections")
|
||||||
|
}
|
||||||
|
|
||||||
let collection = Collection::new(org_id.clone(), data.name, data.external_id);
|
let collection = Collection::new(org_id.clone(), data.name, data.external_id);
|
||||||
collection.save(&conn).await?;
|
collection.save(&conn).await?;
|
||||||
|
|
||||||
@@ -540,10 +544,6 @@ async fn post_organization_collections(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if headers.membership.atype == MembershipType::Manager && !headers.membership.access_all {
|
|
||||||
CollectionUser::save(&headers.membership.user_uuid, &collection.uuid, false, false, false, &conn).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Json(collection.to_json_details(&headers.membership.user_uuid, None, &conn).await))
|
Ok(Json(collection.to_json_details(&headers.membership.user_uuid, None, &conn).await))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use chrono::{TimeDelta, Utc};
|
use chrono::{TimeDelta, Utc};
|
||||||
use data_encoding::BASE32;
|
use data_encoding::BASE32;
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
|
use serde::Deserialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -14,7 +16,7 @@ use crate::{
|
|||||||
db::{
|
db::{
|
||||||
models::{
|
models::{
|
||||||
DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, TwoFactor,
|
DeviceType, EventType, Membership, MembershipType, OrgPolicyType, Organization, OrganizationId, TwoFactor,
|
||||||
TwoFactorIncomplete, User, UserId,
|
TwoFactorIncomplete, TwoFactorType, User, UserId,
|
||||||
},
|
},
|
||||||
DbConn, DbPool,
|
DbConn, DbPool,
|
||||||
},
|
},
|
||||||
@@ -31,6 +33,43 @@ pub mod protected_actions;
|
|||||||
pub mod webauthn;
|
pub mod webauthn;
|
||||||
pub mod yubikey;
|
pub mod yubikey;
|
||||||
|
|
||||||
|
fn has_global_duo_credentials() -> bool {
|
||||||
|
CONFIG._enable_duo() && CONFIG.duo_host().is_some() && CONFIG.duo_ikey().is_some() && CONFIG.duo_skey().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_twofactor_provider_usable(provider_type: TwoFactorType, provider_data: Option<&str>) -> bool {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DuoProviderData {
|
||||||
|
host: String,
|
||||||
|
ik: String,
|
||||||
|
sk: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
match provider_type {
|
||||||
|
TwoFactorType::Authenticator => true,
|
||||||
|
TwoFactorType::Email => CONFIG._enable_email_2fa(),
|
||||||
|
TwoFactorType::Duo | TwoFactorType::OrganizationDuo => {
|
||||||
|
provider_data
|
||||||
|
.and_then(|raw| serde_json::from_str::<DuoProviderData>(raw).ok())
|
||||||
|
.is_some_and(|duo| !duo.host.is_empty() && !duo.ik.is_empty() && !duo.sk.is_empty())
|
||||||
|
|| has_global_duo_credentials()
|
||||||
|
}
|
||||||
|
TwoFactorType::YubiKey => {
|
||||||
|
CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some()
|
||||||
|
}
|
||||||
|
TwoFactorType::Webauthn => CONFIG.is_webauthn_2fa_supported(),
|
||||||
|
TwoFactorType::Remember => !CONFIG.disable_2fa_remember(),
|
||||||
|
TwoFactorType::RecoveryCode => true,
|
||||||
|
TwoFactorType::U2f
|
||||||
|
| TwoFactorType::U2fRegisterChallenge
|
||||||
|
| TwoFactorType::U2fLoginChallenge
|
||||||
|
| TwoFactorType::EmailVerificationChallenge
|
||||||
|
| TwoFactorType::WebauthnRegisterChallenge
|
||||||
|
| TwoFactorType::WebauthnLoginChallenge
|
||||||
|
| TwoFactorType::ProtectedActions => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
let mut routes = routes![
|
let mut routes = routes![
|
||||||
get_twofactor,
|
get_twofactor,
|
||||||
@@ -53,7 +92,13 @@ pub fn routes() -> Vec<Route> {
|
|||||||
#[get("/two-factor")]
|
#[get("/two-factor")]
|
||||||
async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> {
|
async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> {
|
||||||
let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await;
|
let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await;
|
||||||
let twofactors_json: Vec<Value> = twofactors.iter().map(TwoFactor::to_json_provider).collect();
|
let twofactors_json: Vec<Value> = twofactors
|
||||||
|
.iter()
|
||||||
|
.filter_map(|tf| {
|
||||||
|
let provider_type = TwoFactorType::from_i32(tf.atype)?;
|
||||||
|
is_twofactor_provider_usable(provider_type, Some(&tf.data)).then(|| TwoFactor::to_json_provider(tf))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"data": twofactors_json,
|
"data": twofactors_json,
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ impl WebauthnRegistration {
|
|||||||
|
|
||||||
#[post("/two-factor/get-webauthn", data = "<data>")]
|
#[post("/two-factor/get-webauthn", data = "<data>")]
|
||||||
async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
if !CONFIG.domain_set() {
|
if !CONFIG.is_webauthn_2fa_supported() {
|
||||||
err!("`DOMAIN` environment variable is not set. Webauthn disabled")
|
err!("Configured `DOMAIN` is not compatible with Webauthn")
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: PasswordOrOtpData = data.into_inner();
|
let data: PasswordOrOtpData = data.into_inner();
|
||||||
|
|||||||
+22
-3
@@ -14,7 +14,10 @@ use crate::{
|
|||||||
core::{
|
core::{
|
||||||
accounts::{PreloginData, RegisterData, _prelogin, _register, kdf_upgrade},
|
accounts::{PreloginData, RegisterData, _prelogin, _register, kdf_upgrade},
|
||||||
log_user_event,
|
log_user_event,
|
||||||
two_factor::{authenticator, duo, duo_oidc, email, enforce_2fa_policy, webauthn, yubikey},
|
two_factor::{
|
||||||
|
authenticator, duo, duo_oidc, email, enforce_2fa_policy, is_twofactor_provider_usable, webauthn,
|
||||||
|
yubikey,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
master_password_policy,
|
master_password_policy,
|
||||||
push::register_push_device,
|
push::register_push_device,
|
||||||
@@ -739,8 +742,24 @@ async fn twofactor_auth(
|
|||||||
|
|
||||||
TwoFactorIncomplete::mark_incomplete(&user.uuid, &device.uuid, &device.name, device.atype, ip, conn).await?;
|
TwoFactorIncomplete::mark_incomplete(&user.uuid, &device.uuid, &device.name, device.atype, ip, conn).await?;
|
||||||
|
|
||||||
let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect();
|
let twofactor_ids: Vec<_> = twofactors
|
||||||
|
.iter()
|
||||||
|
.filter_map(|tf| {
|
||||||
|
let provider_type = TwoFactorType::from_i32(tf.atype)?;
|
||||||
|
(tf.enabled && is_twofactor_provider_usable(provider_type, Some(&tf.data))).then_some(tf.atype)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if twofactor_ids.is_empty() {
|
||||||
|
err!("No enabled and usable two factor providers are available for this account")
|
||||||
|
}
|
||||||
|
|
||||||
let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one
|
let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, assume the first one
|
||||||
|
if !twofactor_ids.contains(&selected_id) {
|
||||||
|
err_json!(
|
||||||
|
_json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?,
|
||||||
|
"Invalid two factor provider"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let twofactor_code = match data.two_factor_token {
|
let twofactor_code = match data.two_factor_token {
|
||||||
Some(ref code) => code,
|
Some(ref code) => code,
|
||||||
@@ -871,7 +890,7 @@ async fn _json_err_twofactor(
|
|||||||
match TwoFactorType::from_i32(*provider) {
|
match TwoFactorType::from_i32(*provider) {
|
||||||
Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ }
|
Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ }
|
||||||
|
|
||||||
Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => {
|
Some(TwoFactorType::Webauthn) if CONFIG.is_webauthn_2fa_supported() => {
|
||||||
let request = webauthn::generate_webauthn_login(user_id, conn).await?;
|
let request = webauthn::generate_webauthn_login(user_id, conn).await?;
|
||||||
result["TwoFactorProviders2"][provider.to_string()] = request.0;
|
result["TwoFactorProviders2"][provider.to_string()] = request.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,15 +358,16 @@ impl WebSocketUsers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_logout(&self, user: &User, acting_device_id: Option<DeviceId>, conn: &DbConn) {
|
pub async fn send_logout(&self, user: &User, acting_device: Option<&Device>, conn: &DbConn) {
|
||||||
// Skip any processing if both WebSockets and Push are not active
|
// Skip any processing if both WebSockets and Push are not active
|
||||||
if *NOTIFICATIONS_DISABLED {
|
if *NOTIFICATIONS_DISABLED {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let acting_device_id = acting_device.map(|d| d.uuid.clone());
|
||||||
let data = create_update(
|
let data = create_update(
|
||||||
vec![("UserId".into(), user.uuid.to_string().into()), ("Date".into(), serialize_date(user.updated_at))],
|
vec![("UserId".into(), user.uuid.to_string().into()), ("Date".into(), serialize_date(user.updated_at))],
|
||||||
UpdateType::LogOut,
|
UpdateType::LogOut,
|
||||||
acting_device_id.clone(),
|
acting_device_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if CONFIG.enable_websocket() {
|
if CONFIG.enable_websocket() {
|
||||||
@@ -374,7 +375,7 @@ impl WebSocketUsers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if CONFIG.push_enabled() {
|
if CONFIG.push_enabled() {
|
||||||
push_logout(user, acting_device_id.clone(), conn).await;
|
push_logout(user, acting_device, conn).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -13,7 +13,7 @@ use tokio::sync::RwLock;
|
|||||||
use crate::{
|
use crate::{
|
||||||
api::{ApiResult, EmptyResult, UpdateType},
|
api::{ApiResult, EmptyResult, UpdateType},
|
||||||
db::{
|
db::{
|
||||||
models::{AuthRequestId, Cipher, Device, DeviceId, Folder, PushId, Send, User, UserId},
|
models::{AuthRequestId, Cipher, Device, Folder, PushId, Send, User, UserId},
|
||||||
DbConn,
|
DbConn,
|
||||||
},
|
},
|
||||||
http_client::make_http_request,
|
http_client::make_http_request,
|
||||||
@@ -188,15 +188,13 @@ pub async fn push_cipher_update(ut: UpdateType, cipher: &Cipher, device: &Device
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn push_logout(user: &User, acting_device_id: Option<DeviceId>, conn: &DbConn) {
|
pub async fn push_logout(user: &User, acting_device: Option<&Device>, conn: &DbConn) {
|
||||||
let acting_device_id: Value = acting_device_id.map(|v| v.to_string().into()).unwrap_or_else(|| Value::Null);
|
|
||||||
|
|
||||||
if Device::check_user_has_push_device(&user.uuid, conn).await {
|
if Device::check_user_has_push_device(&user.uuid, conn).await {
|
||||||
tokio::task::spawn(send_to_push_relay(json!({
|
tokio::task::spawn(send_to_push_relay(json!({
|
||||||
"userId": user.uuid,
|
"userId": user.uuid,
|
||||||
"organizationId": (),
|
"organizationId": (),
|
||||||
"deviceId": acting_device_id,
|
"deviceId": acting_device.and_then(|d| d.push_uuid.as_ref()),
|
||||||
"identifier": acting_device_id,
|
"identifier": acting_device.map(|d| &d.uuid),
|
||||||
"type": UpdateType::LogOut as i32,
|
"type": UpdateType::LogOut as i32,
|
||||||
"payload": {
|
"payload": {
|
||||||
"userId": user.uuid,
|
"userId": user.uuid,
|
||||||
|
|||||||
+7
-11
@@ -387,7 +387,6 @@ pub mod models;
|
|||||||
#[cfg(sqlite)]
|
#[cfg(sqlite)]
|
||||||
pub fn backup_sqlite() -> Result<String, Error> {
|
pub fn backup_sqlite() -> Result<String, Error> {
|
||||||
use diesel::Connection;
|
use diesel::Connection;
|
||||||
use std::{fs::File, io::Write};
|
|
||||||
|
|
||||||
let db_url = CONFIG.database_url();
|
let db_url = CONFIG.database_url();
|
||||||
if DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::Sqlite).unwrap_or(false) {
|
if DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::Sqlite).unwrap_or(false) {
|
||||||
@@ -401,16 +400,13 @@ pub fn backup_sqlite() -> Result<String, Error> {
|
|||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
match File::create(backup_file.clone()) {
|
diesel::sql_query("VACUUM INTO ?")
|
||||||
Ok(mut f) => {
|
.bind::<diesel::sql_types::Text, _>(&backup_file)
|
||||||
let serialized_db = conn.serialize_database_to_buffer();
|
.execute(&mut conn)
|
||||||
f.write_all(serialized_db.as_slice()).expect("Error writing SQLite backup");
|
.map(|_| ())
|
||||||
Ok(backup_file)
|
.map_res("VACUUM INTO failed")?;
|
||||||
}
|
|
||||||
Err(e) => {
|
Ok(backup_file)
|
||||||
err_silent!(format!("Unable to save SQLite backup: {e:?}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
err_silent!("The database type is not SQLite. Backups only works for SQLite databases")
|
err_silent!("The database type is not SQLite. Backups only works for SQLite databases")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -514,7 +514,8 @@ impl Membership {
|
|||||||
"familySponsorshipValidUntil": null,
|
"familySponsorshipValidUntil": null,
|
||||||
"familySponsorshipToDelete": null,
|
"familySponsorshipToDelete": null,
|
||||||
"accessSecretsManager": false,
|
"accessSecretsManager": false,
|
||||||
"limitCollectionCreation": self.atype < MembershipType::Manager, // If less then a manager return true, to limit collection creations
|
// limit collection creation to managers with access_all permission to prevent issues
|
||||||
|
"limitCollectionCreation": self.atype < MembershipType::Manager || !self.access_all,
|
||||||
"limitCollectionDeletion": true,
|
"limitCollectionDeletion": true,
|
||||||
"limitItemDeletion": false,
|
"limitItemDeletion": false,
|
||||||
"allowAdminAccessToAllCollectionItems": true,
|
"allowAdminAccessToAllCollectionItems": true,
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ pub enum SendType {
|
|||||||
File = 1,
|
File = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SendAuthType {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
// Send requires email OTP verification
|
||||||
|
Email = 0, // Not yet supported by Vaultwarden
|
||||||
|
// Send requires a password
|
||||||
|
Password = 1,
|
||||||
|
// Send requires no auth
|
||||||
|
None = 2,
|
||||||
|
}
|
||||||
|
|
||||||
impl Send {
|
impl Send {
|
||||||
pub fn new(atype: i32, name: String, data: String, akey: String, deletion_date: NaiveDateTime) -> Self {
|
pub fn new(atype: i32, name: String, data: String, akey: String, deletion_date: NaiveDateTime) -> Self {
|
||||||
let now = Utc::now().naive_utc();
|
let now = Utc::now().naive_utc();
|
||||||
@@ -145,6 +155,7 @@ impl Send {
|
|||||||
"maxAccessCount": self.max_access_count,
|
"maxAccessCount": self.max_access_count,
|
||||||
"accessCount": self.access_count,
|
"accessCount": self.access_count,
|
||||||
"password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)),
|
"password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)),
|
||||||
|
"authType": if self.password_hash.is_some() { SendAuthType::Password as i32 } else { SendAuthType::None as i32 },
|
||||||
"disabled": self.disabled,
|
"disabled": self.disabled,
|
||||||
"hideEmail": self.hide_email,
|
"hideEmail": self.hide_email,
|
||||||
|
|
||||||
|
|||||||
+35
-5
@@ -558,6 +558,11 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error>
|
|||||||
let basepath = &CONFIG.domain_path();
|
let basepath = &CONFIG.domain_path();
|
||||||
|
|
||||||
let mut config = rocket::Config::from(rocket::Config::figment());
|
let mut config = rocket::Config::from(rocket::Config::figment());
|
||||||
|
|
||||||
|
// We install our own signal handlers below; disable Rocket's built-in handlers
|
||||||
|
config.shutdown.ctrlc = false;
|
||||||
|
config.shutdown.signals.clear();
|
||||||
|
|
||||||
config.temp_dir = canonicalize(CONFIG.tmp_folder()).unwrap().into();
|
config.temp_dir = canonicalize(CONFIG.tmp_folder()).unwrap().into();
|
||||||
config.cli_colors = false; // Make sure Rocket does not color any values for logging.
|
config.cli_colors = false; // Make sure Rocket does not color any values for logging.
|
||||||
config.limits = Limits::new()
|
config.limits = Limits::new()
|
||||||
@@ -589,11 +594,7 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error>
|
|||||||
|
|
||||||
CONFIG.set_rocket_shutdown_handle(instance.shutdown());
|
CONFIG.set_rocket_shutdown_handle(instance.shutdown());
|
||||||
|
|
||||||
tokio::spawn(async move {
|
spawn_shutdown_signal_handler();
|
||||||
tokio::signal::ctrl_c().await.expect("Error setting Ctrl-C handler");
|
|
||||||
info!("Exiting Vaultwarden!");
|
|
||||||
CONFIG.shutdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(all(unix, sqlite))]
|
#[cfg(all(unix, sqlite))]
|
||||||
{
|
{
|
||||||
@@ -621,6 +622,35 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn spawn_shutdown_signal_handler() {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
use tokio::signal::unix::signal;
|
||||||
|
|
||||||
|
let mut sigint = signal(SignalKind::interrupt()).expect("Error setting SIGINT handler");
|
||||||
|
let mut sigterm = signal(SignalKind::terminate()).expect("Error setting SIGTERM handler");
|
||||||
|
let mut sigquit = signal(SignalKind::quit()).expect("Error setting SIGQUIT handler");
|
||||||
|
|
||||||
|
let signal_name = tokio::select! {
|
||||||
|
_ = sigint.recv() => "SIGINT",
|
||||||
|
_ = sigterm.recv() => "SIGTERM",
|
||||||
|
_ = sigquit.recv() => "SIGQUIT",
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Received {signal_name}, initiating graceful shutdown");
|
||||||
|
CONFIG.shutdown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn spawn_shutdown_signal_handler() {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tokio::signal::ctrl_c().await.expect("Error setting Ctrl-C handler");
|
||||||
|
info!("Received Ctrl-C, initiating graceful shutdown");
|
||||||
|
CONFIG.shutdown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn schedule_jobs(pool: db::DbPool) {
|
fn schedule_jobs(pool: db::DbPool) {
|
||||||
if CONFIG.job_poll_interval_ms() == 0 {
|
if CONFIG.job_poll_interval_ms() == 0 {
|
||||||
info!("Job scheduler disabled.");
|
info!("Job scheduler disabled.");
|
||||||
|
|||||||
Reference in New Issue
Block a user