Website development
JavaScript
Derby.js
May 2014 18

Изучаем Derby 0.6, пример #3

Tutorial

Данный урок, продолжение серии уроков по derbyjs — реактивному фуллстек фреймворку. Читать предыдущие уроки обязательно (первый, второй).

Этот урок будет состоять из двух частей: первая — обзор серверной части дерби-приложения, и вторая — использование модуля derby-auth (обертки над passportjs) для регистрации/авторизации в дерби-приложении с использованием в том числе и социальных сетей.

Часть 1 — серверный код дерби-приложения


Подготовка

В предыдущих примерах для облегчения восприятия информации в качестве серверной части мы использовали derby-starter. Этот модуль скрывал от нас подробности серверной настройки. Напомню, что на сервере, derby-приложение строится поверх стандартного expressjs-приложения, в частности там происходит настройка базы данных, подключение нужных нам express-овских middlware. Для нашего сегодняшнего примера необходимо более глубоко разобраться с серверной частью — derby-starter мы использовать не будем.

Если кто-то не знаком с expressjs либо плохо знает nodejs — советую замечательный курс Ильи Кантора.

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

В качестве базы для нашего сегодняшнего примера необходимо скопировать мой репозиторий derby-boilerplate. По сути — это минимальное приложение, аналогичное тому, что было у нас в первом примере, но серверная часть теперь не в derby-starter-е, а в самом проекте (так же здесь используется последний 4-й экспресс, и вообще не используется redis — теперь можно и без него). Копируем командой:

git clone https://github.com/derbyparty/derby-boilerplate.git


Давайте исследуем структуру проекта — она уже вполне боевая в отличии от того, что было в предыдущих примерах:

src/
  app/
  server/
styles/
views/
index.js

В папке app будет наше дерби-приложение. Схематично, в папке server будет находится «аналог» derby-starter-а (я действительно взял derby-starter, скопировал его туда и чуть причесал), его содержимое мы разберем подробней.

В папках styles и views — стили и шаблоны соответственно. Файл index.js аналогичен тому, что было в предыдущих примерах — несколько строк, запускающих все это дело.

Серверная часть derby-приложения

Прежде всего еще раз обращу ваше внимание на то, что дерби работает поверх expressjs-приложения, поэтому если вы не знаете, что это такое, что такое express-middleware, как они работают, как работают сессии в экспрессе, роутинг — здесь вам нужно остановиться и устранить недостатки в знаниях.

Посмотрим, что у нас в папке server:
error/
index.js
server.js

В папочку error я вынес обработчик ошибок, его я здесь не буду. Скажу только, что это expressjs-middlware, которое срабатывает, когда для набранного url нет обработчика, либо в процессе работы приложения произошли ошибки. Если хотите разберитесь сами.

Основное предназначение файла index.js — подцепить express (который настраивается в файле server.js) и поднять сервер, по указанному в настройках порту:
Исходный код (практически без изменений взят из derby-starter)
var derby = require('derby');

exports.run = function (app, options, cb) {

  options = options || {};
  var port = options.port || process.env.PORT || 3000;

  derby.run(createServer);

  function createServer() {
    if (typeof app === 'string') app = require(app);

    var expressApp = require('./server.js').setup(app, options);

    var server = require('http').createServer(expressApp);
    server.listen(port, function (err) {
      console.log('%d listening. Go to: http://localhost:%d/', process.pid, port);
      cb && cb(err);
    });
  }
}


Дальше идет самое интересное, настройка expressjs, с добавленным derby. Разбор этого момента — ключ к пониманию того, как вообще работает дерби.

Я использую 4-й экспресс, у него есть несколько отличий от 3-го. Основное — то, что стандартный middlware теперь отсутствуют в дистрибутиве express и их необходимо устанавливать отдельно.

Вот так, примерно выглядел бы этот файл без дерби:

Код server.js, если бы мы делали обычное expressjs-приложение:
var express             = require('express');

