Skip to content

Commit

Permalink
Fixes #1037 : Added verification mail (#1041)
Browse files Browse the repository at this point in the history
  • Loading branch information
yashLadha authored and djmgit committed Jun 21, 2018
1 parent 79616d7 commit b003a0e
Show file tree
Hide file tree
Showing 23 changed files with 634 additions and 306 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea/
badgeyay.code-workspace
/*.csv#
*.badges
Expand Down
22 changes: 22 additions & 0 deletions api/controllers/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
from flask import Blueprint, request, jsonify
from flask import current_app as app
from api.schemas.token import ValidTokenSchema
from api.schemas.operation import EmailVerificationOperation
from api.utils.encryptUtil import _decrypt, password
from api.utils.update_user import update_firebase_emailVerified
from api.models.user import User

router = Blueprint('Validator', __name__)

Expand All @@ -20,3 +24,21 @@ def validate_reset_token():
resp['valid'] = False
print(e)
return jsonify(ValidTokenSchema().dump(resp).data)


@router.route('/email')
def validate_email():
args = request.args
if 'id' in args.keys():
encryptID = args['id']
email = _decrypt(encryptID, "", password)
user = User.getUser(email=email)
if not user:
print('User not found')
resp = {'id': user.id}
if not update_firebase_emailVerified(user.uid):
print('Email not verified')
resp['status'] = 'Not verified'
else:
resp['status'] = 'Verified'
return jsonify(EmailVerificationOperation().dump(resp).data)
8 changes: 8 additions & 0 deletions api/schemas/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ class Meta:

id = fields.Str(required=True, dump_only=True)
status = fields.Str(required=True)


class EmailVerificationOperation(Schema):
class Meta:
type_ = 'verify-mails'

id = fields.Str(required=True, dump_only=True)
status = fields.Str(required=True)
50 changes: 50 additions & 0 deletions api/utils/encryptUtil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from Crypto.Cipher import AES
from hashlib import md5
import base64


password = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'

BLOCK_SIZE = 16


def pad(data):
pad = BLOCK_SIZE - len(data) % BLOCK_SIZE
return data + pad * chr(pad)


def unpad(padded):
pad = ord(chr(padded[-1]))
return padded[:-pad]


def _encrypt(data, nonce, password):
m = md5()
m.update(password.encode('utf-8'))
key = m.hexdigest()

m = md5()
m.update((password + key).encode('utf-8'))
iv = m.hexdigest()

data = pad(data)

aes = AES.new(key, AES.MODE_CBC, iv[:16])

encrypted = aes.encrypt(data)
return base64.urlsafe_b64encode(encrypted)


def _decrypt(edata, nonce, password):
edata = base64.urlsafe_b64decode(edata)

m = md5()
m.update(password.encode('utf-8'))
key = m.hexdigest()

m = md5()
m.update((password + key).encode('utf-8'))
iv = m.hexdigest()

aes = AES.new(key, AES.MODE_CBC, iv[:16])
return unpad(aes.decrypt(edata)).decode('utf-8')
12 changes: 12 additions & 0 deletions api/utils/update_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ def update_firebase_photoURL(uid, photoURL):
return False


def update_firebase_emailVerified(uid):
try:
auth.update_user(
uid=uid,
email_verified=True
)
return True
except Exception as e:
print(e)
return False


def update_firebase_password(uid, pwd):
try:
auth.update_user(
Expand Down
157 changes: 115 additions & 42 deletions cloud-functions/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const firebase = require('firebase');
const crypto = require('crypto');
var serviceAccount = require('./config/serviceKey.json');
var clientAccount = require('./config/clientKey.json');
const fs = require('fs');
Expand All @@ -27,13 +28,80 @@ const mailTransport = nodemailer.createTransport({
});

const APP_NAME = 'Badgeyay';
const BASE_URL = 'http://badgeyay.com/';
const PASSWORD_RESET_LINK = 'http://badgeyay.com/reset/password?token=';
var password = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';

String.prototype.format = function() {

var encrypt = function (input, password, callback) {
var m = crypto.createHash('md5');
m.update(password)
var key = m.digest('hex');

m = crypto.createHash('md5');
m.update(password + key)
var iv = m.digest('hex');

var data = new Buffer(input, 'utf8').toString('binary');

var cipher = crypto.createCipheriv('aes-256-cbc', key, iv.slice(0, 16));

// UPDATE: crypto changed in v0.10
// https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10
var nodev = process.version.match(/^v(\d+)\.(\d+)/);
var encrypted;

if (nodev[1] === '0' && parseInt(nodev[2]) < 10) {
encrypted = cipher.update(data, 'binary') + cipher.final('binary');
} else {
encrypted = cipher.update(data, 'utf8', 'binary') + cipher.final('binary');
}

var encoded = new Buffer(encrypted, 'binary').toString('base64');

callback(encoded);
};

var decrypt = function (input, password, callback) {
// Convert urlsafe base64 to normal base64
input = input.replace(/-/g, '+').replace(/_/g, '/');
// Convert from base64 to binary string
var edata = new Buffer(input, 'base64').toString('binary')

// Create key from password
var m = crypto.createHash('md5');
m.update(password)
var key = m.digest('hex');

// Create iv from password and key
m = crypto.createHash('md5');
m.update(password + key)
var iv = m.digest('hex');

// Decipher encrypted data
var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv.slice(0, 16));

// UPDATE: crypto changed in v0.10
// https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10
var nodev = process.version.match(/^v(\d+)\.(\d+)/);
var decrypted, plaintext;

if (nodev[1] === '0' && parseInt(nodev[2]) < 10) {
decrypted = decipher.update(edata, 'binary') + decipher.final('binary');
plaintext = new Buffer(decrypted, 'binary').toString('utf8');
} else {
plaintext = (decipher.update(edata, 'binary', 'utf8') + decipher.final('utf8'));
}

callback(plaintext);
};


String.prototype.format = function () {
var formatted = this;
for (var prop in arguments[0]) {
var regexp = new RegExp('\\{' + prop + '\\}', 'gi');
formatted = formatted.replace(regexp, arguments[0][prop]);
var regexp = new RegExp('\\{' + prop + '\\}', 'gi');
formatted = formatted.replace(regexp, arguments[0][prop]);
}
return formatted;
};
Expand All @@ -42,60 +110,65 @@ exports.sendVerificationMail = functions.auth.user().onCreate((user) => {
const uid = user.uid;
if (user.emailVerified) {
console.log('User has email already verified: ', user.email);
sendGreetingMail(user.email, user.displayName);
return 0;
} else {
return admin.auth().createCustomToken(uid)
.then((customToken) => {
return firebase.auth().signInWithCustomToken(customToken)
})
.then((curUser) => {
return firebase.auth().onAuthStateChanged((user_) => {
if (!user.emailVerified) {
user_.sendEmailVerification();
return console.log('Verification mail sent: ', user_.email);
} else {
return console.log('Email is already verified: ', user_.email);
}
let userEmail = user.email;
const mailOptions = {
from: `${APP_NAME}<[email protected]>`,
to: userEmail,
};

encrypt(userEmail, password, encoded => {
var resp = {
link: BASE_URL + 'verify/email?id=' + encoded
}
mailOptions.subject = 'Please verify your Email | Badgeyay';
mailOptions.html = '<p>Please verify your email ID by clicking on this <a href={link}>Link</a></p>'.format(resp);
return mailTransport.sendMail(mailOptions)
.then(() => {
console.log('Verification Mail Sent');
return 0;
})
})
.catch((err) => {
console.error(err.message);
})
.catch(err => {
console.log(err);
return -1;
});
});
return 0;
}
});

exports.greetingMail = functions.auth.user().onCreate((user) => {
const email = user.email;
const displayName = user.displayName;

return sendGreetingMail(email, displayName);
});

function sendGreetingMail(email, displayName) {
const mailOptions = {
from: `${APP_NAME}<[email protected]>`,
to: email,
};

var user = {
name: displayName
};
fs.readFile('./greeting.html', (err, data) => {
if (err) {
console.error(err.message);
return -1;
} else {
mailOptions.subject = `Welcome to Badgeyay`;
mailOptions.html = data.toString().format(user);
return mailTransport.sendMail(mailOptions).then(() => {
return console.log('Welcome mail sent to: ', email)
}).catch((err) => {
console.error(err.message);
});
}
mailOptions.subject = `Welcome to Badgeyay`;
mailOptions.text = `Hey ${displayName || ''}! Welcome to Badgeyay. We welcome you onboard and pleased to offer you service.`;
return mailTransport.sendMail(mailOptions).then(() => {
return console.log('Welcome mail sent to: ', email)
}).catch((err) => {
console.error(err.message);
return -1;
});
}

exports.sendWelcomeMail = functions.https.onRequest((req, res) => {
let uid = req.query['id'];
admin.auth().getUser(uid)
.then(userRecord => {
let email = userRecord.email;
let displayName = userRecord.displayName;
return sendGreetingMail(email, displayName);
})
.catch(err => {
console.log(err);
return -1;
})
});

exports.sendResetMail = functions.https.onRequest((req, res) => {
let token = req.query['token'];
let email = req.query['email'];
Expand Down
11 changes: 8 additions & 3 deletions cloud-functions/functions/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cloud-functions/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"logs": "firebase functions:log"
},
"dependencies": {
"crypto": "^1.0.1",
"firebase": "^5.0.4",
"firebase-admin": "^5.12.1",
"firebase-functions": "^1.0.4",
Expand Down
12 changes: 12 additions & 0 deletions frontend/app/adapters/verify-mail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import DS from 'ember-data';
import ENV from '../config/environment';

const { JSONAPIAdapter } = DS;
const { APP } = ENV;

export default JSONAPIAdapter.extend({
host : APP.backLink,
pathForType : () => {
return 'validate/email';
}
});
4 changes: 4 additions & 0 deletions frontend/app/components/verify-mail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Component from '@ember/component';

export default Component.extend({
});
6 changes: 6 additions & 0 deletions frontend/app/controllers/verify/email.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Controller from '@ember/controller';

export default Controller.extend({
queryParams : ['id'],
id : null
});
7 changes: 7 additions & 0 deletions frontend/app/models/verify-mail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import DS from 'ember-data';

const { Model, attr } = DS;

export default Model.extend({
status: attr('string')
});
4 changes: 4 additions & 0 deletions frontend/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ router.map(function() {
this.route('password');
});
this.route('user-guide');

this.route('verify', function() {
this.route('email');
});
});

export default router;
Loading

0 comments on commit b003a0e

Please sign in to comment.