Статьи

Basecamp-подобные субдомены с Devise

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

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

Использование собственной системы аутентификации может быть хорошим упражнением, и в большинстве случаев это не слишком большая работа. Наличие чего-то стабильного, многофункционального и модульного, такого как Devise, может значительно сэкономить время.

Сегодня мы рассмотрим реализацию Devise в реальном сценарии. Хотя базовые функции Devise хорошо известны, его гибкость несколько упускается из виду. Мы рассмотрим настройку входа пользователей.

Обычная идиома в мультитенантных приложениях в наши дни состоит в том, чтобы субдомены определялись как учетная запись пользователя, например rubysource.basecamp.comAccount Давайте сделаем это с Devise.

Приложение

Сначала мы начнем с сути нашего приложения. Это будет приложение для заметок на основе Rails 3.1. На этом этапе мы сделаем это по-настоящему простым, и в нашем домене будет только три модели: NoteUserAccount Давайте проверим несколько генераторов для NoteUserrails g model Account subdomain:string
rails g scaffold Note title:string content:text user_id:integer account_id:integer

 Account

В нашей модели class Account < ActiveRecord::Base
has_many :users
has_many :notes, :through => :users
end

 Note

Аналогично для class Note > ActiveRecord::Base
belongs_to :user
belongs_to :account
end

 ActiveRecord

Ничего особенного, просто пара моделей subdomain Единственный интересный Accountgem devisebundle install По сути, это слаг для учетной записи в URL запроса («rubysource» в нашем предыдущем примере).

Представляем Devise

Devise — это драгоценный камень, поэтому его очень просто установить. Добавьте Devise в ваш Gemfile, rails gDevise:
devise
devise:form_for
devise:install
devise:shared_views
devise:simple_form_for
devise:views

Теперь в командной строке запустите rails g devise:install

 notes#index

Мы будем использовать очевидные направления, rails g devise User

После запуска генератора мы должны получить пару инструкций, говорящих нам о том, чтобы установить корневой каталог приложения для отображения, иметь область флэш-памяти в нашей компоновке и установить почтовую программу по умолчанию в наших средах разработки и тестирования. Просто следуйте за этим к письму. Я установил корень приложения, чтобы он указывал на devise_for

Нам нужна пользовательская модель для аутентификации Devise. Как выяснилось, мы можем использовать devise для генерации того, что для нас config/routes.rb Это генерирует модель, соответствующую миграцию и специализированный маршрут UserAccount

Перед запуском миграций нам нужно изменить модель Notehas_many Мы также ассоциируем :confirmablebefore_filter :authenticate_user!

Я должен отметить, что это лучшее время, чтобы решить, какие модули Devise будет использовать, такие как добавление ApplicationController

С установленным Devise, самый простой способ защитить наш контент — использовать одеяло localhost:3000 помещается в lvh.me При запуске сервера и переходе к www.example.com

Зная это работает

Теперь у нас есть Devise, он пока не делает то, что нам нужно. Нам нужно пройти аутентификацию на поддомене. Есть несколько вариантов с точки зрения тестирования. Правда, мы не очень хотим тестировать сам Devise. Это стороннее, хорошо используемое программное обеспечение, поэтому справедливо сказать, что оно было хорошо протестировано. Тем не менее, мы все еще хотим, чтобы наши настройки работали так, как мы ожидаем. Мы могли бы поработать, запустив локальный сервер и используя блестящий домен app_host

Но разве мы не лучше этого? Мы хотим воспроизводимых, автоматических тестов. Давайте использовать наши интеграционные тесты и потрясающую версию Capybara . По умолчанию Capybara запускает наш пакет под доменом describe "LoginToAccounts" do
before do
other_account = Factory.create(:account)
@invalid_user = Factory.create(:user, account: other_account)
@account = Factory.create(:account, subdomain: "test-account")
@user = Factory.create(:user, account: @account)
Capybara.app_host = "http://test-account.example.com"
visit '/'
end

describe "log in to a valid account" do
before do
fill_in 'Email', with: @user.email
fill_in 'Password', with: @user.password
click_button 'Sign in'
end

it "will notify me that I have logged in successfully" do
page.should have_content "Signed in successfully"
end

end

describe "fail login for valid user wrong account" do
before do
fill_in 'Email', with: @invalid_user.email
fill_in 'Password', with: @invalid_user.password
click_button 'Sign in'
end

it "will not notify me that I have logged in successfully" do
page.should_not have_content "Signed in successfully"
end

end
end
Однако, просто используя помощник devise.rb Вот базовый тест поведения, которое мы ищем, используя запросы RSpec .

 config/initializers

Придумать гибкость

Теперь, когда у нас есть тест, пришло время реализовать функцию захвата поддоменов и проверки подлинности пользователя в правильной учетной записи.

Когда мы установили Devise, он сгенерировал инициализатор, метко названный :subdomainUserAccount Этот файл блестяще прокомментирован и должен быть первым пунктом вызова, когда вы хотите добавить функциональность в стандартную установку Devise.

В строке 33 этого файла (на основе установки версии 1.4.7 Devise) вы увидите, что мы можем добавить ключи запроса для аутентификации. Просто раскомментируйте строку и добавьте find_for_authentication В основном, этот параметр запроса будет передан в метод аутентификации Devises для проверки.

Хотя это было достаточно просто, мы еще не совсем там. Помните, что Devise аутентифицируется на Userdef self.find_for_authentication(conditions={})
conditions[:account_id] = Account.find_by_subdomain(conditions.delete(:subdomain)).id
super(conditions)
end
Теперь мы можем просто добавить атрибут subdomain к пользователю и все готово, но мне это кажется просто неправильным.

Вместо этого мы переопределим метод subdomain Этот метод является нашей точкой отсчета до того, как Devise вступит во владение. Чтобы найти учетную запись, которую мы ищем, мы возьмем субдомен из запроса и передадим его по цепочке для аутентификации. Использование Rails 3+ делает это действительно легко. В модели Account

 find_for_authentication

Здесь мы enddo … end{}bundle install

Со всем этим мы можем запустить тесты и убедиться, что у нас есть проходной пакет. Демо-код можно найти на Github .

Куда дальше?

Наше маленькое приложение имеет аутентификацию, и никакие неавторизованные пользователи не могут получить доступ к нашим важным заметкам. У нас еще есть над чем поработать, какие записи отображаются для какого логина. Это решается с помощью общей мультитенантной идиомы: все наши данные в контроллере извлекаются из «текущего арендатора», как это делает DHH .

Это не совсем то, на что мы смотрели сегодня. Обычно я бы избегал тестирования стороннего кода, но поведение нашего приложения и внесенные нами изменения требуют, чтобы мы хотя бы склонили голову перед Devise. Оборачивать это как интеграционный тест — лучшее место для этого, и использование такого инструмента, как Capybara, облегчает его, поэтому было бы преступно не быть тщательным.

Аутентификация — это шаблон, который мы будем использовать снова и снова, когда речь заходит о приложениях на Rails, и такие сложные функции, как Devise, могут быть пугающими. Следовательно, почему некоторые обращаются к более простым драгоценным камням, таким как Restful Authentication , Sorcery или просто катят наши собственные. Я рекомендую вам потратить некоторое время на разработку. При хорошем понимании вы можете почти списать элемент аутентификации из вашего приложения и использовать Devise в качестве замены. Его дизайн настолько хорош, что лично мне еще предстоит оказаться в ситуации, когда я сражаюсь против Devise. Обычно это тот случай, когда я пытаюсь создать именно то, что мне нужно.