feat: BizGaze Connect home, BizGaze login, modular backend, /api/v1

User-facing
- New post-login home (/home): chat rail + Share/Connect (embedded) + Meeting; login lives here when logged out
- Landing: "Log in with BizGaze" + no-login screen share
- Console replaced by a role-scoped Dashboard (/dashboard): admins see all team sessions, others see only their own; stats + CSV/PDF export
- Recordings saved as MP4 (H.264/AAC) with WebM fallback; old .webm still downloadable
- Fix: duplicate "Sign in" on the login card

Auth / integration
- BizGaze as identity provider: /api/login validates against BIZGAZE_LOGIN_URL (env-gated) and provisions a local user
- Phase 2 start: /api/v1 alias for all /api routes; Authorization: Bearer accepted across HTTP + WS; login returns a token (for native desktop/mobile clients)

Backend refactor (Phase 1, behavior-preserving)
- Split server.js into config/lib/session/presence/routes/static/signaling + repos (data-access) + bizgaze (service)
- All SQL behind repos.js, tenant-scoped (tenantId == team_id for now)
- e2e updated to current flow (21/21 pass before and after)

Docs: ARCHITECTURE.md (target architecture + phased plan), CLAUDE.md repo layout, .env.example BIZGAZE_LOGIN_URL

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 00:40:07 +05:30
parent f6ebaa7bfb
commit ba8bfc3f46
21 changed files with 2085 additions and 803 deletions
+19 -31
View File
@@ -1,15 +1,20 @@
// End-to-end test of the backend platform.
// Exercises the full flow: register -> enable MFA -> login (2 steps) ->
// enroll machine -> agent comes online -> technician requests session ->
// consent -> signaling relay -> audit trail. No browser/Electron needed:
// the "agent" and "viewer" are raw WebSocket clients.
// Exercises the full flow: register -> login -> enroll machine -> agent online ->
// technician requests session -> consent -> signaling relay -> audit trail.
// No browser/Electron needed: the "agent" and "viewer" are raw WebSocket clients.
// (Login currently marks the session MFA-passed directly, so there is no separate
// TOTP step in the product flow; the MFA endpoints still exist but aren't exercised here.)
process.env.DB_PATH = '/tmp/ra-e2e.db';
const fs = require('fs');
for (const f of ['/tmp/ra-e2e.db', '/tmp/ra-e2e.db-wal', '/tmp/ra-e2e.db-shm']) { try { fs.unlinkSync(f); } catch {} }
const os = require('os');
const path = require('path');
const DB = path.join(os.tmpdir(), 'ra-e2e.db');
process.env.DB_PATH = DB;
for (const f of [DB, DB + '-wal', DB + '-shm']) { try { fs.unlinkSync(f); } catch {} }
const PORT = 8099;
process.env.PORT = PORT;
process.env.HTTPS_PORT = 8444; // avoid clashing with a running dev server on 8443
const { server } = require('../server');
const A = require('../auth');
const WebSocket = require('ws');
@@ -59,38 +64,21 @@ function nextMsg(ws, type, timeout = 3000) {
await wait(300); // let server bind
console.log('E2E backend tests:');
// 1. Register
// 1. Register (first user becomes admin)
const email = 'tech@example.com';
const reg = await call('/api/register', { email, password: 'supersecret', teamName: 'Acme IT' });
check('register returns mfa setup', reg.status === 200 && reg.data.mfaSetup && reg.data.mfaSetup.secret);
const secret = reg.data.mfaSetup.secret;
check('register succeeds', reg.status === 200 && reg.data.ok === true);
// 2. Login before MFA enabled — allowed, mfaRequired=false
let login = await call('/api/login', { email, password: 'supersecret' });
// 2. Login -> session cookie (login marks the session MFA-passed)
const login = await call('/api/login', { email, password: 'supersecret' });
check('login sets session cookie', !!login.cookie);
const cookie = login.cookie;
// 3. Enable MFA with a valid TOTP
const enable = await call('/api/mfa/enable', { email, code: A.totp(secret) });
check('mfa enable succeeds with valid code', enable.status === 200);
const badEnable = await call('/api/mfa/enable', { email, code: '000000' });
check('mfa enable rejects bad code', badEnable.status === 401);
// 4. Fresh login now requires MFA
login = await call('/api/login', { email, password: 'supersecret' });
check('login now flags mfaRequired', login.data.mfaRequired === true);
let cookie = login.cookie;
// 5. Protected route blocked until MFA passed
const meBlocked = await get('/api/me', cookie);
check('me blocked before mfa', meBlocked.status === 401);
// 6. Pass MFA
const mfa = await call('/api/login/mfa', { code: A.totp(secret) }, cookie);
check('login mfa step succeeds', mfa.status === 200);
// 3. Protected route works right after login, role=admin
const me = await get('/api/me', cookie);
check('me works after mfa, role=admin', me.status === 200 && me.data.role === 'admin');
check('me works after login, role=admin', me.status === 200 && me.data.role === 'admin');
// 7. Wrong password rejected
// 4. Wrong password rejected
const badLogin = await call('/api/login', { email, password: 'wrong' });
check('wrong password rejected', badLogin.status === 401);