diff --git a/backend-legacy/src/backendimpl/base.ts b/backend-legacy/src/backendimpl/base.ts deleted file mode 100644 index 6a8d458..0000000 --- a/backend-legacy/src/backendimpl/base.ts +++ /dev/null @@ -1,77 +0,0 @@ -// @eslint-ignore-file - -export type ParameterReturnedValue = { - success: boolean; - message?: string; -}; - -export type ForwardRule = { - sourceIP: string; - sourcePort: number; - destPort: number; -}; - -export type ConnectedClient = { - ip: string; - port: number; - - connectionDetails: ForwardRule; -}; - -export class BackendBaseClass { - state: "stopped" | "stopping" | "started" | "starting"; - - clients?: ConnectedClient[]; // Not required to be implemented, but more consistency - logs: string[]; - - constructor(parameters: string) { - this.logs = []; - this.clients = []; - - this.state = "stopped"; - } - - addConnection( - sourceIP: string, - sourcePort: number, - destPort: number, - protocol: "tcp" | "udp", - ): void {} - - removeConnection( - sourceIP: string, - sourcePort: number, - destPort: number, - protocol: "tcp" | "udp", - ): void {} - - async start(): Promise { - return true; - } - - async stop(): Promise { - return true; - } - - getAllConnections(): ConnectedClient[] { - if (this.clients == null) return []; - return this.clients; - } - - static checkParametersConnection( - sourceIP: string, - sourcePort: number, - destPort: number, - protocol: "tcp" | "udp", - ): ParameterReturnedValue { - return { - success: true, - }; - } - - static checkParametersBackendInstance(data: string): ParameterReturnedValue { - return { - success: true, - }; - } -} diff --git a/backend-legacy/src/backendimpl/index.ts b/backend-legacy/src/backendimpl/index.ts deleted file mode 100644 index 776abf8..0000000 --- a/backend-legacy/src/backendimpl/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BackendBaseClass } from "./base.js"; - -import { PassyFireBackendProvider } from "./passyfire-reimpl/index.js"; -import { SSHBackendProvider } from "./ssh.js"; - -export const backendProviders: Record = { - ssh: SSHBackendProvider, - passyfire: PassyFireBackendProvider, -}; - -if (process.env.NODE_ENV != "production") { - backendProviders["dummy"] = BackendBaseClass; -} diff --git a/backend-legacy/src/backendimpl/passyfire-reimpl/index.ts b/backend-legacy/src/backendimpl/passyfire-reimpl/index.ts deleted file mode 100644 index de454ed..0000000 --- a/backend-legacy/src/backendimpl/passyfire-reimpl/index.ts +++ /dev/null @@ -1,231 +0,0 @@ -import fastifyWebsocket from "@fastify/websocket"; - -import type { FastifyInstance } from "fastify"; -import Fastify from "fastify"; - -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; -}; - -export type ForwardRuleExt = ForwardRule & { - protocol: "tcp" | "udp"; - userConfig: Record; -}; - -export type ConnectedClientExt = ConnectedClient & { - connectionDetails: ForwardRuleExt; - username: string; -}; - -// Fight me (for better naming) -type BackendParsedProviderString = { - ip: string; - port: number; - publicPort?: number; - isProxied?: boolean; - - users: BackendProviderUser[]; -}; - -type LoggedInUser = { - username: string; - token: string; -}; - -function parseBackendProviderString(data: string): BackendParsedProviderString { - try { - JSON.parse(data); - } catch (e) { - throw new Error("Payload body is not JSON"); - } - - const jsonData = JSON.parse(data); - - 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 (!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"); - } - - return { - ip: jsonData.ip, - port: jsonData.port, - - publicPort: jsonData.publicPort, - isProxied: jsonData.isProxied, - - users: jsonData.users, - }; -} - -export class PassyFireBackendProvider implements BackendBaseClass { - state: "stopped" | "stopping" | "started" | "starting"; - - clients: ConnectedClientExt[]; - proxies: ForwardRuleExt[]; - users: LoggedInUser[]; - logs: string[]; - - options: BackendParsedProviderString; - fastify: FastifyInstance; - - constructor(parameters: string) { - this.logs = []; - this.clients = []; - this.proxies = []; - - this.state = "stopped"; - this.options = parseBackendProviderString(parameters); - - this.users = []; - } - - async start(): Promise { - this.state = "starting"; - - this.fastify = Fastify({ - logger: true, - trustProxy: this.options.isProxied, - }); - - await this.fastify.register(fastifyWebsocket); - route(this); - - this.fastify.get("/", { websocket: true }, (ws, req) => - requestHandler(this, ws, req), - ); - - await this.fastify.listen({ - port: this.options.port, - host: this.options.ip, - }); - - this.state = "started"; - - return true; - } - - async stop(): Promise { - await this.fastify.close(); - - 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 { - const proxy: ForwardRuleExt = { - sourceIP, - sourcePort, - destPort, - protocol, - - userConfig: {}, - }; - - for (const user of this.options.users) { - proxy.userConfig[user.username] = generateRandomData(); - } - - this.proxies.push(proxy); - } - - 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, - ); - 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 { - return { - success: true, - }; - } - - static checkParametersBackendInstance(data: string): ParameterReturnedValue { - try { - parseBackendProviderString(data); - // @ts-expect-error: We write the function, and we know we're returning an error - } catch (e: Error) { - return { - success: false, - message: e.toString(), - }; - } - - return { - success: true, - }; - } -} diff --git a/backend-legacy/src/backendimpl/passyfire-reimpl/routes.ts b/backend-legacy/src/backendimpl/passyfire-reimpl/routes.ts deleted file mode 100644 index d814e12..0000000 --- a/backend-legacy/src/backendimpl/passyfire-reimpl/routes.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { generateRandomData } from "../../libs/generateRandom.js"; -import type { PassyFireBackendProvider } from "./index.js"; - -export function route(instance: PassyFireBackendProvider) { - const { fastify } = instance; - - const proxiedPort: number = instance.options.publicPort ?? 443; - - const unsupportedSpoofedRoutes: string[] = [ - "/api/v1/tunnels/add", - "/api/v1/tunnels/edit", - "/api/v1/tunnels/remove", - - // TODO (greysoh): Should we implement these? We have these for internal reasons. We could expose these /shrug - "/api/v1/tunnels/start", - "/api/v1/tunnels/stop", - - // Same scenario for this API. - "/api/v1/users", - "/api/v1/users/add", - "/api/v1/users/remove", - "/api/v1/users/enable", - "/api/v1/users/disable", - ]; - - fastify.get("/api/v1/static/getScopes", () => { - return { - success: true, - data: { - users: { - add: true, - remove: true, - get: true, - getPasswords: true, - }, - routes: { - add: true, - remove: true, - start: true, - stop: true, - get: true, - getPasswords: true, - }, - }, - }; - }); - - for (const spoofedRoute of unsupportedSpoofedRoutes) { - fastify.post(spoofedRoute, (req, res) => { - return res.status(403).send({ - error: "Invalid scope(s)", - }); - }); - } - - fastify.post( - "/api/v1/users/login", - { - schema: { - body: { - type: "object", - required: ["username", "password"], - - properties: { - username: { type: "string" }, - password: { type: "string" }, - }, - }, - }, - }, - (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - 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" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - 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-expect-error: 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, - })), - }); - }, - ); -} diff --git a/backend-legacy/src/backendimpl/passyfire-reimpl/socket.ts b/backend-legacy/src/backendimpl/passyfire-reimpl/socket.ts deleted file mode 100644 index ee9607a..0000000 --- a/backend-legacy/src/backendimpl/passyfire-reimpl/socket.ts +++ /dev/null @@ -1,140 +0,0 @@ -import dgram from "node:dgram"; -import net from "node:net"; - -import type { WebSocket } from "@fastify/websocket"; -import type { FastifyRequest } from "fastify"; - -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 { - if (!message.startsWith("Accept: ")) { - ws.send("400 Bad Request"); - return false; - } - - const type = message.substring(message.indexOf(":") + 1).trim(); - - if (type == "IsPassedWS") { - ws.send("AcceptResponse IsPassedWS: true"); - } else if (type.startsWith("Bearer")) { - const token = type.substring(type.indexOf("Bearer") + 7); - - 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"); - return true; - } else { - ws.send("AcceptResponse Bearer: false"); - } - } - - return false; -} - -export function requestHandler( - instance: PassyFireBackendProvider, - ws: WebSocket, - req: FastifyRequest, -) { - let state: "authentication" | "data" = "authentication"; - let socket: dgram.Socket | net.Socket | undefined; - - // @ts-expect-error: FIXME because this is a mess - const connectedClient: ConnectedClientExt = {}; - - ws.on("close", () => { - 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.on("connect", () => { - state = "data"; - - ws.send("InitProxy: Attempting to connect"); - ws.send("InitProxy: Connected"); - }); - - socket.on("data", data => { - ws.send(data); - }); - } else if (connectedClient.connectionDetails.protocol == "udp") { - socket = dgram.createSocket("udp4"); - state = "data"; - - ws.send("InitProxy: Attempting to connect"); - ws.send("InitProxy: Connected"); - - socket.on("message", (data, rinfo) => { - if ( - rinfo.address != connectedClient.connectionDetails.sourceIP || - rinfo.port != connectedClient.connectionDetails.sourcePort - ) - return; - ws.send(data); - }); - } - } - } 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; - }, - ); - } 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}'`, - ); - } - }); -} diff --git a/backend-legacy/src/backendimpl/ssh.ts b/backend-legacy/src/backendimpl/ssh.ts deleted file mode 100644 index 7866924..0000000 --- a/backend-legacy/src/backendimpl/ssh.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { NodeSSH } from "node-ssh"; -import { Socket } from "node:net"; - -import type { - BackendBaseClass, - ForwardRule, - ConnectedClient, - ParameterReturnedValue, -} from "./base.js"; - -import { - TcpConnectionDetails, - AcceptConnection, - ClientChannel, - RejectConnection, -} from "ssh2"; - -type ForwardRuleExt = ForwardRule & { - enabled: boolean; -}; - -// Fight me (for better naming) -type BackendParsedProviderString = { - ip: string; - port: number; - - username: string; - privateKey: string; - - listenOnIPs: string[]; -}; - -function parseBackendProviderString(data: string): BackendParsedProviderString { - try { - JSON.parse(data); - } catch (e) { - throw new Error("Payload body is not JSON"); - } - - const jsonData = JSON.parse(data); - - 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.username != "string") { - throw new Error("Username is not a string"); - } - - if (typeof jsonData.privateKey != "string") { - throw new Error("Private key is not a string"); - } - - let listenOnIPs: string[] = []; - - if (!Array.isArray(jsonData.listenOnIPs)) { - listenOnIPs.push("0.0.0.0"); - } else { - listenOnIPs = jsonData.listenOnIPs; - } - - return { - ip: jsonData.ip, - port: jsonData.port, - - username: jsonData.username, - privateKey: jsonData.privateKey, - - listenOnIPs, - }; -} - -export class SSHBackendProvider implements BackendBaseClass { - state: "stopped" | "stopping" | "started" | "starting"; - - clients: ConnectedClient[]; - proxies: ForwardRuleExt[]; - logs: string[]; - - sshInstance: NodeSSH; - options: BackendParsedProviderString; - - constructor(parameters: string) { - this.logs = []; - this.proxies = []; - this.clients = []; - - this.options = parseBackendProviderString(parameters); - - this.state = "stopped"; - } - - async start(): Promise { - this.state = "starting"; - this.logs.push("Starting SSHBackendProvider..."); - - if (this.sshInstance) { - this.sshInstance.dispose(); - } - - this.sshInstance = new NodeSSH(); - - try { - await this.sshInstance.connect({ - host: this.options.ip, - port: this.options.port, - - username: this.options.username, - privateKey: this.options.privateKey, - }); - } catch (e) { - this.logs.push(`Failed to start SSHBackendProvider! Error: '${e}'`); - this.state = "stopped"; - - // @ts-expect-error: We know that stuff will be initialized in order, so this will be safe - this.sshInstance = null; - - return false; - } - - if (this.sshInstance.connection) { - this.sshInstance.connection.on("end", async () => { - if (this.state != "started") return; - this.logs.push("We disconnected from the SSH server. Restarting..."); - - // Create a new array from the existing list of proxies, so we have a backup of the proxy list before - // we wipe the list of all proxies and clients (as we're disconnected anyways) - const proxies = Array.from(this.proxies); - - this.proxies.splice(0, this.proxies.length); - this.clients.splice(0, this.clients.length); - - await this.start(); - - if (this.state != "started") return; - - for (const proxy of proxies) { - if (!proxy.enabled) continue; - - this.addConnection( - proxy.sourceIP, - proxy.sourcePort, - proxy.destPort, - "tcp", - ); - } - }); - } - - this.state = "started"; - this.logs.push("Successfully started SSHBackendProvider."); - - return true; - } - - async stop(): Promise { - this.state = "stopping"; - this.logs.push("Stopping SSHBackendProvider..."); - - this.proxies.splice(0, this.proxies.length); - - this.sshInstance.dispose(); - - // @ts-expect-error: We know that stuff will be initialized in order, so this will be safe - this.sshInstance = null; - - this.logs.push("Successfully stopped SSHBackendProvider."); - this.state = "stopped"; - - return true; - } - - addConnection( - sourceIP: string, - sourcePort: number, - destPort: number, - protocol: "tcp" | "udp", - ): void { - const connectionCheck = SSHBackendProvider.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, - ); - - if (foundProxyEntry) return; - - const connCallback = ( - info: TcpConnectionDetails, - accept: AcceptConnection, - reject: RejectConnection, - ) => { - const foundProxyEntry = this.proxies.find( - i => - i.sourceIP == sourceIP && - i.sourcePort == sourcePort && - i.destPort == destPort, - ); - - if (!foundProxyEntry || !foundProxyEntry.enabled) return reject(); - - const client: ConnectedClient = { - ip: info.srcIP, - port: info.srcPort, - - connectionDetails: foundProxyEntry, - }; - - this.clients.push(client); - - const srcConn = new Socket(); - - srcConn.connect({ - host: sourceIP, - port: sourcePort, - }); - - // Why is this so confusing - const destConn = accept(); - - destConn.addListener("data", (chunk: Uint8Array) => { - srcConn.write(chunk); - }); - - destConn.addListener("end", () => { - this.clients.splice(this.clients.indexOf(client), 1); - srcConn.end(); - }); - - srcConn.on("data", data => { - destConn.write(data); - }); - - srcConn.on("end", () => { - this.clients.splice(this.clients.indexOf(client), 1); - destConn.end(); - }); - }; - - for (const ip of this.options.listenOnIPs) { - this.sshInstance.forwardIn(ip, destPort, connCallback); - } - - this.proxies.push({ - sourceIP, - sourcePort, - destPort, - - enabled: true, - }); - } - - removeConnection( - sourceIP: string, - sourcePort: number, - destPort: number, - protocol: "tcp" | "udp", - ): void { - const connectionCheck = SSHBackendProvider.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, - ); - - if (!foundProxyEntry) return; - - foundProxyEntry.enabled = false; - } - - getAllConnections(): ConnectedClient[] { - return this.clients; - } - - static checkParametersConnection( - sourceIP: string, - sourcePort: number, - destPort: number, - protocol: "tcp" | "udp", - ): ParameterReturnedValue { - if (protocol == "udp") { - return { - success: false, - message: - "SSH does not support UDP tunneling! Please use something like PortCopier instead (if it gets done)", - }; - } - - return { - success: true, - }; - } - - static checkParametersBackendInstance(data: string): ParameterReturnedValue { - try { - parseBackendProviderString(data); - // @ts-expect-error: We write the function, and we know we're returning an error - } catch (e: Error) { - return { - success: false, - message: e.toString(), - }; - } - - return { - success: true, - }; - } -} diff --git a/backend-legacy/src/index.ts b/backend-legacy/src/index.ts deleted file mode 100644 index a411f6a..0000000 --- a/backend-legacy/src/index.ts +++ /dev/null @@ -1,140 +0,0 @@ -import process from "node:process"; - -import { PrismaClient } from "@prisma/client"; -import Fastify from "fastify"; - -import type { - ServerOptions, - SessionToken, - RouteOptions, -} from "./libs/types.js"; - -import type { BackendBaseClass } from "./backendimpl/base.js"; - -import { route as getPermissions } from "./routes/getPermissions.js"; - -import { route as backendCreate } from "./routes/backends/create.js"; -import { route as backendRemove } from "./routes/backends/remove.js"; -import { route as backendLookup } from "./routes/backends/lookup.js"; - -import { route as forwardConnections } from "./routes/forward/connections.js"; -import { route as forwardCreate } from "./routes/forward/create.js"; -import { route as forwardRemove } from "./routes/forward/remove.js"; -import { route as forwardLookup } from "./routes/forward/lookup.js"; -import { route as forwardStart } from "./routes/forward/start.js"; -import { route as forwardStop } from "./routes/forward/stop.js"; - -import { route as userCreate } from "./routes/user/create.js"; -import { route as userRemove } from "./routes/user/remove.js"; -import { route as userLookup } from "./routes/user/lookup.js"; -import { route as userLogin } from "./routes/user/login.js"; - -import { backendInit } from "./libs/backendInit.js"; - -const prisma = new PrismaClient(); - -const isSignupEnabled = Boolean(process.env.IS_SIGNUP_ENABLED); -const unsafeAdminSignup = Boolean(process.env.UNSAFE_ADMIN_SIGNUP); - -const noUsersCheck = (await prisma.user.count()) == 0; - -if (unsafeAdminSignup) { - console.error( - "WARNING: You have admin sign up on! This means that anyone that signs up will have admin rights!", - ); -} - -const serverOptions: ServerOptions = { - isSignupEnabled: isSignupEnabled ? true : noUsersCheck, - isSignupAsAdminEnabled: unsafeAdminSignup ? true : noUsersCheck, - - allowUnsafeGlobalTokens: process.env.NODE_ENV != "production", -}; - -const sessionTokens: Record = {}; -const backends: Record = {}; - -const loggerEnv = { - development: { - transport: { - target: "pino-pretty", - options: { - translateTime: "HH:MM:ss Z", - ignore: "pid,hostname,time", - }, - }, - }, - production: true, - test: false, -}; - -const fastify = Fastify({ - logger: - process.env.NODE_ENV == "production" - ? loggerEnv.production - : loggerEnv.development, - trustProxy: Boolean(process.env.IS_BEHIND_PROXY), -}); - -const routeOptions: RouteOptions = { - fastify: fastify, - prisma: prisma, - tokens: sessionTokens, - options: serverOptions, - - backends: backends, -}; - -fastify.log.info("Initializing forwarding rules..."); - -const createdBackends = await prisma.desinationProvider.findMany(); - -const logWrapper = (arg: string) => fastify.log.info(arg); -const errorWrapper = (arg: string) => fastify.log.error(arg); - -for (const backend of createdBackends) { - fastify.log.info( - `Running init steps for ID '${backend.id}' (${backend.name})`, - ); - - const init = await backendInit( - backend, - backends, - prisma, - logWrapper, - errorWrapper, - ); - - if (init) fastify.log.info("Init successful."); -} - -fastify.log.info("Done."); - -getPermissions(routeOptions); - -backendCreate(routeOptions); -backendRemove(routeOptions); -backendLookup(routeOptions); - -forwardConnections(routeOptions); -forwardCreate(routeOptions); -forwardRemove(routeOptions); -forwardLookup(routeOptions); -forwardStart(routeOptions); -forwardStop(routeOptions); - -userCreate(routeOptions); -userRemove(routeOptions); -userLookup(routeOptions); -userLogin(routeOptions); - -// Run the server! -try { - await fastify.listen({ - port: 3000, - host: process.env.NODE_ENV == "production" ? "0.0.0.0" : "127.0.0.1", - }); -} catch (err) { - fastify.log.error(err); - process.exit(1); -} diff --git a/backend-legacy/src/libs/backendInit.ts b/backend-legacy/src/libs/backendInit.ts deleted file mode 100644 index 36c8e0b..0000000 --- a/backend-legacy/src/libs/backendInit.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { format } from "node:util"; - -import type { PrismaClient } from "@prisma/client"; - -import { backendProviders } from "../backendimpl/index.js"; -import { BackendBaseClass } from "../backendimpl/base.js"; - -type Backend = { - id: number; - name: string; - description: string | null; - backend: string; - connectionDetails: string; -}; - -export async function backendInit( - backend: Backend, - backends: Record, - prisma: PrismaClient, - logger?: (arg: string) => void, - errorOut?: (arg: string) => void, -): Promise { - const log = (...args: string[]) => - logger ? logger(format(...args)) : console.log(...args); - - const error = (...args: string[]) => - errorOut ? errorOut(format(...args)) : log(...args); - - const ourProvider = backendProviders[backend.backend]; - - if (!ourProvider) { - error(" - Error: Invalid backend recieved!"); - - // Prevent crashes when we don't recieve a backend - backends[backend.id] = new BackendBaseClass(""); - - backends[backend.id].logs.push("** Failed To Create Backend **"); - - backends[backend.id].logs.push( - "Reason: Invalid backend recieved (couldn't find the backend to use!)", - ); - - return false; - } - - log(" - Initializing backend..."); - - backends[backend.id] = new ourProvider(backend.connectionDetails); - const ourBackend = backends[backend.id]; - - if (!(await ourBackend.start())) { - error(" - Error initializing backend!"); - error(" - " + ourBackend.logs.join("\n - ")); - - return false; - } - - log(" - Initializing clients..."); - - const clients = await prisma.forwardRule.findMany({ - where: { - destProviderID: backend.id, - enabled: true, - }, - }); - - for (const client of clients) { - if (client.protocol != "tcp" && client.protocol != "udp") { - error( - ` - Error: Client with ID of '${client.id}' has an invalid protocol! (must be either TCP or UDP)`, - ); - continue; - } - - ourBackend.addConnection( - client.sourceIP, - client.sourcePort, - client.destPort, - client.protocol, - ); - } - - return true; -} diff --git a/backend-legacy/src/libs/generateRandom.ts b/backend-legacy/src/libs/generateRandom.ts deleted file mode 100644 index 987986e..0000000 --- a/backend-legacy/src/libs/generateRandom.ts +++ /dev/null @@ -1,22 +0,0 @@ -function getRandomInt(min: number, max: number): number { - const minCeiled = Math.ceil(min); - const maxFloored = Math.floor(max); - return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive -} - -export function generateRandomData(length: number = 128): string { - let newString = ""; - - for (let i = 0; i < length; i += 2) { - const randomNumber = getRandomInt(0, 255); - - if (randomNumber == 0) { - i -= 2; - continue; - } - - newString += randomNumber.toString(16); - } - - return newString; -} diff --git a/backend-legacy/src/libs/permissions.ts b/backend-legacy/src/libs/permissions.ts deleted file mode 100644 index d0b0601..0000000 --- a/backend-legacy/src/libs/permissions.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { PrismaClient } from "@prisma/client"; -import type { SessionToken } from "./types.js"; - -export const permissionListDisabled: Record = { - "routes.add": false, - "routes.remove": false, - "routes.start": false, - "routes.stop": false, - "routes.edit": false, - "routes.visible": false, - "routes.visibleConn": false, - - "backends.add": false, - "backends.remove": false, - "backends.start": false, - "backends.stop": false, - "backends.edit": false, - "backends.visible": false, - "backends.secretVis": false, - - "permissions.see": false, - - "users.add": false, - "users.remove": false, - "users.lookup": false, - "users.edit": false, -}; - -// FIXME: This solution fucking sucks. -export const permissionListEnabled: Record = JSON.parse( - JSON.stringify(permissionListDisabled), -); - -for (const index of Object.keys(permissionListEnabled)) { - permissionListEnabled[index] = true; -} - -export async function hasPermission( - permissionList: string[], - uid: number, - prisma: PrismaClient, -): Promise { - for (const permission of permissionList) { - const permissionNode = await prisma.permission.findFirst({ - where: { - userID: uid, - permission, - }, - }); - - if (!permissionNode || !permissionNode.has) return false; - } - - return true; -} - -export async function getUID( - token: string, - tokens: Record, - prisma: PrismaClient, -): Promise { - let userID = -1; - - // Look up in our currently authenticated users - for (const otherTokenKey of Object.keys(tokens)) { - const otherTokenList = tokens[parseInt(otherTokenKey)]; - - for (const otherTokenIndex in otherTokenList) { - const otherToken = otherTokenList[otherTokenIndex]; - - if (otherToken.token == token) { - if ( - otherToken.expiresAt < - otherToken.createdAt + (otherToken.createdAt - Date.now()) - ) { - otherTokenList.splice(parseInt(otherTokenIndex), 1); - continue; - } else { - userID = parseInt(otherTokenKey); - } - } - } - } - - // Fine, we'll look up for global tokens... - // FIXME: Could this be more efficient? IDs are sequential in SQL I think - if (userID == -1) { - const allUsers = await prisma.user.findMany({ - where: { - isRootServiceAccount: true, - }, - }); - - for (const user of allUsers) { - if (user.rootToken == token) userID = user.id; - } - } - - return userID; -} - -export async function hasPermissionByToken( - permissionList: string[], - token: string, - tokens: Record, - prisma: PrismaClient, -): Promise { - const userID = await getUID(token, tokens, prisma); - return await hasPermission(permissionList, userID, prisma); -} diff --git a/backend-legacy/src/libs/types.ts b/backend-legacy/src/libs/types.ts deleted file mode 100644 index 7638d76..0000000 --- a/backend-legacy/src/libs/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { PrismaClient } from "@prisma/client"; -import type { FastifyInstance } from "fastify"; - -import type { BackendBaseClass } from "../backendimpl/base.js"; - -export type ServerOptions = { - isSignupEnabled: boolean; - isSignupAsAdminEnabled: boolean; - - allowUnsafeGlobalTokens: boolean; -}; - -// NOTE: Someone should probably use Redis for this, but this is fine... -export type SessionToken = { - createdAt: number; - expiresAt: number; // Should be (createdAt + (30 minutes)) - - token: string; -}; - -export type RouteOptions = { - fastify: FastifyInstance; - prisma: PrismaClient; - tokens: Record; - - options: ServerOptions; - backends: Record; -}; diff --git a/backend-legacy/src/routes/ROUTE_PLAN.md b/backend-legacy/src/routes/ROUTE_PLAN.md deleted file mode 100644 index 6b7c0f4..0000000 --- a/backend-legacy/src/routes/ROUTE_PLAN.md +++ /dev/null @@ -1,17 +0,0 @@ -# Route Plan -- [x] /api/v1/users/create -- [x] /api/v1/users/login -- [x] /api/v1/users/remove -- [ ] /api/v1/users/modify -- [x] /api/v1/users/lookup -- [x] /api/v1/backends/create -- [x] /api/v1/backends/remove -- [ ] /api/v1/backends/modify -- [x] /api/v1/backends/lookup -- [x] /api/v1/routes/create -- [x] /api/v1/routes/remove -- [ ] /api/v1/routes/modify -- [x] /api/v1/routes/lookup -- [ ] /api/v1/routes/start -- [ ] /api/v1/routes/stop -- [x] /api/v1/getPermissions \ No newline at end of file diff --git a/backend-legacy/src/routes/backends/create.ts b/backend-legacy/src/routes/backends/create.ts deleted file mode 100644 index 0801572..0000000 --- a/backend-legacy/src/routes/backends/create.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -import { backendProviders } from "../../backendimpl/index.js"; -import { backendInit } from "../../libs/backendInit.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens, backends } = routeOptions; - - const logWrapper = (arg: string) => fastify.log.info(arg); - const errorWrapper = (arg: string) => fastify.log.error(arg); - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new backend to use - */ - fastify.post( - "/api/v1/backends/create", - { - schema: { - body: { - type: "object", - required: ["token", "name", "backend", "connectionDetails"], - - properties: { - token: { type: "string" }, - name: { type: "string" }, - description: { type: "string" }, - backend: { type: "string" }, - connectionDetails: { type: "string" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - name: string; - description?: string; - connectionDetails: string; - backend: string; - } = req.body; - - if (!(await hasPermission(body.token, ["backends.add"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - if (!backendProviders[body.backend]) { - return res.status(400).send({ - error: "Unsupported backend!", - }); - } - - const connectionDetailsValidityCheck = backendProviders[ - body.backend - ].checkParametersBackendInstance(body.connectionDetails); - - if (!connectionDetailsValidityCheck.success) { - return res.status(400).send({ - error: - connectionDetailsValidityCheck.message ?? - "Unknown error while attempting to parse connectionDetails (it's on your side)", - }); - } - - const backend = await prisma.desinationProvider.create({ - data: { - name: body.name, - description: body.description, - - backend: body.backend, - connectionDetails: body.connectionDetails, - }, - }); - - const init = await backendInit( - backend, - backends, - prisma, - logWrapper, - errorWrapper, - ); - - if (!init) { - // TODO: better error code - return res.status(504).send({ - error: "Backend is created, but failed to initalize correctly", - id: backend.id, - }); - } - - return { - success: true, - id: backend.id, - }; - }, - ); -} diff --git a/backend-legacy/src/routes/backends/lookup.ts b/backend-legacy/src/routes/backends/lookup.ts deleted file mode 100644 index a387ffc..0000000 --- a/backend-legacy/src/routes/backends/lookup.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens, backends } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new route to use - */ - fastify.post( - "/api/v1/backends/lookup", - { - schema: { - body: { - type: "object", - required: ["token"], - - properties: { - token: { type: "string" }, - id: { type: "number" }, - name: { type: "string" }, - description: { type: "string" }, - backend: { type: "string" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - id?: number; - name?: string; - description?: string; - backend?: string; - } = req.body; - - if ( - !(await hasPermission(body.token, [ - "backends.visible", // wtf? - ])) - ) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - const canSeeSecrets = await hasPermission(body.token, [ - "backends.secretVis", - ]); - - const prismaBackends = await prisma.desinationProvider.findMany({ - where: { - id: body.id, - name: body.name, - description: body.description, - backend: body.backend, - }, - }); - - return { - success: true, - data: prismaBackends.map(i => ({ - id: i.id, - - name: i.name, - description: i.description, - - backend: i.backend, - connectionDetails: canSeeSecrets ? i.connectionDetails : "", - - logs: backends[i.id].logs, - })), - }; - }, - ); -} diff --git a/backend-legacy/src/routes/backends/remove.ts b/backend-legacy/src/routes/backends/remove.ts deleted file mode 100644 index 711286b..0000000 --- a/backend-legacy/src/routes/backends/remove.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens, backends } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new route to use - */ - fastify.post( - "/api/v1/backends/remove", - { - schema: { - body: { - type: "object", - required: ["token", "id"], - - properties: { - token: { type: "string" }, - id: { type: "number" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - id: number; - } = req.body; - - if (!(await hasPermission(body.token, ["backends.remove"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - if (!backends[body.id]) { - return res.status(400).send({ - error: "Backend not found", - }); - } - - // Unload the backend - if (!(await backends[body.id].stop())) { - return res.status(400).send({ - error: "Failed to stop backend! Please report this issue.", - }); - } - - delete backends[body.id]; - - await prisma.desinationProvider.delete({ - where: { - id: body.id, - }, - }); - - return { - success: true, - }; - }, - ); -} diff --git a/backend-legacy/src/routes/forward/connections.ts b/backend-legacy/src/routes/forward/connections.ts deleted file mode 100644 index 6358ead..0000000 --- a/backend-legacy/src/routes/forward/connections.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens, backends } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - fastify.post( - "/api/v1/forward/connections", - { - schema: { - body: { - type: "object", - required: ["token", "id"], - - properties: { - token: { type: "string" }, - id: { type: "number" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - id: number; - } = req.body; - - if (!(await hasPermission(body.token, ["routes.visibleConn"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - const forward = await prisma.forwardRule.findUnique({ - where: { - id: body.id, - }, - }); - - if (!forward) { - return res.status(400).send({ - error: "Could not find forward entry", - }); - } - - if (!backends[forward.destProviderID]) { - return res.status(400).send({ - error: "Backend not found", - }); - } - - return { - success: true, - data: backends[forward.destProviderID].getAllConnections().filter(i => { - return ( - i.connectionDetails.sourceIP == forward.sourceIP && - i.connectionDetails.sourcePort == forward.sourcePort && - i.connectionDetails.destPort == forward.destPort - ); - }), - }; - }, - ); -} diff --git a/backend-legacy/src/routes/forward/create.ts b/backend-legacy/src/routes/forward/create.ts deleted file mode 100644 index 2363416..0000000 --- a/backend-legacy/src/routes/forward/create.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new route to use - */ - fastify.post( - "/api/v1/forward/create", - { - schema: { - body: { - type: "object", - required: [ - "token", - "name", - "protocol", - "sourceIP", - "sourcePort", - "destinationPort", - "providerID", - ], - - properties: { - token: { type: "string" }, - - name: { type: "string" }, - description: { type: "string" }, - - protocol: { type: "string" }, - - sourceIP: { type: "string" }, - sourcePort: { type: "number" }, - - destinationPort: { type: "number" }, - - providerID: { type: "number" }, - autoStart: { type: "boolean" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - - name: string; - description?: string; - - protocol: "tcp" | "udp"; - - sourceIP: string; - sourcePort: number; - - destinationPort: number; - - providerID: number; - - autoStart?: boolean; - } = req.body; - - if (body.protocol != "tcp" && body.protocol != "udp") { - return res.status(400).send({ - error: "Body protocol field must be either tcp or udp", - }); - } - - if (!(await hasPermission(body.token, ["routes.add"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - const lookupIDForDestProvider = - await prisma.desinationProvider.findUnique({ - where: { - id: body.providerID, - }, - }); - - if (!lookupIDForDestProvider) - return res.status(400).send({ - error: "Could not find provider", - }); - - const forwardRule = await prisma.forwardRule.create({ - data: { - name: body.name, - description: body.description, - - protocol: body.protocol, - - sourceIP: body.sourceIP, - sourcePort: body.sourcePort, - - destPort: body.destinationPort, - destProviderID: body.providerID, - - enabled: Boolean(body.autoStart), - }, - }); - - return { - success: true, - id: forwardRule.id, - }; - }, - ); -} diff --git a/backend-legacy/src/routes/forward/lookup.ts b/backend-legacy/src/routes/forward/lookup.ts deleted file mode 100644 index 3b87c36..0000000 --- a/backend-legacy/src/routes/forward/lookup.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new route to use - */ - fastify.post( - "/api/v1/forward/lookup", - { - schema: { - body: { - type: "object", - required: ["token"], - - properties: { - token: { type: "string" }, - id: { type: "number" }, - - name: { type: "string" }, - protocol: { type: "string" }, - description: { type: "string" }, - - sourceIP: { type: "string" }, - sourcePort: { type: "number" }, - destPort: { type: "number" }, - - providerID: { type: "number" }, - autoStart: { type: "boolean" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - - id?: number; - name?: string; - description?: string; - - protocol?: "tcp" | "udp"; - - sourceIP?: string; - sourcePort?: number; - - destinationPort?: number; - - providerID?: number; - autoStart?: boolean; - } = req.body; - - if (body.protocol && body.protocol != "tcp" && body.protocol != "udp") { - return res.status(400).send({ - error: "Protocol specified in body must be either 'tcp' or 'udp'", - }); - } - - if ( - !(await hasPermission(body.token, [ - "routes.visible", // wtf? - ])) - ) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - const forwardRules = await prisma.forwardRule.findMany({ - where: { - id: body.id, - name: body.name, - description: body.description, - - sourceIP: body.sourceIP, - sourcePort: body.sourcePort, - - destPort: body.destinationPort, - - destProviderID: body.providerID, - enabled: body.autoStart, - }, - }); - - return { - success: true, - data: forwardRules.map(i => ({ - id: i.id, - name: i.name, - description: i.description, - - sourceIP: i.sourceIP, - sourcePort: i.sourcePort, - - destPort: i.destPort, - - providerID: i.destProviderID, - autoStart: i.enabled, // TODO: Add enabled flag in here to see if we're running or not - })), - }; - }, - ); -} diff --git a/backend-legacy/src/routes/forward/remove.ts b/backend-legacy/src/routes/forward/remove.ts deleted file mode 100644 index 264b82a..0000000 --- a/backend-legacy/src/routes/forward/remove.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new route to use - */ - fastify.post( - "/api/v1/forward/remove", - { - schema: { - body: { - type: "object", - required: ["token", "id"], - - properties: { - token: { type: "string" }, - id: { type: "number" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - id: number; - } = req.body; - - if (!(await hasPermission(body.token, ["routes.remove"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - await prisma.forwardRule.delete({ - where: { - id: body.id, - }, - }); - - return { - success: true, - }; - }, - ); -} diff --git a/backend-legacy/src/routes/forward/start.ts b/backend-legacy/src/routes/forward/start.ts deleted file mode 100644 index fea55fa..0000000 --- a/backend-legacy/src/routes/forward/start.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens, backends } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new route to use - */ - fastify.post( - "/api/v1/forward/start", - { - schema: { - body: { - type: "object", - required: ["token", "id"], - - properties: { - token: { type: "string" }, - id: { type: "number" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - id: number; - } = req.body; - - if (!(await hasPermission(body.token, ["routes.start"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - const forward = await prisma.forwardRule.findUnique({ - where: { - id: body.id, - }, - }); - - if (!forward) - return res.status(400).send({ - error: "Could not find forward entry", - }); - - if (!backends[forward.destProviderID]) - return res.status(400).send({ - error: "Backend not found", - }); - - // @ts-expect-error: Other restrictions in place make it so that it MUST be either TCP or UDP - const protocol: "tcp" | "udp" = forward.protocol; - - backends[forward.destProviderID].addConnection( - forward.sourceIP, - forward.sourcePort, - forward.destPort, - protocol, - ); - - return { - success: true, - }; - }, - ); -} diff --git a/backend-legacy/src/routes/forward/stop.ts b/backend-legacy/src/routes/forward/stop.ts deleted file mode 100644 index e933b0a..0000000 --- a/backend-legacy/src/routes/forward/stop.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens, backends } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new route to use - */ - fastify.post( - "/api/v1/forward/stop", - { - schema: { - body: { - type: "object", - required: ["token", "id"], - - properties: { - token: { type: "string" }, - id: { type: "number" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - id: number; - } = req.body; - - if (!(await hasPermission(body.token, ["routes.stop"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - const forward = await prisma.forwardRule.findUnique({ - where: { - id: body.id, - }, - }); - - if (!forward) - return res.status(400).send({ - error: "Could not find forward entry", - }); - - if (!backends[forward.destProviderID]) - return res.status(400).send({ - error: "Backend not found", - }); - - // @ts-expect-error: Other restrictions in place make it so that it MUST be either TCP or UDP - const protocol: "tcp" | "udp" = forward.protocol; - - backends[forward.destProviderID].removeConnection( - forward.sourceIP, - forward.sourcePort, - forward.destPort, - protocol, - ); - - return { - success: true, - }; - }, - ); -} diff --git a/backend-legacy/src/routes/getPermissions.ts b/backend-legacy/src/routes/getPermissions.ts deleted file mode 100644 index e000085..0000000 --- a/backend-legacy/src/routes/getPermissions.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { hasPermission, getUID } from "../libs/permissions.js"; -import type { RouteOptions } from "../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens } = routeOptions; - - /** - * Logs in to a user account. - */ - fastify.post( - "/api/v1/getPermissions", - { - schema: { - body: { - type: "object", - required: ["token"], - - properties: { - token: { type: "string" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - } = req.body; - - const uid = await getUID(body.token, tokens, prisma); - - if (!(await hasPermission(["permissions.see"], uid, prisma))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - const permissionsRaw = await prisma.permission.findMany({ - where: { - userID: uid, - }, - }); - - return { - success: true, - // Get the ones that we have, and transform them into just their name - data: permissionsRaw.filter(i => i.has).map(i => i.permission), - }; - }, - ); -} diff --git a/backend-legacy/src/routes/user/create.ts b/backend-legacy/src/routes/user/create.ts deleted file mode 100644 index 034faa1..0000000 --- a/backend-legacy/src/routes/user/create.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { hash } from "bcrypt"; - -import { permissionListEnabled } from "../../libs/permissions.js"; -import { generateRandomData } from "../../libs/generateRandom.js"; - -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens, options } = routeOptions; - - /** - * Creates a new user account to use, only if it is enabled. - */ - fastify.post( - "/api/v1/users/create", - { - schema: { - body: { - type: "object", - required: ["name", "email", "username", "password"], - - properties: { - name: { type: "string" }, - username: { type: "string" }, - email: { type: "string" }, - password: { type: "string" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - name: string; - email: string; - password: string; - username: string; - } = req.body; - - if (!options.isSignupEnabled) { - return res.status(403).send({ - error: "Signing up is not enabled at this time.", - }); - } - - const userSearch = await prisma.user.findFirst({ - where: { - email: body.email, - }, - }); - - if (userSearch) { - return res.status(400).send({ - error: "User already exists", - }); - } - - const saltedPassword: string = await hash(body.password, 15); - - const userData = { - name: body.name, - email: body.email, - password: saltedPassword, - - username: body.username, - - permissions: { - create: [] as { - permission: string; - has: boolean; - }[], - }, - }; - - // TODO: There's probably a faster way to pull this off, but I'm lazy - for (const permissionKey of Object.keys(permissionListEnabled)) { - if ( - options.isSignupAsAdminEnabled || - permissionKey.startsWith("routes") || - permissionKey == "permissions.see" - ) { - userData.permissions.create.push({ - permission: permissionKey, - has: permissionListEnabled[permissionKey], - }); - } - } - - if (options.allowUnsafeGlobalTokens) { - // @ts-expect-error: Setting this correctly is a goddamn mess, but this is safe to an extent. It won't crash at least - userData.rootToken = generateRandomData(); - // @ts-expect-error: Read above. - userData.isRootServiceAccount = true; - } - - const userCreateResults = await prisma.user.create({ - data: userData, - }); - - // FIXME(?): Redundant checks - if (options.allowUnsafeGlobalTokens) { - return { - success: true, - token: userCreateResults.rootToken, - }; - } else { - const generatedToken = generateRandomData(); - - tokens[userCreateResults.id] = []; - - tokens[userCreateResults.id].push({ - createdAt: Date.now(), - expiresAt: Date.now() + 30 * 60_000, - - token: generatedToken, - }); - - return { - success: true, - token: generatedToken, - }; - } - }, - ); -} diff --git a/backend-legacy/src/routes/user/login.ts b/backend-legacy/src/routes/user/login.ts deleted file mode 100644 index b44d880..0000000 --- a/backend-legacy/src/routes/user/login.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { compare } from "bcrypt"; - -import { generateRandomData } from "../../libs/generateRandom.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens } = routeOptions; - - /** - * Logs in to a user account. - */ - fastify.post( - "/api/v1/users/login", - { - schema: { - body: { - type: "object", - required: ["password"], - - properties: { - email: { type: "string" }, - username: { type: "string" }, - password: { type: "string" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - email?: string; - username?: string; - password: string; - } = req.body; - - if (!body.email && !body.username) - return res.status(400).send({ - error: "missing both email and username. please supply at least one.", - }); - - const userSearch = await prisma.user.findFirst({ - where: { - email: body.email, - username: body.username, - }, - }); - - if (!userSearch) - return res.status(403).send({ - error: "Email or password is incorrect", - }); - - const passwordIsValid = await compare(body.password, userSearch.password); - - if (!passwordIsValid) - return res.status(403).send({ - error: "Email or password is incorrect", - }); - - const token = generateRandomData(); - if (!tokens[userSearch.id]) tokens[userSearch.id] = []; - - tokens[userSearch.id].push({ - createdAt: Date.now(), - expiresAt: Date.now() + 30 * 60_000, - - token, - }); - - return { - success: true, - token, - }; - }, - ); -} diff --git a/backend-legacy/src/routes/user/lookup.ts b/backend-legacy/src/routes/user/lookup.ts deleted file mode 100644 index 8359231..0000000 --- a/backend-legacy/src/routes/user/lookup.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - fastify.post( - "/api/v1/users/lookup", - { - schema: { - body: { - type: "object", - required: ["token"], - - properties: { - token: { type: "string" }, - id: { type: "number" }, - name: { type: "string" }, - email: { type: "string" }, - username: { type: "string" }, - isServiceAccount: { type: "boolean" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - id?: number; - name?: string; - email?: string; - username?: string; - isServiceAccount?: boolean; - } = req.body; - - if (!(await hasPermission(body.token, ["users.lookup"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - const users = await prisma.user.findMany({ - where: { - id: body.id, - name: body.name, - email: body.email, - username: body.username, - isRootServiceAccount: body.isServiceAccount, - }, - }); - - return { - success: true, - data: users.map(i => ({ - id: i.id, - name: i.name, - email: i.email, - isServiceAccount: i.isRootServiceAccount, - username: i.username, - })), - }; - }, - ); -} diff --git a/backend-legacy/src/routes/user/remove.ts b/backend-legacy/src/routes/user/remove.ts deleted file mode 100644 index f7159d9..0000000 --- a/backend-legacy/src/routes/user/remove.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { hasPermissionByToken } from "../../libs/permissions.js"; -import type { RouteOptions } from "../../libs/types.js"; - -export function route(routeOptions: RouteOptions) { - const { fastify, prisma, tokens } = routeOptions; - - function hasPermission( - token: string, - permissionList: string[], - ): Promise { - return hasPermissionByToken(permissionList, token, tokens, prisma); - } - - /** - * Creates a new backend to use - */ - fastify.post( - "/api/v1/users/remove", - { - schema: { - body: { - type: "object", - required: ["token", "uid"], - - properties: { - token: { type: "string" }, - uid: { type: "number" }, - }, - }, - }, - }, - async (req, res) => { - // @ts-expect-error: Fastify routes schema parsing is trustworthy, so we can "assume" invalid types - const body: { - token: string; - uid: number; - } = req.body; - - if (!(await hasPermission(body.token, ["users.remove"]))) { - return res.status(403).send({ - error: "Unauthorized", - }); - } - - await prisma.permission.deleteMany({ - where: { - userID: body.uid, - }, - }); - - await prisma.user.delete({ - where: { - id: body.uid, - }, - }); - - return { - success: true, - }; - }, - ); -}