Initial commit
This commit is contained in:
commit
5e28e20b45
57
res/account.html
Executable file
57
res/account.html
Executable file
@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="./css/styles.css">
|
||||||
|
<script type="text/javascript" src="scripts/api.js" ></script>
|
||||||
|
<script type="text/javascript" src="scripts/view.js" ></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
list();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<title>Web Monitor Project</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="header">Web Monitor</div>
|
||||||
|
<!--div class ="navbar">
|
||||||
|
<div class = "navbar_item">Navbar item</div>
|
||||||
|
</div-->
|
||||||
|
<div class = "main_container">
|
||||||
|
<div id = "active_monitors_label">Active Monitors</div>
|
||||||
|
<div class="button" onclick=list()>Refresh</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Select</th>
|
||||||
|
</thead>
|
||||||
|
<tbody id="table_list_body">
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="button"onclick="deleteSelected()"> Delete Selected</div>
|
||||||
|
<hr />
|
||||||
|
<div >Add new Monitor</div>
|
||||||
|
<table >
|
||||||
|
<thead>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>URL</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><input type="text" id= "new_name" name="Monitor Name"></td>
|
||||||
|
<td><input type="text" id="new_url" name = "Monitor URL"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="button" style = "text-align: center;color:blue;" onclick="addMonitor()">Add </div><br>
|
||||||
|
<div class="button" style = "text-align: center;color:blue;" onclick="logout()">Logout </div>
|
||||||
|
<p> </p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<div class = "license">2021 David Westgate</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
146
res/css/styles.css
Executable file
146
res/css/styles.css
Executable file
@ -0,0 +1,146 @@
|
|||||||
|
/*TAG Styles*/
|
||||||
|
body{
|
||||||
|
background-color: rgb(93, 95, 95);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size:16px;
|
||||||
|
/*background-image: url("../images/background.jpg");*/
|
||||||
|
}
|
||||||
|
|
||||||
|
div{
|
||||||
|
|
||||||
|
}
|
||||||
|
table{
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
th{
|
||||||
|
|
||||||
|
}
|
||||||
|
td{
|
||||||
|
|
||||||
|
}
|
||||||
|
/*Class Styles*/
|
||||||
|
.header{
|
||||||
|
background-color: #F1F1F1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 200%;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
.navbar {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: rgb(65, 17, 17);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar_item{
|
||||||
|
float: left;
|
||||||
|
display: inline;
|
||||||
|
color: #f2f2f2;
|
||||||
|
text-align: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.navbar_item:hover{
|
||||||
|
background-color: #ddd;
|
||||||
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.main_container{
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.prompt{
|
||||||
|
color: #e6e3c8;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
.field{
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.footer{
|
||||||
|
display: block;
|
||||||
|
background-color:#F1F1F1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
display: block;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 95%;
|
||||||
|
|
||||||
|
}
|
||||||
|
.footer_item{
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.button{
|
||||||
|
cursor: pointer;
|
||||||
|
color:blue;
|
||||||
|
margin:5px;
|
||||||
|
background-color: rgb(194, 194, 194);
|
||||||
|
border-radius: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.button:hover{
|
||||||
|
background-color: #ddd;
|
||||||
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
/* Tooltip text */
|
||||||
|
.tooltiptext {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 120px;
|
||||||
|
background-color: black;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
top: -5px;
|
||||||
|
left: 105%;
|
||||||
|
|
||||||
|
/* Position the tooltip text - see examples below! */
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the tooltip text when you mouse over the tooltip container */
|
||||||
|
.td:hover .tooltiptext {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.tooltiptext::after {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 100%; /* To the left of the tooltip */
|
||||||
|
margin-top: -5px;
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent black transparent transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_container{
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*ID styles*/
|
||||||
|
#active_monitors_label{
|
||||||
|
font-size: 150%;
|
||||||
|
font-weight: bolder;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
48
res/index.html
Executable file
48
res/index.html
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link type="text/css" href="css/styles.css" rel="stylesheet"> </link>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="scripts/api.js" ></script>
|
||||||
|
<script type="text/javascript" src="scripts/view.js" ></script>
|
||||||
|
<title>Web Monitor</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="header">Web Monitor</div>
|
||||||
|
<!--div class ="navbar">
|
||||||
|
<div class = "navbar_item">Navbar item</div>
|
||||||
|
</div-->
|
||||||
|
<div class="main_container">
|
||||||
|
<div class="form_container">
|
||||||
|
<div class="prompt" >E-mail</div><input class="field" type="text" id="email">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form_container">
|
||||||
|
<div class="prompt">Password</div><input class="field" type="password" id="password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form_container" id="confirm_password_container" style="visibility: hidden;" form_container>
|
||||||
|
<div class="prompt">Confirm Password</div><input class="field" type="password" id="confirm_password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<div class="button" style="display: inline-block;" id= "login_button" onclick="login()">Login</div><br>
|
||||||
|
<div class="button" style="display: inline-block;"id= "show_registration_button" onclick="showreg()">New User? Register Here</div><br>
|
||||||
|
<div class = "button" style="display: none;"id="confirm_register_button" onclick="register()">Confirm Registration</div><br>
|
||||||
|
<div class = "button" style="display: none;"id="cancle_register_button" onclick="cancle()">Cancel Registration</div><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<div class = "footer_item" id = "license">2021 David Westgate</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
res/res/favicon.ico
Normal file
BIN
res/res/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 591 B |
2
res/robots.txt
Executable file
2
res/robots.txt
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
197
res/scripts/api.js
Normal file
197
res/scripts/api.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
function deleteSelected(){
|
||||||
|
var deleteList = document.getElementsByClassName("deleteBox");
|
||||||
|
var resource = "/api/delete?"
|
||||||
|
var counter = 0;
|
||||||
|
|
||||||
|
for(var i =0; i < deleteList.length; ++i){
|
||||||
|
var checkbox = deleteList[i];
|
||||||
|
if(checkbox.checked != false){
|
||||||
|
if(counter > 0)
|
||||||
|
resource+="&";
|
||||||
|
resource +=('id='+checkbox.id);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(counter >0 ){
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.open('DELETE', resource,true);
|
||||||
|
xhttp.send();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if(this.readyState == 4){
|
||||||
|
list();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMonitor(){
|
||||||
|
var new_name = document.getElementById("new_name");
|
||||||
|
var new_url = document.getElementById("new_url");
|
||||||
|
if(!validURL(new_url.value))
|
||||||
|
alert("invalid URL");
|
||||||
|
else{
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if(this.readyState == 4){
|
||||||
|
|
||||||
|
if(200 <= this.status && this.status< 300){
|
||||||
|
|
||||||
|
list();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert("Error, URL invalid. Status: "+this.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("POST", "/api/newMonitor",true);
|
||||||
|
xhttp.send(JSON.stringify({"name": new_name.value, "url": new_url.value}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout(){
|
||||||
|
document.cookie="ssid=0";
|
||||||
|
window.location.href = '/index.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function login(){
|
||||||
|
var email = document.getElementById("email").value;
|
||||||
|
var password = document.getElementById("password").value;
|
||||||
|
var obj = {"command":"login","method":"POST","email":email,"password":password};
|
||||||
|
if(validEmail(email) && password !== ""){
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
if(200 <= this.status && this.status < 300){
|
||||||
|
window.location.href = '/account.html'
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert("Error. Status: "+this.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open(obj.method, "/api/"+obj.command, true);
|
||||||
|
//xhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||||
|
xhttp.send(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert("Invalid username or password");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function register(){
|
||||||
|
|
||||||
|
var email = document.getElementById("email").value;
|
||||||
|
var password = document.getElementById("password").value;
|
||||||
|
var password_confirm = document.getElementById("confirm_password").value;
|
||||||
|
//Client side input checks
|
||||||
|
if(password != password_confirm){
|
||||||
|
alert("passwords do not match");
|
||||||
|
}
|
||||||
|
else if(!validEmail(email)){
|
||||||
|
alert("Invalid email address");
|
||||||
|
}
|
||||||
|
else if(!validPassword(password)){
|
||||||
|
alert("Invalid password: Must be 8 characters");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4 ) {
|
||||||
|
if(200 <= this.status && this.status < 300){
|
||||||
|
alert("Success");
|
||||||
|
window.location.href = '/index.html';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert("Error. Status: "+this.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhttp.open("POST", "/api/register", true);
|
||||||
|
xhttp.send(JSON.stringify({"email":email,"password":password}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function list(){
|
||||||
|
var table = document.getElementById("table_list_body");
|
||||||
|
//table.innerHTML="<tr><td>Name</td><td>URL</td><td>Active?</td></tr>";
|
||||||
|
table.innerHTML="";
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
if(200 <= this.status && this.status < 300){
|
||||||
|
var array = JSON.parse(xhttp.responseText);
|
||||||
|
|
||||||
|
for(var i = 0; i < array.length; ++i){
|
||||||
|
var next = array[i];
|
||||||
|
var row = document.createElement("tr");
|
||||||
|
|
||||||
|
var nameCell = document.createElement("td");
|
||||||
|
nameCell.innerText = next.name;
|
||||||
|
|
||||||
|
var urlCell = document.createElement("td");
|
||||||
|
urlCell.innerText = next.url.substr(0,25);
|
||||||
|
|
||||||
|
var activeCell = document.createElement("td");
|
||||||
|
var tooltiptext = document.createElement("span");
|
||||||
|
console.log(next.last_change);
|
||||||
|
if(next.last_change.length >3){
|
||||||
|
activeCell.onmouseover = function(){tooltiptext.style.visibility="visible"}
|
||||||
|
activeCell.innerText="Active"
|
||||||
|
activeCell.style.color="#90ee90";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
activeCell.innerText="Inactive"
|
||||||
|
activeCell.style.color="red";
|
||||||
|
activeCell.onmouseover= activeCell.style.cursor="pointer";
|
||||||
|
|
||||||
|
activeCell.onclick=function(){alert("URL may not support ETAG or last-modified headers");};
|
||||||
|
}
|
||||||
|
activeCell.style.fontWeight="bold";
|
||||||
|
var checkboxCell = document.createElement("td");
|
||||||
|
var checkbox = document.createElement("input");
|
||||||
|
checkbox.className="deleteBox";
|
||||||
|
checkbox.id= next.rowid;
|
||||||
|
checkbox.type="checkbox";
|
||||||
|
row.appendChild(nameCell);
|
||||||
|
row.appendChild(urlCell);
|
||||||
|
row.appendChild(activeCell);
|
||||||
|
checkboxCell.appendChild(checkbox);
|
||||||
|
row.appendChild(checkboxCell)
|
||||||
|
table.appendChild(row);
|
||||||
|
/*
|
||||||
|
table.innerHTML+=("<tr>"+
|
||||||
|
"<td>"+next.name+"</td>"+
|
||||||
|
"<td>"+next.url+"...</td>"+
|
||||||
|
"<td>"+next.notify+"</td>"+
|
||||||
|
'<td><input type="checkbox" class="deleteBox" id="'+next.rowid+'"></input></td>'+
|
||||||
|
"</tr>");*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert("Error. Status: "+this.status);
|
||||||
|
window.location.href = '/index.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("GET", "/api/list", true);
|
||||||
|
xhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function validEmail(email){
|
||||||
|
const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
return regex.test(email);
|
||||||
|
}
|
||||||
|
function validPassword(password){
|
||||||
|
return (password.length >= 6)
|
||||||
|
}
|
||||||
|
function validName(){
|
||||||
|
|
||||||
|
}
|
||||||
|
function validURL(url){
|
||||||
|
const regex = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i;
|
||||||
|
return regex.test(url);
|
||||||
|
}
|
33
res/scripts/view.js
Normal file
33
res/scripts/view.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
function showreg(){
|
||||||
|
|
||||||
|
var show_registration_button = document.getElementById("show_registration_button");
|
||||||
|
show_registration_button.style.display = "none";
|
||||||
|
|
||||||
|
var confirm_register_button = document.getElementById("confirm_register_button");
|
||||||
|
confirm_register_button.style.display = "inline-block";
|
||||||
|
|
||||||
|
var cancle_register_button = document.getElementById("cancle_register_button");
|
||||||
|
cancle_register_button.style.display = "inline-block";
|
||||||
|
|
||||||
|
var confirm_password_container = document.getElementById("confirm_password_container");
|
||||||
|
confirm_password_container.style.visibility="visible";
|
||||||
|
|
||||||
|
var login_button = document.getElementById("login_button");
|
||||||
|
login_button.style.display="none";
|
||||||
|
}
|
||||||
|
function cancle(){
|
||||||
|
var show_registration_button = document.getElementById("show_registration_button");
|
||||||
|
show_registration_button.style.display = "inline-block";
|
||||||
|
|
||||||
|
var confirm_register_button = document.getElementById("confirm_register_button");
|
||||||
|
confirm_register_button.style.display = "none";
|
||||||
|
|
||||||
|
var cancle_register_button = document.getElementById("cancle_register_button");
|
||||||
|
cancle_register_button.style.display = "none";
|
||||||
|
|
||||||
|
var confirm_password_container = document.getElementById("confirm_password_container");
|
||||||
|
confirm_password_container.style.visibility="hidden";
|
||||||
|
|
||||||
|
var login_button = document.getElementById("login_button");
|
||||||
|
login_button.style.display="inline-block";
|
||||||
|
}
|
60
server/config.js
Normal file
60
server/config.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
const branches ={
|
||||||
|
"development": {
|
||||||
|
"config_id": "development",
|
||||||
|
"hostname": "someserver.net",
|
||||||
|
"node_port": 8081,
|
||||||
|
"database": "./app-db-dev.db",
|
||||||
|
"ssl": false,
|
||||||
|
"key": "somesecretkey", //Static key for ease of debugging
|
||||||
|
"countries": ['US', 'CA', 'N/A'],
|
||||||
|
"algorithm" : 'aes-256-cbc',
|
||||||
|
"root": "/srv/webserver",
|
||||||
|
"cookie_timeout": 3600000,
|
||||||
|
"transporter":{
|
||||||
|
"host":"smtp.someserver.net",
|
||||||
|
"port":587,
|
||||||
|
"secure":false,
|
||||||
|
"auth": {
|
||||||
|
"user": "someone@someserver.net",
|
||||||
|
"pass": "abadpassword",
|
||||||
|
},
|
||||||
|
"tls":{
|
||||||
|
"rejectUnauthorized": false
|
||||||
|
},
|
||||||
|
"debug":false,
|
||||||
|
"authMethod":"LOGIN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"production": {
|
||||||
|
"config_id": "production",
|
||||||
|
"hostname": "someserver.net",
|
||||||
|
"node_port": 8080,
|
||||||
|
"database": "./app-db-dev.db",
|
||||||
|
"ssl": false,
|
||||||
|
"key": Math.floor((Math.random() * Math.pow(10,20)) ), //Dynamic key on start for security
|
||||||
|
"countries": ['US', 'CA', 'N/A'],
|
||||||
|
"algorithm" : 'aes-256-cbc',
|
||||||
|
"root": "/srv/webserver",
|
||||||
|
"cookie_timeout": 3600000,
|
||||||
|
"transporter":{
|
||||||
|
"host":"smtp.someserver.net",
|
||||||
|
"port":587,
|
||||||
|
"secure":false,
|
||||||
|
"auth": {
|
||||||
|
"user": "someone@someserver.net",
|
||||||
|
"pass": "abadpassword",
|
||||||
|
},
|
||||||
|
"tls":{
|
||||||
|
"rejectUnauthorized": false
|
||||||
|
},
|
||||||
|
"debug":false,
|
||||||
|
"authMethod":"LOGIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Set branch here
|
||||||
|
var config = branches.development;
|
||||||
|
module.exports = {
|
||||||
|
config
|
||||||
|
};
|
180
server/controller.js
Executable file
180
server/controller.js
Executable file
@ -0,0 +1,180 @@
|
|||||||
|
"use strict";
|
||||||
|
const {createUser} = require('./model');
|
||||||
|
const {authenticate} = require('./model');
|
||||||
|
const {createMonitor} = require('./model');
|
||||||
|
const {getAllMonitors} = require('./model');
|
||||||
|
const {removeMonitors} = require('./model');
|
||||||
|
const { getPostData } = require('./utils');
|
||||||
|
const {encrypt} = require('./utils');
|
||||||
|
const {decrypt} = require('./utils');
|
||||||
|
const {getSSID} = require('./utils');
|
||||||
|
const {getLastModified} = require('./utils');
|
||||||
|
const {getETAG} = require('./utils');
|
||||||
|
const {getHeaders} = require('./utils');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//POST->request: Body->{email:email,password:password}
|
||||||
|
//POST->response(success): Body->{email:email}, code: 201
|
||||||
|
//POST->response(fail, already exists): Body->null, code: 409 TODO: more code/handling
|
||||||
|
//POST->response(fail, bad email address): Body->null, code: 409 TODO: more code/handling
|
||||||
|
//POST->response(fail, DB/other): Body->null, code: 500
|
||||||
|
function registerUser(request,response) {
|
||||||
|
//TODO encrypt password before storage
|
||||||
|
const body = getPostData(request);
|
||||||
|
|
||||||
|
body.then((jsonstring)=>{
|
||||||
|
var email = JSON.parse(jsonstring).email;
|
||||||
|
var password = JSON.parse(jsonstring).password;
|
||||||
|
let newUser = createUser(email,password);
|
||||||
|
|
||||||
|
newUser.then((res) =>{
|
||||||
|
response.writeHead(201, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
}).catch((err) =>{
|
||||||
|
response.writeHead(409, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
});
|
||||||
|
}).catch((err)=>{
|
||||||
|
response.writeHead(500, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//POST->request: params->empty,Body->{email:email,password:password}
|
||||||
|
//POST->response(success): Body->{email:email}, code=200
|
||||||
|
//POST->response(fail, wrong user/pass): Body->{}, code=401
|
||||||
|
function loginUser(request,response){
|
||||||
|
//TODO decrypt password before validating
|
||||||
|
const body = getPostData(request);
|
||||||
|
body.then((jsonstring)=>{
|
||||||
|
var email = JSON.parse(jsonstring).email;
|
||||||
|
var password = JSON.parse(jsonstring).password;
|
||||||
|
let login = authenticate(email,password);
|
||||||
|
login.then((row) =>{
|
||||||
|
if(row.email != null && row.email == email){
|
||||||
|
console.log("then & match");
|
||||||
|
var date = new Date();
|
||||||
|
var session_id =""
|
||||||
|
session_id = session_id.concat(email , ';',request.socket.remoteAddress,';', date.getTime());
|
||||||
|
var encrypted = encrypt(session_id);
|
||||||
|
response.writeHead(200, { 'Content-Type': 'application/json',
|
||||||
|
'Set-Cookie': 'ssid='+encrypted+';SameSite=Lax;Path=/;Expires=' +new Date(date.getTime()+3600000).toGMTString()});
|
||||||
|
response.end(JSON.stringify(row));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
//handle null case
|
||||||
|
}
|
||||||
|
}).catch((err) =>{
|
||||||
|
console.log("catch, login dbBB");
|
||||||
|
response.writeHead(409, { 'Content-Type': 'application/json' })
|
||||||
|
response.end(JSON.stringify({'message':err}));
|
||||||
|
});
|
||||||
|
}).catch((err)=>{
|
||||||
|
console.log("catch, body");
|
||||||
|
response.writeHead(500, { 'Content-Type': 'application/json' })
|
||||||
|
response.end(JSON.stringify({'message':err}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//GET->request: params->email=email Body->null cookie->ssid
|
||||||
|
//GET->response (success): Body->[{monitor1},{monitor2},...] code=200
|
||||||
|
//GET->response (fail, bad cookie auth): Body->{} code=401
|
||||||
|
function listMonitors(request, response){
|
||||||
|
var ssid = getSSID(request);
|
||||||
|
if(ssid.email == undefined){
|
||||||
|
response.writeHead(400, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var email = ssid.email;
|
||||||
|
const list = getAllMonitors(email);
|
||||||
|
list.then((rows) =>{
|
||||||
|
|
||||||
|
response.writeHead(200, { 'Content-Type': 'application/json' })
|
||||||
|
response.end(JSON.stringify(rows));
|
||||||
|
}).catch((err) =>{
|
||||||
|
console.log("Nrows")
|
||||||
|
console.log(err);
|
||||||
|
response.writeHead(401, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//DELETE->request: params->email=email&mid=mid cookie->ssid
|
||||||
|
//DELETE->response (success): body->{mid:mid}: code 200
|
||||||
|
//DELETE->response (fail, bad/no resource): Body->{} code 404
|
||||||
|
//DELETE->response (fail, bad cookie/auth): Body->{} code: 401
|
||||||
|
function deleteMonitors(request,response, idArray){
|
||||||
|
var ssid = getSSID(request);
|
||||||
|
if(ssid.email == undefined){
|
||||||
|
console.log("undefined");
|
||||||
|
response.writeHead(401, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
console.log(idArray);
|
||||||
|
const remove = removeMonitors(idArray);
|
||||||
|
remove.then(()=>{ //TODO update resolve with ids of removed data
|
||||||
|
response.writeHead(200, { 'Content-Type': 'application/json' })
|
||||||
|
response.end(); //TODO include removed ids in response
|
||||||
|
}).catch((err)=>{
|
||||||
|
response.writeHead(401, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//POST->request: params->email=email,name=name,url=url, body->null cookie->ssid
|
||||||
|
//POST->response (success) body->{name:name,url:url}: code 202
|
||||||
|
//POST->response (fail, bad url) body->{}: code 400
|
||||||
|
//POST->response (fail, DB error): body={}: code 500
|
||||||
|
//POST->response (fail, cookie/auth): body={}: code 401
|
||||||
|
async function newMonitor(request, response){
|
||||||
|
var ssid = getSSID(request);
|
||||||
|
if(ssid.email == undefined){
|
||||||
|
response.writeHead(401, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
const bodyPromise = getPostData(request);
|
||||||
|
bodyPromise.then((data)=>{
|
||||||
|
var name = JSON.parse(data).name;
|
||||||
|
var url = JSON.parse(data).url;
|
||||||
|
const headersPromise = getHeaders(url);
|
||||||
|
headersPromise.then((headers)=>{
|
||||||
|
var etag = getETAG(headers);
|
||||||
|
var last_modified = getLastModified(headers);
|
||||||
|
last_modified = last_modified +';'+etag;
|
||||||
|
//if(last_modified == ';')
|
||||||
|
// last_modified= "Update data or resource unavalible"
|
||||||
|
const monitorPromise = createMonitor(name,url, ssid.email, last_modified);
|
||||||
|
monitorPromise.then(()=>{ //TODO row inserted, refere to here
|
||||||
|
response.writeHead(202, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();//TODO respond with row inserted
|
||||||
|
}).catch(()=>{
|
||||||
|
response.writeHead(500, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
});
|
||||||
|
}).catch((err)=>{
|
||||||
|
response.writeHead(400, { 'Content-Type': 'application/json' })
|
||||||
|
response.end(err);
|
||||||
|
}); //TODO add catch for headers promise
|
||||||
|
|
||||||
|
}).catch(()=>{
|
||||||
|
response.writeHead(400, { 'Content-Type': 'application/json' })
|
||||||
|
response.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
registerUser,
|
||||||
|
loginUser ,
|
||||||
|
listMonitors,
|
||||||
|
newMonitor,
|
||||||
|
deleteMonitors
|
||||||
|
}
|
30
server/mailer.js
Normal file
30
server/mailer.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
var nodemailer = require("nodemailer");
|
||||||
|
const {config} = require('./config');
|
||||||
|
class Mailer{
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.transporter = nodemailer.createTransport(config.transporter);
|
||||||
|
}
|
||||||
|
async monitorUpdateMail(useremail, monitorname, monitorurl){
|
||||||
|
var subjecttext = "New website update for "+monitorname;
|
||||||
|
var bodytext = "Hello "+useremail+", your monitored website "+monitorname+ " has been updated at "+monitorurl;
|
||||||
|
var bodytextHTML = "<p>"+bodytext+"</p>";
|
||||||
|
await this.transporter.sendMail({
|
||||||
|
from: '"Web Monitor Service" <noreply@'+config.hostname+'>', // sender address
|
||||||
|
to: useremail, // list of receivers
|
||||||
|
subject: subjecttext, // Subject line
|
||||||
|
text: bodytext, // plain text body
|
||||||
|
html: bodytextHTML, // html body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerConfirmMail(useremail){
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Mailer: Mailer
|
||||||
|
}
|
276
server/model.js
Executable file
276
server/model.js
Executable file
@ -0,0 +1,276 @@
|
|||||||
|
const sqlite3 = require('sqlite3').verbose();
|
||||||
|
const {config} = require('./config');
|
||||||
|
|
||||||
|
//success->resolve(row)
|
||||||
|
//fail-> reject(err)
|
||||||
|
function authenticate(email,password){
|
||||||
|
return new Promise((resolve,reject) =>{
|
||||||
|
let db = new sqlite3.Database(config.database, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var sql = 'SELECT email, password FROM users WHERE email=(?) AND password=(?)';
|
||||||
|
var params = [email,password];
|
||||||
|
db.get(sql, params, (err,row)=>{
|
||||||
|
if(err){
|
||||||
|
//console.error(err.message);
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
if(row){
|
||||||
|
resolve(row);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
reject('email not found');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
db.close((err)=>{
|
||||||
|
if(err){
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//success->resolve(row)
|
||||||
|
//fail-> reject(err)
|
||||||
|
function createUser(email,password){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let db = new sqlite3.Database(config.database, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject (err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
db.serialize( () => {
|
||||||
|
db.run('CREATE TABLE IF NOT EXISTS users(email TEXT NOT NULL PRIMARY KEY, password TEXT NOT NULL)', (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject (err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var sql = 'INSERT INTO users (email, password) VALUES (?,?)';
|
||||||
|
var params = [email, password];
|
||||||
|
|
||||||
|
db.run(sql, params, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject (err.message);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resolve(email); //TODO resolve with entire ROW
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
db.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
reject (err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//success->resolve(row)
|
||||||
|
//fail-> reject(err)
|
||||||
|
function updateLastModified(rowid, last_change){
|
||||||
|
return new Promise((resolve,reject) =>{
|
||||||
|
//console.log("data in model is "+data);
|
||||||
|
let db = new sqlite3.Database(config.database, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var sql = 'UPDATE monitors SET last_change = (?) WHERE rowid=(?)';
|
||||||
|
var params = [last_change, rowid];
|
||||||
|
db.run(sql, params, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resolve(); //TODO resolve entire row
|
||||||
|
}
|
||||||
|
});
|
||||||
|
db.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//success->resolve(row.last_change)
|
||||||
|
//fail-> reject(err.message)
|
||||||
|
function getLastModified(rowid){
|
||||||
|
return new Promise((resolve,reject)=>{
|
||||||
|
let db = new sqlite3.Database(config.database, (err) => {
|
||||||
|
if (err) {
|
||||||
|
resolve({accepted:false, message:err.message});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var sql = 'SELECT last_change FROM monitors WHERE rowid=(?)';
|
||||||
|
var params = [rowid];
|
||||||
|
db.get(sql,params, (err,row) =>{
|
||||||
|
if(err){
|
||||||
|
reject(err.mesasage);
|
||||||
|
}
|
||||||
|
else if(row){
|
||||||
|
resolve(row.last_change);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
reject("row not found")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//success->resolve(row)
|
||||||
|
//fail-> reject(err.message)
|
||||||
|
function createMonitor(name, url, email, last_modified){
|
||||||
|
return new Promise((resolve, reject) =>{
|
||||||
|
let db = new sqlite3.Database(config.database, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
db.serialize( () => {
|
||||||
|
|
||||||
|
|
||||||
|
var sql = 'INSERT INTO monitors (email, name, url, notify, last_change) VALUES (?,?,?,?,?)';
|
||||||
|
|
||||||
|
var params = [email, name, url,1,last_modified];
|
||||||
|
db.run(sql, params, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resolve(); //TODO update query to return row inserted
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
db.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
resolve({accepted:false, message:err.message});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//success->resolve(row)
|
||||||
|
//fail-> reject(err.message)
|
||||||
|
function removeMonitors(rowids){
|
||||||
|
return new Promise((resolve,reject) =>{
|
||||||
|
let db = new sqlite3.Database(config.database, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var sql = 'DELETE FROM monitors WHERE rowid in (?';
|
||||||
|
for(var i = 1; i < rowids.length; ++i)
|
||||||
|
sql+=',?';
|
||||||
|
sql+=')';
|
||||||
|
//console.log("sql is "+sql);
|
||||||
|
var params = rowids;
|
||||||
|
db.run(sql,params, function(err){
|
||||||
|
if(err){
|
||||||
|
reject(err.message)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resolve(); //TODO resolve with reference to removed rows
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//success->resolve(rows)
|
||||||
|
//fail-> reject(err.message)
|
||||||
|
function getAllMonitors(email){
|
||||||
|
return new Promise((resolve, reject) =>{
|
||||||
|
let db = new sqlite3.Database(config.database, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
db.serialize( () => {
|
||||||
|
db.run('CREATE TABLE IF NOT EXISTS monitors('
|
||||||
|
+'email TEXT NOT NULL'
|
||||||
|
+',name TEXT NOT NULL'
|
||||||
|
+',url TEXT NOT NULL'
|
||||||
|
+',notify INTEGER NOT NULL'
|
||||||
|
+',last_change INTEGER'
|
||||||
|
+',last_check INTEGER'
|
||||||
|
+',check_frequency INTEGER'
|
||||||
|
+',page_data BLOB'
|
||||||
|
+',FOREIGN KEY(email) REFERENCES users(email)'
|
||||||
|
+')', (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var sql = 'SELECT rowid,email,name,url,notify,last_change,last_check FROM monitors WHERE email=(?)';
|
||||||
|
var params = [email];
|
||||||
|
db.all(sql, params, (err,rows)=>{
|
||||||
|
if(err){
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
if(rows){
|
||||||
|
resolve(rows);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
db.close((err)=>{
|
||||||
|
if(err){
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//success->resolve(rows)
|
||||||
|
//fail-> reject(err.message)
|
||||||
|
function getAllMonitorsGlobal(){
|
||||||
|
return new Promise((resolve, reject) =>{
|
||||||
|
|
||||||
|
let db = new sqlite3.Database(config.database, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var sql = 'SELECT rowid,email,name,url,notify,last_change,last_check FROM monitors';
|
||||||
|
db.all(sql, [], (err,rows)=>{
|
||||||
|
if(err){
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
if(rows){
|
||||||
|
resolve(rows);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
db.close((err)=>{
|
||||||
|
if(err){
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports={
|
||||||
|
createUser,
|
||||||
|
authenticate,
|
||||||
|
getAllMonitors,
|
||||||
|
createMonitor,
|
||||||
|
removeMonitors,
|
||||||
|
getLastModified,
|
||||||
|
updateLastModified,
|
||||||
|
getAllMonitorsGlobal
|
||||||
|
|
||||||
|
}
|
2728
server/package-lock.json
generated
Normal file
2728
server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
server/package.json
Normal file
13
server/package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"crypto-js": "^4.0.0",
|
||||||
|
"file-type": "^16.2.0",
|
||||||
|
"geoip-country": "^4.0.56",
|
||||||
|
"mime": "^2.5.2",
|
||||||
|
"node-cron": "^2.0.3",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
|
"nodemailer": "^6.4.16",
|
||||||
|
"smtp-server": "^3.8.0",
|
||||||
|
"sqlite3": "^5.0.2"
|
||||||
|
}
|
||||||
|
}
|
49
server/scheduler.js
Normal file
49
server/scheduler.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
var cron = require('node-cron');
|
||||||
|
var {getAllMonitorsGlobal} = require('./model');
|
||||||
|
const {getLastModified} = require('./utils');
|
||||||
|
const {getETAG} = require('./utils');
|
||||||
|
const {getHeaders} = require('./utils');
|
||||||
|
const {updateLastModified} = require('./model');
|
||||||
|
const {Mailer} = require('./mailer');
|
||||||
|
const {config} = require('./config');
|
||||||
|
//let Mailer = mailer.Mailer;
|
||||||
|
|
||||||
|
async function scheduler(){
|
||||||
|
var mailer = new Mailer();
|
||||||
|
//TEST: mailer.monitorUpdateMail('davidjwestgate@gmail.com','some_name','some_url');
|
||||||
|
|
||||||
|
//TODO move cron schedule to config file
|
||||||
|
var sched = cron.schedule('0 0 * * * *', async() => {
|
||||||
|
console.log('running a task every minute');
|
||||||
|
var allGlobalMonitorsPromise = getAllMonitorsGlobal();
|
||||||
|
|
||||||
|
allGlobalMonitorsPromise.then(async(rows)=>{
|
||||||
|
for(var i = 0; i < rows.length; ++i){
|
||||||
|
var headersPromise = await getHeaders(rows[i].url); //TODO handle resolve/reject
|
||||||
|
var last_change_db = rows[i].last_change;
|
||||||
|
// console.log('last_change_db: '+last_change_db)
|
||||||
|
var last_modified_live = getLastModified(headersPromise)+';'+getETAG(headersPromise);
|
||||||
|
//console.log('last_modified_live: '+last_modified_live)
|
||||||
|
|
||||||
|
if(last_change_db == last_modified_live){
|
||||||
|
console.log("no change for "+rows[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
else{
|
||||||
|
console.log("change for "+rows[i].name);
|
||||||
|
var updateLastModifiedPromise = await updateLastModified(rows[i].rowid,last_modified_live);
|
||||||
|
updateLastModifiedPromise.finally(()=>{
|
||||||
|
//TODO: Set email_Are_live in config file
|
||||||
|
// mailer.monitorUpdateMail(rows[i].email,rows[i].name,rows[i].url);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch((err)=>{
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
module.exports={
|
||||||
|
scheduler
|
||||||
|
}
|
77
server/server.js
Executable file
77
server/server.js
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
var http = require('http');
|
||||||
|
var readline = require('readline');
|
||||||
|
const {config} = require('./config');
|
||||||
|
const {check_whitelist} = require('./utils');
|
||||||
|
const {serve_resource} = require('./utils');
|
||||||
|
const {registerUser} = require('./controller');
|
||||||
|
const {loginUser} = require('./controller');
|
||||||
|
const {listMonitors} = require('./controller');
|
||||||
|
const {newMonitor} = require('./controller');
|
||||||
|
const {deleteMonitors} = require('./controller');
|
||||||
|
const {scheduler} = require('./scheduler');
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
//Main server listener
|
||||||
|
function server_listener(request, response){
|
||||||
|
var ip = request.socket.remoteAddress;
|
||||||
|
var whitelist = {'stop':false, 'country':'N/A', 'ip':ip};
|
||||||
|
check_whitelist(whitelist);
|
||||||
|
|
||||||
|
//whitelist banned
|
||||||
|
if(whitelist.stop){
|
||||||
|
//console.log('Denied Request from '+whitelist.country + ' ip: '+request.connection.remoteAddress +' res: '+ request.url);
|
||||||
|
response.end('Acess Denied');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Allowed users
|
||||||
|
else{
|
||||||
|
var url = new URL(request.url,`http://${request.headers.host}`);
|
||||||
|
var path = url.pathname
|
||||||
|
var params = new URLSearchParams(url.searchParams);
|
||||||
|
//API calls
|
||||||
|
if(path.startsWith("/api")){
|
||||||
|
switch(request.socket.parser.incoming.method){
|
||||||
|
case "GET":
|
||||||
|
if(path.endsWith("/list"))
|
||||||
|
listMonitors(request,response);
|
||||||
|
else if(path.endsWith("/logout"))
|
||||||
|
logoutUser(request,response);
|
||||||
|
break;
|
||||||
|
case "POST":
|
||||||
|
if(path.endsWith("/register"))
|
||||||
|
registerUser(request,response);
|
||||||
|
else if(path.endsWith("/login"))
|
||||||
|
loginUser(request,response);
|
||||||
|
else if(path.endsWith("/newMonitor"))
|
||||||
|
newMonitor(request,response);
|
||||||
|
break;
|
||||||
|
case "PUT":
|
||||||
|
break;
|
||||||
|
case "DELETE":
|
||||||
|
if(path.endsWith("/delete")){
|
||||||
|
var idArray = JSON.parse('['+params.getAll('id')+']');
|
||||||
|
deleteMonitors(request,response,idArray);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Resource requests
|
||||||
|
else{
|
||||||
|
serve_resource(request,response,whitelist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scheduler();
|
||||||
|
var server = http.createServer(server_listener);
|
||||||
|
server.listen(config.node_port);
|
||||||
|
|
||||||
|
rl.question("Server running (Enter to stop)\n",(answer) =>{
|
||||||
|
server.close();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
198
server/utils.js
Executable file
198
server/utils.js
Executable file
@ -0,0 +1,198 @@
|
|||||||
|
const geoip = require('geoip-country');
|
||||||
|
const fs = require('fs');
|
||||||
|
const mime = require('mime');
|
||||||
|
const CryptoJS = require("crypto-js");
|
||||||
|
const http = require('https');
|
||||||
|
const {config} = require('./config');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getLastModified(headers){
|
||||||
|
var last = headers['last-modified']
|
||||||
|
if(last)
|
||||||
|
return last;
|
||||||
|
else
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getETAG(headers){
|
||||||
|
var etag = headers.etag
|
||||||
|
if(etag)
|
||||||
|
return etag
|
||||||
|
else
|
||||||
|
return '';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeaders(url){
|
||||||
|
return new Promise((resolve,reject)=>{
|
||||||
|
try{
|
||||||
|
var request = http.request(url, {method: 'HEAD'}, res =>{
|
||||||
|
//console.log(res);
|
||||||
|
resolve(res.headers);
|
||||||
|
});
|
||||||
|
request.end();
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function validEmail(email){
|
||||||
|
const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
return email.test(regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validName(name){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function validURL(url){
|
||||||
|
const regex = /^((https?):\/\/)?([w|W]{3}\.)+[a-zA-Z0-9\-\.]{3,}\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?$/;
|
||||||
|
return regex.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCookies (request) {
|
||||||
|
var cookies = {};
|
||||||
|
if(request.headers && request.headers.cookie){
|
||||||
|
request.headers && request.headers.cookie.split(';').forEach(function(cookie) {
|
||||||
|
var parts = cookie.match(/(.*?)=(.*)$/)
|
||||||
|
cookies[ parts[1].trim() ] = (parts[2] || '').trim();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return true if cookie exists, and decrypted SSID is valid
|
||||||
|
function getSSID(request){
|
||||||
|
var cookies = parseCookies(request);
|
||||||
|
|
||||||
|
if(cookies.ssid != undefined && cookies.ssid != null)
|
||||||
|
var encrypted = cookies.ssid.toString();
|
||||||
|
else
|
||||||
|
return {};
|
||||||
|
if(encrypted != null && encrypted != undefined && encrypted != ""){
|
||||||
|
//console.log("encrypted ssid cookie is " +encrypted);
|
||||||
|
var decrpyted = decrypt(encrypted);
|
||||||
|
//console.log('decrypted is '+decrpyted);
|
||||||
|
var userdata = decrpyted.split(';');
|
||||||
|
var email = userdata[0];
|
||||||
|
var ip = userdata[1];
|
||||||
|
var date = userdata[2];
|
||||||
|
if(ip != request.socket.remoteAddress){
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
else if( date > (new Date().getTime+config.cookie_timeout)){
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return {"email":email,"ip":ip,"date":date};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check to see if request IP is in allowed region
|
||||||
|
function check_whitelist(whitelist){
|
||||||
|
var geo = geoip.lookup(whitelist.ip);
|
||||||
|
if(geo != null)
|
||||||
|
whitelist.country = geo.country;
|
||||||
|
|
||||||
|
//Country whitelist
|
||||||
|
if(!config.countries.includes(whitelist.country))
|
||||||
|
whitelist.stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Serve a directory listing in HTML when a folder requested with no index.
|
||||||
|
function response_write_dir(request, response ,systemPath){
|
||||||
|
response.writeHead(200, { 'Content-Type': 'text/html' });
|
||||||
|
var list = fs.readdirSync(systemPath);
|
||||||
|
response.write('<html><body>');
|
||||||
|
for(var i = 0; i < list.length; ++i){
|
||||||
|
response.write('<a href='+request.url+'/'+list[i]+'>'+list[i]+'</a><br>');
|
||||||
|
}
|
||||||
|
response.write('</body></html>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function serve_resource(request, response, whitelist){
|
||||||
|
var url = new URL(request.url,`http://${request.headers.host}`);
|
||||||
|
var systemPath = config.root + url.pathname;
|
||||||
|
var resType = (url.pathname.includes('.')) ? 'file':'dir';
|
||||||
|
var contentType = mime.getType(url.pathname);
|
||||||
|
//Remove trailing '/'s
|
||||||
|
while(systemPath.endsWith('/')){
|
||||||
|
systemPath = systemPath.substr(0,systemPath.length-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Log for requests
|
||||||
|
console.log('Request from '+request.socket.remoteAddress+':'+request.socket.remotePort+', '
|
||||||
|
+request.socket.remoteFamily +'\nFor resource '+url.pathname+'\nCountry: '+whitelist.country+'\tMethod: '
|
||||||
|
+ request.socket.parser.incoming.method+'\n');
|
||||||
|
|
||||||
|
//If navigating to directory and index resource exists, serve index.html
|
||||||
|
if(resType == 'dir' && fs.existsSync(systemPath+'/index.html'))
|
||||||
|
systemPath = systemPath+'/index.html';
|
||||||
|
|
||||||
|
fs.readFile(systemPath, function(err, content) {
|
||||||
|
if (err) {
|
||||||
|
if(err.code == 'ENOENT'){
|
||||||
|
response.writeHead(404, { 'Content-Type': 'text/html' });
|
||||||
|
response.write('<html><body>Error: Resrouce not found</body></html>');
|
||||||
|
response.end(null, 'utf-8');
|
||||||
|
}
|
||||||
|
else {//If reading a directory with no index.html, serve file list in HTML
|
||||||
|
response_write_dir(request,response,systemPath);
|
||||||
|
response.end(null, 'utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response.writeHead(200, { 'Content-Type': contentType });
|
||||||
|
response.end(content, 'utf-8');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPostData(request) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let body = ''
|
||||||
|
request.on('data', (chunk) => {
|
||||||
|
body += chunk.toString()
|
||||||
|
})
|
||||||
|
request.on('end', () => {
|
||||||
|
resolve(body)
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
reject(err.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function encrypt(string){
|
||||||
|
return CryptoJS.AES.encrypt(string,config.key).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrypt(string){
|
||||||
|
return CryptoJS.AES.decrypt(string, config.key).toString(CryptoJS.enc.Utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
check_whitelist,
|
||||||
|
serve_resource,
|
||||||
|
getPostData,
|
||||||
|
encrypt,
|
||||||
|
decrypt,
|
||||||
|
getSSID,
|
||||||
|
validEmail,
|
||||||
|
validName,
|
||||||
|
validURL,
|
||||||
|
getHeaders,
|
||||||
|
getLastModified,
|
||||||
|
getETAG
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user