// В 4-ом экспрессе все middleware вынесены в отдельные модули
// приходится каждый из них подключать по отдельности
var session             = require('express-session');

// Сессии будем хранить в монге
var MongoStore          = require('connect-mongo')(session);

// Обработчик ошибок - я вынес его в отдельную папочку,
// чтобы не отвекал
var midError            = require('./error');

var MongoClient = require('mongodb').MongoClient;

exports.setup = function setup(app, options) {

  var mongoUrl = process.env.MONGO_URL || process.env.MONGOHQ_URL || 'mongodb://localhost:27017/derby-app';

  // Инициализируем подкючение к БД 
  MongoClient.connect(mongoUrl);

  var expressApp = express()

 if (options && options.static) {
    expressApp.use(require('serve-static')(options.static));
  }

  expressApp.use(require('cookie-parser')());
  expressApp.use(session({
    secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE',
    store: new MongoStore({url: mongoUrl})
  }));

  // Если бы у на были обычные экспрессовские роуты - мы бы положили их СЮДА

  // Маршрут по умолчанию - генерируем 404 ошибку
  expressApp.all('*', function(req, res, next) { next('404: ' + req.url); });

  // Обработчик ошибок
  expressApp.use(midError());

  return expressApp;
}


В двух словах напомню, как это работает. Сначала подключаются модули, потом идет самое важное — подключение экспрессовских middleware (через expressApp.use). По сути эти middlware — это простые функции, которые будут в том же порядке, как зарегистрированы, вызываться для каждого запроса, пришедшего на сервер. Каждый из этих middleware может либо ответить на запрос, завершив цепочку обработки (оставшимся middlware управление передано не будет), либо произвести какие-то промежуточные действия с запросом (например, распарсить cookies, по кукам определить сессию и т.д.) и передать управление дальше по цепочке. Порядок подключения middlware очень важен — именно в этой последовательности функции будут вызываться для каждого запроса.

А вот так выглядит дерби-вариант — комментированный код server.js с дерби
// 4-ый экспресс
var express             = require('express');

// В 4-ом экспрессе все middleware вынесены в отдельные модули
// приходится каждый из них подключать по отдельности
var session             = require('express-session');

// Сессии будем хранить в монге
var MongoStore          = require('connect-mongo')(session);

// Обработчик ошибок - я вынес его в отдельную папочку,
// чтобы не отвекал
var midError            = require('./error');

var derby               = require('derby');

// BrowserChannel - аналог socket.io от Гугла - транспорт, используемый
// дерби, для передачи данных из браузеров на сервер

// liveDbMongo - драйвер монги для дерби - умеет реактивно обновлять данные
var racerBrowserChannel = require('racer-browserchannel');
var liveDbMongo         = require('livedb-mongo');

// Подключаем механизм создания бандлов browserify
derby.use(require('racer-bundle'));

exports.setup = function setup(app, options) {

  var mongoUrl = process.env.MONGO_URL || process.env.MONGOHQ_URL || 'mongodb://localhost:27017/derby-app';

  // Инициализируем подкючение к БД (здесь же обычно подключается еще и redis)
  var store = derby.createStore({
    db: liveDbMongo(mongoUrl + '?auto_reconnect', {safe: true})
  });

  var expressApp = express()

  // Здесь приложение отдает свой "бандл"
  // (т.е. здесь обрабатываются запросы к /derby/...)
   expressApp.use(app.scripts(store));

  if (options && options.static) {
    expressApp.use(require('serve-static')(options.static));
  }

  // Здесь в бандл добавляется клиетский скрипт browserchannel,
  // и возвращается middleware, обрабатывающее клиентские сообщения
  // (browserchannel основан на longpooling - т.е. здесь обрабатываются
  // запросы к адресу /channel)
  expressApp.use(racerBrowserChannel(store));

  // В req добавляется метод getModel, позволяющий обычным
  // express-овским котроллерам читать и писать в БД
  // см. createUserId
  expressApp.use(store.modelMiddleware());

  expressApp.use(require('cookie-parser')());
  expressApp.use(session({
    secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE',
    store: new MongoStore({url: mongoUrl})
  }));

  expressApp.use(createUserId);

  // Здесь регистрируем контроллеры дерби-приложения,
  // они будут срабатывать, когда пользователь будет брать страницы
  // с сервера
  expressApp.use(app.router());

  // Если бы у на были обычные экспрессовские роуты - мы бы положили их 
  // СЮДА

  // Маршрут по умолчанию - генерируем 404 ошибку
  expressApp.all('*', function(req, res, next) { next('404: ' + req.url); });

  // Обработчик ошибок
  expressApp.use(midError());

  return expressApp;
}

