Merge pull request #31 from AlexisNP/evol/verification

User verification system
This commit is contained in:
Joururi
2020-12-27 19:13:19 +01:00
committed by GitHub
10 changed files with 341 additions and 49 deletions

View File

@@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS `user` (
`password` VARCHAR(255) NOT NULL,
`role_id` INT UNSIGNED NOT NULL DEFAULT 1,
`verified` BOOLEAN DEFAULT false,
`verification_token` VARCHAR(255),
`banned` BOOLEAN DEFAULT false,
PRIMARY KEY(`id`),
FOREIGN KEY(`role_id`) REFERENCES role(`id`)

View File

@@ -33,13 +33,13 @@ app.use(helmet());
// Server
app.listen(port, () => console.log(`App listening on port ${port}`));
// Auth guard
// TEMP Auth guard
const authguard = (req, res, next) => {
if (req.headers.auracle_key !== process.env.API_KEY_PUBLIC) {
return res.status(401).send('The API key is either missing or incorrect.');
} else {
// if (req.headers.auracle_key !== process.env.API_KEY_PUBLIC) {
// return res.status(401).send('The API key is either missing or incorrect.');
// } else {
next();
}
// }
}
// Routing

40
api/package-lock.json generated
View File

@@ -856,6 +856,25 @@
"which": "^1.2.14"
}
},
"handlebars": {
"version": "4.7.6",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz",
"integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==",
"requires": {
"minimist": "^1.2.5",
"neo-async": "^2.6.0",
"source-map": "^0.6.1",
"uglify-js": "^3.1.4",
"wordwrap": "^1.0.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
@@ -1564,6 +1583,11 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"nocache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz",
@@ -1591,6 +1615,11 @@
"tar": "^4.4.2"
}
},
"nodemailer": {
"version": "6.4.17",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.17.tgz",
"integrity": "sha512-89ps+SBGpo0D4Bi5ZrxcrCiRFaMmkCt+gItMXQGzEtZVR3uAD3QAQIDoxTWnx3ky0Dwwy/dhFrQ+6NNGXpw/qQ=="
},
"nopt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
@@ -2303,6 +2332,12 @@
"mime-types": "~2.1.24"
}
},
"uglify-js": {
"version": "3.12.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.3.tgz",
"integrity": "sha512-feZzR+kIcSVuLi3s/0x0b2Tx4Iokwqt+8PJM7yRHKuldg4MLdam4TCFeICv+lgDtuYiCtdmrtIP+uN9LWvDasw==",
"optional": true
},
"unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
@@ -2414,6 +2449,11 @@
"string-width": "^1.0.2 || 2"
}
},
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View File

@@ -32,12 +32,14 @@
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"handlebars": "^4.7.6",
"helmet": "^3.22.0",
"jsonschema": "^1.2.6",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.1",
"morgan": "^1.10.0",
"mysql": "^2.18.1",
"nodemailer": "^6.4.17",
"uuid": "^8.2.0"
}
}

View File

