use std::{fmt::Display, str::FromStr}; use derive_more::derive::{Display, From}; #[cfg(feature = "ssr")] use rand::{distributions::Alphanumeric, Rng}; use serde::{Deserialize, Serialize}; use thiserror::Error; use super::user::User; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Client { id: uuid::Uuid, user_id: uuid::Uuid, name: ClientName, secret: ClientSecret, redirect_uri: RedirectUri, } impl Client { pub fn new( id: uuid::Uuid, user_id: uuid::Uuid, name: ClientName, secret: ClientSecret, redirect_uri: RedirectUri, ) -> Self { Self { id, user_id, name, secret, redirect_uri, } } pub fn id(&self) -> uuid::Uuid { self.id } pub fn name(&self) -> &ClientName { &self.name } pub fn redirect_uri(&self) -> &RedirectUri { &self.redirect_uri } } #[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct ClientName(String); impl ClientName { pub fn new(name: &str) -> Self { Self(name.to_string()) } } #[derive(Clone, Display, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(from = "String")] pub struct ClientSecret(String); impl From for ClientSecret { fn from(value: String) -> Self { Self(value) } } impl From<&str> for ClientSecret { fn from(value: &str) -> Self { Self(value.to_string()) } } #[cfg(feature = "ssr")] impl ClientSecret { pub fn new() -> Self { let token: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(32) .map(char::from) .collect(); Self(token) } } #[cfg(feature = "ssr")] impl Default for ClientSecret { fn default() -> Self { Self::new() } } #[derive( Clone, Debug, Display, From, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, )] pub struct RedirectUri(String); impl RedirectUri { pub fn new(uri: &str) -> Self { Self(uri.to_string()) } } #[cfg(feature = "ssr")] #[derive(Debug, Error)] pub enum CreateAuthorizationCodeError { #[error(transparent)] Unknown(#[from] anyhow::Error), // to be extended as new error scenarios are introduced } #[derive(Clone, Display, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(from = "String")] pub struct AuthorizationCode(String); impl From for AuthorizationCode { fn from(value: String) -> Self { Self(value) } } impl From<&str> for AuthorizationCode { fn from(value: &str) -> Self { Self(value.to_string()) } } #[cfg(feature = "ssr")] impl AuthorizationCode { pub fn new() -> Self { let token: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(64) .map(char::from) .collect(); Self(token) } } #[cfg(feature = "ssr")] impl Default for AuthorizationCode { fn default() -> Self { Self::new() } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct AuthorizedClient { client: Client, user: User, } #[derive(Debug, Error)] pub enum CodeChallengeMethodError { #[error("Code challenge method is not valid.")] Invalid, #[error(transparent)] Unknown(#[from] anyhow::Error), // to be extended as new error scenarios are introduced } #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum CodeChallengeMethod { #[default] #[serde(rename = "plain")] Plain, #[serde(rename = "S256")] Sha256, } impl Display for CodeChallengeMethod { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { CodeChallengeMethod::Plain => "plain", CodeChallengeMethod::Sha256 => "S256", } ) } } impl FromStr for CodeChallengeMethod { type Err = crate::domain::api::models::oauth::CodeChallengeMethodError; fn from_str(s: &str) -> Result { match s { "plain" => Ok(Self::Plain), "S256" => Ok(Self::Sha256), _ => Err(crate::domain::api::models::oauth::CodeChallengeMethodError::Invalid), } } } impl TryFrom for CodeChallengeMethod { type Error = crate::domain::api::models::oauth::CodeChallengeMethodError; fn try_from(value: String) -> Result { Self::from_str(&value) } } #[derive(Debug, Error)] pub enum ResponseTypeError { #[error("The response type is not valid.")] Invalid, #[error(transparent)] Unknown(#[from] anyhow::Error), // to be extended as new error scenarios are introduced } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum ResponseType { #[serde(rename = "code")] Code, } impl Display for ResponseType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { ResponseType::Code => "code", } ) } } impl FromStr for ResponseType { type Err = crate::domain::api::models::oauth::ResponseTypeError; fn from_str(s: &str) -> Result { match s { "code" => Ok(Self::Code), _ => Err(crate::domain::api::models::oauth::ResponseTypeError::Invalid), } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AuthorizeRequest { client_id: uuid::Uuid, response_type: ResponseType, // Make type (enum:code,) state: Option, // random string for CSRF protection code_challenge: String, // pkce code_challenge_method: Option, // Make type (enum:sha256,) hashing algo redirect_uri: RedirectUri, // Make type scope: Option, // space seperated string with permissions } impl AuthorizeRequest { pub fn new( client_id: uuid::Uuid, response_type: ResponseType, state: Option, code_challenge: String, code_challenge_method: Option, redirect_uri: RedirectUri, scope: Option, ) -> Self { Self { client_id, response_type, state, code_challenge, code_challenge_method, redirect_uri, scope, } } pub fn client_id(&self) -> uuid::Uuid { self.client_id } pub fn response_type(&self) -> ResponseType { self.response_type.clone() } pub fn state(&self) -> Option { self.state.clone() } pub fn code_challenge(&self) -> String { self.code_challenge.clone() } pub fn code_challenge_method(&self) -> Option { self.code_challenge_method.clone() } pub fn redirect_uri(&self) -> RedirectUri { self.redirect_uri.clone() } pub fn scope(&self) -> Option { self.scope.clone() } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AuthorizationResponse { code: AuthorizationCode, state: Option, } impl AuthorizationResponse { pub fn new(code: AuthorizationCode, state: Option) -> Self { Self { code, state } } pub fn code(&self) -> AuthorizationCode { self.code.clone() } pub fn state(&self) -> Option { self.state.clone() } } #[derive(Debug, Error)] pub enum TokenError { #[error("Invalid Token Request")] InvalidRequest, #[error(transparent)] Unknown(#[from] anyhow::Error), // to be extended as new error scenarios are introduced } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TokenSubject { #[serde(rename = "a")] user_id: uuid::Uuid, #[serde(rename = "b")] client_id: uuid::Uuid, #[serde(skip)] code_challenge: String, #[serde(skip)] code_challenge_method: CodeChallengeMethod, } impl TokenSubject { pub fn new( user_id: uuid::Uuid, client_id: uuid::Uuid, code_challenge: String, code_challenge_method: CodeChallengeMethod, ) -> Self { Self { user_id, client_id, code_challenge, code_challenge_method, } } pub fn user_id(&self) -> uuid::Uuid { self.user_id } pub fn client_id(&self) -> uuid::Uuid { self.client_id } pub fn code_challenge(&self) -> String { self.code_challenge.clone() } pub fn code_challenge_method(&self) -> CodeChallengeMethod { self.code_challenge_method.clone() } }