Merge branch 'dev' into reimpl-passyfire

This commit is contained in:
valerie 2024-05-05 17:01:53 -04:00 committed by GitHub
commit a18a770d5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1232 additions and 1029 deletions

2
.gitconfig Normal file
View file

@ -0,0 +1,2 @@
[core]
hooksPath = .githooks/

6
.githooks/pre-commit Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
shopt -s globstar
"$(git rev-parse --show-toplevel)"/api/node_modules/.bin/prettier --ignore-unknown --write $(git rev-parse --show-toplevel)/api/src/**/*.ts
rustfmt $(git rev-parse --show-toplevel)/gui/src/**/*.rs
git update-index --again
exit 0

16
.prettierrc Normal file
View file

@ -0,0 +1,16 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 80,
"proseWrap": "always",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false
}

16
api/package-lock.json generated
View file

@ -21,6 +21,7 @@
"@types/ssh2": "^1.15.0", "@types/ssh2": "^1.15.0",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"nodemon": "^3.0.3", "nodemon": "^3.0.3",
"prettier": "^3.2.5",
"prisma": "^5.13.0", "prisma": "^5.13.0",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
@ -1288,6 +1289,21 @@
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz",
"integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="
}, },
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prisma": { "node_modules/prisma": {
"version": "5.13.0", "version": "5.13.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.13.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.13.0.tgz",

View file

@ -19,6 +19,7 @@
"@types/ssh2": "^1.15.0", "@types/ssh2": "^1.15.0",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"nodemon": "^3.0.3", "nodemon": "^3.0.3",
"prettier": "^3.2.5",
"prisma": "^5.13.0", "prisma": "^5.13.0",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },

View file

@ -1,19 +1,19 @@
export type ParameterReturnedValue = { export type ParameterReturnedValue = {
success: boolean, success: boolean;
message?: string message?: string;
} };
export type ForwardRule = { export type ForwardRule = {
sourceIP: string, sourceIP: string;
sourcePort: number, sourcePort: number;
destPort: number destPort: number;
}; };
export type ConnectedClient = { export type ConnectedClient = {
ip: string, ip: string;
port: number, port: number;
connectionDetails: ForwardRule connectionDetails: ForwardRule;
}; };
export class BackendBaseClass { export class BackendBaseClass {
@ -29,8 +29,18 @@ export class BackendBaseClass {
this.state = "stopped"; this.state = "stopped";
} }
addConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): void {}; addConnection(
removeConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): void {}; sourceIP: string,
sourcePort: number,
destPort: number,
protocol: "tcp" | "udp",
): void {}
removeConnection(
sourceIP: string,
sourcePort: number,
destPort: number,
protocol: "tcp" | "udp",
): void {}
async start(): Promise<boolean> { async start(): Promise<boolean> {
return true; return true;
@ -38,22 +48,27 @@ export class BackendBaseClass {
async stop(): Promise<boolean> { async stop(): Promise<boolean> {
return true; return true;
}; }
getAllConnections(): ConnectedClient[] { getAllConnections(): ConnectedClient[] {
if (this.clients == null) return []; if (this.clients == null) return [];
return this.clients; return this.clients;
};
static checkParametersConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): ParameterReturnedValue {
return {
success: true
} }
static checkParametersConnection(
sourceIP: string,
sourcePort: number,
destPort: number,
protocol: "tcp" | "udp",
): ParameterReturnedValue {
return {
success: true,
}; };
}
static checkParametersBackendInstance(data: string): ParameterReturnedValue { static checkParametersBackendInstance(data: string): ParameterReturnedValue {
return { return {
success: true success: true,
}
}; };
} }
}

View file

