rename membership and adopt newtype pattern (#5320)

* rename membership

rename UserOrganization to Membership to clarify the relation
and prevent confusion whether something refers to a member(ship) or user

* use newtype pattern

* implement custom derive macro IdFromParam

* add UuidFromParam macro for UUIDs

* add macros to Docker build

Co-authored-by: dfunkt <dfunkt@users.noreply.github.com>

---------

Co-authored-by: dfunkt <dfunkt@users.noreply.github.com>
This commit is contained in:
Stefan Melmuk
2025-01-09 18:37:23 +01:00
committed by GitHub
parent 10d12676cf
commit 871a3f214a
51 changed files with 2800 additions and 2114 deletions

View File

@@ -31,7 +31,7 @@ pub fn routes() -> Vec<Route> {
async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn: DbConn) -> JsonResult {
let data: ConnectData = data.into_inner();
let mut user_uuid: Option<String> = None;
let mut user_id: Option<UserId> = None;
let login_result = match data.grant_type.as_ref() {
"refresh_token" => {
@@ -48,7 +48,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
_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_uuid, &mut conn, &client_header.ip).await
_password_login(data, &mut user_id, &mut conn, &client_header.ip).await
}
"client_credentials" => {
_check_is_some(&data.client_id, "client_id cannot be blank")?;
@@ -59,17 +59,17 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
_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_uuid, &mut conn, &client_header.ip).await
_api_key_login(data, &mut user_id, &mut conn, &client_header.ip).await
}
t => err!("Invalid type", t),
};
if let Some(user_uuid) = user_uuid {
if let Some(user_id) = user_id {
match &login_result {
Ok(_) => {
log_user_event(
EventType::UserLoggedIn as i32,
&user_uuid,
&user_id,
client_header.device_type,
&client_header.ip.ip,
&mut conn,
@@ -80,7 +80,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
if let Some(ev) = e.get_event() {
log_user_event(
ev.event as i32,
&user_uuid,
&user_id,
client_header.device_type,
&client_header.ip.ip,
&mut conn,
@@ -111,7 +111,7 @@ async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
// Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
// ---
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await;
// let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
device.save(conn).await?;
@@ -141,7 +141,7 @@ struct MasterPasswordPolicy {
async fn _password_login(
data: ConnectData,
user_uuid: &mut Option<String>,
user_id: &mut Option<UserId>,
conn: &mut DbConn,
ip: &ClientIp,
) -> JsonResult {
@@ -161,8 +161,8 @@ async fn _password_login(
err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username))
};
// Set the user_uuid here to be passed back used for event logging.
*user_uuid = Some(user.uuid.clone());
// Set the user_id here to be passed back used for event logging.
*user_id = Some(user.uuid.clone());
// Check if the user is disabled
if !user.enabled {
@@ -178,9 +178,8 @@ async fn _password_login(
let password = data.password.as_ref().unwrap();
// If we get an auth request, we don't check the user's password, but the access code of the auth request
if let Some(ref auth_request_uuid) = data.auth_request {
let Some(auth_request) = AuthRequest::find_by_uuid_and_user(auth_request_uuid.as_str(), &user.uuid, conn).await
else {
if let Some(ref auth_request_id) = data.auth_request {
let Some(auth_request) = AuthRequest::find_by_uuid_and_user(auth_request_id, &user.uuid, conn).await else {
err!(
"Auth request not found. Try again.",
format!("IP: {}. Username: {}.", ip.ip, username),
@@ -291,7 +290,7 @@ async fn _password_login(
// Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
// ---
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await;
// let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
device.save(conn).await?;
@@ -359,7 +358,7 @@ async fn _password_login(
async fn _api_key_login(
data: ConnectData,
user_uuid: &mut Option<String>,
user_id: &mut Option<UserId>,
conn: &mut DbConn,
ip: &ClientIp,
) -> JsonResult {
@@ -368,7 +367,7 @@ async fn _api_key_login(
// Validate scope
match data.scope.as_ref().unwrap().as_ref() {
"api" => _user_api_key_login(data, user_uuid, conn, ip).await,
"api" => _user_api_key_login(data, user_id, conn, ip).await,
"api.organization" => _organization_api_key_login(data, conn, ip).await,
_ => err!("Scope not supported"),
}
@@ -376,21 +375,22 @@ async fn _api_key_login(
async fn _user_api_key_login(
data: ConnectData,
user_uuid: &mut Option<String>,
user_id: &mut Option<UserId>,
conn: &mut DbConn,
ip: &ClientIp,
) -> JsonResult {
// Get the user via the client_id
let client_id = data.client_id.as_ref().unwrap();
let Some(client_user_uuid) = client_id.strip_prefix("user.") else {
let Some(client_user_id) = client_id.strip_prefix("user.") else {
err!("Malformed client_id", format!("IP: {}.", ip.ip))
};
let Some(user) = User::find_by_uuid(client_user_uuid, conn).await else {
let client_user_id: UserId = client_user_id.into();
let Some(user) = User::find_by_uuid(&client_user_id, conn).await else {
err!("Invalid client_id", format!("IP: {}.", ip.ip))
};
// Set the user_uuid here to be passed back used for event logging.
*user_uuid = Some(user.uuid.clone());
// Set the user_id here to be passed back used for event logging.
*user_id = Some(user.uuid.clone());
// Check if the user is disabled
if !user.enabled {
@@ -440,7 +440,7 @@ async fn _user_api_key_login(
// Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
// ---
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await;
// let members = Membership::find_confirmed_by_user(&user.uuid, conn).await;
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
device.save(conn).await?;
@@ -469,10 +469,11 @@ async fn _user_api_key_login(
async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &ClientIp) -> JsonResult {
// Get the org via the client_id
let client_id = data.client_id.as_ref().unwrap();
let Some(org_uuid) = client_id.strip_prefix("organization.") else {
let Some(org_id) = client_id.strip_prefix("organization.") else {
err!("Malformed client_id", format!("IP: {}.", ip.ip))
};
let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(org_uuid, conn).await else {
let org_id: OrganizationId = org_id.to_string().into();
let Some(org_api_key) = OrganizationApiKey::find_by_org_uuid(&org_id, conn).await else {
err!("Invalid client_id", format!("IP: {}.", ip.ip))
};
@@ -614,7 +615,7 @@ fn _selected_data(tf: Option<TwoFactor>) -> ApiResult<String> {
async fn _json_err_twofactor(
providers: &[i32],
user_uuid: &str,
user_id: &UserId,
data: &ConnectData,
conn: &mut DbConn,
) -> ApiResult<Value> {
@@ -635,12 +636,12 @@ async fn _json_err_twofactor(
Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ }
Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => {
let request = webauthn::generate_webauthn_login(user_uuid, conn).await?;
let request = webauthn::generate_webauthn_login(user_id, conn).await?;
result["TwoFactorProviders2"][provider.to_string()] = request.0;
}
Some(TwoFactorType::Duo) => {
let email = match User::find_by_uuid(user_uuid, conn).await {
let email = match User::find_by_uuid(user_id, conn).await {
Some(u) => u.email,
None => err!("User does not exist"),
};
@@ -672,7 +673,7 @@ async fn _json_err_twofactor(
}
Some(tf_type @ TwoFactorType::YubiKey) => {
let Some(twofactor) = TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await else {
let Some(twofactor) = TwoFactor::find_by_user_and_type(user_id, tf_type as i32, conn).await else {
err!("No YubiKey devices registered")
};
@@ -684,13 +685,13 @@ async fn _json_err_twofactor(
}
Some(tf_type @ TwoFactorType::Email) => {
let Some(twofactor) = TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await else {
let Some(twofactor) = TwoFactor::find_by_user_and_type(user_id, tf_type as i32, conn).await else {
err!("No twofactor email registered")
};
// Send email immediately if email is the only 2FA option
if providers.len() == 1 {
email::send_token(user_uuid, conn).await?
email::send_token(user_id, conn).await?
}
let email_data = email::EmailTokenData::from_json(&twofactor.data)?;
@@ -745,7 +746,7 @@ struct ConnectData {
#[field(name = uncased("device_identifier"))]
#[field(name = uncased("deviceidentifier"))]
device_identifier: Option<String>,
device_identifier: Option<DeviceId>,
#[field(name = uncased("device_name"))]
#[field(name = uncased("devicename"))]
device_name: Option<String>,
@@ -768,7 +769,7 @@ struct ConnectData {
#[field(name = uncased("twofactorremember"))]
two_factor_remember: Option<i32>,
#[field(name = uncased("authrequest"))]
auth_request: Option<String>,
auth_request: Option<AuthRequestId>,
}
fn _check_is_some<T>(value: &Option<T>, msg: &str) -> EmptyResult {