feat(profil): eigenes Profilbild hochladen
Jeder setzt sein Bild unter "Mein Konto", Vorgesetzte und Administratoren auch fuer die Benutzer, die sie verwalten. Wo bisher die Initialen standen, steht jetzt das Bild - Seitenleiste, Kopfzeile, Team, Dashboard, Reports, Projekte, Kunden und die Buchungsliste. Ohne Bild bleiben die Initialen. Der Browser verkleinert das Bild vor dem Hochladen auf ein Quadrat von 256 px und schneidet mittig zu (lib/imageCompress.ts): aus einem 4-MB-Foto werden wenige Kilobyte, und der Server muss kein Bild verarbeiten. Gespeichert wird es als Data-URL in pulse_users.avatar (LONGTEXT). Kein Upload-Verzeichnis, das gesichert und beim Deployment am Leben gehalten werden muesste. Geprueft wird serverseitig in lib/avatar.js: nur PNG, JPEG, GIF, WebP und AVIF als Data-URL, hoechstens 2 MB. SVG ist bewusst nicht erlaubt, es kann Skripte tragen. Externe http(s)-Adressen ebenso wenig - sonst laedt jeder Seitenaufruf ein Bild von einem fremden Server und verraet, wer wann mit Pulse arbeitet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2d3d843988
commit
9576d18abc
@@ -0,0 +1,37 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { normalizeAvatar, AVATAR_MAX_LENGTH } from '../src/lib/avatar.js';
|
||||
|
||||
const PNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==';
|
||||
|
||||
test('nimmt Bild-Data-URLs an', () => {
|
||||
for (const typ of ['png', 'jpeg', 'jpg', 'gif', 'webp', 'avif']) {
|
||||
const url = `data:image/${typ};base64,AAAA`;
|
||||
assert.equal(normalizeAvatar(url).value, url, typ);
|
||||
}
|
||||
});
|
||||
|
||||
test('leer entfernt das Bild', () => {
|
||||
for (const wert of [null, undefined, '', ' ']) {
|
||||
assert.deepEqual(normalizeAvatar(wert), { value: null });
|
||||
}
|
||||
});
|
||||
|
||||
test('weist alles ab, was kein Bild ist', () => {
|
||||
// SVG kann Skripte tragen, eine externe Adresse verraet die Benutzung nach draussen.
|
||||
for (const wert of [
|
||||
'data:image/svg+xml;base64,PHN2Zy8+',
|
||||
'data:text/html;base64,PGgxPmhpPC9oMT4',
|
||||
'https://example.com/foto.png',
|
||||
'javascript:alert(1)',
|
||||
'hallo',
|
||||
]) {
|
||||
assert.ok(normalizeAvatar(wert).error, wert);
|
||||
}
|
||||
});
|
||||
|
||||
test('begrenzt die Groesse', () => {
|
||||
const zuGross = `data:image/png;base64,${'A'.repeat(AVATAR_MAX_LENGTH)}`;
|
||||
assert.match(normalizeAvatar(zuGross).error, /zu groß/);
|
||||
assert.equal(normalizeAvatar(PNG).error, undefined);
|
||||
});
|
||||
Reference in New Issue
Block a user