@ -1,43 +1,51 @@
import { NodeSSH } from "node-ssh"; import { NodeSSH } from "node-ssh";
import { Socket } from "node:net"; import { Socket } from "node:net";
import type { BackendBaseClass, ForwardRule, ConnectedClient, ParameterReturnedValue } from "./base.js"; import type {
BackendBaseClass,
ForwardRule,
ConnectedClient,
ParameterReturnedValue,
} from "./base.js";
type ForwardRuleExt = ForwardRule & { type ForwardRuleExt = ForwardRule & {
enabled: boolean enabled: boolean;
} };
// Fight me (for better naming) // Fight me (for better naming)
type BackendParsedProviderString = { type BackendParsedProviderString = {
ip: string, ip: string;
port: number, port: number;
username: string, username: string;
privateKey: string privateKey: string;
} };
function parseBackendProviderString(data: string): BackendParsedProviderString { function parseBackendProviderString(data: string): BackendParsedProviderString {
try { try {
JSON.parse(data); JSON.parse(data);
} catch (e) { } catch (e) {
throw new Error("Payload body is not JSON") throw new Error("Payload body is not JSON");
} }
const jsonData = JSON.parse(data); 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.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.username != "string")
if (typeof jsonData.privateKey != "string") throw new Error("Private key is not a string"); throw new Error("Username is not a string");
if (typeof jsonData.privateKey != "string")
throw new Error("Private key is not a string");
return { return {
ip: jsonData.ip, ip: jsonData.ip,
port: jsonData.port, port: jsonData.port,
username: jsonData.username, username: jsonData.username,
privateKey: jsonData.privateKey privateKey: jsonData.privateKey,
} };
} }
export class SSHBackendProvider implements BackendBaseClass { export class SSHBackendProvider implements BackendBaseClass {
@ -76,7 +84,7 @@ export class SSHBackendProvider implements BackendBaseClass {
port: this.options.port, port: this.options.port,
username: this.options.username, username: this.options.username,
privateKey: this.options.privateKey privateKey: this.options.privateKey,
}); });
} catch (e) { } catch (e) {
this.logs.push(`Failed to start SSHBackendProvider! Error: '${e}'`); this.logs.push(`Failed to start SSHBackendProvider! Error: '${e}'`);
@ -86,7 +94,7 @@ export class SSHBackendProvider implements BackendBaseClass {
this.sshInstance = null; this.sshInstance = null;
return false; return false;
}; }
this.state = "started"; this.state = "started";
this.logs.push("Successfully started SSHBackendProvider."); this.logs.push("Successfully started SSHBackendProvider.");
@ -109,25 +117,48 @@ export class SSHBackendProvider implements BackendBaseClass {
this.state = "stopped"; this.state = "stopped";
return true; return true;
}; }
addConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): void { addConnection(
const connectionCheck = SSHBackendProvider.checkParametersConnection(sourceIP, sourcePort, destPort, protocol); 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); 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; if (foundProxyEntry) return;
(async () => { (async () => {
await this.sshInstance.forwardIn("0.0.0.0", destPort, (info, accept, reject) => { await this.sshInstance.forwardIn(
const foundProxyEntry = this.proxies.find((i) => i.sourceIP == sourceIP && i.sourcePort == sourcePort && i.destPort == destPort); "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 || !foundProxyEntry.enabled) return reject(); if (!foundProxyEntry || !foundProxyEntry.enabled) return reject();
const client: ConnectedClient = { const client: ConnectedClient = {
ip: info.srcIP, ip: info.srcIP,
port: info.srcPort, port: info.srcPort,
connectionDetails: foundProxyEntry connectionDetails: foundProxyEntry,
}; };
this.clients.push(client); this.clients.push(client);
@ -136,7 +167,7 @@ export class SSHBackendProvider implements BackendBaseClass {
srcConn.connect({ srcConn.connect({
host: sourceIP, host: sourceIP,
port: sourcePort port: sourcePort,
}); });
// Why is this so confusing // Why is this so confusing
@ -151,7 +182,7 @@ export class SSHBackendProvider implements BackendBaseClass {
srcConn.end(); srcConn.end();
}); });
srcConn.on("data", (data) => { srcConn.on("data", data => {
destConn.write(data); destConn.write(data);
}); });
@ -159,7 +190,8 @@ export class SSHBackendProvider implements BackendBaseClass {
this.clients.splice(this.clients.indexOf(client), 1); this.clients.splice(this.clients.indexOf(client), 1);
destConn.close(); destConn.close();
}); });
}); },
);
})(); })();
this.proxies.push({ this.proxies.push({
@ -167,34 +199,56 @@ export class SSHBackendProvider implements BackendBaseClass {
sourcePort, sourcePort,
destPort, destPort,
enabled: true enabled: true,
}); });
}; }
removeConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): void { removeConnection(
const connectionCheck = SSHBackendProvider.checkParametersConnection(sourceIP, sourcePort, destPort, protocol); 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); 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; if (!foundProxyEntry) return;
foundProxyEntry.enabled = false; foundProxyEntry.enabled = false;
}; }
getAllConnections(): ConnectedClient[] { getAllConnections(): ConnectedClient[] {
return this.clients; return this.clients;
}; }
static checkParametersConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): ParameterReturnedValue { static checkParametersConnection(
if (protocol == "udp") return { sourceIP: string,
sourcePort: number,
destPort: number,
protocol: "tcp" | "udp",
): ParameterReturnedValue {
if (protocol == "udp")
return {
success: false, success: false,
message: "SSH does not support UDP tunneling! Please use something like PortCopier instead (if it gets done)" message:
"SSH does not support UDP tunneling! Please use something like PortCopier instead (if it gets done)",
}; };
return { return {
success: true success: true,
}
}; };
}
static checkParametersBackendInstance(data: string): ParameterReturnedValue { static checkParametersBackendInstance(data: string): ParameterReturnedValue {
try { try {
@ -203,12 +257,12 @@ export class SSHBackendProvider implements BackendBaseClass {
} catch (e: Error) { } catch (e: Error) {
return { return {
success: false, success: false,
message: e.toString() message: e.toString(),
} };
} }
return { return {
success: true success: true,
}
}; };
} }
}

View file

@ -1,10 +1,14 @@
import process from "node:process"; import process from "node:process";
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from "@prisma/client";
import Fastify from "fastify"; import Fastify from "fastify";
import type { ServerOptions, SessionToken, RouteOptions } from "./libs/types.js"; import type {
import type { BackendBaseClass } from "./backendimpl/base.js";`` 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 getPermissions } from "./routes/getPermissions.js";
@ -31,17 +35,19 @@ const prisma = new PrismaClient();
const isSignupEnabled: boolean = Boolean(process.env.IS_SIGNUP_ENABLED); const isSignupEnabled: boolean = Boolean(process.env.IS_SIGNUP_ENABLED);
const unsafeAdminSignup: boolean = Boolean(process.env.UNSAFE_ADMIN_SIGNUP); const unsafeAdminSignup: boolean = Boolean(process.env.UNSAFE_ADMIN_SIGNUP);
const noUsersCheck: boolean = await prisma.user.count() == 0; const noUsersCheck: boolean = (await prisma.user.count()) == 0;
if (unsafeAdminSignup) { if (unsafeAdminSignup) {
console.error("WARNING: You have admin sign up on! This means that anyone that signs up will have admin rights!"); console.error(
"WARNING: You have admin sign up on! This means that anyone that signs up will have admin rights!",
);
} }
const serverOptions: ServerOptions = { const serverOptions: ServerOptions = {
isSignupEnabled: isSignupEnabled ? true : noUsersCheck, isSignupEnabled: isSignupEnabled ? true : noUsersCheck,
isSignupAsAdminEnabled: unsafeAdminSignup ? true : noUsersCheck, isSignupAsAdminEnabled: unsafeAdminSignup ? true : noUsersCheck,
allowUnsafeGlobalTokens: process.env.NODE_ENV != "production" allowUnsafeGlobalTokens: process.env.NODE_ENV != "production",
}; };
const sessionTokens: Record<number, SessionToken[]> = {}; const sessionTokens: Record<number, SessionToken[]> = {};
@ -58,7 +64,7 @@ const routeOptions: RouteOptions = {
tokens: sessionTokens, tokens: sessionTokens,
options: serverOptions, options: serverOptions,
backends: backends backends: backends,
}; };
console.log("Initializing forwarding rules..."); console.log("Initializing forwarding rules...");
@ -96,7 +102,7 @@ userLogin(routeOptions);
try { try {
await fastify.listen({ await fastify.listen({
port: 3000, port: 3000,
host: process.env.NODE_ENV == "production" ? "0.0.0.0" : "127.0.0.1" host: process.env.NODE_ENV == "production" ? "0.0.0.0" : "127.0.0.1",
}); });
} catch (err) { } catch (err) {
fastify.log.error(err); fastify.log.error(err);

View file

@ -11,7 +11,11 @@ type Backend = {
connectionDetails: string; connectionDetails: string;
}; };
export async function backendInit(backend: Backend, backends: Record<number, BackendBaseClass>, prisma: PrismaClient): Promise<boolean> { export async function backendInit(
backend: Backend,
backends: Record<number, BackendBaseClass>,
prisma: PrismaClient,
): Promise<boolean> {
const ourProvider = backendProviders[backend.backend]; const ourProvider = backendProviders[backend.backend];
if (!ourProvider) { if (!ourProvider) {
@ -24,7 +28,7 @@ export async function backendInit(backend: Backend, backends: Record<number, Bac
backends[backend.id] = new ourProvider(backend.connectionDetails); backends[backend.id] = new ourProvider(backend.connectionDetails);
const ourBackend = backends[backend.id]; const ourBackend = backends[backend.id];
if (!await ourBackend.start()) { if (!(await ourBackend.start())) {
console.log(" - Error initializing backend!"); console.log(" - Error initializing backend!");
console.log(" - " + ourBackend.logs.join("\n - ")); console.log(" - " + ourBackend.logs.join("\n - "));
@ -36,17 +40,24 @@ export async function backendInit(backend: Backend, backends: Record<number, Bac
const clients = await prisma.forwardRule.findMany({ const clients = await prisma.forwardRule.findMany({
where: { where: {
destProviderID: backend.id, destProviderID: backend.id,
enabled: true enabled: true,
} },
}); });
for (const client of clients) { for (const client of clients) {
if (client.protocol != "tcp" && client.protocol != "udp") { if (client.protocol != "tcp" && client.protocol != "udp") {
console.error(` - Error: Client with ID of '${client.id}' has an invalid protocol! (must be either TCP or UDP)`); console.error(
` - Error: Client with ID of '${client.id}' has an invalid protocol! (must be either TCP or UDP)`,
);
continue; continue;
} }
ourBackend.addConnection(client.sourceIP, client.sourcePort, client.destPort, client.protocol); ourBackend.addConnection(
client.sourceIP,
client.sourcePort,
client.destPort,
client.protocol,
);
} }
return true; return true;

View file

@ -27,19 +27,25 @@ export const permissionListDisabled: Record<string, boolean> = {
}; };
// FIXME: This solution fucking sucks. // FIXME: This solution fucking sucks.
export let permissionListEnabled: Record<string, boolean> = JSON.parse(JSON.stringify(permissionListDisabled)); export let permissionListEnabled: Record<string, boolean> = JSON.parse(
JSON.stringify(permissionListDisabled),
);
for (const index of Object.keys(permissionListEnabled)) { for (const index of Object.keys(permissionListEnabled)) {
permissionListEnabled[index] = true; permissionListEnabled[index] = true;
} }
export async function hasPermission(permissionList: string[], uid: number, prisma: PrismaClient): Promise<boolean> { export async function hasPermission(
permissionList: string[],
uid: number,
prisma: PrismaClient,
): Promise<boolean> {
for (const permission of permissionList) { for (const permission of permissionList) {
const permissionNode = await prisma.permission.findFirst({ const permissionNode = await prisma.permission.findFirst({
where: { where: {
userID: uid, userID: uid,
permission permission,
} },
}); });
if (!permissionNode || !permissionNode.has) return false; if (!permissionNode || !permissionNode.has) return false;
@ -48,7 +54,11 @@ export async function hasPermission(permissionList: string[], uid: number, prism
return true; return true;
} }
export async function getUID(token: string, tokens: Record<number, SessionToken[]>, prisma: PrismaClient): Promise<number> { export async function getUID(
token: string,
tokens: Record<number, SessionToken[]>,
prisma: PrismaClient,
): Promise<number> {
let userID = -1; let userID = -1;
// Look up in our currently authenticated users // Look up in our currently authenticated users
@ -59,7 +69,10 @@ export async function getUID(token: string, tokens: Record<number, SessionToken[
const otherToken = otherTokenList[otherTokenIndex]; const otherToken = otherTokenList[otherTokenIndex];
if (otherToken.token == token) { if (otherToken.token == token) {
if (otherToken.expiresAt < otherToken.createdAt + (otherToken.createdAt - Date.now())) { if (
otherToken.expiresAt <
otherToken.createdAt + (otherToken.createdAt - Date.now())
) {
otherTokenList.splice(parseInt(otherTokenIndex), 1); otherTokenList.splice(parseInt(otherTokenIndex), 1);
continue; continue;
} else { } else {
@ -74,19 +87,24 @@ export async function getUID(token: string, tokens: Record<number, SessionToken[
if (userID == -1) { if (userID == -1) {
const allUsers = await prisma.user.findMany({ const allUsers = await prisma.user.findMany({
where: { where: {
isRootServiceAccount: true isRootServiceAccount: true,
} },
}); });
for (const user of allUsers) { for (const user of allUsers) {
if (user.rootToken == token) userID = user.id; if (user.rootToken == token) userID = user.id;
}; }
} }
return userID; return userID;
} }
export async function hasPermissionByToken(permissionList: string[], token: string, tokens: Record<number, SessionToken[]>, prisma: PrismaClient): Promise<boolean> { export async function hasPermissionByToken(
permissionList: string[],
token: string,
tokens: Record<number, SessionToken[]>,
prisma: PrismaClient,
): Promise<boolean> {
const userID = await getUID(token, tokens, prisma); const userID = await getUID(token, tokens, prisma);
return await hasPermission(permissionList, userID, prisma); return await hasPermission(permissionList, userID, prisma);
} }

View file

@ -8,21 +8,21 @@ export type ServerOptions = {
isSignupAsAdminEnabled: boolean; isSignupAsAdminEnabled: boolean;
allowUnsafeGlobalTokens: boolean; allowUnsafeGlobalTokens: boolean;
} };
// NOTE: Someone should probably use Redis for this, but this is fine... // NOTE: Someone should probably use Redis for this, but this is fine...
export type SessionToken = { export type SessionToken = {
createdAt: number, createdAt: number;
expiresAt: number, // Should be (createdAt + (30 minutes)) expiresAt: number; // Should be (createdAt + (30 minutes))
token: string token: string;
}; };
export type RouteOptions = { export type RouteOptions = {
fastify: FastifyInstance, fastify: FastifyInstance;
prisma: PrismaClient, prisma: PrismaClient;
tokens: Record<number, SessionToken[]>, tokens: Record<number, SessionToken[]>;
options: ServerOptions, options: ServerOptions;
backends: Record<number, BackendBaseClass> backends: Record<number, BackendBaseClass>;
}; };

View file

@ -5,21 +5,21 @@ import { backendProviders } from "../../backendimpl/index.js";
import { backendInit } from "../../libs/backendInit.js"; import { backendInit } from "../../libs/backendInit.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens, backends } = routeOptions;
fastify,
prisma,
tokens,
backends
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new backend to use * Creates a new backend to use
*/ */
fastify.post("/api/v1/backends/create", { fastify.post(
"/api/v1/backends/create",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -29,42 +29,46 @@ export function route(routeOptions: RouteOptions) {
token: { type: "string" }, token: { type: "string" },
name: { type: "string" }, name: { type: "string" },
description: { type: "string" }, description: { type: "string" },
backend: { type: "string" } backend: { type: "string" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
name: string, name: string;
description?: string, description?: string;
connectionDetails: any, connectionDetails: any;
backend: string backend: string;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (!(await hasPermission(body.token, ["backends.add"]))) {
"backends.add"
])) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
if (!backendProviders[body.backend]) { if (!backendProviders[body.backend]) {
return res.status(400).send({ return res.status(400).send({
error: "Unknown/unsupported/deprecated backend!" error: "Unknown/unsupported/deprecated backend!",
}); });
}; }
const connectionDetails = JSON.stringify(body.connectionDetails); const connectionDetails = JSON.stringify(body.connectionDetails);
const connectionDetailsValidityCheck = backendProviders[body.backend].checkParametersBackendInstance(connectionDetails); const connectionDetailsValidityCheck =
backendProviders[body.backend].checkParametersBackendInstance(
connectionDetails,
);
if (!connectionDetailsValidityCheck.success) { if (!connectionDetailsValidityCheck.success) {
return res.status(400).send({ return res.status(400).send({
error: connectionDetailsValidityCheck.message ?? "Unknown error while attempting to parse connectionDetails (it's on your side)" error:
connectionDetailsValidityCheck.message ??
"Unknown error while attempting to parse connectionDetails (it's on your side)",
}); });
}; }
const backend = await prisma.desinationProvider.create({ const backend = await prisma.desinationProvider.create({
data: { data: {
@ -72,8 +76,8 @@ export function route(routeOptions: RouteOptions) {
description: body.description, description: body.description,
backend: body.backend, backend: body.backend,
connectionDetails: JSON.stringify(body.connectionDetails) connectionDetails: JSON.stringify(body.connectionDetails),
} },
}); });
const init = await backendInit(backend, backends, prisma); const init = await backendInit(backend, backends, prisma);
@ -82,13 +86,14 @@ export function route(routeOptions: RouteOptions) {
// TODO: better error code // TODO: better error code
return res.status(504).send({ return res.status(504).send({
error: "Backend is created, but failed to initalize correctly", error: "Backend is created, but failed to initalize correctly",
id: backend.id id: backend.id,
}); });
} }
return { return {
success: true, success: true,
id: backend.id id: backend.id,
}; };
}); },
);
} }

View file

@ -2,21 +2,21 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens, backends } = routeOptions;
fastify,
prisma,
tokens,
backends
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new route to use * Creates a new route to use
*/ */
fastify.post("/api/v1/backends/lookup", { fastify.post(
"/api/v1/backends/lookup",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -27,30 +27,33 @@ export function route(routeOptions: RouteOptions) {
id: { type: "number" }, id: { type: "number" },
name: { type: "string" }, name: { type: "string" },
description: { type: "string" }, description: { type: "string" },
backend: { type: "string" } backend: { type: "string" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
id?: number, id?: number;
name?: string, name?: string;
description?: string, description?: string;
backend?: string backend?: string;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (
"backends.visible" // wtf? !(await hasPermission(body.token, [
])) { "backends.visible", // wtf?
]))
) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
const canSeeSecrets = await hasPermission(body.token, [ const canSeeSecrets = await hasPermission(body.token, [
"backends.secretVis" "backends.secretVis",
]); ]);
const prismaBackends = await prisma.desinationProvider.findMany({ const prismaBackends = await prisma.desinationProvider.findMany({
@ -58,21 +61,22 @@ export function route(routeOptions: RouteOptions) {
id: body.id, id: body.id,
name: body.name, name: body.name,
description: body.description, description: body.description,
backend: body.backend backend: body.backend,
} },
}); });
return { return {
success: true, success: true,
data: prismaBackends.map((i) => ({ data: prismaBackends.map(i => ({
name: i.name, name: i.name,
description: i.description, description: i.description,
backend: i.backend, backend: i.backend,
connectionDetails: canSeeSecrets ? i.connectionDetails : "", connectionDetails: canSeeSecrets ? i.connectionDetails : "",
logs: backends[i.id].logs logs: backends[i.id].logs,
})) })),
} };
}); },
);
} }

View file

@ -2,21 +2,21 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens, backends } = routeOptions;
fastify,
prisma,
tokens,
backends
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new route to use * Creates a new route to use
*/ */
fastify.post("/api/v1/backends/remove", { fastify.post(
"/api/v1/backends/remove",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -24,48 +24,48 @@ export function route(routeOptions: RouteOptions) {
properties: { properties: {
token: { type: "string" }, token: { type: "string" },
id: { type: "number" } id: { type: "number" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
id: number id: number;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (!(await hasPermission(body.token, ["backends.remove"]))) {
"backends.remove"
])) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
if (!backends[body.id]) { if (!backends[body.id]) {
return res.status(400).send({ return res.status(400).send({
error: "Backend not found" error: "Backend not found",
}); });
}; }
// Unload the backend // Unload the backend
if (!await backends[body.id].stop()) { if (!(await backends[body.id].stop())) {
return res.status(400).send({ return res.status(400).send({
error: "Failed to stop backend! Please report this issue." error: "Failed to stop backend! Please report this issue.",
}) });
} }
delete backends[body.id]; delete backends[body.id];
await prisma.desinationProvider.delete({ await prisma.desinationProvider.delete({
where: { where: {
id: body.id id: body.id,
} },
}); });
return { return {
success: true success: true,
} };
}); },
);
} }

View file

@ -2,18 +2,18 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens, backends } = routeOptions;
fastify,
prisma,
tokens,
backends
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
fastify.post("/api/v1/forward/connections", { fastify.post(
"/api/v1/forward/connections",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -21,42 +21,44 @@ export function route(routeOptions: RouteOptions) {
properties: { properties: {
token: { type: "string" }, token: { type: "string" },
id: { type: "number" } id: { type: "number" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
id: number id: number;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (!(await hasPermission(body.token, ["routes.visibleConn"]))) {
"routes.visibleConn"
])) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
const forward = await prisma.forwardRule.findUnique({ const forward = await prisma.forwardRule.findUnique({
where: { where: {
id: body.id id: body.id,
} },
}); });
if (!forward) return res.status(400).send({ if (!forward)
error: "Could not find forward entry" return res.status(400).send({
error: "Could not find forward entry",
}); });
if (!backends[forward.destProviderID]) return res.status(400).send({ if (!backends[forward.destProviderID])
error: "Backend not found" return res.status(400).send({
error: "Backend not found",
}); });
return { return {
success: true, success: true,
data: backends[forward.destProviderID].getAllConnections() data: backends[forward.destProviderID].getAllConnections(),
} };
}) },
);
} }

View file

@ -2,24 +2,33 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens } = routeOptions;
fastify,
prisma,
tokens
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new route to use * Creates a new route to use
*/ */
fastify.post("/api/v1/forward/create", { fastify.post(
"/api/v1/forward/create",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
required: ["token", "name", "protocol", "sourceIP", "sourcePort", "destinationPort", "providerID"], required: [
"token",
"name",
"protocol",
"sourceIP",
"sourcePort",
"destinationPort",
"providerID",
],
properties: { properties: {
token: { type: "string" }, token: { type: "string" },
@ -35,52 +44,53 @@ export function route(routeOptions: RouteOptions) {
destinationPort: { type: "number" }, destinationPort: { type: "number" },
providerID: { type: "number" }, providerID: { type: "number" },
autoStart: { type: "boolean" } autoStart: { type: "boolean" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
name: string, name: string;
description?: string, description?: string;
protocol: "tcp" | "udp", protocol: "tcp" | "udp";
sourceIP: string, sourceIP: string;
sourcePort: number, sourcePort: number;
destinationPort: number, destinationPort: number;
providerID: number, providerID: number;
autoStart?: boolean autoStart?: boolean;
} = req.body; } = req.body;
if (body.protocol != "tcp" && body.protocol != "udp") { if (body.protocol != "tcp" && body.protocol != "udp") {
return res.status(400).send({ return res.status(400).send({
error: "Body protocol field must be either tcp or udp" 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 (!(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({ if (!lookupIDForDestProvider)
error: "Could not find provider" return res.status(400).send({
error: "Could not find provider",
}); });
const forwardRule = await prisma.forwardRule.create({ const forwardRule = await prisma.forwardRule.create({
@ -96,13 +106,14 @@ export function route(routeOptions: RouteOptions) {
destPort: body.destinationPort, destPort: body.destinationPort,
destProviderID: body.providerID, destProviderID: body.providerID,
enabled: Boolean(body.autoStart) enabled: Boolean(body.autoStart),
} },
}); });
return { return {
success: true, success: true,
id: forwardRule.id id: forwardRule.id,
} };
}); },
);
} }

View file

@ -2,20 +2,21 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens } = routeOptions;
fastify,
prisma,
tokens
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new route to use * Creates a new route to use
*/ */
fastify.post("/api/v1/forward/lookup", { fastify.post(
"/api/v1/forward/lookup",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -34,43 +35,46 @@ export function route(routeOptions: RouteOptions) {
destPort: { type: "number" }, destPort: { type: "number" },
providerID: { type: "number" }, providerID: { type: "number" },
autoStart: { type: "boolean" } autoStart: { type: "boolean" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
id?: number, id?: number;
name?: string, name?: string;
description?: string, description?: string;
protocol?: "tcp" | "udp", protocol?: "tcp" | "udp";
sourceIP?: string, sourceIP?: string;
sourcePort?: number, sourcePort?: number;
destinationPort?: number, destinationPort?: number;
providerID?: number, providerID?: number;
autoStart?: boolean autoStart?: boolean;
} = req.body; } = req.body;
if (body.protocol && body.protocol != "tcp" && body.protocol != "udp") { if (body.protocol && body.protocol != "tcp" && body.protocol != "udp") {
return res.status(400).send({ return res.status(400).send({
error: "Protocol specified in body must be either 'tcp' or 'udp'" error: "Protocol specified in body must be either 'tcp' or 'udp'",
}) });
} }
if (!await hasPermission(body.token, [ if (
"routes.visible" // wtf? !(await hasPermission(body.token, [
])) { "routes.visible", // wtf?
]))
) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
const forwardRules = await prisma.forwardRule.findMany({ const forwardRules = await prisma.forwardRule.findMany({
where: { where: {
@ -84,13 +88,13 @@ export function route(routeOptions: RouteOptions) {
destPort: body.destinationPort, destPort: body.destinationPort,
destProviderID: body.providerID, destProviderID: body.providerID,
enabled: body.autoStart enabled: body.autoStart,
} },
}); });
return { return {
success: true, success: true,
data: forwardRules.map((i) => ({ data: forwardRules.map(i => ({
id: i.id, id: i.id,
name: i.name, name: i.name,
description: i.description, description: i.description,
@ -101,8 +105,9 @@ export function route(routeOptions: RouteOptions) {
destPort: i.destPort, destPort: i.destPort,
providerID: i.destProviderID, providerID: i.destProviderID,
autoStart: i.enabled // TODO: Add enabled flag in here to see if we're running or not autoStart: i.enabled, // TODO: Add enabled flag in here to see if we're running or not
})) })),
}; };
}); },
);
} }

View file

@ -2,20 +2,21 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens } = routeOptions;
fastify,
prisma,
tokens
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new route to use * Creates a new route to use
*/ */
fastify.post("/api/v1/forward/remove", { fastify.post(
"/api/v1/forward/remove",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -23,33 +24,33 @@ export function route(routeOptions: RouteOptions) {
properties: { properties: {
token: { type: "string" }, token: { type: "string" },
id: { type: "number" } id: { type: "number" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
id: number id: number;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (!(await hasPermission(body.token, ["routes.remove"]))) {
"routes.remove"
])) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
await prisma.forwardRule.delete({ await prisma.forwardRule.delete({
where: { where: {
id: body.id id: body.id,
} },
}); });
return { return {
success: true success: true,
} };
}); },
);
} }

View file

@ -2,21 +2,21 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens, backends } = routeOptions;
fastify,
prisma,
tokens,
backends
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new route to use * Creates a new route to use
*/ */
fastify.post("/api/v1/forward/start", { fastify.post(
"/api/v1/forward/start",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -24,47 +24,54 @@ export function route(routeOptions: RouteOptions) {
properties: { properties: {
token: { type: "string" }, token: { type: "string" },
id: { type: "number" } id: { type: "number" },
}
}, },
} },
}, async(req, res) => { },
},
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
id: number id: number;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (!(await hasPermission(body.token, ["routes.start"]))) {
"routes.start"
])) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
const forward = await prisma.forwardRule.findUnique({ const forward = await prisma.forwardRule.findUnique({
where: { where: {
id: body.id id: body.id,
} },
}); });
if (!forward) return res.status(400).send({ if (!forward)
error: "Could not find forward entry" return res.status(400).send({
error: "Could not find forward entry",
}); });
if (!backends[forward.destProviderID]) return res.status(400).send({ if (!backends[forward.destProviderID])
error: "Backend not found" return res.status(400).send({
error: "Backend not found",
}); });
// Other restrictions in place make it so that it MUST be either TCP or UDP // Other restrictions in place make it so that it MUST be either TCP or UDP
// @ts-ignore // @ts-ignore
const protocol: "tcp" | "udp" = forward.protocol; const protocol: "tcp" | "udp" = forward.protocol;
backends[forward.destProviderID].addConnection(forward.sourceIP, forward.sourcePort, forward.destPort, protocol); backends[forward.destProviderID].addConnection(
forward.sourceIP,
forward.sourcePort,
forward.destPort,
protocol,
);
return { return {
success: true success: true,
} };
}); },
);
} }

