diff --git a/services/comm-services-lib/src/auth.rs b/services/comm-services-lib/src/auth.rs --- a/services/comm-services-lib/src/auth.rs +++ b/services/comm-services-lib/src/auth.rs @@ -3,6 +3,53 @@ use serde::{Deserialize, Serialize}; use std::{str::FromStr, string::FromUtf8Error}; +/// This implements [`actix_web::FromRequest`], so it can be used to extract user +/// identity information from HTTP requests. +/// # Example +/// ```ignore +/// pub async fn request_handler( +/// principal: AuthorizationCredential, +/// ) -> Result { +/// Ok(HttpResponse::Ok().finish()) +/// } +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum AuthorizationCredential { + UserToken(UserIdentity), + ServicesToken(ServicesAuthToken), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ServicesAuthToken { + #[serde(rename = "servicesToken")] + token_value: String, +} + +impl ServicesAuthToken { + /// Gets the raw token value + pub fn into_inner(self) -> String { + self.token_value + } + + /// Gets the raw token value + pub fn as_str(&self) -> &str { + self.token_value.as_str() + } + + /// Gets the access token value, usable in bearer authorization + /// + /// # Example + /// ```ignore + /// reqwest::get("url").beaerer_auth(token.as_authorization_token()?).send().await?; + /// ``` + pub fn as_authorization_token(&self) -> Result { + let json = serde_json::to_string(self)?; + let base64_str = BASE64_STANDARD.encode(json); + Ok(base64_str) + } +} + /// This implements [`actix_web::FromRequest`], so it can be used to extract user /// identity information from HTTP requests. /// # Example @@ -38,7 +85,7 @@ } #[derive(Debug, Display, Error, From)] -pub enum UserIdentityParseError { +pub enum AuthorizationCredentialParseError { Base64DecodeError(base64::DecodeError), Utf8DecodeError(FromUtf8Error), JsonParseError(serde_json::Error), @@ -46,7 +93,7 @@ /// Parsing of [UserIdentity] from bearer token impl FromStr for UserIdentity { - type Err = UserIdentityParseError; + type Err = AuthorizationCredentialParseError; fn from_str(s: &str) -> Result { let bytes = BASE64_STANDARD.decode(s)?; @@ -56,6 +103,18 @@ } } +/// Parsing of [AuthorizationCredential] from bearer token +impl FromStr for AuthorizationCredential { + type Err = AuthorizationCredentialParseError; + + fn from_str(s: &str) -> Result { + let bytes = BASE64_STANDARD.decode(s)?; + let text = String::from_utf8(bytes)?; + let credential = serde_json::from_str(&text)?; + Ok(credential) + } +} + #[cfg(test)] mod tests { use super::*; @@ -76,4 +135,39 @@ assert_eq!(parsed_identity.unwrap(), identity); } + + #[test] + fn test_user_credential_parsing() { + let identity = UserIdentity { + user_id: "user".to_string(), + access_token: "token".to_string(), + device_id: "device".to_string(), + }; + let json = + r#"{"userID": "user", "accessToken": "token", "deviceID": "device"}"#; + let encoded = BASE64_STANDARD.encode(json); + + let parsed_identity = encoded.parse::(); + assert!(parsed_identity.is_ok(), "Parse error: {parsed_identity:?}"); + + assert_eq!( + parsed_identity.unwrap(), + AuthorizationCredential::UserToken(identity) + ); + } + + #[test] + fn test_services_token_parsing() { + let token = ServicesAuthToken::new("hello".to_string()); + let json = r#"{"servicesToken": "hello"}"#; + let encoded = BASE64_STANDARD.encode(json); + + let parsed_identity = encoded.parse::(); + assert!(parsed_identity.is_ok(), "Parse error: {parsed_identity:?}"); + + assert_eq!( + parsed_identity.unwrap(), + AuthorizationCredential::ServicesToken(token) + ); + } }