add request proxying and basic user auth

This commit is contained in:
sam 2024-01-19 16:44:18 +01:00
parent bfb0a1d1b0
commit 274c527ade
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
31 changed files with 541 additions and 36 deletions

View file

@ -1,3 +1,4 @@
use rsa::{RsaPublicKey, RsaPrivateKey};
use sqlx::{Pool, Postgres}; use sqlx::{Pool, Postgres};
use crate::config::Config; use crate::config::Config;
@ -5,4 +6,6 @@ use crate::config::Config;
pub struct AppState { pub struct AppState {
pub pool: Pool<Postgres>, pub pool: Pool<Postgres>,
pub config: Config, pub config: Config,
pub public_key: RsaPublicKey,
pub private_key: RsaPrivateKey,
} }

View file

@ -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<Arc<AppState>>,
request: FoxRequestData,
Json(params): Json<CreateGuildParams>,
) -> Result<Json<Guild>, 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,
}
}))
}

View file

@ -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))
}

7
chat/src/http/api/mod.rs Normal file
View file

@ -0,0 +1,7 @@
use axum::Router;
pub mod guilds;
pub fn router() -> Router {
Router::new().merge(guilds::router())
}

View file

@ -16,17 +16,15 @@ use rsa::{
use tracing::error; use tracing::error;
use ulid::Ulid; 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( pub async fn post_hello(
Extension(state): Extension<Arc<AppState>>, Extension(state): Extension<Arc<AppState>>,
signature: FoxSignatureData, signature: FoxSignatureData,
Json(data): Json<HelloRequest>, Json(data): Json<HelloRequest>,
) -> Result<Json<HelloResponse>, ApiError> { ) -> Result<Json<HelloResponse>, ApiError> {
let instance = Instance::get(&state.pool).await?;
let node = fed::get::<NodeResponse>( let node = fed::get::<NodeResponse>(
&instance.private_key, &state.private_key,
&state.config.domain, &state.config.domain,
&data.host, &data.host,
"/_fox/ident/node", "/_fox/ident/node",
@ -68,7 +66,7 @@ pub async fn post_hello(
.await?; .await?;
Ok(Json(HelloResponse { Ok(Json(HelloResponse {
public_key: instance public_key: state
.public_key .public_key
.to_pkcs1_pem(rsa::pkcs8::LineEnding::CR) .to_pkcs1_pem(rsa::pkcs8::LineEnding::CR)
.wrap_err("formatting instance public key")?, .wrap_err("formatting instance public key")?,

View file

@ -1,16 +1,23 @@
mod api;
mod hello; 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 axum::{routing::post, Extension, Router};
use sqlx::{Pool, Postgres}; use sqlx::{Pool, Postgres};
use std::sync::Arc; use std::sync::Arc;
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
pub fn new(pool: Pool<Postgres>, config: Config) -> Router { pub fn new(pool: Pool<Postgres>, config: Config, instance: Instance) -> Router {
let app_state = Arc::new(AppState { pool, config }); let app_state = Arc::new(AppState {
pool,
config,
public_key: instance.public_key,
private_key: instance.private_key,
});
let app = Router::new() let app = Router::new()
.route("/_fox/chat/hello", post(hello::post_hello)) .route("/_fox/chat/hello", post(hello::post_hello))
.merge(api::router())
.layer(TraceLayer::new_for_http()) .layer(TraceLayer::new_for_http())
.layer(Extension(app_state)); .layer(Extension(app_state));

View file

@ -5,7 +5,7 @@ mod fed;
mod http; mod http;
mod model; mod model;
use crate::config::Config; use crate::{config::Config, model::instance::Instance};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use eyre::Result; use eyre::Result;
use std::net::{Ipv4Addr, SocketAddrV4}; use std::net::{Ipv4Addr, SocketAddrV4};
@ -64,8 +64,9 @@ async fn main_web(config: Config) -> Result<()> {
db::init_instance(&pool).await?; db::init_instance(&pool).await?;
info!("Initialized instance data!"); info!("Initialized instance data!");
let instance = Instance::get(&pool).await?;
let port = config.port; 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?; let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)).await?;

View file

@ -1,2 +1,3 @@
pub mod instance; pub mod instance;
pub mod identity_instance; pub mod identity_instance;
pub mod user;

60
chat/src/model/user.rs Normal file
View file

@ -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<String>,
}
impl User {
pub async fn get(
state: &Arc<AppState>,
instance: &IdentityInstance,
remote_id: String,
) -> Result<User> {
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::<HttpUser>(
&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)
}
}

View file

@ -19,6 +19,10 @@ pub enum FoxError {
MissingSignature, MissingSignature,
#[error("invalid signature")] #[error("invalid signature")]
InvalidSignature, InvalidSignature,
#[error("missing or invalid token")]
Unauthorized,
#[error("missing target user ID")]
MissingUser,
} }
impl From<ToStrError> for FoxError { impl From<ToStrError> for FoxError {

View file

@ -40,10 +40,14 @@ fn plaintext_string(
.unwrap_or("".to_owned()); .unwrap_or("".to_owned());
let raw_user_id = user_id.unwrap_or("".into()); let raw_user_id = user_id.unwrap_or("".into());
format!( let s = format!(
"{}:{}:{}:{}:{}", "{}:{}:{}:{}:{}",
raw_time, host, request_path, raw_content_length, raw_user_id raw_time, host, request_path, raw_content_length, raw_user_id
) );
println!("{}", s);
s
} }
pub fn format_date(time: DateTime<Utc>) -> String { pub fn format_date(time: DateTime<Utc>) -> String {

View file

@ -42,6 +42,7 @@ pub enum ErrorCode {
InvalidDate, InvalidDate,
InvalidSignature, InvalidSignature,
MissingSignature, MissingSignature,
Unauthorized,
} }
impl From<sqlx::Error> for ApiError { impl From<sqlx::Error> for ApiError {
@ -121,6 +122,16 @@ impl From<FoxError> for ApiError {
code: ErrorCode::InvalidSignature, code: ErrorCode::InvalidSignature,
message: "Invalid signature".into(), 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(),
}
} }
} }
} }

View file

@ -1,7 +1,8 @@
pub mod error; pub mod error;
pub mod http;
pub mod s2s;
pub mod fed; pub mod fed;
pub mod http;
pub mod model;
pub mod s2s;
pub use error::FoxError; pub use error::FoxError;
pub use fed::signature; pub use fed::signature;

View file

@ -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,
}

View file

@ -0,0 +1,6 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct CreateGuildParams {
pub name: String,
}

View file

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

6
foxchat/src/model/mod.rs Normal file
View file

@ -0,0 +1,6 @@
pub mod guild;
pub mod user;
pub mod http;
pub use guild::Guild;
pub use user::User;

16
foxchat/src/model/user.rs Normal file
View file

@ -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<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PartialUser {
pub id: String,
pub username: String,
pub instance: String,
}

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
foxchat = { path = "../foxchat" } 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"] } clap = { version = "4.4.16", features = ["env", "derive"] }
sqlx = { version = "0.7.3", features = ["runtime-tokio", "tls-rustls", "postgres", "migrate", "chrono", "json"] } sqlx = { version = "0.7.3", features = ["runtime-tokio", "tls-rustls", "postgres", "migrate", "chrono", "json"] }
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] }

View file

@ -1,3 +1,4 @@
use rsa::{RsaPrivateKey, RsaPublicKey};
use sqlx::{Pool, Postgres}; use sqlx::{Pool, Postgres};
use crate::config::Config; use crate::config::Config;
@ -5,4 +6,6 @@ use crate::config::Config;
pub struct AppState { pub struct AppState {
pub pool: Pool<Postgres>, pub pool: Pool<Postgres>,
pub config: Config, pub config: Config,
pub private_key: RsaPrivateKey,
pub public_key: RsaPublicKey,
} }

View file

@ -43,7 +43,7 @@ pub async fn check_token(pool: &Pool<Postgres>, token: String) -> Result<Account
a.id, a.username, a.email, a.password, a.role as "role: Role", a.avatar a.id, a.username, a.email, a.password, a.role as "role: Role", a.avatar
from accounts a from accounts a
join tokens t on t.account_id = a.id join tokens t on t.account_id = a.id
where t.account_id = $1"#, where t.token = $1"#,
hash hash
) )
.fetch_optional(pool) .fetch_optional(pool)

136
identity/src/fed/mod.rs Normal file
View file

@ -0,0 +1,136 @@
mod proxy_header;
pub use proxy_header::ProxyServerHeader;
use std::sync::Arc;
use axum::{
async_trait,
extract::FromRequestParts,
http::{
header::{CONTENT_LENGTH, DATE, HOST},
request::Parts,
},
Extension,
};
use chrono::{DateTime, Utc};
use foxchat::{
fed::{SERVER_HEADER, SIGNATURE_HEADER},
http::ApiError,
signature::{parse_date, verify_signature},
FoxError,
};
use tracing::error;
use crate::{app_state::AppState, model::chat_instance::ChatInstance};
/// A parsed and validated federation signature.
pub struct FoxRequestData {
pub instance: ChatInstance,
}
#[async_trait]
impl<S> FromRequestParts<S> for FoxRequestData
where
S: Send + Sync,
{
type Rejection = ApiError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let signature = FoxSignatureData::from_request_parts(parts, state).await?;
let state: Extension<Arc<AppState>> = 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<Utc>,
pub host: String,
pub request_path: String,
pub content_length: Option<usize>,
}
#[async_trait]
impl<S> FromRequestParts<S> for FoxSignatureData
where
S: Send + Sync,
{
type Rejection = ApiError;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
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::<usize>()?)
} else {
None
};
Ok(FoxSignatureData {
domain,
date,
signature,
host,
request_path: parts.uri.path().to_string(),
content_length,
})
}
}

View file

@ -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<S> FromRequestParts<S> for ProxyServerHeader
where
S: Send + Sync,
{
type Rejection = ApiError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let state: Extension<Arc<AppState>> = 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))
}
}