View file

@ -2,21 +2,21 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens, backends } = routeOptions;
fastify,
prisma,
tokens,
backends
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new route to use * Creates a new route to use
*/ */
fastify.post("/api/v1/forward/stop", { fastify.post(
"/api/v1/forward/stop",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -24,47 +24,54 @@ export function route(routeOptions: RouteOptions) {
properties: { properties: {
token: { type: "string" }, token: { type: "string" },
id: { type: "number" } id: { type: "number" },
}
}, },
} },
}, async(req, res) => { },
},
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
id: number id: number;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (!(await hasPermission(body.token, ["routes.stop"]))) {
"routes.stop"
])) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
const forward = await prisma.forwardRule.findUnique({ const forward = await prisma.forwardRule.findUnique({
where: { where: {
id: body.id id: body.id,
} },
}); });
if (!forward) return res.status(400).send({ if (!forward)
error: "Could not find forward entry" return res.status(400).send({
error: "Could not find forward entry",
}); });
if (!backends[forward.destProviderID]) return res.status(400).send({ if (!backends[forward.destProviderID])
error: "Backend not found" return res.status(400).send({
error: "Backend not found",
}); });
// Other restrictions in place make it so that it MUST be either TCP or UDP // Other restrictions in place make it so that it MUST be either TCP or UDP
// @ts-ignore // @ts-ignore
const protocol: "tcp" | "udp" = forward.protocol; const protocol: "tcp" | "udp" = forward.protocol;
backends[forward.destProviderID].removeConnection(forward.sourceIP, forward.sourcePort, forward.destPort, protocol); backends[forward.destProviderID].removeConnection(
forward.sourceIP,
forward.sourcePort,
forward.destPort,
protocol,
);
return { return {
success: true success: true,
} };
}); },
);
} }

