otto 2.0.0.pre2 → 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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -3
  3. data/.github/workflows/claude-code-review.yml +29 -13
  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 +116 -45
  10. data/Gemfile +5 -2
  11. data/Gemfile.lock +70 -24
  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/docs/.gitignore +1 -0
  16. data/docs/ipaddr-encoding-quirk.md +34 -0
  17. data/docs/migrating/v2.0.0-pre2.md +11 -18
  18. data/examples/advanced_routes/README.md +137 -20
  19. data/examples/authentication_strategies/README.md +212 -19
  20. data/examples/authentication_strategies/config.ru +0 -1
  21. data/examples/backtrace_sanitization_demo.rb +86 -0
  22. data/examples/basic/README.md +61 -10
  23. data/examples/error_handler_registration.rb +136 -0
  24. data/examples/logging_improvements.rb +76 -0
  25. data/examples/mcp_demo/README.md +187 -27
  26. data/examples/security_features/README.md +249 -30
  27. data/examples/simple_geo_resolver.rb +107 -0
  28. data/lib/otto/core/configuration.rb +90 -45
  29. data/lib/otto/core/error_handler.rb +138 -8
  30. data/lib/otto/core/file_safety.rb +2 -2
  31. data/lib/otto/core/freezable.rb +93 -0
  32. data/lib/otto/core/middleware_stack.rb +25 -18
  33. data/lib/otto/core/router.rb +62 -9
  34. data/lib/otto/core/uri_generator.rb +2 -2
  35. data/lib/otto/core.rb +10 -0
  36. data/lib/otto/design_system.rb +2 -2
  37. data/lib/otto/env_keys.rb +65 -12
  38. data/lib/otto/helpers/base.rb +2 -2
  39. data/lib/otto/helpers/request.rb +85 -2
  40. data/lib/otto/helpers/response.rb +5 -5
  41. data/lib/otto/helpers/validation.rb +2 -2
  42. data/lib/otto/helpers.rb +6 -0
  43. data/lib/otto/locale/config.rb +56 -0
  44. data/lib/otto/locale/middleware.rb +160 -0
  45. data/lib/otto/locale.rb +10 -0
  46. data/lib/otto/logging_helpers.rb +273 -0
  47. data/lib/otto/mcp/auth/token.rb +2 -2
  48. data/lib/otto/mcp/protocol.rb +2 -2
  49. data/lib/otto/mcp/rate_limiting.rb +2 -2
  50. data/lib/otto/mcp/registry.rb +2 -2
  51. data/lib/otto/mcp/route_parser.rb +2 -2
  52. data/lib/otto/mcp/schema_validation.rb +2 -2
  53. data/lib/otto/mcp/server.rb +2 -2
  54. data/lib/otto/mcp.rb +5 -0
  55. data/lib/otto/privacy/config.rb +201 -0
  56. data/lib/otto/privacy/geo_resolver.rb +285 -0
  57. data/lib/otto/privacy/ip_privacy.rb +177 -0
  58. data/lib/otto/privacy/redacted_fingerprint.rb +146 -0
  59. data/lib/otto/privacy.rb +31 -0
  60. data/lib/otto/response_handlers/auto.rb +2 -0
  61. data/lib/otto/response_handlers/base.rb +2 -0
  62. data/lib/otto/response_handlers/default.rb +2 -0
  63. data/lib/otto/response_handlers/factory.rb +2 -0
  64. data/lib/otto/response_handlers/json.rb +2 -0
  65. data/lib/otto/response_handlers/redirect.rb +2 -0
  66. data/lib/otto/response_handlers/view.rb +2 -0
  67. data/lib/otto/response_handlers.rb +2 -2
  68. data/lib/otto/route.rb +4 -4
  69. data/lib/otto/route_definition.rb +42 -15
  70. data/lib/otto/route_handlers/base.rb +2 -1
  71. data/lib/otto/route_handlers/class_method.rb +18 -25
  72. data/lib/otto/route_handlers/factory.rb +18 -16
  73. data/lib/otto/route_handlers/instance_method.rb +8 -5
  74. data/lib/otto/route_handlers/lambda.rb +8 -20
  75. data/lib/otto/route_handlers/logic_class.rb +25 -8
  76. data/lib/otto/route_handlers.rb +2 -2
  77. data/lib/otto/security/authentication/{failure_result.rb → auth_failure.rb} +5 -5
  78. data/lib/otto/security/authentication/auth_strategy.rb +13 -6
  79. data/lib/otto/security/authentication/route_auth_wrapper.rb +304 -41
  80. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +2 -0
  81. data/lib/otto/security/authentication/strategies/noauth_strategy.rb +7 -1
  82. data/lib/otto/security/authentication/strategies/permission_strategy.rb +2 -0
  83. data/lib/otto/security/authentication/strategies/role_strategy.rb +2 -0
  84. data/lib/otto/security/authentication/strategies/session_strategy.rb +2 -0
  85. data/lib/otto/security/authentication/strategy_result.rb +6 -5
  86. data/lib/otto/security/authentication.rb +5 -6
  87. data/lib/otto/security/authorization_error.rb +73 -0
  88. data/lib/otto/security/config.rb +53 -9
  89. data/lib/otto/security/configurator.rb +17 -15
  90. data/lib/otto/security/csrf.rb +2 -2
  91. data/lib/otto/security/middleware/csrf_middleware.rb +11 -1
  92. data/lib/otto/security/middleware/ip_privacy_middleware.rb +231 -0
  93. data/lib/otto/security/middleware/rate_limit_middleware.rb +2 -0
  94. data/lib/otto/security/middleware/validation_middleware.rb +15 -0
  95. data/lib/otto/security/rate_limiter.rb +2 -2
  96. data/lib/otto/security/rate_limiting.rb +2 -2
  97. data/lib/otto/security/validator.rb +2 -2
  98. data/lib/otto/security.rb +12 -0
  99. data/lib/otto/static.rb +2 -2
  100. data/lib/otto/utils.rb +27 -2
  101. data/lib/otto/version.rb +3 -3
  102. data/lib/otto.rb +344 -89
  103. data/otto.gemspec +9 -2
  104. metadata +72 -8
  105. data/lib/otto/security/authentication/authentication_middleware.rb +0 -140
