From 8e642163337f7219f86a4493d264517af7595537 Mon Sep 17 00:00:00 2001 From: Paul Fitzpatrick Date: Thu, 2 May 2024 16:45:01 -0400 Subject: [PATCH] clean up formatting and pass more helpful information --- app/client/ui/AdminPanel.ts | 52 +++++++++++++++--------------------- app/server/lib/BootProbes.ts | 41 +++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 37 deletions(-) diff --git a/app/client/ui/AdminPanel.ts b/app/client/ui/AdminPanel.ts index b8e9563a..464eefdb 100644 --- a/app/client/ui/AdminPanel.ts +++ b/app/client/ui/AdminPanel.ts @@ -471,22 +471,7 @@ isolated from other documents and isolated from the network.'), info: BootProbeInfo, result: BootProbeResult, details: ProbeDetails|undefined) { - const out: (HTMLElement|string|null)[] = []; - if (result.verdict) { - out.push(dom('pre', result.verdict)); - } - if (result.success !== undefined) { - out.push(dom('p', - result.success ? 'Check succeeded.' : 'Check failed.')); - } - if (result.details) { - for (const [key, val] of Object.entries(result.details)) { - out.push(dom( - 'div', - cssLabel(key), - dom('input', dom.prop('value', JSON.stringify(val))))); - } - } + const status = (result.success !== undefined) ? (result.success ? '✅' : '❗') : '―'; @@ -496,21 +481,31 @@ isolated from other documents and isolated from the network.'), description: info.name, value: cssStatus(status), expandedContent: [ + cssCheckHeader( + 'Results', + { style: 'margin-top: 0px; padding-top: 0px;' }, + ), result.verdict ? dom('pre', result.verdict) : null, (result.success === undefined) ? null : dom('p', result.success ? 'Check succeeded.' : 'Check failed.'), (result.done !== true) ? null : dom('p', 'No fault detected.'), - (details?.info === undefined) ? null : - cssNote(details.info), - (result.details === undefined) ? null : - Object.entries(result.details).map(([key, val]) => { - return dom( - 'div', - cssLabel(key), - dom('input', dom.prop('value', JSON.stringify(val)))); - }), + (details?.info === undefined) ? null : [ + cssCheckHeader('Notes'), + details.info, + ], + (result.details === undefined) ? null : [ + cssCheckHeader('Details'), + ...Object.entries(result.details).map(([key, val]) => { + return dom( + 'div', + cssLabel(key), + dom('input', dom.prop( + 'value', + typeof val === 'string' ? val : JSON.stringify(val)))); + }), + ], ], }); } @@ -520,12 +515,9 @@ function maybeSwitchToggle(value: Observable): DomContents { return toggle(value, dom.hide((use) => use(value) === null)); } -const cssNote = styled('div', ` - border-left: 2px solid ${theme.lightText}; - padding-left: 5px; - padding-bottom: 5px; - padding-top: 5px; +const cssCheckHeader = styled('h4', ` margin-bottom: 5px; + font-style: italic; `); const cssStatus = styled('div', ` diff --git a/app/server/lib/BootProbes.ts b/app/server/lib/BootProbes.ts index 93dc3829..242ab0d8 100644 --- a/app/server/lib/BootProbes.ts +++ b/app/server/lib/BootProbes.ts @@ -78,18 +78,24 @@ const _homeUrlReachableProbe: Probe = { name: 'Is home page available at expected URL', apply: async (server, req) => { const url = server.getHomeUrl(req); + const details: Record = { + url, + }; try { const resp = await fetch(url); + details.status = resp.status; if (resp.status !== 200) { throw new ApiError(await resp.text(), resp.status); } return { success: true, + details, }; } catch (e) { return { success: false, details: { + ...details, error: String(e), }, severity: 'fault', @@ -105,11 +111,12 @@ const _statusCheckProbe: Probe = { const baseUrl = server.getHomeUrl(req); const url = new URL(baseUrl); url.pathname = removeTrailingSlash(url.pathname) + '/status'; - const details = { + const details: Record = { url: url.href, }; try { const resp = await fetch(url); + details.status = resp.status; if (resp.status !== 200) { throw new Error(`Failed with status ${resp.status}`); } @@ -124,8 +131,10 @@ const _statusCheckProbe: Probe = { } catch (e) { return { success: false, - details, - error: String(e), + details: { + ...details, + error: String(e), + }, severity: 'fault', }; } @@ -136,8 +145,12 @@ const _userProbe: Probe = { id: 'system-user', name: 'Is the system user following best practice', apply: async () => { + const details = { + uid: process.getuid ? process.getuid() : 'unavailable', + }; if (process.getuid && process.getuid() === 0) { return { + details, success: false, verdict: 'User appears to be root (UID 0)', severity: 'warning', @@ -145,6 +158,7 @@ const _userProbe: Probe = { } else { return { success: true, + details, }; } }, @@ -154,12 +168,18 @@ const _bootProbe: Probe = { id: 'boot-page', name: 'Is the boot page adequately protected', apply: async (server) => { - if (!server.hasBoot) { - return { success: true }; + const details: Record = { + bootKeySet: server.hasBoot(), + }; + console.log(details); + if (!server.hasBoot()) { + return { success: true, details }; } - const maybeSecureEnough = String(process.env.GRIST_BOOT_KEY).length > 10; + const bootKeyLength = String(process.env.GRIST_BOOT_KEY).length; + details.bootKeyLength = bootKeyLength; return { - success: maybeSecureEnough, + success: bootKeyLength > 10, + details, severity: 'hmm', }; }, @@ -178,19 +198,26 @@ const _hostHeaderProbe: Probe = { apply: async (server, req) => { const host = req.header('host'); const url = new URL(server.getHomeUrl(req)); + const details = { + homeUrlHost: url.hostname, + headerHost: host, + }; if (url.hostname === 'localhost') { return { done: true, + details, }; } if (String(url.hostname).toLowerCase() !== String(host).toLowerCase()) { return { success: false, + details, severity: 'hmm', }; } return { done: true, + details, }; }, };