mirror of
https://github.com/dani-garcia/vaultwarden.wiki.git
synced 2026-07-19 14:33:33 +00:00
[v2026.5.0] Registration request update (#7295)
* Registration request update * Review fix --------- Co-authored-by: Timshel <timshel@users.noreply.github.com>
This commit is contained in:
+105
-15
@@ -97,14 +97,11 @@ pub struct RegisterData {
|
|||||||
email: String,
|
email: String,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
kdf: KDFData,
|
compat: RegisterDataCompat,
|
||||||
|
|
||||||
#[serde(alias = "userSymmetricKey")]
|
|
||||||
key: String,
|
|
||||||
#[serde(alias = "userAsymmetricKeys")]
|
#[serde(alias = "userAsymmetricKeys")]
|
||||||
keys: Option<KeysData>,
|
keys: Option<KeysData>,
|
||||||
|
|
||||||
master_password_hash: String,
|
|
||||||
master_password_hint: Option<String>,
|
master_password_hint: Option<String>,
|
||||||
|
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
@@ -119,6 +116,102 @@ pub struct RegisterData {
|
|||||||
org_invite_token: Option<String>,
|
org_invite_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RegisterData {
|
||||||
|
fn hash(&self) -> String {
|
||||||
|
self.compat.fold(|rdc| &rdc.master_password_hash, |rdcu| &rdcu.master_password_authentication.hash).to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kdf(&self) -> &KDFData {
|
||||||
|
self.compat.fold(|rdc| &rdc.kdf, |rdcu| &rdcu.master_password_authentication.kdf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key(&self) -> String {
|
||||||
|
self.compat.fold(|rdc| &rdc.key, |rdcu| &rdcu.master_password_unlock.key).to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
// When comparing with salt, email need to be normalized:
|
||||||
|
// - https://github.com/bitwarden/clients/blob/web-v2026.5.0/libs/common/src/key-management/master-password/services/master-password.service.ts#L171
|
||||||
|
fn unprocessable(&self) -> bool {
|
||||||
|
let mut unprocessable = false;
|
||||||
|
*self.compat.fold(
|
||||||
|
|_| &false,
|
||||||
|
|rdcu| {
|
||||||
|
let email = self.email.trim().to_lowercase();
|
||||||
|
unprocessable = rdcu.master_password_authentication.kdf != rdcu.master_password_unlock.kdf
|
||||||
|
|| rdcu.master_password_authentication.salt != email
|
||||||
|
|| rdcu.master_password_unlock.salt != email;
|
||||||
|
&unprocessable
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct RegisterDataOld {
|
||||||
|
#[serde(flatten)]
|
||||||
|
kdf: KDFData,
|
||||||
|
|
||||||
|
#[serde(alias = "userSymmetricKey")]
|
||||||
|
key: String,
|
||||||
|
|
||||||
|
#[serde(alias = "masterPasswordHash")]
|
||||||
|
master_password_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct RegisterDataCur {
|
||||||
|
master_password_authentication: MasterPasswordAuthentication,
|
||||||
|
master_password_unlock: MasterPasswordUnlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum RegisterDataCompat {
|
||||||
|
RegisterDataOld(RegisterDataOld),
|
||||||
|
RegisterDataCur(RegisterDataCur),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisterDataCompat {
|
||||||
|
fn fold<'a, T>(
|
||||||
|
&'a self,
|
||||||
|
fct: impl FnOnce(&'a RegisterDataOld) -> &'a T,
|
||||||
|
fcu: impl FnOnce(&'a RegisterDataCur) -> &'a T,
|
||||||
|
) -> &'a T {
|
||||||
|
match self {
|
||||||
|
RegisterDataCompat::RegisterDataOld(rdc) => fct(rdc),
|
||||||
|
RegisterDataCompat::RegisterDataCur(rdcu) => fcu(rdcu),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct KeysData {
|
||||||
|
encrypted_private_key: String,
|
||||||
|
public_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MasterPasswordAuthentication {
|
||||||
|
kdf: KDFData,
|
||||||
|
salt: String,
|
||||||
|
|
||||||
|
#[serde(alias = "masterPasswordAuthenticationHash")]
|
||||||
|
hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MasterPasswordUnlock {
|
||||||
|
kdf: KDFData,
|
||||||
|
salt: String,
|
||||||
|
|
||||||
|
#[serde(alias = "masterKeyWrappedUserKey")]
|
||||||
|
key: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SetPasswordData {
|
pub struct SetPasswordData {
|
||||||
@@ -132,13 +225,6 @@ pub struct SetPasswordData {
|
|||||||
org_identifier: Option<String>,
|
org_identifier: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct KeysData {
|
|
||||||
encrypted_private_key: String,
|
|
||||||
public_key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trims whitespace from password hints, and converts blank password hints to `None`.
|
/// Trims whitespace from password hints, and converts blank password hints to `None`.
|
||||||
fn clean_password_hint(password_hint: Option<&String>) -> Option<String> {
|
fn clean_password_hint(password_hint: Option<&String>) -> Option<String> {
|
||||||
match password_hint {
|
match password_hint {
|
||||||
@@ -177,6 +263,10 @@ pub async fn register(data: Json<RegisterData>, email_verification: bool, conn:
|
|||||||
|
|
||||||
let mut pending_emergency_access = None;
|
let mut pending_emergency_access = None;
|
||||||
|
|
||||||
|
if data.unprocessable() {
|
||||||
|
err_code!("Unexpected RegisterData format", Status::UnprocessableEntity.code);
|
||||||
|
}
|
||||||
|
|
||||||
// First, validate the provided verification tokens
|
// First, validate the provided verification tokens
|
||||||
if email_verification {
|
if email_verification {
|
||||||
match (
|
match (
|
||||||
@@ -257,8 +347,8 @@ pub async fn register(data: Json<RegisterData>, email_verification: bool, conn:
|
|||||||
err!("Registration not allowed or user already exists")
|
err!("Registration not allowed or user already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(token) = data.org_invite_token {
|
if let Some(token) = data.org_invite_token.as_ref() {
|
||||||
let claims = decode_invite(&token)?;
|
let claims = decode_invite(token)?;
|
||||||
if claims.email == email {
|
if claims.email == email {
|
||||||
// Verify the email address when signing up via a valid invite token
|
// Verify the email address when signing up via a valid invite token
|
||||||
email_verified = true;
|
email_verified = true;
|
||||||
@@ -296,9 +386,9 @@ pub async fn register(data: Json<RegisterData>, email_verification: bool, conn:
|
|||||||
// Make sure we don't leave a lingering invitation.
|
// Make sure we don't leave a lingering invitation.
|
||||||
Invitation::take(&email, &conn).await;
|
Invitation::take(&email, &conn).await;
|
||||||
|
|
||||||
set_kdf_data(&mut user, &data.kdf)?;
|
set_kdf_data(&mut user, data.kdf())?;
|
||||||
|
|
||||||
user.set_password(&data.master_password_hash, Some(data.key), true, None, &conn).await?;
|
user.set_password(&data.hash(), Some(data.key()), true, None, &conn).await?;
|
||||||
user.password_hint = password_hint;
|
user.password_hint = password_hint;
|
||||||
|
|
||||||
// Add extra fields if present
|
// Add extra fields if present
|
||||||
|
|||||||
Reference in New Issue
Block a user