From 165bf4875e129e9b85171d5f0113432af0f04e6e Mon Sep 17 00:00:00 2001 From: Jasper Mayone Date: Wed, 26 Feb 2025 03:19:04 -0500 Subject: [PATCH] MAJOR CLEANING --- src/{utils => defs}/headers.ts | 0 src/defs/interfaces.ts | 13 + src/middleware/logRequest.ts | 2 +- src/routes/admin/router.ts | 4 +- src/routes/admin/routes/domain.ts | 8 +- src/routes/admin/swaggerOptions.ts | 2 +- src/routes/domain/check.ts | 268 ++---------------- src/routes/domain/classify.ts | 2 +- src/routes/domain/report.ts | 6 +- src/routes/email.ts | 2 +- src/routes/misc.ts | 2 +- src/routes/user.ts | 8 +- src/services/GoogleSafebrowsing.ts | 2 +- src/services/IpQualityScore.ts | 4 +- src/services/PhishObserver.ts | 4 +- src/services/PhishReport.ts | 4 +- src/services/SecurityTrails.ts | 4 +- src/services/SinkingYahts.ts | 4 +- src/services/UrlScan.ts | 4 +- src/services/VirusTotal.ts | 4 +- src/services/Walshy.ts | 4 +- src/swaggerOptions.ts | 2 +- src/utils/db/findOrCreateDomain.ts | 29 ++ src/{functions => utils}/db/getDbDomain.ts | 0 src/{functions => utils}/db/getDbUser.ts | 0 .../domain/checkAndUpdateDomainStatus.ts | 37 +++ src/{functions => utils/domain}/domain.ts | 39 +-- src/utils/domain/domainReport.ts | 36 +++ src/{functions => utils/domain}/parseData.ts | 0 src/utils/domain/prepareResponse.ts | 45 +++ src/utils/domain/reportToAlienVault.ts | 28 ++ .../domain/validateAndExtractDomainParams.ts | 29 ++ src/{functions => utils}/getVersion.ts | 0 src/{functions => utils}/jwt.ts | 0 .../userNeedsExtendedData.ts | 0 src/{functions => utils}/validateEmail.ts | 0 src/utils/validationError.ts | 7 + 37 files changed, 281 insertions(+), 322 deletions(-) rename src/{utils => defs}/headers.ts (100%) create mode 100644 src/defs/interfaces.ts create mode 100644 src/utils/db/findOrCreateDomain.ts rename src/{functions => utils}/db/getDbDomain.ts (100%) rename src/{functions => utils}/db/getDbUser.ts (100%) create mode 100644 src/utils/domain/checkAndUpdateDomainStatus.ts rename src/{functions => utils/domain}/domain.ts (68%) create mode 100644 src/utils/domain/domainReport.ts rename src/{functions => utils/domain}/parseData.ts (100%) create mode 100644 src/utils/domain/prepareResponse.ts create mode 100644 src/utils/domain/reportToAlienVault.ts create mode 100644 src/utils/domain/validateAndExtractDomainParams.ts rename src/{functions => utils}/getVersion.ts (100%) rename src/{functions => utils}/jwt.ts (100%) rename src/{functions => utils}/userNeedsExtendedData.ts (100%) rename src/{functions => utils}/validateEmail.ts (100%) create mode 100644 src/utils/validationError.ts diff --git a/src/utils/headers.ts b/src/defs/headers.ts similarity index 100% rename from src/utils/headers.ts rename to src/defs/headers.ts diff --git a/src/defs/interfaces.ts b/src/defs/interfaces.ts new file mode 100644 index 0000000..ea0211f --- /dev/null +++ b/src/defs/interfaces.ts @@ -0,0 +1,13 @@ +/** + * Interface for domain check response + */ +export interface DomainCheckResponse { + domain: string; + phishing: boolean; + times: { + createdAt: Date; + updatedAt: Date; + lastChecked: Date | null; + }; + rawData?: any[]; // Optional property for extended data +} diff --git a/src/middleware/logRequest.ts b/src/middleware/logRequest.ts index 78ed2d9..8af0ad3 100644 --- a/src/middleware/logRequest.ts +++ b/src/middleware/logRequest.ts @@ -1,8 +1,8 @@ import { NextFunction, Request, Response } from "express"; import requestIp from "request-ip"; -import { getUserInfo } from "../functions/jwt"; import { prisma } from "../prisma"; +import { getUserInfo } from "../utils/jwt"; import { log } from "../utils/logger"; let monitoringAgents = ["Checkly/", "Uptime-Kuma/"]; diff --git a/src/routes/admin/router.ts b/src/routes/admin/router.ts index 20e2c46..6301cfc 100644 --- a/src/routes/admin/router.ts +++ b/src/routes/admin/router.ts @@ -1,9 +1,9 @@ import express from "express"; import moment from "moment"; -import { getPackageVersion, getVersion } from "../../functions/getVersion"; -import { authenticateToken, getUserInfo } from "../../functions/jwt"; import { logRequest } from "../../middleware/logRequest"; import { prisma } from "../../prisma"; +import { getPackageVersion, getVersion } from "../../utils/getVersion"; +import { authenticateToken, getUserInfo } from "../../utils/jwt"; import domainRouter from "./routes/domain"; import userRouter from "./routes/user"; diff --git a/src/routes/admin/routes/domain.ts b/src/routes/admin/routes/domain.ts index ea11920..e82e9c3 100644 --- a/src/routes/admin/routes/domain.ts +++ b/src/routes/admin/routes/domain.ts @@ -1,11 +1,11 @@ import axios from "axios"; import express, { Request, Response } from "express"; -import { getDbDomain } from "../../../functions/db/getDbDomain"; -import { domainReport } from "../../../functions/domain"; -import { getUserInfo } from "../../../functions/jwt"; +import { headersWithOTX } from "../../../defs/headers"; import { prisma } from "../../../prisma"; -import { headersWithOTX } from "../../../utils/headers"; +import { getDbDomain } from "../../../utils/db/getDbDomain"; +import { domainReport } from "../../../utils/domain/domainReport"; +import { getUserInfo } from "../../../utils/jwt"; /* GET domain - Get all domains diff --git a/src/routes/admin/swaggerOptions.ts b/src/routes/admin/swaggerOptions.ts index 67911bb..6e6a9ff 100644 --- a/src/routes/admin/swaggerOptions.ts +++ b/src/routes/admin/swaggerOptions.ts @@ -1,4 +1,4 @@ -import { getVersion } from "../../functions/getVersion"; +import { getVersion } from "../../utils/getVersion"; let filePattern; if (process.env.NODE_ENV === "production") { diff --git a/src/routes/domain/check.ts b/src/routes/domain/check.ts index 65e15cf..afe4623 100644 --- a/src/routes/domain/check.ts +++ b/src/routes/domain/check.ts @@ -1,272 +1,42 @@ -import axios from "axios"; - import express from "express"; const router = express.Router(); -import { domainCheck } from "../../functions/domain"; -import { authenticateToken } from "../../functions/jwt"; -import { parseData } from "../../functions/parseData"; -import { userNeedsExtendedData } from "../../functions/userNeedsExtendedData"; -import { prisma } from "../../prisma"; - -import { headersWithOTX } from "../../utils/headers"; +import { findOrCreateDomain } from "../../utils/db/findOrCreateDomain"; +import { prepareResponse } from "../../utils/domain/prepareResponse"; +import { validateAndExtractParams } from "../../utils/domain/validateAndExtractDomainParams"; +import { authenticateToken } from "../../utils/jwt"; +import { ValidationError } from "../../utils/validationError"; /** * GET /domain/check * @summary Checks if a domain is phishing/malicious. * @description This endpoint checks if a domain is phishing/malicious. - It will return a boolean value of true if it is phishing/malicious and false if it is not, and some other data. - - Internally, this queries multiple sources to check if the domain is phishing/malicious, including but not limited to: - - Walshy's API - - IPQualityScore - - Google Safebrowsing - - Sinking Yahts - - PhishTank - - OpenPhish - - Lots more... - -We also keep our own database of domains and their status, so we can return the status of the domain quickly if it has been checked before. - + * It will return a boolean value of true if it is phishing/malicious and false if it is not, and some other data. * @tags Domain - Endpoints for checking / reporting domains. * @security BearerAuth * @param {string} domain.query.required - Domain to check * @param {boolean} extendData.query.optional - Optionally, request extra information about the domain (MAY HAVE LONGER RESPONSE TIME). You need special permissions to access this. * @return {object} 200 - Success message * @return {string} 400 - Error message - * @example response - 200 - Success message - * { - * "domain": "google.com", - * "phishing": false, - * "times": { - * "createdAt": "2024-08-14T00:00:00.000Z", - * "updatedAt": "2024-08-14T00:00:00.000Z", - * "lastChecked": "2024-08-14T00:00:00.000Z" - * } - * } - * @example response - 400 - Error: No domain parameter - * "No domain parameter found" - * @example response - 400 - Error: Invalid domain parameter - * "Invalid domain parameter, should be a top level domain. Ex: google.com, amazon.com" */ router.get("/", authenticateToken, async (req, res) => { - // metrics.increment("endpoint.domain.check"); - - // look for the query parameter - const query = req.query!; - - let domain: string = query.domain! as string; - let extendData = await userNeedsExtendedData(req); - - // check for domain parameter - if (!domain || domain === "" || domain === undefined || domain === null) { - return res.status(400).json("No domain parameter found"); - } - - // validate the domain (should be a top level domain - // and not a subdomain - // ex: google.com amazn.com - // not: mail.google.com, docs.google.com) - - let regex = new RegExp("^(?!http://|https://)[a-zA-Z0-9-]+.[a-zA-Z]{2,}$"); - if (!regex.test(domain)) { - return res - .status(400) - .json( - "Invalid domain parameter, should be a top level domain. Ex: google.com, amazon.com" - ); - } - - let dbDomain = await prisma.domain.findFirst({ - where: { - domain: domain, - }, - }); - - if (!dbDomain) { - dbDomain = await prisma.domain.create({ - data: { - domain: domain, - }, - }); - - let data = await domainCheck(domain); - - let walshyData = data.walshyData; - let ipQualityScoreData = data.ipQualityScoreData; - let googleSafebrowsingData = data.googleSafebrowsingData; - let sinkingYahtsData = data.sinkingYahtsData; - let virusTotalData = data.virusTotalData; - let phishObserverData = data.phishObserverData; - let urlScanData = data.urlScanData; - let securitytrailsData = data.securitytrailsData; - let phishreportData = data.phishreportData; - - let isPhish = await parseData( - walshyData, - ipQualityScoreData, - googleSafebrowsingData, - sinkingYahtsData, - virusTotalData, - phishObserverData, - urlScanData, - securitytrailsData, - phishreportData - ); - - if (isPhish) { - await prisma.domain.update({ - where: { - id: dbDomain.id, - }, - data: { - malicious: true, - lastChecked: new Date(), - }, - }); - - await axios.patch( - "https://otx.alienvault.com/api/v1/pulses/6785dccb041b628fde283705", - { - indicators: { - add: [ - { - indicator: `${domain}`, - type: "domain", - role: "phishing", - }, - ], - }, - }, - { - headers: headersWithOTX, - } - ); - - let response = { - domain: domain, - phishing: true, - times: { - createdAt: dbDomain.createdAt, - updatedAt: dbDomain.updatedAt, - lastChecked: dbDomain.lastChecked, - }, - }; - - if (extendData) { - let rawAPIData = await prisma.rawAPIData.findMany({ - where: { - domainId: dbDomain.id, - }, - }); - - // push the raw data to the response - response["rawData"] = rawAPIData; - } - - return res.status(200).json(response); - } else { - await prisma.domain.update({ - where: { - id: dbDomain.id, - }, - data: { - malicious: false, - lastChecked: new Date(), - }, - }); - - let response = { - domain: domain, - phishing: false, - times: { - createdAt: dbDomain.createdAt, - updatedAt: dbDomain.updatedAt, - lastChecked: dbDomain.lastChecked, - }, - }; - - if (extendData) { - let rawAPIData = await prisma.rawAPIData.findMany({ - where: { - domainId: dbDomain.id, - }, - }); - - // push the raw data to the response - response["rawData"] = rawAPIData; - } - - return res.status(200).json(response); - } - } else { - domainCheck(domain); - - if (dbDomain.malicious) { - await axios.patch( - "https://otx.alienvault.com/api/v1/pulses/6785dccb041b628fde283705", - { - indicators: { - add: [ - { - indicator: `${domain}`, - type: "domain", - role: "phishing", - }, - ], - }, - }, - { - headers: headersWithOTX, - } - ); - - let response = { - domain: domain, - phishing: true, - times: { - createdAt: dbDomain.createdAt, - updatedAt: dbDomain.updatedAt, - lastChecked: dbDomain.lastChecked, - }, - }; - - if (extendData) { - let rawAPIData = await prisma.rawAPIData.findMany({ - where: { - domainId: dbDomain.id, - }, - }); - - // push the raw data to the response - response["rawData"] = rawAPIData; - } - - return res.status(200).json(response); - } else { - let response = { - domain: domain, - phishing: false, - times: { - createdAt: dbDomain.createdAt, - updatedAt: dbDomain.updatedAt, - lastChecked: dbDomain.lastChecked, - }, - }; + try { + // Validate and extract query parameters + const { domain, extendData } = await validateAndExtractParams(req); - if (extendData) { - let rawAPIData = await prisma.rawAPIData.findMany({ - where: { - domainId: dbDomain.id, - }, - }); + // Check if domain exists in database + const dbDomain = await findOrCreateDomain(domain); - // push the raw data to the response - response["rawData"] = rawAPIData; - } + // Prepare and send response + const response = await prepareResponse(domain, dbDomain, extendData); - return res.status(200).json(response); + return res.status(200).json(response); + } catch (error) { + if (error instanceof ValidationError) { + return res.status(400).json(error.message); } + console.error("Error in domain check:", error); + return res.status(500).json("Internal server error"); } }); diff --git a/src/routes/domain/classify.ts b/src/routes/domain/classify.ts index 85211ee..6da9749 100644 --- a/src/routes/domain/classify.ts +++ b/src/routes/domain/classify.ts @@ -3,8 +3,8 @@ const router = express.Router(); import { Classifications } from "@prisma/client"; import * as jwt from "jsonwebtoken"; -import { authenticateToken } from "../../functions/jwt"; import { prisma } from "../../prisma"; +import { authenticateToken } from "../../utils/jwt"; /** * PUT /domain/classify diff --git a/src/routes/domain/report.ts b/src/routes/domain/report.ts index 53a7f33..c357f9f 100644 --- a/src/routes/domain/report.ts +++ b/src/routes/domain/report.ts @@ -1,10 +1,10 @@ import express from "express"; const router = express.Router(); -import { getDbDomain } from "../../functions/db/getDbDomain"; -import { domainReport } from "../../functions/domain"; -import { authenticateToken, getUserInfo } from "../../functions/jwt"; import { prisma } from "../../prisma"; +import { getDbDomain } from "../../utils/db/getDbDomain"; +import { domainReport } from "../../utils/domain/domainReport"; +import { authenticateToken, getUserInfo } from "../../utils/jwt"; /** * POST /domain/report diff --git a/src/routes/email.ts b/src/routes/email.ts index fdd3faf..5ecd705 100644 --- a/src/routes/email.ts +++ b/src/routes/email.ts @@ -1,7 +1,7 @@ import * as express from "express"; -import { validateEmail } from "../functions/validateEmail"; import { logRequest } from "../middleware/logRequest"; import { ipQualityScoreService } from "../services/_index"; +import { validateEmail } from "../utils/validateEmail"; const router = express.Router(); router.use(express.json()); diff --git a/src/routes/misc.ts b/src/routes/misc.ts index 0486562..337f96e 100644 --- a/src/routes/misc.ts +++ b/src/routes/misc.ts @@ -1,8 +1,8 @@ import * as express from "express"; import moment from "moment"; -import { getVersion } from "../functions/getVersion"; import { logRequest } from "../middleware/logRequest"; import { prisma } from "../prisma"; +import { getVersion } from "../utils/getVersion"; const router = express.Router(); router.use(express.json()); diff --git a/src/routes/user.ts b/src/routes/user.ts index dc22235..4eee6ef 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,14 +1,14 @@ import bcrypt from "bcrypt"; import express from "express"; +import { logRequest } from "../middleware/logRequest"; +import { prisma } from "../prisma"; import { authenticateToken, generateAccessToken, getUserInfo, -} from "../functions/jwt"; -import { userNeedsExtendedData } from "../functions/userNeedsExtendedData"; -import { logRequest } from "../middleware/logRequest"; -import { prisma } from "../prisma"; +} from "../utils/jwt"; +import { userNeedsExtendedData } from "../utils/userNeedsExtendedData"; const router = express.Router(); router.use(express.json()); diff --git a/src/services/GoogleSafebrowsing.ts b/src/services/GoogleSafebrowsing.ts index 296d97f..c135988 100644 --- a/src/services/GoogleSafebrowsing.ts +++ b/src/services/GoogleSafebrowsing.ts @@ -1,7 +1,7 @@ import axios from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; import { prisma } from "../prisma"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; /** diff --git a/src/services/IpQualityScore.ts b/src/services/IpQualityScore.ts index 04e8928..5a9de2a 100644 --- a/src/services/IpQualityScore.ts +++ b/src/services/IpQualityScore.ts @@ -1,8 +1,8 @@ import axios from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; +import { headers } from "../defs/headers"; import { prisma } from "../prisma"; -import { headers } from "../utils/headers"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; /** diff --git a/src/services/PhishObserver.ts b/src/services/PhishObserver.ts index d2c1664..5b1b532 100644 --- a/src/services/PhishObserver.ts +++ b/src/services/PhishObserver.ts @@ -1,7 +1,7 @@ import axios, { AxiosError } from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; +import { headersWithPhishObserver } from "../defs/headers"; import { prisma } from "../prisma"; -import { headersWithPhishObserver } from "../utils/headers"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; interface PhishObserverError { diff --git a/src/services/PhishReport.ts b/src/services/PhishReport.ts index c33b2bc..1cf9476 100644 --- a/src/services/PhishReport.ts +++ b/src/services/PhishReport.ts @@ -1,8 +1,8 @@ import axios from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; +import { headers } from "../defs/headers"; import { prisma } from "../prisma"; -import { headers } from "../utils/headers"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; /** diff --git a/src/services/SecurityTrails.ts b/src/services/SecurityTrails.ts index 07bf7e4..2298cf0 100644 --- a/src/services/SecurityTrails.ts +++ b/src/services/SecurityTrails.ts @@ -1,7 +1,7 @@ import axios from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; +import { headersWithSecurityTrails } from "../defs/headers"; import { prisma } from "../prisma"; -import { headersWithSecurityTrails } from "../utils/headers"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; /** diff --git a/src/services/SinkingYahts.ts b/src/services/SinkingYahts.ts index 5cad7a3..384cca7 100644 --- a/src/services/SinkingYahts.ts +++ b/src/services/SinkingYahts.ts @@ -1,8 +1,8 @@ import axios from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; +import { headersWithSinkingYahts } from "../defs/headers"; import { prisma } from "../prisma"; -import { headersWithSinkingYahts } from "../utils/headers"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; /** diff --git a/src/services/UrlScan.ts b/src/services/UrlScan.ts index 3b0f65a..cc56695 100644 --- a/src/services/UrlScan.ts +++ b/src/services/UrlScan.ts @@ -1,8 +1,8 @@ import axios from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; +import { headersWithUrlScan } from "../defs/headers"; import { prisma } from "../prisma"; -import { headersWithUrlScan } from "../utils/headers"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; /** diff --git a/src/services/VirusTotal.ts b/src/services/VirusTotal.ts index 07c9295..f0c43b2 100644 --- a/src/services/VirusTotal.ts +++ b/src/services/VirusTotal.ts @@ -1,8 +1,8 @@ import axios from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; +import { headersWithVirusTotal } from "../defs/headers"; import { prisma } from "../prisma"; -import { headersWithVirusTotal } from "../utils/headers"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; /** diff --git a/src/services/Walshy.ts b/src/services/Walshy.ts index 1fb181a..37c10d5 100644 --- a/src/services/Walshy.ts +++ b/src/services/Walshy.ts @@ -1,8 +1,8 @@ import axios from "axios"; -import { getDbDomain } from "../functions/db/getDbDomain"; +import { headers } from "../defs/headers"; import { prisma } from "../prisma"; -import { headers } from "../utils/headers"; +import { getDbDomain } from "../utils/db/getDbDomain"; import { sanitizeDomain } from "../utils/sanitizeDomain"; /** diff --git a/src/swaggerOptions.ts b/src/swaggerOptions.ts index 2a3a84b..09c0afd 100644 --- a/src/swaggerOptions.ts +++ b/src/swaggerOptions.ts @@ -1,4 +1,4 @@ -import { getVersion } from "./functions/getVersion"; +import { getVersion } from "./utils/getVersion"; let filePattern; if (process.env.NODE_ENV === "production") { diff --git a/src/utils/db/findOrCreateDomain.ts b/src/utils/db/findOrCreateDomain.ts new file mode 100644 index 0000000..cc3fc65 --- /dev/null +++ b/src/utils/db/findOrCreateDomain.ts @@ -0,0 +1,29 @@ +import { prisma } from "../../prisma"; +import { checkAndUpdateDomainStatus } from "../domain/checkAndUpdateDomainStatus"; +import { domainCheck } from "../domain/domain"; + +/** + * Finds a domain in the database or creates a new entry if not found + * @param {string} domain - Domain to find or create + * @returns {Object} - Domain database record + */ +export async function findOrCreateDomain(domain) { + let dbDomain = await prisma.domain.findFirst({ + where: { domain: domain }, + }); + + if (!dbDomain) { + dbDomain = await prisma.domain.create({ + data: { domain: domain }, + }); + + await checkAndUpdateDomainStatus(domain, dbDomain.id); + } else { + // Trigger a check but don't wait for results + domainCheck(domain).catch((err) => + console.error(`Error checking domain ${domain}:`, err) + ); + } + + return dbDomain; +} diff --git a/src/functions/db/getDbDomain.ts b/src/utils/db/getDbDomain.ts similarity index 100% rename from src/functions/db/getDbDomain.ts rename to src/utils/db/getDbDomain.ts diff --git a/src/functions/db/getDbUser.ts b/src/utils/db/getDbUser.ts similarity index 100% rename from src/functions/db/getDbUser.ts rename to src/utils/db/getDbUser.ts diff --git a/src/utils/domain/checkAndUpdateDomainStatus.ts b/src/utils/domain/checkAndUpdateDomainStatus.ts new file mode 100644 index 0000000..d78ad29 --- /dev/null +++ b/src/utils/domain/checkAndUpdateDomainStatus.ts @@ -0,0 +1,37 @@ +import { prisma } from "../../prisma"; +import { domainCheck } from "./domain"; +import { parseData } from "./parseData"; +import { reportToAlienVault } from "./reportToAlienVault"; + +/** + * Checks domain against various services and updates its status in the database + * @param {string} domain - Domain to check + * @param {string} domainId - Database ID of the domain + */ +export async function checkAndUpdateDomainStatus(domain, domainId) { + const data = await domainCheck(domain); + + const isPhish = await parseData( + data.walshyData, + data.ipQualityScoreData, + data.googleSafebrowsingData, + data.sinkingYahtsData, + data.virusTotalData, + data.phishObserverData, + data.urlScanData, + data.securitytrailsData, + data.phishreportData + ); + + await prisma.domain.update({ + where: { id: domainId }, + data: { + malicious: Boolean(isPhish), // Ensure it's a boolean primitive + lastChecked: new Date(), + }, + }); + + if (isPhish) { + await reportToAlienVault(domain); + } +} diff --git a/src/functions/domain.ts b/src/utils/domain/domain.ts similarity index 68% rename from src/functions/domain.ts rename to src/utils/domain/domain.ts index 628f19a..ce9751c 100644 --- a/src/functions/domain.ts +++ b/src/utils/domain/domain.ts @@ -1,7 +1,5 @@ -import axios from "axios"; - // import metrics from "../metrics"; -import { prisma } from "../prisma"; +import { prisma } from "../../prisma"; import { googleSafebrowsingService, ipQualityScoreService, @@ -12,8 +10,7 @@ import { urlScanService, virusTotalService, walshyService, -} from "../services/_index"; -import { headersWithOTX } from "../utils/headers"; +} from "../../services/_index"; /** * Check the domain against all the services @@ -64,35 +61,3 @@ export async function domainCheck(domain: string) { phishreportData, }; } - -/** -* Report the domain to all services that support reporting -@param domain -@returns void -*/ -export async function domainReport(domain: string) { - let virustotaldata = await virusTotalService.domain.report(domain); - let walshydata = await walshyService.domain.report(domain); - - await axios.patch( - "https://otx.alienvault.com/api/v1/pulses/6785dccb041b628fde283705", - { - indicators: { - add: [ - { - indicator: `${domain}`, - type: "domain", - }, - ], - }, - }, - { - headers: headersWithOTX, - } - ); - - return { - virustotaldata, - walshydata, - }; -} diff --git a/src/utils/domain/domainReport.ts b/src/utils/domain/domainReport.ts new file mode 100644 index 0000000..b19d053 --- /dev/null +++ b/src/utils/domain/domainReport.ts @@ -0,0 +1,36 @@ +import axios from "axios"; + +import { headersWithOTX } from "../../defs/headers"; +import { virusTotalService, walshyService } from "../../services/_index"; + +/** +* Report the domain to all services that support reporting +@param domain +@returns void +*/ +export async function domainReport(domain: string) { + let virustotaldata = await virusTotalService.domain.report(domain); + let walshydata = await walshyService.domain.report(domain); + + await axios.patch( + "https://otx.alienvault.com/api/v1/pulses/6785dccb041b628fde283705", + { + indicators: { + add: [ + { + indicator: `${domain}`, + type: "domain", + }, + ], + }, + }, + { + headers: headersWithOTX, + } + ); + + return { + virustotaldata, + walshydata, + }; +} diff --git a/src/functions/parseData.ts b/src/utils/domain/parseData.ts similarity index 100% rename from src/functions/parseData.ts rename to src/utils/domain/parseData.ts diff --git a/src/utils/domain/prepareResponse.ts b/src/utils/domain/prepareResponse.ts new file mode 100644 index 0000000..84ad69c --- /dev/null +++ b/src/utils/domain/prepareResponse.ts @@ -0,0 +1,45 @@ +import { DomainCheckResponse } from "../../defs/interfaces"; +import { prisma } from "../../prisma"; +import { reportToAlienVault } from "./reportToAlienVault"; + +/** + * Prepares the response object based on domain status and extended data flag + * @param {string} domain - Domain that was checked + * @param {Object} dbDomain - Domain database record + * @param {boolean} extendData - Whether to include extended data + * @returns {Promise} - Response object + */ +export async function prepareResponse( + domain, + dbDomain, + extendData +): Promise { + // If domain is malicious, report it to AlienVault + if (dbDomain.malicious) { + await reportToAlienVault(domain).catch((err) => + console.error(`Error reporting ${domain} to AlienVault:`, err) + ); + } + + // Prepare base response + const response: DomainCheckResponse = { + domain: domain, + phishing: Boolean(dbDomain.malicious), + times: { + createdAt: dbDomain.createdAt, + updatedAt: dbDomain.updatedAt, + lastChecked: dbDomain.lastChecked, + }, + }; + + // Add raw API data if extended data is requested + if (extendData) { + const rawAPIData = await prisma.rawAPIData.findMany({ + where: { domainId: dbDomain.id }, + }); + + response.rawData = rawAPIData; + } + + return response; +} diff --git a/src/utils/domain/reportToAlienVault.ts b/src/utils/domain/reportToAlienVault.ts new file mode 100644 index 0000000..d5a32e6 --- /dev/null +++ b/src/utils/domain/reportToAlienVault.ts @@ -0,0 +1,28 @@ +import axios from "axios"; +import { headersWithOTX } from "../../defs/headers"; + +/** + * Reports a malicious domain to AlienVault OTX + * @param {string} domain - Domain to report + */ +export async function reportToAlienVault(domain) { + try { + await axios.patch( + "https://otx.alienvault.com/api/v1/pulses/6785dccb041b628fde283705", + { + indicators: { + add: [ + { + indicator: domain, + type: "domain", + role: "phishing", + }, + ], + }, + }, + { headers: headersWithOTX } + ); + } catch (error) { + console.error(`Failed to report domain ${domain} to AlienVault:`, error); + } +} diff --git a/src/utils/domain/validateAndExtractDomainParams.ts b/src/utils/domain/validateAndExtractDomainParams.ts new file mode 100644 index 0000000..2ad0bde --- /dev/null +++ b/src/utils/domain/validateAndExtractDomainParams.ts @@ -0,0 +1,29 @@ +import { userNeedsExtendedData } from "../userNeedsExtendedData"; +import { ValidationError } from "../validationError"; + +/** + * Validates the domain parameter and extracts query parameters + * @param {Request} req - Express request object + * @returns {Object} - Object containing validated domain and extendData flag + * @throws {ValidationError} - If domain is missing or invalid + */ +export async function validateAndExtractParams(req) { + const query = req.query; + const domain = query.domain; + const extendData = await userNeedsExtendedData(req); + + // Check if domain parameter exists + if (!domain || domain === "" || domain === undefined || domain === null) { + throw new ValidationError("No domain parameter found"); + } + + // Validate domain format + const regex = new RegExp("^(?!http://|https://)[a-zA-Z0-9-]+.[a-zA-Z]{2,}$"); + if (!regex.test(domain)) { + throw new ValidationError( + "Invalid domain parameter, should be a top level domain. Ex: google.com, amazon.com" + ); + } + + return { domain, extendData }; +} diff --git a/src/functions/getVersion.ts b/src/utils/getVersion.ts similarity index 100% rename from src/functions/getVersion.ts rename to src/utils/getVersion.ts diff --git a/src/functions/jwt.ts b/src/utils/jwt.ts similarity index 100% rename from src/functions/jwt.ts rename to src/utils/jwt.ts diff --git a/src/functions/userNeedsExtendedData.ts b/src/utils/userNeedsExtendedData.ts similarity index 100% rename from src/functions/userNeedsExtendedData.ts rename to src/utils/userNeedsExtendedData.ts diff --git a/src/functions/validateEmail.ts b/src/utils/validateEmail.ts similarity index 100% rename from src/functions/validateEmail.ts rename to src/utils/validateEmail.ts diff --git a/src/utils/validationError.ts b/src/utils/validationError.ts new file mode 100644 index 0000000..ff30c92 --- /dev/null +++ b/src/utils/validationError.ts @@ -0,0 +1,7 @@ +// Custom error class for validation errors +export class ValidationError extends Error { + constructor(message) { + super(message); + this.name = "ValidationError"; + } +}