Skip to content

Commit

Permalink
add host page and endpoint deletion logic (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
NikhilShahi authored Nov 26, 2022
1 parent a1baa84 commit 8d1b863
Show file tree
Hide file tree
Showing 12 changed files with 723 additions and 3 deletions.
51 changes: 50 additions & 1 deletion backend/src/api/get-endpoints/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Response } from "express"
import validator from "validator"
import { GetEndpointsService } from "services/get-endpoints"
import { GetEndpointParams } from "@common/types"
import { GetEndpointParams, GetHostParams } from "@common/types"
import ApiResponseHandler from "api-response-handler"
import Error404NotFound from "errors/error-404-not-found"
import { MetloRequest } from "types"
import Error400BadRequest from "errors/error-400-bad-request"

export const getEndpointsHandler = async (
req: MetloRequest,
Expand Down Expand Up @@ -72,6 +73,9 @@ export const updateEndpointIsAuthenticated = async (
): Promise<void> => {
try {
const { endpointId } = req.params
if (!validator.isUUID(endpointId)) {
throw new Error404NotFound("Endpoint does not exist.")
}
const params: { authenticated: boolean } = req.body
await GetEndpointsService.updateIsAuthenticated(
req.ctx,
Expand All @@ -83,3 +87,48 @@ export const updateEndpointIsAuthenticated = async (
await ApiResponseHandler.error(res, err)
}
}

export const deleteEndpointHandler = async (
req: MetloRequest,
res: Response,
): Promise<void> => {
try {
const { endpointId } = req.params
if (!validator.isUUID(endpointId)) {
throw new Error404NotFound("Endpoint does not exist.")
}
await GetEndpointsService.deleteEndpoint(req.ctx, endpointId)
await ApiResponseHandler.success(res, "Success")
} catch (err) {
await ApiResponseHandler.error(res, err)
}
}

export const deleteHostHandler = async (
req: MetloRequest,
res: Response,
): Promise<void> => {
try {
const { host } = req.body
if (!host) {
throw new Error400BadRequest("Must provide host.")
}
await GetEndpointsService.deleteHost(req.ctx, host)
await ApiResponseHandler.success(res, "Success")
} catch (err) {
await ApiResponseHandler.error(res, err)
}
}

export const getHostsListHandler = async (
req: MetloRequest,
res: Response,
): Promise<void> => {
const hostsParams: GetHostParams = req.query
try {
const resp = await GetEndpointsService.getHostsList(req.ctx, hostsParams)
await ApiResponseHandler.success(res, resp)
} catch (err) {
await ApiResponseHandler.error(res, err)
}
}
12 changes: 11 additions & 1 deletion backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ dotenv.config()
import express, { Express, Response } from "express"
import { InstanceSettings } from "models"
import {
deleteEndpointHandler,
deleteHostHandler,
getEndpointHandler,
getEndpointsHandler,
getHostsHandler,
getHostsListHandler,
getUsageHandler,
updateEndpointIsAuthenticated,
} from "api/get-endpoints"
Expand Down Expand Up @@ -83,8 +86,15 @@ apiRouter.put(
"/api/v1/endpoint/:endpointId/authenticated",
updateEndpointIsAuthenticated,
)
apiRouter.delete("/api/v1/endpoint/:endpointId", deleteEndpointHandler)
apiRouter.delete("/api/v1/host", deleteHostHandler)
apiRouter.get("/api/v1/hosts", getHostsListHandler)

apiRouter.post("/api/v1/spec/new", MulterSource.single("file"), uploadNewSpecHandler)
apiRouter.post(
"/api/v1/spec/new",
MulterSource.single("file"),
uploadNewSpecHandler,
)
apiRouter.delete("/api/v1/spec/:specFileName", deleteSpecHandler)
apiRouter.put(
"/api/v1/spec/:specFileName",
Expand Down
178 changes: 177 additions & 1 deletion backend/src/services/get-endpoints/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { QueryRunner } from "typeorm"
import { AppDataSource } from "data-source"
import {
ApiEndpoint,
Expand All @@ -7,12 +8,15 @@ import {
Alert,
DataField,
OpenApiSpec,
Attack,
} from "models"
import {
GetEndpointParams,
ApiEndpoint as ApiEndpointResponse,
ApiEndpointDetailed as ApiEndpointDetailedResponse,
Usage as UsageResponse,
GetHostParams,
HostResponse,
} from "@common/types"
import Error500InternalServer from "errors/error-500-internal-server"
import { Test } from "@metlo/testing"
Expand Down Expand Up @@ -50,6 +54,137 @@ ORDER BY
`

export class GetEndpointsService {
static async deleteEndpoint(
ctx: MetloContext,
apiEndpointUuid: string,
): Promise<void> {
const queryRunner = AppDataSource.createQueryRunner()
try {
await queryRunner.connect()
const endpoint = await getEntityManager(ctx, queryRunner).findOneBy(
ApiEndpoint,
{ uuid: apiEndpointUuid },
)
if (!endpoint) {
throw new Error404NotFound("Endpoint not found.")
}
await queryRunner.startTransaction()
await getQB(ctx, queryRunner)
.delete()
.from(AggregateTraceDataHourly)
.andWhere(`"apiEndpointUuid" = :id`, { id: apiEndpointUuid })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(Alert)
.andWhere(`"apiEndpointUuid" = :id`, { id: apiEndpointUuid })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(ApiEndpointTest)
.andWhere(`"apiEndpointUuid" = :id`, { id: apiEndpointUuid })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(ApiTrace)
.andWhere(`"apiEndpointUuid" = :id`, { id: apiEndpointUuid })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(Attack)
.andWhere(`"apiEndpointUuid" = :id`, { id: apiEndpointUuid })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(DataField)
.andWhere(`"apiEndpointUuid" = :id`, { id: apiEndpointUuid })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(ApiEndpoint)
.andWhere("uuid = :id", { id: apiEndpointUuid })
.execute()
await queryRunner.commitTransaction()
} catch (err) {
if (queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction()
}
throw err
} finally {
await queryRunner.release()
}
}

static async deleteEndpointsBatch(
ctx: MetloContext,
apiEndpointUuids: string[],
queryRunner: QueryRunner,
): Promise<void> {
await getQB(ctx, queryRunner)
.delete()
.from(AggregateTraceDataHourly)
.andWhere(`"apiEndpointUuid" IN(:...ids)`, { ids: apiEndpointUuids })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(Alert)
.andWhere(`"apiEndpointUuid" IN(:...ids)`, { ids: apiEndpointUuids })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(ApiEndpointTest)
.andWhere(`"apiEndpointUuid" IN(:...ids)`, { ids: apiEndpointUuids })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(ApiTrace)
.andWhere(`"apiEndpointUuid" IN(:...ids)`, { ids: apiEndpointUuids })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(Attack)
.andWhere(`"apiEndpointUuid" IN(:...ids)`, { ids: apiEndpointUuids })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(DataField)
.andWhere(`"apiEndpointUuid" IN(:...ids)`, { ids: apiEndpointUuids })
.execute()
await getQB(ctx, queryRunner)
.delete()
.from(ApiEndpoint)
.andWhere("uuid IN(:...ids)", { ids: apiEndpointUuids })
.execute()
}

static async deleteHost(ctx: MetloContext, host: string): Promise<void> {
const queryRunner = AppDataSource.createQueryRunner()
try {
await queryRunner.connect()
const endpoints = await getQB(ctx, queryRunner)
.select(["uuid"])
.from(ApiEndpoint, "endpoint")
.andWhere("host = :host", { host })
.getRawMany()
if (endpoints?.length > 0) {
await queryRunner.startTransaction()
await this.deleteEndpointsBatch(
ctx,
endpoints?.map(e => e.uuid),
queryRunner,
)
await queryRunner.commitTransaction()
}
} catch (err) {
if (queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction()
}
throw err
} finally {
await queryRunner.release()
}
}

static async updateIsAuthenticated(
ctx: MetloContext,
apiEndpointUuid: string,
Expand Down Expand Up @@ -134,7 +269,7 @@ export class GetEndpointsService {
whereFilterString = `WHERE ${whereFilter.join(" AND ")}`
}
const limitFilter = `LIMIT ${getEndpointParams?.limit ?? 10}`
const offsetFilter = `OFFSET ${getEndpointParams?.offset ?? 10}`
const offsetFilter = `OFFSET ${getEndpointParams?.offset ?? 0}`

const endpointResults = await queryRunner.query(
getEndpointsQuery(ctx, whereFilterString, limitFilter, offsetFilter),
Expand Down Expand Up @@ -224,6 +359,47 @@ export class GetEndpointsService {
}
}

static async getHostsList(
ctx: MetloContext,
getHostsParams: GetHostParams,
): Promise<[HostResponse[], any]> {
const queryRunner = AppDataSource.createQueryRunner()
try {
await queryRunner.connect()

let qb = getQB(ctx, queryRunner)
.select(["host", `COUNT(uuid) as "numEndpoints"`])
.from(ApiEndpoint, "endpoint")
.distinct(true)
.groupBy("host")
let totalHostsQb = await getQB(ctx, queryRunner)
.select([`COUNT(DISTINCT(host))::int as "numHosts"`])
.from(ApiEndpoint, "endpoint")

if (getHostsParams?.searchQuery) {
qb = qb.andWhere("host ILIKE :searchQuery", {
searchQuery: `%${getHostsParams.searchQuery}%`,
})
totalHostsQb = totalHostsQb.andWhere("host ILIKE :searchQuery", {
searchQuery: `%${getHostsParams.searchQuery}%`,
})
}

qb = qb
.limit(getHostsParams?.limit ?? 10)
.offset(getHostsParams?.offset ?? 0)

const hostsResp = await qb.getRawMany()
const totalHosts = await totalHostsQb.getRawOne()

return [hostsResp, totalHosts?.numHosts ?? 0]
} catch (err) {
throw new Error500InternalServer(err)
} finally {
await queryRunner.release()
}
}

static async getUsage(
ctx: MetloContext,
endpointId: string,
Expand Down
11 changes: 11 additions & 0 deletions common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ export interface GetEndpointParams {
limit?: number
}

export interface GetHostParams {
offset?: number
limit?: number
searchQuery?: string
}

export interface GetAlertParams {
uuid?: string
apiEndpointUuid?: string
Expand Down Expand Up @@ -228,6 +234,11 @@ export interface ApiEndpointDetailed extends ApiEndpoint {
dataFields: DataField[]
}

export interface HostResponse {
host: string
numEndpoints: number
}

export interface TestDetailed {
uuid: string
name: string
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/api/endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
ApiEndpoint,
ApiEndpointDetailed,
GetEndpointParams,
GetHostParams,
HostResponse,
Usage,
} from "@common/types"
import { getAPIURL } from "~/constants"
Expand Down Expand Up @@ -96,3 +98,25 @@ export const updateEndpointAuthenticated = async (
{ headers },
)
}

export const getHostsList = async (
params: GetHostParams,
headers?: AxiosRequestHeaders,
): Promise<[HostResponse[], number]> => {
const resp = await axios.get<[HostResponse[], number]>(
`${getAPIURL()}/hosts`,
{ params, headers },
)
return resp.data
}

export const deleteHost = async (
host: string,
headers?: AxiosRequestHeaders,
): Promise<any> => {
const resp = await axios.delete(`${getAPIURL()}/host`, {
data: { host },
headers,
})
return resp.data
}
Loading

0 comments on commit 8d1b863

Please sign in to comment.