otto 2.0.0.pre3 → 2.0.0.pre8

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 (104) 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 +143 -0
  5. data/.gitignore +4 -0
  6. data/.pre-commit-config.yaml +2 -2
  7. data/.reek.yml +99 -0
  8. data/CHANGELOG.rst +156 -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/examples/advanced_routes/README.md +137 -20
  14. data/examples/authentication_strategies/README.md +212 -19
  15. data/examples/backtrace_sanitization_demo.rb +86 -0
  16. data/examples/basic/README.md +61 -10
  17. data/examples/error_handler_registration.rb +136 -0
  18. data/examples/logging_improvements.rb +76 -0
  19. data/examples/mcp_demo/README.md +187 -27
  20. data/examples/security_features/README.md +249 -30
  21. data/examples/simple_geo_resolver.rb +107 -0
  22. data/lib/otto/core/configuration.rb +15 -20
  23. data/lib/otto/core/error_handler.rb +138 -8
  24. data/lib/otto/core/file_safety.rb +2 -2
  25. data/lib/otto/core/freezable.rb +2 -2
  26. data/lib/otto/core/middleware_stack.rb +2 -2
  27. data/lib/otto/core/router.rb +61 -8
  28. data/lib/otto/core/uri_generator.rb +2 -2
  29. data/lib/otto/core.rb +2 -0
  30. data/lib/otto/design_system.rb +2 -2
  31. data/lib/otto/env_keys.rb +61 -12
  32. data/lib/otto/helpers/base.rb +2 -2
  33. data/lib/otto/helpers/request.rb +8 -3
  34. data/lib/otto/helpers/response.rb +2 -2
  35. data/lib/otto/helpers/validation.rb +2 -2
  36. data/lib/otto/helpers.rb +2 -0
  37. data/lib/otto/locale/config.rb +2 -2
  38. data/lib/otto/locale/middleware.rb +160 -0
  39. data/lib/otto/locale.rb +10 -0
  40. data/lib/otto/logging_helpers.rb +273 -0
  41. data/lib/otto/mcp/auth/token.rb +2 -2
  42. data/lib/otto/mcp/protocol.rb +2 -2
  43. data/lib/otto/mcp/rate_limiting.rb +2 -2
  44. data/lib/otto/mcp/registry.rb +2 -2
  45. data/lib/otto/mcp/route_parser.rb +2 -2
  46. data/lib/otto/mcp/schema_validation.rb +2 -2
  47. data/lib/otto/mcp/server.rb +2 -2
  48. data/lib/otto/mcp.rb +2 -0
  49. data/lib/otto/privacy/config.rb +2 -0
  50. data/lib/otto/privacy/geo_resolver.rb +199 -29
  51. data/lib/otto/privacy/ip_privacy.rb +2 -0
  52. data/lib/otto/privacy/redacted_fingerprint.rb +18 -8
  53. data/lib/otto/privacy.rb +2 -0
  54. data/lib/otto/response_handlers/auto.rb +2 -0
  55. data/lib/otto/response_handlers/base.rb +2 -0
  56. data/lib/otto/response_handlers/default.rb +2 -0
  57. data/lib/otto/response_handlers/factory.rb +2 -0
  58. data/lib/otto/response_handlers/json.rb +2 -0
  59. data/lib/otto/response_handlers/redirect.rb +2 -0
  60. data/lib/otto/response_handlers/view.rb +2 -0
  61. data/lib/otto/response_handlers.rb +2 -2
  62. data/lib/otto/route.rb +4 -4
  63. data/lib/otto/route_definition.rb +42 -15
  64. data/lib/otto/route_handlers/base.rb +2 -0
  65. data/lib/otto/route_handlers/class_method.rb +26 -26
  66. data/lib/otto/route_handlers/factory.rb +2 -2
  67. data/lib/otto/route_handlers/instance_method.rb +16 -6
  68. data/lib/otto/route_handlers/lambda.rb +8 -20
  69. data/lib/otto/route_handlers/logic_class.rb +33 -8
  70. data/lib/otto/route_handlers.rb +2 -2
  71. data/lib/otto/security/authentication/auth_failure.rb +2 -2
  72. data/lib/otto/security/authentication/auth_strategy.rb +11 -4
  73. data/lib/otto/security/authentication/route_auth_wrapper/response_builder.rb +123 -0
  74. data/lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb +120 -0
  75. data/lib/otto/security/authentication/route_auth_wrapper/strategy_resolver.rb +69 -0
  76. data/lib/otto/security/authentication/route_auth_wrapper.rb +185 -195
  77. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +2 -0
  78. data/lib/otto/security/authentication/strategies/noauth_strategy.rb +2 -0
  79. data/lib/otto/security/authentication/strategies/permission_strategy.rb +2 -0
  80. data/lib/otto/security/authentication/strategies/role_strategy.rb +2 -0
  81. data/lib/otto/security/authentication/strategies/session_strategy.rb +2 -0
  82. data/lib/otto/security/authentication/strategy_result.rb +6 -5
  83. data/lib/otto/security/authentication.rb +2 -2
  84. data/lib/otto/security/authorization_error.rb +73 -0
  85. data/lib/otto/security/config.rb +2 -2
  86. data/lib/otto/security/configurator.rb +17 -2
  87. data/lib/otto/security/csrf.rb +2 -2
  88. data/lib/otto/security/middleware/csrf_middleware.rb +11 -1
  89. data/lib/otto/security/middleware/ip_privacy_middleware.rb +31 -11
  90. data/lib/otto/security/middleware/rate_limit_middleware.rb +2 -0
  91. data/lib/otto/security/middleware/validation_middleware.rb +15 -0
  92. data/lib/otto/security/rate_limiter.rb +2 -2
  93. data/lib/otto/security/rate_limiting.rb +2 -2
  94. data/lib/otto/security/validator.rb +2 -2
  95. data/lib/otto/security.rb +3 -0
  96. data/lib/otto/static.rb +2 -2
  97. data/lib/otto/utils.rb +27 -2
  98. data/lib/otto/version.rb +3 -3
  99. data/lib/otto.rb +174 -14
  100. data/otto.gemspec +7 -3
  101. metadata +25 -15
  102. data/benchmark_middleware_wrap.rb +0 -163
  103. data/changelog.d/20251014_144317_delano_54_thats_a_wrapper.rst +0 -36
  104. data/changelog.d/20251014_161526_delano_54_thats_a_wrapper.rst +0 -5
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.pre8
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,7 @@ 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
173
172
  - changelog.d/README.md
174
173
  - changelog.d/scriv.ini
175
174
  - docs/.gitignore
@@ -217,10 +216,13 @@ files:
217
216
  - examples/authentication_strategies/app/controllers/main_controller.rb
218
217
  - examples/authentication_strategies/config.ru
219
218
  - examples/authentication_strategies/routes
219
+ - examples/backtrace_sanitization_demo.rb
220
220
  - examples/basic/README.md
221
221
  - examples/basic/app.rb
222
222
  - examples/basic/config.ru
223
223
  - examples/basic/routes
224
+ - examples/error_handler_registration.rb
225
+ - examples/logging_improvements.rb
224
226
  - examples/mcp_demo/README.md
225
227
  - examples/mcp_demo/app.rb
226
228
  - examples/mcp_demo/config.ru
@@ -229,6 +231,7 @@ files:
229
231
  - examples/security_features/app.rb
230
232
  - examples/security_features/config.ru
231
233
  - examples/security_features/routes
234
+ - examples/simple_geo_resolver.rb
232
235
  - lib/otto.rb
233
236
  - lib/otto/core.rb
234
237
  - lib/otto/core/configuration.rb
@@ -245,7 +248,10 @@ files:
245
248
  - lib/otto/helpers/request.rb
246
249
  - lib/otto/helpers/response.rb
247
250
  - lib/otto/helpers/validation.rb
251
+ - lib/otto/locale.rb
248
252
  - lib/otto/locale/config.rb
253
+ - lib/otto/locale/middleware.rb
254
+ - lib/otto/logging_helpers.rb
249
255
  - lib/otto/mcp.rb
250
256
  - lib/otto/mcp/auth/token.rb
251
257
  - lib/otto/mcp/protocol.rb
@@ -281,12 +287,16 @@ files:
281
287
  - lib/otto/security/authentication/auth_failure.rb
282
288
  - lib/otto/security/authentication/auth_strategy.rb
283
289
  - lib/otto/security/authentication/route_auth_wrapper.rb
290
+ - lib/otto/security/authentication/route_auth_wrapper/response_builder.rb
291
+ - lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb
292
+ - lib/otto/security/authentication/route_auth_wrapper/strategy_resolver.rb
284
293
  - lib/otto/security/authentication/strategies/api_key_strategy.rb
285
294
  - lib/otto/security/authentication/strategies/noauth_strategy.rb
286
295
  - lib/otto/security/authentication/strategies/permission_strategy.rb
287
296
  - lib/otto/security/authentication/strategies/role_strategy.rb
288
297
  - lib/otto/security/authentication/strategies/session_strategy.rb
289
298
  - lib/otto/security/authentication/strategy_result.rb
299
+ - lib/otto/security/authorization_error.rb
290
300
  - lib/otto/security/config.rb
291
301
  - lib/otto/security/configurator.rb
292
302
  - lib/otto/security/csrf.rb
@@ -325,7 +335,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
325
335
  - !ruby/object:Gem::Version
326
336
  version: '0'
327
337
  requirements: []
328
- rubygems_version: 3.6.9
338
+ rubygems_version: 3.7.2
329
339
  specification_version: 4
330
340
  summary: Auto-define your rack-apps in plaintext.
331
341
  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)