data/Gemfile.lock CHANGED
@@ -1,18 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- otto (2.0.0.pre2)
4
+ otto (2.0.0.pre7)
5
+ concurrent-ruby (~> 1.3, < 2.0)
5
6
  facets (~> 3.1)
7
+ ipaddr (~> 1, < 2.0)
6
8
  logger (~> 1, < 2.0)
7
9
  loofah (~> 2.20)
8
10
  rack (~> 3.1, < 4.0)
9
11
  rack-parser (~> 0.7)
10
- rexml (>= 3.3.6)
12
+ rexml (~> 3.4)
11
13
 
12
14
  GEM
13
15
  remote: https://rubygems.org/
14
16
  specs:
15
17
  ast (2.4.3)
18
+ benchmark (0.5.0)
16
19
  bigdecimal (3.2.3)
17
20
  concurrent-ruby (1.3.5)
18
21
  crass (1.0.6)
@@ -21,15 +24,45 @@ GEM
21
24
  irb (~> 1.10)
22
25
  reline (>= 0.3.8)
23
26
  diff-lcs (1.6.2)
24
- erb (5.0.2)
27
+ dry-configurable (1.3.0)
28
+ dry-core (~> 1.1)
29
+ zeitwerk (~> 2.6)
30
+ dry-core (1.1.0)
31
+ concurrent-ruby (~> 1.0)
32
+ logger
33
+ zeitwerk (~> 2.6)
34
+ dry-inflector (1.2.0)
35
+ dry-initializer (3.2.0)
36
+ dry-logic (1.6.0)
37
+ bigdecimal
38
+ concurrent-ruby (~> 1.0)
39
+ dry-core (~> 1.1)
40
+ zeitwerk (~> 2.6)
41
+ dry-schema (1.14.1)
42
+ concurrent-ruby (~> 1.0)
43
+ dry-configurable (~> 1.0, >= 1.0.1)
44
+ dry-core (~> 1.1)
45
+ dry-initializer (~> 3.2)
46
+ dry-logic (~> 1.5)
47
+ dry-types (~> 1.8)
48
+ zeitwerk (~> 2.6)
49
+ dry-types (1.8.3)
50
+ bigdecimal (~> 3.0)
51
+ concurrent-ruby (~> 1.0)
52
+ dry-core (~> 1.0)
53
+ dry-inflector (~> 1.0)
54
+ dry-logic (~> 1.4)
55
+ zeitwerk (~> 2.6)
56
+ erb (5.1.1)
25
57
  facets (3.1.0)
26
58
  hana (1.3.7)
27
59
  io-console (0.8.1)
60
+ ipaddr (1.2.7)
28
61
  irb (1.15.2)
29
62
  pp (>= 0.6.0)
30
63
  rdoc (>= 4.0.0)
31
64
  reline (>= 0.4.2)
32
- json (2.15.1)
65
+ json (2.15.2)
33
66
  json_schemer (2.4.0)
34
67
  bigdecimal
35
68
  hana (~> 1.3)
@@ -41,7 +74,7 @@ GEM
41
74
  loofah (2.24.1)
42
75
  crass (~> 1.0.2)
43
76
  nokogiri (>= 1.12.0)
44
- minitest (5.25.5)
77
+ minitest (5.26.0)
45
78
  nokogiri (1.18.10-aarch64-linux-gnu)
46
79
  racc (~> 1.4)
47
80
  nokogiri (1.18.10-aarch64-linux-musl)
@@ -59,22 +92,22 @@ GEM
59
92
  nokogiri (1.18.10-x86_64-linux-musl)
60
93
  racc (~> 1.4)
61
94
  parallel (1.27.0)
62
- parser (3.3.9.0)
95
+ parser (3.3.10.0)
63
96
  ast (~> 2.4.1)
64
97
  racc
65
98
  pastel (0.8.0)
66
99
  tty-color (~> 0.5)
67
- pp (0.6.2)
100
+ pp (0.6.3)
68
101
  prettyprint
69
102
  prettier_print (1.2.1)
70
103
  prettyprint (0.2.0)
71
- prism (1.5.2)
104
+ prism (1.6.0)
72
105
  psych (5.2.6)
73
106
  date
74
107
  stringio
75
108
  racc (1.8.1)
76
- rack (3.2.2)
77
- rack-attack (6.7.0)
109
+ rack (3.2.4)
110
+ rack-attack (6.8.0)
78
111
  rack (>= 1.0, < 4)
79
112
  rack-parser (0.7.0)
80
113
  rack
@@ -85,27 +118,34 @@ GEM
85
118
  rainbow (3.1.1)
86
119
  rbs (3.9.5)
87
120
  logger
88
- rdoc (6.14.2)
121
+ rdoc (6.15.0)
89
122
  erb
90
123
  psych (>= 4.0.0)
124
+ tsort
125
+ reek (6.5.0)
126
+ dry-schema (~> 1.13)
127
+ logger (~> 1.6)
128
+ parser (~> 3.3.0)
129
+ rainbow (>= 2.0, < 4.0)
130
+ rexml (~> 3.1)
91
131
  regexp_parser (2.11.3)
92
132
  reline (0.6.2)
93
133
  io-console (~> 0.5)
94
134
  rexml (3.4.4)
95
- rspec (3.13.1)
135
+ rspec (3.13.2)
96
136
  rspec-core (~> 3.13.0)
97
137
  rspec-expectations (~> 3.13.0)
98
138
  rspec-mocks (~> 3.13.0)
99
- rspec-core (3.13.5)
139
+ rspec-core (3.13.6)
100
140
  rspec-support (~> 3.13.0)
101
141
  rspec-expectations (3.13.5)
102
142
  diff-lcs (>= 1.2.0, < 2.0)
103
143
  rspec-support (~> 3.13.0)
104
- rspec-mocks (3.13.5)
144
+ rspec-mocks (3.13.6)
105
145
  diff-lcs (>= 1.2.0, < 2.0)
106
146
  rspec-support (~> 3.13.0)
107
- rspec-support (3.13.5)
108
- rubocop (1.81.1)
147
+ rspec-support (3.13.6)
148
+ rubocop (1.81.7)
109
149
  json (~> 2.3)
110
150
  language_server-protocol (~> 3.17.0.2)
111
151
  lint_roller (~> 1.1.0)
@@ -119,10 +159,10 @@ GEM
119
159
  rubocop-ast (1.47.1)
120
160
  parser (>= 3.3.7.2)
121
161
  prism (~> 1.4)
122
- rubocop-performance (1.26.0)
162
+ rubocop-performance (1.26.1)
123
163
  lint_roller (~> 1.1)
124
164
  rubocop (>= 1.75.0, < 2.0)
125
- rubocop-ast (>= 1.44.0, < 2.0)
165
+ rubocop-ast (>= 1.47.1, < 2.0)
126
166
  rubocop-rspec (3.7.0)
127
167
  lint_roller (~> 1.1)
128
168
  rubocop (~> 1.72, >= 1.72.1)
@@ -130,7 +170,7 @@ GEM
130
170
  lint_roller (~> 1.1)
131
171
  rubocop (~> 1.72, >= 1.72.1)
132
172
  rubocop-ast (>= 1.44.0, < 2.0)
133
- ruby-lsp (0.26.1)
173
+ ruby-lsp (0.26.2)
134
174
  language_server-protocol (~> 3.17.0)
135
175
  prism (>= 1.2, < 2.0)
136
176
  rbs (>= 3, < 5)
@@ -140,21 +180,24 @@ GEM
140
180
  stringio (3.1.7)
141
181
  syntax_tree (6.3.0)
142
182
  prettier_print (>= 1.2.0)
143
- tryouts (3.6.0)
144
- concurrent-ruby (~> 1.0)
183
+ tryouts (3.7.1)
184
+ concurrent-ruby (~> 1.0, < 2)
145
185
  irb
