1
0
Fork 0
Bsian 2020-03-29 22:41:28 +01:00
commit e342734317
No known key found for this signature in database
GPG Key ID: 097FB9A291026091
12 changed files with 197 additions and 77 deletions

View File

@ -1,3 +1,4 @@
import { randomBytes } from 'crypto';
import { AccountInterface } from '../models';
import { Client } from '..';
@ -22,20 +23,22 @@ export default class AccountUtil {
let passHash = await this.client.util.createHash(tempPass); passHash = passHash.replace(/[$]/g, '\\$').replace('\n', '');
const acctName = this.client.users.get(data.userID).username.replace(/[!@#$%^&*(),.?":{}|<>]/g, '-').replace(/\s/g, '-');
const etcPasswd = `${acctName},${data.userID},,`;
const code = randomBytes(3).toString('hex').toUpperCase();
const accountInterface = await this.client.util.createAccount(passHash, etcPasswd, data.username, data.userID, data.emailAddress, moderator);
const accountInterface = await this.client.util.createAccount(passHash, etcPasswd, data.username, data.userID, data.emailAddress, moderator, code);
await this.client.util.createModerationLog(data.userID, moderatorMember, 0);
this.client.util.transport.sendMail({
to: data.emailAddress,
from: 'Library of Code sp-us | Cloud Services <help@libraryofcode.org>',
subject: 'Your account has been created',
subject: 'Your account has been created!',
html: `
<body>
<style>* {font-family: 'Calibri';}</style>
<h1>Library of Code | Cloud Services</h1>
<h2>Your Cloud Account has been created, welcome! Please see below for some details regarding your account and our services</h2>
<p><b>Username:</b> ${data.username}</p>
<p><b>Support Key:</b> ${code} || <i>You may be asked for this support key when contacting Library of Code, please keep the code in a safe area.</i></p>
<p><b>SSH Login:</b> <pre><code style="font-family: Courier;">ssh ${data.username}@cloud.libraryofcode.org</code></pre>
<h2>Useful information</h2>
<h3>How to log in:</h3>
@ -61,13 +64,15 @@ export default class AccountUtil {
`,
});
this.client.guilds.get('446067825673633794').members.get(data.userID).addRole('546457886440685578');
const dmChannel = await this.client.getDMChannel(data.userID).catch();
dmChannel.createMessage('<:loc:607695848612167700> **Thank you for creating an account with us!** <:loc:607695848612167700>\n'
+ `Please log into your account by running \`ssh ${data.username}@cloud.libraryofcode.org\` in your terminal, then use the password \`${tempPass}\` to log in.\n`
+ `You will be asked to change your password, \`(current) UNIX password\` is \`${tempPass}\`, then create a password that is at least 12 characters long, with at least one number, special character, and an uppercase letter\n`
+ 'Bear in mind that when you enter your password, it will be blank, so be careful not to type in your password incorrectly.\n'
+ 'You may now return to Modmail, and continue setting up your account from there.\n\n'
+ 'An email containing some useful information has also been sent').catch();
+ 'An email containing some useful information has also been sent.\n'
+ `Your support key is \`${code}\`. Pin this message, you may need this key to contact Library of Code in the future.`).catch();
return { account: accountInterface, tempPass };
}
}

View File

