Fix Avatar-Anzeige: Auth-Header bei Avatar-Requests mitsenden

Avatar-Bilder wurden per <img src="..."> geladen, was keinen Authorization-Header
mitsendet. Der Endpoint erfordert aber Auth, daher kam immer 401 zurück.
Jetzt werden alle Avatar-Requests per fetch() mit Bearer-Token geladen und als Blob-URL gesetzt.
This commit is contained in:
2026-03-26 19:23:58 +01:00
parent 3f3609c944
commit a1001bad68
2 changed files with 56 additions and 26 deletions

View File

@@ -162,17 +162,27 @@ function bindLoginForm() {
// ── Avatar Helper ──────────────────────────────────────── // ── Avatar Helper ────────────────────────────────────────
function loadAvatarImage(avatarEl, username) { function loadAvatarImage(avatarEl, username) {
const img = new Image(); const token = localStorage.getItem('token');
img.onload = () => { fetch(`/api/profile/avatar?t=${Date.now()}`, {
avatarEl.innerHTML = ''; headers: token ? { 'Authorization': `Bearer ${token}` } : {}
img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;inset:0'; })
avatarEl.appendChild(img); .then(res => {
}; if (!res.ok) throw new Error('No avatar');
img.onerror = () => { return res.blob();
avatarEl.innerHTML = ''; })
avatarEl.textContent = (username || '?')[0].toUpperCase(); .then(blob => {
}; const img = new Image();
img.src = `/api/profile/avatar?t=${Date.now()}`; img.onload = () => {
avatarEl.innerHTML = '';
img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;inset:0';
avatarEl.appendChild(img);
};
img.src = URL.createObjectURL(blob);
})
.catch(() => {
avatarEl.innerHTML = '';
avatarEl.textContent = (username || '?')[0].toUpperCase();
});
} }
// ── Start ───────────────────────────────────────────────── // ── Start ─────────────────────────────────────────────────

View File

@@ -4,6 +4,17 @@ import { renderMonth } from './views/month.js';
import { renderWeek } from './views/week.js'; import { renderWeek } from './views/week.js';
import { renderAgenda } from './views/agenda.js'; import { renderAgenda } from './views/agenda.js';
// Fetch avatar image as blob URL (with auth header)
function fetchAvatarBlob() {
const token = localStorage.getItem('token');
return fetch(`/api/profile/avatar?t=${Date.now()}`, {
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
}).then(res => {
if (!res.ok) throw new Error('No avatar');
return res.blob();
}).then(blob => URL.createObjectURL(blob));
}
// week start day global (loaded from settings) // week start day global (loaded from settings)
let weekStartDay = 'monday'; let weekStartDay = 'monday';
@@ -751,9 +762,15 @@ export function openProfileModal() {
const img = document.getElementById('profile-avatar-img'); const img = document.getElementById('profile-avatar-img');
const removeBtn = document.getElementById('profile-avatar-remove'); const removeBtn = document.getElementById('profile-avatar-remove');
if (profile.has_avatar) { if (profile.has_avatar) {
img.src = `/api/profile/avatar?t=${Date.now()}`; fetchAvatarBlob().then(blobUrl => {
img.classList.remove('hidden'); img.src = blobUrl;
letter.classList.add('hidden'); img.classList.remove('hidden');
letter.classList.add('hidden');
}).catch(() => {
img.classList.add('hidden');
letter.classList.remove('hidden');
letter.textContent = (user.username || '?')[0].toUpperCase();
});
removeBtn.classList.remove('hidden'); removeBtn.classList.remove('hidden');
} else { } else {
img.classList.add('hidden'); img.classList.add('hidden');
@@ -899,18 +916,19 @@ function bindProfileModal() {
function updateTopbarAvatar(hasAvatar) { function updateTopbarAvatar(hasAvatar) {
const avatar = document.getElementById('user-avatar'); const avatar = document.getElementById('user-avatar');
if (hasAvatar) { if (hasAvatar) {
const img = new Image(); fetchAvatarBlob().then(blobUrl => {
img.onload = () => { const img = new Image();
avatar.innerHTML = ''; img.onload = () => {
img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;inset:0'; avatar.innerHTML = '';
avatar.appendChild(img); img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;inset:0';
}; avatar.appendChild(img);
img.onerror = () => { };
img.src = blobUrl;
}).catch(() => {
avatar.innerHTML = ''; avatar.innerHTML = '';
const u = JSON.parse(localStorage.getItem('user')||'{}'); const u = JSON.parse(localStorage.getItem('user')||'{}');
avatar.textContent = (u.username||'?')[0].toUpperCase(); avatar.textContent = (u.username||'?')[0].toUpperCase();
}; });
img.src = `/api/profile/avatar?t=${Date.now()}`;
// Update localStorage so avatar persists across reloads // Update localStorage so avatar persists across reloads
const u = JSON.parse(localStorage.getItem('user')||'{}'); const u = JSON.parse(localStorage.getItem('user')||'{}');
u.has_avatar = true; u.has_avatar = true;
@@ -1047,9 +1065,11 @@ document.getElementById('crop-save').onclick = async () => {
showToast('Profilbild hochgeladen'); showToast('Profilbild hochgeladen');
// Update profile modal avatar // Update profile modal avatar
const img = document.getElementById('profile-avatar-img'); const img = document.getElementById('profile-avatar-img');
img.src = `/api/profile/avatar?t=${Date.now()}`; fetchAvatarBlob().then(blobUrl => {
img.classList.remove('hidden'); img.src = blobUrl;
document.getElementById('profile-avatar-letter').classList.add('hidden'); img.classList.remove('hidden');
document.getElementById('profile-avatar-letter').classList.add('hidden');
});
document.getElementById('profile-avatar-remove').classList.remove('hidden'); document.getElementById('profile-avatar-remove').classList.remove('hidden');
// Update topbar avatar // Update topbar avatar
updateTopbarAvatar(true); updateTopbarAvatar(true);