#![allow(dead_code)]
use axum::{
extract::FromRequestParts,
http::{HeaderMap, Method, Uri, header, request::Parts},
response::{IntoResponse, Redirect, Response},
};
use ocre::{Ctx, Error, Result, Session, security::url_from};
use crate::models::{
user::User,
user_session::{self, NewUserSession},
};
pub const SESSION_TOKEN: &str = "session_token";
const RETURN_TO: &str = "return_to";
pub const SESSION_SECONDS: i64 = 14 * 24 * 3600;
pub const OAUTH_PROVIDERS: &[&str] = &[];
pub struct CurrentUser(pub User);
pub struct OptionalUser(pub Option<User>);
pub struct ConfirmedUser(pub User);
impl FromRequestParts<Ctx> for OptionalUser {
type Rejection = Error;
async fn from_request_parts(parts: &mut Parts, ctx: &Ctx) -> Result<Self> {
let session = Session::from_request_parts(parts, ctx).await?;
let Some(token) = session.get::<String>(SESSION_TOKEN)? else { return Ok(Self(None)) };
let user = user_session::authenticate(ctx, &token).await?;
if user.is_none() {
session.remove(SESSION_TOKEN)?;
}
Ok(Self(user))
}
}
impl FromRequestParts<Ctx> for CurrentUser {
type Rejection = Response;
async fn from_request_parts(parts: &mut Parts, ctx: &Ctx) -> std::result::Result<Self, Response> {
match OptionalUser::from_request_parts(parts, ctx).await {
Ok(OptionalUser(Some(user))) => Ok(Self(user)),
Ok(OptionalUser(None)) => Err(to_login(parts, ctx).await.unwrap_or_else(IntoResponse::into_response)),
Err(err) => Err(err.into_response()),
}
}
}
impl FromRequestParts<Ctx> for ConfirmedUser {
type Rejection = Response;
async fn from_request_parts(parts: &mut Parts, ctx: &Ctx) -> std::result::Result<Self, Response> {
let CurrentUser(user) = CurrentUser::from_request_parts(parts, ctx).await?;
if user.confirmed() {
return Ok(Self(user));
}
let session = Session::from_request_parts(parts, ctx).await.map_err(IntoResponse::into_response)?;
session.flash("alert", "Please confirm your email address first.").map_err(IntoResponse::into_response)?;
Err(Redirect::to("/account").into_response())
}
}
async fn to_login(parts: &mut Parts, ctx: &Ctx) -> Result<Response> {
let session = Session::from_request_parts(parts, ctx).await?;
if parts.method == Method::GET {
session.insert(RETURN_TO, parts.uri.path_and_query().map_or("/", |path| path.as_str()))?;
}
session.flash("alert", "Please log in to continue.")?;
Ok(Redirect::to("/login").into_response())
}
pub async fn sign_in(ctx: &Ctx, session: &Session, headers: &HeaderMap, user: &User, remember: bool) -> Result<String> {
let return_to = session.get::<String>(RETURN_TO)?.and_then(|path| url_from(&Uri::default(), &path));
let new = NewUserSession {
user_id: user.id,
ip_address: ocre::remote_ip(headers).map(|ip| ip.to_string()),
user_agent: headers.get(header::USER_AGENT).and_then(|agent| agent.to_str().ok()).map(str::to_owned),
seconds: SESSION_SECONDS,
};
let token = user_session::start(ctx, new).await?;
session.clear()?;
session.insert(SESSION_TOKEN, token)?;
if remember { session.remember_for(SESSION_SECONDS)? } else { session.expire_in(SESSION_SECONDS)? }
Ok(return_to.unwrap_or_else(|| "/".to_owned()))
}
pub async fn sign_out(ctx: &Ctx, session: &Session) -> Result<()> {
if let Some(token) = session.get::<String>(SESSION_TOKEN)? {
user_session::end(ctx, &token).await?;
}
session.clear()
}
pub fn session_token(session: &Session) -> Result<Option<String>> {
session.get(SESSION_TOKEN)
}
pub fn origin(uri: &Uri) -> String {
let scheme = uri.scheme_str().unwrap_or("https");
let host = uri.authority().map_or("localhost", |authority| authority.as_str());
format!("{scheme}://{host}")
}