Skip to content

Commit

Permalink
Merge pull request #89 from picture-pro/87-remember-me-button
Browse files Browse the repository at this point in the history
87 remember me button
  • Loading branch information
johnbchron authored Mar 12, 2024
2 parents 6cb3379 + 551a332 commit c8d76df
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 23 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ jobs:
- uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
extra-conf: |
keep-outputs = true
keep-derivations = true
- name: Use Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Install flyctl
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ jobs:
- uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
extra-conf: |
keep-outputs = true
keep-derivations = true
- name: Use Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Build server package
Expand All @@ -22,6 +26,10 @@ jobs:
- uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
extra-conf: |
keep-outputs = true
keep-derivations = true
- name: Use Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Check flake inputs
Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ strum = { version = "0.26", features = ["derive"] }
surrealdb = { version = "1" }
thiserror = "1"
chrono = { version = "0.4", features = [ "serde" ] }
time = { version = "0.3" }
timeago = { version = "0.4", default-features = false, features = [ "chrono" ] }
tokio = { version = "1", features = ["full"] }
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.5", features = ["full"] }
tower-sessions = { version = "0.10" }
tracing = { version = "0.1" }
ulid = { version = "1.1", features = [ "serde" ] }
wasm-bindgen = "=0.2.92"
Expand Down
4 changes: 3 additions & 1 deletion crates/auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ edition = "2021"
[dependencies]
async-trait = "0.1.77"
redact = { version = "0.1.8", features = [ "serde" ] }
tower-sessions = { version = "0.10" }
tower-sessions-surrealdb-store = { version = "0.3" }

axum-login.workspace = true
serde.workspace = true
color-eyre.workspace = true
thiserror.workspace = true
time.workspace = true
tower-sessions.workspace = true
tokio.workspace = true
tracing.workspace = true
surrealdb.workspace = true

Expand Down
16 changes: 13 additions & 3 deletions crates/auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use color_eyre::eyre::{eyre, Context, OptionExt, Result};
use core_types::CoreId;
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Client;
use tower_sessions::ExpiredDeletion;
use tracing::instrument;

/// The credentials type for the authentication layer.
Expand All @@ -22,6 +23,8 @@ pub struct Credentials {
pub email: String,
/// The password of the user.
pub password: String,
/// The remember-me flag.
pub remember: bool,
}

/// The backend type for the authentication layer.
Expand Down Expand Up @@ -52,8 +55,6 @@ impl Backend {
email: String,
password: String,
) -> Result<core_types::User> {
(*self.surreal_client).use_ns("main").use_db("main").await?;

// check whether a user with the given email already exists
let user: Option<core_types::User> = (*self.surreal_client)
.query("SELECT * FROM users WHERE email = $email")
Expand Down Expand Up @@ -149,8 +150,17 @@ pub async fn build_auth_layer() -> Result<
surreal_client.into_inner(),
"user_session".to_string(),
);

tokio::task::spawn(
session_store
.clone()
.continuously_delete_expired(tokio::time::Duration::from_secs(60)),
);

let session_manager_layer =
tower_sessions::SessionManagerLayer::new(session_store);
tower_sessions::SessionManagerLayer::new(session_store).with_expiry(
tower_sessions::Expiry::OnInactivity(time::Duration::days(30)),
);

