diff --git a/scripts/hydrate.ts b/scripts/hydrate.ts index 31a9a81a..ddd3103e 100644 --- a/scripts/hydrate.ts +++ b/scripts/hydrate.ts @@ -30,26 +30,20 @@ async function insertDummyData() { .get(); // Insert dummy users - await db.insert(users).values([ - { - orgId: org1.orgId, - name: "John Doe", - email: "john@fossorial.com", - groups: "admin,developer", - }, - { - orgId: org1.orgId, - name: "Jane Smith", - email: "jane@fossorial.com", - groups: "developer", - }, - { - orgId: org2.orgId, - name: "Bob Johnson", - email: "bob@fosrl.io", - groups: "admin", - }, - ]); + // await db.insert(users).values([ + // { + // email: "john@fossorial.com", + // groups: "admin,developer", + // }, + // { + // email: "jane@fossorial.com", + // groups: "developer", + // }, + // { + // email: "bob@fosrl.io", + // groups: "admin", + // }, + // ]); // Insert dummy exit nodes const exitNode1 = db @@ -107,6 +101,7 @@ async function insertDummyData() { .values({ resourceId: `web.${site1.subdomain}.${org1.domain}`, siteId: site1.siteId, + orgId: site1.orgId, name: "Web Server", subdomain: "web", }) @@ -118,6 +113,7 @@ async function insertDummyData() { .values({ resourceId: `web2.${site1.subdomain}.${org1.domain}`, siteId: site1.siteId, + orgId: site1.orgId, name: "Web Server 2", subdomain: "web2", }) @@ -129,6 +125,7 @@ async function insertDummyData() { .values({ resourceId: `db.${site2.subdomain}.${org2.domain}`, siteId: site2.siteId, + orgId: site2.orgId, name: "Database", subdomain: "db", }) diff --git a/server/db/schema.ts b/server/db/schema.ts index 949d7a49..26baf3b6 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -31,6 +31,9 @@ export const resources = sqliteTable("resources", { siteId: integer("siteId").references(() => sites.siteId, { onDelete: "cascade", }), + orgId: integer("orgId").references(() => orgs.orgId, { + onDelete: "cascade", + }), name: text("name").notNull(), subdomain: text("subdomain"), }); diff --git a/server/routers/external.ts b/server/routers/external.ts index 857f20d2..ec6de3e7 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -17,32 +17,34 @@ unauthenticated.get("/", (_, res) => { // Authenticated Root routes export const authenticated = Router(); -authenticated.put("/site", site.createSite); -authenticated.get("/site/:siteId", site.getSite); -authenticated.get("/sites", site.listSites); -authenticated.post("/site/:siteId", site.updateSite); -authenticated.delete("/site/:siteId", site.deleteSite); - authenticated.put("/org", org.createOrg); -authenticated.get("/org/:orgId", org.getOrg); authenticated.get("/orgs", org.listOrgs); +authenticated.get("/org/:orgId", org.getOrg); authenticated.post("/org/:orgId", org.updateOrg); authenticated.delete("/org/:orgId", org.deleteOrg); -authenticated.put("/resource", resource.createResource); +authenticated.put("/org/:orgId/site", site.createSite); +authenticated.get("/org/:orgId/sites", site.listSites); +authenticated.get("/site/:siteId", site.getSite); +authenticated.post("/site/:siteId", site.updateSite); +authenticated.delete("/site/:siteId", site.deleteSite); + +authenticated.put("/org/:orgId/site/:siteId/resource", resource.createResource); +authenticated.get("/site/:siteId/resources", resource.listResources); +authenticated.get("/org/:orgId/resources", resource.listResources); authenticated.get("/resource/:resourceId", resource.getResource); -authenticated.get("/resources", resource.listResources); authenticated.post("/resource/:resourceId", resource.updateResource); authenticated.delete("/resource/:resourceId", resource.deleteResource); -authenticated.put("/target", target.createTarget); +authenticated.put("/resource/:resourceId/target", target.createTarget); +authenticated.get("/resource/:resourceId/targets", target.listTargets); authenticated.get("/target/:targetId", target.getTarget); -authenticated.get("/targets", target.listTargets); authenticated.post("/target/:targetId", target.updateTarget); authenticated.delete("/target/:targetId", target.deleteTarget); -authenticated.get("/user/:userId", user.getUser); authenticated.get("/users", user.listUsers); +// authenticated.get("/org/:orgId/users", user.???); // TODO: Implement this +authenticated.get("/user/:userId", user.getUser); authenticated.delete("/user/:userId", user.deleteUser); // Auth routes diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index 2dcd05b8..44e04c38 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -6,9 +6,13 @@ import response from "@server/utils/response"; import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; +const createResourceParamsSchema = z.object({ + siteId: z.number().int().positive(), + orgId: z.number().int().positive(), +}); + // Define Zod schema for request body validation const createResourceSchema = z.object({ - siteId: z.number().int().positive(), name: z.string().min(1).max(255), subdomain: z.string().min(1).max(255).optional(), }); @@ -26,7 +30,20 @@ export async function createResource(req: Request, res: Response, next: NextFunc ); } - const { siteId, name, subdomain } = parsedBody.data; + const { name, subdomain } = parsedBody.data; + + // Validate request params + const parsedParams = createResourceParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { siteId, orgId } = parsedParams.data; // Generate a unique resourceId const resourceId = "subdomain" // TODO: create the subdomain here @@ -35,6 +52,7 @@ export async function createResource(req: Request, res: Response, next: NextFunc const newResource = await db.insert(resources).values({ resourceId, siteId, + orgId, name, subdomain, }).returning(); diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index adf560b9..0f20650b 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -7,10 +7,16 @@ import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; import { sql, eq } from 'drizzle-orm'; +const listResourcesParamsSchema = z.object({ + siteId: z.coerce.number().int().positive().optional(), + orgId: z.coerce.number().int().positive().optional(), +}).refine(data => !!data.siteId !== !!data.orgId, { + message: "Either siteId or orgId must be provided, but not both", +}); + const listResourcesSchema = 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)), - siteId: z.string().optional().transform(Number).pipe(z.number().int().positive()), + limit: z.coerce.number().int().positive().default(10), + offset: z.coerce.number().int().nonnegative().default(0), }); export async function listResources(req: Request, res: Response, next: NextFunction): Promise { @@ -24,10 +30,20 @@ export async function listResources(req: Request, res: Response, next: NextFunct ) ); } + const { limit, offset } = parsedQuery.data; - const { limit, offset, siteId } = parsedQuery.data; + const parsedParams = listResourcesParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + const { siteId, orgId } = parsedParams.data; - let baseQuery = db + let baseQuery: any = db .select({ resourceId: resources.resourceId, name: resources.name, @@ -37,11 +53,14 @@ export async function listResources(req: Request, res: Response, next: NextFunct .from(resources) .leftJoin(sites, eq(resources.siteId, sites.siteId)); - let countQuery = db.select({ count: sql`cast(count(*) as integer)` }).from(resources); + let countQuery: any = db.select({ count: sql`cast(count(*) as integer)` }).from(resources); if (siteId) { baseQuery = baseQuery.where(eq(resources.siteId, siteId)); countQuery = countQuery.where(eq(resources.siteId, siteId)); + } else if (orgId) { + baseQuery = baseQuery.where(eq(resources.orgId, orgId)); + countQuery = countQuery.where(eq(resources.orgId, orgId)); } const resourcesList = await baseQuery.limit(limit).offset(offset); diff --git a/server/routers/site/createSite.ts b/server/routers/site/createSite.ts index 781e91fc..2fcbfaa6 100644 --- a/server/routers/site/createSite.ts +++ b/server/routers/site/createSite.ts @@ -1,19 +1,70 @@ -import { Request, Response, NextFunction } from "express"; +import { Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { db } from '@server/db'; +import { sites } from '@server/db/schema'; import response from "@server/utils/response"; -import HttpCode from "@server/types/HttpCode"; +import HttpCode from '@server/types/HttpCode'; +import createHttpError from 'http-errors'; -// define zod type here +const createSiteParamsSchema = z.object({ + orgId: z.number().int().positive(), +}); -export async function createSite( - req: Request, - res: Response, - next: NextFunction, -): Promise { - return response(res, { - data: null, +// Define Zod schema for request body validation +const createSiteSchema = z.object({ + name: z.string().min(1).max(255), + subdomain: z.string().min(1).max(255).optional(), + pubKey: z.string().optional(), + subnet: z.string().optional(), +}); + +export async function createSite(req: Request, res: Response, next: NextFunction): Promise { + try { + // Validate request body + const parsedBody = createSiteSchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedBody.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { name, subdomain, pubKey, subnet } = parsedBody.data; + + // Validate request params + const parsedParams = createSiteParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { orgId } = parsedParams.data; + + // Create new site in the database + const newSite = await db.insert(sites).values({ + orgId, + name, + subdomain, + pubKey, + subnet, + }).returning(); + + return res.status(HttpCode.CREATED).send( + response(res, { + data: newSite[0], success: true, error: false, - message: "Logged in successfully", - status: HttpCode.OK, - }); -} + message: "Site created successfully", + status: HttpCode.CREATED, + }) + ); + } catch (error) { + next(error); + } +} \ No newline at end of file diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index f808804e..6b7cba79 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -7,10 +7,13 @@ import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; import { sql, eq } from 'drizzle-orm'; +const listSitesParamsSchema = z.object({ + orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()), +}); + const listSitesSchema = 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)), - orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()), }); export async function listSites(req: Request, res: Response, next: NextFunction): Promise { @@ -25,7 +28,19 @@ export async function listSites(req: Request, res: Response, next: NextFunction) ); } - const { limit, offset, orgId } = parsedQuery.data; + const { limit, offset } = parsedQuery.data; + + const parsedParams = listSitesParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { orgId } = parsedParams.data; let baseQuery: any = db .select({ diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 71071764..f668ca20 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -6,8 +6,11 @@ import response from "@server/utils/response"; import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; -const createTargetSchema = z.object({ +const createTargetParamsSchema = z.object({ resourceId: z.string().uuid(), +}); + +const createTargetSchema = z.object({ ip: z.string().ip(), method: z.string().min(1).max(10), port: z.number().int().min(1).max(65535), @@ -29,7 +32,22 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti const targetData = parsedBody.data; - const newTarget = await db.insert(targets).values(targetData).returning(); + const parsedParams = createTargetParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { resourceId } = parsedParams.data; + + const newTarget = await db.insert(targets).values({ + resourceId, + ...targetData + }).returning(); return res.status(HttpCode.CREATED).send( response(res, { diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts index f8a32c85..caa75683 100644 --- a/server/routers/target/listTargets.ts +++ b/server/routers/target/listTargets.ts @@ -7,10 +7,13 @@ import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; import { sql, eq } from 'drizzle-orm'; +const listTargetsParamsSchema = z.object({ + resourceId: z.string().optional() +}); + const listTargetsSchema = 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)), - resourceId: z.string().optional(), }); export async function listTargets(req: Request, res: Response, next: NextFunction): Promise { @@ -25,7 +28,19 @@ export async function listTargets(req: Request, res: Response, next: NextFunctio ); } - const { limit, offset, resourceId } = parsedQuery.data; + const { limit, offset } = parsedQuery.data; + + const parsedParams = listTargetsParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedParams.error.errors.map(e => e.message).join(', ') + ) + ); + } + + const { resourceId } = parsedParams.data; let baseQuery: any = db .select({