Start working on /account/data controller

This commit is contained in:
Calvin Montgomery 2017-08-30 22:45:48 -07:00
parent 33b2bc2d30
commit 8b1b501bbd
9 changed files with 186 additions and 6 deletions

View file

@ -1,6 +1,7 @@
const assert = require('assert');
const AccountDB = require('../../lib/db/account').AccountDB;
const testDB = require('../testutil/db').testDB;
const { InvalidRequestError } = require('../../lib/errors');
const accountDB = new AccountDB(testDB);
@ -142,6 +143,10 @@ describe('AccountDB', () => {
).then(() => {
throw new Error('Expected failure due to missing user');
}).catch(error => {
assert(
error instanceof InvalidRequestError,
'Expected InvalidRequestError'
);
assert.strictEqual(
error.message,
'Cannot update: name "test" does not exist'

View file

@ -1,6 +1,7 @@
const assert = require('assert');
const ChannelDB = require('../../lib/db/channel').ChannelDB;
const testDB = require('../testutil/db').testDB;
const { InvalidRequestError } = require('../../lib/errors');
const channelDB = new ChannelDB(testDB);
@ -130,6 +131,10 @@ describe('ChannelDB', () => {
}).then(() => {
throw new Error('Expected error due to already existing channel');
}).catch(error => {
assert(
error instanceof InvalidRequestError,
'Expected InvalidRequestError'
);
assert.strictEqual(
error.message,
'Channel "i_test" is already registered.'

View file

@ -8,6 +8,7 @@
},
"license": "MIT",
"dependencies": {
"@calzoneman/express-babel-decorators": "^1.0.0",
"@calzoneman/jsli": "^2.0.1",
"bcrypt": "^0.8.5",
"bluebird": "^2.10.1",
@ -59,6 +60,7 @@
"babel-core": "^6.25.0",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-preset-env": "^1.5.2",
"coffee-script": "^1.9.2",
@ -80,7 +82,8 @@
"plugins": [
"transform-async-to-generator",
"add-module-exports",
"transform-flow-strip-types"
"transform-flow-strip-types",
"transform-decorators-legacy"
]
}
}

View file

@ -1,3 +1,5 @@
import { InvalidRequestError } from '../errors';
const LOGGER = require('@calzoneman/jsli')('AccountDB');
class AccountDB {
@ -26,7 +28,9 @@ class AccountDB {
.where({ name });
if (rowsUpdated === 0) {
throw new Error(`Cannot update: name "${name}" does not exist`);
throw new InvalidRequestError(
`Cannot update: name "${name}" does not exist`
);
}
});
}

View file

@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';
import Promise from 'bluebird';
import { InvalidRequestError } from '../errors';
const unlinkAsync = Promise.promisify(fs.unlink);
@ -41,7 +42,9 @@ class ChannelDB {
.first();
if (existing) {
throw new Error(`Channel "${name}" is already registered.`);
throw new InvalidRequestError(
`Channel "${name}" is already registered.`
);
}
await tx.table('channels')

View file

@ -8,3 +8,4 @@ export const HTTPError = createError('HTTPError', {
status: HTTPStatus.INTERNAL_SERVER_ERROR
});
export const ValidationError = createError('ValidationError');
export const InvalidRequestError = createError('InvalidRequestError');

View file

@ -52,6 +52,8 @@ import { LegacyModule } from './legacymodule';
import { PartitionModule } from './partition/partitionmodule';
import * as Switches from './switches';
import { Gauge } from 'prom-client';
import { AccountDB } from './db/account';
import { ChannelDB } from './db/channel';
var Server = function () {
var self = this;
@ -83,6 +85,9 @@ var Server = function () {
self.db.init();
ChannelStore.init();
const accountDB = new AccountDB(db.getDB());
const channelDB = new ChannelDB(db.getDB());
// webserver init -----------------------------------------------------
const ioConfig = IOConfiguration.fromOldConfig(Config);
const webConfig = WebConfiguration.fromOldConfig(Config);
@ -102,7 +107,9 @@ var Server = function () {
clusterClient,
channelIndex,
session,
globalMessageBus);
globalMessageBus,
accountDB,
channelDB);
// http/https/sio server init -----------------------------------------
var key = "", cert = "", ca = undefined;

View file

@ -0,0 +1,141 @@
import { GET, POST, PATCH, DELETE } from '@calzoneman/express-babel-decorators';
import { CSRFError, InvalidRequestError } from '../../../errors';
import { verify as csrfVerify } from '../../csrf';
const LOGGER = require('@calzoneman/jsli')('AccountDataRoute');
function checkAcceptsJSON(req, res) {
if (!req.accepts('application/json')) {
res.status(406).send('Not Acceptable');
return false;
}
return true;
}
async function authorize(req, res) {
if (!req.signedCookies || !req.signedCookies.auth) {
res.status(401).json({
error: 'Authorization required'
});
return false;
}
try {
csrfVerify(req);
} catch (error) {
if (error instanceof CSRFError) {
res.status(403).json({
error: 'Invalid CSRF token'
});
} else {
LOGGER.error('CSRF check failed: %s', error.stack);
res.status(503).json({ error: 'Internal error' });
}
return false;
}
// TODO: verify session
return true;
}
function reportError(req, res, error) {
if (error instanceof InvalidRequestError) {
res.status(400).json({ error: error.message });
} else {
LOGGER.error(
'%s %s: %s',
req.method,
req.originalUrl,
error.stack
);
res.status(503).json({ error: 'Internal error' });
}
}
class AccountDataRoute {
constructor(accountDB, channelDB) {
this.accountDB = accountDB;
this.channelDB = channelDB;
}
@GET('/account/data/:user')
async getAccount(req, res) {
if (!checkAcceptsJSON(req, res)) return;
if (!await authorize(req, res)) return;
try {
const user = await this.accountDB.getByName(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 });
}
} catch (error) {
reportError(req, res, error);
}
}
@PATCH('/account/data/:user')
async updateAccount(req, res) {
if (!checkAcceptsJSON(req, res)) return;
if (!await authorize(req, res)) return;
res.status(501).json({ error: 'Not implemented' });
}
@GET('/account/data/:user/channels')
async listChannels(req, res) {
if (!checkAcceptsJSON(req, res)) return;
if (!await authorize(req, res)) return;
try {
const channels = await this.channelDB.listByOwner(req.params.user).map(
channel => ({
name: channel.name,
owner: channel.owner,
time: channel.time,
last_loaded: channel.last_loaded,
owner_last_seen: channel.owner_last_seen
})
);
res.status(200).json({ result: channels });
} catch (error) {
reportError(req, res, error);
}
}
@POST('/account/data/:user/channels/:name')
async createChannel(req, res) {
if (!checkAcceptsJSON(req, res)) return;
if (!await authorize(req, res)) return;
res.status(501).json({ error: 'Not implemented' });
}
@DELETE('/account/data/:user/channels/:name')
async deleteChannel(req, res) {
if (!checkAcceptsJSON(req, res)) return;
if (!await authorize(req, res)) return;
res.status(501).json({ error: 'Not implemented' });
}
}
export { AccountDataRoute };

View file

@ -191,7 +191,9 @@ module.exports = {
clusterClient,
channelIndex,
session,
globalMessageBus
globalMessageBus,
accountDB,
channelDB
) {
patchExpressToHandleAsync();
const chanPath = Config.get('channel-path');
@ -253,6 +255,15 @@ module.exports = {
require('../google2vtt').attach(app);
require('./routes/google_drive_userscript')(app);
require('./routes/ustream_bypass')(app);
/*
const { AccountDataRoute } = require('./routes/account/data');
require('@calzoneman/express-babel-decorators').bind(
app,
new AccountDataRoute(accountDB, channelDB)
);
*/
app.use(serveStatic(path.join(__dirname, '..', '..', 'www'), {
maxAge: webConfig.getCacheTTL()
}));