Skip to content

Commit

Permalink
(feature) update alerts model, backend logic, frontend UI
Browse files Browse the repository at this point in the history
  • Loading branch information
NikhilShahi committed Aug 28, 2022
1 parent f5cdbac commit 7918088
Show file tree
Hide file tree
Showing 30 changed files with 1,577 additions and 184 deletions.
13 changes: 5 additions & 8 deletions backend/src/api/alert/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request, Response } from "express"
import { AlertService } from "services/alert"
import { GetAlertParams } from "@common/types"
import { GetAlertParams, UpdateAlertParams } from "@common/types"
import ApiResponseHandler from "api-response-handler"

export const getAlertsHandler = async (
Expand All @@ -16,18 +16,15 @@ export const getAlertsHandler = async (
}
}

export const resolveAlertHandler = async (
export const updateAlertHandler = async (
req: Request,
res: Response,
): Promise<void> => {
try {
const { alertId } = req.params
const { resolutionMessage } = req.body
const resolvedAlert = await AlertService.resolveAlert(
alertId,
resolutionMessage,
)
await ApiResponseHandler.success(res, resolvedAlert)
const updateAlertParams: UpdateAlertParams = req.body
const updatedAlert = await AlertService.updateAlert(alertId, updateAlertParams)
await ApiResponseHandler.success(res, updatedAlert)
} catch (err) {
await ApiResponseHandler.error(res, err)
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const DATA_CLASS_TO_RISK_SCORE: Record<DataClass, RiskScore> = {

export const ALERT_TYPE_TO_RISK_SCORE: Record<AlertType, RiskScore> = {
[AlertType.NEW_ENDPOINT]: RiskScore.LOW,
[AlertType.OPEN_API_SPEC_DIFF]: RiskScore.LOW,
[AlertType.OPEN_API_SPEC_DIFF]: RiskScore.MEDIUM,
[AlertType.PII_DATA_DETECTED]: RiskScore.HIGH,
[AlertType.UNDOCUMENTED_ENDPOINT]: RiskScore.LOW,
}
Expand Down
4 changes: 2 additions & 2 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
import {
getAlertsHandler,
getTopAlertsHandler,
resolveAlertHandler,
updateAlertHandler,
} from "api/alert"
import { updateDataFieldClasses } from "api/data-field"
import { getSummaryHandler } from "api/summary"
Expand Down Expand Up @@ -91,7 +91,7 @@ app.post("/api/v1/data-field/:dataFieldId/update-classes", updateDataFieldClasse

app.get("/api/v1/alerts", getAlertsHandler)
app.get("/api/v1/topAlerts", getTopAlertsHandler)
app.put("/api/v1/alert/resolve/:alertId", resolveAlertHandler)
app.put("/api/v1/alert/:alertId", updateAlertHandler)

app.post("/api/v1/setup_connection", setup_connection)
app.post("/api/v1/setup_connection/aws/os", aws_os_choices)
Expand Down
6 changes: 3 additions & 3 deletions backend/src/models/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "typeorm"
import { AlertType, RiskScore } from "@common/enums"
import { AlertType, RiskScore, Status } from "@common/enums"
import { ApiEndpoint } from "models/api-endpoint"

@Entity()
Expand Down Expand Up @@ -36,8 +36,8 @@ export class Alert extends BaseEntity {
@UpdateDateColumn({ type: "timestamptz" })
updatedAt: Date

@Column({ type: "boolean", default: false })
resolved: boolean
@Column({ type: "enum", enum: Status, default: Status.OPEN })
status: Status

@Column({ nullable: true })
resolutionMessage: string
Expand Down
161 changes: 127 additions & 34 deletions backend/src/services/alert/index.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,147 @@
import { FindOptionsWhere } from "typeorm"
import { FindOptionsWhere, FindManyOptions, In, FindOptionsOrder, Not } from "typeorm"
import { AppDataSource } from "data-source"
import { Alert, ApiEndpoint, ApiTrace } from "models"
import { AlertType } from "@common/enums"
import { ALERT_TYPE_TO_RISK_SCORE, RISK_SCORE_ORDER_QUERY } from "~/constants"
import { GetAlertParams, Alert as AlertResponse } from "@common/types"
import { AlertType, SpecExtension, Status, UpdateAlertType } from "@common/enums"
import { ALERT_TYPE_TO_RISK_SCORE } from "~/constants"
import { GetAlertParams, Alert as AlertResponse, UpdateAlertParams } from "@common/types"
import Error409Conflict from "errors/error-409-conflict"
import Error500InternalServer from "errors/error-500-internal-server"

export class AlertService {
static async updateAlert(
alertId: string,
updateAlertParams: UpdateAlertParams
): Promise<Alert> {
const alertRepository = AppDataSource.getRepository(Alert)
const alert = await alertRepository.findOne({
where: {
uuid: alertId,
},
relations: {
apiEndpoint: true,
}
})
switch (updateAlertParams.updateType) {
case UpdateAlertType.IGNORE:
if (alert.status === Status.IGNORED) {
throw new Error409Conflict("Alert is already being ignored.")
} else if (alert.status === Status.RESOLVED) {
throw new Error409Conflict("Alert is resolved and cannot be ignored.")
}
alert.status = Status.IGNORED
break
case UpdateAlertType.UNIGNORE:
if (alert.status !== Status.IGNORED) {
throw new Error409Conflict("Alert is currently not ignored.")
}
alert.status = Status.OPEN
break
case UpdateAlertType.RESOLVE:
if (alert.status === Status.RESOLVED) {
throw new Error409Conflict("Alert is already resolved.")
} else if (alert.status === Status.IGNORED) {
throw new Error409Conflict("Alert is ignored and cannot be resolved.")
}
alert.status = Status.RESOLVED
alert.resolutionMessage = updateAlertParams.resolutionMessage?.trim() || null
break
case UpdateAlertType.UNRESOLVE:
if (alert.status !== Status.RESOLVED) {
throw new Error409Conflict("Alert is currently not resolved.")
}
alert.status = Status.OPEN
break
default:
throw new Error500InternalServer("Unknown update type.")
}
return await alertRepository.save(alert)
}

static async getAlerts(
alertParams: GetAlertParams,
): Promise<[AlertResponse[], number]> {
const alertRepository = AppDataSource.getRepository(Alert)
let whereConditions: FindOptionsWhere<Alert>[] | FindOptionsWhere<Alert> = {};
let paginationParams: FindManyOptions<Alert> = {};
let orderParams: FindOptionsOrder<Alert> = {};

let alertsQb = alertRepository
.createQueryBuilder("alert")
.leftJoinAndSelect("alert.apiEndpoint", "apiEndpoint")

if (alertParams?.apiEndpointUuid) {
whereConditions = {
...whereConditions,
apiEndpointUuid: alertParams.apiEndpointUuid
}
}
if (alertParams?.alertTypes) {
alertsQb = alertsQb.where("alert.type IN (:...types)", {
types: alertParams.alertTypes,
})
whereConditions = {
...whereConditions,
type: In(alertParams.alertTypes),
}
}
if (alertParams?.riskScores) {
alertsQb = alertsQb.andWhere("alert.riskScore IN (:...scores)", {
scores: alertParams.riskScores,
})
whereConditions = {
...whereConditions,
riskScore: In(alertParams.riskScores),
}
}
if (alertParams?.resolved) {
alertsQb = alertsQb.andWhere("alert.resolved = :resolved", {
resolved: alertParams.resolved,
})
if (alertParams?.status) {
whereConditions = {
...whereConditions,
status: In(alertParams.status)
}
}
alertsQb = alertsQb
.orderBy(RISK_SCORE_ORDER_QUERY("alert", "riskScore"), "DESC")
.addOrderBy("alert.createdAt", "DESC")
if (alertParams?.offset) {
alertsQb = alertsQb.offset(alertParams.offset)
paginationParams = {
...paginationParams,
skip: alertParams.offset,
}
}
if (alertParams?.limit) {
alertsQb = alertsQb.limit(alertParams.limit)
paginationParams = {
...paginationParams,
take: alertParams.limit,
}
}
if (alertParams?.order) {
orderParams = {
riskScore: alertParams.order
}
} else {
orderParams = {
riskScore: "DESC"
}
}

return await alertsQb.getManyAndCount()
const alerts = await alertRepository.findAndCount({
where: whereConditions,
...paginationParams,
relations: {
apiEndpoint: true,
},
order: {
status: "ASC",
...orderParams,
createdAt: "DESC",
},
});

return alerts;
}

static async getTopAlerts(): Promise<AlertResponse[]> {
const alertRepository = AppDataSource.getRepository(Alert)
return await alertRepository
.createQueryBuilder("alert")
.leftJoinAndSelect("alert.apiEndpoint", "apiEndpoint")
.where("alert.resolved = false")
.orderBy(RISK_SCORE_ORDER_QUERY("alert", "riskScore"), "DESC")
.addOrderBy("alert.createdAt", "DESC")
.limit(20)
.getMany()
return await alertRepository.find({
where: {
status: Status.OPEN
},
relations: {
apiEndpoint: true
},
order: {
riskScore: "DESC",
createdAt: "DESC"
},
take: 20
});
}

static async getAlert(alertId: string): Promise<AlertResponse> {
Expand All @@ -76,7 +165,7 @@ export class AlertService {
return await alertRepository.findOneBy({
apiEndpointUuid,
type,
resolved: false,
status: Not(Status.RESOLVED),
description,
})
}
Expand Down Expand Up @@ -128,6 +217,8 @@ export class AlertService {
alertItems: Record<string, string[]>,
apiEndpointUuid: string,
apiTrace: ApiTrace,
specString: string,
specExtension: SpecExtension,
): Promise<Alert[]> {
if (!alertItems) {
return []
Expand All @@ -151,6 +242,8 @@ export class AlertService {
newAlert.context = {
pathPointer: alertItems[key],
trace: apiTrace,
spec: specString,
specExtension,
}
newAlert.description = key
alerts.push(newAlert)
Expand All @@ -165,7 +258,7 @@ export class AlertService {
): Promise<Alert> {
const alertRepository = AppDataSource.getRepository(Alert)
const existingAlert = await alertRepository.findOneBy({ uuid: alertId })
existingAlert.resolved = true
existingAlert.status = Status.RESOLVED
existingAlert.resolutionMessage = resolutionMessage || ""
return await alertRepository.save(existingAlert)
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/services/get-endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class GetEndpointsService {
relations: {
dataFields: true,
openapiSpec: true,
alerts: true,
alerts: true
},
order: {
dataFields: {
Expand Down
2 changes: 2 additions & 0 deletions backend/src/services/spec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ export class SpecService {
errorItems,
endpoint.uuid,
trace,
openApiSpec.spec,
openApiSpec.extension,
)
} catch (err) {
console.error(`Error finding OpenAPI Spec diff: ${err}`)
Expand Down
6 changes: 3 additions & 3 deletions backend/src/services/summary/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DataTag, RiskScore } from "@common/enums"
import { DataTag, RiskScore, Status } from "@common/enums"
import { Alert, ApiEndpoint, DataField } from "models"
import { AppDataSource } from "data-source"
import { Summary as SummaryResponse } from "@common/types"
Expand All @@ -10,9 +10,9 @@ export class SummaryService {
const dataFieldRepository = AppDataSource.getRepository(DataField)
const highRiskAlerts = await alertRepository.countBy({
riskScore: RiskScore.HIGH,
resolved: false,
status: Status.OPEN,
})
const newAlerts = await alertRepository.countBy({ resolved: false })
const newAlerts = await alertRepository.countBy({ status: Status.OPEN })
const endpointsTracked = await apiEndpointRepository.count({})
const piiDataFields = await dataFieldRepository.countBy({
dataTag: DataTag.PII,
Expand Down
13 changes: 13 additions & 0 deletions common/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,16 @@ export enum TestTags {
MASS_ASSIGNMENT = "Mass Assignment",
SECURITY_MISCONFIGURATION = "Security Misconfiguration",
}

export enum Status {
RESOLVED = "Resolved",
IGNORED = "Ignored",
OPEN = "Open",
}

export enum UpdateAlertType {
RESOLVE = "resolve",
UNRESOLVE = "unresolve",
IGNORE = "ignore",
UNIGNORE = "unignore",
}
13 changes: 11 additions & 2 deletions common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
RestMethod,
RiskScore,
SpecExtension,
Status,
STEPS,
UpdateAlertType,
} from "./enums"
import "axios"

Expand Down Expand Up @@ -72,11 +74,13 @@ export interface GetEndpointParams {
}

export interface GetAlertParams {
apiEndpointUuid?: string
riskScores?: RiskScore[]
resolved?: boolean
status?: Status[]
alertTypes?: AlertType[]
offset?: number
limit?: number
order?: "DESC" | "ASC"
}

export interface UpdateDataFieldClassesParams {
Expand All @@ -89,6 +93,11 @@ export interface UpdateDataFieldParams {
isRisk: boolean
}

export interface UpdateAlertParams {
updateType: UpdateAlertType
resolutionMessage?: string
}

export type JSONValue =
| string
| number
Expand Down Expand Up @@ -121,7 +130,7 @@ export interface Alert {
description: string
createdAt: Date
updatedAt: Date
resolved: boolean
status: Status
resolutionMessage: string
context: object
}
Expand Down
Loading

0 comments on commit 7918088

Please sign in to comment.