In questo articolo vedremo come implementare un sistema di attivazione di un account in ExpressJS.

Il modello di utente che andremo a definire in MongoDB prevede i campi per memorizzare il token di attivazione, la sua data di archiviazione e un flag booleano che indica se l'account è stato attivato.

'use strict';

const mongoose  = require('mongoose');

const { Schema }  = mongoose;

const UserSchema = new Schema({
    username: String,
    password: String,
    email: String,
    confirmed: {
        type: Boolean,
        default: false
    },
    token: String,
    expires: {
        type: Number,
        default: Date.now()
    }
    

},{collection: 'users'});

module.exports = mongoose.model('user', UserSchema);

Il primo step da implementare è la registrazione dell'utente. Useremo il modulo express-validator per validare i dati lato server. Se la validazione ha successo, salveremo i dati dell'utente nel database ed invieremo l'e-mail con il link di attivazione.

'use strict';

const crypto = require('crypto');
const express = require('express');
const router = express.Router();
const User = require('../models/user')
const Mail = require('../classes/Mail');
const { mail: mailSettings, siteURL } = require('../config');
const auth = require('../middleware/auth');
const { body, validationResult } = require('express-validator');

router.post('/register', [

    body('username').custom(value => {
        if(!/^[a-z0-9]+$/i.test(value)) {
            throw new Error('The username can contain only letters and numbers.');
        }
        return true;
    }),

    body('email').isEmail().withMessage('Invalid e-mail.'),

    body('password').custom(value => {
        const uppercase = /[A-Z]+/;
        const lowercase = /[a-z]+/;
        const digit = /[0-9]+/;
        const special = /[\W]+/;

        if(!uppercase.test(value) && !lowercase.test(value) && !digit.test(value) && !special.test(value) && value.length < 8) {
            throw new Error('The password must be at least 8 characters long and contain uppercase and lowercase letters, digits and special characters.');
        }

        return true;
    })
    
  ], async (req, res, next) => {

    const validationErrors = validationResult(req);
    if (!validationErrors.isEmpty()) {
        return res.status(400).render('index', { errors: validationErrors.array(), success: '' } );
    }

    try {

    const { username, password, email } = req.body;
    const encPassword = crypto.createHash('md5').update(password).digest('hex');
    const token = crypto.createHash('md5').update(Math.random().toString().substring(2)).digest('hex');

    const user = new User({
        username: username,
        email: email,
        password: encPassword,
        token: token
    });

    await user.save();

    res.render('index', {
        errors: [],
        success: 'Check your inbox to activate your account.'
    });

    const mail = new Mail({
        from: mailSettings.from,
        settings: mailSettings.settings
    });

    await mail.send(email, 'Activate Your Account', `<a href="${siteURL}confirm/${token}">Activate</a>`);

    } catch(err) {
        res.sendStatus(500);
    }


});

Per impedire accessi non autorizzati alla dashboard utente, abbiamo creato il middleware auth così definito:

'use strict';

module.exports = (req, res, next) => {
    if(!req.session.user || !req.session.user.confirmed) {
        return res.redirect('/');
    }
    next();
};

L'attivazione dell'account utente prevede la verifica della validità del token, che deve essere presente nel database e non deve avere una durata maggiore di un giorno. Se la verifica ha successo, viene aggiornato il flag confirmed del profilo utente e l'oggetto utente viene salvato nella sessione corrente. Quindi l'utente viene reindirizzato sulla sua dashboard.

router.get('/confirm/:token', async (req, res, next) => {
    const { token } = req.params;

    try {
        const user = await User.findOne({ token: token });
        
        if(!user) {
            return res.redirect('/');
        }

        const expiresIn = 1000 * 60 * 60 * 60 * 24;

        if((Date.now() - user.expires) > expiresIn) {
            await user.remove();
            return res.redirect('/');
        }

        user.confirmed = true;
        await user.save();

        req.session.user = user;

        return res.redirect('/dashboard');

    } catch(err) {
        res.sendStatus(404);
    }
});

Come si può notare, si tratta di una procedura relativamente semplice. Volendo perfezionare l'implementazione, si può usare un modulo specifico per la generazione del token casuale.