ironoxide 0.19.0

A pure-Rust SDK for accessing IronCore's privacy platform
Documentation
pub use crate::internal::user_api::{
    EncryptedPrivateKey, UserCreateResult, UserDevice, UserDeviceListResult, UserId, UserResult,
    UserUpdatePrivateKeyResult,
};
use crate::{
    internal::{
        add_optional_timeout,
        user_api::{self, DeviceId, DeviceName},
        DeviceAddResult, PublicKey, OUR_REQUEST,
    },
    IronOxide, Result, SdkOperation,
};
use recrypt::api::Recrypt;
use std::{collections::HashMap, convert::TryInto};

/// Optional parameters for creating a new device instance.
#[derive(Debug, PartialEq, Clone)]
pub struct DeviceCreateOpts {
    device_name: Option<DeviceName>,
}
impl DeviceCreateOpts {
    /// Create a new device with an optional readable name for the device.
    pub fn new(device_name: Option<DeviceName>) -> DeviceCreateOpts {
        DeviceCreateOpts { device_name }
    }
}
impl Default for DeviceCreateOpts {
    fn default() -> Self {
        DeviceCreateOpts::new(None)
    }
}

/// Options that can be specified at when calling [`user_create`](trait.UserOps.html#tymethod.user_create).
pub struct UserCreateOpts {
    // see docs on `new`
    needs_rotation: bool,
}

impl UserCreateOpts {
    /// Constructor. Also see [`default`](#method.default).
    ///
    /// # Arguments
    /// - `needs_rotation` - Set to true if the private key for this user should be rotated when the user "takes control" of their keys.
    ///
    /// The main use case for this is a workflow that requires that users and groups to be generated
    /// prior to the user logging in for the first time. In this situation, a user's cryptographic identity
    /// can be generated by a third party, like a server process, and then the user can take control of their
    /// keys by rotating the private key using [`rotate_private_key`](https://github.com/IronCoreLabs/ironoxide/issues/44).
    pub fn new(needs_rotation: bool) -> UserCreateOpts {
        UserCreateOpts { needs_rotation }
    }
}

impl Default for UserCreateOpts {
    fn default() -> Self {
        UserCreateOpts::new(false)
    }
}

#[async_trait]
pub trait UserOps {
    /// Sync a new user within the IronCore system.
    ///
    /// # Arguments
    /// - `jwt` - Valid IronCore or Auth0 JWT
    /// - `password` - Password used to encrypt and escrow the user's private master key
    /// - `user_create_opts` - see [`UserCreateOpts`](struct.UserCreateOpts.html)
    /// - `timeout` - timeout for this operation or None for no timeout. Timed out operations return IronOxideErr::OperationTimedOut
    /// # Returns
    /// Newly generated `UserCreateResult` or Err. For most use cases, this public key can
    /// be discarded as IronCore escrows your user's keys. The escrowed keys are unlocked
    /// by the provided password.
    async fn user_create(
        jwt: &str,
        password: &str,
        user_create_opts: &UserCreateOpts,
        timeout: Option<std::time::Duration>,
    ) -> Result<UserCreateResult>;

    /// Get all the devices for the current user
    ///
    /// # Returns
    /// All devices for the current user, sorted by the device id.
    async fn user_list_devices(&self) -> Result<UserDeviceListResult>;

    /// Generates a new device for the user specified in the signed JWT.
    ///
    /// This will result in a new transform key (from the user's master private key to the new device's public key)
    /// being generated and stored with the IronCore Service.
    ///
    /// # Arguments
    /// - `jwt`                   - Valid IronCore JWT
    /// - `password`              - Password used to encrypt and escrow the user's private key
    /// - `device_create_options` - Optional device create arguments, like device name
    /// - `timeout` - timeout for this operation or None for no timeout. Timed out operations return IronOxideErr::OperationTimedOut
    ///
    /// # Returns
    /// Details about the newly created device.
    async fn generate_new_device(
        jwt: &str,
        password: &str,
        device_create_options: &DeviceCreateOpts,
        timeout: Option<std::time::Duration>,
    ) -> Result<DeviceAddResult>;

    /// Delete a user device.
    ///
    /// If deleting the currently signed in device (None for `device_id`), the sdk will need to be
    /// reinitialized with `IronOxide.initialize()` before further use.
    ///
    /// # Arguments
    /// - `device_id` - ID of the device to delete. Get from `user_list_devices`. If None, deletes the currently SDK contexts device which
    ///                 once deleted will cause this SDK instance to no longer function.
    ///
    /// # Returns
    /// Id of deleted device or IronOxideErr
    async fn user_delete_device(&self, device_id: Option<&DeviceId>) -> Result<DeviceId>;

