notes.kaplon.us/server.js

214 lines
8.9 KiB
JavaScript
Raw Normal View History

var express = require('express');
2016-02-16 17:52:38 -05:00
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.text()); // Use defaults for now, size limit is 100kb.
app.use(bodyParser.urlencoded({ extended: true })); // Also need url encoding to handle login form.
2016-02-16 17:52:38 -05:00
var winston = require('winston');
winston.add(winston.transports.File, { filename: './logs/notes.kaplon.us.log', maxsize: 5000000 }); // 5MB
var fileSystem = require('fs');
var favicon = require('serve-favicon');
app.use(favicon(__dirname + '/assets/favicon.ico')); // Put this before setting static dir.
app.use(express.static(__dirname + '/assets'));
/*-----------------------------------------
Ordering of these configs is important, don't shuffle them around.
------------------------------------------*/
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
var db = require('./db');
var Session = require('express-session');
var SessionStore = require('session-file-store')(Session);
var session = Session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { sameSite: true, secure: true },
store: new SessionStore({path: __dirname+'/tmp/sessions'})
});
app.use(session);
app.set('trust proxy', true);
//----------------------------
// Configure the local strategy for use by Passport.
// The local strategy require a `verify` function which receives the credentials
// (`username` and `password`) submitted by the user. The function must verify
// that the password is correct and then invoke `cb` with a user object, which
// will be set at `req.user` in route handlers after authentication.
passport.use(new Strategy(
function(username, password, cb) {
db.utils.findByUsername(username, function(err, user) {
winston.info('trying to lookup user.');
if (err) { winston.info('db.utils.findByUsername error.'); return cb(err); }
if (!user) { winston.info('bad user'); return cb(null, false); }
if (user.password != password) { winston.info('bad pw'); return cb(null, false); }
return cb(null, user);
});
}));
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session. The
// typical implementation of this is as simple as supplying the user ID when
// serializing, and querying the user record by ID from the database when
// deserializing.
passport.serializeUser(function(user, cb) {
cb(null, user.id);
});
passport.deserializeUser(function(id, cb) {
db.utils.findById(id, function (err, user) {
if (err) { return cb(err); }
cb(null, user);
});
});
2016-02-16 17:52:38 -05:00
// Initialize Passport and restore authentication state, if any, from the session.
app.use(passport.initialize());
app.use(passport.session());
2016-06-17 10:02:51 -04:00
// As with any middleware it is quintessential to call next() if the user is authenticated
var isAuthenticated = function (req, res, next) {
if (req.isAuthenticated()) {
return next();
res.redirect('/');
} else {
// If user NOT authenticated, redirect to login page, do not call next().
res.redirect('/login');
}
}
app.get('/', isAuthenticated, function(req, res){
2016-02-16 17:52:38 -05:00
winston.info("GET /");
// Respond with static file, contents will be loaded via websocket.
res.sendFile('index.html', { root: __dirname + '/views/' });
2016-02-16 17:52:38 -05:00
});
app.get('/login', function(req, res){
winston.info('GET /login');
res.sendFile('login.html', { root: __dirname + '/views/' });
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login' }),
function(req, res) {
winston.info('sucessful login for user, ' + req.user.username);
res.redirect('/');
});
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
2016-11-09 11:14:32 -05:00
var http = require('http').Server(app);
var io = require('socket.io')(http, { cookie: false });
var iosess = require('socket.io-express-session');
io.use(iosess(session));
2016-11-09 11:14:32 -05:00
io.on('connection', function(socket){
//winston.info('a user connected');
winston.info('session id, '+ socket.handshake.session.id);
// next line no worky...but i can get by with only user-#.
//winston.info('user name, '+ socket.handshake.session.passport.user.username);
try {
var userNum = socket.handshake.session.passport.user;
} catch (ex) {
// Send redirect to client, socket connection will close when client goes back to /login.
// But disconnect just in case client is misbehaving.
winston.info('Session DNE, redirect and disconnect.');
socket.emit('redirect', '/login');
socket.disconnect();
}
winston.info('user id, '+ userNum);
socket.join(userNum);
var notePath, noteDir
if (userNum === 1) {
notePath = __dirname + '/note-data/allNotes.txt';
noteDir = __dirname + '/note-data/';
} else {
notePath = __dirname + '/note-data/' + userNum + '/allNotes.txt';
noteDir = __dirname + '/note-data/' + userNum + '/';
}
fileSystem.readFile(notePath, {encoding: 'utf-8'}, function(err,data){
if (!err){
winston.info('file read on connection');
socket.emit('download allNotes',data); // Send content only to newly connected client.
} else { winston.error(err); }
});
socket.on('upload allNotes', function(msg){
winston.info('WS data received from user # '+ userNum);
var now = Date.now();
// Overwrite allNotes.txt with new contents from client.
fileSystem.readFile(notePath, 'utf-8', function(err, data){
if (err) {
winston.error(err);
} else {
fileSystem.writeFile(notePath, msg, 'utf-8', function(err) {
if (err) { winston.error(err); }
var exec = require('child_process').exec;
var cmd = `
cd ${noteDir} && \
git checkout -b ${now} && \
git commit -am "Notes modified via websocket, ${now}" && \
git checkout master && \
git merge ${now}
`;
//winston.info(cmd);
exec(cmd, { uid: 1000 }, function(error, stdout, stderr) {
if (error) { winston.error(error); }
if (!stdout.includes('CONFLICT')) { // Case-sensitivity seems brittle here. Better to check return code rather than stdout? Can child_process be called w/signature to include return code?
var cmd2 = `cd ${noteDir} && git branch -d ${now}`
exec(cmd2, function(error, stdout, stderr) {
if (error) { winston.error(error); }
winston.info('temp branch deleted; new content written to allNotes.txt');
// Send new content to all other clients EXCEPT the orig sender.
socket.in(userNum).emit('download allNotes',msg);
winston.info('sent content update to other clients');
});
} else {
// Auto-merge failed, keep the new branch.
// Send a conflict warning message to socket.io clients (prompt for IAP ;-).
socket.in(userNum).emit('conflict', `Auto-merge failed, manually merge ${now} branch to rescue recent edit.`);
socket.in(userNum).emit('download allNotes',data); // Send pre-conflict content back down to client.
winston.info(`Auto-merge failed, manually merge ${now} branch to rescue recent edit.`);
// MVP for now, can deal w/merge conflicts with git on the server if the cotent needs to be saved.
}
winston.info(stdout);
});
});
}
});
});
2016-11-09 11:14:32 -05:00
socket.on('cm-save', function(msg){
winston.info(msg);
});
});
setInterval(
function() {
//winston.info('setInterval fired.');
var exec = require('child_process').exec;
const userIdArray = db.utils.userIdArray();
for (const userId of userIdArray) {
var noteDir;
if (userId === 1) {
noteDir = __dirname + '/note-data/';
} else {
noteDir = __dirname + '/note-data/' + userId + '/';
}
//winston.info('noteDir = ' + noteDir);
exec(
`cd ${noteDir} && git push`,
{ uid: 1000 },
function(err) {
if (err) { winston.error(err); }
}
);
}
}, 120000 // 2 min
);
2016-11-09 11:14:32 -05:00
http.listen(3000, function() {
2016-02-16 17:52:38 -05:00
winston.info("Started on PORT 3000");
});