security
This commit is contained in:
parent
b12a75cfc4
commit
b2a6c19bc0
4 changed files with 554 additions and 116 deletions
|
|
@ -1149,12 +1149,20 @@ function createAvatarSvg(name, size = 512) {
|
|||
}
|
||||
|
||||
async function getAvatarUrl(id, username) {
|
||||
//S22: helper robi fetch istnienia pliku - bez cache każde odświeżenie listy
|
||||
//generowało 1-2 żądania NA wiersz (główny składnik N+1).
|
||||
//Cache'owane TYLKO pozytywne potwierdzenia - nieudane lookupy (chwilowy
|
||||
//network itp.) nie mogą przyklejać fallback-SVG na minutę
|
||||
if (!window.pfpExistenceCache) window.pfpExistenceCache = {};
|
||||
const cached = window.pfpExistenceCache[id];
|
||||
if (cached && Date.now() - cached.ts < 60000) return cached.url;
|
||||
try {
|
||||
let pfpUrl = `${url}/user/storage/public/getentry?id=${id}&e=larp.profile.pfp`;
|
||||
if (id === window.id && window.profileUpdateTimestamp) pfpUrl += `&t=${window.profileUpdateTimestamp}`;
|
||||
if ((await fetchAsync(pfpUrl)) == "") {
|
||||
throw Error();
|
||||
}
|
||||
window.pfpExistenceCache[id] = { url: pfpUrl, ts: Date.now() };
|
||||
return pfpUrl;
|
||||
} catch (e) {
|
||||
return createAvatarSvg(username);
|
||||
|
|
@ -1162,11 +1170,15 @@ async function getAvatarUrl(id, username) {
|
|||
}
|
||||
|
||||
async function getGroupAvatarUrl(groupId, groupName) {
|
||||
if (!window.groupPfpExistenceCache) window.groupPfpExistenceCache = {};
|
||||
const cached = window.groupPfpExistenceCache[groupId];
|
||||
if (cached && Date.now() - cached.ts < 60000) return cached.url;
|
||||
try {
|
||||
let pfpUrl = `${url}/group/storage/public/getentry?id=${groupId}&e=larp.profile.pfp`;
|
||||
if ((await fetchAsync(pfpUrl)) == "") {
|
||||
throw Error();
|
||||
}
|
||||
window.groupPfpExistenceCache[groupId] = { url: pfpUrl, ts: Date.now() };
|
||||
return pfpUrl;
|
||||
} catch (e) {
|
||||
return createAvatarSvg(groupName);
|
||||
|
|
@ -1557,7 +1569,7 @@ async function switchRoomsBar(title, content) {
|
|||
|
||||
await delay(200);
|
||||
|
||||
roomsTopBarTransition.innerHTML = processBlah(title);
|
||||
roomsTopBarTransition.innerHTML = escapeHtmlText(processBlah(title));
|
||||
|
||||
|
||||
let parser = new DOMParser();
|
||||
|
|
@ -1636,10 +1648,10 @@ async function switchRoomContent(title, content, showRoomBar, icon = "", skipMob
|
|||
|
||||
let clickableContent = showRoomBar ?
|
||||
`<button id="room-topbar-clickable" class="inherit" onclick="mobileNavDetails();" style="cursor: pointer; display: flex; align-items: center; border-radius: 0.65rem; background: transparent; outline: none; height: 100%; text-align: left; min-width: 0; width: auto; max-width: 100%;">
|
||||
${icon ? icon + '<space></space>' : ''}<inherit style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; min-width: 0; width: auto;">${processBlah(title)}</inherit>
|
||||
${icon ? icon + '<space></space>' : ''}<inherit style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; min-width: 0; width: auto;">${escapeHtmlText(processBlah(title))}</inherit>
|
||||
</button>` :
|
||||
`<inherit style="display: flex; align-items: center; height: 100%; min-width: 0; width: auto; max-width: 100%;">
|
||||
${icon ? icon + '<space></space>' : ''}<inherit style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; min-width: 0; width: auto;">${processBlah(title)}</inherit>
|
||||
${icon ? icon + '<space></space>' : ''}<inherit style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; min-width: 0; width: auto;">${escapeHtmlText(processBlah(title))}</inherit>
|
||||
</inherit>`;
|
||||
roomsTopBarTransition.innerHTML = `${backBtnHtml}${clickableContent}<div class="flex-spacer"></div>${pinCallBtns}`;
|
||||
|
||||
|
|
@ -1710,7 +1722,7 @@ async function switchDetailsContent(title, content, showBack = false, backAction
|
|||
pinCallBtns = `${groupMenuBtnHtml}${pinCallBtns}`;
|
||||
}
|
||||
let backBtn = showBack ? `<button class="topbar-action-btn" onclick="${backAction}" style="margin-right: 0.5rem">${ICONS.arrowBack({ width: "1.5rem", height: "1.5rem" })}</button>` : "";
|
||||
roomsTopBarTransition.innerHTML = `<inherit style="display: flex; align-items: center;">${backBtn}<inherit>${processBlah(title)}</inherit></inherit><div class="flex-spacer"></div>${pinCallBtns}`;
|
||||
roomsTopBarTransition.innerHTML = `<inherit style="display: flex; align-items: center;">${backBtn}<inherit>${escapeHtmlText(processBlah(title))}</inherit></inherit><div class="flex-spacer"></div>${pinCallBtns}`;
|
||||
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(content, "text/html");
|
||||
|
|
@ -2033,7 +2045,9 @@ async function openProfile(accountId) {
|
|||
});
|
||||
let roleRes = await fetchEncrypted("room/role", payload);
|
||||
if (roleRes && !roleRes.startsWith("error:")) {
|
||||
let mappedRole = "role." + roleRes.replace(" ", "_");
|
||||
//roleRes trafia do innerHTML - whitelist znaków zamiast surowej
|
||||
//podstawienia (audyt W6: serwer-controlled string w szablonie)
|
||||
let mappedRole = "role." + String(roleRes).replace(/[^A-Za-z0-9_.]/g, "_");
|
||||
roleHtml = `<div class="profile-role" style="opacity:0.7; font-size:0.9rem; margin-top:0.3rem;"><blah>${mappedRole}</blah></div>`;
|
||||
}
|
||||
}
|
||||
|
|
@ -2275,7 +2289,7 @@ async function renderInvites(res, type) {
|
|||
}
|
||||
|
||||
let entry = invitesEntry
|
||||
.replaceAll("{username}", groupName)
|
||||
.replaceAll("{username}", escapeHtmlText(groupName))
|
||||
.replaceAll("{pfp}", pfp)
|
||||
.replaceAll("{desc}", `<blah>${desc}</blah>`)
|
||||
.replaceAll("{actions}", actions)
|
||||
|
|
@ -2644,7 +2658,13 @@ function handleMobileSwipe() {
|
|||
}
|
||||
}
|
||||
|
||||
//S22: generacje renderów - przy nakładających się refreshDms/refreshGroups
|
||||
//(ws events, fallbacki) starszy wynik nie może nadpisać nowszego innerHTML-em
|
||||
let dmListRenderGen = 0;
|
||||
let groupListRenderGen = 0;
|
||||
|
||||
async function renderDms(res) {
|
||||
const gen = ++dmListRenderGen;
|
||||
let container = document.getElementById("dms-list");
|
||||
if (!container) return;
|
||||
|
||||
|
|
@ -2665,8 +2685,13 @@ async function renderDms(res) {
|
|||
let myId = `${id};${host}`;
|
||||
let targetId = sanitizeId(parts[0] === myId ? parts[1] : parts[0]);
|
||||
|
||||
let targetUsername = await fetchAsync(`${url}/idtoname?id=${targetId}`);
|
||||
if (!targetUsername || targetUsername.trim() === "" || targetUsername.startsWith("error:")) continue;
|
||||
//S22: cache nazw - idtoname było wołane od nowa przy każdym renderze
|
||||
let targetUsername = window.dmUsernameCache ? window.dmUsernameCache[targetId] : null;
|
||||
if (!targetUsername) {
|
||||
targetUsername = await fetchAsync(`${url}/idtoname?id=${targetId}`);
|
||||
if (!targetUsername || targetUsername.trim() === "" || targetUsername.startsWith("error:")) continue;
|
||||
if (window.dmUsernameCache) window.dmUsernameCache[targetId] = targetUsername;
|
||||
}
|
||||
|
||||
let pfp = await getAvatarUrl(targetId, targetUsername);
|
||||
let displayName = escapeHtml(targetUsername.split(':')[0]);
|
||||
|
|
@ -2694,11 +2719,13 @@ async function renderDms(res) {
|
|||
count++;
|
||||
}
|
||||
|
||||
if (gen !== dmListRenderGen) return; //nowszy render przejął listę
|
||||
if (count > 0) {
|
||||
container.innerHTML = html;
|
||||
container.classList.remove("empty");
|
||||
updateVoicePresenceUI();
|
||||
} else {
|
||||
if (gen !== dmListRenderGen) return;
|
||||
container.innerHTML = `<p style="opacity: 0.5;"><blah>desc.no.dms</blah></p>`;
|
||||
container.classList.add("empty");
|
||||
let blahTags = container.getElementsByTagName("blah");
|
||||
|
|
@ -2716,7 +2743,17 @@ async function renderDms(res) {
|
|||
}
|
||||
}
|
||||
|
||||
//S22: TTL-cache nazw grup (60 s) - nazwy rzadko się zmieniają,
|
||||
//a group/info było najdroższym elementem renderu listy grup
|
||||
function getGroupNameFromCache(groupId) {
|
||||
if (!window.groupNameCache) return null;
|
||||
let entry = window.groupNameCache[sanitizeId(groupId)];
|
||||
if (!entry || Date.now() - entry.ts > 60000) return null;
|
||||
return entry.name;
|
||||
}
|
||||
|
||||
async function renderGroups(res) {
|
||||
const gen = ++groupListRenderGen;
|
||||
let container = document.getElementById("groups-list");
|
||||
if (!container) return;
|
||||
|
||||
|
|
@ -2737,11 +2774,24 @@ async function renderGroups(res) {
|
|||
|
||||
seedCallStateFromList(groupId, groupData.call === true);
|
||||
|
||||
let infoRes = await fetchEncrypted("group/info", groupId);
|
||||
if (infoRes && infoRes.startsWith("{")) {
|
||||
let info = JSON.parse(infoRes);
|
||||
let groupName = info.string2;
|
||||
//S22: TTL-cache nazw grup - group/info było wołane per grupa per render
|
||||
let groupName = getGroupNameFromCache(groupId);
|
||||
if (groupName === null) {
|
||||
let infoRes = await fetchEncrypted("group/info", groupId);
|
||||
if (infoRes && infoRes.startsWith("{")) {
|
||||
groupName = JSON.parse(infoRes).string2;
|
||||
if (!window.groupNameCache) window.groupNameCache = {};
|
||||
window.groupNameCache[sanitizeId(groupId)] = { name: groupName, ts: Date.now() };
|
||||
} else {
|
||||
if (infoRes === "error:group.not.found") {
|
||||
//grupa została usunięta - wyczyść nieaktualny wpis z listy
|
||||
removeRoomFromCachedList(groupId, 'groups');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let safeGroupName = encodeURIComponent(groupName).replace(/'/g, "%27");
|
||||
let pfp = await getGroupAvatarUrl(groupId, groupName);
|
||||
|
||||
|
|
@ -2760,17 +2810,16 @@ async function renderGroups(res) {
|
|||
</button>
|
||||
`;
|
||||
count++;
|
||||
} else if (infoRes === "error:group.not.found") {
|
||||
//grupa została usunięta - wyczyść nieaktualny wpis z listy
|
||||
removeRoomFromCachedList(groupId, 'groups');
|
||||
}
|
||||
}
|
||||
|
||||
if (gen !== groupListRenderGen) return; //nowszy render przejął listę
|
||||
if (count > 0) {
|
||||
container.innerHTML = html;
|
||||
container.classList.remove("empty");
|
||||
updateVoicePresenceUI();
|
||||
} else {
|
||||
if (gen !== groupListRenderGen) return;
|
||||
container.innerHTML = `<p style="opacity: 0.5;"><blah>desc.no.groups</blah></p>`;
|
||||
container.classList.add("empty");
|
||||
let blahTags = container.getElementsByTagName("blah");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue