воскресенье, 13 ноября 2016 г.

Core ES6 features


Table of contents
Please support this book: buy it (PDF, EPUB, MOBI) or donate

4. Core ES6 features

This chapter describes the core ES6 features. These features are easy to adopt; the remaining features are mainly of interest to library authors. I explain each feature via the corresponding ES5 code.

4.1 From var to let/const

In ES5, you declare variables via var. Such variables are function-scoped, their scopes are the innermost enclosing functions. The behavior of var is occasionally confusing. This is an example:
var x = 3;
function func(randomize) {
    if (randomize) {
        var x = Math.random(); // (A) scope: whole function
        return x;
    }
    return x; // accesses the x from line A
}
func(false); // undefined
That func() returns undefined may be surprising. You can see why if you rewrite the code so that it more closely reflects what is actually going on:
var x = 3;
function func(randomize) {
    var x;
    if (randomize) {
        x = Math.random();
        return x;
    }
    return x;
}
func(false); // undefined
In ES6, you can additionally declare variables via let and const. Such variables are block-scoped, their scopes are the innermost enclosing blocks. let is roughly a block-scoped version of var. const works like let, but creates variables whose values can’t be changed.
let and const behave more strictly and throw more exceptions (e.g. when you access their variables inside their scope before they are declared). Block-scoping helps with keeping the effects of code fragments more local (see the next section for a demonstration). And it’s more mainstream than function-scoping, which eases moving between JavaScript and other programming languages.
If you replace var with let in the initial version, you get different behavior:
let x = 3;
function func(randomize) {
    if (randomize) {
        let x = Math.random();
        return x;
    }
    return x;
}
func(false); // 3
That means that you can’t blindly replace var with let or const in existing code; you have to be careful during refactoring.
My advice is:
  • Prefer const. You can use it for all variables whose values never change.
  • Otherwise, use let – for variables whose values do change.
  • Avoid var.
More information: chapter “Variables and scoping”.

4.2 From IIFEs to blocks

In ES5, you had to use a pattern called IIFE (Immediately-Invoked Function Expression) if you wanted to restrict the scope of a variable tmp to a block:
(function () {  // open IIFE
    var tmp = ···;
    ···
}());  // close IIFE

console.log(tmp); // ReferenceError
In ECMAScript 6, you can simply use a block and a let declaration (or a const declaration):
{  // open block
    let tmp = ···;
    ···
}  // close block

console.log(tmp); // ReferenceError
More information: section “Avoid IIFEs in ES6”.

4.3 From concatenating strings to template literals

With ES6, JavaScript finally gets literals for string interpolation and multi-line strings.

4.3.1 String interpolation

In ES5, you put values into strings by concatenating those values and string fragments:
function printCoord(x, y) {
    console.log('('+x+', '+y+')');
}
In ES6 you can use string interpolation via template literals:
function printCoord(x, y) {
    console.log(`(${x}, ${y})`);
}

4.3.2 Multi-line strings

Template literals also help with representing multi-line strings.
For example, this is what you have to do to represent one in ES5:
var HTML5_SKELETON =
    '<!doctype html>\n' +
    '<html>\n' +
    '<head>\n' +
    '    <meta charset="UTF-8">\n' +
    '    <title></title>\n' +
    '</head>\n' +
    '<body>\n' +
    '</body>\n' +
    '</html>\n';
If you escape the newlines via backslashes, things look a bit nicer (but you still have to explicitly add newlines):
var HTML5_SKELETON = '\
    <!doctype html>\n\
    <html>\n\
    <head>\n\
        <meta charset="UTF-8">\n\
        <title></title>\n\
    </head>\n\
    <body>\n\
    </body>\n\
    </html>';
ES6 template literals can span multiple lines:
const HTML5_SKELETON = `
    <!doctype html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    </body>
    </html>`;
(The examples differ in how much whitespace is included, but that doesn’t matter in this case.)
More information: chapter “Template literals and tagged templates”.

4.4 From function expressions to arrow functions

In current ES5 code, you have to be careful with this whenever you are using function expressions. In the following example, I create the helper variable _this (line A) so that the this of UiComponent can be accessed in line B.
function UiComponent() {
    var _this = this; // (A)
    var button = document.getElementById('myButton');
    button.addEventListener('click', function () {
        console.log('CLICK');
        _this.handleClick(); // (B)
    });
}
UiComponent.prototype.handleClick = function () {
    ···
};
In ES6, you can use arrow functions, which don’t shadow this (line A):
function UiComponent() {
    var button = document.getElementById('myButton');
    button.addEventListener('click', () => {
        console.log('CLICK');
        this.handleClick(); // (A)
    });
}
(In ES6, you also have the option of using a class instead of a constructor function. That is explored later.)
Arrow functions are especially handy for short callbacks that only return results of expressions.
In ES5, such callbacks are relatively verbose:
var arr = [1, 2, 3];
var squares = arr.map(function (x) { return x * x });
In ES6, arrow functions are much more concise:
const arr = [1, 2, 3];
const squares = arr.map(x => x * x);
When defining parameters, you can even omit parentheses if the parameters are just a single identifier. Thus: (x) => x * x and x => x * x are both allowed.
More information: chapter “Arrow functions”.

