mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-25 12:27:51 +00:00
tighten up csp
This commit is contained in:
parent
1b8137fe35
commit
37de676069
@ -23,7 +23,7 @@ const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig(
|
||||
|
||||
// Initialize our app. You can pass menu templates into the app here.
|
||||
// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig);
|
||||
const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate);
|
||||
export const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate);
|
||||
|
||||
// If deeplinking is enabled then we will set it up here.
|
||||
if (capacitorFileConfig.electron?.deepLinkingEnabled) {
|
||||
|
@ -5,7 +5,10 @@ console.log('User Preload!');
|
||||
const { contextBridge, shell, ipcRenderer } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
openExternal: (url) => shell.openExternal(url)
|
||||
openExternal: (url) => shell.openExternal(url),
|
||||
setAllowedDomains: (domains) => {
|
||||
ipcRenderer.send('set-allowed-domains', domains);
|
||||
},
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
@ -13,3 +16,5 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
onUpdateDownloaded: (callback) => ipcRenderer.on('update_downloaded', callback),
|
||||
restartApp: () => ipcRenderer.send('restart_app')
|
||||
});
|
||||
|
||||
ipcRenderer.send('test-ipc');
|
@ -6,13 +6,34 @@ import {
|
||||
} from '@capacitor-community/electron';
|
||||
import chokidar from 'chokidar';
|
||||
import type { MenuItemConstructorOptions } from 'electron';
|
||||
import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session } from 'electron';
|
||||
import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session, ipcMain } from 'electron';
|
||||
import electronIsDev from 'electron-is-dev';
|
||||
import electronServe from 'electron-serve';
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
import { join } from 'path';
|
||||
import { myCapacitorApp } from '.';
|
||||
|
||||
|
||||
const defaultDomains = [
|
||||
'http://127.0.0.1:12391',
|
||||
'ws://127.0.0.1:12391',
|
||||
'https://ext-node.qortal.link',
|
||||
'wss://ext-node.qortal.link',
|
||||
'https://appnode.qortal.org',
|
||||
"https://api.qortal.org",
|
||||
"https://api2.qortal.org",
|
||||
"https://appnode.qortal.org",
|
||||
"https://apinode.qortalnodes.live",
|
||||
"https://apinode1.qortalnodes.live",
|
||||
"https://apinode2.qortalnodes.live",
|
||||
"https://apinode3.qortalnodes.live",
|
||||
"https://apinode4.qortalnodes.live"
|
||||
|
||||
];
|
||||
// let allowedDomains: string[] = [...defaultDomains]
|
||||
const domainHolder = {
|
||||
allowedDomains: [...defaultDomains],
|
||||
};
|
||||
// Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode.
|
||||
const reloadWatcher = {
|
||||
debouncer: null,
|
||||
@ -220,15 +241,79 @@ export class ElectronCapacitorApp {
|
||||
}
|
||||
|
||||
// Set a CSP up for our application based on the custom scheme
|
||||
// export function setupContentSecurityPolicy(customScheme: string): void {
|
||||
// session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
// callback({
|
||||
// responseHeaders: {
|
||||
// ...details.responseHeaders,
|
||||
// 'Content-Security-Policy': [
|
||||
// "script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval'; object-src 'self'; connect-src 'self' https://*:* http://*:* wss://*:* ws://*:*",
|
||||
// ],
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
export function setupContentSecurityPolicy(customScheme: string): void {
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
const allowedSources = ["'self'", ...domainHolder.allowedDomains].join(' ');
|
||||
const csp = `
|
||||
script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' ${allowedSources};
|
||||
object-src 'self';
|
||||
connect-src ${allowedSources};
|
||||
`.replace(/\s+/g, ' ').trim();
|
||||
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': [
|
||||
"script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval'; object-src 'self'; connect-src 'self' https://*:* http://*:* wss://*:* ws://*:*",
|
||||
],
|
||||
'Content-Security-Policy': [csp],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// IPC listener for updating allowed domains
|
||||
ipcMain.on('set-allowed-domains', (event, domains: string[]) => {
|
||||
|
||||
// Validate and transform user-provided domains
|
||||
const validatedUserDomains = domains
|
||||
.flatMap((domain) => {
|
||||
try {
|
||||
const url = new URL(domain);
|
||||
const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const socketUrl = `${protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`;
|
||||
return [url.origin, socketUrl];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
// Combine default and validated user domains
|
||||
const newAllowedDomains = [...new Set([...defaultDomains, ...validatedUserDomains])];
|
||||
|
||||
// Sort both current allowed domains and new domains for comparison
|
||||
const sortedCurrentDomains = [...domainHolder.allowedDomains].sort();
|
||||
const sortedNewDomains = [...newAllowedDomains].sort();
|
||||
|
||||
// Check if the lists are different
|
||||
const hasChanged =
|
||||
sortedCurrentDomains.length !== sortedNewDomains.length ||
|
||||
sortedCurrentDomains.some((domain, index) => domain !== sortedNewDomains[index]);
|
||||
|
||||
// If there's a change, update allowedDomains and reload the window
|
||||
if (hasChanged) {
|
||||
domainHolder.allowedDomains = newAllowedDomains;
|
||||
|
||||
const mainWindow = myCapacitorApp.getMainWindow();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -23,15 +23,14 @@ import { set } from "lodash";
|
||||
import { cleanUrl, isUsingLocal } from "../background";
|
||||
|
||||
const manifestData = {
|
||||
version: '0.2.0'
|
||||
}
|
||||
version: "0.2.0",
|
||||
};
|
||||
|
||||
export const NotAuthenticated = ({
|
||||
getRootProps,
|
||||
getInputProps,
|
||||
setExtstate,
|
||||
|
||||
|
||||
apiKey,
|
||||
setApiKey,
|
||||
globalApiKey,
|
||||
@ -54,9 +53,9 @@ export const NotAuthenticated = ({
|
||||
const [customApikey, setCustomApiKey] = React.useState("");
|
||||
const [customNodeToSaveIndex, setCustomNodeToSaveIndex] =
|
||||
React.useState(null);
|
||||
const importedApiKeyRef = useRef(null)
|
||||
const currentNodeRef = useRef(null)
|
||||
const hasLocalNodeRef = useRef(null)
|
||||
const importedApiKeyRef = useRef(null);
|
||||
const currentNodeRef = useRef(null);
|
||||
const hasLocalNodeRef = useRef(null);
|
||||
const isLocal = cleanUrl(currentNode?.url) === "127.0.0.1:12391";
|
||||
const handleFileChangeApiKey = (event) => {
|
||||
const file = event.target.files[0]; // Get the selected file
|
||||
@ -71,7 +70,6 @@ export const NotAuthenticated = ({
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const checkIfUserHasLocalNode = useCallback(async () => {
|
||||
try {
|
||||
const url = `http://127.0.0.1:12391/admin/status`;
|
||||
@ -93,42 +91,47 @@ export const NotAuthenticated = ({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.sendMessage("getCustomNodesFromStorage")
|
||||
window
|
||||
.sendMessage("getCustomNodesFromStorage")
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
setCustomNodes(response || []);
|
||||
window.electronAPI.setAllowedDomains(response?.map((node)=> node.url))
|
||||
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to get custom nodes from storage:", error.message || "An error occurred");
|
||||
console.error(
|
||||
"Failed to get custom nodes from storage:",
|
||||
error.message || "An error occurred"
|
||||
);
|
||||
});
|
||||
|
||||
}, []);
|
||||
|
||||
useEffect(()=> {
|
||||
importedApiKeyRef.current = importedApiKey
|
||||
}, [importedApiKey])
|
||||
useEffect(()=> {
|
||||
currentNodeRef.current = currentNode
|
||||
}, [currentNode])
|
||||
useEffect(() => {
|
||||
importedApiKeyRef.current = importedApiKey;
|
||||
}, [importedApiKey]);
|
||||
useEffect(() => {
|
||||
currentNodeRef.current = currentNode;
|
||||
}, [currentNode]);
|
||||
|
||||
useEffect(()=> {
|
||||
hasLocalNodeRef.current = hasLocalNode
|
||||
}, [hasLocalNode])
|
||||
useEffect(() => {
|
||||
hasLocalNodeRef.current = hasLocalNode;
|
||||
}, [hasLocalNode]);
|
||||
|
||||
const validateApiKey = useCallback(async (key, fromStartUp) => {
|
||||
try {
|
||||
if(!currentNodeRef.current) return
|
||||
if (!currentNodeRef.current) return;
|
||||
const isLocalKey = cleanUrl(key?.url) === "127.0.0.1:12391";
|
||||
if(isLocalKey && !hasLocalNodeRef.current && !fromStartUp){
|
||||
throw new Error('Please turn on your local node')
|
||||
|
||||
if (isLocalKey && !hasLocalNodeRef.current && !fromStartUp) {
|
||||
throw new Error("Please turn on your local node");
|
||||
}
|
||||
const isCurrentNodeLocal = cleanUrl(currentNodeRef.current?.url) === "127.0.0.1:12391";
|
||||
if(isLocalKey && !isCurrentNodeLocal) {
|
||||
const isCurrentNodeLocal =
|
||||
cleanUrl(currentNodeRef.current?.url) === "127.0.0.1:12391";
|
||||
if (isLocalKey && !isCurrentNodeLocal) {
|
||||
setIsValidApiKey(false);
|
||||
setUseLocalNode(false);
|
||||
return
|
||||
return;
|
||||
}
|
||||
let payload = {};
|
||||
|
||||
@ -137,7 +140,7 @@ export const NotAuthenticated = ({
|
||||
apikey: importedApiKeyRef.current || key?.apikey,
|
||||
url: currentNodeRef.current?.url,
|
||||
};
|
||||
} else if(currentNodeRef.current) {
|
||||
} else if (currentNodeRef.current) {
|
||||
payload = currentNodeRef.current;
|
||||
}
|
||||
const url = `${payload?.url}/admin/apikey/test`;
|
||||
@ -152,7 +155,8 @@ export const NotAuthenticated = ({
|
||||
// Assuming the response is in plain text and will be 'true' or 'false'
|
||||
const data = await response.text();
|
||||
if (data === "true") {
|
||||
window.sendMessage("setApiKey", payload)
|
||||
window
|
||||
.sendMessage("setApiKey", payload)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
handleSetGlobalApikey(payload);
|
||||
@ -164,9 +168,11 @@ export const NotAuthenticated = ({
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to set API key:", error.message || "An error occurred");
|
||||
console.error(
|
||||
"Failed to set API key:",
|
||||
error.message || "An error occurred"
|
||||
);
|
||||
});
|
||||
|
||||
} else {
|
||||
setIsValidApiKey(false);
|
||||
setUseLocalNode(false);
|
||||
@ -213,9 +219,12 @@ export const NotAuthenticated = ({
|
||||
}
|
||||
|
||||
setCustomNodes(nodes);
|
||||
window.electronAPI.setAllowedDomains(nodes?.map((node)=> node.url))
|
||||
|
||||
setCustomNodeToSaveIndex(null);
|
||||
if (!nodes) return;
|
||||
window.sendMessage("setCustomNodes", nodes)
|
||||
window
|
||||
.sendMessage("setCustomNodes", nodes)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
setMode("list");
|
||||
@ -225,12 +234,13 @@ export const NotAuthenticated = ({
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to set custom nodes:", error.message || "An error occurred");
|
||||
console.error(
|
||||
"Failed to set custom nodes:",
|
||||
error.message || "An error occurred"
|
||||
);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spacer height="35px" />
|
||||
@ -301,7 +311,7 @@ export const NotAuthenticated = ({
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
visibility: !useLocalNode && 'hidden'
|
||||
visibility: !useLocalNode && "hidden",
|
||||
}}
|
||||
>
|
||||
{"Using node: "} {currentNode?.url}
|
||||
@ -345,9 +355,10 @@ export const NotAuthenticated = ({
|
||||
} else {
|
||||
setCurrentNode({
|
||||
url: "http://127.0.0.1:12391",
|
||||
})
|
||||
setUseLocalNode(false)
|
||||
window.sendMessage("setApiKey", null)
|
||||
});
|
||||
setUseLocalNode(false);
|
||||
window
|
||||
.sendMessage("setApiKey", null)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
setApiKey(null);
|
||||
@ -355,17 +366,18 @@ export const NotAuthenticated = ({
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to set API key:", error.message || "An error occurred");
|
||||
console.error(
|
||||
"Failed to set API key:",
|
||||
error.message || "An error occurred"
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}}
|
||||
disabled={false}
|
||||
defaultChecked
|
||||
/>
|
||||
}
|
||||
label={`Use ${isLocal ? 'Local' : 'Custom'} Node`}
|
||||
label={`Use ${isLocal ? "Local" : "Custom"} Node`}
|
||||
/>
|
||||
</Box>
|
||||
{currentNode?.url === "http://127.0.0.1:12391" && (
|
||||
@ -379,14 +391,12 @@ export const NotAuthenticated = ({
|
||||
onChange={handleFileChangeApiKey} // File input handler
|
||||
/>
|
||||
</Button>
|
||||
<Typography sx={{
|
||||
fontSize: '12px',
|
||||
visibility: importedApiKey ? 'visible' : 'hidden'
|
||||
}}>{`api key : ${importedApiKey}`}</Typography>
|
||||
|
||||
|
||||
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
visibility: importedApiKey ? "visible" : "hidden",
|
||||
}}
|
||||
>{`api key : ${importedApiKey}`}</Typography>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
@ -400,10 +410,14 @@ export const NotAuthenticated = ({
|
||||
Choose custom node
|
||||
</Button>
|
||||
</>
|
||||
<Typography sx={{
|
||||
<Typography
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: '12px'
|
||||
}}>Build version: {manifestData?.version}</Typography>
|
||||
fontSize: "12px",
|
||||
}}
|
||||
>
|
||||
Build version: {manifestData?.version}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
<CustomizedSnackbars
|
||||
@ -430,7 +444,6 @@ export const NotAuthenticated = ({
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
|
||||
{mode === "list" && (
|
||||
<Box
|
||||
sx={{
|
||||
@ -472,7 +485,8 @@ export const NotAuthenticated = ({
|
||||
setMode("list");
|
||||
setShow(false);
|
||||
setUseLocalNode(false);
|
||||
window.sendMessage("setApiKey", null)
|
||||
window
|
||||
.sendMessage("setApiKey", null)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
setApiKey(null);
|
||||
@ -480,9 +494,11 @@ export const NotAuthenticated = ({
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to set API key:", error.message || "An error occurred");
|
||||
console.error(
|
||||
"Failed to set API key:",
|
||||
error.message || "An error occurred"
|
||||
);
|
||||
});
|
||||
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
@ -528,7 +544,8 @@ export const NotAuthenticated = ({
|
||||
setShow(false);
|
||||
setIsValidApiKey(false);
|
||||
setUseLocalNode(false);
|
||||
window.sendMessage("setApiKey", null)
|
||||
window
|
||||
.sendMessage("setApiKey", null)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
setApiKey(null);
|
||||
@ -536,9 +553,11 @@ export const NotAuthenticated = ({
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to set API key:", error.message || "An error occurred");
|
||||
console.error(
|
||||
"Failed to set API key:",
|
||||
error.message || "An error occurred"
|
||||
);
|
||||
});
|
||||
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
@ -563,7 +582,6 @@ export const NotAuthenticated = ({
|
||||
...(customNodes || []),
|
||||
].filter((item) => item?.url !== node?.url);
|
||||
|
||||
|
||||
saveCustomNodes(nodesToSave);
|
||||
}}
|
||||
variant="contained"
|
||||
@ -601,9 +619,7 @@ export const NotAuthenticated = ({
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{mode === "list" && (
|
||||
|
Loading…
x
Reference in New Issue
Block a user