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

16
api/package-lock.json generated
View file

@ -21,6 +21,7 @@
"@types/ssh2": "^1.15.0",
"@types/ws": "^8.5.10",
"nodemon": "^3.0.3",
"prettier": "^3.2.5",
"prisma": "^5.13.0",
"typescript": "^5.3.3"
}
@ -1288,6 +1289,21 @@
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz",
"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": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.13.0.tgz",

View file

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

View file

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

View file

@ -1,43 +1,51 @@
import { NodeSSH } from "node-ssh";
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 & {
enabled: boolean
}
enabled: boolean;
};
// Fight me (for better naming)
type BackendParsedProviderString = {
ip: string,
port: number,
ip: string;
port: number;
username: string,
privateKey: string
}
username: string;
privateKey: string;
};
function parseBackendProviderString(data: string): BackendParsedProviderString {
try {
JSON.parse(data);
} catch (e) {
throw new Error("Payload body is not JSON")
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.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");
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");
return {
ip: jsonData.ip,
port: jsonData.port,
username: jsonData.username,
privateKey: jsonData.privateKey
}
privateKey: jsonData.privateKey,
};
}
export class SSHBackendProvider implements BackendBaseClass {
@ -54,7 +62,7 @@ export class SSHBackendProvider implements BackendBaseClass {
this.logs = [];
this.proxies = [];
this.clients = [];
this.options = parseBackendProviderString(parameters);
this.state = "stopped";
@ -76,7 +84,7 @@ export class SSHBackendProvider implements BackendBaseClass {
port: this.options.port,
username: this.options.username,
privateKey: this.options.privateKey
privateKey: this.options.privateKey,
});
} catch (e) {
this.logs.push(`Failed to start SSHBackendProvider! Error: '${e}'`);
@ -86,7 +94,7 @@ export class SSHBackendProvider implements BackendBaseClass {
this.sshInstance = null;
return false;
};
}
this.state = "started";
this.logs.push("Successfully started SSHBackendProvider.");
@ -109,57 +117,81 @@ export class SSHBackendProvider implements BackendBaseClass {
this.state = "stopped";
return true;
};
}
addConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): void {
const connectionCheck = SSHBackendProvider.checkParametersConnection(sourceIP, sourcePort, destPort, protocol);
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);
const foundProxyEntry = this.proxies.find(
i =>
i.sourceIP == sourceIP &&
i.sourcePort == sourcePort &&
i.destPort == destPort,
);
if (foundProxyEntry) return;
(async() => {
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 || !foundProxyEntry.enabled) return reject();
(async () => {
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 || !foundProxyEntry.enabled) return reject();
const client: ConnectedClient = {
ip: info.srcIP,
port: info.srcPort,
const client: ConnectedClient = {
ip: info.srcIP,
port: info.srcPort,
connectionDetails: foundProxyEntry
};
connectionDetails: foundProxyEntry,
};
this.clients.push(client);
const srcConn = new Socket();
srcConn.connect({
host: sourceIP,
port: sourcePort
});
this.clients.push(client);
// Why is this so confusing
const destConn = accept();
const srcConn = new Socket();
destConn.addListener("data", (chunk: Uint8Array) => {
srcConn.write(chunk);
});
srcConn.connect({
host: sourceIP,
port: sourcePort,
});
destConn.addListener("close", () => {
this.clients.splice(this.clients.indexOf(client), 1);
srcConn.end();
});
// Why is this so confusing
const destConn = accept();
srcConn.on("data", (data) => {
destConn.write(data);
});
destConn.addListener("data", (chunk: Uint8Array) => {
srcConn.write(chunk);
});
srcConn.on("end", () => {
this.clients.splice(this.clients.indexOf(client), 1);
destConn.close();
});
});
destConn.addListener("close", () => {
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.close();
});
},
);
})();
this.proxies.push({
@ -167,34 +199,56 @@ export class SSHBackendProvider implements BackendBaseClass {
sourcePort,
destPort,
enabled: true
enabled: true,
});
};
removeConnection(sourceIP: string, sourcePort: number, destPort: number, protocol: "tcp" | "udp"): void {
const connectionCheck = SSHBackendProvider.checkParametersConnection(sourceIP, sourcePort, destPort, protocol);
}
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);
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)"
};
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
}
};
success: true,
};
}
static checkParametersBackendInstance(data: string): ParameterReturnedValue {
try {
@ -203,12 +257,12 @@ export class SSHBackendProvider implements BackendBaseClass {
} catch (e: Error) {
return {
success: false,
message: e.toString()
}
message: e.toString(),
};
}
return {
success: true
}
};
}
success: true,
};
}
}

View file

@ -1,10 +1,14 @@
import process from "node:process";
import { PrismaClient } from '@prisma/client';
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 type {
ServerOptions,
SessionToken,
RouteOptions,
} from "./libs/types.js";
import type { BackendBaseClass } from "./backendimpl/base.js";
import { route as getPermissions } from "./routes/getPermissions.js";
@ -28,20 +32,22 @@ import { backendInit } from "./libs/backendInit.js";
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 noUsersCheck: boolean = await prisma.user.count() == 0;
const noUsersCheck: boolean = (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!");
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"
allowUnsafeGlobalTokens: process.env.NODE_ENV != "production",
};
const sessionTokens: Record<number, SessionToken[]> = {};
@ -58,7 +64,7 @@ const routeOptions: RouteOptions = {
tokens: sessionTokens,
options: serverOptions,
backends: backends
backends: backends,
};
console.log("Initializing forwarding rules...");
@ -67,7 +73,7 @@ const createdBackends = await prisma.desinationProvider.findMany();
for (const backend of createdBackends) {
console.log(`Running init steps for ID '${backend.id}' (${backend.name})`);
const init = await backendInit(backend, backends, prisma);
const init = await backendInit(backend, backends, prisma);
if (init) console.log("Init successful.");
}
@ -75,7 +81,7 @@ for (const backend of createdBackends) {
console.log("Done.");
getPermissions(routeOptions);
backendCreate(routeOptions);
backendRemove(routeOptions);
backendLookup(routeOptions);
@ -96,9 +102,9 @@ userLogin(routeOptions);
try {
await fastify.listen({
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) {
fastify.log.error(err);
process.exit(1);
}
}

View file

@ -5,15 +5,19 @@ import { backendProviders } from "../backendimpl/index.js";
type Backend = {
id: number;
name: string;
description: string | null;
backend: string;
name: string;
description: string | null;
backend: 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];
if (!ourProvider) {
console.log(" - Error: Invalid backend recieved!");
return false;
@ -24,7 +28,7 @@ export async function backendInit(backend: Backend, backends: Record<number, Bac
backends[backend.id] = new ourProvider(backend.connectionDetails);
const ourBackend = backends[backend.id];
if (!await ourBackend.start()) {
if (!(await ourBackend.start())) {
console.log(" - Error initializing backend!");
console.log(" - " + ourBackend.logs.join("\n - "));
@ -36,18 +40,25 @@ export async function backendInit(backend: Backend, backends: Record<number, Bac
const clients = await prisma.forwardRule.findMany({
where: {
destProviderID: backend.id,
enabled: true
}
enabled: true,
},
});
for (const client of clients) {
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;
}
ourBackend.addConnection(client.sourceIP, client.sourcePort, client.destPort, client.protocol);
ourBackend.addConnection(
client.sourceIP,
client.sourcePort,
client.destPort,
client.protocol,
);
}
return true;
}
}

View file

@ -9,7 +9,7 @@ export function generateRandomData(length: number = 128): string {
for (let i = 0; i < length; i += 2) {
const randomNumber = getRandomInt(0, 255);
if (randomNumber == 0) {
i -= 2;
continue;
@ -19,4 +19,4 @@ export function generateRandomData(length: number = 128): string {
}
return newString;
}
}

View file

@ -2,44 +2,50 @@ import type { PrismaClient } from "@prisma/client";
import type { SessionToken } from "./types.js";
export const permissionListDisabled: Record<string, boolean> = {
"routes.add": false,
"routes.remove": false,
"routes.start": false,
"routes.stop": false,
"routes.edit": false,
"routes.visible": false,
"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.add": false,
"backends.remove": false,
"backends.start": false,
"backends.stop": false,
"backends.edit": false,
"backends.visible": false,
"backends.secretVis": false,
"permissions.see": false,
"permissions.see": false,
"users.add": false,
"users.remove": false,
"users.lookup": false,
"users.edit": false,
"users.add": false,
"users.remove": false,
"users.lookup": false,
"users.edit": false,
};
// 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)) {
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) {
const permissionNode = await prisma.permission.findFirst({
where: {
userID: uid,
permission
}
permission,
},
});
if (!permissionNode || !permissionNode.has) return false;
@ -48,7 +54,11 @@ export async function hasPermission(permissionList: string[], uid: number, prism
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;
// 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];
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);
continue;
} else {
@ -74,19 +87,24 @@ export async function getUID(token: string, tokens: Record<number, SessionToken[
if (userID == -1) {
const allUsers = await prisma.user.findMany({
where: {
isRootServiceAccount: true
}
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<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);
return await hasPermission(permissionList, userID, prisma);
}
}

View file

@ -8,21 +8,21 @@ export type ServerOptions = {
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
createdAt: number;
expiresAt: number; // Should be (createdAt + (30 minutes))
token: string;
};
export type RouteOptions = {
fastify: FastifyInstance,
prisma: PrismaClient,
tokens: Record<number, SessionToken[]>,
options: ServerOptions,
backends: Record<number, BackendBaseClass>
};
fastify: FastifyInstance;
prisma: PrismaClient;
tokens: Record<number, SessionToken[]>;
options: ServerOptions;
backends: Record<number, BackendBaseClass>;
};

View file

@ -5,90 +5,95 @@ import { backendProviders } from "../../backendimpl/index.js";
import { backendInit } from "../../libs/backendInit.js";
export function route(routeOptions: RouteOptions) {
const {
fastify,
prisma,
tokens,
backends
} = routeOptions;
const { 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);
};
}
/**
* Creates a new backend to use
*/
fastify.post("/api/v1/backends/create", {
schema: {
body: {
type: "object",
required: ["token", "name", "backend", "connectionDetails"],
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" }
}
properties: {
token: { type: "string" },
name: { type: "string" },
description: { type: "string" },
backend: { type: "string" },
},
},
},
},
async (req, res) => {
// @ts-ignore
const body: {
token: string;
name: string;
description?: string;
connectionDetails: any;
backend: string;
} = req.body;
if (!(await hasPermission(body.token, ["backends.add"]))) {
return res.status(403).send({
error: "Unauthorized",
});
}
}
}, async(req, res) => {
// @ts-ignore
const body: {
token: string,
name: string,
description?: string,
connectionDetails: any,
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: "Unknown/unsupported/deprecated backend!"
});
};
const connectionDetails = JSON.stringify(body.connectionDetails);
const connectionDetailsValidityCheck = backendProviders[body.backend].checkParametersBackendInstance(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: JSON.stringify(body.connectionDetails)
if (!backendProviders[body.backend]) {
return res.status(400).send({
error: "Unknown/unsupported/deprecated backend!",
});
}
});
const init = await backendInit(backend, backends, prisma);
if (!init) {
// TODO: better error code
return res.status(504).send({
error: "Backend is created, but failed to initalize correctly",
id: backend.id
const connectionDetails = JSON.stringify(body.connectionDetails);
const connectionDetailsValidityCheck =
backendProviders[body.backend].checkParametersBackendInstance(
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: JSON.stringify(body.connectionDetails),
},
});
}
return {
success: true,
id: backend.id
};
});
}
const init = await backendInit(backend, backends, prisma);
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,
};
},
);
}

View file

@ -2,77 +2,81 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) {
const {
fastify,
prisma,
tokens,
backends
} = routeOptions;
const { 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);
};
}
/**
* Creates a new route to use
*/
fastify.post("/api/v1/backends/lookup", {
schema: {
body: {
type: "object",
required: ["token"],
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" }
}
properties: {
token: { type: "string" },
id: { type: "number" },
name: { type: "string" },
description: { type: "string" },
backend: { type: "string" },
},
},
},
},
async (req, res) => {
// @ts-ignore
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",
});
}
}
}, async(req, res) => {
// @ts-ignore
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,
},
});
};
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 => ({
name: i.name,
description: i.description,
return {
success: true,
data: prismaBackends.map((i) => ({
name: i.name,
description: i.description,
backend: i.backend,
connectionDetails: canSeeSecrets ? i.connectionDetails : "",
backend: i.backend,
connectionDetails: canSeeSecrets ? i.connectionDetails : "",
logs: backends[i.id].logs
}))
}
});
}
logs: backends[i.id].logs,
})),
};
},
);
}

View file

@ -2,70 +2,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;
const { 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);
};
}
/**
* Creates a new route to use
*/
fastify.post("/api/v1/backends/remove", {
schema: {
body: {
type: "object",
required: ["token", "id"],
fastify.post(
"/api/v1/backends/remove",
{
schema: {
body: {
type: "object",
required: ["token", "id"],
properties: {
token: { type: "string" },
id: { type: "number" }
}
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, ["backends.remove"]))) {
return res.status(403).send({
error: "Unauthorized",
});
}
}
}, async(req, res) => {
// @ts-ignore
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
if (!backends[body.id]) {
return res.status(400).send({
error: "Backend not found",
});
}
});
return {
success: true
}
});
}
// 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,
};
},
);
}

View file

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

View file

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

View file

@ -2,107 +2,112 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) {
const {
fastify,
prisma,
tokens
} = routeOptions;
const { 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);
};
}
/**
* 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" },
fastify.post(
"/api/v1/forward/lookup",
{
schema: {
body: {
type: "object",
required: ["token"],
name: { type: "string" },
protocol: { type: "string" },
description: { type: "string" },
properties: {
token: { type: "string" },
id: { type: "number" },
sourceIP: { type: "string" },
sourcePort: { type: "number" },
destPort: { type: "number" },
name: { type: "string" },
protocol: { type: "string" },
description: { type: "string" },
providerID: { type: "number" },
autoStart: { type: "boolean" }
}
sourceIP: { type: "string" },
sourcePort: { type: "number" },
destPort: { type: "number" },
providerID: { type: "number" },
autoStart: { type: "boolean" },
},
},
},
},
async (req, res) => {
// @ts-ignore
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'",
});
}
}
}, async(req, res) => {
// @ts-ignore
const body: {
token: string,
id?: number,
name?: string,
description?: string,
if (
!(await hasPermission(body.token, [
"routes.visible", // wtf?
]))
) {
return res.status(403).send({
error: "Unauthorized",
});
}
protocol?: "tcp" | "udp",
const forwardRules = await prisma.forwardRule.findMany({
where: {
id: body.id,
name: body.name,
description: body.description,
sourceIP?: string,
sourcePort?: number,
sourceIP: body.sourceIP,
sourcePort: body.sourcePort,
destinationPort?: number,
destPort: body.destinationPort,
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"
destProviderID: body.providerID,
enabled: body.autoStart,
},
});
};
const forwardRules = await prisma.forwardRule.findMany({
where: {
id: body.id,
name: body.name,
description: body.description,
return {
success: true,
data: forwardRules.map(i => ({
id: i.id,
name: i.name,
description: i.description,
sourceIP: body.sourceIP,
sourcePort: body.sourcePort,
sourceIP: i.sourceIP,
sourcePort: i.sourcePort,
destPort: body.destinationPort,
destPort: i.destPort,
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
}))
};
});
}
providerID: i.destProviderID,
autoStart: i.enabled, // TODO: Add enabled flag in here to see if we're running or not
})),
};
},
);
}

View file

@ -2,54 +2,55 @@ import { hasPermissionByToken } from "../../libs/permissions.js";
import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) {
const {
fastify,
prisma,
tokens
} = routeOptions;
const { 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);
};
}
/**
* 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-ignore
const body: {
token: string,
id: number
} = req.body;
fastify.post(
"/api/v1/forward/remove",
{
schema: {
body: {
type: "object",
required: ["token", "id"],
if (!await hasPermission(body.token, [
"routes.remove"
])) {
return res.status(403).send({
error: "Unauthorized"
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.remove"]))) {
return res.status(403).send({
error: "Unauthorized",
});
}
await prisma.forwardRule.delete({
where: {
id: body.id,
},
});
};
await prisma.forwardRule.delete({
where: {
id: body.id
}
});
return {
success: true
}
});
}
return {
success: true,
};
},
);
}

View file

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

View file

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

View file

@ -2,52 +2,50 @@ import { hasPermission, getUID } from "../libs/permissions.js";
import type { RouteOptions } from "../libs/types.js";
export function route(routeOptions: RouteOptions) {
const {
fastify,
prisma,
tokens
} = routeOptions;
const { fastify, prisma, tokens } = routeOptions;
/**
* Logs in to a user account.
*/
fastify.post("/api/v1/getPermissions", {
schema: {
body: {
type: "object",
required: ["token"],
fastify.post(
"/api/v1/getPermissions",
{
schema: {
body: {
type: "object",
required: ["token"],
properties: {
token: { type: "string" }
}
properties: {
token: { type: "string" },
},
},
},
},
async (req, res) => {
// @ts-ignore
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",
});
}
}
}, async(req, res) => {
// @ts-ignore
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,
},
});
};
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)
}
});
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),
};
},
);
}

View file

@ -6,113 +6,116 @@ import { generateRandomData } from "../../libs/generateRandom.js";
import type { RouteOptions } from "../../libs/types.js";
export function route(routeOptions: RouteOptions) {
const {
fastify,
prisma,
tokens,
options
} = 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", "password"],
fastify.post(
"/api/v1/users/create",
{
schema: {
body: {
type: "object",
required: ["name", "email", "password"],
properties: {
name: { type: "string" },
email: { type: "string" },
password: { type: "string" }
}
}
}
}, async(req, res) => {
// @ts-ignore
const body: {
name: string,
email: string,
password: string
} = req.body;
properties: {
name: { type: "string" },
email: { type: "string" },
password: { type: "string" },
},
},
},
},
async (req, res) => {
// @ts-ignore
const body: {
name: string;
email: string;
password: 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,
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.isSignupEnabled) {
return res.status(403).send({
error: "Signing up is not enabled at this time.",
});
}
};
if (options.allowUnsafeGlobalTokens) {
// @ts-ignore
userData.rootToken = generateRandomData();
// @ts-ignore
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
const userSearch = await prisma.user.findFirst({
where: {
email: body.email,
},
});
return {
success: true,
token: generatedToken
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,
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-ignore
userData.rootToken = generateRandomData();
// @ts-ignore
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,
};
}
},
);
}

View file

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

View file

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

View file

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