From 0c279b459f8e645227f51d61e49a80a765d197fb Mon Sep 17 00:00:00 2001 From: Greyson Date: Fri, 19 Apr 2024 12:48:40 +0000 Subject: [PATCH] feature: Adds permission system. --- .../migration.sql | 20 +++++++++ .../migration.sql | 21 ++++++++++ prisma/schema.prisma | 6 ++- routes/NextNet API/Create User.bru | 10 ++++- src/libs/permissions.ts | 42 +++++++++++++++++++ src/routes/user/create.ts | 31 +++++++++++--- 6 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 prisma/migrations/20240417221012_fix_changes_permission_name_and_uniqueness/migration.sql create mode 100644 prisma/migrations/20240419124642_fix_fixes_permission_ids/migration.sql create mode 100644 src/libs/permissions.ts diff --git a/prisma/migrations/20240417221012_fix_changes_permission_name_and_uniqueness/migration.sql b/prisma/migrations/20240417221012_fix_changes_permission_name_and_uniqueness/migration.sql new file mode 100644 index 0000000..3ab22a3 --- /dev/null +++ b/prisma/migrations/20240417221012_fix_changes_permission_name_and_uniqueness/migration.sql @@ -0,0 +1,20 @@ +/* + Warnings: + + - You are about to drop the column `permissionID` on the `Permission` table. All the data in the column will be lost. + - Added the required column `permission` to the `Permission` table without a default value. This is not possible if the table is not empty. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Permission" ( + "permission" TEXT NOT NULL, + "has" BOOLEAN NOT NULL, + "userID" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + CONSTRAINT "Permission_userID_fkey" FOREIGN KEY ("userID") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_Permission" ("has", "userID") SELECT "has", "userID" FROM "Permission"; +DROP TABLE "Permission"; +ALTER TABLE "new_Permission" RENAME TO "Permission"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/prisma/migrations/20240419124642_fix_fixes_permission_ids/migration.sql b/prisma/migrations/20240419124642_fix_fixes_permission_ids/migration.sql new file mode 100644 index 0000000..78ee943 --- /dev/null +++ b/prisma/migrations/20240419124642_fix_fixes_permission_ids/migration.sql @@ -0,0 +1,21 @@ +/* + Warnings: + + - The primary key for the `Permission` table will be changed. If it partially fails, the table could be left without primary key constraint. + - Added the required column `id` to the `Permission` table without a default value. This is not possible if the table is not empty. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Permission" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "permission" TEXT NOT NULL, + "has" BOOLEAN NOT NULL, + "userID" INTEGER NOT NULL, + CONSTRAINT "Permission_userID_fkey" FOREIGN KEY ("userID") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_Permission" ("has", "permission", "userID") SELECT "has", "permission", "userID" FROM "Permission"; +DROP TABLE "Permission"; +ALTER TABLE "new_Permission" RENAME TO "Permission"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 19fc005..d098c1e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -37,11 +37,13 @@ model ForwardRule { } model Permission { - permissionID String @unique + id Int @id @default(autoincrement()) + + permission String has Boolean user User @relation(fields: [userID], references: [id]) - userID Int @id @default(autoincrement()) + userID Int } model User { diff --git a/routes/NextNet API/Create User.bru b/routes/NextNet API/Create User.bru index b1b2c56..691272b 100644 --- a/routes/NextNet API/Create User.bru +++ b/routes/NextNet API/Create User.bru @@ -6,6 +6,14 @@ meta { post { url: http://127.0.0.1:3000/api/v1/users/create - body: none + body: json auth: none } + +body:json { + { + "name": "Greyson Hofer", + "email": "me@greysoh.dev", + "password": "password" + } +} diff --git a/src/libs/permissions.ts b/src/libs/permissions.ts new file mode 100644 index 0000000..1709dbb --- /dev/null +++ b/src/libs/permissions.ts @@ -0,0 +1,42 @@ +import type { PrismaClient } from "@prisma/client"; + +export const permissionListDisabled: Record = { + "routes.add": false, + "routes.remove": false, + "routes.start": false, + "routes.stop": false, + "routes.edit": false, + "routes.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, + + "users.add": false, + "users.remove": false +}; + +// FIXME: This solution fucking sucks. +export let permissionListEnabled: Record = JSON.parse(JSON.stringify(permissionListDisabled)); + +for (const index of Object.keys(permissionListEnabled)) { + permissionListEnabled[index] = true; +} + +export async function hasPermission(permission: string, uid: number, prisma: PrismaClient): Promise { + const permissionNode = await prisma.permission.findFirst({ + where: { + userID: uid, + permission + } + }); + + if (!permissionNode) return false; + return permissionNode.has; +} \ No newline at end of file diff --git a/src/routes/user/create.ts b/src/routes/user/create.ts index b43fbb7..a22cdd2 100644 --- a/src/routes/user/create.ts +++ b/src/routes/user/create.ts @@ -1,14 +1,17 @@ import type { PrismaClient } from "@prisma/client"; import type { FastifyInstance } from "fastify"; - import { hash } from "bcrypt"; import { ServerOptions, SessionToken } from "../../libs/types.js"; +import { permissionListEnabled } from "../../libs/permissions.js"; import { generateToken } from "../../libs/generateToken.js"; export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Record, options: ServerOptions) { // TODO: Permissions + /** + * Creates a new user account to use, only if it is enabled. + */ fastify.post("/api/v1/users/create", { schema: { body: { @@ -16,8 +19,8 @@ export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Re required: ["name", "email", "password"], properties: { - name: { type: "string" }, - email: { type: "string" }, + name: { type: "string" }, + email: { type: "string" }, password: { type: "string" } } } @@ -31,7 +34,7 @@ export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Re } = req.body; if (!options.isSignupEnabled) { - return res.status(400).send({ + return res.status(403).send({ error: "Signing up is not enabled at this time." }); }; @@ -50,13 +53,31 @@ export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Re const saltedPassword: string = await hash(body.password, 15); - let userData = { + const userData = { name: body.name, email: body.email, password: saltedPassword, + + permissions: { + create: [] as { + permission: string, + has: boolean + }[] + }, + rootToken: null }; + // 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) { userData.rootToken = generateToken() as unknown as null; }