diff --git a/src/app.js b/src/app.js index 6486194..faf5801 100644 --- a/src/app.js +++ b/src/app.js @@ -2,10 +2,10 @@ const express = require('express'); const cors = require('cors'); const dotenv = require('dotenv'); // Corrected require statement const Token = require('./middleware/Token'); -const DB = require('./middleware/DB'); const Api = require('./middleware/Api'); const SignIn = require('./models/SignIn'); -const countries = require("i18n-iso-countries"); +const RiskDetections = require('./models/RiskDetection'); +const db = require('./config/db'); const MSTeams = require('./utilities/MSTeams'); @@ -24,6 +24,9 @@ app.use('/logs/signin', signinRoutes); //Get the logs from MS Graph const retrieveLogs = async () => { + //Base URL for the graph API + const baseUrl = 'https://graph.microsoft.com/v1.0'; + //Get the bearer token const bearer = await Token.get(); @@ -32,45 +35,41 @@ const retrieveLogs = async () => { 'Content-type': 'application/json' }; - const baseUrl = 'https://graph.microsoft.com/v1.0'; - const url = baseUrl + '/auditLogs/signIns?$top=500'; + //DEBUGGING FOR THE HEADER + //console.log(header); - console.log(header); - - //Fetch MS Graph API - data is equal to the json returned + //Fetch MS Graph API - sign ins + var url = baseUrl + '/auditLogs/signIns?$top=500'; const signIns = await Api.call(url, header); + //Fetch MS Graph API - risk detections + url = baseUrl + '/identityProtection/riskDetections'; + const riskDetections = await Api.call(url, header); + //This add the fetched data to the signins table in mariadb await SignIn.add(signIns); + await RiskDetections.add(riskDetections); + + //This will add the suspicious users to the suspicioususer table in maria db //console.log(await SignIn.suspicious()); await SignIn.addSuspicious(await SignIn.getSuspicious()); + await SignIn.updateAttemptCount(await SignIn.getSuspiciousAccounts()); } const notify = async () => { //Debugging - console.log("Sending notifications if any exist"); + const currentTime = new Date(); + console.log("[" + currentTime + "] " + "Sending notifications if any exist"); //console.log(await SignIn.getRecentSuspicious()); try { - const results = await SignIn.getRecentSuspicious(); // You should use 'await' here to wait for the results. + const results = await SignIn.getRecentSuspicious(); - if (results && results.length > 0) { // Check if results exist and if it's not an empty array. - for (const result of results) { - var status = 0; - countryOrRegion = countries.getName(result.countryOrRegion, "en"); - if(result.status == 0){ - status = "

Success

"; - } else { - status = "

Failure

"; - } - - MSTeams.send("Alert: Suspicious Login Attempt Detected", "Suspicious sign-in attempt for "+ "

" + result.userDisplayName + " (" + result.userPrincipleName + ")" + "

" + " originating from " + "

" + result.state + ", " + countryOrRegion + "

" + "

Status:

" + status + ""); // Fixed typo 'Suspcious' to 'Suspicious'. - } - } + SignIn.checkIfNotify(results); } catch (error) { // Handle any errors that occur during the process. console.error("Error notifying: " + error); @@ -83,24 +82,41 @@ retrieveLogs(); //Initially notify of any suspicious activities notify(); +const cleanse = async () => { + const connection = await db.getConnection(); + const currentTime = new Date(); + console.log("[" + currentTime + "] " + "Cleansing the system of old records"); + // Delete old records from 'signins' table + const deleteSigninsQuery = "DELETE FROM signins WHERE insertTime <= CONVERT_TZ(NOW(), 'UTC', 'America/Denver') - INTERVAL 3 DAY"; + await connection.query(deleteSigninsQuery); + // Delete old records from 'suspiciousAccounts' table + const deleteSuspiciousQuery = "DELETE FROM suspiciousAccounts WHERE insertTime <= CONVERT_TZ(NOW(), 'UTC', 'America/Denver') - INTERVAL 3 DAY"; + await connection.query(deleteSuspiciousQuery); + connection.release(); // You should use 'await' here to wait for the results. +}; + +cleanse(); + //TODO: //Change the url in the MSTeams.js file before you put this into production!!!! //MSTeams.send("bob", "bobs description"); + const interval = 240000; // 60 seconds const intervalId = setInterval(retrieveLogs, interval); const intervalNotify = 120100; // 2 Minutes + a little (to ensure the alert is not triggered twice) const intervalIdNotify = setInterval(notify, intervalNotify); -const intervalDelete = 43200000; // 12 hours -const intervalIdDelete = setInterval(DB.deleteOldRecords, intervalDelete); +const intervalDelete = 7200000; // 2 hours +const intervalIdDelete = setInterval(cleanse, intervalDelete); const PORT = process.env.APIPORT || 3001; app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); + const currentTime = new Date(); + console.log(`[` + currentTime + `] ` + `Server is running on port ${PORT}`); }); \ No newline at end of file diff --git a/src/middleware/DB.js b/src/middleware/DB.js deleted file mode 100644 index bb05c1f..0000000 --- a/src/middleware/DB.js +++ /dev/null @@ -1,24 +0,0 @@ -const db = require('../config/db'); - -const deleteOldRecords = async () => { - try { - const connection = await db.getConnection(); - - // Delete old records from 'signins' table - const deleteSigninsQuery = 'DELETE FROM signins WHERE insertTime >= NOW() + INTERVAL 4 DAY'; - await connection.query(deleteSigninsQuery); - - // Delete old records from 'suspiciousAccounts' table - const deleteSuspiciousQuery = 'DELETE FROM suspiciousAccounts WHERE insertTime >= NOW() + INTERVAL 4 DAY'; - await connection.query(deleteSuspiciousQuery); - - connection.release(); - } catch (error) { - throw error; - } -}; - - -module.exports = { - deleteOldRecords, -}; \ No newline at end of file diff --git a/src/models/RiskDetection.js b/src/models/RiskDetection.js index 3b1e7fc..b788781 100644 --- a/src/models/RiskDetection.js +++ b/src/models/RiskDetection.js @@ -1,10 +1,10 @@ const db = require('../config/db'); // Import the database connection pool -// Get a list of all sign ins -const all = async () => { +// Get a list of all sign ins +const retrieve = async () => { try { const connection = await db.getConnection(); - const query = 'SELECT * FROM signins'; + const query = 'SELECT * FROM riskDetections'; const signins = await connection.query(query); connection.release(); return signins; @@ -13,52 +13,20 @@ const all = async () => { } }; - - -const getRiskyDetections = async () => { - try { - const connection = await db.getConnection(); - const query = 'SELECT * FROM signins WHERE countryOrRegion <> ?'; // <> means "not equal to" - const countryToExclude = 'CA'; // Change this if you want to exclude a different value - const signins = await connection.query(query, [countryToExclude]); - //console.log({events: signins}); - connection.release(); - return signins; - } catch (error) { - throw error; - } -}; - -const getSuspiciousAccounts = async () => { - try { - const connection = await db.getConnection(); - const query = 'SELECT * FROM suspiciousAccounts'; // <> means "not equal to" - const countryToExclude = 'CA'; // Change this if you want to exclude a different value - const signins = await connection.query(query, [countryToExclude]); - connection.release(); - return signins; - } catch (error) { - throw error; - } -}; - - // Add data to the sign in table // Get a list of all sign ins -const add = async (data) => { +const add = async (data) => { //For debugging //console.log(data.value); - console.log("Added new signins to database, starting again in 60 seconds"); + const currentTime = new Date(); + console.log("[" + currentTime + "] " + "Added new risk detections to database, starting again in 60 seconds"); try { const connection = await db.getConnection(); const results = []; // To store the results const currentDate = new Date(); - // Format the current date as "YYYY-MM-DD" for sql - const formattedDate = `${currentDate.getFullYear()}-${padNumber(currentDate.getMonth() + 1)}-${padNumber(currentDate.getDate())}`; - function padNumber(number) { return number.toString().padStart(2, '0'); } @@ -68,51 +36,44 @@ const add = async (data) => { const { id, + riskState, + riskLevel, + riskDetail, + source, + activity, + ipAddress, + detectedDateTime, userId, - createdDateTime, userDisplayName, userPrincipalName, - appDisplayName, - ipAddress, - deviceDetail, - location + additionalInfo } = logEntry; - const { - displayName, - operatingSystem, - browser - } = deviceDetail; - - const { - city, - state, - countryOrRegion - } = location; - - const errorCode = logEntry.status.errorCode; - + const city = logEntry.location.city; + const state = logEntry.location.state; + const countryOrRegion = logEntry.location.countryOrRegion; const query = ` - INSERT IGNORE INTO signins - (id, userId, createdDateTime, ip, userDisplayName, email, appDisplayName, deviceDisplayName, operatingSystem, browser, city, state, countryOrRegion, insertTime, status) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CONVERT_TZ(NOW(), 'UTC', 'America/Denver'), ?)`; + INSERT IGNORE INTO riskDetections + (id, riskState, riskLevel, riskDetail, source, activity, ipAddress, detectedDateTime, userId, userDisplayName, userPrincipalName, additionalInfo, city, state, countryOrRegion, insertDate) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CONVERT_TZ(NOW(), 'UTC', 'America/Denver'))`; const values = [ id, - userId, - createdDateTime, + riskState, + riskLevel, + riskDetail, + source, + activity, ipAddress, + detectedDateTime, + userId, userDisplayName, userPrincipalName, - appDisplayName, - displayName, - operatingSystem, - browser, + additionalInfo, city, state, - countryOrRegion, - errorCode + countryOrRegion ]; await connection.query(query, values); @@ -125,53 +86,6 @@ const add = async (data) => { } }; -const addSuspicious = async (data) => { - console.log("Adding new suspicious users to database"); - try { - const connection = await db.getConnection(); - const results = []; // To store the results - - for (const logEntry of data) { // Iterate directly if data.value is an array - const { - userId, - userDisplayName, - email, - countryOrRegion, - state, - city, - status - } = logEntry; - - // Check if countryOrRegion is not empty or contains only whitespace characters - if (countryOrRegion && countryOrRegion.trim() !== '') { - const query = ` - INSERT IGNORE INTO suspiciousAccounts - (id, userDisplayName, userPrincipleName, countryOrRegion, state, city, insertTime, status) - VALUES (?, ?, ?, ?, ?, ?, CONVERT_TZ(NOW(), 'UTC', 'America/Denver'), ?)`; - - const values = [ - userId, - userDisplayName, - email, - countryOrRegion, - state, - city, - status - ]; - - await connection.query(query, values); - } - } - - connection.release(); - return results; - } catch (error) { - throw error; - } -}; - - - const getRecentSuspicious = async () => { try { const connection = await db.getConnection(); @@ -189,12 +103,29 @@ const getRecentSuspicious = async () => { } }; +const checkIfNotify = async (results) => { + try { + if (results && results.length > 0) { // Check if results exist and if it's not an empty array. + for (const result of results) { + var status = 0; + countryOrRegion = countries.getName(result.countryOrRegion, "en"); + if(result.status == 0){ + status = "

Success

"; + } else { + status = "

Failure

"; + } + + MSTeams.send("Alert: Risky Event Detected", "Risky sign-in attempt for "+ "

" + result.userDisplayName + " (" + result.userPrincipleName + ")" + "

" + " originating from " + "

" + result.state + ", " + countryOrRegion + "

" + "

Status:

" + status + ""); // Fixed typo 'Suspcious' to 'Suspicious'. + } + } + } catch (error) { + throw error; + } +}; + module.exports = { - all, + retrieve, add, - getSuspicious, - addSuspicious, - getSuspiciousAccounts, - getRecentSuspicious + checkIfNotify }; \ No newline at end of file diff --git a/src/models/SignIn.js b/src/models/SignIn.js index 060686a..ac6f830 100644 --- a/src/models/SignIn.js +++ b/src/models/SignIn.js @@ -1,4 +1,6 @@ const db = require('../config/db'); // Import the database connection pool +const Countries = require("i18n-iso-countries"); +const MSTeams = require('../utilities/MSTeams'); // Import the MS teams module // Get a list of all sign ins const all = async () => { @@ -47,16 +49,14 @@ const add = async (data) => { //For debugging //console.log(data.value); - console.log("Added new signins to database, starting again in 60 seconds"); + const currentTime = new Date(); + console.log("[" + currentTime + "] " + "Added new signins to database, starting again in 60 seconds"); try { const connection = await db.getConnection(); const results = []; // To store the results const currentDate = new Date(); - // Format the current date as "YYYY-MM-DD" for sql - const formattedDate = `${currentDate.getFullYear()}-${padNumber(currentDate.getMonth() + 1)}-${padNumber(currentDate.getDate())}`; - function padNumber(number) { return number.toString().padStart(2, '0'); } @@ -123,8 +123,51 @@ const add = async (data) => { } }; +const getAttemptCount = async (id) => { + const connection = await db.getConnection(); + + const query = "SELECT COUNT(*) AS attempts FROM signins WHERE userId = ? AND countryOrRegion <> ?"; + + const country = "CA"; + + const results = await connection.query(query, [id, country]); + + // Convert the BigInt to a regular JavaScript number + const attempts = results[0] ? Number(results[0].attempts) : 0; + + connection.release(); + + return attempts; +}; + +const updateAttemptCount = async (userDetails) => { + /* + * @summary Takes an object of objects and updates the attempts column in the suspiciousAccounts table + * @params {Object} userDetails - An object of JSON results from the suspiciousAccounts table showing all user details + * @returns null + */ + + const connection = await db.getConnection(); + + for (const user of userDetails) { + let query = "SELECT COUNT(*) AS attempts FROM signins WHERE userId = ? AND countryOrRegion <> ?"; + const country = "CA"; + const results = await connection.query(query, [user.id, country]); + + const attempts = results[0] ? Number(results[0].attempts) : 0; + + query = "UPDATE suspiciousAccounts SET attemptCount = ? WHERE id = ?"; + await connection.query(query, [attempts, user.id]); + } + + connection.release(); +}; + + + const addSuspicious = async (data) => { - console.log("Adding new suspicious users to database"); + const currentTime = new Date(); + console.log("[" + currentTime + "] " + "Adding new suspicious users to database"); try { const connection = await db.getConnection(); const results = []; // To store the results @@ -169,14 +212,19 @@ const addSuspicious = async (data) => { }; - const getRecentSuspicious = async () => { + /* + * @params null + * @returns {Object} - All of the suspicious accounts that exist that had successful signIns + * + * */ try { const connection = await db.getConnection(); const query = ` - SELECT * - FROM suspiciousAccounts - WHERE insertTime >= CONVERT_TZ(NOW(), 'UTC', 'America/Denver') - INTERVAL 2 MINUTE`; + SELECT * + FROM suspiciousAccounts + WHERE insertTime >= CONVERT_TZ(NOW(), 'UTC', 'America/Denver') - INTERVAL 2 MINUTE + AND status = 0`; const signins = await connection.query(query); @@ -187,6 +235,32 @@ const getRecentSuspicious = async () => { } }; +//Takes a +const checkIfNotify = async (results) => { + /* + * @summary Check if a notification needs to be sent out about a suspicious login + * @param {json Object} results - An object of all of the users and their details that are suspicious and were inserted into the table in the last 2 minutes + * @returns null or {string} error message + */ + try { + if (results && results.length > 0) { // Check if results exist and if it's not an empty array. + for (const result of results) { + var status = 0; + countryOrRegion = Countries.getName(result.countryOrRegion, "en"); + if(result.status == 0){ + status = "

Success

"; + } else { + status = "

Failure

"; + } + + MSTeams.send("Alert: Suspicious Login Attempt Detected", "Suspicious sign-in attempt for "+ "

" + result.userDisplayName + " (" + result.userPrincipleName + ")" + "

" + " originating from " + "

" + result.state + ", " + countryOrRegion + "

" + "

Status:

" + status + ""); + } + } + } catch (error) { + throw error; + } +}; + module.exports = { all, @@ -194,5 +268,8 @@ module.exports = { getSuspicious, addSuspicious, getSuspiciousAccounts, - getRecentSuspicious + getRecentSuspicious, + checkIfNotify, + getAttemptCount, + updateAttemptCount }; \ No newline at end of file diff --git a/src/utilities/MSTeams.js b/src/utilities/MSTeams.js index f5002ac..03f2102 100644 --- a/src/utilities/MSTeams.js +++ b/src/utilities/MSTeams.js @@ -4,8 +4,8 @@ var url = "https://mpeeng.webhook.office.com/webhookb2/698bec0f-4bb5-455a-a5c1-5 var testingUrl = "https://mpeeng.webhook.office.com/webhookb2/1616eaa3-c14c-43eb-881d-517a69df2329@538b9b1c-23fa-4102-b36e-a4d83fc9c4c1/IncomingWebhook/50a58aa7e3d0407a96cea03d467d7b6e/3745f560-1ea1-46a8-96ec-dc6772195127" -//Remove this if you are not testing -//url = testingUrl; +//Comment this out if you are not testing +url = testingUrl; const webhookUrl = url; const webhook = new IncomingWebhook(webhookUrl);