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",
|
"regex",
|
||||||
"rocket",
|
"rocket",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -10,3 +10,4 @@ lazy_static = "1.4.0"
|
|||||||
regex = "1.8.3"
|
regex = "1.8.3"
|
||||||
rocket = { version = "=0.5.0-rc.3", features = ["json"] }
|
rocket = { version = "=0.5.0-rc.3", features = ["json"] }
|
||||||
serde = { version = "1.0.163", features = ["derive"] }
|
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 db;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
|
pub use config::Config;
|
||||||
pub use db::DB;
|
pub use db::DB;
|
||||||
|
|
||||||
pub struct Ctx {
|
pub struct Ctx {
|
||||||
|
pub cfg: Config,
|
||||||
pub db: Box<dyn DB>,
|
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 delete(&self, d: &user::Id) -> Result<(), String>;
|
||||||
fn exists(&self, d: &user::Id) -> Result<bool, String>;
|
fn exists(&self, d: &user::Id) -> Result<bool, String>;
|
||||||
|
|
||||||
fn get_profile(&self, id: &user::Id) -> 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, String>;
|
fn set_profile(&self, id: &user::Id, d: user::Profile) -> Result<user::Profile, WriteError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Null;
|
pub enum ReadError {
|
||||||
impl DB for Null {
|
Missing,
|
||||||
fn new(&self, d: &user::Id) -> Result<(), String> {
|
Internal(String),
|
||||||
Err("not implemented".to_owned())
|
}
|
||||||
}
|
pub enum WriteError {
|
||||||
fn delete(&self, d: &user::Id) -> Result<(), String> {
|
Missing,
|
||||||
Err("not implemented".to_owned())
|
Violation,
|
||||||
}
|
Internal(String),
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,18 @@
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
pub struct Data {
|
|
||||||
id: Id,
|
|
||||||
profile: Option<Profile>,
|
|
||||||
}
|
|
||||||
pub struct Id {
|
pub struct Id {
|
||||||
name: String,
|
pub name: String,
|
||||||
host: Option<String>,
|
pub host: Option<String>,
|
||||||
}
|
}
|
||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
name: String,
|
pub name: String,
|
||||||
}
|
pub private: bool,
|
||||||
|
|
||||||
|
|
||||||
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}
|
// hello@world.com -> Id {hello, world.com}
|
||||||
pub fn parse_id(v: &str) -> Result<Id, String> {
|
impl Id {
|
||||||
|
pub fn parse(v: &str) -> Result<Id, String> {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE: Regex = Regex::new(r"^([A-Za-z0-9_]+)(?:@([A-Za-z0-9\-\.]+))?$").unwrap();
|
static ref RE: Regex = Regex::new(r"^([A-Za-z0-9_]+)(?:@([A-Za-z0-9\-\.]+))?$").unwrap();
|
||||||
}
|
}
|
||||||
@ -35,8 +23,11 @@ pub fn parse_id(v: &str) -> Result<Id, String> {
|
|||||||
}),
|
}),
|
||||||
None => Err("invalid idstr".to_owned()),
|
None => Err("invalid idstr".to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Id {
|
pub fn local(name: &str) -> Id {
|
||||||
|
Id {name: name.to_owned(), host: None}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_string(&self, default_host: &str) -> String {
|
fn to_string(&self, default_host: &str) -> String {
|
||||||
format!("{}@{}", self.name, self.host.as_deref().unwrap_or(default_host))
|
format!("{}@{}", self.name, self.host.as_deref().unwrap_or(default_host))
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
pub mod actor;
|
||||||
pub mod webfinger;
|
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(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -9,10 +9,19 @@ use crate::ctx::Ctx;
|
|||||||
pub struct WebFinger {
|
pub struct WebFinger {
|
||||||
subject: String,
|
subject: String,
|
||||||
aliases: Vec<String>,
|
aliases: Vec<String>,
|
||||||
|
links: Vec<Link>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Link {
|
||||||
|
rel: String,
|
||||||
|
#[serde(rename(serialize = "type"))]
|
||||||
|
type_: String,
|
||||||
|
href: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Responder)]
|
#[derive(Responder)]
|
||||||
pub enum WebFingerResponse {
|
pub enum Response {
|
||||||
#[response(status = 200, content_type = "json")]
|
#[response(status = 200, content_type = "json")]
|
||||||
Found(Json<WebFinger>),
|
Found(Json<WebFinger>),
|
||||||
|
|
||||||
@ -27,22 +36,29 @@ pub enum WebFingerResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/?<resource>")]
|
#[get("/?<resource>")]
|
||||||
pub fn main(ctx: &State<Ctx>, resource: &str) -> WebFingerResponse {
|
pub fn main(ctx: &State<Ctx>, resource: &str) -> Response {
|
||||||
match parse_resource(&resource) {
|
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(id) => match ctx.db.exists(&id) {
|
||||||
Ok(true) => WebFingerResponse::Found(Json(WebFinger {
|
Ok(true) => Response::Found(Json(WebFinger {
|
||||||
subject: resource.to_owned(),
|
subject: resource.to_owned(),
|
||||||
aliases: vec![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)),
|
Ok(false) => Response::Missing(format!("unknown resource: {}", resource)),
|
||||||
Err(msg) => WebFingerResponse::InternalError(format!("error: {}", msg))
|
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)),
|
Ok((kind, _)) => Response::ParseError(format!("error: unknown kind, {}", kind)),
|
||||||
Err(msg) => WebFingerResponse::ParseError(format!("error: {}", msg)),
|
Err(msg) => Response::ParseError(format!("error: {}", msg)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
33
src/main.rs
33
src/main.rs
@ -1,16 +1,45 @@
|
|||||||
|
mod apub;
|
||||||
mod ctx;
|
mod ctx;
|
||||||
mod handler;
|
mod handler;
|
||||||
|
|
||||||
use rocket;
|
use rocket;
|
||||||
|
|
||||||
|
use ctx::db;
|
||||||
|
use ctx::user;
|
||||||
use ctx::Ctx;
|
use ctx::Ctx;
|
||||||
|
use ctx::Config;
|
||||||
|
|
||||||
#[rocket::launch]
|
#[rocket::launch]
|
||||||
fn launch() -> _ {
|
fn launch() -> _ {
|
||||||
let ctx = Ctx {
|
let ctx = Ctx {
|
||||||
db: Box::new(ctx::db::Null {}),
|
cfg: Config::load("fadaway.json").unwrap_or(Config::default()),
|
||||||
|
db : Box::new(FakeDB {}),
|
||||||
};
|
};
|
||||||
rocket::build().
|
rocket::build().
|
||||||
manage(ctx).
|
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