session and auth fix
This commit is contained in:
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustpad"
|
||||
version = "0.0.34"
|
||||
version = "0.0.39"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"aws-config",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustpad"
|
||||
version = "0.0.38"
|
||||
version = "0.0.39"
|
||||
edition = "2024"
|
||||
rust-version = "1.94"
|
||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||
|
||||
@@ -278,7 +278,9 @@ export async function validateCurrentSession() {
|
||||
const token = getAuthToken();
|
||||
if (!token) return null;
|
||||
try {
|
||||
return await api("/api/auth/me", { headers: { Authorization: `Bearer ${token}` } });
|
||||
const session = await api("/api/auth/me", { headers: { Authorization: `Bearer ${token}` } });
|
||||
setAuthSession(session);
|
||||
return session;
|
||||
} catch {
|
||||
clearAuthSession();
|
||||
return null;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
||||
import { bindEmojiPicker } from "@rustpad/emoji-picker";
|
||||
import { alignPreviewLineNumbers, renderMarkdown } from "@rustpad/markdown";
|
||||
import { getNickname, getGuestId, getAuthToken, getAccessToken, setAccessToken } from "@rustpad/session";
|
||||
import { bindIdentityDialog } from "@rustpad/auth-ui";
|
||||
import { bindIdentityDialog, validateCurrentSession } from "@rustpad/auth-ui";
|
||||
import { bindNoteFiles } from "@rustpad/note-files";
|
||||
import { currentShareUrl, readEditorState, writeEditorState } from "@rustpad/url-state";
|
||||
|
||||
@@ -18,7 +18,7 @@ export function startNoteEditor(adapter) {
|
||||
let unreadChat = 0;
|
||||
const compactToggle = document.querySelector("#compact-toggle"), publicTaskUpdates = document.querySelector("#public-task-updates"), fontFamily = document.querySelector("#font-family"), fontSize = document.querySelector("#font-size"), currentUser = document.querySelector("#current-user"), userColorPicker = document.querySelector("#user-color-picker");
|
||||
const shareToken = new URLSearchParams(location.search).get("share"); if (shareToken) setAccessToken(adapter.access.kind, adapter.access.key, shareToken);
|
||||
let accessToken = shareToken || getAuthToken() || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "";
|
||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "";
|
||||
const lineToggle = document.querySelector("#line-numbers-toggle"), previewLineToggle = document.querySelector("#preview-line-numbers-toggle"); lineToggle.checked = localStorage.getItem("rustpad:line-numbers") !== "off";
|
||||
previewLineToggle.checked = localStorage.getItem("rustpad:preview-line-numbers") === "on";
|
||||
compactToggle.checked = localStorage.getItem("rustpad:compact") !== "off";
|
||||
@@ -177,9 +177,32 @@ export function startNoteEditor(adapter) {
|
||||
endpoints: adapter.fileEndpoints,
|
||||
});
|
||||
function connect() { socket?.stop(); socket = adapter.createSocket({ password, accessToken, nickname, color: currentUserColor() || null, sessionToken: getAuthToken(), guestId: getGuestId(), onStatus: s => setStatus(s === "online" ? "online" : s === "offline" ? "offline" : null, s === "online" ? "Connected" : s === "offline" ? "Reconnecting…" : "Connecting…"), onAuthenticated: m => { if (passwordDialog.open) passwordDialog.close(); applyRemote(m.content, m.owner_map); editor.focus(); }, onDocument: m => { applyRemote(m.content, m.owner_map); document.querySelector("#save-state").textContent = `${m.author ? `${m.author} · ` : ""}${new Date(m.updated_at).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}`; }, onPresence: updatePresence, onLatency: updateLatency, onChat: appendChatMessage, onError: m => { document.querySelector("#password-error").textContent = m; if (/nickname|session|account/i.test(m)) { if (!identityDialog.open) identityDialog.showModal(); } else if (info?.protected && !passwordDialog.open) passwordDialog.showModal(); } }); socket.connect(); }
|
||||
bindIdentityDialog({ dialog: identityDialog, onIdentity: async value => { nickname = value; identityDialog.close(); updateCurrentUser(); await loadNoteInfo(); if (info.protected && !accessToken) passwordDialog.showModal(); else { loadFiles(); connect(); } } });
|
||||
bindIdentityDialog({ dialog: identityDialog, onIdentity: async value => { nickname = value; accessToken = shareToken || getAuthToken() || getAccessToken(adapter.access.kind, adapter.access.key); identityDialog.close(); updateCurrentUser(); await loadNoteInfo(); if (info.protected && !accessToken) passwordDialog.showModal(); else { loadFiles(); connect(); } } });
|
||||
identityDialog.addEventListener("close", () => { if (!nickname) queueMicrotask(() => { if (!identityDialog.open) identityDialog.showModal(); }); });
|
||||
async function initialize() { try { await loadNoteInfo(); document.title = adapter.title(info); publicTaskUpdates.checked = Boolean(info.allow_public_task_updates); adapter.configureView?.(info); applyUi({ write: true, replace: true }); if (!nickname) { identityDialog.showModal(); return; } updateCurrentUser(); if (info.protected && !accessToken) passwordDialog.showModal(); else { loadFiles(); connect(); } } catch (e) { document.body.innerHTML = `<main class="error-page"><div><h1>Note not found</h1><p>${escapeHtml(e.message)}</p></div></main>`; } }
|
||||
async function initialize() {
|
||||
try {
|
||||
if (getAuthToken()) {
|
||||
const session = await validateCurrentSession();
|
||||
nickname = session?.nickname || getNickname();
|
||||
}
|
||||
if (!nickname) {
|
||||
if (!identityDialog.open) identityDialog.showModal();
|
||||
return;
|
||||
}
|
||||
|
||||
accessToken = shareToken || getAuthToken() || getAccessToken(adapter.access.kind, adapter.access.key);
|
||||
await loadNoteInfo();
|
||||
document.title = adapter.title(info);
|
||||
publicTaskUpdates.checked = Boolean(info.allow_public_task_updates);
|
||||
adapter.configureView?.(info);
|
||||
applyUi({ write: true, replace: true });
|
||||
updateCurrentUser();
|
||||
if (info.protected && !accessToken) passwordDialog.showModal();
|
||||
else { loadFiles(); connect(); }
|
||||
} catch (e) {
|
||||
document.body.innerHTML = `<main class="error-page"><div><h1>Note not found</h1><p>${escapeHtml(e.message)}</p></div></main>`;
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll("[data-view]").forEach(b => b.addEventListener("click", () => { uiState = { ...uiState, view: b.dataset.view }; applyUi({ write: true }); })); modeToggle.addEventListener("click", () => { uiState = { ...uiState, mode: uiState.mode === "markdown" ? "text" : "markdown" }; applyUi({ write: true }); }); lineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:line-numbers", lineToggle.checked ? "on" : "off"); renderGutter(); }); previewLineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:preview-line-numbers", previewLineToggle.checked ? "on" : "off"); renderGutter(); alignPreviewLineNumbers(preview); }); compactToggle.addEventListener("change", () => { localStorage.setItem("rustpad:compact", compactToggle.checked ? "on" : "off"); applyUi(); }); fontFamily.addEventListener("change", () => { localStorage.setItem("rustpad:font-family", fontFamily.value); applyUi(); }); fontSize.addEventListener("change", () => { localStorage.setItem("rustpad:font-size", fontSize.value); applyUi(); });
|
||||
window.addEventListener("popstate", () => { uiState = readEditorState(); applyUi(); }); window.addEventListener("rustpad:urlchange", updateAddressLabel); document.querySelector("#copy-link").addEventListener("click", async () => { try { await copyText(currentShareUrl(uiState)); toast("Link copied"); } catch (e) { toast(e.message); } }); document.querySelectorAll("[data-format]").forEach(b => b.addEventListener("click", () => { applyFormat(editor, b.dataset.format); b.closest("details")?.removeAttribute("open"); })); bindFormatShortcuts(editor); bindEmojiPicker({ editor, details: document.querySelector("#emoji-picker"), search: document.querySelector("#emoji-search"), categories: document.querySelector("#emoji-categories"), grid: document.querySelector("#emoji-grid"), empty: document.querySelector("#emoji-empty") }); document.querySelector("#shortcuts-button").addEventListener("click", () => document.querySelector("#shortcuts-dialog").showModal()); document.querySelector("#close-shortcuts").addEventListener("click", () => document.querySelector("#shortcuts-dialog").close()); preview.addEventListener("change", event => { const checkbox = event.target.closest(".task-checkbox"); if (!checkbox) return; const lineIndex = Number(checkbox.dataset.sourceLine) - 1; const lines = editor.value.split("\n"); if (lineIndex < 0 || lineIndex >= lines.length) return; lines[lineIndex] = lines[lineIndex].replace(/^(\s*[-*+]\s+\[)[ xX](\])/, `$1${checkbox.checked ? "x" : " "}$2`); editor.value = lines.join("\n"); editor.dispatchEvent(new Event("input", { bubbles: true })); }); preview.addEventListener("keydown", event => { const target = event.target.closest(".preview-editable"); if (!target) return; if (event.key === "Enter") { event.preventDefault(); target.blur(); return; } if (event.key === "ArrowUp" || event.key === "ArrowDown") { if (movePreviewCaret(target, event.key === "ArrowUp" ? -1 : 1)) event.preventDefault(); } }); preview.addEventListener("blur", event => { const target = event.target.closest(".preview-editable"); if (!target) return; const lineIndex = Number(target.dataset.sourceLine) - 1; if (lineIndex < 0) return; const lines = editor.value.split("\n"); const value = markdownFromPreview(target); let next; if (target.dataset.tableCell !== undefined) next = replaceTableCell(lines[lineIndex], Number(target.dataset.tableCell), value); else { const prefix = target.dataset.sourcePrefix || "", suffix = target.dataset.sourceSuffix || ""; next = prefix + value + suffix; } if (lines[lineIndex] === next) return; lines[lineIndex] = next; editor.value = lines.join("\n"); editor.setSelectionRange(editor.value.length, editor.value.length); editor.dispatchEvent(new Event("input", { bubbles: true })); }, { capture: true });
|
||||
|
||||
+41
-3
@@ -4,15 +4,19 @@ installGlobalDiagnostics();
|
||||
import { api } from "@rustpad/api";
|
||||
import { copyText } from "@rustpad/clipboard";
|
||||
import { getNickname, getAccessToken, getAuthToken, setAccessToken } from "@rustpad/session";
|
||||
import { bindIdentityDialog, validateCurrentSession } from "@rustpad/auth-ui";
|
||||
import { askConfirm } from "@rustpad/modal";
|
||||
|
||||
const parts = location.pathname.split("/").filter(Boolean);
|
||||
const slug = parts[1];
|
||||
let info;
|
||||
const shareToken = new URLSearchParams(location.search).get("share");
|
||||
let accessToken = shareToken || getAuthToken() || getAccessToken("workspace", slug);
|
||||
let accessToken = shareToken || getAccessToken("workspace", slug);
|
||||
let nickname = getNickname();
|
||||
if (shareToken) setAccessToken("workspace", slug, shareToken);
|
||||
const dialog = document.querySelector("#password-dialog");
|
||||
const identityDialog = document.querySelector("#identity-dialog");
|
||||
const workspaceContent = document.querySelector("#workspace-content");
|
||||
const notesList = document.querySelector("#notes-list");
|
||||
const notesViewKey = `rustpad:workspace:${slug}:notes-view`;
|
||||
let notesView = localStorage.getItem(notesViewKey) === "table" ? "table" : "grid";
|
||||
@@ -127,7 +131,7 @@ document.querySelector("#note-form").addEventListener("submit", async e => {
|
||||
try {
|
||||
const note = await api(`/api/workspaces/${encodeURIComponent(slug)}/notes`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name: document.querySelector("#note-name").value, access_token: accessToken || null, protect: document.querySelector("#note-protect").checked, created_by: getNickname() || null })
|
||||
body: JSON.stringify({ name: document.querySelector("#note-name").value, access_token: accessToken || null, protect: document.querySelector("#note-protect").checked, created_by: nickname || null })
|
||||
});
|
||||
location.assign(`${note.url}?view=split&mode=markdown`);
|
||||
} catch (err) { error.textContent = err.message; }
|
||||
@@ -155,5 +159,39 @@ document.querySelector("#copy-workspace-link").addEventListener("click", async (
|
||||
try { await copyText(new URL(location.pathname, location.origin).href); toast("Link copied"); }
|
||||
catch (e) { toast(e.message); }
|
||||
});
|
||||
async function startAuthorizedWorkspace() {
|
||||
const hadAccountToken = Boolean(getAuthToken());
|
||||
if (hadAccountToken) {
|
||||
const session = await validateCurrentSession();
|
||||
nickname = session?.nickname || getNickname();
|
||||
}
|
||||
|
||||
if (!nickname) {
|
||||
workspaceContent.hidden = true;
|
||||
if (!identityDialog.open) identityDialog.showModal();
|
||||
return;
|
||||
}
|
||||
|
||||
accessToken = shareToken || getAuthToken() || getAccessToken("workspace", slug);
|
||||
workspaceContent.hidden = false;
|
||||
await init();
|
||||
}
|
||||
|
||||
bindIdentityDialog({
|
||||
dialog: identityDialog,
|
||||
onIdentity: async value => {
|
||||
nickname = value;
|
||||
accessToken = shareToken || getAuthToken() || getAccessToken("workspace", slug);
|
||||
workspaceContent.hidden = false;
|
||||
await init();
|
||||
},
|
||||
});
|
||||
identityDialog.addEventListener("close", () => {
|
||||
if (!nickname) queueMicrotask(() => {
|
||||
workspaceContent.hidden = true;
|
||||
if (!identityDialog.open) identityDialog.showModal();
|
||||
});
|
||||
});
|
||||
|
||||
setNotesView(notesView);
|
||||
init();
|
||||
startAuthorizedWorkspace();
|
||||
|
||||
+33
-2
@@ -11,7 +11,7 @@
|
||||
__APP_ENTRYPOINT__
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<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="document-heading">
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
<div class="header-actions"><button id="copy-workspace-link" class="secondary-button">Copy link</button></div>
|
||||
</header>
|
||||
<main class="workspace-page">
|
||||
<main id="workspace-content" class="workspace-page" hidden>
|
||||
<section class="workspace-top">
|
||||
<div>
|
||||
<h2>Notes</h2>
|
||||
@@ -36,6 +36,37 @@
|
||||
<p id="workspace-error" class="form-message error"></p>
|
||||
<section id="notes-list" class="notes-grid" aria-live="polite"></section>
|
||||
</main>
|
||||
<dialog id="identity-dialog">
|
||||
<form id="identity-form" autocomplete="on" class="dialog-panel identity-panel">
|
||||
<h2>Identify yourself</h2>
|
||||
<p class="dialog-copy">Log in, or choose a free nickname to continue as a guest.</p>
|
||||
<input id="nickname" maxlength="40" autocomplete="off" data-bwignore="true" required
|
||||
placeholder="Name or nickname">
|
||||
<div class="identity-actions">
|
||||
<button id="guest-continue" class="primary-button" type="submit">Continue as guest</button>
|
||||
<button id="show-register" class="text-button" type="button">Register</button>
|
||||
<button id="show-login" class="text-button" type="button">Log in</button>
|
||||
</div>
|
||||
<section id="auth-panel" class="auth-panel" hidden>
|
||||
<h3 id="auth-mode-title">Log in</h3>
|
||||
<label id="auth-email-field">E-mail / organization login
|
||||
<input id="auth-email" name="username" type="email" maxlength="320"
|
||||
autocomplete="username" placeholder="you@example.com">
|
||||
</label>
|
||||
<label>Password
|
||||
<input id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
||||
autocomplete="current-password">
|
||||
</label>
|
||||
<button id="auth-submit" class="primary-button" type="submit">Log in and continue</button>
|
||||
<div class="identity-links">
|
||||
<button id="show-reset" class="text-button" type="button">Forgot password?</button>
|
||||
<button id="auth-back" class="text-button" type="button">Back to nickname</button>
|
||||
<button id="logout-account" class="text-button" type="button">Log out saved account</button>
|
||||
</div>
|
||||
</section>
|
||||
<p id="identity-error" class="form-message error" role="alert"></p>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="password-dialog">
|
||||
<form id="password-form" class="dialog-panel">
|
||||
<h2>Protected workspace</h2><input id="open-password" type="password" autocomplete="current-password"
|
||||
|
||||
Reference in New Issue
Block a user