move client class to class folder (rewrite)

merge-requests/4/head
Matthew 2020-06-29 02:35:05 -04:00
parent c00b5d0a05
commit 9d38a8bec7
No known key found for this signature in database
GPG Key ID: 210AF32ADE3B5C4B
4 changed files with 285 additions and 2 deletions

135
src/class/Client.ts Normal file
View File

@ -0,0 +1,135 @@
import Eris from 'eris';
import Redis from 'ioredis';
import mongoose from 'mongoose';
import signale from 'signale';
import fs from 'fs-extra';
import config from '../config.json';
import CSCLI from '../cscli/main';
import { Server } from '../api';
import { Account, AccountInterface, Moderation, ModerationInterface, Domain, DomainInterface, Tier, TierInterface } from '../models';
import { emojis } from '../stores';
import { Command, Util, Collection } from '.';
import * as commands from '../commands';
export default class Client extends Eris.Client {
public config: { 'token': string; 'cloudflare': string; 'prefix': string; 'emailPass': string; 'mongoURL': string; 'port': number; 'keyPair': { 'publicKey': string; 'privateKey': string; }; };
public util: Util;
public commands: Collection<Command>;
public db: { Account: mongoose.Model<AccountInterface>; Domain: mongoose.Model<DomainInterface>; Moderation: mongoose.Model<ModerationInterface>; Tier: mongoose.Model<TierInterface>; };
public redis: Redis.Redis;
public stores: { emojis: { success: string, loading: string, error: string }; };
public functions: Collection<Function>;
public signale: signale.Signale;
public server: Server;
public updating: boolean;
public buildError: boolean
constructor() {
super(config.token, { getAllUsers: true, restMode: true, defaultImageFormat: 'png' });
process.title = 'cloudservices';
this.config = config;
this.util = new Util(this);
this.commands = new Collection<Command>();
this.functions = new Collection<Function>();
this.db = { Account, Domain, Moderation, Tier };
this.redis = new Redis();
this.stores = { emojis };
this.signale = signale;
this.signale.config({
displayDate: true,
displayTimestamp: true,
displayFilename: true,
});
this.updating = false;
this.buildError = false;
this.events();
this.loadFunctions();
this.init();
}
private async events() {
process.on('unhandledRejection', (error) => {
this.signale.error(error);
});
}
private async loadFunctions() {
const functions = await fs.readdir('./functions');
functions.forEach(async (func) => {
if (func === 'index.ts' || func === 'index.js') return;
try {
const funcRequire: Function = require(`./functions/${func}`).default;
this.functions.set(func.split('.')[0], funcRequire);
} catch (error) {
this.signale.error(`Error occured loading ${func}`);
await this.util.handleError(error);
}
});
}
public loadCommand(CommandFile: any) {
// eslint-disable-next-line no-useless-catch
try {
// eslint-disable-next-line
const command: Command = new CommandFile(this);
if (command.subcmds.length) {
command.subcmds.forEach((C) => {
const cmd: Command = new C(this);
command.subcommands.add(cmd.name, cmd);
});
}
delete command.subcmds;
this.commands.add(command.name, command);
this.signale.complete(`Loaded command ${command.name}`);
} catch (err) { throw err; }
}
public async init() {
const evtFiles = await fs.readdir('./events/');
Object.values(commands).forEach((c: Function) => this.loadCommand(c));
evtFiles.forEach((file) => {
const eventName = file.split('.')[0];
if (file === 'index.js') return;
// eslint-disable-next-line
const event = new (require(`./events/${file}`).default)(this);
this.signale.complete(`Loaded event ${eventName}`);
this.on(eventName, (...args) => event.run(...args));
delete require.cache[require.resolve(`./events/${file}`)];
});
await mongoose.connect(config.mongoURL, { useNewUrlParser: true, useUnifiedTopology: true });
await this.connect();
this.on('ready', () => {
this.signale.info(`Connected to Discord as ${this.user.username}#${this.user.discriminator}`);
});
const intervals = await fs.readdir('./intervals');
intervals.forEach((interval) => {
// eslint-disable-next-line
if (interval === 'index.js') return;
require(`./intervals/${interval}`).default(this);
this.signale.complete(`Loaded interval ${interval.split('.')[0]}`);
});
this.server = new Server(this, { port: this.config.port });
// eslint-disable-next-line no-new
new CSCLI(this);
const corepath = '/opt/CloudServices/dist';
const cmdFiles = await fs.readdir('/opt/CloudServices/dist/commands');
cmdFiles.forEach((f) => delete require.cache[`${corepath}/${f}`]);
delete require.cache[`${corepath}/config.json`];
delete require.cache[`${corepath}/class/Util`];
}
}

77
src/class/Security.ts Normal file
View File

