diff --git a/server/src/guards.rs b/server/src/guards.rs index d3875cd..8e2c2fc 100644 --- a/server/src/guards.rs +++ b/server/src/guards.rs @@ -6,7 +6,7 @@ use crate::models::user::UserModel; use crate::{LoginSessionData, SessionData}; pub struct AuthUser { - pub user: LoginSessionData + pub session: LoginSessionData } #[derive(Debug)] @@ -29,7 +29,7 @@ impl<'r> FromRequest<'r> for AuthUser { _ => return Outcome::Forward(Status::Unauthorized), }; if let Some(login) = &sess.login { - Outcome::Success(Self { user: login.clone() }) + Outcome::Success(Self { session: login.clone() }) } else { Outcome::Forward(Status::Unauthorized) } diff --git a/server/src/main.rs b/server/src/main.rs index 48b4b17..f167d0f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,6 +7,7 @@ use rocket::data::ByteUnit; use rocket::fs::{relative, FileServer}; use rocket::futures::AsyncWriteExt; use rocket::http::private::cookie::CookieBuilder; +use rocket::http::uri::Uri; use rocket::response::Redirect; use rocket::serde::Serialize; use rocket_dyn_templates::handlebars::{handlebars_helper, Context, Handlebars, Helper, HelperResult, Output, RenderContext}; @@ -123,7 +124,7 @@ async fn rocket() -> _ { api::library::move_file, api::library::upload_file, api::library::download_file, api::library::list_files, api::library::get_file, api::library::delete_file, ]) .mount("/auth", routes![ - ui::auth::login, ui::auth::login_handler, ui::auth::register, ui::auth::register_handler, + ui::auth::logout, ui::auth::login, ui::auth::login_handler, ui::auth::register, ui::auth::register_handler, ]) .mount("/", routes![ ui::help::about, @@ -141,7 +142,7 @@ async fn rocket() -> _ { #[catch(401)] pub fn not_authorized(req: &Request) -> Redirect { // uri!(ui::auth::login) doesn't work, it redirects to /login instead - Redirect::to(format!("/auth/login?path={}", req.uri())) + Redirect::to(format!("/auth/login?return_to={}", req.uri().path().percent_encode())) } #[catch(404)] diff --git a/server/src/routes/ui/auth.rs b/server/src/routes/ui/auth.rs index 4025775..cdd5dd0 100644 --- a/server/src/routes/ui/auth.rs +++ b/server/src/routes/ui/auth.rs @@ -1,25 +1,37 @@ use std::net::IpAddr; use log::debug; -use rocket::{get, post, uri, FromForm, Route, State}; +use rocket::{get, post, uri, FromForm, Responder, Route, State}; use rocket::form::{Context, Contextual, Error, Form}; use rocket::form::error::Entity; -use rocket::http::Status; +use rocket::fs::relative; +use rocket::http::{Header, Status}; +use rocket::http::uri::{Origin, Reference, Uri}; use rocket::response::Redirect; use rocket_dyn_templates::{context, Template}; use rocket_session_store::Session; use crate::models::user::{validate_user, validate_user_form, UserAuthError, UserModel}; use crate::{LoginSessionData, SessionData, DB}; +use crate::guards::AuthUser; use crate::routes::ui; use crate::routes::ui::user::list_library_files; use crate::util::{gen_csrf_token, set_csrf, validate_csrf_form, JsonErrorResponse, ResponseError}; -#[get("/login")] -pub async fn login(route: &Route, session: Session<'_, SessionData>) -> Template { +#[get("/logout")] +pub async fn logout(session: Session<'_, SessionData>, user: AuthUser) -> Redirect { + session.remove().await.unwrap(); + Redirect::to(uri!("/auth", login(_, Some(true)))) +} + +#[get("/login?&")] +pub async fn login(route: &Route, session: Session<'_, SessionData>, return_to: Option, logged_out: Option) -> Template { + // TODO: redirect if already logged in let csrf_token = set_csrf(&session).await; Template::render("auth/login", context! { route: route.uri.path(), csrf_token: csrf_token, form: &Context::default(), + return_to, + logged_out }) } @@ -36,15 +48,22 @@ struct LoginForm<'r> { } +#[derive(Responder)] +#[response(status = 302)] +struct HackyRedirectBecauseRocketBug { + inner: String, + location: Header<'static>, +} -#[post("/login", data = "
")] +#[post("/login?", data = "")] pub async fn login_handler( pool: &State, route: &Route, ip_addr: IpAddr, session: Session<'_, SessionData>, mut form: Form>>, -) -> Result { + return_to: Option, +) -> Result { validate_csrf_form(&mut form.context, &session).await; let user = validate_user_form(&mut form.context, &pool).await; if form.context.status() == Status::Ok { @@ -56,15 +75,23 @@ pub async fn login_handler( ip_address: ip_addr, }), }).await.unwrap(); - - return Ok(Redirect::to(uri!(ui::user::index()))) + debug!("returning user to {:?}", return_to); + let return_to_path = return_to.unwrap_or("/".to_string()); + // Rocket redirect fails when `Redirect::to("/path/ has spaces")` has spaces, so manually do location... works better + return Ok(HackyRedirectBecauseRocketBug { + inner: "Login successful, redirecting...".to_string(), + location: Header::new("Location", return_to_path), + }) + // let return_to_uri = Uri::parse::(&return_to_path).unwrap_or(Uri::parse::("/").unwrap()); + // return Ok(Redirect::found(return_to_uri)) } } let csrf_token = set_csrf(&session).await; let ctx = context! { csrf_token, - form: &form.context + form: &form.context, + return_to }; Err(Template::render("auth/login", &ctx)) } diff --git a/server/src/routes/ui/user.rs b/server/src/routes/ui/user.rs index eb65883..47f3271 100644 --- a/server/src/routes/ui/user.rs +++ b/server/src/routes/ui/user.rs @@ -20,12 +20,12 @@ use crate::util::{JsonErrorResponse, ResponseError}; #[get("/")] -pub async fn index(route: &Route) -> Template { - Template::render("index", context! { user: true, route: route.uri.path(), test: "value" }) +pub async fn index(user: AuthUser, route: &Route) -> Template { + Template::render("index", context! { session: user.session, route: route.uri.path(), test: "value" }) } #[get("/library/")] -pub async fn redirect_list_library_files(libraries: &State>>, library_id: &str) +pub async fn redirect_list_library_files(user: AuthUser, libraries: &State>>, library_id: &str) -> Result { let libs = libraries.lock().await; @@ -67,7 +67,7 @@ pub async fn list_library_files(user: AuthUser, route: &Route, libraries: &State debug!("parent={:?}", parent); debug!("segments={:?}", segments); Ok(Template::render("libraries", context! { - user: user.user, + session: user.session, route: route.uri.path(), library: library.model(), files: files, @@ -92,7 +92,7 @@ struct FileAttachment { } #[get("/file//")] -pub async fn get_library_file<'a>(libraries: &State>>, library_id: &str, path: PathBuf) +pub async fn get_library_file<'a>(user: AuthUser, libraries: &State>>, library_id: &str, path: PathBuf) -> Result { let libs = libraries.lock().await; diff --git a/server/templates/auth/login.html.hbs b/server/templates/auth/login.html.hbs index 5d22ee9..fe48481 100644 --- a/server/templates/auth/login.html.hbs +++ b/server/templates/auth/login.html.hbs @@ -4,7 +4,7 @@

storage-app

Login

- {{#unless (eq (len form.form_errors) 0) }} + {{#unless (eq (len form.form_errors) 0) }}
Login failed with errors:
    @@ -14,7 +14,12 @@
{{/unless}} - + {{#if logged_out }} +
+ You have been logged out successfully. +
+ {{/if}} +
diff --git a/server/templates/partials/nav.html.hbs b/server/templates/partials/nav.html.hbs index d23d8bf..382478c 100644 --- a/server/templates/partials/nav.html.hbs +++ b/server/templates/partials/nav.html.hbs @@ -14,7 +14,7 @@