diff --git a/api/database/auracle_db_create.sql b/api/database/auracle_db_create.sql index acbfada..15b31e8 100644 --- a/api/database/auracle_db_create.sql +++ b/api/database/auracle_db_create.sql @@ -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`) diff --git a/api/index.js b/api/index.js index 90449b1..1165be8 100644 --- a/api/index.js +++ b/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 diff --git a/api/package-lock.json b/api/package-lock.json index ab9c5b9..2bdbe7b 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -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", diff --git a/api/package.json b/api/package.json index 244fba0..f62208a 100644 --- a/api/package.json +++ b/api/package.json @@ -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" } } diff --git a/api/repositories/user-repository.js b/api/repositories/user-repository.js index e414f98..dd5d3a3 100644 --- a/api/repositories/user-repository.js +++ b/api/repositories/user-repository.js @@ -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.", diff --git a/api/routes/users.js b/api/routes/users.js index 8e0ac19..175fbea 100644 --- a/api/routes/users.js +++ b/api/routes/users.js @@ -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 \ No newline at end of file diff --git a/api/smtp/config.js b/api/smtp/config.js new file mode 100644 index 0000000..520c05c --- /dev/null +++ b/api/smtp/config.js @@ -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, +}; \ No newline at end of file diff --git a/api/smtp/mails.js b/api/smtp/mails.js new file mode 100644 index 0000000..115ab76 --- /dev/null +++ b/api/smtp/mails.js @@ -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, +} \ No newline at end of file diff --git a/api/smtp/templates/template-sign-up.html b/api/smtp/templates/template-sign-up.html new file mode 100644 index 0000000..e11c838 --- /dev/null +++ b/api/smtp/templates/template-sign-up.html @@ -0,0 +1,102 @@ + + + + Template Email Auracle Inscription + + + +
+
+
+ +

Bienvenue sur Auracle !

+
+
+

Votre compte Auracle a bien été enregistré et est rattaché à cette adresse mail.

+

Cependant, afin de garantir la sécurité de vos données, votre compte doit être vérifié avant votre première connexion. Vous pouvez suivre ce lien afin de confirmer votre inscription.

+

Si vous rencontrez des problèmes, n'hésitez pas à envoyer un mail à support@auracle.io

+

Merci de votre soutien !

+

Bien sincèrement, Izàc Tymos.

+
+ +
+
+ + \ No newline at end of file diff --git a/client/src/pages/user/register-page.vue b/client/src/pages/user/register-page.vue index 5fd97bb..f363b1c 100644 --- a/client/src/pages/user/register-page.vue +++ b/client/src/pages/user/register-page.vue @@ -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 @@