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
`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.
@ -39,6 +39,7 @@ Congrats, you've now deployed your very own web proxy!
- Typescript
- ExpressJS
- Prettier
- ESLint
# License

View file

@ -13,13 +13,14 @@ import { existsSync } from "fs";
import dotenv from "dotenv";
import cookieParser from "cookie-parser";
import wisp from "wisp-server-node";
import fs from "node:fs";
import fetch from "node-fetch";
import { masqrCheck } from "./masqr.js";
dotenv.config();
const whiteListedDomains = ["aluu.xyz", "localhost:3000"];
const LICENSE_SERVER_URL = "https://license.mercurywork.shop/validate?license=";
const whiteListedDomains = ["aluu.xyz", "localhost:3000"]; // Add any public domains you have here
const failureFile = fs.readFileSync("Checkfailed.html", "utf8");
const WISP_ENABLED = process.env.USE_WISP;
const MASQR_ENABLED = process.env.MASQR_ENABLED;
if (!existsSync("./dist")) build();
@ -49,71 +50,12 @@ const app = express();
app.use(compression({ threshold: 0, filter: () => true }));
app.use(cookieParser());
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;
}
// Set process.env.MASQR_ENABLED to "true" to enable masqr protection.
if (process.env.MASQR_ENABLED == "true") {
console.log(chalk.gray("Starting Masqr..."));
app.use(await masqrCheck({ whitelist: whiteListedDomains, licenseServer: LICENSE_SERVER_URL }));
}
// 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(), "build")));
app.use("/uv/", express.static(uvPath));
@ -127,7 +69,7 @@ app.use(
})
);
app.use((req, res, next) => {
if (req.url.includes ("/games/")) {
if (req.url.includes("/games/")) {
res.header("Cross-Origin-Opener-Policy", "same-origin");
res.header("Cross-Origin-Embedder-Policy", "require-corp");
}
@ -136,11 +78,11 @@ app.use((req, res, next) => {
app.use("/custom-favicon", async (req, res) => {
try {
const { url, contentType } = req.query;
const urlExt = url.split('.').pop();
const urlExt = url.split(".").pop();
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
console.log(contentType)
console.log(contentType);
if (contentType || !contentType == "") {
res.setHeader("Content-Type", contentType);
} else {
@ -197,7 +139,7 @@ app.get("/search", async (req, res) => {
res.redirect(302, "/404.html");
}
});
app.get("*", function (req, res) {
app.get("*", (req, res) => {
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);
} else if (shouldRouteRh(req)) {
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);
} else {
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;
}
}

5760
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,18 +1,21 @@
/* latin-ext */
@font-face {
font-family: 'Varela Round';
font-style: normal;
font-weight: 400;
font-display: swap;
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;
}
/* latin */
@font-face {
font-family: 'Varela Round';
font-style: normal;
font-weight: 400;
font-display: swap;
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;
}
/* latin-ext */
@font-face {
font-family: "Varela Round";
font-style: normal;
font-weight: 400;
font-display: swap;
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;
}
/* latin */
@font-face {
font-family: "Varela Round";
font-style: normal;
font-weight: 400;
font-display: swap;
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;
}

View file

@ -86,9 +86,9 @@
let iframe = document.getElementById("proxy-frame") as HTMLIFrameElement;
let topbar = document.getElementById("top-bar") as HTMLDivElement;
let closeButton = document.getElementById("close-button") as HTMLButtonElement;
let backwardsButton = document.getElementById('nav-backwards') as HTMLImageElement;
let forwardsButton = document.getElementById('nav-forwards') as HTMLImageElement;
let shareButton = document.getElementById('nav-share') as HTMLImageElement;
let backwardsButton = document.getElementById("nav-backwards") as HTMLImageElement;
let forwardsButton = document.getElementById("nav-forwards") as HTMLImageElement;
let shareButton = document.getElementById("nav-share") as HTMLImageElement;
let preference = getProxyPreference();
if (preference === "ultraviolet") {
iframe.src = window.__uv$config.prefix + window.__uv$config.encodeUrl(url);
@ -114,7 +114,15 @@
iframe.classList.add("proxy-frame");
document.body.appendChild(iframe);
setTimeout(() => {
iframeLoad(iframe, loadingContent, topbar, closeButton, shareButton, forwardsButton, backwardsButton);
iframeLoad(
iframe,
loadingContent,
topbar,
closeButton,
shareButton,
forwardsButton,
backwardsButton
);
}, 500);
function setActive() {
@ -162,9 +170,17 @@
shareButton.onclick = () => {
let currentProxy = localStorage.getItem("alu__selectedProxy");
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 {
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({
duration: 2000,
@ -265,7 +281,10 @@
proxiedFavicon.src = favicon.href;
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;
}
if (favicon) {

View file

@ -23,7 +23,8 @@ const languageList = [
<Dropdown buttonNameDefault="Alu" dropdownList={themeList} id="dropdown__selected-theme" />
</div>
<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
buttonNameDefault="English"
dropdownList={languageList}

View file

@ -33,7 +33,9 @@ const transportsList = [
<div class="settings-container">
<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
buttonNameDefault="Ultraviolet"
dropdownList={proxyList}
@ -42,7 +44,9 @@ const transportsList = [
/>
</div>
<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
buttonNameDefault="Google"
dropdownList={searchEngineList}
@ -51,7 +55,9 @@ const transportsList = [
/>
</div>
<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
buttonNameDefault={t("settings.proxy.openPageWith.embed")}
dropdownList={openPageWith}
@ -60,11 +66,15 @@ const transportsList = [
/>
</div>
<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" />
</div>
<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" />
</div>
<div class="setting__transport">
@ -78,6 +88,8 @@ const transportsList = [
</div>
</div>
<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/" />
</div>

View file

@ -68,16 +68,14 @@ export const TransportMgr = new TransportManager();
export async function registerSW() {
navigator.serviceWorker.ready.then(async (sw) => {
await registerRemoteListener(sw.active!);
})
});
return new Promise(async (resolve) => {
await navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
registration.update().then(() => {
console.log("Registered SW!");
resolve(null);
});
await navigator.serviceWorker.register("/sw.js").then((registration) => {
registration.update().then(() => {
console.log("Registered SW!");
resolve(null);
});
});
});
}
@ -91,7 +89,6 @@ export async function initTransport() {
export async function loadSelectedTransportScript(): Promise<void> {
return new Promise((resolve) => {
let selectedTransport = localStorage.getItem("alu__selectedTransport");
if (!selectedTransport) {
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" />
<link rel="sitemap" href="/sitemap-index.xml" />
<link
href="/varela-round.css"
rel="stylesheet"
as="style"
/>
<link href="/varela-round.css" rel="stylesheet" as="style" />
{
optionalPreloads?.map((item) => {
return <link rel="preload" href={item.href} as={item.as} />;

View file

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

View file

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

View file

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