standard_id 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 956fc621df693ec184f7d65482d96c4f25d99ae55274b68681c9c6b892bde095
4
- data.tar.gz: 0d5c39a196c8c9e8c99adb24fa61552698672f3df4a5bcf3d06d03b2b61e6a46
3
+ metadata.gz: 514c8e8c18bb9bf6d8bd0268cca374d477b2c276a3caf69b0ef58222b5b14b33
4
+ data.tar.gz: 8e9f2307f6fd2d6a99e78df4604f838c319a61487e0d1549df1712eaf683b639
5
5
  SHA512:
6
- metadata.gz: 584c37aa6aa46abe4a576297d6d754e5632c4b710c59dd1bd2b6f8d7c5c17ac86a8aef109df15fe3de720a7d1be405af31441f833a914b3bc8bfea264b296da5
7
- data.tar.gz: 1733a94c80aba6290cc5eabbe7df67f4ff145c5af18e8cb9202cd3d598b5e5569039fc729cdfed3eacfee55908fe36bf4801e706da7c0e8b544666383b7b6863
6
+ metadata.gz: 21fcdb6b8caaf652bdbe6cb86c52e7cf66480b5aae7bf9fab210dad86b9cb12626920190cd1743c138d8b3fc1608c2f921a3c18b79f0cdfe20407530fbcee596
7
+ data.tar.gz: 30dfa2dae9df664b4ff7bfdb972cbc3970bf8ac81fb185e8f6aca0676b4647903f20e60d0eaf5d531b3e3a1e4fcbaf923b1e05ee7f9f8fd6ca93461b0567e999
@@ -9,6 +9,19 @@ module StandardId
9
9
  # Safe to include even when the Sentry gem is not installed -- the
10
10
  # callback is a no-op if `Sentry` is not defined.
11
11
  #
12
+ # Extra fields can be added via the `sentry_context` config option:
13
+ #
14
+ # StandardId.configure do |c|
15
+ # c.sentry_context = ->(account, session) {
16
+ # { email: account.email, username: account.try(:display_name) }
17
+ # }
18
+ # end
19
+ #
20
+ # The lambda must return a Hash (nil and non-Hash returns are ignored).
21
+ # Base keys (id, session_id) always take precedence and cannot be
22
+ # overridden by the lambda. Exceptions raised by the lambda are not
23
+ # caught — they will propagate to surface misconfiguration immediately.
24
+ #
12
25
  # @example
13
26
  # class ApplicationController < ActionController::Base
14
27
  # include StandardId::WebAuthentication
@@ -27,10 +40,20 @@ module StandardId
27
40
  return unless defined?(Sentry)
28
41
  return unless respond_to?(:current_account, true) && current_account.present?
29
42
 
30
- context = { id: current_account.id }
31
- context[:session_id] = current_session.id if respond_to?(:current_session, true) && current_session.present?
43
+ session_value = current_session.presence if respond_to?(:current_session, true)
44
+
45
+ base = { id: current_account.id }
46
+ base[:session_id] = session_value.id if session_value&.respond_to?(:id)
47
+
48
+ extra = StandardId.config.sentry_context
49
+ if extra.respond_to?(:call)
50
+ result = extra.call(current_account, session_value)
51
+ # Merge lambda result underneath base keys so id/session_id cannot
52
+ # be accidentally overridden by the host app's lambda.
53
+ base = result.merge(base) if result.is_a?(Hash)
54
+ end
32
55
 
33
- Sentry.set_user(context)
56
+ Sentry.set_user(base)
34
57
  end
35
58
  end
36
59
  end
@@ -6,6 +6,19 @@ module StandardId
6
6
  cancancan: :check_authorization
7
7
  }.freeze
8
8
 
9
+ # Frameworks where the skip falls through to skip_after_action.
10
+ # ActionPolicy is NOT listed here because it is handled first via
11
+ # CLASS_METHOD_SKIP. Only :pundit reaches this branch.
12
+ AFTER_ACTION_FRAMEWORKS = %i[pundit].freeze
13
+
14
+ # ActionPolicy provides a dedicated class method to undo
15
+ # verify_authorized. Using skip_before_action or skip_after_action
16
+ # would silently do nothing for ActionPolicy because it manages the
17
+ # callback through its own DSL.
18
+ CLASS_METHOD_SKIP = {
19
+ action_policy: :skip_verify_authorized
20
+ }.freeze
21
+
9
22
  MUTEX = Mutex.new
10
23
  private_constant :MUTEX
11
24
 
@@ -30,11 +43,12 @@ module StandardId
30
43
 
31
44
  MUTEX.synchronize do
32
45
  # Guard against duplicate to_prepare registrations if called more than
33
- # once (e.g. in tests or misconfigured initializers). skip_before_action
34
- # is idempotent so duplicates are harmless, but this keeps things tidy.
46
+ # once (e.g. in tests or misconfigured initializers). The skip methods
47
+ # are idempotent so duplicates are harmless, but this keeps things tidy.
35
48
  return if @callback_name
36
49
 
37
50
  @callback_name = resolve_callback(framework, callback)
51
+ @framework = framework&.to_sym
38
52
  # @prepared is intentionally NOT cleared by reset!. This ensures
39
53
  # at most one to_prepare block is registered per process lifetime.
40
54
  # Trade-off: after reset! + apply (e.g. in tests switching
@@ -64,7 +78,7 @@ module StandardId
64
78
  end
65
79
 
66
80
  # Whether apply has been called. Used by ControllerPolicy.register to
67
- # decide if newly loaded controllers need immediate skip_before_action.
81
+ # decide if newly loaded controllers need an immediate authorization skip.
68
82
  def applied?
69
83
  MUTEX.synchronize { !@callback_name.nil? }
70
84
  end
@@ -72,10 +86,11 @@ module StandardId
72
86
  # Apply skips to a single controller. Called by ControllerPolicy.register
73
87
  # when a controller is lazily loaded after apply has already been called.
74
88
  def apply_to_controller(controller, policy)
75
- callback = MUTEX.synchronize { @callback_name }
89
+ callback, framework = MUTEX.synchronize { [@callback_name, @framework] }
76
90
  return unless callback
77
91
 
78
- controller.skip_before_action callback, raise: false
92
+ skip_authorization_callback(controller, callback, framework)
93
+
79
94
  if policy == :public
80
95
  # authenticate_account! is defined in WebAuthentication, not on API
81
96
  # controllers. raise: false ensures this is a safe no-op for API
@@ -94,11 +109,15 @@ module StandardId
94
109
  end
95
110
 
96
111
  # @api private — intended for test isolation only.
97
- # NOTE: This clears @callback_name (so applied? returns false and apply
98
- # can be called again with a different framework) but intentionally does
99
- # NOT clear @prepared, so no additional to_prepare block is registered.
112
+ # NOTE: This clears @callback_name and @framework (so applied? returns
113
+ # false and apply can be called again with a different framework) but
114
+ # intentionally does NOT clear @prepared, so no additional to_prepare
115
+ # block is registered.
100
116
  def reset!
101
- MUTEX.synchronize { @callback_name = nil }
117
+ MUTEX.synchronize do
118
+ @callback_name = nil
119
+ @framework = nil
120
+ end
102
121
  end
103
122
 
104
123
  private
@@ -116,6 +135,36 @@ module StandardId
116
135
  raise ArgumentError, "Provide either framework: or callback:"
117
136
  end
118
137
  end
138
+
139
+ # Picks the right skip mechanism based on how the framework registers
140
+ # its authorization check:
141
+ #
142
+ # - ActionPolicy: provides `skip_verify_authorized` class method
143
+ # - Pundit: uses after_action, so `skip_after_action` is needed
144
+ # - CanCanCan: uses before_action, so `skip_before_action` works
145
+ # - Custom callback: assumed to be a before_action (caller can use
146
+ # framework: for known frameworks)
147
+ def skip_authorization_callback(controller, callback, framework)
148
+ if (class_method = CLASS_METHOD_SKIP[framework])
149
+ # Engine controllers may inherit the skip class method (e.g.
150
+ # skip_verify_authorized from ActionPolicy) via the host app's
151
+ # ApplicationController without having called verify_authorized
152
+ # themselves. Rails raises ArgumentError when trying to skip a
153
+ # callback that was never registered. We match on the message to
154
+ # avoid masking unrelated ArgumentErrors.
155
+ begin
156
+ controller.public_send(class_method) if controller.respond_to?(class_method)
157
+ rescue ArgumentError => e
158
+ raise unless e.message.include?(":#{callback} has not been defined")
159
+
160
+ Rails.logger.debug { "[StandardId] Skipped #{class_method} on #{controller.name}: #{e.message}" }
161
+ end
162
+ elsif AFTER_ACTION_FRAMEWORKS.include?(framework)
163
+ controller.skip_after_action callback, raise: false
164
+ else
165
+ controller.skip_before_action callback, raise: false
166
+ end
167
+ end
119
168
  end
120
169
  end
121
170
  end
@@ -18,6 +18,9 @@ StandardConfig.schema.draw do
18
18
  field :use_inertia, type: :boolean, default: false
19
19
  field :inertia_component_namespace, type: :string, default: "standard_id"
20
20
  field :alias_current_user, type: :boolean, default: false
21
+ # Callable (lambda/proc) that returns a Hash of extra Sentry user context fields.
22
+ # Receives (account, session) where session may be nil. Non-callable values are ignored.
23
+ field :sentry_context, type: :any, default: nil
21
24
  end
22
25
 
23
26
  scope :events do
@@ -1,3 +1,3 @@
1
1
  module StandardId
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim