RenaiApp/src/main/modules/session/session-util.ts

79 lines
3.2 KiB
TypeScript

import { isDev } from '../../core/env';
import ContentSecurityPolicy = Session.ContentSecurityPolicy;
import CompleteContentSecurityPolicy = Session.CompleteContentSecurityPolicy;
const defaultCsp: Session.ContentSecurityPolicy = {
'default-src': ["'self'"],
'style-src': ["'unsafe-inline'"],
};
function stringifyCspHeader(csp: ContentSecurityPolicy): string {
return Object.entries(csp)
.map(
(directive: [string, Session.CspValue[] | undefined]) =>
`${directive[0]} ${directive[1] ? directive[1]?.join(' ') : ''}`,
)
.join('; ');
}
export function mergeContentSecurityPolicy(...contentSecurityPolicies: ContentSecurityPolicy[]): ContentSecurityPolicy {
return contentSecurityPolicies.reduce((mergedCsp, contentSecurityPolicy) => {
Object.entries(contentSecurityPolicy).forEach(([policyName, policy]) => {
const mergedPolicy = mergedCsp[policyName as keyof ContentSecurityPolicy];
if (mergedPolicy && policy) {
mergedPolicy.push(...policy);
} else if (policy) {
mergedCsp[policyName as keyof ContentSecurityPolicy] = policy;
}
});
return mergedCsp;
}, {});
}
export function setWindowCsp(window: Electron.BrowserWindow, csp: CompleteContentSecurityPolicy): void {
const mergedCsp: ContentSecurityPolicy = { ...defaultCsp, ...csp };
if (isDev()) {
mergedCsp['default-src'] = ['devtools:'].concat(mergedCsp['default-src'] ?? []);
mergedCsp['script-src'] = ["'unsafe-eval'"].concat(mergedCsp['script-src'] ?? []);
mergedCsp['script-src-elem'] = ['file:', 'devtools:', "'unsafe-inline'"].concat(mergedCsp['script-src-elem'] ?? []);
mergedCsp['style-src'] = ['devtools:', "'unsafe-inline'"].concat(mergedCsp['style-src'] ?? []);
mergedCsp['img-src'] = ['devtools:'].concat(mergedCsp['img-src'] ?? []);
mergedCsp['connect-src'] = ['devtools:', 'data:'].concat(mergedCsp['connect-src'] ?? []);
mergedCsp['worker-src'] = ['devtools:'].concat(mergedCsp['worker-src'] ?? []);
}
window.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': stringifyCspHeader(mergedCsp),
},
});
});
}
/**
* fills script-src-elem and script-src-attr with script-src when not present,
* fills rest of missing policies with default-src or nothing when it is not present
*/
export function completeContentSecurityPolicy(csp: ContentSecurityPolicy): CompleteContentSecurityPolicy {
const defaultSrc = csp['default-src'] ?? [];
const scriptSrc = csp['script-src'] ?? [];
return {
'child-src': csp['child-src'] ?? defaultSrc,
'connect-src': csp['connect-src'] ?? defaultSrc,
'default-src': csp['default-src'] ?? defaultSrc,
'font-src': csp['font-src'] ?? defaultSrc,
'frame-src': csp['frame-src'] ?? defaultSrc,
'img-src': csp['img-src'] ?? defaultSrc,
'media-src': csp['media-src'] ?? defaultSrc,
'object-src': ["'none'"],
'script-src': csp['script-src'] ?? defaultSrc,
'script-src-elem': csp['script-src-elem'] ?? scriptSrc ?? defaultSrc,
'script-src-attr': csp['script-src-attr'] ?? scriptSrc ?? defaultSrc,
'style-src': csp['style-src'] ?? defaultSrc,
'worker-src': csp['worker-src'] ?? defaultSrc,
};
}