add webfinger handler

This commit is contained in:
falsycat 2023-05-27 16:26:26 +09:00
parent 49a6cdbec6
commit 5787b16017
9 changed files with 1642 additions and 9 deletions

1485
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,4 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rocket = "=0.5.0-rc.3"
lazy_static = "1.4.0"
regex = "1.8.3"
rocket = { version = "=0.5.0-rc.3", features = ["json"] }
serde = { version = "1.0.163", features = ["derive"] }

2
Rocket.toml Normal file
View File

@ -0,0 +1,2 @@
[default]
address = "0.0.0.0"

8
src/ctx.rs Normal file
View File

@ -0,0 +1,8 @@
pub mod db;
pub mod user;
pub use db::DB;
pub struct Ctx {
pub db: Box<dyn DB>,
}

30
src/ctx/db.rs Normal file
View File

@ -0,0 +1,30 @@
use super::user;
pub trait DB : Send + Sync {
fn new(&self, d: &user::Id) -> Result<(), String>;
fn delete(&self, d: &user::Id) -> Result<(), String>;
fn exists(&self, d: &user::Id) -> Result<bool, String>;
fn get_profile(&self, id: &user::Id) -> Result<user::Profile, String>;
fn set_profile(&self, id: &user::Id, d: user::Profile) -> Result<user::Profile, String>;
}
pub struct Null;
impl DB for Null {
fn new(&self, d: &user::Id) -> Result<(), String> {
Err("not implemented".to_owned())
}
fn delete(&self, d: &user::Id) -> Result<(), String> {
Err("not implemented".to_owned())
}
fn exists(&self, d: &user::Id) -> Result<bool, String> {
Err("not implemented".to_owned())
}
fn get_profile(&self, id: &user::Id) -> Result<user::Profile, String> {
Err("not implemented".to_owned())
}
fn set_profile(&self, id: &user::Id, d: user::Profile) -> Result<user::Profile, String> {
Err("not implemented".to_owned())
}
}

43
src/ctx/user.rs Normal file
View File

@ -0,0 +1,43 @@
use lazy_static::lazy_static;
use regex::Regex;
pub struct Data {
id: Id,
profile: Option<Profile>,
}
pub struct Id {
name: String,
host: Option<String>,
}
pub struct Profile {
name: String,
}
impl Data {
fn get_profile(&self) -> Result<Profile, String> {
Err("not implemented".to_owned())
}
fn set_profile(&self, d: &Profile) -> Result<(), String> {
Err("not implemented".to_owned())
}
}
// hello@world.com -> Id {hello, world.com}
pub fn parse_id(v: &str) -> Result<Id, String> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^([A-Za-z0-9_]+)(?:@([A-Za-z0-9\-\.]+))?$").unwrap();
}
match RE.captures(v) {
Some(caps) => Ok(Id {
name: caps.get(1).unwrap().as_str().to_owned(),
host: caps.get(2).map_or(None, |m| Some(m.as_str().to_owned())),
}),
None => Err("invalid idstr".to_owned()),
}
}
impl Id {
fn to_string(&self, default_host: &str) -> String {
format!("{}@{}", self.name, self.host.as_deref().unwrap_or(default_host))
}
}

1
src/handler.rs Normal file
View File

@ -0,0 +1 @@
pub mod webfinger;

56
src/handler/webfinger.rs Normal file
View File

@ -0,0 +1,56 @@
use rocket::{get, Responder, State};
use rocket::serde::json::Json;
use serde::Serialize;
use crate::ctx;
use crate::ctx::Ctx;
#[derive(Serialize)]
pub struct WebFinger {
subject: String,
aliases: Vec<String>,
}
#[derive(Responder)]
pub enum WebFingerResponse {
#[response(status = 200, content_type = "json")]
Found(Json<WebFinger>),
#[response(status = 404)]
Missing(String),
#[response(status = 400)]
ParseError(String),
#[response(status = 500)]
InternalError(String),
}
#[get("/?<resource>")]
pub fn main(ctx: &State<Ctx>, resource: &str) -> WebFingerResponse {
match parse_resource(&resource) {
Ok(("acct", idstr)) | Ok(("", idstr)) => match ctx::user::parse_id(idstr) {
Ok(id) => match ctx.db.exists(&id) {
Ok(true) => WebFingerResponse::Found(Json(WebFinger {
subject: resource.to_owned(),
aliases: vec![resource.to_owned()],
})),
Ok(false) => WebFingerResponse::Missing(format!("unknown resource: {}", resource)),
Err(msg) => WebFingerResponse::InternalError(format!("error: {}", msg))
}
Err(msg) => WebFingerResponse::ParseError(format!("error: {}", msg)),
},
Ok((kind, _)) => WebFingerResponse::ParseError(format!("error: unknown kind, {}", kind)),
Err(msg) => WebFingerResponse::ParseError(format!("error: {}", msg)),
}
}
fn parse_resource(v: &str) -> Result<(&str, &str), String> {
let mut toks = v.split(':');
match (toks.next(), toks.next(), toks.next()) {
(Some(id), None, None) => Ok(("", id)),
(Some(kind), Some(id), None) => Ok((kind, id)),
_ => Err(format!("invalid resource specifier: {}", v)),
}
}

View File

@ -1,11 +1,16 @@
#[macro_use] extern crate rocket;
mod ctx;
mod handler;
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
use rocket;
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
use ctx::Ctx;
#[rocket::launch]
fn launch() -> _ {
let ctx = Ctx {
db: Box::new(ctx::db::Null {}),
};
rocket::build().
manage(ctx).
mount("/.well-known/webfinger", rocket::routes![handler::webfinger::main])
}