146
186
  minitest (~> 5.0)
147
187
  pastel (~> 0.8)
148
188
  prism (~> 1.0)
149
- rspec (~> 3.0)
189
+ rspec (>= 3.0, < 5.0)
150
190
  tty-cursor (~> 0.7)
151
191
  tty-screen (~> 0.8)
192
+ tsort (0.2.0)
152
193
  tty-color (0.6.0)
153
194
  tty-cursor (0.7.1)
154
195
  tty-screen (0.8.2)
155
196
  unicode-display_width (3.2.0)
156
197
  unicode-emoji (~> 4.1)
157
198
  unicode-emoji (4.1.0)
199
+ user_agent_parser (2.20.0)
200
+ zeitwerk (2.7.3)
158
201
 
159
202
  PLATFORMS
160
203
  aarch64-linux-gnu
@@ -167,21 +210,24 @@ PLATFORMS
167
210
  x86_64-linux-musl
168
211
 
169
212
  DEPENDENCIES
213
+ benchmark
170
214
  debug
171
215
  json_schemer
172
216
  otto!
173
217
  rack-attack
174
218
  rack-test
175
219
  rackup
220
+ reek (~> 6.5)
176
221
  rspec (~> 3.13)
177
- rubocop (~> 1.81.1)
222
+ rubocop (~> 1.81.7)
178
223
  rubocop-performance
179
224
  rubocop-rspec
180
225
  rubocop-thread_safety
181
226
  ruby-lsp
182
227
  stackprof
183
228
  syntax_tree
184
- tryouts (~> 3.6.0)
229
+ tryouts (~> 3.7.1)
230
+ user_agent_parser (~> 2.18)
185
231
 
186
232
  BUNDLED WITH
187
233
  2.7.1
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Define your rack-apps in plain-text with built-in security.**
4
4
 
5
- > **v2.0.0-pre1 Available**: This pre-release includes major improvements to middleware management and test coverage. See [changelog](CHANGELOG.rst) and [migration guide](docs/migrating/v2.0.0-pre1.md) for upgraders.
5
+ > **v2.0.0-pre6 Available**: This pre-release includes major improvements to middleware management, logging, and request callback handling. See [changelog](CHANGELOG.rst) for details and upgrade notes.
6
6
 
7
7
  ![Otto mascot](public/img/otto.jpg "Otto - All Rack, no Pinion")
8
8
 
@@ -13,6 +13,14 @@ $ cd myapp && ls
13
13
  config.ru app.rb routes
14
14
  ```
15
15
 
16
+ ## Why Otto?
17
+
18
+ - **Security by Default**: Automatic IP masking for public addresses, user agent anonymization, CSRF protection, and input validation
19
+ - **Privacy First**: Masks public IPs, strips user agent versions, provides country-level geo-location only—no external APIs needed
20
+ - **Simple Routing**: Define routes in plain-text files with zero configuration overhead
21
+ - **Built-in Authentication**: Multiple strategies including API keys, tokens, role-based access, and custom implementations
22
+ - **Developer Friendly**: Works with any Rack server, minimal dependencies, easy testing and debugging
23
+
16
24
  ## Routes File
17
25
  ```
18
26
  # routes
