code refactoring and added extensive documentation
This commit is contained in:
46
src/config.type.ts
Normal file
46
src/config.type.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Dennis Gunia (c) 2020
|
||||
*
|
||||
* Interface for global config object
|
||||
*
|
||||
* @summary Open-Token entry point
|
||||
* @author Dennis Gunia <info@dennisgunia.de>
|
||||
* @license Licensed under the Apache License, Version 2.0 (the "License").
|
||||
*
|
||||
* You may not use this file except in compliance with the License.
|
||||
* A copy of the License is located at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* or in the "license" file accompanying this file. This file is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import SMTPTransport from 'nodemailer/lib/smtp-transport';
|
||||
import { MLParser } from './mailParser';
|
||||
|
||||
/** Interface containing all config properties for opentoken */
|
||||
export interface OTGlobalConfig {
|
||||
/** nodemailer SMTP configuration */
|
||||
mail: SMTPTransport,
|
||||
/** sender alias */
|
||||
mailFrom: string,
|
||||
/** path to file containing matches */
|
||||
outFileMatch: string
|
||||
/** path to file containing mails */
|
||||
inFileMail?: string
|
||||
/** path to file containing mail template */
|
||||
htmlPath?: string
|
||||
|
||||
/** List of used tokens */
|
||||
usedTokens? : string[]
|
||||
/** List of used mail adresses */
|
||||
usedMails? : MLParser.MLItem[]
|
||||
|
||||
/** switch for dryrun */
|
||||
dryrun: boolean
|
||||
/** switch for force */
|
||||
force: boolean
|
||||
}
|
||||
352
src/generate.ts
352
src/generate.ts
@@ -1,151 +1,233 @@
|
||||
/**
|
||||
* Dennis Gunia (c) 2020
|
||||
*
|
||||
* Generator Implementation.
|
||||
* This File provides the implementation of the code generator and mail sending functionality.
|
||||
*
|
||||
*
|
||||
* @summary Generator Implementation.
|
||||
* @author Dennis Gunia <info@dennisgunia.de>
|
||||
* @license Licensed under the Apache License, Version 2.0 (the "License").
|
||||
*
|
||||
* You may not use this file except in compliance with the License.
|
||||
* A copy of the License is located at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* or in the "license" file accompanying this file. This file is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
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';
|
||||
import { SVault } from './vault';
|
||||
import { MLParser } from './mailParser';
|
||||
import { shuffleArray } from './util/shuffle';
|
||||
import { delay, mkstringCN } from './util/misc';
|
||||
|
||||
interface mail{
|
||||
mail: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface genReturn{
|
||||
codes: string[];
|
||||
mails: mail[];
|
||||
}
|
||||
|
||||
export function generateToken(config: any,dataSafe: SecureVault): Promise<genReturn>{
|
||||
return new Promise<genReturn>((resolve,error) => {
|
||||
parseMails(config, dataSafe).then(res => {
|
||||
generateCodes(resolve,error,res,config,dataSafe);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// generate codes
|
||||
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 = '';
|
||||
|
||||
for(let i = 0; i < mailArray.length; i++){ // as many codes as adresses
|
||||
// check that codes are unique
|
||||
let code = '';
|
||||
do{
|
||||
code = mkstring(4);
|
||||
}while ( (config.force ? codeArray : [...codeArray, ...config.usedTokens]).includes(code))
|
||||
codeArray.push(code);
|
||||
checkString = `${checkString}|${code}`
|
||||
position ++;
|
||||
pbar.update(position);
|
||||
/**
|
||||
* Namespace containing the code for Generating the Code and delivering the mails
|
||||
*/
|
||||
export namespace MLGenerator {
|
||||
|
||||
/**
|
||||
* Interface used to Return codes and mails form the main Function {@link generateToken}
|
||||
*/
|
||||
export interface MLGenReturn{
|
||||
/** List of generated codes */
|
||||
codes: string[];
|
||||
/** List of processed mails */
|
||||
mails: MLParser.MLItem[];
|
||||
}
|
||||
checkString = checkString.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);
|
||||
} catch (err) {
|
||||
|
||||
error(err);
|
||||
}
|
||||
sendMails(resolve,error,mailArray,codeArray,config,dataSafe);
|
||||
}
|
||||
|
||||
|
||||
// randomize mails and tokens
|
||||
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<any>;
|
||||
try {
|
||||
const htmlSrc=fs.readFileSync(config.htmlPath, "utf8")
|
||||
template = Handlebars.compile(htmlSrc)
|
||||
} catch (error) {
|
||||
console.error("Cannote read template file!")
|
||||
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.writeTransaction(`process: ${mailArray[i].mail}`);
|
||||
if (!config.dryrun){
|
||||
dataSafe.pushData({
|
||||
name: mailArray[i].name,
|
||||
mail: mailArray[i].mail,
|
||||
code: codeArray[i]
|
||||
|
||||
/**
|
||||
* Main function used to generate and deliver codes
|
||||
* @param config Main configuration object
|
||||
* @param dataSafe Main safe used for logging and storing data
|
||||
* @returns used codes and processed mails
|
||||
*/
|
||||
export function generateToken(config: any,dataSafe: SVault.SecureVault): Promise<MLGenReturn>{
|
||||
return new Promise<MLGenReturn>((resolve,error) => {
|
||||
MLParser.parseMails(config, dataSafe).then(res => {
|
||||
// next step
|
||||
generateCodes(resolve,error,res,config,dataSafe);
|
||||
})
|
||||
}
|
||||
await send(mailArray[i].name, mailArray[i].mail, codeArray[i],template,mailserver,config,dataSafe);
|
||||
position ++;
|
||||
pbar.update(position);
|
||||
});
|
||||
}
|
||||
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<any>,mailserver: Mail,config: any,dataSafe: SecureVault){
|
||||
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
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the same amout of codes as distinct mail adresses and stores them to the list file
|
||||
* This function also includes previously used tokens to prevent duplicate tokens.
|
||||
* Rejects if matchfile cannot be saved.
|
||||
* @internal
|
||||
* @param resolve Callback to resolve promise
|
||||
* @param error Callback to reject promise
|
||||
* @param mailArray list of mail adresses
|
||||
* @param config Main configuration object
|
||||
* This Function uses the following variables:
|
||||
* usedTokens -> List of previosly used tokens
|
||||
* outFileMatch -> Path to match file
|
||||
* @param dataSafe Main safe used for logging and storing data
|
||||
*/
|
||||
async function generateCodes(resolve: (value?: MLGenReturn) => void,error: (reason?: any) => void,mailArray: MLParser.MLItem[],config: any,dataSafe: SVault.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 = '';
|
||||
|
||||
for(let i = 0; i < mailArray.length; i++){ // as many codes as adresses
|
||||
// check that codes are unique
|
||||
let code = '';
|
||||
do{
|
||||
code = mkstringCN(4);
|
||||
}while ( (config.force ? codeArray : [...codeArray, ...config.usedTokens]).includes(code))
|
||||
codeArray.push(code);
|
||||
checkString = `${checkString}|${code}`
|
||||
position ++;
|
||||
pbar.update(position);
|
||||
}
|
||||
checkString = checkString.substr(1);
|
||||
pbar.stop();
|
||||
//write code lists
|
||||
try {
|
||||
await mailserver.sendMail(mailOptions);
|
||||
dataSafe.writeTransaction(` -> mail sent`);
|
||||
if (!fs.existsSync(path.dirname(config.outFileMatch))){
|
||||
fs.mkdirSync(path.dirname(config.outFileMatch));
|
||||
}
|
||||
fs.writeFileSync(config.outFileMatch, checkString);
|
||||
} catch (err) {
|
||||
|
||||
error(err);
|
||||
}
|
||||
// next
|
||||
sendMails(resolve,error,mailArray,codeArray,config,dataSafe);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads template file and compiles template.
|
||||
* Iterate through mails and codes, randomly assign code to mail and send mail to recipient.
|
||||
* If dryrun is enabled, mails will not be sent and new mails won't be included in return.
|
||||
* Rejects if template cannot be read.
|
||||
* @internal
|
||||
* @param resolve Callback to resolve promise
|
||||
* @param error Callback to reject promise
|
||||
* @param mailArray list of mail adresses
|
||||
* @param codeArray list of generated codes
|
||||
* @param config Main configuration object
|
||||
* This Function uses the following variables:
|
||||
* htmlPath -> Path to html template
|
||||
* dryrun -> Boolean value. If true no mails will be sent and list won't be updated.
|
||||
* force -> Boolean value. If true all mails are resent.
|
||||
* usedTokens -> Array of Strings. Specifies already used tokens adresses.
|
||||
* usedMails -> Array of Strings. Specifies already served mail adresses.
|
||||
* mail -> mailserver settings
|
||||
* @param dataSafe Main safe used for logging and storing data
|
||||
*/
|
||||
async function sendMails(resolve: (value?: MLGenReturn) => void,error: (reason?: any) => void,mailArray: MLParser.MLItem[],codeArray: string[],config: any,dataSafe: SVault.SecureVault){
|
||||
let mailserver = nodemailer.createTransport(config.mail);
|
||||
// read mail template and compile
|
||||
let template!: HandlebarsTemplateDelegate<any>;
|
||||
try {
|
||||
const htmlSrc=fs.readFileSync(config.htmlPath, "utf8")
|
||||
template = Handlebars.compile(htmlSrc)
|
||||
} catch (error) {
|
||||
console.log(`Error sendign mail to ${mail} : ${error}`)
|
||||
dataSafe.writeTransaction(` -> mail failed : ${error}`);
|
||||
console.error("Cannote read template file!")
|
||||
error(error);
|
||||
}
|
||||
|
||||
// send mails
|
||||
console.log("\nSending mails")
|
||||
const pbar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
|
||||
pbar.start(mailArray.length, 0,{
|
||||
speed: "N/A"
|
||||
});
|
||||
let position = 0;
|
||||
|
||||
// randomize arrays
|
||||
shuffleArray(mailArray);
|
||||
shuffleArray(codeArray);
|
||||
if (config.force){dataSafe.clearVault();}
|
||||
for(let i = 0; i < mailArray.length; i++){
|
||||
// send mail
|
||||
dataSafe.writeTransaction(`process: ${mailArray[i].mail}`);
|
||||
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,dataSafe);
|
||||
position ++;
|
||||
pbar.update(position);
|
||||
}
|
||||
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])
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads template file and compiles template.
|
||||
* Iterate through mails and codes, randomly assign code to mail and send mail to recipient.
|
||||
* If dryrun is enabled, mails will not be sent and new mails won't be included in return.
|
||||
* Rejects if template cannot be read.
|
||||
* @internal
|
||||
* @param name Name of recpipient
|
||||
* @param mail Mail of recpipient
|
||||
* @param code Code of recpipient
|
||||
* @param template compiled mail template
|
||||
* @param mailserver Mailserver settings
|
||||
* @param config Main configuration object
|
||||
* This Function uses the following variables:
|
||||
* mail.auth.user -> sender mail adress
|
||||
* mailFrom -> sender mail ailas
|
||||
* dryrun -> Boolean value. If true no mails will be sent.
|
||||
* @param dataSafe Main safe used for logging and storing data
|
||||
*/
|
||||
async function send(name: string, mail: string, code: string,template: HandlebarsTemplateDelegate<any>,mailserver: Mail,config: any,dataSafe: SVault.SecureVault){
|
||||
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);
|
||||
dataSafe.writeTransaction(` -> mail sent`);
|
||||
} catch (error) {
|
||||
console.log(`Error sendign mail to ${mail} : ${error}`)
|
||||
dataSafe.writeTransaction(` -> mail failed : ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function delay(t: number, val?: number) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
resolve(val);
|
||||
}, t);
|
||||
});
|
||||
}
|
||||
@@ -1,70 +1,118 @@
|
||||
/**
|
||||
* Dennis Gunia (c) 2020
|
||||
*
|
||||
* MailParser Implementation.
|
||||
* This File provides a vault for storing encrypted and unencrypted data. Each encrypted element will be encrypted before storing it in an array.
|
||||
* Each element (encrypted or unencrypted) ist stored in an array with an unique identifier.
|
||||
* Safes can be stored and loaded. Unencrypted data can be stored, retrieved and modified. Encrypted data can be stored and retrieved.
|
||||
* This class also implements an transaction function.
|
||||
*
|
||||
* @summary SecureVault Class Implementation
|
||||
* @author Dennis Gunia <info@dennisgunia.de>
|
||||
* @license Licensed under the Apache License, Version 2.0 (the "License").
|
||||
*
|
||||
* You may not use this file except in compliance with the License.
|
||||
* A copy of the License is located at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* or in the "license" file accompanying this file. This file is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import { SecureVault } from "./vault";
|
||||
import { SVault } from "./vault";
|
||||
|
||||
export interface MLItem{
|
||||
mail: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function parseMails(config: any, dataSafe: SecureVault) {
|
||||
return new Promise<MLItem[]>((resolve,reject) => {
|
||||
let mailArray: MLItem[] = [];
|
||||
let currSection: string = "global";
|
||||
let lineCounter: number = 0;
|
||||
let curCounter: number = 0;
|
||||
|
||||
|
||||
console.log(`Reading mails for section ${currSection}`)
|
||||
// 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) {
|
||||
lineCounter ++;
|
||||
if(line.startsWith('[')){
|
||||
if(line.endsWith(']')){
|
||||
console.log(`Read ${curCounter} adresses for section ${currSection}`)
|
||||
curCounter = 0;
|
||||
currSection=line.substring(1,line.length -1);
|
||||
console.log(`Reading mails for section ${currSection}`)
|
||||
}else{
|
||||
console.error(`Error parsing section on line ${lineCounter}: Syntax Error. Missing closing bracket ]`)
|
||||
}
|
||||
}else if (!line.startsWith('#')){
|
||||
const ix = line.indexOf(";")
|
||||
if (ix !== -1){
|
||||
// check if already exist
|
||||
dataSafe.writeTransaction(`reading mail ${line.substr(0,ix)} from category ${currSection}`);
|
||||
if (config.force || config.usedMails.filter((el: MLItem) => el.mail == line.substr(0,ix)).length == 0){
|
||||
// check for duplicate
|
||||
if ( mailArray.filter((el: MLItem) => el.mail == line.substr(0,ix)).length == 0){
|
||||
mailArray.push({
|
||||
mail: line.substr(0,ix),
|
||||
name: line.substr(ix + 1)
|
||||
})
|
||||
curCounter ++;
|
||||
/**
|
||||
* Namespace containing the code for Parsing the mail list.
|
||||
*/
|
||||
export namespace MLParser{
|
||||
/**
|
||||
* Interface containing properties of a single mail line
|
||||
*/
|
||||
export interface MLItem{
|
||||
/** mail adress parsed form file */
|
||||
mail: string;
|
||||
/** name parsed form file */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts and appends data to SecureVault.
|
||||
* Also writes data to transaction log using @function writeTransaction
|
||||
* @param config - Reference to config object.
|
||||
* This Function uses the following variables:
|
||||
* inFileMail -> String reference to mail list
|
||||
* force -> Boolean value. If true all mails are resent.
|
||||
* usedMails -> Array of Strings. Specifies already served mail adresses.
|
||||
*
|
||||
* @param dataSafe - Reference to safe object. This is needed for writing to the vault.log file
|
||||
* @return Returns an array of all parsed mail adresses as promise
|
||||
*/
|
||||
export function parseMails(config: any, dataSafe: SVault.SecureVault) {
|
||||
return new Promise<MLItem[]>((resolve,reject) => {
|
||||
let mailArray: MLItem[] = [];
|
||||
let currSection: string = "global";
|
||||
let lineCounter: number = 0;
|
||||
let curCounter: number = 0;
|
||||
|
||||
|
||||
console.log(`Reading mails for section ${currSection}`)
|
||||
// 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) {
|
||||
lineCounter ++;
|
||||
if(line.startsWith('[')){
|
||||
if(line.endsWith(']')){
|
||||
console.log(`Read ${curCounter} adresses for section ${currSection}`)
|
||||
curCounter = 0;
|
||||
currSection=line.substring(1,line.length -1);
|
||||
console.log(`Reading mails for section ${currSection}`)
|
||||
}else{
|
||||
console.error(`Error parsing section on line ${lineCounter}: Syntax Error. Missing closing bracket ]`)
|
||||
}
|
||||
}else if (!line.startsWith('#')){
|
||||
const ix = line.indexOf(";")
|
||||
if (ix !== -1){
|
||||
// check if already exist
|
||||
dataSafe.writeTransaction(`reading mail ${line.substr(0,ix)} from category ${currSection}`);
|
||||
if (config.force || config.usedMails.filter((el: MLItem) => el.mail == line.substr(0,ix)).length == 0){
|
||||
// check for duplicate
|
||||
if ( mailArray.filter((el: MLItem) => el.mail == line.substr(0,ix)).length == 0){
|
||||
mailArray.push({
|
||||
mail: line.substr(0,ix),
|
||||
name: line.substr(ix + 1)
|
||||
})
|
||||
curCounter ++;
|
||||
}else{
|
||||
dataSafe.writeTransaction(` -> duplicate mail. Skipping`);
|
||||
console.error(`Skipping ${line.substr(0,ix)}: Duplicate`)
|
||||
}
|
||||
}else{
|
||||
dataSafe.writeTransaction(` -> duplicate mail. Skipping`);
|
||||
console.error(`Skipping ${line.substr(0,ix)}: Duplicate`)
|
||||
dataSafe.writeTransaction(` -> already exists. Skipping`);
|
||||
console.error(`Skipping ${line.substr(0,ix)}: Already sent`)
|
||||
}
|
||||
}else{
|
||||
dataSafe.writeTransaction(` -> already exists. Skipping`);
|
||||
console.error(`Skipping ${line.substr(0,ix)}: Already sent`)
|
||||
console.error(`Error parsing mail on line ${lineCounter}: Syntax Error. Missing ;`)
|
||||
}
|
||||
}else{
|
||||
console.error(`Error parsing mail on line ${lineCounter}: Syntax Error. Missing ;`)
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
rl.on('close', function (line:string) {
|
||||
// next step
|
||||
console.log(`Read ${curCounter} adresses for section ${currSection}\n${mailArray.length} mails read!`)
|
||||
resolve(mailArray);
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
});
|
||||
rl.on('close', function (line:string) {
|
||||
// next step
|
||||
console.log(`Read ${curCounter} adresses for section ${currSection}\n${mailArray.length} mails read!`)
|
||||
resolve(mailArray);
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
50
src/util/misc.ts
Normal file
50
src/util/misc.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Dennis Gunia (c) 2020
|
||||
*
|
||||
* Reusable functions for different projects.
|
||||
*
|
||||
* @summary Open-Token entry point
|
||||
* @author Dennis Gunia <info@dennisgunia.de>
|
||||
* @license Licensed under the Apache License, Version 2.0 (the "License").
|
||||
*
|
||||
* You may not use this file except in compliance with the License.
|
||||
* A copy of the License is located at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* or in the "license" file accompanying this file. This file is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generate random string with specified length.
|
||||
* Generates only numbers and capital letters.
|
||||
* @param length length of String
|
||||
* @return generated string
|
||||
*/
|
||||
export function mkstringCN (length:number ) {
|
||||
var result = '';
|
||||
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
var charactersLength = characters.length;
|
||||
for ( var i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps setTimeout into Promise
|
||||
* @param t Millisceonds
|
||||
*/
|
||||
export function delay(t: number) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
resolve();
|
||||
}, t);
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Randomize items in an array
|
||||
* @param array Array to be shuffeled
|
||||
*/
|
||||
export function shuffleArray(array: any[]) {
|
||||
for (var i = array.length - 1; i > 0; i--) {
|
||||
var j = Math.floor(Math.random() * (i + 1));
|
||||
@@ -5,4 +9,4 @@ export function shuffleArray(array: any[]) {
|
||||
array[i] = array[j];
|
||||
array[j] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export function mkstring (length:number ) {
|
||||
var result = '';
|
||||
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
var charactersLength = characters.length;
|
||||
for ( var i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
429
src/vault.ts
429
src/vault.ts
@@ -1,184 +1,285 @@
|
||||
/**
|
||||
* Dennis Gunia (c) 2020
|
||||
*
|
||||
* SecureVault Class Implementation.
|
||||
* This Class provides a vault for storing encrypted and unencrypted data. Each encrypted element will be encrypted before storing it in an array.
|
||||
* Each element (encrypted or unencrypted) ist stored in an array with an unique identifier.
|
||||
* Safes can be stored and loaded. Unencrypted data can be stored, retrieved and modified. Encrypted data can be stored and retrieved.
|
||||
* This class also implements an transaction function.
|
||||
*
|
||||
* @summary SecureVault Class Implementation
|
||||
* @author Dennis Gunia <info@dennisgunia.de>
|
||||
* @license Licensed under the Apache License, Version 2.0 (the "License").
|
||||
*
|
||||
* You may not use this file except in compliance with the License.
|
||||
* A copy of the License is located at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* or in the "license" file accompanying this file. This file is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import * as crypto from 'crypto'
|
||||
import * as uuid from 'uuid'
|
||||
import path from 'path';
|
||||
import * as fs from 'fs'
|
||||
import { generateKeyPair } from 'crypto';
|
||||
import { Console } from 'console';
|
||||
|
||||
const vaultVersion = 'v1.2'
|
||||
/**
|
||||
* Namespace containing the code for the SecureVault.
|
||||
*/
|
||||
export namespace SVault {
|
||||
/** vault version number. This variable will be added to the safe file and is used to chack compatibility. */
|
||||
const vaultVersion = 'v1.2'
|
||||
|
||||
export interface SecureVaultItem {
|
||||
u: string; // uuid
|
||||
d: string; // data
|
||||
k: string; // key
|
||||
iv: string; // init vector
|
||||
}
|
||||
|
||||
export interface StorageItem {
|
||||
u: string; // uuid
|
||||
d: string; // data
|
||||
t: string; // tag
|
||||
}
|
||||
|
||||
export interface secureVaultList {
|
||||
items: SecureVaultItem[];
|
||||
publicKey?: Buffer;
|
||||
privateKey?: Buffer;
|
||||
}
|
||||
|
||||
export class SecureVault {
|
||||
|
||||
safe: secureVaultList;
|
||||
privPath?: string;
|
||||
pubPath?: string;
|
||||
storage: StorageItem[];
|
||||
|
||||
constructor (publicKey: string, privateKey?: string) {
|
||||
this.storage = [];
|
||||
this.safe = {
|
||||
items: [],
|
||||
publicKey: publicKey ?fs.readFileSync(path.resolve(publicKey)): undefined,
|
||||
privateKey: privateKey ? fs.readFileSync(path.resolve(privateKey)): undefined
|
||||
};
|
||||
this.privPath = publicKey ? path.resolve(publicKey): undefined,
|
||||
this.pubPath = privateKey ? path.resolve(privateKey): undefined
|
||||
/** Interface for an vault item containing encrypted data */
|
||||
export interface SecureVaultItem {
|
||||
/** uuid */
|
||||
u: string;
|
||||
/** data */
|
||||
d: string;
|
||||
/** key */
|
||||
k: string;
|
||||
/** init vector */
|
||||
iv: string;
|
||||
}
|
||||
|
||||
async pushData(data: any): Promise<string>{
|
||||
// encrypt payload
|
||||
const txtData = JSON.stringify(data);
|
||||
const key = crypto.randomBytes(32);
|
||||
const iv = crypto.randomBytes(16);
|
||||
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
|
||||
let encrypted = cipher.update(txtData);
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
// encrypt key
|
||||
var buffer = new Buffer(key);
|
||||
if (!this.safe.publicKey){
|
||||
throw new Error("Public Key not found");
|
||||
/** Interface for an vault item containing unencrypted data */
|
||||
export interface StorageItem {
|
||||
/** uuid */
|
||||
u: string;
|
||||
/** data */
|
||||
d: string;
|
||||
/** tag (can be used to find specific items) */
|
||||
t: string;
|
||||
}
|
||||
|
||||
/** Interface for secureVault Array */
|
||||
export interface secureVaultList {
|
||||
items: SecureVaultItem[]; /** Array of encrypted items */
|
||||
publicKey?: Buffer; /** Binary of public key */
|
||||
privateKey?: Buffer; /** Binary of private key */
|
||||
}
|
||||
|
||||
/** Class representing a SecureVault. */
|
||||
export class SecureVault {
|
||||
/** Safe object */
|
||||
safe: secureVaultList;
|
||||
/** Path to private key */
|
||||
privPath?: string;
|
||||
/** Path to public key */
|
||||
pubPath?: string;
|
||||
/** Array of unencrypted items */
|
||||
storage: StorageItem[];
|
||||
|
||||
/**
|
||||
* Create a SecureVault.
|
||||
* @param publicKey - Path to public key.
|
||||
* @param privateKey - Path to private key.
|
||||
*/
|
||||
constructor (publicKey?: string, privateKey?: string) {
|
||||
this.storage = [];
|
||||
this.safe = {
|
||||
items: [],
|
||||
publicKey: publicKey ?fs.readFileSync(path.resolve(publicKey)): undefined,
|
||||
privateKey: privateKey ? fs.readFileSync(path.resolve(privateKey)): undefined
|
||||
};
|
||||
this.privPath = publicKey ? path.resolve(publicKey): undefined,
|
||||
this.pubPath = privateKey ? path.resolve(privateKey): undefined
|
||||
}
|
||||
var asym_encrypted = crypto.publicEncrypt(this.safe.publicKey, buffer);
|
||||
const u = uuid.v4()
|
||||
|
||||
const item = {
|
||||
u,
|
||||
d: encrypted.toString('hex'),
|
||||
k: asym_encrypted.toString("base64"),
|
||||
iv: iv.toString('hex')
|
||||
}
|
||||
this.writeTransaction("push: " + JSON.stringify(item))
|
||||
this.safe.items.push(item)
|
||||
return u;
|
||||
}
|
||||
|
||||
writeTransaction(payload: string){
|
||||
fs.appendFileSync('vault.log', `${payload}\n`);
|
||||
}
|
||||
|
||||
async saveData(path: string): Promise<void>{
|
||||
fs.writeFileSync(path, JSON.stringify({
|
||||
version: vaultVersion,
|
||||
vault: this.safe.items,
|
||||
storage: this.storage
|
||||
}));
|
||||
}
|
||||
|
||||
async loadData(path: string): Promise<void>{
|
||||
const loaded = JSON.parse(fs.readFileSync(path, 'utf8'));
|
||||
switch (loaded.version){
|
||||
case 'v1.1':
|
||||
this.safe.items = loaded.vault;
|
||||
break;
|
||||
case 'v1.2':
|
||||
this.safe.items = loaded.vault;
|
||||
this.storage = loaded.storage;
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown or unsupported vault file version: ${loaded.version}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async decryptData(): Promise<void>{
|
||||
|
||||
this.safe.items.forEach(el => {
|
||||
// decrpyt key
|
||||
let buffer = new Buffer(el.k, "base64");
|
||||
if (!this.safe.privateKey){
|
||||
throw new Error("Private Key not found");
|
||||
/**
|
||||
* Encrypts and appends data to SecureVault.
|
||||
* Also writes data to transaction log using @function writeTransaction
|
||||
* @param data - Path to public key.
|
||||
* @return Returns the uuid of the added object as promise
|
||||
*/
|
||||
async pushData(data: any): Promise<string>{
|
||||
// encrypt payload
|
||||
const txtData = JSON.stringify(data);
|
||||
const key = crypto.randomBytes(32);
|
||||
const iv = crypto.randomBytes(16);
|
||||
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
|
||||
let encrypted = cipher.update(txtData);
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
// encrypt key
|
||||
var buffer = new Buffer(key);
|
||||
if (!this.safe.publicKey){
|
||||
throw new Error("Public Key not found");
|
||||
}
|
||||
var key = crypto.privateDecrypt(this.safe.privateKey, buffer);
|
||||
// decrpyt payload
|
||||
let iv = Buffer.from(el.iv, 'hex');
|
||||
let encryptedText = Buffer.from(el.d, 'hex');
|
||||
let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||
let decrypted = decipher.update(encryptedText);
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
const obj = JSON.parse(decrypted.toString());
|
||||
console.log(obj);
|
||||
})
|
||||
}
|
||||
var asym_encrypted = crypto.publicEncrypt(this.safe.publicKey, buffer);
|
||||
const u = uuid.v4()
|
||||
|
||||
static genKey(publicKeyDir: string, privateKeyDir: string){
|
||||
generateKeyPair('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem',
|
||||
const item = {
|
||||
u,
|
||||
d: encrypted.toString('hex'),
|
||||
k: asym_encrypted.toString("base64"),
|
||||
iv: iv.toString('hex')
|
||||
}
|
||||
}, (err, publicKey, privateKey) => {
|
||||
fs.writeFileSync(privateKeyDir, privateKey);
|
||||
fs.writeFileSync(publicKeyDir, publicKey);
|
||||
});
|
||||
}
|
||||
this.writeTransaction("push: " + JSON.stringify(item))
|
||||
this.safe.items.push(item)
|
||||
return u;
|
||||
}
|
||||
|
||||
pushStorage(tag:string, data: any){
|
||||
if (vaultVersion !== 'v1.2'){
|
||||
throw new Error(`Storage not supported in ${vaultVersion}`);
|
||||
}else{
|
||||
let objJsonStr = JSON.stringify(data);
|
||||
let objJsonB64 = Buffer.from(objJsonStr).toString("base64");
|
||||
this.storage.push({
|
||||
u: uuid.v4(),
|
||||
d: objJsonB64,
|
||||
t: tag
|
||||
/**
|
||||
* Writes data to the vault log file located at ./vault.log
|
||||
* @param payload - Text to append
|
||||
*/
|
||||
writeTransaction(payload: string){
|
||||
fs.appendFileSync('vault.log', `${payload}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves safe to file
|
||||
* @param path - Path to safefile.
|
||||
* @return Resolves promise after loaded
|
||||
*/
|
||||
async saveData(path: string): Promise<void>{
|
||||
fs.writeFileSync(path, JSON.stringify({
|
||||
version: vaultVersion,
|
||||
vault: this.safe.items,
|
||||
storage: this.storage
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads safe from file and check compatibility
|
||||
* @param path - Path to safefile.
|
||||
* @return Resolves promise after loaded
|
||||
*/
|
||||
async loadData(path: string): Promise<void>{
|
||||
const loaded = JSON.parse(fs.readFileSync(path, 'utf8'));
|
||||
switch (loaded.version){
|
||||
case 'v1.1':
|
||||
this.safe.items = loaded.vault;
|
||||
break;
|
||||
case 'v1.2':
|
||||
this.safe.items = loaded.vault;
|
||||
this.storage = loaded.storage;
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown or unsupported vault file version: ${loaded.version}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts safe data.
|
||||
* Requires specified and loaded private key.
|
||||
* Prints data to console.
|
||||
* @return Resolves promise after decrypted
|
||||
*/
|
||||
async decryptData(): Promise<void>{
|
||||
|
||||
this.safe.items.forEach(el => {
|
||||
// decrpyt key
|
||||
let buffer = new Buffer(el.k, "base64");
|
||||
if (!this.safe.privateKey){
|
||||
throw new Error("Private Key not found");
|
||||
}
|
||||
var key = crypto.privateDecrypt(this.safe.privateKey, buffer);
|
||||
// decrpyt payload
|
||||
let iv = Buffer.from(el.iv, 'hex');
|
||||
let encryptedText = Buffer.from(el.d, 'hex');
|
||||
let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||
let decrypted = decipher.update(encryptedText);
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
const obj = JSON.parse(decrypted.toString());
|
||||
console.log(obj);
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Generates RSA keypair.
|
||||
* @param publicKey - Path to public key.
|
||||
* @param privateKey - Path to private key.
|
||||
*/
|
||||
static genKey(publicKeyDir: string, privateKeyDir: string){
|
||||
generateKeyPair('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem',
|
||||
}
|
||||
}, (err, publicKey, privateKey) => {
|
||||
fs.writeFileSync(privateKeyDir, privateKey);
|
||||
fs.writeFileSync(publicKeyDir, publicKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setStorage(suuid:string, data: any){
|
||||
if (vaultVersion !== 'v1.2'){
|
||||
throw new Error(`Storage not supported in ${vaultVersion}`);
|
||||
}else{
|
||||
let objJsonStr = JSON.stringify(data);
|
||||
let objJsonB64 = Buffer.from(objJsonStr,"utf8").toString("base64");
|
||||
this.storage.filter(el => el.u == suuid)[0].d = objJsonB64;
|
||||
/**
|
||||
* Appends unencrypted data to safe.
|
||||
* @param tag - Tag for item
|
||||
* @param data - Data to store.
|
||||
*/
|
||||
pushStorage(tag:string, data: any){
|
||||
if (vaultVersion !== 'v1.2'){
|
||||
throw new Error(`Storage not supported in ${vaultVersion}`);
|
||||
}else{
|
||||
let objJsonStr = JSON.stringify(data);
|
||||
let objJsonB64 = Buffer.from(objJsonStr).toString("base64");
|
||||
this.storage.push({
|
||||
u: uuid.v4(),
|
||||
d: objJsonB64,
|
||||
t: tag
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets unencrypted data for item specified by suuid.
|
||||
* @param suuid - UUID for item
|
||||
* @param data - Data to store.
|
||||
*/
|
||||
setStorage(suuid:string, data: any){
|
||||
if (vaultVersion !== 'v1.2'){
|
||||
throw new Error(`Storage not supported in ${vaultVersion}`);
|
||||
}else{
|
||||
let objJsonStr = JSON.stringify(data);
|
||||
let objJsonB64 = Buffer.from(objJsonStr,"utf8").toString("base64");
|
||||
this.storage.filter(el => el.u == suuid)[0].d = objJsonB64;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets unencrypted data of item specified by suuid.
|
||||
* @param suuid - UUID for item
|
||||
* @return Data from item.
|
||||
*/
|
||||
getStorage(suuid:string){
|
||||
if (vaultVersion !== 'v1.2'){
|
||||
throw new Error(`Storage not supported in ${vaultVersion}`);
|
||||
}else{
|
||||
const data = this.storage.filter(el => el.u == suuid)[0];
|
||||
let objJsonB64 = new Buffer(data.d, 'base64');
|
||||
return JSON.parse(objJsonB64.toString('utf8'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of UUIDs matching the tag.
|
||||
* @param tag - tag to search for
|
||||
* @return UUID from item.
|
||||
*/
|
||||
findStorage(tag:string){
|
||||
if (vaultVersion !== 'v1.2'){
|
||||
throw new Error(`Storage not supported in ${vaultVersion}`);
|
||||
}else{
|
||||
return this.storage.filter(el => el.t == tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all encrypted items from safe.
|
||||
*/
|
||||
clearVault(){
|
||||
this.safe.items = [];
|
||||
}
|
||||
}
|
||||
|
||||
getStorage(suuid:string){
|
||||
if (vaultVersion !== 'v1.2'){
|
||||
throw new Error(`Storage not supported in ${vaultVersion}`);
|
||||
}else{
|
||||
const data = this.storage.filter(el => el.u == suuid)[0];
|
||||
let objJsonB64 = new Buffer(data.d, 'base64');
|
||||
return JSON.parse(objJsonB64.toString('utf8'));
|
||||
}
|
||||
}
|
||||
|
||||
findStorage(tag:string){
|
||||
if (vaultVersion !== 'v1.2'){
|
||||
throw new Error(`Storage not supported in ${vaultVersion}`);
|
||||
}else{
|
||||
return this.storage.filter(el => el.t == tag);
|
||||
}
|
||||
}
|
||||
|
||||
clearVault(){
|
||||
this.safe.items = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user