2023-08-29 14:47:57 +00:00
|
|
|
use actix_web::{
|
|
|
|
body::MessageBody,
|
|
|
|
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
|
2023-10-09 10:46:12 +00:00
|
|
|
http::header::CACHE_CONTROL,
|
2023-09-23 01:39:03 +00:00
|
|
|
Error,
|
|
|
|
HttpMessage,
|
2023-08-29 14:47:57 +00:00
|
|
|
};
|
|
|
|
use core::future::Ready;
|
|
|
|
use futures_util::future::LocalBoxFuture;
|
2023-10-17 15:25:48 +00:00
|
|
|
use lemmy_api::{local_user_view_from_jwt, read_auth_token};
|
|
|
|
use lemmy_api_common::context::LemmyContext;
|
2023-08-29 14:47:57 +00:00
|
|
|
use reqwest::header::HeaderValue;
|
|
|
|
use std::{future::ready, rc::Rc};
|
2023-12-01 14:18:29 +00:00
|
|
|
use tracing::log::warn;
|
2023-08-29 14:47:57 +00:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct SessionMiddleware {
|
|
|
|
context: LemmyContext,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SessionMiddleware {
|
|
|
|
pub fn new(context: LemmyContext) -> Self {
|
|
|
|
SessionMiddleware { context }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl<S, B> Transform<S, ServiceRequest> for SessionMiddleware
|
|
|
|
where
|
|
|
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
|
|
|
|
S::Future: 'static,
|
|
|
|
B: MessageBody + 'static,
|
|
|
|
{
|
|
|
|
type Response = ServiceResponse<B>;
|
|
|
|
type Error = Error;
|
|
|
|
type Transform = SessionService<S>;
|
|
|
|
type InitError = ();
|
|
|
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
|
|
|
|
|
|
|
fn new_transform(&self, service: S) -> Self::Future {
|
|
|
|
ready(Ok(SessionService {
|
|
|
|
service: Rc::new(service),
|
|
|
|
context: self.context.clone(),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct SessionService<S> {
|
|
|
|
service: Rc<S>,
|
|
|
|
context: LemmyContext,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S, B> Service<ServiceRequest> for SessionService<S>
|
|
|
|
where
|
|
|
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
|
|
|
|
S::Future: 'static,
|
|
|
|
B: 'static,
|
|
|
|
{
|
|
|
|
type Response = ServiceResponse<B>;
|
|
|
|
type Error = Error;
|
|
|
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
|
|
|
|
|
|
|
forward_ready!(service);
|
|
|
|
|
|
|
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
|
|
|
let svc = self.service.clone();
|
|
|
|
let context = self.context.clone();
|
|
|
|
|
|
|
|
Box::pin(async move {
|
2023-10-09 10:46:12 +00:00
|
|
|
let jwt = read_auth_token(req.request())?;
|
2023-08-29 14:47:57 +00:00
|
|
|
|
|
|
|
if let Some(jwt) = &jwt {
|
|
|
|
// Ignore any invalid auth so the site can still be used
|
|
|
|
// TODO: this means it will be impossible to get any error message for invalid jwt. Need
|
|
|
|
// to add a separate endpoint for that.
|
|
|
|
// https://github.com/LemmyNet/lemmy/issues/3702
|
2023-12-01 14:18:29 +00:00
|
|
|
let local_user_view = local_user_view_from_jwt(jwt, &context).await;
|
|
|
|
if let Err(e) = &local_user_view {
|
|
|
|
warn!("Failed to handle user login: {e}");
|
|
|
|
}
|
|
|
|
if let Ok(local_user_view) = local_user_view {
|
2023-08-29 14:47:57 +00:00
|
|
|
req.extensions_mut().insert(local_user_view);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut res = svc.call(req).await?;
|
|
|
|
|
|
|
|
// Add cache-control header. If user is authenticated, mark as private. Otherwise cache
|
|
|
|
// up to one minute.
|
2023-12-01 14:18:29 +00:00
|
|
|
|
|
|
|
let disable_cache = context.settings().disable_cache_control.unwrap_or(false);
|
|
|
|
let cache_value = if jwt.is_some() || disable_cache {
|
2023-08-29 14:47:57 +00:00
|
|
|
"private"
|
|
|
|
} else {
|
|
|
|
"public, max-age=60"
|
|
|
|
};
|
2023-12-01 14:18:29 +00:00
|
|
|
|
2023-08-29 14:47:57 +00:00
|
|
|
res
|
|
|
|
.headers_mut()
|
|
|
|
.insert(CACHE_CONTROL, HeaderValue::from_static(cache_value));
|
|
|
|
Ok(res)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-09-21 10:42:28 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
#![allow(clippy::unwrap_used)]
|
|
|
|
#![allow(clippy::indexing_slicing)]
|
|
|
|
|
|
|
|
use super::*;
|
2023-10-09 10:46:12 +00:00
|
|
|
use actix_web::test::TestRequest;
|
2023-10-17 15:25:48 +00:00
|
|
|
use lemmy_api_common::claims::Claims;
|
2023-09-21 10:42:28 +00:00
|
|
|
use lemmy_db_schema::{
|
|
|
|
source::{
|
|
|
|
instance::Instance,
|
|
|
|
local_user::{LocalUser, LocalUserInsertForm},
|
|
|
|
person::{Person, PersonInsertForm},
|
|
|
|
secret::Secret,
|
|
|
|
},
|
|
|
|
traits::Crud,
|
|
|
|
utils::build_db_pool_for_tests,
|
|
|
|
};
|
2023-10-19 13:31:51 +00:00
|
|
|
use lemmy_utils::rate_limit::RateLimitCell;
|
2023-10-09 10:46:12 +00:00
|
|
|
use reqwest::Client;
|
|
|
|
use reqwest_middleware::ClientBuilder;
|
2023-09-21 10:42:28 +00:00
|
|
|
use serial_test::serial;
|
2023-10-09 10:46:12 +00:00
|
|
|
use std::env::set_current_dir;
|
2023-09-21 10:42:28 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_session_auth() {
|
2023-10-09 10:46:12 +00:00
|
|
|
// hack, necessary so that config file can be loaded from hardcoded, relative path
|
|
|
|
set_current_dir("crates/utils").unwrap();
|
2023-09-21 10:42:28 +00:00
|
|
|
|
2023-10-09 10:46:12 +00:00
|
|
|
let pool_ = build_db_pool_for_tests().await;
|
|
|
|
let pool = &mut (&pool_).into();
|
|
|
|
let secret = Secret::init(pool).await.unwrap();
|
|
|
|
let context = LemmyContext::create(
|
|
|
|
pool_.clone(),
|
|
|
|
ClientBuilder::new(Client::default()).build(),
|
|
|
|
secret,
|
2023-10-19 13:31:51 +00:00
|
|
|
RateLimitCell::with_test_config(),
|
2023-10-09 10:46:12 +00:00
|
|
|
);
|
2023-09-21 10:42:28 +00:00
|
|
|
|
|
|
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let new_person = PersonInsertForm::builder()
|
|
|
|
.name("Gerry9812".into())
|
|
|
|
.public_key("pubkey".to_string())
|
|
|
|
.instance_id(inserted_instance.id)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
|
|
|
|
|
|
|
let local_user_form = LocalUserInsertForm::builder()
|
|
|
|
.person_id(inserted_person.id)
|
|
|
|
.password_encrypted("123456".to_string())
|
|
|
|
.build();
|
|
|
|
|
|
|
|
let inserted_local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
|
|
|
|
|
2023-10-09 10:46:12 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let jwt = Claims::generate(inserted_local_user.id, req, &context)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let valid = Claims::validate(&jwt, &context).await;
|
|
|
|
assert!(valid.is_ok());
|
2023-09-21 10:42:28 +00:00
|
|
|
|
|
|
|
let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap();
|
|
|
|
assert_eq!(1, num_deleted);
|
|
|
|
}
|
|
|
|
}
|