use serde::{Deserialize, Serialize};
use activitystreams_vocabulary::{field_access, impl_default, impl_display};
use crate::app::oauth::{CodeChallenge, NormalizedParameter};
use crate::db::{Iri, Uuid};
use crate::{Error, Result, util};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub enum ResponseType {
Code,
Token,
}
impl ResponseType {
pub const CODE: &str = "code";
pub const TOKEN: &str = "token";
#[inline]
pub const fn new() -> Self {
Self::Code
}
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Code => Self::CODE,
Self::Token => Self::TOKEN,
}
}
}
impl_default!(ResponseType);
impl_display!(ResponseType, str);
impl From<ResponseType> for &'static str {
fn from(val: ResponseType) -> Self {
(&val).into()
}
}
impl From<&ResponseType> for &'static str {
fn from(val: &ResponseType) -> Self {
val.as_str()
}
}
impl TryFrom<&str> for ResponseType {
type Error = Error;
fn try_from(val: &str) -> Result<Self> {
match val {
Self::CODE => Ok(Self::Code),
Self::TOKEN => Ok(Self::Token),
_ => Err(Error::http(format!(
"oauth: authz: invalid response type: {val}"
))),
}
}
}
impl std::str::FromStr for ResponseType {
type Err = Error;
fn from_str(val: &str) -> Result<Self> {
val.try_into()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct AuthorizationRequest {
response_type: ResponseType,
#[serde(serialize_with = "util::ser_uuid", deserialize_with = "util::de_uuid")]
client_id: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
redirect_uri: Option<Iri>,
#[serde(skip_serializing_if = "Option::is_none")]
code_challenge: Option<CodeChallenge>,
#[serde(skip_serializing_if = "Option::is_none")]
state: Option<String>,
}
impl AuthorizationRequest {
#[inline]
pub const fn new() -> Self {
Self {
response_type: ResponseType::new(),
client_id: Uuid::nil(),
redirect_uri: None,
code_challenge: None,
state: None,
}
}
pub fn encode_url(&self) -> String {
let mut params = NormalizedParameter::new();
params.insert("response_type", self.response_type().as_str());
params.insert("client_id", self.client_id().to_string());
if let Some(uri) = self.redirect_uri() {
params.insert("redirect_uri", uri.as_str());
}
if let Some(code) = self.code_challenge() {
params.insert("code_challenge", code.code_str());
params.insert("code_challenge_method", code.method().as_str());
}
if let Some(state) = self.state() {
params.insert("state", state);
}
params.to_string()
}
}
field_access! {
AuthorizationRequest {
response_type: ResponseType,
client_id: Uuid,
}
}
field_access! {
AuthorizationRequest {
redirect_uri: option_ref { Iri },
code_challenge: option_ref { CodeChallenge },
}
}
field_access! {
AuthorizationRequest {
state: option_deref { &str, String },
}
}
impl_default!(AuthorizationRequest);
impl std::fmt::Display for AuthorizationRequest {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.encode_url())
}
}