feat(web): add logout button

main
Jef Roosens 2025-06-07 10:20:49 +02:00
parent b04955d70e
commit 957387bed7
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
3 changed files with 26 additions and 6 deletions

View File

@ -1,21 +1,21 @@
use axum::{ use axum::{
Form, RequestExt, Router,
extract::{Request, State}, extract::{Request, State},
http::HeaderMap, http::HeaderMap,
middleware::{self, Next}, middleware::{self, Next},
response::{IntoResponse, Redirect, Response}, response::{IntoResponse, Redirect, Response},
routing::get, routing::{get, post},
Form, RequestExt, Router,
}; };
use axum_extra::{extract::CookieJar, headers::UserAgent, TypedHeader}; use axum_extra::{TypedHeader, extract::CookieJar, headers::UserAgent};
use cookie::{time::Duration, Cookie}; use cookie::{Cookie, time::Duration};
use gpodder::{AuthErr, Session}; use gpodder::{AuthErr, Session};
use serde::Deserialize; use serde::Deserialize;
use crate::web::{Page, TemplateExt, TemplateResponse, View}; use crate::web::{Page, TemplateExt, TemplateResponse, View};
use super::{ use super::{
error::{AppError, AppResult},
Context, Context,
error::{AppError, AppResult},
}; };
const SESSION_ID_COOKIE: &str = "sessionid"; const SESSION_ID_COOKIE: &str = "sessionid";
@ -30,6 +30,7 @@ pub fn router(ctx: Context) -> Router<Context> {
// Login route needs to be handled differently, as the middleware turns it into a redirect // Login route needs to be handled differently, as the middleware turns it into a redirect
// loop // loop
.route("/login", get(get_login).post(post_login)) .route("/login", get(get_login).post(post_login))
.route("/logout", post(post_logout))
} }
async fn get_index(State(ctx): State<Context>, headers: HeaderMap) -> TemplateResponse<Page<View>> { async fn get_index(State(ctx): State<Context>, headers: HeaderMap) -> TemplateResponse<Page<View>> {
@ -96,6 +97,18 @@ async fn post_login(
} }
} }
/// Log out the user by simply removing the session
async fn post_logout(
State(ctx): State<Context>,
jar: CookieJar,
) -> AppResult<(CookieJar, Redirect)> {
if let Some(session) = extract_session(ctx.clone(), &jar).await? {
ctx.store.remove_session(session.id)?;
}
Ok((jar.remove(SESSION_ID_COOKIE), Redirect::to("/")))
}
async fn extract_session(ctx: Context, jar: &CookieJar) -> AppResult<Option<Session>> { async fn extract_session(ctx: Context, jar: &CookieJar) -> AppResult<Option<Session>> {
if let Some(session_id) = jar if let Some(session_id) = jar
.get(SESSION_ID_COOKIE) .get(SESSION_ID_COOKIE)

View File

@ -15,6 +15,13 @@ a:hover {
<body> <body>
<main> <main>
<nav> <nav>
<ul>
</ul>
<ul>
<li>
<a hx-post="/logout" hx-target="#inner">Logout</a>
</li>
</ul>
</nav> </nav>
<article id="inner"> <article id="inner">
{{ inner | safe }} {{ inner | safe }}

View File

@ -1,5 +1,5 @@
<article> <article>
<form hx-post="/login" hx-target="#inner"> <form hx-post="/login" hx-target="#inner" hx-push-url="/">
<label for="username">Username:</label> <label for="username">Username:</label>
<input type="text" id="username" name="username"> <input type="text" id="username" name="username">
<label for="password">Password:</label> <label for="password">Password:</label>