// Пробрасываем id-юзера из сессии в модель дерби,
// если в сессии id нет - генерим случайное
function createUserId(req, res, next) {
  var model = req.getModel();
  var userId = req.session.userId;
  if (!userId) userId = req.session.userId = model.id();
  model.set('_session.userId', userId);
  next();
}


Потратьте определенное время, чтобы понять все это. По сути основное здесь то, что используется browserify — он нужен для того, чтобы отдавать клиенту в браузер так называемый «бандл» — блок данных, содержащий все javascript-файлы дерби-приложения, а также шаблоны (css-скрипты здесь содержаться не будут). Нужно понимать, что при обычном запросе какой-нибудь страницы сайта клиентом — в браузер сразу бандл не отдается, это было бы слишком накладно. Отдается уже отрендеренная страница со стилями, и с какими-то первоначальными данными. Это логично ведь здесь очень важна скорость. Далее уже сама эта страница подгружает «бандл» — делается запрос по адресу "/derby/:bandle-name".

Я уже говорил, что одной серверной части дерби может соответствовать несколько «дерби-приложений» — по сути для каждого из них будет свой бандл. Это логично. Так мы, например, можем отделить клиентскую часть сайта от админки — чтобы не передавать лишние данные всем подряд. Если так, то нужно будет прописать:

   expressApp.use(clientApp.scripts(store));
   expressApp.use(adminApp.scripts(store));


То же касается и обработки роутов. Если у на будет 2 «дерби-приложения», мы получим вместо:

  expressApp.use(app.router());


подключение двух роутеров приложений:

  expressApp.use(clientApp.router());
  expressApp.use(adminApp.router());


Ну и еще вы должны понимать то, что кроме обработчиков роутов «дерби-приложений» мы здесь вполне можем добавить своих expressjs обработчиков, организуя любой, необходимый нам RESTFull API, как говорится, с блекджеком и барышнями. Это все работает благодаря тому, что клиентский роутер дерби-приложений, не находя нужного обработчика у себя в приложении, просто отправляет запрос для обработки на сервер.

Следующим важным моментом является подключение browserchannel — это гугловский аналог socket.io. По сути это транспорт, благодаря которому наше приложение в реальном времени будет синхронизировать данные с сервером. Под капотом находится то, что в определенный момент клиентский скрипт browserchannel добавляется в бандл, а так же обработка запросов от этого модуля (он основан на longpooling — поэтому требуется обработка, в данном случае по адресу /channel)

Да, хотел бы отметить, что в моем примере базовой настройки дерби не используется redis. Недавно вышло обновление дерби, и теперь, redis не является обязательным (он еще нужен, если мы будем делать горизонтальное масштабирование — то есть для повышения производительности запускать сразу несколько дерби-серверов).

Итак, в данный момент вы должны понять, что данная структура позволяет очень гибко добавлять что угодно в наше приложение. Будь то какие-то expressjs-middlware, плагины дерби, отдельные дерби-приложения, экспресс-обработчики — что угодно. Все это будет спокойно жить вместе.

Так, ну и последний момент на который нужно обратить внимание, особенно в свете вопроса авторизации — сессии. Как вы могли заметить сессии в дерби используются вполне себе express-овские. То есть все происходит по классической схеме: для идентификации используется кука (которая будет храниться в браузере клиента), сами сессии хранятся в mongodb в коллекции sessions.

Если заглянуть в функцию createUserId:

function createUserId(req, res, next) {
  var model = req.getModel();
  var userId = req.session.userId;
  if (!userId) userId = req.session.userId = model.id();
  model.set('_session.userId', userId);
  next();
}

Видно, что в ней из сессии извлекается userId, если же он отсутствует, id генерируется случайным образом (model.id()) и записывается и в модель по пути _session.userId (эти данные будут сериализованы и переданы на клиент) и в саму сессию.

Представим себе несколько вариантов запросов:

  1. Пользователь первый раз зашел на сайт и запросил главную страницу — на сервере будет сформирована новая кука, новая сессия (с новым случайным userId) — в браузере отрисовывается главная страница и если мы на клиенте заглянем в модель, то по пути '_session.userId' — увидим наш вновь сформированный айдишник. Теперь бродя по страницам нашего приложения (запросов к серверу не будет) — у клиента всегда есть его Id, и если он сделает какие-то запросы к серверу, мы всегда по куке и по сессии сможем его распознать.
  2. Пользователь так и не зарегистрировался и зашел через неделю. Кука у него сохранилась — ему выдастся тот же userId — все нормально.
  3. Пользователь зашел с другого браузера — то же что и в п. 1

Предположим, как бы мы разработали регистрацию/авторизацию (с точки зрения сервера). Допустим в браузере клиент ввел регистрационные данные — дальше они должны попасть на сервер, где мы бы создали запись в коллекции users, даже id придумывать уже не надо, он уже есть.

При авторизации сложнее, клиент зайдя на сайт получит случайный id, потом зайдет на страницу авторизации — введет логин/пароль, передаст это все на сервер, тут сервер поймет, кто это и должен поменять userId в сессии и в _session.userId.

Что бы мы там хранили в этой коллекции. Скорее всего: username, email, passwordHash, может быть какие-то свои статистические данные. Если на нашем сайте была бы авторизация (регистрация) через соц. сети, то здесь же хранились бы ключи соц.сетей. Еще, конечно, пользовательские настройки — их бы тоже было бы неплохо хранить здесь.

Естественно нам бы захотелось, чтобы часть этой коллекции никто бы не смог просмотреть на клиенте. Сейчас с этим нет проблем. Недавно в дерби появились так называемые проекции (возможность подписываясь на коллекцию, подписываться не на весь документ целиком, а только на определенные его поля), но еще пару недель назад — это было невозможно, и модуль derby-auth, который мы будем рассматривать, этого еще не умеет. Там используется разделение пользовательских данных на 2 части: первая — коллекция auths (здесь только приватные данные), вторая users — здесь открытые общедоступные данные. Итак, derby-auth

Часть 2 — авторизация в derby-приложении (модуль derby-auth)


Немного о derby-auth

На данный момент существует 2 пакета — авторизации для дерби. Это derby-auth и derby-passport. Обе они являются обертками над passportjs, (одна из них, по-моему, форк другой). Актуальные версии и той и той предназначены для использования с пятой версией дерби (напомню 6-ая еще в альфе), под 6-ую еще не обновили, но это не мешает нам использовать derby-auth во всех своих проектах на 6-ке (недоступны только шаблоны html форм, но у нас они все-равно кастомные).

Мой выбор в пользу derby-auth только потому, что я с ним работал.

Еще одно ограничение, о котором я уже упоминал, это то, что пользовательские данные в обоих этих проектах разбиты на две части. Общедоступные данные, должны храниться в коллекции users, а закрытые в auths. Недавно в дерби появилась возможность делать «проекции» — то-есть подписываться только на определенные поля коллекции — думаю, когда эти модули обновятся до 6-ой версии дерби — ограничение уйдет.

update
Через несколько дней после написания данной статьи Владимир Махаев (@vmakhaev) разработал офигенный модуль авторизации derby-login по мотивам derby-auth. Он под 0.6, поддерживает проекции, в нем есть уже готовые компоненты для регистрации, входа, смены пароля — короче, используйте только его.

Кстати, не забудьте поблагодарить Владимира, хотя бы звездочкой на гитхабе. Очень полезную вещь он сделал.

Немного о passportjs


PassportJS — это middleware для авторизации под node.js. Это абстрагирующий модуль, позволяющий вашему приложению использовать как обычную авторизацию логином и паролем, так и авторизацию через практически любой сервис, поддерживающий Oauth, Oauth 2 и т.д. авторизацию. Способы авторизации называются здесь стратегиями. Например, авторизация через логин/пароль называется LocalStrategy («локальной стратегией авторизации»), а автризация через GitHub — GithubStrategy. Каждая стратегия вынесена в отдельный модуль, таким образом подключение всего этого дела к node.js-приложению будет выглядеть примерно так:
var passport       = require('passport');
var LocalStrategy  = require('passport-local').Strategy;
var GithubStrategy  = require('passport-github').Strategy;


Здесь же следует отметить особенности использования стратегий авторизации через социальные сети. Если кто-то еще не знает, то прежде, чем наше приложение сможет использовать такую авторизацию, необходимо его (приложение) зарегистрировать в соц. сети и получить два параметра, обчно это appId (или clientId — некий уникальный id-шник нашего приложения в этой соц. сети) и Secret (секретный ключ, который никому нельзя говорить, он будет использоваться для того, чтобы наше приложение смогло подтвердить, что мы — это мы). При регистрации так же необходимо будет ввести так называемый redirectURL — то-есть url-адрес, куда соц. сеть перенаправит браузер клиента после авторизации. В моем примере это будет: localhost:3000/auth/github/callback — но об этом чуть позже.

В приложении-примере, я буду использовать авторизацию через github, регистрация приложения там очень простая (у других провайдеров не сложнее). Прежде всего нужно иметь обычный пользовательский аккаунт, в его настройках легко создается приложение.


Никому не говорите мой секрет.

Подключаем derby-auth к нашему приложению

Итак, прежде всего ставим derby-auth напрямую из git-репозитория, почему-то мастер версия у них под предыдущую derby:

npm install git://github.com/cray0000/derby-auth#0.5 -S

Derby-auth автоматом ставит passport и passport-local, осталось установить passport-github:

npm install passport-github -S


Теперь переходим к настройкам. Все будем делать в соответствии с инструкцией — https://github.com/lefnire/derby-auth/tree/0.5. Большая часть действий по подключению осуществяется на сервере, то есть нам нужен src/server/server.js.

Шаг 1

Подключаем сам derby-auth, и настраиваем стратегии (естественно в нормальном приложении, настройки должны браться из файла-конфига или переменных окружения)

  // Подключаем derby-auth 
  var auth = require('derby-auth');

  var strategies = {
    github: {
      strategy: require("passport-github").Strategy,
      conf: {
        clientID: 'eeb00e8fa12f5119e5e9',
        clientSecret: '61631bdef37fce808334c83f1336320846647115'
      }
    }
  }


Понятно, что если бы стратегий было больше, все они были бы здесь перечислены:

Шаг 2

Устанавливаем опции:

  var options = {
    passport: {
      failureRedirect: '/login',
      successRedirect: '/'
    },
    site: {
      domain: 'http://localhost:3000',
      name: 'Derby-auth example',
      email: 'admin@mysite.com'
    },
    smtp: {
      service: 'Gmail',
      user: 'zag2art@gmail.com',
      pass: 'blahblahblah'
    }
  }

В принципе, все понятно из названий: failureRedirect, successRedirect — url-адресу куда клиент будет перенаправлен в случае не успешной/успешной авторизации соответственно. Информация о сайте нужна для отсылки в стратегии (например, ее могут использовать соц. сети, если соответствующие поля не были заполнены при регистрации приложения). Параметры почты нужны для того, чтобы работала возможность «забыл пароль — сбросьте его и отошлите мне новый на мыло».

Шаг 3

Инициализируем хранилище

  auth.store(store, false, strategies);


На этом шаге derby-auth добавляет контроль доступа к данным, пользователь не сможет получить чужие записи из коллекции auths — будет видеть только свою учетку. Второй параметр в данный момент не используется.

Шаг 4

Убедимся, что к экспрессу подключены middleware по хранению сессий и body-parser (у нас его пока нет — установим npm i body-parser -S и подключим):

  expressApp.use(require('cookie-parser')());
  expressApp.use(session({
    secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE',
    store: new MongoStore({url: mongoUrl})
  }));

  expressApp.use(require('body-parser')())
  // Добавим method-override тоже,
  // я обычно его ставлю, чтобы PUT стал доступен
  // в контроллерах
  expressApp.use(require('method-override')())


Ну и последнее — подключаем derby-auth middeware — здесь подключается куча всего об этом дальше:

expressApp.use(auth.middleware(strategies, options));


Давайте разберемся, что здесь происходит. Если уж это — middleware, логично предположить, что здесь регистрируются какие-то контроллеры expressjs, обрабатывающие запросы к определенным url. Так оно и есть — вот сводная информация о контроллерах:
Роут Метод Параметры Предназначение
/login post username, password Служит для входа в систему по логину и паролю
/register post username, email, password Служит для регистрации пользователя по логину и паролю
/auth/:provider get отсутствуют Переходя сюда мы будем перенаправлены на страницу авторизации соответствующей соц. сети.
Пример пути: /auth/github
/auth/:provider/callback get отсутствуют Сюда соц. сеть сернет управления после авторизации — derby-auth обработает это и перенаправить нас на failureRedirect или successRedirect url.
Пример пути: /auth/github/callback — этот url нужен для того, чтобы ввести его в настройках при регистрации приложения в соц. сети.
/logout get отсутствуют Сделав такой запрос мы «выйдем» из аккаунта и будем перенаправлены на '/'
/password-reset post email На соответствующий email будет отослан новый пароль (запрос должен быть AJAX)
/password-change post uid, oldPassword, newPassword Смена пароля соответствующего юзера (запрос должен быть AJAX)


В инструкции есть еще один (необязательный) шаг — подключение компонет (html и стили форм авторизации/регистрации и т.д.), но эти компоненты написаны под дерби 0.5 и еще не обновлены. В своем примере, я следаю простейшую реализацию форм, чтобы продемонстрировать базовые принципы работы. Но прежде необходимо обсудить еще один момент.

Что мы увидем в модели на клиенте


Derby-auth передает определенные данные на клиент, которые позволяют узнать авторизовался ли пользователь, его id, а так же вспомогательную информацию, описывающую проблемы со входом и регистрацией:
Пусть Описание
_session.loggedIn Булева переменная — авторизован/не авторизован пользователь
_session.userId id пользователя
_session.flash.error Массив строк — вспомогательные сообщения об ошибках (всевозможные ошибки авторизации/регистрации)
auth.{userId}.local Данные по локальной регистрации
auth.{userId}.{provider} Данные по регистрации через соответсвующую соц. сеть

Все это доступно в шаблонах и будет нами использовано, для определения статуса авторизации.

Пример приложения

Вообще, конечно, если бы визуальные компоненты из derby-auth подходили бы к 6-ой версии дерби, приложение написалось бы в два счета, без них сложнее. Чтобы упростить разработку, подключим bootstrap, для этого в src/app/index.js вписываем
app.use(require('d-bootstrap'));

предварительно сделав
npm i d-bootstrap -S

Третий бутстрап установлен.

В приложении я сделаю 2 страницы: первая, под адресу "/" со статусом авторизации, чтобы мы могли увидеть вошел ли пользователь в свою учетку, а так же учетные данные, вторая, по адресу '/login' непосредственно с фармами регистрации авторизации, чтобы поэкспериментировать. Так же перед рендерингом каждой из этих двух страниц, я буду подтягивать учетные пользовательские данные из коллекции auths.

