diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 7d6f26e0..0fc38564 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -31,7 +31,8 @@ export enum ActionsEnum { getRole = "getRole", listRoles = "listRoles", updateRole = "updateRole", - deleteUser = "deleteUser", + addUser = "addUser", + removeUser = "removeUser", listUsers = "listUsers", listSiteRoles = "listSiteRoles", listUserRoles = "listUserRoles", @@ -44,6 +45,14 @@ export enum ActionsEnum { removeRoleAction = "removeRoleAction", listRoleSites = "listRoleSites", listRoleResources = "listRoleResources", + listRoleActions = "listRoleActions", + addUserRole = "addUserRole", + addUserResource = "addUserResource", + addUserSite = "addUserSite", + addUserAction = "addUserAction", + removeUserAction = "removeUserAction", + removeUserResource = "removeUserResource", + removeUserSite = "removeUserSite", } export async function checkUserActionPermission(actionId: string, req: Request): Promise { diff --git a/server/routers/auth/index.ts b/server/routers/auth/index.ts index ed558a33..7d7b36eb 100644 --- a/server/routers/auth/index.ts +++ b/server/routers/auth/index.ts @@ -10,6 +10,7 @@ export * from "./verifySiteAccess"; export * from "./verifyResourceAccess"; export * from "./verifyTargetAccess"; export * from "./verifyRoleAccess"; +export * from "./verifyUserAccess"; export * from "./verifySuperuser"; export * from "./verifyEmail"; export * from "./requestEmailVerificationCode"; diff --git a/server/routers/auth/verifyResourceAccess.ts b/server/routers/auth/verifyResourceAccess.ts index 16f6b97b..85b133eb 100644 --- a/server/routers/auth/verifyResourceAccess.ts +++ b/server/routers/auth/verifyResourceAccess.ts @@ -7,7 +7,7 @@ import HttpCode from '@server/types/HttpCode'; export async function verifyResourceAccess(req: Request, res: Response, next: NextFunction) { const userId = req.user!.id; // Assuming you have user information in the request - const resourceId = req.params.resourceId; + const resourceId = req.params.resourceId || req.body.resourceId || req.query.resourceId; if (!userId) { return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); diff --git a/server/routers/auth/verifySiteAccess.ts b/server/routers/auth/verifySiteAccess.ts index c2b3f083..7f53ef65 100644 --- a/server/routers/auth/verifySiteAccess.ts +++ b/server/routers/auth/verifySiteAccess.ts @@ -7,7 +7,7 @@ import HttpCode from '@server/types/HttpCode'; export async function verifySiteAccess(req: Request, res: Response, next: NextFunction) { const userId = req.user!.id; // Assuming you have user information in the request - const siteId = parseInt(req.params.siteId); + const siteId = parseInt(req.params.siteId || req.body.siteId || req.query.siteId); if (!userId) { return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); diff --git a/server/routers/auth/verifyUserAccess.ts b/server/routers/auth/verifyUserAccess.ts new file mode 100644 index 00000000..67e6c936 --- /dev/null +++ b/server/routers/auth/verifyUserAccess.ts @@ -0,0 +1,37 @@ +import { Request, Response, NextFunction } from 'express'; +import { db } from '@server/db'; +import { sites, userOrgs, userSites, roleSites, roles } from '@server/db/schema'; +import { and, eq, or } from 'drizzle-orm'; +import createHttpError from 'http-errors'; +import HttpCode from '@server/types/HttpCode'; + +export async function verifyUserAccess(req: Request, res: Response, next: NextFunction) { + const userId = req.user!.id; // Assuming you have user information in the request + const reqUserId = req.params.userId || req.body.userId || req.query.userId; + + if (!userId) { + return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); + } + + if (!reqUserId) { + return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid user ID')); + } + + try { + + const userOrg = await db.select() + .from(userOrgs) + .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, req.userOrgId!))) + .limit(1); + + if (userOrg.length === 0) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this user')); + } + + // If we reach here, the user doesn't have access to the site + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this site')); + + } catch (error) { + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying site access')); + } +} \ No newline at end of file diff --git a/server/routers/external.ts b/server/routers/external.ts index 503a6b66..cc3ce2d6 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -20,7 +20,8 @@ import { verifyTargetAccess, verifyRoleAccess, verifySuperuser, - verifyUserInRole + verifyUserInRole, + verifyUserAccess } from "./auth"; // Root routes @@ -168,21 +169,60 @@ authenticated.delete( "/role/:roleId/action", verifyRoleAccess, verifyUserInRole, + verifySuperuser, role.removeRoleAction, ); authenticated.get( "/role/:roleId/actions", verifyRoleAccess, verifyUserInRole, + verifySuperuser, role.listRoleActions, ); -authenticated.get("/users", user.listUsers); -// authenticated.get("/org/:orgId/users", user.???); // TODO: Implement this authenticated.get("/user", user.getUser); -authenticated.get("/user/roles", user.listUserRoles); -// authenticated.get("/user/:userId", user.getUser); -authenticated.delete("/user/:userId", user.deleteUser); +authenticated.get("/org/:orgId/users", verifyOrgAccess, user.listUsers); +authenticated.delete("/org/:orgId/user/:userId", verifyOrgAccess, verifyUserAccess, user.removeUserOrg); +authenticated.put("/org/:orgId/user/:userId", verifyOrgAccess, verifyUserAccess, user.addUserOrg); + +authenticated.put( + "/user/:userId/site", + verifySiteAccess, + verifyUserAccess, + role.addRoleSite, +); +authenticated.delete( + "/user/:userId/site", + verifySiteAccess, + verifyUserAccess, + role.removeRoleSite, +); +authenticated.put( + "/user/:userId/resource", + verifyResourceAccess, + verifyUserAccess, + role.addRoleResource, +); +authenticated.delete( + "/user/:userId/resource", + verifyResourceAccess, + verifyUserAccess, + role.removeRoleResource, +); +authenticated.put( + "/org/:orgId/user/:userId/action", + verifyOrgAccess, + verifyUserAccess, + verifySuperuser, + role.addRoleAction, +); +authenticated.delete( + "/org/:orgId/user/:userId/action", + verifyOrgAccess, + verifyUserAccess, + verifySuperuser, + role.removeRoleAction, +); // Auth routes export const authRouter = Router(); diff --git a/server/routers/user/addUserAction.ts b/server/routers/user/addUserAction.ts new file mode 100644 index 00000000..d84f717c --- /dev/null +++ b/server/routers/user/addUserAction.ts @@ -0,0 +1,61 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { userActions, users } from '@server/db/schema'; +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 } from 'drizzle-orm'; + +const addUserActionSchema = z.object({ + userId: z.string(), + actionId: z.string(), + orgId: z.string().transform(Number).pipe(z.number().int().positive()), +}); + +export async function addUserAction(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedBody = addUserActionSchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedBody.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { userId, actionId, orgId } = parsedBody.data; + + // Check if the user has permission to add user actions + const hasPermission = await checkUserActionPermission(ActionsEnum.addUserAction, req); + if (!hasPermission) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + } + + // Check if the user exists + const user = await db.select().from(users).where(eq(users.id, userId)).limit(1); + if (user.length === 0) { + return next(createHttpError(HttpCode.NOT_FOUND, `User with ID ${userId} not found`)); + } + + const newUserAction = await db.insert(userActions).values({ + userId, + actionId, + orgId, + }).returning(); + + return response(res, { + data: newUserAction[0], + success: true, + error: false, + message: "Action added to user successfully", + status: HttpCode.CREATED, + }); + } catch (error) { + logger.error(error); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + } +} \ No newline at end of file diff --git a/server/routers/user/addUserOrg.ts b/server/routers/user/addUserOrg.ts new file mode 100644 index 00000000..01b59aba --- /dev/null +++ b/server/routers/user/addUserOrg.ts @@ -0,0 +1,87 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { userOrgs, users, roles } from '@server/db/schema'; +import { and, 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'; + +const addUserParamsSchema = z.object({ + userId: z.string().uuid(), + orgId: z.number().int().positive(), +}); + +const addUserSchema = z.object({ + roleId: z.number().int().positive(), +}); + +export async function addUserOrg(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedParams = addUserParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { userId, orgId } = parsedParams.data; + + const parsedBody = addUserSchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedBody.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { roleId } = parsedBody.data; + + // Check if the user has permission to add users + const hasPermission = await checkUserActionPermission(ActionsEnum.addUser, req); + if (!hasPermission) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + } + + // Check if the user exists + const user = await db.select().from(users).where(eq(users.id, userId)).limit(1); + if (user.length === 0) { + return next(createHttpError(HttpCode.NOT_FOUND, `User with ID ${userId} not found`)); + } + + // Check if the user is already in the organization + const existingUserOrg = await db.select() + .from(userOrgs) + .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))) + .limit(1); + + if (existingUserOrg.length > 0) { + return next(createHttpError(HttpCode.CONFLICT, 'User is already a member of this organization')); + } + + // Add the user to the userOrgs table + const newUserOrg = await db.insert(userOrgs).values({ + userId, + orgId, + roleId + }).returning(); + + return response(res, { + data: newUserOrg[0], + success: true, + error: false, + message: "User added to organization successfully", + status: HttpCode.CREATED, + }); + } catch (error) { + logger.error(error); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + } +} \ No newline at end of file diff --git a/server/routers/user/addUserResource.ts b/server/routers/user/addUserResource.ts new file mode 100644 index 00000000..f62eefbf --- /dev/null +++ b/server/routers/user/addUserResource.ts @@ -0,0 +1,52 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { userResources } from '@server/db/schema'; +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'; + +const addUserResourceSchema = z.object({ + userId: z.string(), + resourceId: z.string(), +}); + +export async function addUserResource(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedBody = addUserResourceSchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedBody.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { userId, resourceId } = parsedBody.data; + + // Check if the user has permission to add user resources + const hasPermission = await checkUserActionPermission(ActionsEnum.addUserResource, req); + if (!hasPermission) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + } + + const newUserResource = await db.insert(userResources).values({ + userId, + resourceId, + }).returning(); + + return response(res, { + data: newUserResource[0], + success: true, + error: false, + message: "Resource added to user successfully", + status: HttpCode.CREATED, + }); + } catch (error) { + logger.error(error); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + } +} \ No newline at end of file diff --git a/server/routers/user/addUserSite.ts b/server/routers/user/addUserSite.ts new file mode 100644 index 00000000..8be945a0 --- /dev/null +++ b/server/routers/user/addUserSite.ts @@ -0,0 +1,65 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { resources, userResources, userSites } from '@server/db/schema'; +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 } from 'drizzle-orm'; + +const addUserSiteSchema = z.object({ + userId: z.string(), + siteId: z.string().transform(Number).pipe(z.number().int().positive()), +}); + +export async function addUserSite(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedBody = addUserSiteSchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedBody.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { userId, siteId } = parsedBody.data; + + // Check if the user has permission to add user sites + const hasPermission = await checkUserActionPermission(ActionsEnum.addUserSite, req); + if (!hasPermission) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + } + + const newUserSite = await db.insert(userSites).values({ + userId, + siteId, + }).returning(); + + // Add all resources associated with the site to the user + const siteResources = await db.select() + .from(resources) + .where(eq(resources.siteId, siteId)); + + for (const resource of siteResources) { + await db.insert(userResources).values({ + userId, + resourceId: resource.resourceId, + }); + } + + return response(res, { + data: newUserSite[0], + success: true, + error: false, + message: "Site added to user successfully", + status: HttpCode.CREATED, + }); + } catch (error) { + logger.error(error); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + } +} \ No newline at end of file diff --git a/server/routers/user/index.ts b/server/routers/user/index.ts index fbc1ecc7..9d494ecd 100644 --- a/server/routers/user/index.ts +++ b/server/routers/user/index.ts @@ -1,5 +1,6 @@ export * from "./getUser"; -export * from "./deleteUser"; +export * from "./removeUserOrg"; +export * from "./addUserOrg"; export * from "./listUsers"; export * from "./listUserRoles"; export * from "./setUserRole"; \ No newline at end of file diff --git a/server/routers/user/listUsers.ts b/server/routers/user/listUsers.ts index e7082ea1..c8b2fa5a 100644 --- a/server/routers/user/listUsers.ts +++ b/server/routers/user/listUsers.ts @@ -9,68 +9,85 @@ import { sql } from 'drizzle-orm'; import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; import logger from '@server/logger'; +const listUsersParamsSchema = z.object({ + orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()), +}); + const listUsersSchema = z.object({ - limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), - offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)), + limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), + offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)), }); export async function listUsers(req: Request, res: Response, next: NextFunction): Promise { - try { - const parsedQuery = listUsersSchema.safeParse(req.query); - if (!parsedQuery.success) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - parsedQuery.error.errors.map(e => e.message).join(', ') - ) - ); + try { + const parsedQuery = listUsersSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedQuery.error.errors.map(e => e.message).join(', ') + ) + ); + } + const { limit, offset } = parsedQuery.data; + + const parsedParams = listUsersParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { orgId } = parsedParams.data; + + // Check if the user has permission to list users + const hasPermission = await checkUserActionPermission(ActionsEnum.listUsers, req); + if (!hasPermission) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + } + + // Query to join users, userOrgs, and roles tables + const usersWithRoles = await db + .select({ + id: users.id, + email: users.email, + emailVerified: users.emailVerified, + dateCreated: users.dateCreated, + orgId: userOrgs.orgId, + roleId: userOrgs.roleId, + roleName: roles.name, + }) + .from(users) + .leftJoin(userOrgs, sql`${users.id} = ${userOrgs.userId}`) + .leftJoin(roles, sql`${userOrgs.roleId} = ${roles.roleId}`) + .where(sql`${userOrgs.orgId} = ${orgId}`) + .limit(limit) + .offset(offset); + + // Count total users + const [{ count }] = await db + .select({ count: sql`count(*)` }) + .from(users); + + return response(res, { + data: { + users: usersWithRoles, + pagination: { + total: count, + limit, + offset, + }, + }, + success: true, + error: false, + message: "Users retrieved successfully", + status: HttpCode.OK, + }); + } catch (error) { + logger.error(error); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); } - const { limit, offset } = parsedQuery.data; - - // Check if the user has permission to list users - const hasPermission = await checkUserActionPermission(ActionsEnum.listUsers, req); - if (!hasPermission) { - return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); - } - - // Query to join users, userOrgs, and roles tables - const usersWithRoles = await db - .select({ - id: users.id, - email: users.email, - emailVerified: users.emailVerified, - dateCreated: users.dateCreated, - orgId: userOrgs.orgId, - roleId: userOrgs.roleId, - roleName: roles.name, - }) - .from(users) - .leftJoin(userOrgs, sql`${users.id} = ${userOrgs.userId}`) - .leftJoin(roles, sql`${userOrgs.roleId} = ${roles.roleId}`) - .limit(limit) - .offset(offset); - - // Count total users - const [{ count }] = await db - .select({ count: sql`count(*)` }) - .from(users); - - return response(res, { - data: { - users: usersWithRoles, - pagination: { - total: count, - limit, - offset, - }, - }, - success: true, - error: false, - message: "Users retrieved successfully", - status: HttpCode.OK, - }); - } catch (error) { - logger.error(error); - return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); - } } \ No newline at end of file diff --git a/server/routers/user/removeUserAction.ts b/server/routers/user/removeUserAction.ts new file mode 100644 index 00000000..3b58ff72 --- /dev/null +++ b/server/routers/user/removeUserAction.ts @@ -0,0 +1,81 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { userActions } from '@server/db/schema'; +import { and, 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'; + +const removeUserActionParamsSchema = z.object({ + userId: z.string(), +}); + +const removeUserActionSchema = z.object({ + actionId: z.string(), + orgId: z.number().int().positive(), +}); + +export async function removeUserAction(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedParams = removeUserActionParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { userId } = parsedParams.data; + + const parsedBody = removeUserActionSchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedBody.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { actionId, orgId } = parsedBody.data; + + // Check if the user has permission to remove user actions + const hasPermission = await checkUserActionPermission(ActionsEnum.removeUserAction, req); + if (!hasPermission) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + } + + const deletedUserAction = await db.delete(userActions) + .where(and( + eq(userActions.userId, userId), + eq(userActions.actionId, actionId), + eq(userActions.orgId, orgId) + )) + .returning(); + + if (deletedUserAction.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Action with ID ${actionId} not found for user with ID ${userId} in organization ${orgId}` + ) + ); + } + + return response(res, { + data: null, + success: true, + error: false, + message: "Action removed from user successfully", + status: HttpCode.OK, + }); + } catch (error) { + logger.error(error); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + } +} \ No newline at end of file diff --git a/server/routers/user/deleteUser.ts b/server/routers/user/removeUserOrg.ts similarity index 63% rename from server/routers/user/deleteUser.ts rename to server/routers/user/removeUserOrg.ts index 4ffe3479..3c8eda5c 100644 --- a/server/routers/user/deleteUser.ts +++ b/server/routers/user/removeUserOrg.ts @@ -1,21 +1,22 @@ import { Request, Response, NextFunction } from 'express'; import { z } from 'zod'; import { db } from '@server/db'; -import { users } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; +import { userOrgs, users } from '@server/db/schema'; +import { and, 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'; -const deleteUserSchema = z.object({ - userId: z.string().uuid() +const removeUserSchema = z.object({ + userId: z.string().uuid(), + orgId: z.number().int().positive(), }); -export async function deleteUser(req: Request, res: Response, next: NextFunction): Promise { +export async function removeUserOrg(req: Request, res: Response, next: NextFunction): Promise { try { - const parsedParams = deleteUserSchema.safeParse(req.params); + const parsedParams = removeUserSchema.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( @@ -25,26 +26,17 @@ export async function deleteUser(req: Request, res: Response, next: NextFunction ); } - const { userId } = parsedParams.data; + const { userId, orgId } = parsedParams.data; // Check if the user has permission to list sites - const hasPermission = await checkUserActionPermission(ActionsEnum.deleteUser, req); + const hasPermission = await checkUserActionPermission(ActionsEnum.removeUser, req); if (!hasPermission) { return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); } - const deletedUser = await db.delete(users) - .where(eq(users.id, userId)) - .returning(); - - if (deletedUser.length === 0) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - `User with ID ${userId} not found` - ) - ); - } + // remove the user from the userOrgs table + await db.delete(userOrgs) + .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))); return response(res, { data: null, diff --git a/server/routers/user/removeUserResource.ts b/server/routers/user/removeUserResource.ts new file mode 100644 index 00000000..925d4ee1 --- /dev/null +++ b/server/routers/user/removeUserResource.ts @@ -0,0 +1,61 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { userResources } from '@server/db/schema'; +import { and, 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'; + +const removeUserResourceSchema = z.object({ + userId: z.string(), + resourceId: z.string(), +}); + +export async function removeUserResource(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedParams = removeUserResourceSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { userId, resourceId } = parsedParams.data; + + // Check if the user has permission to remove user resources + const hasPermission = await checkUserActionPermission(ActionsEnum.removeUserResource, req); + if (!hasPermission) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + } + + const deletedUserResource = await db.delete(userResources) + .where(and(eq(userResources.userId, userId), eq(userResources.resourceId, resourceId))) + .returning(); + + if (deletedUserResource.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource with ID ${resourceId} not found for user with ID ${userId}` + ) + ); + } + + return response(res, { + data: null, + success: true, + error: false, + message: "Resource removed from user successfully", + status: HttpCode.OK, + }); + } catch (error) { + logger.error(error); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + } +} \ No newline at end of file diff --git a/server/routers/user/removeUserSite.ts b/server/routers/user/removeUserSite.ts new file mode 100644 index 00000000..362c02a8 --- /dev/null +++ b/server/routers/user/removeUserSite.ts @@ -0,0 +1,86 @@ +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { resources, userResources, userSites } from '@server/db/schema'; +import { and, 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'; + +const removeUserSiteParamsSchema = z.object({ + userId: z.string(), +}); + +const removeUserSiteSchema = z.object({ + siteId: z.number().int().positive(), +}); + +export async function removeUserSite(req: Request, res: Response, next: NextFunction): Promise { + try { + const parsedParams = removeUserSiteParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { userId } = parsedParams.data; + + const parsedBody = removeUserSiteSchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedBody.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { siteId } = parsedBody.data; + + // Check if the user has permission to remove user sites + const hasPermission = await checkUserActionPermission(ActionsEnum.removeUserSite, req); + if (!hasPermission) { + return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); + } + + const deletedUserSite = await db.delete(userSites) + .where(and(eq(userSites.userId, userId), eq(userSites.siteId, siteId))) + .returning(); + + if (deletedUserSite.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site with ID ${siteId} not found for user with ID ${userId}` + ) + ); + } + + const siteResources = await db.select() + .from(resources) + .where(eq(resources.siteId, siteId)); + + for (const resource of siteResources) { + await db.delete(userResources) + .where(and(eq(userResources.userId, userId), eq(userResources.resourceId, resource.resourceId))) + .returning(); + } + + return response(res, { + data: null, + success: true, + error: false, + message: "Site removed from user successfully", + status: HttpCode.OK, + }); + } catch (error) { + logger.error(error); + return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); + } +} \ No newline at end of file