use axum::extract::{Host, Query};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
#[derive(Serialize)]
struct ResourceDescriptor {
pub subject: String,
pub links: Vec,
}
#[derive(Serialize)]
struct Link {
pub rel: String,
pub href: String,
}
#[derive(Clone)]
enum DescriptorTypes {
Issuer(String),
}
#[derive(Debug, PartialEq)]
pub enum Rels {
Connect1Issuer,
Unsupported,
}
impl From for Rels {
fn from(value: String) -> Self {
match value.as_str() {
"http://openid.net/specs/connect/1.0/issuer" => Self::Connect1Issuer,
_ => Self::Unsupported,
}
}
}
impl Display for Rels {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Rels::Connect1Issuer => write!(f, "http://openid.net/specs/connect/1.0/issuer"),
Rels::Unsupported => write!(f, ""),
}
}
}
impl From for Rels {
fn from(value: DescriptorTypes) -> Self {
match value {
DescriptorTypes::Issuer(_) => Self::Connect1Issuer,
}
}
}
impl From for Link {
fn from(value: DescriptorTypes) -> Self {
match value.clone() {
DescriptorTypes::Issuer(host) => Self {
rel: Rels::from(value).to_string(),
href: format!("https://{}", host),
},
}
}
}
#[derive(Debug, PartialEq)]
enum Resource {
Acct(String),
Unsupported,
}
struct ResourceString(String);
impl From for Resource {
fn from(ResourceString(value): ResourceString) -> Self {
let (t, v) = match value.split_once(':') {
Some(x) => x,
None => return Self::Unsupported,
};
match t {
"acct" => Self::Acct(v.to_string()),
_ => Self::Unsupported,
}
}
}
#[derive(Debug, Deserialize)]
pub struct FingerQuery {
resource: Option,
rel: Option,
}
pub async fn finger(Query(q): Query, Host(host): Host) -> impl IntoResponse {
let rel = match q.rel {
Some(x) => Rels::from(x),
None => Rels::Unsupported,
};
let resource = match q.resource {
Some(x) => Resource::from(ResourceString(x)),
None => Resource::Unsupported,
};
tracing::warn!("{} - {:?}", rel, resource);
match resource {
Resource::Acct(subject) => {
let doc = ResourceDescriptor {
subject,
links: vec![match rel {
Rels::Connect1Issuer => DescriptorTypes::Issuer(host).into(),
Rels::Unsupported => {
return (
StatusCode::NOT_IMPLEMENTED,
"Rel type not supported".to_string(),
)
}
}],
};
(StatusCode::OK, serde_json::to_string(&doc).unwrap())
}
Resource::Unsupported => (
StatusCode::NOT_IMPLEMENTED,
"Resource type not supported".to_string(),
),
}
}