Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2024-12-18 10:40:42 +01:00
Some Backend Admin fixes and updates (#5272)
* Some Backend Admin fixes and updates - Updated datatables - Added a `X-Robots-Tags` header to prevent indexing - Modified some layout settings - Added Websocket check to diagnostics - Added Security Header checks to diagnostics - Added Error page response checks to diagnostics - Modifed support string layout a bit Signed-off-by: BlackDex <black.dex@gmail.com> * Some small fixes Signed-off-by: BlackDex <black.dex@gmail.com> --------- Signed-off-by: BlackDex <black.dex@gmail.com>
Dieser Commit ist enthalten in:
Ursprung
620ad92331
Commit
45e5f06b86
8 geänderte Dateien mit 1194 neuen und 512 gelöschten Zeilen
|
@ -62,6 +62,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
diagnostics,
|
diagnostics,
|
||||||
get_diagnostics_config,
|
get_diagnostics_config,
|
||||||
resend_user_invite,
|
resend_user_invite,
|
||||||
|
get_diagnostics_http,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -713,6 +714,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn)
|
||||||
"ip_header_name": ip_header_name,
|
"ip_header_name": ip_header_name,
|
||||||
"ip_header_config": &CONFIG.ip_header(),
|
"ip_header_config": &CONFIG.ip_header(),
|
||||||
"uses_proxy": uses_proxy,
|
"uses_proxy": uses_proxy,
|
||||||
|
"enable_websocket": &CONFIG.enable_websocket(),
|
||||||
"db_type": *DB_TYPE,
|
"db_type": *DB_TYPE,
|
||||||
"db_version": get_sql_server_version(&mut conn).await,
|
"db_version": get_sql_server_version(&mut conn).await,
|
||||||
"admin_url": format!("{}/diagnostics", admin_url()),
|
"admin_url": format!("{}/diagnostics", admin_url()),
|
||||||
|
@ -734,6 +736,11 @@ fn get_diagnostics_config(_token: AdminToken) -> Json<Value> {
|
||||||
Json(support_json)
|
Json(support_json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/diagnostics/http?<code>")]
|
||||||
|
fn get_diagnostics_http(code: u16, _token: AdminToken) -> EmptyResult {
|
||||||
|
err_code!(format!("Testing error {code} response"), code);
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/config", data = "<data>")]
|
#[post("/config", data = "<data>")]
|
||||||
fn post_config(data: Json<ConfigBuilder>, _token: AdminToken) -> EmptyResult {
|
fn post_config(data: Json<ConfigBuilder>, _token: AdminToken) -> EmptyResult {
|
||||||
let data: ConfigBuilder = data.into_inner();
|
let data: ConfigBuilder = data.into_inner();
|
||||||
|
|
4
src/static/scripts/admin.css
gevendort
4
src/static/scripts/admin.css
gevendort
|
@ -38,8 +38,8 @@ img {
|
||||||
max-width: 130px;
|
max-width: 130px;
|
||||||
}
|
}
|
||||||
#users-table .vw-actions, #orgs-table .vw-actions {
|
#users-table .vw-actions, #orgs-table .vw-actions {
|
||||||
min-width: 130px;
|
min-width: 135px;
|
||||||
max-width: 130px;
|
max-width: 140px;
|
||||||
}
|
}
|
||||||
#users-table .vw-org-cell {
|
#users-table .vw-org-cell {
|
||||||
max-height: 120px;
|
max-height: 120px;
|
||||||
|
|
212
src/static/scripts/admin_diagnostics.js
gevendort
212
src/static/scripts/admin_diagnostics.js
gevendort
|
@ -7,6 +7,8 @@ var timeCheck = false;
|
||||||
var ntpTimeCheck = false;
|
var ntpTimeCheck = false;
|
||||||
var domainCheck = false;
|
var domainCheck = false;
|
||||||
var httpsCheck = false;
|
var httpsCheck = false;
|
||||||
|
var websocketCheck = false;
|
||||||
|
var httpResponseCheck = false;
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// Date & Time Check
|
// Date & Time Check
|
||||||
|
@ -76,18 +78,15 @@ async function generateSupportString(event, dj) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
let supportString = "### Your environment (Generated via diagnostics page)\n";
|
let supportString = "### Your environment (Generated via diagnostics page)\n\n";
|
||||||
|
|
||||||
supportString += `* Vaultwarden version: v${dj.current_release}\n`;
|
supportString += `* Vaultwarden version: v${dj.current_release}\n`;
|
||||||
supportString += `* Web-vault version: v${dj.web_vault_version}\n`;
|
supportString += `* Web-vault version: v${dj.web_vault_version}\n`;
|
||||||
supportString += `* OS/Arch: ${dj.host_os}/${dj.host_arch}\n`;
|
supportString += `* OS/Arch: ${dj.host_os}/${dj.host_arch}\n`;
|
||||||
supportString += `* Running within a container: ${dj.running_within_container} (Base: ${dj.container_base_image})\n`;
|
supportString += `* Running within a container: ${dj.running_within_container} (Base: ${dj.container_base_image})\n`;
|
||||||
supportString += "* Environment settings overridden: ";
|
supportString += `* Database type: ${dj.db_type}\n`;
|
||||||
if (dj.overrides != "") {
|
supportString += `* Database version: ${dj.db_version}\n`;
|
||||||
supportString += "true\n";
|
supportString += `* Environment settings overridden!: ${dj.overrides !== ""}\n`;
|
||||||
} else {
|
|
||||||
supportString += "false\n";
|
|
||||||
}
|
|
||||||
supportString += `* Uses a reverse proxy: ${dj.ip_header_exists}\n`;
|
supportString += `* Uses a reverse proxy: ${dj.ip_header_exists}\n`;
|
||||||
if (dj.ip_header_exists) {
|
if (dj.ip_header_exists) {
|
||||||
supportString += `* IP Header check: ${dj.ip_header_match} (${dj.ip_header_name})\n`;
|
supportString += `* IP Header check: ${dj.ip_header_match} (${dj.ip_header_name})\n`;
|
||||||
|
@ -99,11 +98,12 @@ async function generateSupportString(event, dj) {
|
||||||
supportString += `* Server/NTP Time Check: ${ntpTimeCheck}\n`;
|
supportString += `* Server/NTP Time Check: ${ntpTimeCheck}\n`;
|
||||||
supportString += `* Domain Configuration Check: ${domainCheck}\n`;
|
supportString += `* Domain Configuration Check: ${domainCheck}\n`;
|
||||||
supportString += `* HTTPS Check: ${httpsCheck}\n`;
|
supportString += `* HTTPS Check: ${httpsCheck}\n`;
|
||||||
supportString += `* Database type: ${dj.db_type}\n`;
|
if (dj.enable_websocket) {
|
||||||
supportString += `* Database version: ${dj.db_version}\n`;
|
supportString += `* Websocket Check: ${websocketCheck}\n`;
|
||||||
supportString += "* Clients used: \n";
|
} else {
|
||||||
supportString += "* Reverse proxy and version: \n";
|
supportString += "* Websocket Check: disabled\n";
|
||||||
supportString += "* Other relevant information: \n";
|
}
|
||||||
|
supportString += `* HTTP Response Checks: ${httpResponseCheck}\n`;
|
||||||
|
|
||||||
const jsonResponse = await fetch(`${BASE_URL}/admin/diagnostics/config`, {
|
const jsonResponse = await fetch(`${BASE_URL}/admin/diagnostics/config`, {
|
||||||
"headers": { "Accept": "application/json" }
|
"headers": { "Accept": "application/json" }
|
||||||
|
@ -113,10 +113,30 @@ async function generateSupportString(event, dj) {
|
||||||
throw new Error(jsonResponse);
|
throw new Error(jsonResponse);
|
||||||
}
|
}
|
||||||
const configJson = await jsonResponse.json();
|
const configJson = await jsonResponse.json();
|
||||||
supportString += "\n### Config (Generated via diagnostics page)\n<details><summary>Show Running Config</summary>\n";
|
|
||||||
supportString += `\n**Environment settings which are overridden:** ${dj.overrides}\n`;
|
|
||||||
supportString += "\n\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n</details>\n";
|
|
||||||
|
|
||||||
|
// Start Config and Details section within a details block which is collapsed by default
|
||||||
|
supportString += "\n### Config & Details (Generated via diagnostics page)\n\n";
|
||||||
|
supportString += "<details><summary>Show Config & Details</summary>\n";
|
||||||
|
|
||||||
|
// Add overrides if they exists
|
||||||
|
if (dj.overrides != "") {
|
||||||
|
supportString += `\n**Environment settings which are overridden:** ${dj.overrides}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add http response check messages if they exists
|
||||||
|
if (httpResponseCheck === false) {
|
||||||
|
supportString += "\n**Failed HTTP Checks:**\n";
|
||||||
|
// We use `innerText` here since that will convert <br> into new-lines
|
||||||
|
supportString += "\n```yaml\n" + document.getElementById("http-response-errors").innerText.trim() + "\n```\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the current config in json form
|
||||||
|
supportString += "\n**Config:**\n";
|
||||||
|
supportString += "\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n";
|
||||||
|
|
||||||
|
supportString += "\n</details>\n";
|
||||||
|
|
||||||
|
// Add the support string to the textbox so it can be viewed and copied
|
||||||
document.getElementById("support-string").textContent = supportString;
|
document.getElementById("support-string").textContent = supportString;
|
||||||
document.getElementById("support-string").classList.remove("d-none");
|
document.getElementById("support-string").classList.remove("d-none");
|
||||||
document.getElementById("copy-support").classList.remove("d-none");
|
document.getElementById("copy-support").classList.remove("d-none");
|
||||||
|
@ -199,6 +219,162 @@ function checkDns(dns_resolved) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchCheckUrl(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
return { headers: response.headers, status: response.status, text: await response.text() };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching ${url}: ${error}`);
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkSecurityHeaders(headers, omit) {
|
||||||
|
let securityHeaders = {
|
||||||
|
"x-frame-options": ["SAMEORIGIN"],
|
||||||
|
"x-content-type-options": ["nosniff"],
|
||||||
|
"referrer-policy": ["same-origin"],
|
||||||
|
"x-xss-protection": ["0"],
|
||||||
|
"x-robots-tag": ["noindex", "nofollow"],
|
||||||
|
"content-security-policy": [
|
||||||
|
"default-src 'self'",
|
||||||
|
"base-uri 'self'",
|
||||||
|
"form-action 'self'",
|
||||||
|
"object-src 'self' blob:",
|
||||||
|
"script-src 'self' 'wasm-unsafe-eval'",
|
||||||
|
"style-src 'self' 'unsafe-inline'",
|
||||||
|
"child-src 'self' https://*.duosecurity.com https://*.duofederal.com",
|
||||||
|
"frame-src 'self' https://*.duosecurity.com https://*.duofederal.com",
|
||||||
|
"frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://*",
|
||||||
|
"img-src 'self' data: https://haveibeenpwned.com",
|
||||||
|
"connect-src 'self' https://api.pwnedpasswords.com https://api.2fa.directory https://app.simplelogin.io/api/ https://app.addy.io/api/ https://api.fastmail.com/ https://api.forwardemail.net",
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let messages = [];
|
||||||
|
for (let header in securityHeaders) {
|
||||||
|
// Skip some headers for specific endpoints if needed
|
||||||
|
if (typeof omit === "object" && omit.includes(header) === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the header exists, check if the contents matches what we expect it to be
|
||||||
|
let headerValue = headers.get(header);
|
||||||
|
if (headerValue !== null) {
|
||||||
|
securityHeaders[header].forEach((expectedValue) => {
|
||||||
|
if (headerValue.indexOf(expectedValue) === -1) {
|
||||||
|
messages.push(`'${header}' does not contain '${expectedValue}'`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
messages.push(`'${header}' is missing!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkHttpResponse() {
|
||||||
|
const [apiConfig, webauthnConnector, notFound, notFoundApi, badRequest, unauthorized, forbidden] = await Promise.all([
|
||||||
|
fetchCheckUrl(`${BASE_URL}/api/config`),
|
||||||
|
fetchCheckUrl(`${BASE_URL}/webauthn-connector.html`),
|
||||||
|
fetchCheckUrl(`${BASE_URL}/admin/does-not-exist`),
|
||||||
|
fetchCheckUrl(`${BASE_URL}/admin/diagnostics/http?code=404`),
|
||||||
|
fetchCheckUrl(`${BASE_URL}/admin/diagnostics/http?code=400`),
|
||||||
|
fetchCheckUrl(`${BASE_URL}/admin/diagnostics/http?code=401`),
|
||||||
|
fetchCheckUrl(`${BASE_URL}/admin/diagnostics/http?code=403`),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const respErrorElm = document.getElementById("http-response-errors");
|
||||||
|
|
||||||
|
// Check and validate the default API header responses
|
||||||
|
let apiErrors = checkSecurityHeaders(apiConfig.headers);
|
||||||
|
if (apiErrors.length >= 1) {
|
||||||
|
respErrorElm.innerHTML += "<b>API calls:</b><br>";
|
||||||
|
apiErrors.forEach((errMsg) => {
|
||||||
|
respErrorElm.innerHTML += `<b>Header:</b> ${errMsg}<br>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the special `-connector.html` headers, these should have some headers omitted.
|
||||||
|
const omitConnectorHeaders = ["x-frame-options", "content-security-policy"];
|
||||||
|
let connectorErrors = checkSecurityHeaders(webauthnConnector.headers, omitConnectorHeaders);
|
||||||
|
omitConnectorHeaders.forEach((header) => {
|
||||||
|
if (webauthnConnector.headers.get(header) !== null) {
|
||||||
|
connectorErrors.push(`'${header}' is present while it should not`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (connectorErrors.length >= 1) {
|
||||||
|
respErrorElm.innerHTML += "<b>2FA Connector calls:</b><br>";
|
||||||
|
connectorErrors.forEach((errMsg) => {
|
||||||
|
respErrorElm.innerHTML += `<b>Header:</b> ${errMsg}<br>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check specific error code responses if they are not re-written by a reverse proxy
|
||||||
|
let responseErrors = [];
|
||||||
|
if (notFound.status !== 404 || notFound.text.indexOf("return to the web-vault") === -1) {
|
||||||
|
responseErrors.push("404 (Not Found) HTML is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notFoundApi.status !== 404 || notFoundApi.text.indexOf("\"message\":\"Testing error 404 response\",") === -1) {
|
||||||
|
responseErrors.push("404 (Not Found) JSON is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (badRequest.status !== 400 || badRequest.text.indexOf("\"message\":\"Testing error 400 response\",") === -1) {
|
||||||
|
responseErrors.push("400 (Bad Request) is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unauthorized.status !== 401 || unauthorized.text.indexOf("\"message\":\"Testing error 401 response\",") === -1) {
|
||||||
|
responseErrors.push("401 (Unauthorized) is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forbidden.status !== 403 || forbidden.text.indexOf("\"message\":\"Testing error 403 response\",") === -1) {
|
||||||
|
responseErrors.push("403 (Forbidden) is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseErrors.length >= 1) {
|
||||||
|
respErrorElm.innerHTML += "<b>HTTP error responses:</b><br>";
|
||||||
|
responseErrors.forEach((errMsg) => {
|
||||||
|
respErrorElm.innerHTML += `<b>Response to:</b> ${errMsg}<br>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseErrors.length >= 1 || connectorErrors.length >= 1 || apiErrors.length >= 1) {
|
||||||
|
document.getElementById("http-response-warning").classList.remove("d-none");
|
||||||
|
} else {
|
||||||
|
httpResponseCheck = true;
|
||||||
|
document.getElementById("http-response-success").classList.remove("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchWsUrl(wsUrl) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const ws = new WebSocket(wsUrl);
|
||||||
|
ws.onopen = () => {
|
||||||
|
ws.close();
|
||||||
|
resolve(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = () => {
|
||||||
|
reject(false);
|
||||||
|
};
|
||||||
|
} catch (_) {
|
||||||
|
reject(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkWebsocketConnection() {
|
||||||
|
// Test Websocket connections via the anonymous (login with device) connection
|
||||||
|
const isConnected = await fetchWsUrl(`${BASE_URL}/notifications/anonymous-hub?token=admin-diagnostics`).catch(() => false);
|
||||||
|
if (isConnected) {
|
||||||
|
websocketCheck = true;
|
||||||
|
document.getElementById("websocket-success").classList.remove("d-none");
|
||||||
|
} else {
|
||||||
|
document.getElementById("websocket-error").classList.remove("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function init(dj) {
|
function init(dj) {
|
||||||
// Time check
|
// Time check
|
||||||
document.getElementById("time-browser-string").textContent = browserUTC;
|
document.getElementById("time-browser-string").textContent = browserUTC;
|
||||||
|
@ -225,6 +401,12 @@ function init(dj) {
|
||||||
|
|
||||||
// DNS Check
|
// DNS Check
|
||||||
checkDns(dj.dns_resolved);
|
checkDns(dj.dns_resolved);
|
||||||
|
|
||||||
|
checkHttpResponse();
|
||||||
|
|
||||||
|
if (dj.enable_websocket) {
|
||||||
|
checkWebsocketConnection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// onLoad events
|
// onLoad events
|
||||||
|
|
42
src/static/scripts/datatables.css
gevendort
42
src/static/scripts/datatables.css
gevendort
|
@ -4,10 +4,10 @@
|
||||||
*
|
*
|
||||||
* To rebuild or modify this file with the latest versions of the included
|
* To rebuild or modify this file with the latest versions of the included
|
||||||
* software please visit:
|
* software please visit:
|
||||||
* https://datatables.net/download/#bs5/dt-2.0.8
|
* https://datatables.net/download/#bs5/dt-2.1.8
|
||||||
*
|
*
|
||||||
* Included libraries:
|
* Included libraries:
|
||||||
* DataTables 2.0.8
|
* DataTables 2.1.8
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@charset "UTF-8";
|
@charset "UTF-8";
|
||||||
|
@ -45,15 +45,21 @@ table.dataTable tr.dt-hasChild td.dt-control:before {
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark table.dataTable td.dt-control:before,
|
html.dark table.dataTable td.dt-control:before,
|
||||||
:root[data-bs-theme=dark] table.dataTable td.dt-control:before {
|
:root[data-bs-theme=dark] table.dataTable td.dt-control:before,
|
||||||
|
:root[data-theme=dark] table.dataTable td.dt-control:before {
|
||||||
border-left-color: rgba(255, 255, 255, 0.5);
|
border-left-color: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
|
html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
|
||||||
:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
|
:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before,
|
||||||
|
:root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
|
||||||
border-top-color: rgba(255, 255, 255, 0.5);
|
border-top-color: rgba(255, 255, 255, 0.5);
|
||||||
border-left-color: transparent;
|
border-left-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.dt-scroll {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
div.dt-scroll-body thead tr,
|
div.dt-scroll-body thead tr,
|
||||||
div.dt-scroll-body tfoot tr {
|
div.dt-scroll-body tfoot tr {
|
||||||
height: 0;
|
height: 0;
|
||||||
|
@ -377,6 +383,31 @@ table.table.dataTable.table-hover > tbody > tr.selected:hover > * {
|
||||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
|
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.dt-container div.dt-layout-start > *:not(:last-child) {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
div.dt-container div.dt-layout-end > *:not(:first-child) {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
div.dt-container div.dt-layout-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
div.dt-container div.dt-layout-full > *:only-child {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
div.dt-container div.dt-layout-table > div {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
div.dt-container div.dt-layout-start > *:not(:last-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
div.dt-container div.dt-layout-end > *:not(:first-child) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
div.dt-container div.dt-length label {
|
div.dt-container div.dt-length label {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -400,9 +431,6 @@ div.dt-container div.dt-search input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
div.dt-container div.dt-info {
|
|
||||||
padding-top: 0.85em;
|
|
||||||
}
|
|
||||||
div.dt-container div.dt-paging {
|
div.dt-container div.dt-paging {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
1398
src/static/scripts/datatables.js
gevendort
1398
src/static/scripts/datatables.js
gevendort
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
|
@ -132,6 +132,21 @@
|
||||||
<span class="d-block" title="We have direct internet access, no outgoing proxy configured."><b>No</b></span>
|
<span class="d-block" title="We have direct internet access, no outgoing proxy configured."><b>No</b></span>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</dd>
|
</dd>
|
||||||
|
<dt class="col-sm-5">Websocket enabled
|
||||||
|
{{#if page_data.enable_websocket}}
|
||||||
|
<span class="badge bg-success d-none" id="websocket-success" title="Websocket connection is working.">Ok</span>
|
||||||
|
<span class="badge bg-danger d-none" id="websocket-error" title="Websocket connection error, validate your reverse proxy configuration!">Error</span>
|
||||||
|
{{/if}}
|
||||||
|
</dt>
|
||||||
|
<dd class="col-sm-7">
|
||||||
|
{{#if page_data.enable_websocket}}
|
||||||
|
<span class="d-block" title="Websocket connections are enabled (ENABLE_WEBSOCKET is true)."><b>Yes</b></span>
|
||||||
|
{{/if}}
|
||||||
|
{{#unless page_data.enable_websocket}}
|
||||||
|
<span class="d-block" title="Websocket connections are disabled (ENABLE_WEBSOCKET is false)."><b>No</b></span>
|
||||||
|
{{/unless}}
|
||||||
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-5">DNS (github.com)
|
<dt class="col-sm-5">DNS (github.com)
|
||||||
<span class="badge bg-success d-none" id="dns-success" title="DNS Resolving works!">Ok</span>
|
<span class="badge bg-success d-none" id="dns-success" title="DNS Resolving works!">Ok</span>
|
||||||
<span class="badge bg-danger d-none" id="dns-warning" title="DNS Resolving failed. Please fix.">Error</span>
|
<span class="badge bg-danger d-none" id="dns-warning" title="DNS Resolving failed. Please fix.">Error</span>
|
||||||
|
@ -167,6 +182,14 @@
|
||||||
<span id="domain-server" class="d-block"><b>Server:</b> <span id="domain-server-string">{{page_data.admin_url}}</span></span>
|
<span id="domain-server" class="d-block"><b>Server:</b> <span id="domain-server-string">{{page_data.admin_url}}</span></span>
|
||||||
<span id="domain-browser" class="d-block"><b>Browser:</b> <span id="domain-browser-string"></span></span>
|
<span id="domain-browser" class="d-block"><b>Browser:</b> <span id="domain-browser-string"></span></span>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-5">HTTP Response validation
|
||||||
|
<span class="badge bg-success d-none" id="http-response-success" title="All headers and HTTP request responses seem to be ok.">Ok</span>
|
||||||
|
<span class="badge bg-danger d-none" id="http-response-warning" title="Some headers or HTTP request responses return invalid data!">Error</span>
|
||||||
|
</dt>
|
||||||
|
<dd class="col-sm-7">
|
||||||
|
<span id="http-response-errors" class="d-block"></span>
|
||||||
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{email}}">
|
<svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{email}}">
|
||||||
<div class="float-start">
|
<div>
|
||||||
<strong>{{name}}</strong>
|
<strong>{{name}}</strong>
|
||||||
<span class="d-block">{{email}}</span>
|
<span class="d-block">{{email}}</span>
|
||||||
<span class="d-block">
|
<span class="d-block">
|
||||||
|
|
|
@ -51,9 +51,11 @@ impl Fairing for AppHeaders {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: When modifying or adding security headers be sure to also update the diagnostic checks in `src/static/scripts/admin_diagnostics.js` in `checkSecurityHeaders`
|
||||||
res.set_raw_header("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()");
|
res.set_raw_header("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()");
|
||||||
res.set_raw_header("Referrer-Policy", "same-origin");
|
res.set_raw_header("Referrer-Policy", "same-origin");
|
||||||
res.set_raw_header("X-Content-Type-Options", "nosniff");
|
res.set_raw_header("X-Content-Type-Options", "nosniff");
|
||||||
|
res.set_raw_header("X-Robots-Tag", "noindex, nofollow");
|
||||||
// Obsolete in modern browsers, unsafe (XS-Leak), and largely replaced by CSP
|
// Obsolete in modern browsers, unsafe (XS-Leak), and largely replaced by CSP
|
||||||
res.set_raw_header("X-XSS-Protection", "0");
|
res.set_raw_header("X-XSS-Protection", "0");
|
||||||
|
|
||||||
|
|
Laden …
In neuem Issue referenzieren