rename super user to admin and middleware refactoring

This commit is contained in:
Milo Schwartz 2024-11-05 22:38:57 -05:00
parent 7b755a273c
commit 03051878ef
No known key found for this signature in database
26 changed files with 790 additions and 529 deletions

View file

@ -1,159 +1,130 @@
// import { // import { orgs, sites, resources, exitNodes, targets } from "@server/db/schema";
// orgs,
// sites,
// resources,
// exitNodes,
// routes,
// targets,
// } from "@server/db/schema";
// import db from "@server/db"; // import db from "@server/db";
// import { createSuperUserRole } from "@server/db/ensureActions"; // import { crateAdminRole } from "@server/db/ensureActions";
async function insertDummyData() { // async function insertDummyData() {
// // Insert dummy orgs // const org1 = db
// const org1 = db // .insert(orgs)
// .insert(orgs) // .values({
// .values({ // orgId: "fossorial",
// orgId: "default", // name: "Fossorial",
// name: "Default", // domain: "fossorial.io",
// domain: "fosrl.io", // })
// }) // .returning()
// .returning() // .get();
// .get();
// await createSuperUserRole(org1.orgId!); // await crateAdminRole(org1.orgId!);
// const org2 = db // // Insert dummy exit nodes
// .insert(orgs) // const exitNode1 = db
// .values({ // .insert(exitNodes)
// orgId: "fossorial", // .values({
// name: "Fossorial", // name: "Exit Node 1",
// domain: "fossorial.io", // address: "10.0.0.1/24",
// }) // publicKey: "sKQlCNErB2n+dV8eLp5Yw/avsjK/zkrxJE0n48hjb10=",
// .returning() // listenPort: 51820,
// .get(); // endpoint: "exitnode1.fossorial.io",
// })
// .returning()
// .get();
// await createSuperUserRole(org2.orgId!); // // Insert dummy sites
// const site1 = db
// .insert(sites)
// .values({
// orgId: org1.orgId,
// // Insert dummy exit nodes // exitNodeId: exitNode1.exitNodeId,
// const exitNode1 = db // name: "Main Site",
// .insert(exitNodes) // subdomain: "main",
// .values({ // pubKey: "Kn4eD0kvcTwjO//zqH/CtNVkMNdMiUkbqFxysEym2D8=",
// name: "Exit Node 1", // subnet: "10.0.0.16/28",
// address: "10.0.0.1/24", // })
// privateKey: "sKQlCNErB2n+dV8eLp5Yw/avsjK/zkrxJE0n48hjb10=", // .returning()
// listenPort: 51820, // .get();
// })
// .returning()
// .get();
// const exitNode2 = db // const site2 = db
// .insert(exitNodes) // .insert(sites)
// .values({ // .values({
// name: "Exit Node 2", // orgId: org2.orgId,
// address: "172.16.1.1/24", // exitNode: exitNode2.exitNodeId,
// privateKey: "ACaw+q5vHVm8Xb0jIgIkMzlkJiriC7cURuOiNbGsGHg=", // name: "Dev Site",
// listenPort: 51820, // subdomain: "dev",
// }) // pubKey: "V329Uf/vhnBwYxAuT/ZlMZuLokHy5tug/sGsLfIMK1w=",
// .returning() // subnet: "172.16.1.16/28",
// .get(); // })
// .returning()
// .get();
// // Insert dummy sites // // Insert dummy resources
// const site1 = db // const resource1 = db
// .insert(sites) // .insert(resources)
// .values({ // .values({
// orgId: org1.orgId, // resourceId: `web.${site1.subdomain}.${org1.domain}`,
// exitNode: exitNode1.exitNodeId, // siteId: site1.siteId,
// name: "Main Site", // orgId: site1.orgId,
// subdomain: "main", // name: "Web Server",
// pubKey: "Kn4eD0kvcTwjO//zqH/CtNVkMNdMiUkbqFxysEym2D8=", // subdomain: "web",
// subnet: "10.0.0.16/28", // })
// }) // .returning()
// .returning() // .get();
// .get();
// const site2 = db // const resource2 = db
// .insert(sites) // .insert(resources)
// .values({ // .values({
// orgId: org2.orgId, // resourceId: `web2.${site1.subdomain}.${org1.domain}`,
// exitNode: exitNode2.exitNodeId, // siteId: site1.siteId,
// name: "Dev Site", // orgId: site1.orgId,
// subdomain: "dev", // name: "Web Server 2",
// pubKey: "V329Uf/vhnBwYxAuT/ZlMZuLokHy5tug/sGsLfIMK1w=", // subdomain: "web2",
// subnet: "172.16.1.16/28", // })
// }) // .returning()
// .returning() // .get();
// .get();
// // Insert dummy resources // const resource3 = db
// const resource1 = db // .insert(resources)
// .insert(resources) // .values({
// .values({ // resourceId: `db.${site2.subdomain}.${org2.domain}`,
// resourceId: `web.${site1.subdomain}.${org1.domain}`, // siteId: site2.siteId,
// siteId: site1.siteId, // orgId: site2.orgId,
// orgId: site1.orgId, // name: "Database",
// name: "Web Server", // subdomain: "db",
// subdomain: "web", // })
// }) // .returning()
// .returning() // .get();
// .get();
// const resource2 = db // // Insert dummy routes
// .insert(resources) // await db.insert(routes).values([
// .values({ // { exitNodeId: exitNode1.exitNodeId, subnet: "10.0.0.0/24" },
// resourceId: `web2.${site1.subdomain}.${org1.domain}`, // { exitNodeId: exitNode2.exitNodeId, subnet: "172.16.1.1/24" },
// siteId: site1.siteId, // ]);
// orgId: site1.orgId,
// name: "Web Server 2",
// subdomain: "web2",
// })
// .returning()
// .get();
// const resource3 = db // // Insert dummy targets
// .insert(resources) // await db.insert(targets).values([
// .values({ // {
// resourceId: `db.${site2.subdomain}.${org2.domain}`, // resourceId: resource1.resourceId,
// siteId: site2.siteId, // ip: "10.0.0.16",
// orgId: site2.orgId, // method: "http",
// name: "Database", // port: 4200,
// subdomain: "db", // protocol: "TCP",
// }) // },
// .returning() // {
// .get(); // resourceId: resource2.resourceId,
// ip: "10.0.0.17",
// method: "https",
// port: 443,
// protocol: "TCP",
// },
// {
// resourceId: resource3.resourceId,
// ip: "172.16.1.16",
// method: "http",
// port: 80,
// protocol: "TCP",
// },
// ]);
// // Insert dummy routes // console.log("Dummy data inserted successfully");
// await db.insert(routes).values([ // }
// { exitNodeId: exitNode1.exitNodeId, subnet: "10.0.0.0/24" },
// { exitNodeId: exitNode2.exitNodeId, subnet: "172.16.1.1/24" },
// ]);
// // Insert dummy targets // insertDummyData().catch(console.error);
// await db.insert(targets).values([
// {
// resourceId: resource1.resourceId,
// ip: "10.0.0.16",
// method: "http",
// port: 4200,
// protocol: "TCP",
// },
// {
// resourceId: resource2.resourceId,
// ip: "10.0.0.17",
// method: "https",
// port: 443,
// protocol: "TCP",
// },
// {
// resourceId: resource3.resourceId,
// ip: "172.16.1.16",
// method: "http",
// port: 80,
// protocol: "TCP",
// },
// ]);
// console.log("Dummy data inserted successfully");
}
insertDummyData().catch(console.error);

View file

@ -7,15 +7,19 @@ import logger from "@server/logger";
export async function ensureActions() { export async function ensureActions() {
const actionIds = Object.values(ActionsEnum); const actionIds = Object.values(ActionsEnum);
const existingActions = await db.select().from(actions).execute(); const existingActions = await db.select().from(actions).execute();
const existingActionIds = existingActions.map(action => action.actionId); const existingActionIds = existingActions.map((action) => action.actionId);
const actionsToAdd = actionIds.filter(id => !existingActionIds.includes(id)); const actionsToAdd = actionIds.filter(
const actionsToRemove = existingActionIds.filter(id => !actionIds.includes(id as ActionsEnum)); (id) => !existingActionIds.includes(id)
);
const actionsToRemove = existingActionIds.filter(
(id) => !actionIds.includes(id as ActionsEnum)
);
const defaultRoles = await db const defaultRoles = await db
.select() .select()
.from(roles) .from(roles)
.where(eq(roles.isSuperUserRole, true)) .where(eq(roles.isAdmin, true))
.execute(); .execute();
// Add new actions // Add new actions
@ -24,29 +28,42 @@ export async function ensureActions() {
await db.insert(actions).values({ actionId }).execute(); await db.insert(actions).values({ actionId }).execute();
// Add new actions to the Default role // Add new actions to the Default role
if (defaultRoles.length != 0) { if (defaultRoles.length != 0) {
await db.insert(roleActions) await db
.values(defaultRoles.map(role => ({ roleId: role.roleId!, actionId, orgId: role.orgId! }))) .insert(roleActions)
.values(
defaultRoles.map((role) => ({
roleId: role.roleId!,
actionId,
orgId: role.orgId!,
}))
)
.execute(); .execute();
} }
} }
// Remove deprecated actions // Remove deprecated actions
if (actionsToRemove.length > 0) { if (actionsToRemove.length > 0) {
logger.debug(`Removing actions: ${actionsToRemove.join(', ')}`); logger.debug(`Removing actions: ${actionsToRemove.join(", ")}`);
await db.delete(actions).where(inArray(actions.actionId, actionsToRemove)).execute(); await db
await db.delete(roleActions).where(inArray(roleActions.actionId, actionsToRemove)).execute(); .delete(actions)
.where(inArray(actions.actionId, actionsToRemove))
.execute();
await db
.delete(roleActions)
.where(inArray(roleActions.actionId, actionsToRemove))
.execute();
} }
} }
export async function createSuperUserRole(orgId: string) { export async function createAdminRole(orgId: string) {
// Create the Default role if it doesn't exist // Create the Default role if it doesn't exist
const [insertedRole] = await db const [insertedRole] = await db
.insert(roles) .insert(roles)
.values({ .values({
orgId, orgId,
isSuperUserRole: true, isAdmin: true,
name: 'Super User', name: "Admin",
description: 'Super User role with all actions' description: "Admin role most permissions",
}) })
.returning({ roleId: roles.roleId }) .returning({ roleId: roles.roleId })
.execute(); .execute();
@ -56,12 +73,19 @@ export async function createSuperUserRole(orgId: string) {
const actionIds = await db.select().from(actions).execute(); const actionIds = await db.select().from(actions).execute();
if (actionIds.length === 0) { if (actionIds.length === 0) {
logger.info('No actions to assign to the Super User role'); logger.info("No actions to assign to the Admin role");
return; return;
} }
await db.insert(roleActions) await db
.values(actionIds.map(action => ({ roleId, actionId: action.actionId, orgId }))) .insert(roleActions)
.values(
actionIds.map((action) => ({
roleId,
actionId: action.actionId,
orgId,
}))
)
.execute(); .execute();
return roleId; return roleId;

View file

@ -99,6 +99,7 @@ export const userOrgs = sqliteTable("userOrgs", {
roleId: integer("roleId") roleId: integer("roleId")
.notNull() .notNull()
.references(() => roles.roleId), .references(() => roles.roleId),
isOwner: integer("isOwner", { mode: "boolean" }).notNull().default(false),
}); });
export const emailVerificationCodes = sqliteTable("emailVerificationCodes", { export const emailVerificationCodes = sqliteTable("emailVerificationCodes", {
@ -131,7 +132,7 @@ export const roles = sqliteTable("roles", {
orgId: text("orgId").references(() => orgs.orgId, { orgId: text("orgId").references(() => orgs.orgId, {
onDelete: "cascade", onDelete: "cascade",
}), }),
isSuperUserRole: integer("isSuperUserRole", { mode: "boolean" }), isAdmin: integer("isAdmin", { mode: "boolean" }),
name: text("name").notNull(), name: text("name").notNull(),
description: text("description"), description: text("description"),
}); });
@ -241,3 +242,4 @@ export type RoleResource = InferSelectModel<typeof roleResources>;
export type UserResource = InferSelectModel<typeof userResources>; export type UserResource = InferSelectModel<typeof userResources>;
export type Limit = InferSelectModel<typeof limitsTable>; export type Limit = InferSelectModel<typeof limitsTable>;
export type UserInvite = InferSelectModel<typeof userInvites>; export type UserInvite = InferSelectModel<typeof userInvites>;
export type UserOrg = InferSelectModel<typeof userOrgs>;

View file

@ -12,9 +12,9 @@ import {
} from "@server/middlewares"; } from "@server/middlewares";
import internal from "@server/routers/internal"; import internal from "@server/routers/internal";
import { authenticated, unauthenticated } from "@server/routers/external"; import { authenticated, unauthenticated } from "@server/routers/external";
import { router as wsRouter, handleWSUpgrade } from '@server/routers/ws'; import { router as wsRouter, handleWSUpgrade } from "@server/routers/ws";
import cookieParser from "cookie-parser"; import cookieParser from "cookie-parser";
import { User } from "@server/db/schema"; import { User, UserOrg } from "@server/db/schema";
import { ensureActions } from "./db/ensureActions"; import { ensureActions } from "./db/ensureActions";
import { logIncomingMiddleware } from "./middlewares/logIncoming"; import { logIncomingMiddleware } from "./middlewares/logIncoming";
@ -98,6 +98,7 @@ declare global {
namespace Express { namespace Express {
interface Request { interface Request {
user?: User; user?: User;
userOrg?: UserOrg;
userOrgRoleId?: number; userOrgRoleId?: number;
userOrgId?: string; userOrgId?: string;
userOrgIds?: string[]; userOrgIds?: string[];

View file

@ -11,7 +11,7 @@ export * from "./verifyResourceAccess";
export * from "./verifyTargetAccess"; export * from "./verifyTargetAccess";
export * from "./verifyRoleAccess"; export * from "./verifyRoleAccess";
export * from "./verifyUserAccess"; export * from "./verifyUserAccess";
export * from "./verifySuperUser"; export * from "./verifyAdmin";
export * from "./verifyEmail"; export * from "./verifyEmail";
export * from "./requestEmailVerificationCode"; export * from "./requestEmailVerificationCode";
export * from "./changePassword"; export * from "./changePassword";

View file

@ -0,0 +1,63 @@
import { Request, Response, NextFunction } from "express";
import { db } from "@server/db";
import { roles, userOrgs } from "@server/db/schema";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
export async function verifyAdmin(
req: Request,
res: Response,
next: NextFunction
) {
const userId = req.user?.userId;
const orgId = req.userOrgId;
let userOrg = req.userOrg;
if (!userId) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "User does not have orgId")
);
}
if (!userId) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
);
}
if (!userOrg) {
const userOrgRes = await db
.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId!)))
.limit(1);
userOrg = userOrgRes[0];
}
if (!userOrg) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"
)
);
}
const userRole = await db
.select()
.from(roles)
.where(eq(roles.roleId, userOrg.roleId))
.limit(1);
if (userRole.length === 0 || !userRole[0].isAdmin) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have Admin access"
)
);
}
return next();
}

View file

@ -4,15 +4,15 @@ import { userOrgs } 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";
import { AuthenticatedRequest } from "@server/types/Auth";
export function verifyOrgAccess( export async function verifyOrgAccess(
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ) {
const userId = req.user!.userId; // Assuming you have user information in the request const userId = req.user!.userId;
const orgId = req.params.orgId; const orgId = req.params.orgId;
let userOrg = req.userOrg;
if (!userId) { if (!userId) {
return next( return next(
@ -26,11 +26,18 @@ export function verifyOrgAccess(
); );
} }
db.select() try {
if (!userOrg) {
const userOrgRes = await db
.select()
.from(userOrgs) .from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))) .where(
.then((result) => { and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))
if (result.length === 0) { );
userOrg = userOrgRes[0];
}
if (!userOrg) {
next( next(
createHttpError( createHttpError(
HttpCode.FORBIDDEN, HttpCode.FORBIDDEN,
@ -39,17 +46,16 @@ export function verifyOrgAccess(
); );
} 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.userOrgRoleId = result[0].roleId; req.userOrgRoleId = userOrg.roleId;
req.userOrgId = orgId; req.userOrgId = orgId;
next(); return next();
} }
}) } catch (e) {
.catch((error) => { return next(
next(
createHttpError( createHttpError(
HttpCode.INTERNAL_SERVER_ERROR, HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying organization access" "Error verifying organization access"
) )
); );
}); }
} }

View file

@ -1,49 +1,85 @@
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, userResources, roleResources } from '@server/db/schema'; import {
import { and, eq } from 'drizzle-orm'; resources,
import createHttpError from 'http-errors'; userOrgs,
import HttpCode from '@server/types/HttpCode'; userResources,
roleResources,
} from "@server/db/schema";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
export async function verifyResourceAccess(req: Request, res: Response, next: NextFunction) { export async function verifyResourceAccess(
const userId = req.user!.userId; // Assuming you have user information in the request req: Request,
const resourceId = req.params.resourceId || req.body.resourceId || req.query.resourceId; res: Response,
next: NextFunction
) {
const userId = req.user!.userId;
const resourceId =
req.params.resourceId || req.body.resourceId || req.query.resourceId;
let userOrg = req.userOrg;
if (!userId) { if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
);
} }
try { try {
// Get the resource const resource = await db
const resource = await db.select() .select()
.from(resources) .from(resources)
.where(eq(resources.resourceId, resourceId)) .where(eq(resources.resourceId, resourceId))
.limit(1); .limit(1);
if (resource.length === 0) { if (resource.length === 0) {
return next(createHttpError(HttpCode.NOT_FOUND, `Resource with ID ${resourceId} not found`)); return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`
)
);
} }
if (!resource[0].orgId) { if (!resource[0].orgId) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Resource with ID ${resourceId} does not have an organization ID`)); 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 if (!userOrg) {
const userOrgRole = await db.select() const userOrgRole = await db
.select()
.from(userOrgs) .from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId))) .where(
and(
eq(userOrgs.userId, userId),
eq(userOrgs.orgId, resource[0].orgId)
)
)
.limit(1); .limit(1);
userOrg = userOrgRole[0];
if (userOrgRole.length === 0) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} }
const userOrgRoleId = userOrgRole[0].roleId; if (!userOrg) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"
)
);
}
const userOrgRoleId = userOrg.roleId;
req.userOrgRoleId = userOrgRoleId; req.userOrgRoleId = userOrgRoleId;
req.userOrgId = resource[0].orgId; req.userOrgId = resource[0].orgId;
// Check role-based resource access first const roleResourceAccess = await db
const roleResourceAccess = await db.select() .select()
.from(roleResources) .from(roleResources)
.where( .where(
and( and(
@ -54,25 +90,36 @@ export async function verifyResourceAccess(req: Request, res: Response, next: Ne
.limit(1); .limit(1);
if (roleResourceAccess.length > 0) { if (roleResourceAccess.length > 0) {
// User's role has access to the resource
return next(); return next();
} }
// If role doesn't have access, check user-specific resource access const userResourceAccess = await db
const userResourceAccess = await db.select() .select()
.from(userResources) .from(userResources)
.where(and(eq(userResources.userId, userId), eq(userResources.resourceId, resourceId))) .where(
and(
eq(userResources.userId, userId),
eq(userResources.resourceId, resourceId)
)
)
.limit(1); .limit(1);
if (userResourceAccess.length > 0) { if (userResourceAccess.length > 0) {
// User has direct access to the resource
return next(); return next();
} }
// If we reach here, the user doesn't have access to the resource return next(
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this resource')); createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this resource"
)
);
} catch (error) { } catch (error) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying resource access')); return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying resource access"
)
);
} }
} }

View file

@ -1,50 +1,82 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from "express";
import { db } from '@server/db'; import { db } from "@server/db";
import { roles, userOrgs } from '@server/db/schema'; import { roles, userOrgs } 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";
import logger from '@server/logger'; import logger from "@server/logger";
export async function verifyRoleAccess(req: Request, res: Response, next: NextFunction) { export async function verifyRoleAccess(
const userId = req.user?.userId; // Assuming you have user information in the request req: Request,
const roleId = parseInt(req.params.roleId || req.body.roleId || req.query.roleId); res: Response,
next: NextFunction
) {
const userId = req.user?.userId;
const roleId = parseInt(
req.params.roleId || req.body.roleId || req.query.roleId
);
let userOrg = req.userOrg;
if (!userId) { if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
);
} }
if (isNaN(roleId)) { if (isNaN(roleId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid role ID')); return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid role ID"));
} }
try { try {
// Check if the role exists and belongs to the specified organization const role = await db
const role = await db.select() .select()
.from(roles) .from(roles)
.where(eq(roles.roleId, roleId)) .where(eq(roles.roleId, roleId))
.limit(1); .limit(1);
if (role.length === 0) { if (role.length === 0) {
return next(createHttpError(HttpCode.NOT_FOUND, `Role with ID ${roleId} not found`)); return next(
createHttpError(
HttpCode.NOT_FOUND,
`Role with ID ${roleId} not found`
)
);
} }
// Check if the user has a role in the organization if (!userOrg) {
const userOrgRole = await db.select() const userOrgRole = await db
.select()
.from(userOrgs) .from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, role[0].orgId!))) .where(
and(
eq(userOrgs.userId, userId),
eq(userOrgs.orgId, role[0].orgId!)
)
)
.limit(1); .limit(1);
userOrg = userOrgRole[0];
if (userOrgRole.length === 0) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} }
req.userOrgRoleId = userOrgRole[0].roleId; if (!userOrg) {
req.userOrgId = userOrgRole[0].orgId; return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"
)
);
}
req.userOrgRoleId = userOrg.roleId;
req.userOrgId = userOrg.orgId;
return next(); return next();
} catch (error) { } catch (error) {
logger.error('Error verifying role access:', error); logger.error("Error verifying role access:", error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying role access')); return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying role access"
)
);
} }
} }

View file

