chore: Format code again.

This commit is contained in:
greysoh 2024-05-05 17:03:24 -04:00
parent 5f1df9ca88
commit 3143800c92
No known key found for this signature in database
GPG key ID: FE0F173B8FC01571
5 changed files with 255 additions and 171 deletions

View file

@ -4,6 +4,6 @@ import { PassyFireBackendProvider } from "./passyfire-reimpl/index.js";
import { SSHBackendProvider } from "./ssh.js";
export const backendProviders: Record<string, typeof BackendBaseClass> = {
"ssh": SSHBackendProvider,
"passyfire": PassyFireBackendProvider
};
ssh: SSHBackendProvider,
passyfire: PassyFireBackendProvider,
};

View file

@ -3,40 +3,45 @@ import fastifyWebsocket from "@fastify/websocket";
import type { FastifyInstance } from "fastify";
import Fastify from "fastify";
import type { ForwardRule, ConnectedClient, ParameterReturnedValue, BackendBaseClass } from "../base.js";
import type {
ForwardRule,
ConnectedClient,
ParameterReturnedValue,
BackendBaseClass,
} from "../base.js";
import { generateRandomData } from "../../libs/generateRandom.js";
import { requestHandler } from "./socket.js";
import { route } from "./routes.js";
type BackendProviderUser = {
username: string,
password: string
}
username: string;
password: string;
};
export type ForwardRuleExt = ForwardRule & {
protocol: "tcp" | "udp",
userConfig: Record<string, string>
protocol: "tcp" | "udp";
userConfig: Record<string, string>;
};
export type ConnectedClientExt = ConnectedClient & {
connectionDetails: ForwardRuleExt;
username: string;
username: string;
};
// Fight me (for better naming)
type BackendParsedProviderString = {
ip: string,
port: number,
publicPort?: number,
isProxied?: boolean,
ip: string;
port: number;
publicPort?: number;
isProxied?: boolean;
users: BackendProviderUser[]
}
users: BackendProviderUser[];
};
type LoggedInUser = {
username: string,
token: string
}
username: string;
token: string;
};
function parseBackendProviderString(data: string): BackendParsedProviderString {
try {
@ -47,19 +52,30 @@ function parseBackendProviderString(data: string): BackendParsedProviderString {
const jsonData = JSON.parse(data);
if (typeof jsonData.ip != "string") throw new Error("IP field is not a string");
if (typeof jsonData.ip != "string")
throw new Error("IP field is not a string");
if (typeof jsonData.port != "number") throw new Error("Port is not a number");
if (typeof jsonData.publicPort != "undefined" && typeof jsonData.publicPort != "number") throw new Error("(optional field) Proxied port is not a number");
if (typeof jsonData.isProxied != "undefined" && typeof jsonData.isProxied != "boolean") throw new Error("(optional field) 'Is proxied' is not a boolean");
if (
typeof jsonData.publicPort != "undefined" &&
typeof jsonData.publicPort != "number"
)
throw new Error("(optional field) Proxied port is not a number");
if (
typeof jsonData.isProxied != "undefined" &&
typeof jsonData.isProxied != "boolean"
)
throw new Error("(optional field) 'Is proxied' is not a boolean");
if (!Array.isArray(jsonData.users)) throw new Error("Users is not an array");
for (const userIndex in jsonData.users) {
const user = jsonData.users[userIndex];
if (typeof user.username != "string") throw new Error("Username is not a string, in users array");
if (typeof user.password != "string") throw new Error("Password is not a string, in users array");
if (typeof user.username != "string")
throw new Error("Username is not a string, in users array");
if (typeof user.password != "string")
throw new Error("Password is not a string, in users array");
}
return {
@ -69,8 +85,8 @@ function parseBackendProviderString(data: string): BackendParsedProviderString {
publicPort: jsonData.publicPort,
isProxied: jsonData.isProxied,
users: jsonData.users
}
users: jsonData.users,
};
}
export class PassyFireBackendProvider implements BackendBaseClass {
@ -87,8 +103,8 @@ export class PassyFireBackendProvider implements BackendBaseClass {
constructor(parameters: string) {
this.logs = [];
this.clients = [];
this.proxies = [];
this.proxies = [];
this.state = "stopped";
this.options = parseBackendProviderString(parameters);
@ -100,17 +116,19 @@ export class PassyFireBackendProvider implements BackendBaseClass {
this.fastify = Fastify({
logger: true,
trustProxy: this.options.isProxied
trustProxy: this.options.isProxied,
});
await this.fastify.register(fastifyWebsocket);
route(this);
this.fastify.get("/", { websocket: true }, (ws, req) => requestHandler(this, ws, req));
this.fastify.get("/", { websocket: true }, (ws, req) =>
requestHandler(this, ws, req),
);
await this.fastify.listen({
port: this.options.port,
host: this.options.ip
host: this.options.ip,
});
this.state = "started";
@ -124,18 +142,23 @@ export class PassyFireBackendProvider implements BackendBaseClass {
this.users.splice(0, this.users.length);
this.proxies.splice(0, this.proxies.length);
this.clients.splice(0, this.clients.length);
return true;
};
addConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): void {
return true;
}
addConnection(
sourceIP: string,
sourcePort: number,
destPort: number,
protocol: "tcp" | "udp",
): void {
const proxy: ForwardRuleExt = {
sourceIP,
sourcePort,
destPort,
protocol,
userConfig: {}
userConfig: {},
};
for (const user of this.options.users) {
@ -143,29 +166,49 @@ export class PassyFireBackendProvider implements BackendBaseClass {
}
this.proxies.push(proxy);
};
}
removeConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): void {
const connectionCheck = PassyFireBackendProvider.checkParametersConnection(sourceIP, sourcePort, destPort, protocol);
removeConnection(
sourceIP: string,
sourcePort: number,
destPort: number,
protocol: "tcp" | "udp",
): void {
const connectionCheck = PassyFireBackendProvider.checkParametersConnection(
sourceIP,
sourcePort,
destPort,
protocol,
);
if (!connectionCheck.success) throw new Error(connectionCheck.message);
const foundProxyEntry = this.proxies.find((i) => i.sourceIP == sourceIP && i.sourcePort == sourcePort && i.destPort == destPort);
const foundProxyEntry = this.proxies.find(
i =>
i.sourceIP == sourceIP &&
i.sourcePort == sourcePort &&
i.destPort == destPort,
);
if (!foundProxyEntry) return;
this.proxies.splice(this.proxies.indexOf(foundProxyEntry), 1);
return;
};
}
getAllConnections(): ConnectedClient[] {
if (this.clients == null) return [];
return this.clients;
};
}
static checkParametersConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): ParameterReturnedValue {
static checkParametersConnection(
sourceIP: string,
sourcePort: number,
destPort: number,
protocol: "tcp" | "udp",
): ParameterReturnedValue {
return {
success: true
}
};
success: true,
};
}
static checkParametersBackendInstance(data: string): ParameterReturnedValue {
try {
@ -174,12 +217,12 @@ export class PassyFireBackendProvider implements BackendBaseClass {
} catch (e: Error) {
return {
success: false,
message: e.toString()
}
message: e.toString(),
};
}
return {
success: true
}
};
}
success: true,
};
}
}

