const test = require('node:test'); const assert = require('node:assert/strict'); require('../src/common'); const pages = require('../src/web/pages'); const registerViewAsRoutes = require('../src/web/routes/settings/view-as'); const renderUsersPage = require('../src/web/routes/settings/users/list'); const renderUsersEditPage = require('../src/web/routes/settings/users/edit'); const renderRssFeedEditPage = require('../src/web/routes/data-sources/rss-feeds/edit'); const renderWeatherLocationEditPage = require('../src/web/routes/data-sources/weather/edit'); const { hasAssignedPermission, hasPermission, PERMISSIONS } = require('../src/rbac'); test('preview permission is Impersonate > Allow and read-only execution stays denied', () => { const permission = PERMISSIONS.find(function (item) { return item.key === 'impersonate.allow'; }); assert.ok(permission); assert.equal(permission.resourceName, 'Impersonate'); assert.equal(permission.actionName, 'Allow'); assert.ok(!PERMISSIONS.some(function (item) { return item.key === 'users.allow' || item.key === 'users.impersonate' || item.key === 'dashboard.allow'; })); const previewUser = { isViewingAs: true, permissionKeys: ['clients.allow'] }; assert.equal(hasAssignedPermission(previewUser, 'clients.allow'), true); assert.equal(hasPermission(previewUser, 'clients.allow'), false); assert.equal(hasPermission({ permissionKeys: ['clients.allow'] }, 'clients.allow'), true); }); test('View as is on the user edit footer, not the Users table', () => { const user = { id: 19, name: 'Screen operator', username: 'operator', roleNames: 'Operators' }; const administrator = { id: 7, permissionKeys: ['users.read', 'impersonate.allow'] }; const adminPage = renderUsersPage({ users: [user] }, '', administrator); const editPage = renderUsersEditPage(user, '', administrator, [], []); const previewPage = renderUsersPage({ users: [user] }, '', Object.assign({}, administrator, { isViewingAs: true })); const previewEditPage = renderUsersEditPage(user, '', Object.assign({}, administrator, { isViewingAs: true }), [], []); assert.doesNotMatch(adminPage, /view-as/); assert.match(editPage, /form="view-as-user-form"/); assert.match(editPage, /
{ const previewUser = { id: 19, name: 'Screen operator', username: 'operator', isViewingAs: true, authenticatedUser: { id: 7, name: 'Administrator', username: 'admin' } }; const regularPage = pages.renderAboutPage(previewUser); const errorPage = pages.renderErrorPage({ statusCode: 403 }, previewUser); [regularPage, errorPage].forEach(function (html) { assert.match(html, /Read-only preview/); assert.match(html, /Screen operator/); assert.match(html, /signed in as Administrator/); assert.match(html, /action="\/settings\/view-as\/stop"/); assert.match(html, /class="btn btn-sm btn-dark"/); assert.doesNotMatch(html, /btn-outline-dark/); }); }); test('view-as start and stop routes audit the authenticated administrator', async () => { const handlers = {}; const permissionKeys = []; const auditEvents = []; const helperCalls = []; const app = { post(path, ...routeHandlers) { handlers[path] = routeHandlers; } }; const administrator = { id: 7, name: 'Administrator', username: 'admin', permissionKeys: ['users.read', 'impersonate.allow'] }; registerViewAsRoutes(app, { pool: { async query(sql, params) { assert.match(sql, /FROM a_users/); assert.deepEqual(params, [19]); return [[{ id: 19, name: 'Screen operator', username: 'operator' }]]; } }, requirePermission(permissionKey) { permissionKeys.push(permissionKey); return function (_req, _res, next) { next(); }; }, async startViewAsUser(_pool, _req, actorId, targetId) { helperCalls.push(['start', actorId, targetId]); return true; }, async stopViewAsUser(_pool, _req, actorId) { helperCalls.push(['stop', actorId]); return true; }, async recordRequestAuditEvent(_pool, _req, event) { auditEvents.push(event); } }); const startResponse = { redirect(path) { this.redirectedTo = path; }, status(statusCode) { this.statusCode = statusCode; return this; }, send(body) { this.body = body; return this; }, sendStatus(statusCode) { this.statusCode = statusCode; return this; } }; await handlers['/settings/users/:id/view-as'][2]({ params: { id: '19' }, authenticatedUser: administrator, currentUser: administrator }, startResponse, (error) => { throw error; }); const stopResponse = { redirect(path) { this.redirectedTo = path; }, status(statusCode) { this.statusCode = statusCode; return this; }, send(body) { this.body = body; return this; } }; await handlers['/settings/view-as/stop'][0]({ authenticatedUser: administrator, currentUser: { id: 19, username: 'operator', isViewingAs: true } }, stopResponse, (error) => { throw error; }); assert.deepEqual(permissionKeys, ['users.read', 'impersonate.allow']); assert.deepEqual(helperCalls, [['start', 7, 19], ['stop', 7]]); assert.deepEqual(auditEvents.map(function (event) { return event.actorUserId; }), [7, 7]); assert.deepEqual(auditEvents.map(function (event) { return event.eventType; }), ['user.view_as_started', 'user.view_as_stopped']); assert.equal(startResponse.redirectedTo, '/dashboard'); assert.equal(stopResponse.redirectedTo, '/settings/users'); }); test('RSS and weather refresh actions stay visible but disabled when authorized previewing', () => { const rssPage = renderRssFeedEditPage({ id: 31, name: 'Feed', enabled: 1 }, {}, '', { isViewingAs: true, permissionKeys: ['rss-feeds.update', 'rss-feeds.allow'] }); const weatherPage = renderWeatherLocationEditPage({ id: 12, name: 'Forecast', enabled: 1 }, '', { isViewingAs: true, permissionKeys: ['weather.update', 'weather.allow'] }); assert.match(rssPage, /data-manual-refresh-url="\/data-sources\/rss-feeds\/31\/refresh"[^>]*disabled/); assert.match(rssPage, /data-async-data-source-toggle[^>]*disabled/); assert.match(weatherPage, /data-weather-refresh-url="\/data-sources\/weather\/12\/refresh"[^>]*disabled/); assert.match(weatherPage, /data-async-data-source-toggle[^>]*disabled/); });