From 725af44bd386b0fe31e159dcfc00c734a9a0e3be Mon Sep 17 00:00:00 2001 From: Stof <93355168+0x73746F66@users.noreply.github.com> Date: Tue, 26 Nov 2024 02:13:10 +1100 Subject: [PATCH] Dependencies (#75) * feat: dependency list * feat: add middleware to do authentication implicitly * feat: improved findings page details * feat: Add syntax highlight * feat: Add foldable text component * feat: Add string output sanitisation * feat: Add markdown support * feat: import repos and branches from any page * feat: git branches --------- Co-authored-by: Christopher Langton --- .repo/scratchad.sql | 26 +- functions/_middleware.js | 167 ++++++ functions/_middleware.ts | 20 - functions/api/[analysisState]/issues.js | 27 +- functions/api/[source]/integration.js | 34 +- functions/api/[source]/log.js | 25 +- functions/api/analytics.js | 20 +- functions/api/archive.js | 25 +- functions/api/artifact/[uuid].js | 31 +- functions/api/artifact/files.js | 31 +- functions/api/cdx.js | 82 +-- functions/api/exploitable.js | 25 +- .../api/github/[installation_id]/uninstall.js | 28 +- functions/api/github/[patId]/remove.js | 25 +- .../[installation_id]}/[code].js | 48 +- functions/api/github/pat.js | 46 +- functions/api/github/repos.js | 61 +- .../api/github/repos/[org]/[repo]/branches.js | 95 +++ .../api/github/repos/[org]/[repo]/sarif.js | 53 +- .../api/github/repos/[org]/[repo]/spdx.js | 65 +-- functions/api/github/repos/cached.js | 24 +- functions/api/issue/[uuid].js | 99 ++-- functions/api/login/[email]/[hash].js | 17 +- functions/api/login/github/[code].js | 46 +- functions/api/me.js | 86 +-- functions/api/next-issue.js | 29 +- functions/api/org/integrations.js | 35 +- .../api/register/[org]/[email]/[hash].js | 24 +- functions/api/sarif.js | 53 +- functions/api/sarif/[reportId].js | 27 +- functions/api/search.js | 34 +- functions/api/spdx.js | 85 +-- functions/api/unresolved.js | 28 +- functions/api/vulncheck/integrate.js | 42 +- functions/tea/collection/[collectionId].js | 14 +- functions/tea/leaf/[tei].js | 17 +- functions/tea/product/[tei].js | 18 +- migrations/0019_product_tags.sql | 69 +++ migrations/0020_repo_branches.sql | 9 + package.json | 2 + prisma/schema.prisma | 98 +++- src/@core/components/AppBarSearch.vue | 15 +- src/components/DependencyGraph.vue | 157 ++++- src/components/Finding.vue | 218 +++++-- src/components/TruncatableText.vue | 63 ++ src/finding.js | 545 +++++++++--------- .../DefaultLayoutWithVerticalNav.vue | 422 +++++++++++++- src/main.js | 2 - src/pages/GitHubLogin.vue | 2 +- src/pages/HomePage.vue | 2 +- src/pages/Integrations.vue | 21 +- src/pages/IssuePage.vue | 117 ++-- src/pages/Login.vue | 2 +- src/pages/NewIssues.vue | 175 +++--- src/pages/Projects.vue | 59 +- src/pages/Register.vue | 2 +- src/router/index.js | 19 +- src/utils.js | 342 ++++------- src/views/dashboard/AnalyticsWelcome.vue | 87 +-- yarn.lock | 30 + 60 files changed, 2242 insertions(+), 1828 deletions(-) create mode 100644 functions/_middleware.js delete mode 100644 functions/_middleware.ts rename functions/api/github/{[installation_id]/install => install/[installation_id]}/[code].js (77%) create mode 100644 functions/api/github/repos/[org]/[repo]/branches.js create mode 100644 migrations/0019_product_tags.sql create mode 100644 migrations/0020_repo_branches.sql create mode 100644 src/components/TruncatableText.vue diff --git a/.repo/scratchad.sql b/.repo/scratchad.sql index 58de3728..bed397bb 100644 --- a/.repo/scratchad.sql +++ b/.repo/scratchad.sql @@ -10,13 +10,19 @@ -- SELECT * -- FROM Triage -- WHERE findingUuid = "c00f2661-a1fc-4a72-83af-5894580ee510"; -SELECT A."accessToken", - A."avatarUrl", - A."created", - A."expires", - A."installationId", - A."login", - A."memberEmail", - B."orgId" -FROM "GitHubApp" A - INNER JOIN "Member" B ON A."memberEmail" = B."email"; +-- +-- DELETE FROM Finding; +-- DELETE FROM Triage; +-- DELETE FROM GitRepo; +-- DELETE FROM SARIFInfo; +-- DELETE FROM SarifResults; +-- DELETE FROM CycloneDXInfo; +-- DELETE FROM SPDXInfo; +-- DELETE FROM IntegrationUsageLog; +-- DELETE FROM Link; +-- DELETE FROM Artifact; +-- DELETE FROM Dependency; +-- +SELECT * +FROM `Triage` +WHERE remediation IS NOT NULL; diff --git a/functions/_middleware.js b/functions/_middleware.js new file mode 100644 index 00000000..329079a2 --- /dev/null +++ b/functions/_middleware.js @@ -0,0 +1,167 @@ +import { + AuthResult, + Client, + ensureStrReqBody, + hexStringToUint8Array, + isJSON, + unauthenticatedRoutes, +} from "@/utils"; +import { PrismaD1 } from '@prisma/adapter-d1'; +import { PrismaClient } from '@prisma/client'; + +export const errorHandling = async context => { + const { + request, // same as existing Worker API + env, // same as existing Worker API + params, // if filename includes [id] or [[path]] + waitUntil, // same as ctx.waitUntil in existing Worker API + next, // used for middleware or to fetch assets + data, // arbitrary space for passing data between middlewares + } = context + try { + return await next() + } catch (err) { + console.error(err.message, err.stack) + return new Response(err.message, { status: 500 }) + } +} + +// Respond to OPTIONS method +export const onRequestOptions = async () => { + return new Response(null, { + status: 204, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Max-Age': '86400', + }, + }) +} + +export const setup = async context => { + const { request, env, data, next } = context + const adapter = new PrismaD1(env.d1db) + const { searchParams } = new URL(request.url) + data.searchParams = searchParams + if (["POST", "PUT", "DELETE", "PATCH"].includes(request.method.toUpperCase())) { + data.body = await ensureStrReqBody(request) + if (isJSON(data.body)) { + data.json = JSON.parse(data.body) + } + } + const clientOptions = { + adapter, + transactionOptions: { + maxWait: 1500, // default: 2000 + timeout: 2000, // default: 5000 + }, + } + data.logger = () => { } + if (env.LOGGER === "DEBUG") { + data.logger = console.log + clientOptions.log = [ + { + emit: "event", + level: "query", + }, + ] + } + data.prisma = new PrismaClient(clientOptions) + data.prisma.$on("query", async e => { + data.logger(`${e.query} ${e.params}`) + }) + + return await next() +} + +export const authentication = async context => { + const { request, next, data } = context + const url = new URL(request.url) + if (request.cf.botManagement.verifiedBot || request.cf.botManagement.score <= 60) { + return new Response(JSON.stringify({ ok: false, error: { message: AuthResult.FORBIDDEN } }), { status: 403 }) + } + const authRequired = + !unauthenticatedRoutes.static.includes(url.pathname) && + !unauthenticatedRoutes.prefixes.map(i => url.pathname.startsWith(i)).includes(true) + + if (!authRequired || !url.pathname.startsWith('/api/')) { + return await next() + } + const method = request.method.toUpperCase() + const path = url.pathname + url.search + const body = ['GET', 'DELETE'].includes(method.toUpperCase()) ? '' : await ensureStrReqBody(request) + // Retrieve signature and timestamp from headers + const signature = request.headers.get('authorization')?.replace('HMAC ', '') + const timestampStr = request.headers.get('x-timestamp') + const kid = request.headers.get('x-vulnetix-kid') + + if (!signature || !timestampStr || !kid) { + return new Response(JSON.stringify({ ok: false, error: { message: AuthResult.FORBIDDEN } }), { status: 403 }) + } + + // Convert timestamp from string to integer + const timestamp = parseInt(timestampStr, 10) + if (isNaN(timestamp)) { + data.logger('Invalid timestamp format', timestamp) + return new Response(JSON.stringify({ ok: false, error: { message: AuthResult.FORBIDDEN } }), { status: 403 }) + } + // Validate timestamp (you may want to add a check to ensure the request isn't too old) + const currentTimestamp = new Date().getTime() + if (Math.abs(currentTimestamp - timestamp) > 3e+5) { // e.g., allow a 5-minute skew + data.logger('expired, skew', timestamp) + return new Response(JSON.stringify({ ok: false, error: { message: AuthResult.EXPIRED } }), { status: 401 }) + } + // Retrieve the session key from the database using Prisma + const session = await data.prisma.Session.findFirstOrThrow({ + where: { kid } + }) + if (!session.expiry || session.expiry <= new Date().getTime()) { + data.logger('expired', timestamp) + return new Response(JSON.stringify({ ok: false, error: { message: AuthResult.EXPIRED } }), { status: 401 }) + } + const secretKeyBytes = new TextEncoder().encode(session.secret) + const payloadBytes = Client.makePayload({ + method, + path, + kid, + timestamp, + body: encodeURIComponent(body) + }) + const key = await crypto.subtle.importKey( + "raw", + secretKeyBytes, + { name: "HMAC", hash: "SHA-512" }, + false, + ["verify"] + ) + + const signatureBytes = hexStringToUint8Array(signature) + const isValid = await crypto.subtle.verify("HMAC", key, signatureBytes, payloadBytes) + + if (!isValid) { + data.logger('Invalid signature', signature) + return new Response(JSON.stringify({ ok: false, error: { message: AuthResult.FORBIDDEN } }), { status: 401 }) + } + data.session = session + + return await next() +} + +// Set CORS to all /api responses +const dynamicHeaders = async context => { + const { + request, // same as existing Worker API + env, // same as existing Worker API + params, // if filename includes [id] or [[path]] + waitUntil, // same as ctx.waitUntil in existing Worker API + next, // used for middleware or to fetch assets + data, // arbitrary space for passing data between middlewares + } = context + const response = await next() + response.headers.set('Access-Control-Allow-Origin', '*') + response.headers.set('Access-Control-Max-Age', '86400') + return response +} + +export const onRequest = [errorHandling, setup, authentication, dynamicHeaders] diff --git a/functions/_middleware.ts b/functions/_middleware.ts deleted file mode 100644 index 964c20d4..00000000 --- a/functions/_middleware.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Respond to OPTIONS method -export const onRequestOptions: PagesFunction = async () => { - return new Response(null, { - status: 204, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Max-Age': '86400', - }, - }); -}; - -// Set CORS to all /api responses -export const onRequest: PagesFunction = async (context) => { - const response = await context.next(); - response.headers.set('Access-Control-Allow-Origin', '*'); - response.headers.set('Access-Control-Max-Age', '86400'); - return response; -}; diff --git a/functions/api/[analysisState]/issues.js b/functions/api/[analysisState]/issues.js index 9b72985e..6226ec13 100644 --- a/functions/api/[analysisState]/issues.js +++ b/functions/api/[analysisState]/issues.js @@ -1,6 +1,4 @@ -import { AuthResult, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestGet(context) { const { @@ -12,25 +10,12 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const analysisState = searchParams.get('analysisState') || 'in_triage' - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - const findings = await prisma.Finding.findMany({ + const analysisState = data.searchParams.get('analysisState') || 'in_triage' + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + const findings = await data.prisma.Finding.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, AND: { triage: { every: { analysisState } diff --git a/functions/api/[source]/integration.js b/functions/api/[source]/integration.js index 6a59541a..843b395b 100644 --- a/functions/api/[source]/integration.js +++ b/functions/api/[source]/integration.js @@ -1,6 +1,4 @@ -import { AuthResult, Server, ensureStrReqBody } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestPost(context) { const { @@ -15,45 +13,31 @@ export async function onRequestPost(context) { if (!['osv', 'first', 'vulncheck', 'github', 'mitre-cve'].includes((params?.source || '').toLowerCase())) { return Response.json({ ok: false, error: { message: `Invalid log source` }, results: [] }) } - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const bodyStr = await ensureStrReqBody(request) - const data = JSON.parse(bodyStr) const where = { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, AND: { name: params.source }, } - const original = await prisma.IntegrationConfig.findFirst({ where }) + const original = await data.prisma.IntegrationConfig.findFirst({ where }) if (original === null) { - const info = await prisma.IntegrationConfig.create({ + const info = await data.prisma.IntegrationConfig.create({ data: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, name: params.source, created: new Date().getTime(), - suspend: data?.suspend === undefined ? 0 : (data.suspend ? 1 : 0), + suspend: data.json?.suspend === undefined ? 0 : (data.json.suspend ? 1 : 0), } }) return Response.json({ ok: true, info }) } - if ((data?.suspend ? 1 : 0) === original.suspend) { + if ((data.json?.suspend ? 1 : 0) === original.suspend) { return Response.json({ ok: false, result: 'No Change' }) } - const info = await prisma.IntegrationConfig.update({ + const info = await data.prisma.IntegrationConfig.update({ where: { uuid: original.uuid }, data: { - suspend: data?.suspend === undefined ? original.suspend : (data.suspend ? 1 : 0), + suspend: data.json?.suspend === undefined ? original.suspend : (data.json.suspend ? 1 : 0), } }) return Response.json({ ok: true, info }) diff --git a/functions/api/[source]/log.js b/functions/api/[source]/log.js index f6bcb65d..66a47cf6 100644 --- a/functions/api/[source]/log.js +++ b/functions/api/[source]/log.js @@ -1,6 +1,4 @@ -import { AuthResult, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestGet(context) { const { @@ -15,24 +13,11 @@ export async function onRequestGet(context) { if (!['osv', 'first', 'vulncheck', 'github', 'mitre-cve'].includes((params?.source || '').toLowerCase())) { return Response.json({ ok: false, error: { message: `Invalid log source` }, results: [] }) } - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message, results: [] }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - let results = await prisma.IntegrationUsageLog.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + let results = await data.prisma.IntegrationUsageLog.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, source: params?.source, }, take, diff --git a/functions/api/analytics.js b/functions/api/analytics.js index 7e3dfaa1..896e1ecb 100644 --- a/functions/api/analytics.js +++ b/functions/api/analytics.js @@ -1,6 +1,4 @@ -import { AuthResult, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; import { ActionCISA, ActionFIRST } from "ssvc"; export async function onRequestGet(context) { @@ -13,22 +11,10 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const findings = await prisma.Finding.findMany({ + const findings = await data.prisma.Finding.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, include: { triage: true, diff --git a/functions/api/archive.js b/functions/api/archive.js index 05b25753..1768b97a 100644 --- a/functions/api/archive.js +++ b/functions/api/archive.js @@ -1,6 +1,4 @@ -import { AuthResult, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestGet(context) { const { @@ -12,24 +10,11 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - const findings = await prisma.Finding.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + const findings = await data.prisma.Finding.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, AND: { triage: { some: { analysisState: { in: ['resolved', 'resolved_with_pedigree', 'false_positive', 'not_affected'] } } diff --git a/functions/api/artifact/[uuid].js b/functions/api/artifact/[uuid].js index 1aaa7ba8..b87a5fe1 100644 --- a/functions/api/artifact/[uuid].js +++ b/functions/api/artifact/[uuid].js @@ -1,6 +1,3 @@ -import { Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestDelete(context) { const { @@ -11,19 +8,7 @@ export async function onRequestDelete(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const artifacts = await prisma.Artifact.findMany({ + const artifacts = await data.prisma.Artifact.findMany({ where: { uuid: params.uuid }, include: { downloadLinks: true, @@ -39,26 +24,26 @@ export async function onRequestDelete(context) { }) for (const artifact of artifacts) { if (artifact?.spdx?.spdxId) { - await prisma.SPDXInfo.delete({ where: { spdxId: artifact.spdx.spdxId } }) + await data.prisma.SPDXInfo.delete({ where: { spdxId: artifact.spdx.spdxId } }) } if (artifact?.cdx?.cdxId) { - await prisma.CycloneDXInfo.delete({ where: { cdxId: artifact.cdx.cdxId } }) + await data.prisma.CycloneDXInfo.delete({ where: { cdxId: artifact.cdx.cdxId } }) } if (artifact?.sarif?.results) { for (const result of artifact.sarif.results) { - await prisma.SarifResults.delete({ where: { guid: result.guid } }) + await data.prisma.SarifResults.delete({ where: { guid: result.guid } }) } } if (artifact?.sarif?.reportId) { - await prisma.SARIFInfo.delete({ where: { reportId: artifact.sarif.reportId } }) + await data.prisma.SARIFInfo.delete({ where: { reportId: artifact.sarif.reportId } }) } if (artifact?.vex?.uuid) { - await prisma.CycloneDXInfo.delete({ where: { cdxId: artifact.vex.uuid } }) + await data.prisma.CycloneDXInfo.delete({ where: { cdxId: artifact.vex.uuid } }) } for (const link of artifact.downloadLinks) { - await prisma.Link.delete({ where: { id: link.id } }) + await data.prisma.Link.delete({ where: { id: link.id } }) } - await prisma.Artifact.delete({ where: { uuid: artifact.uuid } }) + await data.prisma.Artifact.delete({ where: { uuid: artifact.uuid } }) } return Response.json({ ok: true, uuid: params.uuid }) diff --git a/functions/api/artifact/files.js b/functions/api/artifact/files.js index b4c0d12f..e578a764 100644 --- a/functions/api/artifact/files.js +++ b/functions/api/artifact/files.js @@ -1,6 +1,4 @@ -import { AuthResult, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestGet(context) { const { @@ -12,42 +10,29 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - } - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - const result = await prisma.Artifact.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + const result = await data.prisma.Artifact.findMany({ where: { OR: [ { spdx: { some: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, }, }, { sarif: { some: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, }, }, { cdx: { some: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, }, }, @@ -56,7 +41,7 @@ export async function onRequestGet(context) { some: { finding: { is: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, }, }, diff --git a/functions/api/cdx.js b/functions/api/cdx.js index c34f293a..ce7e1ec5 100644 --- a/functions/api/cdx.js +++ b/functions/api/cdx.js @@ -1,7 +1,5 @@ import { parseCycloneDXComponents } from "@/finding"; -import { AuthResult, ensureStrReqBody, hex, isCDX, OSV, saveArtifact, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult, hex, isCDX, OSV, saveArtifact } from "@/utils"; export async function onRequestGet(context) { @@ -13,24 +11,11 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - let cdx = await prisma.CycloneDXInfo.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + let cdx = await data.prisma.CycloneDXInfo.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, include: { repo: true, @@ -67,35 +52,21 @@ export async function onRequestPost(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } const files = [] let errors = new Set() try { - const body = await ensureStrReqBody(request) - const inputs = JSON.parse(body) - for (const cdx of inputs) { + for (const cdx of data.json) { if (!isCDX(cdx)) { return Response.json({ ok: false, error: { message: 'CDX is missing necessary fields.' } }) } const componentsJSON = JSON.stringify(cdx.components) const cdxId = await hex(cdx.metadata?.component?.name + componentsJSON) - const originalCdx = await prisma.CycloneDXInfo.findFirst({ + const originalCdx = await data.prisma.CycloneDXInfo.findFirst({ where: { cdxId, - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, } }) const artifactUuid = originalCdx?.artifactUuid || (cdx?.serialNumber?.startsWith('urn:uuid:') ? cdx.serialNumber.substring(9) : crypto.randomUUID()) @@ -104,7 +75,7 @@ export async function onRequestPost(context) { } const dependencies = [] for (const dep of await parseCycloneDXComponents(cdx, cdxId)) { - const info = await prisma.Dependency.upsert({ + const info = await data.prisma.Dependency.upsert({ where: { cdx_dep: { cdxId, @@ -118,10 +89,11 @@ export async function onRequestPost(context) { }, create: { ...dep, cdxId } }) + data.logger(`Dependency ${cdxId}`, info) dependencies.push({ ...dep, cdxId }) } const cdxStr = JSON.stringify(cdx) - const artifact = await saveArtifact(prisma, env.r2artifacts, cdxStr, artifactUuid, `cyclonedx`) + const artifact = await saveArtifact(data.prisma, env.r2artifacts, cdxStr, artifactUuid, `cyclonedx`) const cdxData = { cdxId, source: 'upload', @@ -132,10 +104,10 @@ export async function onRequestPost(context) { createdAt: cdx.metadata?.timestamp ? new Date(cdx.metadata.timestamp).getTime() : new Date().getTime(), toolName: cdx.metadata.tools.map(t => `${t?.vendor} ${t?.name} ${t?.version}`.trim()).join(', '), } - const info = await prisma.CycloneDXInfo.upsert({ + const info = await data.prisma.CycloneDXInfo.upsert({ where: { cdxId, - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, update: { createdAt: cdxData.createdAt, @@ -143,11 +115,11 @@ export async function onRequestPost(context) { }, create: { ...cdxData, - org: { connect: { uuid: verificationResult.session.orgId } }, + org: { connect: { uuid: data.session.orgId } }, artifact: { connect: { uuid: artifactUuid } }, } }) - // console.log(`/upload/cdx ${cdxId} kid=${verificationResult.session.kid}`, info) + data.logger(`/upload/cdx ${cdxId} kid=${data.session.kid}`, info) cdxData.dependencies = dependencies files.push(cdxData) @@ -174,7 +146,7 @@ export async function onRequestPost(context) { } return { package: { name: q.name }, version: q.version } }) - const results = await osv.queryBatch(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail, queries) + const results = await osv.queryBatch(data.prisma, data.session.orgId, data.session.memberEmail, queries) let i = 0 for (const result of results) { const { purl = null, name, version, license } = osvQueries[i] @@ -182,10 +154,10 @@ export async function onRequestPost(context) { if (!vuln?.id) { continue } - const findingId = await hex(`${verificationResult.session.orgId}${vuln.id}${name}${version}`) + const findingId = await hex(`${data.session.orgId}${vuln.id}${name}${version}`) const findingData = { findingId, - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, source: 'osv.dev', category: 'sca', createdAt: (new Date()).getTime(), @@ -198,17 +170,17 @@ export async function onRequestPost(context) { malicious: vuln.id.startsWith("MAL-") ? 1 : 0, cdxId } - const originalFinding = await prisma.Finding.findFirst({ + const originalFinding = await data.prisma.Finding.findFirst({ where: { findingId, AND: { - orgId: verificationResult.session.orgId + orgId: data.session.orgId }, } }) let finding; if (originalFinding) { - finding = await prisma.Finding.update({ + finding = await data.prisma.Finding.update({ where: { uuid: originalFinding.uuid, }, @@ -221,9 +193,9 @@ export async function onRequestPost(context) { }, }) } else { - finding = await prisma.Finding.create({ data: findingData }) + finding = await data.prisma.Finding.create({ data: findingData }) } - // console.log(`findings SCA`, finding) + data.logger(`findings SCA`, finding) // TODO lookup EPSS const vexData = { findingUuid: finding.uuid, @@ -232,7 +204,7 @@ export async function onRequestPost(context) { seen: 0, analysisState: 'in_triage' } - const originalVex = await prisma.Triage.findFirst({ + const originalVex = await data.prisma.Triage.findFirst({ where: { findingUuid: finding.uuid, analysisState: 'in_triage', @@ -240,7 +212,7 @@ export async function onRequestPost(context) { }) let vex; if (originalVex) { - vex = await prisma.Triage.update({ + vex = await data.prisma.Triage.update({ where: { uuid: originalVex.uuid, }, @@ -249,9 +221,9 @@ export async function onRequestPost(context) { }, }) } else { - vex = await prisma.Triage.create({ data: vexData }) + vex = await data.prisma.Triage.create({ data: vexData }) } - // console.log(`findings VEX`, vex) + data.logger(`findings VEX`, vex) } i++ } diff --git a/functions/api/exploitable.js b/functions/api/exploitable.js index e0885e69..ad4ff7be 100644 --- a/functions/api/exploitable.js +++ b/functions/api/exploitable.js @@ -1,6 +1,4 @@ -import { AuthResult, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestGet(context) { const { @@ -12,24 +10,11 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - const findings = await prisma.Finding.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + const findings = await data.prisma.Finding.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, triage: { every: { analysisState: 'exploitable' } } }, include: { diff --git a/functions/api/github/[installation_id]/uninstall.js b/functions/api/github/[installation_id]/uninstall.js index c6e326a5..bd6be262 100644 --- a/functions/api/github/[installation_id]/uninstall.js +++ b/functions/api/github/[installation_id]/uninstall.js @@ -1,6 +1,4 @@ -import { AuthResult, GitHub, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult, GitHub } from "@/utils"; export async function onRequestGet(context) { const { @@ -11,30 +9,18 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } try { const where = { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, installationId: parseInt(params.installation_id, 10), } - const app = await prisma.GitHubApp.findUniqueOrThrow({ where }) - const gh = new GitHub(app.accessToken) - const result = await gh.revokeToken(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail) + const app = await data.prisma.GitHubApp.findUniqueOrThrow({ where }) + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, app.accessToken) + const result = await gh.revokeToken() if ([204, 401].includes(result.status)) { - const response = await prisma.GitHubApp.delete({ where }) - // console.log(`/github/uninstall session kid=${verificationResult.session.token}`, response) + const response = await data.prisma.GitHubApp.delete({ where }) + data.logger(`/github/uninstall session kid=${data.session.token}`, response) return Response.json(response) } diff --git a/functions/api/github/[patId]/remove.js b/functions/api/github/[patId]/remove.js index 0afb7e2c..3ecebe6d 100644 --- a/functions/api/github/[patId]/remove.js +++ b/functions/api/github/[patId]/remove.js @@ -1,6 +1,3 @@ -import { Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestDelete(context) { const { @@ -11,33 +8,21 @@ export async function onRequestDelete(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const patInfo = await prisma.GitHubPAT.delete({ + const patInfo = await data.prisma.GitHubPAT.delete({ where: { keyId: parseInt(params.patId, 10), } }) - // console.log(`/github/[${params.patId}]/remove github_pat`, patInfo) - const tokenInfo = await prisma.MemberKey.delete({ + data.logger(`/github/[${params.patId}]/remove github_pat`, patInfo) + const tokenInfo = await data.prisma.MemberKey.delete({ where: { id: parseInt(params.patId, 10), - memberEmail: verificationResult.session.memberEmail, + memberEmail: data.session.memberEmail, } }) tokenInfo.secretMasked = mask(tokenInfo.secret) delete tokenInfo.secret - // console.log(`/github/[${params.patId}]/remove github_pat`, tokenInfo) + data.logger(`/github/[${params.patId}]/remove github_pat`, tokenInfo) return Response.json(tokenInfo) } const mask = s => s.slice(0, 11) + s.slice(10).slice(4, s.length - 4).replace(/(.)/g, '*') + s.slice(s.length - 4) diff --git a/functions/api/github/[installation_id]/install/[code].js b/functions/api/github/install/[installation_id]/[code].js similarity index 77% rename from functions/api/github/[installation_id]/install/[code].js rename to functions/api/github/install/[installation_id]/[code].js index a7f58b7e..394cfe0d 100644 --- a/functions/api/github/[installation_id]/install/[code].js +++ b/functions/api/github/install/[installation_id]/[code].js @@ -1,6 +1,4 @@ import { appExpiryPeriod, AuthResult, GitHub, hex, pbkdf2, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestGet(context) { const { @@ -11,16 +9,7 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) try { - const app = new Server(request, prisma) let oauthData if (params?.code && params?.installation_id) { const method = "POST" @@ -45,7 +34,7 @@ export async function onRequestGet(context) { if (!oauthData?.access_token) { return Response.json({ ok: false, error: { message: 'OAuth authorization failed' } }) } - const gh = new GitHub(oauthData.access_token) + const gh = new GitHub(data.prisma, data?.session?.orgId, data?.session?.memberEmail, oauthData.access_token) const created = (new Date()).getTime() const response = { ok: false, installationId: parseInt(params.installation_id, 10), session: {}, member: {} } const { content, error, tokenExpiry } = await gh.getUser() @@ -53,8 +42,7 @@ export async function onRequestGet(context) { return Response.json({ ok: false, error }) } const expires = tokenExpiry || appExpiryPeriod + created - const verificationResult = await app.authenticate() - let memberEmail = verificationResult?.session?.memberEmail + let memberEmail = data?.session?.memberEmail if (!memberEmail) { const ghUserEmails = await gh.getUserEmails() if (!ghUserEmails?.ok || ghUserEmails?.error?.message || !ghUserEmails?.content || !ghUserEmails.content?.length) { @@ -70,7 +58,7 @@ export async function onRequestGet(context) { memberEmail = content.email.toLowerCase() } } - const memberCheck = await app.memberExists(memberEmail) + const memberCheck = await (new Server(request, data.prisma)).memberExists(memberEmail) if (memberCheck.exists) { response.member = memberCheck.member } else { @@ -83,7 +71,7 @@ export async function onRequestGet(context) { lastName = words.join(' ') || '' } if (content?.company) { - const originalOrg = await prisma.Org.findFirst({ + const originalOrg = await data.prisma.Org.findFirst({ where: { name: content.company } @@ -91,22 +79,22 @@ export async function onRequestGet(context) { if (originalOrg?.uuid) { orgId = originalOrg.uuid } else { - const orgInfo = await prisma.Org.create({ + const orgInfo = await data.prisma.Org.create({ data: { uuid: orgId, name: content.company, } }) - // console.log(`/github/install register orgId=${orgId}`, orgInfo) + data.logger(`/github/install register orgId=${orgId}`, orgInfo) } } else { - const orgInfo = await prisma.Org.create({ + const orgInfo = await data.prisma.Org.create({ data: { uuid: orgId, name: memberEmail.toLowerCase(), } }) - // console.log(`/github/install register orgId=${orgId}`, orgInfo) + data.logger(`/github/install register orgId=${orgId}`, orgInfo) } response.member = { @@ -117,10 +105,10 @@ export async function onRequestGet(context) { firstName, lastName } - const memberInfo = await prisma.Member.create({ + const memberInfo = await data.prisma.Member.create({ data: response.member }) - // console.log(`/github/install register email=${memberEmail}`, memberInfo) + data.logger(`/github/install register email=${memberEmail}`, memberInfo) delete response.member.passwordHash } const token = crypto.randomUUID() @@ -138,13 +126,13 @@ export async function onRequestGet(context) { authn_ip, authn_ua } - const sessionInfo = await prisma.Session.create({ + const sessionInfo = await data.prisma.Session.create({ data: response.session }) - // console.log(`/github/install session kid=${token}`, sessionInfo) + data.logger(`/github/install session kid=${token}`, sessionInfo) const appData = { installationId: parseInt(params.installation_id, 10), - memberEmail: response.member.email, + org: { connect: { uuid: response.member.orgId } }, accessToken: oauthData.access_token, login: content.login, avatarUrl: content?.avatar_url, @@ -152,24 +140,24 @@ export async function onRequestGet(context) { expires } try { - await prisma.GitHubApp.findUniqueOrThrow({ where: { login: appData.login } }) - const GHAppInfo = await prisma.GitHubApp.update({ + await data.prisma.GitHubApp.findUniqueOrThrow({ where: { login: appData.login } }) + const GHAppInfo = await data.prisma.GitHubApp.update({ where: { login: appData.login }, data: { accessToken: appData.accessToken, expires: appData.expires, } }) - // console.log(`/github/install installationId=${params.installation_id}`, GHAppInfo) + data.logger(`/github/install installationId=${params.installation_id}`, GHAppInfo) return data } catch (_) { // No records to update OAuth token } - const GHAppInfo = await prisma.GitHubApp.create({ + const GHAppInfo = await data.prisma.GitHubApp.create({ data: appData }) - // console.log(`/github/install installationId=${params.installation_id}`, GHAppInfo) + data.logger(`/github/install installationId=${params.installation_id}`, GHAppInfo) response.result = AuthResult.AUTHENTICATED response.ok = true return Response.json(response) diff --git a/functions/api/github/pat.js b/functions/api/github/pat.js index d0fa3c4b..7757e4b5 100644 --- a/functions/api/github/pat.js +++ b/functions/api/github/pat.js @@ -1,6 +1,4 @@ -import { GitHub, Server, ensureStrReqBody } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { GitHub } from "@/utils"; export async function onRequestPost(context) { const { @@ -11,45 +9,31 @@ export async function onRequestPost(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const bodyStr = await ensureStrReqBody(request) - const body = JSON.parse(bodyStr) - if (!body.token.startsWith('github_pat_')) { + if (!data.json.token.startsWith('github_pat_')) { return Response.json({ error: { message: `Invalid PAT provided, expected "github_pat_" prefix.` } }) } - const tokenInfo = await prisma.MemberKey.upsert({ + const tokenInfo = await data.prisma.MemberKey.upsert({ where: { memberEmail_secret: { - memberEmail: verificationResult.session.memberEmail, - secret: body.token, + memberEmail: data.session.memberEmail, + secret: data.json.token, } }, update: { - keyLabel: body.label, + keyLabel: data.json.label, }, create: { - memberEmail: verificationResult.session.memberEmail, - keyLabel: body.label, + memberEmail: data.session.memberEmail, + keyLabel: data.json.label, keyType: 'github_pat', - secret: body.token, + secret: data.json.token, } }) tokenInfo.secretMasked = mask(tokenInfo.secret) delete tokenInfo.secret - // console.log(`/github/pat github_pat label=${body.label}`, tokenInfo) - const gh = new GitHub(body.token) - const { content, error, tokenExpiry } = await gh.getUser(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail) + data.logger(`/github/pat github_pat label=${data.json.label}`, tokenInfo) + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, data.json.token) + const { content, error, tokenExpiry } = await gh.getUser() if (error?.message) { return Response.json({ error }) } @@ -60,7 +44,7 @@ export async function onRequestPost(context) { created: content?.created_at ? (new Date(content.created_at)).getTime() : (new Date()).getTime(), avatarUrl: content?.avatar_url, } - const patInfo = await prisma.GitHubPAT.upsert({ + const patInfo = await data.prisma.GitHubPAT.upsert({ where: { keyId: tokenInfo.githubPat.keyId, }, @@ -70,10 +54,10 @@ export async function onRequestPost(context) { }, create: { ...tokenInfo.githubPat, - memberEmail: verificationResult.session.memberEmail, + memberEmail: data.session.memberEmail, }, }) - // console.log(`/github/pat github_pat label=${body.label}`, patInfo) + data.logger(`/github/pat github_pat label=${data.json.label}`, patInfo) return Response.json(tokenInfo) } diff --git a/functions/api/github/repos.js b/functions/api/github/repos.js index 8c1b8219..155f3ff2 100644 --- a/functions/api/github/repos.js +++ b/functions/api/github/repos.js @@ -1,6 +1,4 @@ -import { GitHub, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { GitHub } from "@/utils"; export async function onRequestGet(context) { const { @@ -11,46 +9,25 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const githubIntegration = await prisma.IntegrationConfig.findFirst({ - where: { - orgId: verificationResult.session.orgId, - AND: { name: `github` }, - } - }) - if (!!githubIntegration?.suspend) { - return Response.json({ ok: false, error: { message: 'GitHub Disabled' } }) - } const githubApps = [] const gitRepos = [] - const installs = await prisma.GitHubApp.findMany({ + const installs = await data.prisma.GitHubApp.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, AND: { expires: { gte: (new Date()).getTime(), } } }, }) for (const app of installs) { if (!app.accessToken) { - // console.log(`github_apps kid=${verificationResult.session.kid} installationId=${app.installationId}`) + data.logger(`github_apps kid=${data.session.kid} installationId=${app.installationId}`) throw new Error('github_apps invalid') } - const gh = new GitHub(app.accessToken) - const { content, error } = await gh.getRepos(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail) + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, app.accessToken) + const { content, error } = await gh.getRepos() if (error?.message) { if ("Bad credentials" === error.message) { app.expires = (new Date()).getTime() - await prisma.GitHubApp.update({ + await data.prisma.GitHubApp.update({ where: { installationId: parseInt(app.installationId, 10), AND: { orgId: app.orgId, }, @@ -63,10 +40,8 @@ export async function onRequestGet(context) { return Response.json({ error, app }) } - for (const repo of content) { - const data = await store(prisma, verificationResult.session, repo) - gitRepos.push(data) - } + const repos = await Promise.all(content.map(repo => store(data.prisma, data.session, repo))) + gitRepos.push(...repos) githubApps.push({ installationId: parseInt(app.installationId, 10), login: app.login, @@ -76,25 +51,25 @@ export async function onRequestGet(context) { }) } - const memberKeys = await prisma.MemberKey.findMany({ + const memberKeys = await data.prisma.MemberKey.findMany({ where: { - memberEmail: verificationResult.session.memberEmail, + memberEmail: data.session.memberEmail, keyType: 'github_pat', }, }) for (const memberKey of memberKeys) { - const gh = new GitHub(memberKey.secret) - const { content, error } = await gh.getRepos(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail) + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, memberKey.secret) + const { content, error } = await gh.getRepos() if (error?.message) { return Response.json({ error, app: { login: memberKey.keyLabel } }) } - for (const repo of content) { - const data = await store(prisma, verificationResult.session, repo) - gitRepos.push(data) - } + const repos = await Promise.all(content.map(repo => store(data.prisma, data.session, repo))) + gitRepos.push(...repos) } - return Response.json({ githubApps, gitRepos }) + return Response.json({ + githubApps, gitRepos + }) } const store = async (prisma, session, repo) => { const create = { diff --git a/functions/api/github/repos/[org]/[repo]/branches.js b/functions/api/github/repos/[org]/[repo]/branches.js new file mode 100644 index 00000000..b49bada6 --- /dev/null +++ b/functions/api/github/repos/[org]/[repo]/branches.js @@ -0,0 +1,95 @@ +import { GitHub } from "@/utils"; + +export async function onRequestGet(context) { + const { + request, // same as existing Worker API + env, // same as existing Worker API + params, // if filename includes [id] or [[path]] + waitUntil, // same as ctx.waitUntil in existing Worker API + next, // used for middleware or to fetch assets + data, // arbitrary space for passing data between middlewares + } = context + const repoName = `${params.org}/${params.repo}` + const errors = [] + const responses = [] + const branches = [] + const branchSeen = [] + + const githubApps = await data.prisma.GitHubApp.findMany({ + where: { + orgId: data.session.orgId, + }, + }) + + for (const app of githubApps) { + if (!app.accessToken) { + data.logger(`github_apps kid=${data.session.kid} installationId=${app.installationId}`) + throw new Error('github_apps invalid') + } + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, app.accessToken) + const { content, error } = await gh.getBranches(repoName) + if (error?.message) { + if ("Bad credentials" === error.message) { + app.expires = (new Date()).getTime() + await data.prisma.GitHubApp.update({ + where: { + installationId: parseInt(app.installationId, 10), + AND: { orgId: app.orgId }, + }, + data: app, + }) + continue + } + delete app.accessToken + errors.push({ error, app }) + continue + } + responses.push(...content) + } + + const memberKeys = await data.prisma.MemberKey.findMany({ + where: { + memberEmail: data.session.memberEmail, + keyType: 'github_pat', + }, + }) + + for (const memberKey of memberKeys) { + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, memberKey.secret) + const { content, error } = await gh.getBranches(repoName) + if (error?.message) { + errors.push({ error, app: { login: memberKey.keyLabel } }) + continue + } + responses.push(...content) + } + + for (const response of responses) { + if (response.name in branchSeen) { + continue + } + const gitBranch = await data.prisma.GitBranch.upsert({ + where: { + repoName_name: { + name: response.name, + repoName, + } + }, + create: { + name: response.name, + repoName, + commitSha: response?.commit?.sha, + protected: response.protected ? 1 : 0, + orgId: data.session.orgId, + }, + update: { + commitSha: response?.commit?.sha, + protected: response.protected ? 1 : 0, + }, + }) + branches.push(gitBranch) + branchSeen.push(response.name) + } + + return Response.json({ ok: !errors.length, error: { message: errors.join('. ') || null }, branches }) +} diff --git a/functions/api/github/repos/[org]/[repo]/sarif.js b/functions/api/github/repos/[org]/[repo]/sarif.js index 98347a28..f90f51bc 100644 --- a/functions/api/github/repos/[org]/[repo]/sarif.js +++ b/functions/api/github/repos/[org]/[repo]/sarif.js @@ -1,6 +1,4 @@ -import { GitHub, Server, saveArtifact } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { GitHub, saveArtifact } from "@/utils"; export async function onRequestGet(context) { const { @@ -11,47 +9,26 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const githubIntegration = await prisma.IntegrationConfig.findFirst({ - where: { - orgId: verificationResult.session.orgId, - AND: { name: `github` }, - } - }) - if (!!githubIntegration?.suspend) { - return Response.json({ ok: false, error: { message: 'GitHub Disabled' } }) - } const errors = [] - const githubApps = await prisma.GitHubApp.findMany({ + const githubApps = await data.prisma.GitHubApp.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, }) const repoName = `${params.org}/${params.repo}` const files = [] for (const app of githubApps) { if (!app.accessToken) { - console.error(`Invalid github_apps kid=${verificationResult.session.kid} installationId=${app.installationId}`) + console.error(`Invalid github_apps kid=${data.session.kid} installationId=${app.installationId}`) continue } - const gh = new GitHub(app.accessToken) + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, app.accessToken) - const { content, error } = await gh.getRepoSarif(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail, repoName) + const { content, error } = await gh.getRepoSarif(repoName) if (error?.message) { if ("Bad credentials" === error.message) { app.expires = (new Date()).getTime() - await prisma.GitHubApp.update({ + await data.prisma.GitHubApp.update({ where: { installationId: parseInt(app.installationId, 10), AND: { orgId: app.orgId }, @@ -65,27 +42,27 @@ export async function onRequestGet(context) { continue } for (const data of content) { - const artifact = await saveArtifact(prisma, env.r2artifacts, JSON.stringify(data.sarif), data?.report?.sarif_id ? data.report.sarif_id : crypto.randomUUID(), `sarif`) - files.push(await process(prisma, verificationResult.session, data, repoName, artifact.uuid)) + const artifact = await saveArtifact(data.prisma, env.r2artifacts, JSON.stringify(data.sarif), data?.report?.sarif_id ? data.report.sarif_id : crypto.randomUUID(), `sarif`) + files.push(await process(data.prisma, data.session, data, repoName, artifact.uuid)) } } - const memberKeys = await prisma.MemberKey.findMany({ + const memberKeys = await data.prisma.MemberKey.findMany({ where: { - memberEmail: verificationResult.session.memberEmail, + memberEmail: data.session.memberEmail, keyType: 'github_pat', }, }) for (const memberKey of memberKeys) { - const gh = new GitHub(memberKey.secret) - const { content, error } = await gh.getRepoSarif(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail, repoName) + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, memberKey.secret) + const { content, error } = await gh.getRepoSarif(repoName) if (error?.message) { errors.push({ error, app: { login: memberKey.keyLabel } }) continue } for (const data of content) { - const artifact = await saveArtifact(prisma, env.r2artifacts, JSON.stringify(data.sarif), data.report.sarif_id, `sarif`) - files.push(await process(prisma, verificationResult.session, data, repoName, artifact.uuid)) + const artifact = await saveArtifact(data.prisma, env.r2artifacts, JSON.stringify(data.sarif), data.report.sarif_id, `sarif`) + files.push(await process(data.prisma, data.session, data, repoName, artifact.uuid)) } } diff --git a/functions/api/github/repos/[org]/[repo]/spdx.js b/functions/api/github/repos/[org]/[repo]/spdx.js index 07ec1f2c..3e22e046 100644 --- a/functions/api/github/repos/[org]/[repo]/spdx.js +++ b/functions/api/github/repos/[org]/[repo]/spdx.js @@ -1,7 +1,5 @@ import { parsePackageRef, parseSPDXComponents } from "@/finding"; -import { GitHub, hex, isSPDX, OSV, saveArtifact, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { GitHub, hex, isSPDX, OSV, saveArtifact } from "@/utils"; export async function onRequestGet(context) { const { @@ -12,48 +10,27 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const githubIntegration = await prisma.IntegrationConfig.findFirst({ - where: { - orgId: verificationResult.session.orgId, - AND: { name: `github` }, - } - }) - if (!!githubIntegration?.suspend) { - return Response.json({ ok: false, error: { message: 'GitHub Disabled' } }) - } const repoName = `${params.org}/${params.repo}` const errors = [] const files = [] let findings = [] - const githubApps = await prisma.GitHubApp.findMany({ + const githubApps = await data.prisma.GitHubApp.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, }) for (const app of githubApps) { if (!app.accessToken) { - // console.log(`github_apps kid=${verificationResult.session.kid} installationId=${app.installationId}`) + data.logger(`github_apps kid=${data.session.kid} installationId=${app.installationId}`) throw new Error('github_apps invalid') } - const gh = new GitHub(app.accessToken) - const { content, error } = await gh.getRepoSpdx(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail, repoName) + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, app.accessToken) + const { content, error } = await gh.getRepoSpdx(repoName) if (error?.message) { if ("Bad credentials" === error.message) { app.expires = (new Date()).getTime() - await prisma.GitHubApp.update({ + await data.prisma.GitHubApp.update({ where: { installationId: parseInt(app.installationId, 10), AND: { orgId: app.orgId }, @@ -75,22 +52,22 @@ export async function onRequestGet(context) { } const spdx = content.sbom const spdxId = await makeId(spdx) - const originalSpdx = await prisma.SPDXInfo.findFirst({ + const originalSpdx = await data.prisma.SPDXInfo.findFirst({ where: { spdxId, - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, } }) let artifact; if (!originalSpdx) { const spdxStr = JSON.stringify(spdx) - artifact = await saveArtifact(prisma, env.r2artifacts, spdxStr, crypto.randomUUID(), `spdx`) + artifact = await saveArtifact(data.prisma, env.r2artifacts, spdxStr, crypto.randomUUID(), `spdx`) } - const findingIds = await process(prisma, verificationResult.session, repoName, spdx, spdxId, originalSpdx?.artifactUuid || artifact?.uuid) + const findingIds = await process(data.prisma, data.session, repoName, spdx, spdxId, originalSpdx?.artifactUuid || artifact?.uuid) findings = [...findings, ...findingIds] const dependencies = [] for (const dep of await parseSPDXComponents(spdx, spdxId)) { - const info = await prisma.Dependency.upsert({ + const info = await data.prisma.Dependency.upsert({ where: { spdx_dep: { spdxId, @@ -105,20 +82,20 @@ export async function onRequestGet(context) { create: { ...dep, spdxId } }) dependencies.push({ ...dep, spdxId }) - // console.log(`Dependency ${dep.name}@${dep.version}`, info) + data.logger(`Dependency ${dep.name}@${dep.version}`, info) } spdx.dependencies = dependencies files.push({ spdx, errors }) } - const memberKeys = await prisma.MemberKey.findMany({ + const memberKeys = await data.prisma.MemberKey.findMany({ where: { - memberEmail: verificationResult.session.memberEmail, + memberEmail: data.session.memberEmail, keyType: 'github_pat', }, }) for (const memberKey of memberKeys) { - const gh = new GitHub(memberKey.secret) - const { content, error } = await gh.getRepoSpdx(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail, repoName) + const gh = new GitHub(data.prisma, data.session.orgId, data.session.memberEmail, memberKey.secret) + const { content, error } = await gh.getRepoSpdx(repoName) if (error?.message) { errors.push({ error, app: { login: memberKey.keyLabel } }) continue @@ -132,18 +109,18 @@ export async function onRequestGet(context) { } const spdx = content.sbom const spdxId = await makeId(spdx) - const originalSpdx = await prisma.SPDXInfo.findFirst({ + const originalSpdx = await data.prisma.SPDXInfo.findFirst({ where: { spdxId, - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, } }) let artifact; if (!originalSpdx) { const spdxStr = JSON.stringify(spdx) - artifact = await saveArtifact(prisma, env.r2artifacts, spdxStr, crypto.randomUUID(), `spdx`) + artifact = await saveArtifact(data.prisma, env.r2artifacts, spdxStr, crypto.randomUUID(), `spdx`) } - const findingIds = await process(prisma, verificationResult.session, repoName, spdx, spdxId, originalSpdx?.artifactUuid || artifact?.uuid) + const findingIds = await process(data.prisma, data.session, repoName, spdx, spdxId, originalSpdx?.artifactUuid || artifact?.uuid) findings = [...findings, ...findingIds] files.push({ spdx, errors }) } diff --git a/functions/api/github/repos/cached.js b/functions/api/github/repos/cached.js index 7e0fc934..cdf2771b 100644 --- a/functions/api/github/repos/cached.js +++ b/functions/api/github/repos/cached.js @@ -1,6 +1,3 @@ -import { Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestGet(context) { const { @@ -11,24 +8,11 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - const gitRepos = await prisma.GitRepo.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + const gitRepos = await data.prisma.GitRepo.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, take, skip, diff --git a/functions/api/issue/[uuid].js b/functions/api/issue/[uuid].js index 007f5976..e2be2503 100644 --- a/functions/api/issue/[uuid].js +++ b/functions/api/issue/[uuid].js @@ -1,15 +1,11 @@ import { processFinding } from '@/finding'; import { AuthResult, - ensureStrReqBody, - Server, VexAnalysisJustification, VexAnalysisResponse, VexAnalysisState } from "@/utils"; import { CVSS30, CVSS31, CVSS40 } from '@pandatix/js-cvss'; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestPost(context) { const { @@ -21,74 +17,62 @@ export async function onRequestPost(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const body = await ensureStrReqBody(request) - const input = JSON.parse(body) const { uuid } = params - const finding = await prisma.Finding.findFirst({ where: { uuid } }) + const finding = await data.prisma.Finding.findFirst({ where: { uuid } }) if (!finding) { return Response.json({ ok: false, error: { message: 'Dependency failed' } }) } // CVSS vector customisation - if (input?.customCvssVector) { + if (data.json?.customCvssVector) { let customCvssScore - if (input.customCvssVector.startsWith('CVSS:4.0/')) { - customCvssScore = new CVSS40(input.customCvssVector).Score().toString() - } else if (input.customCvssVector.startsWith('CVSS:3.1/')) { - customCvssScore = new CVSS31(input.customCvssVector).BaseScore().toString() - } else if (input.customCvssVector.startsWith('CVSS:3.0/')) { - customCvssScore = new CVSS30(input.customCvssVector).BaseScore().toString() + if (data.json.customCvssVector.startsWith('CVSS:4.0/')) { + customCvssScore = new CVSS40(data.json.customCvssVector).Score().toString() + } else if (data.json.customCvssVector.startsWith('CVSS:3.1/')) { + customCvssScore = new CVSS31(data.json.customCvssVector).BaseScore().toString() + } else if (data.json.customCvssVector.startsWith('CVSS:3.0/')) { + customCvssScore = new CVSS30(data.json.customCvssVector).BaseScore().toString() } else { return Response.json({ ok: false, error: { message: 'Invalid CVSS vectorString' } }) } - const info = await prisma.Finding.update({ + const info = await data.prisma.Finding.update({ where: { uuid }, data: { - customCvssVector: input?.customCvssVector, + customCvssVector: data.json?.customCvssVector, customCvssScore, modifiedAt: new Date().getTime(), } }) - // console.log(`Update Finding ${uuid}`, info) + data.logger(`Update Finding ${uuid}`, info) return Response.json({ ok: true, result: `Update Finding ${uuid} CVSS custom vector` }) } // Triage if ( - input?.analysisState && input?.analysisResponse && input?.analysisJustification - && VexAnalysisState?.[input.analysisState] - && VexAnalysisResponse?.[input.analysisResponse] - && VexAnalysisJustification?.[input.analysisJustification] + data.json?.analysisState && data.json?.analysisResponse && data.json?.analysisJustification + && VexAnalysisState?.[data.json.analysisState] + && VexAnalysisResponse?.[data.json.analysisResponse] + && VexAnalysisJustification?.[data.json.analysisJustification] ) { - const where = { - findingUuid: uuid, - AND: { - analysisState: 'in_triage' - } + let triage = null + const triageRecords = await data.prisma.Triage.findMany({ where: { findingUuid: uuid } }) + if (triageRecords && triageRecords.filter(t => t.analysisState === 'in_triage')) { + triage = triageRecords.filter(t => t.analysisState === 'in_triage').pop() + } + if (!triage && triageRecords) { + triage = triageRecords.sort((a, b) => b.lastObserved - a.lastObserved).pop() } - const triage = await prisma.Triage.findFirst({ where }) const triageData = { uuid: crypto.randomUUID(), findingUuid: uuid, - analysisState: input.analysisState, - analysisResponse: input.analysisResponse, - analysisJustification: input.analysisJustification, - analysisDetail: input?.analysisDetail || '', //TODO add commit hash and comment + analysisState: data.json.analysisState, + analysisResponse: data.json.analysisResponse, + analysisJustification: data.json.analysisJustification, + analysisDetail: data.json?.analysisDetail || '', //TODO add commit hash and comment triagedAt: new Date().getTime(), createdAt: new Date().getTime(), lastObserved: new Date().getTime(), seen: 1, triageAutomated: 0, + memberEmail: data.session.memberEmail } if (triage) { triageData.uuid = triage.uuid @@ -116,15 +100,15 @@ export async function onRequestPost(context) { } let info; if (triage) { - info = await prisma.Triage.update({ + info = await data.prisma.Triage.update({ where: { uuid: triageData.uuid }, data: triageData, }) } else { - info = await prisma.Triage.create({ data: triageData }) + info = await data.prisma.Triage.create({ data: triageData }) } - // console.log(`Upsert Triage ${triageData.uuid}`, info) - return Response.json({ ok: true, triage }) + data.logger(`Upsert Triage ${triageData.uuid}`, info) + return Response.json({ ok: true, triage: triageData }) } return Response.json({ ok: false, error: { message: "Required arguments not provided" } }) @@ -144,25 +128,12 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const seen = parseInt(searchParams.get('seen'), 10) || 0 + const seen = parseInt(data.searchParams.get('seen'), 10) || 0 const { uuid } = params - let finding = await prisma.Finding.findUnique({ + let finding = await data.prisma.Finding.findUnique({ where: { uuid, - AND: { orgId: verificationResult.session.orgId } + AND: { orgId: data.session.orgId } }, include: { triage: true, @@ -193,7 +164,7 @@ export async function onRequestGet(context) { if (!finding) { return new Response(null, { status: 404 }) } - finding = await processFinding(prisma, env.r2artifacts, verificationResult, finding, seen) + finding = await processFinding(data.prisma, env.r2artifacts, data.session, finding, seen) let spdxJson, cdxJson; if (finding?.spdx?.artifact?.uuid) { diff --git a/functions/api/login/[email]/[hash].js b/functions/api/login/[email]/[hash].js index e9fea35f..39c12cd5 100644 --- a/functions/api/login/[email]/[hash].js +++ b/functions/api/login/[email]/[hash].js @@ -1,6 +1,4 @@ import { AuthResult, hex, pbkdf2Verify } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestGet(context) { const { @@ -17,16 +15,7 @@ export async function onRequestGet(context) { params?.email && params?.hash ) { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - - const member = await prisma.Member.findFirst({ + const member = await data.prisma.Member.findFirst({ where: { email: params.email, }, @@ -52,10 +41,10 @@ export async function onRequestGet(context) { authn_ip, authn_ua } - const sessionInfo = await prisma.Session.create({ + const sessionInfo = await data.prisma.Session.create({ data: response.session }) - // console.log(`/login session kid=${token}`, sessionInfo) + data.logger(`/login session kid=${token}`, sessionInfo) response.result = AuthResult.AUTHENTICATED response.ok = true } diff --git a/functions/api/login/github/[code].js b/functions/api/login/github/[code].js index 1b30606e..e4b32d79 100644 --- a/functions/api/login/github/[code].js +++ b/functions/api/login/github/[code].js @@ -1,6 +1,4 @@ import { appExpiryPeriod, AuthResult, GitHub, hex, pbkdf2, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestGet(context) { const { @@ -11,16 +9,7 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) try { - const app = new Server(request, prisma) let oauthData if (params?.code) { const method = "POST" @@ -45,7 +34,7 @@ export async function onRequestGet(context) { if (!oauthData?.access_token) { return Response.json({ ok: false, error: { message: 'OAuth authorization failed' } }) } - const gh = new GitHub(oauthData.access_token) + const gh = new GitHub(data.prisma, data?.session?.orgId, data?.session?.memberEmail, oauthData.access_token) const created = (new Date()).getTime() const response = { ok: false, session: {}, member: {} } const { content, error, tokenExpiry } = await gh.getUser() @@ -53,10 +42,9 @@ export async function onRequestGet(context) { return Response.json({ ok: false, error }) } const expires = tokenExpiry || appExpiryPeriod + created - const verificationResult = await app.authenticate() - let memberEmail = verificationResult?.session?.memberEmail + let memberEmail = data?.session?.memberEmail if (!memberEmail) { - const ghUserEmails = await gh.getUserEmails(prisma) + const ghUserEmails = await gh.getUserEmails() if (!ghUserEmails?.ok || ghUserEmails?.error?.message || !ghUserEmails?.content || !ghUserEmails.content?.length) { return Response.json({ ok: ghUserEmails.ok, error: ghUserEmails.error, result: `${ghUserEmails.status} ${ghUserEmails.statusText}` }) } @@ -70,7 +58,7 @@ export async function onRequestGet(context) { memberEmail = content.email.toLowerCase() } } - const memberCheck = await app.memberExists(memberEmail) + const memberCheck = await (new Server(request, data.prisma)).memberExists(memberEmail) if (memberCheck.exists) { response.member = memberCheck.member } else { @@ -83,7 +71,7 @@ export async function onRequestGet(context) { lastName = words.join(' ') || '' } if (content?.company) { - const originalOrg = await prisma.Org.findFirst({ + const originalOrg = await data.prisma.Org.findFirst({ where: { name: content.company } @@ -91,22 +79,22 @@ export async function onRequestGet(context) { if (originalOrg?.uuid) { orgId = originalOrg.uuid } else { - const orgInfo = await prisma.Org.create({ + const orgInfo = await data.prisma.Org.create({ data: { uuid: orgId, name: content.company, } }) - // console.log(`/github/install register orgId=${orgId}`, orgInfo) + data.logger(`/github/install register orgId=${orgId}`, orgInfo) } } else { - const orgInfo = await prisma.Org.create({ + const orgInfo = await data.prisma.Org.create({ data: { uuid: orgId, name: memberEmail.toLowerCase(), } }) - // console.log(`/github/install register orgId=${orgId}`, orgInfo) + data.logger(`/github/install register orgId=${orgId}`, orgInfo) } response.member = { @@ -117,10 +105,10 @@ export async function onRequestGet(context) { firstName, lastName } - const memberInfo = await prisma.Member.create({ + const memberInfo = await data.prisma.Member.create({ data: response.member }) - // console.log(`/github/install register email=${memberEmail}`, memberInfo) + data.logger(`/github/install register email=${memberEmail}`, memberInfo) delete response.member.passwordHash } const token = crypto.randomUUID() @@ -138,16 +126,16 @@ export async function onRequestGet(context) { authn_ip, authn_ua } - const sessionInfo = await prisma.Session.create({ + const sessionInfo = await data.prisma.Session.create({ data: response.session }) - // console.log(`/github/install session kid=${token}`, sessionInfo) - const githubApp = await prisma.GitHubApp.findFirst({ + data.logger(`/github/install session kid=${token}`, sessionInfo) + const githubApp = await data.prisma.GitHubApp.findFirst({ where: { orgId: response.member.orgId }, }) let installationId = githubApp?.installationId if (!installationId) { - const ghInstalls = await gh.getInstallations(prisma, response.session.orgId, response.session.memberEmail) + const ghInstalls = await gh.getInstallations() if (!ghInstalls?.ok || ghInstalls?.error?.message || !ghInstalls?.content?.installations || !ghInstalls.content?.installations?.length) { return Response.json({ ok: ghInstalls.ok, error: ghInstalls.error, result: `${ghInstalls.status} ${ghInstalls.statusText}` }) } @@ -159,7 +147,7 @@ export async function onRequestGet(context) { } } if (installationId) { - const GHAppInfo = await prisma.GitHubApp.upsert({ + const GHAppInfo = await data.prisma.GitHubApp.upsert({ where: { installationId: parseInt(installationId, 10), }, @@ -177,7 +165,7 @@ export async function onRequestGet(context) { expires } }) - // console.log(`/github/install installationId=${installationId}`, GHAppInfo) + data.logger(`/github/install installationId=${installationId}`, GHAppInfo) response.result = AuthResult.AUTHENTICATED response.ok = true } else { diff --git a/functions/api/me.js b/functions/api/me.js index 5c976c99..cf4d3494 100644 --- a/functions/api/me.js +++ b/functions/api/me.js @@ -1,6 +1,4 @@ -import { AuthResult, Server, ensureStrReqBody } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestGet(context) { const { @@ -12,21 +10,9 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const member = await prisma.Member.findFirst({ + const member = await data.prisma.Member.findFirst({ where: { - email: verificationResult.session.memberEmail, + email: data.session.memberEmail, }, omit: { orgId: true @@ -56,81 +42,67 @@ export async function onRequestPost(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const originalMember = await prisma.Member.findFirst({ + const originalMember = await data.prisma.Member.findFirst({ where: { - email: verificationResult.session.memberEmail, + email: data.session.memberEmail, }, }) const member = {} - const body = await ensureStrReqBody(request) - const data = JSON.parse(body) - if (data?.email && originalMember.email !== data.email) { + if (data.json?.email && originalMember.email !== data.json.email) { member.email = data.email.toLowerCase() } - if (data?.passwordHash && originalMember.passwordHash !== data.passwordHash) { - member.passwordHash = data.passwordHash + if (data.json?.passwordHash && originalMember.passwordHash !== data.json.passwordHash) { + member.passwordHash = data.json.passwordHash } - if (data?.firstName && originalMember.firstName !== data.firstName) { - member.firstName = data.firstName + if (data.json?.firstName && originalMember.firstName !== data.json.firstName) { + member.firstName = data.json.firstName } - if (data?.lastName && originalMember.lastName !== data.lastName) { - member.lastName = data.lastName + if (data.json?.lastName && originalMember.lastName !== data.json.lastName) { + member.lastName = data.json.lastName } - if (data?.avatarUrl && originalMember.avatarUrl !== data.avatarUrl) { - member.avatarUrl = data.avatarUrl + if (data.json?.avatarUrl && originalMember.avatarUrl !== data.json.avatarUrl) { + member.avatarUrl = data.json.avatarUrl } - if (typeof data?.alertNews !== 'undefined' && originalMember.alertNews !== data.alertNews) { - member.alertNews = parseInt(data.alertNews, 10) + if (typeof data.json?.alertNews !== 'undefined' && originalMember.alertNews !== data.json.alertNews) { + member.alertNews = parseInt(data.json.alertNews, 10) } - if (typeof data?.alertOverdue !== 'undefined' && originalMember.alertOverdue !== data.alertOverdue) { - member.alertOverdue = parseInt(data.alertOverdue, 10) + if (typeof data.json?.alertOverdue !== 'undefined' && originalMember.alertOverdue !== data.json.alertOverdue) { + member.alertOverdue = parseInt(data.json.alertOverdue, 10) } - if (typeof data?.alertFindings !== 'undefined' && originalMember.alertFindings !== data.alertFindings) { - member.alertFindings = parseInt(data.alertFindings, 10) + if (typeof data.json?.alertFindings !== 'undefined' && originalMember.alertFindings !== data.json.alertFindings) { + member.alertFindings = parseInt(data.json.alertFindings, 10) } - if (typeof data?.alertType !== 'undefined' && originalMember.alertType !== data.alertType) { - member.alertType = parseInt(data.alertType, 10) + if (typeof data.json?.alertType !== 'undefined' && originalMember.alertType !== data.json.alertType) { + member.alertType = parseInt(data.json.alertType, 10) } let updatedOrg = false - const originalOrg = await prisma.Org.findFirst({ + const originalOrg = await data.prisma.Org.findFirst({ where: { uuid: originalMember.orgId, }, }) - if (data?.orgName && originalOrg.name !== data.orgName) { + if (data.json?.orgName && originalOrg.name !== data.json.orgName) { //TODO: temp until organisations feature is finished - const orgInfo = await prisma.Org.update({ + const orgInfo = await data.prisma.Org.update({ where: { uuid: originalMember.orgId, }, data: { - name: data.orgName + name: data.json.orgName } }) updatedOrg = true - // console.log(`/me update org ${originalMember.orgId} ${data.orgName}`, orgInfo) + data.logger(`/me update org ${originalMember.orgId} ${data.json.orgName}`, orgInfo) } if (Object.keys(member).length > 0) { - const memberInfo = await prisma.Member.update({ + const memberInfo = await data.prisma.Member.update({ where: { uuid: originalMember.uuid, }, data: member }) - // console.log(`/me update member ${verificationResult.session.memberEmail}`, memberInfo) + data.logger(`/me update member ${data.session.memberEmail}`, memberInfo) return Response.json({ ok: true, result: 'Updated' }) } diff --git a/functions/api/next-issue.js b/functions/api/next-issue.js index 8b867262..77686fbb 100644 --- a/functions/api/next-issue.js +++ b/functions/api/next-issue.js @@ -1,7 +1,5 @@ import { processFinding } from '@/finding'; -import { AuthResult, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestGet(context) { const { @@ -13,34 +11,21 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 1 - const skip = parseInt(searchParams.get('skip'), 10) || 0 + const take = parseInt(data.searchParams.get('take'), 10) || 1 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 const where = { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, AND: { triage: { every: { analysisState: 'in_triage' } } }, } - const findingCount = await prisma.Finding.count({ where }) + const findingCount = await data.prisma.Finding.count({ where }) let finding, spdxJson, cdxJson; if (findingCount > 0) { - finding = await prisma.Finding.findFirst({ + finding = await data.prisma.Finding.findFirst({ where, include: { triage: { @@ -80,7 +65,7 @@ export async function onRequestGet(context) { if (!finding) { return Response.json({ ok: true, finding, findingCount }) } - finding = await processFinding(prisma, env.r2artifacts, verificationResult, finding) + finding = await processFinding(data.prisma, env.r2artifacts, data.session, finding) if (finding?.spdx?.artifact?.uuid) { const resp = await env.r2artifacts.get(`spdx/${finding.spdx.artifact.uuid}.json`) diff --git a/functions/api/org/integrations.js b/functions/api/org/integrations.js index ca3aaa49..e28b9bb7 100644 --- a/functions/api/org/integrations.js +++ b/functions/api/org/integrations.js @@ -1,6 +1,3 @@ -import { Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestGet(context) { const { @@ -11,29 +8,17 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const githubApps = await prisma.GitHubApp.findMany({ + const githubApps = await data.prisma.GitHubApp.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, omit: { accessToken: true, }, }) - const patTokens = await prisma.MemberKey.findMany({ + const patTokens = await data.prisma.MemberKey.findMany({ where: { - memberEmail: verificationResult.session.memberEmail, + memberEmail: data.session.memberEmail, keyType: 'github_pat', }, include: { @@ -47,9 +32,9 @@ export async function onRequestGet(context) { const keepRecords = 1000 const thirtyDaysAgo = new Date() thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); - const deleteOldRecords = await prisma.IntegrationUsageLog.deleteMany({ + const deleteOldRecords = await data.prisma.IntegrationUsageLog.deleteMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, createdAt: { lt: thirtyDaysAgo.getTime() } @@ -57,8 +42,8 @@ export async function onRequestGet(context) { }) console.log('Aged IntegrationUsageLog cleanup. oldRecordsDeleted:', deleteOldRecords.count) for (const source of ['osv', 'first', 'vulncheck', 'github', 'mitre-cve']) { - const allRecords = await prisma.IntegrationUsageLog.findMany({ - where: { source, orgId: verificationResult.session.orgId }, + const allRecords = await data.prisma.IntegrationUsageLog.findMany({ + where: { source, orgId: data.session.orgId }, select: { id: true }, orderBy: { createdAt: 'desc' } }) @@ -74,9 +59,9 @@ export async function onRequestGet(context) { } catch (error) { console.error('Error during cleanup:', error) } - const integrations = await prisma.IntegrationConfig.findMany({ + const integrations = await data.prisma.IntegrationConfig.findMany({ where: { - orgId: verificationResult.session.orgId + orgId: data.session.orgId } }) const result = { diff --git a/functions/api/register/[org]/[email]/[hash].js b/functions/api/register/[org]/[email]/[hash].js index 3a748e0d..9c7ab5c2 100644 --- a/functions/api/register/[org]/[email]/[hash].js +++ b/functions/api/register/[org]/[email]/[hash].js @@ -1,6 +1,4 @@ import { pbkdf2 } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestGet(context) { const { @@ -11,14 +9,6 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) if ( params?.org && params?.email && @@ -26,7 +16,7 @@ export async function onRequestGet(context) { ) { let orgId = crypto.randomUUID() if (params?.org) { - const originalOrg = await prisma.Org.findFirst({ + const originalOrg = await data.prisma.Org.findFirst({ where: { name: params.org } @@ -34,22 +24,22 @@ export async function onRequestGet(context) { if (originalOrg?.uuid) { orgId = originalOrg.uuid } else { - const orgInfo = await prisma.Org.create({ + const orgInfo = await data.prisma.Org.create({ data: { uuid: orgId, name: params.org, } }) - // console.log(`/register orgId=${orgId}`, orgInfo) + data.logger(`/register orgId=${orgId}`, orgInfo) } } else { - const orgInfo = await prisma.Org.create({ + const orgInfo = await data.prisma.Org.create({ data: { uuid: orgId, name: params.email.toLowerCase(), } }) - // console.log(`/register orgId=${orgId}`, orgInfo) + data.logger(`/register orgId=${orgId}`, orgInfo) } const member = { uuid: crypto.randomUUID(), @@ -57,10 +47,10 @@ export async function onRequestGet(context) { orgId, passwordHash: await pbkdf2(params.hash) } - const info = await prisma.Member.create({ + const info = await data.prisma.Member.create({ data: member }) - // console.log(`/register email=${member.email}`, info) + data.logger(`/register email=${member.email}`, info) return Response.json({ ok: true, member }) } diff --git a/functions/api/sarif.js b/functions/api/sarif.js index 002935f8..dc35c9fc 100644 --- a/functions/api/sarif.js +++ b/functions/api/sarif.js @@ -1,6 +1,4 @@ -import { Server, ensureStrReqBody, hex, isSARIF, saveArtifact } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { hex, isSARIF, saveArtifact } from "@/utils"; export async function onRequestGet(context) { @@ -12,24 +10,11 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - let sarif = await prisma.SARIFInfo.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + let sarif = await data.prisma.SARIFInfo.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, include: { results: true, @@ -69,27 +54,13 @@ export async function onRequestPost(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } const files = [] try { - const body = await ensureStrReqBody(request) - const inputs = JSON.parse(body) - for (const sarif of inputs) { + for (const sarif of data.json) { if (!isSARIF(sarif)) { return Response.json({ ok: false, error: { message: 'SARIF is missing necessary fields.' } }) } - const artifact = await saveArtifact(prisma, env.r2artifacts, JSON.stringify(sarif), crypto.randomUUID(), `sarif`) + const artifact = await saveArtifact(data.prisma, env.r2artifacts, JSON.stringify(sarif), crypto.randomUUID(), `sarif`) const createdAt = (new Date()).getTime() for (const run of sarif.runs) { const reportId = await hex(run.tool.driver.name + run.tool.driver.semanticVersion + JSON.stringify(run.results)) @@ -103,7 +74,7 @@ export async function onRequestPost(context) { toolName: run.tool.driver.name, toolVersion: run.tool.driver.semanticVersion, } - const info = await prisma.SARIFInfo.upsert({ + const info = await data.prisma.SARIFInfo.upsert({ where: { reportId, }, @@ -112,11 +83,11 @@ export async function onRequestPost(context) { }, create: { ...sarifData, - org: { connect: { uuid: verificationResult.session.orgId } }, + org: { connect: { uuid: data.session.orgId } }, artifact: { connect: { uuid: artifact.uuid } }, }, }) - // console.log(`/sarif/upload ${artifact.uuid} kid=${verificationResult.session.kid}`, info) + data.logger(`/sarif/upload ${artifact.uuid} kid=${data.session.kid}`, info) sarifData.results = [] for (const result of run.results) { const locationsJSON = JSON.stringify(result.locations) @@ -159,7 +130,7 @@ export async function onRequestPost(context) { } } sarifData.results.push(resultData) - const reportInfo = await prisma.SarifResults.upsert({ + const reportInfo = await data.prisma.SarifResults.upsert({ where: { guid: resultData.guid, }, @@ -168,7 +139,7 @@ export async function onRequestPost(context) { }, create: resultData, }) - // console.log(`/github/repos/sarif_results ${artifact.uuid} kid=${verificationResult.session.kid}`, reportInfo) + data.logger(`/github/repos/sarif_results ${artifact.uuid} kid=${data.session.kid}`, reportInfo) } files.push(sarifData) } diff --git a/functions/api/sarif/[reportId].js b/functions/api/sarif/[reportId].js index faecefe9..078ad657 100644 --- a/functions/api/sarif/[reportId].js +++ b/functions/api/sarif/[reportId].js @@ -1,6 +1,3 @@ -import { Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; export async function onRequestDelete(context) { const { @@ -11,19 +8,7 @@ export async function onRequestDelete(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const sarif = await prisma.SARIFInfo.findFirst({ + const sarif = await data.prisma.SARIFInfo.findFirst({ where: { reportId: params.reportId }, include: { results: true, @@ -35,13 +20,13 @@ export async function onRequestDelete(context) { } }) for (const link of sarif.artifact.downloadLinks) { - await prisma.Link.delete({ where: { id: link.id } }) + await data.prisma.Link.delete({ where: { id: link.id } }) } - await prisma.Artifact.delete({ where: { uuid: sarif.artifact.uuid } }) + await data.prisma.Artifact.delete({ where: { uuid: sarif.artifact.uuid } }) for (const result of sarif.results) { - await prisma.SarifResults.delete({ where: { guid: result.guid } }) + await data.prisma.SarifResults.delete({ where: { guid: result.guid } }) } - const sarifInfo = await prisma.SARIFInfo.delete({ where: { reportId: params.reportId } }) - // console.log(`DELETE /sarif/[${params.reportId}]`, sarifInfo) + const sarifInfo = await data.prisma.SARIFInfo.delete({ where: { reportId: params.reportId } }) + data.logger(`DELETE /sarif/[${params.reportId}]`, sarifInfo) return Response.json({ ok: true, reportId: params.reportId, artifactUuid: sarif.artifact.uuid }) } diff --git a/functions/api/search.js b/functions/api/search.js index 8129dc50..c5c4dba7 100644 --- a/functions/api/search.js +++ b/functions/api/search.js @@ -1,6 +1,4 @@ -import { AuthResult, Server, parseSearchQuery } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult, parseSearchQuery } from "@/utils"; export async function onRequestGet(context) { const { @@ -12,32 +10,10 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - log: [ - { - emit: "event", - level: "query", - }, - ], - }) - prisma.$on("query", async (e) => { - console.log(`${e.query} ${e.params}`) - }); - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 5 - const { inclusive, exclude, terms } = parseSearchQuery(searchParams.get('q')) + const take = parseInt(data.searchParams.get('take'), 10) || 5 + const { inclusive, exclude, terms } = parseSearchQuery(data.searchParams.get('q')) const findings = [] - const where = { orgId: verificationResult.session.orgId, OR: [], NOT: [] } + const where = { orgId: data.session.orgId, OR: [], NOT: [] } for (const contains of terms) { where.OR.push({ repoName: { contains } }) @@ -46,7 +22,7 @@ export async function onRequestGet(context) { where.OR.push({ cwes: { contains } }) where.OR.push({ detectionTitle: { contains } }) } - const res = await prisma.Finding.findMany({ + const res = await data.prisma.Finding.findMany({ where, select: { uuid: true, diff --git a/functions/api/spdx.js b/functions/api/spdx.js index 10762ace..09394714 100644 --- a/functions/api/spdx.js +++ b/functions/api/spdx.js @@ -1,7 +1,5 @@ import { parsePackageRef, parseSPDXComponents } from "@/finding"; -import { AuthResult, OSV, Server, ensureStrReqBody, hex, isSPDX, saveArtifact } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult, OSV, hex, isSPDX, saveArtifact } from "@/utils"; export async function onRequestGet(context) { @@ -13,24 +11,11 @@ export async function onRequestGet(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - let spdx = await prisma.SPDXInfo.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + let spdx = await data.prisma.SPDXInfo.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, select: { spdxId: true, @@ -60,7 +45,7 @@ export async function onRequestGet(context) { }, }) - const repos = await prisma.gitRepo.findMany({ + const repos = await data.prisma.gitRepo.findMany({ where: { fullName: { in: spdx.map(s => s?.repoName).filter(s => !!s).filter((value, index, array) => array.indexOf(value) === index) }, }, @@ -96,40 +81,26 @@ export async function onRequestPost(context) { next, // used for middleware or to fetch assets data, // arbitrary space for passing data between middlewares } = context - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } const files = [] let errors = new Set() try { - const body = await ensureStrReqBody(request) - const inputs = JSON.parse(body) - for (const spdx of inputs) { + for (const spdx of data.json) { if (!isSPDX(spdx)) { return Response.json({ ok: false, error: { message: 'SPDX is missing necessary fields.' } }) } const spdxId = await makeId(spdx) - const originalSpdx = await prisma.SPDXInfo.findFirst({ + const originalSpdx = await data.prisma.SPDXInfo.findFirst({ where: { spdxId, - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, } }) const spdxStr = JSON.stringify(spdx) - const artifact = await saveArtifact(prisma, env.r2artifacts, spdxStr, crypto.randomUUID(), `spdx`) + const artifact = await saveArtifact(data.prisma, env.r2artifacts, spdxStr, crypto.randomUUID(), `spdx`) const artifactUuid = originalSpdx?.artifactUuid || artifact?.uuid const dependencies = [] for (const dep of await parseSPDXComponents(spdx, spdxId)) { - const info = await prisma.Dependency.upsert({ + const info = await data.prisma.Dependency.upsert({ where: { spdx_dep: { spdxId, @@ -144,7 +115,7 @@ export async function onRequestPost(context) { create: { ...dep, spdxId } }) dependencies.push({ ...dep, spdxId }) - // console.log(`Dependency ${dep.name}@${dep.version}`, info) + data.logger(`Dependency ${dep.name}@${dep.version}`, info) } const spdxData = { spdxId, @@ -158,10 +129,10 @@ export async function onRequestPost(context) { documentDescribes: spdx?.documentDescribes?.join(','), comment: spdx.creationInfo?.comment || '', } - const info = await prisma.SPDXInfo.upsert({ + const info = await data.prisma.SPDXInfo.upsert({ where: { spdxId, - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, }, update: { createdAt: spdxData.createdAt, @@ -169,11 +140,11 @@ export async function onRequestPost(context) { }, create: { ...spdxData, - org: { connect: { uuid: verificationResult.session.orgId } }, + org: { connect: { uuid: data.session.orgId } }, artifact: { connect: { uuid: artifactUuid } }, } }) - // console.log(`/github/repos/spdx ${spdxId} kid=${verificationResult.session.kid}`, info) + data.logger(`/github/repos/spdx ${spdxId} kid=${data.session.kid}`, info) spdxData.dependencies = dependencies files.push(spdxData) @@ -203,7 +174,7 @@ export async function onRequestPost(context) { } return { package: { name: q.name }, version: q.version } }) - const results = await osv.queryBatch(prisma, verificationResult.session.orgId, verificationResult.session.memberEmail, queries) + const results = await osv.queryBatch(data.prisma, data.session.orgId, data.session.memberEmail, queries) let i = 0 for (const result of results) { const { purl = null, name, version, license } = osvQueries[i] @@ -211,10 +182,10 @@ export async function onRequestPost(context) { if (!vuln?.id) { continue } - const findingId = await hex(`${verificationResult.session.orgId}${vuln.id}${name}${version}`) + const findingId = await hex(`${data.session.orgId}${vuln.id}${name}${version}`) const findingData = { findingId, - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, source: 'osv.dev', category: 'sca', createdAt: (new Date()).getTime(), @@ -227,17 +198,17 @@ export async function onRequestPost(context) { malicious: vuln.id.startsWith("MAL-") ? 1 : 0, spdxId } - const originalFinding = await prisma.Finding.findFirst({ + const originalFinding = await data.prisma.Finding.findFirst({ where: { findingId, AND: { - orgId: verificationResult.session.orgId + orgId: data.session.orgId }, } }) let finding; if (originalFinding) { - finding = await prisma.Finding.update({ + finding = await data.prisma.Finding.update({ where: { uuid: originalFinding.uuid, }, @@ -250,9 +221,9 @@ export async function onRequestPost(context) { }, }) } else { - finding = await prisma.Finding.create({ data: findingData }) + finding = await data.prisma.Finding.create({ data: findingData }) } - // console.log(`findings SCA`, finding) + data.logger(`findings SCA`, finding) const vexData = { findingUuid: finding.uuid, createdAt: (new Date()).getTime(), @@ -260,7 +231,7 @@ export async function onRequestPost(context) { seen: 0, analysisState: 'in_triage' } - const originalVex = await prisma.Triage.findFirst({ + const originalVex = await data.prisma.Triage.findFirst({ where: { findingUuid: finding.uuid, analysisState: 'in_triage', @@ -268,7 +239,7 @@ export async function onRequestPost(context) { }) let vex; if (originalVex) { - vex = await prisma.Triage.update({ + vex = await data.prisma.Triage.update({ where: { uuid: originalVex.uuid, }, @@ -277,9 +248,9 @@ export async function onRequestPost(context) { }, }) } else { - vex = await prisma.Triage.create({ data: vexData }) + vex = await data.prisma.Triage.create({ data: vexData }) } - // console.log(`findings VEX`, vex) + data.logger(`findings VEX`, vex) } i++ } diff --git a/functions/api/unresolved.js b/functions/api/unresolved.js index 6fa265b1..3b99e38a 100644 --- a/functions/api/unresolved.js +++ b/functions/api/unresolved.js @@ -1,6 +1,4 @@ -import { AuthResult, Server } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestGet(context) { const { @@ -12,24 +10,11 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const { searchParams } = new URL(request.url) - const take = parseInt(searchParams.get('take'), 10) || 50 - const skip = parseInt(searchParams.get('skip'), 10) || 0 - const findings = await prisma.Finding.findMany({ + const take = parseInt(data.searchParams.get('take'), 10) || 50 + const skip = parseInt(data.searchParams.get('skip'), 10) || 0 + const findings = await data.prisma.Finding.findMany({ where: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, AND: { triage: { every: { analysisState: { in: ['exploitable', 'in_triage'] } } } }, @@ -59,7 +44,8 @@ export async function onRequestGet(context) { }) return Response.json({ - ok: true, findings: findings.map(finding => { + ok: true, + findings: findings.map(finding => { finding.references = JSON.parse(finding.referencesJSON) delete finding.referencesJSON finding.aliases = JSON.parse(finding.aliases) diff --git a/functions/api/vulncheck/integrate.js b/functions/api/vulncheck/integrate.js index 8251de23..6c7c604a 100644 --- a/functions/api/vulncheck/integrate.js +++ b/functions/api/vulncheck/integrate.js @@ -1,6 +1,4 @@ -import { AuthResult, Server, ensureStrReqBody } from "@/utils"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; +import { AuthResult } from "@/utils"; export async function onRequestPost(context) { const { @@ -12,56 +10,42 @@ export async function onRequestPost(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - const verificationResult = await (new Server(request, prisma)).authenticate() - if (!verificationResult.isValid) { - return Response.json({ ok: false, result: verificationResult.message }) - } - const bodyStr = await ensureStrReqBody(request) - const data = JSON.parse(bodyStr) - if (data?.apiKey && !data.apiKey.startsWith('vulncheck_')) { + if (data.json?.apiKey && !data.json.apiKey.startsWith('vulncheck_')) { return Response.json({ error: { message: `Invalid API Key provided, expected "vulncheck_" prefix.` } }) } const where = { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, AND: { name: 'vulncheck' }, } - const original = await prisma.IntegrationConfig.findFirst({ where }) + const original = await data.prisma.IntegrationConfig.findFirst({ where }) if (original === null) { - if (data?.apiKey === undefined) { + if (data.json?.apiKey === undefined) { return Response.json({ ok: false, result: 'No Change' }) } - const info = await prisma.IntegrationConfig.create({ + const info = await data.prisma.IntegrationConfig.create({ data: { - orgId: verificationResult.session.orgId, + orgId: data.session.orgId, name: 'vulncheck', created: new Date().getTime(), - configJSON: JSON.stringify({ secret: data.apiKey }), - suspend: data?.suspend === undefined ? 0 : (data.suspend ? 1 : 0), + configJSON: JSON.stringify({ secret: data.json.apiKey }), + suspend: data.json?.suspend === undefined ? 0 : (data.json.suspend ? 1 : 0), } }) return Response.json({ ok: true, info }) } if (original?.configJSON) { original.config = JSON.parse(original.configJSON) - if (data?.apiKey === original.config.secret && (data?.suspend ? 1 : 0) === original.suspend) { + if (data.json?.apiKey === original.config.secret && (data.json?.suspend ? 1 : 0) === original.suspend) { return Response.json({ ok: false, result: 'No Change' }) } } - const info = await prisma.IntegrationConfig.update({ + const info = await data.prisma.IntegrationConfig.update({ where: { uuid: original.uuid }, data: { - configJSON: data?.apiKey === undefined || data.apiKey.includes('****') ? original.configJSON : JSON.stringify({ secret: data.apiKey }), - suspend: data?.suspend === undefined ? original.suspend : (data.suspend ? 1 : 0), + configJSON: data.json?.apiKey === undefined || data.json.apiKey.includes('****') ? original.configJSON : JSON.stringify({ secret: data.json.apiKey }), + suspend: data.json?.suspend === undefined ? original.suspend : (data.json.suspend ? 1 : 0), } }) return Response.json({ ok: true, info }) diff --git a/functions/tea/collection/[collectionId].js b/functions/tea/collection/[collectionId].js index df42b3b3..48fafdb6 100644 --- a/functions/tea/collection/[collectionId].js +++ b/functions/tea/collection/[collectionId].js @@ -1,5 +1,3 @@ -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; /** * If only the serialNumber parameter is supplied, retrieve the latest version of the BOM from the repository. @@ -19,19 +17,11 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) params.collectionId // id assigned in the system to the specific collection as returned by collection element - // const member = await prisma.Member.findFirst({ + // const member = await data.prisma.Member.findFirst({ // where: { - // email: verificationResult.session.memberEmail, + // email: data.session.memberEmail, // }, // }) // return Response.json([]) // [ Collection ] diff --git a/functions/tea/leaf/[tei].js b/functions/tea/leaf/[tei].js index 298cb13f..89873978 100644 --- a/functions/tea/leaf/[tei].js +++ b/functions/tea/leaf/[tei].js @@ -1,6 +1,4 @@ import { Visibility } from "@/tea"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; /** * If only the serialNumber parameter is supplied, retrieve the latest version of the BOM from the repository. @@ -20,24 +18,15 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) params.tei // TEI unique leaf index - const { searchParams } = new URL(request.url) - const visibility = searchParams.get('visibility') // Used to specify whether we list public or private components + const visibility = data.searchParams.get('visibility') // Used to specify whether we list public or private components if (visibility && ![Visibility.ALLAVAILABLE, Visibility.PUBLICONLY].includes(visibility)) { return Response(null, { status: 422, statusText: `Invalid value provided: visibility=${visibility}` }) } - // const member = await prisma.Member.findFirst({ + // const member = await data.prisma.Member.findFirst({ // where: { - // email: verificationResult.session.memberEmail, + // email: data.session.memberEmail, // }, // }) // return Response.json([]) // [ CollectionEl ] diff --git a/functions/tea/product/[tei].js b/functions/tea/product/[tei].js index 27025f7d..cc9c229a 100644 --- a/functions/tea/product/[tei].js +++ b/functions/tea/product/[tei].js @@ -1,6 +1,4 @@ import { Visibility } from "@/tea"; -import { PrismaD1 } from '@prisma/adapter-d1'; -import { PrismaClient } from '@prisma/client'; /** * If only the serialNumber parameter is supplied, retrieve the latest version of the BOM from the repository. @@ -20,24 +18,14 @@ export async function onRequestGet(context) { data, // arbitrary space for passing data between middlewares } = context try { - const adapter = new PrismaD1(env.d1db) - const prisma = new PrismaClient({ - adapter, - transactionOptions: { - maxWait: 1500, // default: 2000 - timeout: 2000, // default: 5000 - }, - }) - params.tei // TEI unique product index - const { searchParams } = new URL(request.url) - const visibility = searchParams.get('visibility') // Used to specify whether we list public or private components + const visibility = data.searchParams.get('visibility') // Used to specify whether we list public or private components if (visibility && ![Visibility.ALLAVAILABLE, Visibility.PUBLICONLY].includes(visibility)) { return Response(null, { status: 422, statusText: `Invalid value provided: visibility=${visibility}` }) } - // const member = await prisma.Member.findFirst({ + // const member = await data.prisma.Member.findFirst({ // where: { - // email: verificationResult.session.memberEmail, + // email: data.session.memberEmail, // }, // }) // return Response.json([]) // [ Product ] diff --git a/migrations/0019_product_tags.sql b/migrations/0019_product_tags.sql new file mode 100644 index 00000000..879ba095 --- /dev/null +++ b/migrations/0019_product_tags.sql @@ -0,0 +1,69 @@ +-- CreateTable +CREATE TABLE "ProductRepos" ( + "repoName" TEXT NOT NULL, + "productUuid" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + PRIMARY KEY ("repoName", "productUuid") +); +-- CreateTable +CREATE TABLE "Tags" ( + "uuid" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "url" TEXT +); +-- CreateTable +CREATE TABLE "ProductTags" ( + "productUuid" TEXT NOT NULL, + "tagsUuid" TEXT NOT NULL, + PRIMARY KEY ("productUuid", "tagsUuid") +); +CREATE TABLE "new_Product" ( + "uuid" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "source" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "createdAt" INTEGER NOT NULL, + "monitored" INTEGER NOT NULL DEFAULT 0, + "monitoredSchedule" TEXT NOT NULL DEFAULT '0 7 * * mon-fri', + "lastMonitored" INTEGER NOT NULL, + "businessCritical" INTEGER NOT NULL DEFAULT 0, + "productTagsUuid" TEXT, + "ownerEmail" TEXT +); +INSERT INTO "new_Product" ("name", "uuid") +SELECT "name", + "uuid" +FROM "Product"; +DROP TABLE "Product"; +ALTER TABLE "new_Product" + RENAME TO "Product"; +-- Remove memberEmail +CREATE TABLE "new_GitHubApp" ( + "installationId" INTEGER NOT NULL PRIMARY KEY, + "orgId" TEXT NOT NULL, + "accessToken" TEXT NOT NULL, + "login" TEXT, + "created" INTEGER NOT NULL, + "expires" INTEGER, + "avatarUrl" TEXT +); +INSERT INTO "new_GitHubApp" ( + "accessToken", + "avatarUrl", + "created", + "expires", + "installationId", + "login", + "orgId" + ) +SELECT "accessToken", + "avatarUrl", + "created", + "expires", + "installationId", + "login", + "orgId" +FROM "GitHubApp"; +DROP TABLE "GitHubApp"; +ALTER TABLE "new_GitHubApp" + RENAME TO "GitHubApp"; diff --git a/migrations/0020_repo_branches.sql b/migrations/0020_repo_branches.sql new file mode 100644 index 00000000..00a08d7e --- /dev/null +++ b/migrations/0020_repo_branches.sql @@ -0,0 +1,9 @@ +-- CreateTable +CREATE TABLE "GitBranch" ( + "name" TEXT NOT NULL, + "repoName" TEXT NOT NULL, + "commitSha" TEXT NOT NULL, + "protected" INTEGER NOT NULL DEFAULT 0, + "orgId" TEXT NOT NULL, + PRIMARY KEY ("repoName", "name") +); diff --git a/package.json b/package.json index df6d2615..fae91005 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,10 @@ "axios": "^1.3.1", "chart.js": "^4.1.2", "crypto-es": "^2.1.0", + "dompurify": "^3.2.1", "eslint-import-resolver-alias": "^1.1.2", "jwt-decode": "^3.1.2", + "marked": "^15.0.2", "pinia": "^2.1.3", "prismjs": "^1.29.0", "roboto-fontface": "^0.10.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a5a90581..4ec266e5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -33,7 +33,10 @@ model Org { sarif SARIFInfo[] cdx CycloneDXInfo[] spdx SPDXInfo[] - GitHubApp GitHubApp[] + gitHubApps GitHubApp[] + products Product[] + repos ProductRepos[] + branches GitBranch[] } model IntegrationConfig { @@ -64,6 +67,7 @@ model Member { sessions Session[] integration_usage_log IntegrationUsageLog[] triage_activity Triage[] + ownedProducts Product[] @relation("productOwner") } model MemberKey { @@ -99,6 +103,51 @@ model GitHubApp { avatarUrl String? } +model Tags { + uuid String @id @default(uuid()) + name String + url String? + products ProductTags[] +} + +model ProductTags { + product Product @relation(fields: [productUuid], references: [uuid]) + productUuid String + Tags Tags @relation(fields: [tagsUuid], references: [uuid]) + tagsUuid String + + @@id([productUuid, tagsUuid]) +} + +model Product { + uuid String @id @default(uuid()) + name String + source String + orgId String + org Org @relation(fields: [orgId], references: [uuid]) + repos ProductRepos[] + createdAt Int + monitored Int @default(0) + monitoredSchedule String @default("0 7 * * mon-fri") // 7AM weekdays + lastMonitored Int + businessCritical Int @default(0) + tags ProductTags[] + productTagsUuid String? + ownerEmail String? + owner Member? @relation("productOwner", fields: [ownerEmail], references: [email]) +} + +model ProductRepos { + repoName String + repo GitRepo @relation(fields: [repoName, orgId], references: [fullName, orgId]) + productUuid String + product Product @relation(fields: [productUuid], references: [uuid]) + orgId String + org Org @relation(fields: [orgId], references: [uuid]) + + @@id([repoName, productUuid]) +} + model GitRepo { fullName String orgId String @@ -117,14 +166,28 @@ model GitRepo { archived Int @default(0) visibility String avatarUrl String? + products ProductRepos[] sarif SARIFInfo[] spdx SPDXInfo[] cdx CycloneDXInfo[] - Finding Finding[] + findings Finding[] + branches GitBranch[] @@id([fullName, orgId]) } +model GitBranch { + name String + repoName String + repo GitRepo @relation(fields: [repoName, orgId], references: [fullName, orgId]) + commitSha String + protected Int @default(0) + orgId String + org Org @relation(fields: [orgId], references: [uuid]) + + @@id([repoName, name]) +} + model SARIFInfo { reportId String @id sarifId String @@ -362,18 +425,6 @@ model AuthorizedDataPublisher { cves CVEADP[] } -// model Lifecycle { -// uuid String @id @default(uuid()) -// event String // list enum String -// date DateTime -// Collection Collection? @relation(fields: [collectionUuid], references: [uuid]) -// collectionUuid String? -// Leaf Leaf? @relation(fields: [leafUuid], references: [uuid]) -// leafUuid String? -// Product Product? @relation(fields: [productUuid], references: [uuid]) -// productUuid String? -// } - model Link { id Int @id @default(autoincrement()) url String @@ -400,6 +451,18 @@ model Artifact { vex Triage[] } +// model Lifecycle { +// uuid String @id @default(uuid()) +// event String // list enum String +// date DateTime +// Collection Collection? @relation(fields: [collectionUuid], references: [uuid]) +// collectionUuid String? +// Leaf Leaf? @relation(fields: [leafUuid], references: [uuid]) +// leafUuid String? +// Product Product? @relation(fields: [productUuid], references: [uuid]) +// productUuid String? +// } + // model Identity { // id String @id @default(cuid()) // type String @@ -428,10 +491,3 @@ model Artifact { // Product Product? @relation(fields: [productUuid], references: [uuid]) // productUuid String? // } - -// model Product { -// uuid String @id @default(uuid()) -// name String -// lifecycle Lifecycle[] -// leafs Leaf[] -// } diff --git a/src/@core/components/AppBarSearch.vue b/src/@core/components/AppBarSearch.vue index 5242b89b..20023396 100644 --- a/src/@core/components/AppBarSearch.vue +++ b/src/@core/components/AppBarSearch.vue @@ -80,23 +80,16 @@ const dialogModelValueUpdate = val => {