View file

@ -2,52 +2,50 @@ import { hasPermission, getUID } from "../libs/permissions.js";
import type { RouteOptions } from "../libs/types.js"; import type { RouteOptions } from "../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens } = routeOptions;
fastify,
prisma,
tokens
} = routeOptions;
/** /**
* Logs in to a user account. * Logs in to a user account.
*/ */
fastify.post("/api/v1/getPermissions", { fastify.post(
"/api/v1/getPermissions",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
required: ["token"], required: ["token"],
properties: { properties: {
token: { type: "string" } token: { type: "string" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string token: string;
} = req.body; } = req.body;
const uid = await getUID(body.token, tokens, prisma); const uid = await getUID(body.token, tokens, prisma);
if (!await hasPermission([ if (!(await hasPermission(["permissions.see"], uid, prisma))) {
"permissions.see"
], uid, prisma)) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
const permissionsRaw = await prisma.permission.findMany({ const permissionsRaw = await prisma.permission.findMany({
where: { where: {
userID: uid userID: uid,
} },
}); });
return { return {
success: true, success: true,
// Get the ones that we have, and transform them into just their name // Get the ones that we have, and transform them into just their name
data: permissionsRaw.filter((i) => i.has).map((i) => i.permission) data: permissionsRaw.filter(i => i.has).map(i => i.permission),
} };
}); },
);
} }

View file

@ -6,17 +6,14 @@ import { generateRandomData } from "../../libs/generateRandom.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens, options } = routeOptions;
fastify,
prisma,
tokens,
options
} = routeOptions;
/** /**
* Creates a new user account to use, only if it is enabled. * Creates a new user account to use, only if it is enabled.
*/ */
fastify.post("/api/v1/users/create", { fastify.post(
"/api/v1/users/create",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -25,35 +22,36 @@ export function route(routeOptions: RouteOptions) {
properties: { properties: {
name: { type: "string" }, name: { type: "string" },
email: { type: "string" }, email: { type: "string" },
password: { type: "string" } password: { type: "string" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
name: string, name: string;
email: string, email: string;
password: string password: string;
} = req.body; } = req.body;
if (!options.isSignupEnabled) { if (!options.isSignupEnabled) {
return res.status(403).send({ return res.status(403).send({
error: "Signing up is not enabled at this time." error: "Signing up is not enabled at this time.",
}); });
}; }
const userSearch = await prisma.user.findFirst({ const userSearch = await prisma.user.findFirst({
where: { where: {
email: body.email email: body.email,
} },
}); });
if (userSearch) { if (userSearch) {
return res.status(400).send({ return res.status(400).send({
error: "User already exists" error: "User already exists",
}) });
}; }
const saltedPassword: string = await hash(body.password, 15); const saltedPassword: string = await hash(body.password, 15);
@ -64,21 +62,25 @@ export function route(routeOptions: RouteOptions) {
permissions: { permissions: {
create: [] as { create: [] as {
permission: string, permission: string;
has: boolean has: boolean;
}[] }[],
} },
}; };
// TODO: There's probably a faster way to pull this off, but I'm lazy // TODO: There's probably a faster way to pull this off, but I'm lazy
for (const permissionKey of Object.keys(permissionListEnabled)) { for (const permissionKey of Object.keys(permissionListEnabled)) {
if (options.isSignupAsAdminEnabled || (permissionKey.startsWith("routes") || permissionKey == "permissions.see")) { if (
options.isSignupAsAdminEnabled ||
permissionKey.startsWith("routes") ||
permissionKey == "permissions.see"
) {
userData.permissions.create.push({ userData.permissions.create.push({
permission: permissionKey, permission: permissionKey,
has: permissionListEnabled[permissionKey] has: permissionListEnabled[permissionKey],
}); });
} }
}; }
if (options.allowUnsafeGlobalTokens) { if (options.allowUnsafeGlobalTokens) {
// @ts-ignore // @ts-ignore
@ -88,14 +90,14 @@ export function route(routeOptions: RouteOptions) {
} }
const userCreateResults = await prisma.user.create({ const userCreateResults = await prisma.user.create({
data: userData data: userData,
}); });
// FIXME(?): Redundant checks // FIXME(?): Redundant checks
if (options.allowUnsafeGlobalTokens) { if (options.allowUnsafeGlobalTokens) {
return { return {
success: true, success: true,
token: userCreateResults.rootToken token: userCreateResults.rootToken,
}; };
} else { } else {
const generatedToken = generateRandomData(); const generatedToken = generateRandomData();
@ -104,15 +106,16 @@ export function route(routeOptions: RouteOptions) {
tokens[userCreateResults.id].push({ tokens[userCreateResults.id].push({
createdAt: Date.now(), createdAt: Date.now(),
expiresAt: Date.now() + (30 * 60_000), expiresAt: Date.now() + 30 * 60_000,
token: generatedToken token: generatedToken,
}); });
return { return {
success: true, success: true,
token: generatedToken token: generatedToken,
}; };
}; }
}); },
);
} }

