V0.1.0 #3
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
+25
-15
@@ -1,7 +1,8 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
extract::{DefaultBodyLimit, Path, State},
|
extract::{DefaultBodyLimit, Path, Request, State},
|
||||||
http::{HeaderName, HeaderValue, StatusCode, header},
|
http::{HeaderName, HeaderValue, StatusCode, header},
|
||||||
|
middleware::{self, Next},
|
||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
};
|
};
|
||||||
@@ -170,24 +171,10 @@ pub fn router(
|
|||||||
.layer(DefaultBodyLimit::max(
|
.layer(DefaultBodyLimit::max(
|
||||||
upload_max_size_bytes.saturating_add(1024 * 1024),
|
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(
|
.layer(SetResponseHeaderLayer::if_not_present(
|
||||||
HeaderName::from_static("x-frame-options"),
|
HeaderName::from_static("x-frame-options"),
|
||||||
HeaderValue::from_static("DENY"),
|
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(
|
.layer(SetResponseHeaderLayer::if_not_present(
|
||||||
HeaderName::from_static("cross-origin-opener-policy"),
|
HeaderName::from_static("cross-origin-opener-policy"),
|
||||||
HeaderValue::from_static("same-origin"),
|
HeaderValue::from_static("same-origin"),
|
||||||
@@ -197,9 +184,32 @@ pub fn router(
|
|||||||
HeaderValue::from_static("same-origin"),
|
HeaderValue::from_static("same-origin"),
|
||||||
))
|
))
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.layer(middleware::from_fn(add_non_asset_security_headers))
|
||||||
.with_state(state)
|
.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 {
|
async fn private_workspace_error(State(state): State<SharedState>) -> Response {
|
||||||
error_response(
|
error_response(
|
||||||
StatusCode::FORBIDDEN,
|
StatusCode::FORBIDDEN,
|
||||||
|
|||||||
@@ -133,6 +133,7 @@
|
|||||||
<label data-directory-profile-field>Organization<input id="profile-directory-organization" readonly></label>
|
<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>
|
<p id="profile-nickname-suggestion" data-directory-profile-field class="profile-suggestion"></p>
|
||||||
<label>Nickname<input id="profile-nickname" maxlength="40" required></label>
|
<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 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>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>
|
<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>
|
<p id="profile-message" class="form-message" role="status"></p>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -84,7 +84,16 @@ document.querySelector("#workspace-form").addEventListener("submit", async (even
|
|||||||
|
|
||||||
handleAccountConfirmationToken();
|
handleAccountConfirmationToken();
|
||||||
handleResetToken();
|
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 identityDialog = document.querySelector("#identity-dialog");
|
||||||
const guestAccount = document.querySelector("#footer-account-guest");
|
const guestAccount = document.querySelector("#footer-account-guest");
|
||||||
@@ -260,6 +269,7 @@ if (identityDialog) {
|
|||||||
|
|
||||||
document.querySelector("#footer-profile")?.addEventListener("click", () => {
|
document.querySelector("#footer-profile")?.addEventListener("click", () => {
|
||||||
document.querySelector("#profile-nickname").value = currentSession?.nickname || "";
|
document.querySelector("#profile-nickname").value = currentSession?.nickname || "";
|
||||||
|
document.querySelector("#profile-current-email").value = currentSession?.email || "";
|
||||||
document.querySelector("#profile-email").value = "";
|
document.querySelector("#profile-email").value = "";
|
||||||
document.querySelector("#profile-new-password").value = "";
|
document.querySelector("#profile-new-password").value = "";
|
||||||
document.querySelector("#profile-password").value = "";
|
document.querySelector("#profile-password").value = "";
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<body class="pad-page" data-registration-enabled="__REGISTRATION_ENABLED__">
|
<body class="pad-page" data-registration-enabled="__REGISTRATION_ENABLED__">
|
||||||
<header class="app-header">
|
<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">
|
<div class="document-heading">
|
||||||
<h1 id="pad-title">__PAD_TITLE__</h1>
|
<h1 id="pad-title">__PAD_TITLE__</h1>
|
||||||
<p id="pad-url" class="document-url"></p>
|
<p id="pad-url" class="document-url"></p>
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<body class="public-page hide-preview-line-numbers">
|
<body class="public-page hide-preview-line-numbers">
|
||||||
<header class="public-header">
|
<header class="public-header">
|
||||||
<a class="brand" href="/">RustPad</a>
|
<a class="brand home-brand" href="/">RustPad</a>
|
||||||
<div class="public-header__actions">
|
<div class="public-header__actions">
|
||||||
<label class="line-toggle"><input id="public-line-numbers-toggle" type="checkbox"> Line numbers</label>
|
<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>
|
<button id="copy-public-link" class="secondary-button">Copy link</button>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<body data-registration-enabled="__REGISTRATION_ENABLED__">
|
<body data-registration-enabled="__REGISTRATION_ENABLED__">
|
||||||
<header class="app-header">
|
<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">
|
<div class="document-heading">
|
||||||
<h1 id="workspace-title">__WORKSPACE_TITLE__</h1>
|
<h1 id="workspace-title">__WORKSPACE_TITLE__</h1>
|
||||||
<p id="workspace-url" class="document-url"></p>
|
<p id="workspace-url" class="document-url"></p>
|
||||||
|
|||||||
Reference in New Issue
Block a user