Introduce CSP (#3512)

This commit is contained in:
Alexander Zinchuk 2023-07-20 15:58:31 +02:00
parent c3e86fb157
commit 42fe08cb6e
15 changed files with 101 additions and 65 deletions

View File

@ -3,8 +3,10 @@
cp -R ./public/* ${1:-"dist"}
cp ./src/lib/rlottie/rlottie-wasm.wasm ${1:-"dist"}
cp ./src/lib/video-preview/libav-3.10.5.1.2-webcodecs.wasm.js ${1:-"dist"}
cp ./src/lib/video-preview/libav-3.10.5.1.2-webcodecs.wasm.wasm ${1:-"dist"}
cp ./src/lib/webp/webp_wasm.wasm ${1:-"dist"}
cp ./node_modules/opus-recorder/dist/decoderWorker.min.wasm ${1:-"dist"}

5
package-lock.json generated
View File

@ -19478,6 +19478,8 @@
},
"node_modules/raw-loader": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz",
"integrity": "sha512-sf7oGoLuaYAScB4VGr0tzetsYlS8EJH6qnTCfQ/WVEa89hALQ4RQfCKt5xCyPQKPDUbVUAIP1QsxAwfAjlDp7Q==",
"dev": true
},
"node_modules/rc": {
@ -20204,8 +20206,9 @@
},
"node_modules/script-loader": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/script-loader/-/script-loader-0.7.2.tgz",
"integrity": "sha512-UMNLEvgOAQuzK8ji8qIscM3GIrRCWN6MmMXGD4SD5l6cSycgGsCo0tX5xRnfQcoghqct0tjHjcykgI1PyBE2aA==",
"dev": true,
"license": "MIT",
"dependencies": {
"raw-loader": "~0.5.1"
}

25
public/compatTest.js Normal file
View File

@ -0,0 +1,25 @@
function compatTest() {
var hasPromise = typeof Promise !== 'undefined';
var hasWebSockets = typeof WebSocket !== 'undefined';
var hasWebCrypto = window.crypto && typeof window.crypto.subtle !== 'undefined';
var hasObjectFromEntries = typeof Object.fromEntries !== 'undefined';
var isCompatible = hasPromise && hasWebSockets && hasWebCrypto && hasObjectFromEntries;
if (isCompatible || (window.localStorage && window.localStorage.getItem('tt-ignore-compat'))) {
window.isCompatTestPassed = true;
return;
}
if (window.console && console.warn) {
console.warn('Compatibility test report:');
console.warn('Promise', hasPromise);
console.warn('WebSocket', hasWebSockets);
console.warn('WebCrypto', hasWebCrypto);
console.warn('Object.fromEntries', hasObjectFromEntries);
}
document.body.innerHTML = '<iframe src="./unsupported.html" width="100%" height="100%">';
}
compatTest();

11
public/redirect.js Normal file
View File

@ -0,0 +1,11 @@
const { pathname, hostname, href } = window.location;
if (pathname.startsWith('/z')) {
window.location.href = href.replace('/z', '/a');
}
if (
(hostname === 'weba.telegram.org' || hostname === 'webz.telegram.org') && !localStorage.getItem('tt-global-state')
) {
window.location.href = 'https://web.telegram.org/a';
}

View File

@ -17,7 +17,7 @@ export const DEBUG_MORE = false;
export const STRICTERDOM_ENABLED = DEBUG && !IS_ELECTRON;
export const BETA_CHANGELOG_URL = 'https://telegra.ph/WebA-Beta-03-20';
export const ELECTRON_HOST_URL = 'https://telegram-a-host';
export const ELECTRON_HOST_URL = process.env.ELECTRON_HOST_URL!;
export const DEBUG_ALERT_MSG = 'Shoot!\nSomething went wrong, please see the error details in Dev Tools Console.';
export const DEBUG_GRAMJS = false;

View File

@ -12,7 +12,7 @@
<meta name="description"
content="Telegram is a cloud-based mobile and desktop messaging app with a focus on security and speed.">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no, viewport-fit=cover">
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no, viewport-fit=cover">
<meta name="mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-title" content="<%= htmlWebpackPlugin.options.appTitle %>">
<meta name="apple-mobile-web-app-capable" content="yes">
@ -23,12 +23,14 @@
<meta name="theme-color" content="#ffffff">
<meta name="google" content="notranslate">
<meta http-equiv="Content-Security-Policy" content="<%= htmlWebpackPlugin.options.csp %>">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="<%= htmlWebpackPlugin.options.baseUrl %>">
<meta property="og:title" content="<%= htmlWebpackPlugin.options.appTitle %>">
<meta property="og:description"
content="Telegram is a cloud-based mobile and desktop messaging app with a focus on security and speed.">
content="Telegram is a cloud-based mobile and desktop messaging app with a focus on security and speed.">
<meta property="og:image" content="./<%= htmlWebpackPlugin.options.mainIcon %>.png">
<!-- Twitter -->
@ -48,17 +50,8 @@
<link rel="alternate icon" href="./favicon.ico" type="image/x-icon">
<link rel="manifest" id="the-manifest-placeholder" href="./<%= htmlWebpackPlugin.options.manifest %>">
<script>
const { pathname, hostname, href } = window.location;
if (pathname.startsWith('/z')) {
window.location.href = href.replace('/z', '/a');
}
if ((hostname === 'weba.telegram.org' || hostname === 'webz.telegram.org') && !localStorage.getItem('tt-global-state')) {
window.location.href = 'https://web.telegram.org/a';
}
</script>
<script src="./compatTest.js"></script>
<script src="./redirect.js"></script>
</head>
<body id="root">
@ -68,33 +61,6 @@
<p>Please, enable JavaScript to open the app.</p>
</noscript>
<div id="portals"></div>
<script>
function compatTest() {
var hasPromise = typeof Promise !== 'undefined';
var hasWebSockets = typeof WebSocket !== 'undefined';
var hasWebCrypto = window.crypto && typeof window.crypto.subtle !== 'undefined';
var hasObjectFromEntries = typeof Object.fromEntries !== 'undefined';
var isCompatible = hasPromise && hasWebSockets && hasWebCrypto && hasObjectFromEntries;
if (isCompatible || (window.localStorage && window.localStorage.getItem('tt-ignore-compat'))) {
window.isCompatTestPassed = true;
return;
}
if (window.console && console.warn) {
console.warn('Compatibility test report:');
console.warn('Promise', hasPromise);
console.warn('WebSocket', hasWebSockets);
console.warn('WebCrypto', hasWebCrypto);
console.warn('Object.fromEntries', hasObjectFromEntries);
}
document.body.innerHTML = '<iframe src="./unsupported.html" width="100%" height="100%">';
}
compatTest();
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@ import { inflate } from 'pako/dist/pako_inflate';
import { createWorkerInterface } from '../../util/createPostMessageInterface';
import type { CancellableCallback } from '../../util/PostMessageConnector';
import 'script-loader!./rlottie-wasm';
importScripts(new URL('./rlottie-wasm.js', import.meta.url));
declare const Module: any;

View File

@ -75,12 +75,13 @@ function destroy() {
async function loadLibAV() {
if (isLoaded) return;
// @ts-ignore
await import('script-loader!./libav-3.10.5.1.2-webcodecs');
importScripts(new URL('./libav-3.10.5.1.2-webcodecs.js', import.meta.url));
await LibAVWebCodecs.load({
polyfill: true,
libavOptions: { noworker: true, nosimd: true },
});
isLoaded = true;
}

View File

@ -1,5 +1,5 @@
/* eslint-disable */
import 'script-loader!./webp_wasm';
importScripts(new URL('./webp_wasm.js', import.meta.url));
Module.onRuntimeInitialized = async () => {
self.postMessage({ type: 'initialized' });

View File

@ -1,15 +1,15 @@
import { DEBUG } from './config';
import { respondForProgressive } from './serviceWorker/progressive';
import { respondForDownload } from './serviceWorker/download';
import { respondWithCache, clearAssetCache, respondWithCacheNetworkFirst } from './serviceWorker/assetCache';
import { DEBUG, ELECTRON_HOST_URL, IS_ELECTRON } from '../config';
import { respondForProgressive } from './progressive';
import { respondForDownload } from './download';
import { respondWithCache, clearAssetCache, respondWithCacheNetworkFirst } from './assetCache';
import {
handlePush,
handleNotificationClick,
handleClientMessage as handleNotificationMessage,
} from './serviceWorker/pushNotification';
import { respondForShare, handleClientMessage as handleShareMessage } from './serviceWorker/share';
} from './pushNotification';
import { respondForShare, handleClientMessage as handleShareMessage } from './share';
import { pause } from './util/schedulers';
import { pause } from '../util/schedulers';
declare const self: ServiceWorkerGlobalScope;
@ -48,28 +48,35 @@ self.addEventListener('activate', (e) => {
self.addEventListener('fetch', (e: FetchEvent) => {
const { url } = e.request;
const scope = IS_ELECTRON ? ELECTRON_HOST_URL : self.registration.scope;
if (!url.startsWith(scope)) {
return false;
}
if (url.includes('/progressive/')) {
const { pathname, protocol } = new URL(url);
const { pathname: scopePathname } = new URL(scope);
if (pathname.startsWith('/progressive/')) {
e.respondWith(respondForProgressive(e));
return true;
}
if (url.includes('/download/')) {
if (pathname.startsWith('/download/')) {
e.respondWith(respondForDownload(e));
return true;
}
if (url.includes('/share/')) {
if (pathname.startsWith('/share/')) {
e.respondWith(respondForShare(e));
}
if (url.startsWith('http')) {
if (new URL(url).pathname === '/' || url.match(RE_NETWORK_FIRST_ASSETS)) {
if (protocol === 'http:' || protocol === 'https:') {
if (pathname === scopePathname || pathname.match(RE_NETWORK_FIRST_ASSETS)) {
e.respondWith(respondWithCacheNetworkFirst(e));
return true;
}
if (url.match(RE_CACHE_FIRST_ASSETS)) {
if (pathname.match(RE_CACHE_FIRST_ASSETS)) {
e.respondWith(respondWithCache(e));
return true;
}

View File

@ -1,4 +1,4 @@
import { IS_ELECTRON, ELECTRON_HOST_URL } from '../config';
import { ELECTRON_HOST_URL, IS_ELECTRON } from '../config';
// eslint-disable-next-line no-restricted-globals
const cacheApi = self.caches;

View File

@ -60,7 +60,7 @@ if (IS_SERVICE_WORKER_SUPPORTED) {
}
}
await navigator.serviceWorker.register(new URL('../serviceWorker.ts', import.meta.url));
await navigator.serviceWorker.register(new URL('../serviceWorker', import.meta.url));
if (DEBUG) {
// eslint-disable-next-line no-console

View File

@ -7,7 +7,7 @@ import { hasStoredSession } from './sessions';
const WEBSYNC_URLS = [
't.me',
'telegram.me',
].map((domain) => `//${domain}/_websync_?`);
].map((domain) => `https://${domain}/_websync_?`);
const WEBSYNC_VERSION = `${APP_VERSION} Z`;
const WEBSYNC_KEY = 'tgme_sync';
const WEBSYNC_TIMEOUT = 86400;

View File

@ -27,15 +27,30 @@ const {
dotenv.config();
const STATOSCOPE_REFERENCE_URL = 'https://tga.dev/build-stats.json';
let isReferenceFetched = false;
const DEFAULT_APP_TITLE = `Telegram${APP_ENV !== 'production' ? ' Beta' : ''}`;
const {
BASE_URL = 'https://web.telegram.org/a/',
ELECTRON_HOST_URL = 'https://telegram-a-host',
APP_TITLE = DEFAULT_APP_TITLE,
} = process.env;
const CSP = `
default-src 'self';
connect-src 'self' wss://*.web.telegram.org blob: http: https:;
script-src 'self' 'wasm-unsafe-eval' https://t.me/_websync_ https://telegram.me/_websync_;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https://ss3.4sqi.net/img/categories_v2/ ${IS_ELECTRON ? BASE_URL : ''};
media-src 'self' blob: data: ${IS_ELECTRON ? [BASE_URL, ELECTRON_HOST_URL].join(' ') : ''};
object-src 'none';
frame-src http: https:;
base-uri 'none';
form-action 'none';`
.replace(/\s+/g, ' ').trim();
const STATOSCOPE_REFERENCE_URL = 'https://tga.dev/build-stats.json';
let isReferenceFetched = false;
export default function createConfig(
_: any,
{ mode = 'production' }: { mode: 'none' | 'development' | 'production' },
@ -76,6 +91,9 @@ export default function createConfig(
devMiddleware: {
stats: 'minimal',
},
headers: {
'Content-Security-Policy': CSP,
},
},
output: {
@ -180,6 +198,7 @@ export default function createConfig(
mainIcon: APP_ENV === 'production' ? 'icon-192x192' : 'icon-dev-192x192',
manifest: APP_ENV === 'production' ? 'site.webmanifest' : 'site_dev.webmanifest',
baseUrl: BASE_URL,
csp: CSP,
template: 'src/index.html',
}),
new MiniCssExtractPlugin({
@ -199,7 +218,9 @@ export default function createConfig(
TELEGRAM_T_API_HASH: undefined,
// eslint-disable-next-line no-null/no-null
TEST_SESSION: null,
ELECTRON_HOST_URL,
}),
// Updates each dev re-build to provide current git branch or commit hash
new DefinePlugin({
APP_VERSION: JSON.stringify(appVersion),
APP_REVISION: DefinePlugin.runtimeValue(() => {