diff --git a/Cargo.lock b/Cargo.lock index adcfec4..44187e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2581,7 +2581,7 @@ dependencies = [ [[package]] name = "rustpad" -version = "0.1.0" +version = "0.1.1" dependencies = [ "argon2", "aws-config", diff --git a/Cargo.toml b/Cargo.toml index df4bc5e..9b0d241 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpad" -version = "0.1.0" +version = "0.1.1" edition = "2024" rust-version = "1.94" description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL" diff --git a/src/app.rs b/src/app.rs index 635d132..d871d0c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,8 @@ use axum::{ Router, - extract::{DefaultBodyLimit, Path, State}, + extract::{DefaultBodyLimit, Path, Request, State}, http::{HeaderName, HeaderValue, StatusCode, header}, + middleware::{self, Next}, response::{Html, IntoResponse, Response}, routing::{get, post}, }; @@ -170,24 +171,10 @@ pub fn router( .layer(DefaultBodyLimit::max( upload_max_size_bytes.saturating_add(1024 * 1024), )) - .layer(SetResponseHeaderLayer::if_not_present( - HeaderName::from_static("x-content-type-options"), - HeaderValue::from_static("nosniff"), - )) .layer(SetResponseHeaderLayer::if_not_present( HeaderName::from_static("x-frame-options"), HeaderValue::from_static("DENY"), )) - .layer(SetResponseHeaderLayer::if_not_present( - HeaderName::from_static("referrer-policy"), - HeaderValue::from_static("strict-origin-when-cross-origin"), - )) - .layer(SetResponseHeaderLayer::if_not_present( - HeaderName::from_static("permissions-policy"), - HeaderValue::from_static( - "camera=(), microphone=(), geolocation=(), payment=(), usb=()", - ), - )) .layer(SetResponseHeaderLayer::if_not_present( HeaderName::from_static("cross-origin-opener-policy"), HeaderValue::from_static("same-origin"), @@ -197,9 +184,32 @@ pub fn router( HeaderValue::from_static("same-origin"), )) .layer(TraceLayer::new_for_http()) + .layer(middleware::from_fn(add_non_asset_security_headers)) .with_state(state) } +async fn add_non_asset_security_headers(request: Request, next: Next) -> Response { + let is_asset = request.uri().path().starts_with("/assets/"); + let mut response = next.run(request).await; + + if !is_asset { + let headers = response.headers_mut(); + headers + .entry("x-content-type-options") + .or_insert(HeaderValue::from_static("nosniff")); + headers + .entry("referrer-policy") + .or_insert(HeaderValue::from_static("strict-origin-when-cross-origin")); + headers + .entry("permissions-policy") + .or_insert(HeaderValue::from_static( + "camera=(), microphone=(), geolocation=(), payment=(), usb=()", + )); + } + + response +} + async fn private_workspace_error(State(state): State) -> Response { error_response( StatusCode::FORBIDDEN, diff --git a/static/home.html b/static/home.html index a7ddd4b..b736037 100644 --- a/static/home.html +++ b/static/home.html @@ -133,6 +133,7 @@

+ @@ -142,6 +143,7 @@

+
diff --git a/static/js/home.js b/static/js/home.js index 1cb9732..c715eed 100644 --- a/static/js/home.js +++ b/static/js/home.js @@ -84,7 +84,16 @@ document.querySelector("#workspace-form").addEventListener("submit", async (even handleAccountConfirmationToken(); handleResetToken(); -{ const params=new URLSearchParams(location.search); const token=params.get("account_action_token"); if(token){ api("/api/auth/account-action/confirm",{method:"POST",body:JSON.stringify({token})}).then(r=>alert(r.message)).catch(e=>alert(e.message)).finally(()=>{params.delete("account_action_token");history.replaceState({},"",`${location.pathname}${params.size?`?${params}`:""}${location.hash}`);}); } } +function toast(text) { + const element = document.querySelector("#toast"); + if (!element) return; + element.textContent = text; + element.classList.add("visible"); + clearTimeout(toast.timer); + toast.timer = setTimeout(() => element.classList.remove("visible"), 3000); +} + +{ const params=new URLSearchParams(location.search); const token=params.get("account_action_token"); if(token){ api("/api/auth/account-action/confirm",{method:"POST",body:JSON.stringify({token})}).then(r=>toast(r.message)).catch(e=>toast(e.message)).finally(()=>{params.delete("account_action_token");history.replaceState({},"",`${location.pathname}${params.size?`?${params}`:""}${location.hash}`);}); } } const identityDialog = document.querySelector("#identity-dialog"); const guestAccount = document.querySelector("#footer-account-guest"); @@ -260,6 +269,7 @@ if (identityDialog) { document.querySelector("#footer-profile")?.addEventListener("click", () => { document.querySelector("#profile-nickname").value = currentSession?.nickname || ""; + document.querySelector("#profile-current-email").value = currentSession?.email || ""; document.querySelector("#profile-email").value = ""; document.querySelector("#profile-new-password").value = ""; document.querySelector("#profile-password").value = ""; diff --git a/static/pad.html b/static/pad.html index c447612..083a0ad 100644 --- a/static/pad.html +++ b/static/pad.html @@ -13,7 +13,7 @@
-
RustPad +
RustPad

__PAD_TITLE__

diff --git a/static/public.html b/static/public.html index 9184562..57e817b 100644 --- a/static/public.html +++ b/static/public.html @@ -13,7 +13,7 @@
- RustPad + RustPad
diff --git a/static/workspace.html b/static/workspace.html index a2862cb..e2db503 100644 --- a/static/workspace.html +++ b/static/workspace.html @@ -13,7 +13,7 @@
-
RustPad +
RustPad

__WORKSPACE_TITLE__