Add 'Remember Me' feature to login (extends session to 30 days)
All checks were successful
Build MIPS Binary / build (push) Successful in 4m7s
All checks were successful
Build MIPS Binary / build (push) Successful in 4m7s
This commit is contained in:
@@ -13,6 +13,8 @@ use time::Duration;
|
|||||||
pub struct LoginRequest {
|
pub struct LoginRequest {
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
|
#[serde(default)]
|
||||||
|
remember_me: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, ToSchema)]
|
#[derive(Serialize, ToSchema)]
|
||||||
@@ -61,8 +63,13 @@ pub async fn login_handler(
|
|||||||
rand::thread_rng().sample(Alphanumeric) as char
|
rand::thread_rng().sample(Alphanumeric) as char
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
// Expires in 30 days
|
// Expiration: 30 days if remember_me is true, else 1 day
|
||||||
let expires_in = 60 * 60 * 24 * 30;
|
let expires_in = if payload.remember_me {
|
||||||
|
60 * 60 * 24 * 30
|
||||||
|
} else {
|
||||||
|
60 * 60 * 24
|
||||||
|
};
|
||||||
|
|
||||||
let expires_at = time::OffsetDateTime::now_utc().unix_timestamp() + expires_in;
|
let expires_at = time::OffsetDateTime::now_utc().unix_timestamp() + expires_in;
|
||||||
|
|
||||||
if let Err(e) = state.db.create_session(user_id, &token, expires_at).await {
|
if let Err(e) = state.db.create_session(user_id, &token, expires_at).await {
|
||||||
@@ -70,13 +77,14 @@ pub async fn login_handler(
|
|||||||
return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to create session").into_response();
|
return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to create session").into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cookie = Cookie::build(("auth_token", token))
|
let mut cookie = Cookie::build(("auth_token", token))
|
||||||
.path("/")
|
.path("/")
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.same_site(SameSite::Lax)
|
.same_site(SameSite::Lax)
|
||||||
.max_age(Duration::seconds(expires_in))
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
cookie.set_max_age(Duration::seconds(expires_in));
|
||||||
|
|
||||||
tracing::info!("Session created and cookie set for user: {}", payload.username);
|
tracing::info!("Session created and cookie set for user: {}", payload.username);
|
||||||
(StatusCode::OK, jar.add(cookie), Json(UserResponse { username: payload.username })).into_response()
|
(StatusCode::OK, jar.add(cookie), Json(UserResponse { username: payload.username })).into_response()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ use serde::Serialize;
|
|||||||
struct LoginRequest {
|
struct LoginRequest {
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
|
remember_me: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Login() -> impl IntoView {
|
pub fn Login() -> impl IntoView {
|
||||||
let (username, set_username) = create_signal(String::new());
|
let (username, set_username) = create_signal(String::new());
|
||||||
let (password, set_password) = create_signal(String::new());
|
let (password, set_password) = create_signal(String::new());
|
||||||
|
let (remember_me, set_remember_me) = create_signal(false);
|
||||||
let (error, set_error) = create_signal(Option::<String>::None);
|
let (error, set_error) = create_signal(Option::<String>::None);
|
||||||
let (loading, set_loading) = create_signal(false);
|
let (loading, set_loading) = create_signal(false);
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ pub fn Login() -> impl IntoView {
|
|||||||
let req = LoginRequest {
|
let req = LoginRequest {
|
||||||
username: username.get(),
|
username: username.get(),
|
||||||
password: password.get(),
|
password: password.get(),
|
||||||
|
remember_me: remember_me.get(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = gloo_net::http::Request::post("/api/auth/login")
|
let client = gloo_net::http::Request::post("/api/auth/login")
|
||||||
@@ -89,6 +92,19 @@ pub fn Login() -> impl IntoView {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mt-4">
|
||||||
|
<label class="label cursor-pointer justify-start gap-3">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox checkbox-primary checkbox-sm"
|
||||||
|
prop:checked=remember_me
|
||||||
|
on:change=move |ev| set_remember_me.set(event_target_checked(&ev))
|
||||||
|
disabled=move || loading.get()
|
||||||
|
/>
|
||||||
|
<span class="label-text">"Beni Hatırla"</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Show when=move || error.get().is_some()>
|
<Show when=move || error.get().is_some()>
|
||||||
<div class="alert alert-error mt-4 text-sm py-2">
|
<div class="alert alert-error mt-4 text-sm py-2">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||||
|
|||||||
Reference in New Issue
Block a user