Remove duplicated auth example

This commit is contained in:
Jonathan Kelley committed 2025-10-08 19:47:15 -07:00
1 parent 05f3b8d20f
commit fada28f7b1
8 files changed
+143 -499

No files matched your search

+2 -2
View File
@@ -10,7 +10,7 @@ publish = false
[dependencies]
dioxus-web = { workspace = true, features = ["hydrate"], optional = true }
dioxus = { features = ["fullstack"], workspace = true }
axum = { workspace = true, optional = true }
axum = { workspace = true, optional = true, features = ["macros"]}
tokio = { workspace = true, features = ["full"], optional = true }
tower-http = { workspace = true, features = ["auth"], optional = true }
async-trait = { version = "0.1.88", optional = true }
@@ -44,7 +44,7 @@ features = ["sqlite"]
optional = true
[features]
default = []
default = ["web", "server"]
server = [
"dioxus/server",
"dep:axum",
+34 -97
View File
@@ -1,3 +1,6 @@
//! The code here is pulled from the `axum-session-auth` crate examples, requiring little to no
//! modification to work with dioxus fullstack.
use async_trait::async_trait;
use axum_session_auth::*;
use axum_session_sqlx::SessionSqlitePool;
@@ -5,8 +8,9 @@ use serde::{Deserialize, Serialize};
use sqlx::sqlite::SqlitePool;
use std::collections::HashSet;
pub(crate) type Session =
axum_session_auth::AuthSession<User, i64, SessionSqlitePool, sqlx::SqlitePool>;
pub(crate) type Session = axum_session_auth::AuthSession<User, i64, SessionSqlitePool, SqlitePool>;
pub(crate) type AuthLayer =
axum_session_auth::AuthSessionLayer<User, i64, SessionSqlitePool, SqlitePool>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct User {
@@ -24,11 +28,36 @@ pub(crate) struct SqlPermissionTokens {
#[async_trait]
impl Authentication<User, i64, SqlitePool> for User {
async fn load_user(userid: i64, pool: Option<&SqlitePool>) -> Result<User, anyhow::Error> {
let pool = pool.unwrap();
let db = pool.unwrap();
User::get_user(userid, pool)
#[derive(sqlx::FromRow, Clone)]
struct SqlUser {
id: i32,
anonymous: bool,
username: String,
}
let sqluser = sqlx::query_as::<_, SqlUser>("SELECT * FROM users WHERE id = $1")
.bind(userid)
.fetch_one(db)
.await
.ok_or_else(|| anyhow::anyhow!("Could not load user"))
.unwrap();
//lets just get all the tokens the user can use, we will only use the full permissions if modifying them.
let sql_user_perms = sqlx::query_as::<_, SqlPermissionTokens>(
"SELECT token FROM user_permissions WHERE user_id = $1;",
)
.bind(userid)
.fetch_all(db)
.await
.unwrap();
Ok(User {
id: sqluser.id,
anonymous: sqluser.anonymous,
username: sqluser.username,
permissions: sql_user_perms.into_iter().map(|x| x.token).collect(),
})
}
fn is_authenticated(&self) -> bool {
@@ -50,95 +79,3 @@ impl HasPermission<SqlitePool> for User {
self.permissions.contains(perm)
}
}
impl User {
pub async fn get_user(id: i64, pool: &SqlitePool) -> Option<Self> {
#[derive(sqlx::FromRow, Clone)]
struct SqlUser {
id: i32,
anonymous: bool,
username: String,
}
let sqluser = sqlx::query_as::<_, SqlUser>("SELECT * FROM users WHERE id = $1")
.bind(id)
.fetch_one(pool)
.await
.ok()?;
//lets just get all the tokens the user can use, we will only use the full permissions if modifying them.
let sql_user_perms = sqlx::query_as::<_, SqlPermissionTokens>(
"SELECT token FROM user_permissions WHERE user_id = $1;",
)
.bind(id)
.fetch_all(pool)
.await
.ok()?;
Some(User {
id: sqluser.id,
anonymous: sqluser.anonymous,
username: sqluser.username,
permissions: sql_user_perms.into_iter().map(|x| x.token).collect(),
})
}
pub async fn create_user_tables(pool: &SqlitePool) -> Result<(), sqlx::Error> {
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS users (
"id" INTEGER PRIMARY KEY,
"anonymous" BOOLEAN NOT NULL,
"username" VARCHAR(256) NOT NULL
)
"#,
)
.execute(pool)
.await?;
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS user_permissions (
"user_id" INTEGER NOT NULL,
"token" VARCHAR(256) NOT NULL
)"#,
)
.execute(pool)
.await?;
sqlx::query(
r#"
INSERT INTO users
(id, anonymous, username) SELECT 1, true, 'Guest'
ON CONFLICT(id) DO UPDATE SET
anonymous = EXCLUDED.anonymous,
username = EXCLUDED.username
"#,
)
.execute(pool)
.await?;
sqlx::query(
r#"
INSERT INTO users
(id, anonymous, username) SELECT 2, false, 'Test'
ON CONFLICT(id) DO UPDATE SET
anonymous = EXCLUDED.anonymous,
username = EXCLUDED.username
"#,
)
.execute(pool)
.await?;
sqlx::query(
r#"
INSERT INTO user_permissions
(user_id, token) SELECT 2, 'Category::View'
"#,
)
.execute(pool)
.await?;
Ok(())
}
}
+107 -78
View File
@@ -1,129 +1,158 @@
#[cfg(feature = "server")]
mod auth;
//! This example showcases how to use the `axum-session-auth` crate with Dioxus fullstack.
//! We add the `auth::Session` extractor to our server functions to get access to the current user session.
//!
//! To initialize the axum router, we use `dioxus::serve` to spawn a custom axum server that creates
//! our database, session store, and authentication layer.
//!
//! The `.serve_dioxus_application` method is used to mount our Dioxus app as a fallback service to
//! handle HTML rendering and static assets.
//!
//! We easily share the "permissions" between the server and client by using a `HashSet<String>`
//! which is serialized to/from JSON automatically by the server function system.
use std::collections::HashSet;
use dioxus::prelude::*;
#[cfg(feature = "server")]
mod auth;
fn main() {
// On the client, we simply launch the app as normal, taking over the main thread
#[cfg(not(feature = "server"))]
dioxus::launch(app);
// On the server, we can use `dioxus::serve` to create a server that serves our app.
//
// The `serve` function takes a closure that returns a `Future` which resolves to an `axum::Router`.
//
// We return a `Router` such that dioxus sets up logging, hot-reloading, devtools, and wires up the
// IP and PORT environment variables to our server.
#[cfg(feature = "server")]
dioxus::serve(|| async {
use crate::auth::*;
use axum_session::{SessionConfig, SessionLayer, SessionStore};
use axum_session_auth::AuthConfig;
use axum_session_sqlx::SessionSqlitePool;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use std::str::FromStr;
use sqlx::{sqlite::SqlitePoolOptions, Executor};
let pool = SqlitePoolOptions::new()
.max_connections(5)
.connect_with(SqliteConnectOptions::from_str("sqlite::memory:").unwrap())
// Create an in-memory SQLite database and set up our tables
let db = SqlitePoolOptions::new()
.max_connections(20)
.connect_with("sqlite::memory:".parse()?)
.await?;
// This Defaults as normal Cookies.
// To enable Private cookies for integrity, and authenticity please check the next Example.
let session_config = SessionConfig::default().with_table_name("test_table");
let auth_config = AuthConfig::<i64>::default().with_anonymous_user_id(Some(1));
let session_store =
SessionStore::<SessionSqlitePool>::new(Some(pool.clone().into()), session_config)
.await
.unwrap();
// Create the tables (sessions, users)
db.execute(r#"CREATE TABLE IF NOT EXISTS users ( "id" INTEGER PRIMARY KEY, "anonymous" BOOLEAN NOT NULL, "username" VARCHAR(256) NOT NULL )"#,)
.await?;
db.execute(r#"CREATE TABLE IF NOT EXISTS user_permissions ( "user_id" INTEGER NOT NULL, "token" VARCHAR(256) NOT NULL)"#,)
.await?;
User::create_user_tables(&pool).await?;
// Insert in some test data for two users (one anonymous, one normal)
db.execute(r#"INSERT INTO users (id, anonymous, username) SELECT 1, true, 'Guest' ON CONFLICT(id) DO UPDATE SET anonymous = EXCLUDED.anonymous, username = EXCLUDED.username"#,)
.await?;
db.execute(r#"INSERT INTO users (id, anonymous, username) SELECT 2, false, 'Test' ON CONFLICT(id) DO UPDATE SET anonymous = EXCLUDED.anonymous, username = EXCLUDED.username"#,)
.await?;
// build our application with some routes
Ok(
dioxus::server::router(app)
.layer(
axum_session_auth::AuthSessionLayer::<
User,
i64,
SessionSqlitePool,
sqlx::SqlitePool,
>::new(Some(pool))
.with_config(auth_config),
// Make sure our test user has the ability to view categories
db.execute(r#"INSERT INTO user_permissions (user_id, token) SELECT 2, 'Category::View'"#)
.await?;
// Create an axum router that dioxus will attach the app to
Ok(dioxus::server::router(app)
.layer(
AuthLayer::new(Some(db.clone()))
.with_config(AuthConfig::<i64>::default().with_anonymous_user_id(Some(1))),
)
.layer(SessionLayer::new(
SessionStore::<SessionSqlitePool>::new(
Some(db.into()),
SessionConfig::default().with_table_name("test_table"),
)
.layer(SessionLayer::new(session_store)),
)
.await?,
)))
});
dioxus::launch(app);
}
/// The UI for our app - is just a few buttons to call our server functions and display the results.
fn app() -> Element {
let mut login = use_action(login);
let mut user_name = use_action(get_user_name);
let mut permissions = use_action(get_permissions);
let mut login = use_action(login);
let mut logout = use_action(logout);
let fetch_new = move |_| async move {
user_name.call().await;
permissions.call().await;
};
rsx! {
div {
button {
onclick: move |_| {
login.call();
onclick: move |_| async move {
login.call().await;
},
"Login Test User"
}
}
div {
button {
onclick: move |_| {
user_name.call();
onclick: move |_| async move {
logout.call().await;
},
"Get User Name"
"Logout"
}
"User name: {user_name.value().unwrap_or_default()}"
}
div {
button {
onclick: move |_| {
permissions.call();
},
"Get Permissions"
onclick: fetch_new,
"Fetch User Info"
}
"Permissions: {permissions.value().unwrap_or_default()}"
pre { "Logged in: {login.result():?}" }
pre { "User name: {user_name.result():?}" }
pre { "Permissions: {permissions.result():?}" }
}
}
}
#[get("/api/user/name", auth: auth::Session)]
pub async fn get_user_name() -> Result<String> {
Ok(auth
.current_user
.context("UNAUTHORIZED")?
.username
.to_string())
}
/// We use the `auth::Session` extractor to get access to the current user session.
/// This lets us modify the user session, log in/out, and access the current user.
#[post("/api/user/login", auth: auth::Session)]
pub async fn login() -> Result<()> {
auth.login_user(2);
Ok(())
}
/// Just like `login`, but this time we log out the user.
#[post("/api/user/logout", auth: auth::Session)]
pub async fn logout() -> Result<()> {
auth.logout_user();
Ok(())
}
/// We can access the current user via `auth.current_user`.
/// We can have both anonymous user (id 1) and a logged in user (id 2).
///
/// Logged-in users will have more permissions which we can modify.
#[post("/api/user/name", auth: auth::Session)]
pub async fn get_user_name() -> Result<String> {
Ok(auth.current_user.unwrap().username)
}
/// Get the current user's permissions, guarding the endpoint with the `Auth` validator.
/// If this returns false, we use the `or_unauthorized` extension to return a 401 error.
#[get("/api/user/permissions", auth: auth::Session)]
pub async fn get_permissions() -> Result<String> {
pub async fn get_permissions() -> Result<HashSet<String>> {
use crate::auth::User;
use axum_session_auth::{Auth, Rights};
let user = auth.current_user.as_ref().context("UNAUTHORIZED")?;
let user = auth.current_user.unwrap();
// lets check permissions only and not worry about if they are anon or not
let has_permissions =
Auth::<User, i64, sqlx::SqlitePool>::build([axum::http::Method::POST], false)
.requires(Rights::any([
Rights::permission("Category::View"),
Rights::permission("Admin::View"),
]))
.validate(user, &axum::http::Method::GET, None)
.await;
Auth::<User, i64, sqlx::SqlitePool>::build([axum::http::Method::GET], false)
.requires(Rights::any([
Rights::permission("Category::View"),
Rights::permission("Admin::View"),
]))
.validate(&user, &axum::http::Method::GET, None)
.await
.or_unauthorized("You do not have permission to view categories")?;
if !has_permissions {
dioxus::core::bail!(
"User {}, Does not have permissions needed to view this page please login",
user.username
);
}
Ok(format!(
"User has Permissions needed. {:?}",
user.permissions
))
Ok(user.permissions)
}