import { test, after } from 'node:test' import assert from 'node:assert/strict' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' // Portal-Login mit E-Mail-Code fuer Mitarbeiter mit Appwrite-2FA; Kunden und Mitarbeiter ohne 2FA wie bisher const aw = await startAppwriteMock() const st = await startFakeStripe() useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { config } = await import('../server/config.js') const { startTestApp, post, rawGet } = await import('./support/testApp.js') const { default: authRoutes } = await import('../server/routes/auth.js') const app = await startTestApp({ '/api/auth': authRoutes }) after(() => Promise.all([app.close(), aw.close(), st.close()])) aw.addUser({ $id: 'u_kunde', email: 'kunde@example.invalid', password: 'kunde-kunde' }) aw.addUser({ $id: 'u_ohne', email: 'justin@webklar.com', password: 'ohne-ohne-ohne' }) aw.addUser({ $id: 'u_mfa', email: 'kenso@webklar.com', password: 'mit-mit-mit', mfa: true, emailVerification: true, labels: ['admin'] }) aw.seed('customers', { $id: 'c1', appwriteUserId: 'u_kunde', email: 'kunde@example.invalid', name: 'Kunde', customerStatus: 'active', portalAccessEnabled: true }) aw.seed('customerPortalAccess', { $id: 'pa1', customerId: 'c1', enabled: true }) aw.seed('employees', { $id: 'e1', userId: 'u_ohne', displayName: 'Justin', email: 'justin@webklar.com' }) aw.seed('employees', { $id: 'e2', userId: 'u_mfa', displayName: 'Kenso', email: 'kenso@webklar.com' }) const SESSION = config.cookieName const setCookies = (res) => res.headers.getSetCookie() const cookieNamed = (res, name) => setCookies(res).find((c) => c.startsWith(`${name}=`)) || null const jar = (res) => setCookies(res).map((c) => c.split(';')[0]).join('; ') const login = (email, password) => post('/api/auth/login', { email, password }) test('Kunde ohne 2FA: Anmeldung wie bisher', async () => { const res = await login('kunde@example.invalid', 'kunde-kunde') assert.equal(res.status, 200) assert.equal(res.body.mfaRequired, undefined) assert.ok(cookieNamed(res, SESSION)) }) test('Mitarbeiter ohne 2FA: Admin-Anmeldung wie bisher', async () => { const res = await login('justin@webklar.com', 'ohne-ohne-ohne') assert.equal(res.status, 200) assert.equal(res.body.role, 'admin') assert.ok(cookieNamed(res, SESSION)) }) test('Mitarbeiter mit 2FA: vor dem Code keine Portal-Sitzung', async () => { const res = await login('kenso@webklar.com', 'mit-mit-mit') assert.equal(res.status, 200) assert.deepEqual(res.body, { mfaRequired: true, email: 'k…@webklar.com' }) assert.ok(cookieNamed(res, 'webklar_portal_mfa')) assert.equal(cookieNamed(res, SESSION), null) assert.equal(setCookies(res).length, 1) const me = await rawGet('/api/auth/me', { cookie: jar(res) }) assert.equal(me.body.authenticated, false) }) test('Richtiger Code: Admin-Sitzung, Code-Cookie geloescht', async () => { const first = await login('kenso@webklar.com', 'mit-mit-mit') const res = await post('/api/auth/login/mfa', { otp: aw.lastOtp('u_mfa') }, { cookie: jar(first) }) assert.equal(res.status, 200) assert.equal(res.body.role, 'admin') assert.equal(res.body.admin.isAdmin, true) assert.ok(cookieNamed(res, SESSION)) assert.match(cookieNamed(res, 'webklar_portal_mfa'), /Expires=Thu, 01 Jan 1970/) const me = await rawGet('/api/auth/me', { cookie: jar(res) }) assert.equal(me.body.role, 'admin') }) test('Falscher Code: Hinweis, nach 5 Fehlversuchen neu anmelden', async () => { const first = await login('kenso@webklar.com', 'mit-mit-mit') for (let i = 0; i < 5; i++) { const res = await post('/api/auth/login/mfa', { otp: '000000' }, { cookie: jar(first) }) assert.equal(res.status, 401) assert.equal(res.body.error, 'Code falsch oder abgelaufen.') assert.equal(cookieNamed(res, SESSION), null) } const res = await post('/api/auth/login/mfa', { otp: aw.lastOtp('u_mfa') }, { cookie: jar(first) }) assert.equal(res.status, 410) assert.equal(res.body.error, 'Bitte melde dich erneut an.') }) test('Ohne Code-Cookie: neu anmelden', async () => { const res = await post('/api/auth/login/mfa', { otp: '123456' }) assert.equal(res.status, 410) const resend = await post('/api/auth/login/mfa/resend', {}) assert.equal(resend.status, 410) }) test('Code erneut senden: zweite Mail', async () => { const first = await login('kenso@webklar.com', 'mit-mit-mit') const before = aw.mailsSent('u_mfa') const res = await post('/api/auth/login/mfa/resend', {}, { cookie: jar(first) }) assert.equal(res.status, 200) assert.equal(aw.mailsSent('u_mfa'), before + 1) }) test('2FA an, E-Mail nicht bestaetigt: Hinweis statt Sitzung', async () => { aw.addUser({ $id: 'u_unbest', email: 'andrej@webklar.com', password: 'unbest-unbest', mfa: true, emailVerification: false }) aw.seed('employees', { $id: 'e3', userId: 'u_unbest', displayName: 'Andrej', email: 'andrej@webklar.com' }) const res = await login('andrej@webklar.com', 'unbest-unbest') assert.equal(res.status, 403) assert.equal(res.body.error, '2FA ist für dein Konto noch nicht eingerichtet – bitte Kenso Bescheid geben.') assert.equal(cookieNamed(res, SESSION), null) }) test('2FA-Pruefung schlaegt nicht offen fehl, wenn der Konto-Abruf scheitert', async () => { aw.failUserLookups(true) try { const res = await login('kenso@webklar.com', 'mit-mit-mit') assert.equal(cookieNamed(res, SESSION), null) assert.equal(res.body.mfaRequired, true) } finally { aw.failUserLookups(false) } })