Use Diesels MultiConnections Derive (#6279)

* Use Diesels MultiConnections Derive

With this PR we remove almost all custom macro's to create the multiple database type code. This is now handled by Diesel it self.

This removed the need of the following functions/macro's:
 - `db_object!`
 - `::to_db`
 - `.from_db()`

It is also possible to just use one schema instead of multiple per type.

Also done:
 - Refactored the SQLite backup function
 - Some formatting of queries so every call is one a separate line, this looks a bit better
 - Declare `conn` as mut inside each `db_run!` instead of having to declare it as `mut` in functions or calls
 - Added an `ACTIVE_DB_TYPE` static which holds the currently active database type
 - Removed `diesel_logger` crate and use Diesel's `set_default_instrumentation()`
   If you want debug queries you can now simply change the log level of `vaultwarden::db::query_logger`
 - Use PostgreSQL v17 in the Alpine images to match the Debian Trixie version
 - Optimized the Workflows since `diesel_logger` isn't needed anymore

And on the extra plus-side, this lowers the compile-time and binary size too.

Signed-off-by: BlackDex <black.dex@gmail.com>

* Adjust query_logger and some other small items

Signed-off-by: BlackDex <black.dex@gmail.com>

* Remove  macro, replaced with an  function

Signed-off-by: BlackDex <black.dex@gmail.com>

* Implement custom connection manager

Signed-off-by: BlackDex <black.dex@gmail.com>

* Updated some crates to keep up2date

Signed-off-by: BlackDex <black.dex@gmail.com>

* Small adjustment

Signed-off-by: BlackDex <black.dex@gmail.com>

* crate updates

Signed-off-by: BlackDex <black.dex@gmail.com>

* Update crates

Signed-off-by: BlackDex <black.dex@gmail.com>

---------

Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
Mathijs van Veluw
2025-10-29 21:04:30 +01:00
committed by GitHub
parent 7c597e88f9
commit 2ee5819b56
61 changed files with 3210 additions and 4204 deletions

View File

@@ -22,7 +22,13 @@ use crate::{
},
auth,
auth::{generate_organization_api_key_login_claims, AuthMethod, ClientHeaders, ClientIp, ClientVersion},
db::{models::*, DbConn},
db::{
models::{
AuthRequest, AuthRequestId, Device, DeviceId, EventType, Invitation, OrganizationApiKey, OrganizationId,
SsoNonce, SsoUser, TwoFactor, TwoFactorIncomplete, TwoFactorType, User, UserId,
},
DbConn,
},
error::MapResult,
mail, sso,
sso::{OIDCCode, OIDCState},
@@ -48,7 +54,7 @@ async fn login(
data: Form<ConnectData>,
client_header: ClientHeaders,
client_version: Option<ClientVersion>,
mut conn: DbConn,
conn: DbConn,
) -> JsonResult {
let data: ConnectData = data.into_inner();
@@ -57,7 +63,7 @@ async fn login(
let login_result = match data.grant_type.as_ref() {
"refresh_token" => {
_check_is_some(&data.refresh_token, "refresh_token cannot be blank")?;
_refresh_login(data, &mut conn, &client_header.ip).await
_refresh_login(data, &conn, &client_header.ip).await
}
"password" if CONFIG.sso_enabled() && CONFIG.sso_only() => err!("SSO sign-in is required"),
"password" => {
@@ -70,7 +76,7 @@ async fn login(
_check_is_some(&data.device_name, "device_name cannot be blank")?;
_check_is_some(&data.device_type, "device_type cannot be blank")?;
_password_login(data, &mut user_id, &mut conn, &client_header.ip, &client_version).await
_password_login(data, &mut user_id, &conn, &client_header.ip, &client_version).await
}
"client_credentials" => {
_check_is_some(&data.client_id, "client_id cannot be blank")?;
@@ -81,7 +87,7 @@ async fn login(
_check_is_some(&data.device_name, "device_name cannot be blank")?;
_check_is_some(&data.device_type, "device_type cannot be blank")?;
_api_key_login(data, &mut user_id, &mut conn, &client_header.ip).await
_api_key_login(data, &mut user_id, &conn, &client_header.ip).await
}
"authorization_code" if CONFIG.sso_enabled() => {
_check_is_some(&data.client_id, "client_id cannot be blank")?;
@@ -91,7 +97,7 @@ async fn login(
_check_is_some(&data.device_name, "device_name cannot be blank")?;
_check_is_some(&data.device_type, "device_type cannot be blank")?;
_sso_login(data, &mut user_id, &mut conn, &client_header.ip, &client_version).await
_sso_login(data, &mut user_id, &conn, &client_header.ip, &client_version).await
}
"authorization_code" => err!("SSO sign-in is not available"),
t => err!("Invalid type", t),
@@ -105,20 +111,14 @@ async fn login(
&user_id,
client_header.device_type,
&client_header.ip.ip,
&mut conn,
&conn,
)
.await;
}
Err(e) => {
if let Some(ev) = e.get_event() {
log_user_event(
ev.event as i32,
&user_id,
client_header.device_type,
&client_header.ip.ip,
&mut conn,
)
.await
log_user_event(ev.event as i32, &user_id, client_header.device_type, &client_header.ip.ip, &conn)
.await
}
}
}
@@ -128,7 +128,7 @@ async fn login(
}
// Return Status::Unauthorized to trigger logout
async fn _refresh_login(data: ConnectData, conn: &mut DbConn, ip: &ClientIp) -> JsonResult {
async fn _refresh_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> JsonResult {
// Extract token
let refresh_token = match data.refresh_token {
Some(token) => token,
@@ -166,7 +166,7 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn, ip: &ClientIp) ->
async fn _sso_login(
data: ConnectData,
user_id: &mut Option<UserId>,
conn: &mut DbConn,
conn: &DbConn,
ip: &ClientIp,
client_version: &Option<ClientVersion>,
) -> JsonResult {
@@ -319,7 +319,7 @@ async fn _sso_login(
async fn _password_login(
data: ConnectData,
user_id: &mut Option<UserId>,
conn: &mut DbConn,
conn: &DbConn,
ip: &ClientIp,
client_version: &Option<ClientVersion>,
) -> JsonResult {
@@ -444,7 +444,7 @@ async fn authenticated_response(
auth_tokens: auth::AuthTokens,
twofactor_token: Option<String>,
now: &NaiveDateTime,
conn: &mut DbConn,
conn: &DbConn,
ip: &ClientIp,
) -> JsonResult {
if CONFIG.mail_enabled() && device.is_new() {
@@ -504,12 +504,7 @@ async fn authenticated_response(
Ok(Json(result))
}
async fn _api_key_login(
data: ConnectData,
user_id: &mut Option<UserId>,
conn: &mut DbConn,
ip: &ClientIp,
) -> JsonResult {
async fn _api_key_login(data: ConnectData, user_id: &mut Option<UserId>, conn: &DbConn, ip: &ClientIp) -> JsonResult {
// Ratelimit the login
crate::ratelimit::check_limit_login(&ip.ip)?;
@@ -524,7 +519,7 @@ async fn _api_key_login(
async fn _user_api_key_login(
data: ConnectData,
user_id: &mut Option<UserId>,
conn: &mut DbConn,
conn: &DbConn,
ip: &ClientIp,
) -> JsonResult {
// Get the user via the client_id
@@ -614,7 +609,7 @@ async fn _user_api_key_login(
Ok(Json(result))
}
async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &ClientIp) -> JsonResult {
async fn _organization_api_key_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> JsonResult {
// Get the org via the client_id
let client_id = data.client_id.as_ref().unwrap();
let Some(org_id) = client_id.strip_prefix("organization.") else {
@@ -643,7 +638,7 @@ async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &
}
/// Retrieves an existing device or creates a new device from ConnectData and the User
async fn get_device(data: &ConnectData, conn: &mut DbConn, user: &User) -> ApiResult<Device> {
async fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> ApiResult<Device> {
// On iOS, device_type sends "iOS", on others it sends a number
// When unknown or unable to parse, return 14, which is 'Unknown Browser'
let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(14);
@@ -663,7 +658,7 @@ async fn twofactor_auth(
device: &mut Device,
ip: &ClientIp,
client_version: &Option<ClientVersion>,
conn: &mut DbConn,
conn: &DbConn,
) -> ApiResult<Option<String>> {
let twofactors = TwoFactor::find_by_user(&user.uuid, conn).await;
@@ -780,7 +775,7 @@ async fn _json_err_twofactor(
user_id: &UserId,
data: &ConnectData,
client_version: &Option<ClientVersion>,
conn: &mut DbConn,
conn: &DbConn,
) -> ApiResult<Value> {
let mut result = json!({
"error" : "invalid_grant",
@@ -905,13 +900,13 @@ enum RegisterVerificationResponse {
#[post("/accounts/register/send-verification-email", data = "<data>")]
async fn register_verification_email(
data: Json<RegisterVerificationData>,
mut conn: DbConn,
conn: DbConn,
) -> ApiResult<RegisterVerificationResponse> {
let data = data.into_inner();
// the registration can only continue if signup is allowed or there exists an invitation
if !(CONFIG.is_signup_allowed(&data.email)
|| (!CONFIG.mail_enabled() && Invitation::find_by_mail(&data.email, &mut conn).await.is_some()))
|| (!CONFIG.mail_enabled() && Invitation::find_by_mail(&data.email, &conn).await.is_some()))
{
err!("Registration not allowed or user already exists")
}
@@ -922,7 +917,7 @@ async fn register_verification_email(
let token = auth::encode_jwt(&token_claims);
if should_send_mail {
let user = User::find_by_mail(&data.email, &mut conn).await;
let user = User::find_by_mail(&data.email, &conn).await;
if user.filter(|u| u.private_key.is_some()).is_some() {
// There is still a timing side channel here in that the code
// paths that send mail take noticeably longer than ones that