new liquid tag: authorize (redirect to a page if an user is not authenticated)
did
committed Dec 20, 2016
commit 7ea2f860275a9403d521368fe6bd343007d6bccd
Showing 8
changed files with
247 additions
and 1 deletions
locomotive/steam/errors.rb b/lib/locomotive/steam/errors.rb
+4
-0
| @@ | @@ -6,6 +6,10 @@ module Locomotive::Steam |
| class ActionException < ::Exception | |
| end | |
| + | class RedirectionException < ::Exception |
| + | alias url message |
| + | end |
| + | |
| class RenderError < ::StandardError | |
| LINES_RANGE = 10 | |
locomotive/steam/liquid/tags/authorize.rb b/lib/locomotive/steam/liquid/tags/authorize.rb
+72
-0
| @@ | @@ -0,0 +1,72 @@ |
| + | module Locomotive |
| + | module Steam |
| + | module Liquid |
| + | module Tags |
| + | |
| + | # Redirect the current site visitor to another page if she/he |
| + | # is not authenticated. |
| + | # More information about the authentication feature here: |
| + | # https://locomotive-v3.readme.io/v3.3/docs/introduction-1 |
| + | # |
| + | # The Liquid tag requires 2 parameters: |
| + | # - the slug of the content type used for the authentication (a content type with a password field) |
| + | # - the handle of the page we want the user to be redirected to if unauthenticated |
| + | # |
| + | # Basically the authorize tag checks if the liquid context has |
| + | # a reference to the current authenticated content entry. |
| + | # If not, it raises a redirection exception forcing the Steam middleware stack |
| + | # to process a HTTP redirection. |
| + | # |
| + | # Example: |
| + | # |
| + | # {% authorize 'accounts', 'sign_in' %} |
| + | # |
| + | class Authorize < ::Liquid::Tag |
| + | |
| + | Syntax = /(#{::Liquid::QuotedString}+)\s*,\s*(#{::Liquid::QuotedString}+)/o |
| + | |
| + | def initialize(tag_name, markup, options) |
| + | if markup =~ Syntax |
| + | @content_type_slug, @page_handle = $1.try(:gsub, /['"]/, ''), $2.try(:gsub, /['"]/, '') |
| + | else |
| + | raise ::Liquid::SyntaxError.new("Syntax Error in 'authorize' - Valid syntax: authorize [content type slug], [page handle]") |
| + | end |
| + | |
| + | super |
| + | end |
| + | |
| + | def render(context) |
| + | @context = context |
| + | |
| + | unless authenticated_entry = context["current_#{@content_type_slug.singularize}"] |
| + | raise Locomotive::Steam::RedirectionException.new(page_url) |
| + | end |
| + | '' |
| + | end |
| + | |
| + | private |
| + | |
| + | def page_url |
| + | if page = services.page_finder.by_handle(@page_handle) |
| + | services.url_builder.url_for(page, locale) |
| + | else |
| + | |
| + | end |
| + | end |
| + | |
| + | def locale |
| + | @context.registers[:locale] |
| + | end |
| + | |
| + | def services |
| + | @context.registers[:services] |
| + | end |
| + | |
| + | end |
| + | |
| + | ::Liquid::Template.register_tag('authorize'.freeze, Authorize) |
| + | |
| + | end |
| + | end |
| + | end |
| + | end |
locomotive/steam/middlewares/redirection.rb b/lib/locomotive/steam/middlewares/redirection.rb
+24
-0
| @@ | @@ -0,0 +1,24 @@ |
| + | module Locomotive::Steam |
| + | module Middlewares |
| + | |
| + | # When rendering the page, the developer can stop it at anytime by |
| + | # raising an RedirectionException exception. The exception message holds |
| + | # the url we want the user to be redirected to. |
| + | # This is specifically used by the authorize liquid tag. |
| + | # |
| + | class Redirection < ThreadSafe |
| + | |
| + | include Helpers |
| + | |
| + | def _call |
| + | begin |
| + | self.next |
| + | rescue Locomotive::Steam::RedirectionException => e |
| + | redirect_to e.message, 302 |
| + | end |
| + | end |
| + | |
| + | end |
| + | end |
| + | |
| + | end |
locomotive/steam/server.rb b/lib/locomotive/steam/server.rb
+1
-1
| @@ | @@ -58,10 +58,10 @@ module Locomotive::Steam |
| Middlewares::UrlRedirection, | |
| Middlewares::Robots, | |
| Middlewares::Timezone, | |
| - | # Middlewares::Warden, |
| Middlewares::EntrySubmission, | |
| Middlewares::Locale, | |
| Middlewares::LocaleRedirection, | |
| + | Middlewares::Redirection, |
| Middlewares::Auth, | |
| Middlewares::PrivateAccess, | |
| Middlewares::Path, | |
spec/unit/liquid/tags/authorize_spec.rb
+51
-0
| @@ | @@ -0,0 +1,51 @@ |
| + | require 'spec_helper' |
| + | |
| + | describe Locomotive::Steam::Liquid::Tags::Authorize do |
| + | |
| + | let(:site) { instance_double('Site', default_locale: 'en', prefix_default_locale: false) } |
| + | let(:page) { instance_double('Page', fullpath: 'me/sign_in', templatized?: false) } |
| + | let(:page_handle) { "'sign_in'" } |
| + | let(:source) { "{% authorize 'accounts', #{page_handle} %}Hello world!" } |
| + | let(:assigns) { {} } |
| + | let(:services) { Locomotive::Steam::Services.build_instance } |
| + | let(:context) { ::Liquid::Context.new(assigns, {}, { services: services }) } |
| + | |
| + | before { |
| + | allow(services).to receive(:current_site).and_return(site) |
| + | allow(services.page_finder).to receive(:by_handle).and_return(page) |
| + | } |
| + | |
| + | subject { render_template(source, context) } |
| + | |
| + | describe 'validating syntax' do |
| + | |
| + | describe 'no page handle' do |
| + | let(:source) { '{% authorize accounts %}' } |
| + | it { expect { subject }.to raise_exception(Liquid::SyntaxError) } |
| + | end |
| + | |
| + | end |
| + | |
| + | describe '#render' do |
| + | |
| + | context 'unauthenticated account' do |
| + | |
| + | it 'redirects to the sign in page' do |
| + | expect { subject }.to raise_error(Locomotive::Steam::RedirectionException, '/me/sign_in') |
| + | end |
| + | |
| + | end |
| + | |
| + | context 'authenticated account' do |
| + | |
| + | let(:assigns) { { 'current_account' => liquid_instance_double('Account', {}) } } |
| + | |
| + | it 'renders the page' do |
| + | expect(subject).to eq 'Hello world!' |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
spec/unit/middlewares/auth_spec.rb
+31
-0
| @@ | @@ -0,0 +1,31 @@ |
| + | require 'spec_helper' |
| + | |
| + | require_relative '../../../lib/locomotive/steam/middlewares/thread_safe' |
| + | require_relative '../../../lib/locomotive/steam/middlewares/helpers' |
| + | require_relative '../../../lib/locomotive/steam/middlewares/auth' |
| + | |
| + | describe Locomotive::Steam::Middlewares::Auth::AuthOptions do |
| + | |
| + | let(:metafields) { { 'smtp' => { 'address' => '127.0.0.1', 'user_name' => 'John', 'password' => 'doe', 'port' => 25 } } } |
| + | let(:site) { instance_double('Site', metafields: metafields) } |
| + | let(:params) { {} } |
| + | |
| + | let(:options) { described_class.new(site, params) } |
| + | |
| + | describe '#smtp' do |
| + | |
| + | subject { options.smtp } |
| + | |
| + | it { is_expected.to eq('address': '127.0.0.1', 'user_name': 'John', 'password': 'doe', 'port': 25) } |
| + | |
| + | context 'no smtp metafields' do |
| + | |
| + | let(:metafields) { {} } |
| + | |
| + | it { is_expected.to eq({}) } |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
spec/unit/middlewares/redirection_spec.rb
+37
-0
| @@ | @@ -0,0 +1,37 @@ |
| + | require 'spec_helper' |
| + | |
| + | require_relative '../../../lib/locomotive/steam/middlewares/thread_safe' |
| + | require_relative '../../../lib/locomotive/steam/middlewares/helpers' |
| + | require_relative '../../../lib/locomotive/steam/middlewares/redirection' |
| + | |
| + | describe Locomotive::Steam::Middlewares::Redirection do |
| + | |
| + | let(:site) { instance_double('Site') } |
| + | let(:url) { 'http://models.example.com/about-us' } |
| + | let(:locomotive_path) { nil } |
| + | let(:app) { ->(env) { [200, env, 'app'] } } |
| + | let(:middleware) { described_class.new(app) } |
| + | |
| + | subject do |
| + | env = env_for(url, 'steam.site' => site) |
| + | env['steam.request'] = Rack::Request.new(env) |
| + | env['locomotive.path'] = locomotive_path |
| + | code, env = middleware.call(env) |
| + | [code, env['Location']] |
| + | end |
| + | |
| + | describe 'no redirection exception raised' do |
| + | |
| + | it { is_expected.to eq [200, nil] } |
| + | |
| + | end |
| + | |
| + | describe 'redirection exception raised' do |
| + | |
| + | let(:app) { ->(env) { raise Locomotive::Steam::RedirectionException.new('/sign_in') } } |
| + | |
| + | it { is_expected.to eq [302, '/sign_in'] } |
| + | |
| + | end |
| + | |
| + | end |
spec/unit/services/auth_service_spec.rb
+27
-0
| @@ | @@ -71,6 +71,33 @@ describe Locomotive::Steam::AuthService do |
| expect(liquid_context['reset_password_url']).to eq '/reset-password?auth_reset_token=42a' | |
| end | |
| + | context 'no email template' do |
| + | |
| + | let(:_auth_options) { default_auth_options.merge(email_handle: nil) } |
| + | let(:auth_options) { instance_double('AuthOptions', _auth_options) } |
| + | |
| + | it 'also sends the instructions by email with a default email template' do |
| + | allow(SecureRandom).to receive(:hex).and_return('42a') |
| + | entry = build_account('easyone', '42a') |
| + | expect(entries).to receive(:all).with('accounts', { 'email' => 'john@doe.net' }).and_return([entry]) |
| + | expect(entries).to receive(:update_decorated_entry) |
| + | expect(emails).to receive(:send_email).with({ |
| + | from: 'contact@acme.org', |
| + | to: 'john@doe.net', |
| + | subject: 'Instructions for changing your password', |
| + | body: (<<-EMAIL |
| + | Hi, |
| + | To reset your password please follow the link below: /reset-password?auth_reset_token=42a. |
| + | Thanks! |
| + | |
| + | ), |
| + | smtp: {} }, liquid_context) |
| + | is_expected.to eq :reset_password_instructions_sent |
| + | expect(liquid_context['reset_password_url']).to eq '/reset-password?auth_reset_token=42a' |
| + | end |
| + | |
| + | end |
| + | |
| end | |
| describe '#reset_password' do | |