diff --git a/README.md b/README.md index 27eba3c..f758c7b 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,10 @@ Frontend utvikles med React og TypeScript og settes opp med Vite. Frontend er an - brukerinteraksjon I den nåværende prototypen brukes hovedsakelig mock-data og statiske eksempeldata. ### Backend -Backend er utviklet med Node.js og TypeScript og tilbyr et GraphQL API. Software-katalogen og detaljsidene henter MITRE ATT&CK-data fra MySQL-databasen. +Backend skal utvikles med Node.js og TypeScript og tilby et GraphQL API som frontend kan bruke for å hente og endre data. +Backend er foreløpig ikke implementert. ### Database -Prosjektet bruker MySQL. MITRE ATT&CK-data importeres fra JSON-datasettet med import-scriptet i `database/import-mitre.ts`. Den lokale databasen må være satt opp og dataene importert før databasefunksjonene kan brukes. Se [lokal databaseoppsett](./docs/local-database-setup.md). +Det er planlagt å bruke MySQL som database. MITRE ATT&CK-datasettet skal importeres til databasen ved hjelp av et import-script som transformerer dataene fra det opprinnelige JSON-formatet til databaseskjemaet vårt. Database og backend skal senere kjøres på VMen. ## Nåværende status og prototype Det som er implementert frem til nå: - Oppsett av utviklingsmiljø for frontend, backend og database @@ -91,16 +92,41 @@ KI har blitt brukt til veiledning i store deler av prosjektet. Ofte har ideer om ## Kjøre prosjektet lokalt +## 1.Dependencies +Installer dependencies, fra Rooten: + +```bash +pnpm install +``` + +## 2.Database + +**Følg guiden : `docs/local-database-setup.md`** + +`Resultat`: Kjørende CyberDB database lokalt på maskinen -```sh -pnpm --filter backend dev +## 3.Backend + +Opprett en ny `Terminal 1`: + +```bash +cd backend +pnpm run dev ``` +`Resultat`: Kjørende Backend med Apollo GraphQL Server kjører på http://localhost:3001/. + +## 4.Frontend + +Opprett en ny `Terminal 2`: + +```bash +cd frontend +pnpm run dev +``` + +`Resultat`: Kjørende Frontend på http://localhost:5173/project2 eller noe sånt. + + -Sørg for at MySQL kjører, og at databaseskjemaet og MITRE-dataene er satt opp -som beskrevet i [lokal databaseoppsett](./docs/local-database-setup.md). -Åpne URL-en Vite skriver ut i frontend-terminalen (vanligvis -`http://localhost:5173`). Frontenden bruker GraphQL API-et på -`http://localhost:3001`. Den lokale frontend-ruten er `/`, mens produksjonsruten -er satt opp for publisering under `/project2`. diff --git a/backend/src/graphql/resolvers/index.ts b/backend/src/graphql/resolvers/index.ts index 5bdee49..5443653 100644 --- a/backend/src/graphql/resolvers/index.ts +++ b/backend/src/graphql/resolvers/index.ts @@ -14,13 +14,12 @@ import { threatActorResolvers } from "./threatActorResolver.js"; import { techniqueResolvers } from "./techniqueResolver.js"; -import { softwareResolvers } from "./softwareResolver.js"; + export const resolvers = { Query: { ...threatActorResolvers.Query, ...techniqueResolvers.Query, - ...softwareResolvers.Query, }, }; \ No newline at end of file diff --git a/backend/src/graphql/resolvers/softwareResolver.ts b/backend/src/graphql/resolvers/softwareResolver.ts index cc3ebcb..c0d3458 100644 --- a/backend/src/graphql/resolvers/softwareResolver.ts +++ b/backend/src/graphql/resolvers/softwareResolver.ts @@ -1,12 +1,12 @@ -import { - getAllSoftware, - getSoftwareByMitreId, -} from "../../repositories/softwareRepository.js"; - -export const softwareResolvers = { - Query: { - software: async () => getAllSoftware(), - softwareByMitreId: async (_parent: unknown, args: { mitreId: string }) => - getSoftwareByMitreId(args.mitreId), - }, -}; \ No newline at end of file +/** + * Resolver-laget håndterer GraphQL-spørringer fra klienten. + * + * Resolverne kobler GraphQL-feltene til riktig funksjon i + * repository-laget og sender resultatet tilbake til klienten. + * + * Database-logikk skal ikke ligge direkte i resolverne. + * Den håndteres av repositories. + * + * + * GraphQL -> Resolver -> Repository -> Drizzle -> MySQL + */ \ No newline at end of file diff --git a/backend/src/graphql/typeDefs.ts b/backend/src/graphql/typeDefs.ts index c2643ef..15416a8 100644 --- a/backend/src/graphql/typeDefs.ts +++ b/backend/src/graphql/typeDefs.ts @@ -19,26 +19,75 @@ export const typeDefs = `#graphql motivation: String! } + type ThreatActorGridInfo { + items: [ThreatActorGrid!]! + totalCount: Int! + } + + type AssociatedItem { + mitreId: String! + name: String! + } + type AssociatedItemsPage { + items: [AssociatedItem!]! + totalCount: Int! } - type Software { - id: String! + type ThreatActorDetailed { + threatId: String! mitreId: String! name: String! description: String! - type: String! + threatEntryCreated: String! + country: String! + motivation: String! + region: String! + firstSeen: String! + lastSeen: String! + status: String! + dangerLevel: String! + xMitreDeprecated: Boolean! + aliases: [String!]! - platforms: [String!]! - associatedTechniques: [RelatedSoftwareEntity!]! - usedByThreatActors: [RelatedSoftwareEntity!]! + sectors: [String!]! + } + + type Technique { + mitreId: String! + name: String! + description: String! } - # Operasjoner frontend kan spørre API-et om type Query { + ######################################## ThreatActors vvvv + + # Henter alle threat actors som trengs i grid + allThreatActorsGrid(limit: Int!, offset: Int!): ThreatActorGridInfo! + # Henter alt nødvending info for ThreatActorDetailed i frontend + getThreatActorByMitreId(mitreId: String!): ThreatActorDetailed + + getThreatActorTechniques(mitreId: String!, limit: Int!, offset: Int!): AssociatedItemsPage! + + getThreatActorSoftwares(mitreId: String!, limit: Int!, offset: Int!): AssociatedItemsPage! + + ######################################## Techinques vvvv + + # Henter alle Techniques allTechniques: [Technique!]! + + # Henter én Technique basert på MITRE-ID techniqueByMitreId(mitreId: String!): Technique + + ######################################## Software vvvv + + + + ######################################## Tactics vvvv + + } + `; \ No newline at end of file diff --git a/backend/src/repositories/softwareRepository.ts b/backend/src/repositories/softwareRepository.ts index 7fa4347..b6f2fc5 100644 --- a/backend/src/repositories/softwareRepository.ts +++ b/backend/src/repositories/softwareRepository.ts @@ -1,134 +1,16 @@ -import { eq, inArray } from "drizzle-orm"; - -import { db } from "../db/connection.js"; -import { - software, - softwareAlias, - softwarePlatform, - technique, - techniqueSoftware, - threatActor, - threatActorSoftware, -} from "../db/schema.js"; - -async function getSoftwareMetadata(softwareIds: string[]) { - if (softwareIds.length === 0) { - return { - aliases: [], - platforms: [], - associatedTechniques: [], - usedByThreatActors: [], - }; - } - - const [aliases, platforms, associatedTechniques, usedByThreatActors] = - await Promise.all([ - db - .select({ - softwareId: softwareAlias.software_id, - alias: softwareAlias.alias, - }) - .from(softwareAlias) - .where(inArray(softwareAlias.software_id, softwareIds)), - db - .select({ - softwareId: softwarePlatform.software_id, - platform: softwarePlatform.platform, - }) - .from(softwarePlatform) - .where(inArray(softwarePlatform.software_id, softwareIds)), - db - .select({ - softwareId: techniqueSoftware.software_id, - id: technique.technique_id, - mitreId: technique.mitre_id, - name: technique.name, - }) - .from(techniqueSoftware) - .innerJoin( - technique, - eq(techniqueSoftware.technique_id, technique.technique_id), - ) - .where(inArray(techniqueSoftware.software_id, softwareIds)), - db - .select({ - softwareId: threatActorSoftware.software_id, - id: threatActor.threat_id, - mitreId: threatActor.mitre_id, - name: threatActor.name, - }) - .from(threatActorSoftware) - .innerJoin( - threatActor, - eq(threatActorSoftware.threat_id, threatActor.threat_id), - ) - .where(inArray(threatActorSoftware.software_id, softwareIds)), - ]); - - return { aliases, platforms, associatedTechniques, usedByThreatActors }; -} - -export async function getAllSoftware() { - const entries = await db - .select({ - id: software.software_id, - mitreId: software.mitre_id, - name: software.name, - description: software.description, - type: software.software_type, - }) - .from(software) - .orderBy(software.name); - - const metadata = await getSoftwareMetadata(entries.map((entry) => entry.id)); - - return entries.map((entry) => ({ - ...entry, - aliases: metadata.aliases - .filter((alias) => alias.softwareId === entry.id) - .map((alias) => alias.alias), - platforms: metadata.platforms - .filter((platform) => platform.softwareId === entry.id) - .map((platform) => platform.platform), - })); -} - -export async function getSoftwareByMitreId(mitreId: string) { - const [entry] = await db - .select({ - id: software.software_id, - mitreId: software.mitre_id, - name: software.name, - description: software.description, - type: software.software_type, - }) - .from(software) - .where(eq(software.mitre_id, mitreId)) - .limit(1); - - if (!entry) { - return null; - } - - const metadata = await getSoftwareMetadata([entry.id]); - - return { - ...entry, - aliases: metadata.aliases.map((alias) => alias.alias), - platforms: metadata.platforms.map((platform) => platform.platform), - associatedTechniques: metadata.associatedTechniques.map( - ({ id, mitreId: relatedMitreId, name }) => ({ - id, - mitreId: relatedMitreId, - name, - }), - ), - usedByThreatActors: metadata.usedByThreatActors.map( - ({ id, mitreId: relatedMitreId, name }) => ({ - id, - mitreId: relatedMitreId, - name, - }), - ), - }; -} \ No newline at end of file +/** + * Repository-laget samler databaseoperasjoner relatert til Software. + * + * Hensikten er å skille database-logikken fra resten av backenden: + * resolvers skal ikke skrive Drizzle-spørringer direkte, men heller + * kalle funksjoner fra dette repositoryet. + * + * Dermed får vi en tydelig ansvarsfordeling: + * + * GraphQL resolver -> Repository -> Drizzle -> MySQL + * + * Dette gjør koden lettere å lese, teste og endre senere. + * + * + * !!!ALTSÅ HER SKRIVER VI KODEN SOM FAKTISK HENTER UT DATA FRA DB VIA DRIZZLE!!!! + */ \ No newline at end of file diff --git a/frontend/src/data/softwareData.ts b/frontend/src/data/softwareData.ts index a84d715..def4431 100644 --- a/frontend/src/data/softwareData.ts +++ b/frontend/src/data/softwareData.ts @@ -1,30 +1,15 @@ import { graphqlRequest } from "../api/graphql"; +import { mockSoftware, type SoftwareEntry } from "./softwareMockData"; -export type RelatedSoftwareEntry = { - id: string; - mitreId: string; - name: string; -}; - -export type SoftwareEntry = { - id: string; - mitreId: string; - name: string; - type: "TOOL" | "MALWARE"; - summary?: string; - description: string; - aliases: string[]; - platforms: string[]; - associatedTechniques: RelatedSoftwareEntry[]; - usedByThreatActors: RelatedSoftwareEntry[]; -}; - -export type SoftwareCatalogEntry = Omit< - SoftwareEntry, - "associatedTechniques" | "usedByThreatActors" ->; +export type { SoftwareEntry } from "./softwareMockData"; + +export const USE_SOFTWARE_MOCK_DATA = false; // ENDRE DENNE FOR Å IKKE BRUKE MOCK!!! + +export type SoftwareCatalogEntry = Omit; export async function getSoftwareCatalog(): Promise { + if (USE_SOFTWARE_MOCK_DATA) return mockSoftware; + const data = await graphqlRequest<{ software: SoftwareCatalogEntry[] }>(` query SoftwareCatalog { software { @@ -43,6 +28,10 @@ export async function getSoftwareCatalog(): Promise { } export async function getSoftwareByMitreId(mitreId: string): Promise { + if (USE_SOFTWARE_MOCK_DATA) { + return mockSoftware.find((entry) => entry.mitreId === mitreId) ?? null; + } + const data = await graphqlRequest<{ softwareByMitreId: SoftwareEntry | null }>(` query SoftwareDetail($mitreId: String!) { softwareByMitreId(mitreId: $mitreId) { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index bee60d2..59c6d26 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,8 +2,7 @@ import react, { reactCompilerPreset } from "@vitejs/plugin-react"; import babel from "@rolldown/plugin-babel"; import { defineConfig } from "vite"; -// https://vite.dev/config/ -export default defineConfig({ - base: "/project2/", // Stien til frontend på NTNU-VM +export default defineConfig(({ command }) => ({ + base: command === "build" ? "/project2/" : "/", plugins: [react(), babel({ presets: [reactCompilerPreset()] })], -}); +})); \ No newline at end of file