Appunti NodeJS – Express – Passport

Systemd

Per lanciare una applicazione node automaticamente, si può creare una unità di systemd. Tra le varie opzioni, è importante specificare l’utente e il gruppo con cui lanciare la applicazione, e può essere utile specificare dove indirizzare lo stdout e lo stderr.

IMPORTANTE: in Debian bisogna lanciare il comando systemctl daemon-reexec per poter usare systemctl start/stop con user non privilegiato.

Esempio di file unit

[Unit]
#Description=blah blah blah
#Documentation=https://example.com
After=network.target

[Service]
Environment=NODE_PORT=xyzt
Type=simple
User=my-user
Group=my-group
StandardOutput=file:/path/to/app.log
StandardError=file:/path/to/app.log
ExecStart=/usr/bin/node /path/to/app.js
Restart=on-failure

[Install]
WantedBy=multi-user.target

NodeJS

Installando NodeJS dai binari, è necessario impostare il PATH: la guida indica di modificare il profilo utente (locale) in ~/.profile, ma per avere una configurazione globale si può creare un file in /etc/environment.d/:

  • ho scompattato il pacchetto in /usr/local/lib/ e ho impostato root.staff come proprietari
  • ho scritto un file nodejs.env in /usr/local/lib/ impostando il PATH:
    VERSION=v12.16.2
    DISTRO=linux-x64
    export PATH=/usr/local/lib/nodejs/node-$VERSION-$DISTRO/bin:$PATH

  • ho creato un link in /etc/profile.d/ (valido solo per le shell di login): ln -s /usr/local/lib/nodejs.env nodejs.sh

FormData

Per ricevere in Express i dati di un form multipart/form-data è necessario utilizzare un middleware: la guida di riferimento delle API di Express suggerisce busboy, multer, formidable, multiparty, connect-multiparty, o pez.
Il più utilizzato è formidable.

Il suo uso base consiste in una semplice funzione che processa una request e passa i dati ricevuti ad un callback.
Esempio:

const express = require('express');
const formidable = require('formidable');

const app = express();

app.use(express.json());

app.post('/smtg', (req, res) => {
  const form = formidable();
  form.parse(req, (err, fields, files) => {
    // scrivo i dati ricevuti sulla console
    console.log('fields:', fields);
    console.log('files:', files);
    // rimando indietro i dati come JSON
    res.json(fields);
  });
});

app.listen(8000, () => {
  console.log('Prova Express: porta 8000!');
});

Nodemon

Nodemon è un pacchetto per riavviare automaticamente una applicazione di node quando viene rilevato un cambiamento nei file di una directory monitorata.
Non sono necessarie modifiche ai file, basta semplicemente lanciare l’applicazione da riga di comando con nodemon invece che con node.

Con systemd invece è necessario specificare la WorkingDirectory.

Invece di usare il comando nodemon, è possibile usare nodemon come require in uno script e specificare quale script della applicazione avviare:

// file nodemon.js
const nodemon = require('nodemon');

nodemon({
  verbose: true,
  script: 'app.js',
  ext: 'js json'
});

nodemon.on('start', () => {
  console.log('Nodemon: App.js has started');
}).on('quit', () => {
  console.log('Nodemon: App.js has quit');
  process.exit();
}).on('restart', (files) => {
  console.log('Nodemon: App.js restarted due to: ', files);
});

e il file dell’unità sarà tipo:

WorkingDirectory=/path/to/myNodeApp
ExecStart=/usr/bin/node path/to/myNodeApp/nodemon.js

Process

Il modulo process è contenuto nell’installazione di node: non serve il require.

In process ci sono due eventi “propri”:

  • exit: lanciato prima che il processo termini
  • uncaughtException: lanciato se ci sono eccezioni non gestite

Un process è un oggetto globale, e di seguito ci sono alcune proprietà di uso comune:

  • arch returns process architecture: ‘arm’, ‘ia32’, or ‘x64’
  • args returns commands line arguments as an array
  • env returns user environment
  • pid returns process id of the process
  • platform returns platform of the process: ‘darwin’, ‘freebsd’, ‘linux’, ‘sunos’ or ‘win32’
  • release returns the metadata for the current node release
  • version returns the node version
  • versions returns the node version and its dependencies

Alcune funzioni di process di uso comune:

  • cwd() returns path of current working directory
  • hrtime() returns the current high-resolution real time in a [seconds, nanoseconds] array
  • memoryUsage() returns an object having information of memory usage.
  • process.kill(pid[, signal]) is used to kill the given pid.
  • uptime() returns the Node.js process uptime in seconds.

Un uso molto importante di process è lagestione dei segnali.
Si può ascoltare tutti i segnali, come SIGHUP, SIGKILL, SIGINT, ecc….
Esempio:

 
process.on('SIGINT', function() {
  console.log('Got SIGINT.  Press Control-D to exit.');
  process.exit(0) // NECESSARIO CHIAMARE exit() !!
});

// oppure
process.on('exit', (code) => {
  console.log(`About to exit with code: ${code}`);
});

Quando si arresta una applicazione node tramite systemd, il segnale ricevuto è SIGTERM.

Passport

vedere anche:

In una applicazione ExpressJS è necessario usare il middleware passport.initialize() per inizializzare Passport.
Allo stesso modo, per usare le sessioni, è richiesto il middleware passport.session().
Esempio:

app.configure(function() {
app.use(express.static('public'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});

Sessioni

In una applicazione web, le credenziali di accesso vengono trasmesse solo per il login: se l’autenticazione ha avuto successo, viene stabilita e mantenuta una sessione tramite l’uso dei cookie.
Tutte le richieste successive non conterranno le credenziali, ma solo il cookie per identificare la sessione.
Passport supporta le sessioni, e procede alla serializzazione/deserializzazione di istanze user:
Esempio

// carica passport e la strategia "local"
const passport = require('passport'),
  LocalStrategy = require('passport-local').Strategy;

passport.serializeUser(function(user, done) {
  // in questo esempio viene serializzato solo lo user ID:
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

Strategie

Passport supporta diversi metodi (strategie) di autenticazione: dopo averne caricata (require) una o più di una, chiediamo a Passport di usarla con il comando use():
Esempio:

// chiede a Passport di usare la strategia
passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({
      username: username
    }, function(err, user) {
      if (err) {
        return done(err);
      }
      if (!user) {
        return done(null, false, {
          message: 'Incorrect username.'
        });
      }
      if (!user.validPassword(password)) {
        return done(null, false, {
          message: 'Incorrect password.'
        });
      }
      return done(null, user);
    });
  }
));

Le strategie richiedono un callback di verifica del possesso delle credenziali: questo callback verrà richiamato da Passport quando ci sarà una richiesta di autenticazione.
Nell’esempio precedente, il callback utilizza 2 parametri per l’autenticazione (username e password), e il terzo parametro è un ulteriore callback in cui specifichiamo lo stato dell’autenticazione dell’utente:

  • se le credenziali sono valide, invochiamo done() passando l’utente:
    return done(null, user);
  • se le credenziali NON sono valide, invochiamo done() passando FALSE:
    return done(null, false);
    // si può anche specificare un messaggio
    return done(null, false, { message: 'Incorrect password.' });
  • in caso di una eccezione, si può riportare l’errore:
    return done(err);

    Attenzione: una eccezione è diversa da una mancata autenticazione. In caso di credenziali errate non si ha una eccezione, assicurarsi che err sia null.

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.