otto 2.0.0.pre3 → 2.0.0.pre7

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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/.github/workflows/claude-code-review.yml +1 -1
  4. data/.github/workflows/code-smells.yml +146 -0
  5. data/.gitignore +4 -0
  6. data/.pre-commit-config.yaml +2 -2
  7. data/.reek.yml +99 -0
  8. data/CHANGELOG.rst +90 -0
  9. data/CLAUDE.md +74 -540
  10. data/Gemfile +4 -2
  11. data/Gemfile.lock +58 -19
  12. data/README.md +49 -1
  13. data/changelog.d/20251103_235431_delano_86_improve_error_logging.rst +15 -0
  14. data/changelog.d/20251109_025012_claude_fix_backtrace_sanitization.rst +37 -0
  15. data/examples/advanced_routes/README.md +137 -20
  16. data/examples/authentication_strategies/README.md +212 -19
  17. data/examples/backtrace_sanitization_demo.rb +86 -0
  18. data/examples/basic/README.md +61 -10
  19. data/examples/error_handler_registration.rb +136 -0
  20. data/examples/logging_improvements.rb +76 -0
  21. data/examples/mcp_demo/README.md +187 -27
  22. data/examples/security_features/README.md +249 -30
  23. data/examples/simple_geo_resolver.rb +107 -0
  24. data/lib/otto/core/configuration.rb +15 -20
  25. data/lib/otto/core/error_handler.rb +138 -8
  26. data/lib/otto/core/file_safety.rb +2 -2
  27. data/lib/otto/core/freezable.rb +2 -2
  28. data/lib/otto/core/middleware_stack.rb +2 -2
  29. data/lib/otto/core/router.rb +61 -8
  30. data/lib/otto/core/uri_generator.rb +2 -2
  31. data/lib/otto/core.rb +2 -0
  32. data/lib/otto/design_system.rb +2 -2
  33. data/lib/otto/env_keys.rb +61 -12
  34. data/lib/otto/helpers/base.rb +2 -2
  35. data/lib/otto/helpers/request.rb +8 -3
  36. data/lib/otto/helpers/response.rb +2 -2
  37. data/lib/otto/helpers/validation.rb +2 -2
  38. data/lib/otto/helpers.rb +2 -0
  39. data/lib/otto/locale/config.rb +2 -2
  40. data/lib/otto/locale/middleware.rb +160 -0
  41. data/lib/otto/locale.rb +10 -0
  42. data/lib/otto/logging_helpers.rb +273 -0
  43. data/lib/otto/mcp/auth/token.rb +2 -2
  44. data/lib/otto/mcp/protocol.rb +2 -2
  45. data/lib/otto/mcp/rate_limiting.rb +2 -2
  46. data/lib/otto/mcp/registry.rb +2 -2
  47. data/lib/otto/mcp/route_parser.rb +2 -2
  48. data/lib/otto/mcp/schema_validation.rb +2 -2
  49. data/lib/otto/mcp/server.rb +2 -2
  50. data/lib/otto/mcp.rb +2 -0
  51. data/lib/otto/privacy/config.rb +2 -0
  52. data/lib/otto/privacy/geo_resolver.rb +199 -29
  53. data/lib/otto/privacy/ip_privacy.rb +2 -0
  54. data/lib/otto/privacy/redacted_fingerprint.rb +18 -8
  55. data/lib/otto/privacy.rb +2 -0
  56. data/lib/otto/response_handlers/auto.rb +2 -0
  57. data/lib/otto/response_handlers/base.rb +2 -0
  58. data/lib/otto/response_handlers/default.rb +2 -0
  59. data/lib/otto/response_handlers/factory.rb +2 -0
  60. data/lib/otto/response_handlers/json.rb +2 -0
  61. data/lib/otto/response_handlers/redirect.rb +2 -0
  62. data/lib/otto/response_handlers/view.rb +2 -0
  63. data/lib/otto/response_handlers.rb +2 -2
  64. data/lib/otto/route.rb +4 -4
  65. data/lib/otto/route_definition.rb +42 -15
  66. data/lib/otto/route_handlers/base.rb +2 -0
  67. data/lib/otto/route_handlers/class_method.rb +18 -25
  68. data/lib/otto/route_handlers/factory.rb +2 -2
  69. data/lib/otto/route_handlers/instance_method.rb +8 -5
  70. data/lib/otto/route_handlers/lambda.rb +8 -20
  71. data/lib/otto/route_handlers/logic_class.rb +23 -6
  72. data/lib/otto/route_handlers.rb +2 -2
  73. data/lib/otto/security/authentication/auth_failure.rb +2 -2
  74. data/lib/otto/security/authentication/auth_strategy.rb +11 -4
  75. data/lib/otto/security/authentication/route_auth_wrapper.rb +230 -78
  76. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +2 -0
  77. data/lib/otto/security/authentication/strategies/noauth_strategy.rb +2 -0
  78. data/lib/otto/security/authentication/strategies/permission_strategy.rb +2 -0
  79. data/lib/otto/security/authentication/strategies/role_strategy.rb +2 -0
  80. data/lib/otto/security/authentication/strategies/session_strategy.rb +2 -0
  81. data/lib/otto/security/authentication/strategy_result.rb +6 -5
  82. data/lib/otto/security/authentication.rb +2 -2
  83. data/lib/otto/security/authorization_error.rb +73 -0
  84. data/lib/otto/security/config.rb +2 -2
  85. data/lib/otto/security/configurator.rb +17 -2
  86. data/lib/otto/security/csrf.rb +2 -2
  87. data/lib/otto/security/middleware/csrf_middleware.rb +11 -1
  88. data/lib/otto/security/middleware/ip_privacy_middleware.rb +31 -11
  89. data/lib/otto/security/middleware/rate_limit_middleware.rb +2 -0
  90. data/lib/otto/security/middleware/validation_middleware.rb +15 -0
  91. data/lib/otto/security/rate_limiter.rb +2 -2
  92. data/lib/otto/security/rate_limiting.rb +2 -2
  93. data/lib/otto/security/validator.rb +2 -2
  94. data/lib/otto/security.rb +3 -0
  95. data/lib/otto/static.rb +2 -2
  96. data/lib/otto/utils.rb +27 -2
  97. data/lib/otto/version.rb +3 -3
  98. data/lib/otto.rb +174 -14
  99. data/otto.gemspec +7 -3
  100. metadata +24 -15
  101. data/benchmark_middleware_wrap.rb +0 -163
  102. data/changelog.d/20251014_144317_delano_54_thats_a_wrapper.rst +0 -36
  103. data/changelog.d/20251014_161526_delano_54_thats_a_wrapper.rst +0 -5
data/otto.gemspec CHANGED
@@ -10,21 +10,25 @@ Gem::Specification.new do |spec|
10
10
  spec.email = 'gems@solutious.com'
11
11
  spec.authors = ['Delano Mandelbaum']
12
12
  spec.license = 'MIT'
13
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
13
+ spec.files = if File.directory?('.git') && system('git --version > /dev/null 2>&1')
14
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ else
16
+ Dir['**/*'].select { |f| File.file?(f) }.reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ end
14
18
  spec.homepage = 'https://github.com/delano/otto'
15
19
  spec.require_paths = ['lib']
16
20
 
17
21
  spec.required_ruby_version = ['>= 3.2', '< 4.0']
18
22
 
19
- spec.add_dependency 'ipaddr', '~> 1', '< 2.0'
20
23
  spec.add_dependency 'concurrent-ruby', '~> 1.3', '< 2.0'
24
+ spec.add_dependency 'ipaddr', '~> 1', '< 2.0'
21
25
 
22
26
  # Logger is not part of the default gems as of Ruby 3.5.0
23
27
  spec.add_dependency 'logger', '~> 1', '< 2.0'
24
28
 
25
29
  spec.add_dependency 'rack', '~> 3.1', '< 4.0'
26
30
  spec.add_dependency 'rack-parser', '~> 0.7'
27
- spec.add_dependency 'rexml', '>= 3.3.6'
31
+ spec.add_dependency 'rexml', '~> 3.4'
28
32
 
29
33
  # Security dependencies
30
34
  spec.add_dependency 'facets', '~> 3.1'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otto
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre3
4
+ version: 2.0.0.pre7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -10,12 +10,12 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: ipaddr
13
+ name: concurrent-ruby
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '1'
18
+ version: '1.3'
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: '2.0'
@@ -25,17 +25,17 @@ dependencies:
25
25
  requirements:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: '1'
