projects: port session_auth_axum (#2970)

This commit is contained in:
Georg Vienna
2024-10-01 18:40:57 +02:00
committed by GitHub
parent 013ec4a09d
commit 2a4063a259
8 changed files with 71 additions and 109 deletions

View File

@@ -30,23 +30,21 @@ sqlx = { version = "0.8.0", features = [
], optional = true }
thiserror = "1.0"
wasm-bindgen = "0.2.0"
axum_session_auth = { version = "0.14.0", features = [
"sqlite-rustls",
], optional = true }
axum_session = { version = "0.14.0", features = [
"sqlite-rustls",
], optional = true }
axum_session_auth = { version = "0.14.0", features = [], optional = true }
axum_session = { version = "0.14.0", features = [], optional = true }
axum_session_sqlx = { version = "0.3.0", features = [ "sqlite", "tls-rustls"], optional = true }
bcrypt = { version = "0.15.0", optional = true }
async-trait = { version = "0.1.0", optional = true }
[features]
default = ["ssr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:axum_session_sqlx",
"dep:axum_session_auth",
"dep:axum_session",
"dep:async-trait",

View File

@@ -28,9 +28,8 @@ impl Default for User {
#[cfg(feature = "ssr")]
pub mod ssr {
pub use super::{User, UserPasshash};
pub use axum_session_auth::{
Authentication, HasPermission, SessionSqlitePool,
};
pub use axum_session_auth::{Authentication, HasPermission};
use axum_session_sqlx::SessionSqlitePool;
pub use sqlx::SqlitePool;
pub use std::collections::HashSet;
pub type AuthSession = axum_session_auth::AuthSession<

View File

@@ -8,10 +8,10 @@ use leptos_axum::ResponseOptions;
#[component]
pub fn ErrorTemplate(
#[prop(optional)] outside_errors: Option<Errors>,
#[prop(optional)] errors: Option<RwSignal<Errors>>,
#[prop(optional)] errors: Option<ArcRwSignal<Errors>>,
) -> impl IntoView {
let errors = match outside_errors {
Some(e) => RwSignal::new(e),
Some(e) => ArcRwSignal::new(e),
None => match errors {
Some(e) => e,
None => panic!("No Errors found and we expected errors!"),

View File

@@ -1,50 +0,0 @@
use crate::{error_template::ErrorTemplate, errors::TodoAppError};
use axum::{
body::Body,
extract::State,
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::{view, Errors, LeptosOptions};
use tower::ServiceExt;
use tower_http::services::ServeDir;
pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
req: Request<Body>,
) -> AxumResponse {
let root = options.site_root.clone();
let res = get_static_file(uri.clone(), &root).await.unwrap();
if res.status() == StatusCode::OK {
res.into_response()
} else {
let mut errors = Errors::default();
errors.insert_with_default_key(TodoAppError::NotFound);
let handler = leptos_axum::render_app_to_stream(
options.to_owned(),
move || view! {<ErrorTemplate outside_errors=errors.clone()/>},
);
handler(req).await.into_response()
}
}
async fn get_static_file(
uri: Uri,
root: &str,
) -> Result<Response<Body>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
.unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// This path is relative to the cargo root
match ServeDir::new(root).oneshot(req).await {
Ok(res) => Ok(res.into_response()),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {err}"),
)),
}
}

View File

@@ -2,8 +2,6 @@ pub mod auth;
pub mod error_template;
pub mod errors;
#[cfg(feature = "ssr")]
pub mod fallback;
#[cfg(feature = "ssr")]
pub mod state;
pub mod todo;
@@ -14,5 +12,5 @@ pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::mount_to_body(TodoApp);
leptos::mount::hydrate_body(TodoApp);
}

View File

@@ -7,14 +7,16 @@ use axum::{
Router,
};
use axum_session::{SessionConfig, SessionLayer, SessionStore};
use axum_session_auth::{AuthConfig, AuthSessionLayer, SessionSqlitePool};
use leptos::{get_configuration, logging::log, provide_context};
use axum_session_auth::{AuthConfig, AuthSessionLayer};
use axum_session_sqlx::SessionSqlitePool;
use leptos::{
config::get_configuration, logging::log, prelude::provide_context,
};
use leptos_axum::{
generate_route_list, handle_server_fns_with_context, LeptosRoutes,
};
use session_auth_axum::{
auth::{ssr::AuthSession, User},
fallback::file_and_error_handler,
state::AppState,
todo::*,
};
@@ -40,19 +42,19 @@ async fn server_fn_handler(
async fn leptos_routes_handler(
auth_session: AuthSession,
State(app_state): State<AppState>,
state: State<AppState>,
req: Request<AxumBody>,
) -> Response {
let State(app_state) = state.clone();
let handler = leptos_axum::render_route_with_context(
app_state.leptos_options.clone(),
app_state.routes.clone(),
move || {
provide_context(auth_session.clone());
provide_context(app_state.pool.clone());
},
TodoApp,
move || shell(app_state.leptos_options.clone()),
);
handler(req).await.into_response()
handler(state, req).await.into_response()
}
#[tokio::main]
@@ -111,7 +113,7 @@ async fn main() {
get(server_fn_handler).post(server_fn_handler),
)
.leptos_routes_with_handler(routes, get(leptos_routes_handler))
.fallback(file_and_error_handler)
.fallback(leptos_axum::file_and_error_handler::<AppState, _>(shell))
.layer(
AuthSessionLayer::<User, i64, SessionSqlitePool, SqlitePool>::new(
Some(pool.clone()),

View File

@@ -1,6 +1,6 @@
use axum::extract::FromRef;
use leptos::LeptosOptions;
use leptos_router::RouteListing;
use leptos::prelude::LeptosOptions;
use leptos_axum::AxumRouteListing;
use sqlx::SqlitePool;
/// This takes advantage of Axum's SubStates feature by deriving FromRef. This is the only way to have more than one
@@ -9,5 +9,5 @@ use sqlx::SqlitePool;
pub struct AppState {
pub leptos_options: LeptosOptions,
pub pool: SqlitePool,
pub routes: Vec<RouteListing>,
pub routes: Vec<AxumRouteListing>,
}

View File

@@ -1,7 +1,7 @@
use crate::{auth::*, error_template::ErrorTemplate};
use leptos::prelude::*;
use leptos_meta::*;
use leptos_router::*;
use leptos_router::{components::*, *};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -109,13 +109,33 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
.map(|_| ())?)
}
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<link rel="stylesheet" id="leptos" href="/pkg/session_auth_axum.css"/>
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
<MetaTags/>
</head>
<body>
<TodoApp/>
</body>
</html>
}
}
#[component]
pub fn TodoApp() -> impl IntoView {
let login = create_server_action::<Login>();
let logout = create_server_action::<Logout>();
let signup = create_server_action::<Signup>();
let login = ServerAction::<Login>::new();
let logout = ServerAction::<Logout>::new();
let signup = ServerAction::<Signup>::new();
let user = create_resource(
let user = Resource::new(
move || {
(
login.version().get(),
@@ -128,8 +148,6 @@ pub fn TodoApp() -> impl IntoView {
provide_meta_context();
view! {
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
<Stylesheet id="leptos" href="/pkg/session_auth_axum.css"/>
<Router>
<header>
<A href="/">
@@ -149,7 +167,7 @@ pub fn TodoApp() -> impl IntoView {
", "
<span>{format!("Login error: {}", e)}</span>
}
.into_view()
.into_any()
}
Ok(None) => {
view! {
@@ -159,7 +177,7 @@ pub fn TodoApp() -> impl IntoView {
", "
<span>"Logged out."</span>
}
.into_view()
.into_any()
}
Ok(Some(user)) => {
view! {
@@ -169,7 +187,7 @@ pub fn TodoApp() -> impl IntoView {
{format!("Logged in as: {} ({})", user.username, user.id)}
</span>
}
.into_view()
.into_any()
}
})
}}
@@ -178,13 +196,15 @@ pub fn TodoApp() -> impl IntoView {
</header>
<hr/>
<main>
<Routes>
<FlatRoutes fallback=|| "Not found.">
// Route
<Route path="" view=Todos/>
<Route path="signup" view=move || view! { <Signup action=signup/> }/>
<Route path="login" view=move || view! { <Login action=login/> }/>
<Route
path="settings"
<Route path=path!("") view=Todos/>
<Route path=path!("signup") view=move || view! { <Signup action=signup/> }/>
<Route path=path!("login") view=move || view! { <Login action=login/> }/>
<ProtectedRoute
path=path!("settings")
condition=move || user.get().map(|r| r.ok().flatten().is_some())
redirect_path=|| "/"
view=move || {
view! {
<h1>"Settings"</h1>
@@ -193,7 +213,7 @@ pub fn TodoApp() -> impl IntoView {
}
/>
</Routes>
</FlatRoutes>
</main>
</Router>
}
@@ -201,12 +221,12 @@ pub fn TodoApp() -> impl IntoView {
#[component]
pub fn Todos() -> impl IntoView {
let add_todo = create_server_multi_action::<AddTodo>();
let delete_todo = create_server_action::<DeleteTodo>();
let add_todo = ServerMultiAction::<AddTodo>::new();
let delete_todo = ServerAction::<DeleteTodo>::new();
let submissions = add_todo.submissions();
// list of todos is loaded from the server in reaction to changes
let todos = create_resource(
let todos = Resource::new(
move || (add_todo.version().get(), delete_todo.version().get()),
move |_| get_todos(),
);
@@ -231,11 +251,11 @@ pub fn Todos() -> impl IntoView {
view! {
<pre class="error">"Server Error: " {e.to_string()}</pre>
}
.into_view()
.into_any()
}
Ok(todos) => {
if todos.is_empty() {
view! { <p>"No tasks were found."</p> }.into_view()
view! { <p>"No tasks were found."</p> }.into_any()
} else {
todos
.into_iter()
@@ -252,10 +272,11 @@ pub fn Todos() -> impl IntoView {
}
})
.collect_view()
.into_any()
}
}
})
.unwrap_or_default()
.unwrap_or(().into_any())
}
};
let pending_todos = move || {
@@ -266,7 +287,7 @@ pub fn Todos() -> impl IntoView {
.map(|submission| {
view! {
<li class="pending">
{move || submission.input.get().map(|data| data.title)}
{move || submission.input().get().map(|data| data.title)}
</li>
}
})
@@ -282,9 +303,7 @@ pub fn Todos() -> impl IntoView {
}
#[component]
pub fn Login(
action: Action<Login, Result<(), ServerFnError>>,
) -> impl IntoView {
pub fn Login(action: ServerAction<Login>) -> impl IntoView {
view! {
<ActionForm action=action>
<h1>"Log In"</h1>
@@ -317,9 +336,7 @@ pub fn Login(
}
#[component]
pub fn Signup(
action: Action<Signup, Result<(), ServerFnError>>,
) -> impl IntoView {
pub fn Signup(action: ServerAction<Signup>) -> impl IntoView {
view! {
<ActionForm action=action>
<h1>"Sign Up"</h1>
@@ -362,9 +379,7 @@ pub fn Signup(
}
#[component]
pub fn Logout(
action: Action<Logout, Result<(), ServerFnError>>,
) -> impl IntoView {
pub fn Logout(action: ServerAction<Logout>) -> impl IntoView {
view! {
<div id="loginbox">
<ActionForm action=action>