Merge branch 'master' of gitlab.libraryofcode.org:engineering/cloudservices

merge-requests/1/merge
Matthew 2019-11-19 18:27:05 -05:00
commit e934d6d398
No known key found for this signature in database
GPG Key ID: 766BE43AE75F7559
10 changed files with 99 additions and 12 deletions

View File

@ -5,6 +5,7 @@ import nodemailer from 'nodemailer';
import { Message, PrivateChannel, Member, User } from 'eris'; import { Message, PrivateChannel, Member, User } from 'eris';
import uuid from 'uuid/v4'; import uuid from 'uuid/v4';
import moment from 'moment'; import moment from 'moment';
import fs from 'fs';
import { Client } from '..'; import { Client } from '..';
import { Command, RichEmbed } from '.'; import { Command, RichEmbed } from '.';
import { ModerationInterface, AccountInterface } from '../models'; import { ModerationInterface, AccountInterface } from '../models';
@ -195,9 +196,13 @@ export default class Util {
/** /**
* @param type `0` - Create * @param type `0` - Create
*
* `1` - Warn * `1` - Warn
*
* `2` - Lock * `2` - Lock
*
* `3` - Unlock * `3` - Unlock
*
* `4` - Delete * `4` - Delete
*/ */
public async createModerationLog(user: string, moderator: Member|User, type: number, reason?: string, duration?: number): Promise<ModerationInterface> { public async createModerationLog(user: string, moderator: Member|User, type: number, reason?: string, duration?: number): Promise<ModerationInterface> {
@ -251,4 +256,12 @@ export default class Util {
return Promise.resolve(log); return Promise.resolve(log);
} }
public getAcctHash(username: string) {
try {
return fs.readFileSync(`/home/${username}/.securesign/auth`).toString();
} catch (error) {
return null;
}
}
} }

View File

@ -53,10 +53,10 @@ export default class Help extends Command {
if (allowedUsers) { allowedUsers = `**Users:** ${allowedUsers}`; perms.push(allowedUsers); } if (allowedUsers) { allowedUsers = `**Users:** ${allowedUsers}`; perms.push(allowedUsers); }
const displayedPerms = perms.length ? `\n**Permissions:**\n${perms.join('\n')}` : ''; const displayedPerms = perms.length ? `\n**Permissions:**\n${perms.join('\n')}` : '';
const aliases = cmd.aliases.length ? `\n**Aliases:** ${cmd.aliases.map((alias) => `${this.client.config.prefix}${cmd.parentName}${alias}`).join(', ')}` : ''; const aliases = cmd.aliases.length ? `\n**Aliases:** ${cmd.aliases.map((alias) => `${this.client.config.prefix}${cmd.parentName}${alias}`).join(', ')}` : '';
const subcommands = cmd.subcommands.size ? `\n**Subcommands:** ${cmd.subcommands.map((s) => s.parentName || `${cmd.name} ${s.name}`).join(', ')}` : ''; const subcommands = cmd.subcommands.size ? `\n**Subcommands:** ${cmd.subcommands.map((s) => `${cmd.name} ${s.name}`).join(', ')}` : '';
const embed = new RichEmbed(); const embed = new RichEmbed();
embed.setTimestamp(); embed.setFooter(`Requested by ${message.author.username}#${message.author.discriminator}`, message.author.avatarURL); embed.setTimestamp(); embed.setFooter(`Requested by ${message.author.username}#${message.author.discriminator}`, message.author.avatarURL);
embed.setTitle(`${this.client.config.prefix}${cmd.parentName}${cmd.name}`); embed.setAuthor(`${this.client.user.username}#${this.client.user.discriminator}`, this.client.user.avatarURL); embed.setTitle(`${this.client.config.prefix}${cmd.parentName ? `${cmd.parentName}${cmd.name}` : cmd.name}`); embed.setAuthor(`${this.client.user.username}#${this.client.user.discriminator}`, this.client.user.avatarURL);
const description = `**Description**: ${cmd.description}\n**Usage:** ${cmd.usage}${aliases}${displayedPerms}${subcommands}`; const description = `**Description**: ${cmd.description}\n**Usage:** ${cmd.usage}${aliases}${displayedPerms}${subcommands}`;
embed.setDescription(description); embed.setDescription(description);
// @ts-ignore // @ts-ignore

View File

@ -14,6 +14,7 @@ export default class Pull extends Command {
public async run(message: Message) { public async run(message: Message) {
try { try {
this.client.updating = true;
const updateMessage = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Fetching latest commit...***\n\`\`\`sh\ngit pull\n\`\`\``); const updateMessage = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Fetching latest commit...***\n\`\`\`sh\ngit pull\n\`\`\``);
let pull: string; let pull: string;
@ -22,16 +23,19 @@ export default class Pull extends Command {
} catch (error) { } catch (error) {
const updatedMessage = updateMessage.content.replace(`${this.client.stores.emojis.loading} ***Fetching latest commit...***`, `${this.client.stores.emojis.error} ***Could not fetch latest commit***`) const updatedMessage = updateMessage.content.replace(`${this.client.stores.emojis.loading} ***Fetching latest commit...***`, `${this.client.stores.emojis.error} ***Could not fetch latest commit***`)
.replace(/```$/, `${error.message}\n\`\`\``); .replace(/```$/, `${error.message}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage); return updateMessage.edit(updatedMessage);
} }
if (pull.includes('Already up to date')) { if (pull.includes('Already up to date')) {
const updatedMessage = updateMessage.content.replace(`${this.client.stores.emojis.loading} ***Fetching latest commit...***`, `${this.client.stores.emojis.success} ***No updates available***`) const updatedMessage = updateMessage.content.replace(`${this.client.stores.emojis.loading} ***Fetching latest commit...***`, `${this.client.stores.emojis.success} ***No updates available***`)
.replace(/```$/, `${pull}\n\`\`\``); .replace(/```$/, `${pull}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage); return updateMessage.edit(updatedMessage);
} }
if (!pull.includes('origin/master')) { if (!pull.includes('origin/master')) {
const updatedMessage = updateMessage.content.replace(`${this.client.stores.emojis.loading} ***Fetching latest commit...***`, `${this.client.stores.emojis.error} ***Unexpected git output***`) const updatedMessage = updateMessage.content.replace(`${this.client.stores.emojis.loading} ***Fetching latest commit...***`, `${this.client.stores.emojis.error} ***Unexpected git output***`)
.replace(/```$/, `${pull}\n\`\`\``); .replace(/```$/, `${pull}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage); return updateMessage.edit(updatedMessage);
} }
const continueMessage = updateMessage.content.replace(`${this.client.stores.emojis.loading} ***Fetching latest commit...***`, `${this.client.stores.emojis.success} ***Pulled latest commit***\n${this.client.stores.emojis.loading} ***Reinstalling dependencies...***`) const continueMessage = updateMessage.content.replace(`${this.client.stores.emojis.loading} ***Fetching latest commit...***`, `${this.client.stores.emojis.success} ***Pulled latest commit***\n${this.client.stores.emojis.loading} ***Reinstalling dependencies...***`)
@ -43,6 +47,7 @@ export default class Pull extends Command {
try { try {
install = await this.client.util.exec('yarn install'); install = await this.client.util.exec('yarn install');
} catch (error) { } catch (error) {
this.client.updating = false;
const updatedMessage = passedPull.content.replace(`${this.client.stores.emojis.loading} ***Reinstalling dependencies...***`, `${this.client.stores.emojis.error} ***Failed to reinstall dependencies***`) const updatedMessage = passedPull.content.replace(`${this.client.stores.emojis.loading} ***Reinstalling dependencies...***`, `${this.client.stores.emojis.error} ***Failed to reinstall dependencies***`)
.replace(/```$/, `${error.message}\n\`\`\``); .replace(/```$/, `${error.message}\n\`\`\``);
return updateMessage.edit(updatedMessage); return updateMessage.edit(updatedMessage);
@ -59,6 +64,7 @@ export default class Pull extends Command {
} else { } else {
const updatedMessage = passedPull.content.replace(`${this.client.stores.emojis.loading} ***Reinstalling dependencies...***`, `${this.client.stores.emojis.error} ***Unexpected yarn install output***`) const updatedMessage = passedPull.content.replace(`${this.client.stores.emojis.loading} ***Reinstalling dependencies...***`, `${this.client.stores.emojis.error} ***Unexpected yarn install output***`)
.replace(/```$/, `${pull}\n\`\`\``); .replace(/```$/, `${pull}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage); return updateMessage.edit(updatedMessage);
} }
@ -68,13 +74,15 @@ export default class Pull extends Command {
} catch (error) { } catch (error) {
const updatedMessage = updatedPackages.content.replace(`${this.client.stores.emojis.loading} ***Rebuilding files...***`, `${this.client.stores.emojis.error} ***Failed to rebuild files***`) const updatedMessage = updatedPackages.content.replace(`${this.client.stores.emojis.loading} ***Rebuilding files...***`, `${this.client.stores.emojis.error} ***Failed to rebuild files***`)
.replace(/```$/, `${error.message}\n\`\`\``); .replace(/```$/, `${error.message}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage); return updateMessage.edit(updatedMessage);
} }
const finalMessage = updatedPackages.content.replace(`${this.client.stores.emojis.loading} ***Rebuilding files...***`, `${this.client.stores.emojis.success} ***Files rebuilt***`) const finalMessage = updatedPackages.content.replace(`${this.client.stores.emojis.loading} ***Rebuilding files...***`, `${this.client.stores.emojis.success} ***Files rebuilt***`)
.replace(/```$/, `${build}\n\`\`\``); .replace(/```$/, `${build}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(finalMessage); return updateMessage.edit(finalMessage);
} catch (error) { } catch (error) {
this.client.updating = false;
return this.client.util.handleError(error, message, this); return this.client.util.handleError(error, message, this);
} }
} }

View File

@ -5,6 +5,7 @@ import Build from './securesign_build';
import Init from './securesign_init'; import Init from './securesign_init';
import Account from './securesign_account'; import Account from './securesign_account';
import ActivateKey from './securesign_activatekey'; import ActivateKey from './securesign_activatekey';
import CreateCrt from './securesign_createcrt';
export default class SecureSign extends Command { export default class SecureSign extends Command {
constructor(client: Client) { constructor(client: Client) {
@ -13,7 +14,7 @@ export default class SecureSign extends Command {
this.description = 'Runs SecureSign CLI commands'; this.description = 'Runs SecureSign CLI commands';
this.usage = `Run ${this.client.config.prefix}${this.name} [subcommand] for usage information`; this.usage = `Run ${this.client.config.prefix}${this.name} [subcommand] for usage information`;
this.aliases = ['ss']; this.aliases = ['ss'];
this.subcmds = [Build, Init, Account, ActivateKey]; this.subcmds = [Build, Init, Account, ActivateKey, CreateCrt];
this.enabled = true; this.enabled = true;
} }

View File

@ -36,7 +36,7 @@ export default class SecureSign_Account extends Command {
embed.setTitle(title); embed.setTitle(title);
embed.setDescription(description); embed.setDescription(description);
embed.setAuthor(this.client.user.username, this.client.user.avatarURL); embed.setAuthor(this.client.user.username, this.client.user.avatarURL);
embed.setFooter(`Requested by ${message.member.username}#${message.member.discriminator}`, message.member.avatarURL); embed.setFooter(`Requested by ${message.author.username}#${message.author.discriminator}`, message.author.avatarURL);
// @ts-ignore // @ts-ignore
return msg.edit({ content, embed }); return msg.edit({ content, embed });

View File

@ -19,17 +19,18 @@ export default class SecureSign_ActivateKey extends Command {
if (!account) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account not found***`); if (!account) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account not found***`);
if (!account.hash) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account not initialized***`); if (!account.hash) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account not initialized***`);
const msg = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Activating key...***`); const msg = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Activating key...***`);
const hash = this.client.util.getAcctHash(account.username);
try { try {
await axios({ await axios({
method: 'POST', method: 'POST',
url: 'https://api.securesign.org/account/keys/activation', url: 'https://api.securesign.org/account/keys/activation',
headers: { Authorization: account.hash, 'Content-Type': 'application/json' }, headers: { Authorization: hash, 'Content-Type': 'application/json' },
data: JSON.stringify({ key: args[0] }), data: JSON.stringify({ key: args[0] }),
}); });
} catch (error) { } catch (error) {
const { code } = error.response.data; const { code } = error.response.data;
if (code === 1001) { if (code === 1001) {
await this.client.db.Account.updateOne({ hash: account.hash }, { $set: { hash: null } }); await this.client.db.Account.updateOne({ userID: account.userID }, { $set: { hash: false } });
this.client.getDMChannel(account.userID).then((channel) => channel.createMessage('Your SecureSign password has been reset - please reinitialize your SecureSign account')).catch(); this.client.getDMChannel(account.userID).then((channel) => channel.createMessage('Your SecureSign password has been reset - please reinitialize your SecureSign account')).catch();
return msg.edit(`${this.client.stores.emojis.error} ***Authentication failed***`); return msg.edit(`${this.client.stores.emojis.error} ***Authentication failed***`);
} }

View File

@ -0,0 +1,63 @@
import { Message, PrivateChannel, TextChannel } from 'eris';
import axios from 'axios';
import { Client } from '..';
import { Command } from '../class';
export default class SecureSign_Init extends Command {
constructor(client: Client) {
super(client);
this.name = 'createcrt';
this.description = 'Creates a new certificate';
this.usage = `${this.client.config.prefix}securesign createcrt [-s sign] [-c class] [-m digest]\n\`sign\`: Sign type (ecc/rsa)\n\`class\`: Certificate Class (1/2/3)\n\`digest\`: SHA Digest (256/384/512)`;
this.enabled = true;
this.guildOnly = false;
}
public async run(message: Message, args: string[]) {
try {
const account = await this.client.db.Account.findOne({ userID: message.author.id });
if (!account) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account not found***`);
if (!account.hash) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account not initialized***`);
// @ts-ignore
const options: { s?: string, c?: string, m?: string } = args.length ? Object.fromEntries(` ${args.join(' ')}`.split(' -').filter((a) => a).map((a) => a.split(' '))) : {}; // eslint-disable-line
if (options.s && options.s.toLowerCase() !== 'ecc' && options.s.toLowerCase() !== 'rsa') return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid signing type, choose between \`ecc\` or \`rsa\``);
if (options.c && (!Number(options.c) || Number(options.c) < 1 || Number(options.c) > 3)) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid class selected, choose between Class \`1\`, \`2\` or \`3\``);
if (options.m && (!Number(options.m) || (options.m !== '256' && options.m !== '384' && options.m !== '512'))) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid SHA Digest selected, choose between \`256\`, \`384\` or \`512\``);
const msg = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Creating certificate...***`);
const hash = this.client.util.getAcctHash(account.username);
// Check if they can generate certificate
try {
const { data } = await axios({
method: 'GET',
url: 'https://api.securesign.org/account/details',
headers: { Authorization: hash, 'Content-Type': 'application/json' },
});
const { total, allowed } = data.message;
if (total >= allowed) return msg.edit(`${this.client.stores.emojis.error} ***Not enough certificate allowances - please ask a member of staff to increase this limit from ${total}***`);
if (Number(options.c) > data.message.class) return msg.edit(`${this.client.stores.emojis.error} ***Class too low, you are on a class ${data.message.class} account***`);
} catch (error) {
const { code } = error.response.data;
if (code === 1001) {
await this.client.db.Account.updateOne({ userID: account.userID }, { $set: { hash: false } });
this.client.getDMChannel(account.userID).then((channel) => channel.createMessage('Your SecureSign password has been reset - please reinitialize your SecureSign account')).catch();
return msg.edit(`${this.client.stores.emojis.error} ***Authentication failed***`);
}
throw error;
}
const execoptions = `${options.s ? ` -s ${options.s}` : ''}${options.c ? ` -c ${options.c}` : ''}${options.m ? ` -m ${options.m}` : ''}`;
const cmd = `sudo -H -u ${account.username} bash -c 'securesign-canary createcrt${execoptions}'`;
const exec = await this.client.util.exec(cmd);
if (!exec.replace(/^\s+|\s+$/g, '').endsWith('Successfully wrote certificate.')) throw new Error(`Certificate generation did not complete successfully:\n${cmd}`);
return msg.edit(`${this.client.stores.emojis.success} ***Successfully created certificate***`);
} catch (error) {
return this.client.util.handleError(error, message, this);
}
}
}

View File

@ -47,7 +47,7 @@ export default class SecureSign_Init extends Command {
} }
const init = await this.client.util.exec(`sudo -H -u ${account.username} bash -c 'securesign-canary init -a ${args[0]}'`); const init = await this.client.util.exec(`sudo -H -u ${account.username} bash -c 'securesign-canary init -a ${args[0]}'`);
if (!init.replace(/^\s+|\s+$/g, '').endsWith('Initialization sequence completed.')) throw new Error(`Account initialization did not complete successfully:\n${init}`); if (!init.replace(/^\s+|\s+$/g, '').endsWith('Initialization sequence completed.')) throw new Error(`Account initialization did not complete successfully:\n${init}`);
await this.client.db.Account.updateOne({ userID: message.author.id }, { $set: { hash: args[0] } }); await this.client.db.Account.updateOne({ userID: message.author.id }, { $set: { hash: true } });
return msg.edit(`${this.client.stores.emojis.success} ***Account initialized***`); return msg.edit(`${this.client.stores.emojis.success} ***Account initialized***`);
} catch (error) { } catch (error) {
return this.client.util.handleError(error, message, this); return this.client.util.handleError(error, message, this);

View File

@ -7,8 +7,9 @@ export default function checkSS(client: Client) {
try { try {
const accounts = await client.db.Account.find(); const accounts = await client.db.Account.find();
const hashes = accounts.filter((h) => h.hash); const hashes = accounts.filter((h) => h.hash);
for (const { hash, userID } of hashes) { for (const { userID, username } of hashes) {
try { try {
const hash = client.util.getAcctHash(username);
await axios({ await axios({
method: 'get', method: 'get',
url: 'https://api.securesign.org/account/details', url: 'https://api.securesign.org/account/details',
@ -17,7 +18,7 @@ export default function checkSS(client: Client) {
} catch (error) { } catch (error) {
const { status } = error.response; const { status } = error.response;
if (status === 400 || status === 401 || status === 403 || status === 404) { if (status === 400 || status === 401 || status === 403 || status === 404) {
await client.db.Account.updateOne({ hash }, { $set: { hash: null } }); await client.db.Account.updateOne({ userID }, { $set: { hash: false } });
client.getDMChannel(userID).then((channel) => channel.createMessage('Your SecureSign password has been reset - please reinitialize your SecureSign account')).catch(); client.getDMChannel(userID).then((channel) => channel.createMessage('Your SecureSign password has been reset - please reinitialize your SecureSign account')).catch();
} }
} }

View File

@ -15,7 +15,7 @@ export interface AccountInterface extends Document {
engineer: boolean engineer: boolean
}, },
root: boolean, root: boolean,
hash: string, hash: boolean,
salt: string, salt: string,
authTag: Buffer authTag: Buffer
} }
@ -35,7 +35,7 @@ const Account: Schema = new Schema({
engineer: Boolean, engineer: Boolean,
}, },
root: Boolean, root: Boolean,
hash: String, hash: Boolean,
salt: String, salt: String,
authTag: Buffer, authTag: Buffer,
}); });