Add support for optionally enabling masqr via .env and optionally enabling wisp, format using prettier.

This commit is contained in:
wearrrrr 2024-04-16 09:27:00 -05:00
parent f1a56b9f91
commit f8bde78717
15 changed files with 4356 additions and 1803 deletions

3
.env.example Normal file
View file

@ -0,0 +1,3 @@
USE_WISP=true
MASQR_ENABLED=true
PORT=3000

View file

@ -20,7 +20,7 @@ Alu is a beautiful, functional, and sleek web proxy, which focuses on customizat
Deploying Alu is about as simple as it gets, from your terminal, type Deploying Alu is about as simple as it gets, from your terminal, type
`git clone https://github.com/wearrrrr/Alu --recurse-submodules` `git clone https://github.com/titaniumnetwork-dev/Alu --recurse-submodules`
This command should clone Alu's frontend, as well as [alu-games](https://github.com/wearrrrr/alu-games). If you wish to skip cloning games, then leave out the last flag. This command should clone Alu's frontend, as well as [alu-games](https://github.com/wearrrrr/alu-games). If you wish to skip cloning games, then leave out the last flag.
@ -39,6 +39,7 @@ Congrats, you've now deployed your very own web proxy!
- Typescript - Typescript
- ExpressJS - ExpressJS
- Prettier - Prettier
- ESLint
# License # License

View file

@ -13,13 +13,14 @@ import { existsSync } from "fs";
import dotenv from "dotenv"; import dotenv from "dotenv";
import cookieParser from "cookie-parser"; import cookieParser from "cookie-parser";
import wisp from "wisp-server-node"; import wisp from "wisp-server-node";
import fs from "node:fs";
import fetch from "node-fetch"; import fetch from "node-fetch";
import { masqrCheck } from "./masqr.js";
dotenv.config(); dotenv.config();
const whiteListedDomains = ["aluu.xyz", "localhost:3000"];
const LICENSE_SERVER_URL = "https://license.mercurywork.shop/validate?license="; const LICENSE_SERVER_URL = "https://license.mercurywork.shop/validate?license=";
const whiteListedDomains = ["aluu.xyz", "localhost:3000"]; // Add any public domains you have here const WISP_ENABLED = process.env.USE_WISP;
const failureFile = fs.readFileSync("Checkfailed.html", "utf8"); const MASQR_ENABLED = process.env.MASQR_ENABLED;
if (!existsSync("./dist")) build(); if (!existsSync("./dist")) build();
@ -49,70 +50,11 @@ const app = express();
app.use(compression({ threshold: 0, filter: () => true })); app.use(compression({ threshold: 0, filter: () => true }));
app.use(cookieParser()); app.use(cookieParser());
async function MasqFail(req, res) { // Set process.env.MASQR_ENABLED to "true" to enable masqr protection.
if (!req.headers.host) { if (process.env.MASQR_ENABLED == "true") {
return; console.log(chalk.gray("Starting Masqr..."));
app.use(await masqrCheck({ whitelist: whiteListedDomains, licenseServer: LICENSE_SERVER_URL }));
} }
const unsafeSuffix = req.headers.host + ".html";
let safeSuffix = path.normalize(unsafeSuffix).replace(/^(\.\.(\/|\\|$))+/, "");
let safeJoin = path.join(process.cwd() + "/Masqrd", safeSuffix);
try {
await fs.promises.access(safeJoin); // man do I wish this was an if-then instead of a "exception on fail"
const failureFileLocal = await fs.promises.readFile(safeJoin, "utf8");
res.setHeader("Content-Type", "text/html");
res.send(failureFileLocal);
return;
} catch (e) {
res.setHeader("Content-Type", "text/html");
res.send(failureFile);
return;
}
}
// Woooooo masqr yayyyy (said no one)
// uncomment for masqr
app.use(async (req, res, next) => {
if (req.headers.host && whiteListedDomains.includes(req.headers.host)) {
next();
return;
}
const authheader = req.headers.authorization;
if (req.cookies["authcheck"]) {
next();
return;
}
if (req.cookies["refreshcheck"] != "true") {
res.cookie("refreshcheck", "true", { maxAge: 10000 }); // 10s refresh check
MasqFail(req, res);
return;
}
if (!authheader) {
res.setHeader("WWW-Authenticate", "Basic"); // Yeah so we need to do this to get the auth params, kinda annoying and just showing a login prompt gives it away so its behind a 10s refresh check
res.status(401);
MasqFail(req, res);
return;
}
const auth = Buffer.from(authheader.split(" ")[1], "base64").toString().split(":");
const pass = auth[1];
const licenseCheck = (
await (await fetch(LICENSE_SERVER_URL + pass + "&host=" + req.headers.host)).json()
)["status"];
console.log(
LICENSE_SERVER_URL + pass + "&host=" + req.headers.host + " returned " + licenseCheck
);
if (licenseCheck == "License valid") {
res.cookie("authcheck", "true", { expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) }); // authorize session, for like a year, by then the link will be expired lol
res.send(`<script> window.location.href = window.location.href </script>`); // fun hack to make the browser refresh and remove the auth params from the URL
return;
}
MasqFail(req, res);
return;
});
app.use(express.static(path.join(process.cwd(), "static"))); app.use(express.static(path.join(process.cwd(), "static")));
app.use(express.static(path.join(process.cwd(), "build"))); app.use(express.static(path.join(process.cwd(), "build")));
@ -136,11 +78,11 @@ app.use((req, res, next) => {
app.use("/custom-favicon", async (req, res) => { app.use("/custom-favicon", async (req, res) => {
try { try {
const { url, contentType } = req.query; const { url, contentType } = req.query;
const urlExt = url.split('.').pop(); const urlExt = url.split(".").pop();
const response = await fetch(url); const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer); const buffer = Buffer.from(arrayBuffer);
console.log(contentType) console.log(contentType);
if (contentType || !contentType == "") { if (contentType || !contentType == "") {
res.setHeader("Content-Type", contentType); res.setHeader("Content-Type", contentType);
} else { } else {
@ -197,7 +139,7 @@ app.get("/search", async (req, res) => {
res.redirect(302, "/404.html"); res.redirect(302, "/404.html");
} }
}); });
app.get("*", function (req, res) { app.get("*", (req, res) => {
res.sendFile(path.join(process.cwd(), "dist/client/404.html")); res.sendFile(path.join(process.cwd(), "dist/client/404.html"));
}); });
@ -217,7 +159,8 @@ server.on("upgrade", (req, socket, head) => {
bare.routeUpgrade(req, socket, head); bare.routeUpgrade(req, socket, head);
} else if (shouldRouteRh(req)) { } else if (shouldRouteRh(req)) {
routeRhUpgrade(req, socket, head); routeRhUpgrade(req, socket, head);
} else if (req.url.endsWith("/wisp/")) { /* Kinda hacky, I need to do a proper dynamic import. */
} else if (req.url.endsWith("/wisp/") && WISP_ENABLED == "true") {
wisp.routeRequest(req, socket, head); wisp.routeRequest(req, socket, head);
} else { } else {
socket.end(); socket.end();

68
masqr.js Normal file
View file

@ -0,0 +1,68 @@
import path from "path";
const failureFile = fs.readFileSync("Checkfailed.html", "utf8");
export async function masqrCheck(config) {
return async (req, res, next) => {
if (req.headers.host && config.whitelist.includes(req.headers.host)) {
next();
return;
}
const authheader = req.headers.authorization;
if (req.cookies["authcheck"]) {
next();
return;
}
if (req.cookies["refreshcheck"] != "true") {
res.cookie("refreshcheck", "true", { maxAge: 10000 }); // 10s refresh check
MasqFail(req, res);
return;
}
if (!authheader) {
res.setHeader("WWW-Authenticate", "Basic");
res.status(401);
MasqFail(req, res);
return;
}
const auth = Buffer.from(authheader.split(" ")[1], "base64").toString().split(":");
const pass = auth[1];
const licenseCheck = (
await (await fetch(config.licenseServer + pass + "&host=" + req.headers.host)).json()
)["status"];
console.log(
config.licenseServer + pass + "&host=" + req.headers.host + " returned " + licenseCheck
);
if (licenseCheck == "License valid") {
res.cookie("authcheck", "true", { expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) }); // authorize session, for like a year, by then the link will be expired lol
res.send(`<script> window.location.href = window.location.href </script>`); // fun hack to make the browser refresh and remove the auth params from the URL
return;
}
MasqFail(req, res);
return;
}
}
async function MasqFail(req, res) {
if (!req.headers.host) {
return;
}
const unsafeSuffix = req.headers.host + ".html";
let safeSuffix = path.normalize(unsafeSuffix).replace(/^(\.\.(\/|\\|$))+/, "");
let safeJoin = path.join(process.cwd() + "/Masqrd", safeSuffix);
try {
await fs.promises.access(safeJoin); // man do I wish this was an if-then instead of a "exception on fail"
const failureFileLocal = await fs.promises.readFile(safeJoin, "utf8");
res.setHeader("Content-Type", "text/html");
res.send(failureFileLocal);
return;
} catch (e) {
res.setHeader("Content-Type", "text/html");
res.send(failureFile);
return;
}
}

5660
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ importScripts(__uv$config.sw);
const uv = new UVServiceWorker(); const uv = new UVServiceWorker();
self.addEventListener('fetch', event => { self.addEventListener("fetch", (event) => {
event.respondWith( event.respondWith(
(async () => { (async () => {
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) { if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {

View file

@ -1,18 +1,21 @@
/* latin-ext */ /* latin-ext */
@font-face { @font-face {
font-family: 'Varela Round'; font-family: "Varela Round";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url(varela-latin-ext.woff2) format('woff2'); src: url(varela-latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
} }
/* latin */ /* latin */
@font-face { @font-face {
font-family: 'Varela Round'; font-family: "Varela Round";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url(varela-latin.woff2) format('woff2'); src: url(varela-latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
} }

View file

@ -86,9 +86,9 @@
let iframe = document.getElementById("proxy-frame") as HTMLIFrameElement; let iframe = document.getElementById("proxy-frame") as HTMLIFrameElement;
let topbar = document.getElementById("top-bar") as HTMLDivElement; let topbar = document.getElementById("top-bar") as HTMLDivElement;
let closeButton = document.getElementById("close-button") as HTMLButtonElement; let closeButton = document.getElementById("close-button") as HTMLButtonElement;
let backwardsButton = document.getElementById('nav-backwards') as HTMLImageElement; let backwardsButton = document.getElementById("nav-backwards") as HTMLImageElement;
let forwardsButton = document.getElementById('nav-forwards') as HTMLImageElement; let forwardsButton = document.getElementById("nav-forwards") as HTMLImageElement;
let shareButton = document.getElementById('nav-share') as HTMLImageElement; let shareButton = document.getElementById("nav-share") as HTMLImageElement;
let preference = getProxyPreference(); let preference = getProxyPreference();
if (preference === "ultraviolet") { if (preference === "ultraviolet") {
iframe.src = window.__uv$config.prefix + window.__uv$config.encodeUrl(url); iframe.src = window.__uv$config.prefix + window.__uv$config.encodeUrl(url);
@ -114,7 +114,15 @@
iframe.classList.add("proxy-frame"); iframe.classList.add("proxy-frame");
document.body.appendChild(iframe); document.body.appendChild(iframe);
setTimeout(() => { setTimeout(() => {
iframeLoad(iframe, loadingContent, topbar, closeButton, shareButton, forwardsButton, backwardsButton); iframeLoad(
iframe,
loadingContent,
topbar,
closeButton,
shareButton,
forwardsButton,
backwardsButton
);
}, 500); }, 500);
function setActive() { function setActive() {
@ -162,9 +170,17 @@
shareButton.onclick = () => { shareButton.onclick = () => {
let currentProxy = localStorage.getItem("alu__selectedProxy"); let currentProxy = localStorage.getItem("alu__selectedProxy");
if (currentProxy && JSON.parse(currentProxy).value === "rammerhead") { if (currentProxy && JSON.parse(currentProxy).value === "rammerhead") {
navigator.clipboard.writeText(window.location.origin + "/" + getCookie("rammerhead-session") + "/" + input!.value.trim()); navigator.clipboard.writeText(
window.location.origin +
"/" +
getCookie("rammerhead-session") +
"/" +
input!.value.trim()
);
} else { } else {
navigator.clipboard.writeText(window.__uv$config.decodeUrl(iframe.src.split("/service/")[1])); navigator.clipboard.writeText(
window.__uv$config.decodeUrl(iframe.src.split("/service/")[1])
);
} }
new Notyf({ new Notyf({
duration: 2000, duration: 2000,
@ -265,7 +281,10 @@
proxiedFavicon.src = favicon.href; proxiedFavicon.src = favicon.href;
return; return;
} }
if (proxiedFavicon.src == `${window.location.origin}/custom-favicon?url=${encodedHREF}&contentType=${favicon.type ? encodeURIComponent(favicon.type) : ""}`) if (
proxiedFavicon.src ==
`${window.location.origin}/custom-favicon?url=${encodedHREF}&contentType=${favicon.type ? encodeURIComponent(favicon.type) : ""}`
)
return; return;
} }
if (favicon) { if (favicon) {

View file

@ -23,7 +23,8 @@ const languageList = [
<Dropdown buttonNameDefault="Alu" dropdownList={themeList} id="dropdown__selected-theme" /> <Dropdown buttonNameDefault="Alu" dropdownList={themeList} id="dropdown__selected-theme" />
</div> </div>
<div class="setting__language"> <div class="setting__language">
<label aria-label="Language" class="setting-label">{t("settings.customization.language")}</label> <label aria-label="Language" class="setting-label">{t("settings.customization.language")}</label
>
<Dropdown <Dropdown
buttonNameDefault="English" buttonNameDefault="English"
dropdownList={languageList} dropdownList={languageList}

View file

@ -33,7 +33,9 @@ const transportsList = [
<div class="settings-container"> <div class="settings-container">
<div class="setting__selected-proxy"> <div class="setting__selected-proxy">
<label aria-label="Selected Proxy" class="setting-label">{t("settings.proxy.selectedProxy")}</label> <label aria-label="Selected Proxy" class="setting-label"
>{t("settings.proxy.selectedProxy")}</label
>
<Dropdown <Dropdown
buttonNameDefault="Ultraviolet" buttonNameDefault="Ultraviolet"
dropdownList={proxyList} dropdownList={proxyList}
@ -42,7 +44,9 @@ const transportsList = [
/> />
</div> </div>
<div class="setting__search-engine"> <div class="setting__search-engine">
<label aria-label="Search Engine" class="setting-label">{t("settings.proxy.searchEngine")}</label> <label aria-label="Search Engine" class="setting-label"
>{t("settings.proxy.searchEngine")}</label
>
<Dropdown <Dropdown
buttonNameDefault="Google" buttonNameDefault="Google"
dropdownList={searchEngineList} dropdownList={searchEngineList}
@ -51,7 +55,9 @@ const transportsList = [
/> />
</div> </div>
<div class="setting__open_with"> <div class="setting__open_with">
<label aria-label="Open Page With" class="setting-label">{t("settings.proxy.openPageWith")}</label> <label aria-label="Open Page With" class="setting-label"
>{t("settings.proxy.openPageWith")}</label
>
<Dropdown <Dropdown
buttonNameDefault={t("settings.proxy.openPageWith.embed")} buttonNameDefault={t("settings.proxy.openPageWith.embed")}
dropdownList={openPageWith} dropdownList={openPageWith}
@ -60,11 +66,15 @@ const transportsList = [
/> />
</div> </div>
<div class="setting__wisp_url"> <div class="setting__wisp_url">
<label aria-label="Wisp URL" for="wisp-url-input" class="setting-label">{t("settings.proxy.wispURL")}</label> <label aria-label="Wisp URL" for="wisp-url-input" class="setting-label"
>{t("settings.proxy.wispURL")}</label
>
<Input height="50px" inputName="wisp-url" /> <Input height="50px" inputName="wisp-url" />
</div> </div>
<div class="setting__bare_url"> <div class="setting__bare_url">
<label aria-label="Bare Server URL" for="bare-url-input" class="setting-label">{t("settings.proxy.bareURL")}</label> <label aria-label="Bare Server URL" for="bare-url-input" class="setting-label"
>{t("settings.proxy.bareURL")}</label
>
<Input height="50px" inputName="bare-url" /> <Input height="50px" inputName="bare-url" />
</div> </div>
<div class="setting__transport"> <div class="setting__transport">
@ -78,6 +88,8 @@ const transportsList = [
</div> </div>
</div> </div>
<div class="setting__searxng-url"> <div class="setting__searxng-url">
<label aria-label="SearXNG URL" for="searxng-url-input" class="setting-label">{t("settings.proxy.searxngURL")}</label> <label aria-label="SearXNG URL" for="searxng-url-input" class="setting-label"
>{t("settings.proxy.searxngURL")}</label
>
<Input height="50px" inputName="searxng-url" defaultTextContent="https://searxng.site/" /> <Input height="50px" inputName="searxng-url" defaultTextContent="https://searxng.site/" />
</div> </div>

View file

@ -68,11 +68,9 @@ export const TransportMgr = new TransportManager();
export async function registerSW() { export async function registerSW() {
navigator.serviceWorker.ready.then(async (sw) => { navigator.serviceWorker.ready.then(async (sw) => {
await registerRemoteListener(sw.active!); await registerRemoteListener(sw.active!);
}) });
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
await navigator.serviceWorker await navigator.serviceWorker.register("/sw.js").then((registration) => {
.register("/sw.js")
.then((registration) => {
registration.update().then(() => { registration.update().then(() => {
console.log("Registered SW!"); console.log("Registered SW!");
resolve(null); resolve(null);
@ -91,7 +89,6 @@ export async function initTransport() {
export async function loadSelectedTransportScript(): Promise<void> { export async function loadSelectedTransportScript(): Promise<void> {
return new Promise((resolve) => { return new Promise((resolve) => {
let selectedTransport = localStorage.getItem("alu__selectedTransport"); let selectedTransport = localStorage.getItem("alu__selectedTransport");
if (!selectedTransport) { if (!selectedTransport) {
localStorage.setItem("alu__selectedTransport", JSON.stringify({ value: "uv" })); localStorage.setItem("alu__selectedTransport", JSON.stringify({ value: "uv" }));

View file

@ -48,11 +48,7 @@ const { title, optionalPreloads } = Astro.props;
/> />
<meta property="twitter:image" content="/logo.png" /> <meta property="twitter:image" content="/logo.png" />
<link rel="sitemap" href="/sitemap-index.xml" /> <link rel="sitemap" href="/sitemap-index.xml" />
<link <link href="/varela-round.css" rel="stylesheet" as="style" />
href="/varela-round.css"
rel="stylesheet"
as="style"
/>
{ {
optionalPreloads?.map((item) => { optionalPreloads?.map((item) => {
return <link rel="preload" href={item.href} as={item.as} />; return <link rel="preload" href={item.href} as={item.as} />;

View file

@ -33,9 +33,9 @@ export function getStaticPaths() {
<div class="top-bar-left"> <div class="top-bar-left">
<button id="close-button">Close</button> <button id="close-button">Close</button>
<div class="nav-container"> <div class="nav-container">
<img id="nav-backwards" src="/img/nav/backwards.svg" alt="Backwards Arrow"> <img id="nav-backwards" src="/img/nav/backwards.svg" alt="Backwards Arrow" />
<img id="nav-forwards" src="/img/nav/forwards.svg" alt="Forwards Arrow"> <img id="nav-forwards" src="/img/nav/forwards.svg" alt="Forwards Arrow" />
<img id="nav-share" src="/img/nav/share.svg" alt="Share Page"> <img id="nav-share" src="/img/nav/share.svg" alt="Share Page" />
</div> </div>
</div> </div>
<div class="top-bar-right"> <div class="top-bar-right">
@ -144,7 +144,6 @@ export function getStaticPaths() {
</script> </script>
</Layout> </Layout>
<style is:global> <style is:global>
#main-content { #main-content {
width: 100%; width: 100%;

View file

@ -323,7 +323,8 @@ export function getStaticPaths() {
if (savedWispUrl == null || savedWispUrl == "") if (savedWispUrl == null || savedWispUrl == "")
localStorage.setItem("alu__wispUrl", webSocketProtocol + location.host + "/wisp/"); localStorage.setItem("alu__wispUrl", webSocketProtocol + location.host + "/wisp/");
let savedBareUrl = localStorage.getItem("alu__bareUrl"); let savedBareUrl = localStorage.getItem("alu__bareUrl");
if (savedBareUrl == null || savedBareUrl == "") localStorage.setItem("alu__bareUrl", location.origin + "/bare/"); if (savedBareUrl == null || savedBareUrl == "")
localStorage.setItem("alu__bareUrl", location.origin + "/bare/");
wispURLInput.value = localStorage.getItem("alu__wispUrl"); wispURLInput.value = localStorage.getItem("alu__wispUrl");
bareURLInput.value = localStorage.getItem("alu__bareUrl"); bareURLInput.value = localStorage.getItem("alu__bareUrl");
// Proxy settings // Proxy settings
@ -331,14 +332,16 @@ export function getStaticPaths() {
applyInputListeners(wispURLInput, "alu__wispUrl"); applyInputListeners(wispURLInput, "alu__wispUrl");
applyInputListeners(bareURLInput, "alu__bareUrl"); applyInputListeners(bareURLInput, "alu__bareUrl");
[selectedProxyDropdown, openWithDropdown, currentTransportDropdown].forEach((dropdown) => { [selectedProxyDropdown, openWithDropdown, currentTransportDropdown].forEach(
(dropdown) => {
let dropdownButton = document.getElementById(dropdown.id.replace("-menu", "")); let dropdownButton = document.getElementById(dropdown.id.replace("-menu", ""));
applyDropdownEventListeners( applyDropdownEventListeners(
dropdown, dropdown,
dropdownButton.id, dropdownButton.id,
dropdownButton.dataset.localStorageKey dropdownButton.dataset.localStorageKey
); );
}); }
);
applyDropdownEventListeners( applyDropdownEventListeners(
searchEngineDropdown, searchEngineDropdown,
"dropdown__search-engine", "dropdown__search-engine",
@ -553,5 +556,3 @@ export function getStaticPaths() {
} }
</style> </style>
</Layout> </Layout>

View file

@ -31,7 +31,7 @@ import { ViewTransitions } from "astro:transitions";
<ViewTransitions /> <ViewTransitions />
<script> <script>
let currentLang = localStorage.getItem("alu__selectedLanguage"); let currentLang = localStorage.getItem("alu__selectedLanguage");
const redirect = (loc: string) => window.location.href = loc const redirect = (loc: string) => (window.location.href = loc);
if (currentLang) { if (currentLang) {
try { try {
let parsed = JSON.parse(currentLang).value; let parsed = JSON.parse(currentLang).value;