Merge pull request #31 from AlexisNP/evol/verification
User verification system
This commit is contained in:
@@ -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`)
|
||||
|
||||
10
api/index.js
10
api/index.js
@@ -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
40
api/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
@@ -145,23 +193,23 @@ class UserRepository {
|
||||
delete fetchedUser.password
|
||||
|
||||
if (match) {
|
||||
if (fetchedUser.banned) {
|
||||
reject({
|
||||
"message": `L'utilisateur #${fetchedUser.name} a été banni, la connexion est impossible.`,
|
||||
"code": 403
|
||||
})
|
||||
} else if (!fetchedUser.verified) {
|
||||
reject({
|
||||
"message": `L'utilisateur #${fetchedUser.name} n'as pas été vérifié, le compte doit être activé avant la connexion.`,
|
||||
"code": 401
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
"message": `L'utilisateur #${fetchedUser.name} s'est connecté.`,
|
||||
"code": 200,
|
||||
"user": fetchedUser,
|
||||
})
|
||||
}
|
||||
if (fetchedUser.banned) {
|
||||
reject({
|
||||
"message": `L'utilisateur #${fetchedUser.name} a été banni, la connexion est impossible.`,
|
||||
"code": 403
|
||||
})
|
||||
} else if (!fetchedUser.verified) {
|
||||
reject({
|
||||
"message": `L'utilisateur #${fetchedUser.name} n'as pas été vérifié, le compte doit être activé avant la connexion.`,
|
||||
"code": 401
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
"message": `L'utilisateur #${fetchedUser.name} s'est connecté.`,
|
||||
"code": 200,
|
||||
"user": fetchedUser,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
reject({
|
||||
"message": "Les informations de connexions sont erronées.",
|
||||
|
||||
@@ -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
14
api/smtp/config.js
Normal 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
61
api/smtp/mails.js
Normal 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,
|
||||
}
|
||||
102
api/smtp/templates/template-sign-up.html
Normal file
102
api/smtp/templates/template-sign-up.html
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user