add logout
This commit is contained in:
parent
1e10162fb3
commit
a1036aa02a
4 changed files with 83 additions and 7 deletions
|
@ -2,7 +2,11 @@ use anyhow::Result;
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
extract::{FromRequestParts, State},
|
extract::{FromRequestParts, State},
|
||||||
http::{header::COOKIE, request::Parts, HeaderValue, Request, StatusCode},
|
http::{
|
||||||
|
header::{COOKIE, SET_COOKIE},
|
||||||
|
request::Parts,
|
||||||
|
HeaderValue, Request, StatusCode,
|
||||||
|
},
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::Response,
|
response::Response,
|
||||||
Extension,
|
Extension,
|
||||||
|
@ -45,6 +49,23 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RequireSession(pub Session);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<S> FromRequestParts<S> for RequireSession
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = AppError<'static>;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
match Extension::<Session>::from_request_parts(parts, state).await {
|
||||||
|
Ok(Extension(session)) => Ok(RequireSession(session)),
|
||||||
|
_ => Err(INVALID_SESSION),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn refresh_sessions<B>(
|
pub async fn refresh_sessions<B>(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
mut req: Request<B>,
|
mut req: Request<B>,
|
||||||
|
@ -69,8 +90,22 @@ pub async fn refresh_sessions<B>(
|
||||||
.ok()
|
.ok()
|
||||||
{
|
{
|
||||||
let extensions = req.extensions_mut();
|
let extensions = req.extensions_mut();
|
||||||
extensions.insert(session);
|
extensions.insert(session.clone());
|
||||||
extensions.insert(user);
|
extensions.insert(user);
|
||||||
|
|
||||||
|
let mut response = next.run(req).await;
|
||||||
|
// Only set the session cookie if it hasn't been set yet (eg. logout)
|
||||||
|
let headers = response.headers_mut();
|
||||||
|
if !headers.contains_key(SET_COOKIE) {
|
||||||
|
headers.insert(
|
||||||
|
SET_COOKIE,
|
||||||
|
session
|
||||||
|
.cookie(state.config.domain().as_str(), state.config.secure())
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,13 @@ impl Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn destroy(self: Self, pool: &Pool<Postgres>) -> Result<()> {
|
||||||
|
sqlx::query!("DELETE FROM sessions WHERE id = $1", self.id)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn prune_dead(pool: &Pool<Postgres>) -> Result<u64> {
|
pub async fn prune_dead(pool: &Pool<Postgres>) -> Result<u64> {
|
||||||
let now = Utc::now().naive_utc();
|
let now = Utc::now().naive_utc();
|
||||||
let result = sqlx::query!("DELETE FROM sessions WHERE expires_at < $1", now)
|
let result = sqlx::query!("DELETE FROM sessions WHERE expires_at < $1", now)
|
||||||
|
@ -158,6 +165,21 @@ impl Session {
|
||||||
.secure(secure)
|
.secure(secure)
|
||||||
.http_only(!secure)
|
.http_only(!secure)
|
||||||
.path("/")
|
.path("/")
|
||||||
|
.expires(
|
||||||
|
cookie::time::OffsetDateTime::from_unix_timestamp(self.expires_at.timestamp())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.finish()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cookie_for_delete(domain: &str, secure: bool) -> String {
|
||||||
|
Cookie::build("session", "")
|
||||||
|
.domain(domain)
|
||||||
|
.secure(secure)
|
||||||
|
.http_only(!secure)
|
||||||
|
.path("/")
|
||||||
|
.max_age(cookie::time::Duration::seconds(0))
|
||||||
.finish()
|
.finish()
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/auth/login", post(routes::auth::login))
|
.route("/auth/login", post(routes::auth::login))
|
||||||
|
.route("/auth/logout", post(routes::auth::logout))
|
||||||
.route("/me", get(routes::auth::me))
|
.route("/me", get(routes::auth::me))
|
||||||
.route("/pages/:site/:slug", get(routes::content::page))
|
.route("/pages/:site/:slug", get(routes::content::page))
|
||||||
.route("/admin/bootstrap", post(routes::admin::bootstrap))
|
.route("/admin/bootstrap", post(routes::admin::bootstrap))
|
||||||
|
|
|
@ -2,7 +2,7 @@ use axum::{
|
||||||
extract::State,
|
extract::State,
|
||||||
http::{header::SET_COOKIE, StatusCode},
|
http::{header::SET_COOKIE, StatusCode},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
Extension, Json,
|
Json,
|
||||||
};
|
};
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -10,7 +10,12 @@ use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{hash::verify, http::RequireUser, session::Session, user::User},
|
auth::{
|
||||||
|
hash::verify,
|
||||||
|
http::{RequireSession, RequireUser},
|
||||||
|
session::Session,
|
||||||
|
user::User,
|
||||||
|
},
|
||||||
error::AppError,
|
error::AppError,
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
@ -56,12 +61,10 @@ pub async fn login(
|
||||||
let mut response: Response =
|
let mut response: Response =
|
||||||
Json(json!({ "session_token": token, "expires_at": session.expires_at })).into_response();
|
Json(json!({ "session_token": token, "expires_at": session.expires_at })).into_response();
|
||||||
|
|
||||||
let secure = state.config.secure();
|
|
||||||
|
|
||||||
response.headers_mut().insert(
|
response.headers_mut().insert(
|
||||||
SET_COOKIE,
|
SET_COOKIE,
|
||||||
session
|
session
|
||||||
.cookie(state.config.domain().as_str(), secure)
|
.cookie(state.config.domain().as_str(), state.config.secure())
|
||||||
.parse()?,
|
.parse()?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -71,3 +74,18 @@ pub async fn login(
|
||||||
pub async fn me(RequireUser(user): RequireUser) -> Result<String, AppError<'static>> {
|
pub async fn me(RequireUser(user): RequireUser) -> Result<String, AppError<'static>> {
|
||||||
Ok(user.name)
|
Ok(user.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn logout(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
RequireSession(session): RequireSession,
|
||||||
|
) -> Result<impl IntoResponse, AppError<'static>> {
|
||||||
|
session.destroy(&state.database).await?;
|
||||||
|
|
||||||
|
let mut response: Response = Json(json!({ "ok": true })).into_response();
|
||||||
|
response.headers_mut().insert(
|
||||||
|
SET_COOKIE,
|
||||||
|
Session::cookie_for_delete(state.config.domain().as_str(), state.config.secure())
|
||||||
|
.parse()?,
|
||||||
|
);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue