10 Commits
v1 ... v1.3

22 changed files with 446 additions and 973 deletions

3
.gitignore vendored
View File

@@ -3,3 +3,6 @@
/node_modules
/config.json
/mails.txt
/out
/dist/*
template.html

View File

@@ -46,7 +46,6 @@ Der Ausfbau der mail.txt ist:
```
Es werden drei Ausgaben erzeugt:
- List aller Codes
- Eine RegEx vorlage für alle Codes
- Safe-Datei
@@ -76,6 +75,7 @@ Dazu muss golgender Befehl ausgeführt werden:
ts-node .\index.ts --privkey private.key --decrypt --safe .\out\credentials.json
## Config-Datei
```
{
"mail":{
"host": "<mailserver>",
@@ -91,24 +91,102 @@ ts-node .\index.ts --privkey private.key --decrypt --safe .\out\credentials.json
}
},
"mailFrom": "<absender name>",
"outFileCodes": "<ausgabedatei codes>",
"outFileMatch": "<ausgabedatei regex>"
}
```
## Syntax
==> Schlüsselpaar Erzeugen
`ts-node .\index.ts --privkey <path-to-private-key> --pubkey <path-to-public-key> --genkey
`ts-node .\index.ts --privkey <path-to-private-key> --pubkey <path-to-public-key> --genkey`
z.B. `ts-node .\index.ts --privkey private.key --pubkey public.key --genkey
z.B. `ts-node .\index.ts --privkey private.key --pubkey public.key --genkey`
==> Codes Erzeugen und versenden
`ts-node .\index.ts --config <path-to-config-key> --pubkey <path-to-public-key> --send --safe .\out\credentials.json --mails <path-to-mail-list> -html <path-to-html-template>
`ts-node .\index.ts --config <path-to-config-key> --pubkey <path-to-public-key> --send --safe credentials.json --mails <path-to-mail-list> -html <path-to-html-template>`
z.B. `ts-node .\index.ts --config config.json --pubkey public.key --send --safe .\out\credentials.json --mails mail.txt -html template.html
Achtung: Es wird im Safe geprüft, ob Mailadressen bereits "bedient" wurden. Sollte dies der Fall sein, werden keine Mails an diese Adresse gesendet. Dies lässt sich mit dem Schalter `--force` umgehen.
z.B. `ts-node .\index.ts --config config.json --pubkey public.key --send --safe credentials.json --mails mail.txt -html template.html`
==> Safe entschlüsseln
`ts-node .\index.ts --privkey <path-to-private-key> --decrypt --safe .\out\credentials.json
`ts-node .\index.ts --privkey <path-to-private-key> --decrypt --safe credentials.json`
z.B. `ts-node .\index.ts --privkey private.key --decrypt --safe .\out\credentials.json
z.B. `ts-node .\index.ts --privkey private.key --decrypt --safe credentials.json`
### Erweiterte Schalter
- `--dryrun` : Mails werden nicht versendet und der Safe wird nicht verändert.
- `--force` : Alle Codes werden neu generiert und alle mails werden gesendet. Ignoriere bereits gesendete mails.
## Gepackte Binaries
Die gepackten Binaries sind für Linux, MacOS und Windoof verfügbar: [Binaries](https://gitlab.dennisgunia.de/dennisgunia/one-time-code-js/-/tree/master/bin)
Die befehle ändern sich wie folgt:
==> Schlüsselpaar Erzeuge
`./opentoken --privkey <path-to-private-key> --pubkey <path-to-public-key> --genkey`
z.B. `./opentoken --privkey private.key --pubkey public.key --genkey`
==> Codes Erzeugen und versenden
`./opentoken --config <path-to-config-key> --pubkey <path-to-public-key> --send --safe .\out\credentials.json --mails <path-to-mail-list> -html <path-to-html-template>`
z.B. `./opentoken --config config.json --pubkey public.key --send --safe .\out\credentials.json --mails mail.txt -html template.html`
==> Safe entschlüsseln
`./opentoken --privkey <path-to-private-key> --decrypt --safe .\out\credentials.json`
z.B. `./opentoken --privkey private.key --decrypt --safe .\out\credentials.json`
## Ausführen des Quellcodes
Der Sourcecode kann auch über ts-node ausgeführt werden.
Dazu ist Node.js Version 12 zu verwenden
`nvm use 12`
Zum Ausführen sind folgende npm Pakete notwendig:
- typescript
- tslint
- ts-node
Installerien sie diese mit:
`npm install -g typescript tslint ts-node`
Clonen sie dieses Repository auf ihren lokalen rechner und wechseln sie anschließend in dessen verzeichniss:
`git clone https://gitlab.dennisgunia.de/dennisgunia/one-time-code-js.git`
`cd one-time-code-js`
Installieren sie alle lokalen npm Pakete
`npm install`
Kopieren Sie die Config-Template und passen Sie die SMTP-Zugangsdaten an:
`cp config.template.json config.json`
`vim config.json`
Das Skript kann nun über `npm run-script exec` oder `ts-node index.js` ausgeführt werden.
## Packen des Quellcodes
Zum Packen des Quellcodes ist das npm-Paket `pkg` zu installieren:
`npm install -g pkg`
Anschließend wird der Code in JS transpiliert und durch pkg gepackt:
`npm run-script build`
Die Binaries werden in `./bin` gespeichert. Diese sind auch auf Systemen ohne node.js ausführbar.
## Was landet im Safe?
Im safe landen die verschlüsselten Zuordnungen zwischen Codes und Mailadressen.
Zudem werden die verwendeten Codes und die bereits gesendeten Mailadressen seperat voneinander und zufällig gemischt in Klartext gespeichert.
Dies ermöglicht es, nachträglich benutzer hinzuzufügen, ohne allen anderen neue Mails oder gar neue Codes zukommen lassen zu müssen.
Es ist jedoch nocht empfohlen, nachträglich mails hinzuzufügen, da dies, abhängig von der Menge der gleichzeitig hinzugefügten Adressen eine grobe oder ggf. auch sehr genaue zuordnung zwischen Code und Mail der Nachzügler möglich ist.

Binary file not shown.

Binary file not shown.

Binary file not shown.

131
dist/index.js vendored
View File

@@ -1,131 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = __importStar(require("fs"));
var generate_1 = require("./src/generate");
var vault_1 = require("./src/vault");
var configPath = "", action = -1, pubKey = "", privKey = "", safeFile = "", mails = "", html = "";
for (var i = 1; i < process.argv.length; i++) {
if (process.argv[i] === "--config") {
if (i + 1 < process.argv.length && !process.argv[i + 1].startsWith("--")) {
configPath = process.argv[i + 1];
}
else {
throw new Error("Invalid params");
}
}
if (process.argv[i] === "--pubkey") {
if (i + 1 < process.argv.length && !process.argv[i + 1].startsWith("--")) {
pubKey = process.argv[i + 1];
}
else {
throw new Error("Invalid params");
}
}
if (process.argv[i] === "--privkey") {
if (i + 1 < process.argv.length && !process.argv[i + 1].startsWith("--")) {
privKey = process.argv[i + 1];
}
else {
throw new Error("Invalid params");
}
}
if (process.argv[i] === "--safe") {
if (i + 1 < process.argv.length && !process.argv[i + 1].startsWith("--")) {
safeFile = process.argv[i + 1];
}
else {
throw new Error("Invalid params");
}
}
if (process.argv[i] === "--mails") {
if (i + 1 < process.argv.length && !process.argv[i + 1].startsWith("--")) {
mails = process.argv[i + 1];
}
else {
throw new Error("Invalid params");
}
}
if (process.argv[i] === "--html") {
if (i + 1 < process.argv.length && !process.argv[i + 1].startsWith("--")) {
html = process.argv[i + 1];
}
else {
throw new Error("Invalid params");
}
}
if (process.argv[i] === "--send") {
action = 1;
}
if (process.argv[i] === "--decrypt") {
action = 2;
}
if (process.argv[i] === "--genkey") {
action = 3;
}
}
if (action == -1) {
throw new Error("No Action specified");
}
if (!configPath && action == 1) {
throw new Error("Config-Path not specified");
}
if (!pubKey && action != 2) {
throw new Error("Public-Key not specified");
}
if (!safeFile && action != 3) {
throw new Error("Safe-file not specified");
}
if (!privKey && action >= 2) {
throw new Error("Private-Key not specified");
}
if (!mails && action == 1) {
throw new Error("Mail-Input not specified");
}
if (!html && action == 1) {
throw new Error("Mail-Template not specified");
}
if (action == 1) {
var dataSafe_1 = new vault_1.SecureVault(pubKey, privKey);
var confRaw = fs.readFileSync(configPath, 'utf8');
var config = {};
try {
config = JSON.parse(confRaw);
config.inFileMail = mails;
config.htmlPath = html;
}
catch (error) {
console.error("Cannote read config file!");
process.exit(100);
}
generate_1.generateToken(config, dataSafe_1).then(function (el) {
console.log(el);
dataSafe_1.saveData(safeFile);
}).catch(function (err) { return console.error("error", err); });
}
else if (action == 2) {
var dataSafe = new vault_1.SecureVault(pubKey, privKey);
dataSafe.loadData(safeFile);
dataSafe.decryptData();
}
else if (action == 3) {
vault_1.SecureVault.genKey(pubKey, privKey);
}

184
dist/src/generate.js vendored
View File

@@ -1,184 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateToken = void 0;
var fs = __importStar(require("fs"));
var nodemailer = __importStar(require("nodemailer"));
var shuffle_1 = require("./util/shuffle");
var token_1 = require("./util/token");
var Handlebars = __importStar(require("handlebars"));
function generateToken(config, dataSafe) {
var pr = new Promise(function (resolve, error) {
var mailArray = [];
var readline = require('readline'), instream = fs.createReadStream(config.inFileMail), outstream = new (require('stream'))(), rl = readline.createInterface(instream, outstream);
rl.on('line', function (line) {
console.log(line);
mailArray.push({
mail: line.substr(0, line.indexOf(";")),
name: line.substr(line.indexOf(";") + 1)
});
});
rl.on('close', function (line) {
generateCodes(resolve, error, mailArray, config, dataSafe);
});
});
return pr;
}
exports.generateToken = generateToken;
function generateCodes(resolve, error, mailArray, config, dataSafe) {
return __awaiter(this, void 0, void 0, function () {
var codeArray, checkString, listString, i, code;
return __generator(this, function (_a) {
codeArray = [];
checkString = '';
listString = '';
for (i = 0; i < mailArray.length; i++) {
code = token_1.mkstring(4);
while (codeArray.includes(code)) {
code = token_1.mkstring(4);
}
codeArray.push(code);
checkString = checkString + "|" + code;
listString = listString + "\n" + code;
}
checkString = checkString.substr(1);
listString = listString.substr(1);
try {
fs.writeFileSync(config.outFileMatch, checkString);
fs.writeFileSync(config.outFileCodes, listString);
}
catch (error) {
error(error);
}
sendMails(resolve, error, mailArray, codeArray, config, dataSafe);
return [2];
});
});
}
function sendMails(resolve, error, mailArray, codeArray, config, dataSafe) {
return __awaiter(this, void 0, void 0, function () {
var mailserver, template, htmlSrc, i;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
mailserver = nodemailer.createTransport(config.mail);
try {
htmlSrc = fs.readFileSync(config.htmlPath, "utf8");
template = Handlebars.compile(htmlSrc);
}
catch (error) {
console.error("Cannote read template file!");
error(error);
}
shuffle_1.shuffleArray(mailArray);
shuffle_1.shuffleArray(codeArray);
i = 0;
_a.label = 1;
case 1:
if (!(i < mailArray.length)) return [3, 4];
dataSafe.pushData({
name: mailArray[i].name,
mail: mailArray[i].mail,
code: codeArray[i]
});
return [4, send(mailArray[i].name, mailArray[i].mail, codeArray[i], template, mailserver, config)];
case 2:
_a.sent();
_a.label = 3;
case 3:
i++;
return [3, 1];
case 4:
resolve(codeArray);
return [2];
}
});
});
}
function send(name, mail, code, template, mailserver, config) {
return __awaiter(this, void 0, void 0, function () {
var html, mailOptions, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
html = template({
"name": name,
"mail": mail,
"code": code
});
mailOptions = {
from: config.mailFrom + " <" + config.mail.auth.user + ">",
to: mail,
subject: "Dein Zugangscode zur BJR Wahl",
html: html
};
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4, mailserver.sendMail(mailOptions)];
case 2:
_a.sent();
return [3, 4];
case 3:
error_1 = _a.sent();
console.log("Error sendign mail to " + mail + " : " + error_1);
return [3, 4];
case 4: return [2];
}
});
});
}

View File

@@ -1,12 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.shuffleArray = void 0;
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
exports.shuffleArray = shuffleArray;

View File

@@ -1,13 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mkstring = void 0;
function mkstring(length) {
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;
}
exports.mkstring = mkstring;

156
dist/src/vault.js vendored
View File

@@ -1,156 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecureVault = void 0;
var crypto = __importStar(require("crypto"));
var path_1 = __importDefault(require("path"));
var fs = __importStar(require("fs"));
var crypto_1 = require("crypto");
var SecureVault = (function () {
function SecureVault(publicKey, privateKey) {
this.safe = {
items: [],
publicKey: publicKey ? fs.readFileSync(path_1.default.resolve(publicKey)) : undefined,
privateKey: privateKey ? fs.readFileSync(path_1.default.resolve(privateKey)) : undefined
};
this.privPath = publicKey ? path_1.default.resolve(publicKey) : undefined,
this.pubPath = privateKey ? path_1.default.resolve(privateKey) : undefined;
}
SecureVault.prototype.pushData = function (data) {
return __awaiter(this, void 0, void 0, function () {
var txtData, key, iv, cipher, encrypted, buffer, asym_encrypted;
return __generator(this, function (_a) {
txtData = JSON.stringify(data);
key = crypto.randomBytes(32);
iv = crypto.randomBytes(16);
cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
encrypted = cipher.update(txtData);
encrypted = Buffer.concat([encrypted, cipher.final()]);
buffer = new Buffer(key);
if (!this.safe.publicKey) {
throw new Error("Public Key not found");
}
asym_encrypted = crypto.publicEncrypt(this.safe.publicKey, buffer);
this.safe.items.push({
d: encrypted.toString('hex'),
k: asym_encrypted.toString("base64"),
iv: iv.toString('hex')
});
return [2];
});
});
};
SecureVault.prototype.saveData = function (path) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
fs.writeFileSync(path, JSON.stringify(this.safe.items));
return [2];
});
});
};
SecureVault.prototype.loadData = function (path) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
this.safe.items = JSON.parse(fs.readFileSync(path, 'utf8'));
return [2];
});
});
};
SecureVault.prototype.decryptData = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
this.safe.items.forEach(function (el) {
var 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);
var iv = Buffer.from(el.iv, 'hex');
var encryptedText = Buffer.from(el.d, 'hex');
var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
var decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
var obj = JSON.parse(decrypted.toString());
console.log(obj);
});
return [2];
});
});
};
SecureVault.genKey = function (publicKeyDir, privateKeyDir) {
crypto_1.generateKeyPair('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
}
}, function (err, publicKey, privateKey) {
fs.writeFileSync(privateKeyDir, privateKey);
fs.writeFileSync(publicKeyDir, publicKey);
});
};
return SecureVault;
}());
exports.SecureVault = SecureVault;

View File

@@ -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") }
@@ -51,21 +54,47 @@ if (!html && action == 1){ throw new Error("Mail-Template not specified") }
if (action == 1){
let dataSafe: SecureVault = new SecureVault(pubKey,privKey);
dataSafe.writeTransaction(`Started ...`);
// 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);
}).catch(err => console.error("error", err))
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);
}
dataSafe.writeTransaction(`Process exited successfully`);
}).catch(err => {
dataSafe.writeTransaction(`Process exited with error ${err}`);
console.error("error", err)
})
}else if(action == 2){
let dataSafe: SecureVault = new SecureVault(pubKey,privKey);
dataSafe.loadData(safeFile);

1
outcredentials.json Normal file
View File

@@ -0,0 +1 @@
{"version":"v1.2","vault":[{"u":"b9e41c30-858a-4372-8cbf-829e7d247746","d":"eb30084cc2c455ad1615842bddbaca6b0801bc98d8e524610a6dc48ad40c7929eb948f52789c5b985780ea3c5c33341c78a52002b19a540047b3b6918e37b1f2","k":"DbmzPM9CJjp/sW2H6Le66Ju4r2uQ+8NE+ztlzPvgkBk1hOa0ZEAl9mgGsPpKuN2i4jhps0BUUqE6THdvqCa5acFbWB4XJrEd/bgH1h1KMk6+U9lckepr3fr5PKCw7D+U7sSCh+xM6+4/3XL43a5/sTgrP7WuTflGmEZ88RrDT9igHma1ER919LZOBrEgPaKV4fw44IkEQZcay10ZqjvrRsM0g3+fEURz78wY7r9616ABhLfnLoFTmBIKHtWwO6HN24eS97MXEU/nIRGvlqmrH7WuzO+TTTm0m/IhBP2486mbvmAoV653A/sO5EywHtyDa3fO11M7q2qoTSBKwfFLIxEKJK9qbIFJVMZHJ+ihZljpe6aFgofEiTpM2jZHx9WT7OEp3ZCRfWsiD8rsrV1IHgHQcJnI/YeMqM83ybJtaUY4XWe2+7ZFGsSh0g73z2eQXTbJhvcUa7auOS6pEsa6l49Ye6AdPUaX6wES+YU1V2GiZg6QiHMT2+hbK5bEbeGYg6P8vtRXOmKbjmDd0NI+G9wgz40GwrifmgqURi/UGtLNX6xDM0fpc4N7ypK22pIvZycnZJTsHtO6ayXgp1lEbIQP8UNIGXIhZ/6iWEy/+dzg20eRpMk/xSow/d0VxLwypldtu8f5swP1AYeUF+IJE4OkP64AlgNQuHqfn0OwHyY=","iv":"2e85d66e8a944371907673860215a7aa"},{"u":"fd385dac-854d-4df4-ab8f-9470ffc39f86","d":"96b0de2ce736f239d4f533aee518653fd1b4a02eb57d36f9a9a6562ebebdda2e79e80c0f48e0cdeee6968e4ec0449f4962854070fdf57e85f783484a7391d8ab","k":"6LXSzUmzMIl0aoiihO6XhNUCVzrkCrGOdJN/f94ymsZAluPKHYeYnxT91OaxArOI92iAmOCSbbo1VtPkcMdh9jjFezqj4wGV0poGrc+1OKfj7ygE0XZKWA+LfhCejFHj4/xHemk4dM/cXtVJOjWLvWiAvv8eN35aicvw3DNSjf4wLMYMwQmKz6WOl0Dfd7Vix07HyDGUQ9RNCjl9sAZ/JXQIMO6uaW5mCgZWcXNo1MlGIIAE3gLAeLjl36J2N8/PTsPQrp4pDsWIABNkmCqmzc+dQzrBp1NuXwvoLqSpdIMeutmvHZvK5Izrf7TSXcczLfqHBLJUzoDLZHbvo1yG4xDQ54MLEUW51KngDHbvZgLNRhsI+sBs3x6lU0paTm8bhxEUtEd+q8z7A9+bLH9i+Zj6HijZSumPDHIZxwo6B5XsoC9cIG5DW75MbomDhtVBgEGB9Ceg18wyRf9IaKjOvsAKB9TtAwLkwnq1hrBhmhk5dnutUu5ALNhP2QyHC/VzxMCE0whiYxcJqJF7sP5ZBwQ9Ogyz+uCKsoiUYTLKKnR3kB4BzQWO1jA6Z24hveSnt6WUpI/cZgyK3d4Q80BHBM5Uq9NFWmHVEYhMzyI8rz6xkCU3O3ItmDwmMKMlrjKNy6UFnqFltUVN8BoqQSRQldeYnby77I8CvPwaZIEtWws=","iv":"ce645280911a982826f8ee85e9bdc3dd"},{"u":"cb08e958-80b7-466a-9036-941dc3c57004","d":"2a8d688290a3daed9aed02193ec22b49cf7484c897bb331e59270cd8913b508a80f941fa0023315c2754c4c6866655308a0188864c741bcd4a5288fa2c2e427437e31421e7c19b81c212ca7886bcf1da","k":"X31vp76VThvwSTJmz6tyQoCbnC872vRNNFVgYosN9N1YURHUTspj2WIS+wIlFrISubsc8/9bJoPHvM16rNisrG/+qeax6OZMnF+1LCXOzn5UeGHI6X1vlvtz6SZAzAnW+eskBGHvwOQfNJjd0Mqqqu66CiP5kssAJH1tQijQlzHpYs7Fg3LcgAroyAjbr5tkWUPry27+xrlZiKsEoHcMb510MiqPVG1t1bvcN9lsN5uB1cFcdO4qk6EEGPjxMdaWyFVKYy2ztlzgF3mQLR2Alp78L+tZob9t4WnHQNctg+qOdxNzM6haRN5PiYYwpVnq51H2vq+NZygR8QJlz3DCOFg1EFtB3BgiyTfcH94LCXA5Euic/ndz4yBwb2na5NYpQCO6QdRNgy17c7xBRjUIUDxoA9LRlg1xz7z6K9nvb049Z5htk59PtpgfmNpsHNeeQUNRQKsIWj19djorZEIQFr8LmZP5bnJ+4FVTayjHYun4lZD3Td5wCiRykxurnxIHTjE+Sfav1bSC5HSIAt0Voo8MLWCiaXA7eZTY8GOaoVXf6e9wVzrL2D5cNoDvyyA1u9AkGTsp/TNXe2JOyyAod0nOL2AU5vYPJ6pTxVExn/iVw5xfZcCPic5L/eN7FrLuwXcpfC7QFI/o2tKG7CV9HfIlOnL5Vaz7JVwiRvWQ8uc=","iv":"3748759e81a192c18270e5cc6dd99bbb"},{"u":"460cb370-98c7-4c98-90c8-e82ea99a6d86","d":"a9a0104251c0c8806d4d6c362806745e2883229f73b73298ce1c97c48cd44b116f717902de4eb9a3ce318e963186893fbddda1f4cef58039b2dd51628d4b9a72","k":"Tderqbkno7kZ+Y8GVDh70NckKrwU0iyzojczaIzvN1oDXRJj/MEqYNJfrGZASxnTjjX2ZBcQkx7elG9gpylRpt1TnZsq5fYkCAKDy2Il84CT+8UcGcXm92w/qH/GOxBOVlSs34Y4irFoIvySrhuGPXql6846b/+Q24TkER/kufEi1B/Wios9QzEdpx8/n3JqObF72Tzh2Db7bbIPk1DzlxGuRzCQwdsBTSzk+SGqvYM8oDCU4vBOuHYxFIqiDkfRU9i6lk4JJ2ja/8M+j2zHBtU9UiRS6ez/V46B3W8j8A3GqL1THQoX+k3msiA3yAemNz8ho3cF8v3pkpwjGyCxvfXC2/VVtQBE12YFqtjN5LzMwHyoj5N16wXYQrHJ/KkLS5bPhUbVXtHRdZ0QHRnWs2uLzDWebXrmwMVre2C6fekzJxM5fJGEbUTcgwlMCPqZ1U2ilisjGpa3CagulgrhICCHfn/2MNHzvD89s6q0mnzCfKFwHPLoLLSDQxNyplQWt0j5en2QBwAO/orpph8lZYQUmEFT4nK2Cstter0oHhZVZNQ1yD4q2BKaN0kUr6Kv3fCwQpNB8nMbQzd8CMRz/yfg+5jUACHIUgBxR6xKB0yKeG3xk3bI1Awv4KFEZPlXXsNohCPQgNMJswH18vs8/BvwmeFrDSKAOSikOuD3hHw=","iv":"a7a16ee4a8ae0e8a615d55816b9176ac"}],"storage":[{"u":"892f881b-a2e7-4561-9313-0baee1873c3a","d":"WyJaVkZRIiwiSUpOSSIsIjIyWTgiLCI3Rkc4Il0=","t":"usedTokens"},{"u":"38818994-db55-4f97-8a91-85f4365b5a9c","d":"W3sibWFpbCI6InRlc3QzQGRlbm5pc2d1bmlhLmRlIiwibmFtZSI6IlV3ZSJ9LHsibWFpbCI6InRlc3QyQGRlbm5pc2d1bmlhLmRlIiwibmFtZSI6IkthcmwifSx7Im1haWwiOiJ0ZXN0MUBkZW5uaXNndW5pYS5kZSIsIm5hbWUiOiJEZW5uaXMxIn0seyJtYWlsIjoidGVzdDI5QGRlbm5pc2d1bmlhLmRlIiwibmFtZSI6IkRlbm5pczEwIn1d","t":"usedMails"}]}

67
package-lock.json generated
View File

@@ -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",

View File

@@ -5,24 +5,29 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"exec": "ts-node index.ts",
"build": "rm bin/* -f && rm dist/* -Rf && tsc && pkg . --out-path bin --targets linux,macos,win"
},
"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"
}

View File

@@ -1,190 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateToken = void 0;
var fs = __importStar(require("fs"));
var nodemailer = __importStar(require("nodemailer"));
var shuffle_1 = require("./util/shuffle");
var token_1 = require("./util/token");
var Handlebars = __importStar(require("handlebars"));
function generateToken(config, dataSafe) {
var pr = new Promise(function (resolve, error) {
var mailArray = [];
// read and process mail list
var readline = require('readline'), instream = fs.createReadStream(config.inFileMail), outstream = new (require('stream'))(), rl = readline.createInterface(instream, outstream);
rl.on('line', function (line) {
console.log(line);
mailArray.push({
mail: line.substr(0, line.indexOf(";")),
name: line.substr(line.indexOf(";") + 1)
});
});
rl.on('close', function (line) {
// next step
generateCodes(resolve, error, mailArray, config, dataSafe);
});
});
return pr;
}
exports.generateToken = generateToken;
// generate codes
function generateCodes(resolve, error, mailArray, config, dataSafe) {
return __awaiter(this, void 0, void 0, function () {
var codeArray, checkString, listString, i, code;
return __generator(this, function (_a) {
codeArray = [];
checkString = '';
listString = '';
for (i = 0; i < mailArray.length; i++) { // as many codes as adresses
code = token_1.mkstring(4);
while (codeArray.includes(code)) {
code = token_1.mkstring(4);
}
codeArray.push(code);
checkString = checkString + "|" + code;
listString = listString + "\n" + code;
}
checkString = checkString.substr(1);
listString = listString.substr(1);
//write code lists
try {
fs.writeFileSync(config.outFileMatch, checkString);
fs.writeFileSync(config.outFileCodes, listString);
}
catch (error) {
error(error);
}
sendMails(resolve, error, mailArray, codeArray, config, dataSafe);
return [2 /*return*/];
});
});
}
// randomize mails and tokens
function sendMails(resolve, error, mailArray, codeArray, config, dataSafe) {
return __awaiter(this, void 0, void 0, function () {
var mailserver, template, htmlSrc, i;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
mailserver = nodemailer.createTransport(config.mail);
try {
htmlSrc = fs.readFileSync(config.htmlPath, "utf8");
template = Handlebars.compile(htmlSrc);
}
catch (error) {
console.error("Cannote read template file!");
error(error);
}
shuffle_1.shuffleArray(mailArray);
shuffle_1.shuffleArray(codeArray);
i = 0;
_a.label = 1;
case 1:
if (!(i < mailArray.length)) return [3 /*break*/, 4];
// send mail
dataSafe.pushData({
name: mailArray[i].name,
mail: mailArray[i].mail,
code: codeArray[i]
});
return [4 /*yield*/, send(mailArray[i].name, mailArray[i].mail, codeArray[i], template, mailserver, config)];
case 2:
_a.sent();
_a.label = 3;
case 3:
i++;
return [3 /*break*/, 1];
case 4:
resolve(codeArray);
return [2 /*return*/];
}
});
});
}
function send(name, mail, code, template, mailserver, config) {
return __awaiter(this, void 0, void 0, function () {
var html, mailOptions, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
html = template({
"name": name,
"mail": mail,
"code": code
});
mailOptions = {
from: config.mailFrom + " <" + config.mail.auth.user + ">",
to: mail,
subject: "Dein Zugangscode zur BJR Wahl",
html: html
};
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, mailserver.sendMail(mailOptions)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
error_1 = _a.sent();
console.log("Error sendign mail to " + mail + " : " + error_1);
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
});
}

View File

@@ -1,73 +1,73 @@
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<String[]>{
let pr = new Promise<String[]>((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<genReturn>{
return new Promise<genReturn>((resolve,error) => {
parseMails(config, dataSafe).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<any>;
@@ -79,36 +79,73 @@ 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]
})
await send(mailArray[i].name, mailArray[i].mail, codeArray[i],template,mailserver,config);
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);
}
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<any>,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}`)
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
};
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);
});
}

64
src/mailParser.ts Normal file
View File

@@ -0,0 +1,64 @@
import * as fs from 'fs'
import { SecureVault } 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){
mailArray.push({
mail: line.substr(0,ix),
name: line.substr(ix + 1)
})
curCounter ++;
}else{
dataSafe.writeTransaction(` -> already exists. Skipping`);
console.error(`Skipping ${line.substr(0,ix)}: Already sent`)
}
}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);
});
})
}

View File

@@ -1,12 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.shuffleArray = void 0;
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
exports.shuffleArray = shuffleArray;

View File

@@ -1,13 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mkstring = void 0;
function mkstring(length) {
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;
}
exports.mkstring = mkstring;

View File

@@ -1,158 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecureVault = void 0;
var crypto = __importStar(require("crypto"));
var path_1 = __importDefault(require("path"));
var fs = __importStar(require("fs"));
var crypto_1 = require("crypto");
var SecureVault = /** @class */ (function () {
function SecureVault(publicKey, privateKey) {
this.safe = {
items: [],
publicKey: publicKey ? fs.readFileSync(path_1.default.resolve(publicKey)) : undefined,
privateKey: privateKey ? fs.readFileSync(path_1.default.resolve(privateKey)) : undefined
};
this.privPath = publicKey ? path_1.default.resolve(publicKey) : undefined,
this.pubPath = privateKey ? path_1.default.resolve(privateKey) : undefined;
}
SecureVault.prototype.pushData = function (data) {
return __awaiter(this, void 0, void 0, function () {
var txtData, key, iv, cipher, encrypted, buffer, asym_encrypted;
return __generator(this, function (_a) {
txtData = JSON.stringify(data);
key = crypto.randomBytes(32);
iv = crypto.randomBytes(16);
cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
encrypted = cipher.update(txtData);
encrypted = Buffer.concat([encrypted, cipher.final()]);
buffer = new Buffer(key);
if (!this.safe.publicKey) {
throw new Error("Public Key not found");
}
asym_encrypted = crypto.publicEncrypt(this.safe.publicKey, buffer);
this.safe.items.push({
d: encrypted.toString('hex'),
k: asym_encrypted.toString("base64"),
iv: iv.toString('hex')
});
return [2 /*return*/];
});
});
};
SecureVault.prototype.saveData = function (path) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
fs.writeFileSync(path, JSON.stringify(this.safe.items));
return [2 /*return*/];
});
});
};
SecureVault.prototype.loadData = function (path) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
this.safe.items = JSON.parse(fs.readFileSync(path, 'utf8'));
return [2 /*return*/];
});
});
};
SecureVault.prototype.decryptData = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
this.safe.items.forEach(function (el) {
// decrpyt key
var 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
var iv = Buffer.from(el.iv, 'hex');
var encryptedText = Buffer.from(el.d, 'hex');
var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
var decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
var obj = JSON.parse(decrypted.toString());
console.log(obj);
});
return [2 /*return*/];
});
});
};
SecureVault.genKey = function (publicKeyDir, privateKeyDir) {
crypto_1.generateKeyPair('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
}
}, function (err, publicKey, privateKey) {
fs.writeFileSync(privateKeyDir, privateKey);
fs.writeFileSync(publicKeyDir, publicKey);
});
};
return SecureVault;
}());
exports.SecureVault = SecureVault;

View File

@@ -1,14 +1,25 @@
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'
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;
@@ -20,8 +31,10 @@ 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,
@@ -31,7 +44,7 @@ export class SecureVault {
this.pubPath = privateKey ? path.resolve(privateKey): undefined
}
async pushData(data: any): Promise<void>{
async pushData(data: any): Promise<string>{
// encrypt payload
const txtData = JSON.stringify(data);
const key = crypto.randomBytes(32);
@@ -45,20 +58,45 @@ export class SecureVault {
throw new Error("Public Key not found");
}
var asym_encrypted = crypto.publicEncrypt(this.safe.publicKey, buffer);
this.safe.items.push({
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(this.safe.items));
fs.writeFileSync(path, JSON.stringify({
version: vaultVersion,
vault: this.safe.items,
storage: this.storage
}));
}
async loadData(path: string): Promise<void>{
this.safe.items = JSON.parse(fs.readFileSync(path, 'utf8'))
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>{
@@ -98,4 +136,49 @@ export class SecureVault {
});
}
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
});
}
}
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;
}
}
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 = [];
}
}

View File

@@ -1,23 +0,0 @@
Hallo {{name}},<br/><br/>
du wurdest ausgewählt Einfluss auf die Wahl des Bezirks-Jugend-Rats 2020 zu nehmen!<br/>
Du hast das Privileg am Jugendtag unter folgender URL abzustimmen:<br/>
<a href="https://docs.google.com/forms/d/e/1FAIpQLSdHNZSoYTHnS_Tg-BODcwdkXFDaaP1niuj8a_PlADiilMVEIw/viewform?usp=pp_url&entry.220991222={{code}}">https://docs.google.com/forms/d/e/1FAIpQLSdHNZSoYTHnS_Tg-BODcwdkXFDaaP1niuj8a_PlADiilMVEIw/viewform?usp=pp_url&entry.220991222={{code}}</a><br/>
<br/>
Sollte dieser Link nicht funktionieren, verwende bitte den Code <b>{{code}}</b> auf der Seite <a href="https://forms.gle/6WHfptZ7QcbqxBE38">https://forms.gle/6WHfptZ7QcbqxBE38</a>.<br /><br />
Die Wahlen werden im Rahmen des Jugendtags-Sitzung durchgeführt. Der Link wird erst zu diesem Zeitpunk gültig.<br/>
Die Sitzung wird am 10. Oktober 2020 Online über Zoom stattfinden. Link und Uhrzeit hierzu folgen in einer seperaten Mail.<br/><br/>
<font color=#f00><b>WICHTIG: Bitte stimme erst ab, wenn die Anmeldung offiziell eröffnet ist. Alle vorherigen Einsendungen werden nicht berücksichtigt.</b></font><br/>
<font color=#f00><b>WICHTIG: Dies ist deine persönlichen Zugangsdaten. Bitte gib diese nicht weiter.</b></font><br/><br/>
Bei weiteren Fragen zu den Wahlen wende dich bitte an: nyi@nazarener.de
<br/><br/>
Viel Spaß!<br/>
Dein BJR<br/>
<br/>
<img src="https://cdn.dennisgunia.de/nyi_small_dark.png" width="50px"><br><hr><br>
<font color=darkgray>
Wenn Sie nicht der richtige Adressat sind, oder diese E-Mail irrtümlich erhalten haben, informieren Sie bitte den Absender und löschen Sie diese Mail.<br>
Dies ist eine automatisch generierte Mail bitte antworten sie nicht auf diese e-mail sondern wenden sie sich bei Rückfragen direkt an nyi@nazarener.de
</font>