* Migrated manual tests to jest and started working on better coverage * Added a bookmarklet and push key generation tool allowing URL pushing from bookmarklets * Updated web scraping with tons of bug fixes * Updated attachments page to handle new push links * Aggressive note change checking, if patches get out of sync, server overwrites bad updates.
		
			
				
	
	
		
			310 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| //Set up environmental variables, pulled from ~/.env file used as process.env.DB_HOST
 | |
| const os = require('os') //Used to get path of home directory
 | |
| const result = require('dotenv').config({ path:(os.homedir()+'/.env') })
 | |
| 
 | |
| const ports = {
 | |
| 	express: 3000,
 | |
| 	socketIo: 3001
 | |
| }
 | |
| 
 | |
| //Allow user of @ in in require calls. Config in package.json
 | |
| require('module-alias/register')
 | |
| 
 | |
| //Auth helper, used for decoding users web token
 | |
| let Auth = require('@helpers/Auth')
 | |
| 
 | |
| //Helmet adds additional security to express server
 | |
| const helmet = require('helmet')
 | |
| 
 | |
| //Setup express server
 | |
| const express = require('express')
 | |
| const app = express()
 | |
| app.use( helmet() )
 | |
| // allow for the parsing of url encoded forms
 | |
| app.use(express.urlencoded({ extended: true }));
 | |
| 
 | |
| 
 | |
| //
 | |
| // Request Rate Limiter 
 | |
| //
 | |
| const rateLimit = require('express-rate-limit')
 | |
| //Limiter for the entire app
 | |
| const limiter = rateLimit({
 | |
| 	windowMs: 10 * 60 * 1000, // 10 minutes
 | |
| 	max: 1000 // limit each IP to 1000 requests per windowMs
 | |
| })
 | |
|  
 | |
| // apply to all requests
 | |
| app.use(limiter);
 | |
| 
 | |
| 
 | |
| var http = require('http').createServer(app);
 | |
| var io = require('socket.io')(http, {
 | |
| 	path:'/socket'
 | |
| });
 | |
| 
 | |
| //Set socket IO as a global in the app
 | |
| global.SocketIo = io
 | |
| 
 | |
| let noteDiffs = {}
 | |
| 
 | |
