From 82cc332ff1396cfdebd8666688f3d3f80b5db3be Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Tue, 17 Dec 2024 01:43:27 +0000 Subject: [PATCH 01/10] feat: moves user operations to a nested User struct --- src/lib.rs | 1 + src/passage_flex.rs | 212 ++------------------------------------- src/passage_flex/test.rs | 14 ++- src/user.rs | 179 +++++++++++++++++++++++++++++++++ 4 files changed, 201 insertions(+), 205 deletions(-) create mode 100644 src/user.rs diff --git a/src/lib.rs b/src/lib.rs index 7607bc8..126cf59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,4 +63,5 @@ pub mod models; pub mod openapi; pub mod passage_flex; +pub mod user; pub use passage_flex::PassageFlex; diff --git a/src/passage_flex.rs b/src/passage_flex.rs index c9d6065..ca024cc 100644 --- a/src/passage_flex.rs +++ b/src/passage_flex.rs @@ -1,13 +1,13 @@ -use crate::models::{AppInfo, UserInfo}; +use crate::models::AppInfo; use crate::openapi::apis::configuration::Configuration; -use crate::openapi::apis::{ - apps_api, authenticate_api, transactions_api, user_devices_api, users_api, -}; +use crate::openapi::apis::{apps_api, authenticate_api, transactions_api}; +use crate::user::User; use crate::Error; pub struct PassageFlex { app_id: String, configuration: Configuration, + pub user: User, } const SERVER_URL: &str = "https://api.passage.id"; @@ -31,10 +31,6 @@ impl PassageFlex { /// ); /// ``` pub fn new(app_id: String, api_key: String) -> Self { - let mut configuration = Configuration::new(); - // Use the api_key as the bearer access token - configuration.bearer_access_token = Some(api_key); - // Set the Passage-Version header to the version of the crate let mut headers = reqwest::header::HeaderMap::with_capacity(1); headers.insert( "Passage-Version", @@ -43,14 +39,20 @@ impl PassageFlex { env!("CARGO_PKG_VERSION") )), ); + + let mut configuration = Configuration::new(); + configuration.bearer_access_token = Some(api_key); configuration.client = reqwest::Client::builder() .default_headers(headers) .build() .expect("Failed to create reqwest client for Passage"); + let user = User::new(configuration.clone()); + let mut client = Self { app_id, configuration, + user, }; // Set the default server URL client.set_server_url(SERVER_URL.to_string()); @@ -59,7 +61,7 @@ impl PassageFlex { } fn set_server_url(&mut self, server_url: String) { - // Use the app_id and server_url to set the base_path + self.user.configuration.base_path = format!("{}/v1/apps/{}", server_url, self.app_id); self.configuration.base_path = format!("{}/v1/apps/{}", server_url, self.app_id); } @@ -219,198 +221,6 @@ impl PassageFlex { .map(|response| response.external_id) .map_err(Into::into) } - - /// Get a user's ID in Passage by their external ID - async fn get_id(&self, external_id: String) -> Result { - let users = users_api::list_paginated_users( - &self.configuration, - Some(1), - Some(1), - None, - None, - Some(&external_id), - None, - None, - None, - None, - None, - None, - ) - .await - .map(|response| response.users) - .map_err(Into::into); - - match users { - Ok(mut users) => match users.len() { - 0 => Err(Error::UserNotFound), - 1 => { - let user = users.remove(0); - Ok(user.id) - } - _ => Err(Error::Other("Multiple users found".to_string())), - }, - Err(e) => Err(e), - } - } - - /// Retrieves information about a user by their external ID. - /// - /// # Arguments - /// - /// * `external_id` - The unique, immutable ID that represents the user. - /// - /// # Returns - /// - /// A `Result` containing the `UserInfo` struct or an `Error`. - /// - /// # Examples - /// - /// ```ignore - /// use passage_flex::PassageFlex; - /// - /// let passage_flex = PassageFlex::new( - /// std::env::var("PASSAGE_APP_ID").unwrap(), - /// std::env::var("PASSAGE_API_KEY").unwrap(), - /// ); - /// - /// let external_id = "00000000-0000-0000-0000-000000000001"; - /// let user_info = passage_flex.get_user(external_id.to_string()).await.unwrap(); - /// println!("{:?}", user_info.id); - /// ``` - pub async fn get_user(&self, external_id: String) -> Result, Error> { - let user_id = self.get_id(external_id).await?; - self.get_user_by_id(user_id).await - } - - /// Retrieves information about a user's passkey devices. - /// - /// # Arguments - /// - /// * `external_id` - The unique, immutable ID that represents the user. - /// - /// # Returns - /// - /// A `Result` containing a vector of `WebAuthnDevices` or an `Error`. - /// - /// # Examples - /// - /// ```ignore - /// use passage_flex::PassageFlex; - /// - /// let passage_flex = PassageFlex::new( - /// std::env::var("PASSAGE_APP_ID").unwrap(), - /// std::env::var("PASSAGE_API_KEY").unwrap(), - /// ); - /// - /// let external_id = "00000000-0000-0000-0000-000000000001"; - /// let passkey_devices = passage_flex.get_devices(external_id.to_string()).await.unwrap(); - /// for device in passkey_devices { - /// println!("{}", device.usage_count); - /// } - /// ``` - pub async fn get_devices( - &self, - external_id: String, - ) -> Result, Error> { - let user_id = self.get_id(external_id).await?; - user_devices_api::list_user_devices(&self.configuration, &user_id) - .await - .map(|response| response.devices) - .map_err(Into::into) - } - - /// Revokes a user's passkey device. - /// - /// # Arguments - /// - /// * `external_id` - The unique, immutable ID that represents the user. - /// * `device_id` - The ID of the device to be revoked. - /// - /// # Returns - /// - /// A `Result` containing `()` or an `Error`. - /// - /// # Examples - /// - /// ```ignore - /// use passage_flex::PassageFlex; - /// use chrono::{Duration, NaiveDate, Utc}; - /// - /// let passage_flex = PassageFlex::new( - /// std::env::var("PASSAGE_APP_ID").unwrap(), - /// std::env::var("PASSAGE_API_KEY").unwrap(), - /// ); - /// - /// let external_id = "00000000-0000-0000-0000-000000000001"; - /// let last_year = Utc::now().naive_utc().date() - Duration::days(365); - /// - /// let passkey_devices = passage_flex.get_devices(external_id.to_string()).await.unwrap(); - /// - /// for device in passkey_devices { - /// let last_login_at_parsed = - /// NaiveDate::parse_from_str(&device.last_login_at, "%Y-%m-%dT%H:%M:%S%z").unwrap(); - /// - /// if last_login_at_parsed < last_year { - /// if let Err(err) = passage_flex - /// .revoke_device(external_id.clone(), device.id) - /// .await - /// { - /// // device couldn't be revoked - /// } - /// } - /// } - /// ``` - pub async fn revoke_device(&self, external_id: String, device_id: String) -> Result<(), Error> { - let user_id = self.get_id(external_id).await?; - user_devices_api::delete_user_devices(&self.configuration, &user_id, &device_id) - .await - .map_err(Into::into) - } - - /// Retrieves information about a user by their user ID in Passage. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user in Passage. - /// - /// # Returns - /// - /// A `Result` containing the `UserInfo` struct or an `Error`. - /// - /// # Examples - /// - /// ```ignore - /// use passage_flex::PassageFlex; - /// - /// let passage_flex = PassageFlex::new( - /// std::env::var("PASSAGE_APP_ID").unwrap(), - /// std::env::var("PASSAGE_API_KEY").unwrap(), - /// ); - /// - /// let user_id = "user_id_string"; - /// let user_info = passage_flex.get_user_by_id(user_id.to_string()).await.unwrap(); - /// println!("{:?}", user_info.external_id); - /// ``` - pub async fn get_user_by_id(&self, user_id: String) -> Result, Error> { - users_api::get_user(&self.configuration, &user_id) - .await - .map(|response| { - Box::new(UserInfo { - created_at: response.user.created_at, - external_id: response.user.external_id, - id: response.user.id, - last_login_at: response.user.last_login_at, - login_count: response.user.login_count, - status: response.user.status, - updated_at: response.user.updated_at, - user_metadata: response.user.user_metadata, - webauthn: response.user.webauthn, - webauthn_devices: response.user.webauthn_devices, - webauthn_types: response.user.webauthn_types, - }) - }) - .map_err(Into::into) - } } #[cfg(test)] diff --git a/src/passage_flex/test.rs b/src/passage_flex/test.rs index 62c9da4..ea18a37 100644 --- a/src/passage_flex/test.rs +++ b/src/passage_flex/test.rs @@ -289,7 +289,7 @@ async fn test_get_user() { let (app_id, passage_flex, mut server) = setup_passage_flex().await; let m1 = setup_empty_list_paginated_users_mock(&app_id, &mut server).await; - let invalid_result = passage_flex.get_user("invalid".to_string()).await; + let invalid_result = passage_flex.user.get("invalid".to_string()).await; m1.assert_async().await; assert!(invalid_result.is_err()); assert!(matches!(invalid_result, Err(Error::UserNotFound))); @@ -297,7 +297,7 @@ async fn test_get_user() { let m2 = setup_valid_list_paginated_users_mock(&app_id, &mut server).await; let m3 = setup_valid_get_user_mock(&app_id, &mut server).await; - let user_info = passage_flex.get_user("valid".to_string()).await.unwrap(); + let user_info = passage_flex.user.get("valid".to_string()).await.unwrap(); m2.assert_async().await; m3.assert_async().await; assert_eq!(user_info.external_id, "valid"); @@ -308,14 +308,18 @@ async fn test_get_devices() { let (app_id, passage_flex, mut server) = setup_passage_flex().await; let m1 = setup_empty_list_paginated_users_mock(&app_id, &mut server).await; - let invalid_result = passage_flex.get_devices("invalid".to_string()).await; + let invalid_result = passage_flex.user.list_devices("invalid".to_string()).await; m1.assert_async().await; assert!(invalid_result.is_err()); assert!(matches!(invalid_result, Err(Error::UserNotFound))); let m2 = setup_valid_list_paginated_users_mock(&app_id, &mut server).await; let m3 = setup_valid_get_devices_mock(&app_id, &mut server).await; - let devices = passage_flex.get_devices("valid".to_string()).await.unwrap(); + let devices = passage_flex + .user + .list_devices("valid".to_string()) + .await + .unwrap(); m2.assert_async().await; m3.assert_async().await; assert_eq!(devices.len(), 1); @@ -328,6 +332,7 @@ async fn test_revoke_device() { let m1 = setup_empty_list_paginated_users_mock(&app_id, &mut server).await; let invalid_result = passage_flex + .user .revoke_device("invalid".to_string(), "invalid".to_string()) .await; m1.assert_async().await; @@ -337,6 +342,7 @@ async fn test_revoke_device() { let m2 = setup_valid_list_paginated_users_mock(&app_id, &mut server).await; let m3 = setup_valid_revoke_device_mock(&app_id, &mut server).await; let result = passage_flex + .user .revoke_device("valid".to_string(), "test_device_id".to_string()) .await; m2.assert_async().await; diff --git a/src/user.rs b/src/user.rs new file mode 100644 index 0000000..fd0ae65 --- /dev/null +++ b/src/user.rs @@ -0,0 +1,179 @@ +use crate::models::UserInfo; +use crate::openapi::apis::configuration::Configuration; +use crate::openapi::apis::{user_devices_api, users_api}; +use crate::Error; + +pub struct User { + pub(crate) configuration: Configuration, +} + +impl User { + /// Creates a new instance of the `User` struct. + pub fn new(configuration: Configuration) -> Self { + Self { configuration } + } + + /// Get a user's ID in Passage by their external ID + async fn get_id(&self, external_id: String) -> Result { + let users = users_api::list_paginated_users( + &self.configuration, + Some(1), + Some(1), + None, + None, + Some(&external_id), + None, + None, + None, + None, + None, + None, + ) + .await + .map(|response| response.users) + .map_err(Into::into); + + match users { + Ok(mut users) => match users.len() { + 0 => Err(Error::UserNotFound), + 1 => { + let user = users.remove(0); + Ok(user.id) + } + _ => Err(Error::Other("Multiple users found".to_string())), + }, + Err(e) => Err(e), + } + } + + /// Retrieves information about a user by their external ID. + /// + /// # Arguments + /// + /// * `external_id` - The unique, immutable ID that represents the user. + /// + /// # Returns + /// + /// A `Result` containing the `UserInfo` struct or an `Error`. + /// + /// # Examples + /// + /// ```ignore + /// use passage_flex::PassageFlex; + /// + /// let passage_flex = PassageFlex::new( + /// std::env::var("PASSAGE_APP_ID").unwrap(), + /// std::env::var("PASSAGE_API_KEY").unwrap(), + /// ); + /// + /// let external_id = "00000000-0000-0000-0000-000000000001"; + /// let user_info = passage_flex.get(external_id.to_string()).await.unwrap(); + /// println!("{:?}", user_info.id); + /// ``` + pub async fn get(&self, external_id: String) -> Result, Error> { + let user_id = self.get_id(external_id).await?; + users_api::get_user(&self.configuration, &user_id) + .await + .map(|response| { + Box::new(UserInfo { + created_at: response.user.created_at, + external_id: response.user.external_id, + id: response.user.id, + last_login_at: response.user.last_login_at, + login_count: response.user.login_count, + status: response.user.status, + updated_at: response.user.updated_at, + user_metadata: response.user.user_metadata, + webauthn: response.user.webauthn, + webauthn_devices: response.user.webauthn_devices, + webauthn_types: response.user.webauthn_types, + }) + }) + .map_err(Into::into) + } + + /// Retrieves information about a user's passkey devices. + /// + /// # Arguments + /// + /// * `external_id` - The unique, immutable ID that represents the user. + /// + /// # Returns + /// + /// A `Result` containing a vector of `WebAuthnDevices` or an `Error`. + /// + /// # Examples + /// + /// ```ignore + /// use passage_flex::PassageFlex; + /// + /// let passage_flex = PassageFlex::new( + /// std::env::var("PASSAGE_APP_ID").unwrap(), + /// std::env::var("PASSAGE_API_KEY").unwrap(), + /// ); + /// + /// let external_id = "00000000-0000-0000-0000-000000000001"; + /// let passkey_devices = passage_flex.list_devices(external_id.to_string()).await.unwrap(); + /// for device in passkey_devices { + /// println!("{}", device.usage_count); + /// } + /// ``` + pub async fn list_devices( + &self, + external_id: String, + ) -> Result, Error> { + let user_id = self.get_id(external_id).await?; + user_devices_api::list_user_devices(&self.configuration, &user_id) + .await + .map(|response| response.devices) + .map_err(Into::into) + } + + /// Revokes a user's passkey device. + /// + /// # Arguments + /// + /// * `external_id` - The unique, immutable ID that represents the user. + /// * `device_id` - The ID of the device to be revoked. + /// + /// # Returns + /// + /// A `Result` containing `()` or an `Error`. + /// + /// # Examples + /// + /// ```ignore + /// use passage_flex::PassageFlex; + /// use chrono::{Duration, NaiveDate, Utc}; + /// + /// let passage_flex = PassageFlex::new( + /// std::env::var("PASSAGE_APP_ID").unwrap(), + /// std::env::var("PASSAGE_API_KEY").unwrap(), + /// ); + /// + /// let external_id = "00000000-0000-0000-0000-000000000001"; + /// let last_year = Utc::now().naive_utc().date() - Duration::days(365); + /// + /// let passkey_devices = passage_flex.get_devices(external_id.to_string()).await.unwrap(); + /// + /// for device in passkey_devices { + /// let last_login_at_parsed = + /// NaiveDate::parse_from_str(&device.last_login_at, "%Y-%m-%dT%H:%M:%S%z").unwrap(); + /// + /// if last_login_at_parsed < last_year { + /// if let Err(err) = passage_flex + /// .revoke_device(external_id.clone(), device.id) + /// .await + /// { + /// // device couldn't be revoked + /// } + /// } + /// } + /// ``` + pub async fn revoke_device(&self, external_id: String, device_id: String) -> Result<(), Error> { + let user_id = self.get_id(external_id).await?; + user_devices_api::delete_user_devices(&self.configuration, &user_id, &device_id) + .await + .map_err(Into::into) + } +} From 1d012f85c80d1f0eac835f621167ff11caf835c5 Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Tue, 17 Dec 2024 01:51:57 +0000 Subject: [PATCH 02/10] feat: moves auth operations to a nested Auth struct --- src/auth.rs | 139 +++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/passage_flex.rs | 172 ++------------------------------------- src/passage_flex/test.rs | 104 ++--------------------- 4 files changed, 151 insertions(+), 265 deletions(-) create mode 100644 src/auth.rs diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..07d3d85 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,139 @@ +use crate::openapi::apis::configuration::Configuration; +use crate::openapi::apis::{authenticate_api, transactions_api}; +use crate::Error; + +pub struct Auth { + pub(crate) configuration: Configuration, +} + +impl Auth { + /// Creates a new instance of the `Auth` struct. + pub fn new(configuration: Configuration) -> Self { + Self { configuration } + } + + /// Creates a transaction to start a user's registration process. + /// + /// # Arguments + /// + /// * `external_id` - A unique, immutable string that represents the user. + /// * `passkey_display_name` - The label for the user's passkey that they will see when logging in. + /// + /// # Returns + /// + /// A `Result` containing the transaction ID as a string or an `Error`. + /// + /// # Examples + /// + /// ```ignore + /// use passage_flex::PassageFlex; + /// + /// let passage_flex = PassageFlex::new( + /// std::env::var("PASSAGE_APP_ID").unwrap(), + /// std::env::var("PASSAGE_API_KEY").unwrap(), + /// ); + /// + /// let transaction = passage_flex + /// .create_register_transaction( + /// "00000000-0000-0000-0000-000000000001".to_string(), + /// "user@example.com".to_string(), + /// ) + /// .await + /// .unwrap(); + /// ``` + pub async fn create_register_transaction( + &self, + external_id: String, + passkey_display_name: String, + ) -> Result { + transactions_api::create_register_transaction( + &self.configuration, + crate::openapi::models::CreateTransactionRegisterRequest { + external_id, + passkey_display_name, + }, + ) + .await + .map(|response| response.transaction_id) + .map_err(Into::into) + } + + /// Creates a transaction to start a user's authentication process. + /// + /// # Arguments + /// + /// * `external_id` - A unique, immutable string that represents the user. + /// + /// # Returns + /// + /// A `Result` containing the transaction ID as a string or an `Error`. + /// + /// # Examples + /// + /// ```ignore + /// use passage_flex::PassageFlex; + /// + /// let passage_flex = PassageFlex::new( + /// std::env::var("PASSAGE_APP_ID").unwrap(), + /// std::env::var("PASSAGE_API_KEY").unwrap(), + /// ); + /// + /// let transaction = passage_flex + /// .create_authenticate_transaction( + /// "00000000-0000-0000-0000-000000000001".to_string(), + /// ) + /// .await + /// .unwrap(); + /// ``` + pub async fn create_authenticate_transaction( + &self, + external_id: String, + ) -> Result { + transactions_api::create_authenticate_transaction( + &self.configuration, + crate::openapi::models::CreateTransactionAuthenticateRequest { external_id }, + ) + .await + .map(|response| response.transaction_id) + .map_err(Into::into) + } + + /// Verifies the nonce received from a WebAuthn registration or authentication ceremony. + /// + /// # Arguments + /// + /// * `nonce` - The nonce string to be verified. + /// + /// # Returns + /// + /// A `Result` containing the external ID as a string or an `Error`. + /// + /// # Examples + /// + /// ```ignore + /// use passage_flex::PassageFlex; + /// + /// let passage_flex = PassageFlex::new( + /// std::env::var("PASSAGE_APP_ID").unwrap(), + /// std::env::var("PASSAGE_API_KEY").unwrap(), + /// ); + /// + /// match passage_flex.verify_nonce("01234567890123456789".to_string()).await { + /// Ok(external_id) => { + /// // use external_id to do things like generate and send your own auth token + /// } + /// Err(err) => { + /// // nonce was invalid or unable to be verified + /// } + /// } + /// ``` + pub async fn verify_nonce(&self, nonce: String) -> Result { + authenticate_api::authenticate_verify_nonce( + &self.configuration, + crate::openapi::models::Nonce { nonce }, + ) + .await + .map(|response| response.external_id) + .map_err(Into::into) + } +} diff --git a/src/lib.rs b/src/lib.rs index 126cf59..339cd89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,7 @@ pub mod models; #[rustfmt::skip] pub mod openapi; +pub mod auth; pub mod passage_flex; pub mod user; pub use passage_flex::PassageFlex; diff --git a/src/passage_flex.rs b/src/passage_flex.rs index ca024cc..4efbbdb 100644 --- a/src/passage_flex.rs +++ b/src/passage_flex.rs @@ -1,12 +1,10 @@ -use crate::models::AppInfo; +use crate::auth::Auth; use crate::openapi::apis::configuration::Configuration; -use crate::openapi::apis::{apps_api, authenticate_api, transactions_api}; use crate::user::User; -use crate::Error; pub struct PassageFlex { app_id: String, - configuration: Configuration, + pub auth: Auth, pub user: User, } @@ -47,13 +45,10 @@ impl PassageFlex { .build() .expect("Failed to create reqwest client for Passage"); + let auth = Auth::new(configuration.clone()); let user = User::new(configuration.clone()); - let mut client = Self { - app_id, - configuration, - user, - }; + let mut client = Self { app_id, auth, user }; // Set the default server URL client.set_server_url(SERVER_URL.to_string()); @@ -62,164 +57,7 @@ impl PassageFlex { fn set_server_url(&mut self, server_url: String) { self.user.configuration.base_path = format!("{}/v1/apps/{}", server_url, self.app_id); - self.configuration.base_path = format!("{}/v1/apps/{}", server_url, self.app_id); - } - - /// Retrieves information about the application. - /// - /// # Returns - /// - /// A `Result` containing the `AppInfo` struct or an `Error`. - /// - /// # Examples - /// - /// ```ignore - /// use passage_flex::PassageFlex; - /// - /// let passage_flex = PassageFlex::new( - /// std::env::var("PASSAGE_APP_ID").unwrap(), - /// std::env::var("PASSAGE_API_KEY").unwrap(), - /// ); - /// - /// let app_info = passage_flex.get_app().await.unwrap(); - /// println!("{}", app_info.auth_origin); - /// ``` - pub async fn get_app(&self) -> Result, Error> { - apps_api::get_app(&self.configuration) - .await - .map(|response| { - Box::new(AppInfo { - auth_origin: response.app.auth_origin, - id: response.app.id, - name: response.app.name, - }) - }) - .map_err(Into::into) - } - - /// Creates a transaction to start a user's registration process. - /// - /// # Arguments - /// - /// * `external_id` - A unique, immutable string that represents the user. - /// * `passkey_display_name` - The label for the user's passkey that they will see when logging in. - /// - /// # Returns - /// - /// A `Result` containing the transaction ID as a string or an `Error`. - /// - /// # Examples - /// - /// ```ignore - /// use passage_flex::PassageFlex; - /// - /// let passage_flex = PassageFlex::new( - /// std::env::var("PASSAGE_APP_ID").unwrap(), - /// std::env::var("PASSAGE_API_KEY").unwrap(), - /// ); - /// - /// let transaction = passage_flex - /// .create_register_transaction( - /// "00000000-0000-0000-0000-000000000001".to_string(), - /// "user@example.com".to_string(), - /// ) - /// .await - /// .unwrap(); - /// ``` - pub async fn create_register_transaction( - &self, - external_id: String, - passkey_display_name: String, - ) -> Result { - transactions_api::create_register_transaction( - &self.configuration, - crate::openapi::models::CreateTransactionRegisterRequest { - external_id, - passkey_display_name, - }, - ) - .await - .map(|response| response.transaction_id) - .map_err(Into::into) - } - - /// Creates a transaction to start a user's authentication process. - /// - /// # Arguments - /// - /// * `external_id` - A unique, immutable string that represents the user. - /// - /// # Returns - /// - /// A `Result` containing the transaction ID as a string or an `Error`. - /// - /// # Examples - /// - /// ```ignore - /// use passage_flex::PassageFlex; - /// - /// let passage_flex = PassageFlex::new( - /// std::env::var("PASSAGE_APP_ID").unwrap(), - /// std::env::var("PASSAGE_API_KEY").unwrap(), - /// ); - /// - /// let transaction = passage_flex - /// .create_authenticate_transaction( - /// "00000000-0000-0000-0000-000000000001".to_string(), - /// ) - /// .await - /// .unwrap(); - /// ``` - pub async fn create_authenticate_transaction( - &self, - external_id: String, - ) -> Result { - transactions_api::create_authenticate_transaction( - &self.configuration, - crate::openapi::models::CreateTransactionAuthenticateRequest { external_id }, - ) - .await - .map(|response| response.transaction_id) - .map_err(Into::into) - } - - /// Verifies the nonce received from a WebAuthn registration or authentication ceremony. - /// - /// # Arguments - /// - /// * `nonce` - The nonce string to be verified. - /// - /// # Returns - /// - /// A `Result` containing the external ID as a string or an `Error`. - /// - /// # Examples - /// - /// ```ignore - /// use passage_flex::PassageFlex; - /// - /// let passage_flex = PassageFlex::new( - /// std::env::var("PASSAGE_APP_ID").unwrap(), - /// std::env::var("PASSAGE_API_KEY").unwrap(), - /// ); - /// - /// match passage_flex.verify_nonce("01234567890123456789".to_string()).await { - /// Ok(external_id) => { - /// // use external_id to do things like generate and send your own auth token - /// } - /// Err(err) => { - /// // nonce was invalid or unable to be verified - /// } - /// } - /// ``` - pub async fn verify_nonce(&self, nonce: String) -> Result { - authenticate_api::authenticate_verify_nonce( - &self.configuration, - crate::openapi::models::Nonce { nonce }, - ) - .await - .map(|response| response.external_id) - .map_err(Into::into) + self.auth.configuration.base_path = format!("{}/v1/apps/{}", server_url, self.app_id); } } diff --git a/src/passage_flex/test.rs b/src/passage_flex/test.rs index ea18a37..b8f3be2 100644 --- a/src/passage_flex/test.rs +++ b/src/passage_flex/test.rs @@ -1,3 +1,5 @@ +use crate::Error; + use super::*; use mockito::{Matcher, Mock, Server, ServerGuard}; @@ -222,6 +224,7 @@ async fn test_create_register_transaction() { .await; let transaction_id = passage_flex + .auth .create_register_transaction("test".to_string(), "test".to_string()) .await .unwrap(); @@ -244,6 +247,7 @@ async fn test_create_authenticate_transaction() { .await; let transaction_id = passage_flex + .auth .create_authenticate_transaction("test".to_string()) .await .unwrap(); @@ -260,7 +264,7 @@ async fn test_verify_nonce() { .with_body(r#"{"error": "Could not verify nonce: nonce is invalid, expired, or cannot be found","code": "invalid_nonce"}"#) .create_async().await; - let invalid_result = passage_flex.verify_nonce("invalid".to_string()).await; + let invalid_result = passage_flex.auth.verify_nonce("invalid".to_string()).await; assert!(invalid_result.is_err()); assert!(matches!(invalid_result, Err(Error::InvalidNonce))); m1.assert_async().await; @@ -277,6 +281,7 @@ async fn test_verify_nonce() { .await; let external_id = passage_flex + .auth .verify_nonce("valid".to_string()) .await .unwrap(); @@ -349,100 +354,3 @@ async fn test_revoke_device() { m3.assert_async().await; assert!(result.is_ok()); } - -#[tokio::test] -async fn test_get_app() { - let mut server = Server::new_async().await; - let api_key = "test_api_key"; - - let app_id = "invalid"; - let mut invalid_passage = PassageFlex::new(app_id.to_string(), api_key.to_string()); - invalid_passage.set_server_url(server.url()); - let m1 = server - .mock("GET", "/v1/apps/invalid/") - .with_status(401) - .with_body(r#"{"error": "Invalid access token","code": "invalid_access_token"}"#) - .create_async() - .await; - let invalid_result = invalid_passage.get_app().await; - m1.assert_async().await; - assert!(invalid_result.is_err()); - assert!(matches!(invalid_result, Err(Error::InvalidAccessToken))); - - let app_id = "test_app_id"; - let mut passage = PassageFlex::new(app_id.to_string(), api_key.to_string()); - passage.set_server_url(server.url()); - let m2 = server - .mock("GET", "/v1/apps/test_app_id/") - .with_status(200) - .with_body( - r#"{ - "app": { - "additional_auth_origins": [], - "allowed_callback_urls": [], - "allowed_identifier": "external", - "allowed_logout_urls": [], - "application_login_uri": "", - "auth_fallback_method": "none", - "auth_fallback_method_ttl": 300, - "auth_methods": { - "magic_link": { - "enabled": false, - "ttl": 300, - "ttl_display_unit": "s" - }, - "otp": { - "enabled": false, - "ttl": 300, - "ttl_display_unit": "s" - }, - "passkeys": { - "enabled": true - } - }, - "auth_origin": "https://auth.test.com", - "auto_theme_enabled": true, - "created_at": "2021-01-01T00:00:00Z", - "default_language": "en", - "element_customization": {}, - "element_customization_dark": {}, - "hosted": false, - "hosted_subdomain": "test", - "hosted_theme": "auto", - "id": "test_app_id", - "id_token_lifetime": 600, - "layouts": { - "profile": [], - "registration": [] - }, - "login_url": "/", - "name": "Test App", - "passage_branding": true, - "profile_management": false, - "public_signup": false, - "redirect_url": "/", - "refresh_absolute_lifetime": 2592000, - "refresh_enabled": false, - "refresh_inactivity_lifetime": 432000, - "require_email_verification": false, - "require_identifier_verification": false, - "required_identifier": "external", - "role": "", - "rsa_public_key": "", - "secret": null, - "session_timeout_length": 5, - "technologies": [], - "type": "flex", - "user_metadata_schema": [] - } - }"#, - ) - .create_async() - .await; - - let app_info = passage.get_app().await.unwrap(); - m2.assert_async().await; - assert_eq!(app_info.id, "test_app_id"); - assert_eq!(app_info.name, "Test App"); - assert_eq!(app_info.auth_origin, "https://auth.test.com"); -} From a1540af4ccfd4ba260c0d85caf9685cc0de9be30 Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Wed, 18 Dec 2024 09:58:44 -0600 Subject: [PATCH 03/10] chore: update README with new nested classes --- README.md | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 0419373..2c185b0 100644 --- a/README.md +++ b/README.md @@ -59,22 +59,6 @@ Find more details about Passkey Flex on our [Passkey Flex Documentation](https:/ ## API Reference -### Retrieve App Info - -To retrieve information about the app, use the `get_app` method. - -```rust -use passage_flex::PassageFlex; - -let passage_flex = PassageFlex::new( - std::env::var("PASSAGE_APP_ID").unwrap(), - std::env::var("PASSAGE_API_KEY").unwrap(), -); - -let app_info = passage_flex.get_app().await.unwrap(); -println!("{}", app_info.auth_origin); -``` - ### Create a Registration Transaction To create a transaction to start a user passkey registration, use the `create_register_transaction` method. @@ -92,6 +76,7 @@ let passkey_display_name = "the label for the user's passkey that they will see when logging in".to_string(); let transaction = passage_flex + .auth .create_register_transaction(external_id, passkey_display_name) .await .unwrap(); @@ -112,6 +97,7 @@ let passage_flex = PassageFlex::new( let external_id = "a unique immutable string that represents your user".to_string(); let transaction = passage_flex + .auth .create_authenticate_transaction(external_id) .await .unwrap(); @@ -132,7 +118,7 @@ let passage_flex = PassageFlex::new( let nonce = "a unique single-use value received from the client after a passkey ceremony".to_string(); -match passage_flex.verify_nonce(nonce).await { +match passage_flex.auth.verify_nonce(nonce).await { Ok(external_id) => { // use external_id to do things like generate and send your own auth token } @@ -144,7 +130,7 @@ match passage_flex.verify_nonce(nonce).await { ## Retrieve User Info -To retrieve information about a user by their external ID -- which is the unique, immutable ID you supply to associate the Passage user with your user -- use the `get_user` method. +To retrieve information about a user by their external ID -- which is the unique, immutable ID you supply to associate the Passage user with your user -- use the `get` method. ```rust use passage_flex::PassageFlex; @@ -158,13 +144,13 @@ let passage_flex = PassageFlex::new( let external_id = your_user.id; // get user info -let user_info = passage_flex.get_user(external_id).await.unwrap(); +let user_info = passage_flex.user.get_user(external_id).await.unwrap(); println!("{:?}", user_info.webauthn_devices); ``` ## Retrieve a user's passkey devices -To retrieve information about a user's passkey devices, use the `get_devices` method. +To retrieve information about a user's passkey devices, use the `list_devices` method. ```rust use passage_flex::PassageFlex; @@ -177,8 +163,8 @@ let passage_flex = PassageFlex::new( // this is the same value used when creating a transaction let external_id = your_user.id; -// get devices -let passkey_devices = passage_flex.get_devices(external_id).await.unwrap(); +// list devices +let passkey_devices = passage_flex.user.list_devices(external_id).await.unwrap(); for device in passkey_devices { println!("{}", device.usage_count); } @@ -201,8 +187,8 @@ let passage_flex = PassageFlex::new( let external_id = your_user.id; let last_year = Utc::now().naive_utc().date() - Duration::days(365); -// get devices -let passkey_devices = passage_flex.get_devices(external_id.clone()).await.unwrap(); +// list devices +let passkey_devices = passage_flex.user.list_devices(external_id.clone()).await.unwrap(); for device in passkey_devices { // revoke old devices that haven't been used in the last year @@ -211,6 +197,7 @@ for device in passkey_devices { if last_login_at_parsed < last_year { if let Err(err) = passage_flex + .user .revoke_device(external_id.clone(), device.id) .await { From 80ef1f4f6dfd58398bed5a24d768221b4f5edffc Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Wed, 18 Dec 2024 12:17:29 -0600 Subject: [PATCH 04/10] docs: update docstrings to match new method signatures --- src/auth.rs | 4 +++- src/lib.rs | 4 ++-- src/user.rs | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 07d3d85..fb7af0c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -34,6 +34,7 @@ impl Auth { /// ); /// /// let transaction = passage_flex + /// .auth /// .create_register_transaction( /// "00000000-0000-0000-0000-000000000001".to_string(), /// "user@example.com".to_string(), @@ -79,6 +80,7 @@ impl Auth { /// ); /// /// let transaction = passage_flex + /// .auth /// .create_authenticate_transaction( /// "00000000-0000-0000-0000-000000000001".to_string(), /// ) @@ -118,7 +120,7 @@ impl Auth { /// std::env::var("PASSAGE_API_KEY").unwrap(), /// ); /// - /// match passage_flex.verify_nonce("01234567890123456789".to_string()).await { + /// match passage_flex.auth.verify_nonce("01234567890123456789".to_string()).await { /// Ok(external_id) => { /// // use external_id to do things like generate and send your own auth token /// } diff --git a/src/lib.rs b/src/lib.rs index 339cd89..2065606 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,8 @@ //! std::env::var("PASSAGE_API_KEY").unwrap(), //! ); //! -//! let app_info = passage_flex.get_app().await.unwrap(); -//! println!("{}", app_info.auth_origin); +//! let user_info = passage_flex.user.get(external_id).await.unwrap(); +//! println!("{:?}", user_info.webauthn_devices); //! ``` use std::fmt; diff --git a/src/user.rs b/src/user.rs index fd0ae65..a67a78d 100644 --- a/src/user.rs +++ b/src/user.rs @@ -67,7 +67,7 @@ impl User { /// ); /// /// let external_id = "00000000-0000-0000-0000-000000000001"; - /// let user_info = passage_flex.get(external_id.to_string()).await.unwrap(); + /// let user_info = passage_flex.user.get(external_id.to_string()).await.unwrap(); /// println!("{:?}", user_info.id); /// ``` pub async fn get(&self, external_id: String) -> Result, Error> { @@ -113,7 +113,7 @@ impl User { /// ); /// /// let external_id = "00000000-0000-0000-0000-000000000001"; - /// let passkey_devices = passage_flex.list_devices(external_id.to_string()).await.unwrap(); + /// let passkey_devices = passage_flex.user.list_devices(external_id.to_string()).await.unwrap(); /// for device in passkey_devices { /// println!("{}", device.usage_count); /// } @@ -154,7 +154,7 @@ impl User { /// let external_id = "00000000-0000-0000-0000-000000000001"; /// let last_year = Utc::now().naive_utc().date() - Duration::days(365); /// - /// let passkey_devices = passage_flex.get_devices(external_id.to_string()).await.unwrap(); + /// let passkey_devices = passage_flex.user.list_devices(external_id.to_string()).await.unwrap(); /// /// for device in passkey_devices { /// let last_login_at_parsed = @@ -162,6 +162,7 @@ impl User { /// /// if last_login_at_parsed < last_year { /// if let Err(err) = passage_flex + /// .user /// .revoke_device(external_id.clone(), device.id) /// .await /// { From efd394e71e4c42a812767c313a0eedc912dc68d3 Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Wed, 18 Dec 2024 12:17:49 -0600 Subject: [PATCH 05/10] docs: fix readme example to use correct user method name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c185b0..8905ab8 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ let passage_flex = PassageFlex::new( let external_id = your_user.id; // get user info -let user_info = passage_flex.user.get_user(external_id).await.unwrap(); +let user_info = passage_flex.user.get(external_id).await.unwrap(); println!("{:?}", user_info.webauthn_devices); ``` From f22a04ed9e892fbb82313de365b6c06218d26190 Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Wed, 18 Dec 2024 12:18:26 -0600 Subject: [PATCH 06/10] feat: remove unused AppError --- src/error.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/error.rs b/src/error.rs index 64866ab..f569130 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,17 +16,6 @@ fn convert_error(error: crate::openapi::apis::Error, map_fn: fn(Src) - } } -impl From> for Error { - fn from(e: crate::openapi::apis::Error) -> Self { - convert_error(e, |e| match e { - apps_api::GetAppError::Status401(model) => model.into(), - apps_api::GetAppError::Status404(model) => model.into(), - apps_api::GetAppError::Status500(model) => model.into(), - apps_api::GetAppError::UnknownValue(v) => Error::Other(v.to_string()), - }) - } -} - impl From> for Error { fn from( e: crate::openapi::apis::Error, From b9e763c19907409acb6a8c6c9446058ce49eda08 Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Wed, 18 Dec 2024 18:30:35 +0000 Subject: [PATCH 07/10] fix: remove unused import --- src/error.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index f569130..f04339e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,4 @@ -use crate::openapi::apis::{ - apps_api, authenticate_api, transactions_api, user_devices_api, users_api, -}; +use crate::openapi::apis::{authenticate_api, transactions_api, user_devices_api, users_api}; use crate::Error; // This function converts an openapi error into a crate error From aa4942dcc0bf62a5a1889a3754e6ec1e47debe0e Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Wed, 18 Dec 2024 12:44:22 -0600 Subject: [PATCH 08/10] feat!: renames user_info to passage_user --- README.md | 4 ++-- src/lib.rs | 4 ++-- src/models/app_info.rs | 11 ----------- src/models/mod.rs | 6 ++---- src/models/{user_info.rs => passage_user.rs} | 2 +- src/passage_flex/test.rs | 4 ++-- src/user.rs | 10 +++++----- 7 files changed, 14 insertions(+), 27 deletions(-) delete mode 100644 src/models/app_info.rs rename src/models/{user_info.rs => passage_user.rs} (97%) diff --git a/README.md b/README.md index 8905ab8..6eea89b 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,8 @@ let passage_flex = PassageFlex::new( let external_id = your_user.id; // get user info -let user_info = passage_flex.user.get(external_id).await.unwrap(); -println!("{:?}", user_info.webauthn_devices); +let passage_user = passage_flex.user.get(external_id).await.unwrap(); +println!("{:?}", passage_user.webauthn_devices); ``` ## Retrieve a user's passkey devices diff --git a/src/lib.rs b/src/lib.rs index 2065606..af47aad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,8 @@ //! std::env::var("PASSAGE_API_KEY").unwrap(), //! ); //! -//! let user_info = passage_flex.user.get(external_id).await.unwrap(); -//! println!("{:?}", user_info.webauthn_devices); +//! let passage_user = passage_flex.user.get(external_id).await.unwrap(); +//! println!("{:?}", passage_user.webauthn_devices); //! ``` use std::fmt; diff --git a/src/models/app_info.rs b/src/models/app_info.rs deleted file mode 100644 index f215a04..0000000 --- a/src/models/app_info.rs +++ /dev/null @@ -1,11 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct AppInfo { - #[serde(rename = "auth_origin")] - pub auth_origin: String, - #[serde(rename = "id")] - pub id: String, - #[serde(rename = "name")] - pub name: String, -} diff --git a/src/models/mod.rs b/src/models/mod.rs index 7fe6f06..427462d 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,5 +1,3 @@ -mod app_info; -mod user_info; +mod passage_user; -pub use app_info::AppInfo; -pub use user_info::UserInfo; +pub use passage_user::PassageUser; diff --git a/src/models/user_info.rs b/src/models/passage_user.rs similarity index 97% rename from src/models/user_info.rs rename to src/models/passage_user.rs index e15cb96..fe67482 100644 --- a/src/models/user_info.rs +++ b/src/models/passage_user.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct UserInfo { +pub struct PassageUser { #[serde(rename = "created_at")] pub created_at: String, /// The external ID of the user. Only set if the user was created in a Flex app. diff --git a/src/passage_flex/test.rs b/src/passage_flex/test.rs index b8f3be2..358b7cd 100644 --- a/src/passage_flex/test.rs +++ b/src/passage_flex/test.rs @@ -302,10 +302,10 @@ async fn test_get_user() { let m2 = setup_valid_list_paginated_users_mock(&app_id, &mut server).await; let m3 = setup_valid_get_user_mock(&app_id, &mut server).await; - let user_info = passage_flex.user.get("valid".to_string()).await.unwrap(); + let passage_user = passage_flex.user.get("valid".to_string()).await.unwrap(); m2.assert_async().await; m3.assert_async().await; - assert_eq!(user_info.external_id, "valid"); + assert_eq!(passage_user.external_id, "valid"); } #[tokio::test] diff --git a/src/user.rs b/src/user.rs index a67a78d..404fa8d 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,4 +1,4 @@ -use crate::models::UserInfo; +use crate::models::PassageUser; use crate::openapi::apis::configuration::Configuration; use crate::openapi::apis::{user_devices_api, users_api}; use crate::Error; @@ -67,15 +67,15 @@ impl User { /// ); /// /// let external_id = "00000000-0000-0000-0000-000000000001"; - /// let user_info = passage_flex.user.get(external_id.to_string()).await.unwrap(); - /// println!("{:?}", user_info.id); + /// let passage_user = passage_flex.user.get(external_id.to_string()).await.unwrap(); + /// println!("{:?}", passage_user.id); /// ``` - pub async fn get(&self, external_id: String) -> Result, Error> { + pub async fn get(&self, external_id: String) -> Result, Error> { let user_id = self.get_id(external_id).await?; users_api::get_user(&self.configuration, &user_id) .await .map(|response| { - Box::new(UserInfo { + Box::new(PassageUser { created_at: response.user.created_at, external_id: response.user.external_id, id: response.user.id, From bcdc226668240f790f535e611dd2e0c725234dc4 Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Thu, 19 Dec 2024 10:09:15 -0600 Subject: [PATCH 09/10] docs: removes part of usage example that doesnt have enough context --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index af47aad..a30a611 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,9 +10,6 @@ //! std::env::var("PASSAGE_APP_ID").unwrap(), //! std::env::var("PASSAGE_API_KEY").unwrap(), //! ); -//! -//! let passage_user = passage_flex.user.get(external_id).await.unwrap(); -//! println!("{:?}", passage_user.webauthn_devices); //! ``` use std::fmt; From 943b1e915151b953f23d9766960ea30e17ce2510 Mon Sep 17 00:00:00 2001 From: Chris Tran Date: Thu, 19 Dec 2024 12:21:37 -0600 Subject: [PATCH 10/10] fix: updates example server to use nested classes --- examples/poem-openapi/src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/poem-openapi/src/main.rs b/examples/poem-openapi/src/main.rs index 4816a11..e169489 100644 --- a/examples/poem-openapi/src/main.rs +++ b/examples/poem-openapi/src/main.rs @@ -119,6 +119,7 @@ impl Api { let transaction_id = self .state .passage_client + .auth .create_register_transaction(user_id.clone(), req.0.email.clone()) .await .map_err(|e| { @@ -155,6 +156,7 @@ impl Api { let transaction_id = self .state .passage_client + .auth .create_authenticate_transaction(user_id) .await .map_err(|e| match e { @@ -180,7 +182,7 @@ impl Api { #[oai(path = "/auth/verify", method = "post")] async fn verify_nonce(&self, req: Json) -> poem::Result> { // Verify the nonce using the Passage SDK - match self.state.passage_client.verify_nonce(req.0.nonce).await { + match self.state.passage_client.auth.verify_nonce(req.0.nonce).await { Ok(user_id) => { // Find the user in the database and set the auth token let mut users = self.state.users.lock().await; @@ -225,6 +227,7 @@ impl Api { let transaction_id = self .state .passage_client + .auth .create_register_transaction(user.id.clone(), user.email.clone()) .await .map_err(|e| { @@ -261,7 +264,8 @@ impl Api { let passkeys = self .state .passage_client - .get_devices(user.id.clone()) + .user + .list_devices(user.id.clone()) .await .map(|devices| { devices @@ -306,6 +310,7 @@ impl Api { // Revoke the passkey device using the Passage SDK self.state .passage_client + .user .revoke_device(user.id.clone(), req.0.passkey_id.clone()) .await .map_err(|e| {