add actor handler
This commit is contained in:
parent
5787b16017
commit
9449d5812f
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -180,6 +180,7 @@ dependencies = [
|
||||
"regex",
|
||||
"rocket",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -10,3 +10,4 @@ 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"] }
|
||||
serde_json = "1.0.96"
|
||||
|
3
src/apub.rs
Normal file
3
src/apub.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod actor;
|
||||
|
||||
pub use actor::Actor;
|
29
src/apub/actor.rs
Normal file
29
src/apub/actor.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Actor {
|
||||
pub context: Vec<String>,
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub type_: String,
|
||||
pub preferred_username: String,
|
||||
pub inbox: String,
|
||||
pub outbox: String,
|
||||
pub public_key: PublicKey,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PublicKey {
|
||||
pub id: String,
|
||||
pub owner: String,
|
||||
pub public_key_pem: String,
|
||||
}
|
||||
|
||||
impl Actor {
|
||||
pub fn default_context() -> Vec<String> {
|
||||
vec![
|
||||
"https://www.w3.org/ns/activitystreams".to_owned(),
|
||||
"https://w3id.org/security/v1".to_owned(),
|
||||
]
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
pub mod config;
|
||||
pub mod db;
|
||||
pub mod user;
|
||||
|
||||
pub use config::Config;
|
||||
pub use db::DB;
|
||||
|
||||
pub struct Ctx {
|
||||
pub cfg: Config,
|
||||
pub db: Box<dyn DB>,
|
||||
}
|
||||
|
34
src/ctx/config.rs
Normal file
34
src/ctx/config.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub proto: String,
|
||||
pub domain: String,
|
||||
pub basepath: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn default() -> Config {
|
||||
Config {
|
||||
proto: "https".to_owned(),
|
||||
domain: "localhost".to_owned(),
|
||||
basepath: "".to_owned(),
|
||||
}
|
||||
}
|
||||
pub fn load(path: &str) -> Result<Config, String> {
|
||||
match std::fs::read_to_string(path) {
|
||||
Ok(text) => match serde_json::from_str(&text) {
|
||||
Ok(cfg) => Ok(cfg),
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
},
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_baseurl(&self) -> String {
|
||||
let mut p =
|
||||
format!("{}://{}/{}", self.proto, self.domain, self.basepath);
|
||||
while p.ends_with("/") { p.pop(); }
|
||||
return p;
|
||||
}
|
||||
}
|
@ -5,26 +5,16 @@ pub trait DB : Send + Sync {
|
||||
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>;
|
||||
fn get_profile(&self, id: &user::Id) -> Result<user::Profile, ReadError>;
|
||||
fn set_profile(&self, id: &user::Id, d: user::Profile) -> Result<user::Profile, WriteError>;
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
pub enum ReadError {
|
||||
Missing,
|
||||
Internal(String),
|
||||
}
|
||||
pub enum WriteError {
|
||||
Missing,
|
||||
Violation,
|
||||
Internal(String),
|
||||
}
|
||||
|
@ -1,42 +1,33 @@
|
||||
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 name: String,
|
||||
pub 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())
|
||||
}
|
||||
pub name: String,
|
||||
pub private: bool,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
pub fn parse(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()),
|
||||
}
|
||||
}
|
||||
pub fn local(name: &str) -> Id {
|
||||
Id {name: name.to_owned(), host: None}
|
||||
}
|
||||
|
||||
fn to_string(&self, default_host: &str) -> String {
|
||||
format!("{}@{}", self.name, self.host.as_deref().unwrap_or(default_host))
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod actor;
|
||||
pub mod webfinger;
|
||||
|
47
src/handler/actor.rs
Normal file
47
src/handler/actor.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use rocket::{get, Responder, State};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
use crate::apub;
|
||||
use crate::ctx::db;
|
||||
use crate::ctx::user;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
|
||||
#[derive(Responder)]
|
||||
pub enum Response {
|
||||
#[response(status = 200, content_type = "json")]
|
||||
Found(Json<apub::Actor>),
|
||||
|
||||
#[response(status = 404)]
|
||||
Missing(String),
|
||||
|
||||
#[response(status = 500)]
|
||||
InternalError(String),
|
||||
}
|
||||
|
||||
#[get("/<name>")]
|
||||
pub fn main(ctx: &State<Ctx>, name: &str) -> Response {
|
||||
match ctx.db.get_profile(&user::Id::local(name)) {
|
||||
Ok(profile) => Response::Found(Json(new(ctx, name, &profile))),
|
||||
Err(db::ReadError::Missing) => Response::Missing(format!("unknown actor: {}", name)),
|
||||
Err(db::ReadError::Internal(msg)) =>
|
||||
Response::InternalError(format!("internal error: {}", msg)),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(ctx: &Ctx, id: &str, p: &user::Profile) -> apub::Actor {
|
||||
let idurl = format!("{}/{}", ctx.cfg.get_baseurl(), id);
|
||||
apub::Actor {
|
||||
context: apub::Actor::default_context(),
|
||||
id : idurl.clone(),
|
||||
type_ : "Person".to_owned(),
|
||||
preferred_username: id.to_owned(),
|
||||
inbox : format!("{}/inbox", idurl),
|
||||
outbox : format!("{}/outbox", idurl),
|
||||
public_key: apub::actor::PublicKey {
|
||||
id: format!("{}#main-key", idurl),
|
||||
owner: idurl,
|
||||
public_key_pem: "".to_owned(),
|
||||
},
|
||||
}
|
||||
}
|
@ -7,12 +7,21 @@ use crate::ctx::Ctx;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct WebFinger {
|
||||
subject: String,
|
||||
aliases: Vec<String>,
|
||||
subject: String,
|
||||
aliases: Vec<String>,
|
||||
links: Vec<Link>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Link {
|
||||
rel: String,
|
||||
#[serde(rename(serialize = "type"))]
|
||||
type_: String,
|
||||
href: String,
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
pub enum WebFingerResponse {
|
||||
pub enum Response {
|
||||
#[response(status = 200, content_type = "json")]
|
||||
Found(Json<WebFinger>),
|
||||
|
||||
@ -27,22 +36,29 @@ pub enum WebFingerResponse {
|
||||
}
|
||||
|
||||
#[get("/?<resource>")]
|
||||
pub fn main(ctx: &State<Ctx>, resource: &str) -> WebFingerResponse {
|
||||
pub fn main(ctx: &State<Ctx>, resource: &str) -> Response {
|
||||
match parse_resource(&resource) {
|
||||
Ok(("acct", idstr)) | Ok(("", idstr)) => match ctx::user::parse_id(idstr) {
|
||||
Ok(("acct", idstr)) | Ok(("", idstr)) => match ctx::user::Id::parse(idstr) {
|
||||
Ok(id) => match ctx.db.exists(&id) {
|
||||
Ok(true) => WebFingerResponse::Found(Json(WebFinger {
|
||||
Ok(true) => Response::Found(Json(WebFinger {
|
||||
subject: resource.to_owned(),
|
||||
aliases: vec![resource.to_owned()],
|
||||
links: vec![
|
||||
Link {
|
||||
rel: "self".to_owned(),
|
||||
type_: "application/activity+json".to_owned(),
|
||||
href: format!("{}/{}", ctx.cfg.get_baseurl(), id.name),
|
||||
},
|
||||
],
|
||||
})),
|
||||
|
||||
Ok(false) => WebFingerResponse::Missing(format!("unknown resource: {}", resource)),
|
||||
Err(msg) => WebFingerResponse::InternalError(format!("error: {}", msg))
|
||||
Ok(false) => Response::Missing(format!("unknown resource: {}", resource)),
|
||||
Err(msg) => Response::InternalError(format!("error: {}", msg))
|
||||
}
|
||||
Err(msg) => WebFingerResponse::ParseError(format!("error: {}", msg)),
|
||||
Err(msg) => Response::ParseError(format!("error: {}", msg)),
|
||||
},
|
||||
Ok((kind, _)) => WebFingerResponse::ParseError(format!("error: unknown kind, {}", kind)),
|
||||
Err(msg) => WebFingerResponse::ParseError(format!("error: {}", msg)),
|
||||
Ok((kind, _)) => Response::ParseError(format!("error: unknown kind, {}", kind)),
|
||||
Err(msg) => Response::ParseError(format!("error: {}", msg)),
|
||||
}
|
||||
}
|
||||
|
||||
|
33
src/main.rs
33
src/main.rs
@ -1,16 +1,45 @@
|
||||
mod apub;
|
||||
mod ctx;
|
||||
mod handler;
|
||||
|
||||
use rocket;
|
||||
|
||||
use ctx::db;
|
||||
use ctx::user;
|
||||
use ctx::Ctx;
|
||||
use ctx::Config;
|
||||
|
||||
#[rocket::launch]
|
||||
fn launch() -> _ {
|
||||
let ctx = Ctx {
|
||||
db: Box::new(ctx::db::Null {}),
|
||||
cfg: Config::load("fadaway.json").unwrap_or(Config::default()),
|
||||
db : Box::new(FakeDB {}),
|
||||
};
|
||||
rocket::build().
|
||||
manage(ctx).
|
||||
mount("/.well-known/webfinger", rocket::routes![handler::webfinger::main])
|
||||
mount("/.well-known/webfinger", rocket::routes![handler::webfinger::main]).
|
||||
mount("/", rocket::routes![handler::actor::main])
|
||||
}
|
||||
|
||||
pub struct FakeDB;
|
||||
impl ctx::DB for FakeDB {
|
||||
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> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn get_profile(&self, id: &user::Id) -> Result<user::Profile, db::ReadError> {
|
||||
Ok(user::Profile {
|
||||
name: id.name.to_owned(),
|
||||
private: true,
|
||||
})
|
||||
}
|
||||
fn set_profile(&self, id: &user::Id, d: user::Profile) -> Result<user::Profile, db::WriteError> {
|
||||
Err(db::WriteError::Internal("not implemented".to_owned()))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user