Start merging cytube3 account management

This commit is contained in:
calzoneman 2013-12-12 14:48:23 -06:00
parent 8d2587cebd
commit b889f7b4c8
34 changed files with 20908 additions and 1 deletions

View file

@ -73,6 +73,8 @@ var Server = function (cfg) {
self.httplog = new Logger.Logger(path.join(__dirname,
"../httpaccess.log"));
self.express = express();
require("./web/webserver").init(self.express);
/*
self.express.use(express.urlencoded());
self.express.use(express.json());
self.express.use(express.cookieParser());
@ -139,6 +141,7 @@ var Server = function (cfg) {
next(err);
}
});
*/
// http/https/sio server init -----------------------------------------
if (self.cfg["enable-ssl"]) {

View file

@ -1,4 +1,4 @@
/*
/*
Set prototype- simple wrapper around JS objects to
manipulate them like a set
*/
@ -41,6 +41,22 @@ module.exports = {
return name.match(/^[-\w\u00c0-\u00ff]{1,20}$/);
},
isValidEmail: function (email) {
if (email.length > 255) {
return false;
}
if (!email.match(/^[^@]+?@[^@]+$/)) {
return false;
}
if (email.match(/^[^@]+?@(localhost|127\.0\.0\.1)$/)) {
return false;
}
return true;
},
randomSalt: function (length) {
var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789!@#$%^&*_+=~";

240
lib/web/account.js Normal file
View file

@ -0,0 +1,240 @@
/**
* web/account.js - Webserver details for account management
*
* @author Calvin Montgomery <cyzon@cyzon.us>
*/
var webserver = require('./webserver');
var logRequest = webserver.logRequest;
var sendJade = require('./jade').sendJade;
var Logger = require('../logger');
var db = require('../database');
//var dbchannels = require('../database/channels');
var $util = require('../utilities');
/**
* Handles a GET request for /account/edit
*/
function handleAccountEditPage(req, res) {
logRequest(req);
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(':')[0];
}
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName
});
}
/**
* Handles a POST request to edit a user's account
*/
function handleAccountEdit(req, res) {
logRequest(req);
var action = req.body.action;
switch(action) {
case 'change_password':
handleChangePassword(req, res);
break;
case 'change_email':
handleChangeEmail(req, res);
break;
default:
res.send(400);
break;
}
}
/**
* Handles a request to change the user's password
*/
function handleChangePassword(req, res) {
var name = req.body.name;
var oldpassword = req.body.oldpassword;
var newpassword = req.body.newpassword;
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(':')[0];
}
if (typeof name !== 'string' ||
typeof oldpassword !== 'string' ||
typeof newpassword !== 'string') {
res.send(400);
return;
}
if (newpassword.length === 0) {
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: 'New password must not be empty'
});
return;
}
newpassword = newpassword.substring(0, 100);
db.users.verifyLogin(name, oldpassword, function (err, user) {
if (err) {
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: err
});
return;
}
db.users.setPassword(name, newpassword, function (err, dbres) {
if (err) {
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: err
});
return;
}
Logger.eventlog(webserver.ipForRequest(req) + ' changed password for ' + name);
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName,
successMessage: 'Password changed.'
});
});
});
}
/**
* Handles a request to change the user's email
*/
function handleChangeEmail(req, res) {
var name = req.body.name;
var password = req.body.password;
var email = req.body.email;
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(':')[0];
}
if (typeof name !== 'string' ||
typeof password !== 'string' ||
typeof email !== 'string') {
res.send(400);
return;
}
if (!$util.isValidEmail(email)) {
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: 'Invalid email address'
});
return;
}
db.users.verifyLogin(name, password, function (err, user) {
if (err) {
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: err
});
return;
}
db.users.setEmail(name, email, function (err, dbres) {
if (err) {
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: err
});
return;
}
Logger.eventlog(webserver.ipForRequest(req) + ' changed email for ' + name +
' to ' + email);
sendJade(res, 'account-edit', {
loggedIn: loginName !== false,
loginName: loginName,
successMessage: 'Email address changed.'
});
});
});
}
/**
* Handles a GET request for /account/channels
*/
function handleAccountChannelPage(req, res) {
res.send(500);
return;
logRequest(req);
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(':')[0];
}
if (loginName) {
dbchannels.listUserChannels(loginName, function (err, channels) {
sendJade(res, 'account-channels', {
loggedIn: true,
loginName: loginName,
channels: channels
});
});
} else {
sendJade(res, 'account-channels', {
loggedIn: false,
channels: [],
});
}
}
/**
* Handles a POST request to modify a user's channels
*/
function handleAccountChannel(req, res) {
res.send(500);
return;
logRequest(req);
var action = req.body.action;
switch(action) {
case 'new_channel':
handleNewChannel(req, res);
break;
case 'delete_channel':
handleDeleteChannel(req, res);
break;
default:
res.send(400);
break;
}
}
/**
* Handles a request to register a new channel
*/
function handleNewChannel(req, res) {
res.send(500);
}
/**
* Handles a request to delete a new channel
*/
function handleDeleteChannel(req, res) {
res.send(500);
}
module.exports = {
/**
* Initializes the module
*
* @param app - The Express server to initialize
*/
init: function (app) {
app.get('/account/edit', handleAccountEditPage);
app.post('/account/edit', handleAccountEdit);
app.get('/account/channels', handleAccountChannelPage);
app.post('/account/channels', handleAccountChannel);
}
};

177
lib/web/auth.js Normal file
View file

@ -0,0 +1,177 @@
/**
* web/auth.js - Webserver functions for user authentication and registration
*
* @author Calvin Montgomery <cyzon@cyzon.us>
*/
var jade = require('jade');
var fs = require('fs');
var path = require('path');
var webserver = require('./webserver');
var sendJade = require('./jade').sendJade;
var Logger = require('../logger');
var $util = require('../utilities');
var Server = require('../server');
/**
* Processes a login request. Sets a cookie upon successful authentication
*/
function handleLogin(req, res) {
var name = req.body.name;
var password = req.body.password;
if (typeof name !== 'string' || typeof password !== 'string') {
res.send(400);
return;
}
password = password.substring(0, 100);
Server.getServer().db.users.verifyLogin(name, password, function (err, user) {
if (err) {
if (err === 'Invalid username/password combination') {
Logger.eventlog('Login failed (bad password): ' + name
+ '@' + webserver.ipForRequest(req));
}
sendJade(res, 'login', {
loggedIn: false,
loginError: err
});
} else {
res.cookie('auth', user.name + ':' + user.hash, {
expires: new Date(Date.now() + 60*60*1000),
httpOnly: true
});
sendJade(res, 'login', {
loggedIn: true,
loginName: user.name,
redirect: req.body.redirect || req.header('Referrer')
});
}
});
}
/**
* Handles a GET request for /login
*/
function handleLoginPage(req, res) {
if (req.cookies.auth) {
var split = req.cookies.auth.split(':');
if (split.length === 2) {
sendJade(res, 'login', {
wasAlreadyLoggedIn: true,
loggedIn: true,
loginName: split[0]
});
return;
}
}
sendJade(res, 'login', {
loggedIn: false
});
}
/**
* Handles a request for /logout. Clears auth cookie
*/
function handleLogout(req, res) {
res.clearCookie('auth');
sendJade(res, 'logout', {
redirect: req.body.redirect || req.header('Referrer')
});
}
/**
* Handles a GET request for /register
*/
function handleRegisterPage(req, res) {
if (req.cookies.auth) {
var split = req.cookies.auth.split(':');
if (split.length === 2) {
sendJade(res, 'register', {
loggedIn: true,
loginName: split[0]
});
return;
}
}
sendJade(res, 'register', {
registered: false,
registerError: false
});
}
/**
* Processes a registration request.
*/
function handleRegister(req, res) {
var name = req.body.name;
var password = req.body.password;
var email = req.body.email;
if (typeof email !== 'string') {
email = '';
}
var ip = webserver.ipForRequest(req);
if (typeof name !== 'string' || typeof password !== 'string') {
res.send(400);
return;
}
if (name.length === 0) {
sendJade(res, 'register', {
registerError: 'Username must not be empty'
});
return;
}
if (password.length === 0) {
sendJade(res, 'register', {
registerError: 'Password must not be empty'
});
return;
}
password = password.substring(0, 100);
if (!$util.isValidEmail(email)) {
sendJade(res, 'register', {
registerError: 'Invalid email address'
});
return;
}
Server.getServer().db.users.register({
name: name,
password: password,
email: email,
ip: ip
}, function (err) {
if (err) {
sendJade(res, 'register', {
registerError: err
});
} else {
Logger.eventlog(ip + ' registered account: ' + name +
(email.length > 0 ? ' <' + email + '>' : ''));
sendJade(res, 'register', {
registered: true,
registerName: name,
redirect: req.body.redirect
});
}
});
}
module.exports = {
/**
* Initializes auth callbacks
*/
init: function (app) {
app.get('/login', handleLoginPage);
app.post('/login', handleLogin);
app.get('/logout', handleLogout);
app.get('/register', handleRegisterPage);
app.post('/register', handleRegister);
}
};

56
lib/web/jade.js Normal file
View file