@ -0,0 +1,77 @@
/* eslint-disable no-underscore-dangle */
import crypto from 'crypto';
import { Request } from 'express';
import { Client } from '..';
import { AccountInterface } from '../models';
export default class Security {
public client: Client;
protected readonly keyBase: { key: string, iv: string };
constructor(client: Client) {
this.client = client;
this.keyBase = require(`${process.cwd()}/keys.json`);
}
get keys() {
return {
key: Buffer.from(this.keyBase.key, 'base64'),
iv: Buffer.from(this.keyBase.iv, 'base64'),
};
}
/**
* Creates a new Bearer token.
* @param _id The Mongoose Document property labeled ._id
*/
public async createBearer(_id: string): Promise<string> {
let account = await this.client.db.Account.findOne({ _id });
if (!account) throw new Error(`Account [${_id}] cannot be found.`);
const salt = crypto.randomBytes(50).toString('base64');
const cipher = crypto.createCipheriv('aes-256-gcm', this.keys.key, this.keys.iv);
await account.updateOne({ salt });
account = await this.client.db.Account.findOne({ _id });
let encrypted = cipher.update(JSON.stringify(account), 'utf8', 'base64');
encrypted += cipher.final('base64');
await account.updateOne({ authTag: cipher.getAuthTag() });
return `${salt}:${encrypted}`;
}
/**
* If the bearer token is valid, will return the Account, else will return null.
* @param bearer The bearer token provided.
*/
public async checkBearer(bearer: string): Promise<null | AccountInterface> {
const decipher = crypto.createDecipheriv('aes-256-gcm', this.keys.key, this.keys.iv);
try {
const salt = bearer.split(':')[0];
const saltCheck = await this.client.db.Account.findOne({ salt });
const encrypted = bearer.split(':')[1];
let decrypted = decipher.update(encrypted, 'base64', 'utf8');
decipher.setAuthTag(saltCheck.authTag);
decrypted += decipher.final('utf8');
const json = JSON.parse(decrypted);
const account = await this.client.db.Account.findOne({ username: json.username });
if (saltCheck.salt !== account.salt) return null;
return account;
} catch (error) {
this.client.util.handleError(error);
return null;
}
}
/**
* Returns the Bearer token, searches in headers and query.
* @param req The Request object from Express.
*/
public extractBearer(req: Request): string {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
}
if (req.query && req.query.token) {
return req.query.token as string;
}
return '0000000000';
}
}

70
src/class/Server.ts Normal file
View File

@ -0,0 +1,70 @@
/* eslint-disable no-useless-return */
import express from 'express';
import bodyParser from 'body-parser';
import helmet from 'helmet';
import fs from 'fs-extra';
import { Client } from '..';
import { Security } from '../api';
import { Collection, Route } from '.';
export default class Server {
public routes: Collection<Route>
public client: Client;
public security: Security;
public app: express.Express;
public options: { port: number }
constructor(client: Client, options?: { port: number }) {
this.options = options;
this.routes = new Collection();
this.client = client;
this.security = new Security(this.client);
this.app = express();
this.connect();
this.loadRoutes();
}
private async loadRoutes(): Promise<void> {
const routes = await fs.readdir(`${__dirname}/routes`);
routes.forEach(async (routeFile) => {
if (routeFile === 'index.js') return;
try {
// eslint-disable-next-line new-cap
const route: Route = new (require(`${__dirname}/routes/${routeFile}`).default)(this);
if (route.conf.deprecated === true) {
route.deprecated();
} else if (route.conf.maintenance === true) {
route.maintenance();
} else {
route.bind();
}
this.routes.set(route.conf.path, route);
this.app.use(route.conf.path, route.router);
this.client.signale.success(`Successfully loaded route ${route.conf.path}`);
} catch (error) {
this.client.util.handleError(error);
}
});
}
private connect(): void {
this.app.set('trust proxy', 'loopback');
this.app.use(helmet({
hsts: false,
hidePoweredBy: false,
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
},
},
}));
this.app.use(bodyParser.json());
this.app.listen(this.options.port, () => {
this.client.signale.success(`API Server listening on port ${this.options.port}`);
});
}
}

View File

@ -1,6 +1,7 @@
export { default as AccountUtil } from './AccountUtil'; export { default as AccountUtil } from './AccountUtil';
export { default as Client } from './Client';
export { default as Collection } from './Collection';
export { default as Command } from './Command'; export { default as Command } from './Command';
export { default as RichEmbed } from './RichEmbed'; export { default as RichEmbed } from './RichEmbed';
export { default as Util } from './Util';
export { default as Collection } from './Collection';
export { default as Route } from './Route'; export { default as Route } from './Route';
export { default as Util } from './Util';