View file

@ -3,7 +3,7 @@ import type { PassyFireBackendProvider } from "./index.js";
export function route(instance: PassyFireBackendProvider) {
const { fastify } = instance;
const proxiedPort: number = instance.options.publicPort ?? 443;
const unsupportedSpoofedRoutes: string[] = [
@ -16,7 +16,7 @@ export function route(instance: PassyFireBackendProvider) {
"/api/v1/tunnels/stop",
// Same scenario for this API.
"/api/v1/users",
"/api/v1/users",
"/api/v1/users/add",
"/api/v1/users/remove",
"/api/v1/users/enable",
@ -31,7 +31,7 @@ export function route(instance: PassyFireBackendProvider) {
add: true,
remove: true,
get: true,
getPasswords: true
getPasswords: true,
},
routes: {
add: true,
@ -39,125 +39,140 @@ export function route(instance: PassyFireBackendProvider) {
start: true,
stop: true,
get: true,
getPasswords: true
}
}
}
getPasswords: true,
},
},
};
});
for (const spoofedRoute of unsupportedSpoofedRoutes) {
fastify.post(spoofedRoute, (req, res) => {
if (typeof req.body != "string") return res.status(400).send({
error: "Invalid token"
});
if (typeof req.body != "string")
return res.status(400).send({
error: "Invalid token",
});
try {
JSON.parse(req.body);
} catch (e) {
return res.status(400).send({
error: "Invalid token"
})
error: "Invalid token",
});
}
// @ts-ignore
if (!req.body.token) return res.status(400).send({
error: "Invalid token"
});
if (!req.body.token)
return res.status(400).send({
error: "Invalid token",
});
return res.status(403).send({
error: "Invalid scope(s)"
error: "Invalid scope(s)",
});
})
});
}
fastify.post("/api/v1/users/login", {
schema: {
body: {
type: "object",
required: ["username", "password"],
fastify.post(
"/api/v1/users/login",
{
schema: {
body: {
type: "object",
required: ["username", "password"],
properties: {
username: { type: "string" },
password: { type: "string" }
}
}
}
}, (req, res) => {
// @ts-ignore
const body: {
username: string,
password: string
} = req.body;
if (!instance.options.users.find((i) => i.username == body.username && i.password == body.password)) {
return res.status(403).send({
error: "Invalid username/password."
});
};
const token = generateRandomData();
instance.users.push({
username: body.username,
token
});
return {
success: true,
data: {
token
}
}
});
fastify.post("/api/v1/tunnels", {
schema: {
body: {
type: "object",
required: ["token"],
properties: {
token: { type: "string" },
properties: {
username: { type: "string" },
password: { type: "string" },
},
},
},
},
}, async (req, res) => {
// @ts-ignore
const body: {
token: string
} = req.body;
(req, res) => {
// @ts-ignore
const body: {
username: string;
password: string;
} = req.body;
const userData = instance.users.find(user => user.token == body.token);
if (
!instance.options.users.find(
i => i.username == body.username && i.password == body.password,
)
) {
return res.status(403).send({
error: "Invalid username/password.",
});
}
if (!userData) return res.status(403).send({
error: "Invalid token"
});
const token = generateRandomData();
// const host = req.hostname.substring(0, req.hostname.indexOf(":"));
const unparsedPort = req.hostname.substring(req.hostname.indexOf(":") + 1);
// @ts-ignore
// parseInt(...) can take a number just fine, at least in Node.JS
const port = parseInt(unparsedPort == "" ? proxiedPort : unparsedPort);
instance.users.push({
username: body.username,
token,
});
// This protocol is so confusing. I'm sorry.
res.send({
success: true,
data: instance.proxies.map((proxy) => ({
proxyUrlSettings: {
host: "sameAs", // Makes pfC work (this is by design apparently)
port,
protocol: proxy.protocol.toUpperCase()
return {
success: true,
data: {
token,
},
dest: `${proxy.sourceIP}:${proxy.destPort}`,
name: `${proxy.protocol.toUpperCase()} on ::${proxy.sourcePort} -> ::${proxy.destPort}`,
passwords: [
proxy.userConfig[userData.username]
],
};
},
);
running: true
}))
});
});
}
fastify.post(
"/api/v1/tunnels",
{
schema: {
body: {
type: "object",
required: ["token"],
properties: {
token: { type: "string" },
},
},
},
},
async (req, res) => {
// @ts-ignore
const body: {
token: string;
} = req.body;
const userData = instance.users.find(user => user.token == body.token);
if (!userData)
return res.status(403).send({
error: "Invalid token",
});
// const host = req.hostname.substring(0, req.hostname.indexOf(":"));
const unparsedPort = req.hostname.substring(
req.hostname.indexOf(":") + 1,
);
// @ts-ignore
// parseInt(...) can take a number just fine, at least in Node.JS
const port = parseInt(unparsedPort == "" ? proxiedPort : unparsedPort);
// This protocol is so confusing. I'm sorry.
res.send({
success: true,
data: instance.proxies.map(proxy => ({
proxyUrlSettings: {
host: "sameAs", // Makes pfC work (this is by design apparently)
port,
protocol: proxy.protocol.toUpperCase(),
},
dest: `${proxy.sourceIP}:${proxy.destPort}`,
name: `${proxy.protocol.toUpperCase()} on ::${proxy.sourcePort} -> ::${proxy.destPort}`,
passwords: [proxy.userConfig[userData.username]],
running: true,
})),
});
},
);
}

View file

@ -9,7 +9,12 @@ import type { ConnectedClientExt, PassyFireBackendProvider } from "./index.js";
// This code sucks because this protocol sucks BUUUT it works, and I don't wanna reinvent
// the gosh darn wheel for (almost) no reason
function authenticateSocket(instance: PassyFireBackendProvider, ws: WebSocket, message: string, state: ConnectedClientExt): Boolean {
function authenticateSocket(
instance: PassyFireBackendProvider,
ws: WebSocket,
message: string,
state: ConnectedClientExt,
): Boolean {
if (!message.startsWith("Accept: ")) {
ws.send("400 Bad Request");
return false;
@ -25,13 +30,13 @@ function authenticateSocket(instance: PassyFireBackendProvider, ws: WebSocket, m
for (const proxy of instance.proxies) {
for (const username of Object.keys(proxy.userConfig)) {
const currentToken = proxy.userConfig[username];
if (token == currentToken) {
state.connectionDetails = proxy;
state.username = username;
};
};
};
}
}
}
if (state.connectionDetails && state.username) {
ws.send("AcceptResponse Bearer: true");
@ -44,7 +49,11 @@ function authenticateSocket(instance: PassyFireBackendProvider, ws: WebSocket, m
return false;
}
export function requestHandler(instance: PassyFireBackendProvider, ws: WebSocket, req: FastifyRequest) {
export function requestHandler(
instance: PassyFireBackendProvider,
ws: WebSocket,
req: FastifyRequest,
) {
let state: "authentication" | "data" = "authentication";
let socket: dgram.Socket | net.Socket | undefined;
@ -52,25 +61,31 @@ export function requestHandler(instance: PassyFireBackendProvider, ws: WebSocket
let connectedClient: ConnectedClientExt = {};
ws.on("close", () => {
instance.clients.splice(instance.clients.indexOf(connectedClient as ConnectedClientExt), 1);
instance.clients.splice(
instance.clients.indexOf(connectedClient as ConnectedClientExt),
1,
);
});
ws.on("message", (rawData: ArrayBuffer) => {
if (state == "authentication") {
const data = rawData.toString();
if (authenticateSocket(instance, ws, data, connectedClient)) {
ws.send("AcceptResponse Bearer: true");
connectedClient.ip = req.ip;
connectedClient.port = req.socket.remotePort ?? -1;
instance.clients.push(connectedClient);
if (connectedClient.connectionDetails.protocol == "tcp") {
socket = new net.Socket();
socket.connect(connectedClient.connectionDetails.sourcePort, connectedClient.connectionDetails.sourceIP);
socket.connect(
connectedClient.connectionDetails.sourcePort,
connectedClient.connectionDetails.sourceIP,
);
socket.on("connect", () => {
state = "data";
@ -79,7 +94,7 @@ export function requestHandler(instance: PassyFireBackendProvider, ws: WebSocket
ws.send("InitProxy: Connected");
});
socket.on("data", (data) => {
socket.on("data", data => {
ws.send(data);
});
} else if (connectedClient.connectionDetails.protocol == "udp") {
@ -90,7 +105,11 @@ export function requestHandler(instance: PassyFireBackendProvider, ws: WebSocket
ws.send("InitProxy: Connected");
socket.on("message", (data, rinfo) => {
if (rinfo.address != connectedClient.connectionDetails.sourceIP || rinfo.port != connectedClient.connectionDetails.sourcePort) return;
if (
rinfo.address != connectedClient.connectionDetails.sourceIP ||
rinfo.port != connectedClient.connectionDetails.sourcePort
)
return;
ws.send(data);
});
}
@ -98,17 +117,24 @@ export function requestHandler(instance: PassyFireBackendProvider, ws: WebSocket
} else if (state == "data") {
if (socket instanceof dgram.Socket) {
const array = new Uint8Array(rawData);
socket.send(array, connectedClient.connectionDetails.sourcePort, connectedClient.connectionDetails.sourceIP, (err) => {
if (err) throw err;
});
socket.send(
array,
connectedClient.connectionDetails.sourcePort,
connectedClient.connectionDetails.sourceIP,
err => {
if (err) throw err;
},
);
} else if (socket instanceof net.Socket) {
const array = new Uint8Array(rawData);
socket.write(array);
}
} else {
throw new Error(`Whooops, our WebSocket reached an unsupported state: '${state}'`);
throw new Error(
`Whooops, our WebSocket reached an unsupported state: '${state}'`,
);
}
});
}
}

View file

@ -55,7 +55,7 @@ const backends: Record<number, BackendBaseClass> = {};
const fastify = Fastify({
logger: true,
trustProxy: Boolean(process.env.IS_BEHIND_PROXY)
trustProxy: Boolean(process.env.IS_BEHIND_PROXY),
});
const routeOptions: RouteOptions = {