Basic webfinger proxy for Cohost.
This commit is contained in:
parent
32999e67d4
commit
6a5efb48d9
File diff suppressed because it is too large
Load Diff
|
@ -8,6 +8,9 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.0.18", features = [ "derive" ] }
|
clap = { version = "4.0.18", features = [ "derive" ] }
|
||||||
eggbug = { version = "0.1.2", features = [ "tokio" ] }
|
eggbug = { version = "0.1.2", features = [ "tokio" ] }
|
||||||
elefren = { version = "0.22.0", features = [ "toml" ] }
|
reqwest = "0.11.12"
|
||||||
tokio = { version = "1.21.2", features = [ "macros", "rt-multi-thread" ] }
|
rocket = { version = "0.5.0-rc.2", features = [ "json" ] }
|
||||||
|
serde = { version = "1.0.147", features = [ "derive" ] }
|
||||||
|
tokio = { version = "1.21.2", features = [ "full" ] }
|
||||||
|
serde_json = "1.0.87"
|
||||||
|
once_cell = "1.16.0"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"projectId": 8891,
|
||||||
|
"displayName": "nora (noracodes@weirder.earth)",
|
||||||
|
"dek": "queer pagan computer toucher",
|
||||||
|
"description": "go follow me on [the fediverse](https://weirder.earth/@noracodes)\r\n\r\nhi! i'm nora. i was born under the [Great Comet of the Millennium](https://en.wikipedia.org/wiki/Comet_Hale%E2%80%93Bopp). i'm a 🦀👩💻\r\nrustacean, 🍄🔮 witch, 📡📻 radio amateur, hacker, synthesist, and general 🚩🏴 leftie nerd who lives on the outskirts of chicago in a little condo with my polycule.\r\n\r\n\"and when the last of those fires let fall / there was no lord in the world at all.\""
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"subject": "acct:noracodes@example.com",
|
||||||
|
"aliases": [
|
||||||
|
"acct:noracodes@cohost.org"
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"rel": "http://webfinger.net/rel/profile-page",
|
||||||
|
"type": "text/html",
|
||||||
|
"href": "https://cohost.org/noracodes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// The API URL from whence Cohost serves JSON project definitions
|
||||||
|
pub const COHOST_ACCOUNT_API_URL: &str = "https://cohost.org/api/v1/project/";
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct CohostAccount {
|
||||||
|
#[serde(rename = "projectId")]
|
||||||
|
project_id: u64,
|
||||||
|
#[serde(rename = "displayName")]
|
||||||
|
display_name: String,
|
||||||
|
dek: String,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_account() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let example = include_str!("../samples/cohost/api/v1/project.json");
|
||||||
|
let expected_account = CohostAccount {
|
||||||
|
project_id: 8891,
|
||||||
|
display_name: "nora (noracodes@weirder.earth)".into(),
|
||||||
|
dek: "queer pagan computer toucher".into(),
|
||||||
|
description: "go follow me on [the fediverse](https://weirder.earth/@noracodes)\r\n\r\nhi! i'm nora. i was born under the [Great Comet of the Millennium](https://en.wikipedia.org/wiki/Comet_Hale%E2%80%93Bopp). i'm a 🦀👩💻\r\nrustacean, 🍄🔮 witch, 📡📻 radio amateur, hacker, synthesist, and general 🚩🏴 leftie nerd who lives on the outskirts of chicago in a little condo with my polycule.\r\n\r\n\"and when the last of those fires let fall / there was no lord in the world at all.\"".into(),
|
||||||
|
};
|
||||||
|
let actual_account: CohostAccount = serde_json::from_str(example)?;
|
||||||
|
assert_eq!(expected_account, actual_account);
|
||||||
|
Ok(())
|
||||||
|
}
|
100
src/main.rs
100
src/main.rs
|
@ -1,62 +1,68 @@
|
||||||
extern crate elefren;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
use rocket::{Request, Response};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
use elefren::prelude::*;
|
mod cohost_account;
|
||||||
use elefren::helpers::toml;
|
mod webfinger;
|
||||||
use elefren::helpers::cli;
|
use cohost_account::{CohostAccount, COHOST_ACCOUNT_API_URL};
|
||||||
use elefren::entities::event::Event;
|
use webfinger::CohostWebfingerResource;
|
||||||
use eggbug::{Client, Post};
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// URL of the mastodon instance you're using
|
/// The base URL for the northbound instance
|
||||||
#[arg(short, long, required = true)]
|
#[clap(short, long, required = true)]
|
||||||
instance: String,
|
domain: String,
|
||||||
/// Cohost e-mail
|
/// The base URL for the northbound instance
|
||||||
#[arg(short, long, required = true)]
|
#[clap(short, long, default_value_t = default_base_url() )]
|
||||||
email: String,
|
base_url: String,
|
||||||
/// Cohost password
|
|
||||||
#[arg(short, long, required = true)]
|
|
||||||
password: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
fn default_base_url() -> String { "/.well-known/webfinger".into() }
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let args = Args::parse();
|
static ARGS: once_cell::sync::Lazy<Args> = once_cell::sync::Lazy::new(|| {
|
||||||
let mastodon = if let Ok(data) = toml::from_file("mastodon-data.toml") {
|
Args::parse()
|
||||||
Mastodon::from(data)
|
});
|
||||||
} else {
|
|
||||||
register(&args.instance)?
|
#[get("/.well-known/webfinger?<params..>")]
|
||||||
|
async fn webfinger_route(params: HashMap<String, String>) -> Option<Json<CohostWebfingerResource>> {
|
||||||
|
if params.len() != 1 {
|
||||||
|
eprintln!("Too may or too few parameters. Expected 1, got {}", params.len());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(param) = params.iter().next() {
|
||||||
|
let url = format!("{}{}", COHOST_ACCOUNT_API_URL, param.0);
|
||||||
|
eprintln!("making request to {}", url);
|
||||||
|
match reqwest::get(url).await {
|
||||||
|
Ok(v) => match v.status() {
|
||||||
|
StatusCode::OK => {
|
||||||
|
return Some(Json(CohostWebfingerResource::new(param.0.as_str(), &ARGS.domain)));
|
||||||
|
},
|
||||||
|
// TODO NORA: Handle possible redirects
|
||||||
|
s => {
|
||||||
|
eprintln!("Didn't receive status code 200 for Cohost project '{}'; got {:?} instead.", param.0, s);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error making request to Cohost for project '{}': {:?}", param.0, e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Good! We're now logged into mastodon, check that it worked
|
|
||||||
mastodon.verify_credentials()?;
|
|
||||||
|
|
||||||
// Now log into Cohost
|
|
||||||
let cohost = Client::new();
|
|
||||||
let cohost = cohost.login(&args.email, &args.password).await?;
|
|
||||||
|
|
||||||
// Now loop over events as they come
|
|
||||||
for event in mastodon.streaming_user()? {
|
|
||||||
match event {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rocket::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Set up the global config
|
||||||
|
once_cell::sync::Lazy::force(&ARGS);
|
||||||
|
let _rocket = rocket::build()
|
||||||
|
.mount("/", routes![webfinger_route])
|
||||||
|
.ignite().await?.launch().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register(instance: &str) -> Result<Mastodon, Box<dyn Error>> {
|
|
||||||
let registration = Registration::new(instance)
|
|
||||||
.client_name("northbound-train")
|
|
||||||
.build()?;
|
|
||||||
let mastodon = cli::authenticate(registration)?;
|
|
||||||
|
|
||||||
// Save app data for using on the next run.
|
|
||||||
toml::to_file(&*mastodon, "mastodon-data.toml")?;
|
|
||||||
|
|
||||||
Ok(mastodon)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct CohostWebfingerResource {
|
||||||
|
subject: String,
|
||||||
|
aliases: Vec<String>,
|
||||||
|
links: Vec<CohostWebfingerProfileLink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct CohostWebfingerProfileLink {
|
||||||
|
rel: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
t_type: String,
|
||||||
|
href: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CohostWebfingerResource {
|
||||||
|
pub fn new<S: AsRef<str>, T: AsRef<str>>(project_id: S, domain: T) -> Self {
|
||||||
|
Self {
|
||||||
|
subject: format!("acct:{}@{}", project_id.as_ref(), domain.as_ref()),
|
||||||
|
aliases: vec![format!("acct:{}@cohost.org", project_id.as_ref())],
|
||||||
|
links: vec![CohostWebfingerProfileLink::new(project_id)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CohostWebfingerProfileLink {
|
||||||
|
pub fn new<S: AsRef<str>>(project_id: S) -> Self {
|
||||||
|
Self {
|
||||||
|
rel: "http://webfinger.net/rel/profile-page".into(),
|
||||||
|
t_type: "text/html".into(),
|
||||||
|
href: format!("https://cohost.org/{}", project_id.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_webfinger_resource() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let expected_json = include_str!("../samples/northbound-train/.well-known/webfinger");
|
||||||
|
let resource: CohostWebfingerResource = CohostWebfingerResource::new("noracodes", "example.com");
|
||||||
|
let actual_json = serde_json::to_string_pretty(&resource)?;
|
||||||
|
println!("{}", actual_json);
|
||||||
|
assert_eq!(&expected_json, &actual_json);
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue