diff --git a/routes/NextNet API/Forward/Start.bru b/routes/NextNet API/Forward/Start.bru new file mode 100644 index 0000000..ca66779 --- /dev/null +++ b/routes/NextNet API/Forward/Start.bru @@ -0,0 +1,28 @@ +meta { + name: Start + type: http + seq: 4 +} + +post { + url: http://127.0.0.1:3000/api/v1/forward/create + body: json + auth: none +} + +body:json { + { + "token": "914abf2223f84375eed884671bfaefd7755d378af496b345f322214e75b51ed4465f11e26c944914c9b4fcc35c53250325fbc6530853ddfed8f72976d6fc5", + "name": "Test Route", + "description": "This is a test route for SSH", + + "protocol": "tcp", + + "sourceIP": "127.0.0.1", + "sourcePort": "8000", + + "destinationPort": "9000", + + "providerID": "1" + } +} diff --git a/routes/NextNet API/Forward/Stop.bru b/routes/NextNet API/Forward/Stop.bru new file mode 100644 index 0000000..1621763 --- /dev/null +++ b/routes/NextNet API/Forward/Stop.bru @@ -0,0 +1,18 @@ +meta { + name: Stop + type: http + seq: 5 +} + +post { + url: http://127.0.0.1:3000/api/v1/forward/start + body: json + auth: none +} + +body:json { + { + "token": "914abf2223f84375eed884671bfaefd7755d378af496b345f322214e75b51ed4465f11e26c944914c9b4fcc35c53250325fbc6530853ddfed8f72976d6fc5", + "id": "1" + } +} diff --git a/src/backendimpl/ssh.ts b/src/backendimpl/ssh.ts index 3f76d9a..6e546e5 100644 --- a/src/backendimpl/ssh.ts +++ b/src/backendimpl/ssh.ts @@ -3,6 +3,10 @@ import { Socket } from "node:net"; import type { BackendBaseClass, ForwardRule, ConnectedClient, ParameterReturnedValue } from "./base.js"; +type ForwardRuleExt = ForwardRule & { + enabled: boolean +} + // Fight me (for better naming) type BackendParsedProviderString = { ip: string, @@ -40,7 +44,7 @@ export class SSHBackendProvider implements BackendBaseClass { state: "stopped" | "stopping" | "started" | "starting"; clients: ConnectedClient[]; - proxies: ForwardRule[]; + proxies: ForwardRuleExt[]; logs: string[]; sshInstance: NodeSSH; @@ -118,6 +122,7 @@ export class SSHBackendProvider implements BackendBaseClass { await this.sshInstance.forwardIn("0.0.0.0", destPort, (info, accept, reject) => { const foundProxyEntry = this.proxies.find((i) => i.sourceIP == sourceIP && i.sourcePort == sourcePort && i.destPort == destPort); if (!foundProxyEntry) return reject(); + if (!foundProxyEntry.enabled) return reject(); const client: ConnectedClient = { ip: info.srcIP, @@ -161,7 +166,9 @@ export class SSHBackendProvider implements BackendBaseClass { this.proxies.push({ sourceIP, sourcePort, - destPort + destPort, + + enabled: true }); }; @@ -172,8 +179,7 @@ export class SSHBackendProvider implements BackendBaseClass { const foundProxyEntry = this.proxies.find((i) => i.sourceIP == sourceIP && i.sourcePort == sourcePort && i.destPort == destPort); if (!foundProxyEntry) return; - const proxyIndex = this.proxies.indexOf(foundProxyEntry); - this.proxies.splice(proxyIndex, 1); + foundProxyEntry.enabled = false; }; getAllConnections(): ConnectedClient[] { diff --git a/src/index.ts b/src/index.ts index 26efceb..056e41a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,11 +15,14 @@ import { route as backendLookup } from "./routes/backends/lookup.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(); @@ -78,6 +81,8 @@ backendLookup(routeOptions); forwardCreate(routeOptions); forwardRemove(routeOptions); forwardLookup(routeOptions); +forwardStart(routeOptions); +forwardStop(routeOptions); userCreate(routeOptions); userRemove(routeOptions); diff --git a/src/routes/forward/start.ts b/src/routes/forward/start.ts new file mode 100644 index 0000000..cb98afa --- /dev/null +++ b/src/routes/forward/start.ts @@ -0,0 +1,72 @@ +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-ignore + const body: { + token: string, + id: number + } = req.body; + + console.log(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" + }); + + // Other restrictions in place make it so that it MUST be either TCP or UDP + // @ts-ignore + const protocol: "tcp" | "udp" = forward.protocol; + + backends[forward.destProviderID].addConnection(forward.sourceIP, forward.sourcePort, forward.destPort, protocol); + + return { + success: true + } + }); +} \ No newline at end of file diff --git a/src/routes/forward/stop.ts b/src/routes/forward/stop.ts new file mode 100644 index 0000000..2c81a77 --- /dev/null +++ b/src/routes/forward/stop.ts @@ -0,0 +1,70 @@ +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-ignore + 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" + }); + + // Other restrictions in place make it so that it MUST be either TCP or UDP + // @ts-ignore + const protocol: "tcp" | "udp" = forward.protocol; + + backends[forward.destProviderID].removeConnection(forward.sourceIP, forward.sourcePort, forward.destPort, protocol); + + return { + success: true + } + }); +} \ No newline at end of file