@ -147,12 +147,12 @@ export default class Util {
return tempPass;
}
public async createAccount(hash: string, etcPasswd: string, username: string, userID: string, emailAddress: string, moderatorID: string): Promise<AccountInterface> {
public async createAccount(hash: string, etcPasswd: string, username: string, userID: string, emailAddress: string, moderatorID: string, code: string): Promise<AccountInterface> {
await this.exec(`useradd -m -p ${hash} -c ${etcPasswd} -s /bin/bash ${username}`);
await this.exec(`chage -d0 ${username}`);
const account = new this.client.db.Account({
username, userID, emailAddress, createdBy: moderatorID, createdAt: new Date(), locked: false, tier: 1, ssInit: false, homepath: `/home/${username}`,
username, userID, emailAddress, createdBy: moderatorID, createdAt: new Date(), locked: false, tier: 1, supportKey: code, ssInit: false, homepath: `/home/${username}`,
});
return account.save();
}

View File

@ -24,17 +24,17 @@ export default class CreateAccount extends Command {
try {
if (message.channel instanceof PrivateChannel || message.channel instanceof GroupChannel) return message; // Stop TS being gay
if (!args[2]) return this.client.commands.get('help').run(message, [this.name]);
if (!message.channel.guild.members.has(args[0])) return message.channel.createMessage(`${this.client.stores.emojis.error} ***User not found***`);
if (message.channel.guild.members.get(args[0]).bot) return message.channel.createMessage(`${this.client.stores.emojis.error} ***I cannot create accounts for bots***`);
if (!message.channel.guild.members.has(args[0])) return message.channel.createMessage(`${this.client.stores.emojis.error} ***User not found.***`);
if (message.channel.guild.members.get(args[0]).bot) return message.channel.createMessage(`${this.client.stores.emojis.error} ***I cannot create accounts for bots.***`);
const checkUser = await this.client.db.Account.findOne({ userID: args[0] });
if (checkUser) return message.channel.createMessage(`${this.client.stores.emojis.error} ***<@${args[0]}> already has an account***`);
if (checkUser) return message.channel.createMessage(`${this.client.stores.emojis.error} ***<@${args[0]}> already has an account.***`);
const checkEmail = await this.client.db.Account.findOne({ emailAddress: args[1] });
if (checkEmail) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account already exists with this email address***`);
if (checkEmail) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account already exists with this email address.***`);
const checkAccount = await this.client.db.Account.findOne({ username: args[2] });
if (checkAccount) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account already exists with this username***`);
if (checkAccount) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account already exists with this username.***`);
if (!this.client.util.isValidEmail(args[1])) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid email address supplied***`);
if (!/^[a-z][-a-z0-9]*$/.test(args[2])) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid username supplied***`);
if (!this.client.util.isValidEmail(args[1])) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid email address supplied.***`);
if (!/^[a-z][-a-z0-9]*$/.test(args[2])) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid username supplied.***`);
const confirmation = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Creating account...***`);
const data = await this.client.util.accounts.createAccount({ userID: args[0], username: args[2], emailAddress: args[1] }, message.author.id);

43
src/commands/emailcode.ts Normal file
View File

@ -0,0 +1,43 @@
/* eslint-disable consistent-return */
import { randomBytes } from 'crypto';
import { Message } from 'eris';
import { Client } from '..';
import { Command, RichEmbed } from '../class';
export default class EmailCode extends Command {
constructor(client: Client) {
super(client);
this.name = 'emailcode';
this.description = 'Sends a code to an email address to use for address verification.';
this.usage = `${this.client.config.prefix}emailcode <email address>`;
this.permissions = { roles: ['446104438969466890'] };
this.aliases = ['code'];
this.enabled = true;
}
public async run(message: Message, args: string[]) {
try {
const code = randomBytes(5).toString('hex');
if (!this.client.util.isValidEmail(args[0])) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid email address supplied.***`);
this.client.util.transport.sendMail({
to: args[0],
from: 'Library of Code sp-us | Cloud Services <help@libraryofcode.org>',
subject: 'Email Verification Code',
html: `
<body>
<style>* {font-family: 'Calibri';}</style>
<h1>Library of Code | Cloud Services</h1>
<p>Please provide the code provided below to the Staff member working with you on account creation.</p>
<h2>${code}</h2>
<h3>Want to support us?</h3>
<p>You can support us on Patreon! Head to <a target="_blank" href="https://www.patreon.com/libraryofcode">our Patreon page</a> and feel free to donate as much or as little as you want!<br>Donating $5 or more will grant you Tier 3, which means we will manage your account at your request, longer certificates, increased Tier limits as well as some roles in the server!</p>
<b><i>Library of Code sp-us | Support Team</i></b>
</body>
`,
});
message.channel.createMessage(`***${this.client.stores.emojis.success} Code: \`${code}\` | Email Address: ${args[0]}***`);
} catch (error) {
await this.client.util.handleError(error, message, this);
}
}
}

View File

@ -4,6 +4,7 @@ export { default as createaccount } from './createaccount';
export { default as cwg } from './cwg';
export { default as deleteaccount } from './deleteaccount';
export { default as disk } from './disk';
export { default as emailcode } from './emailcode';
export { default as eval } from './eval';
export { default as exec } from './exec';
export { default as help } from './help';
@ -19,6 +20,7 @@ export { default as resetpassword } from './resetpassword';
export { default as restart } from './restart';
export { default as securesign } from './securesign';
export { default as sysinfo } from './sysinfo';
export { default as tier } from './tier';
export { default as unban } from './unban';
export { default as unlock } from './unlock';
export { default as warn } from './warn';

41
src/commands/tier.ts Normal file
View File

@ -0,0 +1,41 @@
/* eslint-disable consistent-return */
import { Message } from 'eris';
import { Client } from '..';
import { Command, RichEmbed } from '../class';
export default class Tier extends Command {
constructor(client: Client) {
super(client);
this.name = 'tier';
this.description = 'Changes the tier level for an account.';
this.usage = `${this.client.config.prefix}tier <username | user ID> <1 | 2 | 3>`;
this.permissions = { roles: ['446104438969466890'] };
this.enabled = true;
}
public async run(message: Message, args: string[]) {
try {
if (!args.length) return this.client.commands.get('help').run(message, [this.name]);
const edit = await message.channel.createMessage(`***${this.client.stores.emojis.loading} Editing tier...***`);
const account = await this.client.db.Account.findOne({ $or: [{ username: args[0] }, { userID: args[0].replace(/[<@!>]/gi, '') }] });
if (!account) return edit.edit(`***${this.client.stores.emojis.error} Cannot find user.***`);
if (account.root) return edit.edit(`***${this.client.stores.emojis.error} Permission denied.***`);
if (Number.isNaN(Number(args[1]))) return edit.edit(`***${this.client.stores.emojis.error} The tier you provided is not a valid number. It should be between 1 and 3.***`);
if (Number(args[1]) > 3 || Number(args[1]) < 1) return edit.edit(`***${this.client.stores.emojis.error} You can only choose a Tier between 1 and 3.***`);
message.delete();
await account.updateOne({ $set: { tier: Number(args[1]) } });
message.channel.createMessage(`***${this.client.stores.emojis.success} Tier for ${account.username} has been changed to ${args[1]}.***`);
const embed = new RichEmbed();
embed.setTitle('Cloud Account | Tier Change');
embed.setColor('#0099ff');
embed.addField('User', `${account.username} | <@${account.userID}>`, true);
embed.addField('Moderator', `<@${message.author.id}>`, true);
embed.addField('Old Tier -> New Tier', `${account.tier} -> ${args[1]}`, true);
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setTimestamp();
this.client.createMessage('580950455581147146', { embed }); this.client.getDMChannel(account.userID).then((channel) => channel.createMessage({ embed })).catch();
} catch (error) {
await this.client.util.handleError(error, message, this);
}
}
}

View File

@ -39,6 +39,7 @@ export default class Whois extends Command {
embed.addField('ID', account.userID, true);
embed.addField('Email Address', account.emailAddress, true);
embed.addField('Tier', String(account.tier), true);
embed.addField('Support Key', account.supportKey, true);
embed.addField('Created By', `<@${this.client.users.get(account.createdBy).id}>`, true);
embed.addField('Created At', moment(account.createdAt).format('dddd, MMMM Do YYYY, h:mm:ss A'), true);
const cpuUsage = await this.client.util.exec(`top -b -n 1 -u ${account.username} | awk 'NR>7 { sum += $9; } END { print sum; }'`);

View File

@ -1,5 +1,6 @@
/* eslint-disable no-await-in-loop */
import { Client } from '..';
import { RichEmbed } from '../class';
export default function checkStaffStatus(client: Client) {
setInterval(async () => {
@ -26,8 +27,17 @@ export default function checkStaffStatus(client: Client) {
await client.db.Account.updateOne({ username: acc.username }, { $set: { 'permissions.staff': false } });
}
if (acc.permissions.staff && acc.tier > 3) {
if (acc.permissions.staff && acc.tier < 3) {
await client.db.Account.updateOne({ username: acc.username }, { $set: { tier: 3 } });
const embed = new RichEmbed();
embed.setTitle('Cloud Account | Tier Change');
embed.setColor('#0099ff');
embed.addField('User', `${acc.username} | <@${acc.userID}>`, true);
embed.addField('Moderator', `<@${client.user.id}>`, true);
embed.addField('Old Tier -> New Tier', `${acc.tier} -> 3`, true);
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setTimestamp();
this.client.createMessage('580950455581147146', { embed });
client.getDMChannel(acc.userID).then((chan) => {
chan.createMessage('***Your account has automatically been upgraded to Tier 3 since you are a Staff member.***');
});

View File

@ -1,10 +1,11 @@
/* eslint-disable no-continue */
/* eslint-disable no-await-in-loop */
import { Client } from '..';
import { RichEmbed } from '../class';
const channelID = '691824484230889546';
export const memoryLimits = {
const memoryLimits = {
TIER_1_SOFT: 200,
TIER_1_HARD: 350,
TIER_2_SOFT: 350,
@ -15,61 +16,76 @@ export const memoryLimits = {
export default function memory(client: Client) {
setInterval(async () => {
const accounts = await client.db.Account.find();
for (const acc of accounts) {
if (acc.root) return;
// memory in bytes
const mem = Number(await client.util.exec(`memory ${acc.username}`)) * 1000;
// memory in megabytes
const memoryConversion = mem / 1024 / 1024;
let userLimits: { soft: number, hard: number };
if (acc.tier === 1) {
userLimits = { soft: memoryLimits.TIER_1_SOFT, hard: memoryLimits.TIER_1_HARD };
} else if (acc.tier === 2) {
userLimits = { soft: memoryLimits.TIER_2_SOFT, hard: memoryLimits.TIER_2_HARD };
} else if (acc.tier === 3) {
userLimits = { soft: memoryLimits.TIER_3_SOFT, hard: memoryLimits.TIER_3_HARD };
}
/* if the user has exceeded their soft memory limit, which is the one described in the
resource limit guidelines, we'll inform staff.
*/
if (acc.tier === 1 && memoryConversion >= userLimits.soft) {
const embed = new RichEmbed();
if (client.users.get(acc.userID)) embed.setThumbnail(client.users.get(acc.userID).avatarURL);
embed.addField('User', `${acc.username} | <@${acc.userID}> | ${acc.userID}`, true);
embed.addField('Tier', String(acc.tier), true);
embed.addField('Memory Usage', `${String(memoryConversion)} MB`, true);
embed.addField('Memory Limit', `${String(userLimits.soft)} MB`, true);
embed.setFooter(client.user.username, client.user.avatarURL);
embed.setTimestamp();
// if they exceed the hard limit, we'll kill all of their processes.
if (memoryConversion >= userLimits.hard) {
client.util.exec(`killall -9 -u ${acc.username}`);
embed.setTitle('Resource Enforcement Notification');
embed.setDescription('Someone has reached the (hard) resource limit for their tier on RAM. The system has automatically killed all of their processes.');
client.util.createModerationLog(acc.userID, client.guilds.get('446067825673633794').members.get(client.user.id), 1, '[AUTO] Exceeded resource limit for RAM.');
client.util.transport.sendMail({
to: acc.emailAddress,
from: 'Library of Code sp-us | Cloud Services <help@libraryofcode.org>',
subject: 'Your account has been warned',
html: `
<h1>Library of Code sp-us | Cloud Services</h1>
<p>Your account has received an official warning from a Moderator. Please get the underlying issue resolved to avoid <i>possible</i> moderative action.</p>
<p><strong>Reason:</strong> [AUTO] Exceeded resource limit for RAM.</p>
<p><strong>Moderator:</strong> ${client.user.username}</p>
<b><i>Library of Code sp-us | Support Team</i></b>
`,
});
} else {
embed.setTitle('Resource Limit Notification');
embed.setDescription('Someone has reached the (soft) resource limit for their tier on RAM.');
try {
const accounts = await client.db.Account.find();
for (const acc of accounts) {
if (acc.root === true) continue;
// memory in bytes
const mem = Number(await client.util.exec(`memory ${acc.username}`)) * 1000;
// memory in megabytes
const memoryConversion = mem / 1024 / 1024;
const userLimits: { soft?: number, hard?: number } = {};
switch (acc.tier) {
case 1:
userLimits.soft = memoryLimits.TIER_1_SOFT;
userLimits.hard = memoryLimits.TIER_1_HARD;
break;
case 2:
userLimits.soft = memoryLimits.TIER_2_SOFT;
userLimits.hard = memoryLimits.TIER_2_HARD;
break;
case 3:
userLimits.soft = memoryLimits.TIER_3_SOFT;
userLimits.hard = memoryLimits.TIER_3_HARD;
break;
default:
break;
}
/* if the user has exceeded their soft memory limit, which is the one described in the
resource limit guidelines, we'll inform staff.
*/
if (memoryConversion >= userLimits.soft) {
client.signale.info(`RAM Soft Limit Reached | ${acc.username} | ${memoryConversion}/${userLimits.soft} MB`);
const embed = new RichEmbed();
if (client.users.get(acc.userID)) embed.setThumbnail(client.users.get(acc.userID).avatarURL);
embed.addField('User', `${acc.username} | <@${acc.userID}> | ${acc.userID}`, true);
embed.addField('Tier', String(acc.tier), true);
embed.addField('Memory Usage', `${String(memoryConversion)} MB`, true);
embed.addField('Memory Limit', `${String(userLimits.soft)} MB`, true);
embed.setFooter(client.user.username, client.user.avatarURL);
embed.setTimestamp();
// if they exceed the hard limit, we'll kill all of their processes.
if (memoryConversion >= userLimits.hard) {
client.signale.info(`RAM Hard Limit Reached | ${acc.username} | ${memoryConversion}/${userLimits.hard} MB`);
client.util.exec(`killall -9 -u ${acc.username}`);
embed.setTitle('Resource Enforcement Notification');
embed.setDescription('Someone has reached the (hard) resource limit for their tier on RAM. The system has automatically killed all of their processes.');
client.util.createModerationLog(acc.userID, client.guilds.get('446067825673633794').members.get(client.user.id), 1, '[AUTO] Exceeded resource limit for RAM.');
client.util.transport.sendMail({
to: acc.emailAddress,
from: 'Library of Code sp-us | Cloud Services <help@libraryofcode.org>',
subject: 'Your account has been warned',
html: `
<h1>Library of Code sp-us | Cloud Services</h1>
<p>Your account has received an official warning from a Moderator. Please get the underlying issue resolved to avoid <i>possible</i> moderative action.</p>
<p><strong>Reason:</strong> [AUTO] Exceeded resource limit for RAM.</p>
<p><strong>Moderator:</strong> ${client.user.username}</p>
<b><i>Library of Code sp-us | Support Team</i></b>
`,
});
} else {
embed.setTitle('Resource Limit Notification');
embed.setDescription('Someone has reached the (soft) resource limit for their tier on RAM.');
}
// @ts-ignore
client.createMessage(channelID, { embed });
}
// @ts-ignore
client.createMessage(channelID, { embed });
}
} catch (err) {
client.util.handleError(err);
}
}, 300000);
}, 60000);
}

View File

@ -8,7 +8,8 @@ export interface AccountInterface extends Document {
createdBy: string,
createdAt: Date,
locked: boolean,
tier: number;
tier: number,
supportKey: string,
permissions: {
staff: boolean,
sheriff: boolean,
@ -29,6 +30,7 @@ const Account: Schema = new Schema({
createdAt: Date,
locked: Boolean,
tier: Number,
supportKey: String,
permissions: {
staff: Boolean,
sheriff: Boolean,

View File

@ -24,19 +24,19 @@ server {
limit_req zone=one burst=15;
location / {
proxy_set_header Host $host;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://localhost:[PORT];
proxy_pass http://localhost:[PORT];
proxy_read_timeout 90;
proxy_read_timeout 90;
proxy_redirect http://localhost:[PORT] https://[DOMAIN];
proxy_redirect http://localhost:[PORT] https://[DOMAIN];
}
}

View File

@ -2,7 +2,7 @@
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": ["ES2019.Object", "ES2020.Promise"], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */