codify "platform" into resolution logic, server-fn/fullstack overhaul (#4596)
overhaul dioxus fullstack using a new implementation of server_fn This PR is a complete rewrite of dioxus fullstack designed to improve compatibility with axum and to fix a number of compilation errors based on feature flag unification plumbing through macros.
This commit is contained in:
1 parent
8421c3aef0
commit
f2b1c6d97f
424 files changed
+17284
-6624
No files matched your search
@@ -0,0 +1,144 @@
|
||||
use async_trait::async_trait;
|
||||
use axum_session_auth::*;
|
||||
use axum_session_sqlx::SessionSqlitePool;
|
||||
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>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct User {
|
||||
pub id: i32,
|
||||
pub anonymous: bool,
|
||||
pub username: String,
|
||||
pub permissions: HashSet<String>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Clone)]
|
||||
pub(crate) struct SqlPermissionTokens {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
User::get_user(userid, pool)
|
||||
.await
|
||||
.ok_or_else(|| anyhow::anyhow!("Could not load user"))
|
||||
}
|
||||
|
||||
fn is_authenticated(&self) -> bool {
|
||||
!self.anonymous
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
!self.anonymous
|
||||
}
|
||||
|
||||
fn is_anonymous(&self) -> bool {
|
||||
self.anonymous
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HasPermission<SqlitePool> for User {
|
||||
async fn has(&self, perm: &str, _pool: &Option<&SqlitePool>) -> bool {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
#[cfg(feature = "server")]
|
||||
mod auth;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
#[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;
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect_with(SqliteConnectOptions::from_str("sqlite::memory:").unwrap())
|
||||
.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();
|
||||
|
||||
User::create_user_tables(&pool).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),
|
||||
)
|
||||
.layer(SessionLayer::new(session_store)),
|
||||
)
|
||||
});
|
||||
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut user_name = use_action(get_user_name);
|
||||
let mut permissions = use_action(get_permissions);
|
||||
let mut login = use_action(login);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
login.call();
|
||||
},
|
||||
"Login Test User"
|
||||
}
|
||||
}
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
user_name.call();
|
||||
},
|
||||
"Get User Name"
|
||||
}
|
||||
"User name: {user_name.value().unwrap_or_default()}"
|
||||
}
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
permissions.call();
|
||||
},
|
||||
"Get Permissions"
|
||||
}
|
||||
"Permissions: {permissions.value().unwrap_or_default()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/user/name", auth: auth::Session)]
|
||||
pub async fn get_user_name() -> Result<String> {
|
||||
Ok(auth
|
||||
.current_user
|
||||
.context("UNAUTHORIZED")?
|
||||
.username
|
||||
.to_string())
|
||||
}
|
||||
|
||||
#[post("/api/user/login", auth: auth::Session)]
|
||||
pub async fn login() -> Result<()> {
|
||||
auth.login_user(2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[get("/api/user/permissions", auth: auth::Session)]
|
||||
pub async fn get_permissions() -> Result<String> {
|
||||
use crate::auth::User;
|
||||
use axum_session_auth::{Auth, Rights};
|
||||
|
||||
let user = auth.current_user.as_ref().context("UNAUTHORIZED")?;
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
))
|
||||
}
|
||||
Reference in new issue
Block a user