@@ -7,6 +7,9 @@ const model = require('../models/user-model')
const bcrypt = require('bcrypt')
const { v4: uuidv4 } = require('uuid')
// Mailing methods
const mails = require('../smtp/mails')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
@@ -92,7 +95,9 @@ class UserRepository {
})
} else {
let hash = await bcrypt.hash(u.password, 10)
let uuid = uuidv4()
let verification_token = uuidv4();
this.checkIfEmailAvailable(u.mail)
.then(() => {
@@ -102,6 +107,7 @@ class UserRepository {
'name': u.name,
'mail': u.mail,
'password': hash,
'verification_token': verification_token,
})
.save(null, {
transacting: t
@@ -114,6 +120,17 @@ class UserRepository {
return this.getOneByUUID(uuid, false)
})
.then(newUser => {
// Send a mail to the new user's email with a link to verification url
mails.sendRegistrationMail({
user: {
name: newUser.name,
mail: newUser.mail,
token: verification_token,
}
})
// Then resolves the api call
resolve({
"message": `Compte utilisateur #${newUser.id} créé avec succès.`,
"code": 201,
@@ -134,6 +151,37 @@ class UserRepository {
})
}
verifyUser(token) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'verification_token' : token })
.fetch()
.then(v => {
bookshelf.transaction(t => {
return v.save({
'verification_token': null,
'verified': 1,
}, {
method: 'update',
transacting: t
})
})
.then(v => {
resolve({
"message": "Insérez ici une future redirection vers le client.",
"code": 202,
})
})
})
.catch(() => {
reject({
"message": "Le lien de vérification ne semble pas correct.",
"code": 404
})
})
});
}
// Log user with an email address and a password
logUser(mail, password) {
return new Promise((resolve, reject) => {

View File

@@ -85,29 +85,6 @@ router.get('/available/:mail/', async (req, res) => {
})
// LOG A USER ------------------
const logUser = (mail, password) => {
return Users.logUser(mail, password)
.catch(err => {
throw err
})
}
router.post('/login', async (req, res) => {
logUser(req.body.mail, req.body.password)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// CREATE ONE ------------------
const addUser = (u) => {
return Users.addOne(u)
@@ -131,4 +108,51 @@ router.post('/', async (req, res) => {
})
})
// VERIFY A USER
const verifyUser = (token) => {
return Users.verifyUser(token)
.catch(err => {
throw err;
})
}
router.get('/verification/:token', async (req, res) => {
verifyUser(req.params.token)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.send(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
});
// LOG A USER ------------------
const logUser = (mail, password) => {
return Users.logUser(mail, password)
.catch(err => {
throw err
})
}
router.post('/login', async (req, res) => {
logUser(req.body.mail, req.body.password)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
module.exports = router

14
api/smtp/config.js Normal file
View File

@@ -0,0 +1,14 @@
const nodemailer = require('nodemailer');
const transport = nodemailer.createTransport({
host: "smtp.mailtrap.io",
port: 2525,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
}
});
module.exports = {
transport,
};

61
api/smtp/mails.js Normal file
View File

@@ -0,0 +1,61 @@
const smtp = require('./config');
const fs = require('fs');
const handlebars = require('handlebars');
// Sender address for mail service
const sender = 'tymos@auracle.io';
// Fetches a HTML template file for parsing
const getTemplateFile = (path) => {
return new Promise((resolve, reject) => {
fs.readFile(path, {encoding: 'utf-8'}, (err, html) => {
if (err) {
reject(err);
} else {
resolve(html);
}
})
})
};
/**
* SEND REGISTRATION MAIL FUNCTION
* @param {Object} data
* - user
* - name
* - mail
* - token
*/
const sendRegistrationMail = (data) => {
getTemplateFile(__dirname + '/templates/template-sign-up.html')
.then(html => {
let template = handlebars.compile(html);
let template_parsed = template(data);
let message = {
from: sender,
to: data.user.mail,
subject: 'Inscription Auracle.io',
html: template_parsed,
};
smtp.transport.sendMail(message, (err, info) => {
if (err) {
throw err;
} else {
console.log(info);
}
});
})
.catch(err => {
console.log(err);
});
}
const sendBanEmail = (date) => {
return null;
}
module.exports = {
sendRegistrationMail,
}

View File

@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<head>
<title>Template Email Auracle Inscription</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&family=Playfair+Display:wght@700;800;900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--black: 52, 58, 64;
--white: #FFF;
--primary: 89, 158, 244;
}
body {
color: rgb(var(--black));
font-family: 'Lato', sans-serif;
max-width: 100%;
}
.display {
font-family: 'Playfair Display', serif;
}
h1 {
font-size: 32px;
}
p:not(:last-child) {
margin-bottom: 15px;
}
.italics {
font-style: italic;
}
.wrapper {
position: relative;
padding: 10vh 5vw;
background: url('https://i.imgur.com/IxEKKEY.png') center center fixed repeat;
}
.wrapper:before {
display: block;
content: '';
position: absolute;
top: 0;
height: 120px;
left: 0;
right: 0;
background-color: rgb(var(--primary));
z-index: 0;
}
.container {
position: relative;
background-color: var(--white);
z-index: 5;
}
header, footer {
text-align: center;
color: var(--white);
background-color: rgb(var(--black));
}
header {
padding: 15px;
}
main {
padding: 30px;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="container">
<header>
<h1 class="display">Bienvenue sur Auracle !</h1>
</header>
<main>
<p>Votre compte Auracle a bien été enregistré et est rattaché à cette adresse mail.</p>
<p>Cependant, afin de garantir la sécurité de vos données, votre compte doit être vérifié avant votre première connexion. Vous pouvez <a href="http://localhost:2814/api/users/verification/{{ user.token }}">suivre ce lien afin de confirmer votre inscription.</a></p>
<p>Si vous rencontrez des problèmes, n'hésitez pas à envoyer un mail à <a href="mailto:support@auracle.io">support@auracle.io</a></p>
<p><strong>Merci de votre soutien !</strong></p>
<p class="italics">Bien sincèrement, Izàc Tymos.</p>
</main>
<footer>
<div class="icon">
<!-- <img src="https://i.imgur.com/dJTklvU.png" alt=""> -->
</div>
</footer>
</div>
</div>
</body>
</html>

View File

@@ -11,7 +11,7 @@
v-model="name"
:class="{
'is-invalid': errors.name,
'is-valid': !errors.name }"
'is-valid': submitted && !errors.name }"
type="text"
id="username"
class="form-control"
@@ -38,7 +38,7 @@
v-model="email"
:class="{
'is-invalid': errors.email,
'is-valid': !errors.email }"
'is-valid': submitted && !errors.email }"
type="email"
id="email"
class="form-control"
@@ -64,7 +64,7 @@
<input
:class="{
'is-invalid': errors.password,
'is-valid': !errors.password }"
'is-valid': submitted && !errors.password }"
v-model="password"
type="password"
id="password"
@@ -78,7 +78,7 @@
v-model="password_check"
:class="{
'is-invalid': errors.password_check || errors.password,
'is-valid': !errors.password_check}"
'is-valid': submitted && !errors.password_check}"
type="password"
id="password_check"
class="form-control"