login implemented

This commit is contained in:
Sebastian Moser
2026-03-09 20:20:05 +01:00
parent 9ad752c16f
commit d9e0e81e61
17 changed files with 11896 additions and 295 deletions

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ rust_dist
deno_dist
node_modules
.env
mize_config.toml
# I put testing stuff for this projekt that I don't want to push to github into a folder called gitignore
gitignore

1425
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,5 @@ members = [
"packages/marts",
"packages/mize",
"packages/ppc",
"test", "test",
]
resolver = "2"

View File

@@ -12,7 +12,7 @@ mize = { path = "../mize/", default-features = false }
tokio = "1.49.0"
# https://github.com/rscarson/rustyscript/pull/415
rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
deno_core = { version = "0.380.1", optional = true }
#deno_core = { version = "0.380.1", optional = true }
chrono = "0.4.44"
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
serde = "1.0.228"
@@ -21,7 +21,7 @@ rusqlite = { version = "0.31", features = ["bundled"], optional = true }
[features]
default = ["target-os"]
target-os = [ "clap", "mize/target-os", "deno_core", "rustyscript", "rusqlite" ]
target-os = [ "clap", "mize/target-os", "rustyscript", "rusqlite" ]
target-wasm = [ "mize/target-wasm" ]
clap = ["dep:clap"]

View File

@@ -1,6 +1,6 @@
use chrono::{DateTime, Utc};
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizeResult};
use reqwest::{blocking::Client, blocking::RequestBuilder, blocking::Response, header, Method};
use reqwest::{blocking::Client, blocking::Response, header, Method};
use serde_json::{json, Value};
use std::thread::sleep;
use std::time::Duration;

View File

@@ -1,3 +1,4 @@
use mize::MizeResult;
#[cfg(feature = "target-os")]
@@ -15,5 +16,7 @@ pub mod c2vi;
#[cfg(feature = "target-os")]
pub use c2vi::*;
#[cfg(feature = "target-os")]
pub mod js;
#[cfg(feature = "target-os")]
pub use js::*;

View File

