Start merging cytube3 account management
This commit is contained in:
parent
8d2587cebd
commit
b889f7b4c8
34 changed files with 20908 additions and 1 deletions
240
lib/web/account.js
Normal file
240
lib/web/account.js
Normal 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
177
lib/web/auth.js
Normal 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
56
lib/web/jade.js
Normal 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
152
lib/web/webserver.js
Normal 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
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue