Skip to content

Commit 861539e

Browse files
chore: progress
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
1 parent 0b9739e commit 861539e

23 files changed

Lines changed: 850 additions & 2339 deletions

Cargo.lock

Lines changed: 21 additions & 207 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ ahash="0.8"
4848
# web
4949
axum="0.8"
5050
axum-extra={version="0.12", default-features=false, features=["cookie"]}
51+
http="1.4"
5152
tower={version="0.5", default-features=false, features=["limit"]}
5253
tower-http={version="0.6", default-features=false, features=[
5354
"cors",
@@ -74,7 +75,6 @@ tikv-jemallocator="0.6"
7475

7576
[dev-dependencies]
7677
figment={version="*", features=["test"]}
77-
poem={version="*", features=["test"]}
7878
cookie={version="*", default-features=false}
7979

8080
[features]

src/app/core/reports.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ impl Display for DateRange {
3838
}
3939

4040
#[derive(Debug, Enum, Clone, Copy, PartialEq, Eq, Hash)]
41-
#[oai(rename_all = "snake_case")]
41+
#[serde(rename_all = "snake_case")]
4242
pub enum Metric {
4343
Views,
4444
UniqueVisitors,
@@ -47,7 +47,7 @@ pub enum Metric {
4747
}
4848

4949
#[derive(Debug, Enum, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
50-
#[oai(rename_all = "snake_case")]
50+
#[serde(rename_all = "snake_case")]
5151
pub enum Dimension {
5252
Url,
5353
Fqdn,
@@ -66,7 +66,7 @@ pub enum Dimension {
6666
}
6767

6868
#[derive(Enum, Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
69-
#[oai(rename_all = "snake_case")]
69+
#[serde(rename_all = "snake_case")]
7070
pub enum FilterType {
7171
// Generic filters
7272
IsNull,
@@ -86,7 +86,7 @@ pub type ReportGraph = Vec<f64>;
8686
pub type ReportTable = BTreeMap<String, f64>;
8787

8888
#[derive(Object, Clone, Debug, Default)]
89-
#[oai(rename_all = "camelCase")]
89+
#[serde(rename_all = "camelCase")]
9090
pub struct ReportStats {
9191
pub total_views: u64,
9292
pub unique_visitors: u64,
@@ -95,7 +95,7 @@ pub struct ReportStats {
9595
}
9696

9797
#[derive(Object, Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
98-
#[oai(rename_all = "camelCase")]
98+
#[serde(rename_all = "camelCase")]
9999
pub struct DimensionFilter {
100100
/// The dimension to filter by
101101
dimension: Dimension,

src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::{Context, Result, bail};
22
use figment::Figment;
33
use figment::providers::{Env, Format, Toml};
4-
use poem::http::Uri;
4+
use http::Uri;
55
use serde::{Deserialize, Serialize};
66
use std::num::NonZeroU16;
77
use std::str::FromStr;

src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@ pub mod cli;
33
pub mod config;
44
pub mod utils;
55
pub mod web;
6-
pub mod web_axum;

src/utils/hash.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ use argon2::password_hash::{PasswordHasher, SaltString, rand_core};
44

55
use anyhow::Result;
66
use rand::RngCore;
7+
use rand::rngs::OsRng;
78
use sha3::Digest;
89
use std::net::IpAddr;
910

1011
pub fn hash_password(password: &str) -> Result<String> {
11-
let salt = SaltString::generate(&mut rand_core::OsRng);
12+
let salt = SaltString::generate(&mut OsRng);
1213
let hash = Argon2::default()
1314
.hash_password(password.as_bytes(), &salt)
1415
.map_err(|_| anyhow::anyhow!("Failed to hash password"))?;
@@ -22,7 +23,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result<()> {
2223
}
2324

2425
pub fn generate_salt() -> String {
25-
SaltString::generate(&mut rand_core::OsRng).to_string()
26+
SaltString::generate(&mut OsRng).to_string()
2627
}
2728

2829
pub fn hash_ip(ip: &IpAddr, user_agent: &str, daily_salt: &str, entity_id: &str) -> String {

src/utils/referrer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub enum Referrer {
5050
}
5151

5252
pub fn process_referer(referer: Option<&str>) -> Referrer {
53-
match referer.map(poem::http::Uri::from_str) {
53+
match referer.map(http::Uri::from_str) {
5454
// valid referer are stripped to the FQDN
5555
Some(Ok(referer_uri)) => {
5656
// ignore localhost / IP addresses

src/web/mod.rs

Lines changed: 55 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,25 @@ pub mod routes;
22
pub mod session;
33
pub mod webext;
44

5-
use std::sync::Arc;
6-
7-
use crate::app::Liwan;
8-
use crate::app::models::Event;
9-
use routes::{dashboard_service, event_service};
10-
use std::sync::mpsc::Sender;
11-
use webext::{EmbeddedFilesEndpoint, PoemErrExt, catch_error};
12-
13-
pub use session::SessionUser;
5+
use std::net::SocketAddr;
6+
use std::ops::Deref;
7+
use std::sync::{Arc, mpsc::Sender};
148

159
use anyhow::{Context, Result};
1610
use rust_embed::RustEmbed;
1711

18-
use poem::endpoint::EmbeddedFileEndpoint;
19-
use poem::listener::TcpListener;
20-
use poem::middleware::{AddData, Compression, CookieJarManager, Cors, SetHeader};
21-
use poem::web::CompressionAlgo;
22-
use poem::{EndpointExt, IntoEndpoint, Route, Server};
12+
use aide::{axum::ApiRouter, openapi};
13+
use http::{Method, header};
14+
use tower_http::{
15+
compression::CompressionLayer,
16+
cors::{Any, CorsLayer},
17+
set_header::SetResponseHeaderLayer,
18+
};
19+
20+
use crate::app::{Liwan, models::Event};
21+
22+
pub use session::SessionUser;
23+
use webext::StaticFile;
2324

2425
#[derive(RustEmbed, Clone)]
2526
#[folder = "./web/dist"]
@@ -29,69 +30,46 @@ struct Files;
2930
#[folder = "./tracker"]
3031
struct Script;
3132

32-
#[cfg(debug_assertions)]
33-
fn save_spec() -> Result<()> {
34-
use std::path::Path;
35-
36-
let path = Path::new("./web/src/api/dashboard.ts");
37-
if path.exists() {
38-
let spec = serde_json::to_string(&serde_json::from_str::<serde_json::Value>(&dashboard_service().spec())?)?
39-
.replace(r#""servers":[],"#, "") // fets doesn't work with an empty servers array
40-
.replace("; charset=utf-8", "") // fets doesn't detect the json content type correctly
41-
.replace(r#""format":"int64","#, ""); // fets uses bigint for int64
42-
43-
// check if the spec has changed
44-
let old_spec = std::fs::read_to_string(path)?;
45-
if old_spec == format!("export default {spec} as const;\n") {
46-
return Ok(());
47-
}
48-
49-
tracing::info!("API has changed, updating the openapi spec...");
50-
std::fs::write(path, format!("export default {spec} as const;\n"))?;
51-
}
52-
Ok(())
33+
#[derive(Clone)]
34+
pub struct RouterState {
35+
pub app: Arc<Liwan>,
36+
pub events: Sender<Event>,
5337
}
5438

55-
pub fn create_router(app: Arc<Liwan>, events: Sender<Event>) -> impl IntoEndpoint {
56-
let handle_events = event_service().with(Cors::new().allow_method("POST").allow_credentials(false));
57-
58-
let serve_script = EmbeddedFileEndpoint::<Script>::new("script.min.js")
59-
.with(Cors::new().allow_method("GET").allow_credentials(false))
60-
.with(SetHeader::new().appending("Content-Type", "application/javascript"));
39+
impl Deref for RouterState {
40+
type Target = Arc<Liwan>;
6141

62-
let headers = SetHeader::new()
63-
.appending("X-Frame-Options", "DENY")
64-
.appending("X-Content-Type-Options", "nosniff")
65-
.appending("X-XSS-Protection", "1; mode=block")
66-
.appending(
67-
"Content-Security-Policy",
68-
"default-src 'self' data: 'unsafe-inline'; img-src 'self' data: https://*",
69-
)
70-
.appending("Referrer-Policy", "same-origin")
71-
.appending("Permissions-Policy", "geolocation=(), microphone=(), camera=()");
72-
73-
let api_router = Route::new()
74-
.nest_no_strip("/event", handle_events)
75-
.nest("/dashboard", dashboard_service().with(CookieJarManager::new()))
76-
.catch_all_error(catch_error);
77-
78-
Route::new()
79-
.nest("/api", api_router)
80-
.at("/script.js", serve_script)
81-
.nest("/", EmbeddedFilesEndpoint::<Files>::new())
82-
.with(AddData::new(app))
83-
.with(AddData::new(events))
84-
.with(CookieJarManager::new())
85-
.with(Compression::new().algorithms([CompressionAlgo::BR, CompressionAlgo::GZIP]))
86-
.with(headers)
42+
fn deref(&self) -> &Self::Target {
43+
&self.app
44+
}
8745
}
8846

8947
pub async fn start_webserver(app: Arc<Liwan>, events: Sender<Event>) -> Result<()> {
90-
#[cfg(debug_assertions)]
91-
save_spec()?;
92-
93-
let router = create_router(app.clone(), events.clone());
94-
let listener = TcpListener::bind(("0.0.0.0", app.config.port));
48+
let event_cors = CorsLayer::new().allow_methods([Method::POST]).allow_origin(Any).allow_credentials(false);
49+
let script_cors = CorsLayer::new().allow_methods([Method::GET]).allow_origin(Any).allow_credentials(false);
50+
51+
let set_headers = tower::ServiceBuilder::new()
52+
.layer(SetResponseHeaderLayer::if_not_present(header::X_FRAME_OPTIONS, "DENY"))
53+
.layer(SetResponseHeaderLayer::if_not_present(header::X_CONTENT_TYPE_OPTIONS, "nosniff"))
54+
.layer(SetResponseHeaderLayer::if_not_present(header::X_XSS_PROTECTION, "1; mode=block"))
55+
.layer(SetResponseHeaderLayer::if_not_present(
56+
header::CONTENT_SECURITY_POLICY,
57+
"default-src 'self' data: 'unsafe-inline'; img-src 'self' data: https://*",
58+
))
59+
.layer(SetResponseHeaderLayer::if_not_present(header::REFERRER_POLICY, "same-origin"));
60+
61+
let dashboard = ApiRouter::new()
62+
.merge(routes::admin::router())
63+
.merge(routes::auth::router())
64+
.merge(routes::dashboard::router());
65+
66+
let router = ApiRouter::new()
67+
.nest("/api", routes::event::router().layer(event_cors))
68+
.nest("/api/dashboard", dashboard)
69+
.route_service("/script.js", StaticFile::<Script>::new("script.min.js").layer(script_cors))
70+
.layer(CompressionLayer::new())
71+
.layer(set_headers)
72+
.with_state(RouterState { app: app.clone(), events });
9573

9674
match app.onboarding.token()? {
9775
Some(onboarding) => {
@@ -105,5 +83,10 @@ pub async fn start_webserver(app: Arc<Liwan>, events: Sender<Event>) -> Result<(
10583
}
10684
}
10785

108-
Server::new(listener).run(router).await.context("server exited unexpectedly")
86+
let mut api = openapi::OpenApi::default();
87+
api.info = openapi::Info { description: Some("an example API".to_string()), ..openapi::Info::default() };
88+
89+
let listener = tokio::net::TcpListener::bind(("0.0.0.0", app.config.port)).await.unwrap();
90+
let server = router.finish_api(&mut api).into_make_service_with_connect_info::<SocketAddr>();
91+
axum::serve(listener, server).await.context("server exited unexpectedly")
10992
}

0 commit comments

Comments
 (0)