fix security 1

This commit is contained in:
Mateusz Gruszczyński
2026-07-26 16:21:58 +02:00
parent a075d06b6d
commit 364af50a7c
9 changed files with 57 additions and 18 deletions
+5 -4
View File
@@ -5,6 +5,7 @@ import { bindIdentityDialog, handleAccountConfirmationToken, handleResetToken, l
import { getAuthToken, setAccessToken } from "@rustpad/session";
import { api } from "@rustpad/api";
import { copyText } from "@rustpad/clipboard";
import { safeAppUrl } from "@rustpad/security";
function slugify(value, fallback) {
return value.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || fallback;
@@ -52,7 +53,7 @@ document.querySelector("#pad-form").addEventListener("submit", async (event) =>
if (password.value) payload.password = password.value;
const result = await api("/api/pads", { method: "POST", headers: authHeaders(), body: JSON.stringify(payload) });
if (password.value) { const grant = await api("/api/access-token", { method: "POST", body: JSON.stringify({ kind: "pad", slug: result.slug, password: password.value }) }); setAccessToken("pad", result.slug, grant.access_token); }
window.location.assign(`${result.url}?view=split&mode=markdown`);
window.location.assign(safeAppUrl(`${result.url}?view=split&mode=markdown`));
} catch (requestError) {
error.textContent = requestError.message;
} finally {
@@ -73,7 +74,7 @@ document.querySelector("#workspace-form").addEventListener("submit", async (even
if (password.value) payload.password = password.value;
const result = await api("/api/workspaces", { method: "POST", headers: authHeaders(), body: JSON.stringify(payload) });
if (password.value) { const grant = await api("/api/access-token", { method: "POST", body: JSON.stringify({ kind: "workspace", slug: result.slug, password: password.value }) }); setAccessToken("workspace", result.slug, grant.access_token); }
window.location.assign(result.url);
window.location.assign(safeAppUrl(result.url));
} catch (requestError) {
error.textContent = requestError.message;
} finally {
@@ -110,7 +111,7 @@ async function loadResources() {
const sharedLabel = !item.owned ? `<span class="resource-shared-badge">Shared by ${escapeHtml(item.shared_by || "another user")}</span>` : "";
const permissionLabel = item.permission === "rw" ? "Read and write" : "Read only";
row.classList.toggle("resource-row--shared", !Boolean(item.owned));
row.innerHTML = `<div class="resource-main"><div class="resource-copy"><div class="resource-title-line"><a href="${item.url}">${escapeHtml(item.title)}</a>${sharedLabel}</div><small>${item.kind === "workspace" ? "Workspace" : "Note"}${item.private ? " · private" : ""}${!item.owned ? ` · ${permissionLabel}` : item.protected ? " · password protected" : ""}</small></div><div class="resource-actions">${item.owned ? `<button class="action-button action-button--secondary compact-button" type="button" data-privacy>${item.private ? "Make public" : "Make private"}</button><button class="action-button action-button--primary compact-button" type="button" data-share>Share</button><button class="action-button action-button--secondary compact-button" type="button" data-password>Change password</button><button class="action-button action-button--danger compact-button" type="button" data-delete>Delete</button>` : ""}</div></div><div class="resource-inline" data-inline hidden></div>`;
row.innerHTML = `<div class="resource-main"><div class="resource-copy"><div class="resource-title-line"><a href="${escapeHtml(safeAppUrl(item.url))}">${escapeHtml(item.title)}</a>${sharedLabel}</div><small>${item.kind === "workspace" ? "Workspace" : "Note"}${item.private ? " · private" : ""}${!item.owned ? ` · ${permissionLabel}` : item.protected ? " · password protected" : ""}</small></div><div class="resource-actions">${item.owned ? `<button class="action-button action-button--secondary compact-button" type="button" data-privacy>${item.private ? "Make public" : "Make private"}</button><button class="action-button action-button--primary compact-button" type="button" data-share>Share</button><button class="action-button action-button--secondary compact-button" type="button" data-password>Change password</button><button class="action-button action-button--danger compact-button" type="button" data-delete>Delete</button>` : ""}</div></div><div class="resource-inline" data-inline hidden></div>`;
const inline = row.querySelector("[data-inline]");
const closeInline = () => { inline.hidden = true; inline.innerHTML = ""; };
@@ -210,7 +211,7 @@ async function loadResources() {
row.querySelector("[data-delete]")?.addEventListener("click", () => {
inline.hidden = false;
inline.innerHTML = `<div class="resource-delete-confirm"><p>Delete “${item.title}” permanently?</p><p class="form-message resource-inline-message" data-inline-message role="status"></p><div class="resource-inline-actions"><button class="danger-button" type="button" data-confirm-delete>Delete</button><button class="secondary-button" type="button" data-cancel>Cancel</button></div></div>`;
inline.innerHTML = `<div class="resource-delete-confirm"><p>Delete “${escapeHtml(item.title)}” permanently?</p><p class="form-message resource-inline-message" data-inline-message role="status"></p><div class="resource-inline-actions"><button class="danger-button" type="button" data-confirm-delete>Delete</button><button class="secondary-button" type="button" data-cancel>Cancel</button></div></div>`;
inline.querySelector("[data-cancel]").addEventListener("click", closeInline);
inline.querySelector("[data-confirm-delete]").addEventListener("click", async event => {
event.currentTarget.disabled = true;