working on the global page fairing state
This commit is contained in:
parent
2aeb49954d
commit
d8acbcb7fa
5 changed files with 119 additions and 19 deletions
|
@ -30,6 +30,32 @@ pub fn logout(cookies: &CookieJar<'_>) {
|
|||
#[derive(Debug)]
|
||||
pub struct SessionInternalFailure();
|
||||
|
||||
/// A struct that checks the presence of a session in cookie
|
||||
///
|
||||
/// This guard can be used to know the probable session status,
|
||||
/// e.g. for rendering pages (to determine if a login button may be displayed).
|
||||
///
|
||||
/// In no way this guard should be trusted for an actual authentication check as
|
||||
/// it does not verify the authenticity of the session against the user database.
|
||||
/// For authentication, use the [`User`] guard instead.
|
||||
///
|
||||
/// The Uuid is the currently logged-in account's UUID.
|
||||
#[derive(Debug)]
|
||||
pub struct Session(pub Option<Uuid>);
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'a> FromRequest<'a> for Session {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(req: &'a Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let cookies = req.cookies();
|
||||
|
||||
Outcome::Success(Session(check_login(&cookies)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Guard that checks that the user has an active session cookie and,
|
||||
/// using the UUID from this cookie checks in the DB if the user is enabled
|
||||
#[rocket::async_trait]
|
||||
impl<'a> FromRequest<'a> for User {
|
||||
type Error = SessionInternalFailure;
|
||||
|
@ -47,7 +73,13 @@ impl<'a> FromRequest<'a> for User {
|
|||
error!("User::from_request internal error: {:?}", err);
|
||||
Outcome::Error((Status::InternalServerError, SessionInternalFailure()))
|
||||
}
|
||||
Ok(Some(user)) => Outcome::Success(user),
|
||||
Ok(Some(user)) => {
|
||||
if user.enabled {
|
||||
Outcome::Success(user)
|
||||
} else {
|
||||
Outcome::Forward(Status::Unauthorized)
|
||||
}
|
||||
}
|
||||
Ok(None) => Outcome::Forward(Status::Unauthorized),
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -68,6 +68,14 @@ pub struct CreateDollProfile<'a> {
|
|||
pub chassis_color: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// A user
|
||||
///
|
||||
/// This type implements [`FromRequest`][rocket::request::FromRequest],
|
||||
/// using it as a guard is the safe way to verify that the user is logged in
|
||||
/// and that they can be logged in.
|
||||
///
|
||||
/// If you only need to check the login status without any security impact
|
||||
/// (e.g. for page template rendering reasons), use [`Session`][crate::auth::session::Session] instead.
|
||||
#[derive(Debug)]
|
||||
pub struct User {
|
||||
pub id: Uuid,
|
||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -1,35 +1,25 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use auth::session;
|
||||
use db::migrate::run_migrations;
|
||||
use db::schema::{DollTags, User};
|
||||
use db::schema::DollTags;
|
||||
use rocket::fairing::AdHoc;
|
||||
use rocket::fs::{relative, FileServer};
|
||||
use rocket_db_pools::Database;
|
||||
use rocket_dyn_templates::tera::try_get_value;
|
||||
use rocket_dyn_templates::Template;
|
||||
use routes::form::accounts;
|
||||
use routes::{error_handlers, form, public};
|
||||
use serde_json::{to_value, Value};
|
||||
|
||||
pub mod auth;
|
||||
pub mod db;
|
||||
pub mod ids;
|
||||
pub mod pages;
|
||||
pub mod routes;
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.attach(Template::custom(|engines| {
|
||||
engines
|
||||
.tera
|
||||
.register_filter("pretty_id", |v: &Value, _: &HashMap<String, Value>| {
|
||||
let value = try_get_value!("pretty_id", "value", i32, v);
|
||||
Ok(to_value(ids::id_db_to_public(value)).unwrap())
|
||||
});
|
||||
}))
|
||||
.attach(pages::init_templates())
|
||||
.attach(DollTags::init())
|
||||
.attach(AdHoc::try_on_ignite("SQLx migrations", run_migrations))
|
||||
.register(
|
||||
|
|
46
src/pages.rs
Normal file
46
src/pages.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use rocket::{
|
||||
fairing::Fairing,
|
||||
outcome::try_outcome,
|
||||
request::{FromRequest, Outcome},
|
||||
Request,
|
||||
};
|
||||
use rocket_dyn_templates::{tera::try_get_value, Template};
|
||||
use serde_json::{to_value, Value};
|
||||
|
||||
use crate::{auth::session::Session, ids};
|
||||
|
||||
pub fn init_templates() -> impl Fairing {
|
||||
Template::custom(|engines| {
|
||||
engines
|
||||
.tera
|
||||
.register_filter("pretty_id", |v: &Value, _: &HashMap<String, Value>| {
|
||||
let value = try_get_value!("pretty_id", "value", i32, v);
|
||||
Ok(to_value(ids::id_db_to_public(value)).unwrap())
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/// A bundle of all the state all pages may make use of.
|
||||
///
|
||||
/// Implements [`FromRequest`] to provide easy access to it as a guard.
|
||||
/// This guard never fails.
|
||||
#[derive(Debug)]
|
||||
pub struct CommonTemplateState {
|
||||
/// true if the user is logged in (doesn't check the DB, instead uses [`Session`])
|
||||
pub logged_in: bool,
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for CommonTemplateState {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> Outcome<CommonTemplateState, ()> {
|
||||
let session_state = try_outcome!(request.guard::<Session>().await);
|
||||
|
||||
Outcome::Success(CommonTemplateState {
|
||||
logged_in: session_state.0.is_some(),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -9,7 +9,10 @@ use rocket_dyn_templates::{context, Template};
|
|||
|
||||
use crate::{
|
||||
auth::{pw, session},
|
||||
db::{schema::DollTagsDb, user},
|
||||
db::{
|
||||
schema::{DollTagsDb, User},
|
||||
user,
|
||||
},
|
||||
routes::error_handlers::{PageResponse, PageResult},
|
||||
};
|
||||
|
||||
|
@ -24,8 +27,13 @@ pub struct AuthForm<'a> {
|
|||
// needed cuz the var. name changes how the query string is parsed
|
||||
#[allow(unused_variables)]
|
||||
#[get("/login?<next>")]
|
||||
pub fn show_login(next: Option<&str>) -> Template {
|
||||
Template::render("account/login", context! {})
|
||||
pub fn show_login(next: Option<&str>, maybe_loggedin: Option<User>) -> PageResult {
|
||||
if maybe_loggedin.is_some() {
|
||||
let next = String::from(next.unwrap_or("/"));
|
||||
Ok(Redirect::to(next).into())
|
||||
} else {
|
||||
Ok(Template::render("account/login", context! {}).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/login?<next>", data = "<form>")]
|
||||
|
@ -34,7 +42,13 @@ pub async fn handle_login(
|
|||
next: Option<&str>,
|
||||
form: Form<Contextual<'_, AuthForm<'_>>>,
|
||||
cookies: &CookieJar<'_>,
|
||||
maybe_loggedin: Option<User>,
|
||||
) -> PageResult {
|
||||
if maybe_loggedin.is_some() {
|
||||
let next = String::from(next.unwrap_or("/"));
|
||||
return Ok(Redirect::to(next).into());
|
||||
}
|
||||
|
||||
let miss = || PageResponse::Page(Template::render("account/login", context! {failure: true}));
|
||||
|
||||
let values = match &form.value {
|
||||
|
@ -62,13 +76,18 @@ pub async fn handle_login(
|
|||
}
|
||||
|
||||
#[get("/register")]
|
||||
pub fn show_register() -> Template {
|
||||
Template::render(
|
||||
pub fn show_register(maybe_loggedin: Option<User>) -> PageResult {
|
||||
if maybe_loggedin.is_some() {
|
||||
return Ok(Redirect::to("/").into());
|
||||
}
|
||||
|
||||
Ok(Template::render(
|
||||
"account/register",
|
||||
context! {
|
||||
previous: form::Context::default(),
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[derive(Debug, FromForm)]
|
||||
|
@ -104,7 +123,12 @@ pub async fn handle_register(
|
|||
db: DollTagsDb,
|
||||
form: Form<Contextual<'_, RegisterForm<'_>>>,
|
||||
cookies: &CookieJar<'_>,
|
||||
maybe_loggedin: Option<User>,
|
||||
) -> PageResult {
|
||||
if maybe_loggedin.is_some() {
|
||||
return Ok(Redirect::to("/").into());
|
||||
}
|
||||
|
||||
let values = match form.value {
|
||||
Some(ref v) => v,
|
||||
None => {
|
||||
|
|
Loading…
Add table
Reference in a new issue