diff --git a/src/ctanker.rs b/src/ctanker.rs index 0834223..9d9f16d 100644 --- a/src/ctanker.rs +++ b/src/ctanker.rs @@ -12,6 +12,7 @@ use self::bindings::*; pub type CVerification = tanker_verification; pub type CEmailVerification = tanker_email_verification; +pub type CPhoneNumberVerification = tanker_phone_number_verification; pub type CVerificationMethod = tanker_verification_method; pub type CDevice = tanker_device_list_elem; pub type CTankerPtr = *mut tanker_t; diff --git a/src/verification.rs b/src/verification.rs index 946e238..0f965d6 100644 --- a/src/verification.rs +++ b/src/verification.rs @@ -1,8 +1,9 @@ -use crate::ctanker::{CEmailVerification, CVerification}; +use crate::ctanker::{CEmailVerification, CPhoneNumberVerification, CVerification}; use std::ffi::CString; -const CVERIFICATION_VERSION: u8 = 3; +const CVERIFICATION_VERSION: u8 = 4; const CEMAIL_VERIFICATION_VERSION: u8 = 1; +const CPHONE_NUMBER_VERIFICATION_VERSION: u8 = 1; #[repr(u8)] enum Type { @@ -11,6 +12,7 @@ enum Type { VerificationKey = 3, #[allow(clippy::upper_case_acronyms)] OIDCIDToken = 4, + PhoneNumber = 5, } pub(crate) struct CVerificationWrapper { @@ -33,6 +35,11 @@ impl CVerificationWrapper { }, passphrase: std::ptr::null(), oidc_id_token: std::ptr::null(), + phone_number_verification: CPhoneNumberVerification { + version: CPHONE_NUMBER_VERIFICATION_VERSION, + phone_number: std::ptr::null(), + verification_code: std::ptr::null(), + }, }, } } @@ -51,6 +58,20 @@ impl CVerificationWrapper { wrapper } + pub(self) fn with_phone_number(phone_number: &str, verif_code: &str) -> Self { + let mut wrapper = Self::new(); + let cphone_number = CString::new(phone_number).unwrap(); + let cverif_code = CString::new(verif_code).unwrap(); + + wrapper.cverif.verification_method_type = Type::PhoneNumber as u8; + wrapper.cverif.phone_number_verification.phone_number = cphone_number.as_ptr(); + wrapper.cverif.phone_number_verification.verification_code = cverif_code.as_ptr(); + + wrapper.cstrings.push(cphone_number); + wrapper.cstrings.push(cverif_code); + wrapper + } + pub(self) fn with_passphrase(passphrase: &str) -> Self { let mut wrapper = Self::new(); let cpass = CString::new(passphrase).unwrap(); @@ -100,6 +121,10 @@ pub enum Verification { VerificationKey(String), #[allow(clippy::upper_case_acronyms)] OIDCIDToken(String), + PhoneNumber { + phone_number: String, + verification_code: String, + }, } impl Verification { @@ -114,6 +139,10 @@ impl Verification { } Verification::VerificationKey(key) => CVerificationWrapper::with_verification_key(&key), Verification::OIDCIDToken(token) => CVerificationWrapper::with_oidc_id_token(&token), + Verification::PhoneNumber { + phone_number, + verification_code, + } => CVerificationWrapper::with_phone_number(&phone_number, &verification_code), } } } diff --git a/src/verification_methods.rs b/src/verification_methods.rs index c5ff371..98504eb 100644 --- a/src/verification_methods.rs +++ b/src/verification_methods.rs @@ -16,6 +16,7 @@ pub enum VerificationMethod { VerificationKey, #[allow(clippy::upper_case_acronyms)] OIDCIDToken, + PhoneNumber(String), } #[derive(FromPrimitive)] @@ -27,6 +28,7 @@ enum CMethodType { VerificationKey = 3, #[allow(clippy::upper_case_acronyms)] OIDCIDToken = 4, + PhoneNumber = 5, #[num_enum(default)] Invalid, @@ -38,13 +40,19 @@ impl VerificationMethod { match ctype.into() { CMethodType::Email => { // SAFETY: If we get a valid Email verification method, the email is a valid string - let c_email = unsafe { CStr::from_ptr(method.email) }; + let c_email = unsafe { CStr::from_ptr(method.value) }; let email = c_email.to_str().unwrap().into(); Ok(VerificationMethod::Email(email)) } CMethodType::Passphrase => Ok(VerificationMethod::Passphrase), CMethodType::VerificationKey => Ok(VerificationMethod::VerificationKey), CMethodType::OIDCIDToken => Ok(VerificationMethod::OIDCIDToken), + CMethodType::PhoneNumber => { + // SAFETY: If we get a valid PhoneNumber verification method, the number is a valid string + let c_phone_number = unsafe { CStr::from_ptr(method.value) }; + let phone_number = c_phone_number.to_str().unwrap().into(); + Ok(VerificationMethod::PhoneNumber(phone_number)) + } CMethodType::Invalid => Err(Error::new( ErrorCode::InternalError, format!("Invalid verification method type {}", ctype), diff --git a/tests/identity/app.rs b/tests/identity/app.rs index d82542c..d404375 100644 --- a/tests/identity/app.rs +++ b/tests/identity/app.rs @@ -12,7 +12,7 @@ pub struct App { } impl App { - pub async fn get_verification_code(&self, email: &str) -> Result { + pub async fn get_email_verification_code(&self, email: &str) -> Result { let client = reqwest::Client::new(); let reply = admin_rest_request( client @@ -27,4 +27,20 @@ impl App { let code = reply["verification_code"].as_str().unwrap().to_owned(); Ok(code) } + + pub async fn get_sms_verification_code(&self, phone_number: &str) -> Result { + let client = reqwest::Client::new(); + let reply = admin_rest_request( + client + .post(&format!("{}/verification/sms/code", &self.url)) + .json( + &json!({ "phone_number": phone_number, "app_id": &self.id, "auth_token": &self.auth_token }), + ) + .header(ACCEPT, "application/json"), + ) + .await?; + + let code = reply["verification_code"].as_str().unwrap().to_owned(); + Ok(code) + } } diff --git a/tests/identity/test_app.rs b/tests/identity/test_app.rs index 094cb5e..202976e 100644 --- a/tests/identity/test_app.rs +++ b/tests/identity/test_app.rs @@ -65,8 +65,12 @@ impl TestApp { &self.app.auth_token } - pub async fn get_verification_code(&self, email: &str) -> Result { - self.app.get_verification_code(email).await + pub async fn get_email_verification_code(&self, email: &str) -> Result { + self.app.get_email_verification_code(email).await + } + + pub async fn get_sms_verification_code(&self, phone_number: &str) -> Result { + self.app.get_sms_verification_code(phone_number).await } pub async fn app_update( diff --git a/tests/tanker_tests.rs b/tests/tanker_tests.rs index a558fca..8425754 100644 --- a/tests/tanker_tests.rs +++ b/tests/tanker_tests.rs @@ -181,7 +181,7 @@ async fn share_with_provisional_user() -> Result<(), Error> { let verif = Verification::Email { email: bob_email.clone(), - verification_code: app.get_verification_code(&bob_email).await?, + verification_code: app.get_email_verification_code(&bob_email).await?, }; bob.verify_provisional_identity(&verif).await?; @@ -209,7 +209,7 @@ async fn throws_if_identity_is_already_attached() -> Result<(), Error> { let verif = Verification::Email { email: bob_email.clone(), - verification_code: app.get_verification_code(&bob_email).await?, + verification_code: app.get_email_verification_code(&bob_email).await?, }; bob.verify_provisional_identity(&verif).await?; @@ -218,7 +218,7 @@ async fn throws_if_identity_is_already_attached() -> Result<(), Error> { assert_eq!(attach_result.status, Status::IdentityVerificationNeeded); let verif = Verification::Email { email: bob_email.clone(), - verification_code: app.get_verification_code(&bob_email).await?, + verification_code: app.get_email_verification_code(&bob_email).await?, }; let err = alice.verify_provisional_identity(&verif).await.unwrap_err(); assert_eq!(err.code(), ErrorCode::IdentityAlreadyAttached); @@ -246,7 +246,7 @@ async fn attach_provisional_with_single_verif() -> Result<(), Error> { bob.start(&app.create_identity(None)).await?; let verif = Verification::Email { email: bob_email.clone(), - verification_code: app.get_verification_code(&bob_email).await?, + verification_code: app.get_email_verification_code(&bob_email).await?, }; bob.register_identity(&verif, &VerificationOptions::new()) .await?; diff --git a/tests/verify_tests.rs b/tests/verify_tests.rs index ba63fae..473c647 100644 --- a/tests/verify_tests.rs +++ b/tests/verify_tests.rs @@ -97,7 +97,7 @@ async fn check_email_verif_is_setup() -> Result<(), Error> { let email = "cold@in.af".to_string(); let verif = Verification::Email { email: email.clone(), - verification_code: app.get_verification_code(&email).await?, + verification_code: app.get_email_verification_code(&email).await?, }; let tanker = Core::new(app.make_options()).await?; @@ -112,6 +112,28 @@ async fn check_email_verif_is_setup() -> Result<(), Error> { Ok(()) } +#[tokio::test(flavor = "multi_thread")] +async fn check_sms_verif_is_setup() -> Result<(), Error> { + let app = TestApp::get().await; + let id = &app.create_identity(None); + let phone_number = "+33600001111".to_string(); + let verif = Verification::PhoneNumber { + phone_number: phone_number.clone(), + verification_code: app.get_sms_verification_code(&phone_number).await?, + }; + + let tanker = Core::new(app.make_options()).await?; + tanker.start(&id).await?; + tanker + .register_identity(&verif, &VerificationOptions::new()) + .await?; + let methods = tanker.get_verification_methods().await?; + tanker.stop().await?; + + assert_eq!(&methods, &[VerificationMethod::PhoneNumber(phone_number)]); + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn unlock_with_verif_code() -> Result<(), Error> { let app = TestApp::get().await; @@ -122,7 +144,7 @@ async fn unlock_with_verif_code() -> Result<(), Error> { tanker.start(&id).await?; let verif = Verification::Email { email: email.to_owned(), - verification_code: app.get_verification_code(&email).await?, + verification_code: app.get_email_verification_code(&email).await?, }; tanker .register_identity(&verif, &VerificationOptions::new()) @@ -133,7 +155,7 @@ async fn unlock_with_verif_code() -> Result<(), Error> { tanker.start(&id).await?; let verif = Verification::Email { email: email.to_owned(), - verification_code: app.get_verification_code(&email).await?, + verification_code: app.get_email_verification_code(&email).await?, }; tanker .verify_identity(&verif, &VerificationOptions::new())