Keine Beschreibung
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

e2e.js 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // End-to-end test of the backend platform.
  2. // Exercises the full flow: register -> enable MFA -> login (2 steps) ->
  3. // enroll machine -> agent comes online -> technician requests session ->
  4. // consent -> signaling relay -> audit trail. No browser/Electron needed:
  5. // the "agent" and "viewer" are raw WebSocket clients.
  6. process.env.DB_PATH = '/tmp/ra-e2e.db';
  7. const fs = require('fs');
  8. for (const f of ['/tmp/ra-e2e.db', '/tmp/ra-e2e.db-wal', '/tmp/ra-e2e.db-shm']) { try { fs.unlinkSync(f); } catch {} }
  9. const PORT = 8099;
  10. process.env.PORT = PORT;
  11. const { server } = require('../server');
  12. const A = require('../auth');
  13. const WebSocket = require('ws');
  14. const BASE = `http://localhost:${PORT}`;
  15. let passed = 0, failed = 0;
  16. function check(name, cond) {
  17. if (cond) { console.log(' ok -', name); passed++; }
  18. else { console.log(' FAIL-', name); failed++; }
  19. }
  20. // minimal cookie-aware fetch
  21. async function call(path, body, cookie) {
  22. const r = await fetch(BASE + path, {
  23. method: 'POST',
  24. headers: { 'Content-Type': 'application/json', ...(cookie ? { Cookie: cookie } : {}) },
  25. body: body ? JSON.stringify(body) : undefined,
  26. });
  27. const setCookie = r.headers.get('set-cookie');
  28. const data = await r.json().catch(() => ({}));
  29. return { status: r.status, data, cookie: setCookie ? setCookie.split(';')[0] : cookie };
  30. }
  31. async function get(path, cookie) {
  32. const r = await fetch(BASE + path, { headers: cookie ? { Cookie: cookie } : {} });
  33. return { status: r.status, data: await r.json().catch(() => ({})) };
  34. }
  35. const wait = (ms) => new Promise((r) => setTimeout(r, ms));
  36. function wsClient() {
  37. const ws = new WebSocket(`ws://localhost:${PORT}/ws`);
  38. ws.q = [];
  39. ws.on('message', (d) => ws.q.push(JSON.parse(d)));
  40. return ws;
  41. }
  42. function nextMsg(ws, type, timeout = 3000) {
  43. return new Promise((resolve, reject) => {
  44. const start = Date.now();
  45. (function poll() {
  46. const i = ws.q.findIndex((m) => m.type === type);
  47. if (i >= 0) return resolve(ws.q.splice(i, 1)[0]);
  48. if (Date.now() - start > timeout) return reject(new Error('timeout waiting for ' + type));
  49. setTimeout(poll, 20);
  50. })();
  51. });
  52. }
  53. (async () => {
  54. await wait(300); // let server bind
  55. console.log('E2E backend tests:');
  56. // 1. Register
  57. const email = 'tech@example.com';
  58. const reg = await call('/api/register', { email, password: 'supersecret', teamName: 'Acme IT' });
  59. check('register returns mfa setup', reg.status === 200 && reg.data.mfaSetup && reg.data.mfaSetup.secret);
  60. const secret = reg.data.mfaSetup.secret;
  61. // 2. Login before MFA enabled — allowed, mfaRequired=false
  62. let login = await call('/api/login', { email, password: 'supersecret' });
  63. check('login sets session cookie', !!login.cookie);
  64. // 3. Enable MFA with a valid TOTP
  65. const enable = await call('/api/mfa/enable', { email, code: A.totp(secret) });
  66. check('mfa enable succeeds with valid code', enable.status === 200);
  67. const badEnable = await call('/api/mfa/enable', { email, code: '000000' });
  68. check('mfa enable rejects bad code', badEnable.status === 401);
  69. // 4. Fresh login now requires MFA
  70. login = await call('/api/login', { email, password: 'supersecret' });
  71. check('login now flags mfaRequired', login.data.mfaRequired === true);
  72. let cookie = login.cookie;
  73. // 5. Protected route blocked until MFA passed
  74. const meBlocked = await get('/api/me', cookie);
  75. check('me blocked before mfa', meBlocked.status === 401);
  76. // 6. Pass MFA
  77. const mfa = await call('/api/login/mfa', { code: A.totp(secret) }, cookie);
  78. check('login mfa step succeeds', mfa.status === 200);
  79. const me = await get('/api/me', cookie);
  80. check('me works after mfa, role=admin', me.status === 200 && me.data.role === 'admin');
  81. // 7. Wrong password rejected
  82. const badLogin = await call('/api/login', { email, password: 'wrong' });
  83. check('wrong password rejected', badLogin.status === 401);
  84. // 8. Enroll a machine (consent-required)
  85. const mach = await call('/api/machines', { name: 'Dana-Laptop', unattended: false }, cookie);
  86. check('machine enrolled, returns token', mach.status === 200 && mach.data.enrollToken);
  87. const enrollToken = mach.data.enrollToken;
  88. // 9. Agent comes online
  89. const agent = wsClient();
  90. await new Promise((r) => agent.on('open', r));
  91. agent.send(JSON.stringify({ type: 'agent-hello', enrollToken }));
  92. const reg2 = await nextMsg(agent, 'agent-registered');
  93. check('agent registers via enroll token', reg2.name === 'Dana-Laptop');
  94. // machine shows online in API
  95. const machines = await get('/api/machines', cookie);
  96. check('machine reports online', machines.data[0].online === true);
  97. // 10. Technician (viewer) requests a session — needs cookie on the WS upgrade
  98. const viewer = new WebSocket(`ws://localhost:${PORT}/ws`, { headers: { Cookie: cookie } });
  99. viewer.q = []; viewer.on('message', (d) => viewer.q.push(JSON.parse(d)));
  100. await new Promise((r) => viewer.on('open', r));
  101. viewer.send(JSON.stringify({ type: 'viewer-connect', machineId: machines.data[0].id }));
  102. const pending = await nextMsg(viewer, 'session-pending');
  103. check('viewer gets session-pending', !!pending.sessionId);
  104. // 11. Agent receives the consent request
  105. const reqMsg = await nextMsg(agent, 'session-request');
  106. check('agent receives session-request with technician email', reqMsg.technician === email);
  107. // 12. Agent grants consent -> both sides proceed
  108. agent.send(JSON.stringify({ type: 'consent', sessionId: reqMsg.sessionId, granted: true }));
  109. const ready = await nextMsg(viewer, 'session-ready');
  110. const startStream = await nextMsg(agent, 'start-stream');
  111. check('consent grant -> viewer session-ready', !!ready);
  112. check('consent grant -> agent start-stream', !!startStream);
  113. // 13. Signaling relay: agent offer reaches viewer; viewer answer reaches agent
  114. agent.send(JSON.stringify({ type: 'offer', sessionId: reqMsg.sessionId, sdp: { fake: 'offer' } }));
  115. const relayedOffer = await nextMsg(viewer, 'offer');
  116. check('offer relayed agent->viewer', relayedOffer.sdp.fake === 'offer');
  117. viewer.send(JSON.stringify({ type: 'answer', sessionId: reqMsg.sessionId, sdp: { fake: 'answer' } }));
  118. const relayedAnswer = await nextMsg(agent, 'answer');
  119. check('answer relayed viewer->agent', relayedAnswer.sdp.fake === 'answer');
  120. // 14. End session
  121. viewer.send(JSON.stringify({ type: 'end-session', sessionId: reqMsg.sessionId }));
  122. await nextMsg(agent, 'session-ended');
  123. check('session-ended delivered to agent', true);
  124. // 15. Audit log captured the full flow
  125. const audit = await get('/api/audit', cookie);
  126. const actions = audit.data.map((a) => a.action);
  127. for (const a of ['user_registered', 'login', 'machine_enrolled', 'session_requested', 'consent_granted', 'session_ended']) {
  128. check(`audit contains "${a}"`, actions.includes(a));
  129. }
  130. // 16. Denial path
  131. viewer.send(JSON.stringify({ type: 'viewer-connect', machineId: machines.data[0].id }));
  132. const pending2 = await nextMsg(viewer, 'session-pending');
  133. const req2 = await nextMsg(agent, 'session-request');
  134. agent.send(JSON.stringify({ type: 'consent', sessionId: req2.sessionId, granted: false }));
  135. const denied = await nextMsg(viewer, 'session-denied');
  136. check('consent denial -> viewer session-denied', !!denied);
  137. agent.close(); viewer.close();
  138. console.log(`\n${passed} passed, ${failed} failed.`);
  139. server.close();
  140. process.exit(failed ? 1 : 0);
  141. })().catch((e) => { console.error('E2E ERROR:', e); process.exit(1); });