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 uuid from 'uuid/v4';
import moment from 'moment';
import fs from 'fs';
import { Client } from '..';
import { Command, RichEmbed } from '.';
import { ModerationInterface, AccountInterface } from '../models';
@ -195,9 +196,13 @@ export default class Util {
/**
* @param type `0` - Create
*
* `1` - Warn
*
* `2` - Lock
*
* `3` - Unlock
*
* `4` - Delete
*/
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);
}
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); }
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 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();
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}`;
embed.setDescription(description);
// @ts-ignore

View File

@ -14,6 +14,7 @@ export default class Pull extends Command {
public async run(message: Message) {
try {
this.client.updating = true;
const updateMessage = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Fetching latest commit...***\n\`\`\`sh\ngit pull\n\`\`\``);
let pull: string;
@ -22,16 +23,19 @@ export default class Pull extends Command {
} 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***`)
.replace(/```$/, `${error.message}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage);
}
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***`)
.replace(/```$/, `${pull}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage);
}
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***`)
.replace(/```$/, `${pull}\n\`\`\``);
this.client.updating = false;
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...***`)
@ -43,6 +47,7 @@ export default class Pull extends Command {
try {
install = await this.client.util.exec('yarn install');
} 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***`)
.replace(/```$/, `${error.message}\n\`\`\``);
return updateMessage.edit(updatedMessage);
@ -59,6 +64,7 @@ export default class Pull extends Command {
} else {
const updatedMessage = passedPull.content.replace(`${this.client.stores.emojis.loading} ***Reinstalling dependencies...***`, `${this.client.stores.emojis.error} ***Unexpected yarn install output***`)
.replace(/```$/, `${pull}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage);
}
@ -68,13 +74,15 @@ export default class Pull extends Command {
} catch (error) {
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\`\`\``);
this.client.updating = false;
return updateMessage.edit(updatedMessage);
}
const finalMessage = updatedPackages.content.replace(`${this.client.stores.emojis.loading} ***Rebuilding files...***`, `${this.client.stores.emojis.success} ***Files rebuilt***`)
.replace(/```$/, `${build}\n\`\`\``);
this.client.updating = false;
return updateMessage.edit(finalMessage);
} catch (error) {
this.client.updating = false;
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 Account from './securesign_account';
import ActivateKey from './securesign_activatekey';
import CreateCrt from './securesign_createcrt';
export default class SecureSign extends Command {
constructor(client: Client) {
@ -13,7 +14,7 @@ export default class SecureSign extends Command {
this.description = 'Runs SecureSign CLI commands';
this.usage = `Run ${this.client.config.prefix}${this.name} [subcommand] for usage information`;
this.aliases = ['ss'];
this.subcmds = [Build, Init, Account, ActivateKey];
this.subcmds = [Build, Init, Account, ActivateKey, CreateCrt];
this.enabled = true;
}

View File

@ -36,7 +36,7 @@ export default class SecureSign_Account extends Command {
embed.setTitle(title);
embed.setDescription(description);
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
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.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 hash = this.client.util.getAcctHash(account.username);
try {
await axios({
method: 'POST',
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] }),
});
} catch (error) {
const { code } = error.response.data;
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();
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]}'`);
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***`);
} catch (error) {
return this.client.util.handleError(error, message, this);

View File

@ -7,8 +7,9 @@ export default function checkSS(client: Client) {
try {
const accounts = await client.db.Account.find();
const hashes = accounts.filter((h) => h.hash);
for (const { hash, userID } of hashes) {
for (const { userID, username } of hashes) {
try {
const hash = client.util.getAcctHash(username);
await axios({
method: 'get',
url: 'https://api.securesign.org/account/details',
@ -17,7 +18,7 @@ export default function checkSS(client: Client) {
} catch (error) {
const { status } = error.response;
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();
}
}

View File

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