@@ -76,6 +84,22 @@ app = Otto.new("./routes", {
76
84
 
77
85
  Security features include CSRF protection, input validation, security headers, and trusted proxy configuration.
78
86
 
87
+ ## Privacy by Default
88
+
89
+ Otto automatically masks public IP addresses and anonymizes user agents to comply with GDPR, CCPA, and other privacy regulations:
90
+
91
+ ```ruby
92
+ # Public IPs are automatically masked (203.0.113.9 → 203.0.113.0)
93
+ # Private IPs are NOT masked by default (127.0.0.1, 192.168.x.x, 10.x.x.x)
94
+ app = Otto.new("./routes")
95
+
96
+ # User agents: versions stripped for privacy
97
+ # Geo-location: country-level only, no external APIs or databases
98
+ # IP hashing: daily-rotating hashes enable analytics without tracking
99
+ ```
100
+
101
+ Private and localhost IPs are exempted by default for development convenience, but this behavior can be customized via `configure_ip_privacy()` method. Geolocation uses CDN headers (Cloudflare, AWS, etc.) with fallback to IP ranges—no external services required. See [CLAUDE.md](CLAUDE.md) for detailed configuration options.
102
+
79
103
  ## Internationalization Support
80
104
 
81
105
  Otto provides built-in locale detection and management:
@@ -132,6 +156,24 @@ end
132
156
 
133
157
  The locale helper checks multiple sources in order of precedence and validates against your configured locales.
134
158
 
159
+ ## Examples
160
+
161
+ Otto includes comprehensive examples demonstrating different features:
162
+
163
+ - **[Basic Example](examples/basic/)** - Get your first Otto app running in minutes
164
+ - **[Advanced Routes](examples/advanced_routes/)** - Response types, CSRF exemption, logic classes, and namespaced routing
165
+ - **[Authentication Strategies](examples/authentication_strategies/)** - Token, API key, and role-based authentication
166
+ - **[Security Features](examples/security_features/)** - CSRF protection, input validation, file uploads, and security headers
167
+ - **[MCP Demo](examples/mcp_demo/)** - JSON-RPC 2.0 endpoints for CLI automation and integrations
168
+
169
+ ### Standalone Tutorials
170
+
171
+ - **[Error Handler Registration](examples/error_handler_registration.rb)** - Prevent 500 errors for expected business exceptions
172
+ - **[Logging Improvements](examples/logging_improvements.rb)** - Structured logging with automatic timing
173
+ - **[Geo-location Extension](examples/simple_geo_resolver.rb)** - Extending geo-location with custom resolvers
174
+
175
+ See the [examples/](examples/) directory for more.
176
+
135
177
  ## Requirements
136
178
 
137
179
  - Ruby 3.2+
@@ -143,6 +185,12 @@ The locale helper checks multiple sources in order of precedence and validates a
143
185
  gem install otto
144
186
  ```
145
187
 
188
+ ## Documentation
189
+
190
+ - **[CLAUDE.md](CLAUDE.md)** - Comprehensive developer guidance covering authentication architecture, configuration freezing, IP privacy, structured logging, and multi-app patterns
191
+ - **[docs/](docs/)** - Technical guides and migration guides
192
+ - **[CHANGELOG.rst](CHANGELOG.rst)** - Version history, breaking changes, and upgrade notes
193
+
146
194
  ## AI Development Assistance
147
195
 
148
196
  Version 1.2.0's security features were developed with AI assistance:
@@ -0,0 +1,15 @@
1
+ Added
2
+ -----
3
+
4
+ - Error handler registration system for expected business logic errors. Register handlers with ``otto.register_error_handler(ErrorClass, status: 404, log_level: :info)`` to return proper HTTP status codes and avoid logging expected errors as 500s with backtraces. Supports custom response handlers via blocks for complete control over error responses.
5
+
6
+ Changed
7
+ -------
8
+
9
+ - Backtrace logging now always logs at ERROR level (was DEBUG) with sanitized file paths for security. Backtraces for unhandled 500 errors are always logged regardless of ``OTTO_DEBUG`` setting, with paths sanitized to prevent exposing system information (project files show relative paths, gems show ``[GEM] name-version/path``, Ruby stdlib shows ``[RUBY] filename``).
10
+ - Increased backtrace limit from 10 to 20 lines for critical errors to provide better debugging context.
11
+
12
+ AI Assistance
13
+ -------------
14
+
15
+ - Implemented error handler registration architecture with comprehensive test coverage (17 test cases) using sequential thinking to work through security implications and design decisions. AI assisted with path sanitization strategy, error classification patterns, and ensuring backward compatibility with existing error handling.
@@ -0,0 +1,37 @@
1
+ .. Changed in: otto
2
+ .. Fixes issue:
3
+
4
+ Improved backtrace sanitization security and readability
5
+ ---------------------------------------------------------
6
+
7
+ **Security Enhancements:**
8
+
9
+ - Fixed bundler gem path detection to correctly sanitize git-based gems
10
+ - Now properly handles nested gem paths like ``/gems/3.4.0/bundler/gems/otto-abc123/``
11
+ - Strips git hash suffixes from bundler gems (``otto-abc123def456`` → ``otto``)
12
+ - Removes version numbers from regular gems (``rack-3.2.4`` → ``rack``)
13
+ - Prevents exposure of absolute paths, usernames, and project names in logs
14
+
15
+ **Improvements:**
16
+
17
+ - Bundler gems now show as ``[GEM] otto/lib/otto/route.rb:142`` instead of ``[GEM] 3.4.0/bundler/gems/...``
18
+ - Regular gems show cleaner output: ``[GEM] rack/lib/rack.rb:20`` instead of ``[GEM] rack-3.2.4/lib/rack.rb:20``
19
+ - Multi-hyphenated gem names handled correctly (``active-record-import-1.5.0`` → ``active-record-import``)
20
+ - Better handling of version-only directory names in gem paths
21
+
22
+ **Documentation:**
23
+
24
+ - Added comprehensive backtrace sanitization section to CLAUDE.md
25
+ - Documented security guarantees and sanitization rules
26
+ - Added examples showing before/after path transformations
27
+ - Created comprehensive test suite for backtrace sanitization
28
+
29
+ **Rationale:**
30
+
31
+ Raw backtraces expose sensitive information:
32
+ - Usernames (``/Users/alice/``, ``/home/admin/``)
33
+ - Project structure and internal organization
34
+ - Gem installation paths and Ruby versions
35
+ - System architecture details
36
+
37
+ This improvement ensures all backtraces are sanitized automatically, preventing accidental leakage of sensitive system information while maintaining readability for debugging.
data/docs/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  !.gitignore
3
3
  !migrating/
4
4
  !migrating/*.md
5
+ !ipaddr-encoding-quirk.md
@@ -0,0 +1,34 @@
1
+ # IPAddr#to_s Encoding Quirk in Ruby 3
2
+
3
+ Ruby's `IPAddr#to_s` returns inconsistent encodings: IPv4 addresses use US-ASCII, IPv6 addresses use UTF-8.
4
+
5
+ ## Behavior
6
+
7
+ ```ruby
8
+ IPAddr.new('192.168.1.1').to_s.encoding # => #<Encoding:US-ASCII>
9
+ IPAddr.new('::1').to_s.encoding # => #<Encoding:UTF-8>
10
+ ```
11
+
12
+ ## Cause
13
+
14
+ Different string construction in IPAddr's `_to_string` method:
15
+
16
+ - **IPv4**: `Array#join('.')` → US-ASCII optimization
17
+ - **IPv6**: `String#%` → UTF-8 default
18
+
19
+ ## Impact
20
+
21
+ - Rack expects UTF-8 strings
22
+ - Mixed encodings cause `Encoding::CompatibilityError`
23
+ - String operations fail on encoding mismatches
24
+
25
+ ## Solution
26
+
27
+ Use `force_encoding('UTF-8')` instead of `encode('UTF-8')`:
28
+
29
+ - IP addresses contain only ASCII characters
30
+ - ASCII bytes are identical in US-ASCII and UTF-8
31
+ - `force_encoding` changes label only (O(1))
32
+ - `encode` creates new string (O(n))
33
+
34
+ This ensures consistent UTF-8 encoding across all IP strings.
@@ -27,12 +27,6 @@ if strategy_result.is_a?(Otto::Security::Authentication::StrategyResult)
27
27
  # handle success
28
28
  end
29
29
 
30
- # Or for middleware - FailureResult indicates failure
31
- if strategy_result.is_a?(Otto::Security::Authentication::FailureResult)
32
- # handle failure
33
- else
34
- # handle success
35
- end
36
30
  ```
37
31
 
38
32
  ### 2. New Semantic Distinction
@@ -116,7 +110,7 @@ session['authenticated_at'] # Timestamp
116
110
  **Optional session keys:**
117
111
  ```ruby
118
112
  session['email'] # User email
119
- session['ip_address'] # Client IP
113
+ session['ip_address'] # Client IP (masked by default via IPPrivacyMiddleware)
120
114
  session['user_agent'] # Client UA
121
115
  session['locale'] # User locale
122
116
  ```
@@ -140,7 +134,7 @@ class Controller::Base
140
134
  session: session,
141
135
  user: cust,
142
136
  auth_method: 'session', # Hardcoded - loses semantic meaning
143
- metadata: { ip: req.client_ipaddress }
137
+ metadata: { ip: req.masked_ip } # Uses masked IP (privacy by default)
144
138
  )
145
139
  end
146
140
  end
@@ -148,10 +142,10 @@ end
148
142
 
149
143
  **Correct approach:**
150
144
  ```ruby
151
- # GOOD - Use middleware-provided result
145
+ # GOOD - Use RouteAuthWrapper-provided result
152
146
  class Controller::Base
153
147
  def strategy_result
154
- req.env['otto.strategy_result'] # Created by AuthenticationMiddleware
148
+ req.env['otto.strategy_result'] # Created by RouteAuthWrapper
155
149
  end
156
150
  end
157
151
 
@@ -188,7 +182,6 @@ expect(result).to be_failure
188
182
 
189
183
  # After
190
184
  expect(result).to be_a(Otto::Security::Authentication::StrategyResult)
191
- expect(result).to be_a(Otto::Security::Authentication::FailureResult)
192
185
  ```
193
186
 
194
187
  ## Architecture Clarifications
@@ -196,13 +189,13 @@ expect(result).to be_a(Otto::Security::Authentication::FailureResult)
196
189
  ### When StrategyResult is Created
197
190
 
198
191
  1. **Routes WITH `auth=...` requirement:**
199
- - Strategy executes
200
- - Returns `StrategyResult` (success) or `FailureResult` (failure)
201
- - Middleware converts `FailureResult` to anonymous `StrategyResult` + 401 response
192
+ - RouteAuthWrapper executes strategy
193
+ - Always returns `StrategyResult` (success or failure)
194
+ - RouteAuthWrapper returns 401/302 response on `AuthFailure`
202
195
 
203
196
  2. **Routes WITHOUT `auth=...` requirement:**
204
- - Middleware creates anonymous `StrategyResult`
205
- - Sets `auth_method: 'anonymous'`
197
+ - No RouteAuthWrapper wrapping
198
+ - No `StrategyResult` created (routes without auth don't need it)
206
199
 
207
200
  3. **Auth app (Roda) routes:**
208
201
  - Manually creates `StrategyResult` for Logic class compatibility
@@ -213,7 +206,7 @@ expect(result).to be_a(Otto::Security::Authentication::FailureResult)
213
206
  **Multi-app setup (Auth + Core + API):**
214
207
  - **Shared:** Session middleware, Redis session, Logic classes, Customer model
215
208
  - **Auth app:** Creates StrategyResult manually, uses Roda routing
216
- - **Core/API apps:** StrategyResult from AuthenticationMiddleware
209
+ - **Core/API apps:** StrategyResult from RouteAuthWrapper
217
210
  - **Integration:** Pure session-based, no direct code calls between apps
218
211
 
219
212
  ## Testing Your Migration
@@ -339,7 +332,7 @@ middleware.add_with_position(
339
332
 
340
333
  Review the comprehensive inline documentation in:
341
334
  - `lib/otto/security/authentication/strategy_result.rb` (lines 1-90) - Auth semantics
342
- - `lib/otto/security/authentication/authentication_middleware.rb` - Auth middleware
335
+ - `lib/otto/security/authentication/route_auth_wrapper.rb` - Auth handler wrapper
343
336
  - `lib/otto/env_keys.rb` - Complete env key registry
344
337
 
345
338
  The documentation includes detailed usage patterns, session contracts, and examples for common scenarios.
@@ -1,33 +1,150 @@
1
1
  # Otto - Advanced Routes Example
2
2
 
3
- This example demonstrates the advanced routing syntax features available in Otto, such as response type negotiation, CSRF exemptions, and routing to logic classes.
3
+ This example demonstrates advanced routing features in Otto, including response type negotiation, CSRF exemptions, logic classes, and namespaced routing.
4
+
5
+ ## What You'll Learn
6
+
7
+ - How to define response types (JSON, view, redirect) in routes
8
+ - Using logic classes to encapsulate business logic
9
+ - CSRF exemption for APIs and webhooks
10
+ - Routing to namespaced classes with complex hierarchies
11
+ - Custom route parameters for flexible routing
12
+ - How Otto handles multiple controllers and modules
4
13
 
5
14
  ## Project Structure
6
15
 
7
- The example is structured to separate concerns, making it easier to navigate:
16
+ The example is organized to separate concerns:
8
17
 
9
- - `config.ru`: The main Rackup file that loads and runs the Otto application.
10
- - `routes`: A comprehensive file demonstrating various advanced routing syntaxes. This file serves as a good reference.
11
- - `app.rb`: A simple loader that requires all controller and logic files.
12
- - `app/controllers/`: Contains the `RoutesApp` class and other namespaced controller modules that handle incoming requests.
13
- - `app/logic/`: Contains various "Logic Classes". These are special classes that can be routed to directly, encapsulating business logic for a specific route. They are organized into namespaces to show how Otto handles complex class names.
14
- - `run.rb`, `puma.rb`, `test.rb`: Alternative ways to run or test the application.
18
+ - `config.ru`: Rack configuration that loads and runs the Otto application
19
+ - `routes`: Comprehensive reference for advanced routing syntax
20
+ - `app.rb`: Loader that requires all controller and logic files
21
+ - `app/controllers/`: Handler classes (`RoutesApp`, namespaced controllers)
22
+ - `app/logic/`: Business logic classes (simple, nested, namespaced)
23
+ - `run.rb`, `puma.rb`, `test.rb`: Alternative server/test runners
15
24
 
16
25
  ## Key Features Demonstrated
17
26
 
18
- - **Response Types:** Defining `response=json`, `response=view`, etc., directly in the `routes` file.
19
- - **CSRF Exemption:** Using `csrf=exempt` for APIs or webhooks.
20
- - **Logic Classes:** Routing directly to a class (e.g., `GET /logic/simple SimpleLogic`). Otto automatically instantiates it and calls its `process` method.
21
- - **Namespaced Targets:** Routing to deeply namespaced classes and modules (e.g., `GET /logic/v2/dashboard V2::Logic::Dashboard`).
22
- - **Custom Parameters:** Adding arbitrary key-value parameters to a route for custom logic.
27
+ ### Response Types
28
+ Define how responses are formatted directly in routes:
29
+ ```
30
+ GET /api/users UserController#list response=json
31
+ GET /page PageController#show response=view
32
+ GET /old-url PageController#new-url response=redirect
33
+ ```
34
+
35
+ ### Logic Classes
36
+ Route to specialized classes that encapsulate business logic:
37
+ ```
38
+ GET /calculate DataProcessor # Otto auto-instantiates and calls #process
39
+ GET /report ReportGenerator # Same pattern
40
+ ```
41
+
42
+ ### CSRF Exemption
43
+ Mark routes that don't need CSRF tokens (APIs, webhooks):
44
+ ```
45
+ POST /api/webhook WebhookHandler#receive csrf=exempt
46
+ ```
47
+
48
+ ### Namespaced Routing
49
+ Handle complex class hierarchies naturally:
50
+ ```
51
+ GET /v2/dashboard V2::Logic::Dashboard
52
+ GET /admin/panel Admin::Panel#dashboard
53
+ ```
54
+
55
+ ### Custom Parameters
56
+ Add arbitrary key-value pairs for flexible routing:
57
+ ```
58
+ GET /admin AdminPanel#dashboard role=admin
59
+ ```
60
+
61
+ ## How to Run
62
+
63
+ ### Using rackup (recommended)
64
+
65
+ ```sh
66
+ cd examples/advanced_routes
67
+ rackup config.ru
68
+ ```
69
+
70
+ ### Using alternative runners
71
+
72
+ ```sh
73
+ ruby run.rb # Basic rackup
74
+ ruby puma.rb # Using puma server
75
+ ruby test.rb # For testing
76
+ ```
77
+
78
+ The application will be running at `http://localhost:9292`.
79
+
80
+ ## Testing Routes
81
+
82
+ Use curl to test the different routes:
83
+
84
+ ```sh
85
+ # JSON response
86
+ curl http://localhost:9292/json/test
87
+
88
+ # View response
89
+ curl http://localhost:9292/view/test
90
+
91
+ # Logic class routing
92
+ curl http://localhost:9292/logic/simple
93
+
94
+ # Namespaced routing
95
+ curl http://localhost:9292/logic/v2/dashboard
96
+
97
+ # Custom parameters
98
+ curl "http://localhost:9292/custom?role=admin"
99
+ ```
100
+
101
+ ## Expected Output
102
+
103
+ ```
104
+ Listening on 127.0.0.1:9292, CTRL+C to stop
105
+
106
+ [JSON response]
107
+ GET /json/test 200 OK
108
+ Content-Type: application/json
109
+ {"status": "success", "data": {...}}
110
+
111
+ [Logic class routing]
112
+ GET /logic/simple 200 OK
113
+ {"processed": true, "input": "test"}
114
+
115
+ [Namespaced routing]
116
+ GET /logic/v2/dashboard 200 OK
117
+ {"version": "2.0", "dashboard": {...}}
118
+ ```
119
+
120
+ ## File Structure Details
121
+
122
+ ### Routes File
123
+ The `routes` file is extensively commented to explain each feature:
124
+ - Response type specification
125
+ - CSRF exemption for APIs
126
+ - Logic class routing syntax
127
+ - Namespaced class resolution
128
+ - Custom parameter examples
129
+
130
+ ### Controllers (`app/controllers/`)
131
+ - `RoutesApp`: Main controller with basic handlers
132
+ - Namespaced modules: Demonstrate complex class hierarchies
133
+ - Handlers return appropriate responses (JSON, HTML, redirects)
134
+
135
+ ### Logic Classes (`app/logic/`)
136
+ - Simple classes: Basic business logic
137
+ - Nested classes: Show how Otto handles namespace resolution
138
+ - Parameterized logic: Demonstrate custom route parameters
23
139
 
24
- ## Running the Example
140
+ ## Next Steps
25
141
 
26
- 1. Make sure you have the necessary gems installed (`bundle install`).
27
- 2. Run the application from the root of the `otto` project:
142
+ - Review the `routes` file for syntax reference
143
+ - Examine handler methods to see request/response patterns
144
+ - Check logic classes for business logic encapsulation patterns
145
+ - Explore [Authentication](../authentication_strategies/) for protecting routes
146
+ - See [Security Features](../security_features/) for CSRF, validation, file uploads
28
147
 
29
- ```sh
30
- rackup examples/advanced_routes/config.ru
31
- ```
148
+ ## Further Reading
32
149
 
33
- 3. The application will be running at `http://localhost:9292`. You can use `curl` or your browser to test the various routes defined in the `routes` file.
150
+ - [CLAUDE.md](../../CLAUDE.md) - Comprehensive developer guidance