View file

@ -4,16 +4,14 @@ import { generateRandomData } from "../../libs/generateRandom.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens } = routeOptions;
fastify,
prisma,
tokens
} = routeOptions;
/** /**
* Logs in to a user account. * Logs in to a user account.
*/ */
fastify.post("/api/v1/users/login", { fastify.post(
"/api/v1/users/login",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -21,31 +19,34 @@ export function route(routeOptions: RouteOptions) {
properties: { properties: {
email: { type: "string" }, email: { type: "string" },
password: { type: "string" } password: { type: "string" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
email: string, email: string;
password: string password: string;
} = req.body; } = req.body;
const userSearch = await prisma.user.findFirst({ const userSearch = await prisma.user.findFirst({
where: { where: {
email: body.email email: body.email,
} },
}); });
if (!userSearch) return res.status(403).send({ if (!userSearch)
error: "Email or password is incorrect" return res.status(403).send({
error: "Email or password is incorrect",
}); });
const passwordIsValid = await compare(body.password, userSearch.password); const passwordIsValid = await compare(body.password, userSearch.password);
if (!passwordIsValid) return res.status(403).send({ if (!passwordIsValid)
error: "Email or password is incorrect" return res.status(403).send({
error: "Email or password is incorrect",
}); });
const token = generateRandomData(); const token = generateRandomData();
@ -53,14 +54,15 @@ export function route(routeOptions: RouteOptions) {
tokens[userSearch.id].push({ tokens[userSearch.id].push({
createdAt: Date.now(), createdAt: Date.now(),
expiresAt: Date.now() + (30 * 60_000), expiresAt: Date.now() + 30 * 60_000,
token token,
}); });
return { return {
success: true, success: true,
token token,
} };
}); },
);
} }

View file

@ -2,17 +2,18 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens } = routeOptions;
fastify,
prisma,
tokens
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
fastify.post("/api/v1/users/lookup", { fastify.post(
"/api/v1/users/lookup",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -23,44 +24,44 @@ export function route(routeOptions: RouteOptions) {
id: { type: "number" }, id: { type: "number" },
name: { type: "string" }, name: { type: "string" },
email: { type: "string" }, email: { type: "string" },
isServiceAccount: { type: "boolean" } isServiceAccount: { type: "boolean" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
id?: number, id?: number;
name?: string, name?: string;
email?: string, email?: string;
isServiceAccount?: boolean isServiceAccount?: boolean;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (!(await hasPermission(body.token, ["users.lookup"]))) {
"users.lookup"
])) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
const users = await prisma.user.findMany({ const users = await prisma.user.findMany({
where: { where: {
id: body.id, id: body.id,
name: body.name, name: body.name,
email: body.email, email: body.email,
isRootServiceAccount: body.isServiceAccount isRootServiceAccount: body.isServiceAccount,
} },
}); });
return { return {
success: true, success: true,
data: users.map((i) => ({ data: users.map(i => ({
name: i.name, name: i.name,
email: i.email, email: i.email,
isServiceAccount: i.isRootServiceAccount isServiceAccount: i.isRootServiceAccount,
})) })),
} };
}); },
);
} }