4.5 Handling multiple return values

Some functions or methods return multiple values via arrays or objects. In ES5, you always need to create intermediate variables if you want to access those values. In ES6, you can avoid intermediate variables via destructuring.

4.5.1 Multiple return values via arrays

exec() returns captured groups via an Array-like object. In ES5, you need an intermediate variable (matchObj in the example below), even if you are only interested in the groups:
var matchObj =
    /^(\d\d\d\d)-(\d\d)-(\d\d)$/
    .exec('2999-12-31');
var year = matchObj[1];
var month = matchObj[2];
var day = matchObj[3];
In ES6, destructuring makes this code simpler:
const [, year, month, day] =
    /^(\d\d\d\d)-(\d\d)-(\d\d)$/
    .exec('2999-12-31');
The empty slot at the beginning of the Array pattern skips the Array element at index zero.

4.5.2 Multiple return values via objects

The method Object.getOwnPropertyDescriptor() returns a property descriptor, an object that holds multiple values in its properties.
In ES5, even if you are only interested in the properties of an object, you still need an intermediate variable (propDesc in the example below):
var obj = { foo: 123 };

var propDesc = Object.getOwnPropertyDescriptor(obj, 'foo');
var writable = propDesc.writable;
var configurable = propDesc.configurable;

console.log(writable, configurable); // true true
In ES6, you can use destructuring:
const obj = { foo: 123 };

const {writable, configurable} =
    Object.getOwnPropertyDescriptor(obj, 'foo');

console.log(writable, configurable); // true true
{writable, configurable} is an abbreviation for:
{ writable: writable, configurable: configurable }
More information: chapter “Destructuring”.

4.6 From for to forEach() to for-of

Prior to ES5, you iterated over Arrays as follows:
var arr = ['a', 'b', 'c'];
for (var i=0; i<arr.length; i++) {
    var elem = arr[i];
    console.log(elem);
}
In ES5, you have the option of using the Array method forEach():
arr.forEach(function (elem) {
    console.log(elem);
});
A for loop has the advantage that you can break from it, forEach() has the advantage of conciseness.
In ES6, the for-of loop combines both advantages:
const arr = ['a', 'b', 'c'];
for (const elem of arr) {
    console.log(elem);
}
If you want both index and value of each array element, for-of has got you covered, too, via the new Array method entries() and destructuring:
for (const [index, elem] of arr.entries()) {
    console.log(index+'. '+elem);
}
More information: Chap. “The for-of loop”.

4.7 Handling parameter default values

In ES5, you specify default values for parameters like this:
function foo(x, y) {
    x = x || 0;
    y = y || 0;
    ···
}
ES6 has nicer syntax:
function foo(x=0, y=0) {
    ···
}
An added benefit is that in ES6, a parameter default value is only triggered by undefined, while it is triggered by any falsy value in the previous ES5 code.
More information: section “Parameter default values”.

4.8 Handling named parameters

A common way of naming parameters in JavaScript is via object literals (the so-called options object pattern):
selectEntries({ start: 0, end: -1 });
Two advantages of this approach are: Code becomes more self-descriptive and it is easier to omit arbitrary parameters.
In ES5, you can implement selectEntries() as follows:
function selectEntries(options) {
    var start = options.start || 0;
    var end = options.end || -1;
    var step = options.step || 1;
    ···
}
In ES6, you can use destructuring in parameter definitions and the code becomes simpler:
function selectEntries({ start=0, end=-1, step=1 }) {
    ···
}

4.8.1 Making the parameter optional

To make the parameter options optional in ES5, you’d add line A to the code:
function selectEntries(options) {
    options = options || {}; // (A)
    var start = options.start || 0;
    var end = options.end || -1;
    var step = options.step || 1;
    ···
}
In ES6 you can specify {} as a parameter default value:
function selectEntries({ start=0, end=-1, step=1 } = {}) {
    ···
}
More information: section “Simulating named parameters”.

4.9 From arguments to rest parameters

In ES5, if you want a function (or method) to accept an arbitrary number of arguments, you must use the special variable arguments:
function logAllArguments() {
    for (var i=0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}
In ES6, you can declare a rest parameter (args in the example below) via the ... operator:
function logAllArguments(...args) {
    for (const arg of args) {
        console.log(arg);
    }
}
Rest parameters are even nicer if you are only interested in trailing parameters:
function format(pattern, ...args) {
    ···
}
Handling this case in ES5 is clumsy:
function format(pattern) {
    var args = [].slice.call(arguments, 1);
    ···
}
Rest parameters make code easier to read: You can tell that a function has a variable number of parameters just by looking at its parameter definitions.
More information: section “Rest parameters”.

4.10 From apply() to the spread operator (...)

In ES5, you turn arrays into parameters via apply(). ES6 has the spread operator for this purpose.

4.10.1 Math.max()

Math.max() returns the numerically greatest of its arguments. It works for an arbitrary number of arguments, but not for Arrays.
ES5 – apply():
> Math.max.apply(Math, [-1, 5, 11, 3])
11
ES6 – spread operator:
> Math.max(...[-1, 5, 11, 3])
11

4.10.2 Array.prototype.push()

Array.prototype.push() appends all of its arguments as elements to its receiver. There is no method that destructively appends an Array to another one.
ES5 – apply():
var arr1 = ['a', 'b'];
var arr2 = ['c', 'd'];

arr1.push.apply(arr1, arr2);
    // arr1 is now ['a', 'b', 'c', 'd']
ES6 – spread operator:
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];

