untested WIP
This commit is contained in:
@@ -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(),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user