2017-08-30 22:45:48 -07:00
|
|
|
import { GET, POST, PATCH, DELETE } from '@calzoneman/express-babel-decorators';
|
|
|
|
|
import { CSRFError, InvalidRequestError } from '../../../errors';
|
2017-09-01 21:20:07 -07:00
|
|
|
import Promise from 'bluebird';
|
2017-08-30 22:45:48 -07:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-01 21:20:07 -07:00
|
|
|
async function authorize(req, res, csrfVerify, verifySessionAsync) {
|
2017-08-30 22:45:48 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-01 21:20:07 -07:00
|
|
|
try {
|
|
|
|
|
const user = await verifySessionAsync(req.signedCookies.auth);
|
|
|
|
|
|
|
|
|
|
if (user.name !== req.params.user) {
|
|
|
|
|
res.status(403).json({
|
|
|
|
|
error: 'Session username does not match'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(403).json({
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-08-30 22:45:48 -07:00
|
|
|
|
|
|
|
|
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 {
|
2017-09-01 21:20:07 -07:00
|
|
|
constructor(accountDB, channelDB, csrfVerify, verifySessionAsync) {
|
2017-08-30 22:45:48 -07:00
|
|
|
this.accountDB = accountDB;
|
|
|
|
|
this.channelDB = channelDB;
|
2017-09-01 21:20:07 -07:00
|
|
|
this.csrfVerify = csrfVerify;
|
|
|
|
|
this.verifySessionAsync = verifySessionAsync;
|
2017-08-30 22:45:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@GET('/account/data/:user')
|
|
|
|
|
async getAccount(req, res) {
|
|
|
|
|
if (!checkAcceptsJSON(req, res)) return;
|
2017-09-01 21:20:07 -07:00
|
|
|
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
|
2017-08-30 22:45:48 -07:00
|
|
|
|
|
|
|
|
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;
|
2017-09-01 21:20:07 -07:00
|
|
|
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
|
2017-08-30 22:45:48 -07:00
|
|
|
|
|
|
|
|
res.status(501).json({ error: 'Not implemented' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@GET('/account/data/:user/channels')
|
|
|
|
|
async listChannels(req, res) {
|
|
|
|
|
if (!checkAcceptsJSON(req, res)) return;
|
2017-09-01 21:20:07 -07:00
|
|
|
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
|
2017-08-30 22:45:48 -07:00
|
|
|
|
|
|
|
|
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;
|
2017-09-01 21:20:07 -07:00
|
|
|
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
|
2017-08-30 22:45:48 -07:00
|
|
|
|
|
|
|
|
res.status(501).json({ error: 'Not implemented' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@DELETE('/account/data/:user/channels/:name')
|
|
|
|
|
async deleteChannel(req, res) {
|
|
|
|
|
if (!checkAcceptsJSON(req, res)) return;
|
2017-09-01 21:20:07 -07:00
|
|
|
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
|
2017-08-30 22:45:48 -07:00
|
|
|
|
|
|
|
|
res.status(501).json({ error: 'Not implemented' });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export { AccountDataRoute };
|