Fix some issues
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" novalidate>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<!-- ── Identity ─────────────────────────────────────────────────── -->
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin-bottom:14px;">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<div class="card-header"><i class="bi bi-person-gear me-2"></i>Edit: {{ user.full_name }}</div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Full Name</label>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<form method="POST" enctype="multipart/form-data" id="kb-form"
|
||||
data-article-id="{{ article.id if article else '' }}"
|
||||
data-atts-url="{{ url_for('admin.kb_get_attachments', article_id=article.id) if article else '' }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title *</label>
|
||||
<input type="text" class="form-control" name="title" required
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<a href="{{ url_for('admin.kb_edit', article_id=art.id) }}" class="btn btn-secondary btn-sm" title="Edit"><i class="bi bi-pencil"></i></a>
|
||||
<!-- Publish / Unpublish quick toggle -->
|
||||
<form method="POST" action="{{ url_for('admin.kb_toggle_publish', article_id=art.id) }}" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
{% if art.is_published %}
|
||||
<button type="submit" class="btn btn-sm"
|
||||
style="background:rgba(251,191,36,.1);border:1px solid rgba(251,191,36,.3);color:var(--warning);"
|
||||
@@ -52,6 +53,7 @@
|
||||
{% endif %}
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('admin.kb_delete', article_id=art.id) }}" onsubmit="return confirm('Delete this article?');" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" class="btn btn-sm" style="background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.2);color:var(--danger);" title="Delete"><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
{% if u.id != current_user.id and u.is_active %}
|
||||
<form method="POST" action="{{ url_for('admin.delete_user', user_id=u.id) }}"
|
||||
onsubmit="return confirm('Deactivate {{ u.full_name }}?');">
|
||||
<button type="submit" class="btn btn-sm" style="background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.2);color:var(--danger);">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <button type="submit" class="btn btn-sm" style="background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.2);color:var(--danger);">
|
||||
<i class="bi bi-person-dash"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="form-group">
|
||||
<label>Email Address</label>
|
||||
<input type="email" name="email" required placeholder="you@company.com" autofocus/>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<div class="card-header"><i class="bi bi-person-circle me-2"></i>Account Settings</div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<!-- Avatar placeholder -->
|
||||
<div class="text-center mb-4">
|
||||
<div style="width:80px;height:80px;border-radius:50%;background:var(--accent2);display:flex;align-items:center;justify-content:center;font-size:32px;font-weight:700;color:#fff;margin:0 auto 10px;">
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label>Full Name *</label>
|
||||
|
||||
+25
-6
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}"/>
|
||||
<title>{% block title %}IT Helpdesk{% endblock %} — TechDesk</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
@@ -438,6 +439,27 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
<script>
|
||||
// ── CSRF helper ───────────────────────────────────────────────────────────────
|
||||
// All state-changing fetch() calls must include the CSRF token header.
|
||||
// Use csrfPost(url, body) instead of raw fetch(..., {method:'POST'}) to ensure
|
||||
// the token from the <meta> tag is sent automatically.
|
||||
const _csrfToken = () => document.querySelector('meta[name="csrf-token"]')?.content || '';
|
||||
async function csrfPost(url, body = null) {
|
||||
const opts = {
|
||||
method : 'POST',
|
||||
headers: { 'X-CSRFToken': _csrfToken() },
|
||||
};
|
||||
if (body !== null) {
|
||||
if (typeof body === 'object' && !(body instanceof FormData)) {
|
||||
opts.headers['Content-Type'] = 'application/json';
|
||||
opts.body = JSON.stringify(body);
|
||||
} else {
|
||||
opts.body = body;
|
||||
}
|
||||
}
|
||||
return fetch(url, opts);
|
||||
}
|
||||
|
||||
// ── WebSocket ────────────────────────────────────────────────────────────────
|
||||
{% if current_user.is_authenticated %}
|
||||
const socket = io({
|
||||
@@ -506,7 +528,7 @@ function _bindNotifClick(el) {
|
||||
const link = el.dataset.link;
|
||||
// Mark as read in DB
|
||||
if(el.classList.contains('unread')){
|
||||
try { await fetch(`/api/notifications/${id}/read`, { method: 'POST' }); } catch(_){}
|
||||
try { await csrfPost(`/api/notifications/${id}/read`); } catch(_){}
|
||||
el.classList.remove('unread');
|
||||
const dot = el.querySelector('.notif-dot');
|
||||
if(dot) dot.remove();
|
||||
@@ -567,7 +589,7 @@ function prependNotif(n){
|
||||
}
|
||||
|
||||
async function markAllRead(){
|
||||
try { await fetch('/api/notifications/mark-all-read', { method: 'POST' }); } catch(_){}
|
||||
try { await csrfPost('/api/notifications/mark-all-read'); } catch(_){}
|
||||
updateBadge(0);
|
||||
document.querySelectorAll('.notif-item.unread').forEach(el => {
|
||||
el.classList.remove('unread');
|
||||
@@ -601,10 +623,7 @@ async function sendChat(){
|
||||
chatHistory.push({role:'user',content:msg});
|
||||
const typing = appendTyping();
|
||||
try{
|
||||
const r = await fetch('/chatbot/message',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({message:msg,history:chatHistory})
|
||||
});
|
||||
const r = await csrfPost('/chatbot/message', {message:msg,history:chatHistory});
|
||||
const d = await r.json();
|
||||
typing.remove();
|
||||
const reply = d.reply || 'Sorry, I encountered an error.';
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label">Issue Title *</label>
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
{% if current_user.is_it_staff or comment.author_id == current_user.id %}
|
||||
<form method="POST" action="{{ url_for('tickets.delete_comment', comment_id=comment.id) }}"
|
||||
onsubmit="return confirm('Delete this comment?');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" class="btn btn-sm" style="background:none;border:none;color:var(--muted);padding:2px 6px;"
|
||||
title="Delete comment">
|
||||
<i class="bi bi-trash"></i>
|
||||
@@ -122,6 +123,7 @@
|
||||
<div class="card-header"><i class="bi bi-chat-plus me-2"></i>Add Comment</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="mb-3">
|
||||
<textarea class="form-control" name="body" rows="4" required
|
||||
placeholder="Add your update, follow-up, or response here…"></textarea>
|
||||
@@ -157,6 +159,7 @@
|
||||
<div class="card-header"><i class="bi bi-pencil-square me-2"></i>Update Ticket</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('tickets.update_ticket', ticket_id=ticket.id) }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Status</label>
|
||||
<select class="form-select" name="status">
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<span><i class="bi bi-bell me-2"></i>All Notifications</span>
|
||||
<form method="POST" action="{{ url_for('tickets.mark_notifications_read') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" class="btn btn-secondary btn-sm">
|
||||
<i class="bi bi-check2-all me-1"></i>Mark All Read
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user