@ -1,42 +1,81 @@
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, userSites, roleSites, roles } from '@server/db/schema'; import {
import { and, eq, or } from 'drizzle-orm'; sites,
import createHttpError from 'http-errors'; userOrgs,
import HttpCode from '@server/types/HttpCode'; 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 verifySiteAccess(req: Request, res: Response, next: NextFunction) { export async function verifySiteAccess(
req: Request,
res: Response,
next: NextFunction
) {
const userId = req.user!.userId; // Assuming you have user information in the request const userId = req.user!.userId; // Assuming you have user information in the request
const siteId = parseInt(req.params.siteId || req.body.siteId || req.query.siteId); const siteId = parseInt(
req.params.siteId || req.body.siteId || req.query.siteId
);
if (!userId) { if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
);
} }
if (isNaN(siteId)) { if (isNaN(siteId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid site ID')); return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid site ID"));
} }
try { try {
// Get the site // Get the site
const site = await db.select().from(sites).where(eq(sites.siteId, siteId)).limit(1); const site = await db
.select()
.from(sites)
.where(eq(sites.siteId, siteId))
.limit(1);
if (site.length === 0) { if (site.length === 0) {
return next(createHttpError(HttpCode.NOT_FOUND, `Site with ID ${siteId} not found`)); return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
} }
if (!site[0].orgId) { if (!site[0].orgId) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Site with ID ${siteId} does not have an organization ID`)); 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 // Get user's role ID in the organization
const userOrgRole = await db.select() const userOrgRole = await db
.select()
.from(userOrgs) .from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId))) .where(
and(
eq(userOrgs.userId, userId),
eq(userOrgs.orgId, site[0].orgId)
)
)
.limit(1); .limit(1);
if (userOrgRole.length === 0) { if (userOrgRole.length === 0) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"
)
);
} }
const userOrgRoleId = userOrgRole[0].roleId; const userOrgRoleId = userOrgRole[0].roleId;
@ -44,7 +83,8 @@ export async function verifySiteAccess(req: Request, res: Response, next: NextFu
req.userOrgId = site[0].orgId; req.userOrgId = site[0].orgId;
// Check role-based site access first // Check role-based site access first
const roleSiteAccess = await db.select() const roleSiteAccess = await db
.select()
.from(roleSites) .from(roleSites)
.where( .where(
and( and(
@ -60,9 +100,12 @@ export async function verifySiteAccess(req: Request, res: Response, next: NextFu
} }
// If role doesn't have access, check user-specific site access // If role doesn't have access, check user-specific site access
const userSiteAccess = await db.select() const userSiteAccess = await db
.select()
.from(userSites) .from(userSites)
.where(and(eq(userSites.userId, userId), eq(userSites.siteId, siteId))) .where(
and(eq(userSites.userId, userId), eq(userSites.siteId, siteId))
)
.limit(1); .limit(1);
if (userSiteAccess.length > 0) { if (userSiteAccess.length > 0) {
@ -71,9 +114,18 @@ export async function verifySiteAccess(req: Request, res: Response, next: NextFu
} }
// If we reach here, the user doesn't have access to the site // 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')); return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this site"
)
);
} catch (error) { } catch (error) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying site access')); return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying site access"
)
);
} }
} }

View file

@ -1,48 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import { db } from '@server/db';
import { roles, userOrgs } from '@server/db/schema';
import { and, eq } from 'drizzle-orm';
import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
import logger from '@server/logger';
export async function verifySuperUser(req: Request, res: Response, next: NextFunction) {
const userId = req.user?.userId; // Assuming you have user information in the request
const orgId = req.userOrgId;
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User does not have orgId'));
}
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
try {
// Check if the user has a role in the organization
const userOrgRole = await db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId!)))
.limit(1);
if (userOrgRole.length === 0) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
}
// get userOrgRole[0].roleId
// Check if the user's role in the organization is a Super User role
const userRole = await db.select()
.from(roles)
.where(eq(roles.roleId, userOrgRole[0].roleId))
.limit(1);
if (userRole.length === 0 || !userRole[0].isSuperUserRole) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have Super User access'));
}
return next();
} catch (error) {
logger.error('Error verifying role access:', error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying role access'));
}
}

View file

@ -1,23 +1,33 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from "express";
import { db } from '@server/db'; import { db } from "@server/db";
import { resources, targets, userOrgs } from '@server/db/schema'; import { resources, targets, userOrgs } 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";
export async function verifyTargetAccess(req: Request, res: Response, next: NextFunction) { export async function verifyTargetAccess(
const userId = req.user!.userId; // Assuming you have user information in the request req: Request,
res: Response,
next: NextFunction
) {
const userId = req.user!.userId;
const targetId = parseInt(req.params.targetId); const targetId = parseInt(req.params.targetId);
let userOrg = req.userOrg;
if (!userId) { if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
);
} }
if (isNaN(targetId)) { if (isNaN(targetId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID')); return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
);
} }
const target = await db.select() const target = await db
.select()
.from(targets) .from(targets)
.where(eq(targets.targetId, targetId)) .where(eq(targets.targetId, targetId))
.limit(1); .limit(1);
@ -42,7 +52,9 @@ export async function verifyTargetAccess(req: Request, res: Response, next: Next
); );
} }
const resource = await db.select() try {
const resource = await db
.select()
.from(resources) .from(resources)
.where(eq(resources.resourceId, resourceId!)) .where(eq(resources.resourceId, resourceId!))
.limit(1); .limit(1);
@ -51,7 +63,7 @@ export async function verifyTargetAccess(req: Request, res: Response, next: Next
return next( return next(
createHttpError( createHttpError(
HttpCode.NOT_FOUND, HttpCode.NOT_FOUND,
`resource with ID ${resourceId} not found` `Resource with ID ${resourceId} not found`
) )
); );
} }
@ -65,20 +77,37 @@ export async function verifyTargetAccess(req: Request, res: Response, next: Next
); );
} }
db.select() if (!userOrg) {
const res = await db
.select()
.from(userOrgs) .from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId))) .where(
.then((result) => { and(
if (result.length === 0) { eq(userOrgs.userId, userId),
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); eq(userOrgs.orgId, resource[0].orgId)
)
);
userOrg = res[0];
}
if (!userOrg) {
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 req.userOrgRoleId = userOrg.roleId;
req.userOrgRoleId = result[0].roleId;
req.userOrgId = resource[0].orgId!; req.userOrgId = resource[0].orgId!;
next(); next();
} }
}) } catch (e) {
.catch((error) => { return next(
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access')); createHttpError(
}); HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying organization access"
)
);
}
} }

View file

@ -25,7 +25,7 @@ export type VerifyTotpResponse = {
export async function verifyTotp( export async function verifyTotp(
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction, next: NextFunction
): Promise<any> { ): Promise<any> {
const parsedBody = verifyTotpBody.safeParse(req.body); const parsedBody = verifyTotpBody.safeParse(req.body);
@ -33,8 +33,8 @@ export async function verifyTotp(
return next( return next(
createHttpError( createHttpError(
HttpCode.BAD_REQUEST, HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString(), fromError(parsedBody.error).toString()
), )
); );
} }
@ -46,8 +46,8 @@ export async function verifyTotp(
return next( return next(
createHttpError( createHttpError(
HttpCode.BAD_REQUEST, HttpCode.BAD_REQUEST,
"Two-factor authentication is already enabled", "Two-factor authentication is already enabled"
), )
); );
} }
@ -55,13 +55,17 @@ export async function verifyTotp(
return next( return next(
createHttpError( createHttpError(
HttpCode.BAD_REQUEST, HttpCode.BAD_REQUEST,
"User has not requested two-factor authentication", "User has not requested two-factor authentication"
), )
); );
} }
try { try {
const valid = await verifyTotpCode(code, user.twoFactorSecret, user.userId); const valid = await verifyTotpCode(
code,
user.twoFactorSecret,
user.userId
);
let codes; let codes;
if (valid) { if (valid) {
@ -101,8 +105,8 @@ export async function verifyTotp(
return next( return next(
createHttpError( createHttpError(
HttpCode.INTERNAL_SERVER_ERROR, HttpCode.INTERNAL_SERVER_ERROR,
"Failed to verify two-factor authentication code", "Failed to verify two-factor authentication code"
), )
); );
} }
} }

View file

@ -1,12 +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 { import { userOrgs } from "@server/db/schema";
sites,
userOrgs,
userSites,
roleSites,
roles,
} from "@server/db/schema";
import { and, eq, or } 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";
@ -16,9 +10,11 @@ export async function verifyUserAccess(
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ) {
const userId = req.user!.userId; // Assuming you have user information in the request const userId = req.user!.userId;
const reqUserId = req.params.userId || req.body.userId || req.query.userId; const reqUserId = req.params.userId || req.body.userId || req.query.userId;
let userOrg = req.userOrg;
if (!userId) { if (!userId) {
return next( return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
@ -30,7 +26,8 @@ export async function verifyUserAccess(
} }
try { try {
const userOrg = await db if (!userOrg) {
const res = await db
.select() .select()
.from(userOrgs) .from(userOrgs)
.where( .where(
@ -40,8 +37,10 @@ export async function verifyUserAccess(
) )
) )
.limit(1); .limit(1);
userOrg = res[0];
}
if (userOrg.length === 0) { if (userOrg) {
return next( return next(
createHttpError( createHttpError(
HttpCode.FORBIDDEN, HttpCode.FORBIDDEN,

View file

@ -1,31 +1,51 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from "express";
import { db } from '@server/db'; import createHttpError from "http-errors";
import { roles, userOrgs } from '@server/db/schema'; import HttpCode from "@server/types/HttpCode";
import { and, eq } from 'drizzle-orm'; import logger from "@server/logger";
import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
import logger from '@server/logger';
export async function verifyUserInRole(req: Request, res: Response, next: NextFunction) { export async function verifyUserInRole(
req: Request,
res: Response,
next: NextFunction
) {
try { try {
const roleId = parseInt(req.params.roleId || req.body.roleId || req.query.roleId); const roleId = parseInt(
req.params.roleId || req.body.roleId || req.query.roleId
);
const userRoleId = req.userOrgRoleId; const userRoleId = req.userOrgRoleId;
if (isNaN(roleId)) { if (isNaN(roleId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid role ID')); return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid role ID")
);
} }
if (!userRoleId) { if (!userRoleId) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"
)
);
} }
if (userRoleId !== roleId) { if (userRoleId !== roleId) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this role')); return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this role"
)
);
} }
return next(); return next();
} catch (error) { } catch (error) {
logger.error('Error verifying role access:', error); logger.error("Error verifying role access:", error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying role access')); return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying role access"
)
);
} }
} }

View file

@ -19,7 +19,7 @@ import {
verifyResourceAccess, verifyResourceAccess,
verifyTargetAccess, verifyTargetAccess,
verifyRoleAccess, verifyRoleAccess,
verifySuperUser, verifyAdmin,
verifyUserInRole, verifyUserInRole,
verifyUserAccess, verifyUserAccess,
} from "./auth"; } from "./auth";
@ -121,7 +121,7 @@ authenticated.delete(
// authenticated.put( // authenticated.put(
// "/org/:orgId/role", // "/org/:orgId/role",
// verifyOrgAccess, // verifyOrgAccess,
// verifySuperUser, // verifyAdmin,
// role.createRole // role.createRole
// ); // );
// authenticated.get("/org/:orgId/roles", verifyOrgAccess, role.listRoles); // authenticated.get("/org/:orgId/roles", verifyOrgAccess, role.listRoles);
@ -134,13 +134,13 @@ authenticated.delete(
// authenticated.post( // authenticated.post(
// "/role/:roleId", // "/role/:roleId",
// verifyRoleAccess, // verifyRoleAccess,
// verifySuperUser, // verifyAdmin,
// role.updateRole // role.updateRole
// ); // );
// authenticated.delete( // authenticated.delete(
// "/role/:roleId", // "/role/:roleId",
// verifyRoleAccess, // verifyRoleAccess,
// verifySuperUser, // verifyAdmin,
// role.deleteRole // role.deleteRole
// ); // );
@ -190,14 +190,14 @@ authenticated.delete(
// "/role/:roleId/action", // "/role/:roleId/action",
// verifyRoleAccess, // verifyRoleAccess,
// verifyUserInRole, // verifyUserInRole,
// verifySuperUser, // verifyAdmin,
// role.removeRoleAction // role.removeRoleAction
// ); // );
// authenticated.get( // authenticated.get(
// "/role/:roleId/actions", // "/role/:roleId/actions",
// verifyRoleAccess, // verifyRoleAccess,
// verifyUserInRole, // verifyUserInRole,
// verifySuperUser, // verifyAdmin,
// role.listRoleActions // role.listRoleActions
// ); // );
@ -239,14 +239,14 @@ authenticated.delete(
// "/org/:orgId/user/:userId/action", // "/org/:orgId/user/:userId/action",
// verifyOrgAccess, // verifyOrgAccess,
// verifyUserAccess, // verifyUserAccess,
// verifySuperUser, // verifyAdmin,
// role.addRoleAction // role.addRoleAction
// ); // );
// authenticated.delete( // authenticated.delete(
// "/org/:orgId/user/:userId/action", // "/org/:orgId/user/:userId/action",
// verifyOrgAccess, // verifyOrgAccess,
// verifyUserAccess, // verifyUserAccess,
// verifySuperUser, // verifyAdmin,
// role.removeRoleAction // role.removeRoleAction
// ); // );

View file

@ -1,16 +1,16 @@
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 { eq } from 'drizzle-orm'; import { eq } from "drizzle-orm";
import { orgs, userOrgs } from '@server/db/schema'; import { orgs, userOrgs } 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 { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from '@server/logger'; import logger from "@server/logger";
import { createSuperUserRole } from '@server/db/ensureActions'; import { createAdminRole } from "@server/db/ensureActions";
import config, { APP_PATH } from "@server/config"; import config, { APP_PATH } from "@server/config";
import { fromError } from 'zod-validation-error'; import { fromError } from "zod-validation-error";
const createOrgSchema = z.object({ const createOrgSchema = z.object({
orgId: z.string(), orgId: z.string(),
@ -20,7 +20,11 @@ const createOrgSchema = z.object({
const MAX_ORGS = 5; const MAX_ORGS = 5;
export async function createOrg(req: Request, res: Response, next: NextFunction): Promise<any> { export async function createOrg(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try { try {
const parsedBody = createOrgSchema.safeParse(req.body); const parsedBody = createOrgSchema.safeParse(req.body);
if (!parsedBody.success) { if (!parsedBody.success) {
@ -52,7 +56,8 @@ export async function createOrg(req: Request, res: Response, next: NextFunction)
const { orgId, name } = parsedBody.data; const { orgId, name } = parsedBody.data;
// make sure the orgId is unique // make sure the orgId is unique
const orgExists = await db.select() const orgExists = await db
.select()
.from(orgs) .from(orgs)
.where(eq(orgs.orgId, orgId)) .where(eq(orgs.orgId, orgId))
.limit(1); .limit(1);
@ -69,29 +74,34 @@ export async function createOrg(req: Request, res: Response, next: NextFunction)
// create a url from config.app.base_url and get the hostname // create a url from config.app.base_url and get the hostname
const domain = new URL(config.app.base_url).hostname; const domain = new URL(config.app.base_url).hostname;
const newOrg = await db.insert(orgs).values({ const newOrg = await db
.insert(orgs)
.values({
orgId, orgId,
name, name,
domain domain,
}).returning(); })
.returning();
const roleId = await createSuperUserRole(newOrg[0].orgId); const roleId = await createAdminRole(newOrg[0].orgId);
if (!roleId) { if (!roleId) {
return next( return next(
createHttpError( createHttpError(
HttpCode.INTERNAL_SERVER_ERROR, HttpCode.INTERNAL_SERVER_ERROR,
`Error creating Super User role` `Error creating Admin role`
) )
); );
} }
// put the user in the super user role await db
await db.insert(userOrgs).values({ .insert(userOrgs)
.values({
userId: req.user!.userId, userId: req.user!.userId,
orgId: newOrg[0].orgId, orgId: newOrg[0].orgId,
roleId: roleId, roleId: roleId,
}).execute(); })
.execute();
return response(res, { return response(res, {
data: newOrg[0], data: newOrg[0],
@ -102,6 +112,11 @@ export async function createOrg(req: Request, res: Response, next: NextFunction)
}); });
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
);
} }
} }

View file

@ -1,20 +1,24 @@
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 { orgs } from '@server/db/schema'; import { orgs, userActions } from "@server/db/schema";
import { eq } from 'drizzle-orm'; import { eq } from "drizzle-orm";
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 { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from '@server/logger'; import logger from "@server/logger";
import { fromError } from 'zod-validation-error'; import { fromError } from "zod-validation-error";
const deleteOrgSchema = z.object({ const deleteOrgSchema = z.object({
orgId: z.string() orgId: z.string(),
}); });
export async function deleteOrg(req: Request, res: Response, next: NextFunction): Promise<any> { export async function deleteOrg(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try { try {
const parsedParams = deleteOrgSchema.safeParse(req.params); const parsedParams = deleteOrgSchema.safeParse(req.params);
if (!parsedParams.success) { if (!parsedParams.success) {
@ -28,13 +32,22 @@ export async function deleteOrg(req: Request, res: Response, next: NextFunction)
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
// Check if the user has permission to list sites // // Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteOrg, req); // const hasPermission = await checkUserActionPermission(
if (!hasPermission) { // ActionsEnum.deleteOrg,
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); // req
} // );
// if (!hasPermission) {
// return next(
// createHttpError(
// HttpCode.FORBIDDEN,
// "User does not have permission to perform this action"
// )
// );
// }
const deletedOrg = await db.delete(orgs) const deletedOrg = await db
.delete(orgs)
.where(eq(orgs.orgId, orgId)) .where(eq(orgs.orgId, orgId))
.returning(); .returning();
@ -56,6 +69,11 @@ export async function deleteOrg(req: Request, res: Response, next: NextFunction)
}); });
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
);
} }
} }

View file

@ -1,19 +1,29 @@
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 { orgs, resources, roleResources, roles, userResources } from '@server/db/schema'; import {
orgs,
resources,
roleResources,
roles,
userResources,
} 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 { ActionsEnum, checkUserActionPermission } from '@server/auth/actions'; import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import logger from '@server/logger'; import logger from "@server/logger";
import { eq, and } from 'drizzle-orm'; import { eq, and } from "drizzle-orm";
import stoi from '@server/utils/stoi'; import stoi from "@server/utils/stoi";
import { fromError } from 'zod-validation-error'; import { fromError } from "zod-validation-error";
const createResourceParamsSchema = z.object({ const createResourceParamsSchema = z.object({
siteId: z.string().optional().transform(stoi).pipe(z.number().int().positive().optional()), siteId: z
orgId: z.string() .string()
.optional()
.transform(stoi)
.pipe(z.number().int().positive().optional()),
orgId: z.string(),
}); });
// Define Zod schema for request body validation // Define Zod schema for request body validation
@ -22,7 +32,11 @@ const createResourceSchema = z.object({
subdomain: z.string().min(1).max(255).optional(), subdomain: z.string().min(1).max(255).optional(),
}); });
export async function createResource(req: Request, res: Response, next: NextFunction): Promise<any> { export async function createResource(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try { try {
// Validate request body // Validate request body
const parsedBody = createResourceSchema.safeParse(req.body); const parsedBody = createResourceSchema.safeParse(req.body);
@ -51,17 +65,28 @@ export async function createResource(req: Request, res: Response, next: NextFunc
const { siteId, orgId } = parsedParams.data; const { siteId, orgId } = parsedParams.data;
// Check if the user has permission to list sites // Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createResource, req); const hasPermission = await checkUserActionPermission(
ActionsEnum.createResource,
req
);
if (!hasPermission) { if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to perform this action')); return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have permission to perform this action"
)
);
} }
if (!req.userOrgRoleId) { if (!req.userOrgRoleId) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have a role')); return next(
createHttpError(HttpCode.FORBIDDEN, "User does not have a role")
);
} }
// get the org // get the org
const org = await db.select() const org = await db
.select()
.from(orgs) .from(orgs)
.where(eq(orgs.orgId, orgId)) .where(eq(orgs.orgId, orgId))
.limit(1); .limit(1);
@ -79,35 +104,35 @@ export async function createResource(req: Request, res: Response, next: NextFunc
const fullDomain = `${subdomain}.${org[0].domain}`; const fullDomain = `${subdomain}.${org[0].domain}`;
// Create new resource in the database // Create new resource in the database
const newResource = await db.insert(resources).values({ const newResource = await db
.insert(resources)
.values({
fullDomain, fullDomain,
siteId, siteId,
orgId, orgId,
name, name,
subdomain, subdomain,
}).returning(); })
.returning();
// find the Super User roleId and also add the resource to the Super User role const adminRole = await db
const superUserRole = await db.select() .select()
.from(roles) .from(roles)
.where(and(eq(roles.isSuperUserRole, true), eq(roles.orgId, orgId))) .where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1); .limit(1);
if (superUserRole.length === 0) { if (adminRole.length === 0) {
return next( return next(
createHttpError( createHttpError(HttpCode.NOT_FOUND, `Admin role not found`)
HttpCode.NOT_FOUND,
`Super User role not found`
)
); );
} }
await db.insert(roleResources).values({ await db.insert(roleResources).values({
roleId: superUserRole[0].roleId, roleId: adminRole[0].roleId,
resourceId: newResource[0].resourceId, resourceId: newResource[0].resourceId,
}); });
if (req.userOrgRoleId != superUserRole[0].roleId) { if (req.userOrgRoleId != adminRole[0].roleId) {
// make sure the user can access the resource // make sure the user can access the resource
await db.insert(userResources).values({ await db.insert(userResources).values({
userId: req.user?.userId!, userId: req.user?.userId!,
@ -124,6 +149,11 @@ export async function createResource(req: Request, res: Response, next: NextFunc
}); });
} catch (error) { } catch (error) {
throw error; throw error;
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
);
} }
} }

View file

@ -51,7 +51,7 @@ export async function listResourceRoles(
roleId: roles.roleId, roleId: roles.roleId,
name: roles.name, name: roles.name,
description: roles.description, description: roles.description,
isSuperUserRole: roles.isSuperUserRole, isAdmin: roles.isAdmin,
}) })
.from(roleResources) .from(roleResources)
.innerJoin(roles, eq(roleResources.roleId, roles.roleId)) .innerJoin(roles, eq(roleResources.roleId, roles.roleId))

View file

@ -61,11 +61,11 @@ export async function deleteRole(
); );
} }
if (role[0].isSuperUserRole) { if (role[0].isAdmin) {
return next( return next(
createHttpError( createHttpError(
HttpCode.FORBIDDEN, HttpCode.FORBIDDEN,
`Cannot delete a Super User role` `Cannot delete a Admin role`
) )
); );
} }

View file

@ -80,7 +80,7 @@ export async function listRoles(
.select({ .select({
roleId: roles.roleId, roleId: roles.roleId,
orgId: roles.orgId, orgId: roles.orgId,
isSuperUserRole: roles.isSuperUserRole, isAdmin: roles.isAdmin,
name: roles.name, name: roles.name,
description: roles.description, description: roles.description,
orgName: orgs.name, orgName: orgs.name,

View file

@ -81,11 +81,11 @@ export async function updateRole(
); );
} }
if (role[0].isSuperUserRole) { if (role[0].isAdmin) {
return next( return next(
createHttpError( createHttpError(
HttpCode.FORBIDDEN, HttpCode.FORBIDDEN,
`Cannot update a Super User role` `Cannot update a Admin role`
) )
); );
} }

View file

@ -107,25 +107,24 @@ export async function createSite(
subnet, subnet,
}) })
.returning(); .returning();
// find the Super User roleId and also add the resource to the Super User role const adminRole = await db
const superUserRole = await db
.select() .select()
.from(roles) .from(roles)
.where(and(eq(roles.isSuperUserRole, true), eq(roles.orgId, orgId))) .where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1); .limit(1);
if (superUserRole.length === 0) { if (adminRole.length === 0) {
return next( return next(
createHttpError(HttpCode.NOT_FOUND, `Super User role not found`) createHttpError(HttpCode.NOT_FOUND, `Admin role not found`)
); );
} }
await db.insert(roleSites).values({ await db.insert(roleSites).values({
roleId: superUserRole[0].roleId, roleId: adminRole[0].roleId,
siteId: newSite.siteId, siteId: newSite.siteId,
}); });
if (req.userOrgRoleId != superUserRole[0].roleId) { if (req.userOrgRoleId != adminRole[0].roleId) {
// make sure the user can access the site // make sure the user can access the site
db.insert(userSites).values({ db.insert(userSites).values({
userId: req.user?.userId!, userId: req.user?.userId!,

View file

@ -51,7 +51,7 @@ export async function listSiteRoles(
roleId: roles.roleId, roleId: roles.roleId,
name: roles.name, name: roles.name,
description: roles.description, description: roles.description,
isSuperUserRole: roles.isSuperUserRole, isAdmin: roles.isAdmin,
}) })
.from(roleSites) .from(roleSites)
.innerJoin(roles, eq(roleSites.roleId, roles.roleId)) .innerJoin(roles, eq(roleSites.roleId, roles.roleId))

View file

@ -57,13 +57,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [expiresInDays, setExpiresInDays] = useState(1); const [expiresInDays, setExpiresInDays] = useState(1);
const roles = [ const roles = [{ roleId: 1, name: "Admin" }];
{ roleId: 1, name: "Super User" },
{ roleId: 2, name: "Admin" },
{ roleId: 3, name: "Power User" },
{ roleId: 4, name: "User" },
{ roleId: 5, name: "Guest" },
];
const validFor = [ const validFor = [
{ hours: 24, name: "1 day" }, { hours: 24, name: "1 day" },
@ -122,13 +116,16 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
return ( return (
<> <>
<Credenza open={open} onOpenChange={(val) => { <Credenza
open={open}
onOpenChange={(val) => {
setOpen(val); setOpen(val);
setInviteLink(null); setInviteLink(null);
setLoading(false); setLoading(false);
setExpiresInDays(1); setExpiresInDays(1);
form.reset(); form.reset();
}}> }}
>
<CredenzaContent> <CredenzaContent>
<CredenzaHeader> <CredenzaHeader>
<CredenzaTitle>Invite User</CredenzaTitle> <CredenzaTitle>Invite User</CredenzaTitle>