View file

View file

@ -1,7 +1,38 @@
mod create_user; 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 { 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<Arc<AppState>>,
_data: FoxRequestData,
Path(id): Path<String>,
) -> Result<Json<User>, 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,
}))
} }

39
identity/src/http/auth.rs Normal file
View file

@ -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<S> FromRequestParts<S> for AuthUser
where
S: Send + Sync,
{
type Rejection = ApiError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let state: Extension<Arc<AppState>> = 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())
}
}

View file

@ -1,5 +1,7 @@
mod account; mod account;
mod auth;
mod node; mod node;
mod proxy;
use std::sync::Arc; use std::sync::Arc;
@ -7,13 +9,19 @@ use axum::{routing::get, Extension, Router};
use sqlx::{Pool, Postgres}; use sqlx::{Pool, Postgres};
use tower_http::trace::TraceLayer; 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<Postgres>, config: Config) -> Router { pub fn new(pool: Pool<Postgres>, config: Config, instance: Instance) -> Router {
let app_state = Arc::new(AppState { pool, config }); let app_state = Arc::new(AppState {
pool,
config,
public_key: instance.public_key,
private_key: instance.private_key,
});
let app = Router::new() let app = Router::new()
.merge(account::router()) .merge(account::router())
.nest("/_fox/proxy", proxy::router())
.route("/_fox/ident/node", get(node::get_node)) .route("/_fox/ident/node", get(node::get_node))
.route("/_fox/ident/node/:domain", get(node::get_chat_node)) .route("/_fox/ident/node/:domain", get(node::get_chat_node))
.layer(TraceLayer::new_for_http()) .layer(TraceLayer::new_for_http())

View file

@ -5,14 +5,12 @@ use eyre::{Context, Result};
use foxchat::{http::ApiError, s2s::http::NodeResponse}; use foxchat::{http::ApiError, s2s::http::NodeResponse};
use rsa::pkcs1::EncodeRsaPublicKey; 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( pub async fn get_node(
Extension(state): Extension<Arc<AppState>>, Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<NodeResponse>, ApiError> { ) -> Result<Json<NodeResponse>, ApiError> {
let instance = Instance::get(&state.pool).await?; let public_key: String = state
let public_key = instance
.public_key .public_key
.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF) .to_pkcs1_pem(rsa::pkcs8::LineEnding::LF)
.wrap_err("serializing public key")?; .wrap_err("serializing public key")?;
@ -29,7 +27,7 @@ pub async fn get_chat_node(
Extension(state): Extension<Arc<AppState>>, Extension(state): Extension<Arc<AppState>>,
Path(domain): Path<String>, Path(domain): Path<String>,
) -> Result<Json<ChatInstance>, ApiError> { ) -> Result<Json<ChatInstance>, ApiError> {
let instance = ChatInstance::get(state, domain).await?; let instance = ChatInstance::get(state, &domain).await?;
Ok(Json(instance)) Ok(Json(instance))
} }

View file

@ -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::<CreateGuildParams, Guild>))
}
async fn proxy_get<R: Serialize + DeserializeOwned>(
Extension(state): Extension<Arc<AppState>>,
ProxyServerHeader(remote): ProxyServerHeader,
AuthUser(user): AuthUser,
OriginalUri(original_uri): OriginalUri,
) -> Result<Json<R>, 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::<R>(
&state.private_key,
&state.config.domain,
&remote.domain,
&format!("/_fox/chat/{}", proxy_path),
Some(user.id),
)
.await?;
Ok(Json(resp))
}
async fn proxy_post<B: Serialize, R: Serialize + DeserializeOwned>(
Extension(state): Extension<Arc<AppState>>,
ProxyServerHeader(remote): ProxyServerHeader,
AuthUser(user): AuthUser,
OriginalUri(original_uri): OriginalUri,
Json(body): Json<B>,
) -> Result<Json<R>, 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::<B, R>(
&state.private_key,
&state.config.domain,
&remote.domain,
&format!("/_fox/chat/{}", proxy_path),
Some(user.id),
&body,
)
.await?;
Ok(Json(resp))
}

