const assert = require('assert'); const axios = require('axios'); const api = axios.create({ baseURL: 'http://127.0.0.1:3000/api', validateStatus: () => true, }); const SUPER_ADMIN = { email: 'super_admin@flatlogic.com', password: '252a3d31', }; function decodeUserId(token) { const payload = token.split('.')[1]; return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')).user.id; } async function signIn(credentials) { const response = await api.post('/auth/signin/local', credentials); assert.strictEqual(response.status, 200, `Expected signin to succeed for ${credentials.email}, got ${response.status}: ${response.data}`); return response.data; } async function sql(token, sqlQuery) { const response = await api.post( '/sql', { sql: sqlQuery }, { headers: { Authorization: `Bearer ${token}`, }, }, ); assert.strictEqual(response.status, 200, `Expected SQL helper to succeed, got ${response.status}: ${JSON.stringify(response.data)}`); return response.data.rows; } async function createOrganization(token, name) { const response = await api.post( '/organizations', { data: { name } }, { headers: { Authorization: `Bearer ${token}`, }, }, ); assert.strictEqual(response.status, 200, `Expected organization creation to succeed, got ${response.status}: ${response.data}`); const rows = await sql(token, `select id from organizations where name = '${name.replace(/'/g, "''")}' order by "createdAt" desc limit 1`); assert.ok(rows[0]?.id, 'Expected organization lookup to return an id'); return rows[0].id; } async function createTenant(token, { name, subdomain, organizationsId }) { const response = await api.post( '/tenants', { data: { name, subdomain, organizations: organizationsId, default_language: 'fr', primary_currency: 'USD', }, }, { headers: { Authorization: `Bearer ${token}`, }, }, ); assert.strictEqual(response.status, 200, `Expected tenant creation to succeed, got ${response.status}: ${response.data}`); } async function promoteUser(token, { userId, email, organizationsId, roleId }) { const response = await api.put( `/users/${userId}`, { id: userId, data: { email, firstName: email.split('@')[0], emailVerified: true, app_role: roleId, organizations: organizationsId, }, }, { headers: { Authorization: `Bearer ${token}`, }, }, ); assert.strictEqual(response.status, 200, `Expected user promotion to succeed, got ${response.status}: ${response.data}`); } async function signupTenantUser({ subdomain, organizationsId, email, roleId, superAdminToken }) { const signupResponse = await api.post( '/auth/signup', { email, password: 'demo1234', organizationsId, }, { headers: { Host: `${subdomain}.example.com`, Referer: `http://${subdomain}.example.com/register`, }, }, ); assert.strictEqual(signupResponse.status, 200, `Expected tenant signup to succeed, got ${signupResponse.status}: ${signupResponse.data}`); const userId = decodeUserId(signupResponse.data); await promoteUser(superAdminToken, { userId, email, organizationsId, roleId, }); const rows = await sql(superAdminToken, `select "organizationsId" from users where id = '${userId}'`); assert.strictEqual(rows[0].organizationsId, organizationsId, 'Expected signup to persist the resolved organizationsId'); return signIn({ email, password: 'demo1234' }); } function buildContactPayload(suffix) { return { full_name: 'Jean Test', company_name: `Company ${suffix}`, email: `lead-${suffix}@company.com`, phone: '+243800000000', service_line: 'cloud', company_size: '11_50', budget_range: '20_50k', preferred_channel: 'email', urgency: 'urgent', message: 'Nous avons besoin d\'une migration cloud sécurisée et bien cadrée.', }; } describe('tenant isolation and tenant-scoped public intake', function () { this.timeout(120000); it('rejects tenant discovery and contact intake when no valid tenant context is present', async () => { const orgResponse = await api.get('/org-for-auth'); assert.strictEqual(orgResponse.status, 400); assert.match(String(orgResponse.data), /tenant subdomain/i); const intakeResponse = await api.post('/contact-form', buildContactPayload('unknown'), { headers: { Host: 'unknown.example.com', Referer: 'http://unknown.example.com/', }, }); assert.strictEqual(intakeResponse.status, 400); assert.match(String(intakeResponse.data), /No tenant is configured/i); }); it('prevents cross-organization read, update, delete, and direct SQL access by id', async () => { const superAdminToken = await signIn(SUPER_ADMIN); const timestamp = Date.now(); const adminRoleRows = await sql(superAdminToken, "select id from roles where name = 'Administrator' limit 1"); const adminRoleId = adminRoleRows[0].id; const orgA = await createOrganization(superAdminToken, `Isolation Org A ${timestamp}`); const orgB = await createOrganization(superAdminToken, `Isolation Org B ${timestamp}`); const subA = `isolation-a-${timestamp}`; const subB = `isolation-b-${timestamp}`; await createTenant(superAdminToken, { name: `Isolation Tenant A ${timestamp}`, subdomain: subA, organizationsId: orgA, }); await createTenant(superAdminToken, { name: `Isolation Tenant B ${timestamp}`, subdomain: subB, organizationsId: orgB, }); const tenantOrgResponse = await api.get('/org-for-auth', { headers: { Host: `${subA}.example.com`, }, }); assert.strictEqual(tenantOrgResponse.status, 200); assert.strictEqual(tenantOrgResponse.data[0].id, orgA); const userAToken = await signupTenantUser({ subdomain: subA, organizationsId: orgA, email: `tenant-a-${timestamp}@example.com`, roleId: adminRoleId, superAdminToken, }); const userBToken = await signupTenantUser({ subdomain: subB, organizationsId: orgB, email: `tenant-b-${timestamp}@example.com`, roleId: adminRoleId, superAdminToken, }); const intakeResponse = await api.post('/contact-form', buildContactPayload(timestamp), { headers: { Host: `${subA}.example.com`, Referer: `http://${subA}.example.com/`, }, }); assert.strictEqual(intakeResponse.status, 200, JSON.stringify(intakeResponse.data)); const { leadId } = intakeResponse.data; assert.ok(leadId, 'Expected contact form to create a lead'); const readOwnLead = await api.get(`/leads/${leadId}`, { headers: { Authorization: `Bearer ${userAToken}`, }, }); assert.strictEqual(readOwnLead.status, 200); assert.strictEqual(readOwnLead.data.id, leadId); const readOtherLead = await api.get(`/leads/${leadId}`, { headers: { Authorization: `Bearer ${userBToken}`, }, }); assert.strictEqual(readOtherLead.status, 404); const updateOtherLead = await api.put( `/leads/${leadId}`, { id: leadId, data: { status: 'new', }, }, { headers: { Authorization: `Bearer ${userBToken}`, }, }, ); assert.ok([400, 404].includes(updateOtherLead.status), `Expected cross-org update to fail, got ${updateOtherLead.status}`); const deleteOtherLead = await api.delete(`/leads/${leadId}`, { headers: { Authorization: `Bearer ${userBToken}`, }, }); assert.strictEqual(deleteOtherLead.status, 404); const visibleRowsForOwner = await sql(userAToken, `select id from leads where id = '${leadId}'`); assert.strictEqual(visibleRowsForOwner.length, 1, 'Expected owner SQL query to return the lead'); const visibleRowsForOtherOrg = await sql(userBToken, `select id from leads where id = '${leadId}'`); assert.strictEqual(visibleRowsForOtherOrg.length, 0, 'Expected RLS to hide the lead from another organization'); }); });