This commit is contained in:
Mateusz Gruszczyński
2026-07-27 12:03:18 +02:00
parent 3f27aa10b3
commit 63999f0878
8 changed files with 43 additions and 21 deletions
Generated
+1 -1
View File
@@ -2581,7 +2581,7 @@ dependencies = [
[[package]]
name = "rustpad"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"argon2",
"aws-config",
+1 -1
View File
@@ -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"
+25 -15
View File
@@ -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<SharedState>) -> Response {
error_response(
StatusCode::FORBIDDEN,
+2
View File
@@ -133,6 +133,7 @@
<label data-directory-profile-field>Organization<input id="profile-directory-organization" readonly></label>
<p id="profile-nickname-suggestion" data-directory-profile-field class="profile-suggestion"></p>
<label>Nickname<input id="profile-nickname" maxlength="40" required></label>
<label data-local-profile-field>Current e-mail<input id="profile-current-email" type="email" readonly></label>
<label data-local-profile-field>New e-mail<input id="profile-email" type="email" maxlength="320" placeholder="Leave empty to keep current"></label>
<label data-local-profile-field>New password<input id="profile-new-password" type="password" minlength="8" maxlength="128" placeholder="Leave empty to keep current"></label>
<label data-local-profile-field>Current password<input id="profile-password" type="password" minlength="8" maxlength="128" required></label>
@@ -142,6 +143,7 @@
<p id="profile-message" class="form-message" role="status"></p>
</form>
</dialog>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
</body>
+11 -1
View File
@@ -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 = "";
+1 -1
View File
@@ -13,7 +13,7 @@
<body class="pad-page" data-registration-enabled="__REGISTRATION_ENABLED__">
<header class="app-header">
<div class="app-header__main"><a class="brand" href="/">RustPad</a><span class="header-divider"></span>
<div class="app-header__main"><a class="brand home-brand" href="/">RustPad</a><span class="header-divider"></span>
<div class="document-heading">
<h1 id="pad-title">__PAD_TITLE__</h1>
<p id="pad-url" class="document-url"></p>
+1 -1
View File
@@ -13,7 +13,7 @@
<body class="public-page hide-preview-line-numbers">
<header class="public-header">
<a class="brand" href="/">RustPad</a>
<a class="brand home-brand" href="/">RustPad</a>
<div class="public-header__actions">
<label class="line-toggle"><input id="public-line-numbers-toggle" type="checkbox"> Line numbers</label>
<button id="copy-public-link" class="secondary-button">Copy link</button>
+1 -1
View File
@@ -13,7 +13,7 @@
<body data-registration-enabled="__REGISTRATION_ENABLED__">
<header class="app-header">
<div class="app-header__main"><a class="brand" href="/">RustPad</a><span class="header-divider"></span>
<div class="app-header__main"><a class="brand home-brand" href="/">RustPad</a><span class="header-divider"></span>
<div class="document-heading">
<h1 id="workspace-title">__WORKSPACE_TITLE__</h1>
<p id="workspace-url" class="document-url"></p>