View file

@ -3,8 +3,9 @@ mod config;
mod db; mod db;
mod http; mod http;
mod model; mod model;
mod fed;
use crate::config::Config; use crate::{config::Config, model::instance::Instance};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::net::{Ipv4Addr, SocketAddrV4}; use std::net::{Ipv4Addr, SocketAddrV4};
@ -63,8 +64,9 @@ async fn main_web(config: Config) -> Result<()> {
db::init_instance(&pool).await?; db::init_instance(&pool).await?;
info!("Initialized instance data!"); info!("Initialized instance data!");
let instance = Instance::get(&pool).await?;
let port = config.port; 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?; let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)).await?;

View file

@ -1,15 +1,16 @@
use std::sync::Arc; use std::sync::Arc;
use eyre::Result; use eyre::{Context, Result};
use foxchat::{ use foxchat::{
fed::{self, request::is_valid_domain}, fed::{self, request::is_valid_domain},
s2s::http::{HelloRequest, HelloResponse}, s2s::http::{HelloRequest, HelloResponse},
FoxError, FoxError,
}; };
use rsa::{pkcs1::DecodeRsaPublicKey, RsaPublicKey};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ulid::Ulid; use ulid::Ulid;
use crate::{app_state::AppState, model::instance::Instance}; use crate::app_state::AppState;
#[derive(Serialize)] #[derive(Serialize)]
pub struct ChatInstance { pub struct ChatInstance {
@ -29,7 +30,7 @@ pub enum InstanceStatus {
} }
impl ChatInstance { impl ChatInstance {
pub async fn get(state: Arc<AppState>, domain: String) -> Result<Self> { pub async fn get(state: Arc<AppState>, domain: &str) -> Result<Self> {
if !is_valid_domain(&domain) { if !is_valid_domain(&domain) {
return Err(FoxError::InvalidServer.into()); return Err(FoxError::InvalidServer.into());
} }
@ -47,10 +48,8 @@ impl ChatInstance {
return Ok(instance); return Ok(instance);
} }
let current_instance = Instance::get(&state.pool).await?;
let resp: HelloResponse = fed::post( let resp: HelloResponse = fed::post(
&current_instance.private_key, &state.private_key,
&state.config.domain, &state.config.domain,
&domain, &domain,
"/_fox/chat/hello", "/_fox/chat/hello",
@ -61,7 +60,7 @@ impl ChatInstance {
) )
.await?; .await?;
if resp.host != domain.clone() { if resp.host != domain {
return Err(FoxError::InvalidServer.into()); return Err(FoxError::InvalidServer.into());
} }
@ -72,7 +71,7 @@ impl ChatInstance {
returning id, domain, base_url, public_key, returning id, domain, base_url, public_key,
status as "status: InstanceStatus", reason"#, status as "status: InstanceStatus", reason"#,
Ulid::new().to_string(), Ulid::new().to_string(),
domain.clone(), domain,
format!("https://{domain}"), format!("https://{domain}"),
resp.public_key resp.public_key
) )
@ -81,4 +80,9 @@ impl ChatInstance {
Ok(instance) Ok(instance)
} }
pub fn parse_public_key(&self) -> Result<RsaPublicKey> {
RsaPublicKey::from_pkcs1_pem(&self.public_key)
.wrap_err("parsing identity instance public key")
}
} }