mirror of https://github.com/calzoneman/sync.git
Start working on /account/data controller
This commit is contained in:
parent
33b2bc2d30
commit
8b1b501bbd
|
@ -1,6 +1,7 @@
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const AccountDB = require('../../lib/db/account').AccountDB;
|
const AccountDB = require('../../lib/db/account').AccountDB;
|
||||||
const testDB = require('../testutil/db').testDB;
|
const testDB = require('../testutil/db').testDB;
|
||||||
|
const { InvalidRequestError } = require('../../lib/errors');
|
||||||
|
|
||||||
const accountDB = new AccountDB(testDB);
|
const accountDB = new AccountDB(testDB);
|
||||||
|
|
||||||
|
@ -142,6 +143,10 @@ describe('AccountDB', () => {
|
||||||
).then(() => {
|
).then(() => {
|
||||||
throw new Error('Expected failure due to missing user');
|
throw new Error('Expected failure due to missing user');
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
assert(
|
||||||
|
error instanceof InvalidRequestError,
|
||||||
|
'Expected InvalidRequestError'
|
||||||
|
);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
error.message,
|
error.message,
|
||||||
'Cannot update: name "test" does not exist'
|
'Cannot update: name "test" does not exist'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const ChannelDB = require('../../lib/db/channel').ChannelDB;
|
const ChannelDB = require('../../lib/db/channel').ChannelDB;
|
||||||
const testDB = require('../testutil/db').testDB;
|
const testDB = require('../testutil/db').testDB;
|
||||||
|
const { InvalidRequestError } = require('../../lib/errors');
|
||||||
|
|
||||||
const channelDB = new ChannelDB(testDB);
|
const channelDB = new ChannelDB(testDB);
|
||||||
|
|
||||||
|
@ -130,6 +131,10 @@ describe('ChannelDB', () => {
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
throw new Error('Expected error due to already existing channel');
|
throw new Error('Expected error due to already existing channel');
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
assert(
|
||||||
|
error instanceof InvalidRequestError,
|
||||||
|
'Expected InvalidRequestError'
|
||||||
|
);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
error.message,
|
error.message,
|
||||||
'Channel "i_test" is already registered.'
|
'Channel "i_test" is already registered.'
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@calzoneman/express-babel-decorators": "^1.0.0",
|
||||||
"@calzoneman/jsli": "^2.0.1",
|
"@calzoneman/jsli": "^2.0.1",
|
||||||
"bcrypt": "^0.8.5",
|
"bcrypt": "^0.8.5",
|
||||||
"bluebird": "^2.10.1",
|
"bluebird": "^2.10.1",
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
"babel-core": "^6.25.0",
|
"babel-core": "^6.25.0",
|
||||||
"babel-plugin-add-module-exports": "^0.2.1",
|
"babel-plugin-add-module-exports": "^0.2.1",
|
||||||
"babel-plugin-transform-async-to-generator": "^6.24.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-plugin-transform-flow-strip-types": "^6.22.0",
|
||||||
"babel-preset-env": "^1.5.2",
|
"babel-preset-env": "^1.5.2",
|
||||||
"coffee-script": "^1.9.2",
|
"coffee-script": "^1.9.2",
|
||||||
|
@ -80,7 +82,8 @@
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"transform-async-to-generator",
|
"transform-async-to-generator",
|
||||||
"add-module-exports",
|
"add-module-exports",
|
||||||
"transform-flow-strip-types"
|
"transform-flow-strip-types",
|
||||||
|
"transform-decorators-legacy"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { InvalidRequestError } from '../errors';
|
||||||
|
|
||||||
const LOGGER = require('@calzoneman/jsli')('AccountDB');
|
const LOGGER = require('@calzoneman/jsli')('AccountDB');
|
||||||
|
|
||||||
class AccountDB {
|
class AccountDB {
|
||||||
|
@ -26,7 +28,9 @@ class AccountDB {
|
||||||
.where({ name });
|
.where({ name });
|
||||||
|
|
||||||
if (rowsUpdated === 0) {
|
if (rowsUpdated === 0) {
|
||||||
throw new Error(`Cannot update: name "${name}" does not exist`);
|
throw new InvalidRequestError(
|
||||||
|
`Cannot update: name "${name}" does not exist`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
import { InvalidRequestError } from '../errors';
|
||||||
|
|
||||||
const unlinkAsync = Promise.promisify(fs.unlink);
|
const unlinkAsync = Promise.promisify(fs.unlink);
|
||||||
|
|
||||||
|
@ -41,7 +42,9 @@ class ChannelDB {
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
throw new Error(`Channel "${name}" is already registered.`);
|
throw new InvalidRequestError(
|
||||||
|
`Channel "${name}" is already registered.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await tx.table('channels')
|
await tx.table('channels')
|
||||||
|
|
|
@ -7,4 +7,5 @@ export const CSRFError = createError('CSRFError');
|
||||||
export const HTTPError = createError('HTTPError', {
|
export const HTTPError = createError('HTTPError', {
|
||||||
status: HTTPStatus.INTERNAL_SERVER_ERROR
|
status: HTTPStatus.INTERNAL_SERVER_ERROR
|
||||||
});
|
});
|
||||||
export const ValidationError = createError('ValidationError');
|
export const ValidationError = createError('ValidationError');
|
||||||
|
export const InvalidRequestError = createError('InvalidRequestError');
|
||||||
|
|
|
@ -52,6 +52,8 @@ import { LegacyModule } from './legacymodule';
|
||||||
import { PartitionModule } from './partition/partitionmodule';
|
import { PartitionModule } from './partition/partitionmodule';
|
||||||
import * as Switches from './switches';
|
import * as Switches from './switches';
|
||||||
import { Gauge } from 'prom-client';
|
import { Gauge } from 'prom-client';
|
||||||
|
import { AccountDB } from './db/account';
|
||||||
|
import { ChannelDB } from './db/channel';
|
||||||
|
|
||||||
var Server = function () {
|
var Server = function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -83,6 +85,9 @@ var Server = function () {
|
||||||
self.db.init();
|
self.db.init();
|
||||||
ChannelStore.init();
|
ChannelStore.init();
|
||||||
|
|
||||||
|
const accountDB = new AccountDB(db.getDB());
|
||||||
|
const channelDB = new ChannelDB(db.getDB());
|
||||||
|
|
||||||
// webserver init -----------------------------------------------------
|
// webserver init -----------------------------------------------------
|
||||||
const ioConfig = IOConfiguration.fromOldConfig(Config);
|
const ioConfig = IOConfiguration.fromOldConfig(Config);
|
||||||
const webConfig = WebConfiguration.fromOldConfig(Config);
|
const webConfig = WebConfiguration.fromOldConfig(Config);
|
||||||
|
@ -102,7 +107,9 @@ var Server = function () {
|
||||||
clusterClient,
|
clusterClient,
|
||||||
channelIndex,
|
channelIndex,
|
||||||
session,
|
session,
|
||||||
globalMessageBus);
|
globalMessageBus,
|
||||||
|
accountDB,
|
||||||
|
channelDB);
|
||||||
|
|
||||||
// http/https/sio server init -----------------------------------------
|
// http/https/sio server init -----------------------------------------
|
||||||
var key = "", cert = "", ca = undefined;
|
var key = "", cert = "", ca = undefined;
|
||||||
|
|
|
@ -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 };
|
|
@ -191,7 +191,9 @@ module.exports = {
|
||||||
clusterClient,
|
clusterClient,
|
||||||
channelIndex,
|
channelIndex,
|
||||||
session,
|
session,
|
||||||
globalMessageBus
|
globalMessageBus,
|
||||||
|
accountDB,
|
||||||
|
channelDB
|
||||||
) {
|
) {
|
||||||
patchExpressToHandleAsync();
|
patchExpressToHandleAsync();
|
||||||
const chanPath = Config.get('channel-path');
|
const chanPath = Config.get('channel-path');
|
||||||
|
@ -253,6 +255,15 @@ module.exports = {
|
||||||
require('../google2vtt').attach(app);
|
require('../google2vtt').attach(app);
|
||||||
require('./routes/google_drive_userscript')(app);
|
require('./routes/google_drive_userscript')(app);
|
||||||
require('./routes/ustream_bypass')(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'), {
|
app.use(serveStatic(path.join(__dirname, '..', '..', 'www'), {
|
||||||
maxAge: webConfig.getCacheTTL()
|
maxAge: webConfig.getCacheTTL()
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue