This commit is contained in:
parent
c65967acd6
commit
cba741a94f
4 changed files with 80 additions and 8 deletions
|
|
@ -739,6 +739,7 @@ std::string AuthService::generateToken(const UserInfo& user) {
|
||||||
.set_payload_claim("is_moderator", jwt::claim(std::to_string(user.isModerator)))
|
.set_payload_claim("is_moderator", jwt::claim(std::to_string(user.isModerator)))
|
||||||
.set_payload_claim("is_streamer", jwt::claim(std::to_string(user.isStreamer)))
|
.set_payload_claim("is_streamer", jwt::claim(std::to_string(user.isStreamer)))
|
||||||
.set_payload_claim("is_restreamer", jwt::claim(std::to_string(user.isRestreamer)))
|
.set_payload_claim("is_restreamer", jwt::claim(std::to_string(user.isRestreamer)))
|
||||||
|
.set_payload_claim("is_texter", jwt::claim(std::to_string(user.isTexter)))
|
||||||
.set_payload_claim("is_disabled", jwt::claim(std::to_string(user.isDisabled))) // SECURITY FIX #26
|
.set_payload_claim("is_disabled", jwt::claim(std::to_string(user.isDisabled))) // SECURITY FIX #26
|
||||||
.set_payload_claim("token_version", jwt::claim(std::to_string(user.tokenVersion))) // SECURITY FIX #10
|
.set_payload_claim("token_version", jwt::claim(std::to_string(user.tokenVersion))) // SECURITY FIX #10
|
||||||
.set_payload_claim("color_code", jwt::claim(
|
.set_payload_claim("color_code", jwt::claim(
|
||||||
|
|
@ -775,6 +776,8 @@ bool AuthService::validateToken(const std::string& token, UserInfo& userInfo) {
|
||||||
decoded.get_payload_claim("is_streamer").as_string() == "1" : false;
|
decoded.get_payload_claim("is_streamer").as_string() == "1" : false;
|
||||||
userInfo.isRestreamer = decoded.has_payload_claim("is_restreamer") ?
|
userInfo.isRestreamer = decoded.has_payload_claim("is_restreamer") ?
|
||||||
decoded.get_payload_claim("is_restreamer").as_string() == "1" : false;
|
decoded.get_payload_claim("is_restreamer").as_string() == "1" : false;
|
||||||
|
userInfo.isTexter = decoded.has_payload_claim("is_texter") ?
|
||||||
|
decoded.get_payload_claim("is_texter").as_string() == "1" : false;
|
||||||
|
|
||||||
// SECURITY FIX #26: Extract disabled status
|
// SECURITY FIX #26: Extract disabled status
|
||||||
userInfo.isDisabled = decoded.has_payload_claim("is_disabled") ?
|
userInfo.isDisabled = decoded.has_payload_claim("is_disabled") ?
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,9 @@ function cleanTextForTTS(text) {
|
||||||
clean = clean.replace(/~~(.+?)~~/g, '$1'); // strikethrough
|
clean = clean.replace(/~~(.+?)~~/g, '$1'); // strikethrough
|
||||||
clean = clean.replace(/`(.+?)`/g, '$1'); // code
|
clean = clean.replace(/`(.+?)`/g, '$1'); // code
|
||||||
|
|
||||||
|
// Remove markdown heading markers (# ## ### etc.)
|
||||||
|
clean = clean.replace(/^#{1,6}\s*/gm, '');
|
||||||
|
|
||||||
// Remove URLs completely (don't read them aloud)
|
// Remove URLs completely (don't read them aloud)
|
||||||
clean = clean.replace(/https?:\/\/\S+/g, '');
|
clean = clean.replace(/https?:\/\/\S+/g, '');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,15 @@
|
||||||
}
|
}
|
||||||
return match;
|
return match;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Replace <marquee><h1>:sticker: with marquee-wrapped h1 sticker
|
||||||
|
msg = msg.replace(/^<marquee><h1>:(\w+):$/gim, (match, stickerName) => {
|
||||||
|
const stickerKey = stickerName.toLowerCase();
|
||||||
|
if (stickersMap[stickerKey]) {
|
||||||
|
return `<marquee><h1><img src="${stickersMap[stickerKey]}" alt="${stickerName}" title="${stickerName}" data-sticker="${stickerName}" class="sticker-img" onerror="this.onerror=null;this.src='/dlive2.gif';" /></h1></marquee>`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace inline :sticker: with img tags (all modes with stickers enabled)
|
// Replace inline :sticker: with img tags (all modes with stickers enabled)
|
||||||
|
|
@ -203,7 +212,7 @@
|
||||||
|
|
||||||
// Step 5: Sanitize with DOMPurify - allow img tags and safe CSS
|
// Step 5: Sanitize with DOMPurify - allow img tags and safe CSS
|
||||||
html = DOMPurify.sanitize(html, {
|
html = DOMPurify.sanitize(html, {
|
||||||
ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'img', 'br', 'strong', 'em', 'code', 'pre', 'del', 'span'],
|
ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'img', 'br', 'strong', 'em', 'code', 'pre', 'del', 'span', 'marquee'],
|
||||||
ALLOWED_ATTR: ['src', 'alt', 'title', 'class', 'style', 'data-sticker', 'onerror'],
|
ALLOWED_ATTR: ['src', 'alt', 'title', 'class', 'style', 'data-sticker', 'onerror'],
|
||||||
FORBID_TAGS: ['a', 'button', 'script'],
|
FORBID_TAGS: ['a', 'button', 'script'],
|
||||||
ALLOW_DATA_ATTR: false
|
ALLOW_DATA_ATTR: false
|
||||||
|
|
@ -901,6 +910,25 @@
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Marquee stickers */
|
||||||
|
.message-content :global(marquee) {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content :global(marquee h1) {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content :global(marquee h1 img) {
|
||||||
|
max-width: 475px;
|
||||||
|
max-height: none;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
.mod-menu {
|
.mod-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
|
||||||
|
|
@ -251,20 +251,58 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch for content size changes (e.g., when images load)
|
// Watch for images loading inside messages (e.g., stickers)
|
||||||
// This ensures autoscroll works even when images finish loading after message is added
|
// This ensures autoscroll works even when images finish loading after message is added
|
||||||
let resizeObserver;
|
let mutationObserver;
|
||||||
if (messagesContainer) {
|
if (messagesContainer) {
|
||||||
resizeObserver = new ResizeObserver(() => {
|
// Track images we've already added listeners to
|
||||||
scrollToBottom();
|
const trackedImages = new WeakSet();
|
||||||
|
|
||||||
|
const attachImageLoadListeners = (container) => {
|
||||||
|
const images = container.querySelectorAll('img');
|
||||||
|
images.forEach(img => {
|
||||||
|
if (!trackedImages.has(img)) {
|
||||||
|
trackedImages.add(img);
|
||||||
|
if (!img.complete) {
|
||||||
|
img.addEventListener('load', scrollToBottom, { once: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach to existing images
|
||||||
|
attachImageLoadListeners(messagesContainer);
|
||||||
|
|
||||||
|
// Watch for new images being added
|
||||||
|
mutationObserver = new MutationObserver((mutations) => {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (mutation.type === 'childList') {
|
||||||
|
mutation.addedNodes.forEach(node => {
|
||||||
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
attachImageLoadListeners(node);
|
||||||
|
// Also check if the node itself is an image
|
||||||
|
if (node.tagName === 'IMG' && !trackedImages.has(node)) {
|
||||||
|
trackedImages.add(node);
|
||||||
|
if (!node.complete) {
|
||||||
|
node.addEventListener('load', scrollToBottom, { once: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mutationObserver.observe(messagesContainer, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
});
|
});
|
||||||
resizeObserver.observe(messagesContainer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
if (resizeObserver) {
|
if (mutationObserver) {
|
||||||
resizeObserver.disconnect();
|
mutationObserver.disconnect();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue