Module: Mbeditor::ChannelAuthentication
- Included in:
- CollaborationChannel, EditorChannel
- Defined in:
- app/channels/mbeditor/channel_authentication.rb
Overview
Runs the configured authenticate_with hook on the WebSocket handshake (at
channel subscribe) and rejects the subscription when it denies. This is the
same proc the engine runs as a controller before_action; on the cable side
there is no controller, so it is evaluated against a probe that mirrors the
surface the hook relies on (session, cookies, redirect_to/render/
head). A hook that halts — or raises — denies the socket (fail-closed).
The probe reads cookies/session from the upgrade request env when available; because the cable mount can bypass host middleware, hooks that depend on request-scoped state may see less than they do over HTTP. Restricting network exposure (trusted tunnel / LAN) and securing the host's ActionCable connection remain the primary controls — see the README pairing section.
Defined Under Namespace
Classes: AuthProbe
Constant Summary collapse
- LOG_THROTTLE_SECONDS =
Throttled hard, because a rejection is not a one-off event: the client retries every 30s, every open tab retries independently, and both the editor channel and every per-file collaboration room authenticate. Logging each one turned a silent failure into a flooded console, which is no more usable. One message per distinct reason per interval says the same thing.
300
Class Method Summary collapse
Instance Method Summary collapse
-
#mbeditor_auth_hook ⇒ Object
cable_authenticate_withwhen set, otherwise the HTTP hook. -
#mbeditor_authenticated? ⇒ Boolean
True when the connection is allowed (or no hook is configured); otherwise rejects the subscription and returns false.
- #mbeditor_log_denial(reason) ⇒ Object
Class Method Details
.last_logged ⇒ Object
62 63 64 |
# File 'app/channels/mbeditor/channel_authentication.rb', line 62 def self.last_logged @last_logged ||= {} end |
Instance Method Details
#mbeditor_auth_hook ⇒ Object
cable_authenticate_with when set, otherwise the HTTP hook. A hook that
depends on controller filters cannot work here at all, and asking people to
write one proc that straddles both contexts is worse than letting them
supply the cable one explicitly.
48 49 50 51 |
# File 'app/channels/mbeditor/channel_authentication.rb', line 48 def mbeditor_auth_hook Mbeditor.configuration.cable_authenticate_with || Mbeditor.configuration.authenticate_with end |
#mbeditor_authenticated? ⇒ Boolean
True when the connection is allowed (or no hook is configured); otherwise rejects the subscription and returns false.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'app/channels/mbeditor/channel_authentication.rb', line 21 def mbeditor_authenticated? hook = mbeditor_auth_hook return true unless hook probe = AuthProbe.new(mbeditor_connection_env) probe.instance_exec(&hook) return true unless probe.denied? # Both denial paths used to be completely silent, which is what makes this # so hard to diagnose: pairing simply never works and nothing anywhere says # why. The commonest cause is a hook that reads state a controller filter # populates — Current.user, an Authlogic session — because a WebSocket # subscribe runs no controller, so that state is nil or raises here while # working perfectly over HTTP. mbeditor_log_denial("the authenticate_with hook denied the connection") mbeditor_reject_subscription false rescue StandardError => e mbeditor_log_denial("the authenticate_with hook raised #{e.class}: #{e.}") mbeditor_reject_subscription false end |
#mbeditor_log_denial(reason) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'app/channels/mbeditor/channel_authentication.rb', line 66 def mbeditor_log_denial(reason) now = Process.clock_gettime(Process::CLOCK_MONOTONIC) should_log = LOG_MUTEX.synchronize do seen = ChannelAuthentication.last_logged previous = seen[reason] if previous.nil? || (now - previous) > LOG_THROTTLE_SECONDS seen[reason] = now true else false end end return unless should_log Rails.logger&.warn( "[mbeditor] WebSocket subscription rejected: #{reason}. " \ "Realtime collaboration will not work. A WebSocket subscribe runs no " \ "controller, so Current.*, Authlogic sessions and other request-scoped " \ "state set by before_actions are unavailable here — resolve the user " \ "from `session` instead, or set config.cable_authenticate_with. " \ "(Further identical rejections suppressed for #{LOG_THROTTLE_SECONDS}s.)" ) rescue StandardError # Logging must never be the thing that breaks the socket. end |