Allow passing auth via header or cookie (#3725)
* Allow passing auth via header or cookie * revert submodule * taplo * fix build * working * convert apub api methods * also set cache-control header * opt * clippy * deduplicate code, ignore invalid auth * clippy --------- Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
parent
7fd14b3d2a
commit
b2aee565f3
13 changed files with 184 additions and 20 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2738,10 +2738,12 @@ dependencies = [
|
||||||
name = "lemmy_db_views"
|
name = "lemmy_db_views"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"actix-web",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-async",
|
"diesel-async",
|
||||||
"diesel_ltree",
|
"diesel_ltree",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
|
"lemmy_utils",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
|
|
|
@ -159,6 +159,16 @@ pub async fn local_user_view_from_jwt_opt(
|
||||||
) -> Option<LocalUserView> {
|
) -> Option<LocalUserView> {
|
||||||
local_user_view_from_jwt(jwt?, context).await.ok()
|
local_user_view_from_jwt(jwt?, context).await.ok()
|
||||||
}
|
}
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn local_user_view_from_jwt_opt_new(
|
||||||
|
local_user_view: &mut Option<LocalUserView>,
|
||||||
|
jwt: Option<&Sensitive<String>>,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) {
|
||||||
|
if local_user_view.is_none() {
|
||||||
|
*local_user_view = local_user_view_from_jwt_opt(jwt, context).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if user's token was issued before user's password reset.
|
/// Checks if user's token was issued before user's password reset.
|
||||||
pub fn check_validator_time(
|
pub fn check_validator_time(
|
||||||
|
|
|
@ -8,21 +8,22 @@ use actix_web::web::{Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
comment::{GetComments, GetCommentsResponse},
|
comment::{GetComments, GetCommentsResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_private_instance, local_user_view_from_jwt_opt},
|
utils::{check_private_instance, local_user_view_from_jwt_opt_new},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{comment::Comment, community::Community, local_site::LocalSite},
|
source::{comment::Comment, community::Community, local_site::LocalSite},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::comment_view::CommentQuery;
|
use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn list_comments(
|
pub async fn list_comments(
|
||||||
data: Query<GetComments>,
|
data: Query<GetComments>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
mut local_user_view: Option<LocalUserView>,
|
||||||
) -> Result<Json<GetCommentsResponse>, LemmyError> {
|
) -> Result<Json<GetCommentsResponse>, LemmyError> {
|
||||||
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await;
|
local_user_view_from_jwt_opt_new(&mut local_user_view, data.auth.as_ref(), &context).await;
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
check_private_instance(&local_user_view, &local_site)?;
|
check_private_instance(&local_user_view, &local_site)?;
|
||||||
|
|
||||||
|
|
|
@ -8,18 +8,19 @@ use actix_web::web::{Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{GetPosts, GetPostsResponse},
|
post::{GetPosts, GetPostsResponse},
|
||||||
utils::{check_private_instance, local_user_view_from_jwt_opt},
|
utils::{check_private_instance, local_user_view_from_jwt_opt_new},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{community::Community, local_site::LocalSite};
|
use lemmy_db_schema::source::{community::Community, local_site::LocalSite};
|
||||||
use lemmy_db_views::post_view::PostQuery;
|
use lemmy_db_views::{post_view::PostQuery, structs::LocalUserView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn list_posts(
|
pub async fn list_posts(
|
||||||
data: Query<GetPosts>,
|
data: Query<GetPosts>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
mut local_user_view: Option<LocalUserView>,
|
||||||
) -> Result<Json<GetPostsResponse>, LemmyError> {
|
) -> Result<Json<GetPostsResponse>, LemmyError> {
|
||||||
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await;
|
local_user_view_from_jwt_opt_new(&mut local_user_view, data.auth.as_ref(), &context).await;
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
check_private_instance(&local_user_view, &local_site)?;
|
check_private_instance(&local_user_view, &local_site)?;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use actix_web::web::{Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
community::{GetCommunity, GetCommunityResponse},
|
community::{GetCommunity, GetCommunityResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_private_instance, is_mod_or_admin_opt, local_user_view_from_jwt_opt},
|
utils::{check_private_instance, is_mod_or_admin_opt, local_user_view_from_jwt_opt_new},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
actor_language::CommunityLanguage,
|
actor_language::CommunityLanguage,
|
||||||
|
@ -12,6 +12,7 @@ use lemmy_db_schema::source::{
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
site::Site,
|
site::Site,
|
||||||
};
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType};
|
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType};
|
||||||
|
|
||||||
|
@ -19,8 +20,9 @@ use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorTy
|
||||||
pub async fn get_community(
|
pub async fn get_community(
|
||||||
data: Query<GetCommunity>,
|
data: Query<GetCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
mut local_user_view: Option<LocalUserView>,
|
||||||
) -> Result<Json<GetCommunityResponse>, LemmyError> {
|
) -> Result<Json<GetCommunityResponse>, LemmyError> {
|
||||||
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await;
|
local_user_view_from_jwt_opt_new(&mut local_user_view, data.auth.as_ref(), &context).await;
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
if data.name.is_none() && data.id.is_none() {
|
if data.name.is_none() && data.id.is_none() {
|
||||||
|
|
|
@ -4,13 +4,13 @@ use actix_web::web::{Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{GetPersonDetails, GetPersonDetailsResponse},
|
person::{GetPersonDetails, GetPersonDetailsResponse},
|
||||||
utils::{check_private_instance, local_user_view_from_jwt_opt},
|
utils::{check_private_instance, local_user_view_from_jwt_opt_new},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{local_site::LocalSite, person::Person},
|
source::{local_site::LocalSite, person::Person},
|
||||||
utils::post_to_comment_sort_type,
|
utils::post_to_comment_sort_type,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery};
|
use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery, structs::LocalUserView};
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView};
|
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType};
|
use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType};
|
||||||
|
|
||||||
|
@ -18,13 +18,14 @@ use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType};
|
||||||
pub async fn read_person(
|
pub async fn read_person(
|
||||||
data: Query<GetPersonDetails>,
|
data: Query<GetPersonDetails>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
mut local_user_view: Option<LocalUserView>,
|
||||||
) -> Result<Json<GetPersonDetailsResponse>, LemmyError> {
|
) -> Result<Json<GetPersonDetailsResponse>, LemmyError> {
|
||||||
// Check to make sure a person name or an id is given
|
// Check to make sure a person name or an id is given
|
||||||
if data.username.is_none() && data.person_id.is_none() {
|
if data.username.is_none() && data.person_id.is_none() {
|
||||||
return Err(LemmyErrorType::NoIdGiven)?;
|
return Err(LemmyErrorType::NoIdGiven)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await;
|
local_user_view_from_jwt_opt_new(&mut local_user_view, data.auth.as_ref(), &context).await;
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
check_private_instance(&local_user_view, &local_site)?;
|
check_private_instance(&local_user_view, &local_site)?;
|
||||||
|
|
|
@ -9,10 +9,10 @@ use diesel::NotFound;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{ResolveObject, ResolveObjectResponse},
|
site::{ResolveObject, ResolveObjectResponse},
|
||||||
utils::{check_private_instance, local_user_view_from_jwt_opt},
|
utils::{check_private_instance, local_user_view_from_jwt_opt_new},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{newtypes::PersonId, source::local_site::LocalSite, utils::DbPool};
|
use lemmy_db_schema::{newtypes::PersonId, source::local_site::LocalSite, utils::DbPool};
|
||||||
use lemmy_db_views::structs::{CommentView, PostView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
|
||||||
use lemmy_db_views_actor::structs::{CommunityView, PersonView};
|
use lemmy_db_views_actor::structs::{CommunityView, PersonView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType};
|
use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType};
|
||||||
|
|
||||||
|
@ -20,8 +20,9 @@ use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType};
|
||||||
pub async fn resolve_object(
|
pub async fn resolve_object(
|
||||||
data: Query<ResolveObject>,
|
data: Query<ResolveObject>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
mut local_user_view: Option<LocalUserView>,
|
||||||
) -> Result<Json<ResolveObjectResponse>, LemmyError> {
|
) -> Result<Json<ResolveObjectResponse>, LemmyError> {
|
||||||
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await;
|
local_user_view_from_jwt_opt_new(&mut local_user_view, data.auth.as_ref(), &context).await;
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
check_private_instance(&local_user_view, &local_site)?;
|
check_private_instance(&local_user_view, &local_site)?;
|
||||||
let person_id = local_user_view.map(|v| v.person.id);
|
let person_id = local_user_view.map(|v| v.person.id);
|
||||||
|
|
|
@ -4,14 +4,14 @@ use actix_web::web::{Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{Search, SearchResponse},
|
site::{Search, SearchResponse},
|
||||||
utils::{check_private_instance, is_admin, local_user_view_from_jwt_opt},
|
utils::{check_private_instance, is_admin, local_user_view_from_jwt_opt_new},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{community::Community, local_site::LocalSite},
|
source::{community::Community, local_site::LocalSite},
|
||||||
utils::{post_to_comment_sort_type, post_to_person_sort_type},
|
utils::{post_to_comment_sort_type, post_to_person_sort_type},
|
||||||
SearchType,
|
SearchType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery};
|
use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery, structs::LocalUserView};
|
||||||
use lemmy_db_views_actor::{community_view::CommunityQuery, person_view::PersonQuery};
|
use lemmy_db_views_actor::{community_view::CommunityQuery, person_view::PersonQuery};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
|
|
||||||
|
@ -19,8 +19,9 @@ use lemmy_utils::error::LemmyError;
|
||||||
pub async fn search(
|
pub async fn search(
|
||||||
data: Query<Search>,
|
data: Query<Search>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
mut local_user_view: Option<LocalUserView>,
|
||||||
) -> Result<Json<SearchResponse>, LemmyError> {
|
) -> Result<Json<SearchResponse>, LemmyError> {
|
||||||
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await;
|
local_user_view_from_jwt_opt_new(&mut local_user_view, data.auth.as_ref(), &context).await;
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
check_private_instance(&local_user_view, &local_site)?;
|
check_private_instance(&local_user_view, &local_site)?;
|
||||||
|
|
|
@ -13,16 +13,18 @@ doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
full = [
|
full = [
|
||||||
"lemmy_db_schema/full",
|
"lemmy_utils",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-async",
|
"diesel-async",
|
||||||
"diesel_ltree",
|
"diesel_ltree",
|
||||||
"tracing",
|
"tracing",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
|
"actix-web",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_db_schema = { workspace = true }
|
lemmy_db_schema = { workspace = true }
|
||||||
|
lemmy_utils = { workspace = true, optional = true }
|
||||||
diesel = { workspace = true, optional = true }
|
diesel = { workspace = true, optional = true }
|
||||||
diesel-async = { workspace = true, optional = true }
|
diesel-async = { workspace = true, optional = true }
|
||||||
diesel_ltree = { workspace = true, optional = true }
|
diesel_ltree = { workspace = true, optional = true }
|
||||||
|
@ -30,6 +32,7 @@ serde = { workspace = true }
|
||||||
serde_with = { workspace = true }
|
serde_with = { workspace = true }
|
||||||
tracing = { workspace = true, optional = true }
|
tracing = { workspace = true, optional = true }
|
||||||
ts-rs = { workspace = true, optional = true }
|
ts-rs = { workspace = true, optional = true }
|
||||||
|
actix-web = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::structs::LocalUserView;
|
use crate::structs::LocalUserView;
|
||||||
|
use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest};
|
||||||
use diesel::{result::Error, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl};
|
use diesel::{result::Error, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
@ -9,6 +10,8 @@ use lemmy_db_schema::{
|
||||||
traits::JoinView,
|
traits::JoinView,
|
||||||
utils::{functions::lower, DbConn, DbPool, ListFn, Queries, ReadFn},
|
utils::{functions::lower, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||||
};
|
};
|
||||||
|
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||||
|
use std::future::{ready, Ready};
|
||||||
|
|
||||||
type LocalUserViewTuple = (LocalUser, Person, PersonAggregates);
|
type LocalUserViewTuple = (LocalUser, Person, PersonAggregates);
|
||||||
|
|
||||||
|
@ -116,3 +119,15 @@ impl JoinView for LocalUserView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromRequest for LocalUserView {
|
||||||
|
type Error = LemmyError;
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||||
|
ready(match req.extensions().get::<LocalUserView>() {
|
||||||
|
Some(c) => Ok(c.clone()),
|
||||||
|
None => Err(LemmyErrorType::IncorrectLogin.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -208,6 +208,7 @@ pub enum LemmyErrorType {
|
||||||
InvalidUrlScheme,
|
InvalidUrlScheme,
|
||||||
CouldntSendWebmention,
|
CouldntSendWebmention,
|
||||||
ContradictingFilters,
|
ContradictingFilters,
|
||||||
|
AuthCookieInsecure,
|
||||||
Unknown(String),
|
Unknown(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -4,10 +4,15 @@ pub mod code_migrations;
|
||||||
pub mod prometheus_metrics;
|
pub mod prometheus_metrics;
|
||||||
pub mod root_span_builder;
|
pub mod root_span_builder;
|
||||||
pub mod scheduled_tasks;
|
pub mod scheduled_tasks;
|
||||||
|
pub mod session_middleware;
|
||||||
#[cfg(feature = "console")]
|
#[cfg(feature = "console")]
|
||||||
pub mod telemetry;
|
pub mod telemetry;
|
||||||
|
|
||||||
use crate::{code_migrations::run_advanced_migrations, root_span_builder::QuieterRootSpanBuilder};
|
use crate::{
|
||||||
|
code_migrations::run_advanced_migrations,
|
||||||
|
root_span_builder::QuieterRootSpanBuilder,
|
||||||
|
session_middleware::SessionMiddleware,
|
||||||
|
};
|
||||||
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
|
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
|
@ -204,7 +209,8 @@ pub async fn start_lemmy_server() -> Result<(), LemmyError> {
|
||||||
.wrap(ErrorHandlers::new().default_handler(jsonify_plain_text_errors))
|
.wrap(ErrorHandlers::new().default_handler(jsonify_plain_text_errors))
|
||||||
.app_data(Data::new(context.clone()))
|
.app_data(Data::new(context.clone()))
|
||||||
.app_data(Data::new(rate_limit_cell.clone()))
|
.app_data(Data::new(rate_limit_cell.clone()))
|
||||||
.wrap(FederationMiddleware::new(federation_config.clone()));
|
.wrap(FederationMiddleware::new(federation_config.clone()))
|
||||||
|
.wrap(SessionMiddleware::new(context.clone()));
|
||||||
|
|
||||||
#[cfg(feature = "prometheus-metrics")]
|
#[cfg(feature = "prometheus-metrics")]
|
||||||
let app = app.wrap(prom_api_metrics.clone());
|
let app = app.wrap(prom_api_metrics.clone());
|
||||||
|
|
120
src/session_middleware.rs
Normal file
120
src/session_middleware.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
use actix_web::{
|
||||||
|
body::MessageBody,
|
||||||
|
cookie::SameSite,
|
||||||
|
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
|
||||||
|
http::header::CACHE_CONTROL,
|
||||||
|
Error,
|
||||||
|
HttpMessage,
|
||||||
|
};
|
||||||
|
use core::future::Ready;
|
||||||
|
use futures_util::future::LocalBoxFuture;
|
||||||
|
use lemmy_api_common::{context::LemmyContext, utils::local_user_view_from_jwt};
|
||||||
|
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||||
|
use reqwest::header::HeaderValue;
|
||||||
|
use std::{future::ready, rc::Rc};
|
||||||
|
|
||||||
|
static AUTH_COOKIE_NAME: &str = "auth";
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
// Try reading jwt from auth header
|
||||||
|
let auth_header = req
|
||||||
|
.headers()
|
||||||
|
.get(AUTH_COOKIE_NAME)
|
||||||
|
.and_then(|h| h.to_str().ok());
|
||||||
|
let jwt = if let Some(a) = auth_header {
|
||||||
|
Some(a.to_string())
|
||||||
|
}
|
||||||
|
// If that fails, try auth cookie. Dont use the `jwt` cookie from lemmy-ui because
|
||||||
|
// its not http-only.
|
||||||
|
else {
|
||||||
|
let auth_cookie = req.cookie(AUTH_COOKIE_NAME);
|
||||||
|
if let Some(a) = &auth_cookie {
|
||||||
|
// ensure that its marked as httponly and secure
|
||||||
|
let secure = a.secure().unwrap_or_default();
|
||||||
|
let http_only = a.http_only().unwrap_or_default();
|
||||||
|
let same_site = a.same_site();
|
||||||
|
if !secure || !http_only || same_site != Some(SameSite::Strict) {
|
||||||
|
return Err(LemmyError::from(LemmyErrorType::AuthCookieInsecure).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auth_cookie.map(|c| c.value().to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
let local_user_view = local_user_view_from_jwt(jwt, &context).await.ok();
|
||||||
|
if let Some(local_user_view) = local_user_view {
|
||||||
|
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.
|
||||||
|
let cache_value = if jwt.is_some() {
|
||||||
|
"private"
|
||||||
|
} else {
|
||||||
|
"public, max-age=60"
|
||||||
|
};
|
||||||
|
res
|
||||||
|
.headers_mut()
|
||||||
|
.insert(CACHE_CONTROL, HeaderValue::from_static(cache_value));
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue