From bcd32eefe5fe339f57e5de462755278e2d3ee545 Mon Sep 17 00:00:00 2001 From: UNITRONIX <36471318+UNITRONIX@users.noreply.github.com> Date: Sat, 6 Jun 2026 00:45:30 +0200 Subject: [PATCH] feat(permissions): add server attestation permission and update related UI - Introduced a new permission for server attestation, allowing users to run and view server performance attestations. - Updated localization files in English and Polish to include descriptions for the new permission. - Modified authentication middleware and various UI components to integrate the server attestation feature, ensuring proper access control and visibility in the sidebar. - Enhanced the server attestation page to handle rendering and error management effectively. --- betterdesk-server/auth/permissions.go | 9 ++--- web-nodejs/lang/en.json | 4 ++- web-nodejs/lang/pl.json | 4 ++- web-nodejs/middleware/auth.js | 2 +- web-nodejs/public/js/permissions.js | 2 +- .../routes/server-attestation.routes.js | 28 +++++++++------ web-nodejs/views/partials/sidebar.ejs | 4 ++- web-nodejs/views/server-attestation.ejs | 35 ++++++++++++------- 8 files changed, 57 insertions(+), 31 deletions(-) diff --git a/betterdesk-server/auth/permissions.go b/betterdesk-server/auth/permissions.go index 6b67e6e7..b28f2aba 100644 --- a/betterdesk-server/auth/permissions.go +++ b/betterdesk-server/auth/permissions.go @@ -24,8 +24,9 @@ const ( PermUserDelete = "user.delete" // Server configuration - PermServerConfig = "server.config" // read/write server_config - PermServerKeys = "server.keys" // manage API keys + PermServerConfig = "server.config" // read/write server_config + PermServerKeys = "server.keys" // manage API keys + PermServerAttestation = "server.attestation" // run/view server performance attestation // Organization permissions PermOrgCreate = "org.create" @@ -61,7 +62,7 @@ var AllPermissions = []string{ PermDeviceView, PermDeviceConnect, PermDeviceEdit, PermDeviceDelete, PermDeviceBan, PermDeviceChangeID, PermUserView, PermUserCreate, PermUserEdit, PermUserDelete, - PermServerConfig, PermServerKeys, + PermServerConfig, PermServerKeys, PermServerAttestation, PermOrgCreate, PermOrgEdit, PermOrgDelete, PermOrgManageUsers, PermOrgManageDevices, PermAuditView, PermMetricsView, PermBlocklistEdit, PermCDAPView, PermCDAPCommand, PermCDAPTerminal, PermCDAPFiles, @@ -88,7 +89,7 @@ var DefaultRolePermissions = map[string]map[string]bool{ // Server Admin: infrastructure + monitoring, read-only user visibility. // Cannot create/edit/delete users, cannot manage orgs. RoleServerAdmin: buildPermMap([]string{ - PermServerConfig, PermServerKeys, + PermServerConfig, PermServerKeys, PermServerAttestation, PermBlocklistEdit, PermUserView, // read-only PermDeviceView, // read-only diff --git a/web-nodejs/lang/en.json b/web-nodejs/lang/en.json index 6fd8de6c..073386d6 100644 --- a/web-nodejs/lang/en.json +++ b/web-nodejs/lang/en.json @@ -2130,6 +2130,7 @@ "perm_user_delete": "Delete users", "perm_server_config": "Server configuration", "perm_server_keys": "Manage API keys", + "perm_server_attestation": "Server performance attestation", "perm_org_create": "Create organizations", "perm_org_edit": "Edit organizations", "perm_org_delete": "Delete organizations", @@ -3546,6 +3547,7 @@ "starting": "Starting…", "complete": "Benchmark complete", "signal_peers": "Signal peers", - "relay_sessions": "Relay sessions" + "relay_sessions": "Relay sessions", + "page_error": "Failed to load the Server Attestation page" } } diff --git a/web-nodejs/lang/pl.json b/web-nodejs/lang/pl.json index b51d3766..28e316d2 100644 --- a/web-nodejs/lang/pl.json +++ b/web-nodejs/lang/pl.json @@ -2131,6 +2131,7 @@ "perm_user_delete": "Usuwanie użytkowników", "perm_server_config": "Konfiguracja serwera", "perm_server_keys": "Zarządzanie kluczami API", + "perm_server_attestation": "Atest wydajności serwera", "perm_org_create": "Tworzenie organizacji", "perm_org_edit": "Edycja organizacji", "perm_org_delete": "Usuwanie organizacji", @@ -3547,6 +3548,7 @@ "starting": "Uruchamianie…", "complete": "Benchmark zakończony", "signal_peers": "Peery signal", - "relay_sessions": "Sesje relay" + "relay_sessions": "Sesje relay", + "page_error": "Nie udało się załadować strony atestu serwera" } } diff --git a/web-nodejs/middleware/auth.js b/web-nodejs/middleware/auth.js index c53db888..93123741 100644 --- a/web-nodejs/middleware/auth.js +++ b/web-nodejs/middleware/auth.js @@ -17,7 +17,7 @@ const DEFAULT_ROLE_PERMISSIONS = { // Server Admin: infrastructure + read-only users server_admin: new Set([ - 'server.config', 'server.keys', + 'server.config', 'server.keys', 'server.attestation', 'blocklist.edit', 'user.view', 'device.view', diff --git a/web-nodejs/public/js/permissions.js b/web-nodejs/public/js/permissions.js index 06cb6fee..8826a4f3 100644 --- a/web-nodejs/public/js/permissions.js +++ b/web-nodejs/public/js/permissions.js @@ -39,7 +39,7 @@ { id: 'server', icon: 'dns', - permissions: ['server.config', 'server.keys'] + permissions: ['server.config', 'server.keys', 'server.attestation'] }, { id: 'org', diff --git a/web-nodejs/routes/server-attestation.routes.js b/web-nodejs/routes/server-attestation.routes.js index 5ca8a8a7..8768e4c2 100644 --- a/web-nodejs/routes/server-attestation.routes.js +++ b/web-nodejs/routes/server-attestation.routes.js @@ -9,7 +9,7 @@ const router = express.Router(); const { requireAuth, requirePermission } = require('../middleware/auth'); const attestation = require('../services/serverAttestation'); -const REQUIRED_PERMISSION = 'server.config'; +const REQUIRED_PERMISSION = 'server.attestation'; const auth = [requireAuth, requirePermission(REQUIRED_PERMISSION)]; let runPromise = null; @@ -17,15 +17,23 @@ let runPromise = null; // ─── Page ───────────────────────────────────────────────────────────────────── router.get('/server-attestation', ...auth, async (req, res) => { - const lastResult = await attestation.getLastResult(); - res.render('server-attestation', { - title: req.t('server_attestation.title'), - pageStyles: ['server-attestation'], - pageScripts: ['server-attestation'], - currentPage: 'server-attestation', - breadcrumb: [{ label: req.t('server_attestation.title') }], - lastResult - }); + try { + const lastResult = await attestation.getLastResult(); + res.render('server-attestation', { + title: req.t('server_attestation.title'), + pageStyles: ['server-attestation'], + pageScripts: ['server-attestation'], + currentPage: 'server-attestation', + breadcrumb: [{ label: req.t('server_attestation.title') }], + lastResult + }); + } catch (err) { + console.error('[ServerAttestation] page render failed:', err); + res.status(500).render('errors/500', { + title: 'Error', + message: req.t('server_attestation.page_error') || 'Failed to load Server Attestation page' + }); + } }); // ─── Authenticated API ──────────────────────────────────────────────────────── diff --git a/web-nodejs/views/partials/sidebar.ejs b/web-nodejs/views/partials/sidebar.ejs index 407f70e8..07144a48 100644 --- a/web-nodejs/views/partials/sidebar.ejs +++ b/web-nodejs/views/partials/sidebar.ejs @@ -48,6 +48,8 @@ data-category="server-mgmt" title="<%= _('nav.server_management') || 'Server Management' %>"> dns + <% } %> + <% if (hasPermission('server.attestation')) { %> - @@ -66,7 +77,7 @@ hub

${_('server_attestation.max_connections')}

-
${(typeof lastResult !== 'undefined' && lastResult && lastResult.maxConnections) ? lastResult.maxConnections : '—'}
+
${saHasResult && lastResult.maxConnections ? lastResult.maxConnections : '—'}
@@ -77,9 +88,9 @@ - - - + + +
CPU${(typeof lastResult !== 'undefined' && lastResult && lastResult.finalMetrics) ? lastResult.finalMetrics.cpu + '%' : '—'}
RAM${(typeof lastResult !== 'undefined' && lastResult && lastResult.finalMetrics) ? lastResult.finalMetrics.mem + '%' : '—'}
${_('server_attestation.disk')}${(typeof lastResult !== 'undefined' && lastResult && lastResult.finalMetrics) ? lastResult.finalMetrics.disk + '%' : '—'}
CPU${saHasResult && lastResult.finalMetrics ? lastResult.finalMetrics.cpu + '%' : '—'}
RAM${saHasResult && lastResult.finalMetrics ? lastResult.finalMetrics.mem + '%' : '—'}
${_('server_attestation.disk')}${saHasResult && lastResult.finalMetrics ? lastResult.finalMetrics.disk + '%' : '—'}