arr1.push(...arr2);
    // arr1 is now ['a', 'b', 'c', 'd']
More information: section “The spread operator (...)”.

4.11 From concat() to the spread operator (...)

The spread operator can also (non-destructively) turn the contents of its operand into Array elements. That means that it becomes an alternative to the Array method concat().
ES5 – concat():
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

console.log(arr1.concat(arr2, arr3));
    // [ 'a', 'b', 'c', 'd', 'e' ]
ES6 – spread operator:
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

console.log([...arr1, ...arr2, ...arr3]);
    // [ 'a', 'b', 'c', 'd', 'e' ]
More information: section “The spread operator (...)”.

4.12 From function expressions in object literals to method definitions

In JavaScript, methods are properties whose values are functions.
In ES5 object literals, methods are created like other properties. The property values are provided via function expressions.
var obj = {
    foo: function () {
        ···
    },
    bar: function () {
        this.foo();
    }, // trailing comma is legal in ES5
}
ES6 has method definitions, special syntax for creating methods:
const obj = {
    foo() {
        ···
    },
    bar() {
        this.foo();
    },
}
More information: section “Method definitions”.

4.13 From constructors to classes

ES6 classes are mostly just more convenient syntax for constructor functions.

4.13.1 Base classes

In ES5, you implement constructor functions directly:
function Person(name) {
    this.name = name;
}
Person.prototype.describe = function () {
    return 'Person called '+this.name;
};
In ES6, classes provide slightly more convenient syntax for constructor functions:
class Person {
    constructor(name) {
        this.name = name;
    }
    describe() {
        return 'Person called '+this.name;
    }
}
Note the compact syntax for method definitions – no keyword function needed. Also note that there are no commas between the parts of a class.

4.13.2 Derived classes