Ok(
AuthManagerLayerBuilder::new(Backend::new().await?, session_manager_layer)
Expand Down
5 changes: 4 additions & 1 deletion crates/site-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ cfg-if.workspace = true
thiserror.workspace = true
serde.workspace = true
bytes = { workspace = true, optional = true }
time = { workspace = true, optional = true }
chrono = { workspace = true }
timeago = { workspace = true }
tower-sessions = { workspace = true, optional = true }
tracing = { workspace = true, optional = true }

web-sys = { version = "0.3", features = [ "Window", "Location", "EventTarget", "File", "FileList", "FileReaderSync", "Blob" ] }
Expand All @@ -36,6 +38,7 @@ default = []
hydrate = ["bl/hydrate", "leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"bl/ssr", "leptos/ssr", "leptos/tracing", "leptos_meta/ssr", "leptos_router/ssr",
"dep:leptos_axum", "dep:auth", "dep:bytes", "dep:tracing"
"dep:leptos_axum", "dep:auth", "dep:bytes", "dep:tracing", "dep:tower-sessions",
"dep:time",
]

43 changes: 42 additions & 1 deletion crates/site-app/src/components/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ impl<P: NewType> IntoView for ActiveFormElement<P> {
skip_validate_on_empty,
} = self;

let class = match html_form_input_type {
Some("checkbox") => "d-checkbox w-full max-w-xs",
_ => "d-input d-input-bordered w-full max-w-xs",
};

let write_callback = move |ev: Event| {
// attempt to parse the input value to the inner validation type
let Ok(value) = event_target_value(&ev).parse() else {
Expand Down Expand Up @@ -55,7 +60,7 @@ impl<P: NewType> IntoView for ActiveFormElement<P> {
<div class="d-form-control">
<label class="d-label">{ display_name }</label>
<input
class="d-input d-input-bordered w-full max-w-xs"
class=class
placeholder={ display_name } type=html_form_input_type.unwrap_or("text")
on:input=write_callback value=read_callback
/>
Expand All @@ -67,3 +72,39 @@ impl<P: NewType> IntoView for ActiveFormElement<P> {
.into_view()
}
}

/// A form element for a checkbox
///
/// This one is separate because no validation is needed and the value property
/// works differently for checkboxes.
pub struct ActiveFormCheckboxElement {
pub field_read_signal: ReadSignal<bool>,
pub field_write_signal: WriteSignal<bool>,
pub display_name: &'static str,
}

impl IntoView for ActiveFormCheckboxElement {
fn into_view(self) -> View {
let ActiveFormCheckboxElement {
field_read_signal: _,
field_write_signal,
display_name,
} = self;

let write_callback = move |ev: Event| {
field_write_signal(event_target_checked(&ev));
};

view! {
<div class="flex flex-row justify-between items-center">
<label class="d-label">{ display_name }</label>
<input
class="d-checkbox"
type="checkbox"
on:input=write_callback
/>
</div>
}
.into_view()
}
}
1 change: 0 additions & 1 deletion crates/site-app/src/components/photo_upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ pub fn PhotoUpload() -> impl IntoView {
let target = e.target().unwrap().dyn_into::<HtmlInputElement>().unwrap();
set_files(target.files());
}
value=files().map(|f| f.length().to_string())
/>

// upload button
Expand Down
30 changes: 25 additions & 5 deletions crates/site-app/src/pages/auth/login_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use leptos::*;
use validation::{Email, LoginParams, Password};

use crate::{
components::{form::ActiveFormElement, navigation::navigate_to},
components::{
form::{ActiveFormCheckboxElement, ActiveFormElement},
navigation::navigate_to,
},
pages::SmallPageWrapper,
};

Expand All @@ -20,13 +23,15 @@ pub fn LoginPageInner() -> impl IntoView {
// create the signals
let (email, set_email) = create_signal(String::new());
let (password, set_password) = create_signal(String::new());
let (remember, set_remember) = create_signal(false);

// create the params, aborting if validation fails
let params: Memo<LoginParams> = create_memo(move |_| {
with!(|email, password| {
with!(|email, password, remember| {
LoginParams {
email: email.to_string(),
password: password.to_string(),
remember: *remember,
}
})
});
Expand All @@ -50,6 +55,11 @@ pub fn LoginPageInner() -> impl IntoView {
skip_validate: true,
skip_validate_on_empty: false,
};
let remember_element = ActiveFormCheckboxElement {
field_read_signal: remember,
field_write_signal: set_remember,
display_name: "Remember me",
};

// create the login action
let login_action = create_server_action::<Login>();
Expand Down Expand Up @@ -95,8 +105,9 @@ pub fn LoginPageInner() -> impl IntoView {
<div class="d-card-body">
<p class="d-card-title text-2xl">"Login to PicturePro"</p>

{ email_element.into_view() }
{ password_element.into_view() }
{ email_element }
{ password_element }
{ remember_element }

{ result_message }

Expand All @@ -116,7 +127,7 @@ pub fn LoginPageInner() -> impl IntoView {
}

#[cfg_attr(feature = "ssr", tracing::instrument)]
#[server(Login)]
#[server]
pub async fn login(params: LoginParams) -> Result<bool, ServerFnError> {
// construct the nutype wrappers and fail if validation fails
let _ = Email::new(params.email.clone())
Expand All @@ -127,9 +138,12 @@ pub async fn login(params: LoginParams) -> Result<bool, ServerFnError> {
let creds = auth::Credentials {
email: params.email,
password: params.password,
remember: params.remember,
};
let mut auth_session = use_context::<auth::AuthSession>()
.ok_or_else(|| ServerFnError::new("Failed to get auth session"))?;
let session = use_context::<tower_sessions::Session>()
.ok_or_else(|| ServerFnError::new("Failed to get session"))?;

let user = match auth_session.authenticate(creds.clone()).await {
Ok(Some(user)) => user,
Expand All @@ -144,6 +158,12 @@ pub async fn login(params: LoginParams) -> Result<bool, ServerFnError> {
.await
.map_err(|e| ServerFnError::new(format!("Failed to log in: {e}")))?;

if creds.remember {
session.set_expiry(Some(tower_sessions::Expiry::AtDateTime(
time::OffsetDateTime::now_utc() + time::Duration::days(30),
)));
}

tracing::info!("logged in user: {} ({})", user.name, user.id.0);
leptos_axum::redirect("/");

Expand Down
Loading

0 comments on commit c8d76df

Please sign in to comment.