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>
38 lines
1.2 KiB
JavaScript
38 lines
1.2 KiB
JavaScript
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);
|
|
});
|