This commit is contained in:
Donavon McDowell 2023-10-03 08:08:21 -06:00
parent 2251b40756
commit faca208a50
5 changed files with 181 additions and 181 deletions

View File

@ -2,10 +2,10 @@ const express = require('express');
const cors = require('cors'); const cors = require('cors');
const dotenv = require('dotenv'); // Corrected require statement const dotenv = require('dotenv'); // Corrected require statement
const Token = require('./middleware/Token'); const Token = require('./middleware/Token');
const DB = require('./middleware/DB');
const Api = require('./middleware/Api'); const Api = require('./middleware/Api');
const SignIn = require('./models/SignIn'); 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'); const MSTeams = require('./utilities/MSTeams');
@ -24,6 +24,9 @@ app.use('/logs/signin', signinRoutes);
//Get the logs from MS Graph //Get the logs from MS Graph
const retrieveLogs = async () => { const retrieveLogs = async () => {
//Base URL for the graph API
const baseUrl = 'https://graph.microsoft.com/v1.0';
//Get the bearer token //Get the bearer token
const bearer = await Token.get(); const bearer = await Token.get();
@ -32,45 +35,41 @@ const retrieveLogs = async () => {
'Content-type': 'application/json' 'Content-type': 'application/json'
}; };
const baseUrl = 'https://graph.microsoft.com/v1.0'; //DEBUGGING FOR THE HEADER
const url = baseUrl + '/auditLogs/signIns?$top=500'; //console.log(header);
console.log(header); //Fetch MS Graph API - sign ins
var url = baseUrl + '/auditLogs/signIns?$top=500';
//Fetch MS Graph API - data is equal to the json returned
const signIns = await Api.call(url, header); 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 //This add the fetched data to the signins table in mariadb
await SignIn.add(signIns); await SignIn.add(signIns);
await RiskDetections.add(riskDetections);
//This will add the suspicious users to the suspicioususer table in maria db //This will add the suspicious users to the suspicioususer table in maria db
//console.log(await SignIn.suspicious()); //console.log(await SignIn.suspicious());
await SignIn.addSuspicious(await SignIn.getSuspicious()); await SignIn.addSuspicious(await SignIn.getSuspicious());
await SignIn.updateAttemptCount(await SignIn.getSuspiciousAccounts());
} }
const notify = async () => { const notify = async () => {
//Debugging //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()); //console.log(await SignIn.getRecentSuspicious());
try { 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. SignIn.checkIfNotify(results);
for (const result of results) {
var status = 0;
countryOrRegion = countries.getName(result.countryOrRegion, "en");
if(result.status == 0){
status = "<h1 style='color: green'>Success</h1>";
} else {
status = "<h1 style='color: red'>Failure</h1>";
}
MSTeams.send("Alert: Suspicious Login Attempt Detected", "Suspicious sign-in attempt for "+ "<h2>" + result.userDisplayName + " (" + result.userPrincipleName + ")" + "</h2>" + " originating from " + "<h2>" + result.state + ", " + countryOrRegion + "<br></br><div></h1>" + "<h2>Status:</h2> " + status + "</div>"); // Fixed typo 'Suspcious' to 'Suspicious'.
}
}
} catch (error) { } catch (error) {
// Handle any errors that occur during the process. // Handle any errors that occur during the process.
console.error("Error notifying: " + error); console.error("Error notifying: " + error);
@ -83,24 +82,41 @@ retrieveLogs();
//Initially notify of any suspicious activities //Initially notify of any suspicious activities
notify(); 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: //TODO:
//Change the url in the MSTeams.js file before you put this into production!!!! //Change the url in the MSTeams.js file before you put this into production!!!!
//MSTeams.send("bob", "bobs description"); //MSTeams.send("bob", "bobs description");
const interval = 240000; // 60 seconds const interval = 240000; // 60 seconds
const intervalId = setInterval(retrieveLogs, interval); const intervalId = setInterval(retrieveLogs, interval);
const intervalNotify = 120100; // 2 Minutes + a little (to ensure the alert is not triggered twice) const intervalNotify = 120100; // 2 Minutes + a little (to ensure the alert is not triggered twice)
const intervalIdNotify = setInterval(notify, intervalNotify); const intervalIdNotify = setInterval(notify, intervalNotify);
const intervalDelete = 43200000; // 12 hours const intervalDelete = 7200000; // 2 hours
const intervalIdDelete = setInterval(DB.deleteOldRecords, intervalDelete); const intervalIdDelete = setInterval(cleanse, intervalDelete);
const PORT = process.env.APIPORT || 3001; const PORT = process.env.APIPORT || 3001;
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`); const currentTime = new Date();
console.log(`[` + currentTime + `] ` + `Server is running on port ${PORT}`);
}); });

View File

@ -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,
};

View File

@ -1,10 +1,10 @@
const db = require('../config/db'); // Import the database connection pool const db = require('../config/db'); // Import the database connection pool
// Get a list of all sign ins // Get a list of all sign ins
const all = async () => { const retrieve = async () => {
try { try {
const connection = await db.getConnection(); const connection = await db.getConnection();
const query = 'SELECT * FROM signins'; const query = 'SELECT * FROM riskDetections';
const signins = await connection.query(query); const signins = await connection.query(query);
connection.release(); connection.release();
return signins; 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 // Add data to the sign in table
// Get a list of all sign ins // Get a list of all sign ins
const add = async (data) => { const add = async (data) => {
//For debugging //For debugging
//console.log(data.value); //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 { try {
const connection = await db.getConnection(); const connection = await db.getConnection();
const results = []; // To store the results const results = []; // To store the results
const currentDate = new Date(); 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) { function padNumber(number) {
return number.toString().padStart(2, '0'); return number.toString().padStart(2, '0');
} }
@ -68,51 +36,44 @@ const add = async (data) => {
const { const {
id, id,
riskState,
riskLevel,
riskDetail,
source,
activity,
ipAddress,
detectedDateTime,
userId, userId,
createdDateTime,
userDisplayName, userDisplayName,
userPrincipalName, userPrincipalName,
appDisplayName, additionalInfo
ipAddress,
deviceDetail,
location
} = logEntry; } = logEntry;
const { const city = logEntry.location.city;
displayName, const state = logEntry.location.state;
operatingSystem, const countryOrRegion = logEntry.location.countryOrRegion;
browser
} = deviceDetail;
const {
city,
state,
countryOrRegion
} = location;
const errorCode = logEntry.status.errorCode;
const query = ` const query = `
INSERT IGNORE INTO signins INSERT IGNORE INTO riskDetections
(id, userId, createdDateTime, ip, userDisplayName, email, appDisplayName, deviceDisplayName, operatingSystem, browser, city, state, countryOrRegion, insertTime, status) (id, riskState, riskLevel, riskDetail, source, activity, ipAddress, detectedDateTime, userId, userDisplayName, userPrincipalName, additionalInfo, city, state, countryOrRegion, insertDate)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CONVERT_TZ(NOW(), 'UTC', 'America/Denver'), ?)`; VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CONVERT_TZ(NOW(), 'UTC', 'America/Denver'))`;
const values = [ const values = [
id, id,
userId, riskState,
createdDateTime, riskLevel,
riskDetail,
source,
activity,
ipAddress, ipAddress,
detectedDateTime,
userId,
userDisplayName, userDisplayName,
userPrincipalName, userPrincipalName,
appDisplayName, additionalInfo,
displayName,
operatingSystem,
browser,
city, city,
state, state,
countryOrRegion, countryOrRegion
errorCode
]; ];
await connection.query(query, values); 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 () => { const getRecentSuspicious = async () => {
try { try {
const connection = await db.getConnection(); 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 = "<h1 style='color: green'>Success</h1>";
} else {
status = "<h1 style='color: red'>Failure</h1>";
}
MSTeams.send("Alert: Risky Event Detected", "Risky sign-in attempt for "+ "<h2>" + result.userDisplayName + " (" + result.userPrincipleName + ")" + "</h2>" + " originating from " + "<h2>" + result.state + ", " + countryOrRegion + "<br></br><div></h1>" + "<h2>Status:</h2> " + status + "</div>"); // Fixed typo 'Suspcious' to 'Suspicious'.
}
}
} catch (error) {
throw error;
}
};
module.exports = { module.exports = {
all, retrieve,
add, add,
getSuspicious, checkIfNotify
addSuspicious,
getSuspiciousAccounts,
getRecentSuspicious
}; };

View File

@ -1,4 +1,6 @@
const db = require('../config/db'); // Import the database connection pool 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 // Get a list of all sign ins
const all = async () => { const all = async () => {
@ -47,16 +49,14 @@ const add = async (data) => {
//For debugging //For debugging
//console.log(data.value); //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 { try {
const connection = await db.getConnection(); const connection = await db.getConnection();
const results = []; // To store the results const results = []; // To store the results
const currentDate = new Date(); 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) { function padNumber(number) {
return number.toString().padStart(2, '0'); 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) => { 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 { try {
const connection = await db.getConnection(); const connection = await db.getConnection();
const results = []; // To store the results const results = []; // To store the results
@ -169,14 +212,19 @@ const addSuspicious = async (data) => {
}; };
const getRecentSuspicious = async () => { const getRecentSuspicious = async () => {
/*
* @params null
* @returns {Object} - All of the suspicious accounts that exist that had successful signIns
*
* */
try { try {
const connection = await db.getConnection(); const connection = await db.getConnection();
const query = ` const query = `
SELECT * SELECT *
FROM suspiciousAccounts FROM suspiciousAccounts
WHERE insertTime >= CONVERT_TZ(NOW(), 'UTC', 'America/Denver') - INTERVAL 2 MINUTE`; WHERE insertTime >= CONVERT_TZ(NOW(), 'UTC', 'America/Denver') - INTERVAL 2 MINUTE
AND status = 0`;
const signins = await connection.query(query); 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 = "<h1 style='color: green'>Success</h1>";
} else {
status = "<h1 style='color: red'>Failure</h1>";
}
MSTeams.send("Alert: Suspicious Login Attempt Detected", "Suspicious sign-in attempt for "+ "<h2>" + result.userDisplayName + " (" + result.userPrincipleName + ")" + "</h2>" + " originating from " + "<h2>" + result.state + ", " + countryOrRegion + "<br></br><div></h1>" + "<h2>Status:</h2> " + status + "</div>");
}
}
} catch (error) {
throw error;
}
};
module.exports = { module.exports = {
all, all,
@ -194,5 +268,8 @@ module.exports = {
getSuspicious, getSuspicious,
addSuspicious, addSuspicious,
getSuspiciousAccounts, getSuspiciousAccounts,
getRecentSuspicious getRecentSuspicious,
checkIfNotify,
getAttemptCount,
updateAttemptCount
}; };

View File

@ -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" 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 //Comment this out if you are not testing
//url = testingUrl; url = testingUrl;
const webhookUrl = url; const webhookUrl = url;
const webhook = new IncomingWebhook(webhookUrl); const webhook = new IncomingWebhook(webhookUrl);