|
|
@ -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,17 +116,70 @@ 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() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn unprocessable(&self) -> bool { |
|
|
|
|
|
let mut unprocessable = false; |
|
|
|
|
|
*self.compat.fold( |
|
|
|
|
|
|_| &false, |
|
|
|
|
|
|rdcu| { |
|
|
|
|
|
unprocessable = rdcu.master_password_authentication.kdf != rdcu.master_password_unlock.kdf |
|
|
|
|
|
|| rdcu.master_password_authentication.salt != self.email |
|
|
|
|
|
|| rdcu.master_password_unlock.salt != self.email; |
|
|
|
|
|
&unprocessable |
|
|
|
|
|
}, |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)] |
|
|
#[derive(Debug, Deserialize)] |
|
|
#[serde(rename_all = "camelCase")] |
|
|
struct RegisterDataOld { |
|
|
pub struct SetPasswordData { |
|
|
|
|
|
#[serde(flatten)] |
|
|
#[serde(flatten)] |
|
|
kdf: KDFData, |
|
|
kdf: KDFData, |
|
|
|
|
|
|
|
|
|
|
|
#[serde(alias = "userSymmetricKey")] |
|
|
key: String, |
|
|
key: String, |
|
|
keys: Option<KeysData>, |
|
|
|
|
|
|
|
|
#[serde(alias = "masterPasswordHash")] |
|
|
master_password_hash: String, |
|
|
master_password_hash: String, |
|
|
master_password_hint: Option<String>, |
|
|
} |
|
|
org_identifier: Option<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)] |
|
|
#[derive(Debug, Deserialize)] |
|
|
@ -139,6 +189,39 @@ struct KeysData { |
|
|
public_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)] |
|
|
|
|
|
#[serde(rename_all = "camelCase")] |
|
|
|
|
|
pub struct SetPasswordData { |
|
|
|
|
|
#[serde(flatten)] |
|
|
|
|
|
kdf: KDFData, |
|
|
|
|
|
|
|
|
|
|
|
key: String, |
|
|
|
|
|
keys: Option<KeysData>, |
|
|
|
|
|
master_password_hash: String, |
|
|
|
|
|
master_password_hint: Option<String>, |
|
|
|
|
|
org_identifier: Option<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 +260,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 +344,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 +383,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
|
|
|
|