Add logout functionality
This commit is contained in:
parent
f25f42a830
commit
e5179782e3
15
src/store.rs
15
src/store.rs
@ -356,6 +356,21 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Given the login session's ID, destroy the session.
|
||||
pub async fn destroy_login_session(&mut self, login_session_id: i64) -> eyre::Result<()> {
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM login_sessions
|
||||
WHERE login_session_id = $1
|
||||
",
|
||||
login_session_id
|
||||
)
|
||||
.execute(&mut **self.txn)
|
||||
.await
|
||||
.context("failed to destroy login session")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given an access token's hash, looks up the corresponding application session.
|
||||
pub async fn lookup_application_session(
|
||||
&mut self,
|
||||
|
16
src/web.rs
16
src/web.rs
@ -51,9 +51,12 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
pub mod ambient;
|
||||
use self::logout::{get_logout, post_logout};
|
||||
|
||||
pub(crate) mod ambient;
|
||||
pub mod errors;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod oauth_openid;
|
||||
pub mod sessionless_xsrf;
|
||||
|
||||
@ -150,6 +153,17 @@ pub(crate) async fn make_router(
|
||||
HeaderValue::from_static("DENY"),
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
// CORS NOT to be made available on this endpoint!
|
||||
// Block loading this in a frame!
|
||||
"/logout",
|
||||
get(get_logout)
|
||||
.post(post_logout)
|
||||
.layer(SetResponseHeaderLayer::overriding(
|
||||
HeaderName::from_static("x-frame-options"),
|
||||
HeaderValue::from_static("DENY"),
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/oidc/token",
|
||||
post(oidc_token).layer(CorsLayer::permissive().max_age(Duration::from_secs(600))),
|
||||
|
@ -135,6 +135,8 @@ pub const LOGIN_SESSION_TOKEN_HASH_BYTES: usize = 32;
|
||||
/// e.g. perhaps the persona should be a hash of the user's UUID?
|
||||
pub const LOGIN_SESSION_XSRF_SECRET_BYTES: usize = 32;
|
||||
|
||||
pub const LOGIN_SESSION_COOKIE_NAME: &str = "__Host-LoginSession";
|
||||
|
||||
/// Represents a login session, which is effectively just a 'web UI' session for a user.
|
||||
pub struct LoginSession {
|
||||
/// The system name of the user who is logged in to the idCoop web UI
|
||||
@ -231,7 +233,7 @@ where
|
||||
else {
|
||||
return Err((StatusCode::UNAUTHORIZED, "No login session."));
|
||||
};
|
||||
let Some(cookie_val) = cookies.get("__Host-LoginSession").map(str::to_owned) else {
|
||||
let Some(cookie_val) = cookies.get(LOGIN_SESSION_COOKIE_NAME).map(str::to_owned) else {
|
||||
return Err((StatusCode::UNAUTHORIZED, "No login session."));
|
||||
};
|
||||
let Ok(login_session_token) = BASE64_URL_SAFE_NO_PAD.decode(&cookie_val) else {
|
||||
@ -496,7 +498,7 @@ pub async fn post_login(
|
||||
.context("failed to store session in database")?;
|
||||
|
||||
cookies.add(
|
||||
Cookie::build(("__Host-LoginSession", login_session_token_b64.clone()))
|
||||
Cookie::build((LOGIN_SESSION_COOKIE_NAME, login_session_token_b64.clone()))
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.secure(true)
|
||||
|
88
src/web/logout.rs
Normal file
88
src/web/logout.rs
Normal file
@ -0,0 +1,88 @@
|
||||
//! logout: let users log out
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Extension,
|
||||
};
|
||||
use hornbeam::render_template_string;
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
|
||||
use crate::store::IdCoopStore;
|
||||
|
||||
use super::{
|
||||
ambient::Ambient,
|
||||
login::{LoginSession, LOGIN_SESSION_COOKIE_NAME},
|
||||
sessionless_xsrf, DesiredLocale, Rendered, WebResult, TEMPLATING,
|
||||
};
|
||||
|
||||
/// `GET /logout`
|
||||
///
|
||||
/// If logged in, show a button to send a POST request to log out.
|
||||
///
|
||||
/// If not logged in, shows a success message.
|
||||
pub async fn get_logout(ambient: Ambient, DesiredLocale(locale): DesiredLocale) -> Response {
|
||||
if ambient.user.is_some() {
|
||||
// display logout button
|
||||
Rendered(render_template_string!(TEMPLATING, logout_ask, locale, {
|
||||
ambient
|
||||
}))
|
||||
.into_response()
|
||||
} else {
|
||||
// display success message
|
||||
Rendered(render_template_string!(
|
||||
TEMPLATING,
|
||||
logout_success,
|
||||
locale,
|
||||
{ ambient }
|
||||
))
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
/// `POST /logout`
|
||||
///
|
||||
/// If logged in, destroy session and show success message.
|
||||
///
|
||||
/// If not logged in, show success message.
|
||||
///
|
||||
/// We don't care about any specific form data.
|
||||
///
|
||||
/// We don't require an XSRF secret, because SameSite=Lax will protect us
|
||||
/// in modern browsers.
|
||||
/// In other browsers, being logged out is not a huge deal; it doesn't seem to be
|
||||
/// a particularly interesting risk or attack vector, therefore.
|
||||
pub async fn post_logout(
|
||||
current_session: Result<LoginSession, (StatusCode, &'static str)>,
|
||||
mut ambient: Ambient,
|
||||
cookies: Cookies,
|
||||
Extension(store): Extension<Arc<IdCoopStore>>,
|
||||
DesiredLocale(locale): DesiredLocale,
|
||||
) -> WebResult<Response> {
|
||||
if let Ok(session) = current_session {
|
||||
store
|
||||
.txn(|mut txn| {
|
||||
Box::pin(async move { txn.destroy_login_session(session.login_session_id).await })
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Clear out cookies
|
||||
for cookie_name in &[LOGIN_SESSION_COOKIE_NAME, sessionless_xsrf::COOKIE_NAME] {
|
||||
if let Some(cookie) = cookies.get(cookie_name).map(Cookie::into_owned) {
|
||||
cookies.remove(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
ambient.user = None;
|
||||
}
|
||||
|
||||
Ok(Rendered(render_template_string!(
|
||||
TEMPLATING,
|
||||
logout_success,
|
||||
locale,
|
||||
{ ambient }
|
||||
))
|
||||
.into_response())
|
||||
}
|
15
templates/pages/logout_ask.hnb
Normal file
15
templates/pages/logout_ask.hnb
Normal file
@ -0,0 +1,15 @@
|
||||
CentredPage {$ambient}
|
||||
:title
|
||||
@logout_ask_title
|
||||
|
||||
:main
|
||||
h1
|
||||
@logout_ask_title
|
||||
|
||||
form {method = "POST"}
|
||||
article
|
||||
@logout_ask_main
|
||||
|
||||
fieldset.grid
|
||||
button {type = "submit"}
|
||||
@logout_ask_logout
|
10
templates/pages/logout_success.hnb
Normal file
10
templates/pages/logout_success.hnb
Normal file
@ -0,0 +1,10 @@
|
||||
CentredPage {$ambient}
|
||||
:title
|
||||
@logout_success_title
|
||||
|
||||
:main
|
||||
h1
|
||||
@logout_success_title
|
||||
|
||||
article
|
||||
@logout_success_main
|
@ -29,3 +29,11 @@ consent_deny = Deny
|
||||
|
||||
#consent_warn_info = Your username, user ID, display name and avatar will be sent to the service.
|
||||
consent_warn_info = Your username and user ID will be sent to the service.
|
||||
|
||||
|
||||
logout_ask_title = Log out?
|
||||
logout_ask_logout = Confirm log out
|
||||
logout_ask_main = Would you like to log out?
|
||||
|
||||
logout_success_title = Logged out!
|
||||
logout_success_main = Successfully logged out. See you again soon!
|
||||
|
Loading…
x
Reference in New Issue
Block a user