From 274c527ade7b481cef23f869a665535c74680882 Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 19 Jan 2024 16:44:18 +0100 Subject: [PATCH] add request proxying and basic user auth --- chat/src/app_state.rs | 3 + chat/src/http/api/guilds/create_guild.rs | 40 +++++++ chat/src/http/api/guilds/mod.rs | 8 ++ chat/src/http/api/mod.rs | 7 ++ chat/src/http/hello.rs | 8 +- chat/src/http/mod.rs | 13 ++- chat/src/main.rs | 5 +- chat/src/model/mod.rs | 1 + chat/src/model/user.rs | 60 ++++++++++ foxchat/src/error/mod.rs | 4 + foxchat/src/fed/signature.rs | 8 +- foxchat/src/http/response.rs | 11 ++ foxchat/src/lib.rs | 5 +- foxchat/src/model/guild.rs | 10 ++ foxchat/src/model/http/guild.rs | 6 + foxchat/src/model/http/mod.rs | 1 + foxchat/src/model/mod.rs | 6 + foxchat/src/model/user.rs | 16 +++ identity/Cargo.toml | 2 +- identity/src/app_state.rs | 3 + identity/src/db/account.rs | 2 +- identity/src/fed/mod.rs | 136 +++++++++++++++++++++++ identity/src/fed/proxy_header.rs | 31 ++++++ identity/src/http/account/get_user.rs | 0 identity/src/http/account/mod.rs | 35 +++++- identity/src/http/auth.rs | 39 +++++++ identity/src/http/mod.rs | 14 ++- identity/src/http/node.rs | 8 +- identity/src/http/proxy/mod.rs | 69 ++++++++++++ identity/src/main.rs | 6 +- identity/src/model/chat_instance.rs | 20 ++-- 31 files changed, 541 insertions(+), 36 deletions(-) create mode 100644 chat/src/http/api/guilds/create_guild.rs create mode 100644 chat/src/http/api/guilds/mod.rs create mode 100644 chat/src/http/api/mod.rs create mode 100644 chat/src/model/user.rs create mode 100644 foxchat/src/model/guild.rs create mode 100644 foxchat/src/model/http/guild.rs create mode 100644 foxchat/src/model/http/mod.rs create mode 100644 foxchat/src/model/mod.rs create mode 100644 foxchat/src/model/user.rs create mode 100644 identity/src/fed/mod.rs create mode 100644 identity/src/fed/proxy_header.rs create mode 100644 identity/src/http/account/get_user.rs create mode 100644 identity/src/http/auth.rs create mode 100644 identity/src/http/proxy/mod.rs diff --git a/chat/src/app_state.rs b/chat/src/app_state.rs index b052dba..07bd75d 100644 --- a/chat/src/app_state.rs +++ b/chat/src/app_state.rs @@ -1,3 +1,4 @@ +use rsa::{RsaPublicKey, RsaPrivateKey}; use sqlx::{Pool, Postgres}; use crate::config::Config; @@ -5,4 +6,6 @@ use crate::config::Config; pub struct AppState { pub pool: Pool, pub config: Config, + pub public_key: RsaPublicKey, + pub private_key: RsaPrivateKey, } diff --git a/chat/src/http/api/guilds/create_guild.rs b/chat/src/http/api/guilds/create_guild.rs new file mode 100644 index 0000000..0d094f2 --- /dev/null +++ b/chat/src/http/api/guilds/create_guild.rs @@ -0,0 +1,40 @@ +use std::sync::Arc; + +use axum::{Extension, Json}; +use foxchat::{ + http::ApiError, + model::{http::guild::CreateGuildParams, Guild, user::PartialUser}, + FoxError, +}; +use ulid::Ulid; + +use crate::{app_state::AppState, fed::FoxRequestData, model::user::User}; + +pub async fn create_guild( + Extension(state): Extension>, + request: FoxRequestData, + Json(params): Json, +) -> Result, ApiError> { + let user_id = request.user_id.ok_or(FoxError::MissingUser)?; + + let user = User::get(&state, &request.instance, user_id).await?; + + let guild = sqlx::query!( + "insert into guilds (id, owner_id, name) values ($1, $2, $3) returning *", + Ulid::new().to_string(), + user.id, + params.name + ) + .fetch_one(&state.pool) + .await?; + + Ok(Json(Guild { + id: guild.id, + name: guild.name, + owner: PartialUser { + id: user.id, + username: user.username, + instance: request.instance.domain, + } + })) +} diff --git a/chat/src/http/api/guilds/mod.rs b/chat/src/http/api/guilds/mod.rs new file mode 100644 index 0000000..e625dc7 --- /dev/null +++ b/chat/src/http/api/guilds/mod.rs @@ -0,0 +1,8 @@ +mod create_guild; + +use axum::{Router, routing::post}; + +pub fn router() -> Router { + Router::new() + .route("/_fox/chat/guilds", post(create_guild::create_guild)) +} diff --git a/chat/src/http/api/mod.rs b/chat/src/http/api/mod.rs new file mode 100644 index 0000000..054cc8e --- /dev/null +++ b/chat/src/http/api/mod.rs @@ -0,0 +1,7 @@ +use axum::Router; + +pub mod guilds; + +pub fn router() -> Router { + Router::new().merge(guilds::router()) +} diff --git a/chat/src/http/hello.rs b/chat/src/http/hello.rs index c3932a9..8a4caeb 100644 --- a/chat/src/http/hello.rs +++ b/chat/src/http/hello.rs @@ -16,17 +16,15 @@ use rsa::{ use tracing::error; use ulid::Ulid; -use crate::{app_state::AppState, fed::FoxSignatureData, model::instance::Instance}; +use crate::{app_state::AppState, fed::FoxSignatureData}; pub async fn post_hello( Extension(state): Extension>, signature: FoxSignatureData, Json(data): Json, ) -> Result, ApiError> { - let instance = Instance::get(&state.pool).await?; - let node = fed::get::( - &instance.private_key, + &state.private_key, &state.config.domain, &data.host, "/_fox/ident/node", @@ -68,7 +66,7 @@ pub async fn post_hello( .await?; Ok(Json(HelloResponse { - public_key: instance + public_key: state .public_key .to_pkcs1_pem(rsa::pkcs8::LineEnding::CR) .wrap_err("formatting instance public key")?, diff --git a/chat/src/http/mod.rs b/chat/src/http/mod.rs index 5d7c812..00fb7c4 100644 --- a/chat/src/http/mod.rs +++ b/chat/src/http/mod.rs @@ -1,16 +1,23 @@ +mod api; mod hello; -use crate::{app_state::AppState, config::Config}; +use crate::{app_state::AppState, config::Config, model::instance::Instance}; use axum::{routing::post, Extension, Router}; use sqlx::{Pool, Postgres}; use std::sync::Arc; use tower_http::trace::TraceLayer; -pub fn new(pool: Pool, config: Config) -> Router { - let app_state = Arc::new(AppState { pool, config }); +pub fn new(pool: Pool, config: Config, instance: Instance) -> Router { + let app_state = Arc::new(AppState { + pool, + config, + public_key: instance.public_key, + private_key: instance.private_key, + }); let app = Router::new() .route("/_fox/chat/hello", post(hello::post_hello)) + .merge(api::router()) .layer(TraceLayer::new_for_http()) .layer(Extension(app_state)); diff --git a/chat/src/main.rs b/chat/src/main.rs index 2b11e53..d921680 100644 --- a/chat/src/main.rs +++ b/chat/src/main.rs @@ -5,7 +5,7 @@ mod fed; mod http; mod model; -use crate::config::Config; +use crate::{config::Config, model::instance::Instance}; use clap::{Parser, Subcommand}; use eyre::Result; use std::net::{Ipv4Addr, SocketAddrV4}; @@ -64,8 +64,9 @@ async fn main_web(config: Config) -> Result<()> { db::init_instance(&pool).await?; info!("Initialized instance data!"); + let instance = Instance::get(&pool).await?; let port = config.port; - let app = http::new(pool, config); + let app = http::new(pool, config, instance); let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)).await?; diff --git a/chat/src/model/mod.rs b/chat/src/model/mod.rs index 200e199..cb1354c 100644 --- a/chat/src/model/mod.rs +++ b/chat/src/model/mod.rs @@ -1,2 +1,3 @@ pub mod instance; pub mod identity_instance; +pub mod user; diff --git a/chat/src/model/user.rs b/chat/src/model/user.rs new file mode 100644 index 0000000..1c81619 --- /dev/null +++ b/chat/src/model/user.rs @@ -0,0 +1,60 @@ +use std::sync::Arc; + +use eyre::Result; +use foxchat::{fed, model::User as HttpUser}; +use ulid::Ulid; + +use crate::app_state::AppState; + +use super::identity_instance::IdentityInstance; + +pub struct User { + pub id: String, + pub instance_id: String, + pub remote_user_id: String, + pub username: String, + pub avatar: Option, +} + +impl User { + pub async fn get( + state: &Arc, + instance: &IdentityInstance, + remote_id: String, + ) -> Result { + if let Some(user) = sqlx::query_as!( + User, + "select * from users where instance_id = $1 and remote_user_id = $2", + instance.id, + remote_id + ) + .fetch_optional(&state.pool) + .await? + { + return Ok(user); + } + + let http_user = fed::get::( + &state.private_key, + &state.config.domain, + &instance.domain, + &format!("/_fox/ident/users/{}", remote_id), + None, + ) + .await?; + + let user = sqlx::query_as!( + User, + "insert into users (id, instance_id, remote_user_id, username, avatar) values ($1, $2, $3, $4, $5) returning *", + Ulid::new().to_string(), + instance.id, + http_user.id, + http_user.username, + http_user.avatar_url + ) + .fetch_one(&state.pool) + .await?; + + Ok(user) + } +} diff --git a/foxchat/src/error/mod.rs b/foxchat/src/error/mod.rs index 129f179..2f23a21 100644 --- a/foxchat/src/error/mod.rs +++ b/foxchat/src/error/mod.rs @@ -19,6 +19,10 @@ pub enum FoxError { MissingSignature, #[error("invalid signature")] InvalidSignature, + #[error("missing or invalid token")] + Unauthorized, + #[error("missing target user ID")] + MissingUser, } impl From for FoxError { diff --git a/foxchat/src/fed/signature.rs b/foxchat/src/fed/signature.rs index 44b07d1..d60cd3f 100644 --- a/foxchat/src/fed/signature.rs +++ b/foxchat/src/fed/signature.rs @@ -40,10 +40,14 @@ fn plaintext_string( .unwrap_or("".to_owned()); let raw_user_id = user_id.unwrap_or("".into()); - format!( + let s = format!( "{}:{}:{}:{}:{}", raw_time, host, request_path, raw_content_length, raw_user_id - ) + ); + + println!("{}", s); + + s } pub fn format_date(time: DateTime) -> String { diff --git a/foxchat/src/http/response.rs b/foxchat/src/http/response.rs index 4623ad5..ad22a00 100644 --- a/foxchat/src/http/response.rs +++ b/foxchat/src/http/response.rs @@ -42,6 +42,7 @@ pub enum ErrorCode { InvalidDate, InvalidSignature, MissingSignature, + Unauthorized, } impl From for ApiError { @@ -121,6 +122,16 @@ impl From for ApiError { code: ErrorCode::InvalidSignature, message: "Invalid signature".into(), }, + FoxError::MissingUser => ApiError { + status: StatusCode::BAD_REQUEST, + code: ErrorCode::InvalidHeader, + message: "Missing user header".into(), + }, + FoxError::Unauthorized => ApiError { + status: StatusCode::UNAUTHORIZED, + code: ErrorCode::Unauthorized, + message: "Missing or invalid token".into(), + } } } } diff --git a/foxchat/src/lib.rs b/foxchat/src/lib.rs index 4d4a419..b697129 100644 --- a/foxchat/src/lib.rs +++ b/foxchat/src/lib.rs @@ -1,7 +1,8 @@ pub mod error; -pub mod http; -pub mod s2s; pub mod fed; +pub mod http; +pub mod model; +pub mod s2s; pub use error::FoxError; pub use fed::signature; diff --git a/foxchat/src/model/guild.rs b/foxchat/src/model/guild.rs new file mode 100644 index 0000000..8f3a90f --- /dev/null +++ b/foxchat/src/model/guild.rs @@ -0,0 +1,10 @@ +use serde::{Serialize, Deserialize}; + +use super::user::PartialUser; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Guild { + pub id: String, + pub name: String, + pub owner: PartialUser, +} diff --git a/foxchat/src/model/http/guild.rs b/foxchat/src/model/http/guild.rs new file mode 100644 index 0000000..66a1ace --- /dev/null +++ b/foxchat/src/model/http/guild.rs @@ -0,0 +1,6 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize)] +pub struct CreateGuildParams { + pub name: String, +} diff --git a/foxchat/src/model/http/mod.rs b/foxchat/src/model/http/mod.rs new file mode 100644 index 0000000..4931e5f --- /dev/null +++ b/foxchat/src/model/http/mod.rs @@ -0,0 +1 @@ +pub mod guild; diff --git a/foxchat/src/model/mod.rs b/foxchat/src/model/mod.rs new file mode 100644 index 0000000..f44bc11 --- /dev/null +++ b/foxchat/src/model/mod.rs @@ -0,0 +1,6 @@ +pub mod guild; +pub mod user; +pub mod http; + +pub use guild::Guild; +pub use user::User; diff --git a/foxchat/src/model/user.rs b/foxchat/src/model/user.rs new file mode 100644 index 0000000..6268a5e --- /dev/null +++ b/foxchat/src/model/user.rs @@ -0,0 +1,16 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct User { + pub id: String, + pub username: String, + pub instance: String, + pub avatar_url: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PartialUser { + pub id: String, + pub username: String, + pub instance: String, +} diff --git a/identity/Cargo.toml b/identity/Cargo.toml index 272d930..da31cf7 100644 --- a/identity/Cargo.toml +++ b/identity/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] foxchat = { path = "../foxchat" } -axum = { version = "0.7.4", features = ["macros", "query", "tracing", "ws"] } +axum = { version = "0.7.4", features = ["macros", "query", "tracing", "ws", "original-uri"] } clap = { version = "4.4.16", features = ["env", "derive"] } sqlx = { version = "0.7.3", features = ["runtime-tokio", "tls-rustls", "postgres", "migrate", "chrono", "json"] } serde = { version = "1.0.195", features = ["derive"] } diff --git a/identity/src/app_state.rs b/identity/src/app_state.rs index b052dba..3da8486 100644 --- a/identity/src/app_state.rs +++ b/identity/src/app_state.rs @@ -1,3 +1,4 @@ +use rsa::{RsaPrivateKey, RsaPublicKey}; use sqlx::{Pool, Postgres}; use crate::config::Config; @@ -5,4 +6,6 @@ use crate::config::Config; pub struct AppState { pub pool: Pool, pub config: Config, + pub private_key: RsaPrivateKey, + pub public_key: RsaPublicKey, } diff --git a/identity/src/db/account.rs b/identity/src/db/account.rs index c9f81e4..7067bfa 100644 --- a/identity/src/db/account.rs +++ b/identity/src/db/account.rs @@ -43,7 +43,7 @@ pub async fn check_token(pool: &Pool, token: String) -> Result FromRequestParts for FoxRequestData +where + S: Send + Sync, +{ + type Rejection = ApiError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let signature = FoxSignatureData::from_request_parts(parts, state).await?; + let state: Extension> = Extension::from_request_parts(parts, state) + .await + .expect("AppState was not added as an extension"); + + let instance = ChatInstance::get(state.0.clone(), &signature.domain).await?; + let public_key = instance.parse_public_key()?; + if state.config.domain.clone() != signature.host { + return Err(FoxError::InvalidHeader.into()); + } + + if let Err(e) = verify_signature( + &public_key, + signature.signature, + signature.date, + &signature.host, + &signature.request_path, + signature.content_length, + None, + ) { + error!( + "Verifying signature from request for {} from {}: {}", + parts.uri.path(), + signature.domain, + e + ); + + return Err(FoxError::InvalidSignature.into()); + } + + Ok(FoxRequestData { + instance, + }) + } +} + +// An unvalidated federation signature. Use with caution, you should almost always use `FoxRequestData` instead. +pub struct FoxSignatureData { + pub domain: String, + pub signature: String, + pub date: DateTime, + pub host: String, + pub request_path: String, + pub content_length: Option, +} + +#[async_trait] +impl FromRequestParts for FoxSignatureData +where + S: Send + Sync, +{ + type Rejection = ApiError; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + let domain = parts + .headers + .get(SERVER_HEADER) + .ok_or(FoxError::InvalidHeader)? + .to_str()? + .to_string(); + + let date = parse_date( + parts + .headers + .get(DATE) + .ok_or(FoxError::InvalidHeader)? + .to_str()?, + )?; + let signature = parts + .headers + .get(SIGNATURE_HEADER) + .ok_or(FoxError::MissingSignature)? + .to_str()? + .to_string(); + let host = parts + .headers + .get(HOST) + .ok_or(FoxError::InvalidHeader)? + .to_str()? + .to_string(); + + let content_length = if let Some(raw_length) = parts.headers.get(CONTENT_LENGTH) { + Some(raw_length.to_str()?.parse::()?) + } else { + None + }; + + Ok(FoxSignatureData { + domain, + date, + signature, + host, + request_path: parts.uri.path().to_string(), + content_length, + }) + } +} diff --git a/identity/src/fed/proxy_header.rs b/identity/src/fed/proxy_header.rs new file mode 100644 index 0000000..2e97c53 --- /dev/null +++ b/identity/src/fed/proxy_header.rs @@ -0,0 +1,31 @@ +use axum::{async_trait, extract::FromRequestParts, http::request::Parts, Extension}; +use foxchat::{http::ApiError, fed::SERVER_HEADER, FoxError}; + +use crate::{app_state::AppState, model::chat_instance::ChatInstance}; +use std::sync::Arc; + +pub struct ProxyServerHeader(pub ChatInstance); + +#[async_trait] +impl FromRequestParts for ProxyServerHeader +where + S: Send + Sync, +{ + type Rejection = ApiError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let state: Extension> = Extension::from_request_parts(parts, state) + .await + .expect("AppState was not added as an extension"); + + let domain = parts + .headers + .get(SERVER_HEADER) + .ok_or(FoxError::InvalidHeader)? + .to_str()?; + + let instance = ChatInstance::get(state.0.clone(), domain).await?; + + Ok(ProxyServerHeader(instance)) + } +} diff --git a/identity/src/http/account/get_user.rs b/identity/src/http/account/get_user.rs new file mode 100644 index 0000000..e69de29 diff --git a/identity/src/http/account/mod.rs b/identity/src/http/account/mod.rs index 7399efb..5b56b2d 100644 --- a/identity/src/http/account/mod.rs +++ b/identity/src/http/account/mod.rs @@ -1,7 +1,38 @@ mod create_user; -use axum::{routing::post, Router}; +use std::sync::Arc; + +use axum::{ + extract::Path, + routing::{get, post}, + Extension, Json, Router, +}; +use foxchat::{http::ApiError, model::User}; + +use crate::{app_state::AppState, fed::FoxRequestData}; pub fn router() -> Router { - Router::new().route("/_fox/ident/accounts", post(create_user::create_user)) + Router::new() + .route("/_fox/ident/users", post(create_user::create_user)) + .route("/_fox/ident/users/:id", get(get_user)) +} + +pub async fn get_user( + Extension(state): Extension>, + _data: FoxRequestData, + Path(id): Path, +) -> Result, ApiError> { + let user = sqlx::query!( + "select id, username, avatar from accounts where id = $1", + id + ) + .fetch_one(&state.pool) + .await?; + + Ok(Json(User { + id: user.id, + username: user.username, + instance: state.config.domain.clone(), + avatar_url: None, + })) } diff --git a/identity/src/http/auth.rs b/identity/src/http/auth.rs new file mode 100644 index 0000000..7885a5c --- /dev/null +++ b/identity/src/http/auth.rs @@ -0,0 +1,39 @@ +use std::sync::Arc; + +use crate::{app_state::AppState, db::check_token, model::account::Account}; +use axum::{ + async_trait, + extract::FromRequestParts, + http::{header::AUTHORIZATION, request::Parts}, + Extension, +}; +use foxchat::{http::ApiError, FoxError}; + +pub struct AuthUser(pub Account); + +#[async_trait] +impl FromRequestParts for AuthUser +where + S: Send + Sync, +{ + type Rejection = ApiError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let state: Extension> = Extension::from_request_parts(parts, state) + .await + .expect("AppState was not added as an extension"); + + let token = parts + .headers + .get(AUTHORIZATION) + .ok_or(FoxError::Unauthorized)? + .to_str()? + .to_string(); + + if let Ok(user) = check_token(&state.pool, token).await { + return Ok(AuthUser(user)); + } + + Err(FoxError::Unauthorized.into()) + } +} diff --git a/identity/src/http/mod.rs b/identity/src/http/mod.rs index bd1715b..926283b 100644 --- a/identity/src/http/mod.rs +++ b/identity/src/http/mod.rs @@ -1,5 +1,7 @@ mod account; +mod auth; mod node; +mod proxy; use std::sync::Arc; @@ -7,13 +9,19 @@ use axum::{routing::get, Extension, Router}; use sqlx::{Pool, Postgres}; use tower_http::trace::TraceLayer; -use crate::{app_state::AppState, config::Config}; +use crate::{app_state::AppState, config::Config, model::instance::Instance}; -pub fn new(pool: Pool, config: Config) -> Router { - let app_state = Arc::new(AppState { pool, config }); +pub fn new(pool: Pool, config: Config, instance: Instance) -> Router { + let app_state = Arc::new(AppState { + pool, + config, + public_key: instance.public_key, + private_key: instance.private_key, + }); let app = Router::new() .merge(account::router()) + .nest("/_fox/proxy", proxy::router()) .route("/_fox/ident/node", get(node::get_node)) .route("/_fox/ident/node/:domain", get(node::get_chat_node)) .layer(TraceLayer::new_for_http()) diff --git a/identity/src/http/node.rs b/identity/src/http/node.rs index 14b8f73..91faacb 100644 --- a/identity/src/http/node.rs +++ b/identity/src/http/node.rs @@ -5,14 +5,12 @@ use eyre::{Context, Result}; use foxchat::{http::ApiError, s2s::http::NodeResponse}; use rsa::pkcs1::EncodeRsaPublicKey; -use crate::{app_state::AppState, model::{instance::Instance, chat_instance::ChatInstance}}; +use crate::{app_state::AppState, model::chat_instance::ChatInstance}; pub async fn get_node( Extension(state): Extension>, ) -> Result, ApiError> { - let instance = Instance::get(&state.pool).await?; - - let public_key = instance + let public_key: String = state .public_key .to_pkcs1_pem(rsa::pkcs8::LineEnding::LF) .wrap_err("serializing public key")?; @@ -29,7 +27,7 @@ pub async fn get_chat_node( Extension(state): Extension>, Path(domain): Path, ) -> Result, ApiError> { - let instance = ChatInstance::get(state, domain).await?; + let instance = ChatInstance::get(state, &domain).await?; Ok(Json(instance)) } diff --git a/identity/src/http/proxy/mod.rs b/identity/src/http/proxy/mod.rs new file mode 100644 index 0000000..601030c --- /dev/null +++ b/identity/src/http/proxy/mod.rs @@ -0,0 +1,69 @@ +use std::sync::Arc; + +use axum::{extract::OriginalUri, routing::post, Extension, Json, Router}; +use eyre::ContextCompat; +use foxchat::{ + fed, + http::ApiError, + model::{http::guild::CreateGuildParams, Guild}, +}; +use serde::{de::DeserializeOwned, Serialize}; +use tracing::debug; + +use crate::{app_state::AppState, fed::ProxyServerHeader}; + +use super::auth::AuthUser; + +pub fn router() -> Router { + Router::new().route("/guilds", post(proxy_post::)) +} + +async fn proxy_get( + Extension(state): Extension>, + ProxyServerHeader(remote): ProxyServerHeader, + AuthUser(user): AuthUser, + OriginalUri(original_uri): OriginalUri, +) -> Result, ApiError> { + let original = original_uri.to_string(); + let proxy_path = original + .strip_prefix("/_fox/proxy/") + .wrap_err("invalid url")?; + println!("{}", proxy_path); + + let resp = fed::get::( + &state.private_key, + &state.config.domain, + &remote.domain, + &format!("/_fox/chat/{}", proxy_path), + Some(user.id), + ) + .await?; + + Ok(Json(resp)) +} + +async fn proxy_post( + Extension(state): Extension>, + ProxyServerHeader(remote): ProxyServerHeader, + AuthUser(user): AuthUser, + OriginalUri(original_uri): OriginalUri, + Json(body): Json, +) -> Result, ApiError> { + let original = original_uri.to_string(); + let proxy_path = original + .strip_prefix("/_fox/proxy/") + .wrap_err("invalid url")?; + debug!("Proxying request to {}", proxy_path); + + let resp = fed::post::( + &state.private_key, + &state.config.domain, + &remote.domain, + &format!("/_fox/chat/{}", proxy_path), + Some(user.id), + &body, + ) + .await?; + + Ok(Json(resp)) +} diff --git a/identity/src/main.rs b/identity/src/main.rs index c7441ee..f83271d 100644 --- a/identity/src/main.rs +++ b/identity/src/main.rs @@ -3,8 +3,9 @@ mod config; mod db; mod http; mod model; +mod fed; -use crate::config::Config; +use crate::{config::Config, model::instance::Instance}; use clap::{Parser, Subcommand}; use color_eyre::eyre::Result; use std::net::{Ipv4Addr, SocketAddrV4}; @@ -63,8 +64,9 @@ async fn main_web(config: Config) -> Result<()> { db::init_instance(&pool).await?; info!("Initialized instance data!"); + let instance = Instance::get(&pool).await?; let port = config.port; - let app = http::new(pool, config); + let app = http::new(pool, config, instance); let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)).await?; diff --git a/identity/src/model/chat_instance.rs b/identity/src/model/chat_instance.rs index 75d24a3..30316f2 100644 --- a/identity/src/model/chat_instance.rs +++ b/identity/src/model/chat_instance.rs @@ -1,15 +1,16 @@ use std::sync::Arc; -use eyre::Result; +use eyre::{Context, Result}; use foxchat::{ fed::{self, request::is_valid_domain}, s2s::http::{HelloRequest, HelloResponse}, FoxError, }; +use rsa::{pkcs1::DecodeRsaPublicKey, RsaPublicKey}; use serde::{Deserialize, Serialize}; use ulid::Ulid; -use crate::{app_state::AppState, model::instance::Instance}; +use crate::app_state::AppState; #[derive(Serialize)] pub struct ChatInstance { @@ -29,7 +30,7 @@ pub enum InstanceStatus { } impl ChatInstance { - pub async fn get(state: Arc, domain: String) -> Result { + pub async fn get(state: Arc, domain: &str) -> Result { if !is_valid_domain(&domain) { return Err(FoxError::InvalidServer.into()); } @@ -47,10 +48,8 @@ impl ChatInstance { return Ok(instance); } - let current_instance = Instance::get(&state.pool).await?; - let resp: HelloResponse = fed::post( - ¤t_instance.private_key, + &state.private_key, &state.config.domain, &domain, "/_fox/chat/hello", @@ -61,7 +60,7 @@ impl ChatInstance { ) .await?; - if resp.host != domain.clone() { + if resp.host != domain { return Err(FoxError::InvalidServer.into()); } @@ -72,7 +71,7 @@ impl ChatInstance { returning id, domain, base_url, public_key, status as "status: InstanceStatus", reason"#, Ulid::new().to_string(), - domain.clone(), + domain, format!("https://{domain}"), resp.public_key ) @@ -81,4 +80,9 @@ impl ChatInstance { Ok(instance) } + + pub fn parse_public_key(&self) -> Result { + RsaPublicKey::from_pkcs1_pem(&self.public_key) + .wrap_err("parsing identity instance public key") + } }