@@ -267,6 +267,10 @@ rec {
################# dev Shels ###################
mkMizeRustShell = attrs: mkMizeModuleShell (attrs // {
shellHook = ''
mize_project_root=$(${lib.getExe pkgs.git} rev-parse --show-toplevel)
export MIZE_CONFIG_FILES=$mize_project_root/mize_config.toml
'';
#_shell_type = "rust";
nativeBuildInputs = attrs.nativeBuildInputs or [] ++ [
pkgs.wasm-pack
@@ -274,6 +278,10 @@ rec {
pkgs.spacetimedb
pkgs.deno
pkgs.pkg-config
pkgs.gobject-introspection
pkgs.gobject-introspection.dev
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
(pkgs.buildWasmBindgenCli rec {
src = pkgs.fetchCrate {
pname = "wasm-bindgen-cli";
@@ -307,8 +315,38 @@ rec {
})
];
buildInputs = [
pkgsCross.openssl
buildInputs = with pkgs; [
at-spi2-atk
atkmm
cairo
gdk-pixbuf
glib
gtk3
harfbuzz
librsvg
libsoup_3
pango
webkitgtk_4_1
openssl
wasm-bindgen-cli
lld_20
xdotool
];
LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [
xdotool
webkitgtk_4_1
pango
openssl
at-spi2-atk
atkmm
cairo
gdk-pixbuf
glib
gtk3
harfbuzz
librsvg
libsoup_3
];

View File

@@ -1,9 +1,12 @@
use crate::mize_err;
use crate::MizeResult;
use std::collections::HashMap;
use std::env;
use std::sync::{Arc, RwLock};
use crate::item::ItemData;
use crate::Mize;
use crate::MizeError;
type ConfigThunk = Box<dyn Fn() -> ItemData + Send + Sync>;
@@ -42,5 +45,34 @@ pub fn gather_config(mize: &mut Mize) -> MizeResult<()> {
part.as_deref().unwrap().opts(&mut mize.clone());
}
// populate values from config files
let config_file_paths = env::var("MIZE_CONFIG_FILES")?;
for config_file_path in config_file_paths.split(":") {
println!("reading config file: {config_file_path}");
let content = std::fs::read_to_string(config_file_path)?;
let mut data = ItemData::from_toml(content.as_str())?;
println!("config data: {data}");
for path in data.get_paths_recursive()? {
let conf_name = path.replace("/", ".");
println!("adding config {conf_name} from config file {config_file_path}");
let val = data.get_path(path.split("/").collect::<Vec<&str>>())?;
match config_opts.get_mut(&conf_name) {
Some(opt) => {
opt.val = Some(val);
}
None => {
config_opts.insert(
conf_name.clone(),
ConfigOpt {
name: conf_name,
val: Some(val),
thunk: None,
},
);
}
};
}
}
Ok(())
}

View File

@@ -296,7 +296,7 @@ impl Mize {
let mut instance = Mize::empty()?;
instance.init();
instance.init()?;
debug!(
"instance inited with config: {}",
@@ -868,6 +868,7 @@ impl Mize {
// get the cached value
if let Some(val) = opt.val.clone() {
println!("get_config: {name} has cached value: {val}");
return Ok(val);
}
@@ -886,12 +887,17 @@ impl Mize {
pub fn new_opt(&mut self, name: &str) -> ConfigOptNameAndMize {
let mut config_opts = self.config_opts.lock().unwrap();
match config_opts.get_mut(name) {
Some(opt) => {}
None => {
let opt = ConfigOpt {
name: name.to_owned(),
val: None,
thunk: None,
};
config_opts.insert(name.to_string(), opt);
}
}
ConfigOptNameAndMize {
name: name.to_string(),
mize: self.clone(),

View File

@@ -286,6 +286,46 @@ impl ItemData {
.to_owned()
.into_item_data())
}
pub fn get_paths_recursive(&mut self) -> MizeResult<Vec<String>> {
let mut paths = Vec::new();
let mut path_accu = Vec::new();
get_paths(&mut self.0, &mut paths, &mut path_accu, true)?;
Ok(paths)
}
pub fn get_paths(&mut self) -> MizeResult<Vec<String>> {
let mut paths = Vec::new();
let mut path_accu = Vec::new();
get_paths(&mut self.0, &mut paths, &mut path_accu, false)?;
Ok(paths)
}
}
fn get_paths(
val: &mut CborValue,
paths: &mut Vec<String>,
path_accu: &mut Vec<String>,
recurse: bool,
) -> MizeResult<()> {
let map = val
.as_map_mut()
.ok_or(mize_err!("ItemData is not map... when calling get_paths"))?;
for (key, val) in map {
let text = key
.as_text()
.ok_or(mize_err!("ItemData::get_paths but key is not of type text"))?
.to_owned();
if recurse && val.is_map() {
let mut path_accu = path_accu.clone();
path_accu.push(text);
get_paths(val, paths, &mut path_accu, recurse)?;
} else {
let path = path_accu.join("/") + "/" + text.as_str();
paths.push(path);
}
}
Ok(())
}
impl Default for ItemData {

10223
packages/ppc/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,26 @@ clap = { version = "4.5.59", optional = true }
js-sys = { version = "0.3.85", optional = true }
wasm-bindgen = { version = "0.2.108", optional = true }
# server deps
axum = { version = "0.8.0", optional = true }
axum-extra = { version = "0.10.3", features = ["cookie-private"], optional = true }
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true }
reqwest = { version = "0.11", features = ["rustls-tls"], optional = true }
rand = { version = "0.8", optional = true }
url = { version = "2", optional = true }
time = { version = "0.3", optional = true }
tokio = { version = "1.0", features = ["full"], optional = true }
dioxus = { version = "0.7.1", features = ["router", "fullstack", "web", "desktop", "mobile", "server", "ssr"], optional = true }
dioxus-ssr = { version = "0.5", optional = true }
html-escape = { version = "0.2.13", optional = true }
axum-oidc = { version = "1.0.0-dev-1", optional = true }
tracing = { version = "0.1.44", optional = true }
tower = { version = "0.5", optional = true }
tower-sessions = { version = "0.14.0", optional = true }
tower-http = { version = "0.6.8", features = ["trace"] }
[[bin]]
name = "ppc"
path = "platform/os/main.rs"
@@ -21,8 +41,10 @@ name = "ppc"
[features]
default = ["target-os"]
target-os = [ "clap", "mize/target-os", "marts/target-os" ]
target-os = [ "clap", "mize/target-os", "marts/target-os", "server", "tracing" ]
target-obsidian = [ "js-sys", "wasm-bindgen", "mize/target-wasm", "marts/target-wasm" ]
server = [ "axum", "axum-extra", "openidconnect", "serde", "serde_json", "reqwest", "rand", "url", "time", "tokio", "dioxus", "dioxus-ssr", "html-escape", "axum-oidc", "tower", "tower-sessions" ]
[package.metadata.wasm-pack.profile.release]
wasm-opt = false

View File

@@ -1,4 +1,18 @@
mod platform {
#[cfg(feature = "target-obsidian")]
mod obsidian;
pub mod obsidian;
#[cfg(feature = "target-os")]
pub mod os {
pub mod server;
pub mod ui;
}
}
#[cfg(feature = "target-os")]
mod website;
#[cfg(feature = "target-os")]
pub use platform::os::server::server;
#[cfg(feature = "target-os")]
pub use platform::os::ui;

View File

@@ -5,12 +5,14 @@ use clap::crate_version;
use mize::{Mize, MizeResult};
fn main() {
let mut mize = Mize::new().expect("failed to create mize instance");
let mut mize = Mize::new().expect("failed to create mize");
#[cfg(feature = "target-os")]
os_main(&mut mize);
let result = os_main(&mut mize);
mize.run();
if let Err(err) = result {
println!("EER: {:?}", err)
}
}
#[cfg(feature = "target-os")]
@@ -19,6 +21,7 @@ fn os_main(mize: &mut Mize) -> MizeResult<()> {
//marts::js(mize)?;
marts::habitica(mize)?;
marts::c2vi(mize)?;
ppc::server(mize)?;
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
@@ -34,5 +37,12 @@ fn os_main(mize: &mut Mize) -> MizeResult<()> {
println!("test ppc...");
Ok(())
});
cli.subcommand(Command::new("gui"), |_, _| {
ppc::ui::launch_desktop_app();
Ok(())
});
drop(cli);
mize.run()
}

View File

@@ -0,0 +1,138 @@
use std::str::FromStr;
use axum::{
Router,
error_handling::HandleErrorLayer,
http::Uri,
response::IntoResponse,
routing::{any, get},
};
use axum_oidc::handle_oidc_redirect;
use axum_oidc::{
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer,
OidcRpInitiatedLogout, error::MiddlewareError,
};
use clap::Command;
use dioxus::prelude::*;
use mize::Mize;
use mize::MizeResult;
use openidconnect::Scope;
use openidconnect::{ClientId, ClientSecret, IssuerUrl};
use tower::ServiceBuilder;
use tower_sessions::{
Expiry, MemoryStore, SessionManagerLayer,
cookie::{SameSite, time::Duration},
};
pub fn server(mize: &mut Mize) -> MizeResult<()> {
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
mize.add_name_only_part("ppc.server");
mize.new_opt("auth.issuer");
mize.new_opt("auth.client_id");
mize.new_opt("auth.client_secret");
mize.new_opt("auth.redirect");
mize.new_opt("auth.cookie_key");
let mut mize = mize.clone();
cli.subcommand(Command::new("server"), move |_, _| {
// Create tokio runtime for async server
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async { start_server(&mut mize).await })?;
Ok(())
});
Ok(())
}
pub async fn start_server(mize: &mut Mize) -> MizeResult<()> {
dioxus::logger::initialize_default();
let issuer = mize.get_config("auth.issuer")?.value_string()?;
let client_id = mize.get_config("auth.client_id")?.value_string()?;
let client_secret = mize.get_config("auth.client_secret")?.value_string()?;
let url = mize.get_config("web.url")?.value_string()?;
let redirect_url = format!("{url}/oidc");
let session_store = MemoryStore::default();
let session_layer = SessionManagerLayer::new(session_store)
.with_secure(false)
.with_same_site(SameSite::Lax)
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
let oidc_login_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
dbg!(&e);
e.into_response()
}))
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
let oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
.with_default_http_client()
.with_redirect_url(Uri::from_str(redirect_url.as_str())?)
.with_client_id(ClientId::new(client_id))
.add_scope(Scope::new("profile".into()))
.add_scope(Scope::new("email".into()))
// Optional: add untrusted audiences. If the `aud` claim contains any of these audiences, the token is rejected.
//.add_untrusted_audience(Audience::new("123456789".to_string()))
.with_client_secret(ClientSecret::new(client_secret))
.discover(IssuerUrl::new(issuer.into()).expect("Invalid IssuerUrl"))
.await
.unwrap()
.build();
let oidc_auth_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
dbg!(&e);
e.into_response()
}))
.layer(OidcAuthLayer::new(oidc_client));
let app = Router::new()
.route("/foo", get(authenticated))
.route("/logout", get(logout))
.layer(oidc_login_service)
.route("/bar", get(maybe_authenticated))
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
.layer(oidc_auth_service)
.layer(session_layer)
.layer(tower_http::trace::TraceLayer::new_for_http())
// not authenticated routes
.route("/", get(ppc_main_page));
tracing::info!("Running on http://localhost:3000");
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
Ok(())
}
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
format!("Hello {}", claims.subject().as_str())
}
async fn ppc_main_page() -> impl IntoResponse {
"hi"
}
#[axum::debug_handler]
async fn maybe_authenticated(
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
) -> impl IntoResponse {
if let Ok(claims) = claims {
format!(
"Hello {}! You are already logged in from another Handler.",
claims.subject().as_str()
)
} else {
"Hello anon!".to_string()
}
}
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
logout.with_post_logout_redirect(Uri::from_static("https://localhost:3000"))
}

