sleutel/src/api/openid/webfinger.rs

136 lines
3.2 KiB
Rust
Raw Normal View History

2023-11-18 18:00:54 +01:00
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<Link>,
}
#[derive(Serialize)]
struct Link {
pub rel: String,
pub href: String,
}
#[derive(Clone)]
enum DescriptorTypes {
Issuer(String),
}
#[derive(Debug, PartialEq)]
pub enum Rels {
Connect1Issuer,
Unsupported,
All,
2023-11-18 18:00:54 +01:00
}
impl From<String> 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, ""),
Rels::All => write!(f, ""),
2023-11-18 18:00:54 +01:00
}
}
}
impl From<DescriptorTypes> for Rels {
fn from(value: DescriptorTypes) -> Self {
match value {
DescriptorTypes::Issuer(_) => Self::Connect1Issuer,
}
}
}
impl From<DescriptorTypes> 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<ResourceString> 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<String>,
// Todo: rel should allow for multiple values
2023-11-18 18:00:54 +01:00
rel: Option<String>,
}
pub async fn finger(Query(q): Query<FingerQuery>, Host(host): Host) -> impl IntoResponse {
let rel = match q.rel {
Some(x) => Rels::from(x),
None => Rels::All,
2023-11-18 18:00:54 +01:00
};
if rel == Rels::Unsupported {
return (
StatusCode::NOT_IMPLEMENTED,
"Rel type not supported".to_string(),
);
}
2023-11-18 18:00:54 +01:00
let resource = match q.resource {
Some(x) => Resource::from(ResourceString(x)),
None => Resource::Unsupported,
};
tracing::warn!("{:?} - {:?}", rel, resource);
2023-11-18 18:00:54 +01:00
match resource {
Resource::Acct(subject) => {
let links = match rel {
Rels::Connect1Issuer => vec![DescriptorTypes::Issuer(host).into()],
Rels::All => vec![DescriptorTypes::Issuer(host).into()],
Rels::Unsupported => vec![],
2023-11-18 18:00:54 +01:00
};
let doc = ResourceDescriptor { subject, links };
2023-11-18 18:00:54 +01:00
(StatusCode::OK, serde_json::to_string(&doc).unwrap())
}
Resource::Unsupported => (
StatusCode::NOT_IMPLEMENTED,
"Resource type not supported".to_string(),
),
}
}