fix(web): refresh navbar on login and logout
parent
957387bed7
commit
fc46c4874a
|
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased](https://git.rustybever.be/Chewing_Bever/otter)
|
||||
|
||||
### Added
|
||||
|
||||
* Web UI
|
||||
* Login/logout button
|
||||
|
||||
## [0.1.0](https://git.rustybever.be/Chewing_Bever/otter/src/tag/0.1.0)
|
||||
|
||||
### Added
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use axum::{
|
||||
Form, RequestExt, Router,
|
||||
extract::{Request, State},
|
||||
http::HeaderMap,
|
||||
http::{HeaderMap, HeaderName, HeaderValue, header},
|
||||
middleware::{self, Next},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::{get, post},
|
||||
|
@ -23,18 +23,27 @@ const SESSION_ID_COOKIE: &str = "sessionid";
|
|||
pub fn router(ctx: Context) -> Router<Context> {
|
||||
Router::new()
|
||||
.route("/", get(get_index))
|
||||
.layer(middleware::from_fn_with_state(
|
||||
ctx.clone(),
|
||||
auth_web_middleware,
|
||||
))
|
||||
// .layer(middleware::from_fn_with_state(
|
||||
// ctx.clone(),
|
||||
// auth_web_middleware,
|
||||
// ))
|
||||
// Login route needs to be handled differently, as the middleware turns it into a redirect
|
||||
// loop
|
||||
.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>> {
|
||||
View::Index.page(&headers).response(&ctx.tera)
|
||||
async fn get_index(
|
||||
State(ctx): State<Context>,
|
||||
headers: HeaderMap,
|
||||
jar: CookieJar,
|
||||
) -> AppResult<TemplateResponse<Page<View>>> {
|
||||
let authenticated = extract_session(ctx.clone(), &jar).await?.is_some();
|
||||
|
||||
Ok(View::Index
|
||||
.page(&headers)
|
||||
.authenticated(authenticated)
|
||||
.response(&ctx.tera))
|
||||
}
|
||||
|
||||
async fn get_login(State(ctx): State<Context>, headers: HeaderMap, jar: CookieJar) -> Response {
|
||||
|
@ -79,15 +88,16 @@ async fn post_login(
|
|||
.unwrap()
|
||||
{
|
||||
Ok(session) => Ok((
|
||||
jar.add(
|
||||
// Redirect forces htmx to reload the full page, refreshing the navbar
|
||||
[("HX-Redirect", "/")],
|
||||
(jar.add(
|
||||
Cookie::build((SESSION_ID_COOKIE, session.id.to_string()))
|
||||
.secure(true)
|
||||
.same_site(cookie::SameSite::Lax)
|
||||
.http_only(true)
|
||||
.path("/")
|
||||
.max_age(Duration::days(365)),
|
||||
),
|
||||
Redirect::to("/"),
|
||||
)),
|
||||
)
|
||||
.into_response()),
|
||||
Err(AuthErr::UnknownUser | AuthErr::InvalidPassword) => {
|
||||
|
@ -98,15 +108,13 @@ 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)> {
|
||||
async fn post_logout(State(ctx): State<Context>, jar: CookieJar) -> AppResult<impl IntoResponse> {
|
||||
if let Some(session) = extract_session(ctx.clone(), &jar).await? {
|
||||
ctx.store.remove_session(session.id)?;
|
||||
}
|
||||
|
||||
Ok((jar.remove(SESSION_ID_COOKIE), Redirect::to("/")))
|
||||
// Redirect forces htmx to reload the full page, refreshing the navbar
|
||||
Ok(([("HX-Redirect", "/")], jar.remove(SESSION_ID_COOKIE)))
|
||||
}
|
||||
|
||||
async fn extract_session(ctx: Context, jar: &CookieJar) -> AppResult<Option<Session>> {
|
||||
|
|
|
@ -10,6 +10,7 @@ const HX_HISTORY_RESTORE_HEADER: &str = "HX-History-Restore-Request";
|
|||
pub struct Page<T> {
|
||||
template: T,
|
||||
wrap_with_base: bool,
|
||||
authenticated: bool,
|
||||
}
|
||||
|
||||
impl<T: Template> Template for Page<T> {
|
||||
|
@ -23,6 +24,7 @@ impl<T: Template> Template for Page<T> {
|
|||
if self.wrap_with_base {
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("inner", &inner);
|
||||
ctx.insert("authenticated", &self.authenticated);
|
||||
|
||||
tera.render(super::BASE_TEMPLATE, &ctx)
|
||||
} else {
|
||||
|
@ -36,6 +38,7 @@ impl<T> Page<T> {
|
|||
Self {
|
||||
template,
|
||||
wrap_with_base: false,
|
||||
authenticated: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,4 +53,10 @@ impl<T> Page<T> {
|
|||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn authenticated(mut self, authenticated: bool) -> Self {
|
||||
self.authenticated = authenticated;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,17 @@ a:hover {
|
|||
<main>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<a hx-get="/" hx-target="#inner" hx-push-url="true"><strong>Otter</strong></a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
{% if authenticated %}
|
||||
<a hx-post="/logout" hx-target="#inner">Logout</a>
|
||||
{% else %}
|
||||
<a hx-get="/login" hx-target="#inner" hx-push-url="true">Login</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
<h1>Otter</h1>
|
||||
|
||||
Otter is a self-hostable Gpodder implementation.
|
||||
|
||||
If you're seeing this, you're logged in.
|
||||
|
|
Loading…
Reference in New Issue