    /// Verify a user given a JWT for their user record.
    ///
    /// # Arguments
    /// - `jwt` - Valid IronCore JWT
    /// - `timeout` - timeout for this operation or None for no timeout. Timed out operations return IronOxideErr::OperationTimedOut
    ///
    /// # Returns
    /// Option of whether the user's account record exists in the IronCore system or not. Err if the request couldn't be made.
    async fn user_verify(
        jwt: &str,
        timeout: Option<std::time::Duration>,
    ) -> Result<Option<UserResult>>;

    /// Get a list of user public keys given their IDs. Allows discovery of which user IDs have keys in the
    /// IronCore system to determine of they can be added to groups or have documents shared with them.
    ///
    /// # Arguments
    /// - users - List of user IDs to check
    ///
    /// # Returns
    /// Map from user ID to users public key. Only users who have public keys will be returned in the map.
    async fn user_get_public_key(&self, users: &[UserId]) -> Result<HashMap<UserId, PublicKey>>;

    /// Rotate the current user's private key, but leave the public key the same.
    /// There's no black magic here! This is accomplished via multi-party computation with the
    /// IronCore webservice.
    ///
    /// # Arguments
    /// `password` - Password to unlock the current user's user master key
    ///
    /// # Returns
    /// The (encrypted) updated private key and associated metadata
    async fn user_rotate_private_key(&self, password: &str) -> Result<UserUpdatePrivateKeyResult>;
}

#[async_trait]
impl UserOps for IronOxide {
    async fn user_create(
        jwt: &str,
        password: &str,
        user_create_opts: &UserCreateOpts,
        timeout: Option<std::time::Duration>,
    ) -> Result<UserCreateResult> {
        let recrypt = Recrypt::new();
        add_optional_timeout(
            user_api::user_create(
                &recrypt,
                jwt.try_into()?,
                password.try_into()?,
                user_create_opts.needs_rotation,
                *OUR_REQUEST,
            ),
            timeout,
            SdkOperation::UserCreate,
        )
        .await?
    }

    async fn user_list_devices(&self) -> Result<UserDeviceListResult> {
        add_optional_timeout(
            user_api::device_list(self.device.auth()),
            self.config.sdk_operation_timeout,
            SdkOperation::UserListDevices,
        )
        .await?
    }

    async fn generate_new_device(
        jwt: &str,
        password: &str,
        device_create_options: &DeviceCreateOpts,
        timeout: Option<std::time::Duration>,
    ) -> Result<DeviceAddResult> {
        let recrypt = Recrypt::new();

        let device_create_options = device_create_options.clone();

        add_optional_timeout(
            user_api::generate_device_key(
                &recrypt,
                &jwt.try_into()?,
                password.try_into()?,
                device_create_options.device_name,
                &std::time::SystemTime::now().into(),
                &OUR_REQUEST,
            ),
            timeout,
            SdkOperation::GenerateNewDevice,
        )
        .await?
    }

    async fn user_delete_device(&self, device_id: Option<&DeviceId>) -> Result<DeviceId> {
        add_optional_timeout(
            user_api::device_delete(self.device.auth(), device_id),
            self.config.sdk_operation_timeout,
            SdkOperation::UserDeleteDevice,
        )
        .await?
    }

    async fn user_verify(
        jwt: &str,
        timeout: Option<std::time::Duration>,
    ) -> Result<Option<UserResult>> {
        add_optional_timeout(
            user_api::user_verify(jwt.try_into()?, *OUR_REQUEST),
            timeout,
            SdkOperation::UserVerify,
        )
        .await?
    }

    async fn user_get_public_key(&self, users: &[UserId]) -> Result<HashMap<UserId, PublicKey>> {
        add_optional_timeout(
            user_api::user_key_list(self.device.auth(), &users.to_vec()),
            self.config.sdk_operation_timeout,
            SdkOperation::UserGetPublicKey,
        )
        .await?
    }

    async fn user_rotate_private_key(&self, password: &str) -> Result<UserUpdatePrivateKeyResult> {
        add_optional_timeout(
            user_api::user_rotate_private_key(
                &self.recrypt,
                password.try_into()?,
                self.device().auth(),
            ),
            self.config.sdk_operation_timeout,
            SdkOperation::UserRotatePrivateKey,
        )
        .await?
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use galvanic_assert::matchers::*;

    #[test]
    fn user_create_opts_defaults() {
        let opts = UserCreateOpts::default();
        assert_that!(
            &opts,
            has_structure!(UserCreateOpts {
                needs_rotation: eq(false)
            })
        )
    }
    #[test]
    fn user_create_opts_new() {
        let opts = UserCreateOpts::new(true);
        assert_that!(
            &opts,
            has_structure!(UserCreateOpts {
                needs_rotation: eq(true)
            })
        )
    }
}