Skip to content

Commit

Permalink
feat: active support notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
adamcooke committed May 20, 2022
1 parent b8ac3d0 commit ce0c895
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 121 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,29 @@ class LoginController < ApplicationController
end
```

## Instrumentation/Notification

Authie will publish events to the ActiveSupport::Notification instrumentation system. The following events are published
with the given attributes.

* `set_browser_id.authie` - when a new browser ID is set for a user. Provides `:browser_id` and `:controller` arguments.
* `cleanup.authie` - when session cleanup is run. Provides no arguments.
* `touch.authie` - when a session is touched. Provides `:session` argument.
* `see_password.authie` - when a session sees a password. Provides `:session` argument.
* `mark_as_two_factor.authie` - when a session has two factor credentials provided. Provides `:session` argument.
* `session_start.authie` - when a session is started. Provides `:session` argument.
* `browser_id_mismatch_error.authie` - when a session is validated when the browser ID does not match. Provides `:session` argument.
* `invalid_session_error.authie` - when a session is validated when invalid. Provides `:session` argument.
* `expired_session_error.authie` - when a session is validated when expired. Provides `:session` argument.
* `inactive_session_error.authie` - when a session is validated when inactive. Provides `:session` argument.
* `host_mismatch_error.authie` - when a session is validated and the host does not match. Provides `:session` argument.

## Differences for Authie 4.0

Authie 4.0 introduces a number of changes to the library which are worth noting when upgrading from any version less than 4.

* Authie 4.0 removes the impersonation features which may make a re-appearance in a futre version.
* All previous callback/events have been replaced with standard ActiveSupport instrumentation notifications.
* Various methods on Authie::Session (more commonly known as `auth_session`) have been renamed as follows.
* `check_security!` is now `validate`
* `persist!` is now `persist`
Expand Down
8 changes: 4 additions & 4 deletions lib/authie/config.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# frozen_string_literal: true

require 'authie/event_manager'

module Authie
class Config
attr_accessor :session_inactivity_timeout
Expand All @@ -10,7 +8,6 @@ class Config
attr_accessor :browser_id_cookie_name
attr_accessor :session_token_length
attr_accessor :extend_session_expiry_on_touch
attr_accessor :events

def initialize
@session_inactivity_timeout = 12.hours
Expand All @@ -19,7 +16,6 @@ def initialize
@browser_id_cookie_name = :browser_id
@session_token_length = 64
@extend_session_expiry_on_touch = false
@events = EventManager.new
end
end

Expand All @@ -32,5 +28,9 @@ def configure(&block)
block.call(config)
config
end

def notify(event, args = {}, &block)
ActiveSupport::Notifications.instrument("#{event}.authie", args, &block)
end
end
end
4 changes: 3 additions & 1 deletion lib/authie/controller_delegate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def set_browser_id
httponly: true,
secure: @controller.request.ssl?
}
Authie.config.events.dispatch(:set_browser_id, proposed_browser_id)
Authie.notify(:set_browser_id,
browser_id: proposed_browser_id,
controller: @controller)
end
proposed_browser_id
end
Expand Down
32 changes: 0 additions & 32 deletions lib/authie/event_manager.rb

This file was deleted.

20 changes: 10 additions & 10 deletions lib/authie/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def touch
@session.requests += 1
extend_session_expiry_if_appropriate
@session.save!
Authie.config.events.dispatch(:session_touched, self)
Authie.notify(:touch, session: self)
self
end

Expand All @@ -105,7 +105,7 @@ def touch
def see_password
@session.password_seen_at = Time.now
@session.save!
Authie.config.events.dispatch(:seen_password, self)
Authie.notify(:see_password, session: self)
self
end

Expand All @@ -119,7 +119,7 @@ def mark_as_two_factored(skip: nil)
@session.two_factored_ip = @controller.request.ip
@session.skip_two_factor = skip unless skip.nil?
@session.save!
Authie.config.events.dispatch(:marked_as_two_factor, self)
Authie.notify(:mark_as_two_factor, session: self)
self
end

Expand All @@ -130,7 +130,7 @@ def mark_as_two_factored(skip: nil)
# @return [Authie::Session]
def start
set_cookie
Authie.config.events.dispatch(:start_session, session)
Authie.notify(:session_start, session: self)
self
end

Expand All @@ -153,7 +153,7 @@ def set_cookie(value = @session.temporary_token)
httponly: true,
expires: @session.expires_at
}
Authie.config.events.dispatch(:session_cookie_updated, self)
Authie.notify(:cookie_updated, session: session)
true
end
# rubocop:enable Naming/AccessorMethodName
Expand All @@ -165,7 +165,7 @@ def cookies
def validate_browser_id
if cookies[:browser_id] != @session.browser_id
invalidate
Authie.config.events.dispatch(:browser_id_mismatch_error, self)
Authie.notify(:browser_id_mismatch_error, session: self)
raise BrowserMismatch, 'Browser ID mismatch'
end

Expand All @@ -175,7 +175,7 @@ def validate_browser_id
def validate_active
unless @session.active?
invalidate
Authie.config.events.dispatch(:invalid_session_error, self)
Authie.notify(:invalid_session_error, session: self)
raise InactiveSession, 'Session is no longer active'
end

Expand All @@ -185,7 +185,7 @@ def validate_active
def validate_expiry
if @session.expired?
invalidate
Authie.config.events.dispatch(:expired_session_error, self)
Authie.notify(:expired_session_error, session: self)
raise ExpiredSession, 'Persistent session has expired'
end

Expand All @@ -195,7 +195,7 @@ def validate_expiry
def validate_inactivity
if @session.inactive?
invalidate
Authie.config.events.dispatch(:inactive_session_error, self)
Authie.notify(:inactive_session_error, session: self)
raise InactiveSession, 'Non-persistent session has expired'
end

Expand All @@ -205,7 +205,7 @@ def validate_inactivity
def validate_host
if @session.host && @session.host != @controller.request.host
invalidate
Authie.config.events.dispatch(:host_mismatch_error, self)
Authie.notify(:host_mismatch_error, session: self)
raise HostMismatch, "Session was created on #{@session.host} but accessed using #{@controller.request.host}"
end

Expand Down
14 changes: 7 additions & 7 deletions lib/authie/session_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,13 @@ def find_session_by_token(token)

# Cleanup any old sessions.
def cleanup
Authie.config.events.dispatch(:before_cleanup)
# Invalidate transient sessions that haven't been used
active.where('expires_at IS NULL AND last_activity_at < ?',
Authie.config.session_inactivity_timeout.ago).each(&:invalidate!)
# Invalidate persistent sessions that have expired
active.where('expires_at IS NOT NULL AND expires_at < ?', Time.now).each(&:invalidate!)
Authie.config.events.dispatch(:after_cleanup)
Authie.notify(:cleanup) do
# Invalidate transient sessions that haven't been used
active.where('expires_at IS NULL AND last_activity_at < ?',
Authie.config.session_inactivity_timeout.ago).each(&:invalidate!)
# Invalidate persistent sessions that have expired
active.where('expires_at IS NOT NULL AND expires_at < ?', Time.now).each(&:invalidate!)
end
true
end

Expand Down
6 changes: 0 additions & 6 deletions spec/lib/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,4 @@
expect(config.browser_id_cookie_name).to eq :auth_browser_id
end
end

describe '#events' do
it 'returns an event manager instance' do
expect(config.events).to be_a Authie::EventManager
end
end
end
2 changes: 1 addition & 1 deletion spec/lib/controller_delegate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
end

it 'dispatches an event' do
expect(Authie.config.events).to receive(:dispatch).with(:set_browser_id, /\A[a-f0-9-]{36}\z/)
expect(Authie).to receive(:notify).with(:set_browser_id, hash_including(browser_id: /\A[a-f0-9-]{36}\z/))
delegate.set_browser_id
end
end
Expand Down
51 changes: 0 additions & 51 deletions spec/lib/event_manager_spec.rb

This file was deleted.

3 changes: 1 addition & 2 deletions spec/lib/session_model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,7 @@
end

it 'dispatches an event before and after' do
expect(Authie.config.events).to receive(:dispatch).with(:before_cleanup)
expect(Authie.config.events).to receive(:dispatch).with(:after_cleanup)
expect(Authie).to receive(:notify).with(:cleanup)
described_class.cleanup
end
end
Expand Down
27 changes: 20 additions & 7 deletions spec/lib/session_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

it 'dispatches an event if the browser ID does not match' do
controller.send(:cookies)[:browser_id] = 'invalid'
expect(Authie.config.events).to receive(:dispatch).with(:browser_id_mismatch_error, session)
expect(Authie).to receive(:notify).with(:browser_id_mismatch_error, session: session)
begin
session.validate
rescue StandardError
Expand All @@ -34,7 +34,7 @@

it 'dispatches an event if the session is not valid' do
session_model.update!(active: false)
expect(Authie.config.events).to receive(:dispatch).with(:invalid_session_error, session)
expect(Authie).to receive(:notify).with(:invalid_session_error, session: session)
begin
session.validate
rescue StandardError
Expand All @@ -49,7 +49,7 @@

it 'dispatches an event if the session has expired' do
session_model.update!(expires_at: 5.minutes.ago)
expect(Authie.config.events).to receive(:dispatch).with(:expired_session_error, session)
expect(Authie).to receive(:notify).with(:expired_session_error, session: session)
begin
session.validate
rescue StandardError
Expand All @@ -64,7 +64,7 @@

it 'dispatches an event if the session is inactive' do
session_model.update!(last_activity_at: 13.hours.ago, active: true)
expect(Authie.config.events).to receive(:dispatch).with(:inactive_session_error, session)
expect(Authie).to receive(:notify).with(:inactive_session_error, session: session)
begin
session.validate
rescue StandardError
Expand Down Expand Up @@ -150,7 +150,7 @@
end

it 'dispatches an event' do
expect(Authie.config.events).to receive(:dispatch).with(:session_touched, session)
expect(Authie).to receive(:notify).with(:touch, session: session)
session.touch
end

Expand Down Expand Up @@ -216,7 +216,7 @@
end

it 'dispatches an event' do
expect(Authie.config.events).to receive(:dispatch).with(:seen_password, session)
expect(Authie).to receive(:notify).with(:see_password, session: session)
session.see_password
end
end
Expand All @@ -237,7 +237,7 @@
end

it 'it dispatched an event' do
expect(Authie.config.events).to receive(:dispatch).with(:marked_as_two_factor, session)
expect(Authie).to receive(:notify).with(:mark_as_two_factor, session: session)
session.mark_as_two_factored
end

Expand Down Expand Up @@ -277,6 +277,19 @@
end
end

describe '#start' do
it 'sets the cookie' do
expect(session).to receive(:set_cookie)
session.start
end

it 'dispatches an event' do
expect(Authie).to receive(:notify) # for cookies
expect(Authie).to receive(:notify).with(:session_start, session: session)
session.start
end
end

describe '.start' do
it 'creates a new session with details from the request' do
time = Time.new(2022, 3, 4, 2, 31, 22)
Expand Down

0 comments on commit ce0c895

Please sign in to comment.