Security audit, bug fixes, auto-save, and writing assistant improvements
Backend security & correctness: - Add rate_limit.py: sliding-window rate limiter (asyncio) applied to login, register, forgot/reset password endpoints (10/60s or 5/300s per IP) - app.py: add security headers in after_request (X-Frame-Options, CSP, X-Content-Type-Options, Referrer-Policy) using setdefault to preserve SSE headers - auth.py: refactor duplicate login_required/admin_required into shared _check_auth() - config.py: add TRUST_PROXY_HEADERS for proxy-aware client IP resolution - routes/auth.py: rate limiting, _client_ip() helper, cleaned-up reset_password route - routes/chat.py, notes.py, tasks.py: int() DoS fix on last_event_id; limit capped at 500; date.fromisoformat() wrapped in try/except → 400 on invalid dates - services/auth.py: fix Setting.user_id update bug (filter on NULL not user.id); reset_password_with_token returns int|None (user_id) instead of bool - services/backup.py: add _security_notice to full backup JSON export - services/assist.py: system prompt explicitly preserves markdown list structure and nested indented sub-items Infrastructure: - docker-compose.yml: add healthcheck on app service (/api/health, 10s interval) - .dockerignore: prevent secrets/node_modules/__pycache__/.env.* leaking into build Frontend bug fixes: - TaskCard.vue, TaskViewerView.vue: fix isOverdue() timezone bug (ISO string compare) - useAssist.ts: accept() now resets state to idle when document changed since proposal - stores/chat.ts: fix memory leak in _pollUntilLoaded() (try/catch around fetchStatus) - TiptapEditor.vue: selection offset uses closest-match strategy (not first-match) - utils/markdown.ts: explicit DOMPurify config with FORCE_BODY; remove as const (DOMPurify expects mutable string[]) New features: - Auto-save (5-minute interval) in NoteEditorView and TaskEditorView — only when editing an existing dirty record; silent on error, shows "Auto-saved" toast - sectionParser.ts: top-level bullet/numbered list items are now individual sections in the AI Assist panel (previously treated as one undifferentiated block) - editor-shared.css: extracted ~500 lines of CSS duplicated between both editors; includes .inline-assist-btn at global scope (required for teleported elements) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,8 @@ function goEdit() {
|
||||
|
||||
function isOverdue(): boolean {
|
||||
if (!props.task.due_date || props.task.status === "done") return false;
|
||||
return new Date(props.task.due_date) < new Date(new Date().toDateString());
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
return props.task.due_date < today;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -90,12 +90,24 @@ try {
|
||||
if (!selectedText) return;
|
||||
|
||||
const md = lastEmittedMarkdown;
|
||||
const idx = md.indexOf(selectedText);
|
||||
if (idx !== -1) {
|
||||
const fraction = from / (ed.state.doc.content.size || 1);
|
||||
const estIdx = Math.floor(fraction * md.length);
|
||||
|
||||
let bestIdx = -1;
|
||||
let bestDist = Infinity;
|
||||
let searchFrom = 0;
|
||||
while (true) {
|
||||
const idx = md.indexOf(selectedText, searchFrom);
|
||||
if (idx === -1) break;
|
||||
const dist = Math.abs(idx - estIdx);
|
||||
if (dist < bestDist) { bestDist = dist; bestIdx = idx; }
|
||||
searchFrom = idx + 1;
|
||||
}
|
||||
if (bestIdx !== -1) {
|
||||
emit("selectionChange", {
|
||||
text: selectedText,
|
||||
start: idx,
|
||||
end: idx + selectedText.length,
|
||||
start: bestIdx,
|
||||
end: bestIdx + selectedText.length,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user