diff --git a/.gitignore b/.gitignore
index a9e5b9e..90dd4f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ deploy.sh
package-lock.json
launch.json
firebase.json
+pnpm-lock.yaml
diff --git a/index.js b/index.js
index bbb7aad..7084cbf 100644
--- a/index.js
+++ b/index.js
@@ -14,10 +14,10 @@ const express = require('express'),
https = require('https'),
httpsServer = ((!srv_config.DEBUG && srv_config.CHAIN_PATH &&
srv_config.PRIVATE_KEY_PATH && srv_config.CERTIFICATE_PATH) ? https.createServer({
- ca: fs.readFileSync(srv_config.CHAIN_PATH, 'utf-8'),
- key: fs.readFileSync(srv_config.PRIVATE_KEY_PATH, 'utf-8'),
- cert: fs.readFileSync(srv_config.CERTIFICATE_PATH, 'utf-8')
- }, app) : false),
+ ca: fs.readFileSync(srv_config.CHAIN_PATH, 'utf-8'),
+ key: fs.readFileSync(srv_config.PRIVATE_KEY_PATH, 'utf-8'),
+ cert: fs.readFileSync(srv_config.CERTIFICATE_PATH, 'utf-8')
+ }, app) : false),
Rollbar = require('rollbar'),
rollbar = ((srv_config.ROLLBAR_TOKEN) ? new Rollbar({
accessToken: srv_config.ROLLBAR_TOKEN,
@@ -32,7 +32,7 @@ const express = require('express'),
getApiVersion: () => '2',
skip: req => {
const path = req.path.toLowerCase();
-
+
return path === '/location' || path === '/debug' || path === '/soc' || path === '/extended'
}
}) : false),
@@ -105,7 +105,7 @@ app.use((req, res, next) => {
// set default headers
app.use((req, res, next) => {
- res.contentType('application/json');
+ //res.contentType('application/json');
res.setHeader('Access-Control-Allow-Origin', ((!req.get('origin') || req.get('origin') === 'null') ? '*' : req.get('origin')));
res.setHeader('Access-Control-Allow-Credentials', 'true');
next();
@@ -118,6 +118,8 @@ app.post('/login', account.login);
app.post('/changepw', account.changePW);
app.get('/settings', settings.getSettings);
app.put('/settings', settings.setSettings);
+app.use('/verify/:id', express.static('static/verify'))
+app.post('/verify/:id', settings.verifyMail);
app.post('/soc', sync.postSoC);
app.get('/soc', sync.getSoC);
app.post('/extended', sync.postExtended);
@@ -148,7 +150,7 @@ app.post('/debug', (req, res) => {
req.body.data, req.body.akey, ((parseInt(req.body.timestamp)) ? req.body.timestamp : parseInt(new Date() / 1000))
], (err, dbRes) => {
if (!err && dbRes) {
- res.json({status: true});
+ res.json({ status: true });
} else {
res.status(422).json({
error: srv_errors.UNPROCESSABLE_ENTITY,
diff --git a/modules/db/db_template.sql b/modules/db/db_template.sql
index d0337ae..ee6ed36 100644
--- a/modules/db/db_template.sql
+++ b/modules/db/db_template.sql
@@ -1,3 +1,10 @@
+CREATE TABLE IF NOT EXISTS `system` (
+ `key` VARCHAR(20) NOT NULL PRIMARY KEY,
+ `value` VARCHAR(20) NOT NULL
+);
+
+INSERT INTO `system` VALUES("version", 1) ON DUPLICATE KEY UPDATE `value`=VALUES(`value`);
+
-- accounts table structure
CREATE TABLE IF NOT EXISTS `accounts` (
`akey` VARCHAR(6) NOT NULL PRIMARY KEY,
@@ -11,7 +18,6 @@ CREATE TABLE IF NOT EXISTS `settings` (
`user` VARCHAR(6) NOT NULL,
`akey` VARCHAR(6) NOT NULL,
`webhook` VARCHAR(100) DEFAULT NULL,
- `email` VARCHAR(1000) DEFAULT NULL,
`telegram` INT(100) DEFAULT 0,
`abrp` VARCHAR(36) DEFAULT NULL,
`summary` TINYINT(1) DEFAULT 0,
@@ -25,6 +31,23 @@ CREATE TABLE IF NOT EXISTS `settings` (
FOREIGN KEY (`akey`) REFERENCES `accounts`(`akey`)
);
+CREATE TABLE IF NOT EXISTS `notificationMail` (
+ `akey` VARCHAR(6) NOT NULL,
+ `mail` VARCHAR(1000) NOT NULL,
+ `verified` BOOLEAN NOT NULL DEFAULT FALSE,
+ `identifier` BINARY(16) NOT NULL,
+ PRIMARY KEY (`akey`),
+ UNIQUE KEY (`identifier`),
+ FOREIGN KEY (`akey`) REFERENCES `accounts`(`akey`)
+);
+
+CREATE TABLE IF NOT EXISTS `mailLock` (
+ `hash` BINARY(32) NOT NULL,
+ `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `weight` INTEGER NOT NULL DEFAULT 1,
+ PRIMARY KEY (`hash`)
+);
+
-- sync table structure
CREATE TABLE IF NOT EXISTS `sync` (
`user` VARCHAR(6) NOT NULL,
diff --git a/modules/db/index.js b/modules/db/index.js
index d6e32fb..3184e32 100644
--- a/modules/db/index.js
+++ b/modules/db/index.js
@@ -26,12 +26,18 @@ module.exports = {
if (typeof params === 'function') callback = params;
if (!Array.isArray(params)) params = [];
if (typeof sql === 'string') {
- if (params.length>0 && Array.isArray(params[0])) {
+ if (params.length > 0 && Array.isArray(params[0])) {
return db.query(sql, params, callback);
- }else {
+ } else {
return db.query(mysql.format(sql, params), callback);
}
} else if (typeof callback === 'function') callback(srv_errors.INVALID_PARAMETERS);
},
+ /**
+ * Gets a database connection from the pool. Requested connections have to be released manually!
+ */
+ getConnection: (callback) => {
+ db.getConnection(callback);
+ },
close: callback => db.end(callback),
};
\ No newline at end of file
diff --git a/modules/db/upgrades/1.sql b/modules/db/upgrades/1.sql
new file mode 100644
index 0000000..3cb7d91
--- /dev/null
+++ b/modules/db/upgrades/1.sql
@@ -0,0 +1,25 @@
+CREATE TABLE IF NOT EXISTS `system` (
+ `key` VARCHAR(20) NOT NULL PRIMARY KEY,
+ `value` VARCHAR(20) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS `notificationMail` (
+ `akey` VARCHAR(6) NOT NULL PRIMARY KEY,
+ `mail` VARCHAR(1000) NOT NULL,
+ `verified` BOOLEAN NOT NULL DEFAULT FALSE,
+ `identifier` BINARY(16) NOT NULL,
+ UNIQUE KEY (`identifier`),
+ FOREIGN KEY (`akey`) REFERENCES `accounts`(`akey`)
+);
+
+CREATE TABLE IF NOT EXISTS `mailLock` (
+ `hash` BINARY(32) NOT NULL PRIMARY KEY,
+ `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `weight` INTEGER NOT NULL DEFAULT 1
+);
+
+INSERT INTO `notificationMail` SELECT `akey`,`email`,TRUE,UNHEX(MD5(RAND())) FROM `settings` WHERE email IS NOT NULL;
+
+ALTER TABLE `settings`DROP COLUMN `email`;
+
+INSERT INTO `system` VALUES("version", 1);
\ No newline at end of file
diff --git a/modules/db/upgrades/upgradeSql.js b/modules/db/upgrades/upgradeSql.js
new file mode 100644
index 0000000..a3b8288
--- /dev/null
+++ b/modules/db/upgrades/upgradeSql.js
@@ -0,0 +1,43 @@
+const db = require('..');
+const path = require('path');
+const fs = require('fs')
+
+fs.readdir(".", function (err, files) {
+ if (err) {
+ return console.err('Unable to scan directory: ' + err);
+ }
+ var max = files.reduce((prev, file) => {
+ var part = file.split(".")[0];
+ if (!isNaN(part)) {
+ return Math.max(prev, parseInt(part));
+ }
+ return prev;
+ }, 0);
+ if (!max) return console.error('did not find any upgrade files in current directory');
+
+ db.getConnection((err, connection) => {
+ if (err) {
+ connection.release();
+ console.error('unable to get connection', err);
+ return;
+ }
+ connection.doQuery = require('util').promisify(connection.query);
+ connection.doBeginTransaction = require('util').promisify(connection.beginTransaction);
+ connection.doBeginTransaction()
+ .then(() => connection.doQuery('CREATE TABLE IF NOT EXISTS `system` (`key` VARCHAR(20) NOT NULL PRIMARY KEY, `value` VARCHAR(20) NOT NULL)'))
+ .then(() => connection.doQuery('SELECT value FROM system WHERE "key"="version"'))
+ .then(res => res.length == 0 ? 0 : res.value)
+ .then(currentVersion => [...Array(max - currentVersion)].reduce((p, _, i) => p.then(() => {
+ const content = fs.readFileSync((1 + i + currentVersion) + ".sql", "utf-8");
+ var queries = content.replace(/[\r\n]+/g, ' ').split(";");
+ return queries.reduce((p, q) => p.then(() => connection.doQuery(q)), Promise.resolve());
+ }), Promise.resolve()))
+ .then(connection.commit())
+ .catch(err => {
+ connection.rollback();
+ console.error(err);
+ })
+ .then(() => connection.release())
+ .then(() => db.close());
+ });
+});
diff --git a/modules/notification/index.js b/modules/notification/index.js
index 401b54b..4eaf889 100644
--- a/modules/notification/index.js
+++ b/modules/notification/index.js
@@ -26,8 +26,8 @@ const send = (req, res) => {
});
}
// retrieve required information
- db.query('SELECT accounts.akey, token, car, email, telegram, push, lng, soc_display, soc_bms, consumption, last_notification FROM accounts \
- INNER JOIN sync ON accounts.akey=sync.akey INNER JOIN settings ON settings.akey=accounts.akey WHERE accounts.akey=?', [
+ db.query('SELECT accounts.akey, token, car, mail as email, telegram, push, lng, soc_display, soc_bms, consumption, last_notification FROM accounts \
+ INNER JOIN sync ON accounts.akey=sync.akey INNER JOIN settings ON settings.akey=accounts.akey LEFT JOIN notificationMail ON notificationMail.akey=accounts.akey AND verified=TRUE WHERE accounts.akey=?', [
req.body.akey
], (err, dbRes) => {
if (!err && dbRes && (userObj = dbRes[0]) != null) {
diff --git a/modules/notification/mail/index.js b/modules/notification/mail/index.js
index 91d3ade..d8b3e5e 100644
--- a/modules/notification/mail/index.js
+++ b/modules/notification/mail/index.js
@@ -4,14 +4,18 @@
* @description Mail notification module
*/
const nodemailer = require('nodemailer'),
+ db = require('./../../db'),
srv_config = require('./../../../srv_config.json'),
srv_errors = require('./../../../srv_errors.json'),
encryption = require('./../../encryption'),
helper = require('./../../helper'),
translation = require('./../../translation');
-const transporter = ((!srv_config.MAIL_SERVICE || !srv_config.MAIL_HOST || !srv_config.MAIL_PORT || !srv_config.MAIL_USER ||
- !srv_config.MAIL_PASSWORD || !srv_config.MAIL_ADDRESS) ? null :
+const doQuery = require('util').promisify(db.query);
+const getRandomBytes = require('util').promisify(require('crypto').randomBytes);
+
+const transporter = (((!srv_config.MAIL_SERVICE && (!srv_config.MAIL_HOST || !srv_config.MAIL_PORT)) || !srv_config.MAIL_USER ||
+ !srv_config.MAIL_PASSWORD || !srv_config.MAIL_ADDRESS) ? null :
nodemailer.createTransport({
host: srv_config.MAIL_HOST,
port: srv_config.MAIL_PORT,
@@ -46,14 +50,14 @@ const sendMail = (userObj, abort) => {
SOC: ((
userObj.soc_display == null) ? SOC_BMS : ((
userObj.soc_bms == null) ?
- SOC_DISPLAY : SOC_DISPLAY))
+ SOC_DISPLAY : SOC_DISPLAY))
}, // use only defined values for text
textObj = {
SOC: ((
userObj.soc_display == null) ? '' + SOC_BMS + ' (BMS)' : ((
userObj.soc_bms == null) ?
- '' + SOC_DISPLAY + ' (Display)' : '' + SOC_DISPLAY + ' (Display) / ' + SOC_BMS + ' (BMS)')),
+ '' + SOC_DISPLAY + ' (Display)' : '' + SOC_DISPLAY + ' (Display) / ' + SOC_BMS + ' (BMS)')),
RANGE: helper.calculateRange(userObj.car, userObj.soc_display || userObj.soc_bms, userObj.consumption) + 'km'
};
@@ -108,9 +112,86 @@ const simpleSend = (mail, subject, html, attachments, callback) => {
});
};
+const setMail = (userObj, mail, callback) => {
+ const akey = userObj.akey;
+ if (!mail) {
+ return doQuery('DELETE FROM notificationMail WHERE akey=?', [akey]).then(() => callback(false)).catch(callback);
+ }
+ if (!validateMail(mail)) {
+ if (typeof callback === 'function') callback(srv_errors.INVALID_PARAMETERS);
+ return;
+ }
+ doQuery('SELECT mail FROM notificationMail WHERE akey=? AND verified=TRUE', [akey])
+ .then(result => {
+ if (result.length > 0 && encryption.decrypt(result[0].mail) === mail) return Promise.reject(srv_errors.CURRENT_MAIL);
+ return true;
+ })
+ .then(() => module.exports.checkMailUnlocked(mail))
+ .then(() => getRandomBytes(16))
+ .then(id => new Promise((res, rej) => {
+ module.exports.simpleSend(mail, translation.translate('MAIL_SUBJECT_VERIFY', userObj.lng, true),
+ translation.translateWithData('MAIL_TEXT_VERIFY', userObj.lng, { BASE_URL: srv_config.BASE_URL, ID: id.toString('hex') }, true), null, (err) => {
+ if (err) {
+ if (err.responseCode === 550) return rej(srv_errors.INVALID_MAIL)
+ return rej(err);
+ }
+ return res(id);
+ });
+ }))
+ .then(id => doQuery('INSERT INTO notificationMail(akey,mail,verified,identifier) VALUES(?,?,false,?) ON DUPLICATE KEY UPDATE mail=VALUES(mail), verified=false, identifier=VALUES(identifier)', [akey, encryption.encrypt(mail), id]))
+ .then(() => module.exports.updateMailLock(mail))
+ .then(() => callback(false)).catch(callback);
+};
+
+const verifyMail = (identifier, callback) => {
+ doQuery('UPDATE notificationMail SET verified=TRUE WHERE identifier=UNHEX(?)', [identifier]).then(queryResult => {
+ if (queryResult.affectedRows !== 1) return Promise.reject(srv_errors.NOT_FOUND);
+ if (queryResult.changedRows !== 1) return Promise.reject(srv_errors.CONFLICT);
+ return true;
+ }).then(() => callback(false)).catch(callback);
+}
+
+const checkMailUnlocked = mail => {
+ return doQuery('SELECT time FROM mailLock WHERE hash=UNHEX(SHA2(?,256))', [mail]).then(dbRes => {
+ if (dbRes.length === 0) return mail;
+ if (dbRes[0].time.getTime() <= new Date().getTime()) return mail;
+ throw new Error('Recipient is currently locked')
+ });
+}
+
+const updateQuery = `INSERT INTO
+ mailLock
+ SELECT
+ hash,
+ TIMESTAMPADD(MINUTE, POWER(weight + 1, 2), CURRENT_TIMESTAMP()) as time,
+ weight + 1 as weight
+ FROM
+ mailLock
+ WHERE
+ hash = UNHEX(SHA2(?, 256))
+ AND time > TIMESTAMPADD(MINUTE, - POWER(weight + 1, 3), CURRENT_TIMESTAMP())
+ UNION
+ SELECT
+ UNHEX(SHA2(?, 256)),
+ CURRENT_TIMESTAMP() as time,
+ 1 as weight
+ LIMIT
+ 1
+ ON DUPLICATE KEY UPDATE
+ time = VALUES(time),
+ weight = values(weight)
+ `
+const updateMailLock = mail => {
+ return doQuery(updateQuery, [mail, mail]);
+}
+
module.exports = {
validateMail,
sendMail,
sendQRMail,
- simpleSend
+ simpleSend,
+ checkMailUnlocked,
+ updateMailLock,
+ setMail,
+ verifyMail,
};
\ No newline at end of file
diff --git a/modules/settings/index.js b/modules/settings/index.js
index 94eb9ad..53229e7 100644
--- a/modules/settings/index.js
+++ b/modules/settings/index.js
@@ -7,7 +7,8 @@ const srv_config = require('./../../srv_config.json'),
srv_errors = require('./../../srv_errors.json'),
db = require('./../db'),
token = require('./../token'),
- encryption = require('./../encryption');
+ encryption = require('./../encryption'),
+ mail = require('./../notification/mail');
/**
* Retrieves settings from database for given akey
@@ -15,7 +16,7 @@ const srv_config = require('./../../srv_config.json'),
* @param {Function} callback callback function
*/
const getSettings = (akey, callback) => {
- db.query('SELECT email, telegram, abrp, push, soc, consumption, car, device, lng, summary FROM settings WHERE akey=?', [
+ db.query('SELECT verified as emailVerified, mail as email, telegram, abrp, push, soc, consumption, car, device, lng, summary FROM settings LEFT JOIN notificationMail ON notificationMail.akey=settings.akey WHERE settings.akey=?', [
akey
], (err, dbRes) => {
if (!err && dbRes && dbRes[0] && dbRes[0].email) dbRes[0].email = encryption.decrypt(dbRes[0].email);
@@ -30,8 +31,7 @@ const getSettings = (akey, callback) => {
* @param {Function} callback callback function
*/
const setSettings = (akey, settings, callback) => {
- db.query('UPDATE settings SET email=?, telegram=?, push=?, soc=?, consumption=?, car=?, device=?, lng=?, summary=? WHERE akey=?', [
- ((settings.email) ? encryption.encrypt(settings.email) : ''),
+ db.query('UPDATE settings SET telegram=?, push=?, soc=?, consumption=?, car=?, device=?, lng=?, summary=? WHERE akey=?', [
settings.telegram,
settings.push,
settings.soc,
@@ -41,7 +41,13 @@ const setSettings = (akey, settings, callback) => {
settings.lng,
settings.summary,
akey
- ], (err, dbRes) => callback(err, ((!err && dbRes && dbRes[0]) ? dbRes[0] : null)));
+ ], (err) => {
+ if (err) callback(err);
+ mail.setMail({ akey: akey, lng: settings.lng }, settings.email, (err) => {
+ if (err && err.code !== 1102) return callback(err);
+ callback(false);
+ });
+ });
};
module.exports = {
@@ -111,10 +117,19 @@ module.exports = {
settings: req.body.settings
});
} else {
- res.status(422).json({
- error: srv_errors.UNPROCESSABLE_ENTITY,
- debug: ((srv_config.DEBUG) ? err : null)
- });
+ if (err.code && err.message && typeof err.code === "number") {
+ if (!err.status) {
+ err.status = err.code < 600 ? err.code : 400;
+ }
+ var debug = ((srv_config.DEBUG) ? err.debug : null)
+ delete err.debug;
+ return res.status(err.status).json({ error: err, debug });
+ } else {
+ res.status(500).json({
+ error: srv_errors.INTERNAL_SERVER_ERROR,
+ debug: ((srv_config.DEBUG) ? err : null)
+ });
+ }
}
});
} else {
@@ -130,5 +145,27 @@ module.exports = {
});
}
});
+ },
+
+ verifyMail: (req, res) => {
+ if (!req.params.id) {
+ return res.status(400).json({
+ error: srv_errors.INVALID_PARAMETERS
+ });
+ }
+ mail.verifyMail(req.params.id, (err) => {
+ if (err) {
+ if (err.code && err.message) {
+ var debug = ((srv_config.DEBUG) ? err.debug : null)
+ err.debug = null;
+ return res.status(err.code).json({ error: err, debug });
+ }
+ return res.status(500).json({
+ error: srv_errors.INTERNAL_SERVER_ERROR,
+ debug: ((srv_config.DEBUG) ? err : null)
+ });
+ }
+ res.status(204).send();
+ });
}
};
diff --git a/modules/translation/lng/de.json b/modules/translation/lng/de.json
index 746fb5b..9403918 100644
--- a/modules/translation/lng/de.json
+++ b/modules/translation/lng/de.json
@@ -29,5 +29,7 @@
"TELEGRAM_NOTIFICATION_MESSAGE": "Hallo! Ich wollte Dir nur kurz Bescheid geben, dass Dein Elektroauto den gewünschten Ladezustand von {SOC} erreicht hat! Damit wirst Du ungefähr {RANGE} weit fahren können.",
"TELEGRAM_NOTIFICATION_ABORT_MESSAGE": "Hallo! Es scheint so, als sei der Ladevorgang bei {SOC} abgebrochen worden oder es gab einen Fehler bei der Kommunikation mit dem Auto. Am besten mal nachschauen!",
"MAIL_SUBJECT_QR": "QRNotify: Jemand möchte laden",
- "MAIL_TEXT_QR": "Dein QR Code von QRNotify, welcher in EVNotify generiert wurde, wurde soeben eingescannt. Jemand möchte laden. Wenn möglich, kannst Du die Ladesäule frei machen."
-}
+ "MAIL_TEXT_QR": "Dein QR Code von QRNotify, welcher in EVNotify generiert wurde, wurde soeben eingescannt. Jemand möchte laden. Wenn möglich, kannst Du die Ladesäule frei machen.",
+ "MAIL_SUBJECT_VERIFY": "Verifizierung der E-Mail-Adresse für EVNotify",
+ "MAIL_TEXT_VERIFY": "Hallo,
Um Benachrichtigungen von EVNotify zu erhalten, muss zuerst die E-Mail-Adresse verifiziert werden. Klicke hier oder öffne den folgenden Link um diesen Prozess abzuschließen: {BASE_URL}/verify/{ID}/de.html"
+}
\ No newline at end of file
diff --git a/modules/translation/lng/en.json b/modules/translation/lng/en.json
index 8c35543..a042547 100644
--- a/modules/translation/lng/en.json
+++ b/modules/translation/lng/en.json
@@ -29,5 +29,7 @@
"TELEGRAM_NOTIFICATION_MESSAGE": "Hello! I just wanted to notify you that your electric vehicle now has reached the desired charging status of {SOC}%! With that, you can drive about {RANGE}.",
"TELEGRAM_NOTIFICATION_ABORT_MESSAGE": "Hello! It seems as if the charging process had been interrupted or there was an error within the communication with the car. Better have a look!",
"MAIL_SUBJECT_QR": "QRNotify: Someone wants to charge",
- "MAIL_TEXT_QR": "Your QR code of QRNotify generated in EVNotify has just been scanned. Someone wants to charge. If possible, you can clear the charging station."
+ "MAIL_TEXT_QR": "Your QR code of QRNotify generated in EVNotify has just been scanned. Someone wants to charge. If possible, you can clear the charging station.",
+ "MAIL_SUBJECT_VERIFY": "Please verify your email address for EVNotify",
+ "MAIL_TEXT_VERIFY": "Hello,
In order to receive notifications from EVNotify, we need you to verify your email address. Klick here or open the following link to complete this process: {BASE_URL}/verify/{ID}/en.html"
}
\ No newline at end of file
diff --git a/package.json b/package.json
index dc5e9bb..d5c3c7b 100644
--- a/package.json
+++ b/package.json
@@ -25,10 +25,14 @@
"rollbar": "2.8.1",
"stringinject": "2.1.1"
},
- "devDependencies": {},
+ "devDependencies": {
+ "chai-as-promised": "^7.1.1",
+ "chai-datetime": "^1.5.0",
+ "sinon": "^7.3.2"
+ },
"scripts": {
"pretest": "npm run createEnv",
- "test": "mocha tests/*",
+ "test": "mocha tests --recursive",
"start": "node server.js",
"createEnv": "/bin/bash createEnv.sh"
},
diff --git a/srv_errors.json b/srv_errors.json
index d52af80..a245c94 100644
--- a/srv_errors.json
+++ b/srv_errors.json
@@ -1,5 +1,6 @@
{
"BAD_REQUEST": {
+ "status": 400,
"code": 400,
"message": "Bad request. Your request could not be processed. This has been automatically reported."
},
@@ -11,6 +12,10 @@
"code": 404,
"message": "Requested source not found."
},
+ "CONFLICT": {
+ "code": 409,
+ "message": "Request conflicts with the current state"
+ },
"UNPROCESSABLE_ENTITY": {
"code": 422,
"message": "Your request could not be processed. This is either a client side error or a server error."
@@ -24,34 +29,52 @@
"message": "Internal server error occured. It has been automatically reported."
},
"UNKNOWN_ROUTE": {
+ "status": 404,
"code": 1000,
"message": "Requested route does not exist. Unable to handle request."
},
"INVALID_PARAMETERS": {
+ "status": 422,
"code": 1100,
"message": "Missing or invalid parameters."
},
+ "INVALID_MAIL": {
+ "status": 422,
+ "code": 1101,
+ "message": "Missing or invalid target mail address."
+ },
+ "CURRENT_MAIL": {
+ "status": 409,
+ "code": 1102,
+ "message": "The target mail address is already the current mail address for this user."
+ },
"DB_QUERY": {
+ "status": 500,
"code": 1200,
"message": "Error on database query."
},
"MALFORMED_AKEY": {
+ "status": 422,
"code": 1300,
"message": "AKey format is not valid. Must be a string with 6 characters."
},
"MALFORMED_PASSWORD": {
+ "status": 422,
"code": 1400,
"message": "Password must be a string with at least 6 characters and less than 72 characters."
},
"ALREADY_REGISTERED": {
+ "status": 409,
"code": 1500,
"message": "Requested AKey already registered."
},
"HASH_FAILED": {
+ "status": 500,
"code": 1600,
"message": "Generation of password hash failed."
},
"USER_NOT_EXISTING": {
+ "status": 404,
"code": 1700,
"message": "Requested user does not exist."
},
@@ -64,6 +87,7 @@
"message": "Provided token is invalid or no longer valid."
},
"MAIL_ALREADY_REGISTERED": {
+ "status": 409,
"code": 2000,
"message": "Requested Mail already registered."
},
diff --git a/static/verify/de.html b/static/verify/de.html
new file mode 100644
index 0000000..1eb0f2d
--- /dev/null
+++ b/static/verify/de.html
@@ -0,0 +1,17 @@
+
+
+