untested WIP

This commit is contained in:
2026-01-24 16:59:50 -06:00
parent 935c7296f6
commit 4be36e6ca0
56 changed files with 8645 additions and 3 deletions
+56
View File
@@ -0,0 +1,56 @@
import { ZodError } from "zod";
/**
* @ignore
*/
export const apiResponse = {
successful: (message: string, data?: any) => ({
status: 200,
message,
data,
successful: true,
meta: {
timestamp: Date.now(),
},
}),
created: (message: string, data?: any) => ({
status: 201,
message,
data,
successful: true,
meta: {
timestamp: Date.now(),
},
}),
error: (err: Error) => ({
// @ts-ignore
status: err["status"] ?? 400,
message: err.message,
error: err.name,
successful: false,
meta: {
timestamp: Date.now(),
},
}),
internalError: () => ({
status: 500,
message: "An Internal Server Error has occured...",
error: "InternalServerError",
successful: false,
meta: {
timestamp: Date.now(),
},
}),
zodError: (err: ZodError) => {
const data = JSON.parse(err.message);
return {
status: 400,
message: "TypeError",
error: data,
successful: false,
meta: {
timestamp: Date.now(),
},
};
},
};
+40
View File
@@ -0,0 +1,40 @@
import { Handler, Hono, MiddlewareHandler } from "hono";
import { Variables } from "../../types/HonoTypes";
/**
* Create a route.
*
* This method exists to serve the purpose of allowing us to split all of our api routes into different files and
* easily and quickly be able to rope them back into the main api server instance.
*
* One of the sortfallings of this method is that I was not able to figure out how to integrate the middleware to come
* before the handler, so if somebody feels upto it please figure out a way to have the middleware come before the handler
* method naturally as you would if you using a plain hono method.
*
* @TODO Move middleware handlers to come before primary handler naturally.
*
* @param method - HTTP Method
* @param path - URL Path
* @param handler - Handler function for Hono
* @param middleware - Array of Middleware Handlers for Hono
* @returns {Hono} - A new Hono instance containing the newly created route.
*/
export function createRoute(
method: string | string[],
path: string[],
handler: Handler<{
Variables: Variables;
}>,
...middleware: MiddlewareHandler<{
Variables: Variables;
}>[]
): Hono<{ Variables: Variables }> {
if (middleware)
return new Hono<{ Variables: Variables }>().on(
method as any,
path,
...middleware,
handler
);
return new Hono<{ Variables: Variables }>().on(method as any, path, handler);
}
+64
View File
@@ -0,0 +1,64 @@
import { Eventra } from "@duxcore/eventra";
import UserController from "../controllers/UserController";
import {
SessionController,
SessionTokensObject,
} from "../controllers/SessionController";
import { RoleController } from "../controllers/RoleController";
import { JsonWebTokenError } from "jsonwebtoken";
import { User } from "../../generated/prisma/client";
interface EventTypes {
"api:started": () => void;
"user:created": (user: UserController) => void;
"user:updated": (data: {
user: UserController;
updatedValues: Partial<User>;
}) => void;
"user:authenticated": (data: {
user: UserController;
tokens: SessionTokensObject;
}) => void;
"session:created": (data: {
user: UserController;
session: SessionController;
}) => void;
"session:tokens_generated": (data: {
session: SessionController;
tokens: SessionTokensObject;
}) => void;
"session:token_refresh": (data: {
session: SessionController;
tokens: SessionTokensObject;
}) => void;
"session:invalidated": (session: SessionController) => void;
"session:terminated": (session: SessionController) => void;
"role:created": (role: RoleController) => void;
"role:deleted": (role: RoleController) => void;
"role:updated": (data: {
role: RoleController;
updateData: Parameters<typeof RoleController.prototype.update>["0"];
}) => void;
"role:permissions:updated": (data: {
previous: string[];
previousSigned: string;
current: string[];
currentSigned: string;
action: "set" | "added" | "removed";
role: RoleController;
}) => void;
"role:permissions:verification_error": (data: {
currentSigned: string;
attemptedVerification: string;
err: Error;
role: RoleController;
}) => void;
}
export const events = new Eventra<EventTypes>();
export function setupEventDebugger() {
events.any((eventName, ...args) => {
console.log(`[ Event Debugger ] (${eventName})`);
});
}
@@ -0,0 +1,9 @@
export function genImplicitPerm(
resource: string,
resourceId: string,
userId: string
) {
return ["resource", resource, resourceId, "user", userId, "implicit"].join(
"."
);
}
@@ -0,0 +1,53 @@
/**
* Permission Validator
*
* This method is used for validaing user and role permissions. This method is given a single or and array
* of permission nodes that the user has and it is also given the permission node that is required for whatever
* query they are trying to execute, and this will determine if any of the permission nodes match or will
* verify the given permission node.
*
* Special token types:
* - Asterisk (*): verifies it's token and all following tokens.
* - Question Mark (?): verifies it's token and only it's token.
* - Inclusive List ([a,b,c]): verifies only the tokens in the list.
* - Exclusive List (<a,b,c>): verifies all tokens except for the ones in the list.
*
* @param permission - The required permission
* @param permissionExpressions - The owned permission(s)
* @returns {boolean} Does the user have the permission?
*/
export function permissionValidator(
permission: string,
permissionExpressions: string | string[]
): boolean {
if (typeof permissionExpressions === "string") {
// If the second parameter is a string, treat it as a single expression
permissionExpressions = [permissionExpressions];
}
// Iterate over each expression in the array and check if any of them match the permission
for (const expression of permissionExpressions) {
const rx = expression
.replace(/\./g, "\\.")
.replace(/\*/g, ".*")
.replace(/\?/g, ".")
.replace(/\[([^\]\[]*)\]/g, "($1)")
.replace(/<([^<>]+)>/g, "(?:(?!$1)[^.])*")
.replace(/,/g, "|");
if (new RegExp(`^${rx}$`).test(permission)) {
return true;
}
}
return false;
}
/**
* @TODO It's okay, you can't always get everything done and that is fine.
* Just take a breath and move on, come back if you feel upto it.
* What you make is good and whilst you can always do more,
* you can't do everything. Nothing will ever be perfect,
* so stop trying to be perfect and allow your self to move on
* even if you know there is more you can do.
*/
@@ -0,0 +1,24 @@
import jwt from "jsonwebtoken";
import { permissionsPrivateKey } from "../../constants";
import { PermissionIssuers } from "../../types/PermissionTypes";
/**
* Sign Permissions
*
* This will sign the array of permissions with the private key for permissions, and then return
* a JWT which will be stored in the databse.
*
* @param permissions - All the permissions to be signed
* @returns {string} - The signed permissions object
*/
export function signPermissions(data: {
issuer: PermissionIssuers;
subject: string;
permissions: string[];
}) {
return jwt.sign({ permissions: data.permissions }, permissionsPrivateKey, {
algorithm: "RS256",
issuer: data.issuer,
subject: data.subject,
});
}
+34
View File
@@ -0,0 +1,34 @@
import { blake2sHex } from "blakets";
import crypto from "crypto";
export default class Password {
public static generateSalt(options?: GenerateSaltOptions): string {
const length = options?.length ?? 12;
const randomBytes = crypto.randomBytes(Math.ceil(length / 2));
return randomBytes.toString("hex").slice(0, length);
}
public static hash(password: string, options?: HashPasswordOptions): string {
const salt =
options?.overrideSalt ?? Password.generateSalt(options?.saltOpts);
const hash = blake2sHex(`$BLAKE2s$${password}$${salt}`);
return `BLAKE2s$${hash}$${salt}`;
}
public static validate(newPass: string, hashed: string): boolean {
const [algo, oldHash, salt] = hashed.split(/\$/g);
return crypto.timingSafeEqual(
Buffer.from(hashed),
Buffer.from(Password.hash(newPass, { overrideSalt: salt }))
);
}
}
export interface HashPasswordOptions {
overrideSalt?: string;
saltOpts?: GenerateSaltOptions;
}
export interface GenerateSaltOptions {
length?: number; // default 12
}
+8
View File
@@ -0,0 +1,8 @@
export const mergeArrays = (a, b, predicate = (a, b) => a === b) => {
const c = [...a]; // copy to avoid side effects
// add all items from B to copy C if they're not already present
b.forEach((bItem) =>
c.some((cItem) => predicate(bItem, cItem)) ? null : c.push(bItem)
);
return c;
};