diff --git a/.gitignore b/.gitignore index 1d34e75..1e9f974 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /node_modules /config.json /mails.txt +/out \ No newline at end of file diff --git a/index.ts b/index.ts index 6dbec81..ad02af9 100644 --- a/index.ts +++ b/index.ts @@ -1,8 +1,9 @@ import * as fs from 'fs' import { generateToken } from './src/generate' import { SecureVault } from './src/vault' +import { exit } from 'process'; -let configPath = "", action = -1, pubKey = "", privKey = "", safeFile = "", mails = "", html = ""; +let configPath = "", action = -1, pubKey = "", privKey = "", safeFile = "", mails = "", html = "", dryrun = false, force = false; // parse cli args for (let i = 1; i < process.argv.length ; i++){ if (process.argv[i] === "--config"){ @@ -38,6 +39,8 @@ for (let i = 1; i < process.argv.length ; i++){ if (process.argv[i] === "--send"){ action = 1 } if (process.argv[i] === "--decrypt"){ action = 2 } if (process.argv[i] === "--genkey"){ action = 3 } + if (process.argv[i] === "--dryrun"){ dryrun = true } + if (process.argv[i] === "--force"){ force = true } } if ( action == -1){ throw new Error("No Action specified") } if (!configPath && action == 1){ throw new Error("Config-Path not specified") } @@ -54,17 +57,39 @@ if (action == 1){ // load config const confRaw = fs.readFileSync(configPath, 'utf8') let config:any = {} + let addition: boolean = false; // wenn nur weitere hinzugefügt werden + config = JSON.parse(confRaw) + // load safe if present + if (fs.existsSync(safeFile)){ + dataSafe.loadData(safeFile); + config.usedTokens = dataSafe.getStorage(dataSafe.findStorage("usedTokens")[0].u); + config.usedMails = dataSafe.getStorage(dataSafe.findStorage("usedMails")[0].u); + addition = true; + }else{ + config.usedTokens = []; + config.usedMails = []; + } + try { - config = JSON.parse(confRaw) config.inFileMail = mails; config.htmlPath = html; + config.dryrun = dryrun; + config.force = force; } catch (error) { console.error("Cannote read config file!") process.exit(100); } generateToken(config,dataSafe).then(el => { - console.log(el) - dataSafe.saveData(safeFile); + if (addition){ + dataSafe.setStorage(dataSafe.findStorage("usedTokens")[0].u,el.codes) + dataSafe.setStorage(dataSafe.findStorage("usedMails")[0].u,el.mails) + dataSafe.saveData(safeFile); + }else{ + dataSafe.pushStorage('usedTokens',el.codes) + dataSafe.pushStorage('usedMails',el.mails) + dataSafe.saveData(safeFile); + } + }).catch(err => console.error("error", err)) }else if(action == 2){ let dataSafe: SecureVault = new SecureVault(pubKey,privKey); diff --git a/package-lock.json b/package-lock.json index 01a8456..98463d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,17 @@ { - "name": "random", + "name": "opentoken", "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/cli-progress": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.8.0.tgz", + "integrity": "sha512-2OV7ybuYQc6ju6Xlg8ncQcPA/vVbMTSGS0vygjszi9O7swC63S6f2oAnP4CE+4ppRo7eCQovlj268KySt1MaUQ==", + "requires": { + "@types/node": "*" + } + }, "@types/handlebars": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.1.0.tgz", @@ -25,11 +33,40 @@ "@types/node": "*" } }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==" + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "cli-progress": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.8.2.tgz", + "integrity": "sha512-qRwBxLldMSfxB+YGFgNRaj5vyyHe1yMpVeDL79c+7puGujdKJHQHydgqXDcrkvQgJ5U/d3lpf6vffSoVVUftVQ==", + "requires": { + "colors": "^1.1.2", + "string-width": "^4.2.0" + } + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, "crypto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "handlebars": { "version": "4.7.6", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", @@ -42,6 +79,11 @@ "wordwrap": "^1.0.0" } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", @@ -62,12 +104,35 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, "uglify-js": { "version": "3.10.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.4.tgz", "integrity": "sha512-kBFT3U4Dcj4/pJ52vfjCSfyLyvG9VYYuGYPmrPvAxRw/i7xHiT4VvCev+uiEMcEEiu6UNB6KgWmGtSUYIWScbw==", "optional": true }, + "uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 730c088..5bbcbbf 100644 --- a/package.json +++ b/package.json @@ -10,19 +10,23 @@ "author": "", "license": "ISC", "dependencies": { + "@types/cli-progress": "^3.8.0", "@types/handlebars": "^4.1.0", "@types/node": "^14.11.1", "@types/nodemailer": "^6.4.0", + "@types/uuid": "^8.3.0", + "cli-progress": "^3.8.2", "crypto": "^1.0.1", "handlebars": "^4.7.6", - "nodemailer": "^6.4.11" + "nodemailer": "^6.4.11", + "uuid": "^8.3.0" }, "pkg": { "scripts": "dist/**/*.js", "targets": [ "node12" ], - "outputPath":"bin/" + "outputPath": "bin/" }, "bin": "dist/index.js" } diff --git a/src/generate.ts b/src/generate.ts index d039fc9..ab810ea 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -1,73 +1,76 @@ import * as fs from 'fs' +import * as path from 'path' import * as nodemailer from 'nodemailer'; +import * as cliProgress from 'cli-progress' import { shuffleArray } from './util/shuffle'; import { mkstring } from './util/token'; import * as Handlebars from "handlebars"; import Mail from 'nodemailer/lib/mailer'; import { SecureVault } from './vault'; +import { parseMails } from './mailParser'; interface mail{ mail: string; name: string; } -export function generateToken(config: any,dataSafe: SecureVault): Promise{ - let pr = new Promise((resolve,error) => { - let mailArray: mail[] = []; +export interface genReturn{ + codes: string[]; + mails: mail[]; +} - // read and process mail list - let readline = require('readline'), - instream = fs.createReadStream(config.inFileMail), - outstream = new (require('stream'))(), - rl = readline.createInterface(instream, outstream); - - rl.on('line', function (line:string) { - console.log(line); - mailArray.push({ - mail: line.substr(0,line.indexOf(";")), - name: line.substr(line.indexOf(";") + 1) - }) - }); - rl.on('close', function (line:string) { - // next step - generateCodes(resolve,error,mailArray,config,dataSafe); - }); +export function generateToken(config: any,dataSafe: SecureVault): Promise{ + return new Promise((resolve,error) => { + parseMails(config).then(res => { + generateCodes(resolve,error,res,config,dataSafe); + }) }); - return pr; } // generate codes -async function generateCodes(resolve: (value?: String[]) => void,error: (reason?: any) => void,mailArray: mail[],config: any,dataSafe: SecureVault){ +async function generateCodes(resolve: (value?: genReturn) => void,error: (reason?: any) => void,mailArray: mail[],config: any,dataSafe: SecureVault){ + console.log("\nGenerating codes") + const pbar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic); + pbar.start(mailArray.length, 0,{ + speed: "N/A" + }); + let position = 0; + let codeArray: string[] = []; let checkString: string = ''; let listString: string = ''; for(let i = 0; i < mailArray.length; i++){ // as many codes as adresses // check that codes are unique - let code = mkstring(4); - while (codeArray.includes(code)){ + let code = ''; + do{ code = mkstring(4); - } + }while ( (config.force ? codeArray : [...codeArray, ...config.usedTokens]).includes(code)) codeArray.push(code); checkString = `${checkString}|${code}` listString = `${listString}\n${code}` + position ++; + pbar.update(position); } checkString = checkString.substr(1); listString = listString.substr(1); - + pbar.stop(); //write code lists try { + if (!fs.existsSync(path.dirname(config.outFileMatch))){ + fs.mkdirSync(path.dirname(config.outFileMatch)); + } fs.writeFileSync(config.outFileMatch, checkString); - fs.writeFileSync(config.outFileCodes, listString); - } catch (error) { - error(error); + } catch (err) { + + error(err); } sendMails(resolve,error,mailArray,codeArray,config,dataSafe); } // randomize mails and tokens -async function sendMails(resolve: (value?: String[]) => void,error: (reason?: any) => void,mailArray: mail[],codeArray: string[],config: any,dataSafe: SecureVault){ +async function sendMails(resolve: (value?: genReturn) => void,error: (reason?: any) => void,mailArray: mail[],codeArray: string[],config: any,dataSafe: SecureVault){ let mailserver = nodemailer.createTransport(config.mail); // read mail template let template!: HandlebarsTemplateDelegate; @@ -79,36 +82,70 @@ async function sendMails(resolve: (value?: String[]) => void,error: (reason?: an error(error); } + console.log("\nSending mails") + const pbar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic); + pbar.start(mailArray.length, 0,{ + speed: "N/A" + }); + let position = 0; + shuffleArray(mailArray); shuffleArray(codeArray); + if (config.force){dataSafe.clearVault();} for(let i = 0; i < mailArray.length; i++){ // send mail - dataSafe.pushData({ - name: mailArray[i].name, - mail: mailArray[i].mail, - code: codeArray[i] - }) + if (!config.dryrun){ + dataSafe.pushData({ + name: mailArray[i].name, + mail: mailArray[i].mail, + code: codeArray[i] + }) + } await send(mailArray[i].name, mailArray[i].mail, codeArray[i],template,mailserver,config); + position ++; + pbar.update(position); } - resolve(codeArray); + pbar.stop(); + shuffleArray(mailArray); + shuffleArray(codeArray); + shuffleArray(mailArray); + shuffleArray(codeArray); + resolve({ + codes: config.force ? codeArray : (config.dryrun ? config.usedTokens : [...codeArray, ...config.usedTokens]), + mails: config.force ? mailArray : (config.dryrun ? config.usedMails : [...mailArray, ...config.usedMails]) + }); + } async function send(name: string, mail: string, code: string,template: HandlebarsTemplateDelegate,mailserver: Mail,config: any){ - // fill template - let html = template({ - "name": name, - "mail": mail, - "code": code - }) - let mailOptions = { - from: `${config.mailFrom} <${config.mail.auth.user}>`, // sender address - to: mail, // list of receivers - subject: `Dein Zugangscode zur BJR Wahl`, // Subject line - html: html - }; - try { - await mailserver.sendMail(mailOptions); - } catch (error) { - console.log(`Error sendign mail to ${mail} : ${error}`) + if (config.dryrun){ + await delay(100); + console.log(`\n\x1b[36m -> dryrun: would send to ${mail}\x1b[0m`); + }else{ + // fill template + let html = template({ + "name": name, + "mail": mail, + "code": code + }) + let mailOptions = { + from: `${config.mailFrom} <${config.mail.auth.user}>`, // sender address + to: mail, // list of receivers + subject: `Dein Zugangscode zur BJR Wahl`, // Subject line + html: html + }; + try { + await mailserver.sendMail(mailOptions); + } catch (error) { + console.log(`Error sendign mail to ${mail} : ${error}`) + } } -} \ No newline at end of file +} + +function delay(t: number, val?: number) { + return new Promise(function(resolve) { + setTimeout(function() { + resolve(val); + }, t); + }); + } \ No newline at end of file