2024-04-13 05:26:50 +02:00
|
|
|
use axum::{extract::{FromRef, FromRequestParts}, http::{header, request::Parts}};
|
2024-03-25 01:58:30 +01:00
|
|
|
use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
|
2024-06-01 05:21:57 +02:00
|
|
|
use httpsign::HttpSignature;
|
2024-07-06 06:08:42 +02:00
|
|
|
use upub::traits::{fetch::RequestError, Fetcher};
|
2024-03-25 01:58:30 +01:00
|
|
|
|
2024-06-01 05:21:57 +02:00
|
|
|
use crate::ApiError;
|
2024-04-18 05:25:56 +02:00
|
|
|
|
2024-03-25 01:58:30 +01:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum Identity {
|
|
|
|
Anonymous,
|
2024-05-25 07:00:03 +02:00
|
|
|
Remote {
|
2024-05-31 14:43:48 +02:00
|
|
|
user: String,
|
2024-05-25 07:00:03 +02:00
|
|
|
domain: String,
|
|
|
|
internal: i64,
|
|
|
|
},
|
|
|
|
Local {
|
|
|
|
id: String,
|
|
|
|
internal: i64,
|
|
|
|
},
|
2024-03-25 01:58:30 +01:00
|
|
|
}
|
|
|
|
|
2024-04-12 19:36:00 +02:00
|
|
|
impl Identity {
|
2024-12-10 23:21:52 +01:00
|
|
|
// TODO i hate having to do this, but if i don't include `activity.actor` column
|
|
|
|
// we can't see our own activities (likes, announces, follows), and if i do
|
|
|
|
// all queries which don't bring up activities break. so it's necessary to
|
|
|
|
// split these two in order to manually include the extra filter when
|
|
|
|
// needed
|
|
|
|
|
|
|
|
pub fn filter_objects(&self) -> Condition {
|
|
|
|
let base_cond = Condition::any().add(upub::model::addressing::Column::Actor.is_null());
|
|
|
|
match self {
|
|
|
|
Identity::Anonymous => base_cond,
|
|
|
|
Identity::Remote { internal, .. } => base_cond.add(upub::model::addressing::Column::Instance.eq(*internal)),
|
|
|
|
Identity::Local { internal, id } => base_cond
|
|
|
|
.add(upub::model::addressing::Column::Actor.eq(*internal))
|
|
|
|
.add(upub::model::object::Column::AttributedTo.eq(id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn filter_activities(&self) -> Condition {
|
2024-06-08 02:18:45 +02:00
|
|
|
let base_cond = Condition::any().add(upub::model::addressing::Column::Actor.is_null());
|
|
|
|
match self {
|
|
|
|
Identity::Anonymous => base_cond,
|
|
|
|
Identity::Remote { internal, .. } => base_cond.add(upub::model::addressing::Column::Instance.eq(*internal)),
|
2024-11-08 14:15:34 +01:00
|
|
|
Identity::Local { internal, id } => base_cond
|
|
|
|
.add(upub::model::addressing::Column::Actor.eq(*internal))
|
2024-12-10 22:47:44 +01:00
|
|
|
.add(upub::model::object::Column::AttributedTo.eq(id))
|
|
|
|
.add(upub::model::activity::Column::Actor.eq(id)),
|
2024-06-08 02:18:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-25 07:00:03 +02:00
|
|
|
pub fn my_id(&self) -> Option<i64> {
|
2024-04-30 01:50:25 +02:00
|
|
|
match self {
|
2024-05-25 07:00:03 +02:00
|
|
|
Identity::Local { internal, .. } => Some(*internal),
|
2024-04-30 01:50:25 +02:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-27 05:38:51 +02:00
|
|
|
pub fn is(&self, uid: &str) -> bool {
|
2024-05-01 17:47:21 +02:00
|
|
|
match self {
|
|
|
|
Identity::Anonymous => false,
|
2024-05-25 07:00:03 +02:00
|
|
|
Identity::Remote { .. } => false, // TODO per-actor server auth should check this
|
2024-05-27 05:38:51 +02:00
|
|
|
Identity::Local { id, .. } => id.as_str() == uid
|
2024-05-01 17:47:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-27 16:58:51 +02:00
|
|
|
#[allow(unused)]
|
2024-04-18 03:06:40 +02:00
|
|
|
pub fn is_anon(&self) -> bool {
|
2024-04-18 03:41:27 +02:00
|
|
|
matches!(self, Self::Anonymous)
|
2024-04-18 03:06:40 +02:00
|
|
|
}
|
|
|
|
|
2024-05-27 16:58:51 +02:00
|
|
|
#[allow(unused)]
|
2024-04-18 04:48:49 +02:00
|
|
|
pub fn is_local(&self) -> bool {
|
2024-05-25 07:00:03 +02:00
|
|
|
matches!(self, Self::Local { .. })
|
2024-04-18 04:48:49 +02:00
|
|
|
}
|
|
|
|
|
2024-05-27 16:58:51 +02:00
|
|
|
#[allow(unused)]
|
2024-04-18 04:48:49 +02:00
|
|
|
pub fn is_remote(&self) -> bool {
|
2024-05-25 07:00:03 +02:00
|
|
|
matches!(self, Self::Remote { .. })
|
2024-04-18 04:48:49 +02:00
|
|
|
}
|
2024-04-12 19:36:00 +02:00
|
|
|
}
|
|
|
|
|
2024-03-25 01:58:30 +01:00
|
|
|
pub struct AuthIdentity(pub Identity);
|
|
|
|
|
2025-01-21 02:39:43 +01:00
|
|
|
#[axum::async_trait]
|
2024-03-25 01:58:30 +01:00
|
|
|
impl<S> FromRequestParts<S> for AuthIdentity
|
|
|
|
where
|
2024-06-01 05:21:57 +02:00
|
|
|
upub::Context: FromRef<S>,
|
2024-03-25 01:58:30 +01:00
|
|
|
S: Send + Sync,
|
|
|
|
{
|
2024-06-01 05:21:57 +02:00
|
|
|
type Rejection = ApiError;
|
2024-03-25 01:58:30 +01:00
|
|
|
|
|
|
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
2024-06-01 05:21:57 +02:00
|
|
|
let ctx = upub::Context::from_ref(state);
|
2024-03-25 01:58:30 +01:00
|
|
|
let mut identity = Identity::Anonymous;
|
|
|
|
|
|
|
|
let auth_header = parts
|
|
|
|
.headers
|
|
|
|
.get(header::AUTHORIZATION)
|
|
|
|
.map(|v| v.to_str().unwrap_or(""))
|
|
|
|
.unwrap_or("");
|
|
|
|
|
|
|
|
if auth_header.starts_with("Bearer ") {
|
2024-06-01 05:21:57 +02:00
|
|
|
match upub::model::session::Entity::find()
|
|
|
|
.filter(upub::model::session::Column::Secret.eq(auth_header.replace("Bearer ", "")))
|
|
|
|
.filter(upub::model::session::Column::Expires.gt(chrono::Utc::now()))
|
2024-03-25 01:58:30 +01:00
|
|
|
.one(ctx.db())
|
2024-06-01 05:21:57 +02:00
|
|
|
.await?
|
2024-03-25 01:58:30 +01:00
|
|
|
{
|
2024-06-01 05:21:57 +02:00
|
|
|
None => return Err(ApiError::unauthorized()),
|
|
|
|
Some(x) => {
|
2024-05-25 07:00:03 +02:00
|
|
|
// TODO could we store both actor ap id and internal id in session? to avoid this extra
|
|
|
|
// lookup on *every* local authed request we receive...
|
2024-06-01 05:21:57 +02:00
|
|
|
let internal = upub::model::actor::Entity::ap_to_internal(&x.actor, ctx.db())
|
|
|
|
.await?
|
|
|
|
.ok_or_else(ApiError::internal_server_error)?;
|
2024-05-25 07:00:03 +02:00
|
|
|
identity = Identity::Local { id: x.actor, internal };
|
|
|
|
},
|
2024-03-25 01:58:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-13 01:49:23 +02:00
|
|
|
if let Some(sig) = parts
|
|
|
|
.headers
|
|
|
|
.get("Signature")
|
|
|
|
.map(|v| v.to_str().unwrap_or(""))
|
|
|
|
{
|
2024-06-29 16:50:31 +02:00
|
|
|
tracing::debug!("validating http signature '{sig}'");
|
2024-04-13 21:22:19 +02:00
|
|
|
let mut http_signature = HttpSignature::parse(sig);
|
2024-04-13 05:26:50 +02:00
|
|
|
|
2024-04-13 21:22:19 +02:00
|
|
|
// TODO assert payload's digest is equal to signature's
|
2024-06-01 05:21:57 +02:00
|
|
|
// really annoying to do here because we're streaming
|
|
|
|
// the request, maybe even impossible with this design?
|
|
|
|
|
2024-04-16 19:19:49 +02:00
|
|
|
let user_id = http_signature.key_id
|
2024-05-31 15:56:25 +02:00
|
|
|
.replace("/main-key", "") // gotosocial whyyyyy
|
2024-04-16 19:19:49 +02:00
|
|
|
.split('#')
|
2024-06-01 05:21:57 +02:00
|
|
|
.next().ok_or(ApiError::bad_request())?
|
2024-04-16 19:19:49 +02:00
|
|
|
.to_string();
|
2024-04-13 21:22:19 +02:00
|
|
|
|
2024-06-07 19:05:37 +02:00
|
|
|
match ctx.fetch_user(&user_id, ctx.db()).await {
|
2024-07-06 06:08:42 +02:00
|
|
|
Err(RequestError::Database(x)) => return Err(RequestError::Database(x).into()),
|
2024-06-15 02:13:52 +02:00
|
|
|
Err(e) => tracing::debug!("could not fetch {user_id} to verify signature: {e}"),
|
2024-06-07 19:05:37 +02:00
|
|
|
Ok(user) => {
|
2024-06-29 16:50:31 +02:00
|
|
|
let signature = http_signature.build_from_parts(parts);
|
|
|
|
tracing::debug!("constructed http signature {signature:?}");
|
|
|
|
let valid = signature.verify(&user.public_key)?;
|
2024-06-06 21:04:04 +02:00
|
|
|
|
2024-06-07 19:05:37 +02:00
|
|
|
if !valid {
|
|
|
|
tracing::warn!("refusing mismatching http signature");
|
|
|
|
return Err(ApiError::unauthorized());
|
|
|
|
}
|
2024-06-06 21:04:04 +02:00
|
|
|
|
2024-12-31 16:30:45 +01:00
|
|
|
if ctx.cfg().reject.requests.contains(&user.domain) {
|
2024-12-29 03:31:59 +01:00
|
|
|
return Err(ApiError::Status(axum::http::StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS));
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ctx.cfg().reject.access.contains(&user.domain) {
|
|
|
|
let internal = upub::model::instance::Entity::domain_to_internal(&user.domain, ctx.db())
|
|
|
|
.await?
|
|
|
|
.ok_or_else(ApiError::internal_server_error)?; // user but not their domain???
|
|
|
|
identity = Identity::Remote { user: user.id, domain: user.domain, internal };
|
|
|
|
}
|
2024-06-07 19:05:37 +02:00
|
|
|
},
|
2024-04-13 03:31:37 +02:00
|
|
|
}
|
2024-06-06 21:04:04 +02:00
|
|
|
|
2024-04-13 01:49:23 +02:00
|
|
|
}
|
2024-03-25 05:12:49 +01:00
|
|
|
|
2024-03-25 01:58:30 +01:00
|
|
|
Ok(AuthIdentity(identity))
|
|
|
|
}
|
|
|
|
}
|