2019-10-20 19:28:35 -04:00
import fs from 'fs-extra' ;
import axios from 'axios' ;
2019-10-28 20:24:56 -04:00
import moment from 'moment' ;
2019-10-20 19:28:35 -04:00
import x509 from '@ghaiklor/x509' ;
import { Message } from 'eris' ;
import { AccountInterface } from '../models' ;
import { Command , RichEmbed } from '../class' ;
2019-10-28 17:29:06 -04:00
import { Client } from '..' ;
2019-10-20 19:28:35 -04:00
export default class CWG extends Command {
constructor ( client : Client ) {
super ( client ) ;
this . name = 'cwg' ;
this . description = 'Manages aspects for the CWG.' ;
2019-10-28 21:13:14 -04:00
this . usage = ` ${ this . client . config . prefix } cwg create [User ID/Username] [Domain] [Port] <Path to x509 cert> <Path to x509 key> \ n ${ this . client . config . prefix } cwg data [Domain/Port] ` ;
2019-10-29 16:01:21 -04:00
this . permissions = { roles : [ '446104438969466890' ] } ;
2019-10-20 19:28:35 -04:00
this . enabled = true ;
}
public async run ( message : Message , args? : string [ ] ) {
2019-10-28 17:41:23 -04:00
try {
if ( ! args . length ) return this . client . commands . get ( 'help' ) . run ( message , [ this . name ] ) ;
/ *
2019-10-20 19:28:35 -04:00
args [ 1 ] should be the user ' s ID OR account username ; required
args [ 2 ] should be the domain ; required
args [ 3 ] should be the port ; required
args [ 4 ] should be the path to the x509 certificate ; not required
args [ 5 ] should be the path to the x509 key ; not required
* /
2019-10-28 17:41:23 -04:00
if ( args [ 0 ] === 'create' ) {
2019-10-28 17:45:31 -04:00
if ( ! args [ 3 ] ) return this . client . commands . get ( 'help' ) . run ( message , [ this . name ] ) ;
2019-10-28 17:41:23 -04:00
try {
2019-10-29 16:01:21 -04:00
if ( ! message . member . roles . includes ( '525441307037007902' ) ) return ; // eslint-disable-line
2019-10-29 20:20:00 -04:00
const edit = await message . channel . createMessage ( ` *** ${ this . client . stores . emojis . loading } Binding domain...*** ` ) ;
2019-10-28 20:24:56 -04:00
const account = await this . client . db . Account . findOne ( { $or : [ { account : args [ 1 ] } , { userID : args [ 1 ] } ] } ) ;
if ( ! account ) return edit . edit ( ` ${ this . client . stores . emojis . error } Cannot locate account, please try again. ` ) ;
if ( args [ 4 ] && ! args [ 5 ] ) return edit . edit ( ` ${ this . client . stores . emojis . error } x509 Certificate key required ` ) ;
2019-10-28 18:06:56 -04:00
let certs : { cert? : string , key? : string } ; if ( args [ 5 ] ) certs = { cert : args [ 4 ] , key : args [ 5 ] } ;
2019-10-28 20:24:56 -04:00
if ( await this . client . db . Domain . exists ( { domain : args [ 2 ] } ) ) return edit . edit ( ` *** ${ this . client . stores . emojis . error } This domain already exists.*** ` ) ;
if ( await this . client . db . Domain . exists ( { port : Number ( args [ 3 ] ) } ) ) return edit . edit ( ` *** ${ this . client . stores . emojis . error } This port is already binded to a domain.*** ` ) ;
2019-10-28 17:58:44 -04:00
const domain = await this . createDomain ( account , args [ 2 ] , Number ( args [ 3 ] ) , certs ) ;
2019-10-28 17:41:23 -04:00
const embed = new RichEmbed ( ) ;
embed . setTitle ( 'Domain Creation' ) ;
embed . setColor ( 3066993 ) ;
embed . addField ( 'Account Username' , account . username , true ) ;
embed . addField ( 'Account ID' , account . id , true ) ;
embed . addField ( 'Engineer' , ` <@ ${ message . author . id } > ` , true ) ;
embed . addField ( 'Domain' , domain . domain , true ) ;
embed . addField ( 'Port' , String ( domain . port ) , true ) ;
const cert = x509 . parseCert ( await fs . readFile ( domain . x509 . cert , { encoding : 'utf8' } ) ) ;
embed . addField ( 'Certificate Issuer' , cert . issuer . organizationName , true ) ;
embed . addField ( 'Certificate Subject' , cert . subject . commonName , true ) ;
embed . setFooter ( this . client . user . username , this . client . user . avatarURL ) ;
embed . setTimestamp ( new Date ( message . timestamp ) ) ;
2019-10-28 20:24:56 -04:00
message . delete ( ) ;
2019-10-29 19:37:27 -04:00
await this . client . util . exec ( 'systemctl reload nginx' ) ;
2019-10-28 20:24:56 -04:00
edit . edit ( ` *** ${ this . client . stores . emojis . success } Successfully binded ${ domain . domain } to port ${ domain . port } for ${ account . userID } .*** ` ) ;
2019-10-28 17:41:23 -04:00
// @ts-ignore
this . client . createMessage ( '580950455581147146' , { embed } ) ;
// @ts-ignore
this . client . getDMChannel ( account . userID ) . then ( ( r ) = > r . createMessage ( { embed } ) ) ;
await this . client . util . transport . sendMail ( {
to : account.emailAddress ,
from : 'Library of Code sp-us | Support Team <support@libraryofcode.org>' ,
subject : 'Your domain has been binded' ,
html : `
2019-10-20 20:14:04 -04:00
< h1 > Library of Code sp - us | Cloud Services < / h1 >
< p > Hello , this is an email informing you that a new domain under your account has been binded .
Information is below . < / p >
2019-10-26 14:32:56 -04:00
< b > Domain : < / b > $ { domain . domain }
< b > Port : < / b > $ { domain . port }
< b > Certificate Issuer : < / b > $ { cert . issuer . organizationName }
< b > Certificate Subject : < / b > $ { cert . subject . commonName }
< b > Responsible Engineer : < / b > $ { message . author . username } # $ { message . author . discriminator }
2019-10-20 20:14:04 -04:00
If you have any questions about additional setup , you can reply to this email or send a message in # cloud - support in our Discord server .
2019-10-26 00:01:16 -04:00
2019-10-26 14:32:56 -04:00
< b > < i > Library of Code sp - us | Support Team < / i > < / b >
2019-10-20 20:14:04 -04:00
` ,
2019-10-28 17:41:23 -04:00
} ) ;
if ( ! domain . domain . includes ( 'cloud.libraryofcode.org' ) ) {
2019-10-29 23:25:04 -04:00
const content = ` __**DNS Record Setup**__ \ nYou recently a binded a custom domain to your Library of Code sp-us Account. You'll have to update your DNS records. We've provided the records below. \ n \ n \` ${ domain . domain } IN CNAME cloud.libraryofcode.org AUTO/500 \` \ nThis basically means you need to make a CNAME record with the key/host of ${ domain . domain } and the value/point to cloud.libraryofcode.org. If you have any questions, don't hesitate to ask us. ` ;
2019-10-28 17:41:23 -04:00
this . client . getDMChannel ( account . userID ) . then ( ( r ) = > r . createMessage ( content ) ) ;
}
} catch ( err ) {
this . client . util . handleError ( err , message , this ) ;
2019-10-29 20:27:36 -04:00
await fs . unlink ( ` /etc/nginx/sites-available/ ${ args [ 2 ] } ` ) ;
await fs . unlink ( ` /etc/nginx/sites-enabled/ ${ args [ 2 ] } ` ) ;
await this . client . db . Domain . deleteMany ( { domain : args [ 2 ] } ) ;
2019-10-20 19:51:44 -04:00
}
2019-10-28 20:24:56 -04:00
} else if ( args [ 0 ] === 'data' ) {
if ( ! args [ 1 ] ) return this . client . commands . get ( 'help' ) . run ( message , [ this . name ] ) ;
2019-10-28 21:13:14 -04:00
const domain = await this . client . db . Domain . findOne ( { $or : [ { domain : args [ 1 ] } , { port : Number ( args [ 1 ] ) || '' } ] } ) ;
2019-10-28 20:30:55 -04:00
if ( ! domain ) return message . channel . createMessage ( ` *** ${ this . client . stores . emojis . error } The domain or port you provided could not be found.*** ` ) ;
2019-10-28 20:24:56 -04:00
const embed = new RichEmbed ( ) ;
embed . setTitle ( 'Domain Information' ) ;
embed . addField ( 'Account Username' , domain . account . username , true ) ;
embed . addField ( 'Account ID' , domain . account . userID , true ) ;
embed . addField ( 'Domain' , domain . domain , true ) ;
embed . addField ( 'Port' , String ( domain . port ) , true ) ;
2019-10-28 20:30:55 -04:00
embed . addField ( 'Certificate Issuer' , x509 . getIssuer ( await fs . readFile ( domain . x509 . cert , { encoding : 'utf8' } ) ) . organizationName , true ) ;
2019-10-28 20:24:56 -04:00
embed . addField ( 'Certificate Subject' , x509 . getSubject ( await fs . readFile ( domain . x509 . cert , { encoding : 'utf8' } ) ) . commonName , true ) ;
embed . addField ( 'Certificate Expiration Date' , moment ( x509 . parseCert ( await fs . readFile ( domain . x509 . cert , { encoding : 'utf8' } ) ) . notAfter ) . format ( 'dddd, MMMM Do YYYY, h:mm:ss A' ) , true ) ;
embed . setFooter ( this . client . user . username , this . client . user . avatarURL ) ;
embed . setTimestamp ( ) ;
// @ts-ignore
message . channel . createMessage ( { embed } ) ;
2019-10-29 19:12:36 -04:00
} else if ( args [ 0 ] === 'delete' ) {
if ( ! args [ 1 ] ) return this . client . commands . get ( 'help' ) . run ( message , [ this . name ] ) ;
const domain = await this . client . db . Domain . findOne ( { $or : [ { domain : args [ 1 ] } , { port : Number ( args [ 1 ] ) || '' } ] } ) ;
if ( ! domain ) return message . channel . createMessage ( ` *** ${ this . client . stores . emojis . error } The domain or port you provided could not be found.*** ` ) ;
2019-10-30 08:32:40 -04:00
const edit = await message . channel . createMessage ( ` *** ${ this . client . stores . emojis . loading } Deleting domain...*** ` ) ;
2019-10-29 19:12:36 -04:00
const embed = new RichEmbed ( ) ;
embed . setTitle ( 'Domain Deletion' ) ;
embed . addField ( 'Account Username' , domain . account . username , true ) ;
embed . addField ( 'Account ID' , domain . account . userID , true ) ;
embed . addField ( 'Domain' , domain . domain , true ) ;
embed . addField ( 'Port' , String ( domain . port ) , true ) ;
embed . setFooter ( this . client . user . username , this . client . user . avatarURL ) ;
embed . setTimestamp ( ) ;
2019-10-29 19:32:04 -04:00
if ( domain . domain . includes ( 'cloud.libraryofcode.org' ) ) {
const resultID = await axios ( {
method : 'get' ,
url : ` https://api.cloudflare.com/client/v4/zones/5e82fc3111ed4fbf9f58caa34f7553a7/dns_records?name= ${ domain . domain } ` ,
headers : { Authorization : ` Bearer ${ this . client . config . cloudflare } ` } ,
} ) ;
2019-10-30 11:46:47 -04:00
this . client . signale . debug ( resultID . data ) ;
2019-10-30 11:51:48 -04:00
if ( resultID . data . result [ 0 ] ) {
2019-10-30 11:53:18 -04:00
const recordID = resultID . data . result [ 0 ] . id ;
2019-10-30 11:51:48 -04:00
await axios ( {
2019-10-29 19:32:04 -04:00
method : 'delete' ,
url : ` https://api.cloudflare.com/client/v4/zones/5e82fc3111ed4fbf9f58caa34f7553a7/dns_records/ ${ recordID } ` ,
headers : { Authorization : ` Bearer ${ this . client . config . cloudflare } ` } ,
} ) ;
2019-10-30 11:51:48 -04:00
}
2019-10-29 19:32:04 -04:00
}
2019-10-29 19:37:27 -04:00
await fs . unlink ( ` /etc/nginx/sites-available/ ${ domain . domain } ` ) ;
2019-10-30 08:34:57 -04:00
await fs . unlink ( ` /etc/nginx/sites-enabled/ ${ domain . domain } ` ) ;
2019-10-29 19:37:27 -04:00
await this . client . db . Domain . deleteOne ( { domain : domain.domain } ) ;
await this . client . util . exec ( 'systemctl reload nginx' ) ;
edit . edit ( ` *** ${ this . client . stores . emojis . success } Domain ${ domain . domain } with port ${ domain . port } has been successfully deleted.*** ` ) ;
2019-10-29 19:12:36 -04:00
// @ts-ignore
message . channel . createMessage ( { embed } ) ;
2019-10-28 17:41:23 -04:00
} else { message . channel . createMessage ( ` ${ this . client . stores . emojis . error } Not a valid subcommand. ` ) ; }
return true ;
} catch ( error ) {
return this . client . util . handleError ( error , message , this ) ;
}
2019-10-20 19:28:35 -04:00
}
/ * *
* This function binds a domain to a port on the CWG .
* @param account The account of the user .
* @param subdomain The domain to use . ` mydomain.cloud.libraryofcode.org `
* @param port The port to use , must be between 1024 and 65535 .
* @param x509 The paths to the certificate and key files . Must be already existant .
* @example await CWG . createDomain ( 'mydomain.cloud.libraryofcode.org' , 6781 ) ;
* /
2019-10-28 17:58:44 -04:00
public async createDomain ( account : AccountInterface , domain : string , port : number , x509Certificate : { cert? : string , key? : string } = { cert : '/etc/nginx/ssl/cloud-org.chain.crt' , key : '/etc/nginx/ssl/cloud-org.key.pem' } ) {
2019-10-28 19:46:03 -04:00
try {
if ( port <= 1024 || port >= 65535 ) throw new RangeError ( ` Port range must be between 1024 and 65535, received ${ port } . ` ) ;
if ( await this . client . db . Domain . exists ( { port } ) ) throw new Error ( ` Port ${ port } already exists in the database. ` ) ;
if ( await this . client . db . Domain . exists ( { domain } ) ) throw new Error ( ` Domain ${ domain } already exists in the database. ` ) ;
if ( ! await this . client . db . Account . exists ( { userID : account.userID } ) ) throw new Error ( ` Cannot find account ${ account . userID } . ` ) ;
await fs . access ( x509Certificate . cert , fs . constants . R_OK ) ;
await fs . access ( x509Certificate . key , fs . constants . R_OK ) ;
let cfg = await fs . readFile ( '/var/CloudServices/dist/static/nginx.conf' , { encoding : 'utf8' } ) ;
cfg = cfg . replace ( /\[DOMAIN]/g , domain ) ;
cfg = cfg . replace ( /\[PORT]/g , String ( port ) ) ;
cfg = cfg . replace ( /\[CERTIFICATE]/g , x509Certificate . cert ) ;
cfg = cfg . replace ( /\[KEY]/g , x509Certificate . key ) ;
await fs . writeFile ( ` /etc/nginx/sites-available/ ${ domain } ` , cfg , { encoding : 'utf8' } ) ;
await fs . symlink ( ` /etc/nginx/sites-available/ ${ domain } ` , ` /etc/nginx/sites-enabled/ ${ domain } ` ) ;
const entry = new this . client . db . Domain ( {
account ,
domain ,
port ,
x509 : x509Certificate ,
enabled : true ,
2019-10-20 19:28:35 -04:00
} ) ;
2019-10-28 19:46:03 -04:00
if ( domain . includes ( 'cloud.libraryofcode.org' ) ) {
const dmn = domain . split ( '.' ) ;
await axios ( {
method : 'post' ,
url : 'https://api.cloudflare.com/client/v4/zones/5e82fc3111ed4fbf9f58caa34f7553a7/dns_records' ,
headers : { Authorization : ` Bearer ${ this . client . config . cloudflare } ` , 'Content-Type' : 'application/json' } ,
data : JSON.stringify ( { type : 'CNAME' , name : ` ${ dmn [ 0 ] } . ${ dmn [ 1 ] } ` , content : 'cloud.libraryofcode.org' , proxied : false } ) ,
} ) ;
}
return entry . save ( ) ;
} catch ( error ) {
await fs . unlink ( ` /etc/nginx/sites-available/ ${ domain } ` ) ;
await fs . unlink ( ` /etc/nginx/sites-enabled/ ${ domain } ` ) ;
await this . client . db . Domain . deleteMany ( { domain } ) ;
throw error ;
2019-10-20 19:28:35 -04:00
}
}
}