lapsoss 0.1.0 → 0.3.0
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 +4 -4
- data/README.md +153 -733
- data/lib/lapsoss/adapters/appsignal_adapter.rb +7 -8
- data/lib/lapsoss/adapters/base.rb +0 -3
- data/lib/lapsoss/adapters/bugsnag_adapter.rb +12 -0
- data/lib/lapsoss/adapters/insight_hub_adapter.rb +102 -101
- data/lib/lapsoss/adapters/logger_adapter.rb +7 -7
- data/lib/lapsoss/adapters/rollbar_adapter.rb +93 -54
- data/lib/lapsoss/adapters/sentry_adapter.rb +11 -17
- data/lib/lapsoss/backtrace_frame.rb +35 -214
- data/lib/lapsoss/backtrace_frame_factory.rb +228 -0
- data/lib/lapsoss/backtrace_processor.rb +37 -37
- data/lib/lapsoss/client.rb +2 -6
- data/lib/lapsoss/configuration.rb +25 -22
- data/lib/lapsoss/current.rb +9 -1
- data/lib/lapsoss/event.rb +30 -6
- data/lib/lapsoss/exception_backtrace_frame.rb +39 -0
- data/lib/lapsoss/exclusion_configuration.rb +30 -0
- data/lib/lapsoss/exclusion_filter.rb +156 -0
- data/lib/lapsoss/{exclusions.rb → exclusion_presets.rb} +1 -181
- data/lib/lapsoss/fingerprinter.rb +9 -13
- data/lib/lapsoss/http_client.rb +42 -8
- data/lib/lapsoss/merged_scope.rb +63 -0
- data/lib/lapsoss/middleware/base.rb +15 -0
- data/lib/lapsoss/middleware/conditional_filter.rb +18 -0
- data/lib/lapsoss/middleware/event_enricher.rb +19 -0
- data/lib/lapsoss/middleware/event_transformer.rb +19 -0
- data/lib/lapsoss/middleware/exception_filter.rb +43 -0
- data/lib/lapsoss/middleware/metrics_collector.rb +44 -0
- data/lib/lapsoss/middleware/rate_limiter.rb +31 -0
- data/lib/lapsoss/middleware/release_tracker.rb +117 -0
- data/lib/lapsoss/middleware/sample_filter.rb +23 -0
- data/lib/lapsoss/middleware/sampling_middleware.rb +18 -0
- data/lib/lapsoss/middleware/user_context_enhancer.rb +46 -0
- data/lib/lapsoss/middleware.rb +0 -347
- data/lib/lapsoss/pipeline.rb +1 -73
- data/lib/lapsoss/pipeline_builder.rb +69 -0
- data/lib/lapsoss/rails_error_subscriber.rb +42 -0
- data/lib/lapsoss/rails_middleware.rb +78 -0
- data/lib/lapsoss/railtie.rb +22 -50
- data/lib/lapsoss/registry.rb +34 -20
- data/lib/lapsoss/release_providers.rb +110 -0
- data/lib/lapsoss/release_tracker.rb +112 -207
- data/lib/lapsoss/router.rb +3 -5
- data/lib/lapsoss/sampling/adaptive_sampler.rb +46 -0
- data/lib/lapsoss/sampling/base.rb +11 -0
- data/lib/lapsoss/sampling/composite_sampler.rb +26 -0
- data/lib/lapsoss/sampling/consistent_hash_sampler.rb +30 -0
- data/lib/lapsoss/sampling/exception_type_sampler.rb +44 -0
- data/lib/lapsoss/sampling/health_based_sampler.rb +19 -0
- data/lib/lapsoss/sampling/rate_limiter.rb +32 -0
- data/lib/lapsoss/sampling/sampling_factory.rb +69 -0
- data/lib/lapsoss/sampling/time_based_sampler.rb +44 -0
- data/lib/lapsoss/sampling/uniform_sampler.rb +15 -0
- data/lib/lapsoss/sampling/user_based_sampler.rb +42 -0
- data/lib/lapsoss/sampling.rb +0 -326
- data/lib/lapsoss/scope.rb +17 -57
- data/lib/lapsoss/scrubber.rb +16 -18
- data/lib/lapsoss/user_context.rb +18 -198
- data/lib/lapsoss/user_context_integrations.rb +39 -0
- data/lib/lapsoss/user_context_middleware.rb +50 -0
- data/lib/lapsoss/user_context_provider.rb +93 -0
- data/lib/lapsoss/utils.rb +13 -0
- data/lib/lapsoss/validators.rb +14 -27
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +12 -25
- metadata +106 -21
data/lib/lapsoss/scrubber.rb
CHANGED
@@ -25,22 +25,20 @@ module Lapsoss
|
|
25
25
|
@rails_parameter_filter = rails_parameter_filter
|
26
26
|
|
27
27
|
# Only use custom scrubbing if Rails parameter filter is not available
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
return if @rails_parameter_filter
|
29
|
+
|
30
|
+
@scrub_fields = Array(config[:scrub_fields] || DEFAULT_SCRUB_FIELDS)
|
31
|
+
@scrub_all = config[:scrub_all] || false
|
32
|
+
@whitelist_fields = Array(config[:whitelist_fields] || [])
|
33
|
+
@randomize_scrub_length = config[:randomize_scrub_length] || false
|
34
|
+
@scrub_value = config[:scrub_value] || "**SCRUBBED**"
|
35
35
|
end
|
36
36
|
|
37
37
|
def scrub(data)
|
38
38
|
return data if data.nil?
|
39
39
|
|
40
40
|
# If Rails parameter filter is available, use it exclusively
|
41
|
-
if @rails_parameter_filter
|
42
|
-
return @rails_parameter_filter.filter(data)
|
43
|
-
end
|
41
|
+
return @rails_parameter_filter.filter(data) if @rails_parameter_filter
|
44
42
|
|
45
43
|
# Fallback to custom scrubbing logic only if Rails filter is not available
|
46
44
|
@scrubbed_objects = {}.compare_by_identity
|
@@ -68,14 +66,14 @@ module Lapsoss
|
|
68
66
|
hash.each_with_object({}) do |(key, value), result|
|
69
67
|
key_string = key.to_s.downcase
|
70
68
|
|
71
|
-
if should_scrub_field?(key_string)
|
72
|
-
|
69
|
+
result[key] = if should_scrub_field?(key_string)
|
70
|
+
generate_scrub_value(value)
|
73
71
|
elsif value.is_a?(Hash)
|
74
|
-
|
72
|
+
scrub_recursive(value)
|
75
73
|
elsif value.is_a?(Array)
|
76
|
-
|
74
|
+
scrub_array(value)
|
77
75
|
else
|
78
|
-
|
76
|
+
scrub_value(value)
|
79
77
|
end
|
80
78
|
end
|
81
79
|
end
|
@@ -118,7 +116,7 @@ module Lapsoss
|
|
118
116
|
PROTECTED_EVENT_FIELDS.include?(field_name.to_s)
|
119
117
|
end
|
120
118
|
|
121
|
-
def whitelisted_value?(
|
119
|
+
def whitelisted_value?(_value)
|
122
120
|
# Basic implementation - could be extended
|
123
121
|
false
|
124
122
|
end
|
@@ -136,7 +134,7 @@ module Lapsoss
|
|
136
134
|
original_filename: safe_call(attachment, :original_filename),
|
137
135
|
size: safe_call(attachment, :size) || safe_call(attachment, :tempfile, :size)
|
138
136
|
}
|
139
|
-
rescue => e
|
137
|
+
rescue StandardError => e
|
140
138
|
{ __attachment__: true, error: "Failed to extract attachment info: #{e.message}" }
|
141
139
|
end
|
142
140
|
|
@@ -146,7 +144,7 @@ module Lapsoss
|
|
146
144
|
end
|
147
145
|
end
|
148
146
|
|
149
|
-
def generate_scrub_value(
|
147
|
+
def generate_scrub_value(_original_value)
|
150
148
|
if @randomize_scrub_length
|
151
149
|
"*" * rand(6..12)
|
152
150
|
else
|
data/lib/lapsoss/user_context.rb
CHANGED
@@ -30,15 +30,13 @@ module Lapsoss
|
|
30
30
|
key_sym = key.to_sym
|
31
31
|
|
32
32
|
# Skip if not in allowed fields list (when specified)
|
33
|
-
if @allowed_fields
|
34
|
-
next
|
35
|
-
end
|
33
|
+
next if @allowed_fields&.exclude?(key_sym)
|
36
34
|
|
37
35
|
# Apply privacy filtering
|
38
|
-
if @privacy_mode && sensitive_field?(key_sym)
|
39
|
-
|
36
|
+
processed[key] = if @privacy_mode && sensitive_field?(key_sym)
|
37
|
+
apply_privacy_filter(key_sym, value)
|
40
38
|
else
|
41
|
-
|
39
|
+
transform_field(key_sym, value)
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
@@ -70,18 +68,18 @@ module Lapsoss
|
|
70
68
|
segments = {}
|
71
69
|
|
72
70
|
# Check for common user segments
|
73
|
-
segments[:internal] =
|
74
|
-
segments[:premium] =
|
75
|
-
segments[:beta] =
|
76
|
-
segments[:admin] =
|
71
|
+
segments[:internal] = !(user_data[:internal] || user_data["internal"]).nil?
|
72
|
+
segments[:premium] = !(user_data[:premium] || user_data["premium"]).nil?
|
73
|
+
segments[:beta] = !(user_data[:beta] || user_data["beta"]).nil?
|
74
|
+
segments[:admin] = !(user_data[:admin] || user_data["admin"]).nil?
|
77
75
|
|
78
76
|
# Check role-based segments
|
79
|
-
if role =
|
77
|
+
if role = user_data[:role] || user_data["role"]
|
80
78
|
segments[:role] = role.to_s.downcase
|
81
79
|
end
|
82
80
|
|
83
81
|
# Check plan-based segments
|
84
|
-
if plan =
|
82
|
+
if plan = user_data[:plan] || user_data["plan"]
|
85
83
|
segments[:plan] = plan.to_s.downcase
|
86
84
|
end
|
87
85
|
|
@@ -96,14 +94,14 @@ module Lapsoss
|
|
96
94
|
user_data.each do |key, value|
|
97
95
|
key_sym = key.to_sym
|
98
96
|
|
99
|
-
if sensitive_field?(key_sym)
|
100
|
-
|
97
|
+
sanitized[key] = if sensitive_field?(key_sym)
|
98
|
+
"[REDACTED]"
|
101
99
|
elsif value.is_a?(Hash)
|
102
|
-
|
100
|
+
sanitize_for_logging(value)
|
103
101
|
elsif value.is_a?(Array)
|
104
|
-
|
102
|
+
value.map { |v| v.is_a?(Hash) ? sanitize_for_logging(v) : v }
|
105
103
|
else
|
106
|
-
|
104
|
+
value
|
107
105
|
end
|
108
106
|
end
|
109
107
|
|
@@ -153,7 +151,7 @@ module Lapsoss
|
|
153
151
|
return "[INVALID_PHONE]" if digits.length < 4
|
154
152
|
|
155
153
|
# Show last 4 digits
|
156
|
-
"*" * (digits.length - 4) + digits[-4
|
154
|
+
("*" * (digits.length - 4)) + digits[-4..]
|
157
155
|
end
|
158
156
|
|
159
157
|
def mask_ip(ip)
|
@@ -162,194 +160,16 @@ module Lapsoss
|
|
162
160
|
if ip.include?(":")
|
163
161
|
# IPv6 - mask last 4 groups
|
164
162
|
parts = ip.split(":")
|
165
|
-
parts[-4..-1] = ["****"] * 4 if parts.length >= 4
|
163
|
+
parts[-4..-1] = [ "****" ] * 4 if parts.length >= 4
|
166
164
|
parts.join(":")
|
167
165
|
else
|
168
166
|
# IPv4 - mask last octet
|
169
167
|
parts = ip.split(".")
|
170
168
|
return "[INVALID_IP]" if parts.length != 4
|
169
|
+
|
171
170
|
parts[-1] = "***"
|
172
171
|
parts.join(".")
|
173
172
|
end
|
174
173
|
end
|
175
174
|
end
|
176
|
-
|
177
|
-
# User context provider that integrates with various authentication systems
|
178
|
-
class UserContextProvider
|
179
|
-
def initialize(providers: {})
|
180
|
-
@providers = providers
|
181
|
-
end
|
182
|
-
|
183
|
-
def get_user_context(event, hint = {})
|
184
|
-
context = {}
|
185
|
-
|
186
|
-
# Try each provider in order
|
187
|
-
@providers.each do |name, provider|
|
188
|
-
begin
|
189
|
-
if provider_context = provider.call(event, hint)
|
190
|
-
context.merge!(provider_context)
|
191
|
-
end
|
192
|
-
rescue StandardError => e
|
193
|
-
# Log provider error but don't fail
|
194
|
-
warn "User context provider #{name} failed: #{e.message}"
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
context
|
199
|
-
end
|
200
|
-
|
201
|
-
# Built-in providers for common authentication systems
|
202
|
-
def self.devise_provider
|
203
|
-
lambda do |event, hint|
|
204
|
-
return {} unless defined?(Devise) && defined?(Warden)
|
205
|
-
|
206
|
-
# Try to get user from Warden (used by Devise)
|
207
|
-
if request = hint[:request]
|
208
|
-
user = request.env["warden"]&.user
|
209
|
-
return {} unless user
|
210
|
-
|
211
|
-
{
|
212
|
-
id: user.id,
|
213
|
-
email: user.email,
|
214
|
-
username: user.respond_to?(:username) ? user.username : nil,
|
215
|
-
created_at: user.created_at,
|
216
|
-
role: user.respond_to?(:role) ? user.role : nil
|
217
|
-
}.compact
|
218
|
-
end
|
219
|
-
|
220
|
-
{}
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
def self.omniauth_provider
|
225
|
-
lambda do |event, hint|
|
226
|
-
return {} unless defined?(OmniAuth)
|
227
|
-
|
228
|
-
if request = hint[:request]
|
229
|
-
if auth_info = request.env["omniauth.auth"]
|
230
|
-
{
|
231
|
-
provider: auth_info["provider"],
|
232
|
-
uid: auth_info["uid"],
|
233
|
-
name: auth_info.dig("info", "name"),
|
234
|
-
email: auth_info.dig("info", "email"),
|
235
|
-
username: auth_info.dig("info", "nickname")
|
236
|
-
}.compact
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
{}
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
def self.session_provider
|
245
|
-
lambda do |event, hint|
|
246
|
-
return {} unless hint[:request]
|
247
|
-
|
248
|
-
request = hint[:request]
|
249
|
-
session = request.session rescue {}
|
250
|
-
|
251
|
-
{
|
252
|
-
session_id: session[:session_id] || session["session_id"],
|
253
|
-
user_id: session[:user_id] || session["user_id"],
|
254
|
-
csrf_token: session[:_csrf_token] || session["_csrf_token"]
|
255
|
-
}.compact
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
def self.thread_local_provider
|
260
|
-
lambda do |event, hint|
|
261
|
-
# Get user from thread-local storage
|
262
|
-
Thread.current[:current_user] || {}
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
# Integration with popular authentication gems
|
268
|
-
module UserContextIntegrations
|
269
|
-
def self.setup_devise_integration
|
270
|
-
return unless defined?(Devise)
|
271
|
-
|
272
|
-
# Add middleware to capture user context
|
273
|
-
if defined?(Rails)
|
274
|
-
Rails.application.config.middleware.use(UserContextMiddleware)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
def self.setup_clearance_integration
|
279
|
-
return unless defined?(Clearance)
|
280
|
-
|
281
|
-
# Clearance integration
|
282
|
-
if defined?(Rails)
|
283
|
-
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
284
|
-
middleware.user_provider = lambda do |request|
|
285
|
-
request.env[:clearance].current_user if request.env[:clearance]
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def self.setup_authlogic_integration
|
292
|
-
return unless defined?(Authlogic)
|
293
|
-
|
294
|
-
# Authlogic integration
|
295
|
-
if defined?(Rails)
|
296
|
-
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
297
|
-
middleware.user_provider = lambda do |request|
|
298
|
-
UserSession.find&.user if defined?(UserSession)
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
# Middleware to automatically capture user context
|
306
|
-
class UserContextMiddleware
|
307
|
-
def initialize(app, user_provider: nil)
|
308
|
-
@app = app
|
309
|
-
@user_provider = user_provider
|
310
|
-
end
|
311
|
-
|
312
|
-
def call(env)
|
313
|
-
request = Rack::Request.new(env)
|
314
|
-
|
315
|
-
# Capture user context
|
316
|
-
user_context = extract_user_context(request)
|
317
|
-
|
318
|
-
# Store in thread-local for access during request processing
|
319
|
-
Thread.current[:lapsoss_user_context] = user_context
|
320
|
-
|
321
|
-
@app.call(env)
|
322
|
-
ensure
|
323
|
-
Thread.current[:lapsoss_user_context] = nil
|
324
|
-
end
|
325
|
-
|
326
|
-
private
|
327
|
-
|
328
|
-
def extract_user_context(request)
|
329
|
-
if @user_provider
|
330
|
-
user = @user_provider.call(request)
|
331
|
-
return {} unless user
|
332
|
-
|
333
|
-
context = {
|
334
|
-
id: user.id,
|
335
|
-
email: user.email,
|
336
|
-
username: user.respond_to?(:username) ? user.username : nil
|
337
|
-
}
|
338
|
-
|
339
|
-
# Add role information if available
|
340
|
-
if user.respond_to?(:role)
|
341
|
-
context[:role] = user.role
|
342
|
-
end
|
343
|
-
|
344
|
-
# Add plan information if available
|
345
|
-
if user.respond_to?(:plan)
|
346
|
-
context[:plan] = user.plan
|
347
|
-
end
|
348
|
-
|
349
|
-
context.compact
|
350
|
-
else
|
351
|
-
{}
|
352
|
-
end
|
353
|
-
end
|
354
|
-
end
|
355
175
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
# Integration with popular authentication gems
|
5
|
+
module UserContextIntegrations
|
6
|
+
def self.setup_devise_integration
|
7
|
+
return unless defined?(Devise)
|
8
|
+
|
9
|
+
# Add middleware to capture user context
|
10
|
+
Rails.application.config.middleware.use(UserContextMiddleware) if defined?(Rails)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.setup_clearance_integration
|
14
|
+
return unless defined?(Clearance)
|
15
|
+
|
16
|
+
# Clearance integration
|
17
|
+
return unless defined?(Rails)
|
18
|
+
|
19
|
+
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
20
|
+
middleware.user_provider = lambda do |request|
|
21
|
+
request.env[:clearance].current_user if request.env[:clearance]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.setup_authlogic_integration
|
27
|
+
return unless defined?(Authlogic)
|
28
|
+
|
29
|
+
# Authlogic integration
|
30
|
+
return unless defined?(Rails)
|
31
|
+
|
32
|
+
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
33
|
+
middleware.user_provider = lambda do |_request|
|
34
|
+
UserSession.find&.user if defined?(UserSession)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
# Middleware to automatically capture user context
|
5
|
+
class UserContextMiddleware
|
6
|
+
def initialize(app, user_provider: nil)
|
7
|
+
@app = app
|
8
|
+
@user_provider = user_provider
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
request = Rack::Request.new(env)
|
13
|
+
|
14
|
+
# Capture user context
|
15
|
+
user_context = extract_user_context(request)
|
16
|
+
|
17
|
+
# Store in thread-local for access during request processing
|
18
|
+
Thread.current[:lapsoss_user_context] = user_context
|
19
|
+
|
20
|
+
@app.call(env)
|
21
|
+
ensure
|
22
|
+
Thread.current[:lapsoss_user_context] = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def extract_user_context(request)
|
28
|
+
if @user_provider
|
29
|
+
user = @user_provider.call(request)
|
30
|
+
return {} unless user
|
31
|
+
|
32
|
+
context = {
|
33
|
+
id: user.id,
|
34
|
+
email: user.email,
|
35
|
+
username: user.respond_to?(:username) ? user.username : nil
|
36
|
+
}
|
37
|
+
|
38
|
+
# Add role information if available
|
39
|
+
context[:role] = user.role if user.respond_to?(:role)
|
40
|
+
|
41
|
+
# Add plan information if available
|
42
|
+
context[:plan] = user.plan if user.respond_to?(:plan)
|
43
|
+
|
44
|
+
context.compact
|
45
|
+
else
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lapsoss
|
4
|
+
# User context provider that integrates with various authentication systems
|
5
|
+
class UserContextProvider
|
6
|
+
def initialize(providers: {})
|
7
|
+
@providers = providers
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_user_context(event, hint = {})
|
11
|
+
context = {}
|
12
|
+
|
13
|
+
# Try each provider in order
|
14
|
+
@providers.each do |name, provider|
|
15
|
+
if provider_context = provider.call(event, hint)
|
16
|
+
context.merge!(provider_context)
|
17
|
+
end
|
18
|
+
rescue StandardError => e
|
19
|
+
# Log provider error but don't fail
|
20
|
+
warn "User context provider #{name} failed: #{e.message}"
|
21
|
+
end
|
22
|
+
|
23
|
+
context
|
24
|
+
end
|
25
|
+
|
26
|
+
# Built-in providers for common authentication systems
|
27
|
+
def self.devise_provider
|
28
|
+
lambda do |_event, hint|
|
29
|
+
return {} unless defined?(Devise) && defined?(Warden)
|
30
|
+
|
31
|
+
# Try to get user from Warden (used by Devise)
|
32
|
+
if request = hint[:request]
|
33
|
+
user = request.env["warden"]&.user
|
34
|
+
return {} unless user
|
35
|
+
|
36
|
+
{
|
37
|
+
id: user.id,
|
38
|
+
email: user.email,
|
39
|
+
username: user.respond_to?(:username) ? user.username : nil,
|
40
|
+
created_at: user.created_at,
|
41
|
+
role: user.respond_to?(:role) ? user.role : nil
|
42
|
+
}.compact
|
43
|
+
end
|
44
|
+
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.omniauth_provider
|
50
|
+
lambda do |_event, hint|
|
51
|
+
return {} unless defined?(OmniAuth)
|
52
|
+
|
53
|
+
if (request = hint[:request]) && (auth_info = request.env["omniauth.auth"])
|
54
|
+
{
|
55
|
+
provider: auth_info["provider"],
|
56
|
+
uid: auth_info["uid"],
|
57
|
+
name: auth_info.dig("info", "name"),
|
58
|
+
email: auth_info.dig("info", "email"),
|
59
|
+
username: auth_info.dig("info", "nickname")
|
60
|
+
}.compact
|
61
|
+
end
|
62
|
+
|
63
|
+
{}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.session_provider
|
68
|
+
lambda do |_event, hint|
|
69
|
+
return {} unless hint[:request]
|
70
|
+
|
71
|
+
request = hint[:request]
|
72
|
+
session = begin
|
73
|
+
request.session
|
74
|
+
rescue StandardError
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
|
78
|
+
{
|
79
|
+
session_id: session[:session_id] || session["session_id"],
|
80
|
+
user_id: session[:user_id] || session["user_id"],
|
81
|
+
csrf_token: session[:_csrf_token] || session["_csrf_token"]
|
82
|
+
}.compact
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.thread_local_provider
|
87
|
+
lambda do |_event, _hint|
|
88
|
+
# Get user from thread-local storage
|
89
|
+
Thread.current[:current_user] || {}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/lapsoss/validators.rb
CHANGED
@@ -17,7 +17,7 @@ module Lapsoss
|
|
17
17
|
def validate_type!(value, types, name)
|
18
18
|
types = Array(types)
|
19
19
|
unless types.any? { |type| value.is_a?(type) }
|
20
|
-
type_names = types.map(&:name).join(
|
20
|
+
type_names = types.map(&:name).join(" or ")
|
21
21
|
raise ValidationError, "#{name} must be a #{type_names}, got #{value.class} (#{value.inspect})"
|
22
22
|
end
|
23
23
|
end
|
@@ -29,7 +29,7 @@ module Lapsoss
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def validate_numeric_range!(value, range, name)
|
32
|
-
validate_type!(value, [Numeric], name)
|
32
|
+
validate_type!(value, [ Numeric ], name)
|
33
33
|
unless range.cover?(value)
|
34
34
|
raise ValidationError, "#{name} must be between #{range.min} and #{range.max}, got #{value}"
|
35
35
|
end
|
@@ -38,16 +38,12 @@ module Lapsoss
|
|
38
38
|
def validate_url!(value, name)
|
39
39
|
return if value.nil?
|
40
40
|
|
41
|
-
validate_type!(value, [String], name)
|
41
|
+
validate_type!(value, [ String ], name)
|
42
42
|
|
43
43
|
begin
|
44
44
|
uri = URI.parse(value)
|
45
|
-
unless uri.scheme && uri.host
|
46
|
-
|
47
|
-
end
|
48
|
-
unless %w[http https].include?(uri.scheme)
|
49
|
-
raise ValidationError, "#{name} must use http or https scheme"
|
50
|
-
end
|
45
|
+
raise ValidationError, "#{name} must be a valid URL with scheme and host" unless uri.scheme && uri.host
|
46
|
+
raise ValidationError, "#{name} must use http or https scheme" unless %w[http https].include?(uri.scheme)
|
51
47
|
rescue URI::InvalidURIError => e
|
52
48
|
raise ValidationError, "#{name} is not a valid URL: #{e.message}"
|
53
49
|
end
|
@@ -56,7 +52,7 @@ module Lapsoss
|
|
56
52
|
def validate_dsn!(dsn_string, name = "DSN")
|
57
53
|
return if dsn_string.nil?
|
58
54
|
|
59
|
-
validate_type!(dsn_string, [String], name)
|
55
|
+
validate_type!(dsn_string, [ String ], name)
|
60
56
|
|
61
57
|
begin
|
62
58
|
uri = URI.parse(dsn_string)
|
@@ -67,9 +63,7 @@ module Lapsoss
|
|
67
63
|
validate_presence!(uri.user, "#{name} public key")
|
68
64
|
|
69
65
|
# Validate scheme
|
70
|
-
unless %w[http https].include?(uri.scheme)
|
71
|
-
raise ValidationError, "#{name} must use http or https scheme"
|
72
|
-
end
|
66
|
+
raise ValidationError, "#{name} must use http or https scheme" unless %w[http https].include?(uri.scheme)
|
73
67
|
|
74
68
|
# Extract project ID from path
|
75
69
|
path_parts = uri.path&.split("/") || []
|
@@ -80,7 +74,6 @@ module Lapsoss
|
|
80
74
|
unless project_id.match?(/^\d+$/)
|
81
75
|
raise ValidationError, "#{name} project ID must be numeric, got '#{project_id}'"
|
82
76
|
end
|
83
|
-
|
84
77
|
rescue URI::InvalidURIError => e
|
85
78
|
raise ValidationError, "#{name} is not a valid URI: #{e.message}"
|
86
79
|
end
|
@@ -88,7 +81,7 @@ module Lapsoss
|
|
88
81
|
|
89
82
|
def validate_api_key!(value, name, format: nil)
|
90
83
|
validate_presence!(value, name)
|
91
|
-
validate_type!(value, [String], name)
|
84
|
+
validate_type!(value, [ String ], name)
|
92
85
|
|
93
86
|
case format
|
94
87
|
when :uuid
|
@@ -96,20 +89,16 @@ module Lapsoss
|
|
96
89
|
raise ValidationError, "#{name} must be a valid UUID format"
|
97
90
|
end
|
98
91
|
when :hex
|
99
|
-
unless value.match?(/\A[0-9a-f]+\z/i)
|
100
|
-
raise ValidationError, "#{name} must be a valid hexadecimal string"
|
101
|
-
end
|
92
|
+
raise ValidationError, "#{name} must be a valid hexadecimal string" unless value.match?(/\A[0-9a-f]+\z/i)
|
102
93
|
when :alphanumeric
|
103
|
-
unless value.match?(/\A[a-z0-9]+\z/i)
|
104
|
-
raise ValidationError, "#{name} must be alphanumeric"
|
105
|
-
end
|
94
|
+
raise ValidationError, "#{name} must be alphanumeric" unless value.match?(/\A[a-z0-9]+\z/i)
|
106
95
|
end
|
107
96
|
end
|
108
97
|
|
109
98
|
def validate_environment!(value, name = "environment")
|
110
99
|
return if value.nil?
|
111
100
|
|
112
|
-
validate_type!(value, [String, Symbol], name)
|
101
|
+
validate_type!(value, [ String, Symbol ], name)
|
113
102
|
|
114
103
|
env_string = value.to_s
|
115
104
|
unless env_string.match?(/\A[a-z0-9_-]+\z/i)
|
@@ -126,16 +115,14 @@ module Lapsoss
|
|
126
115
|
def validate_timeout!(value, name = "timeout")
|
127
116
|
return if value.nil?
|
128
117
|
|
129
|
-
validate_type!(value, [Numeric], name)
|
130
|
-
if value <= 0
|
131
|
-
raise ValidationError, "#{name} must be positive, got #{value}"
|
132
|
-
end
|
118
|
+
validate_type!(value, [ Numeric ], name)
|
119
|
+
raise ValidationError, "#{name} must be positive, got #{value}" if value <= 0
|
133
120
|
end
|
134
121
|
|
135
122
|
def validate_retries!(value, name = "max_retries")
|
136
123
|
return if value.nil?
|
137
124
|
|
138
|
-
validate_type!(value, [Integer], name)
|
125
|
+
validate_type!(value, [ Integer ], name)
|
139
126
|
validate_numeric_range!(value, 0..10, name)
|
140
127
|
end
|
141
128
|
end
|
data/lib/lapsoss/version.rb
CHANGED