181 lines
5.9 KiB
TypeScript
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"),
|
|
);
|
|
}
|