| io.on('connection', function(socket){
 | |
| 
 | |
| 	//When a user connects, add them to their own room
 | |
| 	// This allows the server to emit events to that specific user
 | |
| 	// access socket.io in the controller with SocketIo global
 | |
| 	socket.on('user_connect', token => {
 | |
| 		Auth.decodeToken(token)
 | |
| 		.then(userData => {
 | |
| 			socket.join(userData.userId)
 | |
| 
 | |
| 			//Track active logged in user accounts
 | |
| 			const usersInRoom = io.sockets.adapter.rooms[userData.userId]
 | |
| 			io.to(userData.userId).emit('update_active_user_count', usersInRoom.length)
 | |
| 
 | |
| 		}).catch(error => {
 | |
| 			//Don't add user to room if they are not logged in
 | |
| 			// console.log(error)
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	socket.on('get_active_user_count', token => {
 | |
| 		Auth.decodeToken(token)
 | |
| 		.then(userData => {
 | |
| 			socket.join(userData.userId)
 | |
| 
 | |
| 			//Track active logged in user accounts
 | |
| 			const usersInRoom = io.sockets.adapter.rooms[userData.userId]
 | |
| 			io.to(userData.userId).emit('update_active_user_count', usersInRoom.length)
 | |
| 
 | |
| 		}).catch(error => {
 | |
| 			// console.log(error)
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	//Renew Session tokens when users request a new one
 | |
| 	socket.on('renew_session_token', token => {
 | |
| 
 | |
| 		//Decode the token they currently have
 | |
| 		Auth.decodeToken(token)
 | |
| 		.then(userData => {
 | |
| 
 | |
| 			if(userData.active == 1){
 | |
| 				//Create a new one using credentials and session keys from current
 | |
| 				Auth.createToken(userData.userId, userData.masterKey, userData.sessionId, userData.created)
 | |
| 				.then(newToken => {
 | |
| 
 | |
| 					//Emit new token only to user on socket
 | |
| 					socket.emit('recievend_new_token', newToken)
 | |
| 				})
 | |
| 			} else {
 | |
| 				//Attempting to reactivate disabled session, kills it all
 | |
| 				Auth.terminateSession(userData.sessionId)
 | |
| 			}
 | |
| 		})
 | |
| 	})
 | |
| 
 | |
| 	socket.on('join_room', rawTextId => {
 | |
| 		// Join user to rawtextid room when they enter
 | |
| 		socket.join(rawTextId)
 | |
| 
 | |
| 		//If there are past diffs for this note, send them to the user
 | |
| 		if(noteDiffs[rawTextId] != undefined){
 | |
| 			
 | |
| 			//Sort all note diffs by when they were created.
 | |
| 			noteDiffs[rawTextId].sort((a,b) => { return a.time - b.time })
 | |
| 
 | |
| 			//Emit all sorted diffs to user
 | |
| 			socket.emit('past_diffs', noteDiffs[rawTextId])
 | |
| 		}
 | |
| 
 | |
| 		const usersInRoom = io.sockets.adapter.rooms[rawTextId]
 | |
| 		if(usersInRoom){
 | |
| 			//Update users in room count
 | |
| 			io.to(rawTextId).emit('update_user_count', usersInRoom.length)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	socket.on('leave_room', roomId => {
 | |
| 		socket.leave(roomId)
 | |
| 		// console.log('User Left room')
 | |
| 
 | |
| 		const usersInRoom = io.sockets.adapter.rooms[roomId]
 | |
| 		if(usersInRoom){
 | |
| 			// console.log('Users in room', usersInRoom.length)
 | |
| 			io.to(roomId).emit('update_user_count', usersInRoom.length)
 | |
| 		}
 | |
| 		
 | |
| 	})
 | |
| 
 | |
| 	socket.on('note_diff', data => {
 | |
| 
 | |
| 		//Log each diff for note
 | |
| 		const noteId = data.id
 | |
| 		delete data.id
 | |
| 		if(noteDiffs[noteId] == undefined){ noteDiffs[noteId] = [] }
 | |
| 		data.time = +new Date
 | |
| 		
 | |
| 		noteDiffs[noteId].push(data)
 | |
| 
 | |
| 		// Go over each user in this note-room
 | |
| 		io.in(noteId).clients((error, clients) => {
 | |
| 			if (error) throw error;
 | |
| 
 | |
| 			//Go through each client in note-room and send them the diff
 | |
| 			clients.forEach(socketId => {
 | |
| 				// only send off diff if user
 | |
| 				if(socketId != socket.id){
 | |
| 					io.to(socketId).emit('incoming_diff', data)
 | |
| 				}
 | |
| 			})
 | |
| 
 | |
| 		});
 | |
| 
 | |
| 	})
 | |
| 
 | |
| 	socket.on('truncate_diffs_at_save', checkpoint => {
 | |
| 
 | |
| 		let diffSet = noteDiffs[checkpoint.rawTextId]
 | |
| 		if(diffSet && diffSet.length > 0){
 | |
| 
 | |
| 			//Make sure all diffs are sorted before cleaning
 | |
| 			noteDiffs[checkpoint.rawTextId].sort((a,b) => { return a.time - b.time })
 | |
| 
 | |
| 			// Remove all diffs until it reaches the current hash
 | |
| 			let sliceTo = 0
 | |
| 			for (var i = 0; i < diffSet.length; i++) {
 | |
| 				if(diffSet[i].hash == checkpoint){
 | |
| 					sliceTo = i
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			noteDiffs[checkpoint.rawTextId] = diffSet.slice(0, sliceTo)
 | |
| 
 | |
| 			if(noteDiffs[checkpoint.rawTextId].length == 0){
 | |
| 				delete noteDiffs[checkpoint.rawTextId]
 | |
| 			} 
 | |
| 			//Debugging
 | |
| 			else {
 | |
| 				console.log('Diffset after save')
 | |
| 				console.log(noteDiffs[checkpoint.rawTextId])
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	socket.on('disconnect', function(socket){
 | |
| 		// console.log('user disconnected');
 | |
| 	});
 | |
| });
 | |
| 
 | |
| 
 | |
| http.listen(ports.socketIo, function(){
 | |
| 	console.log(`Socke.io: Listening on port ${ports.socketIo}`)
 | |
| });
 | |
| 
 | |
| //Enable json body parsing in requests. Allows me to post data in ajax calls
 | |
| app.use(express.json({limit: '5mb'}))
 | |
| 
 | |
| //App Auth, all requests will come in with a token, decode the token and set global var
 | |
| app.use(function(req, res, next){
 | |
| 
 | |
| 	//Always null out master key, never allow it set from outside
 | |
| 	req.headers.userId = null
 | |
| 	req.headers.masterKey = null
 | |
| 	req.headers.sessionId = null
 | |
| 
 | |
| 	//auth token set by axios in headers
 | |
| 	let token = req.headers.authorizationtoken
 | |
| 	if(token !== undefined && token.length > 0){
 | |
| 		Auth.decodeToken(token, req)
 | |
| 		.then(userData => {
 | |
| 
 | |
| 			//Update headers for the rest of the application
 | |
| 			req.headers.userId = userData.userId
 | |
| 			req.headers.masterKey = userData.masterKey
 | |
| 			req.headers.sessionId = userData.sessionId
 | |
| 
 | |
| 			//Tell front end remaining uses on current token
 | |
| 			res.set('remainingUses', userData.remainingUses)
 | |
| 
 | |
| 			next()
 | |
| 		})
 | |
| 		.catch(error => {
 | |
| 
 | |
| 			next('Unauthorized')
 | |
| 		})
 | |
| 	} else {
 | |
| 		next() //No token. Move along.
 | |
| 	}
 | |
| })
 | |
| 
 | |
| 
 | |
| // Test Area
 | |
| // const printResults = true
 | |
| // let UserTest = require('@models/User')
 | |
| // let NoteTest = require('@models/Note')
 | |
| // let AuthTest = require('@helpers/Auth')
 | |
| // Auth.test()
 | |
| // UserTest.keyPairTest('genMan30', '1', printResults)
 | |
| // .then( ({testUserId, masterKey}) => 
 | |
| // 	NoteTest.test(testUserId, masterKey, printResults))
 | |
| // .then( message => { 
 | |
| // 	if(printResults) console.log(message) 
 | |
| // 	Auth.testTwoFactor()
 | |
| // })
 | |
| // .catch((error) => {
 | |
| // 	console.log(error)
 | |
| // })
 | |
| 
 | |
| //Test 
 | |
| app.get('/api', (req, res) => res.send('Solidscribe /API is up and running'))
 | |
| 
 | |
| //Serve up uploaded files
 | |
| app.use('/api/static', express.static( __dirname+'/../staticFiles' ))
 | |
| 
 | |
| //Public routes
 | |
| var public = require('@routes/publicController')
 | |
| app.use('/api/public', public)
 | |
| 
 | |
| //user endpoint
 | |
| var user = require('@routes/userController')
 | |
| app.use('/api/user', user)
 | |
| 
 | |
| //notes endpoint
 | |
| var notes = require('@routes/noteController')
 | |
| app.use('/api/note', notes)
 | |
| 
 | |
| //tags endpoint
 | |
| var tags = require('@routes/tagController')
 | |
| app.use('/api/tag', tags)
 | |
| 
 | |
| //notes endpoint
 | |
| var attachment = require('@routes/attachmentController')
 | |
| app.use('/api/attachment', attachment)
 | |
| 
 | |
| //quick notes endpoint
 | |
| var quickNote = require('@routes/quicknoteController')
 | |
| app.use('/api/quick-note', quickNote)
 | |
| 
 | |
| //cycle tracking endpoint
 | |
| var metricTracking = require('@routes/metrictrackingController')
 | |
| app.use('/api/metric-tracking', metricTracking)
 | |
| 
 | |
| //Output running status
 | |
| app.listen(ports.express, () => { 
 | |
| 	console.log(`Express: Listening on port ${ports.express}!`)
 | |
| })
 | |
| 
 | |
| //
 | |
| //Error handlers
 | |
| //
 | |
| //Default error handler just say unauthorized for everything
 | |
| app.use(function (err, req, res, next) {
 | |
| 	if (err) {
 | |
| 		res.status(401).send('Unauthorized')
 | |
| 		return
 | |
| 	}
 | |
| 	next()
 | |
| }) |