Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds user_change_password #267

Merged
merged 4 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,17 @@ impl BlockingIronOxide {
self.runtime
.block_on(self.ironoxide.user_rotate_private_key(password))
}
/// See [ironoxide::user::UserOps::user_change_password](trait.UserOps.html#tymethod.user_change_password)
pub fn user_change_password(
&self,
current_password: &str,
new_password: &str,
) -> Result<UserUpdateResult> {
self.runtime.block_on(
self.ironoxide
.user_change_password(current_password, new_password),
)
}
}

/// Creates a tokio runtime on the current thread
Expand Down
2 changes: 2 additions & 0 deletions src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ lazy_static! {
pub enum RequestErrorCode {
UserVerify,
UserCreate,
UserUpdate,
UserDeviceAdd,
UserDeviceDelete,
UserDeviceList,
Expand Down Expand Up @@ -99,6 +100,7 @@ pub enum SdkOperation {
UserVerify,
UserGetPublicKey,
UserRotatePrivateKey,
UserChangePassword,
GroupList,
GroupCreate,
GroupGetMetadata,
Expand Down
37 changes: 37 additions & 0 deletions src/internal/user_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ pub struct UserCreateResult {
needs_rotation: bool,
}

pub type UserUpdateResult = UserCreateResult;

impl UserCreateResult {
/// Public key for the user
///
Expand Down Expand Up @@ -772,6 +774,41 @@ fn gen_device_add_signature<CR: rand::CryptoRng + rand::RngCore>(
.into()
}

/// Change the password for the user
pub async fn user_change_password(
password: Password,
new_password: Password,
auth: &RequestAuth,
) -> Result<UserCreateResult, IronOxideErr> {
let requests::user_get::CurrentUserResponse {
user_private_key: encrypted_priv_key,
id: curr_user_id,
..
} = requests::user_get::get_curr_user(auth).await?;
let new_encrypted_priv_key = {
let priv_key: PrivateKey = aes::decrypt_user_master_key(
&password.0,
&aes::EncryptedMasterKey::new_from_slice(&encrypted_priv_key.0)?,
)?
.into();

aes::encrypt_user_master_key(
&Mutex::new(OsRng::default()),
&new_password.0,
priv_key.as_bytes(),
)?
};
Ok(
requests::user_update::user_update(
auth,
&curr_user_id,
Some(new_encrypted_priv_key.into()),
)
.await?
.try_into()?,
)
}

#[cfg(test)]
pub(crate) mod tests {
use super::*;
Expand Down
52 changes: 52 additions & 0 deletions src/internal/user_api/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,58 @@ pub mod user_create {
}
}

pub mod user_update {
use crate::internal::{user_api::UserCreateResult, TryInto};

use super::*;

#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserUpdateResponse {
id: String,
status: usize,
segment_id: usize,
pub user_private_key: EncryptedPrivateKey,
pub user_master_public_key: PublicKey,
needs_rotation: bool,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct UserUpdateReq {
user_private_key: Option<EncryptedPrivateKey>,
}

pub async fn user_update(
auth: &RequestAuth,
user_id: &UserId,
encrypted_user_private_key: Option<EncryptedPrivateKey>,
) -> Result<UserUpdateResponse, IronOxideErr> {
let req_body = UserUpdateReq {
user_private_key: encrypted_user_private_key,
};
auth.request
.put(
&format!("users/{}", rest::url_encode(user_id.id())),
&req_body,
RequestErrorCode::UserUpdate,
AuthV2Builder::new(auth, OffsetDateTime::now_utc()),
)
.await
}

impl TryFrom<UserUpdateResponse> for UserCreateResult {
type Error = IronOxideErr;

fn try_from(resp: UserUpdateResponse) -> Result<Self, Self::Error> {
Ok(UserCreateResult {
user_public_key: resp.user_master_public_key.try_into()?,
needs_rotation: resp.needs_rotation,
})
}
}
}

pub mod user_key_list {
use super::*;

Expand Down
44 changes: 43 additions & 1 deletion src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
pub use crate::internal::user_api::{
DeviceAddResult, DeviceId, DeviceName, EncryptedPrivateKey, Jwt, JwtClaims, KeyPair,
UserCreateResult, UserDevice, UserDeviceListResult, UserId, UserResult,
UserUpdatePrivateKeyResult,
UserUpdatePrivateKeyResult, UserUpdateResult,
};
use crate::{
common::{PublicKey, SdkOperation},
Expand Down Expand Up @@ -250,6 +250,31 @@ pub trait UserOps {
/// # }
/// ```
async fn user_delete_device(&self, device_id: Option<&DeviceId>) -> Result<DeviceId>;

/// Change the password for the user
///
/// This will result in the password that is being used to encrypt the user private key to be changed.
///
/// # Arguments
/// `current_password` - Password to unlock the current user's private key
/// `new_password` - New password to lock the current user's private key
///
/// # Examples
/// ```
/// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
/// # use ironoxide::prelude::*;
/// # let sdk: IronOxide = unimplemented!();
/// let password = "foobar";
/// let new_password = "barbaz";
/// let change_password_result = sdk.user_change_password(password, new_password).await?;
/// # Ok(())
/// # }
/// ```
async fn user_change_password(
&self,
current_password: &str,
new_password: &str,
) -> Result<UserUpdateResult>;
}

#[async_trait]
Expand Down Expand Up @@ -351,6 +376,23 @@ impl UserOps for IronOxide {
)
.await?
}

async fn user_change_password(
&self,
current_password: &str,
new_password: &str,
) -> Result<UserUpdateResult> {
add_optional_timeout(
user_api::user_change_password(
current_password.try_into()?,
new_password.try_into()?,
self.device.auth(),
),
self.config.sdk_operation_timeout,
SdkOperation::UserChangePassword,
)
.await?
}
}

#[cfg(test)]
Expand Down
51 changes: 51 additions & 0 deletions tests/user_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,57 @@ async fn user_private_key_rotation() -> Result<(), IronOxideErr> {
Ok(())
}

#[tokio::test]
async fn user_change_password() -> Result<(), IronOxideErr> {
let account_id: UserId = Uuid::new_v4().to_string().try_into()?;
let first_password = "foo";
let new_password = "bar";
let initial_result = IronOxide::user_create(
&gen_jwt(Some(account_id.id())).0,
first_password,
&Default::default(),
None,
)
.await?;
let device: DeviceContext = IronOxide::generate_new_device(
&gen_jwt(Some(account_id.id())).0,
first_password,
&Default::default(),
None,
)
.await?
.into();
let sdk = ironoxide::initialize(&device, &Default::default()).await?;
let change_passcode_result = sdk
.user_change_password(first_password, new_password)
.await?;

assert_eq!(
initial_result.user_public_key(),
change_passcode_result.user_public_key()
);

//Make sure we can't add a device with the old password.
assert!(IronOxide::generate_new_device(
&gen_jwt(Some(account_id.id())).0,
first_password,
&Default::default(),
None,
)
.await
.is_err());

//Make sure we can add a new device with the new password
IronOxide::generate_new_device(
&gen_jwt(Some(account_id.id())).0,
new_password,
&Default::default(),
None,
)
.await?;
Ok(())
}

#[tokio::test]
async fn sdk_init_with_private_key_rotation() -> Result<(), IronOxideErr> {
use ironoxide::InitAndRotationCheck;
Expand Down