Статьи

Разработка веб-клиента с использованием Ember.js и мобильных служб Windows Azure. Часть 4

В предыдущих статьях ( 1 , 2 , 3 ) я рассмотрел создание одностраничного приложения на основе Ember.js, а затем подключил его к Windows Azure Mobile Services для хранения данных. Но в настоящее время это позволяет каждому публиковать анонимно. По крайней мере, мне нужна базовая аутентификация, чтобы я мог убедиться, что пользователь — реальный человек, и, возможно, привлечь его к ответственности за то, что он публикует.

Ограничение разрешений

Я хочу, чтобы только авторизованные пользователи могли писать сообщения, но я хочу, чтобы все могли их читать. Это можно сделать довольно легко с помощью портала управления Windows Azure , выполнив следующие действия:

  1. Откройте браузер и перейдите к своему экземпляру Mobile Service, выберите « Данные» , таблицу сообщений и, наконец, выберите « Разрешения» .

  2. Установите разрешения на вставку , обновление и удаление только для прошедших проверку пользователей . Оставьте прочтение в любом месте с ключом приложения .

  3. Нажмите кнопку Сохранить внизу, чтобы применить изменения.

На этом этапе, если вы попытаетесь создать новое сообщение с помощью приложения, оно молча завершится неудачей.

Настройка аутентификации

Службы Windows Azure Mobile Services упрощают настройку проверки подлинности с использованием Facebook, Google, учетной записи Microsoft (ранее LiveID) или Twitter в качестве поставщика проверки подлинности. Документация на WindowsAzure.com довольно обширна, чтобы уже настроить это, поэтому на этом шаге я просто следовал « Зарегистрировать ваше приложение для аутентификации» и настроить раздел « Мобильные сервисы » документа. Для моих провайдеров я просто использовал Google и Twitter.

Если вы следуете дальше, просто сделайте этот раздел и остановитесь, когда вы нажмете на «Ограничить разрешения для аутентифицированных пользователей», поскольку это уже было сделано на предыдущем шаге.

Реализация входа / выхода

Поскольку вход в систему / выход из системы глобален, я хотел, чтобы ссылка была очень заметной. Так что ставить его в конце панели навигации было естественным выбором.

Мне также нужен был способ глобального отслеживания состояния входа в систему, поэтому я использовал Ember.StateManager для отслеживания этого состояния.

Тогда есть действия для обработки входа и выхода. Они должны работать независимо от страницы, которую я просматриваю; Я не хочу реализовывать эти действия на всех контроллерах. К счастью, действия всплывают в ApplicationController, если они не обрабатываются другими контроллерами.

высказывать

Спасибо ветке StackOverflow за указание на решение этой проблемы. Вот LoginStateManager

  //login statemachine
  App.LoginStateManager = Ember.StateManager.create({
      initialState: "isNotAuthenticated",
      isAuthenticated: Ember.State.create({
          enter: function () {
              console.log("enter " + this.name);
          },
          logout: function (manager, context) {
              manager.transitionTo('isNotAuthenticated');
          }
      }),
      isNotAuthenticated: Ember.State.create({
          enter: function () {
              console.log("enter " + this.name);
          },
          login: function (manager, credentials) {
              console.log(credentials);
              manager.transitionTo('isAuthenticated');
          }
      })
  });

Состояние либо isNotAuthenticated (по умолчанию), либо isAuthenticated. Он будет входить в консоль при изменении состояния. Обратите внимание manager.transitionTo(). Это переход между состояниями.

Контроллер (ы)

Мне нужно было определить ApplicationController, так как именно там я и хотел обрабатывать события входа / выхода. Это также должно сказать конечному автомату об изменении состояния, а также предоставить свойство isAuthenticated, которое панель навигации может использовать, чтобы определить, показывать ли вход / выход из системы. Я также хотел использовать это в NewpostController, поэтому я также добавил его туда. И я хочу получить имя пользователя автоматически из токена аутентификации, поэтому я просто удалил поле автора здесь и в шаблоне ниже. Вот код:

//Application controller, since we want login to be an application wide thing
  //login/logout events are bubbled up to here regardless of the page you are on
  App.ApplicationController = Em.Controller.extend({

      authStateBinding: Ember.Binding.oneWay('App.LoginStateManager.currentState.name'),
      authState: null,

      isAuthenticated: function () {
          return (this.get('authState') == 'isAuthenticated');
      }.property('authState'),
      login: function(provider) {
        client.login(provider).then(function(results) {
          App.LoginStateManager.send("login", client.currentUser);
        });
      },
      logout: function() {
        client.logout();
        App.LoginStateManager.send("logout");
      }
  });
// Newpost controller
  App.NewpostController = Ember.ObjectController.extend({
    title: '',
    body: '',
    authStateBinding: Ember.Binding.oneWay('App.LoginStateManager.currentState.name'),
      authState: null,
    isAuthenticated: function () {
          return (this.get('authState') == 'isAuthenticated');
      }.property('authState'),
    save: function() {
      //create the post
      var now = new Date();
      var post=App.Post.create({
        title: this.get('title'),
        body: this.get('body'),
        posted: now.toString('dddd, MMMM, yyyy'),
      });
      post.save();
      // set these back to '' so the form is pretty
      this.set('title','');
      this.set('body','');
      //transition back to posts
      this.transitionToRoute('posts');
    }
  });

Обратите внимание, что isAuthenticatedпросто проверяет authState объекта LoginStateManager для определения состояния. Но чтобы добраться до этой точки, вы должны подключить привязку, которая возвращает текущее состояние.

Для событий входа в систему я передаю провайдера из пользовательского выбора на панели навигации в качестве providerпараметра, а затем использую его, client.login()чтобы сообщить Windows Azure Mobile Services, какого из сконфигурированных провайдеров использовать для этого запроса аутентификации.

Для перехода между состоянием входа / выхода из системы он просто отправляет вход / выход из системы менеджеру состояния вместе с информацией currentUser, возвращаемой client.login()запросом на вход в систему.

Navbar

Ниже приведена версия файла application.hbs, обновленная для обеспечения функциональности входа / выхода из системы:

