feat: add remember me on login, server-side admin checks on member management
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
@@ -22,4 +22,5 @@ tower.workspace = true
|
||||
tower-http.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
time.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -139,15 +139,19 @@ impl FromRequestParts<AppState> for MaybeSession {
|
||||
}
|
||||
|
||||
/// Build a Set-Cookie header for the session.
|
||||
pub fn session_cookie(session_id: &SessionId) -> CookieJar {
|
||||
let cookie = Cookie::build((SESSION_COOKIE, session_id.to_string()))
|
||||
/// When `remember` is true, the cookie persists for 30 days; otherwise it is a session cookie.
|
||||
pub fn session_cookie(session_id: &SessionId, remember: bool) -> CookieJar {
|
||||
let mut builder = Cookie::build((SESSION_COOKIE, session_id.to_string()))
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.secure(true)
|
||||
.same_site(axum_extra::extract::cookie::SameSite::Lax)
|
||||
.build();
|
||||
.same_site(axum_extra::extract::cookie::SameSite::Lax);
|
||||
|
||||
CookieJar::new().add(cookie)
|
||||
if remember {
|
||||
builder = builder.max_age(time::Duration::days(30));
|
||||
}
|
||||
|
||||
CookieJar::new().add(builder.build())
|
||||
}
|
||||
|
||||
/// Validate that a submitted CSRF token matches the session's token.
|
||||
|
||||
@@ -125,7 +125,7 @@ async fn signup_submit(
|
||||
|
||||
match state.sessions.create(session_data).await {
|
||||
Ok(session_id) => {
|
||||
let cookie = auth::session_cookie(&session_id);
|
||||
let cookie = auth::session_cookie(&session_id, true);
|
||||
Ok((cookie, Redirect::to("/dashboard")).into_response())
|
||||
}
|
||||
Err(_) => render_signup(
|
||||
@@ -208,6 +208,8 @@ async fn login_page(
|
||||
struct LoginForm {
|
||||
identifier: String,
|
||||
password: String,
|
||||
#[serde(default)]
|
||||
remember_me: Option<String>,
|
||||
}
|
||||
|
||||
async fn login_submit(
|
||||
@@ -273,9 +275,10 @@ async fn login_submit(
|
||||
last_seen_at: now,
|
||||
};
|
||||
|
||||
let remember = form.remember_me.is_some();
|
||||
match state.sessions.create(session_data).await {
|
||||
Ok(session_id) => {
|
||||
let cookie = auth::session_cookie(&session_id);
|
||||
let cookie = auth::session_cookie(&session_id, remember);
|
||||
Ok((cookie, Redirect::to("/dashboard")).into_response())
|
||||
}
|
||||
Err(_) => render_login(
|
||||
|
||||
@@ -386,6 +386,58 @@ async fn expired_session_with_failed_refresh_redirects_to_login() {
|
||||
assert_eq!(sessions.session_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_with_remember_me_sets_persistent_cookie() {
|
||||
let response = test_app()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/login")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.body(Body::from(
|
||||
"identifier=testuser&password=CorrectPass123&remember_me=on",
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::SEE_OTHER);
|
||||
let cookie_str = response
|
||||
.headers()
|
||||
.get("set-cookie")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert!(cookie_str.contains("forage_session="));
|
||||
assert!(cookie_str.contains("Max-Age="));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_without_remember_me_sets_session_cookie() {
|
||||
let response = test_app()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/login")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.body(Body::from(
|
||||
"identifier=testuser&password=CorrectPass123",
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::SEE_OTHER);
|
||||
let cookie_str = response
|
||||
.headers()
|
||||
.get("set-cookie")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert!(cookie_str.contains("forage_session="));
|
||||
assert!(!cookie_str.contains("Max-Age="));
|
||||
}
|
||||
|
||||
// ─── Logout ─────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user