287 lines
8.1 KiB
Rust
287 lines
8.1 KiB
Rust
use crate::{
|
|
error::{Error, OptionExt},
|
|
State,
|
|
};
|
|
use actix_session::{Session, UserSession};
|
|
use actix_web::{
|
|
dev::{Payload, Service, ServiceRequest, ServiceResponse, Transform},
|
|
http::StatusCode,
|
|
web::{Data, Path},
|
|
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
|
|
};
|
|
use event_listener::Event;
|
|
use futures::future::LocalBoxFuture;
|
|
use hyaenidae_accounts::Authenticated;
|
|
use hyaenidae_profiles::store::{Profile, Submission};
|
|
use std::{
|
|
cell::{Cell, RefCell},
|
|
future::{ready, Ready},
|
|
rc::Rc,
|
|
};
|
|
use uuid::Uuid;
|
|
|
|
pub(crate) struct Referer(pub(crate) String);
|
|
|
|
impl FromRequest for Referer {
|
|
type Config = ();
|
|
type Error = actix_web::Error;
|
|
type Future = Ready<Result<Self, Self::Error>>;
|
|
|
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
|
let optionable = || {
|
|
let value = req.headers().get("Referer")?;
|
|
let s = value.to_str().ok()?;
|
|
Some(Referer(s.to_owned()))
|
|
};
|
|
|
|
ready((optionable)().ok_or_else(|| Error::Required.into()))
|
|
}
|
|
}
|
|
|
|
pub struct CurrentSubmission(pub Submission);
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
struct SubmissionPath {
|
|
submission_id: Uuid,
|
|
}
|
|
|
|
impl FromRequest for CurrentSubmission {
|
|
type Config = ();
|
|
type Error = actix_web::Error;
|
|
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
|
|
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
|
let state_fut = Data::<State>::extract(&req);
|
|
let submission_id_fut = Path::<SubmissionPath>::extract(&req);
|
|
|
|
Box::pin(async move {
|
|
let submission_id = submission_id_fut.await?.submission_id;
|
|
let state = state_fut.await?;
|
|
|
|
let store = state.profiles.clone();
|
|
|
|
let submission =
|
|
actix_web::web::block(move || store.store.submissions.by_id(submission_id)?.req())
|
|
.await??;
|
|
|
|
Ok(CurrentSubmission(submission))
|
|
})
|
|
}
|
|
}
|
|
|
|
pub(crate) struct UserProfile(pub Profile);
|
|
|
|
impl UserProfile {
|
|
pub fn into_inner(self) -> Profile {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl FromRequest for UserProfile {
|
|
type Config = ();
|
|
type Error = actix_web::Error;
|
|
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
|
|
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
|
let opt = req
|
|
.extensions()
|
|
.get::<ProfileExtractor>()
|
|
.map(|extractor| (extractor.clone(), extractor.2.listen()));
|
|
|
|
let user_fut = Authenticated::extract(&req);
|
|
let state_fut = Data::<State>::extract(&req);
|
|
|
|
Box::pin(async move {
|
|
let (extractor, listen_fut) = match opt {
|
|
Some(tuple) => tuple,
|
|
None => return Err(ServerError("/404".to_owned()).into()),
|
|
};
|
|
|
|
if let Some(profile) = extractor.0.borrow().as_ref() {
|
|
return Ok(UserProfile(profile.clone()));
|
|
}
|
|
|
|
if !extractor.1.get() {
|
|
listen_fut.await;
|
|
|
|
if let Some(profile) = extractor.0.borrow().as_ref() {
|
|
return Ok(UserProfile(profile.clone()));
|
|
}
|
|
}
|
|
|
|
let state = state_fut.await?;
|
|
let user_id = user_fut.await?.user().id();
|
|
|
|
let has_profiles = state
|
|
.profiles
|
|
.store
|
|
.profiles
|
|
.for_local(user_id)
|
|
.next()
|
|
.is_some();
|
|
|
|
let redirect = if has_profiles {
|
|
"/profiles/change"
|
|
} else {
|
|
"/profiles/create/handle"
|
|
};
|
|
|
|
Err(ServerError(redirect.to_owned()).into())
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
pub(super) struct ProfileData {
|
|
id: Uuid,
|
|
}
|
|
|
|
impl ProfileData {
|
|
pub(super) fn set_data(id: Uuid, session: &Session) -> Result<(), SessionError> {
|
|
session
|
|
.insert("profile-data", ProfileData { id })
|
|
.map_err(|_| SessionError::Set)
|
|
}
|
|
|
|
fn data(session: &Session) -> Result<Option<Self>, actix_web::Error> {
|
|
Ok(session.get("profile-data")?)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub(super) enum SessionError {
|
|
#[error("Error setting key")]
|
|
Set,
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
#[error("Redirecting to {0}")]
|
|
struct ServerError(String);
|
|
|
|
impl ResponseError for ServerError {
|
|
fn status_code(&self) -> StatusCode {
|
|
StatusCode::SEE_OTHER
|
|
}
|
|
|
|
fn error_response(&self) -> HttpResponse {
|
|
HttpResponse::SeeOther()
|
|
.insert_header(("Location", self.0.clone()))
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
pub(crate) struct CurrentProfile(pub State);
|
|
pub(crate) struct ProfileMiddleware<S>(State, S);
|
|
|
|
#[derive(Clone)]
|
|
struct ProfileExtractor(Rc<RefCell<Option<Profile>>>, Rc<Cell<bool>>, Rc<Event>);
|
|
struct ProfileDropGuard(Rc<Event>, Rc<Cell<bool>>);
|
|
|
|
fn profile_extractor() -> (ProfileExtractor, ProfileDropGuard) {
|
|
let event = Rc::new(Event::new());
|
|
let state = Rc::new(RefCell::new(None));
|
|
let flag = Rc::new(Cell::new(false));
|
|
|
|
(
|
|
ProfileExtractor(state, Rc::clone(&flag), Rc::clone(&event)),
|
|
ProfileDropGuard(event, flag),
|
|
)
|
|
}
|
|
|
|
impl Drop for ProfileDropGuard {
|
|
fn drop(&mut self) {
|
|
self.1.set(true);
|
|
self.0.notify(usize::MAX);
|
|
}
|
|
}
|
|
|
|
impl<S, B> Transform<S, ServiceRequest> for CurrentProfile
|
|
where
|
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
|
S::Future: 'static,
|
|
{
|
|
type Response = S::Response;
|
|
type Error = S::Error;
|
|
type InitError = ();
|
|
type Transform = ProfileMiddleware<S>;
|
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
|
|
|
fn new_transform(&self, service: S) -> Self::Future {
|
|
ready(Ok(ProfileMiddleware(self.0.clone(), service)))
|
|
}
|
|
}
|
|
|
|
impl<S, B> Service<ServiceRequest> for ProfileMiddleware<S>
|
|
where
|
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
|
S::Future: 'static,
|
|
{
|
|
type Response = S::Response;
|
|
type Error = S::Error;
|
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
|
|
|
fn poll_ready(
|
|
&self,
|
|
cx: &mut std::task::Context<'_>,
|
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
|
self.1.poll_ready(cx)
|
|
}
|
|
|
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
|
let session = req.get_session();
|
|
|
|
let (extractor, drop_guard) = profile_extractor();
|
|
req.extensions_mut().insert(extractor.clone());
|
|
|
|
let (req, pl) = req.into_parts();
|
|
|
|
let user_fut = Authenticated::extract(&req);
|
|
|
|
let req = ServiceRequest::from_parts(req, pl);
|
|
let fut = self.1.call(req);
|
|
|
|
let state = self.0.clone();
|
|
|
|
Box::pin(async move {
|
|
let user_id = if let Ok(auth) = user_fut.await {
|
|
auth.user().id()
|
|
} else {
|
|
drop(drop_guard);
|
|
return fut.await;
|
|
};
|
|
|
|
if let Some(ProfileData { id: profile_id }) = ProfileData::data(&session)? {
|
|
match actix_web::web::block(move || {
|
|
Ok(state.profiles.store.profiles.by_id(profile_id)?)
|
|
as Result<Option<Profile>, Error>
|
|
})
|
|
.await
|
|
{
|
|
Ok(Ok(Some(profile))) => {
|
|
if profile
|
|
.local_owner()
|
|
.map(|id| id == user_id)
|
|
.unwrap_or(false)
|
|
{
|
|
*extractor.0.borrow_mut() = Some(profile);
|
|
}
|
|
}
|
|
Ok(Ok(None)) => {
|
|
log::warn!("No profile found for {}", profile_id);
|
|
}
|
|
Ok(Err(e)) => {
|
|
log::warn!("Error fetching profile {}: {}", profile_id, e);
|
|
}
|
|
Err(_) => {
|
|
log::warn!("Panic while fetching profile {}", profile_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
drop(drop_guard);
|
|
|
|
fut.await
|
|
})
|
|
}
|
|
}
|