@ -0,0 +1,56 @@
/**
* web/jade.js - Provides functionality for rendering/serving jade templates
*
* @author Calvin Montgomery <cyzon@cyzon.us>
*/
var jade = require('jade');
var fs = require('fs');
var path = require('path');
var templates = path.join(__dirname, '..', '..', 'templates');
var cache = {};
/**
* Merges locals with globals for jade rendering
*
* @param {Object} locals - The locals to merge
* @return {Object} an object containing globals and locals
*/
function merge(locals) {
var _locals = {
siteTitle: 'CyTube Beta',
siteDescription: 'Free, open source synchtube',
siteAuthor: 'Calvin "calzoneman" "cyzon" Montgomery'
};
if (typeof locals !== 'object') {
return _locals;
}
for (var key in locals) {
_locals[key] = locals[key];
}
return _locals;
}
/**
* Renders and serves a jade template
*
* @param res - The HTTP response
* @param view - The view to render
* @param locals - The locals to pass to the renderer
*/
function sendJade(res, view, locals) {
if (!(view in cache) || process.env['DEBUG']) {
var file = path.join(templates, view + '.jade');
var fn = jade.compile(fs.readFileSync(file), {
filename: file
});
cache[view] = fn;
}
var html = cache[view](merge(locals));
res.send(html);
}
module.exports = {
sendJade: sendJade
};

152
lib/web/webserver.js Normal file
View file

@ -0,0 +1,152 @@
/**
* web/webserver.js - functions for serving web content
*
* @author Calvin Montgomery <cyzon@cyzon.us>
*/
var path = require('path');
var net = require('net');
var express = require('express');
var webroot = path.join(__dirname, '..', 'www');
var sendJade = require('./jade').sendJade;
var Server = require('../server');
var $util = require('../utilities');
var Logger = require('../logger');
var httplog = new Logger.Logger(path.join(__dirname, '..', '..', 'http.log'));
var suspiciousPath = (/admin|adm|\.\.|\/etc\/passwd|\\x5c|%5c|0x5c|setup|install|php|pma|blog|sql|scripts|aspx?|database/ig);
/**
* Determines whether a request is suspected of being illegitimate
*/
function isSuspicious(req) {
// ZmEu is a penetration script
if (req.header('user-agent') &&
req.header('user-agent').toLowerCase() === 'zmeu') {
return true;
}
if (req.path.match(suspiciousPath)) {
return true;
}
return false;
}
/**
* Extracts an IP address from a request. Uses X-Forwarded-For if the IP is localhost
*/
function ipForRequest(req) {
var ip = req.ip;
if (ip === '127.0.0.1' || ip === '::1') {
var xforward = req.header('x-forwarded-for');
if (typeof xforward !== 'string' || !net.isIP(xforward)) {
return ip;
} else {
return xforward;
}
}
return ip;
}
/**
* Logs an HTTP request
*/
function logRequest(req, status) {
if (status === undefined) {
status = 200;
}
httplog.log([
ipForRequest(req),
req.route.method.toUpperCase(),
req.path,
status,
req.header('user-agent')
].join(' '));
}
/**
* Handles a GET request for /r/:channel - serves channel.html
*/
function handleChannel(req, res) {
if (!$util.isValidChannelName(req.params.channel)) {
logRequest(req, 404);
res.status(404);
res.send('Invalid channel name "' + req.params.channel + '"');
return;
}
logRequest(req);
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(':')[0];
}
var inst = Server.getServer();
var iourl = '';
sendJade(res, 'channel', {
channelName: req.params.channel,
layout: 'hd',
loggedIn: loginName !== false,
loginName: loginName,
/*ioUrl: 'http://' + inst.config.sio.domain + ':' + inst.config.sio.port*/
});
}
/**
* Handles a request for the index page
*/
function handleIndex(req, res) {
logRequest(req);
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(':')[0];
}
sendJade(res, 'index', {
loggedIn: loginName !== false,
loginName: loginName
});
}
module.exports = {
/**
* Initializes webserver callbacks
*
* @param app - The express instance to initialize
*/
init: function (app) {
app.use(express.json());
app.use(express.urlencoded());
app.use(express.cookieParser());
/* Order here is important
* Since I placed /r/:channel above *, the function will
* not apply to the /r/:channel route. This prevents
* duplicate logging, since /r/:channel's callback does
* its own logging
*/
app.get('/r/:channel', handleChannel);
app.get('/', handleIndex);
app.all('*', function (req, res, next) {
if (isSuspicious(req)) {
logRequest(req, 403);
res.status(403);
if (req.header('user-agent').toLowerCase() === 'zmeu') {
res.send('This server disallows requests from ZmEu.');
} else {
res.send('The request ' + req.route.method.toUpperCase() + ' ' +
req.path + ' looks pretty fishy to me. Double check that ' +
'you typed it correctly.');
}
return;
}
logRequest(req);
next();
});
app.use(express.static('www'));
require('./auth').init(app);
require('./account').init(app);
},
logRequest: logRequest,
ipForRequest: ipForRequest
};