View File

@@ -0,0 +1,17 @@
// Minimal cross-platform UI component
use dioxus::prelude::*;
pub fn hello_world() -> Element {
rsx! {
div {
style: "text-align: center; padding: 2rem;",
h1 { "Hello from Mize!" }
p { "This UI works on Web, Desktop, and Obsidian plugin" }
p { "Current platform: {std::env::consts::OS}" }
}
}
}
pub fn launch_desktop_app() {
dioxus::launch(hello_world);
}

185
packages/ppc/website/mod.rs Normal file
View File

@@ -0,0 +1,185 @@
// PPC Website UI Module
// This module contains the user interface components for the PPC website
use dioxus::prelude::*;
/// Main PPC website component
pub fn ppc_website() -> Element {
rsx! {
div {
class: "min-h-screen bg-gray-100",
// Header
header {
class: "bg-blue-600 text-white py-4",
div {
class: "container mx-auto px-4",
h1 {
class: "text-2xl font-bold",
"People Corner (PPC)"
}
}
}
// Main content
main {
class: "container mx-auto px-4 py-8",
// Hero section
section {
class: "bg-white rounded-lg shadow-md p-6 mb-8",
h2 {
class: "text-xl font-semibold mb-4 text-blue-600",
"Welcome to People Corner"
}
p {
class: "text-gray-700 mb-4",
"PPC is a social platform for intentional people who want to get more out of life together."
}
p {
class: "text-gray-700",
"We're building a community-driven social media network that brings people together to do cool things, share resources, and support each other."
}
}
// Mission section
section {
class: "bg-white rounded-lg shadow-md p-6 mb-8",
h2 {
class: "text-xl font-semibold mb-4 text-blue-600",
"Our Mission"
}
ul {
class: "list-disc pl-6 space-y-2",
li { "Bring people together to get more out of life" }
li { "Create meaningful connections beyond superficial social media" }
li { "Support intentional living and personal growth" }
li { "Build a platform by people, for people" }
li { "Foster real-world meetups and collaborations" }
li { "Create a social media for diligent, intentional people" }
}
}
// Key concepts section
section {
class: "bg-white rounded-lg shadow-md p-6",
h2 {
class: "text-xl font-semibold mb-4 text-blue-600",
"Key Concepts"
}
ul {
class: "list-disc pl-6 space-y-2",
li {
strong { "Trust Network: " }
"A system for building and maintaining trust between members"
}
li {
strong { "Intentional Communities: " }
"Both online and physical spaces for like-minded people"
}
li {
strong { "Resource Sharing: " }
"Platform for sharing skills, knowledge, and physical resources"
}
li {
strong { "Real Connections: " }
"Focus on meaningful relationships over superficial interactions"
}
li {
strong { "Personal Growth: " }
"Support for individuals to achieve their life goals"
}
}
}
}
// Footer
footer {
class: "bg-blue-600 text-white py-4 mt-8",
div {
class: "container mx-auto px-4 text-center",
p { "© 2024 People Corner. All rights reserved." }
}
}
}
}
}
/// About page component
pub fn about_page() -> Element {
rsx! {
div {
class: "container mx-auto px-4 py-8",
h1 {
class: "text-2xl font-bold mb-4 text-blue-600",
"About PPC"
}
p {
class: "text-gray-700 mb-4",
"People Corner (PPC) is more than just a social platform - it's a movement to bring intentional people together to create meaningful connections and achieve more in life."
}
p {
class: "text-gray-700",
"Our vision is to build a world where people support each other, share resources, and grow together through real-world interactions and digital connections."
}
}
}
}
/// Contact page component
pub fn contact_page() -> Element {
rsx! {
div {
class: "container mx-auto px-4 py-8",
h1 {
class: "text-2xl font-bold mb-4 text-blue-600",
"Contact Us"
}
p {
class: "text-gray-700 mb-4",
"We'd love to hear from you! Whether you have questions, feedback, or want to get involved, please reach out."
}
form {
class: "max-w-md space-y-4",
div {
label {
class: "block text-gray-700 mb-2",
"Name"
}
input {
class: "w-full px-3 py-2 border rounded-lg",
r#type: "text",
placeholder: "Your name"
}
}
div {
label {
class: "block text-gray-700 mb-2",
"Email"
}
input {
class: "w-full px-3 py-2 border rounded-lg",
r#type: "email",
placeholder: "Your email"
}
}
div {
label {
class: "block text-gray-700 mb-2",
"Message"
}
textarea {
class: "w-full px-3 py-2 border rounded-lg h-32",
placeholder: "Your message"
}
}
button {
class: "bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700",
r#type: "submit",
"Send Message"
}
}
}
}
}