jws-backend/src/services/permission.ts
2025-03-10 13:08:28 +07:00

181 lines
5.9 KiB
TypeScript

import { Prisma, Status } from "@prisma/client";
import prisma from "../db";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
import { RequestWithUser } from "../interfaces/user";
import { isSystem } from "../utils/keycloak";
import { ExpressionBuilder } from "kysely";
import { DB } from "../generated/kysely/types";
export function branchRelationPermInclude(user: RequestWithUser["user"]) {
return {
headOffice: {
include: {
branch: { where: { user: { some: { userId: user.sub } } } },
user: { where: { userId: user.sub } },
},
},
branch: { where: { user: { some: { userId: user.sub } } } },
user: { where: { userId: user.sub } },
};
}
export function branchActiveOnlyCond(activeOnly?: boolean) {
return activeOnly
? {
OR: [
{ headOffice: { status: { not: Status.INACTIVE } } },
{ headOffice: null, status: { not: Status.INACTIVE } },
],
}
: undefined;
}
export function createPermCondition(
globalAllow: (user: RequestWithUser["user"]) => boolean,
): (
user: RequestWithUser["user"],
opts?: { alwaysIncludeHead?: boolean; activeOnly?: boolean; targetBranchId?: string },
) => Prisma.BranchWhereInput["OR"] {
return (user, opts) =>
isSystem(user)
? opts?.targetBranchId
? [
{
id: opts.targetBranchId,
},
{
headOffice: { id: opts.targetBranchId },
},
{
branch: { some: { id: opts.targetBranchId } },
},
]
: undefined
: [
{
AND: opts?.activeOnly ? { status: { not: Status.INACTIVE } } : undefined,
OR: [
{
user: { some: { userId: user.sub } },
},
{
branch:
opts?.alwaysIncludeHead || globalAllow(user)
? {
some: { user: { some: { userId: user.sub } } },
}
: undefined,
},
{
headOffice: globalAllow(user)
? {
status: opts?.activeOnly ? { not: Status.INACTIVE } : undefined,
branch: {
some: { user: { some: { userId: user.sub } } },
},
}
: undefined,
},
{
headOffice: globalAllow(user)
? {
status: opts?.activeOnly ? { not: Status.INACTIVE } : undefined,
user: { some: { userId: user.sub } },
}
: undefined,
},
],
},
];
}
export async function getBranchPermissionCheck(user: RequestWithUser["user"], branchId: string) {
return await prisma.branch.findUnique({
include: branchRelationPermInclude(user),
where: { id: branchId },
});
}
export function createPermCheck(globalAllow: (user: RequestWithUser["user"]) => boolean) {
return async (
user: RequestWithUser["user"],
branch: Awaited<ReturnType<typeof getBranchPermissionCheck>> | string,
) => {
if (typeof branch === "string") {
branch = await getBranchPermissionCheck(user, branch);
}
if (!branch) {
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound");
}
if (!isSystem(user)) {
if (!globalAllow(user) && branch.user.length === 0) {
throw new HttpError(
HttpStatus.FORBIDDEN,
"You do not have permission to perform this action.",
"noPermission",
);
} else {
if (
(branch.user.length === 0 && branch.branch.length === 0 && !branch.headOffice) ||
(branch.headOffice &&
branch.headOffice.user.length === 0 &&
branch.headOffice.branch.length === 0)
) {
throw new HttpError(
HttpStatus.FORBIDDEN,
"You do not have permission to perform this action.",
"noPermission",
);
}
}
}
return branch;
};
}
export function createQueryPermissionCondition(
globalAllow: (user: RequestWithUser["user"]) => boolean,
opts?: { alwaysIncludeHead?: boolean },
) {
return (user: RequestWithUser["user"]) =>
({ eb, exists }: ExpressionBuilder<DB, keyof DB>) =>
exists(
eb
.selectFrom("Branch")
.leftJoin("BranchUser", "BranchUser.branchId", "Branch.id")
.leftJoin("Branch as SubBranch", "SubBranch.headOfficeId", "Branch.id")
.leftJoin("BranchUser as SubBranchUser", "SubBranchUser.branchId", "SubBranch.id")
.leftJoin("Branch as HeadBranch", "HeadBranch.id", "Branch.id")
.leftJoin("BranchUser as HeadBranchUser", "HeadBranchUser.branchId", "HeadBranch.id")
.leftJoin("Branch as SubHeadBranch", "SubHeadBranch.headOfficeId", "HeadBranch.id")
.leftJoin(
"BranchUser as SubHeadBranchUser",
"SubHeadBranchUser.branchId",
"SubHeadBranch.id",
)
.where((eb) => {
const cond = [
eb("BranchUser.userId", "=", user.sub), // NOTE: if user belong to current branch.
];
if (globalAllow?.(user) || opts?.alwaysIncludeHead) {
cond.push(
eb("SubBranchUser.userId", "=", user.sub), // NOTE: if user belong to branch under current branch.
);
}
if (globalAllow(user)) {
cond.push(
eb("HeadBranchUser.userId", "=", user.sub), // NOTE: if the current branch is under head branch user belong to.
eb("SubHeadBranchUser.userId", "=", user.sub), // NOTE: if the current branch is under the same head branch user belong to.
);
}
return eb.or(cond);
})
.select("Branch.id"),
);
}