Split into c2s and s2s endpoints
This commit is contained in:
parent
6a73ee7a43
commit
8f6e7a0d02
|
@ -1,17 +1,32 @@
|
||||||
use crate::error::GementionError;
|
use crate::error::GementionError;
|
||||||
use crate::models::mentions::{Mention, MentionUpload};
|
use crate::models::mentions::{C2SMentionRequest, Mention};
|
||||||
use crate::models::web::MentionResponse;
|
use crate::models::web::MentionResponse;
|
||||||
use crate::{verification::verify};
|
use crate::validation::Validation;
|
||||||
|
use crate::verification::verify;
|
||||||
use fluffer::Client;
|
use fluffer::Client;
|
||||||
|
|
||||||
// Receive a mention over Titan.
|
/// Receive a client-to-server Gemention: i.e. a user posting a
|
||||||
// - Make Gemini request to see if target page supports gemention.
|
/// comment to a gemention-enabled page. The client must identify
|
||||||
// - If so, store mention in DB.
|
/// themselves with a cert that at least has a username.
|
||||||
pub(crate) async fn receive_mention(client: Client) -> Result<MentionResponse, GementionError> {
|
pub(crate) async fn receive_c2s_gemention(
|
||||||
// TODO change to return MentionResponse or something like that.
|
client: Client,
|
||||||
let titan = MentionUpload::try_from(&client)?;
|
) -> Result<MentionResponse, GementionError> {
|
||||||
let mut mention = Mention::try_from(titan)?;
|
let mention_upload = C2SMentionRequest::try_from(&client)?.validate()?;
|
||||||
verify(&mut mention).await?;
|
let mut mention = Mention::try_from(mention_upload)?;
|
||||||
|
verify(&client.url, &mut mention).await?;
|
||||||
|
|
||||||
|
Ok(MentionResponse::from(mention))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive a server-to-server Gemention: another capsule linking to a
|
||||||
|
/// gemention-enabled page on this capsule. There is an exchange of
|
||||||
|
/// titan keys so that the servers can talk to one another.
|
||||||
|
pub(crate) async fn receive_s2s_gemention(
|
||||||
|
client: Client,
|
||||||
|
) -> Result<MentionResponse, GementionError> {
|
||||||
|
let mention_upload = C2SMentionRequest::try_from(&client)?;
|
||||||
|
let mut mention = Mention::try_from(mention_upload)?;
|
||||||
|
verify(&client.url, &mut mention).await?;
|
||||||
|
|
||||||
Ok(MentionResponse::from(mention))
|
Ok(MentionResponse::from(mention))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ use async_trait::async_trait;
|
||||||
use fluffer::GemBytes;
|
use fluffer::GemBytes;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::validation::c2s::C2SValidationError;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub(crate) enum GementionError {
|
pub(crate) enum GementionError {
|
||||||
#[error("No content found for target")]
|
#[error("No content found for target")]
|
||||||
|
@ -30,6 +32,9 @@ pub(crate) enum GementionError {
|
||||||
#[error("value was not utf8: {0}")]
|
#[error("value was not utf8: {0}")]
|
||||||
Utf8Error(#[from] Utf8Error),
|
Utf8Error(#[from] Utf8Error),
|
||||||
|
|
||||||
|
#[error("c2s validation error: {0}")]
|
||||||
|
C2SValidationError(#[from] C2SValidationError),
|
||||||
|
|
||||||
#[error("generic error: {0}")]
|
#[error("generic error: {0}")]
|
||||||
UnclassifiedError(#[from] anyhow::Error),
|
UnclassifiedError(#[from] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use fluffer::AppErr;
|
||||||
mod comments;
|
mod comments;
|
||||||
mod error;
|
mod error;
|
||||||
mod models;
|
mod models;
|
||||||
|
mod validation;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod verification;
|
mod verification;
|
||||||
|
|
||||||
|
|
|
@ -29,15 +29,20 @@ fn parse_content_body(raw_body: &[u8]) -> Result<&str, GementionError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps an incoming Titan request and provides handy methods for
|
/// Wraps an incoming Titan request and provides handy methods for
|
||||||
/// extracting the expected information.
|
/// extracting the expected information for a client-to-server
|
||||||
pub(crate) struct MentionUpload<'a> {
|
/// gemention.
|
||||||
|
pub(crate) struct C2SMentionRequest<'a> {
|
||||||
client: &'a Client,
|
client: &'a Client,
|
||||||
|
receiving_endpoint: &'a Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MentionUpload<'a> {
|
impl<'a> C2SMentionRequest<'a> {
|
||||||
pub fn from_client(client: &'a Client) -> Result<MentionUpload, GementionError> {
|
pub fn from_client(client: &'a Client) -> Result<C2SMentionRequest, GementionError> {
|
||||||
if let Some(_) = client.titan {
|
if let Some(_) = client.titan {
|
||||||
Ok(Self { client: &client })
|
Ok(Self {
|
||||||
|
client: &client,
|
||||||
|
receiving_endpoint: &client.url,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(GementionError::NotTitanResource)
|
Err(GementionError::NotTitanResource)
|
||||||
}
|
}
|
||||||
|
@ -59,6 +64,10 @@ impl<'a> MentionUpload<'a> {
|
||||||
titan!(self).size
|
titan!(self).size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn certificate(&self) -> Option<String> {
|
||||||
|
self.client.certificate()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fingerprint(&self) -> Option<String> {
|
pub fn fingerprint(&self) -> Option<String> {
|
||||||
self.client.fingerprint()
|
self.client.fingerprint()
|
||||||
}
|
}
|
||||||
|
@ -74,13 +83,17 @@ impl<'a> MentionUpload<'a> {
|
||||||
pub fn content(&self) -> Result<&str, GementionError> {
|
pub fn content(&self) -> Result<&str, GementionError> {
|
||||||
parse_content_body(self.raw_body())
|
parse_content_body(self.raw_body())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn receiving_endpoint(&self) -> &Url {
|
||||||
|
self.receiving_endpoint
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a Client> for MentionUpload<'a> {
|
impl<'a> TryFrom<&'a Client> for C2SMentionRequest<'a> {
|
||||||
type Error = GementionError;
|
type Error = GementionError;
|
||||||
|
|
||||||
fn try_from(client: &'a Client) -> Result<Self, Self::Error> {
|
fn try_from(client: &'a Client) -> Result<Self, Self::Error> {
|
||||||
MentionUpload::from_client(client)
|
C2SMentionRequest::from_client(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,10 +201,10 @@ impl TryFrom<&[u8]> for MentionType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<MentionUpload<'a>> for Mention {
|
impl<'a> TryFrom<C2SMentionRequest<'a>> for Mention {
|
||||||
type Error = GementionError;
|
type Error = GementionError;
|
||||||
|
|
||||||
fn try_from(resource: MentionUpload<'a>) -> Result<Self, Self::Error> {
|
fn try_from(resource: C2SMentionRequest<'a>) -> Result<Self, Self::Error> {
|
||||||
if resource.mime() == "text/plain" {
|
if resource.mime() == "text/plain" {
|
||||||
// Be flexible on mention type: if first line isn't a
|
// Be flexible on mention type: if first line isn't a
|
||||||
// mention type, just assume reply.
|
// mention type, just assume reply.
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::error::GementionError;
|
use crate::error::GementionError;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::mentions::Mention;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum VerificationStatus {
|
pub(crate) enum VerificationStatus {
|
||||||
Verified {
|
Verified {
|
||||||
|
|
|
@ -70,7 +70,7 @@ impl GemBytes for MentionResponse {
|
||||||
let status_line = self.status_line();
|
let status_line = self.status_line();
|
||||||
let body = if self.should_have_body() {
|
let body = if self.should_have_body() {
|
||||||
format!(
|
format!(
|
||||||
"{}\n\n{}\n\n{},",
|
"{}\n\n{}\n\n{}",
|
||||||
self.headline(),
|
self.headline(),
|
||||||
self.description(),
|
self.description(),
|
||||||
self.mention_body()
|
self.mention_body()
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use fluffer::App;
|
use fluffer::App;
|
||||||
use crate::comments::receive_mention;
|
use crate::comments::{receive_c2s_gemention, receive_s2s_gemention};
|
||||||
|
|
||||||
pub(crate) fn create_app() -> App {
|
pub(crate) fn create_app() -> App {
|
||||||
App::default()
|
App::default()
|
||||||
.titan("/receive/*target", receive_mention, 20_000)
|
.titan("/c2s/receive/*target", receive_c2s_gemention, 2048)
|
||||||
|
.titan("/s2s/receive/*target", receive_s2s_gemention, 2048)
|
||||||
.route("/", |_| async {
|
.route("/", |_| async {
|
||||||
"# Welcome\n=> titan://localhost/receive/agnos.is/posts/webmentions-test.gmi Receive Mention"
|
"# Welcome\n=> titan://localhost/c2s/receive/agnos.is/posts/webmentions-test.gmi Receive Mention"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::{models::mentions::C2SMentionRequest, error::GementionError};
|
||||||
|
use super::Validation;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum C2SValidationError {
|
||||||
|
#[error("a username is required on the client certificate")]
|
||||||
|
UsernameRequired,
|
||||||
|
|
||||||
|
#[error("a client certificate is required")]
|
||||||
|
ClientCertificateRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Validation for C2SMentionRequest<'_> {
|
||||||
|
type Error = C2SValidationError;
|
||||||
|
|
||||||
|
fn validate(self) -> Result<Self, Self::Error> {
|
||||||
|
if self.username().is_none() {
|
||||||
|
return Err(C2SValidationError::UsernameRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.certificate().is_none() {
|
||||||
|
return Err(C2SValidationError::ClientCertificateRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
pub(crate) mod c2s;
|
||||||
|
|
||||||
|
pub trait Validation {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn validate(self) -> Result<Self, Self::Error> where Self: Sized;
|
||||||
|
}
|
|
@ -69,9 +69,9 @@ fn verify_mentions<S: AsRef<str>>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify_mention(mention: &mut Mention) -> Result<(), GementionError> {
|
async fn verify_mention(expected_link: &Url, mention: &mut Mention) -> Result<(), GementionError> {
|
||||||
let url = Url::parse(&format!("gemini://{}", mention.target()))?;
|
let url = Url::parse(&format!("gemini://{}", mention.target()))?;
|
||||||
let expected_link = Url::parse(OUR_ENDPOINT)?.join(mention.target())?;
|
println!("expected link is {}", expected_link);
|
||||||
|
|
||||||
let resp = germ_request(&url).await?;
|
let resp = germ_request(&url).await?;
|
||||||
let meta = GeminiMetadata::from_string(resp.meta());
|
let meta = GeminiMetadata::from_string(resp.meta());
|
||||||
|
@ -89,6 +89,6 @@ async fn verify_mention(mention: &mut Mention) -> Result<(), GementionError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn verify(mention: &mut Mention) -> Result<(), GementionError> {
|
pub(crate) async fn verify(expected_url: &Url, mention: &mut Mention) -> Result<(), GementionError> {
|
||||||
verify_mention(mention).await
|
verify_mention(expected_url, mention).await
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue