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);