Думаю файл дерби-приложения src/app/index.js не требует особых комментариев, единственное, что мы не обсуждали, это контроллер"*", но это стандартная штука для экспресса:

var derby = require('derby');
var app = module.exports = derby.createApp('auth', __filename);

global.app = app;

app.use(require('d-bootstrap'));


app.loadViews (__dirname+'/../../views');
app.loadStyles(__dirname+'/../../styles');

app.get('*', function(page, model, params, next){

  var user = 'auths.' + model.get('_session.userId');
  model.subscribe(user, function(){
    model.ref('_page.user', user);
    next();
  });

});

app.get('/', function (page, model){
  page.render('home');
});

app.get('/login', function (page, model){
  page.render('login');
});



По шаблонам поступим так — сделаем layout c менюшкой в файле index.html, а в файлы home.html и login.html положим, соответственно, страницу с информацией о статусе пользователя (вошел/не вошел, привязан ли github-аккаунт), и страницу с авторизацией/регистрацией.

Итак, index.html
<import: src="./home">
<import: src="./login">


<Title:>
  Derby-auth example

<Body:>
  <div class="navbar navbar-inverse" role="navigation">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="/">Derby-auth Example</a>
      </div>
      <div class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
          <view name="nav-link" href="/">Информация</view>
          <view name="nav-link" href="/login">Логин</view>
        </ul>
      </div>
    </div>
  </div>


  {{if _session.flash.error}}
    <div class="container">
      {{each _session.flash.error as #error}}
        <div class="alert alert-warning">{{#error}}</div>
      {{/}}
    </div>
  {{/}}


  <view name="{{$render.ns}}"></view>

<nav-link: element="nav-link">
  <li class="{{if $render.url === @href}}active{{/}}">
    <a href="{{@href}}">{{@content}}</a>
  </li>



Эта менюшка и вообще большая часть разметки здесь — чисто вспомогательные и особо не связаны с авторизацией как таковой (ну кроме alert-ов). Единственное, что здесь может вызвать вопросы, это:

<view name="{{$render.ns}}"></view>


Вместо этой строки дерби будет вставлять содержимое либо home.html, либо login.html, в зависимости от того, что мы прописали в контроллере: page.render('home') или page.render('login')

Страничка со вспомогательной информацией, home.html:

<index:>
  <div class="container">

    <h1>Информация:</h1>
    {{if _session.loggedIn}}
      <p>Пользователь вошел в систему</p>


      <h2>Локальная стратегия:</h2>
      {{if _page.user.local}}
        <p>Имя пользователя: <b>{{_page.user.local.username}}</b></p>
      {{else}}
        <p>Данные отсутствуют</p>
      {{/}}

      <h2>GitHub:</h2>
      {{if _page.user.github}}
        <p>Имя пользователя: <b>{{_page.user.github.username}}</b></p>
      {{else}}
        <p>Данные отсутствуют</p>
      {{/}}

      <a class="btn btn-danger" href="/logout">Выйти</a>
    {{else}}
      <p>Пользователь НЕ вошел в систему</p>
      Старница <a href="/login">Регистрации/Авторизации</a>
    {{/}}
  </div>


Обратите внимание на кнопку «Выйти» — здесь используется контроллер GET /logout из derby-auth

Не будем особо заморачиваться и возьмет форму логина вместе со стилями прямиком с сайта бутстрапа, там есть отличный пример «sign in» http://getbootstrap.com/examples/signin/

Стили тупо скопрированы из бутстраповского примера - файл slyles/index.styl
.form-signin {
  max-width: 330px;
  padding: 15px;
  margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
  margin-bottom: 10px;
}
.form-signin .checkbox {
  font-weight: normal;
}
.form-signin .form-control {
  position: relative;
  height: auto;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  padding: 10px;
  font-size: 16px;
}
.form-signin .form-control:focus {
  z-index: 2;
}
.form-signin input[type="email"] {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}



Файл login.html с авторизацией и регистрацией:

<index:>

  <view name="signin"></view>
  <hr/>
  <view name="signup"></view>

<signin:>
  <div class="container">
    <form class="form-signin" role="form" action='/login' method='post'>
      <h3 class="form-signin-heading">Вход</h3>
      <input name="username" type="text" class="form-control" placeholder="имя" required="" autofocus="">
      <input name="password" type="password" class="form-control" placeholder="пароль" required="">

      <button class="btn btn-lg btn-primary btn-block" type="submit">Войти</button>
      <br/>
      <a class="btn btn-lg btn-danger btn-block" href="/auth/github">Через GitHub</a>
    </form>
  </div>

<signup:>
  <div class="container">
    <form class="form-signin" role="form" action='/register' method='post'>
      <h3 class="form-signin-heading">Регистрация</h3>
      <input name="username" type="text" class="form-control" placeholder="имя" required="" autofocus="">
      <input name="email" type="email" class="form-control" placeholder="имеил" required="">
      <input name="password" type="password" class="form-control" placeholder="пароль" required="">

      <button class="btn btn-lg btn-primary btn-block" type="submit">Зарегистрироваться</button>
    </form>
  </div>


Если запустить все будет выглядеть примерно вот-так:


Бутстаповскую форму я немножко поменял, исходя из наших derby-auth контролленов. На что обращаем внимание в форме регистрации: это post форма ведущая к экшену /login — обрабатывать будет derby-auth, имена полей username и password — все из нашей таблички. Вход через GitHub — get запрос к /auth/github — тоже оттуда.

Форма регистрации. То же самое — post к /register, поля: username, email, password.

Запускаем приложение: npm start — смотрим статус — «пользователь не вошел в систему», регистрируемся, видим — «пользователь в системе». Можем выдти — /logout. Пока не вышли у нас есть возможность привязать к текущей учетке github-аккаунт, для этого нужно кликнуть «войти через github», мы будем перенаправлены на гитхаб, там авторизуемся, все подтвердим и вернемся обратно на сайт.

Derby-auth так же позволяет, не регистрируясь через локальную стратегию, регистрироваться на сайте через аккаунты соц. сетей. Мне больше по душе методика, применяемая на хабре, когда лишь зарегистрированный пользователь может привязать к своей учетке аккаунты соц. сетей, но для этого derby-auth потребуется немножко подправить.

Поиграйтесь с тем, что получислось. Просмотрите доступные данные, используя консоль разработчика (введите app.model.get() в консоли).

Прошу прощения, что мы не реализовали в примере оставшиеся фишки derby-auth (сброс и изменение пароля) — этот урок и так вышел довольно большим из-за необходимости объяснять серверную часть дерби.

Так же мы реализовали упрощенно формы, здесь дело еще и в том, что в полноценном приложении данные блоки будут выносится в отдельные компоненты, а мы их еще не изучали.

Итоговый вариант на github

Прошу тех, кто хоть что-то пробовал сам, отписаться в комментариях и рассказать, что получилось. Не можете писать комментарии на храбре — пишите на мыло. Интересно же, не зря я вообще пишу.

P. S.
Если не хотите пропустить следующие статьи по derbyjs, подписывайтесь на обновления в моем профиле: zag2art. Сам так делаю — на хабре же нет возможности добавить в трекер определенный (очень интересный) хаб, чтобы точно ничего не пропустить.

P.P.S.
Кстати, уже вышла дерби 0.6 alpha 7 — окончательный релиз все ближе и ближе.

Если нравится derbyjs — не сочтите за труд поставить звездочку на github
Ну так как, продолжать серию?
92.8% Да 130
7.1% Нет 10
Voted 140 users. Passed 17 users.
+17
8.5k 104
Comments 11
Top of the day