Module: Sessions::Adapters::Omakase
- Defined in:
- lib/sessions/adapters/omakase.rb
Overview
The Rails 8 omakase auth adapter — zero app-code changes.
Installed from ‘config.to_prepare` (so it re-applies to freshly reloaded constants in development) and entirely capability-detected: nothing happens unless the app actually has the generated authentication code. Three attachment points, each name-stable since Rails 8.0 (the Authentication concern is byte-identical from 8.0.5 through 8.1.3 to main — → docs/research/03-rails-core.md):
1. The Session MODEL gets Sessions::Model included. Its callbacks
observe 100% of the generated lifecycle: `start_new_session_for`
uses create!, `terminate_session` uses destroy, and 8.1's
password reset uses destroy_all (which instantiates and runs
callbacks) — so logins and revocations are captured with zero
controller coupling.
2. ApplicationController gets ControllerHooks PREPENDED — the
prepend sits in front of the included Authentication concern in
the ancestor chain, so `super`-wrapping `resume_session`
(throttled touch + opt-in expiry) and `terminate_session`
(labeling the destroy as a logout) is clean.
3. The generated SessionsController#create gets FailedLoginHooks
prepended: `authenticate_by` is deliberately silent on failure
(no hook, no notification), so we observe the controller seam —
after `super`, no session + a credentials POST = a failed login.
Rails 8.1's `rate_limit.action_controller` notification adds the
brute-force-threshold signal for free.
Defined Under Namespace
Modules: ControllerHooks, FailedLoginHooks
Class Method Summary collapse
-
.decorate_session_model! ⇒ Object
‘Session.include Sessions::Model` when the host has a Rails-8-shaped session model (the Devise-mode shell model includes it explicitly; this is a no-op there).
- .install! ⇒ Object
-
.omakase_controller? ⇒ Boolean
The duck test: the generated Authentication concern defines these private methods on ApplicationController.
- .prepend_controller_hooks! ⇒ Object
- .prepend_failed_login_hooks! ⇒ Object
- .record_rate_limited(payload) ⇒ Object
- .session_class ⇒ Object
-
.subscribe_rate_limit_notifications! ⇒ Object
Rails 8.1+ instruments rate-limit hits with a payload carrying the request — a free brute-force signal.
Class Method Details
.decorate_session_model! ⇒ Object
‘Session.include Sessions::Model` when the host has a Rails-8-shaped session model (the Devise-mode shell model includes it explicitly; this is a no-op there).
47 48 49 50 51 52 53 |
# File 'lib/sessions/adapters/omakase.rb', line 47 def decorate_session_model! klass = session_class return unless klass return if klass.include?(Sessions::Model) klass.include(Sessions::Model) end |
.install! ⇒ Object
36 37 38 39 40 41 42 |
# File 'lib/sessions/adapters/omakase.rb', line 36 def install! Sessions.safely("omakase.install") do decorate_session_model! prepend_controller_hooks! prepend_failed_login_hooks! end end |
.omakase_controller? ⇒ Boolean
The duck test: the generated Authentication concern defines these private methods on ApplicationController. Capability-based, so a host that renamed or removed its auth code simply isn’t touched.
72 73 74 75 76 |
# File 'lib/sessions/adapters/omakase.rb', line 72 def omakase_controller? defined?(::ApplicationController) && ::ApplicationController.private_method_defined?(:start_new_session_for) && ::ApplicationController.private_method_defined?(:resume_session) end |
.prepend_controller_hooks! ⇒ Object
55 56 57 58 59 |
# File 'lib/sessions/adapters/omakase.rb', line 55 def prepend_controller_hooks! return unless omakase_controller? ::ApplicationController.prepend(ControllerHooks) end |
.prepend_failed_login_hooks! ⇒ Object
61 62 63 64 65 66 67 |
# File 'lib/sessions/adapters/omakase.rb', line 61 def prepend_failed_login_hooks! return unless omakase_controller? return unless defined?(::SessionsController) return unless ::SessionsController.method_defined?(:create) ::SessionsController.prepend(FailedLoginHooks) end |
.record_rate_limited(payload) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/sessions/adapters/omakase.rb', line 106 def record_rate_limited(payload) Sessions.safely("omakase.rate_limit") do next unless Sessions.config.track_failed_logins request = payload[:request] next unless request # Only the sessions controller: a rate-limited LOGIN burst is # failed-login activity; throttles elsewhere (password resets, # API endpoints) are not — recording them here would put # non-logins in the failed_login vocabulary. controller = request.path_parameters[:controller].to_s.split("/").last next unless controller == "sessions" Sessions::Event.record_failure( request, reason: :rate_limited, metadata: { count: payload[:count], limit: payload[:to] }.compact ) end end |
.session_class ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/sessions/adapters/omakase.rb', line 78 def session_class klass = Sessions.config.session_class.safe_constantize return nil unless klass.is_a?(Class) return nil unless defined?(::ActiveRecord::Base) && klass < ::ActiveRecord::Base # The Rails 8 base columns prove the shape; last_seen_at proves the # install migration ran (decorating a half-migrated table would give # candy methods nothing to stand on). return nil unless klass.table_exists? && (%w[ip_address user_agent last_seen_at] - klass.column_names).empty? klass rescue StandardError nil end |
.subscribe_rate_limit_notifications! ⇒ Object
Rails 8.1+ instruments rate-limit hits with a payload carrying the request — a free brute-force signal. Subscribed once per process (from the engine initializer, not to_prepare); on Rails ≤ 8.0 the notification never fires and this is inert.
97 98 99 100 101 102 103 104 |
# File 'lib/sessions/adapters/omakase.rb', line 97 def subscribe_rate_limit_notifications! return if @rate_limit_subscribed @rate_limit_subscribed = true ActiveSupport::Notifications.subscribe("rate_limit.action_controller") do |*_args, payload| record_rate_limited(payload) end end |