Merge pull request 'Ship CSRF-drift + markdown fixes to production' (#25) from development into main
Lint / PHP (phpcs PSR-12) (push) Successful in 32s
Lint / JS (eslint) (push) Successful in 13s
Lint / PHP requirements (version + extensions) (push) Successful in 59s
Security / PHP Security (semgrep) (push) Successful in 1m12s
Lint / Deploy (push) Successful in 5s
Lint / Notify on failure (push) Has been skipped
Lint / PHP (phpcs PSR-12) (push) Successful in 32s
Lint / JS (eslint) (push) Successful in 13s
Lint / PHP requirements (version + extensions) (push) Successful in 59s
Security / PHP Security (semgrep) (push) Successful in 1m12s
Lint / Deploy (push) Successful in 5s
Lint / Notify on failure (push) Has been skipped
This commit was merged in pull request #25.
This commit is contained in:
+10
-1
@@ -52,9 +52,15 @@ try {
|
||||
if (!CsrfMiddleware::validateToken($csrfToken)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid CSRF token']);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Invalid CSRF token',
|
||||
'csrf_token' => CsrfMiddleware::getToken()
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
// Rotate token after successful validation
|
||||
$newCsrfToken = CsrfMiddleware::rotateToken();
|
||||
}
|
||||
|
||||
$currentUser = $_SESSION['user'];
|
||||
@@ -208,6 +214,9 @@ try {
|
||||
if ($result['success']) {
|
||||
$result['user_name'] = $currentUser['display_name'] ?? $currentUser['username'];
|
||||
$result['user_id'] = $userId;
|
||||
if (isset($newCsrfToken)) {
|
||||
$result['csrf_token'] = $newCsrfToken;
|
||||
}
|
||||
}
|
||||
|
||||
// Discard any unexpected output
|
||||
|
||||
@@ -48,9 +48,14 @@ try {
|
||||
if (!CsrfMiddleware::validateToken($csrfToken)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid CSRF token']);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Invalid CSRF token',
|
||||
'csrf_token' => CsrfMiddleware::getToken()
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
$GLOBALS['newCsrfToken'] = CsrfMiddleware::rotateToken();
|
||||
}
|
||||
|
||||
$currentUser = $_SESSION['user'];
|
||||
@@ -279,7 +284,8 @@ try {
|
||||
'status' => $updateData['status'],
|
||||
'priority' => $updateData['priority'],
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'message' => 'Ticket updated successfully'
|
||||
'message' => 'Ticket updated successfully',
|
||||
'csrf_token' => $GLOBALS['newCsrfToken'] ?? null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+24
-6
@@ -41,10 +41,22 @@ function parseMarkdown(markdown) {
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// Code blocks (```code```) - preserve content and don't process further
|
||||
// Code blocks (```lang\ncode\n```) - preserve content and don't process further
|
||||
const codeBlocks = [];
|
||||
html = html.replace(/```([\s\S]*?)```/g, function(match, code) {
|
||||
codeBlocks.push('<pre class="code-block"><code>' + code + '</code></pre>');
|
||||
html = html.replace(/```([a-zA-Z0-9_+-]*)\n?([\s\S]*?)```/g, function(match, lang, code) {
|
||||
lang = lang ? lang.trim() : '';
|
||||
const displayLang = lang || 'text';
|
||||
|
||||
// Build header with optional copy button if one exists in your UI, otherwise just lang
|
||||
const header = '<div class="lt-code-header"><span class="lt-code-lang">' + displayLang + '</span></div>';
|
||||
|
||||
// Remove exactly one trailing newline from code block if it exists
|
||||
if (code.endsWith('\n')) {
|
||||
code = code.slice(0, -1);
|
||||
}
|
||||
|
||||
// Wrap in the specific UI classes expected by base.css
|
||||
codeBlocks.push('<div class="lt-code-block">' + header + '<pre><code>' + code + '</code></pre></div>');
|
||||
return '%%CODEBLOCK' + (codeBlocks.length - 1) + '%%';
|
||||
});
|
||||
|
||||
@@ -321,9 +333,13 @@ function buildTable(rows) {
|
||||
|
||||
// Apply markdown rendering to all elements with data-markdown attribute
|
||||
function renderMarkdownElements() {
|
||||
document.querySelectorAll('[data-markdown]').forEach(element => {
|
||||
const markdownText = element.getAttribute('data-markdown') || element.textContent;
|
||||
document.querySelectorAll('[data-markdown]:not([data-rendered])').forEach(element => {
|
||||
// Trim so template indentation/whitespace in the element's text content
|
||||
// doesn't get parsed as a leading code block (which breaks headings,
|
||||
// tables, etc. and diverges from the live preview).
|
||||
const markdownText = (element.getAttribute('data-markdown') || element.textContent).trim();
|
||||
element.innerHTML = parseMarkdown(markdownText);
|
||||
element.dataset.rendered = '1';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -574,7 +590,9 @@ function processPlainTextComments() {
|
||||
function renderMarkdownComments() {
|
||||
document.querySelectorAll('.comment-text[data-markdown]:not([data-rendered])').forEach(el => {
|
||||
el.classList.add('lt-markdown');
|
||||
el.innerHTML = parseMarkdown(el.textContent);
|
||||
// Trim template whitespace so the first line isn't parsed as an
|
||||
// indented code block (matches the live-preview rendering).
|
||||
el.innerHTML = parseMarkdown(el.textContent.trim());
|
||||
el.dataset.rendered = '1';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -621,11 +621,14 @@ include __DIR__ . '/layout_header.php';
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-text<?= $markdownEnabled ? ' lt-markdown' : '' ?>" id="comment-text-<?= $commentId ?>"
|
||||
<?= $markdownEnabled ? 'data-markdown' : '' ?>>
|
||||
<?= $markdownEnabled
|
||||
<?= $markdownEnabled ? 'data-markdown' : '' ?>><?=
|
||||
// Emit inline (no surrounding whitespace) so a markdown
|
||||
// comment's text content isn't prefixed with template
|
||||
// indentation, which would be parsed as a code block.
|
||||
$markdownEnabled
|
||||
? htmlspecialchars($comment['comment_text'])
|
||||
: nl2br(htmlspecialchars($comment['comment_text'])) ?>
|
||||
</div>
|
||||
: nl2br(htmlspecialchars($comment['comment_text']))
|
||||
?></div>
|
||||
<textarea class="lt-input lt-textarea comment-edit-raw is-hidden"
|
||||
id="comment-raw-<?= $commentId ?>"
|
||||
aria-hidden="true"><?= htmlspecialchars($comment['comment_text']) ?></textarea>
|
||||
|
||||
Reference in New Issue
Block a user