diff --git a/backend/src/api/get-endpoints/index.ts b/backend/src/api/get-endpoints/index.ts index c011092a..46d3ccc8 100644 --- a/backend/src/api/get-endpoints/index.ts +++ b/backend/src/api/get-endpoints/index.ts @@ -53,3 +53,20 @@ export const getUsageHandler = async ( await ApiResponseHandler.error(res, err) } } + +export const updateEndpointIsAuthenticated = async ( + req: Request, + res: Response, +): Promise => { + try { + const { endpointId } = req.params + const params: { authenticated: boolean } = req.body + await GetEndpointsService.updateIsAuthenticated( + endpointId, + params.authenticated, + ) + await ApiResponseHandler.success(res) + } catch (err) { + await ApiResponseHandler.error(res, err) + } +} diff --git a/backend/src/index.ts b/backend/src/index.ts index 5c93f76f..a086ec46 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -10,6 +10,7 @@ import { getEndpointsHandler, getHostsHandler, getUsageHandler, + updateEndpointIsAuthenticated, } from "api/get-endpoints" import { deleteSpecHandler, @@ -97,6 +98,10 @@ app.get("/api/v1/endpoints/hosts", getHostsHandler) app.get("/api/v1/endpoints", getEndpointsHandler) app.get("/api/v1/endpoint/:endpointId", getEndpointHandler) app.get("/api/v1/endpoint/:endpointId/usage", getUsageHandler) +app.put( + "/api/v1/endpoint/:endpointId/authenticated", + updateEndpointIsAuthenticated, +) app.post("/api/v1/spec/new", MulterSource.single("file"), uploadNewSpecHandler) app.delete("/api/v1/spec/:specFileName", deleteSpecHandler) diff --git a/backend/src/models/api-endpoint.ts b/backend/src/models/api-endpoint.ts index c5b2c2da..c7ec79c4 100644 --- a/backend/src/models/api-endpoint.ts +++ b/backend/src/models/api-endpoint.ts @@ -63,6 +63,9 @@ export class ApiEndpoint extends BaseEntity { @Column({ type: "bool", default: true }) isAuthenticatedDetected: boolean + @Column({ type: "bool", nullable: true }) + isAuthenticatedUserSet: boolean + @Column({ nullable: true }) openapiSpecName: string diff --git a/backend/src/models/openapi-spec.ts b/backend/src/models/openapi-spec.ts index 502fd804..483bf182 100644 --- a/backend/src/models/openapi-spec.ts +++ b/backend/src/models/openapi-spec.ts @@ -26,7 +26,7 @@ export class OpenApiSpec extends BaseEntity { @UpdateDateColumn({ type: "timestamptz" }) updatedAt: Date - @Column({ type: "timestamptz" }) + @Column({ type: "timestamptz", nullable: true }) specUpdatedAt: Date @Column({ type: "enum", enum: SpecExtension, default: SpecExtension.JSON }) diff --git a/backend/src/services/get-endpoints/index.ts b/backend/src/services/get-endpoints/index.ts index d3202ba3..eb6bf3cd 100644 --- a/backend/src/services/get-endpoints/index.ts +++ b/backend/src/services/get-endpoints/index.ts @@ -18,6 +18,17 @@ import Error404NotFound from "errors/error-404-not-found" import { getRiskScore } from "utils" export class GetEndpointsService { + static async updateIsAuthenticated( + apiEndpointUuid: string, + authenticated: boolean, + ): Promise { + await AppDataSource.createQueryBuilder() + .update(ApiEndpoint) + .set({ isAuthenticatedUserSet: authenticated }) + .where("uuid = :id", { id: apiEndpointUuid }) + .execute() + } + static async updateEndpointRiskScore( apiEndpointUuid: string, ): Promise { diff --git a/common/src/types.ts b/common/src/types.ts index 5b7c845d..babc85ca 100644 --- a/common/src/types.ts +++ b/common/src/types.ts @@ -209,6 +209,7 @@ export interface ApiEndpoint { riskScore: RiskScore openapiSpecName: string isAuthenticatedDetected: boolean + isAuthenticatedUserSet: boolean } export interface ApiEndpointDetailed extends ApiEndpoint { diff --git a/frontend/src/api/endpoints/index.ts b/frontend/src/api/endpoints/index.ts index 7d1732c4..1bf0c1ac 100644 --- a/frontend/src/api/endpoints/index.ts +++ b/frontend/src/api/endpoints/index.ts @@ -69,3 +69,12 @@ export const getUsage = async (endpointId: string): Promise => { return [] } } + +export const updateEndpointAuthenticated = async ( + endpointId: string, + authenticated: boolean, +): Promise => { + await axios.put(`${getAPIURL()}/endpoint/${endpointId}/authenticated`, { + authenticated, + }) +} diff --git a/frontend/src/components/Endpoint/Overview.tsx b/frontend/src/components/Endpoint/Overview.tsx index 7a6b3574..10481aca 100644 --- a/frontend/src/components/Endpoint/Overview.tsx +++ b/frontend/src/components/Endpoint/Overview.tsx @@ -1,6 +1,14 @@ -import React from "react" +import React, { useState } from "react" import { ApiEndpointDetailed, Usage } from "@common/types" -import { Box, Badge, Grid, GridItem, Stack } from "@chakra-ui/react" +import { + Box, + Badge, + Grid, + GridItem, + Stack, + HStack, + Checkbox, +} from "@chakra-ui/react" import dynamic from "next/dynamic" import { DataAttribute, DataHeading } from "components/utils/Card" import EndpointUsageChart from "./UsageChart" @@ -8,6 +16,7 @@ import { RISK_TO_COLOR } from "~/constants" import EndpointPIIChart from "./PIIChart" import { getDateTimeString } from "utils" import { DataTag, Status } from "@common/enums" +import { updateEndpointAuthenticated } from "api/endpoints" const SpecComponent = dynamic(() => import("./SpecComponent"), { ssr: false }) @@ -21,6 +30,20 @@ const EndpointOverview: React.FC = React.memo( const piiFields = endpoint.dataFields.filter( field => field.dataTag === DataTag.PII, ) + const [authenticated, setAuthenticated] = useState( + endpoint.isAuthenticatedUserSet, + ) + + const handleAuthenticatedCheck = ( + checked: boolean, + authenticated: boolean, + ) => { + if (!checked) { + authenticated = null + } + updateEndpointAuthenticated(endpoint.uuid, authenticated) + setAuthenticated(authenticated) + } return ( = React.memo( {getDateTimeString(endpoint.lastActive) || "N/A"} + + Authenticated + + + handleAuthenticatedCheck(e.target.checked, true) + } + > + Yes + + + handleAuthenticatedCheck(e.target.checked, false) + } + > + No + + + {usage.length > 0 && ( Usage