332 lines
8.3 KiB
JavaScript
332 lines
8.3 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') })
|
|
|
|
//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() )
|
|
const port = 3000
|
|
|
|
|
|
//
|
|
// 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])
|
|
} else {
|
|
socket.emit('past_diffs', null)
|
|
}
|
|
|
|
const usersInRoom = io.sockets.adapter.rooms[rawTextId]
|
|
if(usersInRoom){
|
|
//Update users in room count
|
|
io.to(rawTextId).emit('update_user_count', usersInRoom.length)
|
|
|
|
//Debugging text - prints out notes in limbo
|
|
let noteDiffKeys = Object.keys(noteDiffs)
|
|
let totalDiffs = 0
|
|
noteDiffKeys.forEach(diffSetKey => {
|
|
if(noteDiffs[diffSetKey]){
|
|
totalDiffs += noteDiffs[diffSetKey].length
|
|
}
|
|
})
|
|
//Debugging Text
|
|
if(noteDiffKeys.length > 0){
|
|
console.log('Total notes in limbo -> ', noteDiffKeys.length)
|
|
console.log('Total Diffs for all notes -> ', totalDiffs)
|
|
}
|
|
|
|
}
|
|
})
|
|
|
|
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)
|
|
|
|
//Remove duplicate diffs if they exist
|
|
for (var i = noteDiffs[noteId].length - 1; i >= 0; i--) {
|
|
|
|
let pastDiff = noteDiffs[noteId][i]
|
|
|
|
for (var j = noteDiffs[noteId].length - 1; j >= 0; j--) {
|
|
let currentDiff = noteDiffs[noteId][j]
|
|
|
|
if(i == j){
|
|
continue
|
|
}
|
|
|
|
if(currentDiff.diff == pastDiff.diff || currentDiff.time == pastDiff.time){
|
|
console.log('Removing Duplicate')
|
|
noteDiffs[noteId].splice(i,1)
|
|
}
|
|
}
|
|
}
|
|
|
|
//Each user joins a room when they open the app.
|
|
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 => {
|
|
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(3001, function(){
|
|
console.log('socket.io liseting on port 3001');
|
|
});
|
|
|
|
//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()
|
|
})
|
|
|
|
//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)
|
|
|
|
//Output running status
|
|
app.listen(port, () => {
|
|
console.log(`Listening on port ${port}!`)
|
|
})
|
|
|
|
//
|
|
//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()
|
|
}) |