0.2.89
This commit is contained in:
1 parent
9e133628e5
commit
0abc8e871d
6 files changed
+47
-7
No files matched your search
Generated
+1
-1
@@ -2674,7 +2674,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.88"
|
version = "0.2.89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.88"
|
version = "0.2.89"
|
||||||
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"
|
||||||
|
|||||||
+4
-3
@@ -1830,9 +1830,10 @@ pub async fn resource_mentions(
|
|||||||
.await
|
.await
|
||||||
.map_err(AuthError::database)?;
|
.map_err(AuthError::database)?;
|
||||||
if access.is_none() {
|
if access.is_none() {
|
||||||
return Err(AuthError::forbidden(
|
// A signed-in visitor can read public resources without a direct share.
|
||||||
"This resource was not shared with your account.",
|
// An empty list keeps member identities private and avoids treating
|
||||||
));
|
// unavailable mention suggestions as a failed page request.
|
||||||
|
return Ok(Json(serde_json::json!({ "users": [] })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rows = sqlx::query(queries::get(state.db.kind(), query))
|
let rows = sqlx::query(queries::get(state.db.kind(), query))
|
||||||
|
|||||||
+3
-1
@@ -164,7 +164,9 @@ export async function api(path, options = {}) {
|
|||||||
logWarn("api.timeout", { method: options.method || "GET", path });
|
logWarn("api.timeout", { method: options.method || "GET", path });
|
||||||
throw new Error(translateApiMessage("Timed out"));
|
throw new Error(translateApiMessage("Timed out"));
|
||||||
}
|
}
|
||||||
logError("api.network_error", error, { method: options.method || "GET", path });
|
// HTTP failures have already been logged as api.failed above.
|
||||||
|
// Only transport failures should be reported as network errors.
|
||||||
|
if (!error?.status) logError("api.network_error", error, { method: options.method || "GET", path });
|
||||||
if (options.body instanceof FormData && error instanceof TypeError) {
|
if (options.body instanceof FormData && error instanceof TypeError) {
|
||||||
throw new Error(translateApiMessage("Upload failed before the server returned a response. The file may exceed the server or proxy upload limit."));
|
throw new Error(translateApiMessage("Upload failed before the server returned a response. The file may exceed the server or proxy upload limit."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,9 @@ export function startNoteEditor(adapter) {
|
|||||||
setMarkdownMentions([]);
|
setMarkdownMentions([]);
|
||||||
mentionAutocomplete.setUsers([]);
|
mentionAutocomplete.setUsers([]);
|
||||||
mentionNavigator.setUsers([]);
|
mentionNavigator.setUsers([]);
|
||||||
if (!accountSession) { render(); return; }
|
// Session validation can finish before loadNoteInfo(); avoid a premature
|
||||||
|
// request (and a second request when note info becomes available).
|
||||||
|
if (!accountSession || !info) { render(); return; }
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams({ kind: adapter.access.kind, slug: adapter.access.key });
|
const params = new URLSearchParams({ kind: adapter.access.kind, slug: adapter.access.key });
|
||||||
const response = await api(`/api/auth/resources/mentions?${params}`);
|
const response = await api(`/api/auth/resources/mentions?${params}`);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
|
||||||
|
const events = globalThis.__apiEvents = [];
|
||||||
|
let source = readFileSync('static/js/api.js', 'utf8')
|
||||||
|
.replace('import { logDebug, logError, logWarn } from "@rustpad/logger";',
|
||||||
|
'const logDebug = () => {}; const logError = (...args) => globalThis.__apiEvents.push(["error", ...args]); const logWarn = (...args) => globalThis.__apiEvents.push(["warn", ...args]);')
|
||||||
|
.replace('import { formatNumber, restoreGuestLanguage, translateApiMessage } from "@rustpad/i18n";',
|
||||||
|
'const formatNumber = value => String(value); const restoreGuestLanguage = () => {}; const translateApiMessage = message => message;')
|
||||||
|
.replace('import { restoreGuestTheme } from "@rustpad/theme";',
|
||||||
|
'const restoreGuestTheme = () => {};');
|
||||||
|
const { api } = await import('data:text/javascript;charset=utf-8,' + encodeURIComponent(source));
|
||||||
|
|
||||||
|
globalThis.window = {};
|
||||||
|
globalThis.fetch = async () => new Response(JSON.stringify({ error: 'Access denied.' }), {
|
||||||
|
status: 403,
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
});
|
||||||
|
await assert.rejects(api('/api/auth/resources/mentions?kind=pad&slug=test'), error => error.status === 403);
|
||||||
|
assert.equal(events.filter(([level, name]) => level === 'warn' && name === 'api.failed').length, 1);
|
||||||
|
assert.equal(events.filter(([level, name]) => level === 'error' && name === 'api.network_error').length, 0);
|
||||||
|
|
||||||
|
events.length = 0;
|
||||||
|
globalThis.fetch = async () => new Response(JSON.stringify({ users: [] }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
});
|
||||||
|
assert.deepEqual(await api('/api/auth/resources/mentions?kind=pad&slug=test'), { users: [] });
|
||||||
|
assert.equal(events.length, 0);
|
||||||
|
|
||||||
|
events.length = 0;
|
||||||
|
globalThis.fetch = async () => { throw new TypeError('Network down'); };
|
||||||
|
await assert.rejects(api('/api/auth/resources/mentions?kind=pad&slug=test'), TypeError);
|
||||||
|
assert.equal(events.filter(([level, name]) => level === 'error' && name === 'api.network_error').length, 1);
|
||||||
|
console.log('PASS: HTTP 403 logged once, empty mentions succeeds, transport failures logged as network errors');
|
||||||
Reference in new issue
Block a user