code-kumpel/tests/auth.test.js
2026-10-31 10:35:44 +01:00

81 lines
5 KiB
JavaScript

const assert = require('assert');
const { createAuth, createLimiter, hashPassword, verifyPassword, validEmail, normEmail } = require('../brain/auth.js');
const { memoryStore } = require('../brain/db.js');
const sync = require('../brain/sync.js');
(async () => {
// Passwort-Hashing
const h = await hashPassword('geheim123');
assert.ok(h.startsWith('scrypt$'));
assert.notStrictEqual(h, await hashPassword('geheim123'), 'Salt muss variieren');
assert.ok(await verifyPassword('geheim123', h));
assert.ok(!(await verifyPassword('falsch123', h)));
assert.ok(validEmail(normEmail(' Test@Example.COM ')));
assert.ok(!validEmail('kein-at.de') && !validEmail('a@b') && !validEmail('a b@c.de'));
let t = 1_000_000;
const kv = memoryStore();
const auth = createAuth(kv, { now: () => t, loginLimiter: createLimiter(1000, 1000, () => t), registerLimiter: createLimiter(1000, 1000, () => t) });
assert.strictEqual((await auth.register({ email: 'x', password: 'abcdefgh' }, 'ip')).body.error, 'invalid_email');
assert.strictEqual((await auth.register({ email: 'a@b.de', password: 'kurz' }, 'ip')).body.error, 'password_too_short');
const r = await auth.register({ email: 'Byte@Test.de', password: 'pixelpixel' }, 'ip');
assert.strictEqual(r.status, 201); assert.strictEqual(r.body.user.email, 'byte@test.de'); assert.ok(r.body.token.length >= 40);
assert.strictEqual((await auth.register({ email: 'byte@test.de', password: 'pixelpixel' }, 'ip')).status, 409);
// Passwort-Hash und Token nie im Klartext gespeichert
const raw = JSON.stringify(await kv.get('sessions', require('../brain/auth.js').tokenHash(r.body.token)));
assert.ok(raw && !raw.includes(r.body.token));
// Login generisch
const bad1 = await auth.login({ email: 'byte@test.de', password: 'falschfalsch' }, 'ip');
const bad2 = await auth.login({ email: 'niemand@test.de', password: 'falschfalsch' }, 'ip');
assert.deepStrictEqual([bad1.status, bad1.body], [bad2.status, bad2.body]);
const l = await auth.login({ email: 'BYTE@test.de', password: 'pixelpixel' }, 'ip');
assert.strictEqual(l.status, 200);
const B = 'Bearer ' + l.body.token;
assert.strictEqual((await auth.me(B)).body.user.email, 'byte@test.de');
assert.strictEqual((await auth.me('Bearer ungueltigungueltigungueltig')).status, 401);
assert.strictEqual((await auth.me(undefined)).status, 401);
// Sync an Konto gebunden
const a = await auth.authenticate(B);
assert.strictEqual((await sync.handleAsync(kv, 'PUT', 'user:' + a.user.id, { version: 1, state: { xp: 42 } })).status, 200);
assert.strictEqual((await sync.handleAsync(kv, 'GET', 'user:' + a.user.id)).body.state.xp, 42);
assert.strictEqual((await sync.handleAsync(kv, 'GET', null)).status, 401);
// Passwort ändern beendet andere Sessions
const B1 = 'Bearer ' + r.body.token;
assert.strictEqual((await auth.changePassword(B, { currentPassword: 'falsch', newPassword: 'neuespasswort' }, 'ip')).status, 401);
assert.strictEqual((await auth.changePassword(B, { currentPassword: 'pixelpixel', newPassword: 'kurz' }, 'ip')).status, 400);
assert.strictEqual((await auth.changePassword(B, { currentPassword: 'pixelpixel', newPassword: 'neuespasswort' }, 'ip')).status, 200);
assert.strictEqual((await auth.me(B1)).status, 401);
assert.strictEqual((await auth.me(B)).status, 200);
assert.strictEqual((await auth.login({ email: 'byte@test.de', password: 'pixelpixel' }, 'ip')).status, 401);
// Logout
const l2 = await auth.login({ email: 'byte@test.de', password: 'neuespasswort' }, 'ip');
assert.strictEqual((await auth.logout('Bearer ' + l2.body.token)).status, 200);
assert.strictEqual((await auth.me('Bearer ' + l2.body.token)).status, 401);
// Ablauf nach 30 Tagen
t += 31 * 24 * 3600 * 1000;
assert.strictEqual((await auth.me(B)).status, 401);
// Konto löschen
const l3 = await auth.login({ email: 'byte@test.de', password: 'neuespasswort' }, 'ip');
const B3 = 'Bearer ' + l3.body.token;
assert.strictEqual((await auth.deleteAccount(B3, { password: 'falsch' }, 'ip')).status, 401);
assert.strictEqual((await auth.deleteAccount(B3, { password: 'neuespasswort' }, 'ip')).status, 200);
assert.strictEqual((await auth.me(B3)).status, 401);
assert.strictEqual(await kv.get('sync', 'user:' + a.user.id), null);
assert.strictEqual((await auth.login({ email: 'byte@test.de', password: 'neuespasswort' }, 'ip')).status, 401);
// Rate-Limit
const auth2 = createAuth(memoryStore(), { loginLimiter: createLimiter(3, 60000), registerLimiter: createLimiter(2, 60000) });
for (let i = 0; i < 3; i++) await auth2.login({ email: 'q@q.de', password: 'xxxxxxxx' }, 'ip9');
assert.strictEqual((await auth2.login({ email: 'q@q.de', password: 'xxxxxxxx' }, 'ip9')).status, 429);
await auth2.register({ email: 'r1@q.de', password: 'xxxxxxxx' }, 'ip8'); await auth2.register({ email: 'r2@q.de', password: 'xxxxxxxx' }, 'ip8');
assert.strictEqual((await auth2.register({ email: 'r3@q.de', password: 'xxxxxxxx' }, 'ip8')).status, 429);
console.log('auth.test.js OK');
})().catch((e) => { console.error(e); process.exit(1); });