replaced onclick handlers with event listeners to resolve possible xss vector from single quotes

This commit is contained in:
elmerohueso 2026-04-28 18:40:51 -06:00
parent 7f94597706
commit 95b1a8507b

View file

@ -1834,9 +1834,22 @@ function _genreWhitelistRender(genres) {
const searchVal = (document.getElementById('genre-whitelist-search')?.value || '').toLowerCase();
const filtered = searchVal ? _genreWhitelistCache.filter(g => g.toLowerCase().includes(searchVal)) : _genreWhitelistCache;
container.innerHTML = filtered.map(g =>
`<span class="genre-chip">${escapeHtml(g)}<button class="genre-chip-x" onclick="_genreWhitelistRemove('${escapeHtml(g.replace(/'/g, "\\'"))}')">&times;</button></span>`
`<span class="genre-chip">${escapeHtml(g)}<button class="genre-chip-x" data-genre="${escapeHtml(g)}">&times;</button></span>`
).join('');
if (countEl) countEl.textContent = `${_genreWhitelistCache.length} genres`;
_initGenreChipClickHandler();
}
function _initGenreChipClickHandler() {
const container = document.getElementById('genre-whitelist-chips');
if (!container) return;
container.onclick = (e) => {
const btn = e.target.closest('.genre-chip-x');
if (btn) {
e.preventDefault();
_genreWhitelistRemove(btn.dataset.genre);
}
};
}
function _genreWhitelistRemove(genre) {
@ -2787,19 +2800,32 @@ function renderApiKeys(keys) {
container.innerHTML = keys.map(k => `
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; margin-bottom: 4px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 6px;">
<div style="flex: 1; min-width: 0;">
<div style="font-size: 13px; color: #e0e0e0; font-weight: 500;">${k.label || 'Unnamed'}</div>
<div style="font-size: 13px; color: #e0e0e0; font-weight: 500;">${escapeHtml(k.label || 'Unnamed')}</div>
<div style="font-size: 11px; color: #666; margin-top: 2px;">
<code>${k.key_prefix || 'sk_...'}...</code>
<code>${escapeHtml(k.key_prefix || 'sk_...')}...</code>
&middot; Created ${k.created_at ? new Date(k.created_at).toLocaleDateString() : 'unknown'}
${k.last_used_at ? '&middot; Last used ' + new Date(k.last_used_at).toLocaleDateString() : ''}
</div>
</div>
<button onclick="revokeApiKey('${k.id}', '${(k.label || 'this key').replace(/'/g, "\\'")}')"
<button class="revoke-api-key-btn" data-key-id="${escapeHtml(k.id)}" data-key-label="${escapeHtml(k.label || 'this key')}"
style="padding: 4px 10px; background: rgba(255,82,82,0.1); border: 1px solid rgba(255,82,82,0.2); color: #ff5252; border-radius: 4px; cursor: pointer; font-size: 11px; white-space: nowrap;">
Revoke
</button>
</div>
`).join('');
_initApiKeyClickHandler();
}
function _initApiKeyClickHandler() {
const container = document.getElementById('api-keys-list');
if (!container) return;
container.onclick = (e) => {
const btn = e.target.closest('.revoke-api-key-btn');
if (btn) {
e.preventDefault();
revokeApiKey(btn.dataset.keyId, btn.dataset.keyLabel);
}
};
}
async function generateApiKey() {
@ -3291,16 +3317,17 @@ async function loadHiFiInstances() {
listEl.innerHTML = data.instances.map((inst, i) => {
const enabledClass = inst.enabled ? '' : 'hifi-instance-disabled';
const checkHtml = inst.enabled
? `<span class="hifi-instance-toggle on" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to disable">&#x2714;</span>`
: `<span class="hifi-instance-toggle off" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to enable">&#x2718;</span>`;
? `<span class="hifi-instance-toggle on" data-url="${escapeHtml(inst.url)}" title="Click to disable">&#x2714;</span>`
: `<span class="hifi-instance-toggle off" data-url="${escapeHtml(inst.url)}" title="Click to enable">&#x2718;</span>`;
return `<div class="hifi-instance-item${inst.enabled ? '' : ' disabled'}" draggable="true" data-url="${escapeHtml(inst.url)}">
<span class="hifi-instance-grip">&#x2630;</span>
<span class="hifi-instance-url">${escapeHtml(inst.url)}</span>
${checkHtml}
<span class="hifi-instance-remove" onclick="removeHiFiInstance('${escapeHtml(inst.url)}')" title="Remove instance">&#x2716;</span>
<span class="hifi-instance-remove" data-url="${escapeHtml(inst.url)}" title="Remove instance">&#x2716;</span>
</div>`;
}).join('');
_initHiFiDragDrop();
_initHiFiClickHandlers();
} catch (e) {
listEl.innerHTML = `<div style="color:#f44336;font-size:0.85em;">Error loading instances: ${escapeHtml(e.message)}</div>`;
}
@ -3349,6 +3376,24 @@ function _initHiFiDragDrop() {
});
}
function _initHiFiClickHandlers() {
const listEl = document.getElementById('hifi-instances-list');
if (!listEl) return;
listEl.onclick = (e) => {
const toggle = e.target.closest('.hifi-instance-toggle');
if (toggle) {
e.preventDefault();
toggleHiFiInstance(toggle.dataset.url);
return;
}
const remove = e.target.closest('.hifi-instance-remove');
if (remove) {
e.preventDefault();
removeHiFiInstance(remove.dataset.url);
}
};
}
async function _saveHiFiInstanceOrder(urls) {
try {
await fetch('/api/hifi/instances/reorder', {