28
+ version: '1.3'
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: '2.0'
32
32
  - !ruby/object:Gem::Dependency
33
- name: concurrent-ruby
33
+ name: ipaddr
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: '1.3'
38
+ version: '1'
39
39
  - - "<"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '2.0'
@@ -45,7 +45,7 @@ dependencies:
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '1.3'
48
+ version: '1'
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
51
  version: '2.0'
@@ -107,16 +107,16 @@ dependencies:
107
107
  name: rexml
108
108
  requirement: !ruby/object:Gem::Requirement
109
109
  requirements:
110
- - - ">="
110
+ - - "~>"
111
111
  - !ruby/object:Gem::Version
112
- version: 3.3.6
112
+ version: '3.4'
113
113
  type: :runtime
114
114
  prerelease: false
115
115
  version_requirements: !ruby/object:Gem::Requirement
116
116
  requirements:
117
- - - ">="
117
+ - - "~>"
118
118
  - !ruby/object:Gem::Version
119
- version: 3.3.6
119
+ version: '3.4'
120
120
  - !ruby/object:Gem::Dependency
121
121
  name: facets
122
122
  requirement: !ruby/object:Gem::Requirement
@@ -155,9 +155,11 @@ files:
155
155
  - ".github/workflows/ci.yml"
156
156
  - ".github/workflows/claude-code-review.yml"
157
157
  - ".github/workflows/claude.yml"
158
+ - ".github/workflows/code-smells.yml"
158
159
  - ".gitignore"
159
160
  - ".pre-commit-config.yaml"
160
161
  - ".pre-push-config.yaml"
162
+ - ".reek.yml"
161
163
  - ".rspec"
162
164
  - ".rubocop.yml"
163
165
  - CHANGELOG.rst
@@ -166,10 +168,9 @@ files:
166
168
  - Gemfile.lock
167
169
  - LICENSE.txt
168
170
  - README.md
169
- - benchmark_middleware_wrap.rb
170
171
  - bin/rspec
171
- - changelog.d/20251014_144317_delano_54_thats_a_wrapper.rst
172
- - changelog.d/20251014_161526_delano_54_thats_a_wrapper.rst
172
+ - changelog.d/20251103_235431_delano_86_improve_error_logging.rst
173
+ - changelog.d/20251109_025012_claude_fix_backtrace_sanitization.rst
173
174
  - changelog.d/README.md
174
175
  - changelog.d/scriv.ini
175
176
  - docs/.gitignore
@@ -217,10 +218,13 @@ files:
217
218
  - examples/authentication_strategies/app/controllers/main_controller.rb
218
219
  - examples/authentication_strategies/config.ru
219
220
  - examples/authentication_strategies/routes
221
+ - examples/backtrace_sanitization_demo.rb
220
222
  - examples/basic/README.md
221
223
  - examples/basic/app.rb
222
224
  - examples/basic/config.ru
223
225
  - examples/basic/routes
226
+ - examples/error_handler_registration.rb
227
+ - examples/logging_improvements.rb
224
228
  - examples/mcp_demo/README.md
225
229
  - examples/mcp_demo/app.rb
226
230
  - examples/mcp_demo/config.ru
@@ -229,6 +233,7 @@ files:
229
233
  - examples/security_features/app.rb
230
234
  - examples/security_features/config.ru
231
235
  - examples/security_features/routes
236
+ - examples/simple_geo_resolver.rb
232
237
  - lib/otto.rb
233
238
  - lib/otto/core.rb
234
239
  - lib/otto/core/configuration.rb
@@ -245,7 +250,10 @@ files:
245
250
  - lib/otto/helpers/request.rb
246
251
  - lib/otto/helpers/response.rb
247
252
  - lib/otto/helpers/validation.rb
253
+ - lib/otto/locale.rb
248
254
  - lib/otto/locale/config.rb
255
+ - lib/otto/locale/middleware.rb
256
+ - lib/otto/logging_helpers.rb
249
257
  - lib/otto/mcp.rb
250
258
  - lib/otto/mcp/auth/token.rb
251
259
  - lib/otto/mcp/protocol.rb
@@ -287,6 +295,7 @@ files:
287
295
  - lib/otto/security/authentication/strategies/role_strategy.rb
288
296
  - lib/otto/security/authentication/strategies/session_strategy.rb
289
297
  - lib/otto/security/authentication/strategy_result.rb
298
+ - lib/otto/security/authorization_error.rb
290
299
  - lib/otto/security/config.rb
291
300
  - lib/otto/security/configurator.rb
292
301
  - lib/otto/security/csrf.rb
@@ -325,7 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
325
334
  - !ruby/object:Gem::Version
326
335
  version: '0'
327
336
  requirements: []
328
- rubygems_version: 3.6.9
337
+ rubygems_version: 3.7.2
329
338
  specification_version: 4
330
339
  summary: Auto-define your rack-apps in plaintext.
331
340
  test_files: []
@@ -1,163 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Benchmark to measure real-world Otto performance with actual routes and middleware
5
- # Usage: ruby benchmark_middleware_wrap.rb
6
-
7
- require 'bundler/setup'
8
- require 'benchmark'
9
- require 'stringio'
10
- require 'tempfile'
11
-
12
- require_relative 'lib/otto'
13
-
14
- REQUEST_COUNT = 50_000
15
- MIDDLEWARE_COUNT = 40
16
-
17
- # Create a temporary routes file
18
- routes_content = <<~ROUTES
19
- GET / TestApp.index
20
- GET /users/:id TestApp.show
21
- POST /users TestApp.create
22
- GET /health TestApp.health
23
- ROUTES
24
-
25
- routes_file = Tempfile.new(['routes', '.txt'])
26
- routes_file.write(routes_content)
27
- routes_file.close
28
-
29
- # Define test application
30
- class TestApp
31
- def self.index(_env, _params = {})
32
- [200, { 'Content-Type' => 'text/html' }, ['Welcome']]
33
- end
34
-
35
- def self.show(env, params = {})
36
- user_id = params[:id] || env.dig('otto.params', :id) || '123'
37
- [200, { 'Content-Type' => 'text/html' }, ["User #{user_id}"]]
38
- end
39
-
40
- def self.create(_env, _params = {})
41
- [201, { 'Content-Type' => 'application/json' }, ['{"id": 123}']]
42
- end
43
-
44
- def self.health(_env, _params = {})
45
- [200, { 'Content-Type' => 'text/plain' }, ['OK']]
46
- end
47
- end
48
-
49
- # Create real Rack middleware
50
- class BenchmarkMiddleware
51
- def initialize(app, _config = nil)
52
- @app = app
53
- end
54
-
55
- def call(env)
56
- @app.call(env)
57
- end
58
- end
59
-
60
- # Create Otto instance with real configuration
61
- otto = Otto.new(routes_file.path)
62
-
63
- # Add real Otto security middleware
64
- otto.enable_csrf_protection!
65
- otto.enable_request_validation!
66
-
67
- # Add custom middleware to reach target count
68
- current_count = otto.middleware.size
69
- (MIDDLEWARE_COUNT - current_count).times do
70
- otto.use(Class.new(BenchmarkMiddleware))
71
- end
72
-
73
- # Suppress error logging for benchmark
74
- Otto.logger.level = Logger::FATAL
75
-
76
- puts "\n" + ("=" * 70)
77
- puts "Otto Performance Benchmark"
78
- puts ("=" * 70)
79
- puts "Configuration:"
80
- puts " Routes: #{otto.instance_variable_get(:@route_definitions).size}"
81
- actual_app = otto.instance_variable_get(:@app)
82
- puts " Middleware: #{otto.middleware.size} (#{MIDDLEWARE_COUNT} total in stack, app built: #{!actual_app.nil?})"
83
- puts " Requests: #{REQUEST_COUNT.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse}"
84
- puts ("=" * 70)
85
-
86
- # Create realistic Rack environments for different routes
87
- def make_env(method, path)
88
- {
89
- 'REQUEST_METHOD' => method,
90
- 'PATH_INFO' => path,
91
- 'QUERY_STRING' => '',
92
- 'SERVER_NAME' => 'example.com',
93
- 'SERVER_PORT' => '80',
94
- 'rack.version' => [1, 3],
95
- 'rack.url_scheme' => 'http',
96
- 'rack.input' => StringIO.new,
97
- 'rack.errors' => StringIO.new,
98
- 'rack.multithread' => false,
99
- 'rack.multiprocess' => true,
100
- 'rack.run_once' => false,
101
- 'REMOTE_ADDR' => '192.168.1.100',
102
- 'HTTP_USER_AGENT' => 'Benchmark/1.0',
103
- 'rack.session' => {}
104
- }
105
- end
106
-
107
- # Test different routes
108
- routes = [
109
- ['GET', '/'],
110
- ['GET', '/users/123'],
111
- ['POST', '/users'],
112
- ['GET', '/health']
113
- ]
114
-
115
- # Warmup
116
- puts "\nWarming up (1,000 requests)..."
117
- 1_000.times do |i|
118
- method, path = routes[i % routes.size]
119
- env = make_env(method, path)
120
- otto.call(env)
121
- end
122
-
123
- puts "\n" + ("=" * 70)
124
- puts "Running benchmark..."
125
- puts ("=" * 70)
126
-
127
- # Benchmark
128
- result = Benchmark.measure do
129
- REQUEST_COUNT.times do |i|
130
- method, path = routes[i % routes.size]
131
- env = make_env(method, path)
132
- otto.call(env)
133
- end
134
- end
135
-
136
- total_time = result.real
137
- per_request = (total_time / REQUEST_COUNT * 1_000_000).round(2)
138
- requests_per_sec = (REQUEST_COUNT / total_time).round(0)
139
-
140
- puts "\nResults:"
141
- puts ("=" * 70)
142
- puts " Total time: #{(total_time * 1000).round(2)}ms"
143
- puts " Time per request: #{per_request}µs"
144
- puts " Requests/sec: #{requests_per_sec.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse}"
145
- puts ("=" * 70)
146
-
147
- # Performance analysis
148
- puts "\nPerformance Analysis:"
149
- if per_request < 20
150
- puts " ✓ Excellent performance (< 20µs per request)"
151
- elsif per_request < 50
152
- puts " ✓ Good performance (< 50µs per request)"
153
- elsif per_request < 100
154
- puts " ~ Acceptable performance (< 100µs per request)"
155
- else
156
- puts " ⚠ May need optimization (#{per_request}µs per request)"
157
- end
158
-
159
- puts "\nMiddleware overhead: ~#{((per_request - 2.5) / MIDDLEWARE_COUNT).round(3)}µs per middleware"
160
- puts
161
-
162
- # Cleanup
163
- routes_file.unlink
@@ -1,36 +0,0 @@
1
- Changed
2
- -------
3
-
4
- - Authentication now handled by RouteAuthWrapper at handler level instead of middleware
5
- - RouteAuthWrapper enhanced with session persistence, security headers, strategy caching, and sophisticated pattern matching
6
- - env['otto.strategy_result'] now GUARANTEED to be present on all routes (authenticated or anonymous)
7
- - RouteAuthWrapper now wraps all route handlers, not just routes with auth requirements
8
-
9
- Removed
10
- -------
11
-
12
- - Removed AuthenticationMiddleware (architecturally broken - executed before routing)
13
- - Removed enable_authentication! (no longer needed - RouteAuthWrapper handles auth automatically)
14
- - Removed defensive nil fallback from LogicClassHandler (no longer needed)
15
-
16
- Fixed
17
- -----
18
-
19
- - Session persistence now works correctly (env['rack.session'] references same object as strategy_result.session)
20
- - Security headers now included on all authentication failure responses (401/302)
21
- - Strategy lookups now cached for performance
22
- - env['otto.strategy_result'] is now guaranteed to be present (anonymous StrategyResult for public routes)
23
- - Routes without auth requirements now get anonymous StrategyResult with IP metadata
24
-
25
- Security
26
- --------
27
-
28
- - Authentication strategies now execute after routing when route_definition is available
29
- - Supports exact match, prefix match (role:admin), and fallback patterns for strategies
30
-
31
- Documentation
32
- -------------
33
-
34
- - Updated CLAUDE.md with RouteAuthWrapper architecture overview
35
- - Updated env_keys.rb to document guaranteed presence of strategy_result
36
- - Added comprehensive tests for anonymous route handling
@@ -1,5 +0,0 @@
1
- Changed
2
- -------
3
-
4
- - Renamed MiddlewareStack#build_app to #wrap to better reflect per-request behavior
5
- (wraps base app in middleware layers on each request, not a one-time initialization)