<div class="navbar navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </a>
      {{#linkTo index class="brand"}}My Great Blog{{/linkTo}}
      <div class="nav-collapse collapse">
        <ul class="nav">
          <li>{{#linkTo posts}}<i class="icon-book icon-white"></i> Posts{{/linkTo}}</li>
          <li>{{#linkTo newpost}}<i class="icon-pencil icon-white"></i> Write{{/linkTo}}</li>
          {{#if isAuthenticated}}
            <li>
              <a {{action logout}} href="#"><i class="icon-remove icon-white"></i> Logout</a>
            </li>
          {{else}}
            <li class="dropdown">
              <a id="loginproviders" href="#" role="button" class="dropdown-toggle" data-toggle="dropdown">
                <i class="icon-user icon-white"></i> Login <b class="caret"></b>
              </a>
              <ul class="dropdown-menu" role="menu" aria-labelledby="loginproviders">
                <li role="presentation"><a {{action login "twitter"}} href="#">Twitter</a></li>
                <li role="presentation"><a {{action login "google"}} href="#">Google</a></li>
              </ul>
            </li>
          {{/if}}
        </ul>
      </div>
    </div>
  </div>
</div>

{{outlet}}

В {{#if isAuthenticated}} отображает вход или выход из системы меню выбора по мере необходимости.

Для пункта меню входа в систему я выбрал выпадающий список между Twitter и Google. Важный бит здесь является {{Действие Войти «твиттер»}} и {{Действие Войти «Google»}} . Оба вызывают действие входа и передают ему строку «twitter» или «google». Это будет отображаться в контроллере как providerпараметр, который обсуждался ранее, если вы используете другого провайдера, просто измените соответствующие поля.

Для выхода из системы это просто вызывает действие выхода из системы.

Улучшения после записи

Ранее я упоминал, что после ограничения доступа к функциям CRUD в мобильных службах Windows Azure, если вы отправите новое сообщение, выйдя из системы, произойдет молчание. Кто на самом деле хочет заполнить огромный пост в блоге, нажать «Отправить» и увидеть все свои работы потерянными? Почти никто. Поэтому, чтобы сделать это лучше, я добавил проверку isAuthenticated и отображал предупреждение, если вы не вошли в систему.

<div class="navbar navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </a>
      {{#linkTo index class="brand"}}My Great Blog{{/linkTo}}
      <div class="nav-collapse collapse">
        <ul class="nav">
          <li>{{#linkTo posts}}<i class="icon-book icon-white"></i> Posts{{/linkTo}}</li>
          <li>{{#linkTo newpost}}<i class="icon-pencil icon-white"></i> Write{{/linkTo}}</li>
          {{#if isAuthenticated}}
            <li>
              <a {{action logout}} href="#"><i class="icon-remove icon-white"></i> Logout</a>
            </li>
          {{else}}
            <li class="dropdown">
              <a id="loginproviders" href="#" role="button" class="dropdown-toggle" data-toggle="dropdown">
                <i class="icon-user icon-white"></i> Login <b class="caret"></b>
              </a>
              <ul class="dropdown-menu" role="menu" aria-labelledby="loginproviders">
                <li role="presentation"><a {{action login "twitter"}} href="#">Twitter</a></li>
                <li role="presentation"><a {{action login "google"}} href="#">Google</a></li>
              </ul>
            </li>
          {{/if}}
        </ul>
      </div>
    </div>
  </div>
</div>

{{outlet}}

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

Получение имени пользователя

Mobile Services предоставляет серверный JavaScript (node.js) для операций CRUD. Я использую операцию вставки, чтобы вытащить имя пользователя и добавить его в сообщение, поскольку оно сохраняется в хранилище данных. Вот шаги, чтобы сделать это.

  1. Используя портал управления Windows Azure, перейдите к своему экземпляру Mobile Services и выберите  таблицу сообщений .

  2. Выберите « Сценарий» и, наконец, выберите « Вставить» в раскрывающемся списке « Операция» .

  3. Замените существующий код следующим, затем нажмите кнопку Сохранить

    function insert(item, user, request) {
    
        item.author = "Unknown";
        var identities = user.getIdentities();
        var url;
        
        if (identities.google) {
            var googleAccessToken = identities.google.accessToken;
            url = 'https://www.googleapis.com/oauth2/v1/userinfo?access_token=' + googleAccessToken;
        } else if (identities.facebook) {
            var fbAccessToken = identities.facebook.accessToken;
            url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
        } else if (identities.microsoft) {
           var liveAccessToken = identities.microsoft.accessToken;
            url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + liveAccessToken;
        } else if (identities.twitter) {
           var userId = user.userId;
           var twitterId = userId.substring(userId.indexOf(':') + 1);
          url = 'https://api.twitter.com/1/users/show.json?user_id=' + twitterId;
        }
        if (url) {
            var requestCallback = function (err, resp, body) {
                if (err || resp.statusCode !== 200) {
                    console.error('Error sending data to the provider: ', err);
                    request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
                } else {
                    try {
                        var userData = JSON.parse(body);
                        item.author = userData.name;
                        request.execute();
                    } catch (ex) {
                        console.error('Error parsing response from the provider API: ', ex);
                        request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
                    }
                }
            }
            var req = require('request');
            var reqOptions = {
                uri: url,
                headers: { Accept: "application/json" }
            };
            req(reqOptions, requestCallback);
        } else {
            // Insert with default user name
            request.execute();
        }
    }

Это выполнит поиск понятного имени пользователя на основе недружественного идентификатора пользователя, возвращенного поставщиком аутентификации, а затем использует его для заполнения поля «Авторы».

Также большое спасибо Карлосу Фигейре и Девхаммеру за их блоги об этом.

Тестирование новых изменений

Если вы запустите приложение локально, используя grunt server, вы заметите, что теперь есть меню входа в систему, и что выбор одного из элементов из этого предоставляет вам страницу аутентификации / входа для этого провайдера. Если вы перейдете по ссылке «Написать» во время выхода из системы, то заметите, что теперь вы получаете приятное сообщение о том, что вам необходимо войти в систему, чтобы написать сообщение.

Но есть небольшая проблема в этой точке; некоторым браузерам ( кашель IE кашляет ) не нравится тот факт, что вы запускаете страницу локально, но аутентификация происходит в Интернете. Так что, хотя у вас есть окно аутентификации, оно на самом деле не работает. Вы можете заставить его работать, отключив некоторые средства безопасности, чтобы проверить это локально. Для этого откройте вкладку « Безопасность » в окне «Свойства обозревателя», нажмите « Локальная интрасеть» , нажмите « Сайты» и отключите параметр « Автоматически определять сеть интрасети» . Не забудьте включить его после тестирования.

Или вы можете просто опубликовать это на веб-сайте Windows Azure и попробовать его таким образом.

Создать веб-сайт Windows Azure

Вы можете создать новый веб-сайт из консоли управления Windows Azure , выполнив следующие действия:

  1. Нажмите + NEW , выберите Compute , Web Site и Quick Create . При появлении запроса укажите уникальный URL-адрес и выберите регион.

    Если веб-сайт недоступен, вам нужно будет выполнить шаги, чтобы включить предварительный просмотр для вашей учетной записи.

  2. Как только сайт будет создан, выберите его и перейдите на панель инструментов . Здесь вы увидите несколько быстрых ссылок для таких вещей, как URL сайта, сброс / создание учетных данных для развертывания и т. Д. Если вы еще не включили развертывание FTP или не задали учетные данные, вы можете сделать это, используя ссылки здесь.

    Части, которые вы хотите, это имя хоста FTP и DEPLOYMENT / FTP USER . Сохраните это.

Настройка Grunt для развертывания FTP

Существует задача Grunt, которая будет развертывать скомпилированное приложение с использованием FTP. Вот шаги для настройки этого:

  1. Из сеанса командной строки / bash / Terminal измените каталоги на каталог emberapp и выполните следующую команду:

    npm install grunt-ftp-deploy --save-dev 

    Это установит биты задачи ftp-deploy и сохранит их в разделе devDependencies файла package.json .

  2. Откройте файл gruntfile.js и добавьте в него следующее, а затем сохраните файл:

    'ftp-deploy': {
              build: {
                auth: {
                  host: "waws-prod-blah-001.ftp.azurewebsites.windows.net",
                  port: 21,
                  authKey: 'key1'
                },
                src: 'dist',
                dest: '/site/wwwroot'
              }
            },

    Замените хост на FTP-хост, возвращенный из Dashboard вашего веб-сайта. Например, если имя хоста FTP на панели мониторинга отображается как ftp://waws-prod-blah-001.ftp.azurewebsites.windows.net/, вы просто установите хост как waws-prod-blah-001. .ftp.azurewebsites.windows.net.

    Я просто добавил это между clean:и jshint:секции gruntfile, так что должно быть безопасным местом.

  3. Откройте файл .gitignore и добавьте в конце новую строку, содержащую следующее значение, а затем сохраните файл:

    .ftppass 

    .Ftppass файл содержит имя пользователя / пароль для FTP — сервера. Добавление его в .gitignore не позволяет Git записать значение в хранилище.

  4. Создайте новый файл с именем .ftppass в каталоге emberapp . Добавьте к нему следующее, заменив «website \ username» и «website website» на ваше имя пользователя и пароль.

    {
      "key1": {
        "username": "website\\username",
        "password": "website password"
      }
    }

    Вы можете получить свое имя пользователя для FTP-сервера с панели инструментов . Обратите внимание, что в нем есть символ «\». Вам придется удвоить это при записи в файл .ftppass , так как один ‘\’ интерпретируется как escape-последовательность.

    Сохраните файл.

Построить и развернуть

Для сборки и развертывания выполните следующие действия:

  1. В командной строке \ bash \ Terminal session выполните следующее:

    grunt build 

    Это будет вращаться некоторое время, а затем, в конце концов, скажет « Готово» без ошибок. Теперь у вас должна быть папка dist под emberapp . Задача сборки берет файлы для приложения, минимизирует JS, сжимает вещи и т. Д., И это то, что вы получаете.

  2. Далее разверните:

    grunt ftp-deploy 

    Это должно пройти через несколько операторов выгрузки файлов для каждого файла в папке dist и, наконец, дать вам ** Готово без ошибок. «

Авторизуйте свой веб-сайт для использования мобильных сервисов

По умолчанию мобильные сервисы разрешают запросы от localhost, но не где-либо еще. Поэтому нам нужно добавить ваш веб-сайт Windows Azure, чтобы он мог работать с мобильными службами. Для этого выполните следующие действия:

  1. Перейдите на панель инструментов веб-сайта Windows Azure и найдите ссылку в разделе URL-адрес сайта . Возьмите имя хоста оттуда. Например, если URL-адрес сайтаhttp://myawesomesite.azurewebsites.net, то именем хоста будет просто myawesomesite.azurewebsites.net.

  2. Перейдите к своему экземпляру Windows Azure Mobile Services и выберите « Настроить» . Прокрутите страницу вниз, пока не найдете раздел « Перекрестное распределение ресурсов» (CORS) . Введите здесь имя хоста для вашего сайта, а затем нажмите кнопку Сохранить внизу, чтобы сохранить изменения.

На этом этапе вы должны иметь возможность посещать URL сайта в браузере, и он должен позволять вам входить / выходить, просматривать и создавать сообщения.

Резюме

Этот пост закончился дольше, чем я ожидал, надеюсь, это был не TL; DR. И, надеюсь, вы узнали, что подключить мобильные сервисы к Ember.js довольно просто и что серверные скрипты для мобильных сервисов довольно аккуратны. Так куда дальше? Не уверен. Для этого приложения я на данный момент готов, хотя я думаю, что было бы неплохо добавить фактическую таблицу пользователей, где я могу хранить идентификаторы пользователей и помечать их как «аутентифицированные», так что не любой случайный пользователь с логином может публиковать сообщения. Это то, что я могу решить за лето.

Я загрузил биты для аутентификации в ветку auth репозитория по адресу https://github.com/Blackmist/emberapp/tree/auth,  так что не стесняйтесь клонировать / раскладывать и взламывать.