View file

@ -2,20 +2,21 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js"; import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) { export function route(routeOptions: RouteOptions) {
const { const { fastify, prisma, tokens } = routeOptions;
fastify,
prisma,
tokens
} = routeOptions;
function hasPermission(token: string, permissionList: string[]): Promise<boolean> { function hasPermission(
token: string,
permissionList: string[],
): Promise<boolean> {
return hasPermissionByToken(permissionList, token, tokens, prisma); return hasPermissionByToken(permissionList, token, tokens, prisma);
}; }
/** /**
* Creates a new backend to use * Creates a new backend to use
*/ */
fastify.post("/api/v1/users/remove", { fastify.post(
"/api/v1/users/remove",
{
schema: { schema: {
body: { body: {
type: "object", type: "object",
@ -23,39 +24,39 @@ export function route(routeOptions: RouteOptions) {
properties: { properties: {
token: { type: "string" }, token: { type: "string" },
uid: { type: "number" } uid: { type: "number" },
} },
} },
} },
}, async(req, res) => { },
async (req, res) => {
// @ts-ignore // @ts-ignore
const body: { const body: {
token: string, token: string;
uid: number uid: number;
} = req.body; } = req.body;
if (!await hasPermission(body.token, [ if (!(await hasPermission(body.token, ["users.remove"]))) {
"users.remove"
])) {
return res.status(403).send({ return res.status(403).send({
error: "Unauthorized" error: "Unauthorized",
}); });
}; }
await prisma.permission.deleteMany({ await prisma.permission.deleteMany({
where: { where: {
userID: body.uid userID: body.uid,
} },
}); });
await prisma.user.delete({ await prisma.user.delete({
where: { where: {
id: body.uid id: body.uid,
} },
}); });
return { return {
success: true success: true,
}
});
}; };
},
);
}

View file

@ -3,17 +3,22 @@ use serde::Deserialize;
use serde_json::json; use serde_json::json;
pub struct NextAPIClient { pub struct NextAPIClient {
pub url: String pub url: String,
} }
#[derive(Deserialize, Debug, Default)] #[derive(Deserialize, Debug, Default)]
pub struct LoginResponse { pub struct LoginResponse {
pub error: Option<String>, pub error: Option<String>,
pub token: Option<String> pub token: Option<String>,
} }
impl NextAPIClient { impl NextAPIClient {
pub fn login(&self, email: &str, password: &str, mut callback: impl 'static + Send + FnMut(LoginResponse)) { pub fn login(
&self,
email: &str,
password: &str,
mut callback: impl 'static + Send + FnMut(LoginResponse),
) {
let json_data = json!({ let json_data = json!({
"email": email, "email": email,
"password": password "password": password
@ -24,7 +29,10 @@ impl NextAPIClient {
println!("{}", json_str); println!("{}", json_str);
let mut request = Request::post(self.url.clone() + "/api/v1/users/login", json_str.as_bytes().to_vec()); let mut request = Request::post(
self.url.clone() + "/api/v1/users/login",
json_str.as_bytes().to_vec(),
);
request.headers.insert("Content-Type", "application/json"); request.headers.insert("Content-Type", "application/json");
fetch(request, move |result: ehttp::Result<ehttp::Response>| { fetch(request, move |result: ehttp::Result<ehttp::Response>| {
@ -36,9 +44,7 @@ impl NextAPIClient {
} }
pub fn new(url: String) -> NextAPIClient { pub fn new(url: String) -> NextAPIClient {
let api_client: NextAPIClient = NextAPIClient { let api_client: NextAPIClient = NextAPIClient { url };
url
};
return api_client; return api_client;
} }

View file

@ -1,5 +1,5 @@
use std::sync::Arc;
use eframe::egui; use eframe::egui;
use std::sync::Arc;
use crate::api; use crate::api;
use crate::ApplicationState; use crate::ApplicationState;
@ -21,18 +21,20 @@ pub fn main(state: &mut ApplicationState, api: &api::NextAPIClient, ctx: &eframe
if ui.button("Login").clicked() { if ui.button("Login").clicked() {
let token_clone = Arc::clone(&state.token); let token_clone = Arc::clone(&state.token);
api.login(state.username.as_str(), state.password.as_str(), Box::new(move |res: api::LoginResponse| { api.login(
match res.token { state.username.as_str(),
state.password.as_str(),
Box::new(move |res: api::LoginResponse| match res.token {
Some(x) => { Some(x) => {
let mut token = token_clone.lock().unwrap(); let mut token = token_clone.lock().unwrap();
*token = x; *token = x;
}, }
None => { None => {
let mut token = token_clone.lock().unwrap(); let mut token = token_clone.lock().unwrap();
*token = "".to_string(); *token = "".to_string();
} }
} }),
})); );
} }
}); });
} }

View file

@ -1,8 +1,8 @@
use std::sync::{Arc, Mutex};
use eframe::egui; use eframe::egui;
use std::sync::{Arc, Mutex};
mod components;
mod api; mod api;
mod components;
pub struct ApplicationState { pub struct ApplicationState {
token: Arc<Mutex<String>>, token: Arc<Mutex<String>>,
@ -27,7 +27,7 @@ fn main() -> Result<(), eframe::Error> {
// components/log_in.rs // components/log_in.rs
username: "replace@gmail.com".to_owned(), username: "replace@gmail.com".to_owned(),
password: "replace123".to_owned() password: "replace123".to_owned(),
}; };
eframe::run_simple_native("NextNet GUI", options, move |ctx, _frame| { eframe::run_simple_native("NextNet GUI", options, move |ctx, _frame| {

View file

@ -1,3 +1,5 @@
pushd api > /dev/null 2> /dev/null pushd api > /dev/null 2> /dev/null
source init.sh source init.sh
git config --local include.path .gitconfig
popd > /dev/null 2> /dev/null popd > /dev/null 2> /dev/null

View file

@ -5,6 +5,7 @@
# gui/ # gui/
cargo cargo
rustc rustc
rustfmt
# api/ # api/
nodejs nodejs