mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-08 13:04:50 +02:00
Update to verify middleware & lists agenst new permissions tables
This commit is contained in:
parent
0838679120
commit
20db6d450c
12 changed files with 275 additions and 128 deletions
53
server/auth/actions.ts
Normal file
53
server/auth/actions.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { db } from '@server/db';
|
||||||
|
import { userActions, roleActions, userOrgs } 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 checkUserActionPermission(actionId: number, req: Request): Promise<boolean> {
|
||||||
|
const userId = req.user?.id;
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if the user has direct permission for the action
|
||||||
|
const userActionPermission = await db.select()
|
||||||
|
.from(userActions)
|
||||||
|
.where(and(eq(userActions.userId, userId), eq(userActions.actionId, actionId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (userActionPermission.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no direct permission, check role-based permission
|
||||||
|
const userOrgRoles = await db.select()
|
||||||
|
.from(userOrgs)
|
||||||
|
.where(eq(userOrgs.userId, userId));
|
||||||
|
|
||||||
|
if (userOrgRoles.length === 0) {
|
||||||
|
return false; // User doesn't belong to any organization
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleIds = userOrgRoles.map(role => role.roleId);
|
||||||
|
|
||||||
|
const roleActionPermission = await db.select()
|
||||||
|
.from(roleActions)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(roleActions.actionId, actionId),
|
||||||
|
or(...roleIds.map(roleId => eq(roleActions.roleId, roleId)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return roleActionPermission.length > 0;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking user action permission:', error);
|
||||||
|
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error checking action permission');
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,7 +107,7 @@ export const userOrgs = sqliteTable("userOrgs", {
|
||||||
orgId: integer("orgId")
|
orgId: integer("orgId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => orgs.orgId),
|
.references(() => orgs.orgId),
|
||||||
role: text("role").notNull(), // e.g., 'admin', 'member', etc.
|
roleId: integer("roleId").notNull().references(() => roles.roleId),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const emailVerificationCodes = sqliteTable("emailVerificationCodes", {
|
export const emailVerificationCodes = sqliteTable("emailVerificationCodes", {
|
||||||
|
@ -149,6 +149,9 @@ export const roleActions = sqliteTable("roleActions", {
|
||||||
actionId: integer("actionId")
|
actionId: integer("actionId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => actions.actionId, { onDelete: "cascade" }),
|
.references(() => actions.actionId, { onDelete: "cascade" }),
|
||||||
|
orgId: integer("orgId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const userActions = sqliteTable("userActions", {
|
export const userActions = sqliteTable("userActions", {
|
||||||
|
@ -158,10 +161,13 @@ export const userActions = sqliteTable("userActions", {
|
||||||
actionId: integer("actionId")
|
actionId: integer("actionId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => actions.actionId, { onDelete: "cascade" }),
|
.references(() => actions.actionId, { onDelete: "cascade" }),
|
||||||
|
orgId: integer("orgId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const roleSites = sqliteTable("roleActions", {
|
export const roleSites = sqliteTable("roleSites", {
|
||||||
roleId: integer("role]Id")
|
roleId: integer("roleId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => roles.roleId, { onDelete: "cascade" }),
|
.references(() => roles.roleId, { onDelete: "cascade" }),
|
||||||
siteId: integer("siteId")
|
siteId: integer("siteId")
|
||||||
|
@ -169,8 +175,8 @@ export const roleSites = sqliteTable("roleActions", {
|
||||||
.references(() => sites.siteId, { onDelete: "cascade" }),
|
.references(() => sites.siteId, { onDelete: "cascade" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const userSites = sqliteTable("userActions", {
|
export const userSites = sqliteTable("userSites", {
|
||||||
userId: text("user]Id")
|
userId: text("userId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => users.id, { onDelete: "cascade" }),
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
siteId: integer("siteId")
|
siteId: integer("siteId")
|
||||||
|
@ -178,20 +184,20 @@ export const userSites = sqliteTable("userActions", {
|
||||||
.references(() => sites.siteId, { onDelete: "cascade" }),
|
.references(() => sites.siteId, { onDelete: "cascade" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const roleResources = sqliteTable("roleActions", {
|
export const roleResources = sqliteTable("roleResources", {
|
||||||
roleId: integer("role]Id")
|
roleId: integer("roleId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => roles.roleId, { onDelete: "cascade" }),
|
.references(() => roles.roleId, { onDelete: "cascade" }),
|
||||||
resourceId: integer("resourceId")
|
resourceId: text("resourceId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const userResources = sqliteTable("userActions", {
|
export const userResources = sqliteTable("userResources", {
|
||||||
userId: text("user]Id")
|
userId: text("userId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => users.id, { onDelete: "cascade" }),
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
resourceId: integer("resourceId")
|
resourceId: text("resourceId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -82,8 +82,8 @@ declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface Request {
|
interface Request {
|
||||||
user?: User;
|
user?: User;
|
||||||
userOrgRole?: string;
|
userOrgRoleId?: number;
|
||||||
userOrgs?: number[];
|
userOrgId?: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ export async function getUserOrgs(req: Request, res: Response, next: NextFunctio
|
||||||
.where(eq(userOrgs.userId, userId));
|
.where(eq(userOrgs.userId, userId));
|
||||||
|
|
||||||
req.userOrgs = userOrganizations.map(org => org.orgId);
|
req.userOrgs = userOrganizations.map(org => org.orgId);
|
||||||
// req.userOrgRoles = userOrganizations.reduce((acc, org) => {
|
// req.userOrgRoleIds = userOrganizations.reduce((acc, org) => {
|
||||||
// acc[org.orgId] = org.role;
|
// acc[org.orgId] = org.role;
|
||||||
// return acc;
|
// return acc;
|
||||||
// }, {} as Record<number, string>);
|
// }, {} as Record<number, string>);
|
||||||
|
|
|
@ -26,7 +26,8 @@ export function verifyOrgAccess(req: Request, res: Response, next: NextFunction)
|
||||||
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
||||||
} else {
|
} else {
|
||||||
// User has access, attach the user's role to the request for potential future use
|
// User has access, attach the user's role to the request for potential future use
|
||||||
req.userOrgRole = result[0].role;
|
req.userOrgRoleId = result[0].roleId;
|
||||||
|
req.userOrgId = orgId;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { db } from '@server/db';
|
import { db } from '@server/db';
|
||||||
import { resources, userOrgs } from '@server/db/schema';
|
import { resources, userOrgs, userResources, roleResources } from '@server/db/schema';
|
||||||
import { and, eq } from 'drizzle-orm';
|
import { and, eq } from 'drizzle-orm';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
@ -13,42 +13,66 @@ export async function verifyResourceAccess(req: Request, res: Response, next: Ne
|
||||||
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const resource = await db.select()
|
try {
|
||||||
.from(resources)
|
// Get the resource
|
||||||
.where(eq(resources.resourceId, resourceId))
|
const resource = await db.select()
|
||||||
.limit(1);
|
.from(resources)
|
||||||
|
.where(eq(resources.resourceId, resourceId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
if (resource.length === 0) {
|
if (resource.length === 0) {
|
||||||
return next(
|
return next(createHttpError(HttpCode.NOT_FOUND, `Resource with ID ${resourceId} not found`));
|
||||||
createHttpError(
|
}
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`resource with ID ${resourceId} not found`
|
if (!resource[0].orgId) {
|
||||||
|
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Resource with ID ${resourceId} does not have an organization ID`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's role ID in the organization
|
||||||
|
const userOrgRole = await db.select()
|
||||||
|
.from(userOrgs)
|
||||||
|
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (userOrgRole.length === 0) {
|
||||||
|
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const userOrgRoleId = userOrgRole[0].roleId;
|
||||||
|
req.userOrgRoleId = userOrgRoleId;
|
||||||
|
req.userOrgId = resource[0].orgId;
|
||||||
|
|
||||||
|
// Check role-based resource access first
|
||||||
|
const roleResourceAccess = await db.select()
|
||||||
|
.from(roleResources)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(roleResources.resourceId, resourceId),
|
||||||
|
eq(roleResources.roleId, userOrgRoleId)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
.limit(1);
|
||||||
}
|
|
||||||
|
|
||||||
if (!resource[0].orgId) {
|
if (roleResourceAccess.length > 0) {
|
||||||
return next(
|
// User's role has access to the resource
|
||||||
createHttpError(
|
return next();
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
}
|
||||||
`resource with ID ${resourceId} does not have an organization ID`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.select()
|
// If role doesn't have access, check user-specific resource access
|
||||||
.from(userOrgs)
|
const userResourceAccess = await db.select()
|
||||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
|
.from(userResources)
|
||||||
.then((result) => {
|
.where(and(eq(userResources.userId, userId), eq(userResources.resourceId, resourceId)))
|
||||||
if (result.length === 0) {
|
.limit(1);
|
||||||
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
|
||||||
} else {
|
if (userResourceAccess.length > 0) {
|
||||||
// User has access, attach the user's role to the request for potential future use
|
// User has direct access to the resource
|
||||||
req.userOrgRole = result[0].role;
|
return next();
|
||||||
next();
|
}
|
||||||
}
|
|
||||||
})
|
// If we reach here, the user doesn't have access to the resource
|
||||||
.catch((error) => {
|
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this resource'));
|
||||||
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
|
|
||||||
});
|
} catch (error) {
|
||||||
|
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying resource access'));
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { db } from '@server/db';
|
import { db } from '@server/db';
|
||||||
import { sites, userOrgs } from '@server/db/schema';
|
import { sites, userOrgs, userSites, roleSites, roles } from '@server/db/schema';
|
||||||
import { and, eq } from 'drizzle-orm';
|
import { and, eq, or } from 'drizzle-orm';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
|
||||||
|
@ -14,45 +14,66 @@ export async function verifySiteAccess(req: Request, res: Response, next: NextFu
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(siteId)) {
|
if (isNaN(siteId)) {
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
|
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid site ID'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const site = await db.select()
|
try {
|
||||||
.from(sites)
|
// Get the site
|
||||||
.where(eq(sites.siteId, siteId))
|
const site = await db.select().from(sites).where(eq(sites.siteId, siteId)).limit(1);
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (site.length === 0) {
|
if (site.length === 0) {
|
||||||
return next(
|
return next(createHttpError(HttpCode.NOT_FOUND, `Site with ID ${siteId} not found`));
|
||||||
createHttpError(
|
}
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Site with ID ${siteId} not found`
|
if (!site[0].orgId) {
|
||||||
|
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Site with ID ${siteId} does not have an organization ID`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's role ID in the organization
|
||||||
|
const userOrgRole = await db.select()
|
||||||
|
.from(userOrgs)
|
||||||
|
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (userOrgRole.length === 0) {
|
||||||
|
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const userOrgRoleId = userOrgRole[0].roleId;
|
||||||
|
req.userOrgRoleId = userOrgRoleId;
|
||||||
|
req.userOrgId = site[0].orgId;
|
||||||
|
|
||||||
|
// Check role-based site access first
|
||||||
|
const roleSiteAccess = await db.select()
|
||||||
|
.from(roleSites)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(roleSites.siteId, siteId),
|
||||||
|
eq(roleSites.roleId, userOrgRoleId)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
.limit(1);
|
||||||
}
|
|
||||||
|
|
||||||
if (!site[0].orgId) {
|
if (roleSiteAccess.length > 0) {
|
||||||
return next(
|
// User's role has access to the site
|
||||||
createHttpError(
|
return next();
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
}
|
||||||
`Site with ID ${siteId} does not have an organization ID`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.select()
|
// If role doesn't have access, check user-specific site access
|
||||||
.from(userOrgs)
|
const userSiteAccess = await db.select()
|
||||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId)))
|
.from(userSites)
|
||||||
.then((result) => {
|
.where(and(eq(userSites.userId, userId), eq(userSites.siteId, siteId)))
|
||||||
if (result.length === 0) {
|
.limit(1);
|
||||||
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
|
||||||
} else {
|
if (userSiteAccess.length > 0) {
|
||||||
// User has access, attach the user's role to the request for potential future use
|
// User has direct access to the site
|
||||||
req.userOrgRole = result[0].role;
|
return next();
|
||||||
next();
|
}
|
||||||
}
|
|
||||||
})
|
// If we reach here, the user doesn't have access to the site
|
||||||
.catch((error) => {
|
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this site'));
|
||||||
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
|
|
||||||
});
|
} catch (error) {
|
||||||
|
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying site access'));
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -73,7 +73,8 @@ export async function verifyTargetAccess(req: Request, res: Response, next: Next
|
||||||
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
||||||
} else {
|
} else {
|
||||||
// User has access, attach the user's role to the request for potential future use
|
// User has access, attach the user's role to the request for potential future use
|
||||||
req.userOrgRole = result[0].role;
|
req.userOrgRoleId = result[0].roleId;
|
||||||
|
req.userOrgId = resource[0].orgId!;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -62,7 +62,7 @@ export async function listOrgs(req: Request, res: Response, next: NextFunction):
|
||||||
// // Add the user's role for each organization
|
// // Add the user's role for each organization
|
||||||
// const organizationsWithRoles = organizations.map(org => ({
|
// const organizationsWithRoles = organizations.map(org => ({
|
||||||
// ...org,
|
// ...org,
|
||||||
// userRole: req.userOrgRoles[org.orgId],
|
// userRole: req.userOrgRoleIds[org.orgId],
|
||||||
// }));
|
// }));
|
||||||
|
|
||||||
return res.status(HttpCode.OK).send(
|
return res.status(HttpCode.OK).send(
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { db } from '@server/db';
|
import { db } from '@server/db';
|
||||||
import { resources, sites } from '@server/db/schema';
|
import { resources, sites, userResources, roleResources } from '@server/db/schema';
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql, eq } from 'drizzle-orm';
|
import { sql, eq, and, or, inArray } from 'drizzle-orm';
|
||||||
|
|
||||||
const listResourcesParamsSchema = z.object({
|
const listResourcesParamsSchema = z.object({
|
||||||
siteId: z.coerce.number().int().positive().optional(),
|
siteId: z.coerce.number().int().positive().optional(),
|
||||||
|
@ -19,30 +19,50 @@ const listResourcesSchema = z.object({
|
||||||
offset: z.coerce.number().int().nonnegative().default(0),
|
offset: z.coerce.number().int().nonnegative().default(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listResources(req: Request, res: Response, next: NextFunction): Promise<any> {
|
interface RequestWithOrgAndRole extends Request {
|
||||||
|
userOrgRoleId?: number;
|
||||||
|
orgId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listResources(req: RequestWithOrgAndRole, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
// Check if the user has permission to list resources
|
||||||
|
// const LIST_RESOURCES_ACTION_ID = 3; // Assume 3 is the action ID for listing resources
|
||||||
|
// const hasPermission = await checkUserActionPermission(LIST_RESOURCES_ACTION_ID, req);
|
||||||
|
// if (!hasPermission) {
|
||||||
|
// return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list resources'));
|
||||||
|
// }
|
||||||
|
|
||||||
const parsedQuery = listResourcesSchema.safeParse(req.query);
|
const parsedQuery = listResourcesSchema.safeParse(req.query);
|
||||||
if (!parsedQuery.success) {
|
if (!parsedQuery.success) {
|
||||||
return next(
|
return next(createHttpError(HttpCode.BAD_REQUEST, parsedQuery.error.errors.map(e => e.message).join(', ')));
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
parsedQuery.error.errors.map(e => e.message).join(', ')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const { limit, offset } = parsedQuery.data;
|
const { limit, offset } = parsedQuery.data;
|
||||||
|
|
||||||
const parsedParams = listResourcesParamsSchema.safeParse(req.params);
|
const parsedParams = listResourcesParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(createHttpError(HttpCode.BAD_REQUEST, parsedParams.error.errors.map(e => e.message).join(', ')));
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const { siteId, orgId } = parsedParams.data;
|
const { siteId, orgId } = parsedParams.data;
|
||||||
|
|
||||||
|
if (orgId && orgId !== req.orgId) {
|
||||||
|
return next(createHttpError(HttpCode.FORBIDDEN, '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<string>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})` })
|
||||||
|
.from(userResources)
|
||||||
|
.fullJoin(roleResources, eq(userResources.resourceId, roleResources.resourceId))
|
||||||
|
.where(
|
||||||
|
or(
|
||||||
|
eq(userResources.userId, req.user!.id),
|
||||||
|
eq(roleResources.roleId, req.userOrgRoleId!)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const accessibleResourceIds = accessibleResources.map(resource => resource.resourceId);
|
||||||
|
|
||||||
let baseQuery: any = db
|
let baseQuery: any = db
|
||||||
.select({
|
.select({
|
||||||
resourceId: resources.resourceId,
|
resourceId: resources.resourceId,
|
||||||
|
@ -51,16 +71,21 @@ export async function listResources(req: Request, res: Response, next: NextFunct
|
||||||
siteName: sites.name,
|
siteName: sites.name,
|
||||||
})
|
})
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.leftJoin(sites, eq(resources.siteId, sites.siteId));
|
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
||||||
|
.where(inArray(resources.resourceId, accessibleResourceIds));
|
||||||
|
|
||||||
let countQuery: any = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(resources);
|
let countQuery: any = db
|
||||||
|
.select({ count: sql<number>`cast(count(*) as integer)` })
|
||||||
|
.from(resources)
|
||||||
|
.where(inArray(resources.resourceId, accessibleResourceIds));
|
||||||
|
|
||||||
if (siteId) {
|
if (siteId) {
|
||||||
baseQuery = baseQuery.where(eq(resources.siteId, siteId));
|
baseQuery = baseQuery.where(eq(resources.siteId, siteId));
|
||||||
countQuery = countQuery.where(eq(resources.siteId, siteId));
|
countQuery = countQuery.where(eq(resources.siteId, siteId));
|
||||||
} else if (orgId) {
|
} else {
|
||||||
baseQuery = baseQuery.where(eq(resources.orgId, orgId));
|
// If orgId is provided, it's already checked to match req.orgId
|
||||||
countQuery = countQuery.where(eq(resources.orgId, orgId));
|
baseQuery = baseQuery.where(eq(resources.orgId, req.orgId!));
|
||||||
|
countQuery = countQuery.where(eq(resources.orgId, req.orgId!));
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourcesList = await baseQuery.limit(limit).offset(offset);
|
const resourcesList = await baseQuery.limit(limit).offset(offset);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { db } from '@server/db';
|
import { db } from '@server/db';
|
||||||
import { sites, orgs, exitNodes } from '@server/db/schema';
|
import { sites, orgs, exitNodes, userSites, roleSites } from '@server/db/schema';
|
||||||
import response from "@server/utils/response";
|
import response from "@server/utils/response";
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql, eq } from 'drizzle-orm';
|
import { sql, eq, and, or, inArray } from 'drizzle-orm';
|
||||||
|
// import { checkUserActionPermission } from './checkUserActionPermission'; // Import the function we created earlier
|
||||||
|
|
||||||
const listSitesParamsSchema = z.object({
|
const listSitesParamsSchema = z.object({
|
||||||
orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
|
orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
|
||||||
|
@ -18,29 +19,41 @@ const listSitesSchema = z.object({
|
||||||
|
|
||||||
export async function listSites(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function listSites(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
// Check if the user has permission to list sites
|
||||||
|
// const LIST_SITES_ACTION_ID = 1; // Assume 1 is the action ID for listing sites
|
||||||
|
// const hasPermission = await checkUserActionPermission(LIST_SITES_ACTION_ID, req);
|
||||||
|
// if (!hasPermission) {
|
||||||
|
// return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
|
||||||
|
// }
|
||||||
|
|
||||||
const parsedQuery = listSitesSchema.safeParse(req.query);
|
const parsedQuery = listSitesSchema.safeParse(req.query);
|
||||||
if (!parsedQuery.success) {
|
if (!parsedQuery.success) {
|
||||||
return next(
|
return next(createHttpError(HttpCode.BAD_REQUEST, parsedQuery.error.errors.map(e => e.message).join(', ')));
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
parsedQuery.error.errors.map(e => e.message).join(', ')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { limit, offset } = parsedQuery.data;
|
const { limit, offset } = parsedQuery.data;
|
||||||
|
|
||||||
const parsedParams = listSitesParamsSchema.safeParse(req.params);
|
const parsedParams = listSitesParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(createHttpError(HttpCode.BAD_REQUEST, parsedParams.error.errors.map(e => e.message).join(', ')));
|
||||||
createHttpError(
|
}
|
||||||
HttpCode.BAD_REQUEST,
|
const { orgId } = parsedParams.data;
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
|
||||||
)
|
if (orgId && orgId !== req.userOrgId) {
|
||||||
);
|
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
const accessibleSites = await db
|
||||||
|
.select({ siteId: sql<number>`COALESCE(${userSites.siteId}, ${roleSites.siteId})` })
|
||||||
|
.from(userSites)
|
||||||
|
.fullJoin(roleSites, eq(userSites.siteId, roleSites.siteId))
|
||||||
|
.where(
|
||||||
|
or(
|
||||||
|
eq(userSites.userId, req.user!.id),
|
||||||
|
eq(roleSites.roleId, req.userOrgRoleId!)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const accessibleSiteIds = accessibleSites.map(site => site.siteId);
|
||||||
|
|
||||||
let baseQuery: any = db
|
let baseQuery: any = db
|
||||||
.select({
|
.select({
|
||||||
|
@ -56,9 +69,12 @@ export async function listSites(req: Request, res: Response, next: NextFunction)
|
||||||
})
|
})
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
|
||||||
.leftJoin(exitNodes, eq(sites.exitNode, exitNodes.exitNodeId));
|
.where(inArray(sites.siteId, accessibleSiteIds));
|
||||||
|
|
||||||
let countQuery: any = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(sites);
|
let countQuery: any = db
|
||||||
|
.select({ count: sql<number>`cast(count(*) as integer)` })
|
||||||
|
.from(sites)
|
||||||
|
.where(inArray(sites.siteId, accessibleSiteIds));
|
||||||
|
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
baseQuery = baseQuery.where(eq(sites.orgId, orgId));
|
baseQuery = baseQuery.where(eq(sites.orgId, orgId));
|
||||||
|
|
|
@ -5,5 +5,5 @@ import { Session } from "lucia";
|
||||||
export interface AuthenticatedRequest extends Request {
|
export interface AuthenticatedRequest extends Request {
|
||||||
user: User;
|
user: User;
|
||||||
session: Session;
|
session: Session;
|
||||||
userOrgRole?: string;
|
userOrgRoleId?: number;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue