Remove code that was never finished and likely won't be used
This commit is contained in:
parent
553052f901
commit
7b0427afa2
9 changed files with 0 additions and 1434 deletions
|
|
@ -1,103 +0,0 @@
|
|||
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 (updates.profile) {
|
||||
validateProfile(updates.profile);
|
||||
|
||||
fields.profile = {
|
||||
image: updates.profile.image.trim(),
|
||||
text: updates.profile.text
|
||||
};
|
||||
}
|
||||
|
||||
if (requirePassword) {
|
||||
if (!password) {
|
||||
throw new InvalidRequestError('Password required');
|
||||
}
|
||||
|
||||
const user = await this.accountDB.getByName(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);
|
||||
}
|
||||
}
|
||||
|
||||
function validateProfile(profile) {
|
||||
// TODO: replace all of these errors with a standard errorcode + field checker
|
||||
if (profile.toString() !== '[object Object]')
|
||||
throw new InvalidRequestError('Invalid profile');
|
||||
if (typeof profile.text !== 'string')
|
||||
throw new InvalidRequestError('Invalid profile');
|
||||
if (typeof profile.image !== 'string')
|
||||
throw new InvalidRequestError('Invalid profile');
|
||||
if (profile.text.length > 255)
|
||||
throw new InvalidRequestError('Profile text must not exceed 255 characters');
|
||||
if (profile.image.length > 255)
|
||||
throw new InvalidRequestError('Profile image URL must not exceed 255 characters');
|
||||
|
||||
if (profile.image.trim() === '') return true;
|
||||
|
||||
const url = parseURL(profile.image);
|
||||
if (!url.host)
|
||||
throw new InvalidRequestError('Invalid profile image URL');
|
||||
if (url.protocol !== 'https:')
|
||||
throw new InvalidRequestError('Profile image URL must start with "https:"');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export { AccountController };
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import { InvalidRequestError } from '../errors';
|
||||
|
||||
const LOGGER = require('@calzoneman/jsli')('AccountDB');
|
||||
|
||||
class AccountDB {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
getByName(name) {
|
||||
return this.db.runTransaction(async tx => {
|
||||
const user = await tx.table('users').where({ name }).first();
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return this.mapUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
updateByName(name, changedFields) {
|
||||
return this.db.runTransaction(async tx => {
|
||||
if (changedFields.profile) {
|
||||
changedFields.profile = JSON.stringify(changedFields.profile);
|
||||
}
|
||||
|
||||
const rowsUpdated = await tx.table('users')
|
||||
.update(changedFields)
|
||||
.where({ name });
|
||||
|
||||
if (rowsUpdated === 0) {
|
||||
throw new InvalidRequestError(
|
||||
`Cannot update: name "${name}" does not exist`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mapUser(user) {
|
||||
// Backwards compatibility
|
||||
// Maybe worth backfilling one day to be done with it?
|
||||
try {
|
||||
let profile;
|
||||
|
||||
if (!user.profile) {
|
||||
profile = { image: '', text: '' };
|
||||
} else {
|
||||
profile = JSON.parse(user.profile);
|
||||
}
|
||||
|
||||
if (!profile.image) profile.image = '';
|
||||
if (!profile.text) profile.text = '';
|
||||
|
||||
user.profile = profile;
|
||||
} catch (error) {
|
||||
// TODO: backfill erroneous records and remove this check
|
||||
LOGGER.warn('Invalid profile "%s": %s', user.profile, error);
|
||||
user.profile = { image: '', text: '' };
|
||||
}
|
||||
|
||||
user.time = new Date(user.time);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
export { AccountDB };
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Promise from 'bluebird';
|
||||
import { InvalidRequestError } from '../errors';
|
||||
|
||||
const unlinkAsync = Promise.promisify(fs.unlink);
|
||||
|
||||
class ChannelDB {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
getByName(name) {
|
||||
return this.db.runTransaction(async tx => {
|
||||
const channel = await tx.table('channels')
|
||||
.where({ name })
|
||||
.first();
|
||||
|
||||
if (!channel) return null;
|
||||
|
||||
return this.mapChannel(channel);
|
||||
});
|
||||
}
|
||||
|
||||
listByOwner(owner) {
|
||||
return this.db.runTransaction(async tx => {
|
||||
const rows = await tx.table('channels')
|
||||
.where({ owner })
|
||||
.select();
|
||||
|
||||
return rows.map(row => this.mapChannel(row));
|
||||
});
|
||||
}
|
||||
|
||||
insert(params) {
|
||||
const { name, owner } = params;
|
||||
|
||||
return this.db.runTransaction(async tx => {
|
||||
const existing = await tx.table('channels')
|
||||
.where({ name })
|
||||
.forUpdate()
|
||||
.first();
|
||||
|
||||
if (existing) {
|
||||
throw new InvalidRequestError(
|
||||
`Channel "${name}" is already registered.`
|
||||
);
|
||||
}
|
||||
|
||||
await tx.table('channels')
|
||||
.insert({
|
||||
name,
|
||||
owner,
|
||||
time: Date.now(), // Old column, does not use datetime type
|
||||
last_loaded: new Date(),
|
||||
owner_last_seen: new Date()
|
||||
});
|
||||
|
||||
await tx.table('channel_ranks')
|
||||
.insert({
|
||||
name: owner,
|
||||
rank: 5,
|
||||
channel: name
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: should this be a soft-delete?
|
||||
deleteByName(name) {
|
||||
return this.db.runTransaction(async tx => {
|
||||
const channel = await tx.table('channels')
|
||||
.where({ name })
|
||||
.forUpdate()
|
||||
.first();
|
||||
|
||||
if (!channel) return;
|
||||
|
||||
await tx.table('channel_ranks').where({ channel: name }).del();
|
||||
await tx.table('channel_bans').where({ channel: name }).del();
|
||||
await tx.table('channel_libraries').where({ channel: name }).del();
|
||||
await tx.table('channel_data').where({ channel_id: channel.id }).del();
|
||||
await tx.table('channels').where({ name }).del();
|
||||
|
||||
// TODO: deprecate and remove flatfile chandumps
|
||||
const chandump = path.resolve(__dirname, '..', '..', 'chandump', name);
|
||||
|
||||
try {
|
||||
await unlinkAsync(chandump);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mapChannel(channel) {
|
||||
// TODO: fix to datetime column?
|
||||
channel.time = new Date(channel.time);
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
export { ChannelDB };
|
||||
|
|
@ -49,9 +49,6 @@ import session from './session';
|
|||
import { LegacyModule } from './legacymodule';
|
||||
import { PartitionModule } from './partition/partitionmodule';
|
||||
import { Gauge } from 'prom-client';
|
||||
import { AccountDB } from './db/account';
|
||||
import { ChannelDB } from './db/channel';
|
||||
import { AccountController } from './controller/account';
|
||||
import { EmailController } from './controller/email';
|
||||
|
||||
var Server = function () {
|
||||
|
|
@ -84,12 +81,6 @@ var Server = function () {
|
|||
self.db.init();
|
||||
ChannelStore.init();
|
||||
|
||||
const accountDB = new AccountDB(db.getDB());
|
||||
const channelDB = new ChannelDB(db.getDB());
|
||||
|
||||
// controllers
|
||||
const accountController = new AccountController(accountDB, globalMessageBus);
|
||||
|
||||
let emailTransport;
|
||||
if (Config.getEmailConfig().getPasswordReset().isEnabled()) {
|
||||
const smtpConfig = Config.getEmailConfig().getSmtp();
|
||||
|
|
@ -138,8 +129,6 @@ var Server = function () {
|
|||
channelIndex,
|
||||
session,
|
||||
globalMessageBus,
|
||||
accountController,
|
||||
channelDB,
|
||||
Config.getEmailConfig(),
|
||||
emailController
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
// TODO: either finish this refactoring or just delete it
|
||||
import { GET, POST, PATCH, DELETE } from '@calzoneman/express-babel-decorators';
|
||||
import { CSRFError, InvalidRequestError } from '../../../errors';
|
||||
|
||||
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, csrfVerify, verifySessionAsync) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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(accountController, channelDB, csrfVerify, verifySessionAsync) {
|
||||
this.accountController = accountController;
|
||||
this.channelDB = channelDB;
|
||||
this.csrfVerify = csrfVerify;
|
||||
this.verifySessionAsync = verifySessionAsync;
|
||||
}
|
||||
|
||||
@GET('/account/data/:user')
|
||||
async getAccount(req, res) {
|
||||
if (!checkAcceptsJSON(req, res)) return;
|
||||
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
|
||||
|
||||
try {
|
||||
const user = await this.accountController.getAccount(req.params.user);
|
||||
|
||||
res.status(user === null ? 404 : 200).json({ result: user });
|
||||
} catch (error) {
|
||||
reportError(req, res, error);
|
||||
}
|
||||
}
|
||||
|
||||
@PATCH('/account/data/:user')
|
||||
async updateAccount(req, res) {
|
||||
if (!checkAcceptsJSON(req, res)) return;
|
||||
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) return;
|
||||
|
||||
const { password, updates } = req.body;
|
||||
|
||||
try {
|
||||
await this.accountController.updateAccount(
|
||||
req.params.user,
|
||||
updates,
|
||||
password
|
||||
);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
reportError(req, res, error);
|
||||
}
|
||||
}
|
||||
|
||||
@GET('/account/data/:user/channels')
|
||||
async listChannels(req, res) {
|
||||
if (!checkAcceptsJSON(req, res)) return;
|
||||
if (!await authorize(req, res, this.csrfVerify, this.verifySessionAsync)) 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, this.csrfVerify, this.verifySessionAsync)) 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, this.csrfVerify, this.verifySessionAsync)) return;
|
||||
|
||||
res.status(501).json({ error: 'Not implemented' });
|
||||
}
|
||||
}
|
||||
|
||||
export { AccountDataRoute };
|
||||
|
|
@ -12,7 +12,6 @@ import { CSRFError, HTTPError } from '../errors';
|
|||
import counters from '../counters';
|
||||
import { Summary, Counter } from 'prom-client';
|
||||
import session from '../session';
|
||||
import { verify as csrfVerify } from './csrf';
|
||||
const verifySessionAsync = require('bluebird').promisify(session.verifySession);
|
||||
|
||||
const LOGGER = require('@calzoneman/jsli')('webserver');
|
||||
|
|
@ -142,8 +141,6 @@ module.exports = {
|
|||
channelIndex,
|
||||
session,
|
||||
globalMessageBus,
|
||||
accountController,
|
||||
channelDB,
|
||||
emailConfig,
|
||||
emailController
|
||||
) {
|
||||
|
|
@ -210,19 +207,6 @@ module.exports = {
|
|||
require('../google2vtt').attach(app);
|
||||
require('./routes/google_drive_userscript')(app);
|
||||
|
||||
if (process.env.UNFINISHED_FEATURE) {
|
||||
const { AccountDataRoute } = require('./routes/account/data');
|
||||
require('@calzoneman/express-babel-decorators').bind(
|
||||
app,
|
||||
new AccountDataRoute(
|
||||
accountController,
|
||||
channelDB,
|
||||
csrfVerify,
|
||||
verifySessionAsync
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
app.use(serveStatic(path.join(__dirname, '..', '..', 'www'), {
|
||||
maxAge: webConfig.getCacheTTL()
|
||||
}));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue