Updates
This commit is contained in:
parent
2251b40756
commit
faca208a50
66
src/app.js
66
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 = "<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'.
|
||||
}
|
||||
}
|
||||
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}`);
|
||||
});
|
@ -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,
|
||||
};
|
@ -1,10 +1,10 @@
|
||||
const db = require('../config/db'); // Import the database connection pool
|
||||
|
||||
// Get a list of all sign ins
|
||||
const all = async () => {
|
||||
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) => {
|
||||
//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 = "<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 = {
|
||||
all,
|
||||
retrieve,
|
||||
add,
|
||||
getSuspicious,
|
||||
addSuspicious,
|
||||
getSuspiciousAccounts,
|
||||
getRecentSuspicious
|
||||
checkIfNotify
|
||||
};
|
@ -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`;
|
||||
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 = "<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 = {
|
||||
all,
|
||||
@ -194,5 +268,8 @@ module.exports = {
|
||||
getSuspicious,
|
||||
addSuspicious,
|
||||
getSuspiciousAccounts,
|
||||
getRecentSuspicious
|
||||
getRecentSuspicious,
|
||||
checkIfNotify,
|
||||
getAttemptCount,
|
||||
updateAttemptCount
|
||||
};
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user