move action permission check to middleware

This commit is contained in:
Milo Schwartz
2024-11-05 23:55:46 -05:00
parent 03051878ef
commit 372e51c0a5
48 changed files with 266 additions and 936 deletions

View File

@@ -11,8 +11,6 @@ import {
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from "@server/logger";
import { eq, and } from "drizzle-orm";
import stoi from "@server/utils/stoi";
import { fromError } from "zod-validation-error";
@@ -26,7 +24,6 @@ const createResourceParamsSchema = z.object({
orgId: z.string(),
});
// Define Zod schema for request body validation
const createResourceSchema = z.object({
name: z.string().min(1).max(255),
subdomain: z.string().min(1).max(255).optional(),
@@ -38,7 +35,6 @@ export async function createResource(
next: NextFunction
): Promise<any> {
try {
// Validate request body
const parsedBody = createResourceSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
@@ -64,20 +60,6 @@ export async function createResource(
const { siteId, orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(
ActionsEnum.createResource,
req
);
if (!hasPermission) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have permission to perform this action"
)
);
}
if (!req.userOrgRoleId) {
return next(
createHttpError(HttpCode.FORBIDDEN, "User does not have a role")

View File

@@ -6,7 +6,6 @@ import { eq } from "drizzle-orm";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
@@ -21,7 +20,6 @@ export async function deleteResource(
next: NextFunction
): Promise<any> {
try {
// Validate request parameters
const parsedParams = deleteResourceSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
@@ -34,21 +32,6 @@ export async function deleteResource(
const { resourceId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(
ActionsEnum.deleteResource,
req
);
if (!hasPermission) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have permission to perform this action"
)
);
}
// Delete the resource from the database
const deletedResource = await db
.delete(resources)
.where(eq(resources.resourceId, resourceId))
@@ -73,10 +56,7 @@ export async function deleteResource(
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -6,11 +6,8 @@ import { eq } from "drizzle-orm";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
// Define Zod schema for request parameters validation
const getResourceSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
@@ -28,7 +25,6 @@ export async function getResource(
next: NextFunction
): Promise<any> {
try {
// Validate request parameters
const parsedParams = getResourceSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
@@ -41,21 +37,6 @@ export async function getResource(
const { resourceId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(
ActionsEnum.getResource,
req
);
if (!hasPermission) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have permission to perform this action"
)
);
}
// Fetch the resource from the database
const resource = await db
.select()
.from(resources)
@@ -84,12 +65,8 @@ export async function getResource(
status: HttpCode.OK,
});
} catch (error) {
throw error;
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -6,7 +6,6 @@ import { eq } from "drizzle-orm";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
@@ -32,20 +31,6 @@ export async function listResourceRoles(
const { resourceId } = parsedParams.data;
// Check if the user has permission to list resource roles
const hasPermission = await checkUserActionPermission(
ActionsEnum.listResourceRoles,
req
);
if (!hasPermission) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have permission to perform this action"
)
);
}
const resourceRolesList = await db
.select({
roleId: roles.roleId,
@@ -67,10 +52,7 @@ export async function listResourceRoles(
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -11,13 +11,16 @@ import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { sql, eq, or, inArray, and, count } from "drizzle-orm";
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from "@server/logger";
import stoi from "@server/utils/stoi";
const listResourcesParamsSchema = z
.object({
siteId: z.string().optional().transform(stoi).pipe(z.number().int().positive().optional()),
siteId: z
.string()
.optional()
.transform(stoi)
.pipe(z.number().int().positive().optional()),
orgId: z.string().optional(),
})
.refine((data) => !!data.siteId !== !!data.orgId, {
@@ -43,7 +46,7 @@ const listResourcesSchema = z.object({
function queryResources(
accessibleResourceIds: number[],
siteId?: number,
orgId?: string,
orgId?: string
) {
if (siteId) {
return db
@@ -58,8 +61,8 @@ function queryResources(
.where(
and(
inArray(resources.resourceId, accessibleResourceIds),
eq(resources.siteId, siteId),
),
eq(resources.siteId, siteId)
)
);
} else if (orgId) {
return db
@@ -74,8 +77,8 @@ function queryResources(
.where(
and(
inArray(resources.resourceId, accessibleResourceIds),
eq(resources.orgId, orgId),
),
eq(resources.orgId, orgId)
)
);
}
}
@@ -88,7 +91,7 @@ export type ListResourcesResponse = {
export async function listResources(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = listResourcesSchema.safeParse(req.query);
@@ -96,8 +99,8 @@ export async function listResources(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map((e) => e.message).join(", "),
),
parsedQuery.error.errors.map((e) => e.message).join(", ")
)
);
}
const { limit, offset } = parsedQuery.data;
@@ -107,36 +110,21 @@ export async function listResources(
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map((e) => e.message).join(", "),
),
parsedParams.error.errors.map((e) => e.message).join(", ")
)
);
}
const { siteId, orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(
ActionsEnum.listResources,
req,
);
if (!hasPermission) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have permission to perform this action",
),
);
}
if (orgId && orgId !== req.userOrgId) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization",
),
"User does not have access to this organization"
)
);
}
// Get the list of resources the user has access to
const accessibleResources = await db
.select({
resourceId: sql<number>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})`,
@@ -144,17 +132,17 @@ export async function listResources(
.from(userResources)
.fullJoin(
roleResources,
eq(userResources.resourceId, roleResources.resourceId),
eq(userResources.resourceId, roleResources.resourceId)
)
.where(
or(
eq(userResources.userId, req.user!.userId),
eq(roleResources.roleId, req.userOrgRoleId!),
),
eq(roleResources.roleId, req.userOrgRoleId!)
)
);
const accessibleResourceIds = accessibleResources.map(
(resource) => resource.resourceId,
(resource) => resource.resourceId
);
let countQuery: any = db
@@ -185,10 +173,7 @@ export async function listResources(
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred...",
),
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -6,16 +6,13 @@ import { eq } from "drizzle-orm";
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
// Define Zod schema for request parameters validation
const updateResourceParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
});
// Define Zod schema for request body validation
const updateResourceBodySchema = z
.object({
name: z.string().min(1).max(255).optional(),
@@ -31,7 +28,6 @@ export async function updateResource(
next: NextFunction
): Promise<any> {
try {
// Validate request parameters
const parsedParams = updateResourceParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
@@ -42,7 +38,6 @@ export async function updateResource(
);
}
// Validate request body
const parsedBody = updateResourceBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
@@ -56,21 +51,6 @@ export async function updateResource(
const { resourceId } = parsedParams.data;
const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(
ActionsEnum.updateResource,
req
);
if (!hasPermission) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have permission to perform this action"
)
);
}
// Update the resource in the database
const updatedResource = await db
.update(resources)
.set(updateData)
@@ -96,10 +76,7 @@ export async function updateResource(
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}