Refactoring

This commit is contained in:
Calvin Montgomery 2017-09-05 22:47:29 -07:00
parent 3eb97bab6a
commit 5b6f86668a
6 changed files with 103 additions and 22 deletions

70
src/controller/account.js Normal file
View File

@ -0,0 +1,70 @@
import { InvalidRequestError } from '../errors';
import { isValidEmail } from '../utilities';
import { parse as parseURL } from 'url';
import bcrypt from 'bcrypt';
import Promise from 'bluebird';
Promise.promisifyAll(bcrypt);
class AccountController {
constructor(accountDB, globalMessageBus) {
this.accountDB = accountDB;
this.globalMessageBus = globalMessageBus;
}
async getAccount(name) {
const user = await this.accountDB.getByName(name);
if (user) {
return {
name: user.name,
email: user.email,
profile: user.profile,
time: user.time
};
} else {
return null;
}
}
async updateAccount(name, updates, password = null) {
let requirePassword = false;
const fields = {};
if (!updates || updates.toString() !== '[object Object]') {
throw new InvalidRequestError('Malformed input');
}
if (updates.email) {
if (!isValidEmail(updates.email)) {
throw new InvalidRequestError('Invalid email address');
}
fields.email = updates.email;
requirePassword = true;
}
if (requirePassword) {
if (!password) {
throw new InvalidRequestError('Password required');
}
const user = await this.accountDB.getUserByName(name);
if (!user) {
throw new InvalidRequestError('User does not exist');
}
// For legacy reasons, the password was truncated to 100 chars.
password = password.substring(0, 100);
if (!await bcrypt.compareAsync(password, user.password)) {
throw new InvalidRequestError('Invalid password');
}
}
await this.accountDB.updateByName(name, fields);
}
}
export { AccountController };

View File

@ -54,6 +54,7 @@ import * as Switches from './switches';
import { Gauge } from 'prom-client';
import { AccountDB } from './db/account';
import { ChannelDB } from './db/channel';
import { AccountController } from './controller/account';
var Server = function () {
var self = this;
@ -88,6 +89,8 @@ var Server = function () {
const accountDB = new AccountDB(db.getDB());
const channelDB = new ChannelDB(db.getDB());
const accountController = new AccountController(accountDB, globalMessageBus);
// webserver init -----------------------------------------------------
const ioConfig = IOConfiguration.fromOldConfig(Config);
const webConfig = WebConfiguration.fromOldConfig(Config);
@ -108,7 +111,7 @@ var Server = function () {
channelIndex,
session,
globalMessageBus,
accountDB,
accountController,
channelDB);
// http/https/sio server init -----------------------------------------

View File

@ -53,6 +53,10 @@
},
root.isValidEmail = function (email) {
if (typeof email !== "string") {
return false;
}
if (email.length > 255) {
return false;
}

View File

@ -75,8 +75,8 @@ function reportError(req, res, error) {
}
class AccountDataRoute {
constructor(accountDB, channelDB, csrfVerify, verifySessionAsync) {
this.accountDB = accountDB;
constructor(accountController, channelDB, csrfVerify, verifySessionAsync) {
this.accountController = accountController;
this.channelDB = channelDB;
this.csrfVerify = csrfVerify;
this.verifySessionAsync = verifySessionAsync;
@ -88,22 +88,9 @@ class AccountDataRoute {
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
try {
const user = await this.accountDB.getByName(req.params.user);
const user = await this.accountController.getAccount(req.params.user);
if (user) {
// Whitelist fields to expose, to avoid accidental
// information leaks when new fields are added.
const result = {
name: user.name,
email: user.email,
profile: user.profile,
time: user.time
};
res.status(200).json({ result });
} else {
res.status(404).json({ result: null });
}
res.status(user === null ? 404 : 200).json({ result: user });
} catch (error) {
reportError(req, res, error);
}
@ -114,7 +101,14 @@ class AccountDataRoute {
if (!checkAcceptsJSON(req, res)) return;
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
res.status(501).json({ error: 'Not implemented' });
const { password, updates } = req.body;
try {
this.accountController.updateAccount(req.user, updates, password);
res.status(204).send();
} catch (error) {
reportError(req, res, error);
}
}
@GET('/account/data/:user/channels')

View File

@ -193,7 +193,7 @@ module.exports = {
channelIndex,
session,
globalMessageBus,
accountDB,
accountController,
channelDB
) {
patchExpressToHandleAsync();
@ -209,6 +209,9 @@ module.exports = {
extended: false,
limit: '1kb' // No POST data should ever exceed this size under normal usage
}));
app.use(bodyParser.json({
limit: '1kb'
}));
if (webConfig.getCookieSecret() === 'change-me') {
LOGGER.warn('The configured cookie secret was left as the ' +
'default of "change-me".');
@ -261,7 +264,12 @@ module.exports = {
const { AccountDataRoute } = require('./routes/account/data');
require('@calzoneman/express-babel-decorators').bind(
app,
new AccountDataRoute(accountDB, channelDB, csrfVerify, verifySessionAsync)
new AccountDataRoute(
accountController,
channelDB,
csrfVerify,
verifySessionAsync
)
);
}

View File

@ -3,6 +3,7 @@ const sinon = require('sinon');
const express = require('express');
const { AccountDB } = require('../../../../lib/db/account');
const { ChannelDB } = require('../../../../lib/db/channel');
const { AccountController } = require('../../../../lib/controller/account');
const { AccountDataRoute } = require('../../../../lib/web/routes/account/data');
const http = require('http');
const expressBabelDecorators = require('@calzoneman/express-babel-decorators');
@ -10,6 +11,7 @@ const nodeurl = require('url');
const Promise = require('bluebird');
const bodyParser = require('body-parser');
const { CSRFError } = require('../../../../lib/errors');
const { EventEmitter } = require('events');
const TEST_PORT = 10111;
const URL_BASE = `http://localhost:${TEST_PORT}`;
@ -89,7 +91,7 @@ describe('AccountDataRoute', () => {
}));
accountDataRoute = new AccountDataRoute(
realAccountDB,
new AccountController(realAccountDB, new EventEmitter()),
realChannelDB,
csrfVerify,
verifySessionAsync