Module: Phlex::Reactive::Authorization
- Defined in:
- lib/phlex/reactive/authorization.rb
Overview
The runtime machinery behind verify_authorized (issue #168): a time-window tracking cell, an interceptor that marks it when a configured authorization method returns, and the enforcement decision the endpoint calls after an action runs.
The design mirrors Phlex::Reactive.with_connection_id: a fiber-local cell (Thread.current, which is fiber-local in Ruby, so Falcon's fiber-per-request model is safe) saved and restored around the action. Because tracking is a WINDOW — anything marking during the action counts — authorization performed in a HELPER the action calls is detected too, not just a top-level call.
Constant Summary collapse
- CELL =
The fiber-local key holding the tracking cell during an action. A truthy value means "some authorization method has been called (and returned) during this window".
:phlex_reactive_authorized
Class Method Summary collapse
-
.instrument!(component_class) ⇒ Object
Wrap each configured authorization method the class defines (public OR private, own OR inherited) with a prepended module that calls super and then mark! on a NON-RAISING return — so a denial (which raises) never marks and still propagates to the endpoint's authorization_errors → 403.
-
.mark! ⇒ Object
Mark the current window authorized.
-
.marked? ⇒ Boolean
Has anything marked the current window?.
-
.verify!(component_class, action_def) ⇒ Object
The enforcement decision, called by the endpoint AFTER the action runs, INSIDE the transaction (so a raise rolls the mutation back).
-
.with_tracking ⇒ Object
Open a fresh tracking window, run the block, and restore the previous cell — exactly the with_connection_id save/restore shape, so nesting is correct and a raise still restores.
Class Method Details
.instrument!(component_class) ⇒ Object
Wrap each configured authorization method the class defines (public OR private, own OR inherited) with a prepended module that calls super and then mark! on a NON-RAISING return — so a denial (which raises) never marks and still propagates to the endpoint's authorization_errors → 403.
Idempotent per class OBJECT (memoized on an ivar the class carries), so a Zeitwerk reload's fresh class re-instruments naturally and a repeated call in one process is a no-op. Re-reads authorization_methods each first call, so an initializer that widened the list is honored.
76 77 78 79 80 81 82 83 84 |
# File 'lib/phlex/reactive/authorization.rb', line 76 def instrument!(component_class) return if component_class.instance_variable_get(:@reactive_authorization_instrumented) names = Phlex::Reactive..select do component_class.method_defined?(it) || component_class.private_method_defined?(it) end MODULE_PREPEND.bind_call(component_class, interceptor_for(names)) if names.any? component_class.instance_variable_set(:@reactive_authorization_instrumented, true) end |
.mark! ⇒ Object
Mark the current window authorized. Called by the interceptor (on a non-raising authorization-method return) and by Component#mark_authorized! for a bespoke check. A no-op semantically outside a window (the cell is simply overwritten and restored by the nearest with_tracking).
45 46 47 |
# File 'lib/phlex/reactive/authorization.rb', line 45 def mark! Thread.current[CELL] = true end |
.marked? ⇒ Boolean
Has anything marked the current window?
50 51 52 |
# File 'lib/phlex/reactive/authorization.rb', line 50 def marked? Thread.current[CELL] == true end |
.verify!(component_class, action_def) ⇒ Object
The enforcement decision, called by the endpoint AFTER the action runs, INSIDE the transaction (so a raise rolls the mutation back). A no-op unless verify_authorized is on, the action isn't skipped, and nothing marked the window. Otherwise raises AuthorizationNotVerified naming the component#action and the three remedies.
59 60 61 62 63 64 65 |
# File 'lib/phlex/reactive/authorization.rb', line 59 def verify!(component_class, action_def) return unless Phlex::Reactive. return if skipped?(component_class, action_def.name) return if marked? raise Phlex::Reactive::AuthorizationNotVerified, (component_class, action_def) end |
.with_tracking ⇒ Object
Open a fresh tracking window, run the block, and restore the previous cell — exactly the with_connection_id save/restore shape, so nesting is correct and a raise still restores. The inner window starts UNMARKED regardless of an outer mark (a fresh action authorizes for itself).
33 34 35 36 37 38 39 |
# File 'lib/phlex/reactive/authorization.rb', line 33 def with_tracking previous = Thread.current[CELL] Thread.current[CELL] = false yield ensure Thread.current[CELL] = previous end |