use crate::common::{
frontend::{FrontendRequest, FrontendRequestMethod, OAuthValidationError},
model::CodeChallenge,
syntax::{ValidateSyntax, CLIENT_ID_SYNTAX, STATE_SYNTAX},
util::NoneIfEmpty,
};
#[derive(Debug, PartialEq)]
pub enum ResponseType {
Code,
}
#[derive(Debug)]
pub struct AuthorizationRequest {
pub response_type: ResponseType,
pub client_id: String,
pub code_challenge: CodeChallenge,
pub redirect_uri: Option<String>,
pub scope: Option<String>,
pub state: Option<String>,
}
impl TryFrom<&dyn FrontendRequest> for AuthorizationRequest {
type Error = OAuthValidationError;
fn try_from(request: &dyn FrontendRequest) -> Result<Self, Self::Error> {
if !matches!(
request.request_method(),
FrontendRequestMethod::GET | FrontendRequestMethod::POST
) {
return Err(OAuthValidationError::InvalidRequestMethod {
expected: FrontendRequestMethod::GET,
actual: request.request_method(),
});
}
let param = |key| {
request.query_param(key).none_if_empty().or_else(|| {
if let FrontendRequestMethod::POST = request.request_method() {
request.body_param(key).none_if_empty()
} else {
None
}
})
};
let response_type = match param("response_type") {
Some(str) => str.try_into()?,
None => return Err(OAuthValidationError::MissingRequiredParameter("response_type")),
};
let Some(client_id) = param("client_id") else {
return Err(OAuthValidationError::MissingRequiredParameter("client_id"));
};
client_id.validate_syntax("client_id", &CLIENT_ID_SYNTAX)?;
let code_challenge =
(param("code_challenge"), param("code_challenge_method")).try_into()?;
let state = param("state");
state.validate_syntax("state", &STATE_SYNTAX)?;
Ok(Self {
response_type,
client_id,
code_challenge,
state,
redirect_uri: param("redirect_uri"),
scope: param("scope"),
})
}
}
impl TryFrom<String> for ResponseType {
type Error = OAuthValidationError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(match value.to_lowercase().as_str() {
"code" => Self::Code,
_ => {
return Err(OAuthValidationError::InvalidParameterValue(
"response_type",
value.to_string(),
))
}
})
}
}
impl TryFrom<(Option<String>, Option<String>)> for CodeChallenge {
type Error = OAuthValidationError;
fn try_from(
(code_challenge, code_challenge_method): (Option<String>, Option<String>),
) -> Result<Self, Self::Error> {
let Some(code_challenge) = code_challenge else {
return Ok(Self::None);
};
let Some(method) = code_challenge_method else {
return Ok(Self::Plain { code_challenge });
};
Ok(match method.to_lowercase().as_str() {
"plain" => Self::Plain { code_challenge },
"s256" => Self::S256 { code_challenge },
_ => {
return Err(OAuthValidationError::InvalidParameterValue(
"code_challenge_method",
method,
))
}
})
}
}