diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fa3faa..c62af39 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,7 @@ jobs: dist/omnisd.zip dist/application.zip dist/manifest.webapp + dist/security.json - name: discord webhook env: DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} diff --git a/scripts/esbuild.mjs b/scripts/esbuild.mjs index f0a7c65..8333c8e 100755 --- a/scripts/esbuild.mjs +++ b/scripts/esbuild.mjs @@ -85,13 +85,30 @@ const options = { external: ["*.png", "*.ttf", "*.svg"], }; +import crypto from "crypto"; + +async function getFile(path) { + const file = await fs.readFile(path); + return digest(file.buffer); +} + +async function digest(buffer) { + const hashBuffer = await crypto.webcrypto.subtle.digest("SHA-256", buffer); // hash the message + const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); // convert bytes to hex string + return hashHex; +} + try { - await esbuild.build(options); - const regexp = /for((\s?)*)\(((\s?)*)const/g; - const text = await fs.readFile(outfile, "utf8"); - // on KaiOS aka Firefox48, for(const is broken - await fs.writeFile(outfile, text.replace(regexp, "for(let ")); + await esbuild.build(options); + const regexp = /for((\s?)*)\(((\s?)*)const/g; + const text = await fs.readFile(outfile, "utf8"); + // on KaiOS aka Firefox48, for(const is broken + await fs.writeFile(outfile, text.replace(regexp, "for(let ")); + + const [main_page, bundle] = await Promise.all([getFile("./dist/index.html"), getFile("./dist/build/bundle.js")]); + await fs.writeFile("./dist/security.json", JSON.stringify({ main_page, bundle })); } catch (err) { - console.error(err); - process.exit(1); + console.error(err); + process.exit(1); } diff --git a/src/assets/manifest.json b/src/assets/manifest.json index 22391d8..2f40458 100644 --- a/src/assets/manifest.json +++ b/src/assets/manifest.json @@ -1,34 +1,34 @@ { - "version": "2.0.12", - "name": "Sveltecord", - "description": "Discord Client for KaiOS written in svelte.", - "launch_path": "/index.html", - "icons": { - "56": "/css/56px.png", - "112": "/css/112px.png" - }, - "developer": { - "name": "Cyan", - "url": "https://github.com/cyan-2048/Discord4KaiOS" - }, - "origin": "app://sveltecord.cyankindasus.com", - "type": "privileged", - "permissions": { - "systemXHR": { - "description": "Required to access Discord's Server" - }, - "video-capture": { - "description": "Reading Codes using the Camera" - }, - "desktop-notification": {} - }, - "installs_allowed_from": ["*"], - "locales": { - "en-US": { - "name": "Sveltecord", - "subtitle": "Discord Client for KaiOS", - "description": "Sveltecord is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Discordâ„¢, or any of its subsidiaries or its affiliates." - } - }, - "default_locale": "en" + "version": "2.0.13", + "name": "Sveltecord", + "description": "Discord Client for KaiOS written in svelte.", + "launch_path": "/index.html", + "icons": { + "56": "/css/56px.png", + "112": "/css/112px.png" + }, + "developer": { + "name": "Cyan", + "url": "https://github.com/cyan-2048/Discord4KaiOS" + }, + "origin": "app://sveltecord.cyankindasus.com", + "type": "privileged", + "permissions": { + "systemXHR": { + "description": "Required to access Discord's Server" + }, + "video-capture": { + "description": "Reading Codes using the Camera" + }, + "desktop-notification": {} + }, + "installs_allowed_from": ["*"], + "locales": { + "en-US": { + "name": "Sveltecord", + "subtitle": "Discord Client for KaiOS", + "description": "Sveltecord is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Discordâ„¢, or any of its subsidiaries or its affiliates." + } + }, + "default_locale": "en" } diff --git a/src/lib/security.js b/src/lib/security.js new file mode 100644 index 0000000..115c32c --- /dev/null +++ b/src/lib/security.js @@ -0,0 +1,20 @@ +import { xhr } from "./helper"; + +async function getFile(url) { + const resp = await fetch(url); + return digest(await resp.arrayBuffer()); +} + +async function digest(buffer) { + const hashBuffer = await crypto.subtle.digest("SHA-256", buffer); // hash the message + const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); // convert bytes to hex string + return hashHex; +} + +export async function checkSecurity({ main_page, bundle }) { + if (_main_page == main_page && _bundle == bundle) { + return true; + } + throw new Error("Security check failed"); +} diff --git a/src/main.js b/src/main.js index 5abd9c7..09db386 100644 --- a/src/main.js +++ b/src/main.js @@ -5,8 +5,10 @@ import manifest from "./assets/manifest.json"; import App from "./App.svelte"; import scrollBy from "./lib/scrollBy"; import { navigate } from "svelte-routing"; -import { self } from "./lib/shared"; +import { self, settings } from "./lib/shared"; import * as helper from "./lib/helper"; +import { checkSecurity } from "./lib/security"; +import { localStorage } from "./lib/database"; //polyfill scrollBy(); @@ -14,15 +16,27 @@ navigate("/", { replace: true }); document.documentElement.lang = navigator.language; -new App({ - target: document.body, -}); +async function init() { + if (PRODUCTION && typeof SECURITY_CHECK == "object") { + await settings.init; + try { + await checkSecurity(SECURITY_CHECK); + } catch (e) { + console.error(e); -if (PRODUCTION) { - self.then((self) => { - if (self !== null) + alert("security check failed! for your safety, the token is not revoked."); + } + } + + new App({ + target: document.body, + }); + + if (PRODUCTION) { + const _self = await self; + if (_self !== null) for (const key in manifest) { - if (typeof manifest[key] === "string" && self.manifest[key] !== manifest[key]) { + if (typeof manifest[key] === "string" && _self.manifest[key] !== manifest[key]) { alert("cannot guarantee the authenticity of this app!"); alert("Beware of using this app! Your token may be compromised."); alert("only install the app from here: https://notabug.org/cyan-2048/Discord4KaiOS_rewrite"); @@ -31,44 +45,46 @@ if (PRODUCTION) { return; } } - }); - function _async(generator, __arguments = null, __this) { - return new Promise((resolve, reject) => { - var fulfilled = (value) => { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - }; - var rejected = (value) => { - try { - step(generator.throw(value)); - } catch (e) { - reject(e); - } - }; - var step = (x) => (x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected)); - step((generator = generator.apply(__this, __arguments)).next()); - }); - } + function _async(generator, __arguments = null, __this) { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => (x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected)); + step((generator = generator.apply(__this, __arguments)).next()); + }); + } - window.async = function (generator, _arguments) { - _async(generator, _arguments, this); - }; -} else { - function softkey(e) { - const { target, key, bubbles, cancelable, repeat, type } = e; - if (!/Left|Right/.test(key) || !key.startsWith("Arrow") || !e.ctrlKey) return; - e.stopImmediatePropagation(); - e.stopPropagation(); - e.preventDefault(); - target.dispatchEvent(new KeyboardEvent(type, { key: "Soft" + key.slice(5), bubbles, cancelable, repeat })); - } + window.async = function (generator, _arguments) { + _async(generator, _arguments, this); + }; + } else { + function softkey(e) { + const { target, key, bubbles, cancelable, repeat, type } = e; + if (!/Left|Right/.test(key) || !key.startsWith("Arrow") || !e.ctrlKey) return; + e.stopImmediatePropagation(); + e.stopPropagation(); + e.preventDefault(); + target.dispatchEvent(new KeyboardEvent(type, { key: "Soft" + key.slice(5), bubbles, cancelable, repeat })); + } - document.addEventListener("keyup", softkey, true); - document.addEventListener("keydown", softkey, true); + document.addEventListener("keyup", softkey, true); + document.addEventListener("keydown", softkey, true); - Object.assign(window, helper); + Object.assign(window, helper); + } } + +init();