145 lines
3.8 KiB
Rust
145 lines
3.8 KiB
Rust
|
use actix_session::Session;
|
||
|
use actix_web::{
|
||
|
dev::Payload,
|
||
|
http::{header::LOCATION, StatusCode},
|
||
|
web::Data,
|
||
|
FromRequest, HttpRequest, HttpResponse, ResponseError,
|
||
|
};
|
||
|
use futures::future::LocalBoxFuture;
|
||
|
use uuid::Uuid;
|
||
|
|
||
|
use crate::{store::User, State};
|
||
|
|
||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||
|
pub(crate) struct CookieData {
|
||
|
accepted: bool,
|
||
|
}
|
||
|
|
||
|
impl CookieData {
|
||
|
pub(crate) fn set_accepted(session: &Session) -> Result<(), SessionError> {
|
||
|
session
|
||
|
.set("accepted", CookieData { accepted: true })
|
||
|
.map_err(|_| SessionError::Set)
|
||
|
}
|
||
|
|
||
|
fn accepted(session: &Session) -> bool {
|
||
|
session
|
||
|
.get::<CookieData>("accepted")
|
||
|
.map(|opt| opt.is_some())
|
||
|
.unwrap_or(false)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||
|
pub(crate) struct UserData {
|
||
|
id: Uuid,
|
||
|
}
|
||
|
|
||
|
impl UserData {
|
||
|
pub(crate) fn set_data(id: Uuid, session: &Session) -> Result<(), SessionError> {
|
||
|
session
|
||
|
.set("user-data", UserData { id })
|
||
|
.map_err(|_| SessionError::Set)
|
||
|
}
|
||
|
|
||
|
pub(crate) fn remove(session: &Session) {
|
||
|
session.remove("user-data")
|
||
|
}
|
||
|
|
||
|
fn data(session: &Session) -> Result<Option<UserData>, actix_web::Error> {
|
||
|
Ok(session.get::<UserData>("user-data")?)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub struct AcceptedCookies(());
|
||
|
|
||
|
pub struct Authenticated {
|
||
|
user: User,
|
||
|
}
|
||
|
|
||
|
impl Authenticated {
|
||
|
pub fn user(self) -> User {
|
||
|
self.user
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, thiserror::Error)]
|
||
|
#[error("{0}")]
|
||
|
struct ServerError(String);
|
||
|
|
||
|
impl ResponseError for ServerError {
|
||
|
fn status_code(&self) -> StatusCode {
|
||
|
StatusCode::SEE_OTHER
|
||
|
}
|
||
|
|
||
|
fn error_response(&self) -> HttpResponse {
|
||
|
HttpResponse::build(self.status_code())
|
||
|
.header(LOCATION, self.0.clone())
|
||
|
.finish()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, thiserror::Error)]
|
||
|
pub enum SessionError {
|
||
|
#[error("Failed to set sesion data")]
|
||
|
Set,
|
||
|
}
|
||
|
|
||
|
impl FromRequest for AcceptedCookies {
|
||
|
type Error = actix_web::Error;
|
||
|
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||
|
type Config = ();
|
||
|
|
||
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||
|
let session_fut = Session::extract(req);
|
||
|
let state_fut = Data::<State>::extract(req);
|
||
|
|
||
|
Box::pin(async move {
|
||
|
let session = session_fut.await?;
|
||
|
let state = state_fut.await?;
|
||
|
|
||
|
if CookieData::accepted(&session) {
|
||
|
Ok(AcceptedCookies(()))
|
||
|
} else {
|
||
|
log::debug!("Browser has not accepted cookies");
|
||
|
Err(ServerError(state.pages.not_found_path()).into())
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl FromRequest for Authenticated {
|
||
|
type Error = actix_web::Error;
|
||
|
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||
|
type Config = ();
|
||
|
|
||
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||
|
let session_fut = Session::extract(req);
|
||
|
let state_fut = Data::<State>::extract(req);
|
||
|
|
||
|
Box::pin(async move {
|
||
|
let session = session_fut.await?;
|
||
|
let state = state_fut.await?;
|
||
|
|
||
|
if let Some(UserData { id }) = UserData::data(&session)? {
|
||
|
let res = state.user_store.user_by_id(id).await;
|
||
|
|
||
|
let user_opt = match res {
|
||
|
Ok(user_opt) => user_opt,
|
||
|
Err(_) => {
|
||
|
log::debug!("Error fetching user data from store");
|
||
|
return Err(ServerError(state.pages.internal_server_error_path()).into());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if let Some(user) = user_opt {
|
||
|
return Ok(Authenticated { user });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
log::debug!("No user data found for browser");
|
||
|
Err(ServerError(state.pages.not_found_path()).into())
|
||
|
})
|
||
|
}
|
||
|
}
|