Subclassing is complicated in ES5, especially referring to super-constructors and super-properties. This is the canonical way of creating a sub-constructor Employee of Person:
function Employee(name, title) {
    Person.call(this, name); // super(name)
    this.title = title;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.describe = function () {
    return Person.prototype.describe.call(this) // super.describe()
           + ' (' + this.title + ')';
};
ES6 has built-in support for subclassing, via the extends clause:
class Employee extends Person {
    constructor(name, title) {
        super(name);
        this.title = title;
    }
    describe() {
        return super.describe() + ' (' + this.title + ')';
    }
}
More information: chapter “Classes”.

4.14 From custom error constructors to subclasses of Error

In ES5, it is impossible to subclass the built-in constructor for exceptions, Error. The following code shows a work-around that gives the constructor MyError important features such as a stack trace:
function MyError() {
    // Use Error as a function
    var superInstance = Error.apply(null, arguments);
    copyOwnPropertiesFrom(this, superInstance);
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

function copyOwnPropertiesFrom(target, source) {
    Object.getOwnPropertyNames(source)
    .forEach(function(propKey) {
        var desc = Object.getOwnPropertyDescriptor(source, propKey);
        Object.defineProperty(target, propKey, desc);
    });
    return target;
};
In ES6, all built-in constructors can be subclassed, which is why the following code achieves what the ES5 code can only simulate:
class MyError extends Error {
}
More information: section “Subclassing built-in constructors”.

4.15 From objects to Maps

Using the language construct object as a map from strings to arbitrary values (a data structure) has always been a makeshift solution in JavaScript. The safest way to do so is by creating an object whose prototype is null. Then you still have to ensure that no key is ever the string '__proto__', because that property key triggers special functionality in many JavaScript engines.
The following ES5 code contains the function countWords that uses the object dict as a map:
var dict = Object.create(null);
function countWords(word) {
    var escapedWord = escapeKey(word);
    if (escapedWord in dict) {
        dict[escapedWord]++;
    } else {
        dict[escapedWord] = 1;
    }
}
function escapeKey(key) {
    if (key.indexOf('__proto__') === 0) {
        return key+'%';
    } else {
        return key;
    }
}
In ES6, you can use the built-in data structure Map and don’t have to escape keys. As a downside, incrementing values inside Maps is less convenient.
const map = new Map();
function countWords(word) {
    const count = map.get(word) || 0;
    map.set(word, count + 1);
}
Another benefit of Maps is that you can use arbitrary values as keys, not just strings.
More information:

4.16 New string methods

The ECMAScript 6 standard library provides several new methods for strings.
From indexOf to startsWith:
if (str.indexOf('x') === 0) {} // ES5
if (str.startsWith('x')) {} // ES6
From indexOf to endsWith:
function endsWith(str, suffix) { // ES5
  var index = str.indexOf(suffix);
  return index >= 0
    && index === str.length-suffix.length;
}
str.endsWith(suffix); // ES6
From indexOf to includes:
if (str.indexOf('x') >= 0) {} // ES5
if (str.includes('x')) {} // ES6
From join to repeat (the ES5 way of repeating a string is more of a hack):
new Array(3+1).join('#') // ES5
'#'.repeat(3) // ES6
More information: Chapter “New string features

4.17 New Array methods

There are also several new Array methods in ES6.

4.17.1 From Array.prototype.indexOf to Array.prototype.findIndex

The latter can be used to find NaN, which the former can’t detect:
const arr = ['a', NaN];

arr.indexOf(NaN); // -1
arr.findIndex(x => Number.isNaN(x)); // 1
As an aside, the new Number.isNaN() provides a safe way to detect NaN (because it doesn’t coerce non-numbers to numbers):
> isNaN('abc')
true
> Number.isNaN('abc')
false

4.17.2 From Array.prototype.slice() to Array.from() or the spread operator

In ES5, Array.prototype.slice() was used to convert Array-like objects to Arrays. In ES6, you have Array.from():
var arr1 = Array.prototype.slice.call(arguments); // ES5
const arr2 = Array.from(arguments); // ES6
If a value is iterable (as all Array-like DOM data structure are by now), you can also use the spread operator (...) to convert it to an Array:
const arr1 = [...'abc'];
    // ['a', 'b', 'c']
const arr2 = [...new Set().add('a').add('b')];
    // ['a', 'b']

4.17.3 From apply() to Array.prototype.fill()

In ES5, you can use apply(), as a hack, to create in Array of arbitrary length that is filled with undefined:
// Same as Array(undefined, undefined)
var arr1 = Array.apply(null, new Array(2));
    // [undefined, undefined]
In ES6, fill() is a simpler alternative:
const arr2 = new Array(2).fill(undefined);
    // [undefined, undefined]
fill() is even more convenient if you want to create an Array that is filled with an arbitrary value:
// ES5
var arr3 = Array.apply(null, new Array(2))
    .map(function (x) { return 'x' });
    // ['x', 'x']

// ES6
const arr4 = new Array(2).fill('x');
    // ['x', 'x']
fill() replaces all Array elements with the given value. Holes are treated as if they were elements.
More information: Sect. “Creating Arrays filled with values

4.18 From CommonJS modules to ES6 modules

Even in ES5, module systems based on either AMD syntax or CommonJS syntax have mostly replaced hand-written solutions such as the revealing module pattern.
ES6 has built-in support for modules. Alas, no JavaScript engine supports them natively, yet. But tools such as browserify, webpack or jspm let you use ES6 syntax to create modules, making the code you write future-proof.

4.18.1 Multiple exports

4.18.1.1 Multiple exports in CommonJS
In CommonJS, you export multiple entities as follows:
//------ lib.js ------
var sqrt = Math.sqrt;
function square(x) {
    return x * x;
}
function diag(x, y) {
    return sqrt(square(x) + square(y));
}
module.exports = {
    sqrt: sqrt,
    square: square,
    diag: diag,
};

//------ main1.js ------
var square = require('lib').square;
var diag = require('lib').diag;

console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
Alternatively, you can import the whole module as an object and access square and diag via it:
//------ main2.js ------
var lib = require('lib');
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
4.18.1.2 Multiple exports in ES6
In ES6, multiple exports are called named exports and handled like this:
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

//------ main1.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
The syntax for importing modules as objects looks as follows (line A):
//------ main2.js ------
import * as lib from 'lib'; // (A)
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5

4.18.2 Single exports

4.18.2.1 Single exports in CommonJS
Node.js extends CommonJS and lets you export single values from modules, via module.exports:
//------ myFunc.js ------
module.exports = function () { ··· };

//------ main1.js ------
var myFunc = require('myFunc');
myFunc();
4.18.2.2 Single exports in ES6
In ES6, the same thing is done via a so-called default export (declared via export default):
//------ myFunc.js ------
export default function () { ··· } // no semicolon!

//------ main1.js ------
import myFunc from 'myFunc';
myFunc();
More information: chapter “Modules”.

4.19 What to do next

Now that you got a first taste of ES6, you can continue your exploration by browsing the chapters: Each chapter covers a feature or a set of related features and starts with an overview. The last chapter collects all of these overview sections in a single location.
Next: II Data

среда, 9 ноября 2016 г.

Аутентификация пользователей с помощью Node.js, Express, Passport, и Orchestrate




via
Одним из самых заковыристых моментов во время разработки моего первого приложения оказалась реализация аутентификации пользователей. Часов, которые я провел за клавиатурой в попытках заставить эту систему работать, вместо того, чтобы радостно совершать логины и логауты, уже не вернешь. Так позвольте же мне помочь вам проскочить через все эти хитрые ловушки! В этой статье мы попробуем построить простое приложение с целью исследовать реализацию аутентификации пользователей с помощью базы данных Orchestrate и Node.js-приложения на базе сервера Express в сочетании с модулем  Passport. Мы организуем локальную аутентификацию, а также аутентификацию через Twitter, Google и Facebook. Я покажу как создавать User Authentication Strategy, как называет их Passport, применительно к каждому из перечисленных четырех методов. Первый раздел показывает, как производить установку нашего приложения и аутентифицировать локальных пользователей.

Начинаем разработку

Давайте сначала сделаем обзор технологий, которые мы собираемся использовать:
  • приложение, написанное для среды Node.js
  • запускается на сервере Express
  • с модулем Passport для аутентификации
  • базой данных Orchestrate для хранения и получения информации о пользователях 
  • Twitter Bootstrap - для организации красивого внешнего вида
  • Handlebars - для шаблонов
В этом разделе мы:
  • соберем наше базовое приложение
  • соберем шаблоны внешнего вида
  • настроим маршрутизацию
  • сконфигурируем Passport для локальной регистрации и входа
  • протестируем регистрацию и логин
  • дружески похлопаем себя по спине и расскажем друзьям о том, какие мы крутые.

Базовое приложение

Для начала нам нужно установить базовое приложение. Если у вас уже установлены среда Node.js и пакетный менеджер npm, тогда вперед! Если нет - отправляйтесь на сайт Node.js. там вы найдете всё необходимое. Теперь давайте установим необходимые зависимости и пакеты. Создайте файл package.json вручную или скачайте его в составе репозитория, содержащего материалы данной стать, с  GitHub (ссылка приводится в конце текста). Убедитесь, что в вашем файле package.json присутствуют такие строки:
 
//package.json
{
 "name": "userAuth",
 "main": "index.js",
 "dependencies": {
   "bcryptjs": "^0.7.12",
   "express": "^4.9.5",
   "express-handlebars": "^1.1.0",
   "morgan": "^1.3.2",
   "body-parser": "^1.9.0",
   "cookie-parser": "^1.3.3",
   "method-override": "^2.2.0",
   "express-session": "^1.8.2",
   "orchestrate": "^0.3.11",
   "passport": "^0.2.1",
   "passport-facebook": "^1.0.3",
   "passport-google": "^0.3.0",
   "passport-local": "^1.0.0",
   "passport-twitter": "^1.0.2",
   "q": "^1.0.1"
 }
}
 
Создав и проверив файл, запустите команду:
 
$ npm install
 
Теперь, когда установлены необходимые зависимости, самое время создать наш сервер. (Обратите внимание: в данном разделе будут задействованы не все эти зависимости, и это нормально, поскольку некоторые из них понадобятся нам в будущем). Давайте пробежимся по файлу index.js, в котором описан наш сервер, и задействуем необходимые пакеты:
 
//index.js/
var express = require('express'),
    exphbs = require('express-handlebars'),
    logger = require('morgan'),
    cookieParser = require('cookie-parser'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    session = require('express-session'),
    passport = require('passport'),
    LocalStrategy = require('passport-local'),
    TwitterStrategy = require('passport-twitter'),
    GoogleStrategy = require('passport-google'),
    FacebookStrategy = require('passport-facebook');

//Эти файлы пока не нужны, их создадим чуть позже:
// var config = require('./config.js'), //config file contains all tokens and other private info
//    funct = require('./functions.js'); //funct file contains our helper functions for our Passport and database work

var app = express();

//===============PASSPORT===============

//Здесь будет всё, что касается модуля Passport.

//===============EXPRESS================
// Сконфигурируем Express
app.use(logger('combined'));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(methodOverride('X-HTTP-Method-Override'));
app.use(session({secret: 'supernova', saveUninitialized: true, resave: true}));
app.use(passport.initialize());
app.use(passport.session());

// Установим модуль поддержки "вечных" сессий:
app.use(function(req, res, next){
  var err = req.session.error,
      msg = req.session.notice,
      success = req.session.success;

  delete req.session.error;
  delete req.session.success;
  delete req.session.notice;

  if (err) res.locals.error = err;
  if (msg) res.locals.notice = msg;
  if (success) res.locals.success = success;

  next();
});

// Сконфигурируем Express lkz hf,jns с шаблонами handlebars:
var hbs = exphbs.create({
    defaultLayout: 'main', // вскоре займемся этим шаблоном
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');

//===============ROUTES===============

// Здесь будут маршруты.

//===============PORT=================
var port = process.env.PORT || 5000; //select your port or let it pull from your .env file
app.listen(port);
console.log("listening on " + port + "!");
 
Всё готово к тестированию нашего сервера:
 
$ node index.js
 
Если всё настроено без ошибок, мы должны увидеть сообщение со строкой “listening on port 5000!” или другим портом, который вы задали при запуске приложения. Двинемся дальше и остановим сервер, настало время создать шаблоны, чтобы приложение обрело приятный внешний вид.
Если вы создаете файлы вручную, а не клонировали их из репозитория, создайте папку views в каталоге вашего проекта. В папке  views будут находиться два файла (home.handlebars и signin.handlebars) и одна подпапка (layouts). Подпапка layouts будет содержать один файл (main.handlebars), который отвечает за внешний вид по умолчанию нашего приложения. Файл main.handlebars выглядит так:
 
<!-- views/layouts/main.handlebars -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="User Authentication">
  <meta name="author" content="">

  <title>User Authentication</title>

  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">

  </head>

  <body>

    <div class="container">

      <nav class="navbar navbar-default" role="navigation">
      <div class="container-fluid">

      <!-- Brand and toggle get grouped for better mobile display -->
      <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
      </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
          <ul class="nav navbar-nav">
            <li>
              {{#if user}}
                <p class="navbar-text">
                  <strong>Hi,</strong>
                  <img src="{{user.avatar}}" width="20" height="20">
                  {{user.username}}
                </p>
                </li>
                </ul>
                <ul class="nav navbar-nav navbar-right">
                  <li>
                    <a href="/logout">Log Out</a>
                  </li>
              {{else}}
                <a href="/signin">Sign In</a>
                </li>
              {{/if}}
          </ul>
        </div><!-- /.navbar-collapse -->
      </div><!-- /.container-fluid -->
    </nav>

    {{#if error}}
      <p class="alert alert-warning">{{error}}</p>
    {{/if}}

    {{#if success}}
      <p class="alert alert-success">{{success}}</p>
    {{/if}}

    {{#if notice}}
      <p class="alert alert-info">{{notice}}</p>
    {{/if}}

    <!--where our other templates will insert-->
    {{{body}}}

    </div> <!-- /container -->

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>

    <!-- Latest compiled and minified JavaScript -->
  <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
</body>
</html>
 
Вот содержимое файла home.handlebars:
 
<!-- views/home.handlebars -->
{{#if user}}
  <div class="jumbotron">
    <h1>Orchestrate User Authentication</h1>
    <p>Below is your profile information!</p>
  </div>
  <div class="panel panel-default">
    <div class="panel-heading">
      <h3 class="panel-title">Profile Information</h3>
    </div>
    <div class="panel-body">
      <p>Username: {{user.username}}</p>
      <p>Avatar: <img src="{{user.avatar}}"/></p>
    </div>
  </div>

{{else}}
  <div class="jumbotron">
    <h1>Orchestrate User Authentication</h1>
    <p>Sign in and view your profile!</p>
    <p>
      <a href="/signin" class="btn btn-primary btn-lg" role="button">
        <span class="glyphicon glyphicon-user"></span>
        Sign in!
      </a>
    </p>
  </div>

{{/if}}
 
Наконец, наш signin.handlebars:

<!-- views/signin.handlebars -->
<div class="jumbotron">
  <h1>Sign in</h1>
  <p>We're using passport.js to demonstrate user authentication. Please sign-in with your Local, Twitter, Google, or Facebook account to view your profile.</p>
  <p>
    <a data-toggle="collapse" href="#local" class="btn btn-warning btn-lg" role="button"><span class="glyphicon glyphicon-user"></span> Sign in Locally</a>
    <a href="/auth/twitter" class="btn btn-info btn-lg" role="button"><span class="glyphicon glyphicon-user"></span> Sign in with Twitter</a>
    <a href="/auth/google" class="btn btn-danger btn-lg" role="button"><span class="glyphicon glyphicon-user"></span> Sign in with Google</a>
    <a href="/auth/facebook" class="btn btn-primary btn-lg" role="button"><span class="glyphicon glyphicon-user"></span> Sign in with Facebook</a>
  </p>

  <div id="local" class="collapse">
    <a data-toggle="collapse" href="#local-sign-in" class="btn btn-default btn-md" role="button">I already have an account</a>
    <a data-toggle="collapse" href="#local-reg" class="btn btn-default btn-md" role="button">I need to make an account</a>
  </div>

  <form id="local-sign-in" class="collapse" action="/login" method="post">
    <div>
        <p></p>
        <label>Username:</label>
        <input type="text" name="username"/>
    </div>
    <div>
        <label>Password:</label>
        <input type="password" name="password"/>
    </div>
    <div>
        <input type="submit" class="btn btn-primary btn-sm" value="Log In"/>
    </div>
  </form>

  <form id="local-reg" class="collapse" action="/local-reg" method="post">
    <div>
        <p></p>
        <label>New Username:</label>
        <input type="text" name="username"/>
    </div>
    <div>
        <label>New Password:</label>
        <input type="password" name="password"/>
    </div>
    <div>
        <input type="submit" class="btn btn-primary btn-sm" value="Register"/>
    </div>
  </form>
</div>

Маршруты

Теперь, когда мы разобрались с внешним видом, авайте пропишем маршруты, чтобы мы реально смогли увидеть наши страницы. В секции routes файла index.js добавим следующие строки:
 
//===============ROUTES=================
//displays our homepage
app.get('/', function(req, res){
  res.render('home', {user: req.user});
});

//displays our signup page
app.get('/signin', function(req, res){
  res.render('signin');
});

//sends the request through our local signup strategy, and if successful takes user to homepage, otherwise returns then to signin page
app.post('/local-reg', passport.authenticate('local-signup', {
  successRedirect: '/',
  failureRedirect: '/signin'
  })
);

//sends the request through our local login/signin strategy, and if successful takes user to homepage, otherwise returns then to signin page
app.post('/login', passport.authenticate('local-signin', {
  successRedirect: '/',
  failureRedirect: '/signin'
  })
);

//logs user out of site, deleting them from the session, and returns to homepage
app.get('/logout', function(req, res){
  var name = req.user.username;
  console.log("LOGGIN OUT " + req.user.username)
  req.logout();
  res.redirect('/');
  req.session.notice = "You have successfully been logged out " + name + "!";
});
 
Хотя мы еще и не настроили стратегии модуля Passport, мы уже можем видеть домашнюю страницу и осуществлять вход с помощью браузера. Пока всё идет хорошо.

Модуль Passport

Говоря о стратегиях модуля Passport, давайте сначала рассмотрим как он вообще работает. Согласно документации, различные способы аутентификации, называемые стратегиями, могут быть установлены в виде модулей. В этй статье мы задействуем так называемую локальную стратегию. Toon Ketels хорошо описывает последовательность работы модуля Passport в своем блоге. Важно здесь то, что когда пользователь вводит свои учетные данные, запрос попадает в функцию authenticate() модуля Passport, и задействует ту стратегию, которую мы сконфигурировали для данного маршрута. Эти учетные данные (req.body.username и req.body.password) попадают "внутрь" стратегии и подвергаются верификации. Если она прошла успешно, данные пользователя будут преобразованы в идентификатор сессии и произойдет взод в приложение, если нет - стратегия вернет сообщение об ошибкеи функция authenticate() перенаправит запрос на соответствующую страницу. Процесс выглядит так:



orchestrate-passport-strategy



Теперь, когда мы понимаем, как работает Passport, бовайте создадим нашу локальную стратегию Для нашего приложения мы задействуем две локальные стратегии, одну для регистрации нового пользователя (signup) и одну для входа уже существующего (signin). У нас уже есть маршруты, использующие каждую из них, и теперь нужно сделать так, чтобы обе стратегии соответствовали каждому из них. Перед построением стратегий создадим несколько вспомогательных функций для сохранения данных в базе Orchestrate и извлечения из нее, а также файл для хранения токенов. Начнем с того, что раскомментируем нужные строки в файле index.js:
 
// index.js/

var config = require('./config.js'), //config file contains all tokens and other private info
    funct = require('./functions.js'); //funct file contains our helper functions for our Passport and database work

Содадим файл config.js, в котором будет храниться ключ Orchestrate API key:
 
// config.js/
module.exports = {
  "db": “YOUR_ORCHESTRATE_API_KEY”
}
 
Теперь подключаем файл functions.js, содержащий полезные функции:
 
// functions.js/
var bcrypt = require('bcryptjs'),
    Q = require('q'),
    config = require('./config.js'), //config file contains all tokens and other private info
    db = require('orchestrate')(config.db); //config.db holds Orchestrate token

//used in local-signup strategy
exports.localReg = function (username, password) {
  var deferred = Q.defer();
  var hash = bcrypt.hashSync(password, 8);
  var user = {
    "username": username,
    "password": hash,
    "avatar": "http://placepuppy.it/images/homepage/Beagle_puppy_6_weeks.JPG"
  }
  //check if username is already assigned in our database
  db.get('local-users', username)
  .then(function (result){ //case in which user already exists in db
    console.log('username already exists');
    deferred.resolve(false); //username already exists
  })
  .fail(function (result) {//case in which user does not already exist in db
      console.log(result.body);
      if (result.body.message == 'The requested items could not be found.'){
        console.log('Username is free for use');
        db.put('local-users', username, user)
        .then(function () {
          console.log("USER: " + user);
          deferred.resolve(user);
        })
        .fail(function (err) {
          console.log("PUT FAIL:" + err.body);
          deferred.reject(new Error(err.body));
        });
      } else {
        deferred.reject(new Error(result.body));
      }
  });

  return deferred.promise;
};

//check if user exists
    //if user exists check if passwords match (use bcrypt.compareSync(password, hash); // true where 'hash' is password in DB)
      //if password matches take into website
  //if user doesn't exist or password doesn't match tell them it failed
exports.localAuth = function (username, password) {
  var deferred = Q.defer();

  db.get('local-users', username)
  .then(function (result){
    console.log("FOUND USER");
    var hash = result.body.password;
    console.log(hash);
    console.log(bcrypt.compareSync(password, hash));
    if (bcrypt.compareSync(password, hash)) {
      deferred.resolve(result.body);
    } else {
      console.log("PASSWORDS NOT MATCH");
      deferred.resolve(false);
    }
  }).fail(function (err){
    if (err.body.message == 'The requested items could not be found.'){
          console.log("COULD NOT FIND USER IN DB FOR SIGNIN");
          deferred.resolve(false);
    } else {
      deferred.reject(new Error(err));
    }
  });

  return deferred.promise;
}
 
Функция localReg будет использоваться в нашей локальной стратегии для регистрации нового пользователя и созранения данных о нем в базе данных. Она проверяет объект user, шифруя при этом пароль. Затем функция проверяет, не суествует ли уже такой пользователь в базе данных. Если да - запрос отклоняется и возвращается ложное значение, что предотвращает повторную регистрацию пользователей. Если регистрирующийся пользователь не существует, объект user, созданный нами, будет сохранен с использованием поля username как ключа. Затем этот объект будет озвращен, чтобы стратегия могда произвести его верификацию.

Функция  localAuth используется для проверки соответствия данных пользователя тем, что хранятся в базе данных. Она ищет в базе данных запись по заданному ключу username. Если таковая найдена, извлеченный из базы данных пароль сверяется с предоставленным в запросе. Если проверка пароля не прошла, запрос будет отклонен с возвратом ложного значения. Если пароль введен правильно, вернется объект user, который будет передан стратегии для верификации.
Теперь, написав вспомогательные функции, давайте создадим, наконец, нашу локальную стратегию! В секции модуля Passport файла index.js добавим:
 
// index.js/
//===============PASSPORT=================
// Use the LocalStrategy within Passport to login/”signin” users.
passport.use('local-signin', new LocalStrategy(
  {passReqToCallback : true}, //allows us to pass back the request to the callback
  function(req, username, password, done) {
    funct.localAuth(username, password)
    .then(function (user) {
      if (user) {
        console.log("LOGGED IN AS: " + user.username);
        req.session.success = 'You are successfully logged in ' + user.username + '!';
        done(null, user);
      }
      if (!user) {
        console.log("COULD NOT LOG IN");
        req.session.error = 'Could not log user in. Please try again.'; //inform user could not log them in
        done(null, user);
      }
    })
    .fail(function (err){
      console.log(err.body);
    });
  }
));
// Use the LocalStrategy within Passport to register/"signup" users.
passport.use('local-signup', new LocalStrategy(
  {passReqToCallback : true}, //allows us to pass back the request to the callback
  function(req, username, password, done) {
    funct.localReg(username, password)
    .then(function (user) {
      if (user) {
        console.log("REGISTERED: " + user.username);
        req.session.success = 'You are successfully registered and logged in ' + user.username + '!';
        done(null, user);
      }
      if (!user) {
        console.log("COULD NOT REGISTER");
        req.session.error = 'That username is already in use, please try a different one.'; //inform user could not log them in
        done(null, user);
      }
    })
    .fail(function (err){
      console.log(err.body);
    });
  }
));
 
Последний фрагмент секции Passport - сериализация и десериализация пользователя из данныхсессии. Поскольку на объект user очень простой, мы сериализируем и десериализируем его полностью, по по мере того, как он будет разростаться и усложняться, мы будем выбирать из него какой-нибудь один аспект. Для наших целей достаточно вставить в секцию  Passport следующее:
 
// index.js/
//===============PASSPORT=================
// Passport session setup.
passport.serializeUser(function(user, done) {
  console.log("serializing " + user.username);
  done(null, user);
});

passport.deserializeUser(function(obj, done) {
  console.log("deserializing " + obj);
  done(null, obj);
});
 
Следует заметить, что если в вашем приложении есть зоны, которые вы хотите защитить так, чтобы только авторизованные пользователи имели доступ к ним, Passport предоставляет простую возможность для этого. Просто используйте следующую функцию в ваших маршрутах к защищенным секциям:
 
// Simple route middleware to ensure user is authenticated.
function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) { return next(); }
  req.session.error = 'Please sign in!';
  res.redirect('/signin');
}
 
Теперь у нас есть приложение с работающей локальной аутентификацией! Испытаем ее. Если мы запустим сервер и направим браузер на нужный  порт, мы увидим большую синюю кнопку, кликнув по которой вызовем меню  “Sign in”. Отсюда пункт “Sign in Locally” предоставит возможность либо войти с существующей записью, либо создать новую. Поскольку у нас еще нет пользователей в базе данных, нам нужно будет создать новую. Вы можете проверить безопасность, попробовав войти в несуществующий аккаунт, что, как увидите, запрещено. Создав учетную запись, вы должны получить возможность войти на сайт.  Если вы заглянете в вашу базу данных Orchestrate, то в разделе “local-users” увидите нового пользователя. Подравляю! Вы освоили локальную аторизацию!