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/mailer.js b/helpers/mailer.js new file mode 100644 index 0000000..7b0708c --- /dev/null +++ b/helpers/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/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/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..a255c71 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('../helpers/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