mirror of
https://github.com/Jackzmc/storage.git
synced 2025-05-06 21:53:21 +00:00
add logout feature
This commit is contained in:
parent
2c6dfa0797
commit
21e02257c2
6 changed files with 69 additions and 34 deletions
|
@ -6,7 +6,7 @@ use crate::models::user::UserModel;
|
||||||
use crate::{LoginSessionData, SessionData};
|
use crate::{LoginSessionData, SessionData};
|
||||||
|
|
||||||
pub struct AuthUser {
|
pub struct AuthUser {
|
||||||
pub user: LoginSessionData
|
pub session: LoginSessionData
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -29,7 +29,7 @@ impl<'r> FromRequest<'r> for AuthUser {
|
||||||
_ => return Outcome::Forward(Status::Unauthorized),
|
_ => return Outcome::Forward(Status::Unauthorized),
|
||||||
};
|
};
|
||||||
if let Some(login) = &sess.login {
|
if let Some(login) = &sess.login {
|
||||||
Outcome::Success(Self { user: login.clone() })
|
Outcome::Success(Self { session: login.clone() })
|
||||||
} else {
|
} else {
|
||||||
Outcome::Forward(Status::Unauthorized)
|
Outcome::Forward(Status::Unauthorized)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use rocket::data::ByteUnit;
|
||||||
use rocket::fs::{relative, FileServer};
|
use rocket::fs::{relative, FileServer};
|
||||||
use rocket::futures::AsyncWriteExt;
|
use rocket::futures::AsyncWriteExt;
|
||||||
use rocket::http::private::cookie::CookieBuilder;
|
use rocket::http::private::cookie::CookieBuilder;
|
||||||
|
use rocket::http::uri::Uri;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::serde::Serialize;
|
use rocket::serde::Serialize;
|
||||||
use rocket_dyn_templates::handlebars::{handlebars_helper, Context, Handlebars, Helper, HelperResult, Output, RenderContext};
|
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,
|
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![
|
.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![
|
.mount("/", routes![
|
||||||
ui::help::about,
|
ui::help::about,
|
||||||
|
@ -141,7 +142,7 @@ async fn rocket() -> _ {
|
||||||
#[catch(401)]
|
#[catch(401)]
|
||||||
pub fn not_authorized(req: &Request) -> Redirect {
|
pub fn not_authorized(req: &Request) -> Redirect {
|
||||||
// uri!(ui::auth::login) doesn't work, it redirects to /login instead
|
// 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)]
|
#[catch(404)]
|
||||||
|
|
|
@ -1,25 +1,37 @@
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use log::debug;
|
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::{Context, Contextual, Error, Form};
|
||||||
use rocket::form::error::Entity;
|
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::response::Redirect;
|
||||||
use rocket_dyn_templates::{context, Template};
|
use rocket_dyn_templates::{context, Template};
|
||||||
use rocket_session_store::Session;
|
use rocket_session_store::Session;
|
||||||
use crate::models::user::{validate_user, validate_user_form, UserAuthError, UserModel};
|
use crate::models::user::{validate_user, validate_user_form, UserAuthError, UserModel};
|
||||||
use crate::{LoginSessionData, SessionData, DB};
|
use crate::{LoginSessionData, SessionData, DB};
|
||||||
|
use crate::guards::AuthUser;
|
||||||
use crate::routes::ui;
|
use crate::routes::ui;
|
||||||
use crate::routes::ui::user::list_library_files;
|
use crate::routes::ui::user::list_library_files;
|
||||||
use crate::util::{gen_csrf_token, set_csrf, validate_csrf_form, JsonErrorResponse, ResponseError};
|
use crate::util::{gen_csrf_token, set_csrf, validate_csrf_form, JsonErrorResponse, ResponseError};
|
||||||
|
|
||||||
#[get("/login")]
|
#[get("/logout")]
|
||||||
pub async fn login(route: &Route, session: Session<'_, SessionData>) -> Template {
|
pub async fn logout(session: Session<'_, SessionData>, user: AuthUser) -> Redirect {
|
||||||
|
session.remove().await.unwrap();
|
||||||
|
Redirect::to(uri!("/auth", login(_, Some(true))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/login?<return_to>&<logged_out>")]
|
||||||
|
pub async fn login(route: &Route, session: Session<'_, SessionData>, return_to: Option<String>, logged_out: Option<bool>) -> Template {
|
||||||
|
// TODO: redirect if already logged in
|
||||||
let csrf_token = set_csrf(&session).await;
|
let csrf_token = set_csrf(&session).await;
|
||||||
Template::render("auth/login", context! {
|
Template::render("auth/login", context! {
|
||||||
route: route.uri.path(),
|
route: route.uri.path(),
|
||||||
csrf_token: csrf_token,
|
csrf_token: csrf_token,
|
||||||
form: &Context::default(),
|
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 = "<form>")]
|
#[post("/login?<return_to>", data = "<form>")]
|
||||||
pub async fn login_handler(
|
pub async fn login_handler(
|
||||||
pool: &State<DB>,
|
pool: &State<DB>,
|
||||||
route: &Route,
|
route: &Route,
|
||||||
ip_addr: IpAddr,
|
ip_addr: IpAddr,
|
||||||
session: Session<'_, SessionData>,
|
session: Session<'_, SessionData>,
|
||||||
mut form: Form<Contextual<'_, LoginForm<'_>>>,
|
mut form: Form<Contextual<'_, LoginForm<'_>>>,
|
||||||
) -> Result<Redirect, Template> {
|
return_to: Option<String>,
|
||||||
|
) -> Result<HackyRedirectBecauseRocketBug, Template> {
|
||||||
validate_csrf_form(&mut form.context, &session).await;
|
validate_csrf_form(&mut form.context, &session).await;
|
||||||
let user = validate_user_form(&mut form.context, &pool).await;
|
let user = validate_user_form(&mut form.context, &pool).await;
|
||||||
if form.context.status() == Status::Ok {
|
if form.context.status() == Status::Ok {
|
||||||
|
@ -56,15 +75,23 @@ pub async fn login_handler(
|
||||||
ip_address: ip_addr,
|
ip_address: ip_addr,
|
||||||
}),
|
}),
|
||||||
}).await.unwrap();
|
}).await.unwrap();
|
||||||
|
debug!("returning user to {:?}", return_to);
|
||||||
return Ok(Redirect::to(uri!(ui::user::index())))
|
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::<Origin>(&return_to_path).unwrap_or(Uri::parse::<Origin>("/").unwrap());
|
||||||
|
// return Ok(Redirect::found(return_to_uri))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let csrf_token = set_csrf(&session).await;
|
let csrf_token = set_csrf(&session).await;
|
||||||
let ctx = context! {
|
let ctx = context! {
|
||||||
csrf_token,
|
csrf_token,
|
||||||
form: &form.context
|
form: &form.context,
|
||||||
|
return_to
|
||||||
};
|
};
|
||||||
Err(Template::render("auth/login", &ctx))
|
Err(Template::render("auth/login", &ctx))
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,12 @@ use crate::util::{JsonErrorResponse, ResponseError};
|
||||||
|
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn index(route: &Route) -> Template {
|
pub async fn index(user: AuthUser, route: &Route) -> Template {
|
||||||
Template::render("index", context! { user: true, route: route.uri.path(), test: "value" })
|
Template::render("index", context! { session: user.session, route: route.uri.path(), test: "value" })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/library/<library_id>")]
|
#[get("/library/<library_id>")]
|
||||||
pub async fn redirect_list_library_files(libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str)
|
pub async fn redirect_list_library_files(user: AuthUser, libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str)
|
||||||
-> Result<Redirect, ResponseError>
|
-> Result<Redirect, ResponseError>
|
||||||
{
|
{
|
||||||
let libs = libraries.lock().await;
|
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!("parent={:?}", parent);
|
||||||
debug!("segments={:?}", segments);
|
debug!("segments={:?}", segments);
|
||||||
Ok(Template::render("libraries", context! {
|
Ok(Template::render("libraries", context! {
|
||||||
user: user.user,
|
session: user.session,
|
||||||
route: route.uri.path(),
|
route: route.uri.path(),
|
||||||
library: library.model(),
|
library: library.model(),
|
||||||
files: files,
|
files: files,
|
||||||
|
@ -92,7 +92,7 @@ struct FileAttachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/file/<library_id>/<path..>")]
|
#[get("/file/<library_id>/<path..>")]
|
||||||
pub async fn get_library_file<'a>(libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str, path: PathBuf)
|
pub async fn get_library_file<'a>(user: AuthUser, libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str, path: PathBuf)
|
||||||
-> Result<FileAttachment, ResponseError>
|
-> Result<FileAttachment, ResponseError>
|
||||||
{
|
{
|
||||||
let libs = libraries.lock().await;
|
let libs = libraries.lock().await;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<h1 class="title is-1 has-text-centered">storage-app</h1>
|
<h1 class="title is-1 has-text-centered">storage-app</h1>
|
||||||
<div class="box is-radiusless">
|
<div class="box is-radiusless">
|
||||||
<h4 class="title is-4 has-text-centered">Login</h4>
|
<h4 class="title is-4 has-text-centered">Login</h4>
|
||||||
{{#unless (eq (len form.form_errors) 0) }}
|
{{#unless (eq (len form.form_errors) 0) }}
|
||||||
<div class="notification is-danger is-light">
|
<div class="notification is-danger is-light">
|
||||||
<b>Login failed with errors:</b>
|
<b>Login failed with errors:</b>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -14,7 +14,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
<form method="post" action="/auth/login">
|
{{#if logged_out }}
|
||||||
|
<div class="notification is-success is-light">
|
||||||
|
You have been logged out successfully.
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<form method="post" action="/auth/login?return_to={{return_to}}">
|
||||||
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Username / Email</label>
|
<label class="label">Username / Email</label>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
{{#if user}} <!-- TODO: only show w/ route is library/files -->
|
{{#if session.user }} <!-- TODO: only show w/ route is library/files -->
|
||||||
<div class="navbar-item" style="width:300px">
|
<div class="navbar-item" style="width:300px">
|
||||||
<div class="field" style="width:100%" >
|
<div class="field" style="width:100%" >
|
||||||
<p class="control has-icons-left">
|
<p class="control has-icons-left">
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if user}}
|
{{#if session.user }}
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<a class="icon has-text-black">
|
<a class="icon has-text-black">
|
||||||
<i class="far fa-bell"></i>
|
<i class="far fa-bell"></i>
|
||||||
|
@ -37,25 +37,27 @@
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link">
|
<a class="navbar-link">
|
||||||
<img src="/static/img/default_user.png" alt="User Image" />
|
<img src="/static/img/default_user.png" alt="User Image" />
|
||||||
{{ debug user }}
|
{{ session.user.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown mr-4 is-right is-radiusless">
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
<div class="block px-4 py-4">
|
<div class="navbar-item">
|
||||||
<b>Quota</b>
|
{{ session.user.username }}
|
||||||
<progress class="progress" min=0 value=20 max=100 />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="navbar-item">
|
||||||
<a class="navbar-item" href="/help/about">
|
{{ session.user.email }}
|
||||||
About
|
</div>
|
||||||
|
<hr class="navbar-divider">
|
||||||
|
<a class="navbar-item" href="/settings">
|
||||||
|
<i class="fa fa-cog"></i>Settings
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item">
|
<a class="navbar-item" href="/admin">
|
||||||
Contact
|
<i class="fa fa-star"></i> Admin Panel
|
||||||
</a>
|
</a>
|
||||||
<hr class="navbar-divider">
|
<hr class="navbar-divider">
|
||||||
<a class="navbar-item">
|
<a class="navbar-item has-text-danger" href="/auth/logout">
|
||||||
Report an issue
|
<i class="fa fa-square-up-right"></i>Logout
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue