From 14865114632d16a7c077dc4b4021ec6c9b51fa27 Mon Sep 17 00:00:00 2001 From: RedDucks Date: Wed, 28 Nov 2018 14:49:31 -0500 Subject: [PATCH 1/2] Starting bringing over a better comment scheme for methods (see helpers/api.js), addedemail confirmation sending, proper logging, fixed registration Server now sends confirmation emails to the provided email address. The values can not yet be validated, but they are stored so validation can be added at any time. Added Winston for event logging. Winston has a log level system that merges certain log types into the same file. Maybe we should use a custom logger solution? Registration now works (was not working for me), with added reCaptcha protection, password validation, and the storing of usernames/email validation values. Also changed the PID to a number, cuz it's a number --- .eslintignore | 2 +- config.example.json | 7 ++ helpers/api.js | 65 ++++++------ helpers/util.js | 10 +- logger.js | 41 ++++++++ mailer.js | 31 ++++++ models/pnid.js | 59 +++++++++-- package-lock.json | 235 +++++++++++++++++++++++++++++++++++++++++++- package.json | 5 +- routes/blog.js | 3 +- routes/contact.js | 3 +- routes/pnid.js | 54 +++++++--- server.js | 4 +- views/register.hbs | 15 ++- 14 files changed, 465 insertions(+), 69 deletions(-) create mode 100644 logger.js create mode 100644 mailer.js diff --git a/.eslintignore b/.eslintignore index f6d6f01..139229b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ # web javascript causes a lot of eslint warnings -assets/js/*.js \ No newline at end of file +assets/js \ No newline at end of file diff --git a/config.example.json b/config.example.json index 89ab907..0fcfe1f 100644 --- a/config.example.json +++ b/config.example.json @@ -13,6 +13,13 @@ "secrets": { "session": "session secret here" }, + "email": { + "service": "gmail", + "auth": { + "user": "example@email.com", + "pass": "password" + } + }, "recaptcha": { "siteKey": "abcd", "secretKey": "1234" diff --git a/helpers/api.js b/helpers/api.js index 977933c..0f37cd4 100644 --- a/helpers/api.js +++ b/helpers/api.js @@ -5,10 +5,14 @@ common api returns */ -// use for any api return. it has basic layout used for every return. +/** + * Send generic API response + * @param {ServerResponse} response An express ServerResponse response + * @param {Object} data Response data + * @param {Array} errors Request errors + */ function sendReturn(response, data, errors) { - response.status(200).json( - // combine 2 objects + return response.status(data.code || 200).json( Object.assign({ code: 200, success: true, @@ -17,44 +21,45 @@ function sendReturn(response, data, errors) { ); } -// use if api endpoint doesnt exist +/** + * Send API 404 + * @param {ServerResponse} response An express ServerResponse response + */ function sendApi404(response) { - response.status(404).json({ - code: 404, - errors: [ - 'Endpoint not in use' - ] - }); + return sendApiError(response, 404, [ + 'Endpoint not in use' + ]); } -// use if not logged in and is required (handled with middleware) +/** + * Send user not authenticated error + * @param {ServerResponse} response An express ServerResponse response + */ function sendApiAuthError(response) { - response.status(401).json({ - code: 401, - errors: [ - 'Not authenticated' - ] - }); + return sendApiError(response, 401, [ + 'Not authenticated' + ]); } -// use for completely broken requests +/** + * Send a generic API error + * @param {ServerResponse} response An express ServerResponse response + */ function sendApiGenericError(response) { - response.status(400).json({ - code: 400, - success: false, - errors: [ - 'Bad request' - ] - }); + return sendApiError(response, 400, [ + 'Bad request' + ]); } -// use for any api not successfull +/** + * Send an API error + * @param {ServerResponse} response An express ServerResponse response + */ function sendApiError(response, code, errors) { - response.status(code).json({ + return sendReturn(response, { code, - success: false, - errors - }); + success: false + }, errors); } module.exports = { diff --git a/helpers/util.js b/helpers/util.js index 66e7a05..cf71125 100644 --- a/helpers/util.js +++ b/helpers/util.js @@ -6,6 +6,7 @@ small commonly used utilities */ const fs = require('fs-extra'); +const logger = require('winston'); // shows 404 template. takes express response object function send404(res) { @@ -46,7 +47,7 @@ function getLocale(region, language) { return require(path); } - console.warn(`Could not find locale ${region}_${language}! Loading default`); + logger.log('warn', `Could not find locale ${region}_${language}! Loading default`); return getDefaultLocale(); } @@ -56,10 +57,15 @@ function getDefaultLocale(locale='default') { return require(`${__dirname}/../locales/${locale}.json`); } +function generateRandomInt(length = 4) { + return Math.floor(Math.pow(10, length-1) + Math.random() * 9 * Math.pow(10, length-1)); +} + module.exports = { send404, templateReadyUser, getLocales, getLocale, - getDefaultLocale + getDefaultLocale, + generateRandomInt }; \ No newline at end of file diff --git a/logger.js b/logger.js new file mode 100644 index 0000000..327e92d --- /dev/null +++ b/logger.js @@ -0,0 +1,41 @@ +const winston = require('winston'); + +const logger = winston.createLogger({ + level: 'verbose', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.printf(log => { + return `${log.timestamp} | ${log.level}: ${log.message}`; + }) + ), + transports: [ + new winston.transports.Console({ colorize: true }), + new winston.transports.File({ + colorize: false, + json: false, + filename: `${__dirname}/logs/events.log` + }), + new winston.transports.File({ + colorize: false, + json: false, + filename: `${__dirname}/logs/error.log`, + level: 'error' + }), + new winston.transports.File({ + colorize: false, + json: false, + filename: `${__dirname}/logs/warn.log`, + level: 'warn' + }), + new winston.transports.File({ + colorize: false, + json: false, + filename: `${__dirname}/logs/debug.log`, + level: 'debug' + }) + ] +}); + +winston.add(logger); + +module.exports = winston; \ No newline at end of file diff --git a/mailer.js b/mailer.js new file mode 100644 index 0000000..c694e49 --- /dev/null +++ b/mailer.js @@ -0,0 +1,31 @@ +const nodemailer = require('nodemailer'); +const config = require('./config'); + +const transporter = nodemailer.createTransport(config.email); + +/** + * Sends an email with the specified subject and message to an email address + * @param {String} email the destination email address + * @param {String} subject The Subject of the email + * @param {String} message The body of the email + */ +function send(email, subject = 'No email subject provided', message = 'No email body provided') { + const options = { + from: config.email.address, + to: email, + subject: subject, + html: message + }; + + transporter.sendMail(options, (error, info) => { + if (error) { + console.warn(error); + } else { + console.log(info); + } + }); +} + +module.exports = { + send: send +}; \ No newline at end of file diff --git a/models/pnid.js b/models/pnid.js index e92b242..5fc2f94 100644 --- a/models/pnid.js +++ b/models/pnid.js @@ -8,7 +8,9 @@ file containing the model for pretendo network id's // imports const mongoose = require('mongoose'); const uniqueValidator = require('mongoose-unique-validator'); +const randtoken = require('rand-token'); const bcrypt = require('bcrypt'); +const utilHelper = require('../helpers/util'); // admin user database layout const PNIDSchema = new mongoose.Schema({ @@ -23,6 +25,14 @@ const PNIDSchema = new mongoose.Schema({ type: Boolean, default: false }, + email_validation_code: { + type: Number, + required: [true, 'Email validation code is required.'], + }, + email_validation_token: { + type: String, + required: [true, 'Email validation token is required.'], + }, // non hashed, gets hashed at save password: { type: String, @@ -32,10 +42,15 @@ const PNIDSchema = new mongoose.Schema({ trim: true }, pnid: { - key: { - type: String // not sure what this should be - }, pid: { + type: Number, + unique: true + }, + username: { + type: String, + unique: true + }, + username_lower: { type: String, unique: true } @@ -79,7 +94,7 @@ PNIDSchema.statics.hashPasswordPrimary = function(password, pid) { const buff1 = require('python-struct').pack(' { blogPostModel.getPost(moment(date), title_lower, (error, post) => { // error exists or no post exists with the date and name if (error || !post) { - console.warn(`'error: ${error} and post: ${post}`); + logger.log('warn', `error: ${error} and post: ${post}`); return utilHelper.send404(response); } diff --git a/routes/contact.js b/routes/contact.js index 8871bea..27fb7d0 100644 --- a/routes/contact.js +++ b/routes/contact.js @@ -6,6 +6,7 @@ file for handling routes regarding contact */ // imports +const logger = require('winston'); const router = require('express').Router(); const apiHelper = require('../helpers/api'); const utilHelper = require('../helpers/util'); @@ -69,7 +70,7 @@ router.post('/api/v1/sendmessage', (req, response) => { // error handling request.on('error', (error) => { - console.warn('request errored' + error); + logger.log('warn', 'request errored' + error); return apiHelper.sendApiGenericError(response); }); diff --git a/routes/pnid.js b/routes/pnid.js index 98090b1..473a0f8 100644 --- a/routes/pnid.js +++ b/routes/pnid.js @@ -6,11 +6,13 @@ file for handling admin api. */ // imports +const logger = require('winston'); const router = require('express').Router(); const passport = require('passport'); const userMiddleware = require('../middleware/authentication'); const apiHelper = require('../helpers/api'); const utilHelper = require('../helpers/util'); +const mailer = require('../mailer'); const config = require('../config.json'); const Recaptcha = require('express-recaptcha').Recaptcha; const recaptcha = new Recaptcha(config.recaptcha.siteKey, config.recaptcha.secretKey); @@ -19,11 +21,11 @@ const recaptcha = new Recaptcha(config.recaptcha.siteKey, config.recaptcha.secre const PNID = require('../models/pnid'); // renders register page -router.get('/pnid/register', recaptcha.middleware.render, (request, response) => { +router.get('/pnid/register', (request, response) => { return response.render('register', { title: 'Pretendo | Register', - captcha: response.recaptcha, - locale: utilHelper.getLocale('US', 'en') + locale: utilHelper.getLocale('US', 'en'), + recaptcha_sitekey: config.recaptcha.siteKey }); }); // renders login page @@ -92,36 +94,58 @@ router.post('/api/v1/login', passport.authenticate('PNIDStrategy'), function (re */ router.post('/api/v1/register', recaptcha.middleware.verify, async (request, response) => { if (!request.body) { - // no post body return apiHelper.sendApiGenericError(response); } - /*if (request.recaptcha.error) { - apiHelper.sendApiError(response, 500, ['Captcha error']); - return; - }*/ - const { email, password } = request.body; + if (request.recaptcha.error) { + logger.log('warn', `[reCaptcha ERROR] ${request.recaptcha.error} | IP: ${request.ip} | Data: ${JSON.stringify(request.body)}`); + return apiHelper.sendApiError(response, 500, ['Captcha error']); + } + + const { email, password, confirm_password, username } = request.body; + + if (password !== confirm_password) { + return apiHelper.sendApiError(response, 400, ['Passwords do not match']); + } + + const email_validation_code = await PNID.PNIDModel.generateEmailValidationCode(); + const email_validation_token = await PNID.PNIDModel.generateEmailValidationToken(); + const newUser = new PNID.PNIDModel({ email, + email_validation_code, + email_validation_token, password, pnid: { - key: 'abcd', - pid: await PNID.PNIDModel.generatePID() + pid: await PNID.PNIDModel.generatePID(), + username, + username_lower: username.toLowerCase() } }); - - // TODO verify password // saving to database newUser.save().then((user) => { + mailer.send( + user.get('email'), + '[Pretendo Network] Please confirm your e-mail address', + `Hello, + Your Pretendo Network ID activation is almost complete. Please click the link below to confirm your e-mail address and complete the activation process. + + https://account.pretendo.cc/account/email-confirmation?token${user.get('email_validation_token')} + + If you are unable to connect to the above URL, please enter the following confirmation code on the device to which your Pretendo Network ID is linked. + + <<Confirmation code: ${user.get('email_validation_code')}>>` + ); + return apiHelper.sendReturn(response, { email: user.email, email_validated: user.email_validated, - pnid: user.pnid.key + pnid: user.pnid.pid }); }).catch((rejection) => { // TODO format exception so it doesnt have a huge list of errors - console.warn(rejection); + logger.log('warn', rejection); return apiHelper.sendApiError(response, 500, [rejection]); }); }); diff --git a/server.js b/server.js index b733275..cacd7b3 100644 --- a/server.js +++ b/server.js @@ -6,6 +6,7 @@ the file that contains the startup code */ // imports +const logger = require('./logger'); const express = require('express'); const handlebars = require('express-handlebars'); const session = require('express-session'); @@ -98,11 +99,12 @@ app.use((request, response) => { // TODO remove param decoding errors from logs example: "host/test/%" // 4 parameters required to read the error, cant help the eslint error app.use((error, request, response, next) => { // eslint-disable-line no-unused-vars - console.warn(error.stack); + logger.log('warn', error.stack); return response.status(500).send('Something broke!'); }); // startup app.listen(config.http.port, () => { + logger.log('debug', `started the server on port: ${config.http.port}`); console.log(`started the server on port: ${new String(config.http.port).green}`); }); diff --git a/views/register.hbs b/views/register.hbs index 563f3b7..b380170 100644 --- a/views/register.hbs +++ b/views/register.hbs @@ -4,6 +4,8 @@ {{> head-common }} + + @@ -12,17 +14,20 @@

{{ locale.register.card.caption }}

{{ locale.register.card.title }}

-
+ - + - + - + + +
+ +
-

ERROR ERROR

From 166a46a338b2945083342ddcc6040946288e773f Mon Sep 17 00:00:00 2001 From: RedDucks Date: Fri, 30 Nov 2018 22:15:07 -0500 Subject: [PATCH 2/2] moved mailer to helper folder as per request --- mailer.js => helpers/mailer.js | 2 +- routes/pnid.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename mailer.js => helpers/mailer.js (94%) diff --git a/mailer.js b/helpers/mailer.js similarity index 94% rename from mailer.js rename to helpers/mailer.js index c694e49..7b0708c 100644 --- a/mailer.js +++ b/helpers/mailer.js @@ -1,5 +1,5 @@ const nodemailer = require('nodemailer'); -const config = require('./config'); +const config = require('../config'); const transporter = nodemailer.createTransport(config.email); diff --git a/routes/pnid.js b/routes/pnid.js index 473a0f8..a255c71 100644 --- a/routes/pnid.js +++ b/routes/pnid.js @@ -12,7 +12,7 @@ const passport = require('passport'); const userMiddleware = require('../middleware/authentication'); const apiHelper = require('../helpers/api'); const utilHelper = require('../helpers/util'); -const mailer = require('../mailer'); +const mailer = require('../helpers/mailer'); const config = require('../config.json'); const Recaptcha = require('express-recaptcha').Recaptcha; const recaptcha = new Recaptcha(config.recaptcha.siteKey, config.recaptcha.secretKey);